mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-09 11:27:28 -03:00
Merge bitcoin/bitcoin#31122: cluster mempool: Implement changeset interface for mempool
5736d1ddac
tracing: pass if replaced by tx/pkg to tracepoint (0xb10c)a4ec07f194
doc: add comments for CTxMemPool::ChangeSet (Suhas Daftuar)83f814b1d1
Remove m_all_conflicts from SubPackageState (Suhas Daftuar)d3c8e7dfb6
Ensure that we don't add duplicate transactions in rbf fuzz tests (Suhas Daftuar)d7dc9fd2f7
Move CalculateChunksForRBF() to the mempool changeset (Suhas Daftuar)284a1d33f1
Move prioritisation into changeset (Suhas Daftuar)446b08b599
Don't distinguish between direct conflicts and all conflicts when doing cluster-size-2-rbf checks (Suhas Daftuar)b53041021a
Duplicate transactions are not permitted within a changeset (Suhas Daftuar)b447416fdd
Public mempool removal methods Assume() no changeset is outstanding (Suhas Daftuar)2b30f4d36c
Make RemoveStaged() private (Suhas Daftuar)18829194ca
Enforce that there is only one changeset at a time (Suhas Daftuar)7fb62f7db6
Apply mempool changeset transactions directly into the mempool (Suhas Daftuar)34b6c5833d
Clean up FinalizeSubpackage to avoid workspace-specific information (Suhas Daftuar)57983b8add
Move LimitMempoolSize to take place outside FinalizeSubpackage (Suhas Daftuar)01e145b975
Move changeset from workspace to subpackage (Suhas Daftuar)802214c083
Introduce mempool changesets (Suhas Daftuar)87d92fa340
test: Add unit test coverage of package rbf + prioritisetransaction (Suhas Daftuar)15d982f91e
Add package hash to package-rbf log message (Suhas Daftuar) Pull request description: part of cluster mempool: #30289 It became clear while working on cluster mempool that it would be helpful for transaction validation if we could consider a full set of proposed changes to the mempool -- consisting of a set of transactions to add, and a set of transactions (ie conflicts) to simultaneously remove -- and perform calculations on what the mempool would look like if the proposed changes were to be applied. Two specific examples of where we'd like to do this: - Determining if ancestor/descendant/TRUC limits would be violated (in the future, cluster limits) if either a single transaction or a package of transactions were to be accepted - Determining if an RBF would make the mempool "better", however that idea is defined, both in the single transaction and package of transaction cases In preparation for cluster mempool, I have pulled this reworking of the mempool interface out of #28676 so it can be reviewed on its own. I have not re-implemented ancestor/descendant limits to be run through the changeset, since with cluster mempool those limits will be going away, so this seems like wasted effort. However, I have rebased #28676 on top of this branch so reviewers can see what the new mempool interface could look like in the cluster mempool setting. There are some minor behavior changes here, which I believe are inconsequential: - In the package validation setting, transactions would be added to the mempool before the `ConsensusScriptChecks()` are run. In theory, `ConsensusScriptChecks()` should always pass if the `PolicyScriptChecks()` have passed and it's just a belt-and-suspenders for us, but if somehow they were to diverge then there could be some small behavior change from adding transactions and then removing them, versus never adding them at all. - The error reporting on `CheckConflictTopology()` has slightly changed due to no longer distinguishing between direct conflicts and indirect conflicts. I believe this should be entirely inconsequential because there shouldn't be a logical difference between those two ideas from the perspective of this function, but I did have to update some error strings in some tests. - Because, in a package setting, RBFs now happen as part of the entire package being accepted, the logging has changed slightly because we do not know which transaction specifically evicted a given removed transaction. - Specifically, the "package hash" is now used to reference the set of transactions that are being accepted, rather than any single txid. The log message relating to package RBF that happen in the `TXPACKAGES` category has been updated as well to include the package hash, so that it's possible to see which specific set of transactions are being referenced by that package hash. - Relatedly, the tracepoint logging in the package rbf case has been updated as well to reference the package hash, rather than a transaction hash. ACKs for top commit: naumenkogs: ACK5736d1ddac
instagibbs: ACK5736d1ddac
ismaelsadeeq: reACK5736d1ddac
glozow: ACK5736d1ddac
Tree-SHA512: 21810872e082920d337c89ac406085aa71c5f8e5151ab07aedf41e6601f60a909b22fbf462ef3b735d5d5881e9b76142c53957158e674dd5dfe6f6aabbdf630b
This commit is contained in:
commit
f34fe0806a
26 changed files with 697 additions and 394 deletions
|
@ -245,14 +245,15 @@ Arguments passed:
|
|||
2. Replaced transaction virtual size as `int32`
|
||||
3. Replaced transaction fee as `int64`
|
||||
4. Replaced transaction mempool entry time (epoch) as `uint64`
|
||||
5. Replacement transaction ID (hash) as `pointer to unsigned chars` (i.e. 32 bytes in little-endian)
|
||||
5. Replacement transaction ID or package hash as `pointer to unsigned chars` (i.e. 32 bytes in little-endian)
|
||||
6. Replacement transaction virtual size as `int32`
|
||||
7. Replacement transaction fee as `int64`
|
||||
8. `bool` indicating if the argument 5. is a transaction ID or package hash (true if it's a transaction ID)
|
||||
|
||||
Note: In cases where a single replacement transaction replaces multiple
|
||||
Note: In cases where a replacement transaction or package replaces multiple
|
||||
existing transactions in the mempool, the tracepoint is called once for each
|
||||
replaced transaction, with data of the replacement transaction being the same
|
||||
in each call.
|
||||
replaced transaction, with data of the replacement transaction or package
|
||||
being the same in each call.
|
||||
|
||||
#### Tracepoint `mempool:rejected`
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <script/script.h>
|
||||
#include <sync.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <test/util/txmempool.h>
|
||||
#include <txmempool.h>
|
||||
#include <util/check.h>
|
||||
|
||||
|
@ -28,7 +29,7 @@ static void AddTx(const CTransactionRef& tx, CTxMemPool& pool) EXCLUSIVE_LOCKS_R
|
|||
unsigned int sigOpCost{4};
|
||||
uint64_t fee{0};
|
||||
LockPoints lp;
|
||||
pool.addUnchecked(CTxMemPoolEntry(
|
||||
AddToMempool(pool, CTxMemPoolEntry(
|
||||
tx, fee, nTime, nHeight, sequence,
|
||||
spendsCoinbase, sigOpCost, lp));
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <script/script.h>
|
||||
#include <sync.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <test/util/txmempool.h>
|
||||
#include <txmempool.h>
|
||||
#include <util/check.h>
|
||||
|
||||
|
@ -26,7 +27,7 @@ static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& po
|
|||
bool spendsCoinbase = false;
|
||||
unsigned int sigOpCost = 4;
|
||||
LockPoints lp;
|
||||
pool.addUnchecked(CTxMemPoolEntry(
|
||||
AddToMempool(pool, CTxMemPoolEntry(
|
||||
tx, nFee, nTime, nHeight, sequence,
|
||||
spendsCoinbase, sigOpCost, lp));
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <script/script.h>
|
||||
#include <sync.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <test/util/txmempool.h>
|
||||
#include <txmempool.h>
|
||||
#include <validation.h>
|
||||
|
||||
|
@ -28,7 +29,7 @@ static void AddTx(const CTransactionRef& tx, CTxMemPool& pool) EXCLUSIVE_LOCKS_R
|
|||
bool spendsCoinbase = false;
|
||||
unsigned int sigOpCost = 4;
|
||||
LockPoints lp;
|
||||
pool.addUnchecked(CTxMemPoolEntry(tx, 1000, nTime, nHeight, sequence, spendsCoinbase, sigOpCost, lp));
|
||||
AddToMempool(pool, CTxMemPoolEntry(tx, 1000, nTime, nHeight, sequence, spendsCoinbase, sigOpCost, lp));
|
||||
}
|
||||
|
||||
struct Available {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <script/script.h>
|
||||
#include <sync.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <test/util/txmempool.h>
|
||||
#include <txmempool.h>
|
||||
#include <univalue.h>
|
||||
#include <util/check.h>
|
||||
|
@ -21,7 +22,7 @@
|
|||
static void AddTx(const CTransactionRef& tx, const CAmount& fee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs)
|
||||
{
|
||||
LockPoints lp;
|
||||
pool.addUnchecked(CTxMemPoolEntry(tx, fee, /*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0, /*spends_coinbase=*/false, /*sigops_cost=*/4, lp));
|
||||
AddToMempool(pool, CTxMemPoolEntry(tx, fee, /*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0, /*spends_coinbase=*/false, /*sigops_cost=*/4, lp));
|
||||
}
|
||||
|
||||
static void RpcMempool(benchmark::Bench& bench)
|
||||
|
|
|
@ -184,14 +184,10 @@ std::optional<std::string> PaysForRBF(CAmount original_fees,
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::pair<DiagramCheckError, std::string>> ImprovesFeerateDiagram(CTxMemPool& pool,
|
||||
const CTxMemPool::setEntries& direct_conflicts,
|
||||
const CTxMemPool::setEntries& all_conflicts,
|
||||
CAmount replacement_fees,
|
||||
int64_t replacement_vsize)
|
||||
std::optional<std::pair<DiagramCheckError, std::string>> ImprovesFeerateDiagram(CTxMemPool::ChangeSet& changeset)
|
||||
{
|
||||
// Require that the replacement strictly improves the mempool's feerate diagram.
|
||||
const auto chunk_results{pool.CalculateChunksForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
|
||||
const auto chunk_results{changeset.CalculateChunksForRBF()};
|
||||
|
||||
if (!chunk_results.has_value()) {
|
||||
return std::make_pair(DiagramCheckError::UNCALCULABLE, util::ErrorString(chunk_results).original);
|
||||
|
|
|
@ -117,19 +117,9 @@ std::optional<std::string> PaysForRBF(CAmount original_fees,
|
|||
|
||||
/**
|
||||
* The replacement transaction must improve the feerate diagram of the mempool.
|
||||
* @param[in] pool The mempool.
|
||||
* @param[in] direct_conflicts Set of in-mempool txids corresponding to the direct conflicts i.e.
|
||||
* input double-spends with the proposed transaction
|
||||
* @param[in] all_conflicts Set of mempool entries corresponding to all transactions to be evicted
|
||||
* @param[in] replacement_fees Fees of proposed replacement package
|
||||
* @param[in] replacement_vsize Size of proposed replacement package
|
||||
* @param[in] changeset The changeset containing proposed additions/removals
|
||||
* @returns error type and string if mempool diagram doesn't improve, otherwise std::nullopt.
|
||||
*/
|
||||
std::optional<std::pair<DiagramCheckError, std::string>> ImprovesFeerateDiagram(CTxMemPool& pool,
|
||||
const CTxMemPool::setEntries& direct_conflicts,
|
||||
const CTxMemPool::setEntries& all_conflicts,
|
||||
CAmount replacement_fees,
|
||||
int64_t replacement_vsize)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
|
||||
std::optional<std::pair<DiagramCheckError, std::string>> ImprovesFeerateDiagram(CTxMemPool::ChangeSet& changeset);
|
||||
|
||||
#endif // BITCOIN_POLICY_RBF_H
|
||||
|
|
|
@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
|
|||
CBlock block(BuildBlockTestCase(rand_ctx));
|
||||
|
||||
LOCK2(cs_main, pool.cs);
|
||||
pool.addUnchecked(entry.FromTx(block.vtx[2]));
|
||||
AddToMempool(pool, entry.FromTx(block.vtx[2]));
|
||||
BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
|
||||
|
||||
// Do a simple ShortTxIDs RT
|
||||
|
@ -151,7 +151,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
|
|||
CBlock block(BuildBlockTestCase(rand_ctx));
|
||||
|
||||
LOCK2(cs_main, pool.cs);
|
||||
pool.addUnchecked(entry.FromTx(block.vtx[2]));
|
||||
AddToMempool(pool, entry.FromTx(block.vtx[2]));
|
||||
BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
|
||||
|
||||
uint256 txhash;
|
||||
|
@ -222,7 +222,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
|
|||
CBlock block(BuildBlockTestCase(rand_ctx));
|
||||
|
||||
LOCK2(cs_main, pool.cs);
|
||||
pool.addUnchecked(entry.FromTx(block.vtx[1]));
|
||||
AddToMempool(pool, entry.FromTx(block.vtx[1]));
|
||||
BOOST_CHECK_EQUAL(pool.get(block.vtx[1]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
|
||||
|
||||
uint256 txhash;
|
||||
|
@ -322,7 +322,7 @@ BOOST_AUTO_TEST_CASE(ReceiveWithExtraTransactions) {
|
|||
extra_txn.resize(10);
|
||||
|
||||
LOCK2(cs_main, pool.cs);
|
||||
pool.addUnchecked(entry.FromTx(block.vtx[2]));
|
||||
AddToMempool(pool, entry.FromTx(block.vtx[2]));
|
||||
BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
|
||||
// Ensure the non_block_tx is actually not in the block
|
||||
for (const auto &block_tx : block.vtx) {
|
||||
|
|
|
@ -59,7 +59,7 @@ FUZZ_TARGET(mini_miner, .init = initialize_miner)
|
|||
TestMemPoolEntryHelper entry;
|
||||
const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
|
||||
assert(MoneyRange(fee));
|
||||
pool.addUnchecked(entry.Fee(fee).FromTx(tx));
|
||||
AddToMempool(pool, entry.Fee(fee).FromTx(tx));
|
||||
|
||||
// All outputs are available to spend
|
||||
for (uint32_t n{0}; n < num_outputs; ++n) {
|
||||
|
@ -154,7 +154,7 @@ FUZZ_TARGET(mini_miner_selection, .init = initialize_miner)
|
|||
TestMemPoolEntryHelper entry;
|
||||
const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
|
||||
assert(MoneyRange(fee));
|
||||
pool.addUnchecked(entry.Fee(fee).FromTx(tx));
|
||||
AddToMempool(pool, entry.Fee(fee).FromTx(tx));
|
||||
transactions.push_back(tx);
|
||||
}
|
||||
std::vector<COutPoint> outpoints;
|
||||
|
|
|
@ -78,7 +78,7 @@ FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb)
|
|||
|
||||
if (add_to_mempool && !pool.exists(GenTxid::Txid(tx->GetHash()))) {
|
||||
LOCK2(cs_main, pool.cs);
|
||||
pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, *tx));
|
||||
AddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, *tx));
|
||||
available.insert(i);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,12 +73,16 @@ FUZZ_TARGET(rbf, .init = initialize_rbf)
|
|||
mtx->vin[0].prevout = COutPoint{another_tx.GetHash(), 0};
|
||||
}
|
||||
LOCK2(cs_main, pool.cs);
|
||||
pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx));
|
||||
if (!pool.GetIter(another_tx.GetHash())) {
|
||||
AddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx));
|
||||
}
|
||||
}
|
||||
const CTransaction tx{*mtx};
|
||||
if (fuzzed_data_provider.ConsumeBool()) {
|
||||
LOCK2(cs_main, pool.cs);
|
||||
pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx));
|
||||
if (!pool.GetIter(tx.GetHash())) {
|
||||
AddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, tx));
|
||||
}
|
||||
}
|
||||
{
|
||||
LOCK(pool.cs);
|
||||
|
@ -104,10 +108,18 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
|
|||
std::vector<CTransaction> mempool_txs;
|
||||
size_t iter{0};
|
||||
|
||||
int32_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(1, 1000000);
|
||||
|
||||
// Keep track of the total vsize of CTxMemPoolEntry's being added to the mempool to avoid overflow
|
||||
// Add replacement_vsize since this is added to new diagram during RBF check
|
||||
std::optional<CMutableTransaction> replacement_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
|
||||
if (!replacement_tx) {
|
||||
return;
|
||||
}
|
||||
assert(iter <= g_outpoints.size());
|
||||
replacement_tx->vin.resize(1);
|
||||
replacement_tx->vin[0].prevout = g_outpoints[iter++];
|
||||
CTransaction replacement_tx_final{*replacement_tx};
|
||||
auto replacement_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, replacement_tx_final);
|
||||
int32_t replacement_vsize = replacement_entry.GetTxSize();
|
||||
int64_t running_vsize_total{replacement_vsize};
|
||||
|
||||
LOCK2(cs_main, pool.cs);
|
||||
|
@ -129,7 +141,8 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
|
|||
mempool_txs.pop_back();
|
||||
break;
|
||||
}
|
||||
pool.addUnchecked(parent_entry);
|
||||
assert(!pool.GetIter(parent_entry.GetTx().GetHash()));
|
||||
AddToMempool(pool, parent_entry);
|
||||
if (fuzzed_data_provider.ConsumeBool()) {
|
||||
child.vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0};
|
||||
}
|
||||
|
@ -141,7 +154,9 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
|
|||
mempool_txs.pop_back();
|
||||
break;
|
||||
}
|
||||
pool.addUnchecked(child_entry);
|
||||
if (!pool.GetIter(child_entry.GetTx().GetHash())) {
|
||||
AddToMempool(pool, child_entry);
|
||||
}
|
||||
|
||||
if (fuzzed_data_provider.ConsumeBool()) {
|
||||
pool.PrioritiseTransaction(mempool_txs.back().GetHash().ToUint256(), fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(-100000, 100000));
|
||||
|
@ -162,9 +177,17 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
|
|||
pool.CalculateDescendants(txiter, all_conflicts);
|
||||
}
|
||||
|
||||
// Calculate the chunks for a replacement.
|
||||
CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);
|
||||
auto calc_results{pool.CalculateChunksForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
for (auto& txiter : all_conflicts) {
|
||||
changeset->StageRemoval(txiter);
|
||||
}
|
||||
changeset->StageAddition(replacement_entry.GetSharedTx(), replacement_fees,
|
||||
replacement_entry.GetTime().count(), replacement_entry.GetHeight(),
|
||||
replacement_entry.GetSequence(), replacement_entry.GetSpendsCoinbase(),
|
||||
replacement_entry.GetSigOpCost(), replacement_entry.GetLockPoints());
|
||||
// Calculate the chunks for a replacement.
|
||||
auto calc_results{changeset->CalculateChunksForRBF()};
|
||||
|
||||
if (calc_results.has_value()) {
|
||||
// Sanity checks on the chunks.
|
||||
|
@ -192,7 +215,7 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
|
|||
}
|
||||
|
||||
// If internals report error, wrapper should too
|
||||
auto err_tuple{ImprovesFeerateDiagram(pool, direct_conflicts, all_conflicts, replacement_fees, replacement_vsize)};
|
||||
auto err_tuple{ImprovesFeerateDiagram(*changeset)};
|
||||
if (!calc_results.has_value()) {
|
||||
assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);
|
||||
} else {
|
||||
|
|
|
@ -72,17 +72,17 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
|
|||
BOOST_CHECK_EQUAL(testPool.size(), poolSize);
|
||||
|
||||
// Just the parent:
|
||||
testPool.addUnchecked(entry.FromTx(txParent));
|
||||
AddToMempool(testPool, entry.FromTx(txParent));
|
||||
poolSize = testPool.size();
|
||||
testPool.removeRecursive(CTransaction(txParent), REMOVAL_REASON_DUMMY);
|
||||
BOOST_CHECK_EQUAL(testPool.size(), poolSize - 1);
|
||||
|
||||
// Parent, children, grandchildren:
|
||||
testPool.addUnchecked(entry.FromTx(txParent));
|
||||
AddToMempool(testPool, entry.FromTx(txParent));
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
testPool.addUnchecked(entry.FromTx(txChild[i]));
|
||||
testPool.addUnchecked(entry.FromTx(txGrandChild[i]));
|
||||
AddToMempool(testPool, entry.FromTx(txChild[i]));
|
||||
AddToMempool(testPool, entry.FromTx(txGrandChild[i]));
|
||||
}
|
||||
// Remove Child[0], GrandChild[0] should be removed:
|
||||
poolSize = testPool.size();
|
||||
|
@ -104,8 +104,8 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
|
|||
// Add children and grandchildren, but NOT the parent (simulate the parent being in a block)
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
testPool.addUnchecked(entry.FromTx(txChild[i]));
|
||||
testPool.addUnchecked(entry.FromTx(txGrandChild[i]));
|
||||
AddToMempool(testPool, entry.FromTx(txChild[i]));
|
||||
AddToMempool(testPool, entry.FromTx(txGrandChild[i]));
|
||||
}
|
||||
// Now remove the parent, as might happen if a block-re-org occurs but the parent cannot be
|
||||
// put into the mempool (maybe because it is non-standard):
|
||||
|
@ -137,28 +137,28 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||
tx1.vout.resize(1);
|
||||
tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx1.vout[0].nValue = 10 * COIN;
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tx1));
|
||||
|
||||
/* highest fee */
|
||||
CMutableTransaction tx2 = CMutableTransaction();
|
||||
tx2.vout.resize(1);
|
||||
tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx2.vout[0].nValue = 2 * COIN;
|
||||
pool.addUnchecked(entry.Fee(20000LL).FromTx(tx2));
|
||||
AddToMempool(pool, entry.Fee(20000LL).FromTx(tx2));
|
||||
|
||||
/* lowest fee */
|
||||
CMutableTransaction tx3 = CMutableTransaction();
|
||||
tx3.vout.resize(1);
|
||||
tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx3.vout[0].nValue = 5 * COIN;
|
||||
pool.addUnchecked(entry.Fee(0LL).FromTx(tx3));
|
||||
AddToMempool(pool, entry.Fee(0LL).FromTx(tx3));
|
||||
|
||||
/* 2nd highest fee */
|
||||
CMutableTransaction tx4 = CMutableTransaction();
|
||||
tx4.vout.resize(1);
|
||||
tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx4.vout[0].nValue = 6 * COIN;
|
||||
pool.addUnchecked(entry.Fee(15000LL).FromTx(tx4));
|
||||
AddToMempool(pool, entry.Fee(15000LL).FromTx(tx4));
|
||||
|
||||
/* equal fee rate to tx1, but newer */
|
||||
CMutableTransaction tx5 = CMutableTransaction();
|
||||
|
@ -166,7 +166,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||
tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx5.vout[0].nValue = 11 * COIN;
|
||||
entry.time = NodeSeconds{1s};
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx5));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tx5));
|
||||
BOOST_CHECK_EQUAL(pool.size(), 5U);
|
||||
|
||||
std::vector<std::string> sortedOrder;
|
||||
|
@ -184,7 +184,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||
tx6.vout.resize(1);
|
||||
tx6.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx6.vout[0].nValue = 20 * COIN;
|
||||
pool.addUnchecked(entry.Fee(0LL).FromTx(tx6));
|
||||
AddToMempool(pool, entry.Fee(0LL).FromTx(tx6));
|
||||
BOOST_CHECK_EQUAL(pool.size(), 6U);
|
||||
// Check that at this point, tx6 is sorted low
|
||||
sortedOrder.insert(sortedOrder.begin(), tx6.GetHash().ToString());
|
||||
|
@ -208,7 +208,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||
BOOST_CHECK(*ancestors_calculated == setAncestors);
|
||||
}
|
||||
|
||||
pool.addUnchecked(entry.FromTx(tx7), setAncestors);
|
||||
AddToMempool(pool, entry.FromTx(tx7));
|
||||
BOOST_CHECK_EQUAL(pool.size(), 7U);
|
||||
|
||||
// Now tx6 should be sorted higher (high fee child): tx7, tx6, tx2, ...
|
||||
|
@ -226,7 +226,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||
tx8.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx8.vout[0].nValue = 10 * COIN;
|
||||
setAncestors.insert(pool.GetIter(tx7.GetHash()).value());
|
||||
pool.addUnchecked(entry.Fee(0LL).Time(NodeSeconds{2s}).FromTx(tx8), setAncestors);
|
||||
AddToMempool(pool, entry.Fee(0LL).Time(NodeSeconds{2s}).FromTx(tx8));
|
||||
|
||||
// Now tx8 should be sorted low, but tx6/tx both high
|
||||
sortedOrder.insert(sortedOrder.begin(), tx8.GetHash().ToString());
|
||||
|
@ -240,7 +240,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||
tx9.vout.resize(1);
|
||||
tx9.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx9.vout[0].nValue = 1 * COIN;
|
||||
pool.addUnchecked(entry.Fee(0LL).Time(NodeSeconds{3s}).FromTx(tx9), setAncestors);
|
||||
AddToMempool(pool, entry.Fee(0LL).Time(NodeSeconds{3s}).FromTx(tx9));
|
||||
|
||||
// tx9 should be sorted low
|
||||
BOOST_CHECK_EQUAL(pool.size(), 9U);
|
||||
|
@ -268,7 +268,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||
BOOST_CHECK(*ancestors_calculated == setAncestors);
|
||||
}
|
||||
|
||||
pool.addUnchecked(entry.FromTx(tx10), setAncestors);
|
||||
AddToMempool(pool, entry.FromTx(tx10));
|
||||
|
||||
/**
|
||||
* tx8 and tx9 should both now be sorted higher
|
||||
|
@ -313,14 +313,14 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
|
|||
tx1.vout.resize(1);
|
||||
tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx1.vout[0].nValue = 10 * COIN;
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tx1));
|
||||
|
||||
/* highest fee */
|
||||
CMutableTransaction tx2 = CMutableTransaction();
|
||||
tx2.vout.resize(1);
|
||||
tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx2.vout[0].nValue = 2 * COIN;
|
||||
pool.addUnchecked(entry.Fee(20000LL).FromTx(tx2));
|
||||
AddToMempool(pool, entry.Fee(20000LL).FromTx(tx2));
|
||||
uint64_t tx2Size = GetVirtualTransactionSize(CTransaction(tx2));
|
||||
|
||||
/* lowest fee */
|
||||
|
@ -328,21 +328,21 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
|
|||
tx3.vout.resize(1);
|
||||
tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx3.vout[0].nValue = 5 * COIN;
|
||||
pool.addUnchecked(entry.Fee(0LL).FromTx(tx3));
|
||||
AddToMempool(pool, entry.Fee(0LL).FromTx(tx3));
|
||||
|
||||
/* 2nd highest fee */
|
||||
CMutableTransaction tx4 = CMutableTransaction();
|
||||
tx4.vout.resize(1);
|
||||
tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx4.vout[0].nValue = 6 * COIN;
|
||||
pool.addUnchecked(entry.Fee(15000LL).FromTx(tx4));
|
||||
AddToMempool(pool, entry.Fee(15000LL).FromTx(tx4));
|
||||
|
||||
/* equal fee rate to tx1, but newer */
|
||||
CMutableTransaction tx5 = CMutableTransaction();
|
||||
tx5.vout.resize(1);
|
||||
tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx5.vout[0].nValue = 11 * COIN;
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx5));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tx5));
|
||||
BOOST_CHECK_EQUAL(pool.size(), 5U);
|
||||
|
||||
std::vector<std::string> sortedOrder;
|
||||
|
@ -371,7 +371,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
|
|||
tx6.vout[0].nValue = 20 * COIN;
|
||||
uint64_t tx6Size = GetVirtualTransactionSize(CTransaction(tx6));
|
||||
|
||||
pool.addUnchecked(entry.Fee(0LL).FromTx(tx6));
|
||||
AddToMempool(pool, entry.Fee(0LL).FromTx(tx6));
|
||||
BOOST_CHECK_EQUAL(pool.size(), 6U);
|
||||
// Ties are broken by hash
|
||||
if (tx3.GetHash() < tx6.GetHash())
|
||||
|
@ -393,7 +393,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
|
|||
/* set the fee to just below tx2's feerate when including ancestor */
|
||||
CAmount fee = (20000/tx2Size)*(tx7Size + tx6Size) - 1;
|
||||
|
||||
pool.addUnchecked(entry.Fee(fee).FromTx(tx7));
|
||||
AddToMempool(pool, entry.Fee(fee).FromTx(tx7));
|
||||
BOOST_CHECK_EQUAL(pool.size(), 7U);
|
||||
sortedOrder.insert(sortedOrder.begin()+1, tx7.GetHash().ToString());
|
||||
CheckSort<ancestor_score>(pool, sortedOrder);
|
||||
|
@ -425,7 +425,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
|
|||
// Check that we sort by min(feerate, ancestor_feerate):
|
||||
// set the fee so that the ancestor feerate is above tx1/5,
|
||||
// but the transaction's own feerate is lower
|
||||
pool.addUnchecked(entry.Fee(5000LL).FromTx(tx8));
|
||||
AddToMempool(pool, entry.Fee(5000LL).FromTx(tx8));
|
||||
sortedOrder.insert(sortedOrder.end()-1, tx8.GetHash().ToString());
|
||||
CheckSort<ancestor_score>(pool, sortedOrder);
|
||||
}
|
||||
|
@ -443,7 +443,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
|||
tx1.vout.resize(1);
|
||||
tx1.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL;
|
||||
tx1.vout[0].nValue = 10 * COIN;
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tx1));
|
||||
|
||||
CMutableTransaction tx2 = CMutableTransaction();
|
||||
tx2.vin.resize(1);
|
||||
|
@ -451,7 +451,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
|||
tx2.vout.resize(1);
|
||||
tx2.vout[0].scriptPubKey = CScript() << OP_2 << OP_EQUAL;
|
||||
tx2.vout[0].nValue = 10 * COIN;
|
||||
pool.addUnchecked(entry.Fee(5000LL).FromTx(tx2));
|
||||
AddToMempool(pool, entry.Fee(5000LL).FromTx(tx2));
|
||||
|
||||
pool.TrimToSize(pool.DynamicMemoryUsage()); // should do nothing
|
||||
BOOST_CHECK(pool.exists(GenTxid::Txid(tx1.GetHash())));
|
||||
|
@ -461,7 +461,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
|||
BOOST_CHECK(pool.exists(GenTxid::Txid(tx1.GetHash())));
|
||||
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx2.GetHash())));
|
||||
|
||||
pool.addUnchecked(entry.FromTx(tx2));
|
||||
AddToMempool(pool, entry.FromTx(tx2));
|
||||
CMutableTransaction tx3 = CMutableTransaction();
|
||||
tx3.vin.resize(1);
|
||||
tx3.vin[0].prevout = COutPoint(tx2.GetHash(), 0);
|
||||
|
@ -469,7 +469,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
|||
tx3.vout.resize(1);
|
||||
tx3.vout[0].scriptPubKey = CScript() << OP_3 << OP_EQUAL;
|
||||
tx3.vout[0].nValue = 10 * COIN;
|
||||
pool.addUnchecked(entry.Fee(20000LL).FromTx(tx3));
|
||||
AddToMempool(pool, entry.Fee(20000LL).FromTx(tx3));
|
||||
|
||||
pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); // tx3 should pay for tx2 (CPFP)
|
||||
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx1.GetHash())));
|
||||
|
@ -532,10 +532,10 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
|||
tx7.vout[1].scriptPubKey = CScript() << OP_7 << OP_EQUAL;
|
||||
tx7.vout[1].nValue = 10 * COIN;
|
||||
|
||||
pool.addUnchecked(entry.Fee(7000LL).FromTx(tx4));
|
||||
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
|
||||
pool.addUnchecked(entry.Fee(1100LL).FromTx(tx6));
|
||||
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
|
||||
AddToMempool(pool, entry.Fee(7000LL).FromTx(tx4));
|
||||
AddToMempool(pool, entry.Fee(1000LL).FromTx(tx5));
|
||||
AddToMempool(pool, entry.Fee(1100LL).FromTx(tx6));
|
||||
AddToMempool(pool, entry.Fee(9000LL).FromTx(tx7));
|
||||
|
||||
// we only require this to remove, at max, 2 txn, because it's not clear what we're really optimizing for aside from that
|
||||
pool.TrimToSize(pool.DynamicMemoryUsage() - 1);
|
||||
|
@ -544,8 +544,8 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
|||
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx7.GetHash())));
|
||||
|
||||
if (!pool.exists(GenTxid::Txid(tx5.GetHash())))
|
||||
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
|
||||
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
|
||||
AddToMempool(pool, entry.Fee(1000LL).FromTx(tx5));
|
||||
AddToMempool(pool, entry.Fee(9000LL).FromTx(tx7));
|
||||
|
||||
pool.TrimToSize(pool.DynamicMemoryUsage() / 2); // should maximize mempool size by only removing 5/7
|
||||
BOOST_CHECK(pool.exists(GenTxid::Txid(tx4.GetHash())));
|
||||
|
@ -553,8 +553,8 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
|||
BOOST_CHECK(pool.exists(GenTxid::Txid(tx6.GetHash())));
|
||||
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx7.GetHash())));
|
||||
|
||||
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
|
||||
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
|
||||
AddToMempool(pool, entry.Fee(1000LL).FromTx(tx5));
|
||||
AddToMempool(pool, entry.Fee(9000LL).FromTx(tx7));
|
||||
|
||||
std::vector<CTransactionRef> vtx;
|
||||
SetMockTime(42);
|
||||
|
@ -613,7 +613,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests)
|
|||
// [tx1]
|
||||
//
|
||||
CTransactionRef tx1 = make_tx(/*output_values=*/{10 * COIN});
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tx1));
|
||||
|
||||
// Ancestors / descendants should be 1 / 1 (itself / itself)
|
||||
pool.GetTransactionAncestry(tx1->GetHash(), ancestors, descendants);
|
||||
|
@ -625,7 +625,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests)
|
|||
// [tx1].0 <- [tx2]
|
||||
//
|
||||
CTransactionRef tx2 = make_tx(/*output_values=*/{495 * CENT, 5 * COIN}, /*inputs=*/{tx1});
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx2));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tx2));
|
||||
|
||||
// Ancestors / descendants should be:
|
||||
// transaction ancestors descendants
|
||||
|
@ -644,7 +644,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests)
|
|||
// [tx1].0 <- [tx2].0 <- [tx3]
|
||||
//
|
||||
CTransactionRef tx3 = make_tx(/*output_values=*/{290 * CENT, 200 * CENT}, /*inputs=*/{tx2});
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx3));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tx3));
|
||||
|
||||
// Ancestors / descendants should be:
|
||||
// transaction ancestors descendants
|
||||
|
@ -669,7 +669,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests)
|
|||
// \---1 <- [tx4]
|
||||
//
|
||||
CTransactionRef tx4 = make_tx(/*output_values=*/{290 * CENT, 250 * CENT}, /*inputs=*/{tx2}, /*input_indices=*/{1});
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx4));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tx4));
|
||||
|
||||
// Ancestors / descendants should be:
|
||||
// transaction ancestors descendants
|
||||
|
@ -706,13 +706,13 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests)
|
|||
CTransactionRef& tyi = *ty[i];
|
||||
tyi = make_tx(/*output_values=*/{v}, /*inputs=*/i > 0 ? std::vector<CTransactionRef>{*ty[i - 1]} : std::vector<CTransactionRef>{});
|
||||
v -= 50 * CENT;
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tyi));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tyi));
|
||||
pool.GetTransactionAncestry(tyi->GetHash(), ancestors, descendants);
|
||||
BOOST_CHECK_EQUAL(ancestors, i+1);
|
||||
BOOST_CHECK_EQUAL(descendants, i+1);
|
||||
}
|
||||
CTransactionRef ty6 = make_tx(/*output_values=*/{5 * COIN}, /*inputs=*/{tx3, ty5});
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(ty6));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(ty6));
|
||||
|
||||
// Ancestors / descendants should be:
|
||||
// transaction ancestors descendants
|
||||
|
@ -778,10 +778,10 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTestsDiamond)
|
|||
tb = make_tx(/*output_values=*/{5 * COIN, 3 * COIN}, /*inputs=*/ {ta});
|
||||
tc = make_tx(/*output_values=*/{2 * COIN}, /*inputs=*/{tb}, /*input_indices=*/{1});
|
||||
td = make_tx(/*output_values=*/{6 * COIN}, /*inputs=*/{tb, tc}, /*input_indices=*/{0, 0});
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(ta));
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tb));
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tc));
|
||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(td));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(ta));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tb));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(tc));
|
||||
AddToMempool(pool, entry.Fee(10000LL).FromTx(td));
|
||||
|
||||
// Ancestors / descendants should be:
|
||||
// transaction ancestors descendants
|
||||
|
|
|
@ -123,19 +123,19 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
|||
tx.vout[0].nValue = 5000000000LL - 1000;
|
||||
// This tx has a low fee: 1000 satoshis
|
||||
Txid hashParentTx = tx.GetHash(); // save this txid for later use
|
||||
tx_mempool.addUnchecked(entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
|
||||
// This tx has a medium fee: 10000 satoshis
|
||||
tx.vin[0].prevout.hash = txFirst[1]->GetHash();
|
||||
tx.vout[0].nValue = 5000000000LL - 10000;
|
||||
Txid hashMediumFeeTx = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
|
||||
// This tx has a high fee, but depends on the first transaction
|
||||
tx.vin[0].prevout.hash = hashParentTx;
|
||||
tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 50k satoshi fee
|
||||
Txid hashHighFeeTx = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
|
||||
std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
|
||||
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 4U);
|
||||
|
@ -147,7 +147,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
|||
tx.vin[0].prevout.hash = hashHighFeeTx;
|
||||
tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee
|
||||
Txid hashFreeTx = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(0).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(0).FromTx(tx));
|
||||
size_t freeTxSize = ::GetSerializeSize(TX_WITH_WITNESS(tx));
|
||||
|
||||
// Calculate a fee on child transaction that will put the package just
|
||||
|
@ -157,7 +157,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
|||
tx.vin[0].prevout.hash = hashFreeTx;
|
||||
tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse;
|
||||
Txid hashLowFeeTx = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(feeToUse).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(feeToUse).FromTx(tx));
|
||||
pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
|
||||
// Verify that the free tx and the low fee tx didn't get selected
|
||||
for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) {
|
||||
|
@ -171,7 +171,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
|||
tx_mempool.removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED);
|
||||
tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee
|
||||
hashLowFeeTx = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(feeToUse + 2).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(feeToUse + 2).FromTx(tx));
|
||||
pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
|
||||
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U);
|
||||
BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashFreeTx);
|
||||
|
@ -185,7 +185,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
|||
tx.vout[0].nValue = 5000000000LL - 100000000;
|
||||
tx.vout[1].nValue = 100000000; // 1BTC output
|
||||
Txid hashFreeTx2 = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
|
||||
|
||||
// This tx can't be mined by itself
|
||||
tx.vin[0].prevout.hash = hashFreeTx2;
|
||||
|
@ -193,7 +193,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
|||
feeToUse = blockMinFeeRate.GetFee(freeTxSize);
|
||||
tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse;
|
||||
Txid hashLowFeeTx2 = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx));
|
||||
pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
|
||||
|
||||
// Verify that this tx isn't selected.
|
||||
|
@ -206,7 +206,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
|||
// as well.
|
||||
tx.vin[0].prevout.n = 1;
|
||||
tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee
|
||||
tx_mempool.addUnchecked(entry.Fee(10000).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(10000).FromTx(tx));
|
||||
pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
|
||||
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 9U);
|
||||
BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2);
|
||||
|
@ -246,7 +246,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
hash = tx.GetHash();
|
||||
bool spendsCoinbase = i == 0; // only first tx spends coinbase
|
||||
// If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails
|
||||
tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
|
||||
tx.vin[0].prevout.hash = hash;
|
||||
}
|
||||
|
||||
|
@ -264,7 +264,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
hash = tx.GetHash();
|
||||
bool spendsCoinbase = i == 0; // only first tx spends coinbase
|
||||
// If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes
|
||||
tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx));
|
||||
tx.vin[0].prevout.hash = hash;
|
||||
}
|
||||
BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
|
||||
|
@ -288,7 +288,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
tx.vout[0].nValue -= LOWFEE;
|
||||
hash = tx.GetHash();
|
||||
bool spendsCoinbase = i == 0; // only first tx spends coinbase
|
||||
tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
|
||||
tx.vin[0].prevout.hash = hash;
|
||||
}
|
||||
BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
|
||||
|
@ -300,7 +300,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
|
||||
// orphan in tx_mempool, template creation fails
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).FromTx(tx));
|
||||
BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
|
||||
}
|
||||
|
||||
|
@ -313,7 +313,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
tx.vin[0].prevout.hash = txFirst[1]->GetHash();
|
||||
tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE;
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
tx.vin[0].prevout.hash = hash;
|
||||
tx.vin.resize(2);
|
||||
tx.vin[1].scriptSig = CScript() << OP_1;
|
||||
|
@ -321,7 +321,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
tx.vin[1].prevout.n = 0;
|
||||
tx.vout[0].nValue = tx.vout[0].nValue + BLOCKSUBSIDY - HIGHERFEE; // First txn output + fresh coinbase - new txn fee
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(HIGHERFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(HIGHERFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey));
|
||||
}
|
||||
|
||||
|
@ -336,7 +336,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
tx.vout[0].nValue = 0;
|
||||
hash = tx.GetHash();
|
||||
// give it a fee so it'll get mined
|
||||
tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
// Should throw bad-cb-multiple
|
||||
BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple"));
|
||||
}
|
||||
|
@ -351,10 +351,10 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE;
|
||||
tx.vout[0].scriptPubKey = CScript() << OP_1;
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
tx.vout[0].scriptPubKey = CScript() << OP_2;
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent"));
|
||||
}
|
||||
|
||||
|
@ -397,12 +397,12 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
CScript script = CScript() << OP_0;
|
||||
tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script));
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
tx.vin[0].prevout.hash = hash;
|
||||
tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end());
|
||||
tx.vout[0].nValue -= LOWFEE;
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
// Should throw block-validation-failed
|
||||
BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed"));
|
||||
|
||||
|
@ -439,7 +439,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
tx.vout[0].scriptPubKey = CScript() << OP_1;
|
||||
tx.nLockTime = 0;
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(HIGHFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes
|
||||
BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail
|
||||
|
||||
|
@ -453,7 +453,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1-m_node.chainman->ActiveChain()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block
|
||||
prevheights[0] = baseheight + 2;
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Time(Now<NodeSeconds>()).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Time(Now<NodeSeconds>()).FromTx(tx));
|
||||
BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes
|
||||
BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail
|
||||
|
||||
|
@ -476,7 +476,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
prevheights[0] = baseheight + 3;
|
||||
tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1;
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Time(Now<NodeSeconds>()).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Time(Now<NodeSeconds>()).FromTx(tx));
|
||||
BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails
|
||||
BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass
|
||||
BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block
|
||||
|
@ -487,7 +487,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
|
|||
prevheights.resize(1);
|
||||
prevheights[0] = baseheight + 4;
|
||||
hash = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Time(Now<NodeSeconds>()).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Time(Now<NodeSeconds>()).FromTx(tx));
|
||||
BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails
|
||||
BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass
|
||||
BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later
|
||||
|
@ -542,7 +542,7 @@ void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const
|
|||
tx.vout.resize(1);
|
||||
tx.vout[0].nValue = 5000000000LL; // 0 fee
|
||||
uint256 hashFreePrioritisedTx = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(0).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(0).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
tx_mempool.PrioritiseTransaction(hashFreePrioritisedTx, 5 * COIN);
|
||||
|
||||
tx.vin[0].prevout.hash = txFirst[1]->GetHash();
|
||||
|
@ -550,20 +550,20 @@ void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const
|
|||
tx.vout[0].nValue = 5000000000LL - 1000;
|
||||
// This tx has a low fee: 1000 satoshis
|
||||
Txid hashParentTx = tx.GetHash(); // save this txid for later use
|
||||
tx_mempool.addUnchecked(entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
|
||||
// This tx has a medium fee: 10000 satoshis
|
||||
tx.vin[0].prevout.hash = txFirst[2]->GetHash();
|
||||
tx.vout[0].nValue = 5000000000LL - 10000;
|
||||
Txid hashMediumFeeTx = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
|
||||
tx_mempool.PrioritiseTransaction(hashMediumFeeTx, -5 * COIN);
|
||||
|
||||
// This tx also has a low fee, but is prioritised
|
||||
tx.vin[0].prevout.hash = hashParentTx;
|
||||
tx.vout[0].nValue = 5000000000LL - 1000 - 1000; // 1000 satoshi fee
|
||||
Txid hashPrioritsedChild = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
|
||||
tx_mempool.PrioritiseTransaction(hashPrioritsedChild, 2 * COIN);
|
||||
|
||||
// Test that transaction selection properly updates ancestor fee calculations as prioritised
|
||||
|
@ -575,19 +575,19 @@ void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const
|
|||
tx.vin[0].prevout.hash = txFirst[3]->GetHash();
|
||||
tx.vout[0].nValue = 5000000000LL; // 0 fee
|
||||
Txid hashFreeParent = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
|
||||
tx_mempool.PrioritiseTransaction(hashFreeParent, 10 * COIN);
|
||||
|
||||
tx.vin[0].prevout.hash = hashFreeParent;
|
||||
tx.vout[0].nValue = 5000000000LL; // 0 fee
|
||||
Txid hashFreeChild = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
|
||||
tx_mempool.PrioritiseTransaction(hashFreeChild, 1 * COIN);
|
||||
|
||||
tx.vin[0].prevout.hash = hashFreeChild;
|
||||
tx.vout[0].nValue = 5000000000LL; // 0 fee
|
||||
Txid hashFreeGrandchild = tx.GetHash();
|
||||
tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
|
||||
AddToMempool(tx_mempool, entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
|
||||
|
||||
auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey);
|
||||
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U);
|
||||
|
|
|
@ -84,7 +84,7 @@ BOOST_FIXTURE_TEST_CASE(miniminer_negative, TestChain100Setup)
|
|||
const CAmount negative_modified_fees{positive_base_fee + negative_fee_delta};
|
||||
BOOST_CHECK(negative_modified_fees < 0);
|
||||
const auto tx_mod_negative = make_tx({COutPoint{m_coinbase_txns[4]->GetHash(), 0}}, /*num_outputs=*/1);
|
||||
pool.addUnchecked(entry.Fee(positive_base_fee).FromTx(tx_mod_negative));
|
||||
AddToMempool(pool, entry.Fee(positive_base_fee).FromTx(tx_mod_negative));
|
||||
pool.PrioritiseTransaction(tx_mod_negative->GetHash(), negative_fee_delta);
|
||||
const COutPoint only_outpoint{tx_mod_negative->GetHash(), 0};
|
||||
|
||||
|
@ -114,21 +114,21 @@ BOOST_FIXTURE_TEST_CASE(miniminer_1p1c, TestChain100Setup)
|
|||
|
||||
// Create a parent tx0 and child tx1 with normal fees:
|
||||
const auto tx0 = make_tx({COutPoint{m_coinbase_txns[0]->GetHash(), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(med_fee).FromTx(tx0));
|
||||
AddToMempool(pool, entry.Fee(med_fee).FromTx(tx0));
|
||||
const auto tx1 = make_tx({COutPoint{tx0->GetHash(), 0}}, /*num_outputs=*/1);
|
||||
pool.addUnchecked(entry.Fee(med_fee).FromTx(tx1));
|
||||
AddToMempool(pool, entry.Fee(med_fee).FromTx(tx1));
|
||||
|
||||
// Create a low-feerate parent tx2 and high-feerate child tx3 (cpfp)
|
||||
const auto tx2 = make_tx({COutPoint{m_coinbase_txns[1]->GetHash(), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx2));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx2));
|
||||
const auto tx3 = make_tx({COutPoint{tx2->GetHash(), 0}}, /*num_outputs=*/1);
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx3));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(tx3));
|
||||
|
||||
// Create a parent tx4 and child tx5 where both have low fees
|
||||
const auto tx4 = make_tx({COutPoint{m_coinbase_txns[2]->GetHash(), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx4));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx4));
|
||||
const auto tx5 = make_tx({COutPoint{tx4->GetHash(), 0}}, /*num_outputs=*/1);
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx5));
|
||||
const CAmount tx5_delta{CENT/100};
|
||||
// Make tx5's modified fee much higher than its base fee. This should cause it to pass
|
||||
// the fee-related checks despite being low-feerate.
|
||||
|
@ -137,9 +137,9 @@ BOOST_FIXTURE_TEST_CASE(miniminer_1p1c, TestChain100Setup)
|
|||
|
||||
// Create a high-feerate parent tx6, low-feerate child tx7
|
||||
const auto tx6 = make_tx({COutPoint{m_coinbase_txns[3]->GetHash(), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx6));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(tx6));
|
||||
const auto tx7 = make_tx({COutPoint{tx6->GetHash(), 0}}, /*num_outputs=*/1);
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx7));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx7));
|
||||
|
||||
std::vector<COutPoint> all_unspent_outpoints({
|
||||
COutPoint{tx0->GetHash(), 1},
|
||||
|
@ -405,23 +405,23 @@ BOOST_FIXTURE_TEST_CASE(miniminer_overlap, TestChain100Setup)
|
|||
|
||||
// Create 3 parents of different feerates, and 1 child spending outputs from all 3 parents.
|
||||
const auto tx0 = make_tx({COutPoint{m_coinbase_txns[0]->GetHash(), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx0));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx0));
|
||||
const auto tx1 = make_tx({COutPoint{m_coinbase_txns[1]->GetHash(), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(med_fee).FromTx(tx1));
|
||||
AddToMempool(pool, entry.Fee(med_fee).FromTx(tx1));
|
||||
const auto tx2 = make_tx({COutPoint{m_coinbase_txns[2]->GetHash(), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx2));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(tx2));
|
||||
const auto tx3 = make_tx({COutPoint{tx0->GetHash(), 0}, COutPoint{tx1->GetHash(), 0}, COutPoint{tx2->GetHash(), 0}}, /*num_outputs=*/3);
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx3));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(tx3));
|
||||
|
||||
// Create 1 grandparent and 1 parent, then 2 children.
|
||||
const auto tx4 = make_tx({COutPoint{m_coinbase_txns[3]->GetHash(), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx4));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(tx4));
|
||||
const auto tx5 = make_tx({COutPoint{tx4->GetHash(), 0}}, /*num_outputs=*/3);
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx5));
|
||||
const auto tx6 = make_tx({COutPoint{tx5->GetHash(), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(med_fee).FromTx(tx6));
|
||||
AddToMempool(pool, entry.Fee(med_fee).FromTx(tx6));
|
||||
const auto tx7 = make_tx({COutPoint{tx5->GetHash(), 1}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx7));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(tx7));
|
||||
|
||||
std::vector<CTransactionRef> all_transactions{tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7};
|
||||
std::vector<int64_t> tx_vsizes;
|
||||
|
@ -604,7 +604,7 @@ BOOST_FIXTURE_TEST_CASE(calculate_cluster, TestChain100Setup)
|
|||
auto& lasttx = m_coinbase_txns[0];
|
||||
for (auto i{0}; i < 500; ++i) {
|
||||
const auto tx = make_tx({COutPoint{lasttx->GetHash(), 0}}, /*num_outputs=*/1);
|
||||
pool.addUnchecked(entry.Fee(CENT).FromTx(tx));
|
||||
AddToMempool(pool, entry.Fee(CENT).FromTx(tx));
|
||||
chain_txids.push_back(tx->GetHash());
|
||||
lasttx = tx;
|
||||
}
|
||||
|
@ -616,7 +616,7 @@ BOOST_FIXTURE_TEST_CASE(calculate_cluster, TestChain100Setup)
|
|||
|
||||
// GatherClusters stops at 500 transactions.
|
||||
const auto tx_501 = make_tx({COutPoint{lasttx->GetHash(), 0}}, /*num_outputs=*/1);
|
||||
pool.addUnchecked(entry.Fee(CENT).FromTx(tx_501));
|
||||
AddToMempool(pool, entry.Fee(CENT).FromTx(tx_501));
|
||||
const auto cluster_501 = pool.GatherClusters({tx_501->GetHash()});
|
||||
BOOST_CHECK_EQUAL(cluster_501.size(), 0);
|
||||
|
||||
|
@ -629,12 +629,12 @@ BOOST_FIXTURE_TEST_CASE(calculate_cluster, TestChain100Setup)
|
|||
std::vector<Txid> zigzag_txids;
|
||||
for (auto p{0}; p < 50; ++p) {
|
||||
const auto txp = make_tx({COutPoint{Txid::FromUint256(GetRandHash()), 0}}, /*num_outputs=*/2);
|
||||
pool.addUnchecked(entry.Fee(CENT).FromTx(txp));
|
||||
AddToMempool(pool, entry.Fee(CENT).FromTx(txp));
|
||||
zigzag_txids.push_back(txp->GetHash());
|
||||
}
|
||||
for (auto c{0}; c < 49; ++c) {
|
||||
const auto txc = make_tx({COutPoint{zigzag_txids[c], 1}, COutPoint{zigzag_txids[c+1], 0}}, /*num_outputs=*/1);
|
||||
pool.addUnchecked(entry.Fee(CENT).FromTx(txc));
|
||||
AddToMempool(pool, entry.Fee(CENT).FromTx(txc));
|
||||
zigzag_txids.push_back(txc->GetHash());
|
||||
}
|
||||
const auto vec_iters_zigzag = pool.GetIterVec(convert_to_uint256_vec(zigzag_txids));
|
||||
|
|
|
@ -63,9 +63,9 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
|
|||
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique
|
||||
{
|
||||
LOCK2(cs_main, mpool.cs);
|
||||
mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
|
||||
AddToMempool(mpool, entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
|
||||
// Since TransactionAddedToMempool callbacks are generated in ATMP,
|
||||
// not addUnchecked, we cheat and create one manually here
|
||||
// not AddToMempool, we cheat and create one manually here
|
||||
const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
|
||||
const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
|
||||
feeV[j],
|
||||
|
@ -164,9 +164,9 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
|
|||
tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
|
||||
{
|
||||
LOCK2(cs_main, mpool.cs);
|
||||
mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
|
||||
AddToMempool(mpool, entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
|
||||
// Since TransactionAddedToMempool callbacks are generated in ATMP,
|
||||
// not addUnchecked, we cheat and create one manually here
|
||||
// not AddToMempool, we cheat and create one manually here
|
||||
const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
|
||||
const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
|
||||
feeV[j],
|
||||
|
@ -228,9 +228,9 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
|
|||
tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
|
||||
{
|
||||
LOCK2(cs_main, mpool.cs);
|
||||
mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
|
||||
AddToMempool(mpool, entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
|
||||
// Since TransactionAddedToMempool callbacks are generated in ATMP,
|
||||
// not addUnchecked, we cheat and create one manually here
|
||||
// not AddToMempool, we cheat and create one manually here
|
||||
const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
|
||||
const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
|
||||
feeV[j],
|
||||
|
|
|
@ -78,7 +78,7 @@ static CTransactionRef add_descendants(const CTransactionRef& tx, int32_t num_de
|
|||
auto tx_to_spend = tx;
|
||||
for (int32_t i{0}; i < num_descendants; ++i) {
|
||||
auto next_tx = make_tx(/*inputs=*/{tx_to_spend}, /*output_values=*/{(50 - i) * CENT});
|
||||
pool.addUnchecked(entry.FromTx(next_tx));
|
||||
AddToMempool(pool, entry.FromTx(next_tx));
|
||||
tx_to_spend = next_tx;
|
||||
}
|
||||
// Return last created tx
|
||||
|
@ -93,7 +93,7 @@ static CTransactionRef add_descendant_to_parents(const std::vector<CTransactionR
|
|||
TestMemPoolEntryHelper entry;
|
||||
// Assumes this isn't already spent in mempool
|
||||
auto child_tx = make_tx(/*inputs=*/parents, /*output_values=*/{50 * CENT});
|
||||
pool.addUnchecked(entry.FromTx(child_tx));
|
||||
AddToMempool(pool, entry.FromTx(child_tx));
|
||||
// Return last created tx
|
||||
return child_tx;
|
||||
}
|
||||
|
@ -107,8 +107,8 @@ static std::pair<CTransactionRef, CTransactionRef> add_children_to_parent(const
|
|||
TestMemPoolEntryHelper entry;
|
||||
// Assumes this isn't already spent in mempool
|
||||
auto children_tx = make_two_siblings(/*parent=*/parent, /*output_values=*/{50 * CENT});
|
||||
pool.addUnchecked(entry.FromTx(children_tx.first));
|
||||
pool.addUnchecked(entry.FromTx(children_tx.second));
|
||||
AddToMempool(pool, entry.FromTx(children_tx.first));
|
||||
AddToMempool(pool, entry.FromTx(children_tx.second));
|
||||
return children_tx;
|
||||
}
|
||||
|
||||
|
@ -124,46 +124,46 @@ BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
|
|||
|
||||
// Create a parent tx1 and child tx2 with normal fees:
|
||||
const auto tx1 = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx1));
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(tx1));
|
||||
const auto tx2 = make_tx(/*inputs=*/ {tx1}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx2));
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(tx2));
|
||||
|
||||
// Create a low-feerate parent tx3 and high-feerate child tx4 (cpfp)
|
||||
const auto tx3 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {1099 * CENT});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx3));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx3));
|
||||
const auto tx4 = make_tx(/*inputs=*/ {tx3}, /*output_values=*/ {999 * CENT});
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx4));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(tx4));
|
||||
|
||||
// Create a parent tx5 and child tx6 where both have very low fees
|
||||
const auto tx5 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {1099 * CENT});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx5));
|
||||
const auto tx6 = make_tx(/*inputs=*/ {tx5}, /*output_values=*/ {1098 * CENT});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx6));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx6));
|
||||
// Make tx6's modified fee much higher than its base fee. This should cause it to pass
|
||||
// the fee-related checks despite being low-feerate.
|
||||
pool.PrioritiseTransaction(tx6->GetHash(), 1 * COIN);
|
||||
|
||||
// Two independent high-feerate transactions, tx7 and tx8
|
||||
const auto tx7 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {999 * CENT});
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx7));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(tx7));
|
||||
const auto tx8 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {999 * CENT});
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(tx8));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(tx8));
|
||||
|
||||
// Normal txs, will chain txns right before CheckConflictTopology test
|
||||
const auto tx9 = make_tx(/*inputs=*/ {m_coinbase_txns[5]}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx9));
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(tx9));
|
||||
const auto tx10 = make_tx(/*inputs=*/ {m_coinbase_txns[6]}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx10));
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(tx10));
|
||||
|
||||
// Will make these two parents of single child
|
||||
const auto tx11 = make_tx(/*inputs=*/ {m_coinbase_txns[7]}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx11));
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(tx11));
|
||||
const auto tx12 = make_tx(/*inputs=*/ {m_coinbase_txns[8]}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx12));
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(tx12));
|
||||
|
||||
// Will make two children of this single parent
|
||||
const auto tx13 = make_tx(/*inputs=*/ {m_coinbase_txns[9]}, /*output_values=*/ {995 * CENT, 995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx13));
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(tx13));
|
||||
|
||||
const auto entry1_normal = pool.GetIter(tx1->GetHash()).value();
|
||||
const auto entry2_normal = pool.GetIter(tx2->GetHash()).value();
|
||||
|
@ -362,48 +362,86 @@ BOOST_FIXTURE_TEST_CASE(improves_feerate, TestChain100Setup)
|
|||
const CAmount normal_fee{CENT/10};
|
||||
|
||||
// low feerate parent with normal feerate child
|
||||
const auto tx1 = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(tx1));
|
||||
const auto tx1 = make_tx(/*inputs=*/ {m_coinbase_txns[0], m_coinbase_txns[1]}, /*output_values=*/ {10 * COIN});
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(tx1));
|
||||
const auto tx2 = make_tx(/*inputs=*/ {tx1}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx2));
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(tx2));
|
||||
|
||||
const auto entry1 = pool.GetIter(tx1->GetHash()).value();
|
||||
const auto tx1_fee = entry1->GetModifiedFee();
|
||||
const auto tx1_size = entry1->GetTxSize();
|
||||
const auto entry2 = pool.GetIter(tx2->GetHash()).value();
|
||||
const auto tx2_fee = entry2->GetModifiedFee();
|
||||
const auto tx2_size = entry2->GetTxSize();
|
||||
|
||||
// conflicting transactions
|
||||
const auto tx1_conflict = make_tx(/*inputs=*/ {m_coinbase_txns[0], m_coinbase_txns[2]}, /*output_values=*/ {10 * COIN});
|
||||
const auto tx3 = make_tx(/*inputs=*/ {tx1_conflict}, /*output_values=*/ {995 * CENT});
|
||||
auto entry3 = entry.FromTx(tx3);
|
||||
|
||||
// Now test ImprovesFeerateDiagram with various levels of "package rbf" feerates
|
||||
|
||||
// It doesn't improve itself
|
||||
const auto res1 = ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee, tx1_size + tx2_size);
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry1);
|
||||
changeset->StageRemoval(entry2);
|
||||
changeset->StageAddition(tx1_conflict, tx1_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
changeset->StageAddition(tx3, tx2_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto res1 = ImprovesFeerateDiagram(*changeset);
|
||||
BOOST_CHECK(res1.has_value());
|
||||
BOOST_CHECK(res1.value().first == DiagramCheckError::FAILURE);
|
||||
BOOST_CHECK(res1.value().second == "insufficient feerate: does not improve feerate diagram");
|
||||
|
||||
// With one more satoshi it does
|
||||
BOOST_CHECK(ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee + 1, tx1_size + tx2_size) == std::nullopt);
|
||||
changeset.reset();
|
||||
changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry1);
|
||||
changeset->StageRemoval(entry2);
|
||||
changeset->StageAddition(tx1_conflict, tx1_fee+1, 0, 1, 0, false, 4, LockPoints());
|
||||
changeset->StageAddition(tx3, tx2_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
BOOST_CHECK(ImprovesFeerateDiagram(*changeset) == std::nullopt);
|
||||
|
||||
changeset.reset();
|
||||
// With prioritisation of in-mempool conflicts, it affects the results of the comparison using the same args as just above
|
||||
pool.PrioritiseTransaction(entry1->GetSharedTx()->GetHash(), /*nFeeDelta=*/1);
|
||||
const auto res2 = ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee + 1, tx1_size + tx2_size);
|
||||
changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry1);
|
||||
changeset->StageRemoval(entry2);
|
||||
changeset->StageAddition(tx1_conflict, tx1_fee+1, 0, 1, 0, false, 4, LockPoints());
|
||||
changeset->StageAddition(tx3, tx2_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto res2 = ImprovesFeerateDiagram(*changeset);
|
||||
BOOST_CHECK(res2.has_value());
|
||||
BOOST_CHECK(res2.value().first == DiagramCheckError::FAILURE);
|
||||
BOOST_CHECK(res2.value().second == "insufficient feerate: does not improve feerate diagram");
|
||||
changeset.reset();
|
||||
|
||||
pool.PrioritiseTransaction(entry1->GetSharedTx()->GetHash(), /*nFeeDelta=*/-1);
|
||||
|
||||
// With one less vB it does
|
||||
BOOST_CHECK(ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee, tx1_size + tx2_size - 1) == std::nullopt);
|
||||
// With fewer vbytes it does
|
||||
CMutableTransaction tx4{entry3.GetTx()};
|
||||
tx4.vin[0].scriptWitness = CScriptWitness(); // Clear out the witness, to reduce size
|
||||
auto entry4 = entry.FromTx(MakeTransactionRef(tx4));
|
||||
changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry1);
|
||||
changeset->StageRemoval(entry2);
|
||||
changeset->StageAddition(tx1_conflict, tx1_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
changeset->StageAddition(entry4.GetSharedTx(), tx2_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
BOOST_CHECK(ImprovesFeerateDiagram(*changeset) == std::nullopt);
|
||||
changeset.reset();
|
||||
|
||||
// Adding a grandchild makes the cluster size 3, which is uncalculable
|
||||
const auto tx3 = make_tx(/*inputs=*/ {tx2}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx3));
|
||||
const auto res3 = ImprovesFeerateDiagram(pool, {entry1}, {entry1, entry2}, tx1_fee + tx2_fee + 1, tx1_size + tx2_size);
|
||||
const auto tx5 = make_tx(/*inputs=*/ {tx2}, /*output_values=*/ {995 * CENT});
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(tx5));
|
||||
const auto entry5 = pool.GetIter(tx5->GetHash()).value();
|
||||
|
||||
changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry1);
|
||||
changeset->StageRemoval(entry2);
|
||||
changeset->StageRemoval(entry5);
|
||||
changeset->StageAddition(tx1_conflict, tx1_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
changeset->StageAddition(entry4.GetSharedTx(), tx2_fee + entry5->GetModifiedFee() + 1, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto res3 = ImprovesFeerateDiagram(*changeset);
|
||||
BOOST_CHECK(res3.has_value());
|
||||
BOOST_CHECK(res3.value().first == DiagramCheckError::UNCALCULABLE);
|
||||
BOOST_CHECK(res3.value().second == strprintf("%s has 2 descendants, max 1 allowed", tx1->GetHash().GetHex()));
|
||||
|
||||
BOOST_CHECK(res3.value().second == strprintf("%s has 2 ancestors, max 1 allowed", tx5->GetHash().GetHex()));
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
|
||||
|
@ -419,24 +457,33 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
|
|||
// low -> high -> medium fee transactions that would result in two chunks together since they
|
||||
// are all same size
|
||||
const auto low_tx = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(low_tx));
|
||||
|
||||
const auto entry_low = pool.GetIter(low_tx->GetHash()).value();
|
||||
const auto low_size = entry_low->GetTxSize();
|
||||
|
||||
const auto replacement_tx = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {9 * COIN});
|
||||
auto entry_replacement = entry.FromTx(replacement_tx);
|
||||
|
||||
// Replacement of size 1
|
||||
{
|
||||
const auto replace_one{pool.CalculateChunksForRBF(/*replacement_fees=*/0, /*replacement_vsize=*/1, {entry_low}, {entry_low})};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry_low);
|
||||
changeset->StageAddition(replacement_tx, 0, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto replace_one{changeset->CalculateChunksForRBF()};
|
||||
BOOST_CHECK(replace_one.has_value());
|
||||
std::vector<FeeFrac> expected_old_chunks{{low_fee, low_size}};
|
||||
BOOST_CHECK(replace_one->first == expected_old_chunks);
|
||||
std::vector<FeeFrac> expected_new_chunks{{0, 1}};
|
||||
std::vector<FeeFrac> expected_new_chunks{{0, int32_t(entry_replacement.GetTxSize())}};
|
||||
BOOST_CHECK(replace_one->second == expected_new_chunks);
|
||||
}
|
||||
|
||||
// Non-zero replacement fee/size
|
||||
{
|
||||
const auto replace_one_fee{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low})};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry_low);
|
||||
changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto replace_one_fee{changeset->CalculateChunksForRBF()};
|
||||
BOOST_CHECK(replace_one_fee.has_value());
|
||||
std::vector<FeeFrac> expected_old_diagram{{low_fee, low_size}};
|
||||
BOOST_CHECK(replace_one_fee->first == expected_old_diagram);
|
||||
|
@ -446,12 +493,16 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
|
|||
|
||||
// Add a second transaction to the cluster that will make a single chunk, to be evicted in the RBF
|
||||
const auto high_tx = make_tx(/*inputs=*/ {low_tx}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(high_tx));
|
||||
const auto entry_high = pool.GetIter(high_tx->GetHash()).value();
|
||||
const auto high_size = entry_high->GetTxSize();
|
||||
|
||||
{
|
||||
const auto replace_single_chunk{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_low}, {entry_low, entry_high})};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry_low);
|
||||
changeset->StageRemoval(entry_high);
|
||||
changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto replace_single_chunk{changeset->CalculateChunksForRBF()};
|
||||
BOOST_CHECK(replace_single_chunk.has_value());
|
||||
std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
|
||||
BOOST_CHECK(replace_single_chunk->first == expected_old_chunks);
|
||||
|
@ -461,7 +512,10 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
|
|||
|
||||
// Conflict with the 2nd tx, resulting in new diagram with three entries
|
||||
{
|
||||
const auto replace_cpfp_child{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high}, {entry_high})};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry_high);
|
||||
changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto replace_cpfp_child{changeset->CalculateChunksForRBF()};
|
||||
BOOST_CHECK(replace_cpfp_child.has_value());
|
||||
std::vector<FeeFrac> expected_old_chunks{{low_fee + high_fee, low_size + high_size}};
|
||||
BOOST_CHECK(replace_cpfp_child->first == expected_old_chunks);
|
||||
|
@ -471,29 +525,37 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
|
|||
|
||||
// third transaction causes the topology check to fail
|
||||
const auto normal_tx = make_tx(/*inputs=*/ {high_tx}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(normal_fee).FromTx(normal_tx));
|
||||
AddToMempool(pool, entry.Fee(normal_fee).FromTx(normal_tx));
|
||||
const auto entry_normal = pool.GetIter(normal_tx->GetHash()).value();
|
||||
const auto normal_size = entry_normal->GetTxSize();
|
||||
|
||||
{
|
||||
const auto replace_too_large{pool.CalculateChunksForRBF(/*replacement_fees=*/normal_fee, /*replacement_vsize=*/normal_size, {entry_low}, {entry_low, entry_high, entry_normal})};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry_low);
|
||||
changeset->StageRemoval(entry_high);
|
||||
changeset->StageRemoval(entry_normal);
|
||||
changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto replace_too_large{changeset->CalculateChunksForRBF()};
|
||||
BOOST_CHECK(!replace_too_large.has_value());
|
||||
BOOST_CHECK_EQUAL(util::ErrorString(replace_too_large).original, strprintf("%s has 2 descendants, max 1 allowed", low_tx->GetHash().GetHex()));
|
||||
BOOST_CHECK_EQUAL(util::ErrorString(replace_too_large).original, strprintf("%s has 2 ancestors, max 1 allowed", normal_tx->GetHash().GetHex()));
|
||||
}
|
||||
|
||||
// Make a size 2 cluster that is itself two chunks; evict both txns
|
||||
const auto high_tx_2 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {10 * COIN});
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(high_tx_2));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(high_tx_2));
|
||||
const auto entry_high_2 = pool.GetIter(high_tx_2->GetHash()).value();
|
||||
const auto high_size_2 = entry_high_2->GetTxSize();
|
||||
|
||||
const auto low_tx_2 = make_tx(/*inputs=*/ {high_tx_2}, /*output_values=*/ {9 * COIN});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(low_tx_2));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(low_tx_2));
|
||||
const auto entry_low_2 = pool.GetIter(low_tx_2->GetHash()).value();
|
||||
const auto low_size_2 = entry_low_2->GetTxSize();
|
||||
|
||||
{
|
||||
const auto replace_two_chunks_single_cluster{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {entry_high_2}, {entry_high_2, entry_low_2})};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(entry_high_2);
|
||||
changeset->StageRemoval(entry_low_2);
|
||||
changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto replace_two_chunks_single_cluster{changeset->CalculateChunksForRBF()};
|
||||
BOOST_CHECK(replace_two_chunks_single_cluster.has_value());
|
||||
std::vector<FeeFrac> expected_old_chunks{{high_fee, high_size_2}, {low_fee, low_size_2}};
|
||||
BOOST_CHECK(replace_two_chunks_single_cluster->first == expected_old_chunks);
|
||||
|
@ -503,19 +565,24 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
|
|||
|
||||
// You can have more than two direct conflicts if the there are multiple affected clusters, all of size 2 or less
|
||||
const auto conflict_1 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {10 * COIN});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(conflict_1));
|
||||
const auto conflict_1_entry = pool.GetIter(conflict_1->GetHash()).value();
|
||||
|
||||
const auto conflict_2 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {10 * COIN});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_2));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(conflict_2));
|
||||
const auto conflict_2_entry = pool.GetIter(conflict_2->GetHash()).value();
|
||||
|
||||
const auto conflict_3 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {10 * COIN});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_3));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(conflict_3));
|
||||
const auto conflict_3_entry = pool.GetIter(conflict_3->GetHash()).value();
|
||||
|
||||
{
|
||||
const auto replace_multiple_clusters{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry})};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(conflict_1_entry);
|
||||
changeset->StageRemoval(conflict_2_entry);
|
||||
changeset->StageRemoval(conflict_3_entry);
|
||||
changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto replace_multiple_clusters{changeset->CalculateChunksForRBF()};
|
||||
BOOST_CHECK(replace_multiple_clusters.has_value());
|
||||
BOOST_CHECK(replace_multiple_clusters->first.size() == 3);
|
||||
BOOST_CHECK(replace_multiple_clusters->second.size() == 1);
|
||||
|
@ -523,11 +590,17 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
|
|||
|
||||
// Add a child transaction to conflict_1 and make it cluster size 2, two chunks due to same feerate
|
||||
const auto conflict_1_child = make_tx(/*inputs=*/{conflict_1}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(low_fee).FromTx(conflict_1_child));
|
||||
AddToMempool(pool, entry.Fee(low_fee).FromTx(conflict_1_child));
|
||||
const auto conflict_1_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
|
||||
|
||||
{
|
||||
const auto replace_multiple_clusters_2{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry})};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(conflict_1_entry);
|
||||
changeset->StageRemoval(conflict_2_entry);
|
||||
changeset->StageRemoval(conflict_3_entry);
|
||||
changeset->StageRemoval(conflict_1_child_entry);
|
||||
changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto replace_multiple_clusters_2{changeset->CalculateChunksForRBF()};
|
||||
|
||||
BOOST_CHECK(replace_multiple_clusters_2.has_value());
|
||||
BOOST_CHECK(replace_multiple_clusters_2->first.size() == 4);
|
||||
|
@ -536,14 +609,21 @@ BOOST_FIXTURE_TEST_CASE(calc_feerate_diagram_rbf, TestChain100Setup)
|
|||
|
||||
// Add another descendant to conflict_1, making the cluster size > 2 should fail at this point.
|
||||
const auto conflict_1_grand_child = make_tx(/*inputs=*/{conflict_1_child}, /*output_values=*/ {995 * CENT});
|
||||
pool.addUnchecked(entry.Fee(high_fee).FromTx(conflict_1_grand_child));
|
||||
AddToMempool(pool, entry.Fee(high_fee).FromTx(conflict_1_grand_child));
|
||||
const auto conflict_1_grand_child_entry = pool.GetIter(conflict_1_child->GetHash()).value();
|
||||
|
||||
{
|
||||
const auto replace_cluster_size_3{pool.CalculateChunksForRBF(/*replacement_fees=*/high_fee, /*replacement_vsize=*/low_size, {conflict_1_entry, conflict_2_entry, conflict_3_entry}, {conflict_1_entry, conflict_2_entry, conflict_3_entry, conflict_1_child_entry, conflict_1_grand_child_entry})};
|
||||
auto changeset = pool.GetChangeSet();
|
||||
changeset->StageRemoval(conflict_1_entry);
|
||||
changeset->StageRemoval(conflict_2_entry);
|
||||
changeset->StageRemoval(conflict_3_entry);
|
||||
changeset->StageRemoval(conflict_1_child_entry);
|
||||
changeset->StageRemoval(conflict_1_grand_child_entry);
|
||||
changeset->StageAddition(replacement_tx, high_fee, 0, 1, 0, false, 4, LockPoints());
|
||||
const auto replace_cluster_size_3{changeset->CalculateChunksForRBF()};
|
||||
|
||||
BOOST_CHECK(!replace_cluster_size_3.has_value());
|
||||
BOOST_CHECK_EQUAL(util::ErrorString(replace_cluster_size_3).original, strprintf("%s has 2 descendants, max 1 allowed", conflict_1->GetHash().GetHex()));
|
||||
BOOST_CHECK_EQUAL(util::ErrorString(replace_cluster_size_3).original, strprintf("%s has both ancestor and descendant, exceeding cluster limit of 2", conflict_1_child->GetHash().GetHex()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1078,7 +1078,25 @@ BOOST_AUTO_TEST_CASE(package_rbf_tests)
|
|||
BOOST_CHECK_EQUAL(it_child_3->second.m_effective_feerate.value().GetFee(package3_total_vsize), 199 + 1300);
|
||||
|
||||
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
|
||||
}
|
||||
|
||||
// Finally, check that we can prioritise tx_child_1 to get package1 into the mempool.
|
||||
// It should not be possible to resubmit package1 and get it in without prioritisation.
|
||||
const auto submit4 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, false, std::nullopt);
|
||||
if (auto err_4{CheckPackageMempoolAcceptResult(package1, submit4, /*expect_valid=*/false, m_node.mempool.get())}) {
|
||||
BOOST_ERROR(err_4.value());
|
||||
}
|
||||
m_node.mempool->PrioritiseTransaction(tx_child_1->GetHash(), 1363);
|
||||
const auto submit5 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, false, std::nullopt);
|
||||
if (auto err_5{CheckPackageMempoolAcceptResult(package1, submit5, /*expect_valid=*/true, m_node.mempool.get())}) {
|
||||
BOOST_ERROR(err_5.value());
|
||||
}
|
||||
it_parent_1 = submit5.m_tx_results.find(tx_parent_1->GetWitnessHash());
|
||||
it_child_1 = submit5.m_tx_results.find(tx_child_1->GetWitnessHash());
|
||||
BOOST_CHECK_EQUAL(it_parent_1->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
|
||||
BOOST_CHECK_EQUAL(it_child_1->second.m_result_type, MempoolAcceptResult::ResultType::VALID);
|
||||
LOCK(m_node.mempool->cs);
|
||||
BOOST_CHECK(m_node.mempool->GetIter(tx_parent_1->GetHash()).has_value());
|
||||
BOOST_CHECK(m_node.mempool->GetIter(tx_child_1->GetHash()).has_value());
|
||||
}
|
||||
}
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -180,7 +180,7 @@ BOOST_FIXTURE_TEST_CASE(ephemeral_tests, RegTestingSetup)
|
|||
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1}, minrelay, pool).has_value());
|
||||
|
||||
// Add first grandparent to mempool and fetch entry
|
||||
pool.addUnchecked(entry.FromTx(grandparent_tx_1));
|
||||
AddToMempool(pool, entry.FromTx(grandparent_tx_1));
|
||||
|
||||
// Ignores ancestors that aren't direct parents
|
||||
BOOST_CHECK(!CheckEphemeralSpends({child_no_dust}, minrelay, pool).has_value());
|
||||
|
@ -194,7 +194,7 @@ BOOST_FIXTURE_TEST_CASE(ephemeral_tests, RegTestingSetup)
|
|||
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_2, parent_with_dust}, minrelay, pool).has_value());
|
||||
|
||||
// Add second grandparent to mempool
|
||||
pool.addUnchecked(entry.FromTx(grandparent_tx_2));
|
||||
AddToMempool(pool, entry.FromTx(grandparent_tx_2));
|
||||
|
||||
// Only spends single dust out of two direct parents
|
||||
BOOST_CHECK(CheckEphemeralSpends({dust_non_spend_both_parents}, minrelay, pool).has_value());
|
||||
|
@ -203,7 +203,7 @@ BOOST_FIXTURE_TEST_CASE(ephemeral_tests, RegTestingSetup)
|
|||
BOOST_CHECK(!CheckEphemeralSpends({parent_with_dust}, minrelay, pool).has_value());
|
||||
|
||||
// Now add dusty parent to mempool
|
||||
pool.addUnchecked(entry.FromTx(parent_with_dust));
|
||||
AddToMempool(pool, entry.FromTx(parent_with_dust));
|
||||
|
||||
// Passes dust checks even with non-parent ancestors
|
||||
BOOST_CHECK(!CheckEphemeralSpends({child_no_dust}, minrelay, pool).has_value());
|
||||
|
@ -219,9 +219,9 @@ BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
|
|||
CTxMemPool::setEntries empty_ancestors;
|
||||
|
||||
auto mempool_tx_v3 = make_tx(random_outpoints(1), /*version=*/3);
|
||||
pool.addUnchecked(entry.FromTx(mempool_tx_v3));
|
||||
AddToMempool(pool, entry.FromTx(mempool_tx_v3));
|
||||
auto mempool_tx_v2 = make_tx(random_outpoints(1), /*version=*/2);
|
||||
pool.addUnchecked(entry.FromTx(mempool_tx_v2));
|
||||
AddToMempool(pool, entry.FromTx(mempool_tx_v2));
|
||||
// Default values.
|
||||
CTxMemPool::Limits m_limits{};
|
||||
|
||||
|
@ -331,7 +331,7 @@ BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
|
|||
package_multi_parents.emplace_back(mempool_tx_v3);
|
||||
for (size_t i{0}; i < 2; ++i) {
|
||||
auto mempool_tx = make_tx(random_outpoints(i + 1), /*version=*/3);
|
||||
pool.addUnchecked(entry.FromTx(mempool_tx));
|
||||
AddToMempool(pool, entry.FromTx(mempool_tx));
|
||||
mempool_outpoints.emplace_back(mempool_tx->GetHash(), 0);
|
||||
package_multi_parents.emplace_back(mempool_tx);
|
||||
}
|
||||
|
@ -356,7 +356,7 @@ BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
|
|||
auto last_outpoint{random_outpoints(1)[0]};
|
||||
for (size_t i{0}; i < 2; ++i) {
|
||||
auto mempool_tx = make_tx({last_outpoint}, /*version=*/3);
|
||||
pool.addUnchecked(entry.FromTx(mempool_tx));
|
||||
AddToMempool(pool, entry.FromTx(mempool_tx));
|
||||
last_outpoint = COutPoint{mempool_tx->GetHash(), 0};
|
||||
package_multi_gen.emplace_back(mempool_tx);
|
||||
if (i == 1) middle_tx = mempool_tx;
|
||||
|
@ -443,7 +443,7 @@ BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
|
|||
BOOST_CHECK(GetTransactionWeight(*tx_mempool_v3_child) <= TRUC_CHILD_MAX_VSIZE * WITNESS_SCALE_FACTOR);
|
||||
auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_mempool_v3_child), m_limits)};
|
||||
BOOST_CHECK(SingleTRUCChecks(tx_mempool_v3_child, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_mempool_v3_child)) == std::nullopt);
|
||||
pool.addUnchecked(entry.FromTx(tx_mempool_v3_child));
|
||||
AddToMempool(pool, entry.FromTx(tx_mempool_v3_child));
|
||||
|
||||
Package package_v3_1p1c{mempool_tx_v3, tx_mempool_v3_child};
|
||||
BOOST_CHECK(PackageTRUCChecks(tx_mempool_v3_child, GetVirtualTransactionSize(*tx_mempool_v3_child), package_v3_1p1c, empty_ancestors) == std::nullopt);
|
||||
|
@ -471,7 +471,7 @@ BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
|
|||
expected_error_str);
|
||||
|
||||
// Configuration where parent already has 2 other children in mempool (no sibling eviction allowed). This may happen as the result of a reorg.
|
||||
pool.addUnchecked(entry.FromTx(tx_v3_child2));
|
||||
AddToMempool(pool, entry.FromTx(tx_v3_child2));
|
||||
auto tx_v3_child3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 24}}, /*version=*/3);
|
||||
auto entry_mempool_parent = pool.GetIter(mempool_tx_v3->GetHash().ToUint256()).value();
|
||||
BOOST_CHECK_EQUAL(entry_mempool_parent->GetCountWithDescendants(), 3);
|
||||
|
@ -490,9 +490,9 @@ BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
|
|||
auto tx_mempool_nibling = make_tx({COutPoint{tx_mempool_sibling->GetHash(), 0}}, /*version=*/3);
|
||||
auto tx_to_submit = make_tx({COutPoint{tx_mempool_grandparent->GetHash(), 1}}, /*version=*/3);
|
||||
|
||||
pool.addUnchecked(entry.FromTx(tx_mempool_grandparent));
|
||||
pool.addUnchecked(entry.FromTx(tx_mempool_sibling));
|
||||
pool.addUnchecked(entry.FromTx(tx_mempool_nibling));
|
||||
AddToMempool(pool, entry.FromTx(tx_mempool_grandparent));
|
||||
AddToMempool(pool, entry.FromTx(tx_mempool_sibling));
|
||||
AddToMempool(pool, entry.FromTx(tx_mempool_nibling));
|
||||
|
||||
auto ancestors_3gen{pool.CalculateMemPoolAncestors(entry.FromTx(tx_to_submit), m_limits)};
|
||||
const auto expected_error_str{strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
|
||||
|
|
|
@ -544,9 +544,11 @@ std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContex
|
|||
if (submit) {
|
||||
LOCK2(cs_main, m_node.mempool->cs);
|
||||
LockPoints lp;
|
||||
m_node.mempool->addUnchecked(CTxMemPoolEntry(ptx, /*fee=*/(total_in - num_outputs * amount_per_output),
|
||||
/*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0,
|
||||
/*spends_coinbase=*/false, /*sigops_cost=*/4, lp));
|
||||
auto changeset = m_node.mempool->GetChangeSet();
|
||||
changeset->StageAddition(ptx, /*fee=*/(total_in - num_outputs * amount_per_output),
|
||||
/*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0,
|
||||
/*spends_coinbase=*/false, /*sigops_cost=*/4, lp);
|
||||
changeset->Apply();
|
||||
}
|
||||
--num_transactions;
|
||||
}
|
||||
|
@ -574,9 +576,13 @@ void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate)
|
|||
// The new mempool min feerate is equal to the removed package's feerate + incremental feerate.
|
||||
const auto tx_fee = target_feerate.GetFee(GetVirtualTransactionSize(*tx)) -
|
||||
m_node.mempool->m_opts.incremental_relay_feerate.GetFee(GetVirtualTransactionSize(*tx));
|
||||
m_node.mempool->addUnchecked(CTxMemPoolEntry(tx, /*fee=*/tx_fee,
|
||||
/*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0,
|
||||
/*spends_coinbase=*/true, /*sigops_cost=*/1, lp));
|
||||
{
|
||||
auto changeset = m_node.mempool->GetChangeSet();
|
||||
changeset->StageAddition(tx, /*fee=*/tx_fee,
|
||||
/*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0,
|
||||
/*spends_coinbase=*/true, /*sigops_cost=*/1, lp);
|
||||
changeset->Apply();
|
||||
}
|
||||
m_node.mempool->TrimToSize(0);
|
||||
assert(m_node.mempool->GetMinFee() == target_feerate);
|
||||
}
|
||||
|
|
|
@ -219,3 +219,13 @@ void CheckMempoolTRUCInvariants(const CTxMemPool& tx_pool)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddToMempool(CTxMemPool& tx_pool, const CTxMemPoolEntry& entry)
|
||||
{
|
||||
LOCK2(cs_main, tx_pool.cs);
|
||||
auto changeset = tx_pool.GetChangeSet();
|
||||
changeset->StageAddition(entry.GetSharedTx(), entry.GetFee(),
|
||||
entry.GetTime().count(), entry.GetHeight(), entry.GetSequence(),
|
||||
entry.GetSpendsCoinbase(), entry.GetSigOpCost(), entry.GetLockPoints());
|
||||
changeset->Apply();
|
||||
}
|
||||
|
|
|
@ -68,4 +68,8 @@ std::vector<uint32_t> GetDustIndexes(const CTransactionRef& tx_ref, CFeeRate dus
|
|||
* */
|
||||
void CheckMempoolTRUCInvariants(const CTxMemPool& tx_pool);
|
||||
|
||||
/** One-line wrapper for creating a mempool changeset with a single transaction
|
||||
* and applying it. */
|
||||
void AddToMempool(CTxMemPool& tx_pool, const CTxMemPoolEntry& entry);
|
||||
|
||||
#endif // BITCOIN_TEST_UTIL_TXMEMPOOL_H
|
||||
|
|
|
@ -357,7 +357,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
|
|||
// of ancestors reachable via GetMemPoolParents()/GetMemPoolChildren()
|
||||
// will be the same as the set of ancestors whose packages include this
|
||||
// transaction, because when we add a new transaction to the mempool in
|
||||
// addUnchecked(), we assume it has no children, and in the case of a
|
||||
// addNewTransaction(), we assume it has no children, and in the case of a
|
||||
// reorg where that assumption is false, the in-mempool children aren't
|
||||
// linked to the in-block tx's until UpdateTransactionsFromBlock() is
|
||||
// called.
|
||||
|
@ -432,21 +432,51 @@ void CTxMemPool::AddTransactionsUpdated(unsigned int n)
|
|||
nTransactionsUpdated += n;
|
||||
}
|
||||
|
||||
void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAncestors)
|
||||
void CTxMemPool::Apply(ChangeSet* changeset)
|
||||
{
|
||||
// Add to memory pool without checking anything.
|
||||
// Used by AcceptToMemoryPool(), which DOES do
|
||||
// all the appropriate checks.
|
||||
indexed_transaction_set::iterator newit = mapTx.emplace(CTxMemPoolEntry::ExplicitCopy, entry).first;
|
||||
AssertLockHeld(cs);
|
||||
RemoveStaged(changeset->m_to_remove, false, MemPoolRemovalReason::REPLACED);
|
||||
|
||||
// Update transaction for any feeDelta created by PrioritiseTransaction
|
||||
CAmount delta{0};
|
||||
ApplyDelta(entry.GetTx().GetHash(), delta);
|
||||
// The following call to UpdateModifiedFee assumes no previous fee modifications
|
||||
Assume(entry.GetFee() == entry.GetModifiedFee());
|
||||
if (delta) {
|
||||
mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(delta); });
|
||||
for (size_t i=0; i<changeset->m_entry_vec.size(); ++i) {
|
||||
auto tx_entry = changeset->m_entry_vec[i];
|
||||
std::optional<CTxMemPool::setEntries> ancestors;
|
||||
if (i == 0) {
|
||||
// Note: ChangeSet::CalculateMemPoolAncestors() will return a
|
||||
// cached value if mempool ancestors for this tranaction were
|
||||
// previously calculated.
|
||||
// We can only use a cached ancestor calculation for the first
|
||||
// transaction in a package, because in-package parents won't be
|
||||
// present in the cached ancestor sets of in-package children.
|
||||
// We pass in Limits::NoLimits() to ensure that this function won't fail
|
||||
// (we're going to be applying this set of transactions whether or
|
||||
// not the mempool policy limits are being respected).
|
||||
ancestors = *Assume(changeset->CalculateMemPoolAncestors(tx_entry, Limits::NoLimits()));
|
||||
}
|
||||
// First splice this entry into mapTx.
|
||||
auto node_handle = changeset->m_to_add.extract(tx_entry);
|
||||
auto result = mapTx.insert(std::move(node_handle));
|
||||
|
||||
Assume(result.inserted);
|
||||
txiter it = result.position;
|
||||
|
||||
// Now update the entry for ancestors/descendants.
|
||||
if (ancestors.has_value()) {
|
||||
addNewTransaction(it, *ancestors);
|
||||
} else {
|
||||
addNewTransaction(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CTxMemPool::addNewTransaction(CTxMemPool::txiter it)
|
||||
{
|
||||
auto ancestors{AssumeCalculateMemPoolAncestors(__func__, *it, Limits::NoLimits())};
|
||||
return addNewTransaction(it, ancestors);
|
||||
}
|
||||
|
||||
void CTxMemPool::addNewTransaction(CTxMemPool::txiter newit, CTxMemPool::setEntries& setAncestors)
|
||||
{
|
||||
const CTxMemPoolEntry& entry = *newit;
|
||||
|
||||
// Update cachedInnerUsage to include contained transaction's usage.
|
||||
// (When we update the entry for in-mempool parents, memory usage will be
|
||||
|
@ -468,7 +498,7 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces
|
|||
|
||||
// Update ancestors with information about this tx
|
||||
for (const auto& pit : GetIterSet(setParentTransactions)) {
|
||||
UpdateParent(newit, pit, true);
|
||||
UpdateParent(newit, pit, true);
|
||||
}
|
||||
UpdateAncestorsOf(true, newit, setAncestors);
|
||||
UpdateEntryForAncestors(newit, setAncestors);
|
||||
|
@ -566,6 +596,7 @@ void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReaso
|
|||
{
|
||||
// Remove transaction from memory pool
|
||||
AssertLockHeld(cs);
|
||||
Assume(!m_have_changeset);
|
||||
setEntries txToRemove;
|
||||
txiter origit = mapTx.find(origTx.GetHash());
|
||||
if (origit != mapTx.end()) {
|
||||
|
@ -597,6 +628,7 @@ void CTxMemPool::removeForReorg(CChain& chain, std::function<bool(txiter)> check
|
|||
// Remove transactions spending a coinbase which are now immature and no-longer-final transactions
|
||||
AssertLockHeld(cs);
|
||||
AssertLockHeld(::cs_main);
|
||||
Assume(!m_have_changeset);
|
||||
|
||||
setEntries txToRemove;
|
||||
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
|
||||
|
@ -635,6 +667,7 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
|
|||
void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight)
|
||||
{
|
||||
AssertLockHeld(cs);
|
||||
Assume(!m_have_changeset);
|
||||
std::vector<RemovedMempoolTransactionInfo> txs_removed_for_block;
|
||||
txs_removed_for_block.reserve(vtx.size());
|
||||
for (const auto& tx : vtx)
|
||||
|
@ -1053,6 +1086,7 @@ void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, MemPool
|
|||
int CTxMemPool::Expire(std::chrono::seconds time)
|
||||
{
|
||||
AssertLockHeld(cs);
|
||||
Assume(!m_have_changeset);
|
||||
indexed_transaction_set::index<entry_time>::type::iterator it = mapTx.get<entry_time>().begin();
|
||||
setEntries toremove;
|
||||
while (it != mapTx.get<entry_time>().end() && it->GetTime() < time) {
|
||||
|
@ -1067,12 +1101,6 @@ int CTxMemPool::Expire(std::chrono::seconds time)
|
|||
return stage.size();
|
||||
}
|
||||
|
||||
void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry)
|
||||
{
|
||||
auto ancestors{AssumeCalculateMemPoolAncestors(__func__, entry, Limits::NoLimits())};
|
||||
return addUnchecked(entry, ancestors);
|
||||
}
|
||||
|
||||
void CTxMemPool::UpdateChild(txiter entry, txiter child, bool add)
|
||||
{
|
||||
AssertLockHeld(cs);
|
||||
|
@ -1129,6 +1157,7 @@ void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) {
|
|||
|
||||
void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining) {
|
||||
AssertLockHeld(cs);
|
||||
Assume(!m_have_changeset);
|
||||
|
||||
unsigned nTxnRemoved = 0;
|
||||
CFeeRate maxFeeRateRemoved(0);
|
||||
|
@ -1283,11 +1312,15 @@ std::optional<std::string> CTxMemPool::CheckConflictTopology(const setEntries& d
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::CalculateChunksForRBF(CAmount replacement_fees, int64_t replacement_vsize, const setEntries& direct_conflicts, const setEntries& all_conflicts)
|
||||
util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::ChangeSet::CalculateChunksForRBF()
|
||||
{
|
||||
Assume(replacement_vsize > 0);
|
||||
LOCK(m_pool->cs);
|
||||
FeeFrac replacement_feerate{0, 0};
|
||||
for (auto it : m_entry_vec) {
|
||||
replacement_feerate += {it->GetModifiedFee(), it->GetTxSize()};
|
||||
}
|
||||
|
||||
auto err_string{CheckConflictTopology(direct_conflicts)};
|
||||
auto err_string{m_pool->CheckConflictTopology(m_to_remove)};
|
||||
if (err_string.has_value()) {
|
||||
// Unsupported topology for calculating a feerate diagram
|
||||
return util::Error{Untranslated(err_string.value())};
|
||||
|
@ -1309,7 +1342,7 @@ util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::
|
|||
// they have a strict topology of 1 or two connected transactions.
|
||||
|
||||
// OLD: Compute existing chunks from all affected clusters
|
||||
for (auto txiter : all_conflicts) {
|
||||
for (auto txiter : m_to_remove) {
|
||||
// Does this transaction have descendants?
|
||||
if (txiter->GetCountWithDescendants() > 1) {
|
||||
// Consider this tx when we consider the descendant.
|
||||
|
@ -1349,13 +1382,13 @@ util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::
|
|||
*/
|
||||
|
||||
// OLD - CON: Add any parents of direct conflicts that are not conflicted themselves
|
||||
for (auto direct_conflict : direct_conflicts) {
|
||||
for (auto direct_conflict : m_to_remove) {
|
||||
// If a direct conflict has an ancestor that is not in all_conflicts,
|
||||
// it can be affected by the replacement of the child.
|
||||
if (direct_conflict->GetMemPoolParentsConst().size() > 0) {
|
||||
// Grab the parent.
|
||||
const CTxMemPoolEntry& parent = direct_conflict->GetMemPoolParentsConst().begin()->get();
|
||||
if (!all_conflicts.count(mapTx.iterator_to(parent))) {
|
||||
if (!m_to_remove.contains(m_pool->mapTx.iterator_to(parent))) {
|
||||
// This transaction would be left over, so add to the NEW
|
||||
// diagram.
|
||||
new_chunks.emplace_back(parent.GetModifiedFee(), parent.GetTxSize());
|
||||
|
@ -1363,9 +1396,32 @@ util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::
|
|||
}
|
||||
}
|
||||
// + CNK: Add the proposed chunk itself
|
||||
new_chunks.emplace_back(replacement_fees, int32_t(replacement_vsize));
|
||||
new_chunks.emplace_back(replacement_feerate);
|
||||
|
||||
// No topology restrictions post-chunking; sort
|
||||
std::sort(new_chunks.begin(), new_chunks.end(), std::greater());
|
||||
return std::make_pair(old_chunks, new_chunks);
|
||||
}
|
||||
|
||||
CTxMemPool::ChangeSet::TxHandle CTxMemPool::ChangeSet::StageAddition(const CTransactionRef& tx, const CAmount fee, int64_t time, unsigned int entry_height, uint64_t entry_sequence, bool spends_coinbase, int64_t sigops_cost, LockPoints lp)
|
||||
{
|
||||
LOCK(m_pool->cs);
|
||||
Assume(m_to_add.find(tx->GetHash()) == m_to_add.end());
|
||||
auto newit = m_to_add.emplace(tx, fee, time, entry_height, entry_sequence, spends_coinbase, sigops_cost, lp).first;
|
||||
CAmount delta{0};
|
||||
m_pool->ApplyDelta(tx->GetHash(), delta);
|
||||
if (delta) m_to_add.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(delta); });
|
||||
|
||||
m_entry_vec.push_back(newit);
|
||||
return newit;
|
||||
}
|
||||
|
||||
void CTxMemPool::ChangeSet::Apply()
|
||||
{
|
||||
LOCK(m_pool->cs);
|
||||
m_pool->Apply(this);
|
||||
m_to_add.clear();
|
||||
m_to_remove.clear();
|
||||
m_entry_vec.clear();
|
||||
m_ancestors.clear();
|
||||
}
|
||||
|
|
172
src/txmempool.h
172
src/txmempool.h
|
@ -260,13 +260,13 @@ struct TxMempoolInfo
|
|||
*
|
||||
* Usually when a new transaction is added to the mempool, it has no in-mempool
|
||||
* children (because any such children would be an orphan). So in
|
||||
* addUnchecked(), we:
|
||||
* - update a new entry's setMemPoolParents to include all in-mempool parents
|
||||
* - update the new entry's direct parents to include the new tx as a child
|
||||
* addNewTransaction(), we:
|
||||
* - update a new entry's m_parents to include all in-mempool parents
|
||||
* - update each of those parent entries to include the new tx as a child
|
||||
* - update all ancestors of the transaction to include the new tx's size/fee
|
||||
*
|
||||
* When a transaction is removed from the mempool, we must:
|
||||
* - update all in-mempool parents to not track the tx in setMemPoolChildren
|
||||
* - update all in-mempool parents to not track the tx in their m_children
|
||||
* - update all ancestors to not include the tx's size/fees in descendant state
|
||||
* - update all in-mempool children to not include it as a parent
|
||||
*
|
||||
|
@ -283,7 +283,7 @@ struct TxMempoolInfo
|
|||
* unreachable from just looking at transactions in the mempool (the linking
|
||||
* transactions may also be in the disconnected block, waiting to be added).
|
||||
* Because of this, there's not much benefit in trying to search for in-mempool
|
||||
* children in addUnchecked(). Instead, in the special case of transactions
|
||||
* children in addNewTransaction(). Instead, in the special case of transactions
|
||||
* being added from a disconnected block, we require the caller to clean up the
|
||||
* state, to account for in-mempool, out-of-block descendants for all the
|
||||
* in-block transactions by calling UpdateTransactionsFromBlock(). Note that
|
||||
|
@ -453,15 +453,6 @@ public:
|
|||
*/
|
||||
void check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||
|
||||
// addUnchecked must updated state for all ancestors of a given transaction,
|
||||
// to track size/count of descendant transactions. First version of
|
||||
// addUnchecked can be used to have it call CalculateMemPoolAncestors(), and
|
||||
// then invoke the second version.
|
||||
// Note that addUnchecked is ONLY called from ATMP outside of tests
|
||||
// and any other callers may break wallet's in-mempool tracking (due to
|
||||
// lack of CValidationInterface::TransactionAddedToMempool callbacks).
|
||||
void addUnchecked(const CTxMemPoolEntry& entry) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
|
||||
void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
|
||||
|
||||
void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
/** After reorg, filter the entries that would no longer be valid in the next block, and update
|
||||
|
@ -519,15 +510,6 @@ public:
|
|||
* don't actually exist in the mempool, returns an empty vector. */
|
||||
std::vector<txiter> GetIterVec(const std::vector<uint256>& txids) const EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
|
||||
/** Remove a set of transactions from the mempool.
|
||||
* If a transaction is in this set, then all in-mempool descendants must
|
||||
* also be in the set, unless this transaction is being removed for being
|
||||
* in a block.
|
||||
* Set updateDescendants to true when removing a tx that was in a block, so
|
||||
* that any in-mempool descendants have their ancestor state updated.
|
||||
*/
|
||||
void RemoveStaged(setEntries& stage, bool updateDescendants, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
|
||||
/** UpdateTransactionsFromBlock is called when adding transactions from a
|
||||
* disconnected block back to the mempool, new mempool entries may have
|
||||
* children in the mempool (which is generally not the case when otherwise
|
||||
|
@ -724,29 +706,21 @@ public:
|
|||
return m_sequence_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sorted chunks for the old and new mempool relating to the
|
||||
* clusters that would be affected by a potential replacement transaction.
|
||||
* (replacement_fees, replacement_vsize) values are gathered from a
|
||||
* proposed set of replacement transactions that are considered as a single
|
||||
* chunk, and represent their complete cluster. In other words, they have no
|
||||
* in-mempool ancestors.
|
||||
*
|
||||
* @param[in] replacement_fees Package fees
|
||||
* @param[in] replacement_vsize Package size (must be greater than 0)
|
||||
* @param[in] direct_conflicts All transactions that would be removed directly by
|
||||
* having a conflicting input with a proposed transaction
|
||||
* @param[in] all_conflicts All transactions that would be removed
|
||||
* @return old and new diagram pair respectively, or an error string if the conflicts don't match a calculable topology
|
||||
*/
|
||||
util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CalculateChunksForRBF(CAmount replacement_fees, int64_t replacement_vsize, const setEntries& direct_conflicts, const setEntries& all_conflicts) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
|
||||
/* Check that all direct conflicts are in a cluster size of two or less. Each
|
||||
* direct conflict may be in a separate cluster.
|
||||
*/
|
||||
std::optional<std::string> CheckConflictTopology(const setEntries& direct_conflicts);
|
||||
|
||||
private:
|
||||
/** Remove a set of transactions from the mempool.
|
||||
* If a transaction is in this set, then all in-mempool descendants must
|
||||
* also be in the set, unless this transaction is being removed for being
|
||||
* in a block.
|
||||
* Set updateDescendants to true when removing a tx that was in a block, so
|
||||
* that any in-mempool descendants have their ancestor state updated.
|
||||
*/
|
||||
void RemoveStaged(setEntries& stage, bool updateDescendants, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
|
||||
/** UpdateForDescendants is used by UpdateTransactionsFromBlock to update
|
||||
* the descendants for a single transaction that has been added to the
|
||||
* mempool but may have child transactions in the mempool, eg during a
|
||||
|
@ -791,7 +765,7 @@ private:
|
|||
/** Before calling removeUnchecked for a given transaction,
|
||||
* UpdateForRemoveFromMempool must be called on the entire (dependent) set
|
||||
* of transactions being removed at the same time. We use each
|
||||
* CTxMemPoolEntry's setMemPoolParents in order to walk ancestors of a
|
||||
* CTxMemPoolEntry's m_parents in order to walk ancestors of a
|
||||
* given transaction that is removed, so we can't remove intermediate
|
||||
* transactions in a chain before we've updated all the state for the
|
||||
* removal.
|
||||
|
@ -816,6 +790,122 @@ public:
|
|||
assert(m_epoch.guarded()); // verify guard even when it==nullopt
|
||||
return !it || visited(*it);
|
||||
}
|
||||
|
||||
/*
|
||||
* CTxMemPool::ChangeSet:
|
||||
*
|
||||
* This class is used for all mempool additions and associated removals (eg
|
||||
* due to rbf). Removals that don't need to be evaluated for acceptance,
|
||||
* such as removing transactions that appear in a block, or due to reorg,
|
||||
* or removals related to mempool limiting or expiry do not need to use
|
||||
* this.
|
||||
*
|
||||
* Callers can interleave calls to StageAddition()/StageRemoval(), and
|
||||
* removals may be invoked in any order, but additions must be done in a
|
||||
* topological order in the case of transaction packages (ie, parents must
|
||||
* be added before children).
|
||||
*
|
||||
* CalculateChunksForRBF() can be used to calculate the feerate diagram of
|
||||
* the proposed set of new transactions and compare with the existing
|
||||
* mempool.
|
||||
*
|
||||
* CalculateMemPoolAncestors() calculates the in-mempool (not including
|
||||
* what is in the change set itself) ancestors of a given transacion.
|
||||
*
|
||||
* Apply() will apply the removals and additions that are staged into the
|
||||
* mempool.
|
||||
*
|
||||
* Only one changeset may exist at a time. While a changeset is
|
||||
* outstanding, no removals or additions may be made directly to the
|
||||
* mempool.
|
||||
*/
|
||||
class ChangeSet {
|
||||
public:
|
||||
explicit ChangeSet(CTxMemPool* pool) : m_pool(pool) {}
|
||||
~ChangeSet() EXCLUSIVE_LOCKS_REQUIRED(m_pool->cs) { m_pool->m_have_changeset = false; }
|
||||
|
||||
ChangeSet(const ChangeSet&) = delete;
|
||||
ChangeSet& operator=(const ChangeSet&) = delete;
|
||||
|
||||
using TxHandle = CTxMemPool::txiter;
|
||||
|
||||
TxHandle StageAddition(const CTransactionRef& tx, const CAmount fee, int64_t time, unsigned int entry_height, uint64_t entry_sequence, bool spends_coinbase, int64_t sigops_cost, LockPoints lp);
|
||||
void StageRemoval(CTxMemPool::txiter it) { m_to_remove.insert(it); }
|
||||
|
||||
const CTxMemPool::setEntries& GetRemovals() const { return m_to_remove; }
|
||||
|
||||
util::Result<CTxMemPool::setEntries> CalculateMemPoolAncestors(TxHandle tx, const Limits& limits)
|
||||
{
|
||||
// Look up transaction in our cache first
|
||||
auto it = m_ancestors.find(tx);
|
||||
if (it != m_ancestors.end()) return it->second;
|
||||
|
||||
// If not found, try to have the mempool calculate it, and cache
|
||||
// for later.
|
||||
LOCK(m_pool->cs);
|
||||
auto ret{m_pool->CalculateMemPoolAncestors(*tx, limits)};
|
||||
if (ret) m_ancestors.try_emplace(tx, *ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<CTransactionRef> GetAddedTxns() const {
|
||||
std::vector<CTransactionRef> ret;
|
||||
ret.reserve(m_entry_vec.size());
|
||||
for (const auto& entry : m_entry_vec) {
|
||||
ret.emplace_back(entry->GetSharedTx());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sorted chunks for the old and new mempool relating to the
|
||||
* clusters that would be affected by a potential replacement transaction.
|
||||
*
|
||||
* @return old and new diagram pair respectively, or an error string if the conflicts don't match a calculable topology
|
||||
*/
|
||||
util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CalculateChunksForRBF();
|
||||
|
||||
size_t GetTxCount() const { return m_entry_vec.size(); }
|
||||
const CTransaction& GetAddedTxn(size_t index) const { return m_entry_vec.at(index)->GetTx(); }
|
||||
|
||||
void Apply() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
private:
|
||||
CTxMemPool* m_pool;
|
||||
CTxMemPool::indexed_transaction_set m_to_add;
|
||||
std::vector<CTxMemPool::txiter> m_entry_vec; // track the added transactions' insertion order
|
||||
// map from the m_to_add index to the ancestors for the transaction
|
||||
std::map<CTxMemPool::txiter, CTxMemPool::setEntries, CompareIteratorByHash> m_ancestors;
|
||||
CTxMemPool::setEntries m_to_remove;
|
||||
|
||||
friend class CTxMemPool;
|
||||
};
|
||||
|
||||
std::unique_ptr<ChangeSet> GetChangeSet() EXCLUSIVE_LOCKS_REQUIRED(cs) {
|
||||
Assume(!m_have_changeset);
|
||||
m_have_changeset = true;
|
||||
return std::make_unique<ChangeSet>(this);
|
||||
}
|
||||
|
||||
bool m_have_changeset GUARDED_BY(cs){false};
|
||||
|
||||
friend class CTxMemPool::ChangeSet;
|
||||
|
||||
private:
|
||||
// Apply the given changeset to the mempool, by removing transactions in
|
||||
// the to_remove set and adding transactions in the to_add set.
|
||||
void Apply(CTxMemPool::ChangeSet* changeset) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
|
||||
// addNewTransaction must update state for all ancestors of a given transaction,
|
||||
// to track size/count of descendant transactions. First version of
|
||||
// addNewTransaction can be used to have it call CalculateMemPoolAncestors(), and
|
||||
// then invoke the second version.
|
||||
// Note that addNewTransaction is ONLY called (via Apply()) from ATMP
|
||||
// outside of tests and any other callers may break wallet's in-mempool
|
||||
// tracking (due to lack of CValidationInterface::TransactionAddedToMempool
|
||||
// callbacks).
|
||||
void addNewTransaction(CTxMemPool::txiter it) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
void addNewTransaction(CTxMemPool::txiter it, CTxMemPool::setEntries& setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -324,7 +324,7 @@ void Chainstate::MaybeUpdateMempoolForReorg(
|
|||
}
|
||||
}
|
||||
|
||||
// AcceptToMemoryPool/addUnchecked all assume that new mempool entries have
|
||||
// AcceptToMemoryPool/addNewTransaction all assume that new mempool entries have
|
||||
// no in-mempool children, which is generally not true when adding
|
||||
// previously-confirmed transactions back to the mempool.
|
||||
// UpdateTransactionsFromBlock finds descendants of any transactions in
|
||||
|
@ -633,10 +633,9 @@ private:
|
|||
CTxMemPool::setEntries m_iters_conflicting;
|
||||
/** All mempool ancestors of this transaction. */
|
||||
CTxMemPool::setEntries m_ancestors;
|
||||
/** Mempool entry constructed for this transaction. Constructed in PreChecks() but not
|
||||
* inserted into the mempool until Finalize(). */
|
||||
std::unique_ptr<CTxMemPoolEntry> m_entry;
|
||||
/** Whether RBF-related data structures (m_conflicts, m_iters_conflicting, m_all_conflicting,
|
||||
/* Handle to the tx in the changeset */
|
||||
CTxMemPool::ChangeSet::TxHandle m_tx_handle;
|
||||
/** Whether RBF-related data structures (m_conflicts, m_iters_conflicting,
|
||||
* m_replaced_transactions) include a sibling in addition to txns with conflicting inputs. */
|
||||
bool m_sibling_eviction{false};
|
||||
|
||||
|
@ -689,9 +688,7 @@ private:
|
|||
bool ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Try to add the transaction to the mempool, removing any conflicts first.
|
||||
// Returns true if the transaction is in the mempool after any size
|
||||
// limiting is performed, false otherwise.
|
||||
bool Finalize(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
void FinalizeSubpackage(const ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Submit all transactions to the mempool and call ConsensusScriptChecks to add to the script
|
||||
// cache - should only be called after successful validation of all transactions in the package.
|
||||
|
@ -742,10 +739,10 @@ private:
|
|||
/** Whether the transaction(s) would replace any mempool transactions and/or evict any siblings.
|
||||
* If so, RBF rules apply. */
|
||||
bool m_rbf{false};
|
||||
/** All directly conflicting mempool transactions and their descendants. */
|
||||
CTxMemPool::setEntries m_all_conflicts;
|
||||
/** Mempool transactions that were replaced. */
|
||||
std::list<CTransactionRef> m_replaced_transactions;
|
||||
/* Changeset representing adding transactions and removing their conflicts. */
|
||||
std::unique_ptr<CTxMemPool::ChangeSet> m_changeset;
|
||||
|
||||
/** Total modified fees of mempool transactions being replaced. */
|
||||
CAmount m_conflicting_fees{0};
|
||||
|
@ -780,7 +777,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
|
||||
// Alias what we need out of ws
|
||||
TxValidationState& state = ws.m_state;
|
||||
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
|
||||
|
||||
if (!CheckTransaction(tx, state)) {
|
||||
return false; // state filled in by CheckTransaction
|
||||
|
@ -891,10 +887,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
|
||||
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
|
||||
|
||||
// ws.m_modified_fees includes any fee deltas from PrioritiseTransaction
|
||||
ws.m_modified_fees = ws.m_base_fees;
|
||||
m_pool.ApplyDelta(hash, ws.m_modified_fees);
|
||||
|
||||
// Keep track of transactions that spend a coinbase, which we re-scan
|
||||
// during reorgs to ensure COINBASE_MATURITY is still met.
|
||||
bool fSpendsCoinbase = false;
|
||||
|
@ -909,9 +901,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
// Set entry_sequence to 0 when bypass_limits is used; this allows txs from a block
|
||||
// reorg to be marked earlier than any child txs that were already in the mempool.
|
||||
const uint64_t entry_sequence = bypass_limits ? 0 : m_pool.GetSequence();
|
||||
entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence,
|
||||
fSpendsCoinbase, nSigOpsCost, lock_points.value()));
|
||||
ws.m_vsize = entry->GetTxSize();
|
||||
if (!m_subpackage.m_changeset) {
|
||||
m_subpackage.m_changeset = m_pool.GetChangeSet();
|
||||
}
|
||||
ws.m_tx_handle = m_subpackage.m_changeset->StageAddition(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence, fSpendsCoinbase, nSigOpsCost, lock_points.value());
|
||||
|
||||
// ws.m_modified_fees includes any fee deltas from PrioritiseTransaction
|
||||
ws.m_modified_fees = ws.m_tx_handle->GetModifiedFee();
|
||||
|
||||
ws.m_vsize = ws.m_tx_handle->GetTxSize();
|
||||
|
||||
// Enforces 0-fee for dust transactions, no incentive to be mined alone
|
||||
if (m_pool.m_opts.require_standard) {
|
||||
|
@ -983,7 +981,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
maybe_rbf_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants();
|
||||
}
|
||||
|
||||
if (auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, maybe_rbf_limits)}) {
|
||||
if (auto ancestors{m_subpackage.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle, maybe_rbf_limits)}) {
|
||||
ws.m_ancestors = std::move(*ancestors);
|
||||
} else {
|
||||
// If CalculateMemPoolAncestors fails second time, we want the original error string.
|
||||
|
@ -1015,7 +1013,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->version == TRUC_VERSION) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
|
||||
}
|
||||
if (auto ancestors_retry{m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits)}) {
|
||||
if (auto ancestors_retry{m_subpackage.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle, cpfp_carve_out_limits)}) {
|
||||
ws.m_ancestors = std::move(*ancestors_retry);
|
||||
} else {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
|
||||
|
@ -1089,13 +1087,15 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
|
|||
strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
|
||||
}
|
||||
|
||||
CTxMemPool::setEntries all_conflicts;
|
||||
|
||||
// Calculate all conflicting entries and enforce Rule #5.
|
||||
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, m_subpackage.m_all_conflicts)}) {
|
||||
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, all_conflicts)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
|
||||
strprintf("too many potential replacements%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
|
||||
}
|
||||
// Enforce Rule #2.
|
||||
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, m_subpackage.m_all_conflicts)}) {
|
||||
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, all_conflicts)}) {
|
||||
// Sibling eviction is only done for TRUC transactions, which cannot have multiple ancestors.
|
||||
Assume(!ws.m_sibling_eviction);
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
|
||||
|
@ -1104,7 +1104,7 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
|
|||
|
||||
// Check if it's economically rational to mine this transaction rather than the ones it
|
||||
// replaces and pays for its own relay fees. Enforce Rules #3 and #4.
|
||||
for (CTxMemPool::txiter it : m_subpackage.m_all_conflicts) {
|
||||
for (CTxMemPool::txiter it : all_conflicts) {
|
||||
m_subpackage.m_conflicting_fees += it->GetModifiedFee();
|
||||
m_subpackage.m_conflicting_size += it->GetTxSize();
|
||||
}
|
||||
|
@ -1114,6 +1114,11 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
|
|||
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE,
|
||||
strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
|
||||
}
|
||||
|
||||
// Add all the to-be-removed transactions to the changeset.
|
||||
for (auto it : all_conflicts) {
|
||||
m_subpackage.m_changeset->StageRemoval(it);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1167,13 +1172,16 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
|
|||
|
||||
// Don't consider replacements that would cause us to remove a large number of mempool entries.
|
||||
// This limit is not increased in a package RBF. Use the aggregate number of transactions.
|
||||
CTxMemPool::setEntries all_conflicts;
|
||||
if (const auto err_string{GetEntriesForConflicts(*child_ws.m_ptx, m_pool, direct_conflict_iters,
|
||||
m_subpackage.m_all_conflicts)}) {
|
||||
all_conflicts)}) {
|
||||
return package_state.Invalid(PackageValidationResult::PCKG_POLICY,
|
||||
"package RBF failed: too many potential replacements", *err_string);
|
||||
}
|
||||
|
||||
for (CTxMemPool::txiter it : m_subpackage.m_all_conflicts) {
|
||||
|
||||
for (CTxMemPool::txiter it : all_conflicts) {
|
||||
m_subpackage.m_changeset->StageRemoval(it);
|
||||
m_subpackage.m_conflicting_fees += it->GetModifiedFee();
|
||||
m_subpackage.m_conflicting_size += it->GetTxSize();
|
||||
}
|
||||
|
@ -1200,14 +1208,15 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
|
|||
|
||||
// Check if it's economically rational to mine this package rather than the ones it replaces.
|
||||
// This takes the place of ReplacementChecks()'s PaysMoreThanConflicts() in the package RBF setting.
|
||||
if (const auto err_tup{ImprovesFeerateDiagram(m_pool, direct_conflict_iters, m_subpackage.m_all_conflicts, m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize)}) {
|
||||
if (const auto err_tup{ImprovesFeerateDiagram(*m_subpackage.m_changeset)}) {
|
||||
return package_state.Invalid(PackageValidationResult::PCKG_POLICY,
|
||||
"package RBF failed: " + err_tup.value().second, "");
|
||||
}
|
||||
|
||||
LogDebug(BCLog::TXPACKAGES, "package RBF checks passed: parent %s (wtxid=%s), child %s (wtxid=%s)\n",
|
||||
LogDebug(BCLog::TXPACKAGES, "package RBF checks passed: parent %s (wtxid=%s), child %s (wtxid=%s), package hash (%s)\n",
|
||||
txns.front()->GetHash().ToString(), txns.front()->GetWitnessHash().ToString(),
|
||||
txns.back()->GetHash().ToString(), txns.back()->GetWitnessHash().ToString());
|
||||
txns.back()->GetHash().ToString(), txns.back()->GetWitnessHash().ToString(),
|
||||
GetPackageHash(txns).ToString());
|
||||
|
||||
|
||||
return true;
|
||||
|
@ -1274,58 +1283,55 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
|
||||
void MemPoolAccept::FinalizeSubpackage(const ATMPArgs& args)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
AssertLockHeld(m_pool.cs);
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
const uint256& hash = ws.m_hash;
|
||||
TxValidationState& state = ws.m_state;
|
||||
const bool bypass_limits = args.m_bypass_limits;
|
||||
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
|
||||
|
||||
if (!m_subpackage.m_all_conflicts.empty()) Assume(args.m_allow_replacement);
|
||||
if (!m_subpackage.m_changeset->GetRemovals().empty()) Assume(args.m_allow_replacement);
|
||||
// Remove conflicting transactions from the mempool
|
||||
for (CTxMemPool::txiter it : m_subpackage.m_all_conflicts)
|
||||
for (CTxMemPool::txiter it : m_subpackage.m_changeset->GetRemovals())
|
||||
{
|
||||
LogDebug(BCLog::MEMPOOL, "replacing mempool tx %s (wtxid=%s, fees=%s, vsize=%s). New tx %s (wtxid=%s, fees=%s, vsize=%s)\n",
|
||||
it->GetTx().GetHash().ToString(),
|
||||
it->GetTx().GetWitnessHash().ToString(),
|
||||
it->GetFee(),
|
||||
it->GetTxSize(),
|
||||
hash.ToString(),
|
||||
tx.GetWitnessHash().ToString(),
|
||||
entry->GetFee(),
|
||||
entry->GetTxSize());
|
||||
std::string log_string = strprintf("replacing mempool tx %s (wtxid=%s, fees=%s, vsize=%s). ",
|
||||
it->GetTx().GetHash().ToString(),
|
||||
it->GetTx().GetWitnessHash().ToString(),
|
||||
it->GetFee(),
|
||||
it->GetTxSize());
|
||||
FeeFrac feerate{m_subpackage.m_total_modified_fees, int32_t(m_subpackage.m_total_vsize)};
|
||||
uint256 tx_or_package_hash{};
|
||||
const bool replaced_with_tx{m_subpackage.m_changeset->GetTxCount() == 1};
|
||||
if (replaced_with_tx) {
|
||||
const CTransaction& tx = m_subpackage.m_changeset->GetAddedTxn(0);
|
||||
tx_or_package_hash = tx.GetHash();
|
||||
log_string += strprintf("New tx %s (wtxid=%s, fees=%s, vsize=%s)",
|
||||
tx.GetHash().ToString(),
|
||||
tx.GetWitnessHash().ToString(),
|
||||
feerate.fee,
|
||||
feerate.size);
|
||||
} else {
|
||||
tx_or_package_hash = GetPackageHash(m_subpackage.m_changeset->GetAddedTxns());
|
||||
log_string += strprintf("New package %s with %lu txs, fees=%s, vsize=%s",
|
||||
tx_or_package_hash.ToString(),
|
||||
m_subpackage.m_changeset->GetTxCount(),
|
||||
feerate.fee,
|
||||
feerate.size);
|
||||
|
||||
}
|
||||
LogDebug(BCLog::MEMPOOL, "%s\n", log_string);
|
||||
TRACEPOINT(mempool, replaced,
|
||||
it->GetTx().GetHash().data(),
|
||||
it->GetTxSize(),
|
||||
it->GetFee(),
|
||||
std::chrono::duration_cast<std::chrono::duration<std::uint64_t>>(it->GetTime()).count(),
|
||||
hash.data(),
|
||||
entry->GetTxSize(),
|
||||
entry->GetFee()
|
||||
tx_or_package_hash.data(),
|
||||
feerate.size,
|
||||
feerate.fee,
|
||||
replaced_with_tx
|
||||
);
|
||||
m_subpackage.m_replaced_transactions.push_back(it->GetSharedTx());
|
||||
}
|
||||
m_pool.RemoveStaged(m_subpackage.m_all_conflicts, false, MemPoolRemovalReason::REPLACED);
|
||||
// Don't attempt to process the same conflicts repeatedly during subpackage evaluation:
|
||||
// they no longer exist on subsequent calls to Finalize() post-RemoveStaged
|
||||
m_subpackage.m_all_conflicts.clear();
|
||||
// Store transaction in memory
|
||||
m_pool.addUnchecked(*entry, ws.m_ancestors);
|
||||
|
||||
// trim mempool and check if tx was trimmed
|
||||
// If we are validating a package, don't trim here because we could evict a previous transaction
|
||||
// in the package. LimitMempoolSize() should be called at the very end to make sure the mempool
|
||||
// is still within limits and package submission happens atomically.
|
||||
if (!args.m_package_submission && !bypass_limits) {
|
||||
LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip());
|
||||
if (!m_pool.exists(GenTxid::Txid(hash)))
|
||||
// The tx no longer meets our (new) mempool minimum feerate but could be reconsidered in a package.
|
||||
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool full");
|
||||
}
|
||||
return true;
|
||||
m_subpackage.m_changeset->Apply();
|
||||
m_subpackage.m_changeset.reset();
|
||||
}
|
||||
|
||||
bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& workspaces,
|
||||
|
@ -1340,6 +1346,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
|
|||
return !m_pool.exists(GenTxid::Txid(ws.m_ptx->GetHash())); }));
|
||||
|
||||
bool all_submitted = true;
|
||||
FinalizeSubpackage(args);
|
||||
// ConsensusScriptChecks adds to the script cache and is therefore consensus-critical;
|
||||
// CheckInputsFromMempoolAndCache asserts that transactions only spend coins available from the
|
||||
// mempool or UTXO set. Submit each transaction to the mempool immediately after calling
|
||||
|
@ -1353,36 +1360,19 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
|
|||
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
|
||||
strprintf("BUG! PolicyScriptChecks succeeded but ConsensusScriptChecks failed: %s",
|
||||
ws.m_ptx->GetHash().ToString()));
|
||||
// Remove the transaction from the mempool.
|
||||
if (!m_subpackage.m_changeset) m_subpackage.m_changeset = m_pool.GetChangeSet();
|
||||
m_subpackage.m_changeset->StageRemoval(m_pool.GetIter(ws.m_ptx->GetHash()).value());
|
||||
}
|
||||
|
||||
// Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the
|
||||
// last calculation done in PreChecks, since package ancestors have already been submitted.
|
||||
{
|
||||
auto ancestors{m_pool.CalculateMemPoolAncestors(*ws.m_entry, m_pool.m_opts.limits)};
|
||||
if(!ancestors) {
|
||||
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
|
||||
// Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail.
|
||||
Assume(false);
|
||||
all_submitted = false;
|
||||
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
|
||||
strprintf("BUG! Mempool ancestors or descendants were underestimated: %s",
|
||||
ws.m_ptx->GetHash().ToString()));
|
||||
}
|
||||
ws.m_ancestors = std::move(ancestors).value_or(ws.m_ancestors);
|
||||
}
|
||||
// If we call LimitMempoolSize() for each individual Finalize(), the mempool will not take
|
||||
// the transaction's descendant feerate into account because it hasn't seen them yet. Also,
|
||||
// we risk evicting a transaction that a subsequent package transaction depends on. Instead,
|
||||
// allow the mempool to temporarily bypass limits, the maximum package size) while
|
||||
// submitting transactions individually and then trim at the very end.
|
||||
if (!Finalize(args, ws)) {
|
||||
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
|
||||
// Since LimitMempoolSize() won't be called, this should never fail.
|
||||
Assume(false);
|
||||
all_submitted = false;
|
||||
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
|
||||
strprintf("BUG! Adding to mempool failed: %s", ws.m_ptx->GetHash().ToString()));
|
||||
}
|
||||
}
|
||||
if (!all_submitted) {
|
||||
Assume(m_subpackage.m_changeset);
|
||||
// This code should be unreachable; it's here as belt-and-suspenders
|
||||
// to try to ensure we have no consensus-invalid transactions in the
|
||||
// mempool.
|
||||
m_subpackage.m_changeset->Apply();
|
||||
m_subpackage.m_changeset.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Wtxid> all_package_wtxids;
|
||||
|
@ -1399,6 +1389,8 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
|
|||
|
||||
// Add successful results. The returned results may change later if LimitMempoolSize() evicts them.
|
||||
for (Workspace& ws : workspaces) {
|
||||
auto iter = m_pool.GetIter(ws.m_ptx->GetHash());
|
||||
Assume(iter.has_value());
|
||||
const auto effective_feerate = args.m_package_feerates ? ws.m_package_feerate :
|
||||
CFeeRate{ws.m_modified_fees, static_cast<uint32_t>(ws.m_vsize)};
|
||||
const auto effective_feerate_wtxids = args.m_package_feerates ? all_package_wtxids :
|
||||
|
@ -1409,7 +1401,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
|
|||
if (!m_pool.m_opts.signals) continue;
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees,
|
||||
ws.m_vsize, ws.m_entry->GetHeight(),
|
||||
ws.m_vsize, (*iter)->GetHeight(),
|
||||
args.m_bypass_limits, args.m_package_submission,
|
||||
IsCurrentForFeeEstimation(m_active_chainstate),
|
||||
m_pool.HasNoInputsOf(tx));
|
||||
|
@ -1434,6 +1426,9 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
|
|||
return MempoolAcceptResult::Failure(ws.m_state);
|
||||
}
|
||||
|
||||
m_subpackage.m_total_vsize = ws.m_vsize;
|
||||
m_subpackage.m_total_modified_fees = ws.m_modified_fees;
|
||||
|
||||
// Individual modified feerate exceeded caller-defined max; abort
|
||||
if (args.m_client_maxfeerate && CFeeRate(ws.m_modified_fees, ws.m_vsize) > args.m_client_maxfeerate.value()) {
|
||||
ws.m_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "max feerate exceeded", "");
|
||||
|
@ -1471,17 +1466,24 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
|
|||
ws.m_base_fees, effective_feerate, single_wtxid);
|
||||
}
|
||||
|
||||
if (!Finalize(args, ws)) {
|
||||
// The only possible failure reason is fee-related (mempool full).
|
||||
// Failed for fee reasons. Provide the effective feerate and which txns were included.
|
||||
Assume(ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE);
|
||||
return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {ws.m_ptx->GetWitnessHash()});
|
||||
FinalizeSubpackage(args);
|
||||
|
||||
// Limit the mempool, if appropriate.
|
||||
if (!args.m_package_submission && !args.m_bypass_limits) {
|
||||
LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip());
|
||||
if (!m_pool.exists(GenTxid::Txid(ws.m_hash))) {
|
||||
// The tx no longer meets our (new) mempool minimum feerate but could be reconsidered in a package.
|
||||
ws.m_state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool full");
|
||||
return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {ws.m_ptx->GetWitnessHash()});
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pool.m_opts.signals) {
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
auto iter = m_pool.GetIter(tx.GetHash());
|
||||
Assume(iter.has_value());
|
||||
const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees,
|
||||
ws.m_vsize, ws.m_entry->GetHeight(),
|
||||
ws.m_vsize, (*iter)->GetHeight(),
|
||||
args.m_bypass_limits, args.m_package_submission,
|
||||
IsCurrentForFeeEstimation(m_active_chainstate),
|
||||
m_pool.HasNoInputsOf(tx));
|
||||
|
@ -1817,6 +1819,11 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
|
|||
AcceptSubPackage(txns_package_eval, args);
|
||||
PackageValidationState& package_state_final = multi_submission_result.m_state;
|
||||
|
||||
// This is invoked by AcceptSubPackage() already, so this is just here for
|
||||
// clarity (since it's not permitted to invoke LimitMempoolSize() while a
|
||||
// changeset is outstanding).
|
||||
ClearSubPackageState();
|
||||
|
||||
// Make sure we haven't exceeded max mempool size.
|
||||
// Package transactions that were submitted to mempool or already in mempool may be evicted.
|
||||
LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip());
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-mempool
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
from decimal import Decimal
|
||||
|
||||
# Test will be skipped if we don't have bcc installed
|
||||
|
@ -63,6 +64,7 @@ struct replaced_event
|
|||
u8 replacement_hash[HASH_LENGTH];
|
||||
s32 replacement_vsize;
|
||||
s64 replacement_fee;
|
||||
bool replaced_by_transaction;
|
||||
};
|
||||
|
||||
// BPF perf buffer to push the data to user space.
|
||||
|
@ -115,6 +117,7 @@ int trace_replaced(struct pt_regs *ctx) {
|
|||
bpf_usdt_readarg_p(5, ctx, &replaced.replacement_hash, HASH_LENGTH);
|
||||
bpf_usdt_readarg(6, ctx, &replaced.replacement_vsize);
|
||||
bpf_usdt_readarg(7, ctx, &replaced.replacement_fee);
|
||||
bpf_usdt_readarg(8, ctx, &replaced.replaced_by_transaction);
|
||||
|
||||
replaced_events.perf_submit(ctx, &replaced, sizeof(replaced));
|
||||
return 0;
|
||||
|
@ -123,6 +126,19 @@ int trace_replaced(struct pt_regs *ctx) {
|
|||
"""
|
||||
|
||||
|
||||
class MempoolReplaced(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("replaced_hash", ctypes.c_ubyte * 32),
|
||||
("replaced_vsize", ctypes.c_int32),
|
||||
("replaced_fee", ctypes.c_int64),
|
||||
("replaced_entry_time", ctypes.c_uint64),
|
||||
("replacement_hash", ctypes.c_ubyte * 32),
|
||||
("replacement_vsize", ctypes.c_int32),
|
||||
("replacement_fee", ctypes.c_int64),
|
||||
("replaced_by_transaction", ctypes.c_bool),
|
||||
]
|
||||
|
||||
|
||||
class MempoolTracepointTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
|
@ -230,7 +246,8 @@ class MempoolTracepointTest(BitcoinTestFramework):
|
|||
bpf = BPF(text=MEMPOOL_TRACEPOINTS_PROGRAM, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"])
|
||||
|
||||
def handle_replaced_event(_, data, __):
|
||||
events.append(bpf["replaced_events"].event(data))
|
||||
event = ctypes.cast(data, ctypes.POINTER(MempoolReplaced)).contents
|
||||
events.append(event)
|
||||
|
||||
bpf["replaced_events"].open_perf_buffer(handle_replaced_event)
|
||||
|
||||
|
@ -261,6 +278,7 @@ class MempoolTracepointTest(BitcoinTestFramework):
|
|||
assert_equal(bytes(event.replacement_hash)[::-1].hex(), replacement_tx["txid"])
|
||||
assert_equal(event.replacement_vsize, replacement_tx["tx"].get_vsize())
|
||||
assert_equal(event.replacement_fee, replacement_fee)
|
||||
assert_equal(event.replaced_by_transaction, True)
|
||||
|
||||
bpf.cleanup()
|
||||
self.generate(self.wallet, 1)
|
||||
|
|
|
@ -391,11 +391,11 @@ class PackageRBFTest(BitcoinTestFramework):
|
|||
# Now make conflicting packages for each coin
|
||||
package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
|
||||
package_result = node.submitpackage(package_hex1)
|
||||
assert_equal(f"package RBF failed: {parent1_result['tx'].rehash()} is not the only parent of child {child_result['tx'].rehash()}", package_result["package_msg"])
|
||||
assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
|
||||
|
||||
package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
|
||||
package_result = node.submitpackage(package_hex2)
|
||||
assert_equal(f"package RBF failed: {parent2_result['tx'].rehash()} is not the only parent of child {child_result['tx'].rehash()}", package_result["package_msg"])
|
||||
assert_equal(f"package RBF failed: {child_result['tx'].rehash()} has 2 ancestors, max 1 allowed", package_result["package_msg"])
|
||||
|
||||
package_hex3, _package_txns3 = self.create_simple_package(coin3, DEFAULT_FEE, DEFAULT_CHILD_FEE)
|
||||
package_result = node.submitpackage(package_hex3)
|
||||
|
@ -445,7 +445,7 @@ class PackageRBFTest(BitcoinTestFramework):
|
|||
# Now make conflicting packages for each coin
|
||||
package_hex1, _package_txns1 = self.create_simple_package(coin1, DEFAULT_FEE, DEFAULT_CHILD_FEE)
|
||||
package_result = node.submitpackage(package_hex1)
|
||||
assert_equal(f"package RBF failed: {parent_result['tx'].rehash()} has 2 descendants, max 1 allowed", package_result["package_msg"])
|
||||
assert_equal(f"package RBF failed: {child2_result['tx'].rehash()} is not the only child of parent {parent_result['tx'].rehash()}", package_result["package_msg"])
|
||||
|
||||
package_hex2, _package_txns2 = self.create_simple_package(coin2, DEFAULT_FEE, DEFAULT_CHILD_FEE)
|
||||
package_result = node.submitpackage(package_hex2)
|
||||
|
|
Loading…
Reference in a new issue