tx builder

This commit is contained in:
Hanh 2022-11-05 09:58:35 +08:00
parent 9e4ce62bd2
commit 74c26bf87d
21 changed files with 479 additions and 104 deletions

View File

@ -5,6 +5,7 @@ pub mod historical_prices;
pub mod mempool;
pub mod message;
pub mod payment;
pub mod payment_v2;
pub mod payment_uri;
pub mod sync;

View File

@ -206,6 +206,12 @@ pub struct Recipient {
pub max_amount_per_note: u64,
}
#[derive(Deserialize)]
pub struct RecipientShort {
pub address: String,
pub amount: u64,
}
pub struct RecipientMemo {
pub address: String,
pub amount: u64,
@ -228,3 +234,14 @@ impl RecipientMemo {
}
}
}
impl From<RecipientShort> for RecipientMemo {
fn from(r: RecipientShort) -> Self {
RecipientMemo {
address: r.address,
amount: r.amount,
memo: Memo::Empty,
max_amount_per_note: 0
}
}
}

5
src/api/payment_v2.rs Normal file
View File

@ -0,0 +1,5 @@
use crate::api::payment::RecipientShort;
async fn prepare_payment_v2(recipients: &[RecipientShort]) -> anyhow::Result<()> {
todo!()
}

View File

@ -423,8 +423,10 @@ impl DbAdapter {
db_tx: &Transaction,
) -> anyhow::Result<u32> {
log::info!("+received_note {} {:?}", id_tx, note);
db_tx.execute("INSERT INTO received_notes(account, tx, height, position, output_index, diversifier, value, rcm, rho, nf, spent)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)", params![note.account, id_tx, note.height, position as u32, note.output_index, note.diversifier, note.value as i64, note.rcm, note.rho, note.nf, note.spent])?;
let orchard = note.rho.is_some();
db_tx.execute("INSERT INTO received_notes(account, tx, height, position, output_index, diversifier, value, rcm, rho, nf, orchard, spent)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)", params![note.account, id_tx, note.height, position as u32, note.output_index,
note.diversifier, note.value as i64, note.rcm, note.rho, note.nf, orchard, note.spent])?;
let id_note: u32 = db_tx
.query_row(
"SELECT id_note FROM received_notes WHERE tx = ?1 AND output_index = ?2",
@ -560,27 +562,16 @@ impl DbAdapter {
}))
}
pub fn get_tree(&self) -> anyhow::Result<(TreeCheckpoint, TreeCheckpoint)> {
self.get_tree_by_name("sapling")?; // TODO: pack in TreeCheckpoint
todo!()
}
pub fn get_tree_by_name(&self, shielded_pool: &str) -> anyhow::Result<TreeCheckpoint> {
let height = self.connection.query_row(
"SELECT MAX(height) FROM blocks",
[], |row| {
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)
})?;
pub fn get_tree_by_name(&self, height: u32, shielded_pool: &str) -> anyhow::Result<TreeCheckpoint> {
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)
}).optional()?;
match tree {
Some(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))?;
@ -593,13 +584,13 @@ impl DbAdapter {
for w in ws {
witnesses.push(w?);
}
TreeCheckpoint { tree, witnesses }
Ok(TreeCheckpoint { tree, witnesses })
}
None => TreeCheckpoint {
None => Ok(TreeCheckpoint {
tree: CTree::new(),
witnesses: vec![],
},
})
})
}
}
pub fn get_nullifiers(&self) -> anyhow::Result<HashMap<Nf, NfRef>> {
@ -965,8 +956,8 @@ impl DbAdapter {
"SELECT sk FROM taddrs WHERE account = ?1",
params![account],
|row| {
let address: String = row.get(0)?;
Ok(address)
let sk: String = row.get(0)?;
Ok(sk)
},
)
.optional()?;

View File

@ -198,7 +198,31 @@ pub fn init_db(connection: &Connection, network: &Network) -> anyhow::Result<()>
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 new_received_notes (
id_note INTEGER PRIMARY KEY,
account INTEGER NOT NULL,
position INTEGER NOT NULL,
tx INTEGER NOT NULL,
height INTEGER NOT NULL,
output_index INTEGER NOT NULL,
diversifier BLOB NOT NULL,
value INTEGER NOT NULL,
rcm BLOB NOT NULL,
nf BLOB NOT NULL UNIQUE,
rho BLOB,
orchard BOOL NOT NULL DEFAULT (false),
spent INTEGER,
excluded BOOL,
CONSTRAINT tx_output UNIQUE (tx, orchard, output_index))",
[],
)?;
connection.execute("INSERT INTO new_received_notes(
id_note, account, position, tx, height, output_index, diversifier, value,
rcm, nf, spent, excluded
) SELECT * FROM received_notes", [])?;
connection.execute("DROP TABLE received_notes", [])?;
connection.execute("ALTER TABLE new_received_notes RENAME TO received_notes", [])?;
connection.execute(
"CREATE TABLE IF NOT EXISTS orchard_witnesses (
id_witness INTEGER PRIMARY KEY,

View File

@ -3,6 +3,7 @@ mod fee;
mod utxo;
mod fill;
mod select;
mod builder;
#[cfg(test)]
mod tests;

View File

@ -0,0 +1,338 @@
use std::slice;
use std::str::FromStr;
use anyhow::anyhow;
use jubjub::Fr;
use orchard::{Address, Anchor, Bundle};
use orchard::keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey};
use secp256k1::{All, PublicKey, Secp256k1, SecretKey};
use zcash_primitives::consensus::{BlockHeight, BranchId, Network, Parameters};
use zcash_primitives::transaction::builder::Builder;
use orchard::builder::Builder as OrchardBuilder;
use orchard::bundle::Flags;
use orchard::circuit::ProvingKey;
use orchard::note::{ExtractedNoteCommitment, Nullifier};
use orchard::value::NoteValue;
use rand::{CryptoRng, RngCore};
use rand::rngs::OsRng;
use ripemd::{Digest, Ripemd160};
use sha2::Sha256;
use zcash_client_backend::encoding::decode_extended_spending_key;
use zcash_primitives::legacy::TransparentAddress;
use zcash_primitives::memo::{Memo, MemoBytes};
use zcash_primitives::merkle_tree::{IncrementalWitness, MerklePath};
use zcash_primitives::sapling::{Diversifier, Node, PaymentAddress, Rseed};
use zcash_primitives::sapling::prover::TxProver;
use zcash_primitives::transaction::components::{Amount, OutPoint, TxOut};
use zcash_primitives::transaction::{Transaction, TransactionData, TxVersion};
use zcash_primitives::transaction::sighash::SignableInput;
use zcash_primitives::transaction::sighash_v5::v5_signature_hash;
use zcash_primitives::transaction::txid::TxIdDigester;
use zcash_primitives::zip32::{DiversifierIndex, ExtendedFullViewingKey, ExtendedSpendingKey};
use zcash_proofs::prover::LocalTxProver;
use crate::{AccountData, broadcast_tx, CoinConfig, DbAdapter, init_coin, set_active, set_coin_lwd_url};
use crate::api::payment::RecipientMemo;
use crate::coinconfig::get_prover;
use crate::note_selection::types::TransactionPlan;
use crate::note_selection::{decode, Destination, FeeFlat, fetch_utxos, note_select_with_fee, NoteSelectConfig, Pool, prepare_multi_payment, PrivacyPolicy, Source};
use crate::orchard::{get_proving_key, ORCHARD_ROOTS, OrchardHasher};
use crate::sapling::{SAPLING_ROOTS, SaplingHasher};
use crate::sync::tree::TreeCheckpoint;
use crate::sync::Witness;
pub struct SecretKeys {
pub transparent: Option<SecretKey>,
pub sapling: ExtendedSpendingKey,
pub orchard: Option<SpendingKey>,
}
pub struct Context {
pub height: u32,
pub sapling_anchor: [u8; 32],
pub orchard_anchor: [u8; 32],
}
impl Context {
pub fn from_height(coin: u8, height: u32) -> anyhow::Result<Self> {
let c = CoinConfig::get(coin);
let db = c.db.as_ref().unwrap();
let db = db.lock().unwrap();
let TreeCheckpoint { tree, .. } = db.get_tree_by_name(height, "sapling")?;
let hasher = SaplingHasher {};
let sapling_anchor= tree.root(32, &SAPLING_ROOTS, &hasher);
let TreeCheckpoint { tree, .. } = db.get_tree_by_name(height, "orchard")?;
let hasher = OrchardHasher::new();
let orchard_anchor= tree.root(32, &ORCHARD_ROOTS, &hasher);
let context = Context {
height,
sapling_anchor,
orchard_anchor,
};
Ok(context)
}
}
const EXPIRY_HEIGHT: u32 = 50;
// TODO: Remove unwrap()
pub fn build_tx(network: &Network, skeys: &SecretKeys, plan: &TransactionPlan, context: Context, mut rng: impl RngCore + CryptoRng + Clone) -> anyhow::Result<Vec<u8>> {
let secp = Secp256k1::<All>::new();
let transparent_address = skeys.transparent.map(|tkey| {
let pub_key = PublicKey::from_secret_key(&secp, &tkey);
let pub_key = pub_key.serialize();
let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key));
TransparentAddress::PublicKey(pub_key.into())
});
let sapling_fvk = ExtendedFullViewingKey::from(&skeys.sapling);
let sapling_ovk = sapling_fvk.fvk.ovk;
let okeys = skeys.orchard.map(|sk| {
let orchard_fvk = FullViewingKey::from(&sk);
let orchard_ovk = orchard_fvk.clone().to_ovk(Scope::External);
(orchard_fvk, orchard_ovk)
});
let (orchard_fvk, orchard_ovk) = match okeys {
Some((a, b)) => (Some(a), Some(b)),
_ => (None, None),
};
let mut has_orchard = false;
let mut builder = Builder::new(*network, BlockHeight::from_u32(context.height));
let anchor: Anchor = orchard::tree::MerkleHashOrchard::from_bytes(&context.orchard_anchor).unwrap().into();
let mut orchard_builder = OrchardBuilder::new(Flags::from_parts(true, true), anchor);
for spend in plan.spends.iter() {
match &spend.source {
Source::Transparent { txid, index } => {
let utxo = OutPoint::new(*txid, *index);
let coin = TxOut {
value: Amount::from_u64(spend.amount).unwrap(),
script_pubkey: transparent_address.ok_or(anyhow!("No transparent key")).map(|ta| ta.script())?,
};
builder.add_transparent_input(skeys.transparent.unwrap(), utxo, coin)?;
}
Source::Sapling { diversifier, rseed, witness, .. } => {
let diversifier = Diversifier(*diversifier);
let sapling_address = sapling_fvk.fvk.vk.to_payment_address(diversifier).unwrap();
let rseed = Rseed::BeforeZip212(Fr::from_bytes(rseed).unwrap());
let note = sapling_address.create_note(spend.amount, rseed).unwrap();
let witness = IncrementalWitness::<Node>::read(witness.as_slice())?;
let merkle_path = witness.path().unwrap();
builder.add_sapling_spend(skeys.sapling.clone(), diversifier, note, merkle_path)?;
}
Source::Orchard { id_note, diversifier, rho, rseed, witness} => {
has_orchard = true;
let diversifier = orchard::keys::Diversifier::from_bytes(*diversifier);
let sender_address = orchard_fvk.as_ref().ok_or(anyhow!("No Orchard key")).map(|fvk| fvk.address(diversifier, Scope::External))?;
let value = NoteValue::from_raw(spend.amount);
let rho = Nullifier::from_bytes(&rho).unwrap();
let rseed = orchard::note::RandomSeed::from_bytes(*rseed, &rho).unwrap();
let note = orchard::Note::from_parts(sender_address, value, rho, rseed).unwrap();
let witness = Witness::from_bytes(*id_note, &witness)?;
let auth_path: Vec<_> = witness.auth_path(32, &ORCHARD_ROOTS, &OrchardHasher::new()).iter()
.map(|n| {
orchard::tree::MerkleHashOrchard::from_bytes(n).unwrap()
}).collect();
let merkle_path = orchard::tree::MerklePath::from_parts(witness.position as u32, auth_path.try_into().unwrap());
orchard_builder.add_spend(orchard_fvk.clone().unwrap(), note, merkle_path).map_err(|e| anyhow!(e.to_string()))?;
}
}
}
for output in plan.outputs.iter() {
let value = Amount::from_u64(output.amount).unwrap();
match &output.destination {
Destination::Transparent(addr) => {
let transparent_address = TransparentAddress::PublicKey(*addr);
builder.add_transparent_output(&transparent_address, value)?;
}
Destination::Sapling(addr) => {
let sapling_address = PaymentAddress::from_bytes(addr).unwrap();
builder.add_sapling_output(Some(sapling_ovk), sapling_address, value, output.memo.clone())?;
}
Destination::Orchard(addr) => {
has_orchard = true;
let orchard_address = Address::from_raw_address_bytes(addr).unwrap();
orchard_builder.add_recipient(orchard_ovk.clone(), orchard_address,
NoteValue::from_raw(output.amount), Some(*output.memo.as_array())).map_err(|_| anyhow!("Orchard::add_recipient"))?;
}
}
}
let transparent_bundle = builder.transparent_builder.build();
let mut ctx = get_prover().new_sapling_proving_context();
let sapling_bundle = builder.sapling_builder
.build(
get_prover(),
&mut ctx,
&mut rng,
BlockHeight::from_u32(context.height), None
).unwrap();
let mut orchard_bundle: Option<Bundle<_, Amount>> = None;
if has_orchard {
orchard_bundle = Some(orchard_builder.build(rng.clone()).unwrap());
}
let unauthed_tx: TransactionData<zcash_primitives::transaction::Unauthorized> =
TransactionData::from_parts(
TxVersion::Zip225,
BranchId::Nu5,
0,
BlockHeight::from_u32(context.height + EXPIRY_HEIGHT),
transparent_bundle,
None,
sapling_bundle,
orchard_bundle,
);
let txid_parts = unauthed_tx.digest(TxIdDigester);
let sig_hash = v5_signature_hash(&unauthed_tx, &SignableInput::Shielded, &txid_parts);
let sig_hash: [u8; 32] = sig_hash.as_bytes().try_into().unwrap();
let transparent_bundle = unauthed_tx.transparent_bundle().map(|tb| {
tb.clone().apply_signatures(&unauthed_tx, &txid_parts)
});
let sapling_bundle = unauthed_tx.sapling_bundle().map(|sb| {
sb.clone().apply_signatures(get_prover(), &mut ctx, &mut rng, &sig_hash).unwrap().0
});
let mut orchard_signing_keys = vec![];
if let Some(sk) = skeys.orchard {
orchard_signing_keys.push(SpendAuthorizingKey::from(&sk));
}
let orchard_bundle = unauthed_tx.orchard_bundle()
.map(|ob| {
let proven = ob.clone().create_proof(get_proving_key(), rng.clone()).unwrap();
proven.apply_signatures(rng.clone(), sig_hash, &orchard_signing_keys).unwrap()
});
let tx_data: TransactionData<zcash_primitives::transaction::Authorized> =
TransactionData::from_parts(
TxVersion::Zip225,
BranchId::Nu5,
0,
BlockHeight::from_u32(context.height + EXPIRY_HEIGHT),
transparent_bundle,
None,
sapling_bundle,
orchard_bundle,
);
let tx = Transaction::from_data(tx_data).unwrap();
let mut tx_bytes = vec![];
tx.write(&mut tx_bytes).unwrap();
Ok(tx_bytes)
}
pub fn get_secret_keys(coin: u8, account: u32) -> anyhow::Result<SecretKeys> {
let c = CoinConfig::get(coin);
let db = c.db.as_ref().unwrap();
let db = db.lock().unwrap();
let transparent_sk = db.get_tsk(account)?.map(|tsk| {
SecretKey::from_str(&tsk).unwrap()
});
let AccountData { sk, .. } = db.get_account_info(account)?;
let sapling_sk = sk.ok_or(anyhow!("No secret key"))?;
let sapling_sk = decode_extended_spending_key(c.chain.network().hrp_sapling_extended_spending_key(), &sapling_sk).unwrap();
let orchard_sk = db.get_orchard(account)?.map(|ob| {
SpendingKey::from_bytes(ob.sk).unwrap()
});
let sk = SecretKeys {
transparent: transparent_sk,
sapling: sapling_sk,
orchard: orchard_sk,
};
Ok(sk)
}
#[tokio::test]
async fn dummy_test() {
let _ = env_logger::try_init();
init_coin(0, "./zec.db").unwrap();
set_coin_lwd_url(0, "http://127.0.0.1:9067");
let c = CoinConfig::get(0);
log::info!("Start test");
let height = {
let db = c.db.as_ref().unwrap();
let db = db.lock().unwrap();
db.get_last_sync_height().unwrap().unwrap()
};
log::info!("Height {}", height);
const REGTEST_CHANGE: &str = "uregtest1mxy5wq2n0xw57nuxa4lqpl358zw4vzyfgadsn5jungttmqcv6nx6cpx465dtpzjzw0vprjle4j4nqqzxtkuzm93regvgg4xce0un5ec6tedquc469zjhtdpkxz04kunqqyasv4rwvcweh3ue0ku0payn29stl2pwcrghyzscrrju9ar57rn36wgz74nmynwcyw27rjd8yk477l97ez8";
let mut config = NoteSelectConfig::new(REGTEST_CHANGE);
config.use_transparent = true;
config.privacy_policy = PrivacyPolicy::AnyPool;
log::info!("Getting signing keys");
let keys = get_secret_keys(0, 1).unwrap();
log::info!("Building signing context");
let context = Context::from_height(0, height).unwrap();
log::info!("Getting available notes");
let utxos = fetch_utxos(0, 1, height, true, 0).await.unwrap();
// let notes: Vec<_> = utxos.into_iter().filter(|utxo| utxo.source.pool() == Pool::Orchard).collect();
log::info!("Preparing outputs");
let mut orders = vec![];
orders.push(decode(1, "tmWXoSBwPoCjJCNZjw4P7heoVMcT2Ronrqq", 10000, MemoBytes::empty()).unwrap());
orders.push(decode(2, "zregtestsapling1qzy9wafd2axnenul6t6wav76dys6s8uatsq778mpmdvmx4k9myqxsd9m73aqdgc7gwnv53wga4j", 20000, MemoBytes::empty()).unwrap());
orders.push(decode(3, "uregtest1mzt5lx5s5u8kczlfr82av97kjckmfjfuq8y9849h6cl9chhdekxsm6r9dklracflqwplrnfzm5rucp5txfdm04z5myrde8y3y5rayev8", 30000, MemoBytes::empty()).unwrap());
orders.push(decode(4, "uregtest1yvucqfqnmq5ldc6fkvuudlsjhxg56hxph9ymmcnmpzpywd752ym8sr5l5d24wqn4enz3gakk6alf5hlpw2cjs3jjrcdae3nksrefyum5x400f9gs3ak9yllcr8czhrlnjufuuy7n5mh", 40000, MemoBytes::empty()).unwrap());
orders.push(decode(5, "uregtest1wqgc0cm50a7a647qrdglgj62fl40q8njsrcfkt2mzlsmj979rdmsdwuysypc6ewxjxz0zc48kmm35jwx4q6c4fgqwkmmqyhwlep4n2hc0229vf6cahcnesr38y7gyzfx6pa8zg9jvv9", 50000, MemoBytes::empty()).unwrap());
orders.push(decode(6, "uregtest1usu9eyxgqu48sa8lqug6ccjc7vcam3mt3a5t7jvyxj7pq5dgdtkjgkqzsyh9pfeav9970xddp2c9h5x44drwnz4f0zwc894k3vt380g6kfsg9j9fmnpljye9r56d94njsv40uaam392xvmky2v38dh3yhayz44z6xv402slujuhwy3mg", 60000, MemoBytes::empty()).unwrap());
orders.push(decode(7, "uregtest1mxy5wq2n0xw57nuxa4lqpl358zw4vzyfgadsn5jungttmqcv6nx6cpx465dtpzjzw0vprjle4j4nqqzxtkuzm93regvgg4xce0un5ec6tedquc469zjhtdpkxz04kunqqyasv4rwvcweh3ue0ku0payn29stl2pwcrghyzscrrju9ar57rn36wgz74nmynwcyw27rjd8yk477l97ez8", 70000, MemoBytes::empty()).unwrap());
log::info!("Building tx plan");
let tx_plan = note_select_with_fee::<FeeFlat>(&utxos, &mut orders, &config).unwrap();
log::info!("Plan: {}", serde_json::to_string(&tx_plan).unwrap());
log::info!("Building tx");
let tx = build_tx(c.chain.network(), &keys, &tx_plan, context, OsRng).unwrap();
println!("{}", hex::encode(&tx));
}
#[tokio::test]
async fn submit_tx() {
let _ = env_logger::try_init();
init_coin(0, "./zec.db").unwrap();
set_coin_lwd_url(0, "http://127.0.0.1:9067");
set_active(0);
let tx = "";
let r = broadcast_tx(&hex::decode(tx).unwrap()).await.unwrap();
log::info!("{}", r);
}
#[test]
fn tree_state() {
let _ = env_logger::try_init();
init_coin(0, "./zec.db").unwrap();
set_coin_lwd_url(0, "http://127.0.0.1:9067");
let h = OrchardHasher::new();
let c = CoinConfig::get(0);
let db = c.db.as_ref().unwrap();
let db = db.lock().unwrap();
let tree = db.get_tree_by_name(235, "orchard").unwrap();
let TreeCheckpoint { tree, witnesses } = tree;
let root = tree.root(32, &ORCHARD_ROOTS, &h);
println!("{}", hex::encode(root));
for witness in witnesses.iter() {
let auth_path: Vec<_> = witness.auth_path(32, &ORCHARD_ROOTS, &h).iter()
.map(|n| {
orchard::tree::MerkleHashOrchard::from_bytes(n).unwrap()
}).collect();
let merkle_path = orchard::tree::MerklePath::from_parts(witness.position as u32, auth_path.try_into().unwrap());
}
}

View File

@ -1,5 +1,4 @@
use std::cmp::max;
use zcash_primitives::memo::MemoBytes;
use crate::note_selection::types::*;
const MARGINAL_FEE: u64 = 5000;

View File

@ -1,8 +1,8 @@
use std::cmp::min;
use zcash_address::{AddressKind, ZcashAddress};
use zcash_address::unified::{Container, Receiver};
use zcash_primitives::memo::{Memo, MemoBytes};
use crate::note_selection::types::{PrivacyPolicy, NoteSelectConfig, Fill, Execution, Order, Pool, PoolAllocation, Destination, PoolPrecedence};
use zcash_primitives::memo::MemoBytes;
use crate::note_selection::types::{PrivacyPolicy, Fill, Execution, Order, Pool, PoolAllocation, Destination, PoolPrecedence};
/// Decode address and return it as an order
///
@ -126,9 +126,10 @@ fn fill_order(from: Pool, to: Pool, order: &mut Order, initial_pool: &PoolAlloca
id_order: order.id,
destination: destination.clone(),
amount,
memo: order.memo.clone(),
is_fee: order.is_fee,
};
log::info!("{:?}", execution);
log::debug!("{:?}", execution);
executions.push(execution);
}
assert!(order.amount == order.filled || initial_pool.0[from] == fills.0[from]); // fill must be to the max

View File

@ -1,6 +1,5 @@
use std::cmp::min;
use std::slice;
use anyhow::anyhow;
use zcash_primitives::memo::MemoBytes;
use crate::note_selection::decode;
use crate::note_selection::fee::FeeCalculator;
@ -22,10 +21,6 @@ pub fn select_notes(allocation: &PoolAllocation, utxos: &[UTXO]) -> anyhow::Resu
Ok(selected)
}
fn has_unfilled(orders: &[Order]) -> bool {
orders.iter().any(|o| o.filled != o.amount)
}
struct OrderExecutor {
pub pool_available: PoolAllocation,
pub pool_used: PoolAllocation,
@ -85,7 +80,7 @@ pub fn note_select_with_fee<F: FeeCalculator>(utxos: &[UTXO], orders: &mut [Orde
if fee == 0 {
let notes = executor.select_notes(utxos)?;
fee = F::calculate_fee(&notes, &executor.fills);
log::info!("base fee: {}", fee);
log::debug!("base fee: {}", fee);
}
let mut fee_order = Order {
id: u32::MAX,
@ -101,7 +96,7 @@ pub fn note_select_with_fee<F: FeeCalculator>(utxos: &[UTXO], orders: &mut [Orde
let prec_1 = config.precedence.iter().filter(|&&p| pool_needed.0[p as usize] != 0);
let prec_2 = config.precedence.iter().filter(|&&p| pool_needed.0[p as usize] == 0);
let fee_precedence: PoolPrecedence = prec_1.chain(prec_2).cloned().collect::<Vec<_>>().try_into().unwrap();
log::info!("Fee precedence: {:?}", fee_precedence);
log::debug!("Fee precedence: {:?}", fee_precedence);
if !executor.execute(slice::from_mut(&mut fee_order), &fee_precedence)? {
anyhow::bail!("Unsufficient Funds [fees]")
@ -116,9 +111,9 @@ pub fn note_select_with_fee<F: FeeCalculator>(utxos: &[UTXO], orders: &mut [Orde
let total_spent = pool_spent.total();
let change = pool_spent - pool_needed; // must be >= 0 because the note selection covers the fills
log::info!("pool_needed: {:?} {}", pool_needed, total_needed);
log::info!("pool_spent: {:?} {}", pool_spent, total_spent);
log::info!("change: {:?}", change);
log::debug!("pool_needed: {:?} {}", pool_needed, total_needed);
log::debug!("pool_spent: {:?} {}", pool_spent, total_spent);
log::debug!("change: {:?}", change);
for pool in 0..3 {
if change.0[pool] != 0 {
@ -126,6 +121,7 @@ pub fn note_select_with_fee<F: FeeCalculator>(utxos: &[UTXO], orders: &mut [Orde
id_order: u32::MAX,
destination: change_destinations[pool].unwrap(),
amount: change.0[pool],
memo: MemoBytes::empty(),
is_fee: false
})
}
@ -133,7 +129,7 @@ pub fn note_select_with_fee<F: FeeCalculator>(utxos: &[UTXO], orders: &mut [Orde
let notes = executor.select_notes(utxos)?;
let new_fee = F::calculate_fee(&notes, &executor.fills);
log::info!("new fee: {}", new_fee);
log::debug!("new fee: {}", new_fee);
if new_fee == fee || n_attempts == MAX_ATTEMPTS {
let plan = TransactionPlan {

View File

@ -3,7 +3,7 @@ use zcash_primitives::memo::Memo;
use crate::{CoinConfig, init_coin, set_coin_lwd_url};
use crate::api::payment::RecipientMemo;
use crate::unified::UnifiedAddressType;
use super::{*, types::*, fill::*};
use super::{*, types::*};
// must have T+S+O receivers
const CHANGE_ADDRESS: &str = "u1pncsxa8jt7aq37r8uvhjrgt7sv8a665hdw44rqa28cd9t6qqmktzwktw772nlle6skkkxwmtzxaan3slntqev03g70tzpky3c58hfgvfjkcky255cwqgfuzdjcktfl7pjalt5sl33se75pmga09etn9dplr98eq2g8cgmvgvx6jx2a2xhy39x96c6rumvlyt35whml87r064qdzw30e";
@ -19,7 +19,7 @@ fn init() {
#[tokio::test]
async fn test_fetch_utxo() {
init();
let utxos = fetch_utxos(0, 1, 1900000, true, 3).await.unwrap();
let utxos = fetch_utxos(0, 1, 235, true, 0).await.unwrap();
for utxo in utxos.iter() {
log::info!("{:?}", utxo);
@ -69,28 +69,6 @@ async fn test_payment() {
assert_eq!(tx_plan.spends[0].amount, tx_plan.outputs[0].amount + tx_plan.outputs[1].amount + tx_plan.fee);
}
fn mock_order(id: u32, amount: u64, tpe: u8) -> Order {
assert!(tpe > 0 && tpe < 8);
let mut destinations = [None; 3];
if tpe & 1 != 0 {
destinations[0] = Some(Destination::Transparent([0u8; 20]));
}
if tpe & 2 != 0 {
destinations[1] = Some(Destination::Sapling([0u8; 43]));
}
if tpe & 4 != 0 {
destinations[2] = Some(Destination::Orchard([0u8; 43]));
}
Order {
id,
destinations,
amount,
memo: MemoBytes::empty(),
is_fee: false,
filled: 0,
}
}
macro_rules! order {
($id:expr, $q:expr, $destinations:expr) => {
Order {
@ -361,7 +339,7 @@ fn test_example8() {
#[test]
fn test_example9() {
let _ = env_logger::try_init();
let mut config = NoteSelectConfig::new(CHANGE_ADDRESS);
let config = NoteSelectConfig::new(CHANGE_ADDRESS);
let utxos = [utxo!(1, 50), sapling!(2, 50)];
let mut orders = [o!(1, 10)];

View File

@ -1,5 +1,5 @@
use std::ops::{Add, Sub};
use zcash_primitives::memo::{Memo, MemoBytes};
use zcash_primitives::memo::MemoBytes;
use serde::Serialize;
use serde_with::serde_as;
use serde_hex::{SerHex,Strict};
@ -53,8 +53,7 @@ pub struct Order {
pub id: u32,
pub destinations: [Option<Destination>; 3],
pub amount: u64,
#[serde(with = "MemoBytesProxy")]
pub memo: MemoBytes,
#[serde(with = "MemoBytesProxy")] pub memo: MemoBytes,
pub is_fee: bool,
pub filled: u64, // mutable
@ -97,6 +96,7 @@ pub struct Fill {
pub id_order: u32,
pub destination: Destination,
pub amount: u64,
#[serde(with = "MemoBytesProxy")] pub memo: MemoBytes,
#[serde(skip)]
pub is_fee: bool,
}
@ -107,7 +107,7 @@ pub struct Execution {
pub fills: Vec<Fill>,
}
#[derive(Serialize)]
#[derive(Serialize, Default)]
pub struct TransactionPlan {
pub spends: Vec<UTXO>,
pub outputs: Vec<Fill>,

View File

@ -1,7 +1,23 @@
use lazy_static::lazy_static;
use lazycell::AtomicLazyCell;
use orchard::circuit::ProvingKey;
lazy_static! {
pub static ref PROVING_KEY: AtomicLazyCell<ProvingKey> = AtomicLazyCell::new();
}
mod hash;
mod note;
mod key;
pub use note::{OrchardDecrypter, OrchardViewKey, DecryptedOrchardNote};
pub use hash::OrchardHasher;
pub use hash::{ORCHARD_ROOTS, OrchardHasher};
pub use key::{derive_orchard_keys, OrchardKeyBytes};
pub fn get_proving_key() -> &'static ProvingKey {
if !PROVING_KEY.filled() {
log::info!("Building Orchard proving key");
let _ = PROVING_KEY.fill(ProvingKey::build());
}
PROVING_KEY.borrow().unwrap()
}

View File

@ -72,17 +72,6 @@ impl OrchardHasher {
}
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 {

View File

@ -52,6 +52,7 @@ impl DecryptedNote<OrchardDomain, OrchardViewKey> for DecryptedOrchardNote {
}
fn to_received_note(&self, _position: u64) -> ReceivedNote {
log::info!("Note {:?}", self.note);
ReceivedNote {
account: self.vk.account,
height: self.output_position.height,
@ -103,9 +104,9 @@ pub fn test_decrypt() -> anyhow::Result<()> {
// let mut cmx = hex::decode("df45e00eb39e4c281e2804a366d3010b7f663724472d12637e0a749e6ce22719").unwrap();
// let ciphertext = hex::decode("d9bc6ee09b0afde5dd69bfdf4b667a38da3e1084e84eb6752d54800b9f5110203b60496ab5313dba3f2acb9ef30bcaf68fbfcc59").unwrap();
let mut nullifier = hex::decode("ea1b97cc83d326db4130433022f68dd32a0bc707448b19b0980e4e6404412b29").unwrap();
let mut epk = hex::decode("e2f666e905666f29bb678c694602b2768bea655c0f2b18f9c342ad8b64b18c0c").unwrap();
let mut cmx = hex::decode("4a95dbf0d1d0cac1376a0b8fb0fc2ed2843d0e2670dd976a63386b293f30de25").unwrap();
let nullifier = hex::decode("ea1b97cc83d326db4130433022f68dd32a0bc707448b19b0980e4e6404412b29").unwrap();
let epk = hex::decode("e2f666e905666f29bb678c694602b2768bea655c0f2b18f9c342ad8b64b18c0c").unwrap();
let cmx = hex::decode("4a95dbf0d1d0cac1376a0b8fb0fc2ed2843d0e2670dd976a63386b293f30de25").unwrap();
let ciphertext = hex::decode("73640095a90bb03d14f687d6acf4822618a3def1da3b71a588da1c68e25042f7c9aa759778e73aa2bb39d1061e51c1e8cf5e0bce").unwrap();
let db_builder = DbAdapterBuilder {

View File

@ -12,7 +12,7 @@ mod hash;
mod note;
pub use note::{SaplingDecrypter, SaplingViewKey, DecryptedSaplingNote};
pub use hash::SaplingHasher;
pub use hash::{SaplingHasher, SAPLING_ROOTS};
fn read_generators_bin() -> Vec<ExtendedNielsPoint> {
let mut generators_bin = GENERATORS;

View File

@ -1,10 +1,19 @@
use ff::PrimeField;
use group::Curve;
use jubjub::{AffinePoint, ExtendedPoint, Fr};
use lazy_static::lazy_static;
use zcash_primitives::constants::PEDERSEN_HASH_CHUNKS_PER_GENERATOR;
use crate::sync::{Hasher, Node};
use crate::Hash;
use super::GENERATORS_EXP;
lazy_static! {
pub static ref SAPLING_ROOTS: Vec<Hash> = {
let h = SaplingHasher {};
h.empty_roots(32)
};
}
#[inline(always)]
fn accumulate_scalar(acc: &mut Fr, cur: &mut Fr, x: u8) {
let mut tmp = *cur;

View File

@ -101,6 +101,7 @@ pub async fn sync_async<'a>(
return Ok(());
}
let mut height = start_height;
let (blocks_tx, mut blocks_rx) = mpsc::channel::<Blocks>(1);
tokio::spawn(async move {
download_chain(&mut client, start_height, end_height, prev_hash, max_cost, cancel, blocks_tx).await?;
@ -128,7 +129,7 @@ pub async fn sync_async<'a>(
db_builder.clone(),
"sapling".to_string(),
);
synchronizer.initialize()?;
synchronizer.initialize(height)?;
synchronizer.process(&blocks.0)?;
}
@ -144,12 +145,13 @@ pub async fn sync_async<'a>(
db_builder.clone(),
"orchard".to_string(),
);
synchronizer.initialize()?;
synchronizer.initialize(height)?;
synchronizer.process(&blocks.0)?;
}
let db = db_builder.build()?;
db.store_block_timestamp(last_height, &last_hash, last_timestamp)?;
height = last_height;
}
Ok(())

View File

@ -53,17 +53,15 @@ impl <N: Parameters + Sync,
}
}
pub fn initialize(&mut self) -> Result<()> {
pub fn initialize(&mut self, height: u32) -> Result<()> {
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(height, &self.shielded_pool)?;
self.tree = tree;
self.witnesses = witnesses;
self.note_position = self.tree.get_position();
for vk in self.vks.iter() {
let nfs = db.get_unspent_nullifiers()?;
for rn in nfs.into_iter() {
self.nullifiers.insert(rn.nf.clone(), rn);
}
let nfs = db.get_unspent_nullifiers()?;
for rn in nfs.into_iter() {
self.nullifiers.insert(rn.nf.clone(), rn);
}
Ok(())
}
@ -178,7 +176,7 @@ mod tests {
_phantom: Default::default()
};
synchronizer.initialize().unwrap();
synchronizer.initialize(1000).unwrap();
synchronizer.process(&vec![]).unwrap();
}

View File

@ -4,6 +4,7 @@ use std::marker::PhantomData;
use byteorder::WriteBytesExt;
use group::Curve;
use zcash_encoding::{Optional, Vector};
use crate::Hash;
pub type Node = [u8; 32];
@ -15,6 +16,17 @@ pub trait Hasher: Clone + Sync {
fn node_combine_extended(&self, depth: u8, left: &Node, right: &Node) -> Self::Extended;
fn normalize(&self, extended: &[Self::Extended]) -> Vec<Node>;
fn empty_roots(&self, height: usize) -> Vec<Hash> {
let mut roots = vec![];
let mut cur = Self::uncommited_node();
roots.push(cur);
for depth in 0..height {
cur = self.node_combine(depth as u8, &cur, &cur);
roots.push(cur);
}
roots
}
}
#[derive(Clone)]
@ -145,7 +157,7 @@ impl Witness {
}
}
pub fn auth_path<H: Hasher>(&self, height: usize, empty_roots: &[Node], hasher: H) -> Vec<Node> {
pub fn auth_path<H: Hasher>(&self, height: usize, empty_roots: &[Node], hasher: &H) -> Vec<Node> {
let mut filled_iter = self.filled.iter();
let mut cursor_used = false;
let mut next_filler = move |depth: usize| {
@ -153,7 +165,7 @@ impl Witness {
*f
} else if !cursor_used {
cursor_used = true;
self.cursor.root(depth, empty_roots, &hasher)
self.cursor.root(depth, empty_roots, hasher)
} else {
empty_roots[depth]
}

View File

@ -1,10 +1,8 @@
use anyhow::anyhow;
use orchard::Address;
use orchard::keys::{FullViewingKey, Scope};
use rusqlite::Connection;
use zcash_address::{ToAddress, unified, ZcashAddress};
use zcash_address::unified::{Container, Encoding, Receiver};
use zcash_client_backend::address::RecipientAddress;
use zcash_client_backend::encoding::{AddressCodec, decode_payment_address, encode_payment_address};
use zcash_primitives::consensus::{Network, Parameters};
use zcash_primitives::legacy::TransparentAddress;
@ -58,7 +56,6 @@ pub fn get_unified_address(network: &Network, db: &DbAdapter, account: u32, tpe:
rcvs.push(rcv);
}
if tpe.orchard {
let AccountData { address: zaddr, .. } = db.get_account_info(account)?;
let okey = db.get_orchard(account)?;
if let Some(okey) = okey {
let fvk = FullViewingKey::from_bytes(&okey.fvk).unwrap();