Skip to content

Commit 549cf20

Browse files
authored
Merge pull request #674 from evoskuil/master
Flesh out and test electrum tx/pkg broadcast.
2 parents 5f8d334 + 1b71e90 commit 549cf20

11 files changed

Lines changed: 337 additions & 39 deletions

File tree

include/bitcoin/server/error.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ enum error_t : uint8_t
6161
not_found,
6262
not_implemented,
6363
invalid_argument,
64+
unsupported_argument,
6465
unconfirmable_transaction,
6566
argument_overflow,
6667
target_overflow,

include/bitcoin/server/interfaces/electrum.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ struct electrum_methods
4444
method<"blockchain.scripthash.subscribe", string_t>{ "scripthash" },
4545
method<"blockchain.scripthash.unsubscribe", string_t>{ "scripthash" },
4646
method<"blockchain.transaction.broadcast", string_t>{ "raw_tx" },
47-
method<"blockchain.transaction.broadcast_package", string_t, optional<true>>{ "raw_txs", "verbose" },
47+
method<"blockchain.transaction.broadcast_package", value_t, optional<false>>{ "raw_txs", "verbose" },
4848
method<"blockchain.transaction.get", string_t, boolean_t>{ "tx_hash", "verbose" },
4949
method<"blockchain.transaction.get_merkle", string_t, number_t>{ "tx_hash", "height" },
5050
method<"blockchain.transaction.id_from_pos", number_t, number_t, optional<false>>{ "height", "tx_pos", "merkle" },

include/bitcoin/server/protocols/protocol_electrum.hpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class BCS_API protocol_electrum
5757
bool handle_event(const code&, node::chase event_,
5858
node::event_value) NOEXCEPT;
5959

60-
/// Handlers (blockchain).
60+
/// Handlers (headers).
6161
void handle_blockchain_block_header(const code& ec,
6262
rpc_interface::blockchain_block_header, double height,
6363
double cp_height) NOEXCEPT;
@@ -66,11 +66,15 @@ class BCS_API protocol_electrum
6666
double count, double cp_height) NOEXCEPT;
6767
void handle_blockchain_headers_subscribe(const code& ec,
6868
rpc_interface::blockchain_headers_subscribe) NOEXCEPT;
69+
70+
/// Handlers (fees).
6971
void handle_blockchain_estimate_fee(const code& ec,
7072
rpc_interface::blockchain_estimate_fee, double number,
7173
const std::string& mode) NOEXCEPT;
7274
void handle_blockchain_relay_fee(const code& ec,
7375
rpc_interface::blockchain_relay_fee) NOEXCEPT;
76+
77+
/// Handlers (addresses).
7478
void handle_blockchain_scripthash_get_balance(const code& ec,
7579
rpc_interface::blockchain_scripthash_get_balance,
7680
const std::string& scripthash) NOEXCEPT;
@@ -89,12 +93,14 @@ class BCS_API protocol_electrum
8993
void handle_blockchain_scripthash_unsubscribe(const code& ec,
9094
rpc_interface::blockchain_scripthash_unsubscribe,
9195
const std::string& scripthash) NOEXCEPT;
96+
97+
/// Handlers (transactions).
9298
void handle_blockchain_transaction_broadcast(const code& ec,
9399
rpc_interface::blockchain_transaction_broadcast,
94100
const std::string& raw_tx) NOEXCEPT;
95101
void handle_blockchain_transaction_broadcast_package(const code& ec,
96102
rpc_interface::blockchain_transaction_broadcast_package,
97-
const std::string& raw_txs, bool verbose) NOEXCEPT;
103+
const interface::value_t& raw_txs, bool verbose) NOEXCEPT;
98104
void handle_blockchain_transaction_get(const code& ec,
99105
rpc_interface::blockchain_transaction_get, const std::string& tx_hash,
100106
bool verbose) NOEXCEPT;
@@ -119,6 +125,8 @@ class BCS_API protocol_electrum
119125
rpc_interface::server_peers_subscribe) NOEXCEPT;
120126
void handle_server_ping(const code& ec,
121127
rpc_interface::server_ping) NOEXCEPT;
128+
129+
/// See protocol_electrum_version.
122130
////void handle_server_version(const code& ec,
123131
//// rpc_interface::server_version, const std::string& client_name,
124132
//// const interface::value_t& protocol_version) NOEXCEPT;

src/error.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error)
5151
{ not_found, "not_found" },
5252
{ not_implemented, "not_implemented" },
5353
{ invalid_argument, "invalid_argument" },
54+
{ unsupported_argument, "unsupported_argument" },
5455
{ unconfirmable_transaction, "unconfirmable_transaction" },
5556
{ argument_overflow, "argument_overflow" },
5657
{ target_overflow, "target_overflow" },

src/protocols/electrum/protocol_electrum.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,31 @@ void protocol_electrum::start() NOEXCEPT
4545
// Events subscription is asynchronous, events may be missed.
4646
subscribe_events(BIND(handle_event, _1, _2, _3));
4747

48-
// Blockchain methods.
48+
// Header methods.
4949
SUBSCRIBE_RPC(handle_blockchain_block_header, _1, _2, _3, _4);
5050
SUBSCRIBE_RPC(handle_blockchain_block_headers, _1, _2, _3, _4, _5);
5151
SUBSCRIBE_RPC(handle_blockchain_headers_subscribe, _1, _2);
52+
53+
// Fee methods.
5254
SUBSCRIBE_RPC(handle_blockchain_estimate_fee, _1, _2, _3, _4);
5355
SUBSCRIBE_RPC(handle_blockchain_relay_fee, _1, _2);
56+
57+
// Address methods.
5458
SUBSCRIBE_RPC(handle_blockchain_scripthash_get_balance, _1, _2, _3);
5559
SUBSCRIBE_RPC(handle_blockchain_scripthash_get_history, _1, _2, _3);
5660
SUBSCRIBE_RPC(handle_blockchain_scripthash_get_mempool, _1, _2, _3);
5761
SUBSCRIBE_RPC(handle_blockchain_scripthash_list_unspent, _1, _2, _3);
5862
SUBSCRIBE_RPC(handle_blockchain_scripthash_subscribe, _1, _2, _3);
5963
SUBSCRIBE_RPC(handle_blockchain_scripthash_unsubscribe, _1, _2, _3);
64+
65+
// Transaction methods.
6066
SUBSCRIBE_RPC(handle_blockchain_transaction_broadcast, _1, _2, _3);
6167
SUBSCRIBE_RPC(handle_blockchain_transaction_broadcast_package, _1, _2, _3, _4);
6268
SUBSCRIBE_RPC(handle_blockchain_transaction_get, _1, _2, _3, _4);
6369
SUBSCRIBE_RPC(handle_blockchain_transaction_get_merkle, _1, _2, _3, _4);
6470
SUBSCRIBE_RPC(handle_blockchain_transaction_id_from_pos, _1, _2, _3, _4, _5);
6571

66-
// Server methods
72+
// Server methods.
6773
SUBSCRIBE_RPC(handle_server_add_peer, _1, _2, _3);
6874
SUBSCRIBE_RPC(handle_server_banner, _1, _2);
6975
SUBSCRIBE_RPC(handle_server_donation_address, _1, _2);

src/protocols/electrum/protocol_electrum_server.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ void protocol_electrum::handle_server_features(const code& ec,
108108
{
109109
{ "genesis_hash", encode_hash(hash) },
110110
{ "hosts", advertised_hosts() },
111-
{ "hash_function", "sha256" },
111+
{ "hash_function", string_t{ "sha256" } },
112112
{ "server_version", options().server_name },
113113
{ "protocol_min", string_t{ version_to_string(minimum) } },
114114
{ "protocol_max", string_t{ version_to_string(maximum) } },
@@ -165,13 +165,18 @@ object_t protocol_electrum::advertised_hosts() const NOEXCEPT
165165
map[safe.host()]["ssl_port"] = safe.port();
166166

167167
object_t hosts{};
168-
for (const auto& [host, object] : map)
168+
for (const auto& [host, object]: map)
169169
hosts[host] = object;
170170

171171
if (hosts.empty()) return
172172
{
173-
{ "tcp_port", null_t{} },
174-
{ "ssl_port", null_t{} }
173+
{
174+
"", object_t
175+
{
176+
{ "tcp_port", null_t{} },
177+
{ "ssl_port", null_t{} }
178+
}
179+
}
175180
};
176181

177182
return hosts;

src/protocols/electrum/protocol_electrum_transactions.cpp

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ void protocol_electrum::handle_blockchain_transaction_broadcast(const code& ec,
4646
return;
4747
}
4848

49-
// TODO: implement error_object.
50-
// Changed in version 1.1: return error vs. bitcoind result.
51-
// Previously it returned text string (bitcoind message) in the error case.
52-
////const auto error_object = at_least(electrum::version::v1_1);
53-
5449
data_chunk tx_data{};
5550
if (!decode_base16(tx_data, raw_tx))
5651
{
@@ -65,22 +60,40 @@ void protocol_electrum::handle_blockchain_transaction_broadcast(const code& ec,
6560
return;
6661
}
6762

68-
// TODO: handle just as any peer annoucement, validate and relay.
6963
// TODO: requires tx pool in order to validate against unconfirmed txs.
70-
constexpr auto confirmable = false;
71-
if (!confirmable)
64+
const code fault{ error::unconfirmable_transaction };
65+
66+
////const auto& query = archive();
67+
////constexpr chain::context next_block_context{};
68+
////fault = tx->check();
69+
////fault = tx->guard_check();
70+
////fault = tx->check(next_block_context);
71+
////fault = tx->guard_check(next_block_context);
72+
////query.populate_with_metadata(*tx);
73+
////fault = tx->accept(next_block_context);
74+
////fault = tx->guard_accept(next_block_context);
75+
////fault = tx->confirm(next_block_context);
76+
////fault = tx->connect(next_block_context);
77+
78+
if (!fault)
79+
{
80+
// TODO: broadcast tx to p2p.
81+
send_result(encode_hash(tx->hash(false)), 42, BIND(complete, _1));
82+
return;
83+
}
84+
85+
if (!at_least(electrum::version::v1_1))
7286
{
73-
send_code(error::unconfirmable_transaction);
87+
send_result(fault.message(), 42, BIND(complete, _1));
7488
return;
7589
}
7690

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

8194
void protocol_electrum::handle_blockchain_transaction_broadcast_package(
8295
const code& ec, rpc_interface::blockchain_transaction_broadcast_package,
83-
const std::string& raw_txs, bool ) NOEXCEPT
96+
const interface::value_t& raw_txs, bool verbose) NOEXCEPT
8497
{
8598
if (stopped(ec))
8699
return;
@@ -91,22 +104,84 @@ void protocol_electrum::handle_blockchain_transaction_broadcast_package(
91104
return;
92105
}
93106

94-
data_chunk txs_data{};
95-
if (!decode_base16(txs_data, raw_txs))
107+
// Electrum documentation: "Exact structure depends on bitcoind impl and
108+
// version, and should not be relied upon... should be considered
109+
// experimental and better-suited for debugging." - do not support this.
110+
if (verbose)
111+
{
112+
send_code(error::unsupported_argument);
113+
return;
114+
}
115+
116+
if (!std::holds_alternative<array_t>(raw_txs.value()))
96117
{
97118
send_code(error::invalid_argument);
98119
return;
99120
}
100121

101-
// TODO: consider whether to support the lousy package p2p protocol.
102-
constexpr auto confirmable = false;
103-
if (!confirmable)
122+
const auto& txs_hex = std::get<array_t>(raw_txs.value());
123+
if (txs_hex.empty())
104124
{
105-
send_code(error::unconfirmable_transaction);
125+
send_code(error::invalid_argument);
106126
return;
107127
}
108128

109-
send_code(error::not_implemented);
129+
size_t error_size{};
130+
////const auto& query = archive();
131+
////constexpr chain::context next_block_context{};
132+
object_t result{ { "success", true }, { "errors", array_t{} } };
133+
auto& success = std::get<bool>(result["success"].value());
134+
auto& errors = std::get<array_t>(result["errors"].value());
135+
136+
for (const auto& tx_hex: txs_hex)
137+
{
138+
if (!std::holds_alternative<string_t>(tx_hex.value()))
139+
{
140+
send_code(error::invalid_argument);
141+
return;
142+
}
143+
144+
data_chunk tx_data{};
145+
if (!decode_base16(tx_data, std::get<string_t>(tx_hex.value())))
146+
{
147+
send_code(error::invalid_argument);
148+
return;
149+
}
150+
151+
const auto tx = to_shared<chain::transaction>(tx_data, true);
152+
if (!tx->is_valid())
153+
{
154+
send_code(error::invalid_argument);
155+
return;
156+
}
157+
158+
// TODO: requires tx pool in order to validate against unconfirmed txs.
159+
const code fault{ error::unconfirmable_transaction };
160+
161+
////fault = tx->check();
162+
////fault = tx->guard_check();
163+
////fault = tx->check(next_block_context);
164+
////fault = tx->guard_check(next_block_context);
165+
////query.populate_with_metadata(*tx);
166+
////fault = tx->accept(next_block_context);
167+
////fault = tx->guard_accept(next_block_context);
168+
////fault = tx->confirm(next_block_context);
169+
////fault = tx->connect(next_block_context);
170+
171+
if (fault)
172+
{
173+
const auto message = fault.message();
174+
error_size += message.size();
175+
errors.push_back(object_t
176+
{
177+
{ "txid", encode_hash(tx->hash(false)) },
178+
{ "error", message }
179+
});
180+
}
181+
}
182+
183+
success = errors.empty();
184+
send_result(result, 42 + error_size, BIND(complete, _1));
110185
}
111186

112187
void protocol_electrum::handle_blockchain_transaction_get(const code& ec,

test/error.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,13 @@ BOOST_AUTO_TEST_CASE(error_t__code__invalid_argument__true_expected_message)
209209
BOOST_REQUIRE_EQUAL(ec.message(), "invalid_argument");
210210
}
211211

212-
BOOST_AUTO_TEST_CASE(error_t__code__argument_overflow__true_expected_message)
212+
BOOST_AUTO_TEST_CASE(error_t__code__unsupported_argument__true_expected_message)
213213
{
214-
constexpr auto value = error::argument_overflow;
214+
constexpr auto value = error::unsupported_argument;
215215
const auto ec = code(value);
216216
BOOST_REQUIRE(ec);
217217
BOOST_REQUIRE(ec == value);
218-
BOOST_REQUIRE_EQUAL(ec.message(), "argument_overflow");
218+
BOOST_REQUIRE_EQUAL(ec.message(), "unsupported_argument");
219219
}
220220

221221
BOOST_AUTO_TEST_CASE(error_t__code__unconfirmable_transaction__true_expected_message)
@@ -227,6 +227,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__unconfirmable_transaction__true_expected_mes
227227
BOOST_REQUIRE_EQUAL(ec.message(), "unconfirmable_transaction");
228228
}
229229

230+
BOOST_AUTO_TEST_CASE(error_t__code__argument_overflow__true_expected_message)
231+
{
232+
constexpr auto value = error::argument_overflow;
233+
const auto ec = code(value);
234+
BOOST_REQUIRE(ec);
235+
BOOST_REQUIRE(ec == value);
236+
BOOST_REQUIRE_EQUAL(ec.message(), "argument_overflow");
237+
}
238+
230239
BOOST_AUTO_TEST_CASE(error_t__code__target_overflow__true_expected_message)
231240
{
232241
constexpr auto value = error::target_overflow;

test/protocols/electrum/electrum_headers.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_block_header__insufficient_version__wr
4040

4141
BOOST_AUTO_TEST_CASE(electrum__blockchain_block_header__genesis_no_checkpoint__expected_no_proof)
4242
{
43-
BOOST_REQUIRE(handshake(electrum::version::v1_4));
43+
BOOST_REQUIRE(handshake(electrum::version::v1_3));
4444

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

207-
BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__genesis_count1_no_checkpoint_v_1_2__expected)
207+
BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__genesis_count1_no_checkpoint_v1_2__expected)
208208
{
209209
BOOST_REQUIRE(handshake(electrum::version::v1_2));
210210

@@ -240,7 +240,7 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__genesis_count1_no_check
240240
BOOST_REQUIRE_EQUAL(result.at("headers").as_array().at(0).as_string(), encode_base16(header0_data));
241241
}
242242

243-
BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__block1to3_no_checkpoint__expected)
243+
BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__block1to3_no_checkpoint_v1_6__expected)
244244
{
245245
BOOST_REQUIRE(handshake(electrum::version::v1_6));
246246

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

263+
BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__block1to3_no_checkpoint_v1_4__expected)
264+
{
265+
BOOST_REQUIRE(handshake(electrum::version::v1_4));
266+
267+
const auto response = get(R"({"id":61,"method":"blockchain.block.headers","params":[1,3]})" "\n");
268+
const auto& result = response.at("result").as_object();
269+
REQUIRE_NO_THROW_TRUE(result.at("max").is_int64());
270+
REQUIRE_NO_THROW_TRUE(result.at("count").is_int64());
271+
REQUIRE_NO_THROW_TRUE(result.at("hex").is_string());
272+
BOOST_REQUIRE_EQUAL(result.at("max").as_int64(), 5);
273+
BOOST_REQUIRE_EQUAL(result.at("count").as_int64(), 3);
274+
275+
// "hex" prior to v1.6
276+
const auto expected = encode_base16(header1_data) + encode_base16(header2_data) + encode_base16(header3_data);
277+
BOOST_REQUIRE_EQUAL(result.at("hex").as_string(), expected);
278+
}
279+
263280
BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__count_exceeds_max__capped)
264281
{
265282
BOOST_REQUIRE(handshake(electrum::version::v1_6));

0 commit comments

Comments
 (0)