diff --git a/.cargo/config.offline b/.cargo/config.offline index d98d2c9b3..f07fd5658 100644 --- a/.cargo/config.offline +++ b/.cargo/config.offline @@ -3,17 +3,17 @@ replace-with = "vendored-sources" [source."https://github.com/zcash/librustzcash.git"] git = "https://github.com/zcash/librustzcash.git" -rev = "cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" +rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" replace-with = "vendored-sources" [source."https://github.com/zcash/orchard.git"] git = "https://github.com/zcash/orchard.git" -rev = "8449fd133c002a349869c09c529a4d214b20c0c0" +rev = "a5f701f3186fb619b40f2dee9939e64e4c9eb7cc" replace-with = "vendored-sources" [source."https://github.com/zcash/incrementalmerkletree.git"] git = "https://github.com/zcash/incrementalmerkletree.git" -rev = "5707d6ac096c2b7a345166e08c378e0a5d64e6d8" +rev = "62c33e4480a71170b02b9eb7d4b0160194f414ee" replace-with = "vendored-sources" [source."https://github.com/nuttycom/hdwallet.git"] diff --git a/Cargo.lock b/Cargo.lock index 87ae4ea5d..d233abadb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -501,7 +501,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "equihash" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9#cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" +source = "git+https://github.com/zcash/librustzcash.git?rev=67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289#67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" dependencies = [ "blake2b_simd 1.0.0", "byteorder", @@ -510,7 +510,7 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9#cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" +source = "git+https://github.com/zcash/librustzcash.git?rev=67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289#67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" dependencies = [ "blake2b_simd 1.0.0", ] @@ -748,7 +748,7 @@ dependencies = [ [[package]] name = "incrementalmerkletree" version = "0.2.0" -source = "git+https://github.com/zcash/incrementalmerkletree.git?rev=5707d6ac096c2b7a345166e08c378e0a5d64e6d8#5707d6ac096c2b7a345166e08c378e0a5d64e6d8" +source = "git+https://github.com/zcash/incrementalmerkletree.git?rev=62c33e4480a71170b02b9eb7d4b0160194f414ee#62c33e4480a71170b02b9eb7d4b0160194f414ee" dependencies = [ "serde", ] @@ -862,6 +862,7 @@ dependencies = [ "tracing-core", "tracing-subscriber", "zcash_address", + "zcash_encoding", "zcash_history", "zcash_note_encryption", "zcash_primitives", @@ -1101,7 +1102,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "orchard" version = "0.1.0-beta.1" -source = "git+https://github.com/zcash/orchard.git?rev=8449fd133c002a349869c09c529a4d214b20c0c0#8449fd133c002a349869c09c529a4d214b20c0c0" +source = "git+https://github.com/zcash/orchard.git?rev=a5f701f3186fb619b40f2dee9939e64e4c9eb7cc#a5f701f3186fb619b40f2dee9939e64e4c9eb7cc" dependencies = [ "aes", "arrayvec 0.7.2", @@ -1930,7 +1931,7 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9#cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" +source = "git+https://github.com/zcash/librustzcash.git?rev=67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289#67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" dependencies = [ "bech32", "bs58", @@ -1941,7 +1942,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9#cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" +source = "git+https://github.com/zcash/librustzcash.git?rev=67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289#67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" dependencies = [ "byteorder", "nonempty", @@ -1950,7 +1951,7 @@ dependencies = [ [[package]] name = "zcash_history" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9#cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" +source = "git+https://github.com/zcash/librustzcash.git?rev=67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289#67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" dependencies = [ "bigint", "blake2b_simd 1.0.0", @@ -1960,7 +1961,7 @@ dependencies = [ [[package]] name = "zcash_note_encryption" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9#cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" +source = "git+https://github.com/zcash/librustzcash.git?rev=67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289#67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" dependencies = [ "chacha20", "chacha20poly1305", @@ -1971,7 +1972,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9#cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" +source = "git+https://github.com/zcash/librustzcash.git?rev=67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289#67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" dependencies = [ "aes", "bip0039", @@ -2007,7 +2008,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9#cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" +source = "git+https://github.com/zcash/librustzcash.git?rev=67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289#67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" dependencies = [ "bellman", "blake2b_simd 1.0.0", diff --git a/Cargo.toml b/Cargo.toml index 2afc194a1..0bdc74d6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ tracing = "0.1" tracing-core = "0.1" tracing-appender = "0.2" zcash_address = "0.0" +zcash_encoding = "0.0" zcash_history = "0.2" zcash_note_encryption = "0.1" zcash_primitives = { version = "0.5", features = ["transparent-inputs"] } @@ -72,10 +73,11 @@ codegen-units = 1 [patch.crates-io] hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" } -incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "5707d6ac096c2b7a345166e08c378e0a5d64e6d8" } -orchard = { git = "https://github.com/zcash/orchard.git", rev = "8449fd133c002a349869c09c529a4d214b20c0c0" } -zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" } -zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" } -zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" } -zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" } -zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "cfb49cfd52e1f5c43a27de1de1156d9504ca9eb9" } +incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "62c33e4480a71170b02b9eb7d4b0160194f414ee" } +orchard = { git = "https://github.com/zcash/orchard.git", rev = "a5f701f3186fb619b40f2dee9939e64e4c9eb7cc" } +zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" } +zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" } +zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" } +zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" } +zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" } +zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" } diff --git a/src/rust/include/rust/orchard/wallet.h b/src/rust/include/rust/orchard/wallet.h index f3b3ff62e..c84bc3e4e 100644 --- a/src/rust/include/rust/orchard/wallet.h +++ b/src/rust/include/rust/orchard/wallet.h @@ -52,8 +52,12 @@ bool orchard_wallet_checkpoint( /** * Returns whether or not the wallet has any checkpointed state to which it can rewind. + * If so, `blockHeightRet` will be modified to contain the last block height at which a + * checkpoint was created. */ -bool orchard_wallet_is_checkpointed(const OrchardWalletPtr* wallet); +bool orchard_wallet_get_last_checkpoint( + const OrchardWalletPtr* wallet, + uint32_t* blockHeightRet); /** * Rewinds to the most recent checkpoint, and marks as unspent any notes previously @@ -287,6 +291,29 @@ OrchardSpendInfoPtr* orchard_wallet_get_spend_info( const unsigned char txid[32], uint32_t action_idx); +/** + * Run the garbage collection operation on the wallet's note commitment + * tree. + */ +bool orchard_wallet_gc_note_commitment_tree(OrchardWalletPtr* wallet); + +/** + * Write the wallet's note commitment tree to the provided stream. + */ +bool orchard_wallet_write_note_commitment_tree( + const OrchardWalletPtr* wallet, + void* stream, + write_callback_t write_cb); + +/** + * Read a note commitment tree from the provided stream, and update the wallet's internal + * note commitment tree state to equal the value that was read. + */ +bool orchard_wallet_load_note_commitment_tree( + OrchardWalletPtr* wallet, + void* stream, + read_callback_t read_cb); + #ifdef __cplusplus } #endif diff --git a/src/rust/src/incremental_merkle_tree.rs b/src/rust/src/incremental_merkle_tree.rs new file mode 100644 index 000000000..ec2e31998 --- /dev/null +++ b/src/rust/src/incremental_merkle_tree.rs @@ -0,0 +1,115 @@ +use byteorder::{ReadBytesExt, WriteBytesExt}; +use std::io::{self, Read, Write}; + +use incrementalmerkletree::{ + bridgetree::{BridgeTree, Checkpoint}, + Hashable, +}; +use zcash_encoding::Vector; +use zcash_primitives::merkle_tree::{ + incremental::{ + read_bridge_v1, read_leu64_usize, read_position, write_bridge_v1, write_position, + write_usize_leu64, SER_V1, + }, + HashSer, +}; + +pub fn write_checkpoint_v1( + mut writer: W, + checkpoint: &Checkpoint, +) -> io::Result<()> { + write_usize_leu64(&mut writer, checkpoint.bridges_len())?; + writer.write_u8(if checkpoint.is_witnessed() { 1 } else { 0 })?; + Vector::write_sized( + &mut writer, + checkpoint.forgotten().iter(), + |mut w, ((pos, leaf_value), idx)| { + write_position(&mut w, *pos)?; + leaf_value.write(&mut w)?; + write_usize_leu64(&mut w, *idx) + }, + )?; + + Ok(()) +} + +pub fn read_checkpoint_v1(mut reader: R) -> io::Result> { + Ok(Checkpoint::from_parts( + read_leu64_usize(&mut reader)?, + reader.read_u8()? == 1, + Vector::read_collected(&mut reader, |mut r| { + Ok(( + (read_position(&mut r)?, H::read(&mut r)?), + read_leu64_usize(&mut r)?, + )) + })?, + )) +} + +pub fn write_tree_v1( + mut writer: W, + tree: &BridgeTree, +) -> io::Result<()> { + Vector::write(&mut writer, tree.bridges(), |w, b| write_bridge_v1(w, b))?; + Vector::write_sized( + &mut writer, + tree.witnessed_indices().iter(), + |mut w, ((pos, a), i)| { + write_position(&mut w, *pos)?; + a.write(&mut w)?; + write_usize_leu64(&mut w, *i) + }, + )?; + Vector::write(&mut writer, tree.checkpoints(), |w, c| { + write_checkpoint_v1(w, c) + })?; + write_usize_leu64(&mut writer, tree.max_checkpoints())?; + + Ok(()) +} + +#[allow(clippy::redundant_closure)] +pub fn read_tree_v1( + mut reader: R, +) -> io::Result> { + BridgeTree::from_parts( + Vector::read(&mut reader, |r| read_bridge_v1(r))?, + Vector::read_collected(&mut reader, |mut r| { + Ok(( + (read_position(&mut r)?, H::read(&mut r)?), + read_leu64_usize(&mut r)?, + )) + })?, + Vector::read(&mut reader, |r| read_checkpoint_v1(r))?, + read_leu64_usize(&mut reader)?, + ) + .map_err(|err| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "Consistency violation found when attempting to deserialize Merkle tree: {:?}", + err + ), + ) + }) +} + +pub fn write_tree( + mut writer: W, + tree: &BridgeTree, +) -> io::Result<()> { + writer.write_u8(SER_V1)?; + write_tree_v1(&mut writer, tree) +} + +pub fn read_tree( + mut reader: R, +) -> io::Result> { + match reader.read_u8()? { + SER_V1 => read_tree_v1(&mut reader), + flag => Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Unrecognized tree serialization version: {:?}", flag), + )), + } +} diff --git a/src/rust/src/rustzcash.rs b/src/rust/src/rustzcash.rs index fc1314b09..c76d574f1 100644 --- a/src/rust/src/rustzcash.rs +++ b/src/rust/src/rustzcash.rs @@ -74,6 +74,7 @@ mod zcashd_orchard; mod address_ffi; mod builder_ffi; mod history_ffi; +mod incremental_merkle_tree; mod incremental_merkle_tree_ffi; mod orchard_ffi; mod orchard_keys_ffi; diff --git a/src/rust/src/wallet.rs b/src/rust/src/wallet.rs index 196c26012..ff07bc946 100644 --- a/src/rust/src/wallet.rs +++ b/src/rust/src/wallet.rs @@ -1,12 +1,14 @@ -use std::convert::TryInto; -use std::ptr; -use std::slice; - +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Position, Tree}; use libc::c_uchar; use std::collections::{BTreeMap, BTreeSet}; +use std::convert::TryInto; +use std::io; +use std::ptr; +use std::slice; use tracing::error; +use zcash_encoding::Optional; use zcash_primitives::{ consensus::BlockHeight, transaction::{components::Amount, TxId}, @@ -20,9 +22,13 @@ use orchard::{ Address, Bundle, Note, }; -use crate::{builder_ffi::OrchardSpendInfo, zcashd_orchard::OrderedAddress}; - -use super::incremental_merkle_tree_ffi::MERKLE_DEPTH; +use crate::{ + builder_ffi::OrchardSpendInfo, + incremental_merkle_tree::{read_tree, write_tree}, + incremental_merkle_tree_ffi::MERKLE_DEPTH, + streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb}, + zcashd_orchard::OrderedAddress, +}; pub const MAX_CHECKPOINTS: usize = 100; @@ -249,8 +255,8 @@ impl Wallet { /// Returns whether or not a checkpoint has been created. If no checkpoint exists, /// the wallet has not yet observed any blocks. - pub fn is_checkpointed(&self) -> bool { - self.last_checkpoint.is_some() + pub fn last_checkpoint(&self) -> Option { + self.last_checkpoint } /// Rewinds the note commitment tree to the given height, removes notes and @@ -713,9 +719,19 @@ pub extern "C" fn orchard_wallet_checkpoint(wallet: *mut Wallet, block_height: u } #[no_mangle] -pub extern "C" fn orchard_wallet_is_checkpointed(wallet: *const Wallet) -> bool { +pub extern "C" fn orchard_wallet_get_last_checkpoint( + wallet: *const Wallet, + block_height_ret: *mut u32, +) -> bool { let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null"); - wallet.is_checkpointed() + let block_height_ret = + unsafe { block_height_ret.as_mut() }.expect("Block height return pointer may not be null"); + if let Some(height) = wallet.last_checkpoint() { + *block_height_ret = height.into(); + true + } else { + false + } } #[no_mangle] @@ -1023,3 +1039,66 @@ pub extern "C" fn orchard_wallet_get_spend_info( ptr::null_mut() } } + +#[no_mangle] +pub extern "C" fn orchard_wallet_gc_note_commitment_tree(wallet: *mut Wallet) { + let wallet = unsafe { wallet.as_mut() }.expect("Wallet pointer may not be null."); + wallet.witness_tree.garbage_collect(); +} + +#[no_mangle] +pub extern "C" fn orchard_wallet_write_note_commitment_tree( + wallet: *const Wallet, + stream: Option, + write_cb: Option, +) -> bool { + let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null."); + let mut writer = CppStreamWriter::from_raw_parts(stream, write_cb.unwrap()); + + let mut write_all = move || -> io::Result<()> { + Optional::write(&mut writer, wallet.last_checkpoint, |w, h| { + w.write_u32::(h.into()) + })?; + write_tree(&mut writer, &wallet.witness_tree) + }; + + match write_all() { + Ok(()) => true, + Err(e) => { + error!("Failure in writing Orchard note commitment tree: {}", e); + false + } + } +} + +#[no_mangle] +pub extern "C" fn orchard_wallet_load_note_commitment_tree( + wallet: *mut Wallet, + stream: Option, + read_cb: Option, +) -> bool { + let wallet = unsafe { wallet.as_mut() }.expect("Wallet pointer may not be null."); + let mut reader = CppStreamReader::from_raw_parts(stream, read_cb.unwrap()); + + let mut read_all = move || -> io::Result<()> { + let last_checkpoint = Optional::read(&mut reader, |r| { + r.read_u32::().map(BlockHeight::from) + })?; + let witness_tree = read_tree(&mut reader)?; + + wallet.last_checkpoint = last_checkpoint; + wallet.witness_tree = witness_tree; + Ok(()) + }; + + match read_all() { + Ok(_) => true, + Err(e) => { + error!( + "Failed to read Orchard note commitment or last checkpoint height: {}", + e + ); + false + } + } +} diff --git a/src/wallet/gtest/test_wallet.cpp b/src/wallet/gtest/test_wallet.cpp index c15cf1974..7387ada8f 100644 --- a/src/wallet/gtest/test_wallet.cpp +++ b/src/wallet/gtest/test_wallet.cpp @@ -33,6 +33,7 @@ public: MOCK_METHOD0(TxnAbort, bool()); MOCK_METHOD1(WriteTx, bool(const CWalletTx& wtx)); + MOCK_METHOD1(WriteOrchardWitnesses, bool(const OrchardWallet& wallet)); MOCK_METHOD1(WriteWitnessCacheSize, bool(int64_t nWitnessCacheSize)); MOCK_METHOD1(WriteBestBlock, bool(const CBlockLocator& loc)); }; @@ -1648,6 +1649,22 @@ TEST(WalletTests, WriteWitnessCache) { EXPECT_CALL(walletdb, WriteTx(wtx)) .WillRepeatedly(Return(true)); + // WriteOrchardWitnesses fails + EXPECT_CALL(walletdb, WriteOrchardWitnesses) + .WillOnce(Return(false)); + EXPECT_CALL(walletdb, TxnAbort()) + .Times(1); + wallet.SetBestChain(walletdb, loc); + + // WriteOrchardWitnesses throws + EXPECT_CALL(walletdb, WriteOrchardWitnesses) + .WillOnce(ThrowLogicError()); + EXPECT_CALL(walletdb, TxnAbort()) + .Times(1); + wallet.SetBestChain(walletdb, loc); + EXPECT_CALL(walletdb, WriteOrchardWitnesses) + .WillRepeatedly(Return(true)); + // WriteWitnessCacheSize fails EXPECT_CALL(walletdb, WriteWitnessCacheSize(0)) .WillOnce(Return(false)); @@ -1765,6 +1782,8 @@ TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) { .Times(1).WillOnce(Return(true)); EXPECT_CALL(walletdb, WriteTx(wtxSaplingTransparent)) .Times(0); + EXPECT_CALL(walletdb, WriteOrchardWitnesses) + .WillOnce(Return(true)); EXPECT_CALL(walletdb, WriteWitnessCacheSize(0)) .WillOnce(Return(true)); EXPECT_CALL(walletdb, WriteBestBlock(loc)) diff --git a/src/wallet/orchard.h b/src/wallet/orchard.h index 752cb3b05..76582f6a4 100644 --- a/src/wallet/orchard.h +++ b/src/wallet/orchard.h @@ -15,6 +15,8 @@ #include "zcash/address/orchard.hpp" class OrchardWallet; +class OrchardWalletNoteCommitmentTreeWriter; +class OrchardWalletNoteCommitmentTreeLoader; class OrchardNoteMetadata { @@ -111,7 +113,8 @@ private: std::unique_ptr inner; friend class ::orchard::UnauthorizedBundle; - + friend class OrchardWalletNoteCommitmentTreeWriter; + friend class OrchardWalletNoteCommitmentTreeLoader; public: OrchardWallet() : inner(orchard_wallet_new(), orchard_wallet_free) {} OrchardWallet(OrchardWallet&& wallet_data) : inner(std::move(wallet_data.inner)) {} @@ -151,8 +154,13 @@ public: /** * Return whether the orchard note commitment tree contains any checkpoints. */ - bool IsCheckpointed() const { - return orchard_wallet_is_checkpointed(inner.get()); + std::optional GetLastCheckpointHeight() const { + uint32_t lastHeight{0}; + if (orchard_wallet_get_last_checkpoint(inner.get(), &lastHeight)) { + return (int) lastHeight; + } else { + return std::nullopt; + } } /** @@ -339,6 +347,46 @@ public: std::vector> GetSpendInfo( const std::vector& noteMetadata) const; + + void GarbageCollect() { + orchard_wallet_gc_note_commitment_tree(inner.get()); + } +}; + +class OrchardWalletNoteCommitmentTreeWriter +{ +private: + const OrchardWallet& wallet; +public: + OrchardWalletNoteCommitmentTreeWriter(const OrchardWallet& wallet): wallet(wallet) {} + + template + void Serialize(Stream& s) const { + RustStream rs(s); + if (!orchard_wallet_write_note_commitment_tree( + wallet.inner.get(), + &rs, RustStream::write_callback)) { + throw std::ios_base::failure("Failed to serialize Orchard note commitment tree."); + } + } +}; + +class OrchardWalletNoteCommitmentTreeLoader +{ +private: + OrchardWallet& wallet; +public: + OrchardWalletNoteCommitmentTreeLoader(OrchardWallet& wallet): wallet(wallet) {} + + template + void Unserialize(Stream& s) { + RustStream rs(s); + if (!orchard_wallet_load_note_commitment_tree( + wallet.inner.get(), + &rs, RustStream::read_callback)) { + throw std::ios_base::failure("Failed to load Orchard note commitment tree."); + } + } }; #endif // ZCASH_ORCHARD_WALLET_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index dfad417b5..89416311b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -298,6 +298,12 @@ bool CWallet::LoadOrchardRawAddress( return orchardWallet.AddRawAddress(addr, ivk); } +// Returns a loader that can be used to read an Orchard note commitment +// tree from a stream into the Orchard wallet. +OrchardWalletNoteCommitmentTreeLoader CWallet::GetOrchardNoteCommitmentTreeLoader() { + return OrchardWalletNoteCommitmentTreeLoader(orchardWallet); +} + // Add spending key to keystore and persist to disk bool CWallet::AddSproutZKey(const libzcash::SproutSpendingKey &key) { @@ -2356,10 +2362,8 @@ void CWallet::IncrementNoteWitnesses( // If we're at or beyond NU5 activation, update the Orchard note commitment tree. if (performOrchardWalletUpdates && consensus.NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_NU5)) { - orchardWallet.AppendNoteCommitments(pindex->nHeight, *pblock); - // TODO ORCHARD: Enable the following assertions. - //assert(orchardWallet.AppendNoteCommitments(pindex->nHeight, *pblock)); - //assert(pindex->hashFinalOrchardRoot == orchardWallet.GetLatestAnchor()); + assert(orchardWallet.AppendNoteCommitments(pindex->nHeight, *pblock)); + assert(pindex->hashFinalOrchardRoot == orchardWallet.GetLatestAnchor()); } // Update witness heights @@ -2429,17 +2433,11 @@ void CWallet::DecrementNoteWitnesses(const Consensus::Params& consensus, const C // pindex->nHeight is the height of the block being removed, so we rewind // to the previous block height uint32_t blocksRewound{0}; - if (!orchardWallet.Rewind(pindex->nHeight - 1, blocksRewound) || blocksRewound != 1) { - LogPrintf( - "DecrementNoteWitnesses: Orchard wallet rewind unsuccessful at height %d; rewound %d", - pindex->nHeight - 1, blocksRewound); + assert(orchardWallet.Rewind(pindex->nHeight - 1, blocksRewound)); + assert(blocksRewound == 1); + if (consensus.NetworkUpgradeActive(pindex->nHeight - 1, Consensus::UPGRADE_NU5)) { + assert(pindex->pprev->hashFinalOrchardRoot == orchardWallet.GetLatestAnchor()); } - // TODO ORCHARD: Enable the following assertions. - //assert(orchardWallet.Rewind(pindex->nHeight - 1, blocksRewound)); - //assert(blocksRewound == 1); - //if (consensus.NetworkUpgradeActive(pindex->nHeight - 1, Consensus::UPGRADE_NU5)) { - // assert(pindex->pprev->hashFinalOrchardRoot == orchardWallet.GetLatestAnchor()); - //} } // For performance reasons, we write out the witness cache in @@ -2924,8 +2922,7 @@ bool CWallet::AddToWalletIfInvolvingMe( const CTransaction& tx, const CBlock* pblock, const int nHeight, - bool fUpdate - ) + bool fUpdate) { { // extra scope left in place for backport whitespace compatibility AssertLockHeld(cs_wallet); @@ -4024,7 +4021,7 @@ int CWallet::ScanForWalletTransactions( // the witness data that is being removed in the rewind here. auto nu5_height = chainParams.GetConsensus().GetActivationHeight(Consensus::UPGRADE_NU5); bool performOrchardWalletUpdates{false}; - if (orchardWallet.IsCheckpointed()) { + if (orchardWallet.GetLastCheckpointHeight().has_value()) { // We have a checkpoint, so attempt to rewind the Orchard wallet at most as // far as the NU5 activation block. // If there's no activation height, we shouldn't have a checkpoint already, @@ -4033,11 +4030,14 @@ int CWallet::ScanForWalletTransactions( // Only attempt to perform scans for Orchard during wallet initialization, // since we do not support Orchard key import. if (isInitScan) { - int rewindHeight = std::max(nu5_height.value(), pindex->nHeight); + int rewindHeight = std::max(nu5_height.value(), pindex->nHeight - 1); uint32_t blocksRewound{0}; + LogPrintf( + "CWallet::ScanForWalletTransactions(): Rewinding Orchard wallet to height %d; current is %d", + rewindHeight, + orchardWallet.GetLastCheckpointHeight().value()); if (orchardWallet.Rewind(rewindHeight, blocksRewound)) { // rewind was successful or a no-op, so perform Orchard wallet updates - LogPrintf("Rewound Orchard wallet by %d blocks.", blocksRewound); performOrchardWalletUpdates = true; } else { // Orchard witnesses will not be able to be correctly updated, because we @@ -4045,9 +4045,7 @@ int CWallet::ScanForWalletTransactions( // can't get back to a valid wallet state without resetting the wallet all // the way back to NU5 activation. - LogPrintf("Orchard wallet is out of sync: %d blockd rewound.", blocksRewound); - // TODO ORCHARD: enable after wallet persistence - // throw std::runtime_error("CWallet::ScanForWalletTransactions(): Orchard wallet is out of sync. Please restart your node with -rescan."); + throw std::runtime_error("CWallet::ScanForWalletTransactions(): Orchard wallet is out of sync. Please restart your node with -rescan."); } } } else if (isInitScan && pindex->nHeight < nu5_height) { @@ -6118,7 +6116,7 @@ bool CWallet::InitLoadWallet(const CChainParams& params, bool clearWitnessCaches uiInterface.InitMessage(_("Rescanning...")); LogPrintf( - "Rescanning last %i blocks (from block %i)...\n", + "CWallet::InitLoadWallet(): Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); nStart = GetTimeMillis(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index efc44635c..539e53f97 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1064,6 +1064,13 @@ protected: } } } + // Add persistence of Orchard incremental witness tree + orchardWallet.GarbageCollect(); + if (!walletdb.WriteOrchardWitnesses(orchardWallet)) { + LogPrintf("SetBestChain(): Failed to write Orchard witnesses, aborting atomic write\n"); + walletdb.TxnAbort(); + return; + } if (!walletdb.WriteWitnessCacheSize(nWitnessCacheSize)) { LogPrintf("SetBestChain(): Failed to write nWitnessCacheSize, aborting atomic write\n"); walletdb.TxnAbort(); @@ -1485,7 +1492,7 @@ public: const std::vector &vchCryptedSecret); // - // Orchard Keys + // Orchard Support // bool AddOrchardZKey(const libzcash::OrchardSpendingKey &sk); @@ -1506,6 +1513,12 @@ public: const libzcash::OrchardRawAddress &addr, const libzcash::OrchardIncomingViewingKey &ivk); + /** + * Returns a loader that can be used to read an Orchard note commitment + * tree from a stream into the Orchard wallet. + */ + OrchardWalletNoteCommitmentTreeLoader GetOrchardNoteCommitmentTreeLoader(); + // // Unified keys, addresses, and accounts // diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index d1b0d7c8e..f885aa4ae 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -218,6 +218,17 @@ bool CWalletDB::EraseSaplingExtendedFullViewingKey( return Erase(std::make_pair(std::string("sapextfvk"), extfvk)); } +// +// Orchard wallet persistence +// + +bool CWalletDB::WriteOrchardWitnesses(const OrchardWallet& wallet) { + nWalletDBUpdateCounter++; + return Write( + std::string("orchard_note_commitment_tree"), + OrchardWalletNoteCommitmentTreeWriter(wallet)); +} + // // Unified address & key storage // @@ -882,6 +893,11 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } } + else if (strType == "orchard_note_commitment_tree") + { + auto loader = pwallet->GetOrchardNoteCommitmentTreeLoader(); + ssValue >> loader; + } } catch (...) { return false; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index e376abc33..d26d0f3c7 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -466,6 +466,9 @@ public: bool WriteSaplingExtendedFullViewingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk); bool EraseSaplingExtendedFullViewingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk); + /// Orchard support. + bool WriteOrchardWitnesses(const OrchardWallet& wallet); + /// Unified key support. bool WriteUnifiedAccountMetadata(const ZcashdUnifiedAccountMetadata& keymeta);