Skip to content
Merged
6 changes: 3 additions & 3 deletions include/bitcoin/server/protocols/protocol_electrum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<system::sha256>;
enum class notify_t { address, scripthash, scriptpubkey };

Expand Down Expand Up @@ -274,15 +274,15 @@ 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;
void scripthash_notify(const hash_digest& status, const hash_digest& hash,
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.
/// -----------------------------------------------------------------------
Expand Down
9 changes: 7 additions & 2 deletions src/protocols/electrum/protocol_electrum_scripthash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -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));
}

Expand Down
102 changes: 26 additions & 76 deletions src/protocols/electrum/protocol_electrum_subscribe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -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
Expand Down Expand Up @@ -202,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;
Expand Down Expand Up @@ -234,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
{
Expand All @@ -252,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
Expand All @@ -263,77 +259,31 @@ 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, history,
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;

// 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();
if (history.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;

// 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;

// 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;
}

auto it = history.cbegin();
while (it != history.cend() && it->confirmed())
write_status(sub.accumulator, *it++);

midstate copy = sub.accumulator;
while (it != history.cend())
write_status(copy, *it++);

sub.status = copy.flush();
return error::success;
}

BC_POP_WARNING()
Expand Down
5 changes: 5 additions & 0 deletions test/protocols/electrum/electrum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/protocols/electrum/electrum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
58 changes: 26 additions & 32 deletions test/protocols/electrum/electrum_addresses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -41,26 +43,24 @@ 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)
{
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();
Expand All @@ -80,7 +80,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();
Expand Down Expand Up @@ -108,26 +108,24 @@ 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)
{
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());
}

Expand All @@ -142,7 +140,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();
Expand Down Expand Up @@ -188,26 +186,24 @@ 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)
{
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());
}

Expand All @@ -222,7 +218,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();
Expand Down Expand Up @@ -260,26 +256,24 @@ 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)
{
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());
}

Expand All @@ -294,7 +288,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();
Expand Down
Loading
Loading