Orchard warp sync
This commit is contained in:
parent
cbe4737439
commit
088a4d1ef5
|
@ -73,6 +73,11 @@ chrono = "0.4.19"
|
||||||
lazycell = "1.3.0"
|
lazycell = "1.3.0"
|
||||||
reqwest = { version = "0.11.4", features = ["json", "rustls-tls"], default-features = false }
|
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"
|
bech32 = "0.8.1"
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
blake2b_simd = "1.0.0"
|
blake2b_simd = "1.0.0"
|
||||||
|
|
|
@ -190,7 +190,6 @@ pub async fn download_chain(
|
||||||
ph.copy_from_slice(&block.hash);
|
ph.copy_from_slice(&block.hash);
|
||||||
prev_hash = Some(ph);
|
prev_hash = Some(ph);
|
||||||
for tx in block.vtx.iter_mut() {
|
for tx in block.vtx.iter_mut() {
|
||||||
tx.actions.clear(); // don't need Orchard actions
|
|
||||||
let mut skipped = false;
|
let mut skipped = false;
|
||||||
if tx.outputs.len() > max_cost as usize {
|
if tx.outputs.len() > max_cost as usize {
|
||||||
for co in tx.outputs.iter_mut() {
|
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 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 {
|
pub fn wrap_query_no_rows(name: &'static str) -> impl Fn(rusqlite::Error) -> anyhow::Error {
|
||||||
move |err: rusqlite::Error| match err {
|
move |err: rusqlite::Error| match err {
|
||||||
QueryReturnedNoRows => anyhow::anyhow!("Query {} returned no rows", name),
|
QueryReturnedNoRows => anyhow::anyhow!("Query {} returned no rows", name),
|
||||||
|
@ -232,6 +237,24 @@ impl DbAdapter {
|
||||||
Ok(fvks)
|
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> {
|
pub fn trim_to_height(&mut self, height: u32) -> anyhow::Result<u32> {
|
||||||
// snap height to an existing checkpoint
|
// snap height to an existing checkpoint
|
||||||
let height = self.connection.query_row(
|
let height = self.connection.query_row(
|
||||||
|
@ -426,16 +449,15 @@ impl DbAdapter {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_tree(height: u32, hash: &[u8], tree: &CTree, db_tx: &Connection, shielded_pool: &str) -> anyhow::Result<()> {
|
pub fn store_block_timestamp(&self, height: u32, hash: &[u8], timestamp: u32) -> anyhow::Result<()> {
|
||||||
let mut bb: Vec<u8> = vec![];
|
self.connection.execute("INSERT INTO blocks(height, hash, timestamp) VALUES (?1,?2,?3)", params![height, hash, timestamp])?;
|
||||||
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])?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_block_timestamp(&self, height: u32, timestamp: u32) -> anyhow::Result<()> {
|
pub fn store_tree(height: u32, tree: &CTree, db_tx: &Connection, shielded_pool: &str) -> anyhow::Result<()> {
|
||||||
self.connection.execute("UPDATE blocks SET timestamp = ?1 WHERE height = ?2", params![timestamp, height])?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,15 +541,21 @@ impl DbAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tree_by_name(&self, shielded_pool: &str) -> anyhow::Result<TreeCheckpoint> {
|
pub fn get_tree_by_name(&self, shielded_pool: &str) -> anyhow::Result<TreeCheckpoint> {
|
||||||
let res = self.connection.query_row(
|
let height = self.connection.query_row(
|
||||||
&format!("SELECT height, {}_tree FROM blocks WHERE height = (SELECT MAX(height) FROM blocks)", shielded_pool),
|
"SELECT MAX(height) FROM blocks",
|
||||||
[], |row| {
|
[], |row| {
|
||||||
let height: u32 = row.get(0)?;
|
let height: Option<u32> = row.get(0)?;
|
||||||
let tree: Vec<u8> = row.get(1)?;
|
Ok(height)
|
||||||
Ok((height, tree))
|
})?;
|
||||||
}).optional()?;
|
Ok(match height {
|
||||||
Ok(match res {
|
Some(height) => {
|
||||||
Some((height, tree)) => {
|
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 tree = sync::CTree::read(&*tree)?;
|
||||||
let mut statement = self.connection.prepare(
|
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))?;
|
&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 {
|
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("ALTER TABLE received_notes ADD rho BLOB", [])?;
|
||||||
connection.execute(
|
connection.execute(
|
||||||
"CREATE TABLE IF NOT EXISTS orchard_witnesses (
|
"CREATE TABLE IF NOT EXISTS orchard_witnesses (
|
||||||
|
|
|
@ -82,8 +82,9 @@ mod contact;
|
||||||
mod db;
|
mod db;
|
||||||
mod fountain;
|
mod fountain;
|
||||||
mod hash;
|
mod hash;
|
||||||
pub(crate) mod sync;
|
mod sync;
|
||||||
pub mod sapling;
|
mod sapling;
|
||||||
|
mod orchard;
|
||||||
mod key;
|
mod key;
|
||||||
mod key2;
|
mod key2;
|
||||||
mod mempool;
|
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> {
|
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);
|
ExtendedPoint::batch_normalize(extended, &mut hash_affine);
|
||||||
hash_affine
|
hash_affine
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -82,7 +82,7 @@ impl <N> SaplingDecrypter<N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <N: Parameters> TrialDecrypter<N, SaplingDomain<N>, SaplingViewKey, DecryptedSaplingNote> for 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)
|
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()
|
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 std::cmp::Ordering;
|
||||||
|
|
||||||
use crate::transaction::retrieve_tx_info;
|
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 crate::chain::{DecryptNode, download_chain};
|
||||||
use ff::PrimeField;
|
use ff::PrimeField;
|
||||||
|
|
||||||
|
@ -14,15 +14,19 @@ use std::collections::HashMap;
|
||||||
use std::panic;
|
use std::panic;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
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::runtime::{Builder, Runtime};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
|
||||||
use zcash_params::coin::{get_coin_chain, CoinType};
|
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::{Node, Note};
|
||||||
use zcash_primitives::sapling::note_encryption::SaplingDomain;
|
use zcash_primitives::sapling::note_encryption::SaplingDomain;
|
||||||
|
use crate::orchard::{DecryptedOrchardNote, OrchardDecrypter, OrchardHasher, OrchardViewKey};
|
||||||
use crate::sapling::{DecryptedSaplingNote, SaplingDecrypter, SaplingHasher, SaplingViewKey};
|
use crate::sapling::{DecryptedSaplingNote, SaplingDecrypter, SaplingHasher, SaplingViewKey};
|
||||||
use crate::sync::{CTree, Synchronizer, WarpProcessor};
|
use crate::sync::{CTree, Synchronizer, WarpProcessor};
|
||||||
|
|
||||||
|
@ -61,6 +65,9 @@ pub struct TxIdHeight {
|
||||||
type SaplingSynchronizer = Synchronizer<Network, SaplingDomain<Network>, SaplingViewKey, DecryptedSaplingNote,
|
type SaplingSynchronizer = Synchronizer<Network, SaplingDomain<Network>, SaplingViewKey, DecryptedSaplingNote,
|
||||||
SaplingDecrypter<Network>, SaplingHasher>;
|
SaplingDecrypter<Network>, SaplingHasher>;
|
||||||
|
|
||||||
|
type OrchardSynchronizer = Synchronizer<Network, OrchardDomain, OrchardViewKey, DecryptedOrchardNote,
|
||||||
|
OrchardDecrypter<Network>, OrchardHasher>;
|
||||||
|
|
||||||
pub async fn sync_async<'a>(
|
pub async fn sync_async<'a>(
|
||||||
coin_type: CoinType,
|
coin_type: CoinType,
|
||||||
_chunk_size: u32,
|
_chunk_size: u32,
|
||||||
|
@ -80,7 +87,7 @@ pub async fn sync_async<'a>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut client = connect_lightwalletd(&ld_url).await?;
|
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 db = DbAdapter::new(coin_type, &db_path)?;
|
||||||
let height = db.get_db_height()?;
|
let height = db.get_db_height()?;
|
||||||
let hash = db.get_db_hash(height)?;
|
let hash = db.get_db_hash(height)?;
|
||||||
|
@ -92,7 +99,22 @@ pub async fn sync_async<'a>(
|
||||||
ivk: ak.ivk.clone()
|
ivk: ak.ivk.clone()
|
||||||
}
|
}
|
||||||
}).collect();
|
}).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 = get_latest_height(&mut client).await?;
|
||||||
let end_height = (end_height - target_height_offset).max(start_height);
|
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
|
let first_block = blocks.0.first().unwrap(); // cannot be empty because blocks are not
|
||||||
log::info!("Height: {}", first_block.height);
|
log::info!("Height: {}", first_block.height);
|
||||||
let last_block = blocks.0.last().unwrap();
|
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_height = last_block.height as u32;
|
||||||
let last_timestamp = last_block.time;
|
let last_timestamp = last_block.time;
|
||||||
|
|
||||||
let decrypter = SaplingDecrypter::new(network);
|
// Sapling
|
||||||
let warper = WarpProcessor::new(SaplingHasher::default());
|
{
|
||||||
let mut sapling_synchronizer = SaplingSynchronizer::new(
|
let decrypter = SaplingDecrypter::new(network);
|
||||||
decrypter,
|
let warper = WarpProcessor::new(SaplingHasher::default());
|
||||||
warper,
|
let mut synchronizer = SaplingSynchronizer::new(
|
||||||
sapling_vks.clone(),
|
decrypter,
|
||||||
db_builder.clone(),
|
warper,
|
||||||
"sapling".to_string(),
|
sapling_vks.clone(),
|
||||||
);
|
db_builder.clone(),
|
||||||
sapling_synchronizer.initialize()?;
|
"sapling".to_string(),
|
||||||
sapling_synchronizer.process(blocks.0)?;
|
);
|
||||||
|
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()?;
|
let db = db_builder.build()?;
|
||||||
db.store_block_timestamp(last_height, last_timestamp)?;
|
db.store_block_timestamp(last_height, &last_hash, last_timestamp)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -53,7 +53,6 @@ impl <N: Parameters + Sync,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn initialize(&mut self) -> Result<()> {
|
pub fn initialize(&mut self) -> Result<()> {
|
||||||
let db = self.db.build()?;
|
let db = self.db.build()?;
|
||||||
let TreeCheckpoint { tree, witnesses } = db.get_tree_by_name(&self.shielded_pool)?;
|
let TreeCheckpoint { tree, witnesses } = db.get_tree_by_name(&self.shielded_pool)?;
|
||||||
|
@ -69,7 +68,7 @@ impl <N: Parameters + Sync,
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process(&mut self, blocks: Vec<CompactBlock>) -> Result<()> {
|
pub fn process(&mut self, blocks: &[CompactBlock]) -> Result<()> {
|
||||||
if blocks.is_empty() { return Ok(()) }
|
if blocks.is_empty() { return Ok(()) }
|
||||||
let decrypter = self.decrypter.clone();
|
let decrypter = self.decrypter.clone();
|
||||||
let decrypted_blocks: Vec<_> = blocks
|
let decrypted_blocks: Vec<_> = blocks
|
||||||
|
@ -135,7 +134,7 @@ impl <N: Parameters + Sync,
|
||||||
for w in updated_witnesses.iter() {
|
for w in updated_witnesses.iter() {
|
||||||
DbAdapter::store_witness(w, height, w.id_note, &db_tx, &self.shielded_pool)?;
|
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.tree = updated_tree;
|
||||||
self.witnesses = updated_witnesses;
|
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::batch::try_compact_note_decryption;
|
||||||
use zcash_note_encryption::{BatchDomain, COMPACT_NOTE_SIZE, EphemeralKeyBytes, ShieldedOutput};
|
use zcash_note_encryption::{BatchDomain, COMPACT_NOTE_SIZE, EphemeralKeyBytes, ShieldedOutput};
|
||||||
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
use zcash_primitives::consensus::{BlockHeight, Parameters};
|
||||||
use crate::{CompactBlock, CompactSaplingOutput, CompactTx};
|
use crate::{CompactBlock, CompactOrchardAction, CompactSaplingOutput, CompactTx};
|
||||||
use crate::db::ReceivedNote;
|
use crate::db::ReceivedNote;
|
||||||
use crate::sync::tree::Node;
|
use crate::sync::tree::Node;
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ pub trait DecryptedNote<D: BatchDomain, VK>: Send + Sync {
|
||||||
|
|
||||||
// Deep copy from protobuf message
|
// Deep copy from protobuf message
|
||||||
pub struct CompactOutputBytes {
|
pub struct CompactOutputBytes {
|
||||||
|
pub nullifier: [u8; 32],
|
||||||
pub epk: [u8; 32],
|
pub epk: [u8; 32],
|
||||||
pub cmx: [u8; 32],
|
pub cmx: [u8; 32],
|
||||||
pub ciphertext: [u8; 52],
|
pub ciphertext: [u8; 52],
|
||||||
|
@ -59,6 +60,7 @@ pub struct CompactOutputBytes {
|
||||||
impl From<&CompactSaplingOutput> for CompactOutputBytes {
|
impl From<&CompactSaplingOutput> for CompactOutputBytes {
|
||||||
fn from(co: &CompactSaplingOutput) -> Self {
|
fn from(co: &CompactSaplingOutput) -> Self {
|
||||||
CompactOutputBytes {
|
CompactOutputBytes {
|
||||||
|
nullifier: [0u8; 32],
|
||||||
epk: if co.epk.is_empty() { [0u8; 32] } else { co.epk.clone().try_into().unwrap() } ,
|
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
|
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() },
|
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);
|
pub struct CompactShieldedOutput(CompactOutputBytes, OutputPosition);
|
||||||
|
|
||||||
impl<D: BatchDomain<ExtractedCommitmentBytes = [u8; 32]>> ShieldedOutput<D, COMPACT_NOTE_SIZE>
|
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 outputs = vec![];
|
||||||
let mut txs = HashMap::new();
|
let mut txs = HashMap::new();
|
||||||
for (tx_index, vtx) in block.vtx.iter().enumerate() {
|
for (tx_index, vtx) in block.vtx.iter().enumerate() {
|
||||||
for cs in vtx.spends.iter() {
|
let tx_inputs = self.spends(vtx);
|
||||||
let mut nf = [0u8; 32];
|
spends.extend(tx_inputs.iter());
|
||||||
nf.copy_from_slice(&cs.nf);
|
|
||||||
spends.push(Nf(nf));
|
|
||||||
}
|
|
||||||
|
|
||||||
let tx_outputs = self.outputs(vtx);
|
let tx_outputs = self.outputs(vtx);
|
||||||
if let Some(fco) = tx_outputs.first() {
|
if let Some(fco) = tx_outputs.first() {
|
||||||
if !fco.epk.is_empty() {
|
if !fco.epk.is_empty() {
|
||||||
for (output_index, cob) in tx_outputs.into_iter().enumerate() {
|
for (output_index, cob) in tx_outputs.into_iter().enumerate() {
|
||||||
let domain = self.domain(height);
|
let domain = self.domain(height, &cob);
|
||||||
let pos = OutputPosition {
|
let pos = OutputPosition {
|
||||||
height: block.height as u32,
|
height: block.height as u32,
|
||||||
tx_index,
|
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 spends(&self, vtx: &CompactTx) -> Vec<Nf>;
|
||||||
fn outputs(&self, vtx: &CompactTx) -> Vec<CompactOutputBytes>;
|
fn outputs(&self, vtx: &CompactTx) -> Vec<CompactOutputBytes>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue