Merge pull request #4470 from oxarbitrage/issue4294

Add transparent value pool to RPC calls
This commit is contained in:
str4d 2023-01-23 21:55:00 +00:00 committed by GitHub
commit 0e931a9397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 16189 additions and 35 deletions

2
.gitignore vendored
View File

@ -130,3 +130,5 @@ src/fuzzing/*/output
src/fuzz.cpp
.updatecheck-token
.env
poetry.lock

View File

@ -0,0 +1,29 @@
[tool.poetry]
name = "zcash-metrics"
version = "0.1.0"
description = "Zcash Metrics"
authors = [
"Jack Grigg <jack@electriccoin.co>",
"Daira Hopwood <daira@jacaranda.org>",
"Kris Nuttycombe <kris@nutty.land>",
]
license = "MIT OR Apache-2.0"
readme = "README.md"
homepage = "https://github.com/zcash/zcash/"
repository = "https://github.com/zcash/zcash/"
documentation = "https://github.com/zcash/zcash/"
classifiers = [
"Private :: Do Not Upload",
]
packages = [
{ include = "supply_check" }
]
[tool.poetry.dependencies]
python = "^3.7"
slick-bitcoinrpc = "0.1.4"
progressbar2 = "4.2.0"
[tool.poetry.scripts]
supply-check = "supply_check:main"

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
import os
import progressbar
from slickrpc.rpc import Proxy
from .deltas_mainnet import MainnetSupplyDeltas
from .theoretical import Network, MAINNET
COIN=100000000
TXIDS_ONLY=1
FULL_TX_DATA=2
# Returns the theoretical supply, the total block rewards claimed,
# and the block at the given height.
#
# - `zcashd` a slickrpc.rpc.Proxy that can be used to access zcashd
# - `deltas` a SupplyDeltas object tracking the deltas observed
# - `height` the block height to consider
# - `flag` Either `TXIDS_ONLY` or `FULL_TX_DATA`. This flag will be provided to
# the getblock RPC call to indicate how much data should be returned. When
# `TXIDS_ONLY` is provided, data for transactions in the block will be limited
# to transaction identifiers; when `FULL_TX_DATA` is provided full transaction
# data will be returned for each transaction in the block.
def TheoreticalAndEmpirical(zcashd, deltas, height, flag):
theoreticalSupply = Network(MAINNET).SupplyAfterHeight(height)
block = zcashd.getblock(str(height), flag)
measuredSupply = block['chainSupply']['chainValueZat']
empiricalMaximum = measuredSupply + deltas.DeviationUpToHeight(height)
return (theoreticalSupply, empiricalMaximum, block)
# Returns `True` if the theoretical supply matches the empirically
# determined supply after accounting for known deltas over the specified
# range, or `False` if it was not possible to determine a miner address
# for a block containing unclaimed fee and/or block reward amounts.
#
# - `startHeight` The block height at which to begin checking
# - `endHeight` The end of the block height range to check (inclusive)
def Bisect(bar, zcashd, deltas, startHeight, endHeight):
assert startHeight <= endHeight
bar.update(startHeight)
flag = FULL_TX_DATA if startHeight == endHeight else TXIDS_ONLY
(theoretical, empirical, block) = TheoreticalAndEmpirical(zcashd, deltas, endHeight, flag)
if theoretical == empirical:
return True
elif startHeight == endHeight:
return deltas.SaveMismatch(block, theoretical, empirical)
else:
midpoint = (startHeight + endHeight) // 2
return (Bisect(bar, zcashd, deltas, startHeight, midpoint) and
Bisect(bar, zcashd, deltas, midpoint + 1, endHeight))
def main():
missing_env = []
if os.environ.get('ZCASHD_RPC_USER') is None:
missing_env.append(' ZCASHD_RPC_USER: username for accessing the zcashd RPC API')
if os.environ.get('ZCASHD_RPC_PASS') is None:
missing_env.append(' ZCASHD_RPC_PASS: RPC API password for <ZCASHD_RPC_USER>')
if os.environ.get('ZCASHD_RPC_HOST') is None:
missing_env.append(' ZCASHD_RPC_HOST: hostname where zcashd is running')
if os.environ.get('ZCASHD_RPC_PORT') is None:
missing_env.append(' ZCASHD_RPC_PORT: zcashd RPC API port (usually 8232 for mainnet)')
if len(missing_env) > 0:
print("Please ensure that the following environment variables have been set:")
for v in missing_env:
print(v)
return
zcashd = Proxy('http://{rpc_user}:{rpc_pass}@{rpc_host}:{rpc_port}'.format(
rpc_user=os.environ['ZCASHD_RPC_USER'],
rpc_pass=os.environ['ZCASHD_RPC_PASS'],
rpc_host=os.environ['ZCASHD_RPC_HOST'],
rpc_port=os.environ['ZCASHD_RPC_PORT'],
))
latestHeight = zcashd.getblockchaininfo()['blocks']
deltas = MainnetSupplyDeltas()
(theoretical, empirical, block) = TheoreticalAndEmpirical(zcashd, deltas, latestHeight, TXIDS_ONLY)
interrupted = False
if theoretical != empirical:
with progressbar.ProgressBar(max_value = latestHeight, redirect_stdout = True) as bar:
try:
Bisect(bar, zcashd, deltas, 0, latestHeight)
except KeyboardInterrupt:
interrupted = True
pass
deltas.PrintDeltas()
print("Block height: {}".format(latestHeight))
print("Chain total value: {} ZEC".format(block['chainSupply']['chainValueZat'] / COIN))
print("Theoretical maximum supply: {} ZEC".format(theoretical / COIN))
if interrupted:
print("Supply check was interrupted; supply delta evaluation incomplete.")
else:
print("Blocks with unclaimed balance: {}".format(len(deltas.delta_cache)))
print("Unclaimed total: {} ZEC".format(deltas.delta_total / COIN))
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
fr_addrs = set([
# FR addresses
"t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd",
"t3cL9AucCajm3HXDhb5jBnJK2vapVoXsop3",
"t3fqvkzrrNaMcamkQMwAyHRjfDdM2xQvDTR",
"t3TgZ9ZT2CTSK44AnUPi6qeNaHa2eC7pUyF",
"t3SpkcPQPfuRYHsP5vz3Pv86PgKo5m9KVmx",
"t3Xt4oQMRPagwbpQqkgAViQgtST4VoSWR6S",
"t3ayBkZ4w6kKXynwoHZFUSSgXRKtogTXNgb",
"t3adJBQuaa21u7NxbR8YMzp3km3TbSZ4MGB",
"t3K4aLYagSSBySdrfAGGeUd5H9z5Qvz88t2",
"t3RYnsc5nhEvKiva3ZPhfRSk7eyh1CrA6Rk",
"t3Ut4KUq2ZSMTPNE67pBU5LqYCi2q36KpXQ",
"t3ZnCNAvgu6CSyHm1vWtrx3aiN98dSAGpnD",
"t3fB9cB3eSYim64BS9xfwAHQUKLgQQroBDG",
"t3cwZfKNNj2vXMAHBQeewm6pXhKFdhk18kD",
"t3YcoujXfspWy7rbNUsGKxFEWZqNstGpeG4",
"t3bLvCLigc6rbNrUTS5NwkgyVrZcZumTRa4",
"t3VvHWa7r3oy67YtU4LZKGCWa2J6eGHvShi",
"t3eF9X6X2dSo7MCvTjfZEzwWrVzquxRLNeY",
"t3esCNwwmcyc8i9qQfyTbYhTqmYXZ9AwK3X",
"t3M4jN7hYE2e27yLsuQPPjuVek81WV3VbBj",
"t3gGWxdC67CYNoBbPjNvrrWLAWxPqZLxrVY",
"t3LTWeoxeWPbmdkUD3NWBquk4WkazhFBmvU",
"t3P5KKX97gXYFSaSjJPiruQEX84yF5z3Tjq",
"t3f3T3nCWsEpzmD35VK62JgQfFig74dV8C9",
"t3Rqonuzz7afkF7156ZA4vi4iimRSEn41hj",
"t3fJZ5jYsyxDtvNrWBeoMbvJaQCj4JJgbgX",
"t3Pnbg7XjP7FGPBUuz75H65aczphHgkpoJW",
"t3WeKQDxCijL5X7rwFem1MTL9ZwVJkUFhpF",
"t3Y9FNi26J7UtAUC4moaETLbMo8KS1Be6ME",
"t3aNRLLsL2y8xcjPheZZwFy3Pcv7CsTwBec",
"t3gQDEavk5VzAAHK8TrQu2BWDLxEiF1unBm",
"t3Rbykhx1TUFrgXrmBYrAJe2STxRKFL7G9r",
"t3aaW4aTdP7a8d1VTE1Bod2yhbeggHgMajR",
"t3YEiAa6uEjXwFL2v5ztU1fn3yKgzMQqNyo",
"t3g1yUUwt2PbmDvMDevTCPWUcbDatL2iQGP",
"t3dPWnep6YqGPuY1CecgbeZrY9iUwH8Yd4z",
"t3QRZXHDPh2hwU46iQs2776kRuuWfwFp4dV",
"t3enhACRxi1ZD7e8ePomVGKn7wp7N9fFJ3r",
"t3PkLgT71TnF112nSwBToXsD77yNbx2gJJY",
"t3LQtHUDoe7ZhhvddRv4vnaoNAhCr2f4oFN",
"t3fNcdBUbycvbCtsD2n9q3LuxG7jVPvFB8L",
"t3dKojUU2EMjs28nHV84TvkVEUDu1M1FaEx",
"t3aKH6NiWN1ofGd8c19rZiqgYpkJ3n679ME",
"t3MEXDF9Wsi63KwpPuQdD6by32Mw2bNTbEa",
"t3WDhPfik343yNmPTqtkZAoQZeqA83K7Y3f",
"t3PSn5TbMMAEw7Eu36DYctFezRzpX1hzf3M",
"t3R3Y5vnBLrEn8L6wFjPjBLnxSUQsKnmFpv",
"t3Pcm737EsVkGTbhsu2NekKtJeG92mvYyoN",
# ECC funding stream addresses
"t3LmX1cxWPPPqL4TZHx42HU3U5ghbFjRiif",
"t3Toxk1vJQ6UjWQ42tUJz2rV2feUWkpbTDs",
"t3ZBdBe4iokmsjdhMuwkxEdqMCFN16YxKe6",
"t3ZuaJziLM8xZ32rjDUzVjVtyYdDSz8GLWB",
"t3bAtYWa4bi8VrtvqySxnbr5uqcG9czQGTZ",
"t3dktADfb5Rmxncpe1HS5BRS5Gcj7MZWYBi",
"t3hgskquvKKoCtvxw86yN7q8bzwRxNgUZmc",
"t3R1VrLzwcxAZzkX4mX3KGbWpNsgtYtMntj",
"t3ff6fhemqPMVujD3AQurxRxTdvS1pPSaa2",
"t3cEUQFG3KYnFG6qYhPxSNgGi3HDjUPwC3J",
"t3WR9F5U4QvUFqqx9zFmwT6xFqduqRRXnaa",
"t3PYc1LWngrdUrJJbHkYPCKvJuvJjcm85Ch",
"t3bgkjiUeatWNkhxY3cWyLbTxKksAfk561R",
"t3Z5rrR8zahxUpZ8itmCKhMSfxiKjUp5Dk5",
"t3PU1j7YW3fJ67jUbkGhSRto8qK2qXCUiW3",
"t3S3yaT7EwNLaFZCamfsxxKwamQW2aRGEkh",
"t3eutXKJ9tEaPSxZpmowhzKhPfJvmtwTEZK",
"t3gbTb7brxLdVVghSPSd3ycGxzHbUpukeDm",
"t3UCKW2LrHFqPMQFEbZn6FpjqnhAAbfpMYR",
"t3NyHsrnYbqaySoQqEQRyTWkjvM2PLkU7Uu",
"t3QEFL6acxuZwiXtW3YvV6njDVGjJ1qeaRo",
"t3PdBRr2S1XTDzrV8bnZkXF3SJcrzHWe1wj",
"t3ZWyRPpWRo23pKxTLtWsnfEKeq9T4XPxKM",
"t3he6QytKCTydhpztykFsSsb9PmBT5JBZLi",
"t3VWxWDsLb2TURNEP6tA1ZSeQzUmPKFNxRY",
"t3NmWLvZkbciNAipauzsFRMxoZGqmtJksbz",
"t3cKr4YxVPvPBG1mCvzaoTTdBNokohsRJ8n",
"t3T3smGZn6BoSFXWWXa1RaoQdcyaFjMfuYK",
"t3gkDUe9Gm4GGpjMk86TiJZqhztBVMiUSSA",
"t3eretuBeBXFHe5jAqeSpUS1cpxVh51fAeb",
"t3dN8g9zi2UGJdixGe9txeSxeofLS9t3yFQ",
"t3S799pq9sYBFwccRecoTJ3SvQXRHPrHqvx",
"t3fhYnv1S5dXwau7GED3c1XErzt4n4vDxmf",
"t3cmE3vsBc5xfDJKXXZdpydCPSdZqt6AcNi",
"t3h5fPdjJVHaH4HwynYDM5BB3J7uQaoUwKi",
"t3Ma35c68BgRX8sdLDJ6WR1PCrKiWHG4Da9",
"t3LokMKPL1J8rkJZvVpfuH7dLu6oUWqZKQK",
"t3WFFGbEbhJWnASZxVLw2iTJBZfJGGX73mM",
"t3L8GLEsUn4QHNaRYcX3EGyXmQ8kjpT1zTa",
"t3PgfByBhaBSkH8uq4nYJ9ZBX4NhGCJBVYm",
"t3WecsqKDhWXD4JAgBVcnaCC2itzyNZhJrv",
"t3ZG9cSfopnsMQupKW5v9sTotjcP5P6RTbn",
"t3hC1Ywb5zDwUYYV8LwhvF5rZ6m49jxXSG5",
"t3VgMqDL15ZcyQDeqBsBW3W6rzfftrWP2yB",
"t3LC94Y6BwLoDtBoK2NuewaEbnko1zvR9rm",
"t3cWCUZJR3GtALaTcatrrpNJ3MGbMFVLRwQ",
"t3YYF4rPLVxDcF9hHFsXyc5Yq1TFfbojCY6",
"t3XHAGxRP2FNfhAjxGjxbrQPYtQQjc3RCQD",
# ZF funding stream addresses
"t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1",
# MG funding stream addresses
"t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym",
])

View File

@ -0,0 +1,60 @@
import pprint
import bisect
class SupplyDeltas:
def __init__(self, fr_addrs, miner_deltas, flush_interval = 500):
self.fr_addrs = fr_addrs
self.miner_deltas = miner_deltas
self.delta_total = 0
self.delta_cache = []
self.flush_interval = flush_interval
deltas_flat = [pair for deltas in miner_deltas.values() for pair in deltas]
for (deltaHeight, delta) in sorted(deltas_flat):
self.AddSupplyDelta(deltaHeight, delta)
# AddSupplyDelta must be called with heights in increasing order.
# It will raise an assertion error if an out-of-order insertion is
# attempted.
def AddSupplyDelta(self, deltaHeight, delta):
assert len(self.delta_cache) == 0 or deltaHeight > self.delta_cache[-1][0]
self.delta_total += delta
bisect.insort(self.delta_cache, (deltaHeight, self.delta_total), key=lambda x: x[0])
def DeviationUpToHeight(self, height):
i = bisect.bisect(self.delta_cache, height, key=lambda x: x[0])
return 0 if i == 0 else self.delta_cache[i - 1][1]
def SaveMismatch(self, block, theoretical, empirical):
height = block['height']
coinbase_tx = block['tx'][0]
delta = theoretical - empirical
print('Mismatch at height {}: {} != {} ({}) miner_deltas: {}'.format(
height,
theoretical,
empirical,
delta,
len(self.miner_deltas),
))
# if delta ever goes negative, we will halt
if delta >= 0:
miner_addrs = set([addr for out in coinbase_tx['vout'] for addr in out['scriptPubKey'].get('addresses', [])]) - self.fr_addrs
if len(miner_addrs) > 0:
self.miner_deltas.setdefault(",".join(sorted(miner_addrs)), []).append((height, delta))
self.AddSupplyDelta(height, delta)
if len(self.delta_cache) % 500 == 0:
with open("delta_cache.{}.out".format(len(self.delta_cache)), 'w', encoding="utf8") as f:
pprint.pprint(self.miner_deltas, stream = f, compact = True)
return True
pprint.pprint(coinbase_tx['vout'], indent = 4)
return False
def PrintDeltas(self):
with open("delta_cache.out", 'w', encoding="utf8") as f:
pprint.pprint(self.miner_deltas, stream = f, compact = True)

View File

@ -0,0 +1,67 @@
def exact_div(x, y):
assert x % y == 0
return x // y
# floor(u/x + v/y)
def div2(u, x, v, y):
return (u*y + v*x) // (x*y)
TESTNET = 0
MAINNET = 1
class Network:
# <https://zips.z.cash/protocol/protocol.pdf#constants>
def __init__(self, network):
self.BlossomActivationHeight = 653600 if network == MAINNET else 584000
SlowStartInterval = 20000
MaxBlockSubsidy = 1250000000 # 12.5 ZEC
PreBlossomHalvingInterval = 840000
PreBlossomPoWTargetSpacing = 150
PostBlossomPoWTargetSpacing = 75
# <https://zips.z.cash/protocol/protocol.pdf#diffadjustment>
def IsBlossomActivated(self, height):
return height >= self.BlossomActivationHeight
BlossomPoWTargetSpacingRatio = exact_div(PreBlossomPoWTargetSpacing, PostBlossomPoWTargetSpacing)
# no need for floor since this is necessarily an integer
PostBlossomHalvingInterval = PreBlossomHalvingInterval * BlossomPoWTargetSpacingRatio
# <https://zips.z.cash/protocol/protocol.pdf#subsidies>
SlowStartShift = exact_div(SlowStartInterval, 2)
SlowStartRate = exact_div(MaxBlockSubsidy, SlowStartInterval)
SupplyCache = []
def Halving(self, height):
if height < self.SlowStartShift:
return 0
elif not self.IsBlossomActivated(height):
return (height - self.SlowStartShift) // self.PreBlossomHalvingInterval
else:
return div2(self.BlossomActivationHeight - self.SlowStartShift, self.PreBlossomHalvingInterval,
height - self.BlossomActivationHeight, self.PostBlossomHalvingInterval)
def BlockSubsidy(self, height):
if height < self.SlowStartShift:
return self.SlowStartRate * height
elif self.SlowStartShift <= height and height < self.SlowStartInterval:
return self.SlowStartRate * (height + 1)
if self.SlowStartInterval <= height and not self.IsBlossomActivated(height):
return self.MaxBlockSubsidy // (1 << self.Halving(height))
else:
return self.MaxBlockSubsidy // (self.BlossomPoWTargetSpacingRatio << self.Halving(height))
def SupplyAfterHeight(self, height):
cacheLen = len(self.SupplyCache)
if cacheLen > height:
return self.SupplyCache[height]
else:
cur = 0 if cacheLen == 0 else self.SupplyCache[-1]
for h in range(cacheLen, height + 1):
cur += self.BlockSubsidy(h)
self.SupplyCache.append(cur)
return self.SupplyCache[-1]

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,22 @@ 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 = pre_halving_subsidy / 2
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 +59,21 @@ 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_id, expected_value):
assert_equal(pool.get('id', None), expected_id)
assert_equal(pool['monitored'], True)
assert_equal(pool['chainValue'], expected_value)
assert_equal(pool['chainValueZat'], expected_value * COIN)
# Verify size of pools
chainInfo = self.nodes[0].getblockchaininfo()
pools = chainInfo['valuePools']
check_chain_value(chainInfo['chainSupply'], None, expected_supply)
check_chain_value(pools[0], 'transparent', expected_supply)
check_chain_value(pools[1], 'sprout', Decimal('0'))
check_chain_value(pools[2], 'sapling', Decimal('0'))
check_chain_value(pools[3], 'orchard', Decimal('0'))
# 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-rc2
# check_chain_value(chainInfo['chainSupply'], None, expected_supply) # Supply
# check_chain_value(pools[0], 'transparent', expected_supply)
check_chain_value(pools[1], 'sprout', Decimal('0'))
check_chain_value(pools[2], 'sapling', Decimal('0'))
check_chain_value(pools[3], 'orchard', Decimal('0'))
# 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-rc2
# check_chain_value(chainInfo['chainSupply'], None, expected_supply) # Supply
# check_chain_value(pools[0], 'transparent', expected_supply - Decimal('20')) # Transparent
check_chain_value(pools[1], 'sprout', Decimal('0'))
check_chain_value(pools[2], 'sapling', Decimal('20'))
check_chain_value(pools[3], 'orchard', Decimal('0'))
# 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-rc2
# check_chain_value(chainInfo['chainSupply'], None, expected_supply) # Supply
# check_chain_value(pools[0], 'transparent', expected_supply - Decimal('20')) # Transparent
check_chain_value(pools[1], 'sprout', Decimal('0'))
check_chain_value(pools[2], 'sapling', Decimal('20'))
check_chain_value(pools[3], 'orchard', Decimal('0'))
# 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 = 5040026;
/**
* Maximum amount of time that a block timestamp is allowed to be ahead of the
@ -250,6 +251,35 @@ 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 (transparent and shielded) 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.
//! Will be std::nullopt 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 +372,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 +574,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,11 @@ const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const
}
CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
{
return GetTransparentValueIn(tx) + tx.GetShieldedValueIn();
}
CAmount CCoinsViewCache::GetTransparentValueIn(const CTransaction& tx) const
{
if (tx.IsCoinBase())
return 0;
@ -998,8 +1003,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 value and the chain's total value,
// both for the supply and for the transparent pool.
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
@ -3557,6 +3597,26 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
block.vtx[0].GetValueOut(), blockReward),
REJECT_INVALID, "bad-cb-amount");
// Ensure that the total chain supply is consistent with the value in each pool.
if (!fJustCheck &&
pindex->nChainTotalSupply.has_value() &&
pindex->nChainTransparentValue.has_value() &&
pindex->nChainSproutValue.has_value() &&
pindex->nChainSaplingValue.has_value() &&
pindex->nChainOrchardValue.has_value())
{
auto expectedChainSupply =
pindex->nChainTransparentValue.value() +
pindex->nChainSproutValue.value() +
pindex->nChainSaplingValue.value() +
pindex->nChainOrchardValue.value();
if (expectedChainSupply != pindex->nChainTotalSupply.value()) {
// This may be added as a rule to ZIP 209 and return a failure in a future soft-fork.
error("ConnectBlock(): chain total supply (%d) does not match sum of pool balances (%d) at height %d",
pindex->nChainTotalSupply.value(), expectedChainSupply, pindex->nHeight);
}
}
// Ensure Sapling authorizations are valid (if we are checking them)
if (saplingAuth.has_value() && !saplingAuth.value()->validate()) {
return state.DoS(100,
@ -4550,10 +4610,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 +4642,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 +4680,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 Sprout chain value pool balance.
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 Sapling chain value pool balance.
if (pindex->pprev->nChainSaplingValue) {
pindex->nChainSaplingValue = *pindex->pprev->nChainSaplingValue + pindex->nSaplingValue;
} else {
pindex->nChainSaplingValue = std::nullopt;
}
// Calculate the block's effect on the Orchard chain value pool balance.
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 +5473,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 +5505,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 +5514,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,18 +341,21 @@ 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");
}
}
if (IsCoinBase() && nValue != 0) {
throw std::runtime_error("CTransaction::GetShieldedValueIn(): shielded value of inputs must be zero in coinbase transactions.");
}
return nValue;
}

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 after this block, in " + CURRENCY_UNIT + "\n"
" \"chainValueZat\": xxxxxx, (numeric, optional) total chain supply after this block, in " + MINOR_CURRENCY_UNIT + "\n"
" \"valueDelta\": xxxxxx, (numeric, optional) change to the chain supply produced by this block, in " + CURRENCY_UNIT + "\n"
" \"valueDeltaZat\": xxxxxx, (numeric, optional) change to the chain supply produced by this block, in " + MINOR_CURRENCY_UNIT + "\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, in " + CURRENCY_UNIT + "\n"
" \"chainValueZat\": xxxxxx, (numeric, optional) total amount in the pool, in " + MINOR_CURRENCY_UNIT + "\n"
" \"valueDelta\": xxxxxx, (numeric, optional) change to the amount in the pool produced by this block, in " + CURRENCY_UNIT + "\n"
" \"valueDeltaZat\": xxxxxx, (numeric, optional) change to the amount in the pool produced by this block, in " + MINOR_CURRENCY_UNIT + "\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 after this block, in " + CURRENCY_UNIT + "\n"
" \"chainValueZat\": xxxxxx, (numeric, optional) total chain supply after this block, in " + MINOR_CURRENCY_UNIT + "\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, in " + CURRENCY_UNIT + "\n"
" \"chainValueZat\": xxxxxx, (numeric, optional) total amount in the pool, in " + MINOR_CURRENCY_UNIT + "\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;