diff --git a/include/bitcoin/server/protocols/protocol_electrum.hpp b/include/bitcoin/server/protocols/protocol_electrum.hpp index 38a7a4cc..305b0d53 100644 --- a/include/bitcoin/server/protocols/protocol_electrum.hpp +++ b/include/bitcoin/server/protocols/protocol_electrum.hpp @@ -210,26 +210,21 @@ class BCS_API protocol_electrum using history = database::history; using unspents = database::unspents; using histories = database::histories; + using cursor_t = database::address_link; + using midstate = system::accumulator; enum class notify_t { address, scripthash, scriptpubkey }; - // Status hash optimization (~200 bytes). - struct midstate - { - hash_digest status{}; - system::stream::out::fast stream{ status }; - system::hash::sha256::fast writer{ stream }; - }; - // Subscription to address/scripthash/scruptpubkey. - struct address_subscription + struct address_subscription final { notify_t type{}; - midstate state{}; - database::address_link cursor{}; + cursor_t cursor{}; + hash_digest status{}; + midstate accumulator{}; }; // Subscription to outpoint. - struct outpoint_subscription + struct outpoint_subscription final { database::history outpoint{}; database::histories spenders{}; @@ -336,7 +331,7 @@ class BCS_API protocol_electrum // Transformations. static array_t transform(const unspents& unspents) NOEXCEPT; static array_t transform(const histories& histories) NOEXCEPT; - static void write_status(system::writer& writer, + static void write_status(midstate& accumulator, const history& history) NOEXCEPT; static bool is_valid_hint(const std::string& hint) NOEXCEPT; static std::string to_method_name(notify_t type) NOEXCEPT; diff --git a/src/protocols/electrum/protocol_electrum.cpp b/src/protocols/electrum/protocol_electrum.cpp index 90bd3ea2..f56b6e4b 100644 --- a/src/protocols/electrum/protocol_electrum.cpp +++ b/src/protocols/electrum/protocol_electrum.cpp @@ -195,9 +195,9 @@ void protocol_electrum::do_reorganized(node::header_t) NOEXCEPT for (auto& [key, sub]: address_subscriptions_) { - // writer.flush resets hash accumulator, sub.type remains unchanged. - sub.state.writer.flush(); - sub.state.status = {}; + // flush resets hash accumulator, sub.type remains unchanged. + sub.accumulator.flush(); + sub.status = {}; sub.cursor = {}; } } diff --git a/src/protocols/electrum/protocol_electrum_subscribe.cpp b/src/protocols/electrum/protocol_electrum_subscribe.cpp index 98a12a7c..020a3be6 100644 --- a/src/protocols/electrum/protocol_electrum_subscribe.cpp +++ b/src/protocols/electrum/protocol_electrum_subscribe.cpp @@ -92,15 +92,15 @@ void protocol_electrum::do_scripthash_subscribe(const hash_digest& hash, code ec{ error::subscription_limit }; if (address_subscriptions_.size() < options_.maximum_subscriptions) { - using mid = midstate; - const auto at = address_subscriptions_.try_emplace(hash, type, mid{}); + const auto at = address_subscriptions_ + .try_emplace(hash, address_subscription{ type }); // Initial subscription is limited by configured maximum history. const auto limit = at.second ? options().maximum_history : max_size_t; // Partially-cached result idempotent (new or redundant subscription). ec = get_scripthash_history(at.first->second, at.first->first, limit); - status = at.first->second.state.status; + status = at.first->second.status; subscribed_address_.store(true, relaxed); } @@ -204,16 +204,18 @@ void protocol_electrum::do_scripthash(node::header_t) NOEXCEPT // Depth limit is never imposed once a subscription is accepted. if (const auto ec = get_scripthash_history(sub, key)) { - if (ec == database::error::query_canceled || - ec == error::not_found) + if (ec == database::error::query_canceled) return; + if (ec == error::not_found) + continue; + LOGF("Electrum::do_scripthash, " << ec.message()); } else { // Asio-buffered message (small, not under caller control). - POST(scripthash_notify, sub.state.status, key, sub.type); + POST(scripthash_notify, sub.status, key, sub.type); } } } @@ -252,13 +254,13 @@ std::string protocol_electrum::to_method_name(notify_t type) NOEXCEPT // static // Height is zero (rooted) or max_size_t for unconfirmed history txs. -void protocol_electrum::write_status(system::writer& writer, +void protocol_electrum::write_status(midstate& accumulator, const history& history) NOEXCEPT { - writer.write_string(encode_hash(history.tx.hash())); - writer.write_string(":"); - writer.write_string(std::to_string(to_signed(history.tx.height()))); - writer.write_string(":"); + accumulator.write(encode_hash(history.tx.hash())); + accumulator.write(":"); + accumulator.write(std::to_string(to_signed(history.tx.height()))); + accumulator.write(":"); } code protocol_electrum::get_scripthash_history(address_subscription& sub, @@ -279,19 +281,18 @@ code protocol_electrum::get_scripthash_history(address_subscription& sub, auto it = records.cbegin(); const auto cend = records.cend(); while (it != cend && it->confirmed()) - write_status(sub.state.writer, *it++); + 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.state; + midstate copy = sub.accumulator; while (it != cend) - write_status(copy.writer, *it++); + write_status(copy, *it++); // Flush, cache and return status (always updated on initial). - copy.writer.flush(); - sub.state.status = std::move(copy.status); + sub.status = copy.flush(); return error::success; } else @@ -305,10 +306,10 @@ code protocol_electrum::get_scripthash_history(address_subscription& sub, auto it = records.cbegin(); auto cend = records.cend(); while (it != cend && it->confirmed()) - write_status(sub.state.writer, *it++); + write_status(sub.accumulator, *it++); // Copy midstate accumulator for write of unconfirmeds. - midstate copy = sub.state; + midstate copy = sub.accumulator; records.clear(); // Update scan queries all unconfirmed independently. @@ -322,15 +323,15 @@ code protocol_electrum::get_scripthash_history(address_subscription& sub, // Accumulate unconfirmed status in order. while (it != cend) - write_status(copy.writer, *it++); + write_status(copy, *it++); // Flush, cache and return not found if no writes. - copy.writer.flush(); - if (sub.state.status == copy.status) + auto status = copy.flush(); + if (sub.status == status) return error::not_found; // Set cache into midstate object for next run. - sub.state.status = std::move(copy.status); + sub.status = std::move(status); return error::success; } } diff --git a/test/protocols/electrum/electrum.cpp b/test/protocols/electrum/electrum.cpp index 34a18245..f447d09d 100644 --- a/test/protocols/electrum/electrum.cpp +++ b/test/protocols/electrum/electrum.cpp @@ -93,6 +93,18 @@ electrum_setup_fixture::~electrum_setup_fixture() BOOST_WARN_MESSAGE(test::clear(test::directory), "electrum cleanup"); } +int64_t electrum_setup_fixture::get_error(const std::string& request) +{ + try + { + return get(request).at("error").as_object().at("code").as_int64(); + } + catch (const boost::system::system_error&) + { + return -1; + } +} + boost::json::value electrum_setup_fixture::get(const std::string& request) { socket_.send(boost::asio::buffer(request)); @@ -104,7 +116,6 @@ boost::json::value electrum_setup_fixture::get(const std::string& request) } catch (const boost::system::system_error&) { - ////BOOST_WARN_MESSAGE(false, e.what()); return boost::json::parse(R"({"dropped":true})"); } @@ -114,6 +125,11 @@ boost::json::value electrum_setup_fixture::get(const std::string& request) return boost::json::parse(response); } +void electrum_setup_fixture::notify(node::chase event_, node::event_value value) +{ + server_.notify(error::success, event_, value); +} + bool electrum_setup_fixture::handshake(electrum::version version, const std::string& name, network::rpc::code_t id) { diff --git a/test/protocols/electrum/electrum.hpp b/test/protocols/electrum/electrum.hpp index 634a08a4..929c2464 100644 --- a/test/protocols/electrum/electrum.hpp +++ b/test/protocols/electrum/electrum.hpp @@ -33,7 +33,9 @@ struct electrum_setup_fixture bool address_index=true); ~electrum_setup_fixture(); + 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); bool handshake(electrum::version version, const std::string& name="test", network::rpc::code_t id=0); diff --git a/test/protocols/electrum/electrum_outpoints.cpp b/test/protocols/electrum/electrum_outpoints.cpp index 16991363..c03ec57e 100644 --- a/test/protocols/electrum/electrum_outpoints.cpp +++ b/test/protocols/electrum/electrum_outpoints.cpp @@ -34,9 +34,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__obsoleted_version__w 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()); - 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((boost::format(request) % hash).str()); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__missing_arguments__dropped) @@ -53,18 +52,16 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__invalid_index__inval constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":901,"method":"blockchain.utxo.get_address","params":["%1%",-1]})" "\n"; - const auto response = get((boost::format(request) % encode_hash(hash)).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(), invalid_argument.value()); + const auto result = get_error((boost::format(request) % encode_hash(hash)).str()); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__invalid_hash__invalid_argument) { BOOST_REQUIRE(handshake(electrum::version::v1_0)); - const auto response = get(R"({"id":904,"method":"blockchain.utxo.get_address","params":["not_a_hash", 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":904,"method":"blockchain.utxo.get_address","params":["not_a_hash", 0]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_utxo_get_address__tx_not_found__null) @@ -129,9 +126,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__insufficient_vers constexpr auto hash = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; const auto request = R"({"id":1101,"method":"blockchain.outpoint.get_status","params":["%1%",0]})" "\n"; - const auto response = get((boost::format(request) % hash).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(), wrong_version.value()); + const auto result = get_error((boost::format(request) % hash).str()); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__missing_arguments__dropped) @@ -146,9 +142,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__invalid_hash__inv { BOOST_REQUIRE(handshake(electrum::version::v1_7)); - const auto response = get(R"({"id":1103,"method":"blockchain.outpoint.get_status","params":["not_a_hash", 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(), invalid_argument.value()); + const auto result = get_error(R"({"id":1103,"method":"blockchain.outpoint.get_status","params":["not_a_hash", 0]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__invalid_index__invalid_argument) @@ -157,9 +152,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__invalid_index__in constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":1104,"method":"blockchain.outpoint.get_status","params":["%1%",-1]})" "\n"; - const auto response = get((boost::format(request) % encode_hash(hash)).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(), invalid_argument.value()); + const auto result = get_error((boost::format(request) % encode_hash(hash)).str()); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__tx_not_found__not_found) @@ -168,9 +162,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__tx_not_found__not constexpr hash_digest hash{ 0x42 }; const auto request = R"({"id":1105,"method":"blockchain.outpoint.get_status","params":["%1%",0]})" "\n"; - const auto response = get((boost::format(request) % encode_hash(hash)).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_found.value()); + const auto result = get_error((boost::format(request) % encode_hash(hash)).str()); + BOOST_REQUIRE_EQUAL(result, not_found.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__confirmed_unspent__expected) @@ -212,6 +205,117 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_get_status__confirmed_spent__ } // blockchain.outpoint.subscribe + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__insufficient_version__wrong_version) +{ + 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()); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__missing_arguments__dropped) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + const auto response = get(R"({"id":1102,"method":"blockchain.outpoint.subscribe","params":[]})" "\n"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__invalid_hash__invalid_argument) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + const auto result = get_error(R"({"id":1103,"method":"blockchain.outpoint.subscribe","params":["not_a_hash", 0]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__invalid_index__invalid_argument) +{ + 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()); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__invalid_hint_encoding__invalid_argument) +{ + 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()); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__invalid_hint__invalid_argument) +{ + 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()); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_subscribe__extra_argument__dropped) +{ + 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"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + // blockchain.outpoint.unsubscribe +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_unsubscribe__insufficient_version__wrong_version) +{ + 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()); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_unsubscribe__missing_arguments__dropped) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + const auto response = get(R"({"id":1102,"method":"blockchain.outpoint.unsubscribe","params":[]})" "\n"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_unsubscribe__invalid_hash__invalid_argument) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + const auto result = get_error(R"({"id":1103,"method":"blockchain.outpoint.unsubscribe","params":["4242", 0]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_unsubscribe__invalid_index__invalid_argument) +{ + 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()); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_outpoint_unsubscribe__extra_argument__dropped) +{ + 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"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/protocols/electrum/electrum_scripthash.cpp b/test/protocols/electrum/electrum_scripthash.cpp index 8504aa3c..fc4acde6 100644 --- a/test/protocols/electrum/electrum_scripthash.cpp +++ b/test/protocols/electrum/electrum_scripthash.cpp @@ -24,8 +24,8 @@ static const code not_found{ server::error::not_found }; static const code wrong_version{ server::error::wrong_version }; static const code not_implemented{ server::error::not_implemented }; static const code invalid_argument{ server::error::invalid_argument }; -static const std::string bogus_script_hash{ "9c2c84a6cf9809e08af19557e28d38257e6fee6981269760637a5f9dfb000b05" }; -static const std::string found_script_hash{ "bad83872c90886be19b98734fd16741611efcd9f5de699c14b712675eec682f5" }; +static const std::string bogus_scripthash{ "9c2c84a6cf9809e08af19557e28d38257e6fee6981269760637a5f9dfb000b05" }; +static const std::string found_scripthash{ "bad83872c90886be19b98734fd16741611efcd9f5de699c14b712675eec682f5" }; BOOST_FIXTURE_TEST_SUITE(electrum_disabled_address_tests, electrum_disabled_address_index_setup_fixture) @@ -35,9 +35,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_balance__no_address_ind BOOST_REQUIRE(handshake(electrum::version::v1_1)); const auto request = R"({"id":901,"method":"blockchain.scripthash.get_balance","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script_hash).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_scripthash).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_history__no_address_index__not_implemented) @@ -46,9 +45,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_history__no_address_ind BOOST_REQUIRE(handshake(electrum::version::v1_1)); const auto request = R"({"id":1001,"method":"blockchain.scripthash.get_history","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script_hash).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_scripthash).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_mempool__no_address_index__not_implemented) @@ -57,9 +55,8 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_mempool__no_address_ind BOOST_REQUIRE(handshake(electrum::version::v1_1)); const auto request = R"({"id":1001,"method":"blockchain.scripthash.get_mempool","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script_hash).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_scripthash).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_list_unspent__no_address_index__not_implemented) @@ -68,9 +65,28 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_list_unspent__no_address_in BOOST_REQUIRE(handshake(electrum::version::v1_1)); const auto request = R"({"id":1001,"method":"blockchain.scripthash.listunspent","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script_hash).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_scripthash).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__no_address_index__not_implemented) +{ + BOOST_REQUIRE(!query_.address_enabled()); + BOOST_REQUIRE(handshake(electrum::version::v1_1)); + + const auto request = R"({"id":1001,"method":"blockchain.scripthash.subscribe","params":["%1%"]})" "\n"; + const auto result = get_error((boost::format(request) % bogus_scripthash).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__no_address_index__not_implemented) +{ + BOOST_REQUIRE(!query_.address_enabled()); + BOOST_REQUIRE(handshake(electrum::version::v1_4_2)); + + const auto request = R"({"id":1001,"method":"blockchain.scripthash.unsubscribe","params":["%1%"]})" "\n"; + const auto result = get_error((boost::format(request) % bogus_scripthash).str()); + BOOST_REQUIRE_EQUAL(result, not_implemented.value()); } BOOST_AUTO_TEST_SUITE_END() @@ -121,7 +137,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_balance__not_found_addr BOOST_REQUIRE(handshake(electrum::version::v1_1)); const auto request = R"({"id":904,"method":"blockchain.scripthash.get_balance","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script_hash).str()); + const auto response = get((boost::format(request) % bogus_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_object()); const auto& result = response.at("result").as_object(); @@ -141,7 +157,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_balance__confirmed_and_ BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); const auto request = R"({"id":905,"method":"blockchain.scripthash.get_balance","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % found_script_hash).str()); + const auto response = get((boost::format(request) % found_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_object()); const auto& result = response.at("result").as_object(); @@ -198,7 +214,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_history__not_found_addr BOOST_REQUIRE(handshake(electrum::version::v1_1)); const auto request = R"({"id":1005,"method":"blockchain.scripthash.get_history","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script_hash).str()); + const auto response = get((boost::format(request) % bogus_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("result").as_array().empty()); } @@ -213,7 +229,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_history__confirmed_and_ BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); const auto request = R"({"id":1006,"method":"blockchain.scripthash.get_history","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % found_script_hash).str()); + const auto response = get((boost::format(request) % found_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_array()); const auto& history = response.at("result").as_array(); @@ -288,7 +304,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_mempool__not_found_addr BOOST_REQUIRE(handshake(electrum::version::v1_1)); const auto request = R"({"id":1005,"method":"blockchain.scripthash.get_mempool","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script_hash).str()); + const auto response = get((boost::format(request) % bogus_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("result").as_array().empty()); } @@ -303,7 +319,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_get_mempool__confirmed_and_ BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); const auto request = R"({"id":1006,"method":"blockchain.scripthash.get_mempool","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % found_script_hash).str()); + const auto response = get((boost::format(request) % found_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("result").is_array()); const auto& history = response.at("result").as_array(); @@ -370,7 +386,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_list_unspent__not_found_add BOOST_REQUIRE(handshake(electrum::version::v1_1)); const auto request = R"({"id":1005,"method":"blockchain.scripthash.listunspent","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % bogus_script_hash).str()); + const auto response = get((boost::format(request) % bogus_scripthash).str()); REQUIRE_NO_THROW_TRUE(response.at("result").as_array().empty()); } @@ -385,7 +401,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_list_unspent__confirmed_and BOOST_REQUIRE(query_.push_confirmed(query_.to_header(test::bogus_block10.hash()), true)); const auto request = R"({"id":1006,"method":"blockchain.scripthash.listunspent","params":["%1%"]})" "\n"; - const auto response = get((boost::format(request) % found_script_hash).str()); + const auto response = get((boost::format(request) % found_scripthash).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 9426998e..20ff1459 100644 --- a/test/protocols/electrum/electrum_subscribe.cpp +++ b/test/protocols/electrum/electrum_subscribe.cpp @@ -19,18 +19,194 @@ #include "../../test.hpp" #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 not_implemented{ server::error::not_implemented }; -////static const code invalid_argument{ server::error::invalid_argument }; +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 bogus_scripthash{ "9c2c84a6cf9809e08af19557e28d38257e6fee6981269760637a5f9dfb000b05" }; +static const chain::script bogus{ chain::script::to_pay_key_hash_pattern({ 0x42 }) }; +static const auto bogus_script = encode_base16(bogus.to_data(false)); BOOST_FIXTURE_TEST_SUITE(electrum_tests, electrum_ten_block_setup_fixture) // blockchain.address.subscribe + +BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__obsolete_version__wrong_version) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_3)); + + const auto request = R"({"id":1101,"method":"blockchain.address.subscribe","params":["%1%"]})" "\n"; + const auto result = get_error((boost::format(request) % bogus_address).str()); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__missing_arguments__dropped) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + const auto response = get(R"({"id":1102,"method":"blockchain.address.subscribe","params":[]})" "\n"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__invalid_hash__invalid_argument) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + const auto result = get_error(R"({"id":1103,"method":"blockchain.address.subscribe","params":["not_a_hash"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_address_subscribe__extra_argument__dropped) +{ + 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()); +} + // blockchain.scripthash.subscribe + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__insufficient_version__wrong_version) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_0)); + + const auto request = R"({"id":1101,"method":"blockchain.scripthash.subscribe","params":["%1%"]})" "\n"; + const auto result = get_error((boost::format(request) % bogus_scripthash).str()); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__missing_arguments__dropped) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_1)); + + const auto response = get(R"({"id":1102,"method":"blockchain.scripthash.subscribe","params":[]})" "\n"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__invalid_hash__invalid_argument) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_1)); + + const auto result = get_error(R"({"id":1103,"method":"blockchain.scripthash.subscribe","params":["not_a_hash"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_subscribe__extra_argument__dropped) +{ + 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"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + // blockchain.scripthash.unsubscribe + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__insufficient_version__wrong_version) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_4_1)); + + const auto request = R"({"id":1101,"method":"blockchain.scripthash.unsubscribe","params":["%1%"]})" "\n"; + const auto result = get_error((boost::format(request) % bogus_scripthash).str()); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__missing_arguments__dropped) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_4_2)); + + const auto response = get(R"({"id":1102,"method":"blockchain.scripthash.unsubscribe","params":[]})" "\n"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__invalid_hash__invalid_argument) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_4_2)); + + const auto result = get_error(R"({"id":1103,"method":"blockchain.scripthash.unsubscribe","params":["4242"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__extra_argument__dropped) +{ + 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"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + // blockchain.scriptpubkey.subscribe + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__insufficient_version__wrong_version) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_6)); + + const auto request = R"({"id":1101,"method":"blockchain.scriptpubkey.subscribe","params":["%1%"]})" "\n"; + const auto result = get_error((boost::format(request) % bogus_script).str()); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__missing_arguments__dropped) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + const auto response = get(R"({"id":1102,"method":"blockchain.scriptpubkey.subscribe","params":[]})" "\n"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__invalid_script_encoding__invalid_argument) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + const auto result = get_error(R"({"id":1103,"method":"blockchain.scriptpubkey.subscribe","params":["not_hex"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__extra_argument__dropped) +{ + 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"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + // blockchain.scriptpubkey.unsubscribe +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_unsubscribe__insufficient_version__wrong_version) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_6)); + + const auto request = R"({"id":1101,"method":"blockchain.scriptpubkey.unsubscribe","params":["%1%"]})" "\n"; + const auto result = get_error((boost::format(request) % bogus_script).str()); + BOOST_REQUIRE_EQUAL(result, wrong_version.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_unsubscribe__missing_arguments__dropped) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + const auto response = get(R"({"id":1102,"method":"blockchain.scriptpubkey.unsubscribe","params":[]})" "\n"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_unsubscribe__invalid_script_encoding__invalid_argument) +{ + BOOST_REQUIRE(handshake(electrum::version::v1_7)); + + const auto result = get_error(R"({"id":1103,"method":"blockchain.scriptpubkey.unsubscribe","params":["not_hex"]})" "\n"); + BOOST_REQUIRE_EQUAL(result, invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_unsubscribe__extra_argument__dropped) +{ + 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"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + BOOST_AUTO_TEST_SUITE_END()