diff --git a/Cargo.lock b/Cargo.lock index 578526394..588e311ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 8983a0b5b..8a0fb9bed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/qa/rpc-tests/feature_zip221.py b/qa/rpc-tests/feature_zip221.py index 0539343f5..e2e6bdcc4 100755 --- a/qa/rpc-tests/feature_zip221.py +++ b/qa/rpc-tests/feature_zip221.py @@ -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() diff --git a/qa/rpc-tests/test_framework/flyclient.py b/qa/rpc-tests/test_framework/flyclient.py index c8bbb1e62..368c6516c 100644 --- a/qa/rpc-tests/test_framework/flyclient.py +++ b/qa/rpc-tests/test_framework/flyclient.py @@ -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 diff --git a/src/gtest/test_history.cpp b/src/gtest/test_history.cpp index 9ea5bf427..28ad5be15 100644 --- a/src/gtest/test_history.cpp +++ b/src/gtest/test_history.cpp @@ -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); } diff --git a/src/main.cpp b/src/main.cpp index a2ad1b2b3..5487c8a49 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 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); } diff --git a/src/primitives/orchard.h b/src/primitives/orchard.h index 7caf7af00..12986a554 100644 --- a/src/primitives/orchard.h +++ b/src/primitives/orchard.h @@ -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. diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 834de7462..065e61448 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -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" diff --git a/src/rust/include/librustzcash.h b/src/rust/include/librustzcash.h index 863777a73..66c5aaa89 100644 --- a/src/rust/include/librustzcash.h +++ b/src/rust/include/librustzcash.h @@ -11,25 +11,6 @@ #include #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 diff --git a/src/rust/include/rust/history.h b/src/rust/include/rust/history.h new file mode 100644 index 000000000..aadc35ca2 --- /dev/null +++ b/src/rust/include/rust/history.h @@ -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 +#include + +#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 diff --git a/src/rust/src/history_ffi.rs b/src/rust/src/history_ffi.rs new file mode 100644 index 000000000..d815de501 --- /dev/null +++ b/src/rust/src/history_ffi.rs @@ -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(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( + // 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, &'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::>()?; + 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::( + cbranch, t_len, ni_ptr, n_ptr, p_len, nn_ptr, rt_ret, buf_ret, + ) + }, + || { + librustzcash_mmr_append_inner::( + cbranch, t_len, ni_ptr, n_ptr, p_len, nn_ptr, rt_ret, buf_ret, + ) + }, + ) +} + +fn librustzcash_mmr_append_inner( + // 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 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::(cbranch, t_len, ni_ptr, n_ptr, p_len, e_len, rt_ret), + || librustzcash_mmr_delete_inner::(cbranch, t_len, ni_ptr, n_ptr, p_len, e_len, rt_ret), + ) +} + +fn librustzcash_mmr_delete_inner( + // 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 = 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::(cbranch, n_ptr, h_ret), + || librustzcash_mmr_hash_node_inner::(cbranch, n_ptr, h_ret), + ) +} + +fn librustzcash_mmr_hash_node_inner( + 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 +} diff --git a/src/rust/src/rustzcash.rs b/src/rust/src/rustzcash.rs index c20084cd4..9df08f25d 100644 --- a/src/rust/src/rustzcash.rs +++ b/src/rust/src/rustzcash.rs @@ -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 { - 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::>()?; - 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) }; diff --git a/src/rust/src/tests/mmr.rs b/src/rust/src/tests/mmr.rs index aa83aa37d..4afb3c004 100644 --- a/src/rust/src/tests/mmr.rs +++ b/src/rust/src/tests/mmr.rs @@ -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)>, + extra: Vec<(u32, Entry)>, } -fn draft(into: &mut Vec<(u32, Entry)>, nodes: &[NodeData], peak_pos: usize, h: u32) { +fn draft(into: &mut Vec<(u32, Entry)>, 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), diff --git a/src/txdb.cpp b/src/txdb.cpp index 33ee323f1..de659dceb 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -10,6 +10,7 @@ #include "main.h" #include "pow.h" #include "uint256.h" +#include "zcash/History.hpp" #include @@ -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 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 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 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; } diff --git a/src/zcash/History.cpp b/src/zcash/History.cpp index b17f41d8c..05cd44cb5 100644 --- a/src/zcash/History.cpp +++ b/src/zcash/History.cpp @@ -3,6 +3,7 @@ #include +#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 startOrchardRoot, + std::optional endOrchardRoot, uint256 subtreeTotalWork, uint64_t startHeight, uint64_t endHeight, - uint64_t saplingTxCount + uint64_t saplingTxCount, + std::optional 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 + ); +} + } diff --git a/src/zcash/History.hpp b/src/zcash/History.hpp index 990764a32..abfbf7956 100644 --- a/src/zcash/History.hpp +++ b/src/zcash/History.hpp @@ -9,6 +9,8 @@ #include "streams.h" #include "uint256.h" +#include + #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;