From 5bad9f8f54ff14a42e61a5fbbeba225b04b117c2 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Mon, 14 Sep 2026 21:40:41 -0400 Subject: [PATCH 1/3] Add trivial tx pool via chaser. --- .../node/chasers/chaser_transaction.hpp | 17 ++- include/bitcoin/node/define.hpp | 1 + include/bitcoin/node/error.hpp | 5 + include/bitcoin/node/full_node.hpp | 4 + include/bitcoin/node/protocols/protocol.hpp | 4 + .../protocol_transaction_out_106.hpp | 24 ---- include/bitcoin/node/sessions/session.hpp | 4 + src/chasers/chaser_transaction.cpp | 115 +++++++++++++++--- src/error.cpp | 5 + src/full_node.cpp | 6 + src/protocols/protocol.cpp | 6 + .../protocol_transaction_out_106.cpp | 70 ----------- src/sessions/session.cpp | 6 + 13 files changed, 151 insertions(+), 116 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_transaction.hpp b/include/bitcoin/node/chasers/chaser_transaction.hpp index 637c1865..31f4a00b 100644 --- a/include/bitcoin/node/chasers/chaser_transaction.hpp +++ b/include/bitcoin/node/chasers/chaser_transaction.hpp @@ -38,15 +38,24 @@ class BCN_API chaser_transaction code start() NOEXCEPT override; - virtual void store(const system::chain::transaction::cptr& block) NOEXCEPT; + /// Validate and archive a submitted package, accepted as a whole. + virtual void submit(const system::chain::transactions_cptr& txs, + submit_handler&& handler) NOEXCEPT; protected: virtual bool handle_chase(const code& ec, chase event_, event_value value) NOEXCEPT; - virtual void do_confirmed(header_t link) NOEXCEPT; - virtual void do_store( - const system::chain::transaction::cptr& header) NOEXCEPT; + virtual void do_submit(const system::chain::transactions_cptr& txs, + const submit_handler& handler) NOEXCEPT; + + /// Recompute the pool context, closing the pool if not current. + virtual void do_bump() NOEXCEPT; + +private: + // These are protected by strand. + system::chain::context pool_{}; + bool pooling_{}; }; } // namespace node diff --git a/include/bitcoin/node/define.hpp b/include/bitcoin/node/define.hpp index 7fa8f00d..91e4f3a5 100644 --- a/include/bitcoin/node/define.hpp +++ b/include/bitcoin/node/define.hpp @@ -54,6 +54,7 @@ typedef std::function estimate_handler; /// Organization types. typedef std::function organize_handler; +typedef std::function submit_handler; typedef database::store store; typedef database::query query; diff --git a/include/bitcoin/node/error.hpp b/include/bitcoin/node/error.hpp index 42c68e88..3c2e2e49 100644 --- a/include/bitcoin/node/error.hpp +++ b/include/bitcoin/node/error.hpp @@ -59,6 +59,10 @@ enum error_t : uint8_t duplicate_block, duplicate_header, + /// transaction pool + pooling_disabled, + empty_package, + /// fee estimation estimate_disabled, estimate_premature, @@ -105,6 +109,7 @@ enum error_t : uint8_t confirm10, confirm11, confirm12, + transaction1, estimates_initialize, estimates_push1, estimates_push2, diff --git a/include/bitcoin/node/full_node.hpp b/include/bitcoin/node/full_node.hpp index 0f36eb57..eabbb84e 100644 --- a/include/bitcoin/node/full_node.hpp +++ b/include/bitcoin/node/full_node.hpp @@ -73,6 +73,10 @@ class BCN_API full_node virtual void prioritize(const system::hash_digest& hash, organize_handler&& handler) NOEXCEPT; + /// Validate and archive a submitted package, accepted as a whole. + virtual void submit(const system::chain::transactions_cptr& txs, + submit_handler&& handler) NOEXCEPT; + /// Manage download queue. virtual void get_hashes(map_handler&& handler) NOEXCEPT; virtual void put_hashes(const map_ptr& map, diff --git a/include/bitcoin/node/protocols/protocol.hpp b/include/bitcoin/node/protocols/protocol.hpp index 4822057c..556f12bf 100644 --- a/include/bitcoin/node/protocols/protocol.hpp +++ b/include/bitcoin/node/protocols/protocol.hpp @@ -112,6 +112,10 @@ class BCN_API protocol virtual void prioritize(const system::hash_digest& hash, organize_handler&& handler) NOEXCEPT; + /// Validate and archive a submitted package, accepted as a whole. + virtual void submit(const system::chain::transactions_cptr& txs, + submit_handler&& handler) NOEXCEPT; + /// Events subscription. /// ----------------------------------------------------------------------- diff --git a/include/bitcoin/node/protocols/protocol_transaction_out_106.hpp b/include/bitcoin/node/protocols/protocol_transaction_out_106.hpp index 192ae5f0..b7b858b8 100644 --- a/include/bitcoin/node/protocols/protocol_transaction_out_106.hpp +++ b/include/bitcoin/node/protocols/protocol_transaction_out_106.hpp @@ -36,7 +36,6 @@ class BCN_API protocol_transaction_out_106 const network::channel::ptr& channel) NOEXCEPT : node::protocol_peer(session, channel), node_witness_(session->node_settings().provide_witness), - broadcast_(maximum_retained), network::tracker(session->log) { } @@ -55,10 +54,6 @@ class BCN_API protocol_transaction_out_106 /// Process tx announcement. virtual bool do_announce(transaction_t link) NOEXCEPT; - virtual bool handle_broadcast_transaction(const code& ec, - const network::messages::peer::transaction::cptr& message, - uint64_t sender) NOEXCEPT; - virtual bool handle_receive_get_data(const code& ec, const network::messages::peer::get_data::cptr& message) NOEXCEPT; virtual void send_transaction(const code& ec, size_t index, @@ -71,28 +66,9 @@ class BCN_API protocol_transaction_out_106 virtual bool announce(const system::hash_digest& hash) NOEXCEPT; - /// Retain a broadcast tx, pending request by the peer. - virtual void retain(const system::hash_digest& hash, - const system::chain::transaction::cptr& tx) NOEXCEPT; - - /// Obtain and drop a retained tx, or nullptr. - virtual system::chain::transaction::cptr release( - const system::hash_digest& hash) NOEXCEPT; - private: - using retained_t = std::pair; - using retained_txs = boost::circular_buffer; - - static constexpr size_t maximum_retained = 42; - - retained_txs::iterator find(const system::hash_digest& hash) NOEXCEPT; - // These are thread safe. const bool node_witness_; - - // This is protected by strand. - retained_txs broadcast_; }; } // namespace node diff --git a/include/bitcoin/node/sessions/session.hpp b/include/bitcoin/node/sessions/session.hpp index 14644fe9..1458a8d0 100644 --- a/include/bitcoin/node/sessions/session.hpp +++ b/include/bitcoin/node/sessions/session.hpp @@ -51,6 +51,10 @@ class BCN_API session virtual void prioritize(const system::hash_digest& hash, organize_handler&& handler) NOEXCEPT; + /// Validate and archive a submitted package, accepted as a whole. + virtual void submit(const system::chain::transactions_cptr& txs, + submit_handler&& handler) NOEXCEPT; + /// Manage download queue. virtual void get_hashes(map_handler&& handler) NOEXCEPT; virtual void put_hashes(const map_ptr& map, diff --git a/src/chasers/chaser_transaction.cpp b/src/chasers/chaser_transaction.cpp index f1f2af0b..58966285 100644 --- a/src/chasers/chaser_transaction.cpp +++ b/src/chasers/chaser_transaction.cpp @@ -21,16 +21,19 @@ #include #include #include +#include namespace libbitcoin { namespace node { #define CLASS chaser_transaction - + +using namespace system; using namespace system::chain; using namespace std::placeholders; BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) +BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) chaser_transaction::chaser_transaction(full_node& node) NOEXCEPT : chaser(node) @@ -40,10 +43,10 @@ chaser_transaction::chaser_transaction(full_node& node) NOEXCEPT // start // ---------------------------------------------------------------------------- -// TODO: initialize tx graph from store, log and stop on error. code chaser_transaction::start() NOEXCEPT { SUBSCRIBE_CHASE(handle_chase, _1, _2, _3); + POST(do_bump); return error::success; } @@ -56,14 +59,14 @@ bool chaser_transaction::handle_chase(const code&, chase event_, if (closed()) return false; - // TODO: allow required messages. - ////// Stop generating query during suspension. - ////// Incoming events may already be flushed to the strand at this point. - ////if (suspended()) - //// return true; - switch (event_) { + case chase::organized: + case chase::reorganized: + { + POST(do_bump); + break; + } case chase::stop: { return false; @@ -77,34 +80,110 @@ bool chaser_transaction::handle_chase(const code&, chase event_, return true; } -// TODO: handle the new confirmed blocks (may issue 'transaction'). -void chaser_transaction::do_confirmed(header_t) NOEXCEPT +// The pool is closed until the confirmed chain is current, and closes again if +// currency is lost, though the store latch is one way, since the txs archived +// while it was open outlive it. +void chaser_transaction::do_bump() NOEXCEPT { BC_ASSERT(stranded()); + pooling_ = false; + + if (closed() || !is_current_chain(true)) + return; + + auto& query = archive(); + const auto top = query.get_top_confirmed(); + const auto state = query.get_confirmed_chain_state(system_settings(), + query.to_confirmed(top), top); + + if (!state) + { + fault(error::transaction1); + return; + } - notify(error::success, chase::transaction, transaction_t{}); + // The context of the next block, in which a pool tx would confirm. + pool_ = chain_state{ *state, system_settings() }.context(); + query.set_pooling(); + pooling_ = true; } // methods // ---------------------------------------------------------------------------- -void chaser_transaction::store(const transaction::cptr&) NOEXCEPT +void chaser_transaction::submit(const transactions_cptr& txs, + submit_handler&& handler) NOEXCEPT { - // Push new checked tx into store and update DAG. Issue transaction event - // so that candidate may construct a new template. + if (closed()) + return; + + POST(do_submit, txs, std::move(handler)); } // private -void chaser_transaction::do_store(const transaction::cptr&) NOEXCEPT +// The package is accepted as a whole, so nothing is archived until all txs +// have validated, and the index identifies the tx that a failure pertains to. +void chaser_transaction::do_submit(const transactions_cptr& txs, + const submit_handler& handler) NOEXCEPT { BC_ASSERT(stranded()); - // TODO: validate and store transaction. + if (closed()) + { + handler(network::error::service_stopped, zero); + return; + } + + if (!pooling_) + { + handler(error::pooling_disabled, zero); + return; + } + + if (txs->empty()) + { + handler(error::empty_package, zero); + return; + } + + // Prevouts internal to the package are populated from it, and those that + // remain are populated from the archive by each tx validation. + constexpr auto coinbase = false; + if (const auto ec = block::populate(*txs, pool_, coinbase)) + { + handler(ec, zero); + return; + } + + auto& query = archive(); + for (size_t index{}; index < txs->size(); ++index) + { + if (const auto ec = validate_transaction(*txs->at(index), query, pool_)) + { + handler(ec, index); + return; + } + } + + // A fault here leaves a prefix of the package archived, which the caller + // resolves by resubmission (an archived tx substitutes its own link). + for (size_t index{}; index < txs->size(); ++index) + { + database::tx_link link{}; + if (const auto ec = query.set_code(link, *txs->at(index))) + { + handler(fault(ec), index); + return; + } + + fire(events::tx_archived, link); + notify(error::success, chase::transaction, transaction_t{ link }); + } - // Relay notification. - ////notify(error::success, chase::transaction, link); + handler(error::success, zero); } +BC_POP_WARNING() BC_POP_WARNING() } // namespace node diff --git a/src/error.cpp b/src/error.cpp index 80953428..00fca068 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -49,6 +49,10 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { duplicate_block, "duplicate block" }, { duplicate_header, "duplicate header" }, + // transaction pool + { pooling_disabled, "pooling disabled" }, + { empty_package, "empty package" }, + // fee estimation { estimate_disabled, "estimate_disabled" }, { estimate_premature, "estimate_premature" }, @@ -95,6 +99,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { confirm10, "confirm10" }, { confirm11, "confirm11" }, { confirm12, "confirm12" }, + { transaction1, "transaction1" }, { estimates_initialize, "estimates_initialize" }, { estimates_push1, "estimates_push1" }, { estimates_push2, "estimates_push2" }, diff --git a/src/full_node.cpp b/src/full_node.cpp index 315c587d..6c644cd5 100644 --- a/src/full_node.cpp +++ b/src/full_node.cpp @@ -183,6 +183,12 @@ void full_node::prioritize(const system::hash_digest& hash, chaser_block_.prioritize(hash, std::move(handler)); } +void full_node::submit(const system::chain::transactions_cptr& txs, + submit_handler&& handler) NOEXCEPT +{ + chaser_transaction_.submit(txs, std::move(handler)); +} + void full_node::get_hashes(map_handler&& handler) NOEXCEPT { chaser_check_.get_hashes(std::move(handler)); diff --git a/src/protocols/protocol.cpp b/src/protocols/protocol.cpp index 99dad0f2..7375e101 100644 --- a/src/protocols/protocol.cpp +++ b/src/protocols/protocol.cpp @@ -139,6 +139,12 @@ void protocol::prioritize(const system::hash_digest& hash, session_->prioritize(hash, std::move(handler)); } +void protocol::submit(const system::chain::transactions_cptr& txs, + submit_handler&& handler) NOEXCEPT +{ + session_->submit(txs, std::move(handler)); +} + void protocol::subscribe_chase(event_notifier&& handler) NOEXCEPT { // This is a shared instance multiply-derived from network::protocol. diff --git a/src/protocols/protocol_transaction_out_106.cpp b/src/protocols/protocol_transaction_out_106.cpp index ca0faabb..4ea50d1c 100644 --- a/src/protocols/protocol_transaction_out_106.cpp +++ b/src/protocols/protocol_transaction_out_106.cpp @@ -18,7 +18,6 @@ */ #include -#include #include namespace libbitcoin { @@ -47,7 +46,6 @@ void protocol_transaction_out_106::start() NOEXCEPT // Events subscription is asynchronous, events may be missed. subscribe_chase(BIND(handle_chase, _1, _2, _3)); - SUBSCRIBE_BROADCAST(transaction, handle_broadcast_transaction, _1, _2, _3); SUBSCRIBE_CHANNEL(get_data, handle_receive_get_data, _1, _2); protocol_peer::start(); } @@ -58,8 +56,6 @@ void protocol_transaction_out_106::stopping(const code& ec) NOEXCEPT // Unsubscriber race is ok. unsubscribe_chase(); - - UNSUBSCRIBE_BROADCAST(); protocol_peer::stopping(ec); } @@ -106,26 +102,6 @@ bool protocol_transaction_out_106::do_announce(transaction_t link) NOEXCEPT return announce(archive().get_tx_key(link)); } -bool protocol_transaction_out_106::handle_broadcast_transaction(const code& ec, - const transaction::cptr& message, uint64_t sender) NOEXCEPT -{ - BC_ASSERT(stranded()); - - if (stopped(ec)) - return false; - - if (sender == identifier()) - return true; - - const auto& tx = message->transaction_ptr; - const auto hash = tx->hash(false); - if (was_announced(hash)) - return true; - - retain(hash, tx); - return announce(hash); -} - bool protocol_transaction_out_106::announce(const hash_digest& hash) NOEXCEPT { BC_ASSERT(stranded()); @@ -146,45 +122,6 @@ bool protocol_transaction_out_106::announce(const hash_digest& hash) NOEXCEPT return true; } -// Retention. -// ---------------------------------------------------------------------------- - -void protocol_transaction_out_106::retain(const hash_digest& hash, - const chain::transaction::cptr& tx) NOEXCEPT -{ - BC_ASSERT(stranded()); - - if (find(hash) != broadcast_.end()) - return; - - broadcast_.push_back({ hash, tx }); -} - -chain::transaction::cptr protocol_transaction_out_106::release( - const hash_digest& hash) NOEXCEPT -{ - BC_ASSERT(stranded()); - - const auto it = find(hash); - if (it == broadcast_.end()) - return {}; - - const auto tx = it->second; - broadcast_.erase(it); - return tx; -} - -protocol_transaction_out_106::retained_txs::iterator -protocol_transaction_out_106::find(const hash_digest& hash) NOEXCEPT -{ - const auto match = [&hash](const auto& entry) NOEXCEPT - { - return entry.first == hash; - }; - - return std::find_if(broadcast_.begin(), broadcast_.end(), match); -} - // Inbound (get_data). // ---------------------------------------------------------------------------- @@ -238,13 +175,6 @@ void protocol_transaction_out_106::send_transaction(const code& ec, return; } - // A broadcast tx is not archived, so is served from retention, once. - if (const auto retained = release(item.hash)) - { - SEND(transaction{ retained }, send_transaction, _1, add1(index), message); - return; - } - // Tx could be always queried with witness and therefore safely cached. // If can then be serialized according to channel configuration, however // that is currently fixed to witness as available in the object. diff --git a/src/sessions/session.cpp b/src/sessions/session.cpp index 9643c009..f89a33b7 100644 --- a/src/sessions/session.cpp +++ b/src/sessions/session.cpp @@ -57,6 +57,12 @@ void session::prioritize(const hash_digest& hash, node_.prioritize(hash, std::move(handler)); } +void session::submit(const transactions_cptr& txs, + submit_handler&& handler) NOEXCEPT +{ + node_.submit(txs, std::move(handler)); +} + void session::get_hashes(map_handler&& handler) NOEXCEPT { node_.get_hashes(std::move(handler)); From 34aa913c3fe76ef9bdbf210aeb83411a216bff2e Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Mon, 14 Sep 2026 22:07:53 -0400 Subject: [PATCH 2/3] Move validate to tx chaser. --- builds/cmake/CMakeLists.txt | 2 +- builds/gnu/Makefile.am | 2 - builds/gnu/configure.ac | 2 +- .../libbitcoin-node/libbitcoin-node.vcxproj | 2 - .../libbitcoin-node.vcxproj.filters | 6 --- .../libbitcoin-node/libbitcoin-node.vcxproj | 2 - .../libbitcoin-node.vcxproj.filters | 6 --- include/bitcoin/node.hpp | 1 - .../node/chasers/chaser_transaction.hpp | 3 ++ include/bitcoin/node/validate.hpp | 40 --------------- src/chasers/chaser_transaction.cpp | 31 +++++++++--- src/validate.cpp | 50 ------------------- 12 files changed, 30 insertions(+), 117 deletions(-) delete mode 100644 include/bitcoin/node/validate.hpp delete mode 100644 src/validate.cpp diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index 351fb6f5..82e81bf7 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -61,7 +61,7 @@ project( libbitcoin-node LANGUAGES C CXX ) -set( repository_root_dir "${CMAKE_CURRENT_SOURCE_DIR}/../../" ) +set( repository_root_dir "${CMAKE_CURRENT_SOURCE_DIR}/../.." ) if ( MSVC ) set( CMAKE_STATIC_LIBRARY_PREFIX "lib" ) diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 753dc613..2f59415c 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -57,7 +57,6 @@ src_libbitcoin_node_la_SOURCES = \ ${srcdir}/../../src/estimator.cpp \ ${srcdir}/../../src/full_node.cpp \ ${srcdir}/../../src/settings.cpp \ - ${srcdir}/../../src/validate.cpp \ ${srcdir}/../../src/channels/channel_peer.cpp \ ${srcdir}/../../src/chasers/chaser.cpp \ ${srcdir}/../../src/chasers/chaser_block.cpp \ @@ -115,7 +114,6 @@ include_bitcoin_node_HEADERS = \ ${srcdir}/../../include/bitcoin/node/events.hpp \ ${srcdir}/../../include/bitcoin/node/full_node.hpp \ ${srcdir}/../../include/bitcoin/node/settings.hpp \ - ${srcdir}/../../include/bitcoin/node/validate.hpp \ ${srcdir}/../../include/bitcoin/node/version.hpp include_bitcoin_node_channelsdir = \ diff --git a/builds/gnu/configure.ac b/builds/gnu/configure.ac index ce72f06e..4c67e1c4 100644 --- a/builds/gnu/configure.ac +++ b/builds/gnu/configure.ac @@ -68,7 +68,7 @@ AC_ARG_VAR([CC], "C compiler to use, such as gcc or clang") AC_ARG_VAR([CXX], "C++ compiler to use, such as g++ or clang++") AC_ARG_VAR([PKG_CONFIG_PATH], "Additional directories for package discovery.") -repository_root_dir="@abs_top_srcdir@/../.." +repository_root_dir=`cd "${srcdir}/../.." && pwd` # Check for baseline language coverage in the compiler for the C++20 standard. #------------------------------------------------------------------------------ diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj index 750d08b1..6d92f85f 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj @@ -164,7 +164,6 @@ - @@ -219,7 +218,6 @@ - diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters index 10876c36..7bbae4c3 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters @@ -186,9 +186,6 @@ src - - src - @@ -347,9 +344,6 @@ include\bitcoin\node - - include\bitcoin\node - include\bitcoin\node diff --git a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj index c43c94a9..5a3101e5 100644 --- a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj @@ -164,7 +164,6 @@ - @@ -219,7 +218,6 @@ - diff --git a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters index 10876c36..7bbae4c3 100644 --- a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters @@ -186,9 +186,6 @@ src - - src - @@ -347,9 +344,6 @@ include\bitcoin\node - - include\bitcoin\node - include\bitcoin\node diff --git a/include/bitcoin/node.hpp b/include/bitcoin/node.hpp index 7ce08dfe..27ee6414 100644 --- a/include/bitcoin/node.hpp +++ b/include/bitcoin/node.hpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/include/bitcoin/node/chasers/chaser_transaction.hpp b/include/bitcoin/node/chasers/chaser_transaction.hpp index 31f4a00b..5c15e1d6 100644 --- a/include/bitcoin/node/chasers/chaser_transaction.hpp +++ b/include/bitcoin/node/chasers/chaser_transaction.hpp @@ -53,6 +53,9 @@ class BCN_API chaser_transaction virtual void do_bump() NOEXCEPT; private: + code validate(const system::chain::transaction& tx, const query& query, + const system::chain::context& pool) NOEXCEPT; + // These are protected by strand. system::chain::context pool_{}; bool pooling_{}; diff --git a/include/bitcoin/node/validate.hpp b/include/bitcoin/node/validate.hpp deleted file mode 100644 index 306192ed..00000000 --- a/include/bitcoin/node/validate.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#ifndef LIBBITCOIN_NODE_VALIDATE_HPP -#define LIBBITCOIN_NODE_VALIDATE_HPP - -#include -#include - -namespace libbitcoin { -namespace node { - -/// Transaction validation against the confirmed chain, for services that -/// accept transactions from clients (there is no tx pool until v5). Shared -/// by the server protocols, which obtain the context of the next block. - -/// Validate tx in the given context, populating its prevout metadata. -/// Includes the block-rule guards, a DoS guard for standalone validation. -BCN_API code validate_transaction(const system::chain::transaction& tx, - const query& query, const system::chain::context& pool) NOEXCEPT; - -} // namespace node -} // namespace libbitcoin - -#endif diff --git a/src/chasers/chaser_transaction.cpp b/src/chasers/chaser_transaction.cpp index 58966285..2723a9de 100644 --- a/src/chasers/chaser_transaction.cpp +++ b/src/chasers/chaser_transaction.cpp @@ -21,7 +21,6 @@ #include #include #include -#include namespace libbitcoin { namespace node { @@ -121,8 +120,6 @@ void chaser_transaction::submit(const transactions_cptr& txs, } // private -// The package is accepted as a whole, so nothing is archived until all txs -// have validated, and the index identifies the tx that a failure pertains to. void chaser_transaction::do_submit(const transactions_cptr& txs, const submit_handler& handler) NOEXCEPT { @@ -146,8 +143,6 @@ void chaser_transaction::do_submit(const transactions_cptr& txs, return; } - // Prevouts internal to the package are populated from it, and those that - // remain are populated from the archive by each tx validation. constexpr auto coinbase = false; if (const auto ec = block::populate(*txs, pool_, coinbase)) { @@ -158,7 +153,7 @@ void chaser_transaction::do_submit(const transactions_cptr& txs, auto& query = archive(); for (size_t index{}; index < txs->size(); ++index) { - if (const auto ec = validate_transaction(*txs->at(index), query, pool_)) + if (const auto ec = validate(*txs->at(index), query, pool_)) { handler(ec, index); return; @@ -183,6 +178,30 @@ void chaser_transaction::do_submit(const transactions_cptr& txs, handler(error::success, zero); } +// methods +// ---------------------------------------------------------------------------- + +code chaser_transaction::validate(const chain::transaction& tx, + const query& query, const chain::context& pool) NOEXCEPT +{ + code ec{}; + + // Ensure tx does not violate tx consensus rules. + if (!ec) ec = tx.check(); + if (!ec) ec = tx.check(pool); + if (!ec) query.populate_with_metadata(tx, true); + if (!ec) ec = tx.accept(pool); + if (!ec) ec = tx.confirm(pool); + if (!ec) ec = tx.connect(pool); + + // Ensure tx does not violate presumed block consensus rules. + // This is a DoS guard when validating a tx outside of a block. + if (!ec) ec = tx.check_guard(); + if (!ec) ec = tx.check_guard(pool); + if (!ec) ec = tx.accept_guard(pool); + return ec; +} + BC_POP_WARNING() BC_POP_WARNING() diff --git a/src/validate.cpp b/src/validate.cpp deleted file mode 100644 index e84ba92e..00000000 --- a/src/validate.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#include - -#include - -namespace libbitcoin { -namespace node { - -using namespace system; - -code validate_transaction(const chain::transaction& tx, const query& query, - const chain::context& pool) NOEXCEPT -{ - code ec{}; - - // Ensure tx does not violate tx consensus rules. - if (!ec) ec = tx.check(); - if (!ec) ec = tx.check(pool); - if (!ec) query.populate_with_metadata(tx, true); - if (!ec) ec = tx.accept(pool); - if (!ec) ec = tx.confirm(pool); - if (!ec) ec = tx.connect(pool); - - // Ensure tx does not violate presumed block consensus rules. - // This is a DoS guard when validating a tx outside of a block. - if (!ec) ec = tx.check_guard(); - if (!ec) ec = tx.check_guard(pool); - if (!ec) ec = tx.accept_guard(pool); - return ec; -} - -} // namespace node -} // namespace libbitcoin From 2c0451a1350bd5732caab1ec1129a199deeb6149 Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Mon, 14 Sep 2026 23:02:15 -0400 Subject: [PATCH 3/3] Add test parameter to tx submit, and test via pool. --- .../node/chasers/chaser_transaction.hpp | 10 ++- include/bitcoin/node/full_node.hpp | 3 +- include/bitcoin/node/protocols/protocol.hpp | 3 +- include/bitcoin/node/sessions/session.hpp | 3 +- src/chasers/chaser_transaction.cpp | 73 ++++++++++--------- src/full_node.cpp | 4 +- src/protocols/protocol.cpp | 4 +- src/sessions/session.cpp | 4 +- 8 files changed, 58 insertions(+), 46 deletions(-) diff --git a/include/bitcoin/node/chasers/chaser_transaction.hpp b/include/bitcoin/node/chasers/chaser_transaction.hpp index 5c15e1d6..8b0ec9c8 100644 --- a/include/bitcoin/node/chasers/chaser_transaction.hpp +++ b/include/bitcoin/node/chasers/chaser_transaction.hpp @@ -39,22 +39,24 @@ class BCN_API chaser_transaction code start() NOEXCEPT override; /// Validate and archive a submitted package, accepted as a whole. + /// The package is only validated when test, so nothing is archived. virtual void submit(const system::chain::transactions_cptr& txs, - submit_handler&& handler) NOEXCEPT; + bool test, submit_handler&& handler) NOEXCEPT; protected: virtual bool handle_chase(const code& ec, chase event_, event_value value) NOEXCEPT; virtual void do_submit(const system::chain::transactions_cptr& txs, - const submit_handler& handler) NOEXCEPT; + bool test, const submit_handler& handler) NOEXCEPT; /// Recompute the pool context, closing the pool if not current. virtual void do_bump() NOEXCEPT; private: - code validate(const system::chain::transaction& tx, const query& query, - const system::chain::context& pool) NOEXCEPT; + code validate(size_t& index, + const system::chain::transaction_cptrs& txs) NOEXCEPT; + code validate(const system::chain::transaction& tx) NOEXCEPT; // These are protected by strand. system::chain::context pool_{}; diff --git a/include/bitcoin/node/full_node.hpp b/include/bitcoin/node/full_node.hpp index eabbb84e..9eba4fa9 100644 --- a/include/bitcoin/node/full_node.hpp +++ b/include/bitcoin/node/full_node.hpp @@ -74,8 +74,9 @@ class BCN_API full_node organize_handler&& handler) NOEXCEPT; /// Validate and archive a submitted package, accepted as a whole. + /// The package is only validated when test, so nothing is archived. virtual void submit(const system::chain::transactions_cptr& txs, - submit_handler&& handler) NOEXCEPT; + bool test, submit_handler&& handler) NOEXCEPT; /// Manage download queue. virtual void get_hashes(map_handler&& handler) NOEXCEPT; diff --git a/include/bitcoin/node/protocols/protocol.hpp b/include/bitcoin/node/protocols/protocol.hpp index 556f12bf..a6f580a9 100644 --- a/include/bitcoin/node/protocols/protocol.hpp +++ b/include/bitcoin/node/protocols/protocol.hpp @@ -113,8 +113,9 @@ class BCN_API protocol organize_handler&& handler) NOEXCEPT; /// Validate and archive a submitted package, accepted as a whole. + /// The package is only validated when test, so nothing is archived. virtual void submit(const system::chain::transactions_cptr& txs, - submit_handler&& handler) NOEXCEPT; + bool test, submit_handler&& handler) NOEXCEPT; /// Events subscription. /// ----------------------------------------------------------------------- diff --git a/include/bitcoin/node/sessions/session.hpp b/include/bitcoin/node/sessions/session.hpp index 1458a8d0..97ceaaf9 100644 --- a/include/bitcoin/node/sessions/session.hpp +++ b/include/bitcoin/node/sessions/session.hpp @@ -52,8 +52,9 @@ class BCN_API session organize_handler&& handler) NOEXCEPT; /// Validate and archive a submitted package, accepted as a whole. + /// The package is only validated when test, so nothing is archived. virtual void submit(const system::chain::transactions_cptr& txs, - submit_handler&& handler) NOEXCEPT; + bool test, submit_handler&& handler) NOEXCEPT; /// Manage download queue. virtual void get_hashes(map_handler&& handler) NOEXCEPT; diff --git a/src/chasers/chaser_transaction.cpp b/src/chasers/chaser_transaction.cpp index 2723a9de..d59354ec 100644 --- a/src/chasers/chaser_transaction.cpp +++ b/src/chasers/chaser_transaction.cpp @@ -110,61 +110,52 @@ void chaser_transaction::do_bump() NOEXCEPT // methods // ---------------------------------------------------------------------------- -void chaser_transaction::submit(const transactions_cptr& txs, +void chaser_transaction::submit(const transactions_cptr& txs, bool test, submit_handler&& handler) NOEXCEPT { if (closed()) return; - POST(do_submit, txs, std::move(handler)); + POST(do_submit, txs, test, std::move(handler)); } // private -void chaser_transaction::do_submit(const transactions_cptr& txs, +void chaser_transaction::do_submit(const transactions_cptr& txs, bool test, const submit_handler& handler) NOEXCEPT { BC_ASSERT(stranded()); if (closed()) { - handler(network::error::service_stopped, zero); + handler(network::error::service_stopped, {}); return; } if (!pooling_) { - handler(error::pooling_disabled, zero); + handler(error::pooling_disabled, {}); return; } - if (txs->empty()) + size_t index{}; + if (const auto ec = validate(index, *txs)) { - handler(error::empty_package, zero); + handler(ec, index); return; } - constexpr auto coinbase = false; - if (const auto ec = block::populate(*txs, pool_, coinbase)) + if (test) { - handler(ec, zero); + handler(error::success, {}); return; } auto& query = archive(); - for (size_t index{}; index < txs->size(); ++index) - { - if (const auto ec = validate(*txs->at(index), query, pool_)) - { - handler(ec, index); - return; - } - } - - // A fault here leaves a prefix of the package archived, which the caller - // resolves by resubmission (an archived tx substitutes its own link). - for (size_t index{}; index < txs->size(); ++index) + for (index = {}; index < txs->size(); ++index) { database::tx_link link{}; + + // Disk full may leave package partly archived, resolves by resubmit. if (const auto ec = query.set_code(link, *txs->at(index))) { handler(fault(ec), index); @@ -175,30 +166,46 @@ void chaser_transaction::do_submit(const transactions_cptr& txs, notify(error::success, chase::transaction, transaction_t{ link }); } - handler(error::success, zero); + handler(error::success, {}); } -// methods +// validation // ---------------------------------------------------------------------------- -code chaser_transaction::validate(const chain::transaction& tx, - const query& query, const chain::context& pool) NOEXCEPT +code chaser_transaction::validate(size_t& index, + const transaction_cptrs& txs) NOEXCEPT +{ + index = zero; + if (txs.empty()) + return error::empty_package; + + if (const auto ec = block::populate(txs, pool_, false)) + return ec; + + for (; index < txs.size(); ++index) + if (const auto ec = validate(*txs.at(index))) + return ec; + + return {}; +} + +code chaser_transaction::validate(const chain::transaction& tx) NOEXCEPT { code ec{}; // Ensure tx does not violate tx consensus rules. if (!ec) ec = tx.check(); - if (!ec) ec = tx.check(pool); - if (!ec) query.populate_with_metadata(tx, true); - if (!ec) ec = tx.accept(pool); - if (!ec) ec = tx.confirm(pool); - if (!ec) ec = tx.connect(pool); + if (!ec) ec = tx.check(pool_); + if (!ec) archive().populate_with_metadata(tx, true); + if (!ec) ec = tx.accept(pool_); + if (!ec) ec = tx.confirm(pool_); + if (!ec) ec = tx.connect(pool_); // Ensure tx does not violate presumed block consensus rules. // This is a DoS guard when validating a tx outside of a block. if (!ec) ec = tx.check_guard(); - if (!ec) ec = tx.check_guard(pool); - if (!ec) ec = tx.accept_guard(pool); + if (!ec) ec = tx.check_guard(pool_); + if (!ec) ec = tx.accept_guard(pool_); return ec; } diff --git a/src/full_node.cpp b/src/full_node.cpp index 6c644cd5..3312fcd5 100644 --- a/src/full_node.cpp +++ b/src/full_node.cpp @@ -183,10 +183,10 @@ void full_node::prioritize(const system::hash_digest& hash, chaser_block_.prioritize(hash, std::move(handler)); } -void full_node::submit(const system::chain::transactions_cptr& txs, +void full_node::submit(const system::chain::transactions_cptr& txs, bool test, submit_handler&& handler) NOEXCEPT { - chaser_transaction_.submit(txs, std::move(handler)); + chaser_transaction_.submit(txs, test, std::move(handler)); } void full_node::get_hashes(map_handler&& handler) NOEXCEPT diff --git a/src/protocols/protocol.cpp b/src/protocols/protocol.cpp index 7375e101..7eaa3c41 100644 --- a/src/protocols/protocol.cpp +++ b/src/protocols/protocol.cpp @@ -139,10 +139,10 @@ void protocol::prioritize(const system::hash_digest& hash, session_->prioritize(hash, std::move(handler)); } -void protocol::submit(const system::chain::transactions_cptr& txs, +void protocol::submit(const system::chain::transactions_cptr& txs, bool test, submit_handler&& handler) NOEXCEPT { - session_->submit(txs, std::move(handler)); + session_->submit(txs, test, std::move(handler)); } void protocol::subscribe_chase(event_notifier&& handler) NOEXCEPT diff --git a/src/sessions/session.cpp b/src/sessions/session.cpp index f89a33b7..a01275ec 100644 --- a/src/sessions/session.cpp +++ b/src/sessions/session.cpp @@ -57,10 +57,10 @@ void session::prioritize(const hash_digest& hash, node_.prioritize(hash, std::move(handler)); } -void session::submit(const transactions_cptr& txs, +void session::submit(const transactions_cptr& txs, bool test, submit_handler&& handler) NOEXCEPT { - node_.submit(txs, std::move(handler)); + node_.submit(txs, test, std::move(handler)); } void session::get_hashes(map_handler&& handler) NOEXCEPT