From d445933552477324049ca51c74c22f6856d83d0a Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 22 Apr 2026 22:50:34 -0400 Subject: [PATCH 1/9] DRY in test constants. --- test/protocols/electrum/electrum_addresses.cpp | 18 ++++++++++-------- test/protocols/electrum/electrum_subscribe.cpp | 4 ++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/test/protocols/electrum/electrum_addresses.cpp b/test/protocols/electrum/electrum_addresses.cpp index 77d338b7..234130fe 100644 --- a/test/protocols/electrum/electrum_addresses.cpp +++ b/test/protocols/electrum/electrum_addresses.cpp @@ -23,6 +23,8 @@ using namespace system; static const code not_found{ server::error::not_found }; static const code wrong_version{ server::error::wrong_version }; static const code invalid_argument{ server::error::invalid_argument }; +static const std::string bogus_address{ "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn" }; +static const std::string found_address{ "1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM" }; BOOST_FIXTURE_TEST_SUITE(electrum_tests, electrum_ten_block_setup_fixture) @@ -60,7 +62,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_balance__not_found_address BOOST_REQUIRE(handshake(electrum::version::v1_0)); const auto request = R"({"id":904,"method":"blockchain.address.get_balance","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); + const auto response = get((boost::format(request) % bogus_address).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_object()); const auto& result = response.at("result").as_object(); @@ -80,7 +82,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_balance__confirmed_and_unc BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); const auto request = R"({"id":905,"method":"blockchain.address.get_balance","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM").str()); + const auto response = get((boost::format(request) % found_address).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_object()); const auto& result = response.at("result").as_object(); @@ -127,7 +129,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_history__not_found_address BOOST_REQUIRE(handshake(electrum::version::v1_0)); const auto request = R"({"id":1005,"method":"blockchain.address.get_history","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); + const auto response = get((boost::format(request) % bogus_address).str()); REQUIRE_NO_THROW_TRUE(response.at("result").as_array().empty()); } @@ -142,7 +144,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_history__confirmed_and_unc BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); const auto request = R"({"id":1006,"method":"blockchain.address.get_history","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM").str()); + const auto response = get((boost::format(request) % found_address).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_array()); const auto& history = response.at("result").as_array(); @@ -207,7 +209,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_mempool__not_found_address BOOST_REQUIRE(handshake(electrum::version::v1_0)); const auto request = R"({"id":1005,"method":"blockchain.address.get_mempool","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); + const auto response = get((boost::format(request) % bogus_address).str()); REQUIRE_NO_THROW_TRUE(response.at("result").as_array().empty()); } @@ -222,7 +224,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_mempool__confirmed_and_unc BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); const auto request = R"({"id":1006,"method":"blockchain.address.get_mempool","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM").str()); + const auto response = get((boost::format(request) % found_address).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_array()); const auto& history = response.at("result").as_array(); @@ -279,7 +281,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_list_unspent__not_found_addres BOOST_REQUIRE(handshake(electrum::version::v1_0)); const auto request = R"({"id":1005,"method":"blockchain.address.listunspent","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); + const auto response = get((boost::format(request) % bogus_address).str()); REQUIRE_NO_THROW_TRUE(response.at("result").as_array().empty()); } @@ -294,7 +296,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_list_unspent__confirmed_and_un BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); const auto request = R"({"id":1006,"method":"blockchain.address.listunspent","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM").str()); + const auto response = get((boost::format(request) % found_address).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_array()); const auto& unspent = response.at("result").as_array(); diff --git a/test/protocols/electrum/electrum_subscribe.cpp b/test/protocols/electrum/electrum_subscribe.cpp index 20ff1459..9bd5b112 100644 --- a/test/protocols/electrum/electrum_subscribe.cpp +++ b/test/protocols/electrum/electrum_subscribe.cpp @@ -23,9 +23,13 @@ using namespace system; static const code wrong_version{ server::error::wrong_version }; static const code invalid_argument{ server::error::invalid_argument }; static const std::string bogus_address{ "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn" }; +////static const std::string found_address{ "1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM" }; static const std::string bogus_scripthash{ "9c2c84a6cf9809e08af19557e28d38257e6fee6981269760637a5f9dfb000b05" }; +static const std::string found_scripthash{ "bad83872c90886be19b98734fd16741611efcd9f5de699c14b712675eec682f5" }; static const chain::script bogus{ chain::script::to_pay_key_hash_pattern({ 0x42 }) }; +static const chain::script found{ chain::script::to_pay_key_hash_pattern({ 0x02 }) }; static const auto bogus_script = encode_base16(bogus.to_data(false)); +static const auto found_script = encode_base16(found.to_data(false)); BOOST_FIXTURE_TEST_SUITE(electrum_tests, electrum_ten_block_setup_fixture) From d1adbd2ff8dd29d02982fc3c5d577e20f0711653 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 23 Apr 2026 01:46:16 -0400 Subject: [PATCH 2/9] Fix get_scripthash_history() status accumulation. --- src/protocols/electrum/protocol_electrum_subscribe.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/protocols/electrum/protocol_electrum_subscribe.cpp b/src/protocols/electrum/protocol_electrum_subscribe.cpp index 020a3be6..90f751e1 100644 --- a/src/protocols/electrum/protocol_electrum_subscribe.cpp +++ b/src/protocols/electrum/protocol_electrum_subscribe.cpp @@ -277,6 +277,10 @@ code protocol_electrum::get_scripthash_history(address_subscription& sub, hash, limit, turbo_)) return ec; + // No change to sub.status. + if (records.empty()) + return error::success; + // Accumulate confirmed status in order. auto it = records.cbegin(); const auto cend = records.cend(); @@ -302,6 +306,8 @@ code protocol_electrum::get_scripthash_history(address_subscription& sub, records, hash, max_size_t, turbo_)) return ec; + const auto confirmed_empty = records.empty(); + // Accumulate confirmed status in order. auto it = records.cbegin(); auto cend = records.cend(); @@ -317,6 +323,10 @@ code protocol_electrum::get_scripthash_history(address_subscription& sub, hash, turbo_)) return ec; + // No change to sub.status. + if (confirmed_empty && records.empty()) + return error::success; + // Reinitialize iterator for unconfirmed writer. it = records.cbegin(); cend = records.cend(); From bcafa3e49ef3a581d0305b231f4386d44f90e18b Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 23 Apr 2026 01:46:46 -0400 Subject: [PATCH 3/9] Fix complete_scripthash_subscribe() return format. --- .../bitcoin/server/protocols/protocol_electrum.hpp | 2 +- .../electrum/protocol_electrum_subscribe.cpp | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/include/bitcoin/server/protocols/protocol_electrum.hpp b/include/bitcoin/server/protocols/protocol_electrum.hpp index 6cdc89c0..8234cd61 100644 --- a/include/bitcoin/server/protocols/protocol_electrum.hpp +++ b/include/bitcoin/server/protocols/protocol_electrum.hpp @@ -274,7 +274,7 @@ class BCS_API protocol_electrum void do_scripthash_subscribe(const hash_digest& hash, notify_t type) NOEXCEPT; void complete_scripthash_subscribe(const code& ec, - const hash_digest& status, const hash_digest& hash) NOEXCEPT; + const hash_digest& status) NOEXCEPT; void scripthash_unsubscribe(const hash_digest& hash) NOEXCEPT; void do_scripthash_unsubscribe(const hash_digest& hash) NOEXCEPT; void complete_scripthash_unsubscribe(bool found) NOEXCEPT; diff --git a/src/protocols/electrum/protocol_electrum_subscribe.cpp b/src/protocols/electrum/protocol_electrum_subscribe.cpp index 90f751e1..2eac8d26 100644 --- a/src/protocols/electrum/protocol_electrum_subscribe.cpp +++ b/src/protocols/electrum/protocol_electrum_subscribe.cpp @@ -104,11 +104,11 @@ void protocol_electrum::do_scripthash_subscribe(const hash_digest& hash, subscribed_address_.store(true, relaxed); } - POST(complete_scripthash_subscribe, ec, hash, std::move(status)); + POST(complete_scripthash_subscribe, ec, std::move(status)); } void protocol_electrum::complete_scripthash_subscribe(const code& ec, - const hash_digest& status, const hash_digest& hash) NOEXCEPT + const hash_digest& status) NOEXCEPT { BC_ASSERT(stranded()); @@ -123,11 +123,8 @@ void protocol_electrum::complete_scripthash_subscribe(const code& ec, return; } - send_result(array_t - { - encode_hash(hash), - status == null_hash ? value_t{} : value_t{ encode_hash(status) } - }, 128, BIND(complete, _1)); + send_result(status == null_hash ? value_t{} : + value_t{ encode_hash(status) }, 128, BIND(complete, _1)); } // unsubscribe From b37526a056c751795777eb06f3c692bf2b37efec Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 23 Apr 2026 01:47:36 -0400 Subject: [PATCH 4/9] DRY and fix electrum tests. --- .../protocols/electrum/electrum_outpoints.cpp | 52 ++++++++----------- .../protocols/electrum/electrum_subscribe.cpp | 20 +++---- 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/test/protocols/electrum/electrum_outpoints.cpp b/test/protocols/electrum/electrum_outpoints.cpp index c03ec57e..075f5a67 100644 --- a/test/protocols/electrum/electrum_outpoints.cpp +++ b/test/protocols/electrum/electrum_outpoints.cpp @@ -25,6 +25,8 @@ using namespace system; static const code not_found{ server::error::not_found }; static const code wrong_version{ server::error::wrong_version }; static const code invalid_argument{ server::error::invalid_argument }; +static const std::string found_address{ "1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM" }; +static const std::string bogus_hash{ "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" }; // blockchain.utxo.get_address @@ -32,9 +34,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__obsoleted_version__w { BOOST_REQUIRE(handshake(electrum::version::v1_1)); - constexpr auto hash = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; const auto request = R"({"id":901,"method":"blockchain.utxo.get_address","params":["%1%",0]})" "\n"; - const auto result = get_error((boost::format(request) % hash).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } @@ -50,9 +51,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__invalid_index__inval { BOOST_REQUIRE(handshake(electrum::version::v1_0)); - constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":901,"method":"blockchain.utxo.get_address","params":["%1%",-1]})" "\n"; - const auto result = get_error((boost::format(request) % encode_hash(hash)).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } @@ -68,9 +68,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__tx_not_found__null) { BOOST_REQUIRE(handshake(electrum::version::v1_0)); - constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":901,"method":"blockchain.utxo.get_address","params":["%1%",0]})" "\n"; - const auto response = get((boost::format(request) % encode_hash(hash)).str()); + const auto response = get((boost::format(request) % bogus_hash).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_null()); } @@ -78,9 +77,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__p2pk__null) { BOOST_REQUIRE(handshake(electrum::version::v1_0)); - constexpr auto hash = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; const auto request = R"({"id":901,"method":"blockchain.utxo.get_address","params":["%1%",0]})" "\n"; - const auto response = get((boost::format(request) % hash).str()); + const auto response = get((boost::format(request) % bogus_hash).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_null()); } @@ -98,7 +96,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__p2kh__expected) REQUIRE_NO_THROW_TRUE(response.at("result").is_string()); const auto& result = response.at("result").as_string(); - BOOST_REQUIRE_EQUAL(result,"1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM"); + BOOST_REQUIRE_EQUAL(result, found_address); } BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__p2sh__expected) @@ -124,9 +122,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__insufficient_vers { BOOST_REQUIRE(handshake(electrum::version::v1_6)); - constexpr auto hash = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; const auto request = R"({"id":1101,"method":"blockchain.outpoint.get_status","params":["%1%",0]})" "\n"; - const auto result = get_error((boost::format(request) % hash).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } @@ -150,9 +147,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__invalid_index__in { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":1104,"method":"blockchain.outpoint.get_status","params":["%1%",-1]})" "\n"; - const auto result = get_error((boost::format(request) % encode_hash(hash)).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } @@ -170,9 +166,9 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__confirmed_unspent { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - const auto hash1 = test::block1.transactions_ptr()->at(0)->hash(false); + const auto hash = test::block1.transactions_ptr()->at(0)->hash(false); const auto request = R"({"id":1106,"method":"blockchain.outpoint.get_status","params":["%1%",0]})" "\n"; - const auto response = get((boost::format(request) % encode_hash(hash1)).str()); + const auto response = get((boost::format(request) % encode_hash(hash)).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_object()); const auto& result = response.at("result").as_object(); @@ -210,9 +206,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__insufficient_versi { BOOST_REQUIRE(handshake(electrum::version::v1_6)); - constexpr auto hash = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; const auto request = R"({"id":1101,"method":"blockchain.outpoint.subscribe","params":["%1%",0]})" "\n"; - const auto result = get_error((boost::format(request) % hash).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } @@ -236,9 +231,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__invalid_index__inv { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":1104,"method":"blockchain.outpoint.subscribe","params":["%1%",-1]})" "\n"; - const auto result = get_error((boost::format(request) % encode_hash(hash)).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } @@ -246,9 +240,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__invalid_hint_encod { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":1104,"method":"blockchain.outpoint.subscribe","params":["%1%",-1,"not_hex"]})" "\n"; - const auto result = get_error((boost::format(request) % encode_hash(hash)).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } @@ -256,9 +249,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__invalid_hint__inva { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":1104,"method":"blockchain.outpoint.subscribe","params":["%1%",-1,""]})" "\n"; - const auto result = get_error((boost::format(request) % encode_hash(hash)).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } @@ -266,8 +258,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__extra_argument__dr { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - constexpr hash_digest hash{ 0x42 }; - const auto response = get(R"({"id":1104,"method":"blockchain.outpoint.subscribe","params":["%1%",-1,"00",42]})" "\n"); + const auto request = R"({"id":1104,"method":"blockchain.outpoint.subscribe","params":["%1%",-1,"00",42]})" "\n"; + const auto response = get((boost::format(request) % bogus_hash).str()); REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } @@ -277,9 +269,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_unsubscribe__insufficient_ver { BOOST_REQUIRE(handshake(electrum::version::v1_6)); - constexpr auto hash = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; const auto request = R"({"id":1101,"method":"blockchain.outpoint.unsubscribe","params":["%1%",0]})" "\n"; - const auto result = get_error((boost::format(request) % hash).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } @@ -303,9 +294,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_unsubscribe__invalid_index__i { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":1104,"method":"blockchain.outpoint.unsubscribe","params":["%1%",-1]})" "\n"; - const auto result = get_error((boost::format(request) % encode_hash(hash)).str()); + const auto result = get_error((boost::format(request) % bogus_hash).str()); BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } @@ -313,8 +303,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_unsubscribe__extra_argument__ { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - constexpr hash_digest hash{ 0x42 }; - const auto response = get(R"({"id":1104,"method":"blockchain.outpoint.unsubscribe","params":["%1%",-1,""]})" "\n"); + const auto request = R"({"id":1104,"method":"blockchain.outpoint.unsubscribe","params":["%1%",-1,""]})" "\n"; + const auto response = get((boost::format(request) % bogus_hash).str()); REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } diff --git a/test/protocols/electrum/electrum_subscribe.cpp b/test/protocols/electrum/electrum_subscribe.cpp index 9bd5b112..d02a78fa 100644 --- a/test/protocols/electrum/electrum_subscribe.cpp +++ b/test/protocols/electrum/electrum_subscribe.cpp @@ -20,10 +20,11 @@ #include "electrum.hpp" using namespace system; +static const code not_found{ server::error::not_found }; static const code wrong_version{ server::error::wrong_version }; static const code invalid_argument{ server::error::invalid_argument }; static const std::string bogus_address{ "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn" }; -////static const std::string found_address{ "1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM" }; +static const std::string found_address{ "1BaMPFdqMUQ46BV8iRcwbVfsam57oBLMM" }; static const std::string bogus_scripthash{ "9c2c84a6cf9809e08af19557e28d38257e6fee6981269760637a5f9dfb000b05" }; static const std::string found_scripthash{ "bad83872c90886be19b98734fd16741611efcd9f5de699c14b712675eec682f5" }; static const chain::script bogus{ chain::script::to_pay_key_hash_pattern({ 0x42 }) }; @@ -64,7 +65,6 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__extra_argument__dro { BOOST_REQUIRE(handshake(electrum::version::v1_0)); - constexpr hash_digest hash{ 0x42 }; const auto response = get(R"({"id":1104,"method":"blockchain.address.subscribe","params":["%1%",42]})" "\n"); REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } @@ -100,8 +100,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__extra_argument__ { BOOST_REQUIRE(handshake(electrum::version::v1_1)); - constexpr hash_digest hash{ 0x42 }; - const auto response = get(R"({"id":1104,"method":"blockchain.scripthash.subscribe","params":["%1%",42]})" "\n"); + const auto request = R"({"id":1104,"method":"blockchain.scripthash.subscribe","params":["%1%",42]})" "\n"; + const auto response = get((boost::format(request) % bogus_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } @@ -136,8 +136,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__extra_argument { BOOST_REQUIRE(handshake(electrum::version::v1_4_2)); - constexpr hash_digest hash{ 0x42 }; - const auto response = get(R"({"id":1104,"method":"blockchain.scripthash.unsubscribe","params":["%1%",-1]})" "\n"); + const auto request = R"({"id":1104,"method":"blockchain.scripthash.unsubscribe","params":["%1%",-1]})" "\n"; + const auto response = get((boost::format(request) % bogus_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } @@ -172,8 +172,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__extra_argument { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - constexpr hash_digest hash{ 0x42 }; - const auto response = get(R"({"id":1104,"method":"blockchain.scriptpubkey.subscribe","params":["%1%",42]})" "\n"); + const auto request = R"({"id":1104,"method":"blockchain.scriptpubkey.subscribe","params":["%1%",42]})" "\n"; + const auto response = get((boost::format(request) % bogus_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } @@ -208,8 +208,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_unsubscribe__extra_argume { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - constexpr hash_digest hash{ 0x42 }; - const auto response = get(R"({"id":1104,"method":"blockchain.scriptpubkey.unsubscribe","params":["%1%",-1]})" "\n"); + const auto request = R"({"id":1104,"method":"blockchain.scriptpubkey.unsubscribe","params":["%1%",-1]})" "\n"; + const auto response = get((boost::format(request) % bogus_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } From c692c375a599089eb9c6d03cc6373c34848bf44d Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 23 Apr 2026 23:12:00 -0400 Subject: [PATCH 5/9] Add blockchain_address_subscribe positive tests, fixes. --- .../server/protocols/protocol_electrum.hpp | 4 +- .../electrum/protocol_electrum_scripthash.cpp | 9 +- .../electrum/protocol_electrum_subscribe.cpp | 99 +++---------- .../protocols/electrum/electrum_addresses.cpp | 40 ++--- test/protocols/electrum/electrum_disabled.cpp | 40 ++--- test/protocols/electrum/electrum_fees.cpp | 20 +-- test/protocols/electrum/electrum_headers.cpp | 25 ++-- test/protocols/electrum/electrum_mempool.cpp | 16 +- .../protocols/electrum/electrum_subscribe.cpp | 137 ++++++++++++++++++ 9 files changed, 226 insertions(+), 164 deletions(-) diff --git a/include/bitcoin/server/protocols/protocol_electrum.hpp b/include/bitcoin/server/protocols/protocol_electrum.hpp index 8234cd61..dd5a1d28 100644 --- a/include/bitcoin/server/protocols/protocol_electrum.hpp +++ b/include/bitcoin/server/protocols/protocol_electrum.hpp @@ -211,7 +211,7 @@ class BCS_API protocol_electrum using history = database::history; using unspents = database::unspents; using histories = database::histories; - using cursor_t = database::address_link; + using cursor_t = database::height_link; using midstate = system::accumulator; enum class notify_t { address, scripthash, scriptpubkey }; @@ -282,7 +282,7 @@ class BCS_API protocol_electrum notify_t type) NOEXCEPT; code get_scripthash_history(address_subscription& sub, - const hash_digest& hash, size_t limit=max_size_t) NOEXCEPT; + const hash_digest& hash, size_t limit) NOEXCEPT; /// Outpoint. /// ----------------------------------------------------------------------- diff --git a/src/protocols/electrum/protocol_electrum_scripthash.cpp b/src/protocols/electrum/protocol_electrum_scripthash.cpp index 6f78cc2e..88a42f2a 100644 --- a/src/protocols/electrum/protocol_electrum_scripthash.cpp +++ b/src/protocols/electrum/protocol_electrum_scripthash.cpp @@ -152,8 +152,11 @@ void protocol_electrum::do_get_history(const hash_digest& hash) NOEXCEPT { BC_ASSERT(!stranded()); histories histories{}; + database::height_link cursor{}; const auto& query = archive(); - const auto ec = query.get_history(stopping_, histories, hash, turbo_); + const auto ec = query.get_history(stopping_, cursor, histories, hash, + options().maximum_history, turbo_); + POST(complete_get_history, ec, std::move(histories)); } @@ -223,7 +226,9 @@ void protocol_electrum::do_get_mempool(const hash_digest& hash) NOEXCEPT BC_ASSERT(!stranded()); histories histories{}; const auto& query = archive(); - auto ec = query.get_unconfirmed_history(stopping_, histories, hash, turbo_); + auto ec = query.get_unconfirmed_history(stopping_, histories, hash, + options().maximum_history, turbo_); + POST(complete_get_mempool, ec, std::move(histories)); } diff --git a/src/protocols/electrum/protocol_electrum_subscribe.cpp b/src/protocols/electrum/protocol_electrum_subscribe.cpp index 2eac8d26..80fb6ca6 100644 --- a/src/protocols/electrum/protocol_electrum_subscribe.cpp +++ b/src/protocols/electrum/protocol_electrum_subscribe.cpp @@ -199,7 +199,7 @@ void protocol_electrum::do_scripthash(node::header_t) NOEXCEPT for (auto& [key, sub]: address_subscriptions_) { // Depth limit is never imposed once a subscription is accepted. - if (const auto ec = get_scripthash_history(sub, key)) + if (const auto ec = get_scripthash_history(sub, key, max_size_t)) { if (ec == database::error::query_canceled) return; @@ -265,82 +265,31 @@ code protocol_electrum::get_scripthash_history(address_subscription& sub, { histories records{}; const auto& query = archive(); + if (const auto ec = query.get_history(stopping_, sub.cursor, records, + hash, limit, turbo_)) + return ec; - if (sub.cursor.is_terminal()) - { - // Initial scan queries all confirmed and unconfired together. - // Initial scan is depth-limited (based on config), others are not. - if (const auto ec = query.get_history(stopping_, sub.cursor, records, - hash, limit, turbo_)) - return ec; - - // No change to sub.status. - if (records.empty()) - return error::success; - - // Accumulate confirmed status in order. - auto it = records.cbegin(); - const auto cend = records.cend(); - while (it != cend && it->confirmed()) - write_status(sub.accumulator, *it++); - - BC_ASSERT(std::none_of(it, cend, [](const auto& at) - { return at.confirmed(); })); - - // Copy midstate accumulator and write unconfirmeds. - midstate copy = sub.accumulator; - while (it != cend) - write_status(copy, *it++); - - // Flush, cache and return status (always updated on initial). - sub.status = copy.flush(); + // No change to sub.status (null_hash if never written). + if (records.empty()) return error::success; - } - else - { - // Update scan queries new (cursor) confirmed independently. - if (const auto ec = query.get_confirmed_history(stopping_, sub.cursor, - records, hash, max_size_t, turbo_)) - return ec; - - const auto confirmed_empty = records.empty(); - - // Accumulate confirmed status in order. - auto it = records.cbegin(); - auto cend = records.cend(); - while (it != cend && it->confirmed()) - write_status(sub.accumulator, *it++); - - // Copy midstate accumulator for write of unconfirmeds. - midstate copy = sub.accumulator; - records.clear(); - - // Update scan queries all unconfirmed independently. - if (const auto ec = query.get_unconfirmed_history(stopping_, records, - hash, turbo_)) - return ec; - - // No change to sub.status. - if (confirmed_empty && records.empty()) - return error::success; - - // Reinitialize iterator for unconfirmed writer. - it = records.cbegin(); - cend = records.cend(); - - // Accumulate unconfirmed status in order. - while (it != cend) - write_status(copy, *it++); - - // Flush, cache and return not found if no writes. - auto status = copy.flush(); - if (sub.status == status) - return error::not_found; - - // Set cache into midstate object for next run. - sub.status = std::move(status); - return error::success; - } + + // Add confirmed status in order. + auto it = records.cbegin(); + const auto end = records.cend(); + while (it != end && it->confirmed()) + write_status(sub.accumulator, *it++); + + BC_ASSERT(std::none_of(it, end, [](const auto& at) + { return at.confirmed(); })); + + // Copy midstate accumulator and add unconfirmed status in order. + midstate copy = sub.accumulator; + while (it != end) + write_status(copy, *it++); + + // Flush, cache and return status (may not be a change). + sub.status = copy.flush(); + return error::success; } BC_POP_WARNING() diff --git a/test/protocols/electrum/electrum_addresses.cpp b/test/protocols/electrum/electrum_addresses.cpp index 234130fe..adfdbd6b 100644 --- a/test/protocols/electrum/electrum_addresses.cpp +++ b/test/protocols/electrum/electrum_addresses.cpp @@ -43,18 +43,16 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_balance__obsoleted_version { BOOST_REQUIRE(handshake(electrum::version::v1_3)); - const auto response = get(R"({"id":902,"method":"blockchain.address.get_balance","params":[""]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":902,"method":"blockchain.address.get_balance","params":[""]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_balance__invalid_address__invalid_argument) { BOOST_REQUIRE(handshake(electrum::version::v1_1)); - const auto response = get(R"({"id":903,"method":"blockchain.address.get_balance","params":["invalid"]})" "\n"); - 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":903,"method":"blockchain.address.get_balance","params":["invalid"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_balance__not_found_address__zero) @@ -110,18 +108,16 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_history__obsoleted_version { BOOST_REQUIRE(handshake(electrum::version::v1_3)); - const auto response = get(R"({"id":1003,"method":"blockchain.address.get_history","params":[""]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":1003,"method":"blockchain.address.get_history","params":[""]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_history__invalid_address__invalid_argument) { BOOST_REQUIRE(handshake(electrum::version::v1_1)); - const auto response = get(R"({"id":1004,"method":"blockchain.address.get_history","params":["invalid"]})" "\n"); - 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":1004,"method":"blockchain.address.get_history","params":["invalid"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_history__not_found_address__empty) @@ -190,18 +186,16 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_mempool__obsoleted_version { BOOST_REQUIRE(handshake(electrum::version::v1_3)); - const auto response = get(R"({"id":1003,"method":"blockchain.address.get_mempool","params":[""]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":1003,"method":"blockchain.address.get_mempool","params":[""]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_mempool__invalid_address__invalid_argument) { BOOST_REQUIRE(handshake(electrum::version::v1_1)); - const auto response = get(R"({"id":1004,"method":"blockchain.address.get_mempool","params":["invalid"]})" "\n"); - 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":1004,"method":"blockchain.address.get_mempool","params":["invalid"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_mempool__not_found_address__empty) @@ -262,18 +256,16 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_list_unspent__obsoleted_versio { BOOST_REQUIRE(handshake(electrum::version::v1_3)); - const auto response = get(R"({"id":1003,"method":"blockchain.address.listunspent","params":[""]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":1003,"method":"blockchain.address.listunspent","params":[""]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_list_unspent__invalid_address__invalid_argument) { BOOST_REQUIRE(handshake(electrum::version::v1_1)); - const auto response = get(R"({"id":1004,"method":"blockchain.address.listunspent","params":["invalid"]})" "\n"); - 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":1004,"method":"blockchain.address.listunspent","params":["invalid"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_list_unspent__not_found_address__empty) diff --git a/test/protocols/electrum/electrum_disabled.cpp b/test/protocols/electrum/electrum_disabled.cpp index 691f4245..dafd940b 100644 --- a/test/protocols/electrum/electrum_disabled.cpp +++ b/test/protocols/electrum/electrum_disabled.cpp @@ -37,9 +37,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_balance__no_address_index_ BOOST_REQUIRE(handshake(electrum::version::v1_0)); const auto request = R"({"id":901,"method":"blockchain.address.get_balance","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); - 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()); + const auto result = get_error((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_history__no_address_index__not_implemented) @@ -48,9 +47,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_history__no_address_index_ BOOST_REQUIRE(handshake(electrum::version::v1_0)); const auto request = R"({"id":1001,"method":"blockchain.address.get_history","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); - 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()); + const auto result = get_error((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_mempool__no_address_index__not_implemented) @@ -59,9 +57,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_get_mempool__no_address_index_ BOOST_REQUIRE(handshake(electrum::version::v1_0)); const auto request = R"({"id":1001,"method":"blockchain.address.get_mempool","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); - 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()); + const auto result = get_error((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_address_list_unspent__no_address_index__not_implemented) @@ -70,9 +67,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_list_unspent__no_address_index BOOST_REQUIRE(handshake(electrum::version::v1_0)); const auto request = R"({"id":1001,"method":"blockchain.address.listunspent","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); - 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()); + const auto result = get_error((boost::format(request) % "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn").str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } // scripthash @@ -165,9 +161,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_get_balance__no_address_i BOOST_REQUIRE(handshake(electrum::version::v1_7)); const auto request = R"({"id":901,"method":"blockchain.scriptpubkey.get_balance","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script).str()); - 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()); + const auto result = get_error((boost::format(request) % bogus_script).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_get_history__no_address_index__not_implemented) @@ -176,9 +171,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_get_history__no_address_i BOOST_REQUIRE(handshake(electrum::version::v1_7)); const auto request = R"({"id":1001,"method":"blockchain.scriptpubkey.get_history","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script).str()); - 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()); + const auto result = get_error((boost::format(request) % bogus_script).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_get_mempool__no_address_index__not_implemented) @@ -187,9 +181,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_get_mempool__no_address_i BOOST_REQUIRE(handshake(electrum::version::v1_7)); const auto request = R"({"id":1001,"method":"blockchain.scriptpubkey.get_mempool","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script).str()); - 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()); + const auto result = get_error((boost::format(request) % bogus_script).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_list_unspent__no_address_index__not_implemented) @@ -198,9 +191,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_list_unspent__no_address_ BOOST_REQUIRE(handshake(electrum::version::v1_7)); const auto request = R"({"id":1001,"method":"blockchain.scriptpubkey.listunspent","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script).str()); - 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()); + const auto result = get_error((boost::format(request) % bogus_script).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/protocols/electrum/electrum_fees.cpp b/test/protocols/electrum/electrum_fees.cpp index 690275b4..2020a209 100644 --- a/test/protocols/electrum/electrum_fees.cpp +++ b/test/protocols/electrum/electrum_fees.cpp @@ -40,27 +40,24 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_estimate_fee__float_number__invalid_ar { BOOST_REQUIRE(handshake(electrum::version::v1_0)); - const auto response = get(R"({"id":801,"method":"blockchain.estimatefee","params":[42.42]})" "\n"); - 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":801,"method":"blockchain.estimatefee","params":[42.42]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_estimate_fee__mode_invalid_version__invalid_argument) { BOOST_REQUIRE(handshake(electrum::version::v1_4)); - const auto response = get(R"({"id":801,"method":"blockchain.estimatefee","params":[42,"mode"]})" "\n"); - 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":801,"method":"blockchain.estimatefee","params":[42,"mode"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_estimate_fee__valid__not_implemented) { BOOST_REQUIRE(handshake(electrum::version::v1_6)); - const auto response = get(R"({"id":801,"method":"blockchain.estimatefee","params":[42,"mode"]})" "\n"); - 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()); + const auto result = get_error(R"({"id":801,"method":"blockchain.estimatefee","params":[42,"mode"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } // blockchain.relayfee @@ -81,9 +78,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_relay_fee__obsoleted__wrong_version) { BOOST_REQUIRE(handshake(electrum::version::v1_6)); - const auto response = get(R"({"id":801,"method":"blockchain.relayfee","params":[]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":801,"method":"blockchain.relayfee","params":[]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/protocols/electrum/electrum_headers.cpp b/test/protocols/electrum/electrum_headers.cpp index e8f5cb12..d44ab2e4 100644 --- a/test/protocols/electrum/electrum_headers.cpp +++ b/test/protocols/electrum/electrum_headers.cpp @@ -33,9 +33,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_number_of_blocks_subscribe__obsoleted_ { BOOST_REQUIRE(handshake(electrum::version::v1_1)); - const auto response = get(R"({"id":1001,"method":"blockchain.numblocks.subscribe","params":[]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":1001,"method":"blockchain.numblocks.subscribe","params":[]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_number_of_blocks_subscribe__9_block_store__returns_9) @@ -53,18 +52,16 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_block_get_chunk__obsoleted_version__wr { BOOST_REQUIRE(handshake(electrum::version::v1_4)); - const auto response = get(R"({"id":43,"method":"blockchain.block.get_chunk","params":[0]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":43,"method":"blockchain.block.get_chunk","params":[0]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_block_get_chunk__invalid_index__invalid_argument) { BOOST_REQUIRE(handshake(electrum::version::v1_0)); - const auto response = get(R"({"id":43,"method":"blockchain.block.get_chunk","params":[42.42]})" "\n"); - 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":43,"method":"blockchain.block.get_chunk","params":[42.42]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_block_get_chunk__above_top__empty) @@ -98,18 +95,16 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_block_get_header__obsoleted_version__w { BOOST_REQUIRE(handshake(electrum::version::v1_4)); - const auto response = get(R"({"id":43,"method":"blockchain.block.get_header","params":[0]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":43,"method":"blockchain.block.get_header","params":[0]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_block_get_header__invalid_height__invalid_argument) { BOOST_REQUIRE(handshake(electrum::version::v1_0)); - const auto response = get(R"({"id":43,"method":"blockchain.block.get_header","params":[42.42]})" "\n"); - 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":43,"method":"blockchain.block.get_header","params":[42.42]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_block_get_header__above_top__null) diff --git a/test/protocols/electrum/electrum_mempool.cpp b/test/protocols/electrum/electrum_mempool.cpp index 13589731..56b1c5bb 100644 --- a/test/protocols/electrum/electrum_mempool.cpp +++ b/test/protocols/electrum/electrum_mempool.cpp @@ -31,9 +31,8 @@ BOOST_AUTO_TEST_CASE(electrum__mempool_get_fee_histogram__insufficient_version__ { BOOST_REQUIRE(handshake(electrum::version::v1_1)); - const auto response = get(R"({"id":600,"method":"mempool.get_fee_histogram","params":[]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":600,"method":"mempool.get_fee_histogram","params":[]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__mempool_get_fee_histogram__no_params_key__dropped) @@ -56,10 +55,8 @@ BOOST_AUTO_TEST_CASE(electrum__mempool_get_fee_histogram__empty_params__not_impl { 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("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()); + const auto result = get_error(R"({"id":603,"method":"mempool.get_fee_histogram","params":[]})" "\n"); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } // mempool.get_info @@ -68,9 +65,8 @@ BOOST_AUTO_TEST_CASE(electrum__mempool_get_info__insufficient_version__wrong_ver { BOOST_REQUIRE(handshake(electrum::version::v1_4)); - const auto response = get(R"({"id":700,"method":"mempool.get_info","params":[]})" "\n"); - 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(), wrong_version.value()); + const auto result = get_error(R"({"id":700,"method":"mempool.get_info","params":[]})" "\n"); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__mempool_get_info__no_params_key__dropped) diff --git a/test/protocols/electrum/electrum_subscribe.cpp b/test/protocols/electrum/electrum_subscribe.cpp index d02a78fa..6fd420b9 100644 --- a/test/protocols/electrum/electrum_subscribe.cpp +++ b/test/protocols/electrum/electrum_subscribe.cpp @@ -69,6 +69,125 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__extra_argument__dro REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } +BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__bogus_p2pkh__null) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + const auto request = R"({"id":1101,"method":"blockchain.address.subscribe","params":["%1%"]})" "\n"; + const auto response = get((boost::format(request) % bogus_address).str()); + REQUIRE_NO_THROW_TRUE(response.at("result").is_null()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__initialization__expected_status) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + // This validates the hash accumulator copy in get_scripthash_history() and incomporates + // confirmed, rooted and unrooted transactions, duplicates, and sort. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + const auto expected_initial = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.address.subscribe","params":["%1%"]})" "\n"; + const auto response = get((boost::format(request) % found_address).str()); + REQUIRE_NO_THROW_TRUE(response.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response.at("result").as_string(), expected_initial); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__repeat_call__idempotent) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + // This validates curosr/midstate consistency. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + const auto expected_initial = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.address.subscribe","params":["%1%"]})" "\n"; + const auto response1 = get((boost::format(request) % found_address).str()); + REQUIRE_NO_THROW_TRUE(response1.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response1.at("result").as_string(), expected_initial); + + const auto response2 = get((boost::format(request) % found_address).str()); + REQUIRE_NO_THROW_TRUE(response2.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response2.at("result").as_string(), expected_initial); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__progressive__expected) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + // This validates curosr/midstate consistency. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + + // Confirming block 10 also makes block 11 to rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + const auto expected_confirm10 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.address.subscribe","params":["%1%"]})" "\n"; + const auto response1 = get((boost::format(request) % found_address).str()); + REQUIRE_NO_THROW_TRUE(response1.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response1.at("result").as_string(), expected_confirm10); + + // Confirming block 11 also makes block 12 rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block11.hash()), true)); + const auto expected_confirm11 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":0:" + )); + + const auto response2 = get((boost::format(request) % found_address).str()); + REQUIRE_NO_THROW_TRUE(response2.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response2.at("result").as_string(), expected_confirm11); + + // Confirming block 12 only makes block 12 confirmed. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block12.hash()), true)); + const auto expected_confirm12 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":12:" + )); + + const auto response3 = get((boost::format(request) % found_address).str()); + REQUIRE_NO_THROW_TRUE(response3.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response3.at("result").as_string(), expected_confirm12); +} + // blockchain.scripthash.subscribe BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__insufficient_version__wrong_version) @@ -105,6 +224,15 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__extra_argument__ REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__bogus_scripthash__null) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + const auto request = R"({"id":1101,"method":"blockchain.scripthash.subscribe","params":["%1%"]})" "\n"; + const auto response = get((boost::format(request) % bogus_scripthash).str()); + REQUIRE_NO_THROW_TRUE(response.at("result").is_null()); +} + // blockchain.scripthash.unsubscribe BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__insufficient_version__wrong_version) @@ -177,6 +305,15 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__extra_argument REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); } +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__bogus_script__null) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + const auto request = R"({"id":1101,"method":"blockchain.scriptpubkey.subscribe","params":["%1%"]})" "\n"; + const auto response = get((boost::format(request) % bogus_script).str()); + REQUIRE_NO_THROW_TRUE(response.at("result").is_null()); +} + // blockchain.scriptpubkey.unsubscribe BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_unsubscribe__insufficient_version__wrong_version) From ec3991f432633725be13bad87cee395d7bba325e Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 23 Apr 2026 23:36:16 -0400 Subject: [PATCH 6/9] Generalize subscribe tests to scripthash (1.1) and scriptpubkey (1.7). --- .../protocols/electrum/electrum_subscribe.cpp | 222 +++++++++++++++++- 1 file changed, 221 insertions(+), 1 deletion(-) diff --git a/test/protocols/electrum/electrum_subscribe.cpp b/test/protocols/electrum/electrum_subscribe.cpp index 6fd420b9..dbd6dae2 100644 --- a/test/protocols/electrum/electrum_subscribe.cpp +++ b/test/protocols/electrum/electrum_subscribe.cpp @@ -82,7 +82,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__initialization__exp { BOOST_REQUIRE(handshake(electrum::version::v1_0)); - // This validates the hash accumulator copy in get_scripthash_history() and incomporates + // This validates the hash accumulator copy in get_scripthash_history() and incorporates // confirmed, rooted and unrooted transactions, duplicates, and sort. BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); @@ -233,6 +233,116 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__bogus_scripthash REQUIRE_NO_THROW_TRUE(response.at("result").is_null()); } +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__initialization__expected_status) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_1)); + + // This validates the hash accumulator copy in get_scripthash_history() and incorporates + // confirmed, rooted and unrooted transactions, duplicates, and sort. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + const auto expected_initial = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.scripthash.subscribe","params":["%1%"]})" "\n"; + const auto response = get((boost::format(request) % found_scripthash).str()); + REQUIRE_NO_THROW_TRUE(response.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response.at("result").as_string(), expected_initial); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__repeat_call__idempotent) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_1)); + + // This validates curosr/midstate consistency. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + const auto expected_initial = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.scripthash.subscribe","params":["%1%"]})" "\n"; + const auto response1 = get((boost::format(request) % found_scripthash).str()); + REQUIRE_NO_THROW_TRUE(response1.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response1.at("result").as_string(), expected_initial); + + const auto response2 = get((boost::format(request) % found_scripthash).str()); + REQUIRE_NO_THROW_TRUE(response2.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response2.at("result").as_string(), expected_initial); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__progressive__expected) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_1)); + + // This validates curosr/midstate consistency. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + + // Confirming block 10 also makes block 11 to rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + const auto expected_confirm10 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.scripthash.subscribe","params":["%1%"]})" "\n"; + const auto response1 = get((boost::format(request) % found_scripthash).str()); + REQUIRE_NO_THROW_TRUE(response1.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response1.at("result").as_string(), expected_confirm10); + + // Confirming block 11 also makes block 12 rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block11.hash()), true)); + const auto expected_confirm11 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":0:" + )); + + const auto response2 = get((boost::format(request) % found_scripthash).str()); + REQUIRE_NO_THROW_TRUE(response2.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response2.at("result").as_string(), expected_confirm11); + + // Confirming block 12 only makes block 12 confirmed. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block12.hash()), true)); + const auto expected_confirm12 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":12:" + )); + + const auto response3 = get((boost::format(request) % found_scripthash).str()); + REQUIRE_NO_THROW_TRUE(response3.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response3.at("result").as_string(), expected_confirm12); +} + // blockchain.scripthash.unsubscribe BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__insufficient_version__wrong_version) @@ -314,6 +424,116 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__bogus_script__ REQUIRE_NO_THROW_TRUE(response.at("result").is_null()); } +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__initialization__expected_status) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + // This validates the hash accumulator copy in get_scripthash_history() and incorporates + // confirmed, rooted and unrooted transactions, duplicates, and sort. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + const auto expected_initial = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.scriptpubkey.subscribe","params":["%1%"]})" "\n"; + const auto response = get((boost::format(request) % found_script).str()); + REQUIRE_NO_THROW_TRUE(response.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response.at("result").as_string(), expected_initial); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__repeat_call__idempotent) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + // This validates curosr/midstate consistency. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + const auto expected_initial = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.scriptpubkey.subscribe","params":["%1%"]})" "\n"; + const auto response1 = get((boost::format(request) % found_script).str()); + REQUIRE_NO_THROW_TRUE(response1.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response1.at("result").as_string(), expected_initial); + + const auto response2 = get((boost::format(request) % found_script).str()); + REQUIRE_NO_THROW_TRUE(response2.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response2.at("result").as_string(), expected_initial); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__progressive__expected) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + // This validates curosr/midstate consistency. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + + // Confirming block 10 also makes block 11 to rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + const auto expected_confirm10 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.scriptpubkey.subscribe","params":["%1%"]})" "\n"; + const auto response1 = get((boost::format(request) % found_script).str()); + REQUIRE_NO_THROW_TRUE(response1.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response1.at("result").as_string(), expected_confirm10); + + // Confirming block 11 also makes block 12 rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block11.hash()), true)); + const auto expected_confirm11 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":0:" + )); + + const auto response2 = get((boost::format(request) % found_script).str()); + REQUIRE_NO_THROW_TRUE(response2.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response2.at("result").as_string(), expected_confirm11); + + // Confirming block 12 only makes block 12 confirmed. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block12.hash()), true)); + const auto expected_confirm12 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":12:" + )); + + const auto response3 = get((boost::format(request) % found_script).str()); + REQUIRE_NO_THROW_TRUE(response3.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response3.at("result").as_string(), expected_confirm12); +} + // blockchain.scriptpubkey.unsubscribe BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_unsubscribe__insufficient_version__wrong_version) From 1c6e913a67ed80a37ccf64794cc8b79519708caf Mon Sep 17 00:00:00 2001 From: evoskuil Date: Fri, 24 Apr 2026 00:12:14 -0400 Subject: [PATCH 7/9] Add notify/receive to electrum test fixture. --- test/protocols/electrum/electrum.cpp | 5 +++++ test/protocols/electrum/electrum.hpp | 1 + 2 files changed, 6 insertions(+) diff --git a/test/protocols/electrum/electrum.cpp b/test/protocols/electrum/electrum.cpp index f447d09d..215b30c2 100644 --- a/test/protocols/electrum/electrum.cpp +++ b/test/protocols/electrum/electrum.cpp @@ -108,6 +108,11 @@ int64_t electrum_setup_fixture::get_error(const std::string& request) boost::json::value electrum_setup_fixture::get(const std::string& request) { socket_.send(boost::asio::buffer(request)); + return receive(); +} + +boost::json::value electrum_setup_fixture::receive() +{ boost::asio::streambuf stream{}; try diff --git a/test/protocols/electrum/electrum.hpp b/test/protocols/electrum/electrum.hpp index 929c2464..337fe615 100644 --- a/test/protocols/electrum/electrum.hpp +++ b/test/protocols/electrum/electrum.hpp @@ -33,6 +33,7 @@ struct electrum_setup_fixture bool address_index=true); ~electrum_setup_fixture(); + boost::json::value receive(); int64_t get_error(const std::string& request); boost::json::value get(const std::string& request); void notify(node::chase event_, node::event_value value); From 9b47b815bead94f07bace7d20fd056a7727a6cce Mon Sep 17 00:00:00 2001 From: evoskuil Date: Fri, 24 Apr 2026 00:13:07 -0400 Subject: [PATCH 8/9] Add progressive notify tests for address/scripthash/scriptpubkey. --- .../protocols/electrum/electrum_subscribe.cpp | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) diff --git a/test/protocols/electrum/electrum_subscribe.cpp b/test/protocols/electrum/electrum_subscribe.cpp index dbd6dae2..2b91c622 100644 --- a/test/protocols/electrum/electrum_subscribe.cpp +++ b/test/protocols/electrum/electrum_subscribe.cpp @@ -188,6 +188,81 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__progressive__expect BOOST_REQUIRE_EQUAL(response3.at("result").as_string(), expected_confirm12); } +BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__progressive_notify__expected) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + // This validates curosr/midstate consistency. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + + // Confirming block 10 also makes block 11 to rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + const auto expected_confirm10 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.address.subscribe","params":["%1%"]})" "\n"; + const auto response1 = get((boost::format(request) % found_address).str()); + REQUIRE_NO_THROW_TRUE(response1.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response1.at("result").as_string(), expected_confirm10); + + // Confirming block 11 also makes block 12 rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block11.hash()), true)); + const auto expected_confirm11 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":0:" + )); + + // Trigger node chaser event to electrum event subscriber. + notify(node::chase::organized, {}); + + const auto notification1 = receive(); + REQUIRE_NO_THROW_TRUE(notification1.at("method").is_string()); + REQUIRE_NO_THROW_TRUE(notification1.at("params").is_array()); + BOOST_CHECK_EQUAL(notification1.at("method").as_string(), "blockchain.address.subscribe"); + + const auto& params1 = notification1.at("params").as_array(); + BOOST_REQUIRE_EQUAL(params1.size(), 2u); + BOOST_CHECK(params1.at(0).is_string()); + BOOST_CHECK(params1.at(1).is_string()); + BOOST_CHECK_EQUAL(params1.at(0).as_string(), found_scripthash); + BOOST_CHECK_EQUAL(params1.at(1).as_string(), expected_confirm11); + + // Confirming block 12 only makes block 12 confirmed. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block12.hash()), true)); + const auto expected_confirm12 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":12:" + )); + + // Trigger node chaser event to electrum event subscriber. + notify(node::chase::organized, {}); + + const auto notification2 = receive(); + REQUIRE_NO_THROW_TRUE(notification2.at("method").is_string()); + REQUIRE_NO_THROW_TRUE(notification2.at("params").is_array()); + BOOST_CHECK_EQUAL(notification2.at("method").as_string(), "blockchain.address.subscribe"); + + const auto& params2 = notification2.at("params").as_array(); + BOOST_REQUIRE_EQUAL(params2.size(), 2u); + BOOST_CHECK(params2.at(0).is_string()); + BOOST_CHECK(params2.at(1).is_string()); + BOOST_CHECK_EQUAL(params2.at(0).as_string(), found_scripthash); + BOOST_CHECK_EQUAL(params2.at(1).as_string(), expected_confirm12); +} + // blockchain.scripthash.subscribe BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__insufficient_version__wrong_version) @@ -343,6 +418,82 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__progressive__exp BOOST_REQUIRE_EQUAL(response3.at("result").as_string(), expected_confirm12); } +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__progressive_notify__expected) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_1)); + + // This validates curosr/midstate consistency. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + + // Confirming block 10 also makes block 11 to rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + const auto expected_confirm10 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.scripthash.subscribe","params":["%1%"]})" "\n"; + const auto response1 = get((boost::format(request) % found_scripthash).str()); + REQUIRE_NO_THROW_TRUE(response1.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response1.at("result").as_string(), expected_confirm10); + + // Confirming block 11 also makes block 12 rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block11.hash()), true)); + const auto expected_confirm11 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":0:" + )); + + // Trigger node chaser event to electrum event subscriber. + notify(node::chase::organized, {}); + + const auto notification1 = receive(); + REQUIRE_NO_THROW_TRUE(notification1.at("method").is_string()); + REQUIRE_NO_THROW_TRUE(notification1.at("params").is_array()); + BOOST_CHECK_EQUAL(notification1.at("method").as_string(), "blockchain.scripthash.subscribe"); + + const auto& params1 = notification1.at("params").as_array(); + BOOST_REQUIRE_EQUAL(params1.size(), 2u); + BOOST_CHECK(params1.at(0).is_string()); + BOOST_CHECK(params1.at(1).is_string()); + BOOST_CHECK_EQUAL(params1.at(0).as_string(), found_scripthash); + BOOST_CHECK_EQUAL(params1.at(1).as_string(), expected_confirm11); + + // Confirming block 12 only makes block 12 confirmed. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block12.hash()), true)); + const auto expected_confirm12 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":12:" + )); + + // Trigger node chaser event to electrum event subscriber. + notify(node::chase::organized, {}); + + const auto notification2 = receive(); + REQUIRE_NO_THROW_TRUE(notification2.at("method").is_string()); + REQUIRE_NO_THROW_TRUE(notification2.at("params").is_array()); + BOOST_CHECK_EQUAL(notification2.at("method").as_string(), "blockchain.scripthash.subscribe"); + + const auto& params2 = notification2.at("params").as_array(); + BOOST_REQUIRE_EQUAL(params2.size(), 2u); + BOOST_CHECK(params2.at(0).is_string()); + BOOST_CHECK(params2.at(1).is_string()); + BOOST_CHECK_EQUAL(params2.at(0).as_string(), found_scripthash); + BOOST_CHECK_EQUAL(params2.at(1).as_string(), expected_confirm12); +} + + // blockchain.scripthash.unsubscribe BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__insufficient_version__wrong_version) @@ -534,6 +685,81 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__progressive__e BOOST_REQUIRE_EQUAL(response3.at("result").as_string(), expected_confirm12); } +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__progressive_notify__expected) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + // This validates curosr/midstate consistency. + BOOST_REQUIRE(query_.set(test::bogus_block10, database::context{ 0, 10, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block11, database::context{ 0, 11, 0 }, false, false)); + BOOST_REQUIRE(query_.set(test::bogus_block12, database::context{ 0, 12, 0 }, false, false)); + const auto hash10 = test::bogus_block10.transactions_ptr()->at(1)->hash(false); + const auto hash11 = test::bogus_block11.transactions_ptr()->at(0)->hash(false); + const auto hash12 = test::bogus_block12.transactions_ptr()->at(0)->hash(false); + + // Confirming block 10 also makes block 11 to rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); + const auto expected_confirm10 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":0:" + + encode_hash(hash12) + ":-1:" + )); + + const auto request = R"({"id":1101,"method":"blockchain.scriptpubkey.subscribe","params":["%1%"]})" "\n"; + const auto response1 = get((boost::format(request) % found_script).str()); + REQUIRE_NO_THROW_TRUE(response1.at("result").is_string()); + BOOST_REQUIRE_EQUAL(response1.at("result").as_string(), expected_confirm10); + + // Confirming block 11 also makes block 12 rooted. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block11.hash()), true)); + const auto expected_confirm11 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":0:" + )); + + // Trigger node chaser event to electrum event subscriber. + notify(node::chase::organized, {}); + + const auto notification1 = receive(); + REQUIRE_NO_THROW_TRUE(notification1.at("method").is_string()); + REQUIRE_NO_THROW_TRUE(notification1.at("params").is_array()); + BOOST_CHECK_EQUAL(notification1.at("method").as_string(), "blockchain.scriptpubkey.subscribe"); + + const auto& params1 = notification1.at("params").as_array(); + BOOST_REQUIRE_EQUAL(params1.size(), 2u); + BOOST_CHECK(params1.at(0).is_string()); + BOOST_CHECK(params1.at(1).is_string()); + BOOST_CHECK_EQUAL(params1.at(0).as_string(), found_scripthash); + BOOST_CHECK_EQUAL(params1.at(1).as_string(), expected_confirm11); + + // Confirming block 12 only makes block 12 confirmed. + BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block12.hash()), true)); + const auto expected_confirm12 = encode_hash(sha256_hash + ( + encode_hash(hash10) + ":10:" + + encode_hash(hash11) + ":11:" + + encode_hash(hash12) + ":12:" + )); + + // Trigger node chaser event to electrum event subscriber. + notify(node::chase::organized, {}); + + const auto notification2 = receive(); + REQUIRE_NO_THROW_TRUE(notification2.at("method").is_string()); + REQUIRE_NO_THROW_TRUE(notification2.at("params").is_array()); + BOOST_CHECK_EQUAL(notification2.at("method").as_string(), "blockchain.scriptpubkey.subscribe"); + + const auto& params2 = notification2.at("params").as_array(); + BOOST_REQUIRE_EQUAL(params2.size(), 2u); + BOOST_CHECK(params2.at(0).is_string()); + BOOST_CHECK(params2.at(1).is_string()); + BOOST_CHECK_EQUAL(params2.at(0).as_string(), found_scripthash); + BOOST_CHECK_EQUAL(params2.at(1).as_string(), expected_confirm12); +} + // blockchain.scriptpubkey.unsubscribe BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_unsubscribe__insufficient_version__wrong_version) From 64da342aa87e6d5f4b85a5d29fc3e5166272c184 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Fri, 24 Apr 2026 00:21:08 -0400 Subject: [PATCH 9/9] Style/comments. --- .../electrum/protocol_electrum_subscribe.cpp | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/protocols/electrum/protocol_electrum_subscribe.cpp b/src/protocols/electrum/protocol_electrum_subscribe.cpp index 80fb6ca6..89e8148e 100644 --- a/src/protocols/electrum/protocol_electrum_subscribe.cpp +++ b/src/protocols/electrum/protocol_electrum_subscribe.cpp @@ -231,9 +231,8 @@ void protocol_electrum::scripthash_notify(const hash_digest& status, // utility // ---------------------------------------------------------------------------- -// private -// static +// private/static // Convert enumeration to json-rpc notification method name. std::string protocol_electrum::to_method_name(notify_t type) NOEXCEPT { @@ -249,7 +248,7 @@ std::string protocol_electrum::to_method_name(notify_t type) NOEXCEPT } } -// static +// private/static // Height is zero (rooted) or max_size_t for unconfirmed history txs. void protocol_electrum::write_status(midstate& accumulator, const history& history) NOEXCEPT @@ -260,34 +259,29 @@ void protocol_electrum::write_status(midstate& accumulator, accumulator.write(":"); } +// protected code protocol_electrum::get_scripthash_history(address_subscription& sub, const hash_digest& hash, size_t limit) NOEXCEPT { - histories records{}; + BC_ASSERT(notification_strand_.running_in_this_thread()); + + histories history{}; const auto& query = archive(); - if (const auto ec = query.get_history(stopping_, sub.cursor, records, + if (const auto ec = query.get_history(stopping_, sub.cursor, history, hash, limit, turbo_)) return ec; - // No change to sub.status (null_hash if never written). - if (records.empty()) + if (history.empty()) return error::success; - // Add confirmed status in order. - auto it = records.cbegin(); - const auto end = records.cend(); - while (it != end && it->confirmed()) + auto it = history.cbegin(); + while (it != history.cend() && it->confirmed()) write_status(sub.accumulator, *it++); - BC_ASSERT(std::none_of(it, end, [](const auto& at) - { return at.confirmed(); })); - - // Copy midstate accumulator and add unconfirmed status in order. midstate copy = sub.accumulator; - while (it != end) + while (it != history.cend()) write_status(copy, *it++); - // Flush, cache and return status (may not be a change). sub.status = copy.flush(); return error::success; }