Merge pull request #5656 from nuttycom/feature/wallet_orchard-note_commitment_tree_ser
Add Orchard note commitment tree serialization & restore.
This commit is contained in:
commit
c3c5236cee
|
@ -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"]
|
||||
|
|
|
@ -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",
|
||||
|
|
16
Cargo.toml
16
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" }
|
||||
|
|
|
@ -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
|
||||
|
@ -289,6 +293,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.
|
||||
*/
|
||||
void 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
|
||||
|
|
|
@ -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),
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -247,10 +253,10 @@ impl Wallet {
|
|||
true
|
||||
}
|
||||
|
||||
/// 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()
|
||||
/// Returns the last checkpoint if any. If no checkpoint exists, the wallet has not
|
||||
/// yet observed any blocks.
|
||||
pub fn last_checkpoint(&self) -> Option<BlockHeight> {
|
||||
self.last_checkpoint
|
||||
}
|
||||
|
||||
/// Rewinds the note commitment tree to the given height, removes notes and
|
||||
|
@ -333,9 +339,9 @@ impl Wallet {
|
|||
}
|
||||
|
||||
/// Add note data for those notes that are decryptable with one of this wallet's
|
||||
/// incoming viewing keys to the wallet, and return a map from each decrypted
|
||||
/// action's index to the incoming viewing key that successfully decrypted that
|
||||
/// action.
|
||||
/// incoming viewing keys to the wallet, and return a a data structure that describes
|
||||
/// the actions that are involved with this wallet, either spending notes belonging
|
||||
/// to this wallet or creating new notes owned by this wallet.
|
||||
pub fn add_notes_from_bundle(
|
||||
&mut self,
|
||||
txid: &TxId,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -2367,10 +2373,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
|
||||
|
@ -2440,17 +2444,12 @@ 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(pindex->nHeight >= 1);
|
||||
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
|
||||
|
@ -2935,8 +2934,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);
|
||||
|
@ -4035,7 +4033,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,
|
||||
|
@ -4044,11 +4042,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
|
||||
|
@ -4056,9 +4057,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) {
|
||||
|
@ -6129,7 +6128,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();
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue