Auto merge of #5227 - str4d:v2-history-tree, r=str4d

Integrate V2 history tree

Closes #5037.
This commit is contained in:
Homu 2021-06-20 22:26:04 +00:00
commit 9253e2828e
16 changed files with 645 additions and 312 deletions

11
Cargo.lock generated
View File

@ -473,7 +473,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "equihash"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff#d88e40113c8dadea751dfcdd72ee90868f9655ff"
source = "git+https://github.com/zcash/librustzcash.git?rev=fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13#fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13"
dependencies = [
"blake2b_simd",
"byteorder",
@ -1677,8 +1677,7 @@ dependencies = [
[[package]]
name = "zcash_history"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abfbab9accba014bbf3098d5aa66c1714d0db4abe25b999b8400bbd626ccd2f4"
source = "git+https://github.com/zcash/librustzcash.git?rev=fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13#fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13"
dependencies = [
"bigint",
"blake2b_simd",
@ -1688,7 +1687,7 @@ dependencies = [
[[package]]
name = "zcash_note_encryption"
version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff#d88e40113c8dadea751dfcdd72ee90868f9655ff"
source = "git+https://github.com/zcash/librustzcash.git?rev=fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13#fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13"
dependencies = [
"blake2b_simd",
"byteorder",
@ -1702,7 +1701,7 @@ dependencies = [
[[package]]
name = "zcash_primitives"
version = "0.5.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff#d88e40113c8dadea751dfcdd72ee90868f9655ff"
source = "git+https://github.com/zcash/librustzcash.git?rev=fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13#fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13"
dependencies = [
"aes",
"bitvec",
@ -1732,7 +1731,7 @@ dependencies = [
[[package]]
name = "zcash_proofs"
version = "0.5.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff#d88e40113c8dadea751dfcdd72ee90868f9655ff"
source = "git+https://github.com/zcash/librustzcash.git?rev=fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13#fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13"
dependencies = [
"bellman",
"blake2b_simd",

View File

@ -62,6 +62,7 @@ codegen-units = 1
ed25519-zebra = { git = "https://github.com/ZcashFoundation/ed25519-zebra.git", rev = "d3512400227a362d08367088ffaa9bd4142a69c7" }
halo2 = { git = "https://github.com/zcash/halo2.git", rev = "d04b532368d05b505e622f8cac4c0693574fbd93" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "f7c64e0437040d831e61711cd9e5695b001cb5cb" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "d88e40113c8dadea751dfcdd72ee90868f9655ff" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "d88e40113c8dadea751dfcdd72ee90868f9655ff" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "d88e40113c8dadea751dfcdd72ee90868f9655ff" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "fe4b63c8fe7300ebc9cd7a1242867525ccfe1e13" }

View File

@ -8,10 +8,14 @@ from test_framework.flyclient import (ZcashMMRNode, append, delete, make_root_co
from test_framework.mininode import (CBlockHeader)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
HEARTWOOD_BRANCH_ID,
BLOSSOM_BRANCH_ID,
HEARTWOOD_BRANCH_ID,
CANOPY_BRANCH_ID,
NU5_BRANCH_ID,
assert_equal,
bytes_to_hex_str,
hex_str_to_bytes,
nuparams,
start_nodes,
)
@ -30,19 +34,51 @@ class Zip221Test(BitcoinTestFramework):
def setup_nodes(self):
return start_nodes(self.num_nodes, self.options.tmpdir, extra_args=[[
'-nuparams=2bb40e60:1', # Blossom
'-nuparams=f5b9230b:10', # Heartwood
nuparams(BLOSSOM_BRANCH_ID, 1),
nuparams(HEARTWOOD_BRANCH_ID, 10),
nuparams(CANOPY_BRANCH_ID, 32),
nuparams(NU5_BRANCH_ID, 35),
'-nurejectoldversions=false',
]] * self.num_nodes)
def node_for_block(self, height):
if height >= 35:
epoch = NU5_BRANCH_ID
elif height >= 32:
epoch = CANOPY_BRANCH_ID
else:
epoch = HEARTWOOD_BRANCH_ID
block_header = CBlockHeader()
block_header.deserialize(BytesIO(hex_str_to_bytes(
self.nodes[0].getblock(str(height), 0))))
sapling_root = hex_str_to_bytes(
self.nodes[0].getblock(str(height))["finalsaplingroot"])[::-1]
if height >= 35:
orchard_root = hex_str_to_bytes(
self.nodes[0].getblock(str(height))["finalorchardroot"])[::-1]
v2_data = (orchard_root, 0)
else:
v2_data = None
return ZcashMMRNode.from_block(
block_header, height, sapling_root, 0, HEARTWOOD_BRANCH_ID)
block_header, height, sapling_root, 0, epoch, v2_data)
def new_tree(self, height):
newRoot = self.node_for_block(height - 1)
assert_equal(
self.nodes[0].getblock(str(height))["chainhistoryroot"],
bytes_to_hex_str(make_root_commitment(newRoot)[::-1]))
return newRoot
def update_tree(self, root, height):
leaf = self.node_for_block(height - 1)
root = append(root, leaf)
assert_equal(
self.nodes[0].getblock(str(height))["chainhistoryroot"],
bytes_to_hex_str(make_root_commitment(root)[::-1]))
return root
def run_test(self):
self.nodes[0].generate(10)
@ -130,6 +166,25 @@ class Zip221Test(BitcoinTestFramework):
self.nodes[2].getblock(str(height))["chainhistoryroot"],
bytes_to_hex_str(make_root_commitment(root)[::-1]))
# Generate 6 blocks on node 0 to activate Canopy and NU5.
print("Mining 6 blocks on node 0")
self.nodes[0].generate(6)
self.sync_all()
# The Canopy activation block should commit to the final Heartwood tree.
root = self.update_tree(root, 32)
# The two blocks after Canopy activation should be a new tree.
canopyRoot = self.new_tree(33)
canopyRoot = self.update_tree(canopyRoot, 34)
# The NU5 activation block should commit to the final Heartwood tree.
canopyRoot = self.update_tree(canopyRoot, 35)
# The two blocks after NU5 activation should be a V2 tree.
nu5Root = self.new_tree(36)
nu5Root = self.update_tree(nu5Root, 37)
if __name__ == '__main__':
Zip221Test().main()

View File

@ -3,6 +3,10 @@ import struct
from typing import (List, Optional)
from .mininode import (CBlockHeader, block_work_from_compact, ser_compactsize, ser_uint256)
from .util import (
NU5_BRANCH_ID,
assert_equal,
)
def H(msg: bytes, consensusBranchId: int) -> bytes:
digest = blake2b(
@ -28,12 +32,29 @@ class ZcashMMRNode():
nEarliestHeight: int
nLatestHeight: int
nSaplingTxCount: int # number of Sapling transactions in block
# NU5 only.
hashEarliestOrchardRoot: Optional[bytes] # left child's Orchard root
hashLatestOrchardRoot: Optional[bytes] # right child's Orchard root
nOrchardTxCount: Optional[int] # number of Orchard transactions in block
consensusBranchId: bytes
@classmethod
def from_block(Z, block: CBlockHeader, height, sapling_root, sapling_tx_count, consensusBranchId) -> 'ZcashMMRNode':
def from_block(
Z, block: CBlockHeader, height,
sapling_root, sapling_tx_count,
consensusBranchId,
v2_data=None
) -> 'ZcashMMRNode':
'''Create a leaf node from a block'''
if v2_data is not None:
assert_equal(consensusBranchId, NU5_BRANCH_ID)
orchard_root = v2_data[0]
orchard_tx_count = v2_data[1]
else:
orchard_root = None
orchard_tx_count = None
node = Z()
node.left_child = None
node.right_child = None
@ -48,6 +69,9 @@ class ZcashMMRNode():
node.nEarliestHeight = height
node.nLatestHeight = height
node.nSaplingTxCount = sapling_tx_count
node.hashEarliestOrchardRoot = orchard_root
node.hashLatestOrchardRoot = orchard_root
node.nOrchardTxCount = orchard_tx_count
node.consensusBranchId = consensusBranchId
return node
@ -65,6 +89,10 @@ class ZcashMMRNode():
buf += ser_compactsize(self.nEarliestHeight)
buf += ser_compactsize(self.nLatestHeight)
buf += ser_compactsize(self.nSaplingTxCount)
if self.hashEarliestOrchardRoot is not None:
buf += self.hashEarliestOrchardRoot
buf += self.hashLatestOrchardRoot
buf += ser_compactsize(self.nOrchardTxCount)
return buf
def make_parent(
@ -87,6 +115,12 @@ def make_parent(
parent.nEarliestHeight = left_child.nEarliestHeight
parent.nLatestHeight = right_child.nLatestHeight
parent.nSaplingTxCount = left_child.nSaplingTxCount + right_child.nSaplingTxCount
parent.hashEarliestOrchardRoot = left_child.hashEarliestOrchardRoot
parent.hashLatestOrchardRoot = right_child.hashLatestOrchardRoot
parent.nOrchardTxCount = (
left_child.nOrchardTxCount + right_child.nOrchardTxCount
if left_child.nOrchardTxCount is not None and right_child.nOrchardTxCount is not None
else None)
parent.consensusBranchId = left_child.consensusBranchId
return parent

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,
@ -81,34 +81,36 @@ TEST(History, Smoky) {
FakeCoinsViewDB fakeDB;
CCoinsViewCache view(&fakeDB);
uint32_t epochId = 0;
// Test initial value
EXPECT_EQ(view.GetHistoryLength(0), 0);
EXPECT_EQ(view.GetHistoryLength(epochId), 0);
view.PushHistoryNode(1, getLeafN(1));
view.PushHistoryNode(epochId, getLeafN(1));
EXPECT_EQ(view.GetHistoryLength(1), 1);
EXPECT_EQ(view.GetHistoryLength(epochId), 1);
view.PushHistoryNode(1, getLeafN(2));
view.PushHistoryNode(epochId, getLeafN(2));
EXPECT_EQ(view.GetHistoryLength(1), 3);
EXPECT_EQ(view.GetHistoryLength(epochId), 3);
view.PushHistoryNode(1, getLeafN(3));
view.PushHistoryNode(epochId, getLeafN(3));
EXPECT_EQ(view.GetHistoryLength(1), 4);
EXPECT_EQ(view.GetHistoryLength(epochId), 4);
view.PushHistoryNode(1, getLeafN(4));
view.PushHistoryNode(epochId, getLeafN(4));
uint256 h4Root = view.GetHistoryRoot(1);
uint256 h4Root = view.GetHistoryRoot(epochId);
EXPECT_EQ(view.GetHistoryLength(1), 7);
EXPECT_EQ(view.GetHistoryLength(epochId), 7);
view.PushHistoryNode(1, getLeafN(5));
EXPECT_EQ(view.GetHistoryLength(1), 8);
view.PushHistoryNode(epochId, getLeafN(5));
EXPECT_EQ(view.GetHistoryLength(epochId), 8);
view.PopHistoryNode(1);
view.PopHistoryNode(epochId);
EXPECT_EQ(view.GetHistoryLength(1), 7);
EXPECT_EQ(h4Root, view.GetHistoryRoot(1));
EXPECT_EQ(view.GetHistoryLength(epochId), 7);
EXPECT_EQ(h4Root, view.GetHistoryRoot(epochId));
}
@ -117,56 +119,64 @@ TEST(History, EpochBoundaries) {
FakeCoinsViewDB fakeDB;
CCoinsViewCache view(&fakeDB);
view.PushHistoryNode(1, getLeafN(1));
// Test with the Heartwood and Canopy epochs
uint32_t epoch1 = 0xf5b9230b;
uint32_t epoch2 = 0xe9ff75a6;
EXPECT_EQ(view.GetHistoryLength(1), 1);
view.PushHistoryNode(epoch1, getLeafN(1));
view.PushHistoryNode(1, getLeafN(2));
EXPECT_EQ(view.GetHistoryLength(epoch1), 1);
EXPECT_EQ(view.GetHistoryLength(1), 3);
view.PushHistoryNode(epoch1, getLeafN(2));
view.PushHistoryNode(1, getLeafN(3));
EXPECT_EQ(view.GetHistoryLength(epoch1), 3);
EXPECT_EQ(view.GetHistoryLength(1), 4);
view.PushHistoryNode(epoch1, getLeafN(3));
view.PushHistoryNode(1, getLeafN(4));
EXPECT_EQ(view.GetHistoryLength(epoch1), 4);
uint256 h4Root = view.GetHistoryRoot(1);
view.PushHistoryNode(epoch1, getLeafN(4));
EXPECT_EQ(view.GetHistoryLength(1), 7);
uint256 h4Root = view.GetHistoryRoot(epoch1);
view.PushHistoryNode(1, getLeafN(5));
EXPECT_EQ(view.GetHistoryLength(1), 8);
EXPECT_EQ(view.GetHistoryLength(epoch1), 7);
view.PushHistoryNode(epoch1, getLeafN(5));
EXPECT_EQ(view.GetHistoryLength(epoch1), 8);
// New Epoch(2)
view.PushHistoryNode(2, getLeafN(6));
EXPECT_EQ(view.GetHistoryLength(1), 8);
EXPECT_EQ(view.GetHistoryLength(2), 1);
// Move to Canopy epoch
view.PushHistoryNode(epoch2, getLeafN(6));
EXPECT_EQ(view.GetHistoryLength(epoch1), 8);
EXPECT_EQ(view.GetHistoryLength(epoch2), 1);
view.PushHistoryNode(2, getLeafN(7));
EXPECT_EQ(view.GetHistoryLength(1), 8);
EXPECT_EQ(view.GetHistoryLength(2), 3);
view.PushHistoryNode(epoch2, getLeafN(7));
EXPECT_EQ(view.GetHistoryLength(epoch1), 8);
EXPECT_EQ(view.GetHistoryLength(epoch2), 3);
view.PushHistoryNode(2, getLeafN(8));
EXPECT_EQ(view.GetHistoryLength(1), 8);
EXPECT_EQ(view.GetHistoryLength(2), 4);
view.PushHistoryNode(epoch2, getLeafN(8));
EXPECT_EQ(view.GetHistoryLength(epoch1), 8);
EXPECT_EQ(view.GetHistoryLength(epoch2), 4);
// Rolling epoch back to 1
view.PopHistoryNode(2);
EXPECT_EQ(view.GetHistoryLength(2), 3);
view.PopHistoryNode(epoch2);
EXPECT_EQ(view.GetHistoryLength(epoch2), 3);
view.PopHistoryNode(2);
EXPECT_EQ(view.GetHistoryLength(2), 1);
EXPECT_EQ(view.GetHistoryLength(1), 8);
view.PopHistoryNode(epoch2);
EXPECT_EQ(view.GetHistoryLength(epoch2), 1);
EXPECT_EQ(view.GetHistoryLength(epoch1), 8);
// And even rolling epoch 1 back a bit
view.PopHistoryNode(1);
EXPECT_EQ(view.GetHistoryLength(1), 7);
view.PopHistoryNode(epoch1);
EXPECT_EQ(view.GetHistoryLength(epoch1), 7);
// And also rolling epoch 2 back to 0
view.PopHistoryNode(2);
EXPECT_EQ(view.GetHistoryLength(2), 0);
view.PopHistoryNode(epoch2);
EXPECT_EQ(view.GetHistoryLength(epoch2), 0);
// Trying to truncate an empty tree is a no-op
view.PopHistoryNode(epoch2);
EXPECT_EQ(view.GetHistoryLength(epoch2), 0);
}

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

@ -222,6 +222,9 @@ UniValue blockToDeltasJSON(const CBlock& block, const CBlockIndex* blockindex)
UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false)
{
AssertLockHeld(cs_main);
bool nu5Active = Params().GetConsensus().NetworkUpgradeActive(
blockindex->nHeight, Consensus::UPGRADE_NU5);
UniValue result(UniValue::VOBJ);
result.pushKV("hash", block.GetHash().GetHex());
int confirmations = -1;
@ -236,6 +239,9 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
result.pushKV("blockcommitments", blockindex->hashBlockCommitments.GetHex());
result.pushKV("authdataroot", blockindex->hashAuthDataRoot.GetHex());
result.pushKV("finalsaplingroot", blockindex->hashFinalSaplingRoot.GetHex());
if (nu5Active) {
result.pushKV("finalorchardroot", blockindex->hashFinalOrchardRoot.GetHex());
}
result.pushKV("chainhistoryroot", blockindex->hashChainHistoryRoot.GetHex());
UniValue txs(UniValue::VARR);
for (const CTransaction&tx : block.vtx)
@ -706,6 +712,9 @@ UniValue getblock(const UniValue& params, bool fHelp)
" \"version\" : n, (numeric) The block version\n"
" \"merkleroot\" : \"xxxx\", (string) The merkle root\n"
" \"finalsaplingroot\" : \"xxxx\", (string) The root of the Sapling commitment tree after applying this block\n"
" \"finalorchardroot\" : \"xxxx\", (string) The root of the Orchard commitment tree after applying this block.\n"
" Omitted for blocks prior to NU5 activation. This will be the null\n"
" hash if this block has never been connected to a main chain.\n"
" \"tx\" : [ (array of string) The transaction ids\n"
" \"transactionid\" (string) The transaction id\n"
" ,...\n"

View File

@ -11,25 +11,6 @@
#include <stdalign.h>
#endif
#define NODE_SERIALIZED_LENGTH 171
#define ENTRY_SERIALIZED_LENGTH (NODE_SERIALIZED_LENGTH + 9)
typedef struct HistoryNode {
unsigned char bytes[NODE_SERIALIZED_LENGTH];
} HistoryNode;
static_assert(
sizeof(HistoryNode) == NODE_SERIALIZED_LENGTH,
"HistoryNode struct is not the same size as the underlying byte array");
static_assert(alignof(HistoryNode) == 1, "HistoryNode struct alignment is not 1");
typedef struct HistoryEntry {
unsigned char bytes[ENTRY_SERIALIZED_LENGTH];
} HistoryEntry;
static_assert(
sizeof(HistoryEntry) == ENTRY_SERIALIZED_LENGTH,
"HistoryEntry struct is not the same size as the underlying byte array");
static_assert(alignof(HistoryEntry) == 1, "HistoryEntry struct alignment is not 1");
#ifdef __cplusplus
extern "C" {
#endif
@ -333,33 +314,6 @@ extern "C" {
unsigned char *addr_ret
);
uint32_t librustzcash_mmr_append(
uint32_t cbranch,
uint32_t t_len,
const uint32_t *ni_ptr,
const HistoryEntry *n_ptr,
size_t p_len,
const HistoryNode *nn_ptr,
unsigned char *rt_ret,
HistoryNode *buf_ret
);
uint32_t librustzcash_mmr_delete(
uint32_t cbranch,
uint32_t t_len,
const uint32_t *ni_ptr,
const HistoryEntry *n_ptr,
size_t p_len,
size_t e_len,
unsigned char *rt_ret
);
uint32_t librustzcash_mmr_hash_node(
uint32_t cbranch,
const HistoryNode *n_ptr,
unsigned char *h_ret
);
/// Fills the provided buffer with random bytes. This is intended to
/// be a cryptographically secure RNG; it uses Rust's `OsRng`, which
/// is implemented in terms of the `getrandom` crate. The first call

View File

@ -0,0 +1,74 @@
// 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 .
#ifndef ZCASH_RUST_INCLUDE_RUST_HISTORY_H
#define ZCASH_RUST_INCLUDE_RUST_HISTORY_H
#include <stddef.h>
#include <stdint.h>
#define NODE_V1_SERIALIZED_LENGTH 171
#define NODE_SERIALIZED_LENGTH 244
#define ENTRY_SERIALIZED_LENGTH (NODE_SERIALIZED_LENGTH + 9)
typedef struct HistoryNode {
unsigned char bytes[NODE_SERIALIZED_LENGTH];
} HistoryNode;
static_assert(
sizeof(HistoryNode) == NODE_SERIALIZED_LENGTH,
"HistoryNode struct is not the same size as the underlying byte array");
static_assert(alignof(HistoryNode) == 1, "HistoryNode struct alignment is not 1");
typedef struct HistoryEntry {
unsigned char bytes[ENTRY_SERIALIZED_LENGTH];
} HistoryEntry;
static_assert(
sizeof(HistoryEntry) == ENTRY_SERIALIZED_LENGTH,
"HistoryEntry struct is not the same size as the underlying byte array");
static_assert(alignof(HistoryEntry) == 1, "HistoryEntry struct alignment is not 1");
#ifdef __cplusplus
extern "C" {
#endif
/// Appends a leaf to the given history tree.
///
/// `t_len` must be at least 1.
///
/// Aborts if `cbranch` is not a valid consensus branch ID.
uint32_t librustzcash_mmr_append(
uint32_t cbranch,
uint32_t t_len,
const uint32_t* ni_ptr,
const HistoryEntry* n_ptr,
size_t p_len,
const HistoryNode* nn_ptr,
unsigned char* rt_ret,
HistoryNode* buf_ret);
/// Deletes the most recently-appended leaf from the given history tree.
///
/// `t_len` must be at least 1.
///
/// Aborts if `cbranch` is not a valid consensus branch ID.
uint32_t librustzcash_mmr_delete(
uint32_t cbranch,
uint32_t t_len,
const uint32_t* ni_ptr,
const HistoryEntry* n_ptr,
size_t p_len,
size_t e_len,
unsigned char* rt_ret);
/// Returns the hash of the given history tree node.
///
/// Aborts if `cbranch` is not a valid consensus branch ID.
uint32_t librustzcash_mmr_hash_node(
uint32_t cbranch,
const HistoryNode* n_ptr,
unsigned char* h_ret);
#ifdef __cplusplus
}
#endif
#endif // ZCASH_RUST_INCLUDE_RUST_HISTORY_H

264
src/rust/src/history_ffi.rs Normal file
View File

@ -0,0 +1,264 @@
use std::{convert::TryFrom, slice};
use libc::{c_uchar, size_t};
use zcash_history::{Entry as MMREntry, Tree as MMRTree, Version, V1, V2};
use zcash_primitives::consensus::BranchId;
/// 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
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,
) -> Result<MMRTree<V>, &'static str> {
let (indices, nodes) = unsafe {
(
slice::from_raw_parts(ni_ptr, p_len + e_len),
slice::from_raw_parts(n_ptr, p_len + e_len),
)
};
let mut peaks: Vec<_> = indices
.iter()
.zip(nodes.iter())
.map(
|(index, node)| match MMREntry::from_bytes(cbranch, &node[..]) {
Ok(entry) => Ok((*index, entry)),
Err(_) => Err("Invalid encoding"),
},
)
.collect::<Result<_, _>>()?;
let extra = peaks.split_off(p_len);
Ok(MMRTree::new(t_len, peaks, extra))
}
#[no_mangle]
pub extern "system" fn librustzcash_mmr_append(
// 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 {
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() {
Some(r) => r,
None => {
return 0;
} // Null pointer passed, error
}
};
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 V::from_bytes(cbranch, &new_node_bytes[..]) {
Ok(node) => node,
_ => {
return 0;
} // error
};
let appended = match tree.append_leaf(node) {
Ok(appended) => appended,
_ => {
return 0;
}
};
let return_count = appended.len();
let root_node = tree
.root_node()
.expect("Just added, should resolve always; qed");
unsafe {
*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()
{
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");
}
}
return_count as u32
}
#[no_mangle]
pub extern "system" fn librustzcash_mmr_delete(
// 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 {
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;
} // error
};
let truncate_len = match tree.truncate_leaf() {
Ok(v) => v,
_ => {
return 0;
} // Error
};
unsafe {
*rt_ret = V::hash(
tree.root_node()
.expect("Just generated without errors, root should be resolving")
.data(),
);
}
truncate_len
}
#[no_mangle]
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() {
Some(r) => r,
None => return 1,
}
};
let node = match V::from_bytes(cbranch, &node_bytes[..]) {
Ok(n) => n,
_ => return 1, // error
};
unsafe {
*h_ret = V::hash(&node);
}
0
}

View File

@ -61,14 +61,13 @@ use zcash_proofs::{
sprout,
};
use zcash_history::{Entry as MMREntry, NodeData as MMRNodeData, Tree as MMRTree};
mod blake2b;
mod ed25519;
mod metrics_ffi;
mod streams_ffi;
mod tracing_ffi;
mod history_ffi;
mod orchard_ffi;
mod transaction_ffi;
@ -1092,183 +1091,6 @@ pub extern "C" fn librustzcash_zip32_xfvk_address(
true
}
fn construct_mmr_tree(
// 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,
) -> Result<MMRTree, &'static str> {
let (indices, nodes) = unsafe {
(
slice::from_raw_parts(ni_ptr, p_len + e_len),
slice::from_raw_parts(n_ptr, p_len + e_len),
)
};
let mut peaks: Vec<_> = indices
.iter()
.zip(nodes.iter())
.map(
|(index, node)| match MMREntry::from_bytes(cbranch, &node[..]) {
Ok(entry) => Ok((*index, entry)),
Err(_) => Err("Invalid encoding"),
},
)
.collect::<Result<_, _>>()?;
let extra = peaks.split_off(p_len);
Ok(MMRTree::new(t_len, peaks, extra))
}
#[no_mangle]
pub extern "system" fn librustzcash_mmr_append(
// 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() {
Some(r) => r,
None => {
return 0;
} // Null pointer passed, error
}
};
let mut tree = match construct_mmr_tree(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[..]) {
Ok(node) => node,
_ => {
return 0;
} // error
};
let appended = match tree.append_leaf(node) {
Ok(appended) => appended,
_ => {
return 0;
}
};
let return_count = appended.len();
let root_node = tree
.root_node()
.expect("Just added, should resolve always; qed");
unsafe {
*rt_ret = root_node.data().hash();
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");
}
}
return_count as u32
}
#[no_mangle]
pub extern "system" fn librustzcash_mmr_delete(
// 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(cbranch, t_len, ni_ptr, n_ptr, p_len, e_len) {
Ok(t) => t,
_ => {
return 0;
} // error
};
let truncate_len = match tree.truncate_leaf() {
Ok(v) => v,
_ => {
return 0;
} // Error
};
unsafe {
*rt_ret = tree
.root_node()
.expect("Just generated without errors, root should be resolving")
.data()
.hash();
}
truncate_len
}
#[no_mangle]
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 {
let node_bytes: &[u8; zcash_history::MAX_NODE_DATA_SIZE] = unsafe {
match n_ptr.as_ref() {
Some(r) => r,
None => return 1,
}
};
let node = match MMRNodeData::from_bytes(cbranch, &node_bytes[..]) {
Ok(n) => n,
_ => return 1, // error
};
unsafe {
*h_ret = node.hash();
}
0
}
#[no_mangle]
pub extern "C" fn librustzcash_getrandom(buf: *mut u8, buf_len: usize) {
let buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };

View File

@ -1,19 +1,19 @@
use zcash_history::{Entry, EntryLink, NodeData};
use zcash_history::{Entry, EntryLink, NodeData, V1};
use crate::{librustzcash_mmr_append, librustzcash_mmr_delete};
use crate::history_ffi::{librustzcash_mmr_append, librustzcash_mmr_delete};
const NODE_DATA_16L: &[u8] = include_bytes!("./res/tree16.dat");
const NODE_DATA_1023L: &[u8] = include_bytes!("./res/tree1023.dat");
struct TreeView {
peaks: Vec<(u32, Entry)>,
extra: Vec<(u32, Entry)>,
peaks: Vec<(u32, Entry<V1>)>,
extra: Vec<(u32, Entry<V1>)>,
}
fn draft(into: &mut Vec<(u32, Entry)>, nodes: &[NodeData], peak_pos: usize, h: u32) {
fn draft(into: &mut Vec<(u32, Entry<V1>)>, nodes: &[NodeData], peak_pos: usize, h: u32) {
let node_data = nodes[peak_pos - 1].clone();
let peak: Entry = match h {
0 => node_data.into(),
let peak = match h {
0 => Entry::new_leaf(node_data),
_ => Entry::new(
node_data,
EntryLink::Stored((peak_pos - (1 << h) - 1) as u32),

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

@ -9,6 +9,8 @@
#include "streams.h"
#include "uint256.h"
#include <rust/history.h>
#include "librustzcash.h"
namespace libzcash {
@ -41,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,
@ -52,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;