Merge pull request #4470 from oxarbitrage/issue4294
Add transparent value pool to RPC calls
This commit is contained in:
commit
0e931a9397
|
@ -130,3 +130,5 @@ src/fuzzing/*/output
|
|||
src/fuzz.cpp
|
||||
|
||||
.updatecheck-token
|
||||
.env
|
||||
poetry.lock
|
||||
|
|
|
@ -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"
|
||||
|
|
@ -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
|
@ -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",
|
||||
])
|
|
@ -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)
|
|
@ -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]
|
|
@ -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')
|
||||
|
|
42
src/chain.h
42
src/chain.h
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
15
src/coins.h
15
src/coins.h
|
@ -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;
|
||||
|
||||
|
|
127
src/main.cpp
127
src/main.cpp
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue