Use V2 history trees from NU5 onward

This commit is contained in:
Jack Grigg 2021-06-17 13:05:58 +01:00
parent 66e2baa6da
commit 16317bc6af
8 changed files with 235 additions and 42 deletions

View File

@ -64,7 +64,7 @@ public:
};
HistoryNode getLeafN(uint64_t block_num) {
HistoryNode node = libzcash::NewLeaf(
HistoryNode node = libzcash::NewV1Leaf(
uint256(),
block_num*10,
block_num*13,

View File

@ -2864,6 +2864,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
auto prevConsensusBranchId = CurrentEpochBranchId(pindex->nHeight - 1, chainparams.GetConsensus());
size_t total_sapling_tx = 0;
size_t total_orchard_tx = 0;
std::vector<PrecomputedTransactionData> txdata;
txdata.reserve(block.vtx.size()); // Required so that pointers to individual PrecomputedTransactionData don't get invalidated
@ -2994,6 +2995,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
if (!(tx.vShieldedSpend.empty() && tx.vShieldedOutput.empty())) {
total_sapling_tx += 1;
}
if (tx.GetOrchardBundle().IsPresent()) {
total_orchard_tx += 1;
}
vPos.push_back(std::make_pair(tx.GetHash(), pos));
pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
@ -3090,15 +3094,30 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
// History read/write is started with Heartwood update.
if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_HEARTWOOD)) {
auto historyNode = libzcash::NewLeaf(
block.GetHash(),
block.nTime,
block.nBits,
pindex->hashFinalSaplingRoot,
ArithToUint256(GetBlockProof(*pindex)),
pindex->nHeight,
total_sapling_tx
);
HistoryNode historyNode;
if (chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_NU5)) {
historyNode = libzcash::NewV2Leaf(
block.GetHash(),
block.nTime,
block.nBits,
pindex->hashFinalSaplingRoot,
pindex->hashFinalOrchardRoot,
ArithToUint256(GetBlockProof(*pindex)),
pindex->nHeight,
total_sapling_tx,
total_orchard_tx
);
} else {
historyNode = libzcash::NewV1Leaf(
block.GetHash(),
block.nTime,
block.nBits,
pindex->hashFinalSaplingRoot,
ArithToUint256(GetBlockProof(*pindex)),
pindex->nHeight,
total_sapling_tx
);
}
view.PushHistoryNode(consensusBranchId, historyNode);
}

View File

@ -56,6 +56,10 @@ public:
inner.reset(bundle);
}
/// Returns true if this contains an Orchard bundle, or false if there is no
/// Orchard component.
bool IsPresent() const { return (bool)inner; }
/// Queues this bundle's signatures for validation.
///
/// `txid` must be for the transaction this bundle is within.

View File

@ -8,7 +8,8 @@
#include <stddef.h>
#include <stdint.h>
#define NODE_SERIALIZED_LENGTH 171
#define NODE_V1_SERIALIZED_LENGTH 171
#define NODE_SERIALIZED_LENGTH 244
#define ENTRY_SERIALIZED_LENGTH (NODE_SERIALIZED_LENGTH + 9)
typedef struct HistoryNode {

View File

@ -1,9 +1,22 @@
use std::slice;
use std::{convert::TryFrom, slice};
use libc::{c_uchar, size_t};
use zcash_history::{Entry as MMREntry, NodeData as MMRNodeData, Tree as MMRTree, V1};
use zcash_history::{Entry as MMREntry, Tree as MMRTree, Version, V1, V2};
use zcash_primitives::consensus::BranchId;
fn construct_mmr_tree(
/// Switch the tree version on the epoch it is for.
fn dispatch<T>(cbranch: u32, v1: impl FnOnce() -> T, v2: impl FnOnce() -> T) -> T {
match BranchId::try_from(cbranch).unwrap() {
BranchId::Sprout
| BranchId::Overwinter
| BranchId::Sapling
| BranchId::Heartwood
| BranchId::Canopy => v1(),
_ => v2(),
}
}
fn construct_mmr_tree<V: Version>(
// Consensus branch id
cbranch: u32,
// Length of tree in array representation
@ -18,7 +31,7 @@ fn construct_mmr_tree(
p_len: size_t,
// Extra nodes loaded (for deletion) count
e_len: size_t,
) -> Result<MMRTree<V1>, &'static str> {
) -> Result<MMRTree<V>, &'static str> {
let (indices, nodes) = unsafe {
(
slice::from_raw_parts(ni_ptr, p_len + e_len),
@ -59,6 +72,39 @@ pub extern "system" fn librustzcash_mmr_append(
rt_ret: *mut [u8; 32],
// Return buffer for appended leaves, should be pre-allocated of ceiling(log2(t_len)) length
buf_ret: *mut [c_uchar; zcash_history::MAX_NODE_DATA_SIZE],
) -> u32 {
dispatch(
cbranch,
|| {
librustzcash_mmr_append_inner::<V1>(
cbranch, t_len, ni_ptr, n_ptr, p_len, nn_ptr, rt_ret, buf_ret,
)
},
|| {
librustzcash_mmr_append_inner::<V2>(
cbranch, t_len, ni_ptr, n_ptr, p_len, nn_ptr, rt_ret, buf_ret,
)
},
)
}
fn librustzcash_mmr_append_inner<V: Version>(
// Consensus branch id
cbranch: u32,
// Length of tree in array representation
t_len: u32,
// Indices of provided tree nodes, length of p_len
ni_ptr: *const u32,
// Provided tree nodes data, length of p_len
n_ptr: *const [c_uchar; zcash_history::MAX_ENTRY_SIZE],
// Peaks count
p_len: size_t,
// New node pointer
nn_ptr: *const [u8; zcash_history::MAX_NODE_DATA_SIZE],
// Return of root commitment
rt_ret: *mut [u8; 32],
// Return buffer for appended leaves, should be pre-allocated of ceiling(log2(t_len)) length
buf_ret: *mut [c_uchar; zcash_history::MAX_NODE_DATA_SIZE],
) -> u32 {
let new_node_bytes: &[u8; zcash_history::MAX_NODE_DATA_SIZE] = unsafe {
match nn_ptr.as_ref() {
@ -69,14 +115,14 @@ pub extern "system" fn librustzcash_mmr_append(
}
};
let mut tree = match construct_mmr_tree(cbranch, t_len, ni_ptr, n_ptr, p_len, 0) {
let mut tree = match construct_mmr_tree::<V>(cbranch, t_len, ni_ptr, n_ptr, p_len, 0) {
Ok(t) => t,
_ => {
return 0;
} // error
};
let node = match MMRNodeData::from_bytes(cbranch, &new_node_bytes[..]) {
let node = match V::from_bytes(cbranch, &new_node_bytes[..]) {
Ok(node) => node,
_ => {
return 0;
@ -96,17 +142,19 @@ pub extern "system" fn librustzcash_mmr_append(
.root_node()
.expect("Just added, should resolve always; qed");
unsafe {
*rt_ret = root_node.data().hash();
*rt_ret = V::hash(root_node.data());
for (idx, next_buf) in slice::from_raw_parts_mut(buf_ret, return_count as usize)
.iter_mut()
.enumerate()
{
tree.resolve_link(appended[idx])
.expect("This was generated by the tree and thus resolvable; qed")
.data()
.write(&mut &mut next_buf[..])
.expect("Write using cursor with enough buffer size cannot fail; qed");
V::write(
tree.resolve_link(appended[idx])
.expect("This was generated by the tree and thus resolvable; qed")
.data(),
&mut &mut next_buf[..],
)
.expect("Write using cursor with enough buffer size cannot fail; qed");
}
}
@ -130,7 +178,30 @@ pub extern "system" fn librustzcash_mmr_delete(
// Return of root commitment
rt_ret: *mut [u8; 32],
) -> u32 {
let mut tree = match construct_mmr_tree(cbranch, t_len, ni_ptr, n_ptr, p_len, e_len) {
dispatch(
cbranch,
|| librustzcash_mmr_delete_inner::<V1>(cbranch, t_len, ni_ptr, n_ptr, p_len, e_len, rt_ret),
|| librustzcash_mmr_delete_inner::<V2>(cbranch, t_len, ni_ptr, n_ptr, p_len, e_len, rt_ret),
)
}
fn librustzcash_mmr_delete_inner<V: Version>(
// Consensus branch id
cbranch: u32,
// Length of tree in array representation
t_len: u32,
// Indices of provided tree nodes, length of p_len+e_len
ni_ptr: *const u32,
// Provided tree nodes data, length of p_len+e_len
n_ptr: *const [c_uchar; zcash_history::MAX_ENTRY_SIZE],
// Peaks count
p_len: size_t,
// Extra nodes loaded (for deletion) count
e_len: size_t,
// Return of root commitment
rt_ret: *mut [u8; 32],
) -> u32 {
let mut tree = match construct_mmr_tree::<V>(cbranch, t_len, ni_ptr, n_ptr, p_len, e_len) {
Ok(t) => t,
_ => {
return 0;
@ -145,11 +216,11 @@ pub extern "system" fn librustzcash_mmr_delete(
};
unsafe {
*rt_ret = tree
.root_node()
.expect("Just generated without errors, root should be resolving")
.data()
.hash();
*rt_ret = V::hash(
tree.root_node()
.expect("Just generated without errors, root should be resolving")
.data(),
);
}
truncate_len
@ -160,6 +231,18 @@ pub extern "system" fn librustzcash_mmr_hash_node(
cbranch: u32,
n_ptr: *const [u8; zcash_history::MAX_NODE_DATA_SIZE],
h_ret: *mut [u8; 32],
) -> u32 {
dispatch(
cbranch,
|| librustzcash_mmr_hash_node_inner::<V1>(cbranch, n_ptr, h_ret),
|| librustzcash_mmr_hash_node_inner::<V2>(cbranch, n_ptr, h_ret),
)
}
fn librustzcash_mmr_hash_node_inner<V: Version>(
cbranch: u32,
n_ptr: *const [u8; zcash_history::MAX_NODE_DATA_SIZE],
h_ret: *mut [u8; 32],
) -> u32 {
let node_bytes: &[u8; zcash_history::MAX_NODE_DATA_SIZE] = unsafe {
match n_ptr.as_ref() {
@ -168,13 +251,13 @@ pub extern "system" fn librustzcash_mmr_hash_node(
}
};
let node = match MMRNodeData::from_bytes(cbranch, &node_bytes[..]) {
let node = match V::from_bytes(cbranch, &node_bytes[..]) {
Ok(n) => n,
_ => return 1, // error
};
unsafe {
*h_ret = node.hash();
*h_ret = V::hash(&node);
}
0

View File

@ -10,6 +10,7 @@
#include "main.h"
#include "pow.h"
#include "uint256.h"
#include "zcash/History.hpp"
#include <stdint.h>
@ -145,15 +146,31 @@ HistoryNode CCoinsViewDB::GetHistoryAt(uint32_t epochId, HistoryIndex index) con
throw runtime_error("History data inconsistent - reindex?");
}
// Read mmrNode into tmp std::array
std::array<unsigned char, NODE_SERIALIZED_LENGTH> tmpMmrNode;
if (libzcash::IsV1HistoryTree(epochId)) {
// History nodes serialized by `zcashd` versions that were unaware of NU5, used
// the previous shorter maximum serialized length. Because we stored this as an
// array, we can't just read the current (longer) maximum serialized length, as
// it will result in an exception for those older nodes.
//
// Instead, we always read an array of the older length. This works as expected
// for V1 nodes serialized by older clients, while for V1 nodes serialized by
// NU5-aware clients this is guaranteed to ignore only trailing zero bytes.
std::array<unsigned char, NODE_V1_SERIALIZED_LENGTH> tmpMmrNode;
if (!db.Read(make_pair(DB_MMR_NODE, make_pair(epochId, index)), tmpMmrNode)) {
throw runtime_error("History data inconsistent (expected node not found) - reindex?");
}
std::copy(std::begin(tmpMmrNode), std::end(tmpMmrNode), mmrNode.bytes);
} else {
// Read mmrNode into tmp std::array
std::array<unsigned char, NODE_SERIALIZED_LENGTH> tmpMmrNode;
if (!db.Read(make_pair(DB_MMR_NODE, make_pair(epochId, index)), tmpMmrNode)) {
throw runtime_error("History data inconsistent (expected node not found) - reindex?");
if (!db.Read(make_pair(DB_MMR_NODE, make_pair(epochId, index)), tmpMmrNode)) {
throw runtime_error("History data inconsistent (expected node not found) - reindex?");
}
std::copy(std::begin(tmpMmrNode), std::end(tmpMmrNode), mmrNode.bytes);
}
std::copy(std::begin(tmpMmrNode), std::end(tmpMmrNode), mmrNode.bytes);
return mmrNode;
}

View File

@ -3,6 +3,7 @@
#include <stdexcept>
#include "consensus/upgrades.h"
#include "serialize.h"
#include "streams.h"
#include "uint256.h"
@ -46,10 +47,13 @@ HistoryNode NewNode(
uint32_t endTarget,
uint256 startSaplingRoot,
uint256 endSaplingRoot,
std::optional<uint256> startOrchardRoot,
std::optional<uint256> endOrchardRoot,
uint256 subtreeTotalWork,
uint64_t startHeight,
uint64_t endHeight,
uint64_t saplingTxCount
uint64_t saplingTxCount,
std::optional<uint64_t> orchardTxCount
)
{
CDataStream buf(SER_DISK, 0);
@ -66,13 +70,19 @@ HistoryNode NewNode(
buf << COMPACTSIZE(startHeight);
buf << COMPACTSIZE(endHeight);
buf << COMPACTSIZE(saplingTxCount);
if (startOrchardRoot) {
// If startOrchardRoot is provided, assume all V2 fields are.
buf << startOrchardRoot.value();
buf << endOrchardRoot.value();
buf << COMPACTSIZE(orchardTxCount.value());
}
assert(buf.size() <= NODE_SERIALIZED_LENGTH);
std::copy(std::begin(buf), std::end(buf), result.bytes);
return result;
}
HistoryNode NewLeaf(
HistoryNode NewV1Leaf(
uint256 commitment,
uint32_t time,
uint32_t target,
@ -89,10 +99,42 @@ HistoryNode NewLeaf(
target,
saplingRoot,
saplingRoot,
std::nullopt,
std::nullopt,
totalWork,
height,
height,
saplingTxCount
saplingTxCount,
std::nullopt
);
}
HistoryNode NewV2Leaf(
uint256 commitment,
uint32_t time,
uint32_t target,
uint256 saplingRoot,
uint256 orchardRoot,
uint256 totalWork,
uint64_t height,
uint64_t saplingTxCount,
uint64_t orchardTxCount
) {
return NewNode(
commitment,
time,
time,
target,
target,
saplingRoot,
saplingRoot,
orchardRoot,
orchardRoot,
totalWork,
height,
height,
saplingTxCount,
orchardTxCount
);
}
@ -134,4 +176,15 @@ HistoryEntry LeafToEntry(const HistoryNode node) {
return result;
}
bool IsV1HistoryTree(uint32_t epochId) {
return (
epochId == NetworkUpgradeInfo[Consensus::BASE_SPROUT].nBranchId ||
epochId == NetworkUpgradeInfo[Consensus::UPGRADE_OVERWINTER].nBranchId ||
epochId == NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId ||
epochId == NetworkUpgradeInfo[Consensus::UPGRADE_BLOSSOM].nBranchId ||
epochId == NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId ||
epochId == NetworkUpgradeInfo[Consensus::UPGRADE_CANOPY].nBranchId
);
}
}

View File

@ -43,8 +43,8 @@ public:
void Truncate(HistoryIndex newLength);
};
// New history node with metadata based on block state.
HistoryNode NewLeaf(
// New V1 history node with metadata based on block state.
HistoryNode NewV1Leaf(
uint256 commitment,
uint32_t time,
uint32_t target,
@ -54,12 +54,28 @@ HistoryNode NewLeaf(
uint64_t saplingTxCount
);
// New V2 history node with metadata based on block state.
HistoryNode NewV2Leaf(
uint256 commitment,
uint32_t time,
uint32_t target,
uint256 saplingRoot,
uint256 orchardRoot,
uint256 totalWork,
uint64_t height,
uint64_t saplingTxCount,
uint64_t orchardTxCount
);
// Convert history node to tree node (with children references)
HistoryEntry NodeToEntry(const HistoryNode node, uint32_t left, uint32_t right);
// Convert history node to leaf node (end nodes without children)
HistoryEntry LeafToEntry(const HistoryNode node);
// Returns true if this epoch used the V1 history tree format.
bool IsV1HistoryTree(uint32_t epochId);
}
typedef libzcash::HistoryCache HistoryCache;