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
47 changes: 32 additions & 15 deletions endpoints/test_btcd_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,15 +532,6 @@ def test_btcd_and_bitcoind_method_share_one_websocket_connection(conn):
("stopnotifynewtransactions", []),
]

# Deprecated upstream (superseded by loadtxfilter/rescanblocks) but still
# wired -- included so a regression can't silently start "working" in a way
# that contradicts the deliberate scope decision to leave these stubbed.
DEPRECATED_STUBS = [
("notifyreceived", [[]]),
("stopnotifyreceived", [[]]),
("notifyspent", [[]]),
("stopnotifyspent", [[]]),
]


@pytest.mark.xfail(reason="wired stub, handler not yet implemented",
Expand All @@ -553,12 +544,38 @@ def test_stub_not_yet_implemented(conn, method, params):
)


@pytest.mark.parametrize("method,params", DEPRECATED_STUBS)
def test_deprecated_method_stays_not_implemented(conn, method, params):
"""Regression guard, not a development target: these are deliberately
never implemented (superseded upstream by loadtxfilter/rescanblocks)."""
data = conn.raw_rpc(method, params)
assert data.get("error") is not None
def test_notifyreceived_valid_address_acknowledges(conn):
"""Confirmed-only matching (no mempool in v4), reusing loadtxfilter's
cursor-based history matching -- see protocol_btcd_filter.cpp."""
response = conn.send_rpc("notifyreceived", [[ReferenceData.EXAMPLE_ADDRESS]])
assert response.get("error") is None


def test_notifyreceived_invalid_address_rejected(conn):
response = conn.raw_rpc("notifyreceived", [["not-an-address"]])
assert response.get("error") is not None


def test_stopnotifyreceived_acknowledges(conn):
response = conn.send_rpc("stopnotifyreceived", [[ReferenceData.EXAMPLE_ADDRESS]])
assert response.get("error") is None


def test_notifyspent_valid_outpoint_acknowledges(conn):
response = conn.send_rpc("notifyspent",
[[{"hash": ReferenceData.GENESIS_TX_HASH, "index": 0}]])
assert response.get("error") is None


def test_notifyspent_malformed_outpoint_rejected(conn):
response = conn.raw_rpc("notifyspent", [[{"hash": "00"}]])
assert response.get("error") is not None


def test_stopnotifyspent_acknowledges(conn):
response = conn.send_rpc("stopnotifyspent",
[[{"hash": ReferenceData.GENESIS_TX_HASH, "index": 0}]])
assert response.get("error") is None


def test_stop_always_not_implemented(conn):
Expand Down
10 changes: 5 additions & 5 deletions include/bitcoin/server/interfaces/btcd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ struct btcd_methods
method<"loadtxfilter", boolean_t, value_t, value_t>{ "reload", "addresses", "outpoints" },
method<"rescanblocks", value_t>{ "blockhashes" },

/// Deprecated.
method<"notifyreceived", value_t>{ unimplemented, "addresses" },
method<"stopnotifyreceived", value_t>{ unimplemented, "addresses" },
method<"notifyspent", value_t>{ unimplemented, "outpoints" },
method<"stopnotifyspent", value_t>{ unimplemented, "outpoints" },
/// Deprecated (confirmed matching only, no mempool in v4).
method<"notifyreceived", value_t>{ "addresses" },
method<"stopnotifyreceived", value_t>{ "addresses" },
method<"notifyspent", value_t>{ "outpoints" },
method<"stopnotifyspent", value_t>{ "outpoints" },
method<"rescan", string_t, value_t, value_t, nullopt<""_t>>{ "beginblock", "addresses", "outpoints", "endblock" }
};

Expand Down
51 changes: 48 additions & 3 deletions include/bitcoin/server/protocols/protocol_btcd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class BCS_API protocol_btcd
btcd_interface::rescan_blocks,
const network::rpc::value_t& blockhashes) NOEXCEPT;

/// Handlers (deprecated, not_implemented).
/// Handlers (deprecated).
bool handle_notify_received(const code& ec,
btcd_interface::notify_received,
const network::rpc::value_t& addresses) NOEXCEPT;
Expand Down Expand Up @@ -155,6 +155,7 @@ class BCS_API protocol_btcd
using header_cptr = system::chain::header::cptr;
using hashes_ptr = std::shared_ptr<system::hashes>;
using array_ptr = std::shared_ptr<network::rpc::array_t>;
using legacy_ptr = std::shared_ptr<std::vector<network::rpc::array_t>>;
using history = database::history;
using histories = database::histories;
using cursor_t = database::height_link;
Expand All @@ -166,7 +167,6 @@ class BCS_API protocol_btcd

struct outpoint_watch final
{
database::history outpoint{};
database::histories spenders{};
};

Expand All @@ -177,6 +177,14 @@ class BCS_API protocol_btcd
const system::chain::points& points) NOEXCEPT;
void complete_load_tx_filter(const code& ec) NOEXCEPT;

void do_notify_received(const system::hashes& keys) NOEXCEPT;
void complete_notify_received(const code& ec) NOEXCEPT;
void do_stop_notify_received(const system::hashes& keys) NOEXCEPT;

void do_notify_spent(const system::chain::points& points) NOEXCEPT;
void complete_notify_spent(const code& ec) NOEXCEPT;
void do_stop_notify_spent(const system::chain::points& points) NOEXCEPT;

void do_rescan_blocks(const hashes_ptr& hashes) NOEXCEPT;
void do_rescan_watches(const hashes_ptr& hashes,
const system::hashes& keys, system::chain::points& points) NOEXCEPT;
Expand All @@ -195,7 +203,8 @@ class BCS_API protocol_btcd
void do_connected(node::header_t link) NOEXCEPT;
void do_disconnected(node::header_t link) NOEXCEPT;
void notify_connected(const header_cptr& header, size_t height,
const array_ptr& txs) NOEXCEPT;
const array_ptr& txs, const legacy_ptr& received,
const legacy_ptr& redeemed) NOEXCEPT;
void notify_disconnected(const header_cptr& header,
size_t height) NOEXCEPT;

Expand All @@ -205,12 +214,39 @@ class BCS_API protocol_btcd
using matches = std::map<size_t, matched_txs>;
using sizes = std::set<size_t>;

code match_filters(network::rpc::array_t& out, size_t height,
const sizes& heights) NOEXCEPT;
code match_receives(std::vector<network::rpc::array_t>& out,
const header_cptr& header, size_t height,
const sizes& heights) NOEXCEPT;
code match_spends(std::vector<network::rpc::array_t>& out,
const header_cptr& header, size_t height,
const sizes& heights) NOEXCEPT;

code match_addresses(matches& out, address_watch& sub,
const hash_digest& key, const sizes& heights) NOEXCEPT;
void match_outpoints(matches& out, outpoint_watch& sub,
const point& prevout, const sizes& heights) NOEXCEPT;
network::rpc::array_t serialize_matches(const matched_txs& txs) NOEXCEPT;

/// Legacy (notifyreceived/notifyspent) individual notifications.
/// -----------------------------------------------------------------------

// Arms a one-shot spent-watch on each output a receive watch matched.
code arm_spent_watches(bool& paid, const system::chain::transaction& tx,
const hash_digest& hash) NOEXCEPT;

// Drops the channel on auto-armed spent-watch overflow.
void complete_overflow(const code& ec) NOEXCEPT;

// Builds [txHex, blockDetails] params for a recvtx/redeemingtx.
network::rpc::array_t serialize_legacy(
const system::chain::transaction& tx, const header_cptr& header,
size_t height, size_t position) NOEXCEPT;

// Combined DoS budget across all watch-list maps.
size_t watch_count() const NOEXCEPT;

private:
template <class Derived, typename Method, typename... Args>
inline void btcd_subscribe(Method&& method, Args&&... args) NOEXCEPT
Expand Down Expand Up @@ -238,6 +274,10 @@ class BCS_API protocol_btcd
std::atomic_bool stopping_{};
std::atomic_bool subscribed_blocks_{};

// Set once a legacy watch arms, so handle_chase posts do_connected
// even without notifyblocks (unlike loadtxfilter, not required here).
std::atomic_bool watching_legacy_{};

// This is protected by strand.
btcd_dispatcher btcd_dispatcher_{};

Expand All @@ -247,6 +287,11 @@ class BCS_API protocol_btcd
// These are protected by notification strand.
std::map<point, outpoint_watch> outpoint_watches_{};
std::map<hash_digest, address_watch> address_watches_{};

// Legacy (notifyreceived/notifyspent) watches: notifyspent (explicit or
// auto-armed) is one-shot, kept separate from loadtxfilter's permanent maps.
std::map<hash_digest, address_watch> receive_watches_{};
std::map<point, outpoint_watch> spent_watches_{};
};

} // namespace server
Expand Down
39 changes: 4 additions & 35 deletions src/protocols/btcd/protocol_btcd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,40 +273,9 @@ bool protocol_btcd::handle_stop_notify_new_transactions(const code& ec,
return true;
}

// Handlers (deprecated, not_implemented).
// Handlers (deprecated).
// ----------------------------------------------------------------------------

bool protocol_btcd::handle_notify_received(const code& ec,
btcd_interface::notify_received, const value_t&) NOEXCEPT
{
if (stopped(ec)) return false;
send_error(error::btcd::unimplemented);
return true;
}

bool protocol_btcd::handle_stop_notify_received(const code& ec,
btcd_interface::stop_notify_received, const value_t&) NOEXCEPT
{
if (stopped(ec)) return false;
send_error(error::btcd::unimplemented);
return true;
}

bool protocol_btcd::handle_notify_spent(const code& ec,
btcd_interface::notify_spent, const value_t&) NOEXCEPT
{
if (stopped(ec)) return false;
send_error(error::btcd::unimplemented);
return true;
}

bool protocol_btcd::handle_stop_notify_spent(const code& ec,
btcd_interface::stop_notify_spent, const value_t&) NOEXCEPT
{
if (stopped(ec)) return false;
send_error(error::btcd::unimplemented);
return true;
}
// notify_received/notify_spent bodies live in protocol_btcd_filter.cpp.

// Implemented only for the empty addresses/outpoints case (as btcd).
// This is the call btcwallet makes to bootstrap its sync starting point.
Expand Down Expand Up @@ -375,7 +344,7 @@ bool protocol_btcd::handle_chase(const code&, node::chase event_,
{
case node::chase::organized:
{
if (subscribed_blocks_.load(relaxed))
if (subscribed_blocks_.load(relaxed) || watching_legacy_.load(relaxed))
{
BC_ASSERT(std::holds_alternative<node::header_t>(value));
POST_NOTIFY(do_connected, std::get<node::header_t>(value));
Expand All @@ -384,7 +353,7 @@ bool protocol_btcd::handle_chase(const code&, node::chase event_,
}
case node::chase::reorganized:
{
if (subscribed_blocks_.load(relaxed))
if (subscribed_blocks_.load(relaxed) || watching_legacy_.load(relaxed))
{
BC_ASSERT(std::holds_alternative<node::header_t>(value));
POST_NOTIFY(do_disconnected, std::get<node::header_t>(value));
Expand Down
Loading
Loading