mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-28 13:02:38 +02:00
Merge branch 'rbf_opts-25+knots' into maxscriptsize-25+knots
This commit is contained in:
commit
f7799cdded
15
configure.ac
15
configure.ac
@ -1061,6 +1061,21 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <malloc.h>]],
|
||||
[ AC_MSG_RESULT([no])]
|
||||
)
|
||||
|
||||
AC_MSG_CHECKING(for compatible sysinfo call)
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/sysinfo.h>]],
|
||||
[[
|
||||
struct sysinfo info;
|
||||
int rv = sysinfo(&info);
|
||||
unsigned long test = info.freeram + info.bufferram + info.mem_unit;
|
||||
]])],
|
||||
[
|
||||
AC_MSG_RESULT(yes);
|
||||
AC_DEFINE(HAVE_LINUX_SYSINFO, 1, [Define this symbol if you have a Linux-compatible sysinfo call])
|
||||
],[
|
||||
AC_MSG_RESULT(no)
|
||||
]
|
||||
)
|
||||
|
||||
dnl Check for posix_fallocate
|
||||
AC_MSG_CHECKING([for posix_fallocate])
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
|
||||
|
15
src/init.cpp
15
src/init.cpp
@ -441,6 +441,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
||||
argsman.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-loadblock=<file>", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-lowmem=<n>", strprintf("If system available memory falls below <n> MiB, flush caches (0 to disable, default: %s)", util::g_low_memory_threshold / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE_MB), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY_HOURS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
@ -586,7 +587,8 @@ void SetupServerArgs(ArgsManager& argsman)
|
||||
argsman.AddArg("-datacarriercost", strprintf("Treat extra data in transactions as at least N vbytes per actual byte (default: %s)", DEFAULT_WEIGHT_PER_DATA_BYTE / 4.0), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-datacarrierfullcount", strprintf("Apply datacarriersize limit to all known datacarrier methods (default: %s)", DEFAULT_DATACARRIER_FULLCOUNT), ArgsManager::ALLOW_ANY | (DEFAULT_DATACARRIER_FULLCOUNT ? uint32_t{ArgsManager::DEBUG_ONLY} : 0), OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", DEFAULT_MEMPOOL_FULL_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", (DEFAULT_MEMPOOL_RBF_POLICY == RBFPolicy::Always)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-mempoolreplacement", strprintf("Set to 0 to disable RBF entirely, \"fee,optin\" to honour RBF opt-out signal, or \"fee,-optin\" to always RBF aka full RBF (default: %s)", "fee,optin"), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY,
|
||||
OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
|
||||
@ -1488,6 +1490,17 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
|
||||
}
|
||||
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024));
|
||||
|
||||
if (gArgs.IsArgSet("-lowmem")) {
|
||||
util::g_low_memory_threshold = gArgs.GetIntArg("-lowmem", 0 /* not used */) * 1024 * 1024;
|
||||
}
|
||||
if (util::g_low_memory_threshold > 0) {
|
||||
LogPrintf("* Flushing caches if available system memory drops below %s MiB\n", util::g_low_memory_threshold / 1024 / 1024);
|
||||
}
|
||||
|
||||
if (mempool_opts.rbf_policy == RBFPolicy::Always) {
|
||||
nLocalServices = ServiceFlags(nLocalServices | NODE_REPLACE_BY_FEE);
|
||||
}
|
||||
|
||||
for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) {
|
||||
node.mempool = std::make_unique<CTxMemPool>(mempool_opts);
|
||||
|
||||
|
@ -16,14 +16,16 @@
|
||||
|
||||
class CBlockPolicyEstimator;
|
||||
|
||||
enum class RBFPolicy { Never, OptIn, Always };
|
||||
|
||||
/** Default for -maxmempool, maximum megabytes of mempool memory usage */
|
||||
static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300};
|
||||
/** Default for -maxmempool when blocksonly is set */
|
||||
static constexpr unsigned int DEFAULT_BLOCKSONLY_MAX_MEMPOOL_SIZE_MB{5};
|
||||
/** Default for -mempoolexpiry, expiration time for mempool transactions in hours */
|
||||
static constexpr unsigned int DEFAULT_MEMPOOL_EXPIRY_HOURS{336};
|
||||
/** Default for -mempoolfullrbf, if the transaction replaceability signaling is ignored */
|
||||
static constexpr bool DEFAULT_MEMPOOL_FULL_RBF{false};
|
||||
/** Default for -mempoolreplacement; must update docs in init.cpp manually */
|
||||
static constexpr RBFPolicy DEFAULT_MEMPOOL_RBF_POLICY{RBFPolicy::OptIn};
|
||||
|
||||
namespace kernel {
|
||||
/**
|
||||
@ -55,7 +57,7 @@ struct MemPoolOptions {
|
||||
bool datacarrier_fullcount{DEFAULT_DATACARRIER_FULLCOUNT};
|
||||
bool permit_bare_multisig{DEFAULT_PERMIT_BAREMULTISIG};
|
||||
bool require_standard{true};
|
||||
bool full_rbf{DEFAULT_MEMPOOL_FULL_RBF};
|
||||
RBFPolicy rbf_policy{DEFAULT_MEMPOOL_RBF_POLICY};
|
||||
MemPoolLimits limits{};
|
||||
};
|
||||
} // namespace kernel
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <consensus/amount.h>
|
||||
#include <kernel/chainparams.h>
|
||||
#include <logging.h>
|
||||
#include <node/interface_ui.h>
|
||||
#include <policy/feerate.h>
|
||||
#include <policy/policy.h>
|
||||
#include <script/standard.h>
|
||||
@ -93,7 +94,59 @@ std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, con
|
||||
return strprintf(Untranslated("acceptnonstdtxn is not currently supported for %s chain"), chainparams.NetworkIDString());
|
||||
}
|
||||
|
||||
mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf);
|
||||
if (argsman.IsArgSet("-mempoolreplacement") || argsman.IsArgSet("-mempoolfullrbf")) {
|
||||
// Generally, mempoolreplacement overrides mempoolfullrbf, but the latter is used to infer intent in some cases
|
||||
std::optional<bool> optin_flag;
|
||||
bool fee_flag{false};
|
||||
if (argsman.GetBoolArg("-mempoolreplacement", false)) {
|
||||
fee_flag = true;
|
||||
} else {
|
||||
for (auto& opt : SplitString(argsman.GetArg("-mempoolreplacement", ""), ",+")) {
|
||||
if (opt == "optin") {
|
||||
optin_flag = true;
|
||||
} else if (opt == "-optin") {
|
||||
optin_flag = false;
|
||||
} else if (opt == "fee") {
|
||||
fee_flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (optin_flag.value_or(false)) {
|
||||
// "optin" is explicitly specified
|
||||
mempool_opts.rbf_policy = RBFPolicy::OptIn;
|
||||
} else if (argsman.GetBoolArg("-mempoolfullrbf", false)) {
|
||||
const bool mempoolreplacement_false{argsman.IsArgSet("-mempoolreplacement") && !(fee_flag || optin_flag.has_value())};
|
||||
if (mempoolreplacement_false) {
|
||||
// This is a contradiction, but override rather than error
|
||||
InitWarning(_("False mempoolreplacement option contradicts true mempoolfullrbf; disallowing all RBF"));
|
||||
mempool_opts.rbf_policy = RBFPolicy::Never;
|
||||
} else {
|
||||
mempool_opts.rbf_policy = RBFPolicy::Always;
|
||||
}
|
||||
} else if (!optin_flag.value_or(true)) {
|
||||
// "-optin" is explicitly specified
|
||||
mempool_opts.rbf_policy = fee_flag ? RBFPolicy::Always : RBFPolicy::Never;
|
||||
} else if (fee_flag) {
|
||||
// Just "fee" by itself
|
||||
if (!argsman.GetBoolArg("-mempoolfullrbf", true)) {
|
||||
mempool_opts.rbf_policy = RBFPolicy::OptIn;
|
||||
} else {
|
||||
// Fallback to default, unless it's been changed to Never
|
||||
if (mempool_opts.rbf_policy == RBFPolicy::Never) {
|
||||
mempool_opts.rbf_policy = RBFPolicy::Always;
|
||||
}
|
||||
}
|
||||
} else if (!argsman.IsArgSet("-mempoolreplacement")) {
|
||||
// mempoolfullrbf is always explicitly false here
|
||||
// Fallback to default, as long as it isn't Always
|
||||
if (mempool_opts.rbf_policy == RBFPolicy::Always) {
|
||||
mempool_opts.rbf_policy = RBFPolicy::OptIn;
|
||||
}
|
||||
} else {
|
||||
// mempoolreplacement is explicitly false here
|
||||
mempool_opts.rbf_policy = RBFPolicy::Never;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyArgsManOptions(argsman, mempool_opts.limits);
|
||||
|
||||
|
@ -198,6 +198,7 @@ static std::string serviceFlagToStr(size_t bit)
|
||||
case NODE_WITNESS: return "WITNESS";
|
||||
case NODE_COMPACT_FILTERS: return "COMPACT_FILTERS";
|
||||
case NODE_NETWORK_LIMITED: return "NETWORK_LIMITED";
|
||||
case NODE_REPLACE_BY_FEE: return "REPLACE_BY_FEE?";
|
||||
// Not using default, so we get warned when a case is missing
|
||||
}
|
||||
|
||||
|
@ -299,6 +299,8 @@ enum ServiceFlags : uint64_t {
|
||||
// collisions and other cases where nodes may be advertising a service they
|
||||
// do not actually support. Other service bits should be allocated via the
|
||||
// BIP process.
|
||||
|
||||
NODE_REPLACE_BY_FEE = (1 << 26),
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -793,7 +793,12 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool, const std::optional<MempoolHi
|
||||
ret.pushKV("minrelaytxfee", ValueFromAmount(pool.m_min_relay_feerate.GetFeePerK()));
|
||||
ret.pushKV("incrementalrelayfee", ValueFromAmount(pool.m_incremental_relay_feerate.GetFeePerK()));
|
||||
ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
|
||||
ret.pushKV("fullrbf", pool.m_full_rbf);
|
||||
ret.pushKV("fullrbf", (pool.m_rbf_policy == RBFPolicy::Always));
|
||||
switch (pool.m_rbf_policy) {
|
||||
case RBFPolicy::Never : ret.pushKV("rbf_policy", "never"); break;
|
||||
case RBFPolicy::OptIn : ret.pushKV("rbf_policy", "optin"); break;
|
||||
case RBFPolicy::Always: ret.pushKV("rbf_policy", "always"); break;
|
||||
}
|
||||
|
||||
if (histogram_floors) {
|
||||
const MempoolHistogramFeeRates& floors{histogram_floors.value()};
|
||||
@ -876,6 +881,7 @@ static RPCHelpMan getmempoolinfo()
|
||||
{RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or replacement in " + CURRENCY_UNIT + "/kvB"},
|
||||
{RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"},
|
||||
{RPCResult::Type::BOOL, "fullrbf", "True if the mempool accepts RBF without replaceability signaling inspection"},
|
||||
{RPCResult::Type::STR, "rbf_policy", "Policy used for replacing conflicting transactions by fee (one of: never, optin, always)"},
|
||||
{RPCResult::Type::OBJ_DYN, "fee_histogram", /*optional=*/true, "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "<fee_rate_group>", "Fee rate group named by its lower bound (in " + CURRENCY_ATOM + "/vB), identical to the \"from_feerate\" field below",
|
||||
|
@ -24,6 +24,8 @@ BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, ChainTestingSetup)
|
||||
//!
|
||||
BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches)
|
||||
{
|
||||
util::g_low_memory_threshold = 0; // disable to get deterministic flushing
|
||||
|
||||
ChainstateManager& manager = *Assert(m_node.chainman);
|
||||
CTxMemPool& mempool = *Assert(m_node.mempool);
|
||||
Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool));
|
||||
|
@ -412,7 +412,7 @@ CTxMemPool::CTxMemPool(const Options& opts)
|
||||
m_max_datacarrier_bytes{opts.max_datacarrier_bytes},
|
||||
m_datacarrier_fullcount{opts.datacarrier_fullcount},
|
||||
m_require_standard{opts.require_standard},
|
||||
m_full_rbf{opts.full_rbf},
|
||||
m_rbf_policy{opts.rbf_policy},
|
||||
m_limits{opts.limits}
|
||||
{
|
||||
}
|
||||
|
@ -464,7 +464,7 @@ public:
|
||||
const std::optional<unsigned> m_max_datacarrier_bytes;
|
||||
bool m_datacarrier_fullcount;
|
||||
const bool m_require_standard;
|
||||
const bool m_full_rbf;
|
||||
const RBFPolicy m_rbf_policy;
|
||||
|
||||
const Limits m_limits;
|
||||
|
||||
|
@ -42,6 +42,10 @@
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LINUX_SYSINFO
|
||||
#include <sys/sysinfo.h>
|
||||
#endif
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
#include <fstream>
|
||||
@ -1149,4 +1153,41 @@ std::pair<int, char**> WinCmdLineArgs::get()
|
||||
return std::make_pair(argc, argv);
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t g_low_memory_threshold = 10 * 1024 * 1024 /* 10 MB */;
|
||||
|
||||
bool SystemNeedsMemoryReleased()
|
||||
{
|
||||
if (g_low_memory_threshold <= 0) {
|
||||
// Intentionally bypass other metrics when disabled entirely
|
||||
return false;
|
||||
}
|
||||
#ifdef WIN32
|
||||
MEMORYSTATUSEX mem_status;
|
||||
mem_status.dwLength = sizeof(mem_status);
|
||||
if (GlobalMemoryStatusEx(&mem_status)) {
|
||||
if (mem_status.dwMemoryLoad >= 99 ||
|
||||
mem_status.ullAvailPhys < g_low_memory_threshold ||
|
||||
mem_status.ullAvailVirtual < g_low_memory_threshold) {
|
||||
LogPrintf("%s: YES: %s%% memory load; %s available physical memory; %s available virtual memory\n", __func__, int(mem_status.dwMemoryLoad), size_t(mem_status.ullAvailPhys), size_t(mem_status.ullAvailVirtual));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LINUX_SYSINFO
|
||||
struct sysinfo sys_info;
|
||||
if (!sysinfo(&sys_info)) {
|
||||
// Explicitly 64-bit in case of 32-bit userspace on 64-bit kernel
|
||||
const uint64_t free_ram = uint64_t(sys_info.freeram) * sys_info.mem_unit;
|
||||
const uint64_t buffer_ram = uint64_t(sys_info.bufferram) * sys_info.mem_unit;
|
||||
if (free_ram + buffer_ram < g_low_memory_threshold) {
|
||||
LogPrintf("%s: YES: %s free RAM + %s buffer RAM\n", __func__, free_ram, buffer_ram);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// NOTE: sysconf(_SC_AVPHYS_PAGES) doesn't account for caches on at least Linux, so not safe to use here
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
@ -522,6 +522,10 @@ private:
|
||||
};
|
||||
#endif
|
||||
|
||||
extern size_t g_low_memory_threshold;
|
||||
|
||||
bool SystemNeedsMemoryReleased();
|
||||
|
||||
} // namespace util
|
||||
|
||||
#endif // BITCOIN_UTIL_SYSTEM_H
|
||||
|
@ -802,7 +802,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
//
|
||||
// If replaceability signaling is ignored due to node setting,
|
||||
// replacement is always allowed.
|
||||
if (!(m_pool.m_full_rbf || ignore_rejects.count("txn-mempool-conflict")) && !SignalsOptInRBF(*ptxConflicting)) {
|
||||
bool allow_replacement;
|
||||
if (m_pool.m_rbf_policy == RBFPolicy::Always || ignore_rejects.count("txn-mempool-conflict")) {
|
||||
allow_replacement = true;
|
||||
} else if (m_pool.m_rbf_policy == RBFPolicy::Never) {
|
||||
allow_replacement = false;
|
||||
} else {
|
||||
allow_replacement = SignalsOptInRBF(*ptxConflicting);
|
||||
}
|
||||
if (!allow_replacement) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
|
||||
}
|
||||
|
||||
@ -2585,8 +2593,15 @@ bool Chainstate::FlushStateToDisk(
|
||||
}
|
||||
// The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing).
|
||||
bool fCacheLarge = mode == FlushStateMode::PERIODIC && cache_state >= CoinsCacheSizeState::LARGE;
|
||||
// The cache is over the limit, we have to write now.
|
||||
bool fCacheCritical = mode == FlushStateMode::IF_NEEDED && cache_state >= CoinsCacheSizeState::CRITICAL;
|
||||
bool fCacheCritical = false;
|
||||
if (mode == FlushStateMode::IF_NEEDED) {
|
||||
if (cache_state >= CoinsCacheSizeState::CRITICAL) {
|
||||
// The cache is over the limit, we have to write now.
|
||||
fCacheCritical = true;
|
||||
} else if (util::SystemNeedsMemoryReleased()) {
|
||||
fCacheCritical = true;
|
||||
}
|
||||
}
|
||||
// It's been a while since we wrote the block index to disk. Do this frequently, so we don't need to redownload after a crash.
|
||||
bool fPeriodicWrite = mode == FlushStateMode::PERIODIC && nNow > m_last_write + DATABASE_WRITE_INTERVAL;
|
||||
// It's been very long since we flushed the cache. Do this infrequently, to optimize cache usage.
|
||||
|
@ -766,6 +766,9 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
|
||||
//! rescanning where new transactions in new blocks could be lost.
|
||||
BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
|
||||
{
|
||||
// FIXME: this test fails for some reason if there's a flush
|
||||
util::g_low_memory_threshold = 0;
|
||||
|
||||
m_args.ForceSetArg("-unsafesqlitesync", "1");
|
||||
// Create new wallet with known key and unload it.
|
||||
WalletContext context;
|
||||
|
@ -25,7 +25,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
self.add_wallet_options(parser)
|
||||
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 2
|
||||
self.num_nodes = 4
|
||||
self.extra_args = [
|
||||
[
|
||||
"-maxorphantx=1000",
|
||||
@ -37,12 +37,40 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
# second node has default mempool parameters
|
||||
[
|
||||
],
|
||||
[
|
||||
"-acceptnonstdtxn=1",
|
||||
"-mempoolreplacement=0",
|
||||
],
|
||||
]
|
||||
self.extra_args.append(
|
||||
[
|
||||
*self.extra_args[0],
|
||||
"-mempoolreplacement=fee,-optin",
|
||||
],
|
||||
)
|
||||
self.supports_cli = False
|
||||
|
||||
def run_test(self):
|
||||
self.wallet = MiniWallet(self.nodes[0])
|
||||
|
||||
self.log.info("Running test RPC rbf_policy")
|
||||
def test_rpc_rbf_policy():
|
||||
assert_equal(self.nodes[0].getmempoolinfo()["rbf_policy"], 'optin')
|
||||
assert_equal(self.nodes[1].getmempoolinfo()["rbf_policy"], 'optin')
|
||||
assert_equal(self.nodes[2].getmempoolinfo()["rbf_policy"], 'never')
|
||||
assert_equal(self.nodes[3].getmempoolinfo()["rbf_policy"], 'always')
|
||||
test_rpc_rbf_policy()
|
||||
|
||||
self.log.info("Running test service flag")
|
||||
def test_service_flag():
|
||||
NODE_REPLACE_BY_FEE = (1 << 26)
|
||||
for i in range(3):
|
||||
assert not (int(self.nodes[i].getnetworkinfo()['localservices'], 0x10) & NODE_REPLACE_BY_FEE)
|
||||
assert 'REPLACE_BY_FEE?' not in self.nodes[i].getnetworkinfo()['localservicesnames']
|
||||
assert int(self.nodes[3].getnetworkinfo()['localservices'], 0x10) & NODE_REPLACE_BY_FEE
|
||||
assert 'REPLACE_BY_FEE?' in self.nodes[3].getnetworkinfo()['localservicesnames']
|
||||
test_service_flag()
|
||||
|
||||
self.log.info("Running test simple doublespend...")
|
||||
self.test_simple_doublespend()
|
||||
|
||||
@ -68,7 +96,10 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
self.test_too_many_replacements_with_default_mempool_params()
|
||||
|
||||
self.log.info("Running test opt-in...")
|
||||
self.test_opt_in()
|
||||
self.test_opt_in(fullrbf=False)
|
||||
self.nodes[0], self.nodes[-1] = self.nodes[-1], self.nodes[0]
|
||||
self.test_opt_in(fullrbf=True)
|
||||
self.nodes[0], self.nodes[-1] = self.nodes[-1], self.nodes[0]
|
||||
|
||||
self.log.info("Running test RPC...")
|
||||
self.test_rpc()
|
||||
@ -112,17 +143,24 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
# we use MiniWallet to create a transaction template with inputs correctly set,
|
||||
# and modify the output (amount, scriptPubKey) according to our needs
|
||||
tx = self.wallet.create_self_transfer()["tx"]
|
||||
tx1a_txid = self.nodes[0].sendrawtransaction(tx.serialize().hex())
|
||||
tx1a_hex = tx.serialize().hex()
|
||||
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex)
|
||||
assert_equal(tx1a_txid, self.nodes[2].sendrawtransaction(tx1a_hex))
|
||||
|
||||
# Should fail because we haven't changed the fee
|
||||
tx.vout[0].scriptPubKey[-1] ^= 1
|
||||
|
||||
# This will raise an exception due to insufficient fee
|
||||
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex(), 0)
|
||||
tx1b_hex = tx.serialize().hex()
|
||||
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
|
||||
# This will raise an exception due to transaction replacement being disabled
|
||||
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[2].sendrawtransaction, tx1b_hex, 0)
|
||||
|
||||
# Extra 0.1 BTC fee
|
||||
tx.vout[0].nValue -= int(0.1 * COIN)
|
||||
tx1b_hex = tx.serialize().hex()
|
||||
# Replacement still disabled even with "enough fee"
|
||||
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[2].sendrawtransaction, tx1b_hex, 0)
|
||||
# Works when enabled
|
||||
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
|
||||
|
||||
@ -133,6 +171,11 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
|
||||
assert_equal(tx1b_hex, self.nodes[0].getrawtransaction(tx1b_txid))
|
||||
|
||||
# Third node is running mempoolreplacement=0, will not replace originally-seen txn
|
||||
mempool = self.nodes[2].getrawmempool()
|
||||
assert tx1a_txid in mempool
|
||||
assert tx1b_txid not in mempool
|
||||
|
||||
def test_doublespend_chain(self):
|
||||
"""Doublespend of a long chain"""
|
||||
|
||||
@ -468,7 +511,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
self.generate(normal_node, 1)
|
||||
self.wallet.rescan_utxos()
|
||||
|
||||
def test_opt_in(self):
|
||||
def test_opt_in(self, fullrbf):
|
||||
"""Replacing should only work if orig tx opted in"""
|
||||
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
|
||||
|
||||
@ -484,14 +527,19 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[0].getmempoolentry(tx1a_utxo["txid"])['bip125-replaceable'], False)
|
||||
|
||||
# Shouldn't be able to double-spend
|
||||
tx1b_hex = self.wallet.create_self_transfer(
|
||||
tx1b_st = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=tx0_outpoint,
|
||||
sequence=0,
|
||||
fee=Decimal("0.2"),
|
||||
)["hex"]
|
||||
)
|
||||
tx1b_hex = tx1b_st["hex"]
|
||||
|
||||
# This will raise an exception
|
||||
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
|
||||
if fullrbf:
|
||||
self.nodes[0].sendrawtransaction(tx1b_hex, 0)
|
||||
tx1a_utxo = tx1b_st["new_utxo"]
|
||||
else:
|
||||
# This will raise an exception
|
||||
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
|
||||
|
||||
tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
|
||||
|
||||
@ -504,14 +552,19 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
)["new_utxo"]
|
||||
|
||||
# Still shouldn't be able to double-spend
|
||||
tx2b_hex = self.wallet.create_self_transfer(
|
||||
tx2b_st = self.wallet.create_self_transfer(
|
||||
utxo_to_spend=tx1_outpoint,
|
||||
sequence=0,
|
||||
fee=Decimal("0.2"),
|
||||
)["hex"]
|
||||
)
|
||||
tx2b_hex = tx2b_st["hex"]
|
||||
|
||||
# This will raise an exception
|
||||
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
|
||||
if fullrbf:
|
||||
self.nodes[0].sendrawtransaction(tx2b_hex, 0)
|
||||
tx2a_utxo = tx2b_st["new_utxo"]
|
||||
else:
|
||||
# This will raise an exception
|
||||
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
|
||||
|
||||
# Now create a new transaction that spends from tx1a and tx2a
|
||||
# opt-in on one of the inputs
|
||||
@ -543,6 +596,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
fee=Decimal("0.4"),
|
||||
)
|
||||
|
||||
self.generate(self.nodes[0], 1) # clean mempool
|
||||
|
||||
def test_prioritised_transactions(self):
|
||||
# Ensure that fee deltas used via prioritisetransaction are
|
||||
# correctly used by replacement logic
|
||||
@ -704,6 +759,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
|
||||
confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN))
|
||||
self.restart_node(0, extra_args=["-mempoolfullrbf=1"])
|
||||
assert self.nodes[0].getmempoolinfo()["fullrbf"]
|
||||
assert_equal(self.nodes[0].getmempoolinfo()["rbf_policy"], 'always')
|
||||
|
||||
# Create an explicitly opt-out transaction
|
||||
optout_tx = self.wallet.send_self_transfer(
|
||||
|
Loading…
Reference in New Issue
Block a user