Merge branch 'rbf_opts-25+knots' into maxscriptsize-25+knots

This commit is contained in:
Luke Dashjr 2023-11-13 00:09:35 +00:00
commit f7799cdded
15 changed files with 237 additions and 24 deletions

View File

@ -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([[

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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
}

View File

@ -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),
};
/**

View File

@ -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",

View File

@ -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));

View File

@ -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}
{
}

View File

@ -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;

View File

@ -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

View File

@ -522,6 +522,10 @@ private:
};
#endif
extern size_t g_low_memory_threshold;
bool SystemNeedsMemoryReleased();
} // namespace util
#endif // BITCOIN_UTIL_SYSTEM_H

View File

@ -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.

View File

@ -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;

View File

@ -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(