diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -16,11 +16,13 @@ - Added a new field `immature_stake_amount` to `getavalancheinfo` to report the total amount of stake that will mature within the next 2016 blocks. - The `testmempoolaccept` RPC now accepts multiple transactions (still experimental at the moment, - API may be unstable). This is intended for testing transaction packages with dependency - relationships; it is not recommended for batch-validating independent transactions. In addition to - mempool policy, package policies apply: the list cannot contain more than 50 transactions or have a - total size exceeding 101K bytes, and cannot conflict with (spend the same inputs as) each other or - the mempool. + API may be unstable). This is intended for testing transaction packages with dependency + relationships; it is not recommended for batch-validating independent transactions. In addition to + mempool policy, package policies apply: the list cannot contain more than 50 transactions or have a + total size exceeding 101K bytes, and cannot conflict with (spend the same inputs as) each other or + the mempool. + - `listunspent` now includes `ancestorcount`, `ancestorsize`, and + `ancestorfees` for each transaction output that is still in the mempool. P2P and network changes ----------------------- diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -211,7 +211,9 @@ //! Calculate mempool ancestor and descendant counts for the given //! transaction. virtual void getTransactionAncestry(const TxId &txid, size_t &ancestors, - size_t &descendants) = 0; + size_t &descendants, + size_t *ancestorsize = nullptr, + Amount *ancestorfees = nullptr) = 0; //! Get the node's package limits. //! Currently only returns the ancestor and descendant count limits, but diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -343,13 +343,14 @@ return err == TransactionError::OK; } void getTransactionAncestry(const TxId &txid, size_t &ancestors, - size_t &descendants) override { + size_t &descendants, size_t *ancestorsize, + Amount *ancestorfees) override { ancestors = descendants = 0; if (!m_node.mempool) { return; } - m_node.mempool->GetTransactionAncestry(txid, ancestors, - descendants); + m_node.mempool->GetTransactionAncestry(txid, ancestors, descendants, + ancestorsize, ancestorfees); } void getPackageLimits(size_t &limit_ancestor_count, size_t &limit_descendant_count) override { diff --git a/src/txmempool.h b/src/txmempool.h --- a/src/txmempool.h +++ b/src/txmempool.h @@ -767,9 +767,14 @@ /** * Calculate the ancestor and descendant count for the given transaction. * The counts include the transaction itself. + * When ancestors is non-zero (ie, the transaction itself is in the + * mempool), ancestorsize and ancestorfees will also be set to the + * appropriate values. */ void GetTransactionAncestry(const TxId &txid, size_t &ancestors, - size_t &descendants) const; + size_t &descendants, + size_t *ancestorsize = nullptr, + Amount *ancestorfees = nullptr) const; /** @returns true if the mempool is fully loaded */ bool IsLoaded() const; diff --git a/src/txmempool.cpp b/src/txmempool.cpp --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1301,12 +1301,20 @@ } void CTxMemPool::GetTransactionAncestry(const TxId &txid, size_t &ancestors, - size_t &descendants) const { + size_t &descendants, + size_t *const ancestorsize, + Amount *const ancestorfees) const { LOCK(cs); auto it = mapTx.find(txid); ancestors = descendants = 0; if (it != mapTx.end()) { ancestors = it->GetCountWithAncestors(); + if (ancestorsize) { + *ancestorsize = it->GetSizeWithAncestors(); + } + if (ancestorfees) { + *ancestorfees = it->GetModFeesWithAncestors(); + } descendants = CalculateDescendantMaximum(it); } } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3581,6 +3581,18 @@ "the transaction output amount in " + ticker}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations"}, + {RPCResult::Type::NUM, "ancestorcount", + /* optional */ true, + "The number of in-mempool ancestor transactions, " + "including this one (if transaction is in the mempool)"}, + {RPCResult::Type::NUM, "ancestorsize", /* optional */ true, + "The virtual transaction size of in-mempool ancestors, " + "including this one (if transaction is in the mempool)"}, + {RPCResult::Type::STR_AMOUNT, "ancestorfees", + /* optional */ true, + "The total fees of in-mempool ancestors (including this " + "one) with fee deltas used for mining priority in " + + ticker + " (if transaction is in the mempool)"}, {RPCResult::Type::STR_HEX, "redeemScript", "The redeemScript if scriptPubKey is P2SH"}, {RPCResult::Type::BOOL, "spendable", @@ -3774,6 +3786,18 @@ entry.pushKV("scriptPubKey", HexStr(scriptPubKey)); entry.pushKV("amount", out.tx->tx->vout[out.i].nValue); entry.pushKV("confirmations", out.nDepth); + if (!out.nDepth) { + size_t ancestor_count, descendant_count, ancestor_size; + Amount ancestor_fees; + pwallet->chain().getTransactionAncestry( + out.tx->GetId(), ancestor_count, descendant_count, + &ancestor_size, &ancestor_fees); + if (ancestor_count) { + entry.pushKV("ancestorcount", uint64_t(ancestor_count)); + entry.pushKV("ancestorsize", uint64_t(ancestor_size)); + entry.pushKV("ancestorfees", ancestor_fees); + } + } entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); if (out.fSolvable) { diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -60,16 +60,33 @@ txid = utxo[0]['txid'] vout = utxo[0]['vout'] value = utxo[0]['amount'] + assert 'ancestorcount' not in utxo[0] + assert 'ancestorsize' not in utxo[0] + assert 'ancestorfees' not in utxo[0] fee = Decimal("100") # MAX_ANCESTORS transactions off a confirmed tx should be fine chain = [] - for _ in range(MAX_ANCESTORS): + ancestor_size = 0 + ancestor_fees = Decimal(0) + for i in range(MAX_ANCESTORS): (txid, sent_value) = self.chain_transaction( self.nodes[0], txid, 0, value, fee, 1) value = sent_value chain.append(txid) + # Check that listunspent ancestor{count, size, fees} yield the + # correct results + wallet_unspent = self.nodes[0].listunspent(minconf=0) + this_unspent = next( + utxo_info for utxo_info in wallet_unspent if utxo_info['txid'] == txid) + assert_equal(this_unspent['ancestorcount'], i + 1) + ancestor_size += self.nodes[0].getrawtransaction( + txid=txid, verbose=True)['size'] + assert_equal(this_unspent['ancestorsize'], ancestor_size) + ancestor_fees -= self.nodes[0].gettransaction(txid=txid)['fee'] + assert_equal(this_unspent['ancestorfees'], ancestor_fees) + # Wait until mempool transactions have passed initial broadcast # (sent inv and received getdata) # Otherwise, getrawmempool may be inconsistent with getmempoolentry if @@ -84,9 +101,11 @@ descendant_fees = 0 descendant_size = 0 - ancestor_size = sum([mempool[tx]['size'] for tx in mempool]) + assert_equal(ancestor_size, + sum([mempool[tx]['size'] for tx in mempool])) ancestor_count = MAX_ANCESTORS - ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool]) + assert_equal(ancestor_fees, + sum([mempool[tx]['fee'] for tx in mempool])) descendants = [] ancestors = list(chain)