Skip to content

Commit b2ae4e6

Browse files
committed
Implement electrum protocol address notifications.
1 parent b03c0cf commit b2ae4e6

13 files changed

Lines changed: 472 additions & 294 deletions

Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ src_libbitcoin_server_la_SOURCES = \
5757
src/protocols/electrum/protocol_electrum_mempool.cpp \
5858
src/protocols/electrum/protocol_electrum_outputs.cpp \
5959
src/protocols/electrum/protocol_electrum_scripthash.cpp \
60+
src/protocols/electrum/protocol_electrum_scripthash_subscribe.cpp \
6061
src/protocols/electrum/protocol_electrum_scriptpubkey.cpp \
6162
src/protocols/electrum/protocol_electrum_server.cpp \
6263
src/protocols/electrum/protocol_electrum_transactions.cpp \

builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_mempool.cpp" />
140140
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_outputs.cpp" />
141141
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_scripthash.cpp" />
142+
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_scripthash_subscribe.cpp" />
142143
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_scriptpubkey.cpp" />
143144
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_server.cpp" />
144145
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_transactions.cpp" />

builds/msvc/vs2022/libbitcoin-server/libbitcoin-server.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@
117117
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_scripthash.cpp">
118118
<Filter>src\protocols\electrum</Filter>
119119
</ClCompile>
120+
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_scripthash_subscribe.cpp">
121+
<Filter>src\protocols\electrum</Filter>
122+
</ClCompile>
120123
<ClCompile Include="..\..\..\..\src\protocols\electrum\protocol_electrum_scriptpubkey.cpp">
121124
<Filter>src\protocols\electrum</Filter>
122125
</ClCompile>

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+
subscription_limit,
6465
unsupported_argument,
6566
unconfirmable_transaction,
6667
argument_overflow,

include/bitcoin/server/protocols/protocol_electrum.hpp

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
#ifndef LIBBITCOIN_SERVER_PROTOCOLS_PROTOCOL_ELECTRUM_HPP
2020
#define LIBBITCOIN_SERVER_PROTOCOLS_PROTOCOL_ELECTRUM_HPP
2121

22+
#include <map>
2223
#include <memory>
23-
#include <unordered_set>
24+
#include <set>
2425
#include <bitcoin/server/channels/channels.hpp>
2526
#include <bitcoin/server/define.hpp>
2627
#include <bitcoin/server/interfaces/interfaces.hpp>
@@ -52,6 +53,7 @@ class BCS_API protocol_electrum
5253
system::wallet::payment_address::mainnet_p2sh :
5354
system::wallet::payment_address::testnet_p2sh),
5455
channel_(std::dynamic_pointer_cast<channel_t>(channel)),
56+
notification_strand_(channel_->service().get_executor()),
5557
network::tracker<protocol_electrum>(session->log)
5658
{
5759
}
@@ -227,48 +229,77 @@ class BCS_API protocol_electrum
227229
void do_get_history(const hash_digest& hash) NOEXCEPT;
228230
void do_get_mempool(const hash_digest& hash) NOEXCEPT;
229231
void do_list_unspent(const hash_digest& hash) NOEXCEPT;
230-
void do_status(const hash_digest& hash,
231-
const status_handler& sender) NOEXCEPT;
232-
233-
void complete_get_balance(const code& ec, uint64_t confirmed,
234-
int64_t unconfirmed) NOEXCEPT;
235-
void complete_get_history(const code& ec,
236-
const histories& histories) NOEXCEPT;
237-
void complete_get_mempool(const code& ec,
238-
const histories& histories) NOEXCEPT;
239-
void complete_list_unspent(const code& ec,
240-
const unspents& unspents) NOEXCEPT;
241-
void complete_status(const code& ec, const hash_digest& hash,
242-
const hash_digest& status, const status_handler& sender) NOEXCEPT;
243-
244-
void send_status(const code& ec, const hash_digest& hash,
245-
const hash_digest& status) NOEXCEPT;
246-
void notify_status(const code& ec, const hash_digest& hash,
247-
const hash_digest& status, notify_t type,
248-
node::header_t link) NOEXCEPT;
249-
250-
/// Notification senders and send handlers.
232+
233+
void complete_get_balance(const code& ec, uint64_t confirmed, int64_t unconfirmed) NOEXCEPT;
234+
void complete_get_history(const code& ec, const histories& histories) NOEXCEPT;
235+
void complete_get_mempool(const code& ec, const histories& histories) NOEXCEPT;
236+
void complete_list_unspent(const code& ec, const unspents& unspents) NOEXCEPT;
237+
238+
/// Notification event handlers.
251239
/// -----------------------------------------------------------------------
252240

253241
void do_height(node::header_t link) NOEXCEPT;
254242
void do_header(node::header_t link) NOEXCEPT;
255243
void do_outpoint(node::header_t link) NOEXCEPT;
256244
void do_scripthash(node::header_t link) NOEXCEPT;
245+
void do_regressed(node::header_t link) NOEXCEPT;
246+
247+
/// Address.
248+
/// -----------------------------------------------------------------------
249+
250+
// subscription.
251+
void scripthash_subscribe(const hash_digest& hash,
252+
notify_t type) NOEXCEPT;
253+
void do_scripthash_subscribe(const hash_digest& hash,
254+
notify_t type) NOEXCEPT;
255+
void complete_scripthash_subscribe(const code& ec,
256+
hash_digest& status, const hash_digest& hash) NOEXCEPT;
257+
258+
// unsubscription.
259+
void scripthash_unsubscribe(const hash_digest& hash) NOEXCEPT;
260+
void do_scripthash_unsubscribe(const hash_digest& hash) NOEXCEPT;
261+
void complete_scripthash_unsubscribe(bool found) NOEXCEPT;
262+
263+
// notification (do_scripthash()).
264+
void scripthash_notify(const hash_digest& status, const hash_digest& hash,
265+
notify_t type) NOEXCEPT;
266+
267+
/// Outpoint.
268+
/// -----------------------------------------------------------------------
269+
270+
// subscription (do_outpoint()).
271+
bool get_outpoint_status(interface::object_t& status,
272+
const system::chain::point& prevout) const NOEXCEPT;
273+
bool send_outpoint_status(const system::chain::point& prevout,
274+
const std::string& spk_hint) NOEXCEPT;
275+
276+
// unsubscription.
277+
// notification.
257278

258279
/// Utilities.
259280
/// -----------------------------------------------------------------------
260281

282+
/// The negotiated version is at least the specified level.
261283
inline bool at_least(server::electrum::version version) const NOEXCEPT
262284
{
263285
return channel_->version() >= version;
264286
}
265287

288+
/// Configuration options.
266289
inline const options_t& options() const NOEXCEPT
267290
{
268291
return options_;
269292
}
270293

271294
private:
295+
// Post to notification strand.
296+
template <class Derived, typename Method, typename... Args>
297+
inline auto notify(Method&& method, Args&&... args) NOEXCEPT
298+
{
299+
return boost::asio::post(notification_strand_,
300+
BIND_SAFE(BIND_SHARED(method, args)));
301+
}
302+
272303
// Status hash optimization (~200 bytes).
273304
struct midstate
274305
{
@@ -277,13 +308,25 @@ class BCS_API protocol_electrum
277308
system::hash::sha256::fast writer{ stream };
278309
};
279310

311+
// Subscription to address/scripthash/scruptpubkey.
312+
struct subscription
313+
{
314+
notify_t type{};
315+
midstate state{};
316+
database::address_link cursor{};
317+
};
318+
280319
// Aliases.
281320
using array_t = network::rpc::array_t;
282321
using object_t = network::rpc::object_t;
283322
using version_t = protocol_electrum_version;
284323
static constexpr electrum::version minimum = version_t::minimum;
285324
static constexpr electrum::version maximum = version_t::maximum;
286325

326+
// Scripthash status.
327+
code get_scripthash_status(hash_digest& out, subscription& sub,
328+
const hash_digest& hash) NOEXCEPT;
329+
287330
// Transformations.
288331
static std::string to_method_name(notify_t type) NOEXCEPT;
289332
static array_t transform(const unspents& unspents) NOEXCEPT;
@@ -304,14 +347,6 @@ class BCS_API protocol_electrum
304347
code validate_tx(const system::chain::transaction& tx) const NOEXCEPT;
305348
code broadcast_tx(const system::chain::transaction::cptr& tx) NOEXCEPT;
306349

307-
// Shared send/get implementations.
308-
void send_scripthash_unsubscribe(const hash_digest& hash) NOEXCEPT;
309-
void send_scripthash_subscribe(const hash_digest& hash) NOEXCEPT;
310-
bool send_outpoint_status(const system::chain::point& prevout,
311-
const std::string& spk_hint) NOEXCEPT;
312-
bool get_outpoint_status(object_t& status,
313-
const system::chain::point& prevout) const NOEXCEPT;
314-
315350
// These are thread safe.
316351
const options_t& options_;
317352
const bool turbo_;
@@ -326,8 +361,12 @@ class BCS_API protocol_electrum
326361
// This is mostly thread safe, and used in a thread safe manner.
327362
const channel_t::ptr channel_;
328363

329-
// This is protected by strand.
330-
std::unordered_set<hash_digest> subscriptions_{};
364+
// This is thread safe, uses network threadpool.
365+
network::asio::strand notification_strand_;
366+
367+
// These are protected by notification strand.
368+
std::set<system::chain::point> outpoint_subscriptions_{};
369+
std::map<hash_digest, subscription> scripthash_subscriptions_{};
331370
};
332371

333372
} // namespace server

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+
{ subscription_limit, "subscription_limit" },
5455
{ unsupported_argument, "unsupported_argument" },
5556
{ unconfirmable_transaction, "unconfirmable_transaction" },
5657
{ argument_overflow, "argument_overflow" },

src/protocols/electrum/protocol_electrum.cpp

Lines changed: 12 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ namespace libbitcoin {
2828
namespace server {
2929

3030
#define CLASS protocol_electrum
31+
#define NOTIFY(method, ...) notify<CLASS>(&CLASS::method, __VA_ARGS__)
3132

3233
using namespace system;
3334
using namespace network::rpc;
@@ -47,7 +48,6 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
4748
void protocol_electrum::start() NOEXCEPT
4849
{
4950
BC_ASSERT(stranded());
50-
5151
if (started())
5252
return;
5353

@@ -158,18 +158,26 @@ bool protocol_electrum::handle_event(const code&, node::chase event_,
158158
if (subscribed_outpoint_.load(relaxed))
159159
{
160160
BC_ASSERT(std::holds_alternative<node::header_t>(value));
161-
POST(do_outpoint, std::get<node::header_t>(value));
161+
NOTIFY(do_outpoint, std::get<node::header_t>(value));
162162
}
163163

164164
if (subscribed_scripthash_.load(relaxed))
165165
{
166166
BC_ASSERT(archive().address_enabled());
167167
BC_ASSERT(std::holds_alternative<node::header_t>(value));
168-
POST(do_scripthash, std::get<node::header_t>(value));
168+
NOTIFY(do_scripthash, std::get<node::header_t>(value));
169169
}
170170

171171
break;
172172
}
173+
case node::chase::regressed:
174+
case node::chase::disorganized:
175+
{
176+
// value is regression branch_point.
177+
BC_ASSERT(std::holds_alternative<node::height_t>(value));
178+
NOTIFY(do_regressed, std::get<node::height_t>(value));
179+
break;
180+
}
173181
default:
174182
{
175183
break;
@@ -179,15 +187,14 @@ bool protocol_electrum::handle_event(const code&, node::chase event_,
179187
return true;
180188
}
181189

182-
// notifications
190+
// height/header notifications.
183191
// ----------------------------------------------------------------------------
184192
// Each notification is an independent message.
185193

186194
// Notifier for handle_blockchain_number_of_blocks_subscribe events.
187195
void protocol_electrum::do_height(node::header_t link) NOEXCEPT
188196
{
189197
BC_ASSERT(stranded());
190-
191198
const auto& query = archive();
192199
const auto height = query.get_height(link);
193200

@@ -207,7 +214,6 @@ void protocol_electrum::do_height(node::header_t link) NOEXCEPT
207214
void protocol_electrum::do_header(node::header_t link) NOEXCEPT
208215
{
209216
BC_ASSERT(stranded());
210-
211217
const auto& query = archive();
212218
const auto height = query.get_height(link);
213219
const auto header = query.get_wire_header(link);
@@ -225,82 +231,6 @@ void protocol_electrum::do_header(node::header_t link) NOEXCEPT
225231
}, 64, BIND(complete, _1));
226232
}
227233

228-
// Notifier for blockchain_outpoint_subscribe events.
229-
void protocol_electrum::do_outpoint(node::header_t link) NOEXCEPT
230-
{
231-
BC_ASSERT(stranded());
232-
233-
// TODO: get prevout from event.
234-
///////////////////////////////////////////////////////////////////////////
235-
chain::point prevout{};
236-
///////////////////////////////////////////////////////////////////////////
237-
238-
object_t status{};
239-
if (!get_outpoint_status(status, prevout))
240-
{
241-
LOGF("Electrum::do_outpoint, outpoint not found (" << link << ").");
242-
return;
243-
}
244-
245-
send_notification("blockchain.outpoint.subscribe", array_t
246-
{
247-
array_t{ encode_hash(prevout.hash()), prevout.index() },
248-
std::move(status)
249-
}, 128, BIND(handle_send, _1));
250-
}
251-
252-
// Notifier for blockchain_scripthash_subscribe events.
253-
void protocol_electrum::do_scripthash(node::header_t link) NOEXCEPT
254-
{
255-
BC_ASSERT(stranded());
256-
257-
// TODO: get hash/type from event.
258-
///////////////////////////////////////////////////////////////////////////
259-
hash_digest hash{};
260-
constexpr auto type = notify_t::scripthash;
261-
///////////////////////////////////////////////////////////////////////////
262-
263-
// Address status is long-running, so cannot tie up strand.
264-
PARALLEL(do_status, hash, BIND(notify_status, _1, _2, _3, type, link));
265-
}
266-
267-
void protocol_electrum::notify_status(const code& ec, const hash_digest& hash,
268-
const hash_digest& status, notify_t type, node::header_t link) NOEXCEPT
269-
{
270-
BC_ASSERT(stranded());
271-
272-
if (ec)
273-
{
274-
LOGF("Electrum::do_scripthash, address not found (" << link << ").");
275-
return;
276-
}
277-
278-
send_notification(to_method_name(type), array_t
279-
{
280-
encode_hash(hash),
281-
status == null_hash ? value_t{} : value_t{ encode_hash(status) }
282-
}, 128, BIND(complete, _1));
283-
}
284-
285-
// utilities
286-
// ----------------------------------------------------------------------------
287-
// private/static
288-
289-
// Convert enumeration to json-rpc notification method name.
290-
std::string protocol_electrum::to_method_name(notify_t type) NOEXCEPT
291-
{
292-
switch (type)
293-
{
294-
case notify_t::address:
295-
return "blockchain.address.subscribe";
296-
case notify_t::scripthash:
297-
return "blockchain.scripthash.subscribe";
298-
default:
299-
case notify_t::scriptpubkey:
300-
return "blockchain.scriptpubkey.subscribe";
301-
}
302-
}
303-
304234
BC_POP_WARNING()
305235

306236
} // namespace server

src/protocols/electrum/protocol_electrum_addresses.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ void protocol_electrum::handle_blockchain_address_subscribe(const code& ec,
118118
return;
119119
}
120120

121-
send_scripthash_subscribe(hash);
121+
scripthash_subscribe(hash, notify_t::address);
122122
}
123123

124124
// utilities

0 commit comments

Comments
 (0)