Orchard warp sync
This commit is contained in:
parent
cbe4737439
commit
088a4d1ef5
|
@ -73,6 +73,11 @@ chrono = "0.4.19"
|
|||
lazycell = "1.3.0"
|
||||
reqwest = { version = "0.11.4", features = ["json", "rustls-tls"], default-features = false }
|
||||
|
||||
# Halo
|
||||
orchard = "0.3.0"
|
||||
halo2_proofs = "0.2"
|
||||
halo2_gadgets = "0.2"
|
||||
|
||||
bech32 = "0.8.1"
|
||||
rand_chacha = "0.3.1"
|
||||
blake2b_simd = "1.0.0"
|
||||
|
|
|
@ -190,7 +190,6 @@ pub async fn download_chain(
|
|||
ph.copy_from_slice(&block.hash);
|
||||
prev_hash = Some(ph);
|
||||
for tx in block.vtx.iter_mut() {
|
||||
tx.actions.clear(); // don't need Orchard actions
|
||||
let mut skipped = false;
|
||||
if tx.outputs.len() > max_cost as usize {
|
||||
for co in tx.outputs.iter_mut() {
|
||||
|
|
58
src/db.rs
58
src/db.rs
|
@ -92,6 +92,11 @@ pub struct AccountBackup {
|
|||
pub t_addr: Option<String>,
|
||||
}
|
||||
|
||||
pub struct AccountSeed {
|
||||
pub id_account: u32,
|
||||
pub seed: String,
|
||||
}
|
||||
|
||||
pub fn wrap_query_no_rows(name: &'static str) -> impl Fn(rusqlite::Error) -> anyhow::Error {
|
||||
move |err: rusqlite::Error| match err {
|
||||
QueryReturnedNoRows => anyhow::anyhow!("Query {} returned no rows", name),
|
||||
|
@ -232,6 +237,24 @@ impl DbAdapter {
|
|||
Ok(fvks)
|
||||
}
|
||||
|
||||
pub fn get_seeds(&self) -> anyhow::Result<Vec<AccountSeed>> {
|
||||
let mut statement = self
|
||||
.connection
|
||||
.prepare("SELECT id_account, seed FROM accounts WHERE seed IS NOT NULL")?;
|
||||
let rows = statement.query_map([], |row| {
|
||||
let id_account: u32 = row.get(0)?;
|
||||
let seed: String = row.get(1)?;
|
||||
Ok(AccountSeed {
|
||||
id_account,
|
||||
seed})
|
||||
})?;
|
||||
let mut accounts = vec![];
|
||||
for row in rows {
|
||||
accounts.push(row?);
|
||||
}
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
pub fn trim_to_height(&mut self, height: u32) -> anyhow::Result<u32> {
|
||||
// snap height to an existing checkpoint
|
||||
let height = self.connection.query_row(
|
||||
|
@ -426,16 +449,15 @@ impl DbAdapter {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn store_tree(height: u32, hash: &[u8], tree: &CTree, db_tx: &Connection, shielded_pool: &str) -> anyhow::Result<()> {
|
||||
let mut bb: Vec<u8> = vec![];
|
||||
tree.write(&mut bb)?;
|
||||
db_tx.execute(&format!("INSERT INTO blocks(height, hash, {pool}_tree, timestamp) VALUES (?1,?2,?3,0) ON CONFLICT DO UPDATE
|
||||
SET {pool}_tree = excluded.{pool}_tree", pool = shielded_pool), params![height, hash, &bb])?;
|
||||
pub fn store_block_timestamp(&self, height: u32, hash: &[u8], timestamp: u32) -> anyhow::Result<()> {
|
||||
self.connection.execute("INSERT INTO blocks(height, hash, timestamp) VALUES (?1,?2,?3)", params![height, hash, timestamp])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn store_block_timestamp(&self, height: u32, timestamp: u32) -> anyhow::Result<()> {
|
||||
self.connection.execute("UPDATE blocks SET timestamp = ?1 WHERE height = ?2", params![timestamp, height])?;
|
||||
pub fn store_tree(height: u32, tree: &CTree, db_tx: &Connection, shielded_pool: &str) -> anyhow::Result<()> {
|
||||
let mut bb: Vec<u8> = vec![];
|
||||
tree.write(&mut bb)?;
|
||||
db_tx.execute(&format!("INSERT INTO {}_tree(height, tree) VALUES (?1,?2)", shielded_pool), params![height, &bb])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -519,15 +541,21 @@ impl DbAdapter {
|
|||
}
|
||||
|
||||
pub fn get_tree_by_name(&self, shielded_pool: &str) -> anyhow::Result<TreeCheckpoint> {
|
||||
let res = self.connection.query_row(
|
||||
&format!("SELECT height, {}_tree FROM blocks WHERE height = (SELECT MAX(height) FROM blocks)", shielded_pool),
|
||||
let height = self.connection.query_row(
|
||||
"SELECT MAX(height) FROM blocks",
|
||||
[], |row| {
|
||||
let height: u32 = row.get(0)?;
|
||||
let tree: Vec<u8> = row.get(1)?;
|
||||
Ok((height, tree))
|
||||
}).optional()?;
|
||||
Ok(match res {
|
||||
Some((height, tree)) => {
|
||||
let height: Option<u32> = row.get(0)?;
|
||||
Ok(height)
|
||||
})?;
|
||||
Ok(match height {
|
||||
Some(height) => {
|
||||
let tree = self.connection.query_row(
|
||||
&format!("SELECT tree FROM {}_tree WHERE height = ?1", shielded_pool),
|
||||
[height], |row| {
|
||||
let tree: Vec<u8> = row.get(0)?;
|
||||
Ok(tree)
|
||||
})?;
|
||||
|
||||
let tree = sync::CTree::read(&*tree)?;
|
||||
let mut statement = self.connection.prepare(
|
||||
&format!("SELECT id_note, witness FROM {}_witnesses w, received_notes n WHERE w.height = ?1 AND w.note = n.id_note AND (n.spent IS NULL OR n.spent = 0)", shielded_pool))?;
|
||||
|
|
|
@ -178,7 +178,14 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
if version < 5 {
|
||||
connection.execute("ALTER TABLE blocks ADD orchard_tree BLOB", [])?;
|
||||
connection.execute("CREATE TABLE sapling_tree(
|
||||
height INTEGER PRIMARY KEY,
|
||||
tree BLOB NOT NULL)", [])?;
|
||||
connection.execute("CREATE TABLE orchard_tree(
|
||||
height INTEGER PRIMARY KEY,
|
||||
tree BLOB NOT NULL)", [])?;
|
||||
connection.execute("INSERT INTO sapling_tree SELECT height, sapling_tree FROM blocks", [])?;
|
||||
connection.execute("ALTER TABLE blocks DROP sapling_tree", [])?;
|
||||
connection.execute("ALTER TABLE received_notes ADD rho BLOB", [])?;
|
||||
connection.execute(
|
||||
"CREATE TABLE IF NOT EXISTS orchard_witnesses (
|
||||
|
|
|
@ -82,8 +82,9 @@ mod contact;
|
|||
mod db;
|
||||
mod fountain;
|
||||
mod hash;
|
||||
pub(crate) mod sync;
|
||||
pub mod sapling;
|
||||
mod sync;
|
||||
mod sapling;
|
||||
mod orchard;
|
||||
mod key;
|
||||
mod key2;
|
||||
mod mempool;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
mod hash;
|
||||
mod note;
|
||||
|
||||
pub use note::{OrchardDecrypter, OrchardViewKey, DecryptedOrchardNote};
|
||||
pub use hash::OrchardHasher;
|
|
@ -0,0 +1,118 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use group::cofactor::CofactorCurveAffine;
|
||||
use halo2_gadgets::sinsemilla::primitives::SINSEMILLA_S;
|
||||
use halo2_proofs::arithmetic::{CurveAffine, CurveExt};
|
||||
use halo2_proofs::pasta::EpAffine;
|
||||
use halo2_proofs::pasta::group::ff::PrimeField;
|
||||
use halo2_proofs::pasta::group::Curve;
|
||||
use halo2_proofs::pasta::pallas::{self, Affine, Point};
|
||||
use lazy_static::lazy_static;
|
||||
use crate::Hash;
|
||||
use crate::sync::{Hasher, Node};
|
||||
|
||||
pub const Q_PERSONALIZATION: &str = "z.cash:SinsemillaQ";
|
||||
pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ORCHARD_ROOTS: Vec<Hash> = {
|
||||
let h = OrchardHasher::new();
|
||||
h.empty_roots(32)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OrchardHasher {
|
||||
Q: Point,
|
||||
}
|
||||
|
||||
impl OrchardHasher {
|
||||
pub fn new() -> Self {
|
||||
let Q: Point =
|
||||
Point::hash_to_curve(Q_PERSONALIZATION)(MERKLE_CRH_PERSONALIZATION.as_bytes());
|
||||
OrchardHasher { Q }
|
||||
}
|
||||
|
||||
fn node_combine_inner(&self, depth: u8, left: &Node, right: &Node) -> Point {
|
||||
let mut acc = self.Q;
|
||||
let (S_x, S_y) = SINSEMILLA_S[depth as usize];
|
||||
let S_chunk = Affine::from_xy(S_x, S_y).unwrap();
|
||||
acc = (acc + S_chunk) + acc; // TODO Bail if + gives point at infinity?
|
||||
|
||||
// Shift right by 1 bit and overwrite the 256th bit of left
|
||||
let mut left = *left;
|
||||
let mut right = *right;
|
||||
left[31] |= (right[0] & 1) << 7; // move the first bit of right into 256th of left
|
||||
for i in 0..32 {
|
||||
// move by 1 bit to fill the missing 256th bit of left
|
||||
let carry = if i < 31 { (right[i + 1] & 1) << 7 } else { 0 };
|
||||
right[i] = right[i] >> 1 | carry;
|
||||
}
|
||||
|
||||
// we have 255*2/10 = 51 chunks
|
||||
let mut bit_offset = 0;
|
||||
let mut byte_offset = 0;
|
||||
for _ in 0..51 {
|
||||
let mut v = if byte_offset < 31 {
|
||||
left[byte_offset] as u16 | (left[byte_offset + 1] as u16) << 8
|
||||
} else if byte_offset == 31 {
|
||||
left[31] as u16 | (right[0] as u16) << 8
|
||||
} else {
|
||||
right[byte_offset - 32] as u16 | (right[byte_offset - 31] as u16) << 8
|
||||
};
|
||||
v = v >> bit_offset & 0x03FF; // keep 10 bits
|
||||
let (S_x, S_y) = SINSEMILLA_S[v as usize];
|
||||
let S_chunk = Affine::from_xy(S_x, S_y).unwrap();
|
||||
acc = (acc + S_chunk) + acc;
|
||||
bit_offset += 10;
|
||||
if bit_offset >= 8 {
|
||||
byte_offset += bit_offset / 8;
|
||||
bit_offset %= 8;
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
pub fn empty_roots(&self, height: usize) -> Vec<Hash> {
|
||||
let mut roots = vec![];
|
||||
let mut cur = pallas::Base::from(2).to_repr();
|
||||
roots.push(cur);
|
||||
for depth in 0..height {
|
||||
cur = self.node_combine(depth as u8, &cur, &cur);
|
||||
roots.push(cur);
|
||||
}
|
||||
roots
|
||||
}
|
||||
}
|
||||
|
||||
impl Hasher for OrchardHasher {
|
||||
type Extended = Point;
|
||||
|
||||
fn uncommited_node() -> Node {
|
||||
pallas::Base::from(2).to_repr()
|
||||
}
|
||||
|
||||
fn node_combine(&self, depth: u8, left: &Node, right: &Node) -> Node {
|
||||
let acc = self.node_combine_inner(depth, left, right);
|
||||
let p = acc
|
||||
.to_affine()
|
||||
.coordinates()
|
||||
.map(|c| *c.x())
|
||||
.unwrap_or_else(pallas::Base::zero);
|
||||
p.to_repr()
|
||||
}
|
||||
|
||||
fn node_combine_extended(&self, depth: u8, left: &Node, right: &Node) -> Self::Extended {
|
||||
self.node_combine_inner(depth, left, right)
|
||||
}
|
||||
|
||||
fn normalize(&self, extended: &[Self::Extended]) -> Vec<Node> {
|
||||
let mut hash_affine = vec![EpAffine::identity(); extended.len()];
|
||||
Point::batch_normalize(extended, &mut hash_affine);
|
||||
hash_affine
|
||||
.iter()
|
||||
.map(|p|
|
||||
p.coordinates().map(|c| *c.x()).unwrap_or_else(pallas::Base::zero).to_repr())
|
||||
.collect()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
use orchard::note_encryption::OrchardDomain;
|
||||
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||
use crate::chain::Nf;
|
||||
use crate::CompactTx;
|
||||
use crate::db::ReceivedNote;
|
||||
use crate::sync::{CompactOutputBytes, DecryptedNote, Node, OutputPosition, TrialDecrypter, ViewKey};
|
||||
use zcash_note_encryption;
|
||||
use zcash_primitives::sapling::Nullifier;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OrchardViewKey {
|
||||
pub account: u32,
|
||||
pub fvk: orchard::keys::FullViewingKey,
|
||||
}
|
||||
|
||||
impl ViewKey<OrchardDomain> for OrchardViewKey {
|
||||
fn account(&self) -> u32 {
|
||||
self.account
|
||||
}
|
||||
|
||||
fn ivk(&self) -> orchard::keys::IncomingViewingKey {
|
||||
self.fvk.to_ivk(orchard::keys::Scope::External)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DecryptedOrchardNote {
|
||||
pub vk: OrchardViewKey,
|
||||
pub note: orchard::Note,
|
||||
pub pa: orchard::Address,
|
||||
pub output_position: OutputPosition,
|
||||
pub cmx: Node,
|
||||
}
|
||||
|
||||
impl DecryptedNote<OrchardDomain, OrchardViewKey> for DecryptedOrchardNote {
|
||||
fn from_parts(vk: OrchardViewKey, note: orchard::Note, pa: orchard::Address, output_position: OutputPosition, cmx: Node) -> Self {
|
||||
DecryptedOrchardNote {
|
||||
vk,
|
||||
note,
|
||||
pa,
|
||||
output_position,
|
||||
cmx
|
||||
}
|
||||
}
|
||||
|
||||
fn position(&self, block_offset: usize) -> usize {
|
||||
block_offset + self.output_position.position_in_block
|
||||
}
|
||||
|
||||
fn cmx(&self) -> Node {
|
||||
self.cmx
|
||||
}
|
||||
|
||||
fn to_received_note(&self, position: u64) -> ReceivedNote {
|
||||
ReceivedNote {
|
||||
account: self.vk.account,
|
||||
height: self.output_position.height,
|
||||
output_index: self.output_position.output_index as u32,
|
||||
diversifier: self.pa.diversifier().as_array().to_vec(),
|
||||
value: self.note.value().inner(),
|
||||
rcm: self.note.rseed().as_bytes().to_vec(),
|
||||
nf: self.note.nullifier(&self.vk.fvk).to_bytes().to_vec(),
|
||||
rho: Some(self.note.rho().to_bytes().to_vec()),
|
||||
spent: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OrchardDecrypter<N> {
|
||||
pub network: N,
|
||||
}
|
||||
|
||||
impl <N> OrchardDecrypter<N> {
|
||||
pub fn new(network: N) -> Self {
|
||||
OrchardDecrypter {
|
||||
network,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl <N: Parameters> TrialDecrypter<N, OrchardDomain, OrchardViewKey, DecryptedOrchardNote> for OrchardDecrypter<N> {
|
||||
fn domain(&self, _height: BlockHeight, cob: &CompactOutputBytes) -> OrchardDomain {
|
||||
OrchardDomain::for_nullifier(orchard::note::Nullifier::from_bytes(&cob.nullifier).unwrap())
|
||||
}
|
||||
|
||||
fn spends(&self, vtx: &CompactTx) -> Vec<Nf> {
|
||||
vtx.actions.iter().map(|co| {
|
||||
let nf: [u8; 32] = co.nullifier.clone().try_into().unwrap();
|
||||
Nf(nf)
|
||||
}).collect()
|
||||
}
|
||||
|
||||
fn outputs(&self, vtx: &CompactTx) -> Vec<CompactOutputBytes> {
|
||||
vtx.actions.iter().map(|co| co.into()).collect()
|
||||
}
|
||||
}
|
|
@ -125,7 +125,7 @@ impl Hasher for SaplingHasher {
|
|||
}
|
||||
|
||||
fn normalize(&self, extended: &[Self::Extended]) -> Vec<Node> {
|
||||
let mut hash_affine: Vec<AffinePoint> = vec![AffinePoint::identity(); extended.len()];
|
||||
let mut hash_affine = vec![AffinePoint::identity(); extended.len()];
|
||||
ExtendedPoint::batch_normalize(extended, &mut hash_affine);
|
||||
hash_affine
|
||||
.iter()
|
||||
|
|
|
@ -82,7 +82,7 @@ impl <N> SaplingDecrypter<N> {
|
|||
}
|
||||
|
||||
impl <N: Parameters> TrialDecrypter<N, SaplingDomain<N>, SaplingViewKey, DecryptedSaplingNote> for SaplingDecrypter<N> {
|
||||
fn domain(&self, height: BlockHeight) -> SaplingDomain<N> {
|
||||
fn domain(&self, height: BlockHeight, _cob: &CompactOutputBytes) -> SaplingDomain<N> {
|
||||
SaplingDomain::<N>::for_height(self.network.clone(), height)
|
||||
}
|
||||
|
||||
|
@ -97,4 +97,3 @@ impl <N: Parameters> TrialDecrypter<N, SaplingDomain<N>, SaplingViewKey, Decrypt
|
|||
vtx.outputs.iter().map(|co| co.into()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
73
src/scan.rs
73
src/scan.rs
|
@ -4,7 +4,7 @@ use serde::Serialize;
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use crate::transaction::retrieve_tx_info;
|
||||
use crate::{connect_lightwalletd, CompactBlock, CompactSaplingOutput, CompactTx, DbAdapterBuilder, chain};
|
||||
use crate::{connect_lightwalletd, CompactBlock, CompactSaplingOutput, CompactTx, DbAdapterBuilder, chain, AccountRec};
|
||||
use crate::chain::{DecryptNode, download_chain};
|
||||
use ff::PrimeField;
|
||||
|
||||
|
@ -14,15 +14,19 @@ use std::collections::HashMap;
|
|||
use std::panic;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use bip39::{Language, Mnemonic};
|
||||
use orchard::keys::{FullViewingKey, SpendingKey};
|
||||
use orchard::note_encryption::OrchardDomain;
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::Mutex;
|
||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||
use zcash_params::coin::{get_coin_chain, CoinType};
|
||||
use zcash_primitives::consensus::{Network, Parameters};
|
||||
use zcash_primitives::consensus::{Network, NetworkUpgrade, Parameters};
|
||||
|
||||
use zcash_primitives::sapling::{Node, Note};
|
||||
use zcash_primitives::sapling::note_encryption::SaplingDomain;
|
||||
use crate::orchard::{DecryptedOrchardNote, OrchardDecrypter, OrchardHasher, OrchardViewKey};
|
||||
use crate::sapling::{DecryptedSaplingNote, SaplingDecrypter, SaplingHasher, SaplingViewKey};
|
||||
use crate::sync::{CTree, Synchronizer, WarpProcessor};
|
||||
|
||||
|
@ -61,6 +65,9 @@ pub struct TxIdHeight {
|
|||
type SaplingSynchronizer = Synchronizer<Network, SaplingDomain<Network>, SaplingViewKey, DecryptedSaplingNote,
|
||||
SaplingDecrypter<Network>, SaplingHasher>;
|
||||
|
||||
type OrchardSynchronizer = Synchronizer<Network, OrchardDomain, OrchardViewKey, DecryptedOrchardNote,
|
||||
OrchardDecrypter<Network>, OrchardHasher>;
|
||||
|
||||
pub async fn sync_async<'a>(
|
||||
coin_type: CoinType,
|
||||
_chunk_size: u32,
|
||||
|
@ -80,7 +87,7 @@ pub async fn sync_async<'a>(
|
|||
};
|
||||
|
||||
let mut client = connect_lightwalletd(&ld_url).await?;
|
||||
let (start_height, prev_hash, sapling_vks) = {
|
||||
let (start_height, prev_hash, sapling_vks, orchard_vks) = {
|
||||
let db = DbAdapter::new(coin_type, &db_path)?;
|
||||
let height = db.get_db_height()?;
|
||||
let hash = db.get_db_hash(height)?;
|
||||
|
@ -92,7 +99,22 @@ pub async fn sync_async<'a>(
|
|||
ivk: ak.ivk.clone()
|
||||
}
|
||||
}).collect();
|
||||
(height, hash, sapling_vks)
|
||||
let orchard_vks: Vec<_> = db.get_seeds()?.iter().map(|a| {
|
||||
let mnemonic = Mnemonic::from_phrase(&a.seed, Language::English).unwrap();
|
||||
let sk = SpendingKey::from_zip32_seed(
|
||||
mnemonic.entropy(),
|
||||
network.coin_type(),
|
||||
a.id_account,
|
||||
).unwrap();
|
||||
let fvk = FullViewingKey::from(&sk);
|
||||
let vk =
|
||||
OrchardViewKey {
|
||||
account: a.id_account,
|
||||
fvk,
|
||||
};
|
||||
vk
|
||||
}).collect();
|
||||
(height, hash, sapling_vks, orchard_vks)
|
||||
};
|
||||
let end_height = get_latest_height(&mut client).await?;
|
||||
let end_height = (end_height - target_height_offset).max(start_height);
|
||||
|
@ -111,25 +133,42 @@ pub async fn sync_async<'a>(
|
|||
let first_block = blocks.0.first().unwrap(); // cannot be empty because blocks are not
|
||||
log::info!("Height: {}", first_block.height);
|
||||
let last_block = blocks.0.last().unwrap();
|
||||
let last_hash: [u8; 32] = last_block.hash.clone().try_into().unwrap();
|
||||
let last_height = last_block.height as u32;
|
||||
let last_timestamp = last_block.time;
|
||||
|
||||
let decrypter = SaplingDecrypter::new(network);
|
||||
let warper = WarpProcessor::new(SaplingHasher::default());
|
||||
let mut sapling_synchronizer = SaplingSynchronizer::new(
|
||||
decrypter,
|
||||
warper,
|
||||
sapling_vks.clone(),
|
||||
db_builder.clone(),
|
||||
"sapling".to_string(),
|
||||
);
|
||||
sapling_synchronizer.initialize()?;
|
||||
sapling_synchronizer.process(blocks.0)?;
|
||||
// Sapling
|
||||
{
|
||||
let decrypter = SaplingDecrypter::new(network);
|
||||
let warper = WarpProcessor::new(SaplingHasher::default());
|
||||
let mut synchronizer = SaplingSynchronizer::new(
|
||||
decrypter,
|
||||
warper,
|
||||
sapling_vks.clone(),
|
||||
db_builder.clone(),
|
||||
"sapling".to_string(),
|
||||
);
|
||||
synchronizer.initialize()?;
|
||||
synchronizer.process(&blocks.0)?;
|
||||
}
|
||||
|
||||
// TODO - Orchard
|
||||
// Orchard
|
||||
{
|
||||
let decrypter = OrchardDecrypter::new(network);
|
||||
let warper = WarpProcessor::new(OrchardHasher::new());
|
||||
let mut synchronizer = OrchardSynchronizer::new(
|
||||
decrypter,
|
||||
warper,
|
||||
orchard_vks.clone(),
|
||||
db_builder.clone(),
|
||||
"orchard".to_string(),
|
||||
);
|
||||
synchronizer.initialize()?;
|
||||
synchronizer.process(&blocks.0)?;
|
||||
}
|
||||
|
||||
let db = db_builder.build()?;
|
||||
db.store_block_timestamp(last_height, last_timestamp)?;
|
||||
db.store_block_timestamp(last_height, &last_hash, last_timestamp)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -53,7 +53,6 @@ impl <N: Parameters + Sync,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn initialize(&mut self) -> Result<()> {
|
||||
let db = self.db.build()?;
|
||||
let TreeCheckpoint { tree, witnesses } = db.get_tree_by_name(&self.shielded_pool)?;
|
||||
|
@ -69,7 +68,7 @@ impl <N: Parameters + Sync,
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process(&mut self, blocks: Vec<CompactBlock>) -> Result<()> {
|
||||
pub fn process(&mut self, blocks: &[CompactBlock]) -> Result<()> {
|
||||
if blocks.is_empty() { return Ok(()) }
|
||||
let decrypter = self.decrypter.clone();
|
||||
let decrypted_blocks: Vec<_> = blocks
|
||||
|
@ -135,7 +134,7 @@ impl <N: Parameters + Sync,
|
|||
for w in updated_witnesses.iter() {
|
||||
DbAdapter::store_witness(w, height, w.id_note, &db_tx, &self.shielded_pool)?;
|
||||
}
|
||||
DbAdapter::store_tree(height, &hash, &updated_tree, &db_tx, &self.shielded_pool)?;
|
||||
DbAdapter::store_tree(height, &updated_tree, &db_tx, &self.shielded_pool)?;
|
||||
self.tree = updated_tree;
|
||||
self.witnesses = updated_witnesses;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::time::Instant;
|
|||
use zcash_note_encryption::batch::try_compact_note_decryption;
|
||||
use zcash_note_encryption::{BatchDomain, COMPACT_NOTE_SIZE, EphemeralKeyBytes, ShieldedOutput};
|
||||
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||
use crate::{CompactBlock, CompactSaplingOutput, CompactTx};
|
||||
use crate::{CompactBlock, CompactOrchardAction, CompactSaplingOutput, CompactTx};
|
||||
use crate::db::ReceivedNote;
|
||||
use crate::sync::tree::Node;
|
||||
|
||||
|
@ -51,6 +51,7 @@ pub trait DecryptedNote<D: BatchDomain, VK>: Send + Sync {
|
|||
|
||||
// Deep copy from protobuf message
|
||||
pub struct CompactOutputBytes {
|
||||
pub nullifier: [u8; 32],
|
||||
pub epk: [u8; 32],
|
||||
pub cmx: [u8; 32],
|
||||
pub ciphertext: [u8; 52],
|
||||
|
@ -59,6 +60,7 @@ pub struct CompactOutputBytes {
|
|||
impl From<&CompactSaplingOutput> for CompactOutputBytes {
|
||||
fn from(co: &CompactSaplingOutput) -> Self {
|
||||
CompactOutputBytes {
|
||||
nullifier: [0u8; 32],
|
||||
epk: if co.epk.is_empty() { [0u8; 32] } else { co.epk.clone().try_into().unwrap() } ,
|
||||
cmx: co.cmu.clone().try_into().unwrap(), // cannot be filtered out
|
||||
ciphertext: if co.ciphertext.is_empty() { [0u8; 52] } else { co.ciphertext.clone().try_into().unwrap() },
|
||||
|
@ -66,6 +68,18 @@ impl From<&CompactSaplingOutput> for CompactOutputBytes {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&CompactOrchardAction> for CompactOutputBytes {
|
||||
fn from(co: &CompactOrchardAction) -> Self {
|
||||
CompactOutputBytes {
|
||||
nullifier: co.nullifier.clone().try_into().unwrap(),
|
||||
epk: if co.ephemeral_key.is_empty() { [0u8; 32] } else { co.ephemeral_key.clone().try_into().unwrap() } ,
|
||||
cmx: co.cmx.clone().try_into().unwrap(), // cannot be filtered out
|
||||
ciphertext: if co.ciphertext.is_empty() { [0u8; 52] } else { co.ciphertext.clone().try_into().unwrap() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct CompactShieldedOutput(CompactOutputBytes, OutputPosition);
|
||||
|
||||
impl<D: BatchDomain<ExtractedCommitmentBytes = [u8; 32]>> ShieldedOutput<D, COMPACT_NOTE_SIZE>
|
||||
|
@ -95,17 +109,14 @@ pub trait TrialDecrypter<N: Parameters, D: BatchDomain<ExtractedCommitmentBytes
|
|||
let mut outputs = vec![];
|
||||
let mut txs = HashMap::new();
|
||||
for (tx_index, vtx) in block.vtx.iter().enumerate() {
|
||||
for cs in vtx.spends.iter() {
|
||||
let mut nf = [0u8; 32];
|
||||
nf.copy_from_slice(&cs.nf);
|
||||
spends.push(Nf(nf));
|
||||
}
|
||||
let tx_inputs = self.spends(vtx);
|
||||
spends.extend(tx_inputs.iter());
|
||||
|
||||
let tx_outputs = self.outputs(vtx);
|
||||
if let Some(fco) = tx_outputs.first() {
|
||||
if !fco.epk.is_empty() {
|
||||
for (output_index, cob) in tx_outputs.into_iter().enumerate() {
|
||||
let domain = self.domain(height);
|
||||
let domain = self.domain(height, &cob);
|
||||
let pos = OutputPosition {
|
||||
height: block.height as u32,
|
||||
tx_index,
|
||||
|
@ -167,7 +178,7 @@ pub trait TrialDecrypter<N: Parameters, D: BatchDomain<ExtractedCommitmentBytes
|
|||
}
|
||||
}
|
||||
|
||||
fn domain(&self, height: BlockHeight) -> D;
|
||||
fn domain(&self, height: BlockHeight, cob: &CompactOutputBytes) -> D;
|
||||
fn spends(&self, vtx: &CompactTx) -> Vec<Nf>;
|
||||
fn outputs(&self, vtx: &CompactTx) -> Vec<CompactOutputBytes>;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue