Compare commits

...

8 commits

Author SHA1 Message Date
Jon Atack
360def914b
Merge 5d0c836294 into 66aa6a47bd 2025-01-08 20:44:51 +01:00
glozow
66aa6a47bd
Merge bitcoin/bitcoin#30391: BlockAssembler: return selected packages virtual size and fee
Some checks failed
CI / test each commit (push) Has been cancelled
CI / macOS 14 native, arm64, no depends, sqlite only, gui (push) Has been cancelled
CI / macOS 14 native, arm64, fuzz (push) Has been cancelled
CI / Win64 native, VS 2022 (push) Has been cancelled
CI / Win64 native fuzz, VS 2022 (push) Has been cancelled
CI / ASan + LSan + UBSan + integer, no depends, USDT (push) Has been cancelled
7c123c08dd  miner: add package feerate vector to CBlockTemplate (ismaelsadeeq)

Pull request description:

  This PR enables `BlockAssembler` to add all selected packages' fee and virtual size to a vector, and then return the vector as a member of `CBlockTemplate` struct.

  This PR is the first step in the https://github.com/bitcoin/bitcoin/issues/30392 project.

  The packages' vsize and fee are used in #30157 to select a percentile fee rate of the top block in the mempool.

ACKs for top commit:
  rkrux:
    tACK 7c123c08dd
  ryanofsky:
    Code review ACK 7c123c08dd. Changes since last review are rebasing due to a test conflict, giving the new field a better name and description, resolving the test conflict, and renaming a lot of test variables. The actual code change is still one-line change.
  glozow:
    reACK 7c123c08dd

Tree-SHA512: 767b0b3d4273cf1589fd2068d729a66c7414c0f9574b15989fbe293f8c85cd6c641dd783cde55bfabab32cd047d7d8a071d6897b06ed4295c0d071e588de0861
2025-01-08 13:01:23 -05:00
ismaelsadeeq
7c123c08dd
miner: add package feerate vector to CBlockTemplate
- The package feerates are ordered by the sequence in which
  packages are selected for inclusion in the block template.

- The commit also tests this new behaviour.

Co-authored-by: willcl-ark <will@256k1.dev>
2025-01-07 15:29:17 -05:00
Jon Atack
5d0c836294 doc: release notes for rpc getbalances#total and -getinfo balances update 2024-11-23 08:53:09 -06:00
Jon Atack
e00979167a test: coverage for getbalances#total field in wallet_avoidreuse 2024-11-23 08:24:57 -06:00
Jon Atack
6cfb73e0e3 cli, test: update -getinfo to use rpc getbalances#total 2024-11-22 13:32:31 -06:00
Jon Atack
1b03a82cff rpc, test: add "total" field to RPC getbalances 2024-11-22 13:32:31 -06:00
Jon Atack
19a4892871 wallet: store total balance in Balance struct 2024-11-22 08:55:59 -06:00
11 changed files with 59 additions and 15 deletions

View file

@ -0,0 +1,9 @@
Updated RPCs
------------
- RPC getbalances has a new `total` field that provides the sum of all wallet
balances returned by the RPC. (#31353)
- CLI -getinfo now displays wallet balances from RPC getbalances `total` instead
of `mine.trusted` in order to include watchonly, reused, untrusted pending, and
immature coinbase outputs in the balance shown. (#31353)

View file

@ -374,7 +374,7 @@ public:
result.pushKV("paytxfee", batch[ID_WALLETINFO]["result"]["paytxfee"]);
}
if (!batch[ID_BALANCES]["result"].isNull()) {
result.pushKV("balance", batch[ID_BALANCES]["result"]["mine"]["trusted"]);
result.pushKV("balance", batch[ID_BALANCES]["result"]["total"]);
}
result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);
result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
@ -1007,7 +1007,7 @@ static void ParseError(const UniValue& error, std::string& strPrint, int& nRet)
/**
* GetWalletBalances calls listwallets; if more than one wallet is loaded, it then
* fetches mine.trusted balances for each loaded wallet and pushes them to `result`.
* fetches the total balance for each loaded wallet and pushes it to `result`.
*
* @param result Reference to UniValue object the wallet names and balances are pushed to.
*/
@ -1023,7 +1023,7 @@ static void GetWalletBalances(UniValue& result)
for (const UniValue& wallet : wallets.getValues()) {
const std::string& wallet_name = wallet.get_str();
const UniValue getbalances = ConnectAndCallRPC(&rh, "getbalances", /* args=*/{}, wallet_name);
const UniValue& balance = getbalances.find_value("result")["mine"]["trusted"];
const UniValue& balance = getbalances.find_value("result")["total"];
balances.pushKV(wallet_name, balance);
}
result.pushKV("balances", std::move(balances));

View file

@ -421,6 +421,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
}
++nPackagesSelected;
pblocktemplate->m_package_feerates.emplace_back(packageFees, static_cast<int32_t>(packageSize));
// Update transactions that depend on each of these
nDescendantsUpdated += UpdatePackagesForAdded(mempool, ancestors, mapModifiedTx);

View file

@ -10,6 +10,7 @@
#include <policy/policy.h>
#include <primitives/block.h>
#include <txmempool.h>
#include <util/feefrac.h>
#include <memory>
#include <optional>
@ -39,6 +40,9 @@ struct CBlockTemplate
std::vector<CAmount> vTxFees;
std::vector<int64_t> vTxSigOpsCost;
std::vector<unsigned char> vchCoinbaseCommitment;
/* A vector of package fee rates, ordered by the sequence in which
* packages are selected for inclusion in the block template.*/
std::vector<FeeFrac> m_package_feerates;
};
// Container for tracking updates to ancestor feerate as we include (parent)

View file

@ -16,6 +16,7 @@
#include <txmempool.h>
#include <uint256.h>
#include <util/check.h>
#include <util/feefrac.h>
#include <util/strencodings.h>
#include <util/time.h>
#include <util/translation.h>
@ -25,6 +26,7 @@
#include <test/util/setup_common.h>
#include <memory>
#include <vector>
#include <boost/test/unit_test.hpp>
@ -123,19 +125,22 @@ 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
AddToMempool(tx_mempool, entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
const auto parent_tx{entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)};
AddToMempool(tx_mempool, parent_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();
AddToMempool(tx_mempool, entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
const auto medium_fee_tx{entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx)};
AddToMempool(tx_mempool, medium_fee_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();
AddToMempool(tx_mempool, entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
const auto high_fee_tx{entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)};
AddToMempool(tx_mempool, high_fee_tx);
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
@ -145,6 +150,21 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
BOOST_CHECK(block.vtx[2]->GetHash() == hashHighFeeTx);
BOOST_CHECK(block.vtx[3]->GetHash() == hashMediumFeeTx);
// Test the inclusion of package feerates in the block template and ensure they are sequential.
const auto block_package_feerates = BlockAssembler{m_node.chainman->ActiveChainstate(), &tx_mempool, options}.CreateNewBlock()->m_package_feerates;
BOOST_CHECK(block_package_feerates.size() == 2);
// parent_tx and high_fee_tx are added to the block as a package.
const auto combined_txs_fee = parent_tx.GetFee() + high_fee_tx.GetFee();
const auto combined_txs_size = parent_tx.GetTxSize() + high_fee_tx.GetTxSize();
FeeFrac package_feefrac{combined_txs_fee, combined_txs_size};
// The package should be added first.
BOOST_CHECK(block_package_feerates[0] == package_feefrac);
// The medium_fee_tx should be added next.
FeeFrac medium_tx_feefrac{medium_fee_tx.GetFee(), medium_fee_tx.GetTxSize()};
BOOST_CHECK(block_package_feerates[1] == medium_tx_feefrac);
// Test that a package below the block min tx fee doesn't get included
tx.vin[0].prevout.hash = hashHighFeeTx;
tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee

View file

@ -314,6 +314,7 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse)
}
ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_SPENDABLE);
ret.m_watchonly_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_WATCH_ONLY);
ret.m_total = ret.m_mine_trusted + ret.m_mine_untrusted_pending + ret.m_mine_immature + ret.m_watchonly_trusted + ret.m_watchonly_untrusted_pending + ret.m_watchonly_immature;
}
}
return ret;

View file

@ -55,6 +55,7 @@ struct Balance {
CAmount m_watchonly_trusted{0};
CAmount m_watchonly_untrusted_pending{0};
CAmount m_watchonly_immature{0};
CAmount m_total{0};
};
Balance GetBalance(const CWallet& wallet, int min_depth = 0, bool avoid_reuse = true);

View file

@ -447,6 +447,7 @@ RPCHelpMan getbalances()
{RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"},
}},
RESULT_LAST_PROCESSED_BLOCK,
{RPCResult::Type::STR_AMOUNT, "total", "total of all balances returned by this RPC"},
}
},
RPCExamples{
@ -466,6 +467,7 @@ RPCHelpMan getbalances()
const auto bal = GetBalance(wallet);
UniValue balances{UniValue::VOBJ};
const Balance full_bal{GetBalance(wallet, /*min_depth=*/0, /*avoid_reuse=*/false)};
{
UniValue balances_mine{UniValue::VOBJ};
balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted));
@ -474,7 +476,6 @@ RPCHelpMan getbalances()
if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
// If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get
// the total balance, and then subtract bal to get the reused address balance.
const auto full_bal = GetBalance(wallet, 0, false);
balances_mine.pushKV("used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending));
}
balances.pushKV("mine", std::move(balances_mine));
@ -489,6 +490,7 @@ RPCHelpMan getbalances()
}
AppendLastProcessedBlock(balances, wallet);
balances.pushKV("total", ValueFromAmount(full_bal.m_total));
return balances;
},
};

View file

@ -21,10 +21,9 @@ from test_framework.util import (
import time
# The block reward of coinbaseoutput.nValue (50) BTC/block matures after
# COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect
# node 0 to have a balance of (BLOCKS - COINBASE_MATURITY) * 50 BTC/block.
# COINBASE_MATURITY (100) blocks.
BLOCKS = COINBASE_MATURITY + 1
BALANCE = (BLOCKS - 100) * 50
BALANCE = BLOCKS * 50
JSON_PARSING_ERROR = 'error: Error parsing JSON: foo'
BLOCKS_VALUE_OF_ZERO = 'error: the first argument (number of blocks to generate, default: 1) must be an integer value greater than zero'
@ -222,7 +221,7 @@ class TestBitcoinCli(BitcoinTestFramework):
# Setup to test -getinfo, -generate, and -rpcwallet= with multiple wallets.
wallets = [self.default_wallet_name, 'Encrypted', 'secret']
amounts = [BALANCE + Decimal('9.999928'), Decimal(9), Decimal(31)]
amounts = [BALANCE, 9, 31]
self.nodes[0].createwallet(wallet_name=wallets[1])
self.nodes[0].createwallet(wallet_name=wallets[2])
w1 = self.nodes[0].get_wallet_rpc(wallets[0])
@ -234,9 +233,11 @@ class TestBitcoinCli(BitcoinTestFramework):
w2.encryptwallet(password)
w1.sendtoaddress(w2.getnewaddress(), amounts[1])
w1.sendtoaddress(w3.getnewaddress(), amounts[2])
amounts[0] -= (amounts[1] + amounts[2])
# Mine a block to confirm; adds a block reward (50 BTC) to the default wallet.
self.generate(self.nodes[0], 1)
amounts[0] += 50
self.log.info("Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance")
for i in range(len(wallets)):

View file

@ -59,9 +59,12 @@ def assert_unspent(node, total_count=None, total_sum=None, reused_supported=None
def assert_balances(node, mine, margin=0.001):
'''Make assertions about a node's getbalances output'''
got = node.getbalances()["mine"]
balances, total = node.getbalances(), 0
for k,v in mine.items():
assert_approx(got[k], v, margin)
assert_approx(balances["mine"][k], v, margin)
total += v
assert_approx(balances["total"], total, margin)
class AvoidReuseTest(BitcoinTestFramework):
def add_options(self, parser):

View file

@ -176,10 +176,12 @@ class WalletTest(BitcoinTestFramework):
'untrusted_pending': Decimal('60.0')},
'watchonly': {'immature': Decimal('5000'),
'trusted': Decimal('50.0'),
'untrusted_pending': Decimal('0E-8')}}
'untrusted_pending': Decimal('0E-8')},
'total': Decimal('69.99' if self.options.descriptors else '5119.99')}
expected_balances_1 = {'mine': {'immature': Decimal('0E-8'),
'trusted': Decimal('0E-8'), # node 1's send had an unsafe input
'untrusted_pending': Decimal('30.0') - fee_node_1}} # Doesn't include output of node 0's send since it was spent
'untrusted_pending': Decimal('30.0') - fee_node_1}, # Doesn't include output of node 0's send since it was spent
'total': Decimal('30.0') - fee_node_1}
if self.options.descriptors:
del expected_balances_0["watchonly"]
balances_0 = self.nodes[0].getbalances()