Add chain supply and transparent value to block index.

Co-authored-by: Jack Grigg <jack@z.cash>
Co-authored-by: Kris Nuttycombe <kris@nutty.land>
Co-authored-by: Daira Hopwood <daira@jacaranda.org>
This commit is contained in:
Alfredo Garcia 2021-03-18 09:34:55 -03:00 committed by Kris Nuttycombe
parent 8bc5740003
commit 486817498f
8 changed files with 272 additions and 35 deletions

View File

@ -3,6 +3,7 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
from test_framework.mininode import COIN
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal, assert_true,
@ -33,9 +34,23 @@ class WalletPersistenceTest (BitcoinTestFramework):
self.sync_all()
def run_test(self):
# Slow start is not enabled for regtest, so the expected subsidy starts at the
# maximum, but the hardcoded genesis block for regtest does not consume the
# available subsidy.
pre_halving_blocks = 143
pre_halving_subsidy = Decimal('12.5')
post_halving_blocks = 57
post_halving_subsidy = Decimal('6.25')
expected_supply = \
pre_halving_blocks * pre_halving_subsidy + \
post_halving_blocks * post_halving_subsidy
blocks_to_mine = pre_halving_blocks + post_halving_blocks
# Sanity-check the test harness
self.nodes[0].generate(200)
assert_equal(self.nodes[0].getblockcount(), 200)
# Note that the genesis block is not counted in the result of `getblockcount`
self.nodes[0].generate(blocks_to_mine)
assert_equal(self.nodes[0].getblockcount(), blocks_to_mine)
self.sync_all()
# Verify Sapling address is persisted in wallet
@ -45,6 +60,20 @@ class WalletPersistenceTest (BitcoinTestFramework):
addresses = self.nodes[0].z_listaddresses()
assert_true(sapling_addr in addresses, "Should contain address before restart")
def check_chain_value(pool, expected_value):
assert_equal(pool['chainValue'], expected_value)
assert_equal(pool['chainValueZat'], expected_value * COIN)
# Verify size of pools
chainInfo = self.nodes[0].getblockchaininfo()
print(str(chainInfo))
assert_equal(chainInfo['chainSupply']['chainValue'], expected_supply) # Supply
pools = chainInfo['valuePools']
check_chain_value(pools[0], expected_supply) # Transparent
check_chain_value(pools[1], Decimal('0')) # Sprout
check_chain_value(pools[2], Decimal('0')) # Sapling
check_chain_value(pools[3], Decimal('0')) # Orchard
# Restart the nodes
stop_nodes(self.nodes)
wait_bitcoinds()
@ -54,6 +83,16 @@ class WalletPersistenceTest (BitcoinTestFramework):
addresses = self.nodes[0].z_listaddresses()
assert_true(sapling_addr in addresses, "Should contain address after restart")
# Verify size of pools after restarting
chainInfo = self.nodes[0].getblockchaininfo()
pools = chainInfo['valuePools']
# Reenable these test in v5.4.0-rc1
# check_chain_value(chainInfo['chainSupply'], expected_supply) # Supply
# check_chain_value(pools[0], expected_supply) # Transparent
check_chain_value(pools[1], Decimal('0')) # Sprout
check_chain_value(pools[2], Decimal('0')) # Sapling
check_chain_value(pools[3], Decimal('0')) # Orchard
# Node 0 shields funds to Sapling address
taddr0 = get_coinbase_address(self.nodes[0])
recipients = []
@ -63,25 +102,36 @@ class WalletPersistenceTest (BitcoinTestFramework):
self.sync_all()
self.nodes[0].generate(1)
expected_supply += post_halving_subsidy
self.sync_all()
# Verify shielded balance
assert_equal(self.nodes[0].z_getbalance(sapling_addr), Decimal('20'))
# Verify size of shielded pools
pools = self.nodes[0].getblockchaininfo()['valuePools']
assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout
assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling
# Verify size of pools
chainInfo = self.nodes[0].getblockchaininfo()
pools = chainInfo['valuePools']
# Reenable these tests in v5.4.0-rc1
# check_chain_value(chainInfo['chainSupply'], expected_supply) # Supply
# check_chain_value(pools[0], expected_supply - Decimal('20')) # Transparent
check_chain_value(pools[1], Decimal('0')) # Sprout
check_chain_value(pools[2], Decimal('20')) # Sapling
check_chain_value(pools[3], Decimal('0')) # Orchard
# Restart the nodes
stop_nodes(self.nodes)
wait_bitcoinds()
self.setup_network()
# Verify size of shielded pools
pools = self.nodes[0].getblockchaininfo()['valuePools']
assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout
assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling
# Verify size of pools
chainInfo = self.nodes[0].getblockchaininfo()
pools = chainInfo['valuePools']
# Reenable these tests in v5.4.0-rc1
# check_chain_value(chainInfo['chainSupply'], expected_supply) # Supply
# check_chain_value(pools[0], expected_supply - Decimal('20')) # Transparent
check_chain_value(pools[1], Decimal('0')) # Sprout
check_chain_value(pools[2], Decimal('20')) # Sapling
check_chain_value(pools[3], Decimal('0')) # Orchard
# Node 0 sends some shielded funds to Node 1
dest_addr = self.nodes[1].z_getnewaddress('sapling')
@ -147,4 +197,4 @@ class WalletPersistenceTest (BitcoinTestFramework):
assert_equal(self.nodes[1].z_getbalance(dest_addr), Decimal('16'))
if __name__ == '__main__':
WalletPersistenceTest().main()
WalletPersistenceTest().main()

View File

@ -23,6 +23,7 @@ static const int SPROUT_VALUE_VERSION = 1001400;
static const int SAPLING_VALUE_VERSION = 1010100;
static const int CHAIN_HISTORY_ROOT_VERSION = 2010200;
static const int NU5_DATA_VERSION = 4050000;
static const int TRANSPARENT_VALUE_VERSION = 5040000;
/**
* Maximum amount of time that a block timestamp is allowed to be ahead of the
@ -250,6 +251,34 @@ public:
//! (memory only) The anchor for the tree state up to the end of this block
uint256 hashFinalSproutRoot;
//! The change to the chain supply caused by this block. This is defined as
//! the value of the coinbase outputs in this block, minus fees not claimed
//! by the miner.
//!
//! Will be std::nullopt under the following conditions:
//! - if the block has never been connected to a chain tip
//! - for older blocks until a reindex has taken place
std::optional<CAmount> nChainSupplyDelta;
//! (memory only) Total chain supply up to and including this block.
//!
//! Will be std::nullopt until a reindex has taken place, if nChainTx is
//! zero, or if the block has never been connected to a chain tip.
std::optional<CAmount> nChainTotalSupply;
//! Change in value in the transparent pool produced by the action of the
//! transparent inputs to and outputs from transactions in this block.
//!
//! Will be std::nullopt for older blocks until a reindex has taken place.
std::optional<CAmount> nTransparentValue;
//! (memory only) Total value of the transparent value pool up to and
//! including this block.
//!
//! Will be std::nullopt until a reindex has taken place.
//! Will be std::nullopt if nChainTx is zero.
std::optional<CAmount> nChainTransparentValue;
//! Change in value held by the Sprout circuit over this block.
//! Will be std::nullopt for older blocks on old nodes until a reindex has taken place.
std::optional<CAmount> nSproutValue;
@ -342,6 +371,11 @@ public:
hashFinalOrchardRoot = uint256();
hashChainHistoryRoot = uint256();
nSequenceId = 0;
nChainSupplyDelta = std::nullopt;
nChainTotalSupply = std::nullopt;
nTransparentValue = std::nullopt;
nChainTransparentValue = std::nullopt;
nSproutValue = std::nullopt;
nChainSproutValue = std::nullopt;
nSaplingValue = 0;
@ -539,6 +573,13 @@ public:
READWRITE(nNonce);
READWRITE(nSolution);
// Only read/write nTransparentValue if the client version used to create
// this index was storing them.
if ((s.GetType() & SER_DISK) && (nVersion >= TRANSPARENT_VALUE_VERSION)) {
READWRITE(nChainSupplyDelta);
READWRITE(nTransparentValue);
}
// Only read/write nSproutValue if the client version used to create
// this index was storing them.
if ((s.GetType() & SER_DISK) && (nVersion >= SPROUT_VALUE_VERSION)) {

View File

@ -990,6 +990,12 @@ const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const
}
CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
{
return GetTransparentValueIn(tx) + tx.GetShieldedValueIn();
}
// TODO: remove this if it ends up unused
CAmount CCoinsViewCache::GetTransparentValueIn(const CTransaction& tx) const
{
if (tx.IsCoinBase())
return 0;
@ -998,8 +1004,6 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
for (unsigned int i = 0; i < tx.vin.size(); i++)
nResult += GetOutputFor(tx.vin[i]).nValue;
nResult += tx.GetShieldedValueIn();
return nResult;
}

View File

@ -601,15 +601,22 @@ public:
size_t DynamicMemoryUsage() const;
/**
* Amount of bitcoins coming in to a transaction
* Note that lightweight clients may not know anything besides the hash of previous transactions,
* so may not be able to calculate this.
* Amount of coins coming in to a transaction
*
* @param[in] tx transaction for which we are checking input total
* @return Sum of value of all inputs (scriptSigs), (positive valueBalance or zero) and JoinSplit vpub_new
* @return Sum of value of all inputs (scriptSigs), JoinSplit vpub_new, and
* positive values of valueBalanceSapling, and valueBalanceOrchard.
*/
CAmount GetValueIn(const CTransaction& tx) const;
/**
* Amount of coins coming in to a transaction in the transparent inputs.
*
* @param[in] tx transaction for which we are checking input total
* @return Sum of value of all inputs (scriptSigs)
*/
CAmount GetTransparentValueIn(const CTransaction& tx) const;
//! Check whether all prevouts of the transaction are present in the UTXO set represented by this view
bool HaveInputs(const CTransaction& tx) const;

View File

@ -3258,6 +3258,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
auto consensusBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus());
auto prevConsensusBranchId = CurrentEpochBranchId(pindex->nHeight - 1, chainparams.GetConsensus());
CAmount chainSupplyDelta = 0;
CAmount transparentValueDelta = 0;
size_t total_sapling_tx = 0;
size_t total_orchard_tx = 0;
@ -3293,7 +3295,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
REJECT_INVALID, "bad-txns-inputs-missingorspent");
for (const auto& input : tx.vin) {
allPrevOutputs.push_back(view.GetOutputFor(input));
const auto prevout = view.GetOutputFor(input);
transparentValueDelta -= prevout.nValue;
allPrevOutputs.push_back(prevout);
}
// insightexplorer
@ -3339,9 +3343,20 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
txdata.emplace_back(tx, allPrevOutputs);
if (!tx.IsCoinBase())
if (tx.IsCoinBase())
{
nFees += view.GetValueIn(tx)-tx.GetValueOut();
// Add the output value of the coinbase transaction to the chain supply
// delta. This includes fees, which are then canceled out by the fee
// subtractions in the other branch of this conditional.
chainSupplyDelta += tx.GetValueOut();
} else {
const auto txFee = view.GetValueIn(tx) - tx.GetValueOut();
nFees += txFee;
// Fees from a transaction do not go into an output of the transaction,
// and therefore decrease the chain supply. If the miner claims them,
// they will be re-added in the other branch of this conditional.
chainSupplyDelta -= txFee;
std::vector<CScriptCheck> vChecks;
if (!ContextualCheckInputs(tx, state, view, fExpensiveChecks, flags, fCacheResults, txdata.back(), chainparams.GetConsensus(), consensusBranchId, nScriptCheckThreads ? &vChecks : NULL))
@ -3415,6 +3430,10 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
REJECT_INVALID, "orchard-commitment-tree-full");
};
for (const auto& out : tx.vout) {
transparentValueDelta += out.nValue;
}
if (!(tx.vShieldedSpend.empty() && tx.vShieldedOutput.empty())) {
total_sapling_tx += 1;
}
@ -3442,6 +3461,27 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
view.PushAnchor(sapling_tree);
view.PushAnchor(orchard_tree);
if (!fJustCheck) {
// Update pindex with the net change in transparent value and the chain's total
// transparent value.
pindex->nChainSupplyDelta = chainSupplyDelta;
pindex->nTransparentValue = transparentValueDelta;
if (pindex->pprev) {
if (pindex->pprev->nChainTotalSupply) {
pindex->nChainTotalSupply = *pindex->pprev->nChainTotalSupply + chainSupplyDelta;
} else {
pindex->nChainTotalSupply = std::nullopt;
}
if (pindex->pprev->nChainTransparentValue) {
pindex->nChainTransparentValue = *pindex->pprev->nChainTransparentValue + transparentValueDelta;
} else {
pindex->nChainTransparentValue = std::nullopt;
}
} else {
pindex->nChainTotalSupply = chainSupplyDelta;
pindex->nChainTransparentValue = transparentValueDelta;
}
pindex->hashFinalSproutRoot = sprout_tree.root();
// - If this block is before Heartwood activation, then we don't set
// hashFinalSaplingRoot here to maintain the invariant documented in
@ -4550,10 +4590,24 @@ bool ReceivedBlockTransactions(
{
pindexNew->nTx = block.vtx.size();
pindexNew->nChainTx = 0;
// the following values are computed here only for the genesis block
CAmount chainSupplyDelta = 0;
CAmount transparentValueDelta = 0;
CAmount sproutValue = 0;
CAmount saplingValue = 0;
CAmount orchardValue = 0;
for (auto tx : block.vtx) {
// For the genesis block only, compute the chain supply delta and the transparent
// output total.
if (pindexNew->pprev == nullptr) {
chainSupplyDelta = tx.GetValueOut();
for (const auto& out : tx.vout) {
transparentValueDelta += out.nValue;
}
}
// Negative valueBalanceSapling "takes" money from the transparent value pool
// and adds it to the Sapling value pool. Positive valueBalanceSapling "gives"
// money to the transparent value pool, removing from the Sapling value
@ -4568,12 +4622,27 @@ bool ReceivedBlockTransactions(
sproutValue -= js.vpub_new;
}
}
// These values can only be computed here for the genesis block.
// For all other blocks, we update them in ConnectBlock instead.
if (pindexNew->pprev == nullptr) {
pindexNew->nChainSupplyDelta = chainSupplyDelta;
pindexNew->nTransparentValue = transparentValueDelta;
} else {
pindexNew->nChainSupplyDelta = std::nullopt;
pindexNew->nTransparentValue = std::nullopt;
}
pindexNew->nChainTotalSupply = std::nullopt;
pindexNew->nChainTransparentValue = std::nullopt;
pindexNew->nSproutValue = sproutValue;
pindexNew->nChainSproutValue = std::nullopt;
pindexNew->nSaplingValue = saplingValue;
pindexNew->nChainSaplingValue = std::nullopt;
pindexNew->nOrchardValue = orchardValue;
pindexNew->nChainOrchardValue = std::nullopt;
pindexNew->nFile = pos.nFile;
pindexNew->nDataPos = pos.nPos;
pindexNew->nUndoPos = 0;
@ -4591,23 +4660,36 @@ bool ReceivedBlockTransactions(
CBlockIndex *pindex = queue.front();
queue.pop_front();
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
if (pindex->pprev) {
// Transparent value and chain total supply are added to the
// block index only in `ConnectBlock`, because that's the only
// place that we have a valid coins view with which to compute
// the transparent input value and fees.
// calculate the block's effect on the chain's net Sprout value
if (pindex->pprev->nChainSproutValue && pindex->nSproutValue) {
pindex->nChainSproutValue = *pindex->pprev->nChainSproutValue + *pindex->nSproutValue;
} else {
pindex->nChainSproutValue = std::nullopt;
}
// calculate the block's effect on the chain's net Sapling value
if (pindex->pprev->nChainSaplingValue) {
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + pindex->nSaplingValue;
} else {
pindex->nChainSaplingValue = std::nullopt;
}
// calculate the block's effect on the chain's net Orchard value
if (pindex->pprev->nChainOrchardValue) {
pindex->nChainOrchardValue = *pindex->pprev->nChainOrchardValue + pindex->nOrchardValue;
} else {
pindex->nChainOrchardValue = std::nullopt;
}
} else {
pindex->nChainTotalSupply = pindex->nChainSupplyDelta;
pindex->nChainTransparentValue = pindex->nTransparentValue;
pindex->nChainSproutValue = pindex->nSproutValue;
pindex->nChainSaplingValue = pindex->nSaplingValue;
pindex->nChainOrchardValue = pindex->nOrchardValue;
@ -5371,16 +5453,31 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams)
if (pindex->pprev) {
if (pindex->pprev->nChainTx) {
pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx;
if (pindex->pprev->nChainTotalSupply && pindex->nChainSupplyDelta) {
pindex->nChainTotalSupply = *pindex->pprev->nChainTotalSupply + *pindex->nChainSupplyDelta;
} else {
pindex->nChainTotalSupply = std::nullopt;
}
if (pindex->pprev->nChainTransparentValue && pindex->nTransparentValue) {
pindex->nChainTransparentValue = *pindex->pprev->nChainTransparentValue + *pindex->nTransparentValue;
} else {
pindex->nChainTransparentValue = std::nullopt;
}
if (pindex->pprev->nChainSproutValue && pindex->nSproutValue) {
pindex->nChainSproutValue = *pindex->pprev->nChainSproutValue + *pindex->nSproutValue;
} else {
pindex->nChainSproutValue = std::nullopt;
}
if (pindex->pprev->nChainSaplingValue) {
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + pindex->nSaplingValue;
} else {
pindex->nChainSaplingValue = std::nullopt;
}
if (pindex->pprev->nChainOrchardValue) {
pindex->nChainOrchardValue = *pindex->pprev->nChainOrchardValue + pindex->nOrchardValue;
} else {
@ -5388,6 +5485,8 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams)
}
} else {
pindex->nChainTx = 0;
pindex->nChainTotalSupply = std::nullopt;
pindex->nChainTransparentValue = std::nullopt;
pindex->nChainSproutValue = std::nullopt;
pindex->nChainSaplingValue = std::nullopt;
pindex->nChainOrchardValue = std::nullopt;
@ -5395,6 +5494,8 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams)
}
} else {
pindex->nChainTx = pindex->nTx;
pindex->nChainTotalSupply = pindex->nChainSupplyDelta;
pindex->nChainTransparentValue = pindex->nTransparentValue;
pindex->nChainSproutValue = pindex->nSproutValue;
pindex->nChainSaplingValue = pindex->nSaplingValue;
pindex->nChainOrchardValue = pindex->nOrchardValue;

View File

@ -265,12 +265,11 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) {
CAmount CTransaction::GetValueOut() const
{
CAmount nValueOut = 0;
for (std::vector<CTxOut>::const_iterator it(vout.begin()); it != vout.end(); ++it)
{
if (!MoneyRange(it->nValue)) {
for (const auto& out : vout) {
if (!MoneyRange(out.nValue)) {
throw std::runtime_error("CTransaction::GetValueOut(): nValue out of range");
}
nValueOut += it->nValue;
nValueOut += out.nValue;
if (!MoneyRange(nValueOut)) {
throw std::runtime_error("CTransaction::GetValueOut(): nValueOut out of range");
}
@ -301,13 +300,12 @@ CAmount CTransaction::GetValueOut() const
}
}
for (std::vector<JSDescription>::const_iterator it(vJoinSplit.begin()); it != vJoinSplit.end(); ++it)
{
for (const auto& jsDescription : vJoinSplit) {
// NB: vpub_old "takes" money from the transparent value pool just as outputs do
if (!MoneyRange(it->vpub_old)) {
if (!MoneyRange(jsDescription.vpub_old)) {
throw std::runtime_error("CTransaction::GetValueOut(): vpub_old out of range");
}
nValueOut += it->vpub_old;
nValueOut += jsDescription.vpub_old;
if (!MoneyRange(nValueOut)) {
throw std::runtime_error("CTransaction::GetValueOut(): value out of range");
}
@ -343,13 +341,12 @@ CAmount CTransaction::GetShieldedValueIn() const
}
}
for (std::vector<JSDescription>::const_iterator it(vJoinSplit.begin()); it != vJoinSplit.end(); ++it)
{
for (const auto& jsDescription : vJoinSplit) {
// NB: vpub_new "gives" money to the transparent value pool just as inputs do
if (!MoneyRange(it->vpub_new)) {
if (!MoneyRange(jsDescription.vpub_new)) {
throw std::runtime_error("CTransaction::GetShieldedValueIn(): vpub_new out of range");
}
nValue += it->vpub_new;
nValue += jsDescription.vpub_new;
if (!MoneyRange(nValue)) {
throw std::runtime_error("CTransaction::GetShieldedValueIn(): value out of range");
}

View File

@ -84,12 +84,14 @@ double GetNetworkDifficulty(const CBlockIndex* blockindex)
}
static UniValue ValuePoolDesc(
const std::string &name,
const std::optional<std::string> name,
const std::optional<CAmount> chainValue,
const std::optional<CAmount> valueDelta)
{
UniValue rv(UniValue::VOBJ);
rv.pushKV("id", name);
if (name.has_value()) {
rv.pushKV("id", name.value());
}
rv.pushKV("monitored", (bool)chainValue);
if (chainValue) {
rv.pushKV("chainValue", ValueFromAmount(*chainValue));
@ -265,8 +267,9 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
result.pushKV("difficulty", GetDifficulty(blockindex));
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
result.pushKV("anchor", blockindex->hashFinalSproutRoot.GetHex());
result.pushKV("chainSupply", ValuePoolDesc(std::nullopt, blockindex->nChainTotalSupply, blockindex->nChainSupplyDelta));
UniValue valuePools(UniValue::VARR);
valuePools.push_back(ValuePoolDesc("transparent", blockindex->nChainTransparentValue, blockindex->nTransparentValue));
valuePools.push_back(ValuePoolDesc("sprout", blockindex->nChainSproutValue, blockindex->nSproutValue));
valuePools.push_back(ValuePoolDesc("sapling", blockindex->nChainSaplingValue, blockindex->nSaplingValue));
valuePools.push_back(ValuePoolDesc("orchard", blockindex->nChainOrchardValue, blockindex->nOrchardValue));
@ -742,6 +745,23 @@ UniValue getblock(const UniValue& params, bool fHelp)
" \"nonce\" : n, (numeric) The nonce\n"
" \"bits\" : \"1d00ffff\", (string) The bits\n"
" \"difficulty\" : x.xxx, (numeric) The difficulty\n"
" \"chainSupply\": { (object) information about the total supply\n"
" \"monitored\": xx, (boolean) true if the total supply is being monitored\n"
" \"chainValue\": xxxxxx, (numeric, optional) total chain supply\n"
" \"chainValueZat\": xxxxxx, (numeric, optional) total chain supply in satoshis\n"
" \"valueDelta\": xxxxxx, (numeric, optional) change to the chain supply produced by this block\n"
" \"valueDeltaZat\": xxxxxx, (numeric, optional) change to the chain supply produced by this block, in satoshis\n"
" }\n"
" \"valuePools\": [ (array) information about each value pool\n"
" {\n"
" \"id\": \"xxxx\", (string) name of the pool\n"
" \"monitored\": xx, (boolean) true if the pool is being monitored\n"
" \"chainValue\": xxxxxx, (numeric, optional) total amount in the pool\n"
" \"chainValueZat\": xxxxxx, (numeric, optional) total amount in the pool in satoshis\n"
" \"valueDelta\": xxxxxx, (numeric, optional) change to the amount in the pool produced by this block\n"
" \"valueDeltaZat\": xxxxxx, (numeric, optional) change to the amount in the pool produced by this block, in satoshis\n"
" }, ...\n"
" ]\n"
" \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n"
" \"nextblockhash\" : \"hash\" (string) The hash of the next block\n"
"}\n"
@ -1036,6 +1056,19 @@ UniValue getblockchaininfo(const UniValue& params, bool fHelp)
" \"chainwork\": \"xxxx\" (string) total amount of work in active chain, in hexadecimal\n"
" \"size_on_disk\": xxxxxx, (numeric) the estimated size of the block and undo files on disk\n"
" \"commitments\": xxxxxx, (numeric) the current number of note commitments in the commitment tree\n"
" \"chainSupply\": { (object) information about the total supply\n"
" \"monitored\": xx, (boolean) true if the total supply is being monitored\n"
" \"chainValue\": xxxxxx, (numeric, optional) total chain supply\n"
" \"chainValueZat\": xxxxxx, (numeric, optional) total chain supply in satoshis\n"
" }\n"
" \"valuePools\": [ (array) information about each value pool\n"
" {\n"
" \"id\": \"xxxx\", (string) name of the pool\n"
" \"monitored\": xx, (boolean) true if the pool is being monitored\n"
" \"chainValue\": xxxxxx, (numeric, optional) total amount in the pool\n"
" \"chainValueZat\": xxxxxx, (numeric, optional) total amount in the pool in satoshis\n"
" }, ...\n"
" ]\n"
" \"softforks\": [ (array) status of softforks in progress\n"
" {\n"
" \"id\": \"xxxx\", (string) name of softfork\n"
@ -1091,7 +1124,9 @@ UniValue getblockchaininfo(const UniValue& params, bool fHelp)
obj.pushKV("commitments", static_cast<uint64_t>(tree.size()));
CBlockIndex* tip = chainActive.Tip();
obj.pushKV("chainSupply", ValuePoolDesc(std::nullopt, tip->nChainTotalSupply, std::nullopt));
UniValue valuePools(UniValue::VARR);
valuePools.push_back(ValuePoolDesc("transparent", tip->nChainTransparentValue, std::nullopt));
valuePools.push_back(ValuePoolDesc("sprout", tip->nChainSproutValue, std::nullopt));
valuePools.push_back(ValuePoolDesc("sapling", tip->nChainSaplingValue, std::nullopt));
valuePools.push_back(ValuePoolDesc("orchard", tip->nChainOrchardValue, std::nullopt));

View File

@ -626,6 +626,8 @@ bool CBlockTreeDB::LoadBlockIndexGuts(
pindexNew->nStatus = diskindex.nStatus;
pindexNew->nCachedBranchId = diskindex.nCachedBranchId;
pindexNew->nTx = diskindex.nTx;
pindexNew->nChainSupplyDelta = diskindex.nChainSupplyDelta;
pindexNew->nTransparentValue = diskindex.nTransparentValue;
pindexNew->nSproutValue = diskindex.nSproutValue;
pindexNew->nSaplingValue = diskindex.nSaplingValue;
pindexNew->nOrchardValue = diskindex.nOrchardValue;