tx builder
This commit is contained in:
parent
9e4ce62bd2
commit
74c26bf87d
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
use crate::api::payment::RecipientShort;
|
||||
|
||||
async fn prepare_payment_v2(recipients: &[RecipientShort]) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
47
src/db.rs
47
src/db.rs
|
@ -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()?;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -3,6 +3,7 @@ mod fee;
|
|||
mod utxo;
|
||||
mod fill;
|
||||
mod select;
|
||||
mod builder;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
use std::cmp::max;
|
||||
use zcash_primitives::memo::MemoBytes;
|
||||
use crate::note_selection::types::*;
|
||||
|
||||
const MARGINAL_FEE: u64 = 5000;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(¬es, &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(¬es, &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 {
|
||||
|
|
|
@ -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)];
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(())
|
||||
|
|
14
src/sync.rs
14
src/sync.rs
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue