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
1 change: 1 addition & 0 deletions include/bitcoin/server/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ enum error_t : uint8_t
not_found,
not_implemented,
invalid_argument,
unsupported_argument,
unconfirmable_transaction,
argument_overflow,
target_overflow,
Expand Down
2 changes: 1 addition & 1 deletion include/bitcoin/server/interfaces/electrum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct electrum_methods
method<"blockchain.scripthash.subscribe", string_t>{ "scripthash" },
method<"blockchain.scripthash.unsubscribe", string_t>{ "scripthash" },
method<"blockchain.transaction.broadcast", string_t>{ "raw_tx" },
method<"blockchain.transaction.broadcast_package", string_t, optional<true>>{ "raw_txs", "verbose" },
method<"blockchain.transaction.broadcast_package", value_t, optional<false>>{ "raw_txs", "verbose" },
method<"blockchain.transaction.get", string_t, boolean_t>{ "tx_hash", "verbose" },
method<"blockchain.transaction.get_merkle", string_t, number_t>{ "tx_hash", "height" },
method<"blockchain.transaction.id_from_pos", number_t, number_t, optional<false>>{ "height", "tx_pos", "merkle" },
Expand Down
12 changes: 10 additions & 2 deletions include/bitcoin/server/protocols/protocol_electrum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class BCS_API protocol_electrum
bool handle_event(const code&, node::chase event_,
node::event_value) NOEXCEPT;

/// Handlers (blockchain).
/// Handlers (headers).
void handle_blockchain_block_header(const code& ec,
rpc_interface::blockchain_block_header, double height,
double cp_height) NOEXCEPT;
Expand All @@ -66,11 +66,15 @@ class BCS_API protocol_electrum
double count, double cp_height) NOEXCEPT;
void handle_blockchain_headers_subscribe(const code& ec,
rpc_interface::blockchain_headers_subscribe) NOEXCEPT;

/// Handlers (fees).
void handle_blockchain_estimate_fee(const code& ec,
rpc_interface::blockchain_estimate_fee, double number,
const std::string& mode) NOEXCEPT;
void handle_blockchain_relay_fee(const code& ec,
rpc_interface::blockchain_relay_fee) NOEXCEPT;

/// Handlers (addresses).
void handle_blockchain_scripthash_get_balance(const code& ec,
rpc_interface::blockchain_scripthash_get_balance,
const std::string& scripthash) NOEXCEPT;
Expand All @@ -89,12 +93,14 @@ class BCS_API protocol_electrum
void handle_blockchain_scripthash_unsubscribe(const code& ec,
rpc_interface::blockchain_scripthash_unsubscribe,
const std::string& scripthash) NOEXCEPT;

/// Handlers (transactions).
void handle_blockchain_transaction_broadcast(const code& ec,
rpc_interface::blockchain_transaction_broadcast,
const std::string& raw_tx) NOEXCEPT;
void handle_blockchain_transaction_broadcast_package(const code& ec,
rpc_interface::blockchain_transaction_broadcast_package,
const std::string& raw_txs, bool verbose) NOEXCEPT;
const interface::value_t& raw_txs, bool verbose) NOEXCEPT;
void handle_blockchain_transaction_get(const code& ec,
rpc_interface::blockchain_transaction_get, const std::string& tx_hash,
bool verbose) NOEXCEPT;
Expand All @@ -119,6 +125,8 @@ class BCS_API protocol_electrum
rpc_interface::server_peers_subscribe) NOEXCEPT;
void handle_server_ping(const code& ec,
rpc_interface::server_ping) NOEXCEPT;

/// See protocol_electrum_version.
////void handle_server_version(const code& ec,
//// rpc_interface::server_version, const std::string& client_name,
//// const interface::value_t& protocol_version) NOEXCEPT;
Expand Down
1 change: 1 addition & 0 deletions src/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error)
{ not_found, "not_found" },
{ not_implemented, "not_implemented" },
{ invalid_argument, "invalid_argument" },
{ unsupported_argument, "unsupported_argument" },
{ unconfirmable_transaction, "unconfirmable_transaction" },
{ argument_overflow, "argument_overflow" },
{ target_overflow, "target_overflow" },
Expand Down
10 changes: 8 additions & 2 deletions src/protocols/electrum/protocol_electrum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,31 @@ void protocol_electrum::start() NOEXCEPT
// Events subscription is asynchronous, events may be missed.
subscribe_events(BIND(handle_event, _1, _2, _3));

// Blockchain methods.
// Header methods.
SUBSCRIBE_RPC(handle_blockchain_block_header, _1, _2, _3, _4);
SUBSCRIBE_RPC(handle_blockchain_block_headers, _1, _2, _3, _4, _5);
SUBSCRIBE_RPC(handle_blockchain_headers_subscribe, _1, _2);

// Fee methods.
SUBSCRIBE_RPC(handle_blockchain_estimate_fee, _1, _2, _3, _4);
SUBSCRIBE_RPC(handle_blockchain_relay_fee, _1, _2);

// Address methods.
SUBSCRIBE_RPC(handle_blockchain_scripthash_get_balance, _1, _2, _3);
SUBSCRIBE_RPC(handle_blockchain_scripthash_get_history, _1, _2, _3);
SUBSCRIBE_RPC(handle_blockchain_scripthash_get_mempool, _1, _2, _3);
SUBSCRIBE_RPC(handle_blockchain_scripthash_list_unspent, _1, _2, _3);
SUBSCRIBE_RPC(handle_blockchain_scripthash_subscribe, _1, _2, _3);
SUBSCRIBE_RPC(handle_blockchain_scripthash_unsubscribe, _1, _2, _3);

// Transaction methods.
SUBSCRIBE_RPC(handle_blockchain_transaction_broadcast, _1, _2, _3);
SUBSCRIBE_RPC(handle_blockchain_transaction_broadcast_package, _1, _2, _3, _4);
SUBSCRIBE_RPC(handle_blockchain_transaction_get, _1, _2, _3, _4);
SUBSCRIBE_RPC(handle_blockchain_transaction_get_merkle, _1, _2, _3, _4);
SUBSCRIBE_RPC(handle_blockchain_transaction_id_from_pos, _1, _2, _3, _4, _5);

// Server methods
// Server methods.
SUBSCRIBE_RPC(handle_server_add_peer, _1, _2, _3);
SUBSCRIBE_RPC(handle_server_banner, _1, _2);
SUBSCRIBE_RPC(handle_server_donation_address, _1, _2);
Expand Down
13 changes: 9 additions & 4 deletions src/protocols/electrum/protocol_electrum_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ void protocol_electrum::handle_server_features(const code& ec,
{
{ "genesis_hash", encode_hash(hash) },
{ "hosts", advertised_hosts() },
{ "hash_function", "sha256" },
{ "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) } },
Expand Down Expand Up @@ -165,13 +165,18 @@ object_t protocol_electrum::advertised_hosts() const NOEXCEPT
map[safe.host()]["ssl_port"] = safe.port();

object_t hosts{};
for (const auto& [host, object] : map)
for (const auto& [host, object]: map)
hosts[host] = object;

if (hosts.empty()) return
{
{ "tcp_port", null_t{} },
{ "ssl_port", null_t{} }
{
"", object_t
{
{ "tcp_port", null_t{} },
{ "ssl_port", null_t{} }
}
}
};

return hosts;
Expand Down
113 changes: 94 additions & 19 deletions src/protocols/electrum/protocol_electrum_transactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ void protocol_electrum::handle_blockchain_transaction_broadcast(const code& ec,
return;
}

// TODO: implement error_object.
// Changed in version 1.1: return error vs. bitcoind result.
// Previously it returned text string (bitcoind message) in the error case.
////const auto error_object = at_least(electrum::version::v1_1);

data_chunk tx_data{};
if (!decode_base16(tx_data, raw_tx))
{
Expand All @@ -65,22 +60,40 @@ void protocol_electrum::handle_blockchain_transaction_broadcast(const code& ec,
return;
}

// TODO: handle just as any peer annoucement, validate and relay.
// TODO: requires tx pool in order to validate against unconfirmed txs.
constexpr auto confirmable = false;
if (!confirmable)
const code fault{ error::unconfirmable_transaction };

////const auto& query = archive();
////constexpr chain::context next_block_context{};
////fault = tx->check();
////fault = tx->guard_check();
////fault = tx->check(next_block_context);
////fault = tx->guard_check(next_block_context);
////query.populate_with_metadata(*tx);
////fault = tx->accept(next_block_context);
////fault = tx->guard_accept(next_block_context);
////fault = tx->confirm(next_block_context);
////fault = tx->connect(next_block_context);

if (!fault)
{
// TODO: broadcast tx to p2p.
send_result(encode_hash(tx->hash(false)), 42, BIND(complete, _1));
return;
}

if (!at_least(electrum::version::v1_1))
{
send_code(error::unconfirmable_transaction);
send_result(fault.message(), 42, BIND(complete, _1));
return;
}

constexpr auto size = two * hash_size;
send_result(encode_base16(tx->hash(false)), size, BIND(complete, _1));
send_code(fault);
}

void protocol_electrum::handle_blockchain_transaction_broadcast_package(
const code& ec, rpc_interface::blockchain_transaction_broadcast_package,
const std::string& raw_txs, bool ) NOEXCEPT
const interface::value_t& raw_txs, bool verbose) NOEXCEPT
{
if (stopped(ec))
return;
Expand All @@ -91,22 +104,84 @@ void protocol_electrum::handle_blockchain_transaction_broadcast_package(
return;
}

data_chunk txs_data{};
if (!decode_base16(txs_data, raw_txs))
// Electrum documentation: "Exact structure depends on bitcoind impl and
// version, and should not be relied upon... should be considered
// experimental and better-suited for debugging." - do not support this.
if (verbose)
{
send_code(error::unsupported_argument);
return;
}

if (!std::holds_alternative<array_t>(raw_txs.value()))
{
send_code(error::invalid_argument);
return;
}

// TODO: consider whether to support the lousy package p2p protocol.
constexpr auto confirmable = false;
if (!confirmable)
const auto& txs_hex = std::get<array_t>(raw_txs.value());
if (txs_hex.empty())
{
send_code(error::unconfirmable_transaction);
send_code(error::invalid_argument);
return;
}

send_code(error::not_implemented);
size_t error_size{};
////const auto& query = archive();
////constexpr chain::context next_block_context{};
object_t result{ { "success", true }, { "errors", array_t{} } };
auto& success = std::get<bool>(result["success"].value());
auto& errors = std::get<array_t>(result["errors"].value());

for (const auto& tx_hex: txs_hex)
{
if (!std::holds_alternative<string_t>(tx_hex.value()))
{
send_code(error::invalid_argument);
return;
}

data_chunk tx_data{};
if (!decode_base16(tx_data, std::get<string_t>(tx_hex.value())))
{
send_code(error::invalid_argument);
return;
}

const auto tx = to_shared<chain::transaction>(tx_data, true);
if (!tx->is_valid())
{
send_code(error::invalid_argument);
return;
}

// TODO: requires tx pool in order to validate against unconfirmed txs.
const code fault{ error::unconfirmable_transaction };

////fault = tx->check();
////fault = tx->guard_check();
////fault = tx->check(next_block_context);
////fault = tx->guard_check(next_block_context);
////query.populate_with_metadata(*tx);
////fault = tx->accept(next_block_context);
////fault = tx->guard_accept(next_block_context);
////fault = tx->confirm(next_block_context);
////fault = tx->connect(next_block_context);

if (fault)
{
const auto message = fault.message();
error_size += message.size();
errors.push_back(object_t
{
{ "txid", encode_hash(tx->hash(false)) },
{ "error", message }
});
}
}

success = errors.empty();
send_result(result, 42 + error_size, BIND(complete, _1));
}

void protocol_electrum::handle_blockchain_transaction_get(const code& ec,
Expand Down
15 changes: 12 additions & 3 deletions test/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,13 @@ BOOST_AUTO_TEST_CASE(error_t__code__invalid_argument__true_expected_message)
BOOST_REQUIRE_EQUAL(ec.message(), "invalid_argument");
}

BOOST_AUTO_TEST_CASE(error_t__code__argument_overflow__true_expected_message)
BOOST_AUTO_TEST_CASE(error_t__code__unsupported_argument__true_expected_message)
{
constexpr auto value = error::argument_overflow;
constexpr auto value = error::unsupported_argument;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "argument_overflow");
BOOST_REQUIRE_EQUAL(ec.message(), "unsupported_argument");
}

BOOST_AUTO_TEST_CASE(error_t__code__unconfirmable_transaction__true_expected_message)
Expand All @@ -227,6 +227,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__unconfirmable_transaction__true_expected_mes
BOOST_REQUIRE_EQUAL(ec.message(), "unconfirmable_transaction");
}

BOOST_AUTO_TEST_CASE(error_t__code__argument_overflow__true_expected_message)
{
constexpr auto value = error::argument_overflow;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "argument_overflow");
}

BOOST_AUTO_TEST_CASE(error_t__code__target_overflow__true_expected_message)
{
constexpr auto value = error::target_overflow;
Expand Down
23 changes: 20 additions & 3 deletions test/protocols/electrum/electrum_headers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_block_header__insufficient_version__wr

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_header__genesis_no_checkpoint__expected_no_proof)
{
BOOST_REQUIRE(handshake(electrum::version::v1_4));
BOOST_REQUIRE(handshake(electrum::version::v1_3));

const auto response = get(R"({"id":43,"method":"blockchain.block.header","params":[0]})" "\n");
REQUIRE_NO_THROW_TRUE(response.at("result").is_object());
Expand Down Expand Up @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__insufficient_version__w
BOOST_REQUIRE_EQUAL(response.at("error").as_object().at("code").as_int64(), wrong_version.value());
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__genesis_count1_no_checkpoint_v_1_2__expected)
BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__genesis_count1_no_checkpoint_v1_2__expected)
{
BOOST_REQUIRE(handshake(electrum::version::v1_2));

Expand Down Expand Up @@ -240,7 +240,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__genesis_count1_no_check
BOOST_REQUIRE_EQUAL(result.at("headers").as_array().at(0).as_string(), encode_base16(header0_data));
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__block1to3_no_checkpoint__expected)
BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__block1to3_no_checkpoint_v1_6__expected)
{
BOOST_REQUIRE(handshake(electrum::version::v1_6));

Expand All @@ -260,6 +260,23 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__block1to3_no_checkpoint
BOOST_REQUIRE_EQUAL(headers.at(2).as_string(), encode_base16(header3_data));
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__block1to3_no_checkpoint_v1_4__expected)
{
BOOST_REQUIRE(handshake(electrum::version::v1_4));

const auto response = get(R"({"id":61,"method":"blockchain.block.headers","params":[1,3]})" "\n");
const auto& result = response.at("result").as_object();
REQUIRE_NO_THROW_TRUE(result.at("max").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("count").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("hex").is_string());
BOOST_REQUIRE_EQUAL(result.at("max").as_int64(), 5);
BOOST_REQUIRE_EQUAL(result.at("count").as_int64(), 3);

// "hex" prior to v1.6
const auto expected = encode_base16(header1_data) + encode_base16(header2_data) + encode_base16(header3_data);
BOOST_REQUIRE_EQUAL(result.at("hex").as_string(), expected);
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__count_exceeds_max__capped)
{
BOOST_REQUIRE(handshake(electrum::version::v1_6));
Expand Down
Loading