Serialize the Orchard note commitment tree to the wallet.

This adds serialization of Orchard note commitment trees
to `SetBestChain` and deserialization and loading of those
trees to wallet load.
This commit is contained in:
Kris Nuttycombe 2022-02-23 12:08:08 -07:00
parent 30dd633de6
commit 4eda33f184
13 changed files with 381 additions and 59 deletions

View File

@ -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"]

21
Cargo.lock generated
View File

@ -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",

View File

@ -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" }

View File

@ -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

View File

@ -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<H: HashSer + Ord, W: Write>(
mut writer: W,
checkpoint: &Checkpoint<H>,
) -> 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<H: HashSer + Ord, R: Read>(mut reader: R) -> io::Result<Checkpoint<H>> {
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<H: Hashable + HashSer + Ord, W: Write>(
mut writer: W,
tree: &BridgeTree<H, 32>,
) -> 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<H: Hashable + HashSer + Ord + Clone, R: Read>(
mut reader: R,
) -> io::Result<BridgeTree<H, 32>> {
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<H: Hashable + HashSer + Ord, W: Write>(
mut writer: W,
tree: &BridgeTree<H, 32>,
) -> io::Result<()> {
writer.write_u8(SER_V1)?;
write_tree_v1(&mut writer, tree)
}
pub fn read_tree<H: Hashable + HashSer + Ord + Clone, R: Read>(
mut reader: R,
) -> io::Result<BridgeTree<H, 32>> {
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),
)),
}
}

View File

@ -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;

View File

@ -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<BlockHeight> {
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<StreamObj>,
write_cb: Option<WriteCb>,
) -> 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::<LittleEndian>(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<StreamObj>,
read_cb: Option<ReadCb>,
) -> 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::<LittleEndian>().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
}
}
}

View File

@ -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))

View File

@ -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<OrchardWalletPtr, decltype(&orchard_wallet_free)> 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<int> 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<std::pair<libzcash::OrchardSpendingKey, orchard::SpendInfo>> GetSpendInfo(
const std::vector<OrchardNoteMetadata>& 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<typename Stream>
void Serialize(Stream& s) const {
RustStream rs(s);
if (!orchard_wallet_write_note_commitment_tree(
wallet.inner.get(),
&rs, RustStream<Stream>::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<typename Stream>
void Unserialize(Stream& s) {
RustStream rs(s);
if (!orchard_wallet_load_note_commitment_tree(
wallet.inner.get(),
&rs, RustStream<Stream>::read_callback)) {
throw std::ios_base::failure("Failed to load Orchard note commitment tree.");
}
}
};
#endif // ZCASH_ORCHARD_WALLET_H

View File

@ -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();

View File

@ -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<unsigned char> &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
//

View File

@ -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;

View File

@ -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);