diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 2f59415c..a128d1ac 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -89,6 +89,7 @@ src_libbitcoin_node_la_SOURCES = \ ${srcdir}/../../src/protocols/protocol_transaction_in_106.cpp \ ${srcdir}/../../src/protocols/protocol_transaction_out_106.cpp \ ${srcdir}/../../src/protocols/protocol_transaction_out_70001.cpp \ + ${srcdir}/../../src/protocols/protocol_transaction_out_70013.cpp \ ${srcdir}/../../src/sessions/session.cpp \ ${srcdir}/../../src/sessions/session_inbound.cpp \ ${srcdir}/../../src/sessions/session_manual.cpp \ @@ -175,6 +176,7 @@ include_bitcoin_node_protocols_HEADERS = \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_transaction_in_106.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_transaction_out_106.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_transaction_out_70001.hpp \ + ${srcdir}/../../include/bitcoin/node/protocols/protocol_transaction_out_70013.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocols.hpp include_bitcoin_node_sessionsdir = \ diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj index 6d92f85f..c8b1f321 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj @@ -159,6 +159,7 @@ + @@ -210,6 +211,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters index 7bbae4c3..9d62a915 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters @@ -171,6 +171,9 @@ src\protocols + + src\protocols + src\sessions @@ -320,6 +323,9 @@ include\bitcoin\node\protocols + + include\bitcoin\node\protocols + include\bitcoin\node\protocols diff --git a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj index 5a3101e5..b6a4db3b 100644 --- a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj @@ -159,6 +159,7 @@ + @@ -210,6 +211,7 @@ + diff --git a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters index 7bbae4c3..9d62a915 100644 --- a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters @@ -171,6 +171,9 @@ src\protocols + + src\protocols + src\sessions @@ -320,6 +323,9 @@ include\bitcoin\node\protocols + + include\bitcoin\node\protocols + include\bitcoin\node\protocols diff --git a/include/bitcoin/node.hpp b/include/bitcoin/node.hpp index 27ee6414..346d143f 100644 --- a/include/bitcoin/node.hpp +++ b/include/bitcoin/node.hpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/node/error.hpp b/include/bitcoin/node/error.hpp index 3c2e2e49..0f2ef87d 100644 --- a/include/bitcoin/node/error.hpp +++ b/include/bitcoin/node/error.hpp @@ -61,6 +61,7 @@ enum error_t : uint8_t /// transaction pool pooling_disabled, + insufficient_fee, empty_package, /// fee estimation diff --git a/include/bitcoin/node/impl/sessions/session_peer.ipp b/include/bitcoin/node/impl/sessions/session_peer.ipp index d766ee94..dc151112 100644 --- a/include/bitcoin/node/impl/sessions/session_peer.ipp +++ b/include/bitcoin/node/impl/sessions/session_peer.ipp @@ -154,7 +154,9 @@ inline void CLASS::attach_protocols(const channel_ptr& channel) NOEXCEPT // Relay is configured, active, and txs are ready (txs in/out). if (txs_in_out && peer->peer_version()->relay) { - if (peer->is_negotiated(level::bip37)) + if (peer->is_negotiated(level::bip133)) + channel->attach(self)->start(); + else if (peer->is_negotiated(level::bip37)) channel->attach(self)->start(); else channel->attach(self)->start(); diff --git a/include/bitcoin/node/protocols/protocol_transaction_out_70013.hpp b/include/bitcoin/node/protocols/protocol_transaction_out_70013.hpp new file mode 100644 index 00000000..c63e8426 --- /dev/null +++ b/include/bitcoin/node/protocols/protocol_transaction_out_70013.hpp @@ -0,0 +1,63 @@ +/** + * 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_PROTOCOLS_PROTOCOL_TRANSACTION_OUT_70013_HPP +#define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_TRANSACTION_OUT_70013_HPP + +#include +#include + +namespace libbitcoin { +namespace node { + +class BCN_API protocol_transaction_out_70013 + : public protocol_transaction_out_70001, + protected network::tracker +{ +public: + typedef std::shared_ptr ptr; + + protocol_transaction_out_70013(const auto& session, + const network::channel::ptr& channel) NOEXCEPT + : protocol_transaction_out_70001(session, channel), + network::tracker(session->log) + { + } + + /// Start protocol (strand required). + void start() NOEXCEPT override; + +protected: + /// Capture the peer's minimum fee rate for announcements. + virtual bool handle_receive_fee_filter(const code& ec, + const network::messages::peer::fee_filter::cptr& message) NOEXCEPT; + + /// Bypasses announcement of a tx below the peer's minimum fee rate. + bool do_announce(transaction_t link) NOEXCEPT override; + +private: + bool insufficient(const database::fee_rate& rate) const NOEXCEPT; + + // This is protected by strand. + uint64_t minimum_fee_{}; +}; + +} // namespace node +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/node/protocols/protocols.hpp b/include/bitcoin/node/protocols/protocols.hpp index 9ff2dc25..ef865577 100644 --- a/include/bitcoin/node/protocols/protocols.hpp +++ b/include/bitcoin/node/protocols/protocols.hpp @@ -36,5 +36,6 @@ #include #include #include +#include #endif diff --git a/include/bitcoin/node/settings.hpp b/include/bitcoin/node/settings.hpp index cd0d7682..5c6b3e13 100644 --- a/include/bitcoin/node/settings.hpp +++ b/include/bitcoin/node/settings.hpp @@ -66,6 +66,7 @@ class BCN_API settings virtual size_t maximum_height_() const NOEXCEPT; virtual size_t maximum_concurrency_() const NOEXCEPT; virtual size_t fee_estimate_horizon_() const NOEXCEPT; + virtual uint64_t minimum_fee_rate_() const NOEXCEPT; virtual uint64_t services_provided() const NOEXCEPT; virtual uint64_t services_required() const NOEXCEPT; virtual bool fee_estimate_enabled() const NOEXCEPT; diff --git a/src/chasers/chaser_transaction.cpp b/src/chasers/chaser_transaction.cpp index d59354ec..9fb1b166 100644 --- a/src/chasers/chaser_transaction.cpp +++ b/src/chasers/chaser_transaction.cpp @@ -31,6 +31,8 @@ using namespace system; using namespace system::chain; using namespace std::placeholders; +constexpr uint64_t vbytes_per_vkbyte = 1'000; + BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) @@ -179,6 +181,10 @@ code chaser_transaction::validate(size_t& index, if (txs.empty()) return error::empty_package; + // Conflict within the package, as with a block, since it is accepted whole. + if (block::is_internal_double_spend(txs, false)) + return system::error::block_internal_double_spend; + if (const auto ec = block::populate(txs, pool_, false)) return ec; @@ -186,6 +192,22 @@ code chaser_transaction::validate(size_t& index, if (const auto ec = validate(*txs.at(index))) return ec; + // The package is accepted as a whole, so the whole must pay the rate. + index = zero; + uint64_t fee{}; + uint64_t size{}; + for (const auto& tx: txs) + { + fee = ceilinged_add(fee, tx->fee()); + size = ceilinged_add(size, possible_wide_cast( + tx->virtual_size())); + } + + // Compared in satoshis per virtual kilobyte, so exact and undivided. + if (ceilinged_multiply(fee, vbytes_per_vkbyte) < + ceilinged_multiply(node_settings().minimum_fee_rate_(), size)) + return error::insufficient_fee; + return {}; } @@ -196,16 +218,18 @@ code chaser_transaction::validate(const chain::transaction& tx) NOEXCEPT // Ensure tx does not violate tx consensus rules. if (!ec) ec = tx.check(); if (!ec) ec = tx.check(pool_); - if (!ec) archive().populate_with_metadata(tx, true); + if (!ec) archive().populate_with_metadata(tx, true, 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.confirm_guard(pool_); + + // Script validation is the most costly, so it follows the guards. + if (!ec) ec = tx.connect(pool_); return ec; } diff --git a/src/error.cpp b/src/error.cpp index 00fca068..e097d614 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -51,6 +51,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) // transaction pool { pooling_disabled, "pooling disabled" }, + { insufficient_fee, "insufficient fee" }, { empty_package, "empty package" }, // fee estimation diff --git a/src/protocols/protocol_transaction_out_70013.cpp b/src/protocols/protocol_transaction_out_70013.cpp new file mode 100644 index 00000000..32814a0d --- /dev/null +++ b/src/protocols/protocol_transaction_out_70013.cpp @@ -0,0 +1,106 @@ +/** + * 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 { + +#define CLASS protocol_transaction_out_70013 + +using namespace system; +using namespace network::messages::peer; +using namespace std::placeholders; + +// The fee filter is expressed in satoshis per virtual kilobyte (bip133). +constexpr uint64_t vbytes_per_vkbyte = 1'000; + +// Shared pointers required for lifetime in handler parameters. +BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) +BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) + +// start +// ---------------------------------------------------------------------------- + +void protocol_transaction_out_70013::start() NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (started()) + return; + + SUBSCRIBE_CHANNEL(fee_filter, handle_receive_fee_filter, _1, _2); + protocol_transaction_out_70001::start(); +} + +// Inbound (feefilter). +// ---------------------------------------------------------------------------- + +bool protocol_transaction_out_70013::handle_receive_fee_filter(const code& ec, + const fee_filter::cptr& message) NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (stopped(ec)) + return false; + + minimum_fee_ = message->minimum_fee; + return true; +} + +// Outbound (inv). +// ---------------------------------------------------------------------------- + +bool protocol_transaction_out_70013::do_announce(transaction_t link) NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (stopped()) + return false; + + // The tx is archived and its prevouts populated, so the rate is known. + database::fee_rate rate{}; + if (!archive().get_tx_fees(rate, link)) + { + fault(database::error::integrity); + return false; + } + + // bip133: the peer is not sent a tx below the rate it advertised. + if (insufficient(rate)) + return true; + + return protocol_transaction_out_70001::do_announce(link); +} + +// private +bool protocol_transaction_out_70013::insufficient( + const database::fee_rate& rate) const NOEXCEPT +{ + return ceilinged_multiply(rate.fee, vbytes_per_vkbyte) < + ceilinged_multiply(minimum_fee_, + possible_wide_cast(rate.bytes)); +} + +BC_POP_WARNING() +BC_POP_WARNING() + +} // namespace node +} // namespace libbitcoin diff --git a/src/settings.cpp b/src/settings.cpp index e0748e5c..b2d77f27 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -80,6 +80,14 @@ size_t settings::maximum_concurrency_() const NOEXCEPT return to_bool(maximum_concurrency) ? maximum_concurrency : max_size_t; } +// The configured rate is btc/vkb, as reported to clients (see relayfee). +uint64_t settings::minimum_fee_rate_() const NOEXCEPT +{ + // Ceilinged, as a configured minimum is not rounded down. + return to_ceilinged_integer(minimum_fee_rate * + chain::satoshi_per_bitcoin); +} + size_t settings::fee_estimate_horizon_() const NOEXCEPT { return std::min(fee_estimate_horizon, estimator::maximum_horizon); diff --git a/test/settings.cpp b/test/settings.cpp index c4c6a4a0..063c3555 100644 --- a/test/settings.cpp +++ b/test/settings.cpp @@ -43,6 +43,7 @@ BOOST_AUTO_TEST_CASE(settings__node__default_context__expected) BOOST_REQUIRE_EQUAL(node.provide_filters, false); BOOST_REQUIRE_EQUAL(node.limited_blocks, false); BOOST_REQUIRE_EQUAL(node.minimum_fee_rate, 0.0); + BOOST_REQUIRE_EQUAL(node.minimum_fee_rate_(), 0_u64); BOOST_REQUIRE_EQUAL(node.minimum_bump_rate, 0.0); BOOST_REQUIRE_EQUAL(node.allowed_deviation, 1.5); BOOST_REQUIRE_EQUAL(node.batch_signatures, 0_u64);