Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/bitcoin/server/interfaces/electrum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ struct electrum_methods
method<"server.donation_address">{},
method<"server.features">{},
method<"server.peers.subscribe">{},
method<"server.ping">{},
method<"server.ping", optional<0.0>, optional<""_t>>{ "pong_len", "data" },
method<"server.version", string_t, optional<empty::value>>{ "client_name", "protocol_version" },

/// Mempool methods.
Expand Down
3 changes: 2 additions & 1 deletion include/bitcoin/server/protocols/protocol_electrum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ class BCS_API protocol_electrum
void handle_server_peers_subscribe(const code& ec,
rpc_interface::server_peers_subscribe) NOEXCEPT;
void handle_server_ping(const code& ec,
rpc_interface::server_ping) NOEXCEPT;
rpc_interface::server_ping, double pong_len,
const std::string& data) NOEXCEPT;

/// See protocol_electrum_version.
////void handle_server_version(const code& ec,
Expand Down
2 changes: 1 addition & 1 deletion src/protocols/electrum/protocol_electrum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ void protocol_electrum::start() NOEXCEPT
SUBSCRIBE_RPC(handle_server_donation_address, _1, _2);
SUBSCRIBE_RPC(handle_server_features, _1, _2);
SUBSCRIBE_RPC(handle_server_peers_subscribe, _1, _2);
SUBSCRIBE_RPC(handle_server_ping, _1, _2);
SUBSCRIBE_RPC(handle_server_ping, _1, _2, _3, _4);
////SUBSCRIBE_RPC(handle_server_version, _1, _2, _3, _4);

// Mempool methods.
Expand Down
50 changes: 36 additions & 14 deletions src/protocols/electrum/protocol_electrum_headers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,26 +349,48 @@ void protocol_electrum::handle_blockchain_headers_subscribe(const code& ec,
return;
}

const auto header = query.get_wire_header(link);
if (header.empty())
size_t size{};
boost::json::value value{};
if (raw)
{
send_code(error::server_error);
return;
}
const auto header = query.get_wire_header(link);
if (header.empty())
{
send_code(error::server_error);
return;
}

// TODO: determine intended encoding.
if (!raw)
size = two * chain::header::serialized_size();
value =
{
{ "height", top },
{ "hex", encode_base16(header) }
};
}
else
{
send_code(error::not_implemented);
return;
const auto header = query.get_header(link);
if (!header)
{
send_code(error::server_error);
return;
}

// !raw is a custom electrumx serialization.
value = value_from(electrumx(*header));
if (!value.is_object())
{
send_code(error::server_error);
return;
}

size = 256;
auto& object = value.as_object();
object["block_height"] = top;
}

subscribed_header_.store(true, relaxed);
send_result(object_t
{
{ "height", top },
{ "hex", encode_base16(header) }
}, 256, BIND(complete, _1));
send_result(std::move(value), size, BIND(complete, _1));
}

// height/header notifications.
Expand Down
67 changes: 61 additions & 6 deletions src/protocols/electrum/protocol_electrum_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace server {

#define CLASS protocol_electrum

using namespace system;
using namespace network::rpc;
using namespace std::placeholders;

Expand Down Expand Up @@ -107,16 +108,38 @@ void protocol_electrum::handle_server_features(const code& ec,
return;
}

send_result(object_t
object_t value
{
{ "genesis_hash", encode_hash(hash) },
{ "hosts", self_hosts() },
{ "hash_function", string_t{ "sha256" } },
{ "server_version", options().server_name },
{ "protocol_min", string_t{ version_to_string(minimum) } },
{ "protocol_max", string_t{ version_to_string(maximum) } },
{ "pruning", null_t{} }
}, 1024, BIND(complete, _1));
};

if (!at_least(electrum::version::v1_6))
{
value["hash_function"] = string_t{ "sha256" };
}

if (at_least(electrum::version::v1_7))
{
value["method_flavours"] = object_t
{
{
// Unreliable verbose tx serialiation option is not supported.
// "Exact structure depends on bitcoind impl and version, and
// should not be relied upon."
"blockchain.transaction.broadcast_package", object_t
{
{ "supports_verbose_true", false }
}
}
};
}

send_result(std::move(value), 1024, BIND(complete, _1));
}

// This is not actually a subscription method.
Expand All @@ -136,8 +159,10 @@ void protocol_electrum::handle_server_peers_subscribe(const code& ec,
send_result(more_hosts(), 1024, BIND(complete, _1));
}

// Server does not send ping notifications (or perform other traffic shaping).
void protocol_electrum::handle_server_ping(const code& ec,
rpc_interface::server_ping) NOEXCEPT
rpc_interface::server_ping, double pong_len,
const std::string& data) NOEXCEPT
{
if (stopped(ec))
return;
Expand All @@ -148,8 +173,38 @@ void protocol_electrum::handle_server_ping(const code& ec,
return;
}

// Any receive, including ping, resets the base channel inactivity timer.
send_result(null_t{}, 42, BIND(complete, _1));
// Default response of null_t.
value_t value{};
size_t size{ 42 };

if (!at_least(electrum::version::v1_7))
{
if (!data.empty() || is_nonzero(pong_len))
{
send_code(error::wrong_version);
return;
}
}
else
{
size_t length{};
data_chunk unused{};

// Base16 encoding validation expects whole octets (even char count).
if (!to_integer(length, pong_len) || (length != data.length()) ||
!decode_base16(unused, data))
{
send_code(error::invalid_argument);
return;
}

// Treat empty as default (args look the same, may not be correct).
if (is_nonzero(length))
value = string_t(length, '0');
}

// Length is limited by maximum_request (DoS protection).
send_result(std::move(value), size, BIND(complete, _1));
}

// utilities
Expand Down
78 changes: 41 additions & 37 deletions src/protocols/electrum/protocol_electrum_transactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ void protocol_electrum::handle_blockchain_transaction_get(const code& ec,
return;
}

size_t size{};
boost::json::value value{};
if (!verbose)
{
const auto tx = query.get_wire_tx(link, true);
Expand All @@ -198,53 +200,55 @@ void protocol_electrum::handle_blockchain_transaction_get(const code& ec,
return;
}

send_result(encode_base16(tx), two * tx.size(), BIND(complete, _1));
return;
}

const auto tx = query.get_transaction(link, true);
if (!tx)
{
send_code(error::server_error);
return;
}

auto value = value_from(bitcoind(*tx));
if (!value.is_object())
{
send_code(error::server_error);
return;
size = two * tx.size();
value = encode_base16(tx);
}

if (const auto block = query.find_strong(link); !block.is_terminal())
else
{
using namespace system;
const auto top = query.get_top_confirmed();
const auto height = query.get_height(block);
const auto block_hash = query.get_header_key(block);

uint32_t timestamp{};
if (height.is_terminal() || (block_hash == null_hash) ||
!query.get_timestamp(timestamp, block))
const auto tx = query.get_transaction(link, true);
if (!tx)
{
send_code(error::server_error);
return;
}

// Floor manages race between getting confirmed top and height.
const auto confirms = add1(floored_subtract(top, height.value));
// Verbose is whatever bitcoind returns for getrawtransaction, lolz.
value = value_from(bitcoind(*tx));
if (!value.is_object())
{
send_code(error::server_error);
return;
}

auto& transaction = value.as_object();
transaction["in_active_chain"] = true;
transaction["blockhash"] = encode_hash(block_hash);
transaction["confirmations"] = confirms;
transaction["blocktime"] = timestamp;
transaction["time"] = timestamp;
size = two * tx->serialized_size(true);
if (const auto block = query.find_strong(link); !block.is_terminal())
{
using namespace system;
const auto top = query.get_top_confirmed();
const auto height = query.get_height(block);
const auto block_hash = query.get_header_key(block);

uint32_t timestamp{};
if (height.is_terminal() || (block_hash == null_hash) ||
!query.get_timestamp(timestamp, block))
{
send_code(error::server_error);
return;
}

// Floor manages race between getting confirmed top and height.
const auto confirms = add1(floored_subtract(top, height.value));

auto& object = value.as_object();
object["in_active_chain"] = true;
object["blockhash"] = encode_hash(block_hash);
object["confirmations"] = confirms;
object["blocktime"] = timestamp;
object["time"] = timestamp;
}
}

// Verbose means whatever bitcoind returns for getrawtransaction, lolz.
const auto size = tx->serialized_size(true);
send_result(std::move(value), two * size, BIND(complete, _1));
send_result(std::move(value), size, BIND(complete, _1));
}

void protocol_electrum::handle_blockchain_transaction_get_merkle(
Expand Down
23 changes: 23 additions & 0 deletions test/protocols/electrum/electrum_scripthash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ 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_address{ "1JqDybm2nWTENrHvMyafbSXXtTk5Uv5QAn" };
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 auto bogus_script = encode_base16(bogus.to_data(false));

BOOST_FIXTURE_TEST_SUITE(electrum_disabled_address_tests, electrum_disabled_address_index_setup_fixture)

Expand Down Expand Up @@ -69,6 +72,16 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_list_unspent__no_address_in
BOOST_REQUIRE_EQUAL(result, not_implemented.value());
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_address_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.address.subscribe","params":["%1%"]})" "\n";
const auto result = get_error((boost::format(request) % bogus_address).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());
Expand All @@ -89,6 +102,16 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_scripthash_unsubscribe__no_address_ind
BOOST_REQUIRE_EQUAL(result, not_implemented.value());
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_scriptpubkey_subscribe__no_address_index__not_implemented)
{
BOOST_REQUIRE(!query_.address_enabled());
BOOST_REQUIRE(handshake(electrum::version::v1_7));

const auto request = R"({"id":1001,"method":"blockchain.scriptpubkey.subscribe","params":["%1%"]})" "\n";
const auto result = get_error((boost::format(request) % bogus_script).str());
BOOST_REQUIRE_EQUAL(result, not_implemented.value());
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_FIXTURE_TEST_SUITE(electrum_tests, electrum_ten_block_setup_fixture)
Expand Down
Loading