From a75e9b902c14e2f762564f11a62459acf3143029 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 14 Apr 2026 00:37:36 -0400 Subject: [PATCH 1/4] Adapt to database changes in electrum scripthash. --- .../electrum/protocol_electrum_scripthash.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/protocols/electrum/protocol_electrum_scripthash.cpp b/src/protocols/electrum/protocol_electrum_scripthash.cpp index 4aa51f6d..5120948d 100644 --- a/src/protocols/electrum/protocol_electrum_scripthash.cpp +++ b/src/protocols/electrum/protocol_electrum_scripthash.cpp @@ -364,21 +364,19 @@ void protocol_electrum::handle_blockchain_scripthash_unsubscribe(const code& ec, array_t protocol_electrum::transform(const database::histories& ins) NOEXCEPT { // Height is set to zero or max_size_t for unconfirmed history. + // to_signed() conversion is simple but sacrifices top height bit (ok). static_assert(to_signed(max_size_t) == -1 && is_max(max_size_t)); array_t out(ins.size()); std::ranges::transform(ins, out.begin(), [](const auto& in) NOEXCEPT { - const auto height = in.tx.height(); - const bool unconfirmed = is_min(height) || is_max(height); - object_t object { - { "height", to_signed(height) }, + { "height", to_signed(in.tx.height()) }, { "tx_hash", encode_hash(in.tx.hash()) } }; - if (unconfirmed) + if (!in.confirmed()) { // A fee of max_uint64 implies missing prevout(s). This will happen // for a block-downloaded tx queried during parallel block download @@ -400,13 +398,13 @@ array_t protocol_electrum::transform(const database::unspents& ins) NOEXCEPT array_t out(ins.size()); std::ranges::transform(ins, out.begin(), [](const auto& in) NOEXCEPT { - const auto& tx = in.tx; + const auto& out = in.out; return object_t { - { "tx_hash", encode_hash(tx.point().hash()) }, - { "tx_pos", tx.point().index() }, + { "tx_hash", encode_hash(out.point().hash()) }, + { "tx_pos", out.point().index() }, { "height", in.height }, - { "value", tx.value() } + { "value", out.value() } }; }); From 17d6f96427406d3982cb4b47c1414b0736ea2cc6 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 14 Apr 2026 00:38:32 -0400 Subject: [PATCH 2/4] Implement most of electrum send_get_status(). --- .../server/protocols/protocol_electrum.hpp | 4 + .../electrum/protocol_electrum_outputs.cpp | 149 +++++++++--------- 2 files changed, 82 insertions(+), 71 deletions(-) diff --git a/include/bitcoin/server/protocols/protocol_electrum.hpp b/include/bitcoin/server/protocols/protocol_electrum.hpp index 0a82c747..5855eaee 100644 --- a/include/bitcoin/server/protocols/protocol_electrum.hpp +++ b/include/bitcoin/server/protocols/protocol_electrum.hpp @@ -279,6 +279,10 @@ class BCS_API protocol_electrum array_t transform(const database::histories& histories) NOEXCEPT; array_t transform(const database::unspents& unspents) NOEXCEPT; + // Shared get_status implementation. + bool send_get_status(const std::string& tx_hash, double txout_idx, + const std::string& spk_hint) NOEXCEPT; + // These are thread safe. const options_t& options_; const bool turbo_; diff --git a/src/protocols/electrum/protocol_electrum_outputs.cpp b/src/protocols/electrum/protocol_electrum_outputs.cpp index c928629b..a7291ecd 100644 --- a/src/protocols/electrum/protocol_electrum_outputs.cpp +++ b/src/protocols/electrum/protocol_electrum_outputs.cpp @@ -18,6 +18,7 @@ */ #include +#include #include namespace libbitcoin { @@ -29,6 +30,8 @@ using namespace system; using namespace network::rpc; using namespace std::placeholders; +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + void protocol_electrum::handle_blockchain_utxo_get_address(const code& ec, rpc_interface::blockchain_utxo_get_address, const std::string& tx_hash, double index) NOEXCEPT @@ -91,48 +94,32 @@ void protocol_electrum::handle_blockchain_outpoint_get_status(const code& ec, return; } - uint32_t index{}; - hash_digest hash{}; - if (!to_integer(index, txout_idx) || - !decode_hash(hash, tx_hash)) - { - send_code(error::invalid_argument); + send_get_status(tx_hash, txout_idx, spk_hint); +} + +void protocol_electrum::handle_blockchain_outpoint_subscribe(const code& ec, + rpc_interface::blockchain_outpoint_subscribe, const std::string& tx_hash, + double txout_idx, const std::string& spk_hint) NOEXCEPT +{ + if (stopped(ec)) return; - } - // script is advisory, and should match prevout script. - if (!spk_hint.empty()) + if (!at_least(electrum::version::v1_7)) { - data_chunk bytes{}; - if (!decode_base16(bytes, spk_hint)) - { - send_code(error::invalid_argument); - return; - } - - chain::script script{ std::move(bytes), false }; - if (!script.is_valid()) - { - send_code(error::invalid_argument); - return; - } + send_code(error::wrong_version); + return; } - // TODO: implement outpoint status query. - chain::point prevout{ hash, index }; - send_result(object_t - { - { "height", 24 }, - { "spender_txhash", "" }, - { "spender_height", 42 } + if (!send_get_status(tx_hash, txout_idx, spk_hint)) + return; - }, 16, BIND(complete, _1)); + // TODO: collect the outpoint into a limited notification set. + subscribed_outpoint_.store(false, std::memory_order_relaxed); } -// TODO: implement. -void protocol_electrum::handle_blockchain_outpoint_subscribe(const code& ec, - rpc_interface::blockchain_outpoint_subscribe, const std::string& tx_hash, - double txout_idx, const std::string& spk_hint) NOEXCEPT +void protocol_electrum::handle_blockchain_outpoint_unsubscribe(const code& ec, + rpc_interface::blockchain_outpoint_unsubscribe, const std::string& tx_hash, + double txout_idx) NOEXCEPT { if (stopped(ec)) return; @@ -152,67 +139,87 @@ void protocol_electrum::handle_blockchain_outpoint_subscribe(const code& ec, return; } - // script is advisory, but should match prevout script. + // TODO: remove outpoint subscription from notification set. + chain::point prevout{ hash, index }; + const auto previous = subscribed_scriptpubkey_.load( + std::memory_order_relaxed); + + send_result(previous, 16, BIND(complete, _1)); +} + +// utility. +// ---------------------------------------------------------------------------- + +bool protocol_electrum::send_get_status(const std::string& tx_hash, + double txout_idx, const std::string& spk_hint) NOEXCEPT +{ + uint32_t index{}; + hash_digest hash{}; + if (!to_integer(index, txout_idx) || !decode_hash(hash, tx_hash)) + { + send_code(error::invalid_argument); + return false; + } + + // This is parsed for correctness but is not used. + // Script is advisory, and should match output script. if (!spk_hint.empty()) { data_chunk bytes{}; if (!decode_base16(bytes, spk_hint)) { send_code(error::invalid_argument); - return; + return false; } chain::script script{ std::move(bytes), false }; if (!script.is_valid()) { send_code(error::invalid_argument); - return; + return false; } } - // TODO: collect the outpoint into a limited notification set. - subscribed_outpoint_.store(false, std::memory_order_relaxed); - - // TODO: implement outpoint status query. - chain::point prevout{ hash, index }; - send_result(object_t + const auto& query = archive(); + const auto tx = query.to_tx(hash); + const auto output = query.to_output(tx, index); + if (output.is_terminal()) { - { "height", 24 }, - { "spender_txhash", "" }, - { "spender_height", 42 } - - }, 16, BIND(complete, _1)); -} - -void protocol_electrum::handle_blockchain_outpoint_unsubscribe(const code& ec, - rpc_interface::blockchain_outpoint_unsubscribe, const std::string& tx_hash, - double txout_idx) NOEXCEPT -{ - if (stopped(ec)) - return; + send_code(error::not_found); + return false; + } - if (!at_least(electrum::version::v1_7)) + // TODO: database query./////////////////////////////////////////////////// + size_t height{ database::history::rooted_height }; + if (const auto block = query.find_confirmed_block(tx); block.is_terminal()) { - send_code(error::wrong_version); - return; + if (!query.is_confirmed_all_prevouts(tx)) + height = database::history::unrooted_height; } - - uint32_t index{}; - hash_digest hash{}; - if (!to_integer(index, txout_idx) || - !decode_hash(hash, tx_hash)) + else if (!query.get_height(height, block)) { - send_code(error::invalid_argument); - return; + send_code(error::server_error); + return false; } + /////////////////////////////////////////////////////////////////////////// - // TODO: remove outpoint subscription from notification set. - chain::point prevout{ hash, index }; - const auto previous = subscribed_scriptpubkey_.load( - std::memory_order_relaxed); + // TODO: query tx spenders sorted history.///////////////////////////////// + const database::histories spenders{}; + /////////////////////////////////////////////////////////////////////////// - send_result(previous, 16, BIND(complete, _1)); + auto result = object_t{ { "height", to_unsigned(height) } }; + if (!spenders.empty()) + { + const auto& spender = spenders.front().tx; + result["spender_txhash"] = encode_hash(spender.hash()); + result["spender_height"] = to_unsigned(spender.height()); + } + + send_result(std::move(result), 16, BIND(complete, _1)); + return true; } +BC_POP_WARNING() + } // namespace server } // namespace libbitcoin From d0ea49f1a96bdd963ba00ea00b723b74907fcae7 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 14 Apr 2026 00:54:31 -0400 Subject: [PATCH 3/4] Delint, update send json size estimation. --- src/protocols/electrum/protocol_electrum.cpp | 4 ++++ src/protocols/electrum/protocol_electrum_outputs.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/protocols/electrum/protocol_electrum.cpp b/src/protocols/electrum/protocol_electrum.cpp index 0aa886fb..f7807620 100644 --- a/src/protocols/electrum/protocol_electrum.cpp +++ b/src/protocols/electrum/protocol_electrum.cpp @@ -33,6 +33,8 @@ using namespace system; using namespace network::rpc; using namespace std::placeholders; +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + // Electrum could be factored into protocols by version, with version-dependent // protocol attachment and with protocol derivations (see p2p). Currently all // methods apart from version are in one protocol class. @@ -310,5 +312,7 @@ void protocol_electrum::do_scriptpubkey(node::address_t) NOEXCEPT }, 128, BIND(handle_send, _1)); } +BC_POP_WARNING() + } // namespace server } // namespace libbitcoin diff --git a/src/protocols/electrum/protocol_electrum_outputs.cpp b/src/protocols/electrum/protocol_electrum_outputs.cpp index a7291ecd..84078246 100644 --- a/src/protocols/electrum/protocol_electrum_outputs.cpp +++ b/src/protocols/electrum/protocol_electrum_outputs.cpp @@ -215,7 +215,7 @@ bool protocol_electrum::send_get_status(const std::string& tx_hash, result["spender_height"] = to_unsigned(spender.height()); } - send_result(std::move(result), 16, BIND(complete, _1)); + send_result(std::move(result), 128, BIND(complete, _1)); return true; } From 8351ed6839ae39dc5f2678d95e17ef0e88a0b499 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 14 Apr 2026 01:03:43 -0400 Subject: [PATCH 4/4] Sent not impl for mempool_get_fee_histogram(). --- src/protocols/electrum/protocol_electrum_mempool.cpp | 3 ++- test/protocols/electrum/electrum_mempool.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/protocols/electrum/protocol_electrum_mempool.cpp b/src/protocols/electrum/protocol_electrum_mempool.cpp index 587e6098..624da5aa 100644 --- a/src/protocols/electrum/protocol_electrum_mempool.cpp +++ b/src/protocols/electrum/protocol_electrum_mempool.cpp @@ -45,7 +45,8 @@ void protocol_electrum::handle_mempool_get_fee_histogram(const code& ec, } // TODO: Empty array (of tuples), could be simulated with block fees. - send_result(array_t{}, 42, BIND(complete, _1)); + ////send_result(array_t{}, 42, BIND(complete, _1)); + send_code(error::not_implemented); } void protocol_electrum::handle_mempool_get_info(const code& ec, diff --git a/test/protocols/electrum/electrum_mempool.cpp b/test/protocols/electrum/electrum_mempool.cpp index 0718e38f..48d9ebe2 100644 --- a/test/protocols/electrum/electrum_mempool.cpp +++ b/test/protocols/electrum/electrum_mempool.cpp @@ -23,6 +23,7 @@ BOOST_FIXTURE_TEST_SUITE(electrum_tests, electrum_setup_fixture) using namespace system; static const code wrong_version{ server::error::wrong_version }; +static const code not_implemented{ server::error::not_implemented }; // mempool.get_fee_histogram @@ -51,12 +52,14 @@ BOOST_AUTO_TEST_CASE(electrum__mempool_get_fee_histogram__extra_param__dropped) REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } -BOOST_AUTO_TEST_CASE(electrum__mempool_get_fee_histogram__empty_params__empty_array) +BOOST_AUTO_TEST_CASE(electrum__mempool_get_fee_histogram__empty_params__not_implemented) { BOOST_REQUIRE(handshake(electrum::version::v1_2)); const auto response = get(R"({"id":603,"method":"mempool.get_fee_histogram","params":[]})" "\n"); - REQUIRE_NO_THROW_TRUE(response.at("result").as_array().empty()); + REQUIRE_NO_THROW_TRUE(response.at("error").as_object().at("code").is_int64()); + BOOST_REQUIRE_EQUAL(response.at("error").as_object().at("code").as_int64(), not_implemented.value()); + ///REQUIRE_NO_THROW_TRUE(response.at("result").as_array().empty()); } // mempool.get_info