diff --git a/CMakeLists.txt b/CMakeLists.txt index 99d0cfbc5..0dc86055c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,6 +137,13 @@ if(ENABLE_TSAN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -matomics -mbulk-memory") + _add_cxx_compile_flag(-matomics _has_matomics_flag) + _add_cxx_compile_flag(-mbulk-memory _has_mbulk_memory_flag) +endif() + _add_cxx_compile_flag(-Wno-missing-braces _has_no_missing_braces_warning_flag) _add_cxx_compile_flag(-Wno-mismatched-tags _has_no_mismatched_tags_warning_flag) _add_c_compile_flag(-Wno-deprecated-declarations _has_no_deprecated_declarations_warning_flag) @@ -174,6 +181,7 @@ set(kth_sources_just_legacy src/chain/script.cpp src/chain/transaction_basis.cpp src/chain/transaction.cpp + src/chain/utxo.cpp src/machine/interpreter.cpp @@ -364,6 +372,7 @@ set(kth_headers include/kth/domain/chain/point.hpp include/kth/domain/chain/output_basis.hpp include/kth/domain/chain/point_value.hpp + include/kth/domain/chain/utxo.hpp include/kth/domain/define.hpp include/kth/domain/multi_crypto_support.hpp include/kth/domain/constants.hpp diff --git a/conan.lock b/conan.lock index be4a0004f..b31c7b997 100644 --- a/conan.lock +++ b/conan.lock @@ -1,21 +1,19 @@ { "version": "0.5", "requires": [ - "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1736442063.163", "spdlog/1.15.0#da21f74dd84627fa68601c4e3b9c3f00%1731353167.297", "secp256k1/0.21.0#ed9709c61dfdb38ac8181f876503dca6%1732364020.326", - "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1725632951.012", - "infrastructure/0.39.0#55f5992bcbce2037eb333af5ba88dd6e%1736516560.537014", + "infrastructure/0.39.0#827fe58dfc8a00ac65933d1dc05f812d%1738181472.350903", "gmp/6.3.0#df20ffb6d21c34d67704305bcd1dea9e%1716966936.742", "fmt/11.0.2#b4a24d70b93466b9b508ddb7b014643b%1735899162.196", "expected-lite/0.8.0#f87b3ec27a4f46894950b70f8d08af24%1717770563.402", - "ctre/3.9.0#318a83b26476a59466795daac928f3ec%1716966898.508", - "bzip2/1.0.8#d00dac990f08d991998d624be81a9526%1725632951.439", + "ctre/3.9.0#318a83b26476a59466795daac928f3ec%1715925273.754", "boost/1.86.0#ce76e7477e466d7d8cbcf738c5d64175%1736442226.917" ], "build_requires": [ + "nodejs/18.15.0#2e468fc390ccdb9595ea1a5bbb74d59f%1699800364.91", "m4/1.4.19#b38ced39a01e31fef5435bc634461fd2%1700758725.451", - "b2/5.2.1#91bc73931a0acb655947a81569ed8b80%1718416612.241" + "emsdk/3.1.66#dbe775f114c0ec093539734107e2ab5e%1733138978.7973351" ], "python_requires": [], "config_requires": [] diff --git a/include/kth/domain/chain/coin_selection.hpp b/include/kth/domain/chain/coin_selection.hpp new file mode 100644 index 000000000..1d0981c1c --- /dev/null +++ b/include/kth/domain/chain/coin_selection.hpp @@ -0,0 +1,23 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef KTH_DOMAIN_CHAIN_COIN_SELECTION_HPP +#define KTH_DOMAIN_CHAIN_COIN_SELECTION_HPP + +namespace kth::domain::chain { + +enum class coin_selection_algorithm { + smallest_first, // Prioriza UTXOs más pequeños + largest_first, // Prioriza UTXOs más grandes + // knapsack, // Algoritmo de la mochila para optimizar + // fifo, // Primero en entrar, primero en salir + // branch_and_bound, // Branch and Bound para optimización global + manual, // Mantiene el orden original de los UTXOs + // privacy, // Prioriza privacidad (mismo tamaño de UTXOs) + send_all // Usa todos los UTXOs disponibles +}; + +} // namespace kth::domain::chain + +#endif \ No newline at end of file diff --git a/include/kth/domain/chain/transaction.hpp b/include/kth/domain/chain/transaction.hpp index 8b1e8f2e1..4e73852da 100644 --- a/include/kth/domain/chain/transaction.hpp +++ b/include/kth/domain/chain/transaction.hpp @@ -18,6 +18,8 @@ #include #include #include +#include + #include #include #include @@ -31,14 +33,20 @@ #include #include #include -#include #include #include #include +#include + +#include +#include + namespace kth::domain::chain { +using template_result = std::tuple, std::vector, std::vector>; + class KD_API transaction : public transaction_basis { public: using ins = input::list; @@ -237,6 +245,25 @@ class KD_API transaction : public transaction_basis { bool is_standard() const; + static + nonstd::expected create_template( + std::vector available_utxos, + uint64_t amount_to_send_hint, + wallet::payment_address const& destination_address, + std::vector const& change_addresses, + coin_selection_algorithm selection_algo + ); + + static + nonstd::expected create_template( + std::vector available_utxos, + uint64_t amount_to_send_hint, + wallet::payment_address const& destination_address, + std::vector const& change_addresses, + std::vector const& change_ratios, + coin_selection_algorithm selection_algo + ); + // protected: void reset(); diff --git a/include/kth/domain/chain/utxo.hpp b/include/kth/domain/chain/utxo.hpp new file mode 100644 index 000000000..822b0595f --- /dev/null +++ b/include/kth/domain/chain/utxo.hpp @@ -0,0 +1,38 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef KTH_DOMAIN_CHAIN_UTXO_HPP +#define KTH_DOMAIN_CHAIN_UTXO_HPP + +#include + +#include +#include + +namespace kth::domain::chain { + +class KD_API utxo { +public: + utxo() = default; + + utxo(output_point const& point, uint64_t amount); + utxo(output_point&& point, uint64_t amount); + + // Getters + output_point& point(); + output_point const& point() const; + uint64_t amount() const; + + // Setters + void set_point(output_point const& point); + void set_amount(uint64_t amount); + +private: + output_point point_; + uint64_t amount_; +}; + +} // namespace kth::domain::chain + +#endif \ No newline at end of file diff --git a/include/kth/domain/impl/machine/program.ipp b/include/kth/domain/impl/machine/program.ipp index dfc3fd8b6..97846b98a 100644 --- a/include/kth/domain/impl/machine/program.ipp +++ b/include/kth/domain/impl/machine/program.ipp @@ -204,8 +204,7 @@ bool program::pop(int32_t& out_value) { number value; if ( ! pop(value, max_integer_size_legacy())) { return false; -} - + } out_value = value.int32(); return true; } @@ -216,7 +215,6 @@ bool program::pop(int64_t& out_value) { if ( ! pop(value, max_integer_size_legacy())) { return false; } - out_value = value.int64(); return true; } @@ -267,11 +265,11 @@ inline bool program::pop(data_stack& section, size_t count) { if (size() < count) { return false; -} + } for (size_t i = 0; i < count; ++i) { section.push_back(pop()); -} + } return true; } @@ -305,8 +303,7 @@ void program::erase(const stack_iterator& position) { // pop1/pop2/.../pop[i]/pop[first]/.../pop[last]/push[i]/.../push2/push1 inline -void program::erase(const stack_iterator& first, - const stack_iterator& last) { +void program::erase(const stack_iterator& first, const stack_iterator& last) { primary_.erase(first, last); } @@ -505,6 +502,12 @@ bool program::succeeded() const { ////return std::all_of(condition_.begin(), condition_.end(), true); } +//TODO: temp: +inline +chain::script const& program::get_script() const { + return script_; +} + } // namespace kth::domain::machine #endif diff --git a/include/kth/domain/machine/interpreter.hpp b/include/kth/domain/machine/interpreter.hpp index f3221e05a..40b3bb280 100644 --- a/include/kth/domain/machine/interpreter.hpp +++ b/include/kth/domain/machine/interpreter.hpp @@ -332,6 +332,21 @@ class KD_API interpreter { static code run(operation const& op, program& program); + +// Debug step by step +// ---------------------------------------------------------------------------- + static + std::pair debug_start(program const& program); + + static + bool debug_steps_available(program const& program, size_t step); + + static + std::tuple debug_step(program program, size_t step); + + static + code debug_end(program const& program); + private: static result run_op(operation const& op, program& program); diff --git a/include/kth/domain/machine/program.hpp b/include/kth/domain/machine/program.hpp index 43f951c0e..8cda2e4d8 100644 --- a/include/kth/domain/machine/program.hpp +++ b/include/kth/domain/machine/program.hpp @@ -196,6 +196,12 @@ class KD_API program { [[nodiscard]] bool succeeded() const; + +// TODO: temp: + [[nodiscard]] + chain::script const& get_script() const; + + private: // A space-efficient dynamic bitset (specialized). using bool_stack = std::vector; diff --git a/include/kth/domain/wallet/payment_address.hpp b/include/kth/domain/wallet/payment_address.hpp index 3b7639ab5..947eba08d 100644 --- a/include/kth/domain/wallet/payment_address.hpp +++ b/include/kth/domain/wallet/payment_address.hpp @@ -75,6 +75,10 @@ class KD_API payment_address { explicit payment_address(chain::script const& script, uint8_t version = mainnet_p2sh); + /// Factories + static + payment_address from_pay_key_hash_script(chain::script const& script, uint8_t version); + /// Operators. bool operator==(payment_address const& x) const; bool operator!=(payment_address const& x) const; diff --git a/src/chain/script.cpp b/src/chain/script.cpp index 088ca90b4..77d9c6919 100644 --- a/src/chain/script.cpp +++ b/src/chain/script.cpp @@ -814,14 +814,22 @@ bool script::is_pay_public_key_pattern(operation::list const& ops) { } bool script::is_pay_key_hash_pattern(operation::list const& ops) { - return ops.size() == 5 && ops[0].code() == opcode::dup && ops[1].code() == opcode::hash160 && ops[2].data().size() == short_hash_size && ops[3].code() == opcode::equalverify && ops[4].code() == opcode::checksig; + return ops.size() == 5 && + ops[0].code() == opcode::dup && + ops[1].code() == opcode::hash160 && + ops[2].data().size() == short_hash_size && + ops[3].code() == opcode::equalverify && + ops[4].code() == opcode::checksig; } //***************************************************************************** // CONSENSUS: this pattern is used to activate bip16 validation rules. //***************************************************************************** bool script::is_pay_script_hash_pattern(operation::list const& ops) { - return ops.size() == 3 && ops[0].code() == opcode::hash160 && ops[1].code() == opcode::push_size_20 && ops[2].code() == opcode::equal; + return ops.size() == 3 && + ops[0].code() == opcode::hash160 && + ops[1].code() == opcode::push_size_20 && + ops[2].code() == opcode::equal; } //***************************************************************************** @@ -869,8 +877,10 @@ operation::list script::to_pay_public_key_pattern(data_slice point) { return {}; } - return operation::list{{to_chunk(point)}, - {opcode::checksig}}; + return operation::list{ + {to_chunk(point)}, + {opcode::checksig} + }; } operation::list script::to_pay_key_hash_pattern(short_hash const& hash) { @@ -879,14 +889,16 @@ operation::list script::to_pay_key_hash_pattern(short_hash const& hash) { {opcode::hash160}, {to_chunk(hash)}, {opcode::equalverify}, - {opcode::checksig}}; + {opcode::checksig} + }; } operation::list script::to_pay_script_hash_pattern(short_hash const& hash) { return operation::list{ {opcode::hash160}, {to_chunk(hash)}, - {opcode::equal}}; + {opcode::equal} + }; } operation::list script::to_pay_multisig_pattern(uint8_t signatures, @@ -1153,7 +1165,8 @@ code script::verify(transaction const& tx, uint32_t input, uint32_t forks) { auto const& prevout = in.previous_output().validation.cache; #if ! defined(KTH_SEGWIT_ENABLED) - return verify(tx, input, forks, in.script(), prevout.script(), prevout.value()); + auto res = verify(tx, input, forks, in.script(), prevout.script(), prevout.value()); + return res; #else return verify(tx, input, forks, in.script(), in.witness(), prevout.script(), prevout.value()); #endif diff --git a/src/chain/script_basis.cpp b/src/chain/script_basis.cpp index e4339b6bb..a2c5468b4 100644 --- a/src/chain/script_basis.cpp +++ b/src/chain/script_basis.cpp @@ -421,7 +421,10 @@ bool script_basis::is_pay_key_hash_pattern(operation::list const& ops) { // CONSENSUS: this pattern is used to activate bip16 validation rules. //***************************************************************************** bool script_basis::is_pay_script_hash_pattern(operation::list const& ops) { - return ops.size() == 3 && ops[0].code() == opcode::hash160 && ops[1].code() == opcode::push_size_20 && ops[2].code() == opcode::equal; + return ops.size() == 3 && + ops[0].code() == opcode::hash160 && + ops[1].code() == opcode::push_size_20 && + ops[2].code() == opcode::equal; } //***************************************************************************** diff --git a/src/chain/transaction.cpp b/src/chain/transaction.cpp index 4b9ec62de..70df11e53 100644 --- a/src/chain/transaction.cpp +++ b/src/chain/transaction.cpp @@ -5,16 +5,20 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include #include +#include + #include #include #include @@ -35,12 +39,17 @@ #include #include #include +#include +#include +#include // using namespace kth::domain::machine; using namespace kth::infrastructure::machine; namespace kth::domain::chain { +using namespace machine; + // Constructors. //----------------------------------------------------------------------------- @@ -826,4 +835,231 @@ code verify(transaction const& tx, uint32_t input, uint32_t forks) { #endif } + +constexpr uint64_t sats_per_byte = 1; +constexpr uint64_t approx_input_size = 148; +constexpr uint64_t approx_output_size = 34; +constexpr uint64_t base_tx_size = 10; + +using utxo_selection_t = std::tuple; + +template + requires std::strict_weak_order +nonstd::expected select_utxos_simple( + std::vector& available_utxos, + uint64_t amount_to_send, + size_t output_count, + Comp cmp +) { + std::sort(available_utxos.begin(), available_utxos.end(), cmp); + + uint64_t total_selected = 0; + size_t utxo_count = 0; + + for (auto const& u : available_utxos) { + total_selected += u.amount(); + ++utxo_count; + + uint64_t const estimated_size = base_tx_size + (utxo_count * approx_input_size) + (output_count * approx_output_size); + uint64_t const estimated_fee = estimated_size * sats_per_byte; + uint64_t const total_needed = amount_to_send + estimated_fee; + if (total_selected >= total_needed) { + return utxo_selection_t { + total_selected, + utxo_count, + estimated_size, + amount_to_send + }; + } + } + return nonstd::make_unexpected(error::insufficient_amount); +} + +nonstd::expected select_utxos_all( + std::vector const& available_utxos, + uint64_t amount_to_send, + size_t output_count +) { + size_t const utxo_count = available_utxos.size(); + uint64_t const estimated_size = base_tx_size + (utxo_count * approx_input_size) + (output_count * approx_output_size); + + uint64_t const total_input = std::accumulate(available_utxos.begin(), available_utxos.end(), + uint64_t(0), [](uint64_t sum, utxo const& u) { + return sum + u.amount(); + }); + + uint64_t const estimated_fee = estimated_size * sats_per_byte; + if (total_input <= estimated_fee) { + return nonstd::make_unexpected(error::insufficient_amount); + } + + amount_to_send = total_input - estimated_fee; + + return utxo_selection_t { + total_input, + utxo_count, + estimated_size, + amount_to_send + }; +} + +std::vector make_change_ratios(size_t change_count) { + std::vector change_ratios(change_count, 0.0); + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> dis(0, 1); + std::generate(change_ratios.begin(), change_ratios.end(), [&gen, &dis]() { return dis(gen); }); + double const sum = std::accumulate(change_ratios.begin(), change_ratios.end(), 0.0); + std::transform(change_ratios.begin(), change_ratios.end(), change_ratios.begin(), [sum](double value) { return value / sum; }); + return change_ratios; +} + +nonstd::expected transaction::create_template( + std::vector available_utxos, + uint64_t amount_to_send_hint, + wallet::payment_address const& destination_address, + std::vector const& change_addresses, + std::vector const& change_ratios, + coin_selection_algorithm selection_algo +) { + // Validations + if (available_utxos.empty()) { + return nonstd::make_unexpected(error::empty_utxo_list); + } + + if (change_addresses.empty()) { + return nonstd::make_unexpected(error::invalid_change); + } + + if (change_ratios.size() != change_addresses.size()) { + return nonstd::make_unexpected(error::invalid_change); + } + + if (std::accumulate(change_ratios.begin(), change_ratios.end(), 0.0) != 1.0) { + return nonstd::make_unexpected(error::invalid_change); + } + + bool const send_all = amount_to_send_hint == 0 || + selection_algo == coin_selection_algorithm::send_all || + selection_algo == coin_selection_algorithm::manual; // in manual mode, we asume that the user provided just the selected utxos + + if (send_all && change_addresses.size() > 1) { + return nonstd::make_unexpected(error::invalid_change); + } + + boost::unordered_flat_map utxo_positions; + for (size_t i = 0; i < available_utxos.size(); ++i) { + utxo_positions[available_utxos[i].point()] = i; + } + + size_t const output_count = change_addresses.size() + 1; // +1 for the destination address + + auto const res = + send_all ? select_utxos_all(available_utxos, amount_to_send_hint, output_count) : + selection_algo == coin_selection_algorithm::smallest_first ? + select_utxos_simple(available_utxos, amount_to_send_hint, output_count, [](utxo const& a, utxo const& b) { return a.amount() < b.amount(); }) : + select_utxos_simple(available_utxos, amount_to_send_hint, output_count, [](utxo const& a, utxo const& b) { return a.amount() > b.amount(); }); + + if ( ! res) { + return nonstd::make_unexpected(res.error()); + } + + auto const [ + total_input, + utxo_count, + estimated_size, + amount_to_send] = *res; + + std::vector selected_utxo_indices; + transaction::ins inputs; + for (size_t i = 0; i < utxo_count; ++i) { + script placeholder_script; + data_chunk const placeholder_signature(71, 0); + data_chunk const placeholder_pubkey(33, 0); + operation::list const ops { + operation(machine::opcode::push_size_0), + operation(placeholder_signature), + operation(placeholder_pubkey) + }; + placeholder_script.from_operations(ops); + + inputs.emplace_back(available_utxos[i].point(), std::move(placeholder_script), max_input_sequence); + auto const it = utxo_positions.find(available_utxos[i].point()); + if (it != utxo_positions.end()) { + selected_utxo_indices.push_back(it->second); + } + } + + std::vector output_addresses; + std::vector output_amounts; + + transaction::outs outputs; + auto dest_ops = script::to_pay_key_hash_pattern(destination_address.hash20()); + chain::script dest_script; + dest_script.from_operations(dest_ops); + outputs.emplace_back(amount_to_send, std::move(dest_script), std::nullopt); + + output_addresses.push_back(destination_address); + output_amounts.push_back(amount_to_send); + + uint64_t const estimated_fee = estimated_size * sats_per_byte; + std::cout << "estimated_size: " << estimated_size << std::endl; + std::cout << "estimated_fee: " << estimated_fee << std::endl; + + if ( ! send_all) { + uint64_t const total_change_amount = total_input - (amount_to_send + estimated_fee); + uint64_t accumulated_change = 0; + + if (total_change_amount > 0) { + for (size_t i = 0; i < change_addresses.size(); ++i) { + auto change_ops = script::to_pay_key_hash_pattern(change_addresses[i].hash20()); + chain::script change_script; + change_script.from_operations(change_ops); + uint64_t const change_amount = total_change_amount * change_ratios[i]; + outputs.emplace_back(change_amount, std::move(change_script), std::nullopt); + output_addresses.push_back(change_addresses[i]); + output_amounts.push_back(change_amount); + accumulated_change += change_amount; + } + } + if (accumulated_change != total_change_amount) { + return nonstd::make_unexpected(error::unknown); + } + uint64_t const total_output = total_change_amount + amount_to_send + estimated_fee; + if (total_input != total_output) { + return nonstd::make_unexpected(error::unknown); + } + // postcondition validation + } else { + // postcondition validation + uint64_t const total_output = amount_to_send + estimated_fee; + if (total_input != total_output) { + return nonstd::make_unexpected(error::unknown); + } + } + + return std::make_tuple( + transaction(1, 0, std::move(inputs), std::move(outputs)), + std::move(selected_utxo_indices), + std::move(output_addresses), + std::move(output_amounts) + ); +} + +nonstd::expected transaction::create_template( + std::vector available_utxos, + uint64_t amount_to_send_hint, + wallet::payment_address const& destination_address, + std::vector const& change_addresses, + coin_selection_algorithm selection_algo +) { + return create_template( + available_utxos, + amount_to_send_hint, + destination_address, + change_addresses, + make_change_ratios(change_addresses.size()), + selection_algo); +} + } // namespace kth::domain::chain diff --git a/src/chain/utxo.cpp b/src/chain/utxo.cpp new file mode 100644 index 000000000..92cca8835 --- /dev/null +++ b/src/chain/utxo.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2016-2025 Knuth Project developers. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +namespace kth::domain::chain { + +utxo::utxo(output_point const& point, uint64_t amount) + : point_(point), amount_(amount) +{} + +utxo::utxo(output_point&& point, uint64_t amount) + : point_(std::move(point)), amount_(amount) +{} + +output_point& utxo::point() { + return point_; +} + +output_point const& utxo::point() const { + return point_; +} + +uint64_t utxo::amount() const { + return amount_; +} + +void utxo::set_point(output_point const& point) { + point_ = point; +} + +void utxo::set_amount(uint64_t amount) { + amount_ = amount; +} + +} // namespace kth::domain::chain \ No newline at end of file diff --git a/src/machine/interpreter.cpp b/src/machine/interpreter.cpp index 0641b8256..385286110 100644 --- a/src/machine/interpreter.cpp +++ b/src/machine/interpreter.cpp @@ -101,4 +101,141 @@ code interpreter::run(operation const& op, program& program) { return run_op(op, program); } + +// Debug step by step +// ------------------------------------------------------------------------------------------------- +// static +std::pair interpreter::debug_start(program const& program) { + code ec; + + if ( ! program.is_valid()) { + return {error::invalid_script, 0}; + } + + // BCHN code: + // bool const chipVmLimitsEnabled = (flags & SCRIPT_ENABLE_MAY2025) != 0; + // size_t const maxScriptElementSize = chipVmLimitsEnabled ? may2025::MAX_SCRIPT_ELEMENT_SIZE + // : MAX_SCRIPT_ELEMENT_SIZE_LEGACY; + + // if (chipVmLimitsEnabled && !metrics.HasValidScriptLimits() && context) { + // // Calculate metrics "scriptLimits", if not already passed-in, and if we have a `context` object + // // from which to get the scriptSig size. + // metrics.SetScriptLimits(flags, context->scriptSig().size()); + // } + + // TODO: flags vs forks + // if (program.is_chip_vm_limits_enabled() && !program.get_metrics().has_valid_script_limits() && context) { + // // Calculate metrics "scriptLimits", if not already passed-in, and if we have a `context` object + // // from which to get the scriptSig size. + // program.get_metrics().set_script_limits(program.get_flags(), context->scriptSig().size()); + // } + + return {error::success, 0}; +} + +// static +bool interpreter::debug_steps_available(program const& program, size_t step) { + // std::cout << "interpreter::debug_steps_available() step: " << step << std::endl; + // std::cout << "interpreter::debug_steps_available() program.operation_count(): " << program.operation_count() << std::endl; + // std::cout << "interpreter::debug_steps_available() program.get_script().operations().size(): " << program.get_script().operations().size() << std::endl; + // return step <= program.operation_count(); + return step < program.get_script().operations().size(); +} + +// static +std::tuple interpreter::debug_step(program program, size_t step) { + // std::cout << "----------------------------------------" << std::endl; + // std::cout << "interpreter::debug_step() BEGIN step: " << step << std::endl; + // std::cout << "interpreter::debug_step() BEGIN program.get_script().operations().size(): " << program.get_script().operations().size() << std::endl; + + // if (step > program.operation_count()) { + if (step >= program.get_script().operations().size()) { + std::cout << "interpreter::debug_step() return 1" << std::endl; + return {error::invalid_operation_count, step, program}; + } + + auto const op_it = program.begin() + step; + if (op_it == program.end()) { + std::cout << "interpreter::debug_step() return 2" << std::endl; + return {error::invalid_operation_count, step, program}; + } + + auto const op = *op_it; + + if (op.is_oversized(program.max_script_element_size())) { + std::cout << "interpreter::debug_step() return 3" << std::endl; + return {error::invalid_push_data_size, step, program}; + } + + if (op.is_disabled(program.forks())) { + // std::cout << "interpreter::debug_step() return 4" << std::endl; + return {error::op_disabled, step, program}; + } + + if ( ! program.increment_operation_count(op)) { + // std::cout << "interpreter::debug_step() return 5" << std::endl; + return {error::invalid_operation_count, step, program}; + } + + if (program.if_(op)) { + code ec = run_op(op, program); + if (ec != error::success) { + // std::cout << "interpreter::debug_step() return 6" << std::endl; + return {ec, step, program}; + } + + if (program.is_stack_overflow()) { + // std::cout << "interpreter::debug_step() return 7" << std::endl; + return {error::invalid_stack_size, step, program}; + } + + // Enforce May 2025 VM limits + if (program.is_chip_vm_limits_enabled()) { + // // Check that this opcode did not cause us to exceed opCost and/or hashIters limits. + // // Note: `metrics` may lack a valid "scriptLimits" object in rare cases (tests only), in which case + // // the below two limit checks are always going to return false. + + // if (metrics.IsOverOpCostLimit(flags)) { + // return set_error(serror, ScriptError::OP_COST); + // } + // if (metrics.IsOverHashItersLimit()) { + // return set_error(serror, ScriptError::TOO_MANY_HASH_ITERS); + // } + + // // Conditional stack may not exceed depth of 100 + // if (vfExec.size() > may2025::MAX_CONDITIONAL_STACK_DEPTH) { + // return set_error(serror, ScriptError::CONDITIONAL_STACK_DEPTH); + // } + + + // TODO: flags vs forks + // if (program.get_metrics().is_over_op_cost_limit(program.get_flags())) { + // return error::op_cost; + // } + + if (program.get_metrics().is_over_hash_iters_limit()) { + // std::cout << "interpreter::debug_step() return 8" << std::endl; + return {error::too_many_hash_iters, step, program}; + } + + // Conditional stack may not exceed depth of 100 + if (program.conditional_stack_size() > ::kth::may2025::max_conditional_stack_depth) { + // std::cout << "interpreter::debug_step() return 9" << std::endl; + return {error::conditional_stack_depth, step, program}; + } + } + } + + // std::cout << "interpreter::debug_step() END step: " << ++step << std::endl; + // std::cout << "interpreter::debug_step() END program.get_script().operations().size(): " << program.get_script().operations().size() << std::endl; + // std::cout << "----------------------------------------" << std::endl; + + return {error::success, ++step, program}; +} + +// static +code interpreter::debug_end(program const& program) { + return program.closed() ? error::success : error::invalid_stack_scope; +} + } // namespace kth::domain::machine diff --git a/src/machine/opcode.cpp b/src/machine/opcode.cpp index f49176bbc..cf1693885 100644 --- a/src/machine/opcode.cpp +++ b/src/machine/opcode.cpp @@ -421,9 +421,9 @@ std::string opcode_to_string(opcode value, uint32_t active_forks) { // This converts only names, not any data for push codes. bool opcode_from_string(opcode& out_code, std::string const& value) { //NOLINT + // Normalize to ASCII lower case. auto const norm = boost::algorithm::to_lower_copy(value); - RETURN_IF_OPCODE("zero", push_size_0); RETURN_IF_OPCODE("push_0", push_size_0); RETURN_IF_OPCODE("push_1", push_size_1); diff --git a/src/machine/operation.cpp b/src/machine/operation.cpp index 00a09cca2..f5bf63c81 100644 --- a/src/machine/operation.cpp +++ b/src/machine/operation.cpp @@ -4,10 +4,10 @@ #include +#include #include #include -#include // #include #include @@ -74,14 +74,14 @@ bool opcode_from_data_prefix(opcode& out_code, } static -bool data_from_number_token(data_chunk& out_data, - std::string const& token) { - try { - out_data = number(boost::lexical_cast(token)).data(); - return true; - } catch (const boost::bad_lexical_cast&) { +bool data_from_number_token(data_chunk& out_data, std::string const& token) { + int64_t value; + auto [ptr, ec] = std::from_chars(token.data(), token.data() + token.size(), value); + if (ec != std::errc()) { return false; } + out_data = number(value).data(); + return true; } // The removal of spaces in v3 data is a compatability break with our v2. diff --git a/src/wallet/payment_address.cpp b/src/wallet/payment_address.cpp index 404a6c6ef..4912dc7d1 100644 --- a/src/wallet/payment_address.cpp +++ b/src/wallet/payment_address.cpp @@ -67,6 +67,20 @@ payment_address::payment_address(hash_digest const& hash, uint8_t version) , hash_size_(hash.size()) {} +// Factories +// ---------------------------------------------------------------------------- + +payment_address payment_address::from_pay_key_hash_script(chain::script const& script, uint8_t version) { + auto const ops = script.operations(); + if ( ! chain::script::is_pay_key_hash_pattern(ops)) { + return {}; + } + short_hash hash; + std::copy(ops[2].data().begin(), ops[2].data().begin() + short_hash_size, hash.begin()); + + return payment_address{hash, version}; +} + // Validators. // ----------------------------------------------------------------------------