From 3f77dfdaf0f0bfe0c4662a616d6943f31bdd5bf4 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Fri, 18 Oct 2019 04:02:50 +0000 Subject: [PATCH 1/4] Expose ancestorsize and ancestorfees via getTransactionAncestry --- src/interfaces/chain.h | 2 +- src/node/interfaces.cpp | 4 ++-- src/txmempool.cpp | 4 +++- src/txmempool.h | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 7cac435e963..bb218cf12d2 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -174,7 +174,7 @@ public: std::string& err_string) = 0; //! Calculate mempool ancestor and descendant counts for the given transaction. - virtual void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) = 0; + virtual void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants, size_t* ancestorsize = nullptr, CAmount* ancestorfees = nullptr) = 0; //! Get the node's package limits. //! Currently only returns the ancestor and descendant count limits, but could be enhanced to diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 183b5a5d912..e4c0dc9491c 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -574,11 +574,11 @@ public: // that Chain clients do not need to know about. return TransactionError::OK == err; } - void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) override + void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants, size_t* ancestorsize, CAmount* 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(unsigned int& limit_ancestor_count, unsigned int& limit_descendant_count) override { diff --git a/src/txmempool.cpp b/src/txmempool.cpp index c5a4bbf1b00..93ad3531272 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1116,12 +1116,14 @@ uint64_t CTxMemPool::CalculateDescendantMaximum(txiter entry) const { return maximum; } -void CTxMemPool::GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const { +void CTxMemPool::GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants, size_t* const ancestorsize, CAmount* 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/txmempool.h b/src/txmempool.h index ae4b16d3779..ea1d62d32d7 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -706,8 +706,10 @@ public: /** * 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 uint256& txid, size_t& ancestors, size_t& descendants) const; + void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants, size_t* ancestorsize = nullptr, CAmount* ancestorfees = nullptr) const; /** @returns true if the mempool is fully loaded */ bool IsLoaded() const; From 6966e80f453c46d5d0a923118205f19ac2f4e336 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Mon, 12 Mar 2018 17:59:50 +0000 Subject: [PATCH 2/4] RPC: Add ancestor{count,size,fees} to listunspent output --- src/wallet/rpcwallet.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f1d51174158..30677c01920 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2880,6 +2880,9 @@ static RPCHelpMan listunspent() {RPCResult::Type::STR, "scriptPubKey", "the script key"}, {RPCResult::Type::STR_AMOUNT, "amount", "the transaction output amount in " + CURRENCY_UNIT}, {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 " + CURRENCY_ATOM + " (if transaction is in the mempool)"}, {RPCResult::Type::STR_HEX, "redeemScript", "The redeemScript if scriptPubKey is P2SH"}, {RPCResult::Type::STR, "witnessScript", "witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH"}, {RPCResult::Type::BOOL, "spendable", "Whether we have the private keys to spend this output"}, @@ -3046,6 +3049,16 @@ static RPCHelpMan listunspent() entry.pushKV("scriptPubKey", HexStr(scriptPubKey)); entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue)); entry.pushKV("confirmations", out.nDepth); + if (!out.nDepth) { + size_t ancestor_count, descendant_count, ancestor_size; + CAmount ancestor_fees; + pwallet->chain().getTransactionAncestry(out.tx->GetHash(), 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", uint64_t(ancestor_fees)); + } + } entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); if (out.fSolvable) { From 0be2f17ef5649c2d77efbbbdd9222332b2ebf0d2 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Thu, 18 Jun 2020 18:25:04 +0000 Subject: [PATCH 3/4] QA: Add tests for listunspent ancestor{count,size,fees} to mempool_packages --- test/functional/mempool_packages.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index 5fc3ec23aea..0bbe4525a8f 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -51,12 +51,17 @@ class MempoolPackagesTest(BitcoinTestFramework): 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("0.0001") # MAX_ANCESTORS transactions off a confirmed tx should be fine chain = [] witness_chain = [] - for _ in range(MAX_ANCESTORS): + ancestor_vsize = 0 + ancestor_fees = Decimal(0) + for i in range(MAX_ANCESTORS): (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) value = sent_value chain.append(txid) @@ -65,6 +70,15 @@ class MempoolPackagesTest(BitcoinTestFramework): witnesstx = self.nodes[0].decoderawtransaction(fulltx, True) witness_chain.append(witnesstx['hash']) + # 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_vsize += self.nodes[0].getrawtransaction(txid=txid, verbose=True)['vsize'] + assert_equal(this_unspent['ancestorsize'], ancestor_vsize) + ancestor_fees -= self.nodes[0].gettransaction(txid=txid)['fee'] + assert_equal(this_unspent['ancestorfees'], ancestor_fees * COIN) + # Wait until mempool transactions have passed initial broadcast (sent inv and received getdata) # Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between peer_inv_store.wait_for_broadcast(witness_chain) @@ -77,9 +91,9 @@ class MempoolPackagesTest(BitcoinTestFramework): descendant_fees = 0 descendant_vsize = 0 - ancestor_vsize = sum([mempool[tx]['vsize'] for tx in mempool]) + assert_equal(ancestor_vsize, sum([mempool[tx]['vsize'] 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) From 6cb60f3e6d652ffa4cf570426a7cf1f690d15c45 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sun, 9 Aug 2020 19:39:54 +0000 Subject: [PATCH 4/4] doc/release-notes: Add new listunspent fields --- doc/release-notes-12677.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 doc/release-notes-12677.md diff --git a/doc/release-notes-12677.md b/doc/release-notes-12677.md new file mode 100644 index 00000000000..d6fea9eae7e --- /dev/null +++ b/doc/release-notes-12677.md @@ -0,0 +1,8 @@ +Notable changes +=============== + +Updated RPCs +------------ + +- `listunspent` now includes `ancestorcount`, `ancestorsize`, and +`ancestorfees` for each transaction output that is still in the mempool.