Auto merge of #5220 - str4d:5197-header-commitments, r=str4d

NU5 header commitments and consensus rules

Part of zcash/zcash#5197.
This commit is contained in:
Homu 2021-06-18 10:24:28 +00:00
commit b0e6119551
16 changed files with 274 additions and 58 deletions

View File

@ -116,6 +116,7 @@ BASE_SCRIPTS= [
'framework.py', 'framework.py',
'sapling_rewind_check.py', 'sapling_rewind_check.py',
'feature_zip221.py', 'feature_zip221.py',
'feature_zip244_blockcommitments.py',
'upgrade_golden.py', 'upgrade_golden.py',
'post_heartwood_rollback.py', 'post_heartwood_rollback.py',
'feature_logging.py', 'feature_logging.py',

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
from test_framework.blocktools import derive_block_commitments_hash
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
bytes_to_hex_str,
hex_str_to_bytes,
start_nodes,
)
TERMINATOR = b'\x00' * 32
# Verify block header field 'hashLightClientRoot' is set correctly for NU5 blocks.
class AuthDataRootTest(BitcoinTestFramework):
def __init__(self):
super().__init__()
self.num_nodes = 4
def setup_nodes(self):
return start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[
'-nuparams=5ba81b19:1', # Overwinter
'-nuparams=76b809bb:1', # Sapling
'-nuparams=2bb40e60:201', # Blossom
'-nuparams=f5b9230b:201', # Heartwood
'-nuparams=e9ff75a6:201', # Canopy
'-nuparams=f919a198:205', # NU5
'-nurejectoldversions=false',
]] * self.num_nodes)
def run_test(self):
# Generate a block so we are on Canopy rules.
self.nodes[0].generate(2)
self.sync_all()
# For blocks prior to NU5 activation, the hashBlockCommitments field of
# the block header contains the root of the ZIP 221 history tree.
for i in range(3):
block_before = self.nodes[0].getblock('%d' % (202 + i))
assert_equal(block_before['blockcommitments'], block_before['chainhistoryroot'])
self.nodes[0].generate(1)
self.sync_all()
# From NU5 activation, the hashBlockCommitments field of the block
# header contains a hash of various block commitments (per ZIP 244).
for i in range(2):
block_after = self.nodes[0].getblock('%d' % (205 + i))
block_commitments = bytes_to_hex_str(derive_block_commitments_hash(
hex_str_to_bytes(block_after['chainhistoryroot'])[::-1],
hex_str_to_bytes(block_after['authdataroot'])[::-1],
)[::-1])
assert_equal(block_after['blockcommitments'], block_commitments)
self.nodes[0].generate(1)
self.sync_all()
if __name__ == '__main__':
AuthDataRootTest().main()

View File

@ -4,6 +4,8 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php . # file COPYING or https://www.opensource.org/licenses/mit-license.php .
from pyblake2 import blake2b
from .mininode import CBlock, CTransaction, CTxIn, CTxOut, COutPoint from .mininode import CBlock, CTransaction, CTxIn, CTxOut, COutPoint
from .script import CScript, OP_0, OP_EQUAL, OP_HASH160, OP_TRUE, OP_CHECKSIG from .script import CScript, OP_0, OP_EQUAL, OP_HASH160, OP_TRUE, OP_CHECKSIG
@ -29,6 +31,15 @@ def create_block(hashprev, coinbase, nTime=None, nBits=None, hashFinalSaplingRoo
block.calc_sha256() block.calc_sha256()
return block return block
def derive_block_commitments_hash(chain_history_root, auth_data_root):
digest = blake2b(
digest_size=32,
person=b'ZcashBlockCommit')
digest.update(chain_history_root)
digest.update(auth_data_root)
digest.update(b'\x00' * 32)
return digest.digest()
def serialize_script_num(value): def serialize_script_num(value):
r = bytearray(0) r = bytearray(0)
if value == 0: if value == 0:

View File

@ -18,6 +18,7 @@
static const int SPROUT_VALUE_VERSION = 1001400; static const int SPROUT_VALUE_VERSION = 1001400;
static const int SAPLING_VALUE_VERSION = 1010100; static const int SAPLING_VALUE_VERSION = 1010100;
static const int CHAIN_HISTORY_ROOT_VERSION = 2010200; static const int CHAIN_HISTORY_ROOT_VERSION = 2010200;
static const int NU5_DATA_VERSION = 4050000;
/** /**
* Maximum amount of time that a block timestamp is allowed to be ahead of the * Maximum amount of time that a block timestamp is allowed to be ahead of the
@ -231,6 +232,14 @@ public:
//! Persisted at each activation height, memory-only for intervening blocks. //! Persisted at each activation height, memory-only for intervening blocks.
std::optional<uint32_t> nCachedBranchId; std::optional<uint32_t> nCachedBranchId;
//! Root of the ZIP 244 authorizing data commitment tree for this block.
//!
//! - For blocks prior to (not including) the NU5 activation block, this is always
//! null.
//! - For blocks including and after the NU5 activation block, this is only set once
//! a block has been connected to the main chain, and will be null otherwise.
uint256 hashAuthDataRoot;
//! The anchor for the tree state up to the start of this block //! The anchor for the tree state up to the start of this block
uint256 hashSproutAnchor; uint256 hashSproutAnchor;
@ -258,23 +267,34 @@ public:
//! Root of the Sapling commitment tree as of the end of this block. //! Root of the Sapling commitment tree as of the end of this block.
//! //!
//! - For blocks prior to (not including) the Heartwood activation block, this is //! - For blocks prior to (not including) the Heartwood activation block, this is
//! always equal to hashLightClientRoot. //! always equal to hashBlockCommitments.
//! - For blocks including and after the Heartwood activation block, this is only set //! - For blocks including and after the Heartwood activation block, this is only set
//! once a block has been connected to the main chain, and will be null otherwise. //! once a block has been connected to the main chain, and will be null otherwise.
uint256 hashFinalSaplingRoot; uint256 hashFinalSaplingRoot;
//! Root of the Orchard commitment tree as of the end of this block.
//!
//! - For blocks prior to (not including) the NU5 activation block, this is always
//! null.
//! - For blocks including and after the NU5 activation block, this is only set
//! once a block has been connected to the main chain, and will be null otherwise.
uint256 hashFinalOrchardRoot;
//! Root of the ZIP 221 history tree as of the end of the previous block. //! Root of the ZIP 221 history tree as of the end of the previous block.
//! //!
//! - For blocks prior to and including the Heartwood activation block, this is //! - For blocks prior to and including the Heartwood activation block, this is
//! always null. //! always null.
//! - For blocks after (not including) the Heartwood activation block, this is //! - For blocks after (not including) the Heartwood activation block, and prior to
//! always equal to hashLightClientRoot. //! (not including) the NU5 activation block, this is always equal to
//! hashBlockCommitments.
//! - For blocks including and after the NU5 activation block, this is only set
//! once a block has been connected to the main chain, and will be null otherwise.
uint256 hashChainHistoryRoot; uint256 hashChainHistoryRoot;
//! block header //! block header
int nVersion; int nVersion;
uint256 hashMerkleRoot; uint256 hashMerkleRoot;
uint256 hashLightClientRoot; uint256 hashBlockCommitments;
unsigned int nTime; unsigned int nTime;
unsigned int nBits; unsigned int nBits;
uint256 nNonce; uint256 nNonce;
@ -297,8 +317,12 @@ public:
nChainTx = 0; nChainTx = 0;
nStatus = 0; nStatus = 0;
nCachedBranchId = std::nullopt; nCachedBranchId = std::nullopt;
hashAuthDataRoot = uint256();
hashSproutAnchor = uint256(); hashSproutAnchor = uint256();
hashFinalSproutRoot = uint256(); hashFinalSproutRoot = uint256();
hashFinalSaplingRoot = uint256();
hashFinalOrchardRoot = uint256();
hashChainHistoryRoot = uint256();
nSequenceId = 0; nSequenceId = 0;
nSproutValue = std::nullopt; nSproutValue = std::nullopt;
nChainSproutValue = std::nullopt; nChainSproutValue = std::nullopt;
@ -307,7 +331,7 @@ public:
nVersion = 0; nVersion = 0;
hashMerkleRoot = uint256(); hashMerkleRoot = uint256();
hashLightClientRoot = uint256(); hashBlockCommitments = uint256();
nTime = 0; nTime = 0;
nBits = 0; nBits = 0;
nNonce = uint256(); nNonce = uint256();
@ -325,7 +349,7 @@ public:
nVersion = block.nVersion; nVersion = block.nVersion;
hashMerkleRoot = block.hashMerkleRoot; hashMerkleRoot = block.hashMerkleRoot;
hashLightClientRoot = block.hashLightClientRoot; hashBlockCommitments = block.hashBlockCommitments;
nTime = block.nTime; nTime = block.nTime;
nBits = block.nBits; nBits = block.nBits;
nNonce = block.nNonce; nNonce = block.nNonce;
@ -357,7 +381,7 @@ public:
if (pprev) if (pprev)
block.hashPrevBlock = pprev->GetBlockHash(); block.hashPrevBlock = pprev->GetBlockHash();
block.hashMerkleRoot = hashMerkleRoot; block.hashMerkleRoot = hashMerkleRoot;
block.hashLightClientRoot = hashLightClientRoot; block.hashBlockCommitments = hashBlockCommitments;
block.nTime = nTime; block.nTime = nTime;
block.nBits = nBits; block.nBits = nBits;
block.nNonce = nNonce; block.nNonce = nNonce;
@ -486,7 +510,7 @@ public:
READWRITE(this->nVersion); READWRITE(this->nVersion);
READWRITE(hashPrev); READWRITE(hashPrev);
READWRITE(hashMerkleRoot); READWRITE(hashMerkleRoot);
READWRITE(hashLightClientRoot); READWRITE(hashBlockCommitments);
READWRITE(nTime); READWRITE(nTime);
READWRITE(nBits); READWRITE(nBits);
READWRITE(nNonce); READWRITE(nNonce);
@ -512,7 +536,15 @@ public:
} else if (ser_action.ForRead()) { } else if (ser_action.ForRead()) {
// For block indices written before the client was Heartwood-aware, // For block indices written before the client was Heartwood-aware,
// these are always identical. // these are always identical.
hashFinalSaplingRoot = hashLightClientRoot; hashFinalSaplingRoot = hashBlockCommitments;
}
// Only read/write NU5 data if the client version used to create this
// index was storing them. For block indices written before the client
// was NU5-aware, these are always null / zero.
if ((s.GetType() & SER_DISK) && (nVersion >= NU5_DATA_VERSION)) {
READWRITE(hashAuthDataRoot);
READWRITE(hashFinalOrchardRoot);
} }
// If you have just added new serialized fields above, remember to add // If you have just added new serialized fields above, remember to add
@ -525,7 +557,7 @@ public:
block.nVersion = nVersion; block.nVersion = nVersion;
block.hashPrevBlock = hashPrev; block.hashPrevBlock = hashPrev;
block.hashMerkleRoot = hashMerkleRoot; block.hashMerkleRoot = hashMerkleRoot;
block.hashLightClientRoot = hashLightClientRoot; block.hashBlockCommitments = hashBlockCommitments;
block.nTime = nTime; block.nTime = nTime;
block.nBits = nBits; block.nBits = nBits;
block.nNonce = nNonce; block.nNonce = nNonce;

View File

@ -2732,7 +2732,8 @@ static bool ShouldCheckTransactions(const CChainParams& chainparams, const CBloc
} }
bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex,
CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck) CCoinsViewCache& view, const CChainParams& chainparams,
bool fJustCheck, bool fCheckAuthDataRoot)
{ {
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
@ -2998,6 +2999,17 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
} }
// Derive the various block commitments.
// We only derive them if they will be used for this block.
std::optional<uint256> hashAuthDataRoot;
std::optional<uint256> hashChainHistoryRoot;
if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_NU5)) {
hashAuthDataRoot = block.BuildAuthDataMerkleTree();
}
if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_HEARTWOOD)) {
hashChainHistoryRoot = view.GetHistoryRoot(prevConsensusBranchId);
}
view.PushAnchor(sprout_tree); view.PushAnchor(sprout_tree);
view.PushAnchor(sapling_tree); view.PushAnchor(sapling_tree);
if (!fJustCheck) { if (!fJustCheck) {
@ -3012,19 +3024,48 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_HEARTWOOD)) { if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_HEARTWOOD)) {
pindex->hashFinalSaplingRoot = sapling_tree.root(); pindex->hashFinalSaplingRoot = sapling_tree.root();
} }
// - If this block is before NU5 activation:
// - hashAuthDataRoot and hashFinalOrchardRoot are always null.
// - We don't set hashChainHistoryRoot here to maintain the invariant
// documented in CBlockIndex (which was ensured in AddToBlockIndex).
// - If this block is on or after NU5 activation, this is where we set
// the correct values of hashAuthDataRoot, hashFinalOrchardRoot, and
// hashChainHistoryRoot; in particular, blocks that are never passed
// to ConnectBlock() (and thus never on the main chain) will stay with
// these set to null.
if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_NU5)) {
pindex->hashAuthDataRoot = hashAuthDataRoot.value();
pindex->hashFinalOrchardRoot = uint256(); // TODO: replace with Orchard tree root
pindex->hashChainHistoryRoot = hashChainHistoryRoot.value();
}
} }
blockundo.old_sprout_tree_root = old_sprout_tree_root; blockundo.old_sprout_tree_root = old_sprout_tree_root;
if (IsActivationHeight(pindex->nHeight, chainparams.GetConsensus(), Consensus::UPGRADE_HEARTWOOD)) { if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_NU5)) {
// In the block that activates ZIP 221, block.hashLightClientRoot MUST if (fCheckAuthDataRoot) {
// If NU5 is active, block.hashBlockCommitments must be the top digest
// of the ZIP 244 block commitments linked list.
// https://zips.z.cash/zip-0244#block-header-changes
uint256 hashBlockCommitments = DeriveBlockCommitmentsHash(
hashChainHistoryRoot.value(),
hashAuthDataRoot.value());
if (block.hashBlockCommitments != hashBlockCommitments) {
return state.DoS(100,
error("ConnectBlock(): block's hashBlockCommitments is incorrect (should be ZIP 244 block commitment)"),
REJECT_INVALID, "bad-block-commitments-hash");
}
}
} else if (IsActivationHeight(pindex->nHeight, chainparams.GetConsensus(), Consensus::UPGRADE_HEARTWOOD)) {
// In the block that activates ZIP 221, block.hashBlockCommitments MUST
// be set to all zero bytes. // be set to all zero bytes.
if (!block.hashLightClientRoot.IsNull()) { if (!block.hashBlockCommitments.IsNull()) {
return state.DoS(100, return state.DoS(100,
error("ConnectBlock(): block's hashLightClientRoot is incorrect (should be null)"), error("ConnectBlock(): block's hashBlockCommitments is incorrect (should be null)"),
REJECT_INVALID, "bad-heartwood-root-in-block"); REJECT_INVALID, "bad-heartwood-root-in-block");
} }
} else if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_HEARTWOOD)) { } else if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_HEARTWOOD)) {
// If Heartwood is active, block.hashLightClientRoot must be the same as // If Heartwood is active, block.hashBlockCommitments must be the same as
// the root of the history tree for the previous block. We only store // the root of the history tree for the previous block. We only store
// one tree per epoch, so we have two possible cases: // one tree per epoch, so we have two possible cases:
// - If the previous block is in the previous epoch, this block won't // - If the previous block is in the previous epoch, this block won't
@ -3032,17 +3073,17 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
// - If the previous block is in this epoch, this block would affect // - If the previous block is in this epoch, this block would affect
// this epoch's tree root, but as we haven't updated the tree for this // this epoch's tree root, but as we haven't updated the tree for this
// block yet, view.GetHistoryRoot() returns the root we need. // block yet, view.GetHistoryRoot() returns the root we need.
if (block.hashLightClientRoot != view.GetHistoryRoot(prevConsensusBranchId)) { if (block.hashBlockCommitments != hashChainHistoryRoot.value()) {
return state.DoS(100, return state.DoS(100,
error("ConnectBlock(): block's hashLightClientRoot is incorrect (should be history tree root)"), error("ConnectBlock(): block's hashBlockCommitments is incorrect (should be history tree root)"),
REJECT_INVALID, "bad-heartwood-root-in-block"); REJECT_INVALID, "bad-heartwood-root-in-block");
} }
} else if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_SAPLING)) { } else if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_SAPLING)) {
// If Sapling is active, block.hashLightClientRoot must be the // If Sapling is active, block.hashBlockCommitments must be the
// same as the root of the Sapling tree // same as the root of the Sapling tree
if (block.hashLightClientRoot != sapling_tree.root()) { if (block.hashBlockCommitments != sapling_tree.root()) {
return state.DoS(100, return state.DoS(100,
error("ConnectBlock(): block's hashLightClientRoot is incorrect (should be Sapling tree root)"), error("ConnectBlock(): block's hashBlockCommitments is incorrect (should be Sapling tree root)"),
REJECT_INVALID, "bad-sapling-root-in-block"); REJECT_INVALID, "bad-sapling-root-in-block");
} }
} }
@ -3910,15 +3951,26 @@ CBlockIndex* AddToBlockIndex(const CBlockHeader& block, const Consensus::Params&
pindexNew->pprev = (*miPrev).second; pindexNew->pprev = (*miPrev).second;
pindexNew->nHeight = pindexNew->pprev->nHeight + 1; pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
if (IsActivationHeight(pindexNew->nHeight, consensusParams, Consensus::UPGRADE_HEARTWOOD)) { if (consensusParams.NetworkUpgradeActive(pindexNew->nHeight, Consensus::UPGRADE_NU5)) {
// hashFinalSaplingRoot is currently null, and will be set correctly in ConnectBlock. // The following hashes will be null if this block has never been
// connected to a main chain; they will be (re)set correctly in
// ConnectBlock:
// - hashFinalSaplingRoot
// - hashFinalOrchardRoot
// - hashChainHistoryRoot
} else if (IsActivationHeight(pindexNew->nHeight, consensusParams, Consensus::UPGRADE_HEARTWOOD)) {
// hashFinalSaplingRoot and hashFinalOrchardRoot will be null if this block has
// never been connected to a main chain; they will be (re)set correctly in
// ConnectBlock.
// hashChainHistoryRoot is null. // hashChainHistoryRoot is null.
} else if (consensusParams.NetworkUpgradeActive(pindexNew->nHeight, Consensus::UPGRADE_HEARTWOOD)) { } else if (consensusParams.NetworkUpgradeActive(pindexNew->nHeight, Consensus::UPGRADE_HEARTWOOD)) {
// hashFinalSaplingRoot is currently null, and will be set correctly in ConnectBlock. // hashFinalSaplingRoot and hashFinalOrchardRoot will be null if this block has
pindexNew->hashChainHistoryRoot = pindexNew->hashLightClientRoot; // never been connected to a main chain; they will be (re)set correctly in
// ConnectBlock.
pindexNew->hashChainHistoryRoot = pindexNew->hashBlockCommitments;
} else { } else {
// hashChainHistoryRoot is null. // hashFinalOrchardRoot and hashChainHistoryRoot are null.
pindexNew->hashFinalSaplingRoot = pindexNew->hashLightClientRoot; pindexNew->hashFinalSaplingRoot = pindexNew->hashBlockCommitments;
} }
pindexNew->BuildSkip(); pindexNew->BuildSkip();
@ -4565,7 +4617,7 @@ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams,
return false; return false;
if (!ContextualCheckBlock(block, state, chainparams, pindexPrev, true)) if (!ContextualCheckBlock(block, state, chainparams, pindexPrev, true))
return false; return false;
if (!ConnectBlock(block, state, &indexDummy, viewNew, chainparams, true)) if (!ConnectBlock(block, state, &indexDummy, viewNew, chainparams, true, fCheckMerkleRoot))
return false; return false;
assert(state.IsValid()); assert(state.IsValid());

View File

@ -486,7 +486,8 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state,
* Validity checks that depend on the UTXO set are also done; ConnectBlock() * Validity checks that depend on the UTXO set are also done; ConnectBlock()
* can fail if those validity checks fail (among other reasons). */ * can fail if those validity checks fail (among other reasons). */
bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins,
const CChainParams& chainparams, bool fJustCheck = false); const CChainParams& chainparams,
bool fJustCheck = false, bool fCheckAuthDataRoot = true);
/** /**
* Check a block is completely valid from start to finish (only works on top * Check a block is completely valid from start to finish (only works on top

View File

@ -621,12 +621,17 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const MinerAddre
// Fill in header // Fill in header
pblock->hashPrevBlock = pindexPrev->GetBlockHash(); pblock->hashPrevBlock = pindexPrev->GetBlockHash();
if (IsActivationHeight(nHeight, chainparams.GetConsensus(), Consensus::UPGRADE_HEARTWOOD)) { if (chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_NU5)) {
pblock->hashLightClientRoot.SetNull(); // hashBlockCommitments depends on the block transactions, so we have to
// update it whenever the coinbase transaction changes. Leave it unset here,
// like hashMerkleRoot, and instead cache what we will need to calculate it.
pblocktemplate->hashChainHistoryRoot = view.GetHistoryRoot(prevConsensusBranchId);
} else if (IsActivationHeight(nHeight, chainparams.GetConsensus(), Consensus::UPGRADE_HEARTWOOD)) {
pblock->hashBlockCommitments.SetNull();
} else if (chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_HEARTWOOD)) { } else if (chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_HEARTWOOD)) {
pblock->hashLightClientRoot = view.GetHistoryRoot(prevConsensusBranchId); pblock->hashBlockCommitments = view.GetHistoryRoot(prevConsensusBranchId);
} else { } else {
pblock->hashLightClientRoot = sapling_tree.root(); pblock->hashBlockCommitments = sapling_tree.root();
} }
UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev); UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev);
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus()); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus());
@ -682,8 +687,13 @@ void GetMinerAddress(MinerAddress &minerAddress)
} }
} }
void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce) void IncrementExtraNonce(
CBlockTemplate* pblocktemplate,
const CBlockIndex* pindexPrev,
unsigned int& nExtraNonce,
const Consensus::Params& consensusParams)
{ {
CBlock *pblock = &pblocktemplate->block;
// Update nExtraNonce // Update nExtraNonce
static uint256 hashPrevBlock; static uint256 hashPrevBlock;
if (hashPrevBlock != pblock->hashPrevBlock) if (hashPrevBlock != pblock->hashPrevBlock)
@ -699,6 +709,11 @@ void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned
pblock->vtx[0] = txCoinbase; pblock->vtx[0] = txCoinbase;
pblock->hashMerkleRoot = pblock->BuildMerkleTree(); pblock->hashMerkleRoot = pblock->BuildMerkleTree();
if (consensusParams.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_NU5)) {
pblock->hashBlockCommitments = DeriveBlockCommitmentsHash(
pblocktemplate->hashChainHistoryRoot,
pblock->BuildAuthDataMerkleTree());
}
} }
static bool ProcessBlockFound(const CBlock* pblock, const CChainParams& chainparams) static bool ProcessBlockFound(const CBlock* pblock, const CChainParams& chainparams)
@ -797,7 +812,7 @@ void static BitcoinMiner(const CChainParams& chainparams)
return; return;
} }
CBlock *pblock = &pblocktemplate->block; CBlock *pblock = &pblocktemplate->block;
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce); IncrementExtraNonce(pblocktemplate.get(), pindexPrev, nExtraNonce, chainparams.GetConsensus());
LogPrintf("Running ZcashMiner with %u transactions in block (%u bytes)\n", pblock->vtx.size(), LogPrintf("Running ZcashMiner with %u transactions in block (%u bytes)\n", pblock->vtx.size(),
::GetSerializeSize(*pblock, SER_NETWORK, PROTOCOL_VERSION)); ::GetSerializeSize(*pblock, SER_NETWORK, PROTOCOL_VERSION));

View File

@ -67,6 +67,9 @@ public:
struct CBlockTemplate struct CBlockTemplate
{ {
CBlock block; CBlock block;
// Cached whenever we update `block`, so we can update hashBlockCommitments
// when we change the coinbase transaction.
uint256 hashChainHistoryRoot;
std::vector<CAmount> vTxFees; std::vector<CAmount> vTxFees;
std::vector<int64_t> vTxSigOps; std::vector<int64_t> vTxSigOps;
}; };
@ -80,7 +83,11 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const MinerAddre
/** Get -mineraddress */ /** Get -mineraddress */
void GetMinerAddress(MinerAddress &minerAddress); void GetMinerAddress(MinerAddress &minerAddress);
/** Modify the extranonce in a block */ /** Modify the extranonce in a block */
void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce); void IncrementExtraNonce(
CBlockTemplate* pblocktemplate,
const CBlockIndex* pindexPrev,
unsigned int& nExtraNonce,
const Consensus::Params& consensusParams);
/** Run the miner threads */ /** Run the miner threads */
void GenerateBitcoins(bool fGenerate, int nThreads, const CChainParams& chainparams); void GenerateBitcoins(bool fGenerate, int nThreads, const CChainParams& chainparams);
#endif #endif

View File

@ -14,6 +14,20 @@
const unsigned char ZCASH_AUTH_DATA_HASH_PERSONALIZATION[BLAKE2bPersonalBytes] = const unsigned char ZCASH_AUTH_DATA_HASH_PERSONALIZATION[BLAKE2bPersonalBytes] =
{'Z','c','a','s','h','A','u','t','h','D','a','t','H','a','s','h'}; {'Z','c','a','s','h','A','u','t','h','D','a','t','H','a','s','h'};
const unsigned char ZCASH_BLOCK_COMMITMENTS_HASH_PERSONALIZATION[BLAKE2bPersonalBytes] =
{'Z','c','a','s','h','B','l','o','c','k','C','o','m','m','i','t'};
uint256 DeriveBlockCommitmentsHash(
uint256 hashChainHistoryRoot,
uint256 hashAuthDataRoot)
{
// https://zips.z.cash/zip-0244#block-header-changes
CBLAKE2bWriter ss(SER_GETHASH, 0, ZCASH_BLOCK_COMMITMENTS_HASH_PERSONALIZATION);
ss << hashChainHistoryRoot;
ss << hashAuthDataRoot;
ss << uint256(); // terminator
return ss.GetHash();
}
uint256 CBlockHeader::GetHash() const uint256 CBlockHeader::GetHash() const
{ {
@ -164,12 +178,12 @@ uint256 CBlock::BuildAuthDataMerkleTree() const
std::string CBlock::ToString() const std::string CBlock::ToString() const
{ {
std::stringstream s; std::stringstream s;
s << strprintf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, hashLightClientRoot=%s, nTime=%u, nBits=%08x, nNonce=%s, vtx=%u)\n", s << strprintf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, hashBlockCommitments=%s, nTime=%u, nBits=%08x, nNonce=%s, vtx=%u)\n",
GetHash().ToString(), GetHash().ToString(),
nVersion, nVersion,
hashPrevBlock.ToString(), hashPrevBlock.ToString(),
hashMerkleRoot.ToString(), hashMerkleRoot.ToString(),
hashLightClientRoot.ToString(), hashBlockCommitments.ToString(),
nTime, nBits, nNonce.ToString(), nTime, nBits, nNonce.ToString(),
vtx.size()); vtx.size());
for (unsigned int i = 0; i < vtx.size(); i++) for (unsigned int i = 0; i < vtx.size(); i++)

View File

@ -10,6 +10,11 @@
#include "serialize.h" #include "serialize.h"
#include "uint256.h" #include "uint256.h"
// Derives the ZIP 244 block commitments hash.
uint256 DeriveBlockCommitmentsHash(
uint256 hashChainHistoryRoot,
uint256 hashAuthDataRoot);
/** Nodes collect new transactions into a block, hash them into a hash tree, /** Nodes collect new transactions into a block, hash them into a hash tree,
* and scan through nonce values to make the block's hash satisfy proof-of-work * and scan through nonce values to make the block's hash satisfy proof-of-work
* requirements. When they solve the proof-of-work, they broadcast the block * requirements. When they solve the proof-of-work, they broadcast the block
@ -26,7 +31,7 @@ public:
int32_t nVersion; int32_t nVersion;
uint256 hashPrevBlock; uint256 hashPrevBlock;
uint256 hashMerkleRoot; uint256 hashMerkleRoot;
uint256 hashLightClientRoot; uint256 hashBlockCommitments;
uint32_t nTime; uint32_t nTime;
uint32_t nBits; uint32_t nBits;
uint256 nNonce; uint256 nNonce;
@ -44,7 +49,7 @@ public:
READWRITE(this->nVersion); READWRITE(this->nVersion);
READWRITE(hashPrevBlock); READWRITE(hashPrevBlock);
READWRITE(hashMerkleRoot); READWRITE(hashMerkleRoot);
READWRITE(hashLightClientRoot); READWRITE(hashBlockCommitments);
READWRITE(nTime); READWRITE(nTime);
READWRITE(nBits); READWRITE(nBits);
READWRITE(nNonce); READWRITE(nNonce);
@ -56,7 +61,7 @@ public:
nVersion = CBlockHeader::CURRENT_VERSION; nVersion = CBlockHeader::CURRENT_VERSION;
hashPrevBlock.SetNull(); hashPrevBlock.SetNull();
hashMerkleRoot.SetNull(); hashMerkleRoot.SetNull();
hashLightClientRoot.SetNull(); hashBlockCommitments.SetNull();
nTime = 0; nTime = 0;
nBits = 0; nBits = 0;
nNonce = uint256(); nNonce = uint256();
@ -118,7 +123,7 @@ public:
block.nVersion = nVersion; block.nVersion = nVersion;
block.hashPrevBlock = hashPrevBlock; block.hashPrevBlock = hashPrevBlock;
block.hashMerkleRoot = hashMerkleRoot; block.hashMerkleRoot = hashMerkleRoot;
block.hashLightClientRoot = hashLightClientRoot; block.hashBlockCommitments = hashBlockCommitments;
block.nTime = nTime; block.nTime = nTime;
block.nBits = nBits; block.nBits = nBits;
block.nNonce = nNonce; block.nNonce = nNonce;
@ -163,7 +168,7 @@ public:
READWRITE(this->nVersion); READWRITE(this->nVersion);
READWRITE(hashPrevBlock); READWRITE(hashPrevBlock);
READWRITE(hashMerkleRoot); READWRITE(hashMerkleRoot);
READWRITE(hashLightClientRoot); READWRITE(hashBlockCommitments);
READWRITE(nTime); READWRITE(nTime);
READWRITE(nBits); READWRITE(nBits);
} }

View File

@ -252,6 +252,7 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) {
*const_cast<Ed25519Signature*>(&joinSplitSig) = tx.joinSplitSig; *const_cast<Ed25519Signature*>(&joinSplitSig) = tx.joinSplitSig;
*const_cast<binding_sig_t*>(&bindingSig) = tx.bindingSig; *const_cast<binding_sig_t*>(&bindingSig) = tx.bindingSig;
*const_cast<uint256*>(&hash) = tx.hash; *const_cast<uint256*>(&hash) = tx.hash;
*const_cast<uint256*>(&authDigest) = tx.authDigest;
return *this; return *this;
} }

View File

@ -233,6 +233,8 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
result.pushKV("height", blockindex->nHeight); result.pushKV("height", blockindex->nHeight);
result.pushKV("version", block.nVersion); result.pushKV("version", block.nVersion);
result.pushKV("merkleroot", block.hashMerkleRoot.GetHex()); result.pushKV("merkleroot", block.hashMerkleRoot.GetHex());
result.pushKV("blockcommitments", blockindex->hashBlockCommitments.GetHex());
result.pushKV("authdataroot", blockindex->hashAuthDataRoot.GetHex());
result.pushKV("finalsaplingroot", blockindex->hashFinalSaplingRoot.GetHex()); result.pushKV("finalsaplingroot", blockindex->hashFinalSaplingRoot.GetHex());
result.pushKV("chainhistoryroot", blockindex->hashChainHistoryRoot.GetHex()); result.pushKV("chainhistoryroot", blockindex->hashChainHistoryRoot.GetHex());
UniValue txs(UniValue::VARR); UniValue txs(UniValue::VARR);

View File

@ -218,7 +218,7 @@ UniValue generate(const UniValue& params, bool fHelp)
CBlock *pblock = &pblocktemplate->block; CBlock *pblock = &pblocktemplate->block;
{ {
LOCK(cs_main); LOCK(cs_main);
IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce); IncrementExtraNonce(pblocktemplate.get(), chainActive.Tip(), nExtraNonce, Params().GetConsensus());
} }
// Hash state // Hash state
@ -444,7 +444,8 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
"{\n" "{\n"
" \"version\" : n, (numeric) The block version\n" " \"version\" : n, (numeric) The block version\n"
" \"previousblockhash\" : \"xxxx\", (string) The hash of current highest block\n" " \"previousblockhash\" : \"xxxx\", (string) The hash of current highest block\n"
" \"lightclientroothash\" : \"xxxx\", (string) The hash of the light client root field in the block header\n" " \"blockcommitmentshash\" : \"xxxx\", (string) The hash of the block commitments field in the block header\n"
" \"lightclientroothash\" : \"xxxx\", (string) (DEPRECATED) The hash of the light client root field in the block header\n"
" \"finalsaplingroothash\" : \"xxxx\", (string) (DEPRECATED) The hash of the light client root field in the block header\n" " \"finalsaplingroothash\" : \"xxxx\", (string) (DEPRECATED) The hash of the light client root field in the block header\n"
" \"transactions\" : [ (array) contents of non-coinbase transactions that should be included in the next block\n" " \"transactions\" : [ (array) contents of non-coinbase transactions that should be included in the next block\n"
" {\n" " {\n"
@ -756,9 +757,11 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
result.pushKV("capabilities", aCaps); result.pushKV("capabilities", aCaps);
result.pushKV("version", pblock->nVersion); result.pushKV("version", pblock->nVersion);
result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex()); result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex());
result.pushKV("lightclientroothash", pblock->hashLightClientRoot.GetHex()); result.pushKV("blockcommitmentshash", pblock->hashBlockCommitments.GetHex());
// Deprecated; remove in a future release. // Deprecated; remove in a future release.
result.pushKV("finalsaplingroothash", pblock->hashLightClientRoot.GetHex()); result.pushKV("lightclientroothash", pblock->hashBlockCommitments.GetHex());
// Deprecated; remove in a future release.
result.pushKV("finalsaplingroothash", pblock->hashBlockCommitments.GetHex());
result.pushKV("transactions", transactions); result.pushKV("transactions", transactions);
if (coinbasetxn) { if (coinbasetxn) {
assert(txCoinbase.isObject()); assert(txCoinbase.isObject());

View File

@ -272,8 +272,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
} }
*/ */
// These tests assume null hashLightClientRoot (before Sapling) // These tests assume null hashBlockCommitments (before Sapling)
pblock->hashLightClientRoot = uint256(); pblock->hashBlockCommitments = uint256();
CValidationState state; CValidationState state;
BOOST_CHECK(ProcessNewBlock(state, chainparams, NULL, pblock, true, NULL)); BOOST_CHECK(ProcessNewBlock(state, chainparams, NULL, pblock, true, NULL));

View File

@ -162,7 +162,7 @@ TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>&
block.vtx.push_back(tx); block.vtx.push_back(tx);
// IncrementExtraNonce creates a valid coinbase and merkleRoot // IncrementExtraNonce creates a valid coinbase and merkleRoot
unsigned int extraNonce = 0; unsigned int extraNonce = 0;
IncrementExtraNonce(&block, chainActive.Tip(), extraNonce); IncrementExtraNonce(pblocktemplate, chainActive.Tip(), extraNonce, chainparams.GetConsensus());
// Hash state // Hash state
eh_HashState eh_state; eh_HashState eh_state;

View File

@ -549,7 +549,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(
pindexNew->hashSproutAnchor = diskindex.hashSproutAnchor; pindexNew->hashSproutAnchor = diskindex.hashSproutAnchor;
pindexNew->nVersion = diskindex.nVersion; pindexNew->nVersion = diskindex.nVersion;
pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot; pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;
pindexNew->hashLightClientRoot = diskindex.hashLightClientRoot; pindexNew->hashBlockCommitments = diskindex.hashBlockCommitments;
pindexNew->nTime = diskindex.nTime; pindexNew->nTime = diskindex.nTime;
pindexNew->nBits = diskindex.nBits; pindexNew->nBits = diskindex.nBits;
pindexNew->nNonce = diskindex.nNonce; pindexNew->nNonce = diskindex.nNonce;
@ -560,7 +560,9 @@ bool CBlockTreeDB::LoadBlockIndexGuts(
pindexNew->nSproutValue = diskindex.nSproutValue; pindexNew->nSproutValue = diskindex.nSproutValue;
pindexNew->nSaplingValue = diskindex.nSaplingValue; pindexNew->nSaplingValue = diskindex.nSaplingValue;
pindexNew->hashFinalSaplingRoot = diskindex.hashFinalSaplingRoot; pindexNew->hashFinalSaplingRoot = diskindex.hashFinalSaplingRoot;
pindexNew->hashFinalOrchardRoot = diskindex.hashFinalOrchardRoot;
pindexNew->hashChainHistoryRoot = diskindex.hashChainHistoryRoot; pindexNew->hashChainHistoryRoot = diskindex.hashChainHistoryRoot;
pindexNew->hashAuthDataRoot = diskindex.hashAuthDataRoot;
// Consistency checks // Consistency checks
auto header = pindexNew->GetBlockHeader(); auto header = pindexNew->GetBlockHeader();
@ -588,18 +590,23 @@ bool CBlockTreeDB::LoadBlockIndexGuts(
// a non-upgraded peer. However that case the entry will be // a non-upgraded peer. However that case the entry will be
// marked as consensus-invalid. // marked as consensus-invalid.
// //
if (diskindex.nClientVersion >= CHAIN_HISTORY_ROOT_VERSION && if (diskindex.nClientVersion >= NU5_DATA_VERSION &&
chainParams.GetConsensus().NetworkUpgradeActive(pindexNew->nHeight, Consensus::UPGRADE_NU5)) {
// From NU5 onwards we don't enforce a consistency check, because
// after ZIP 244, hashBlockCommitments will not match any stored
// commitment.
} else if (diskindex.nClientVersion >= CHAIN_HISTORY_ROOT_VERSION &&
chainParams.GetConsensus().NetworkUpgradeActive(pindexNew->nHeight, Consensus::UPGRADE_HEARTWOOD)) { chainParams.GetConsensus().NetworkUpgradeActive(pindexNew->nHeight, Consensus::UPGRADE_HEARTWOOD)) {
if (pindexNew->hashLightClientRoot != pindexNew->hashChainHistoryRoot) { if (pindexNew->hashBlockCommitments != pindexNew->hashChainHistoryRoot) {
return error( return error(
"LoadBlockIndex(): block index inconsistency detected (post-Heartwood; hashLightClientRoot %s != hashChainHistoryRoot %s): %s", "LoadBlockIndex(): block index inconsistency detected (post-Heartwood; hashBlockCommitments %s != hashChainHistoryRoot %s): %s",
pindexNew->hashLightClientRoot.ToString(), pindexNew->hashChainHistoryRoot.ToString(), pindexNew->ToString()); pindexNew->hashBlockCommitments.ToString(), pindexNew->hashChainHistoryRoot.ToString(), pindexNew->ToString());
} }
} else { } else {
if (pindexNew->hashLightClientRoot != pindexNew->hashFinalSaplingRoot) { if (pindexNew->hashBlockCommitments != pindexNew->hashFinalSaplingRoot) {
return error( return error(
"LoadBlockIndex(): block index inconsistency detected (pre-Heartwood; hashLightClientRoot %s != hashFinalSaplingRoot %s): %s", "LoadBlockIndex(): block index inconsistency detected (pre-Heartwood; hashBlockCommitments %s != hashFinalSaplingRoot %s): %s",
pindexNew->hashLightClientRoot.ToString(), pindexNew->hashFinalSaplingRoot.ToString(), pindexNew->ToString()); pindexNew->hashBlockCommitments.ToString(), pindexNew->hashFinalSaplingRoot.ToString(), pindexNew->ToString());
} }
} }
} }