2023-02-21 00:36:29 -08:00
|
|
|
use crate::api::payment_v2::build_tx_plan_with_utxos;
|
|
|
|
use crate::api::recipient::RecipientMemo;
|
|
|
|
use crate::chain::{get_checkpoint_height, EXPIRY_HEIGHT_OFFSET};
|
2022-06-08 05:48:16 -07:00
|
|
|
use crate::coinconfig::CoinConfig;
|
2022-09-04 04:19:49 -07:00
|
|
|
use crate::db::AccountData;
|
2023-03-06 02:52:00 -08:00
|
|
|
use crate::key2::split_key;
|
2023-02-21 00:36:29 -08:00
|
|
|
use crate::note_selection::{SecretKeys, Source, UTXO};
|
|
|
|
use crate::unified::orchard_as_unified;
|
2023-02-13 19:54:39 -08:00
|
|
|
use crate::{
|
2023-02-25 15:53:50 -08:00
|
|
|
broadcast_tx, build_tx, AddressList, BlockId, CompactTxStreamerClient, GetAddressUtxosArg,
|
|
|
|
GetAddressUtxosReply, Hash, TransparentAddressBlockFilter, TxFilter,
|
2023-02-13 19:54:39 -08:00
|
|
|
};
|
2022-07-16 20:23:56 -07:00
|
|
|
use anyhow::anyhow;
|
2022-07-23 06:25:08 -07:00
|
|
|
use base58check::FromBase58Check;
|
2021-07-16 01:42:29 -07:00
|
|
|
use bip39::{Language, Mnemonic, Seed};
|
2023-02-21 00:36:29 -08:00
|
|
|
use core::slice;
|
2023-02-13 19:54:39 -08:00
|
|
|
use futures::StreamExt;
|
2023-02-21 00:36:29 -08:00
|
|
|
use rand::rngs::OsRng;
|
2022-06-07 09:58:24 -07:00
|
|
|
use ripemd::{Digest, Ripemd160};
|
2021-07-16 01:42:29 -07:00
|
|
|
use secp256k1::{All, PublicKey, Secp256k1, SecretKey};
|
|
|
|
use sha2::Sha256;
|
2023-02-25 15:53:50 -08:00
|
|
|
use std::collections::HashMap;
|
2021-07-16 01:42:29 -07:00
|
|
|
use tiny_hderive::bip32::ExtendedPrivKey;
|
2021-07-09 06:33:05 -07:00
|
|
|
use tonic::transport::Channel;
|
|
|
|
use tonic::Request;
|
2021-11-11 17:39:50 -08:00
|
|
|
use zcash_client_backend::encoding::encode_transparent_address;
|
2023-02-25 15:53:50 -08:00
|
|
|
use zcash_params::coin::get_branch;
|
2022-03-07 06:47:06 -08:00
|
|
|
use zcash_primitives::consensus::{Network, Parameters};
|
2021-11-11 17:39:50 -08:00
|
|
|
use zcash_primitives::legacy::TransparentAddress;
|
2023-02-21 00:36:29 -08:00
|
|
|
use zcash_primitives::memo::Memo;
|
2023-02-25 15:53:50 -08:00
|
|
|
use zcash_primitives::transaction::components::OutPoint;
|
|
|
|
use zcash_primitives::transaction::Transaction;
|
2021-07-09 06:33:05 -07:00
|
|
|
|
2021-07-16 01:42:29 -07:00
|
|
|
pub async fn get_taddr_balance(
|
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
|
|
|
address: &str,
|
|
|
|
) -> anyhow::Result<u64> {
|
2021-07-09 06:33:05 -07:00
|
|
|
let req = AddressList {
|
|
|
|
addresses: vec![address.to_string()],
|
|
|
|
};
|
2021-07-16 01:42:29 -07:00
|
|
|
let rep = client
|
|
|
|
.get_taddress_balance(Request::new(req))
|
|
|
|
.await?
|
|
|
|
.into_inner();
|
2021-07-09 06:33:05 -07:00
|
|
|
Ok(rep.value_zat as u64)
|
|
|
|
}
|
|
|
|
|
2023-02-25 15:53:50 -08:00
|
|
|
pub struct TransparentTxInfo {
|
|
|
|
pub txid: Hash,
|
|
|
|
pub height: u32,
|
|
|
|
pub timestamp: u32,
|
|
|
|
pub inputs: Vec<OutPoint>,
|
|
|
|
pub in_value: u64,
|
|
|
|
pub out_value: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
/* With the current LWD API, this function performs poorly because
|
|
|
|
the server does not return tx timestamp, height and value
|
|
|
|
*/
|
|
|
|
#[allow(unused)]
|
|
|
|
pub async fn get_ttx_history(
|
|
|
|
network: &Network,
|
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
|
|
|
address: &str,
|
|
|
|
) -> anyhow::Result<Vec<TransparentTxInfo>> {
|
|
|
|
let mut rep = client
|
|
|
|
.get_taddress_txids(Request::new(TransparentAddressBlockFilter {
|
|
|
|
address: address.to_string(),
|
|
|
|
range: None,
|
|
|
|
}))
|
|
|
|
.await?
|
|
|
|
.into_inner();
|
|
|
|
let mut heights: HashMap<u32, u32> = HashMap::new();
|
|
|
|
let mut txs = vec![];
|
|
|
|
while let Some(raw_tx) = rep.message().await? {
|
|
|
|
let height = raw_tx.height as u32;
|
|
|
|
heights.insert(height, 0);
|
|
|
|
let consensus_branch_id = get_branch(network, height);
|
|
|
|
let tx = Transaction::read(&*raw_tx.data, consensus_branch_id)?;
|
|
|
|
let txid = tx.txid();
|
|
|
|
let tx_data = tx.into_data();
|
|
|
|
let mut inputs = vec![];
|
|
|
|
if let Some(transparent_bundle) = tx_data.transparent_bundle() {
|
|
|
|
for vin in transparent_bundle.vin.iter() {
|
|
|
|
inputs.push(vin.prevout.clone());
|
|
|
|
}
|
|
|
|
let out_value = transparent_bundle
|
|
|
|
.vout
|
|
|
|
.iter()
|
|
|
|
.map(|vout| i64::from(vout.value))
|
|
|
|
.sum::<i64>() as u64;
|
|
|
|
txs.push(TransparentTxInfo {
|
|
|
|
txid: txid.as_ref().clone(),
|
|
|
|
height,
|
|
|
|
timestamp: 0,
|
|
|
|
inputs,
|
|
|
|
in_value: 0,
|
|
|
|
out_value,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (h, timestamp) in heights.iter_mut() {
|
|
|
|
let block = client
|
|
|
|
.get_block(Request::new(BlockId {
|
|
|
|
height: *h as u64,
|
|
|
|
hash: vec![],
|
|
|
|
}))
|
|
|
|
.await?
|
|
|
|
.into_inner();
|
|
|
|
*timestamp = block.time;
|
|
|
|
}
|
|
|
|
|
|
|
|
for tx in txs.iter_mut() {
|
|
|
|
let mut in_value = 0;
|
|
|
|
for input in tx.inputs.iter() {
|
|
|
|
let raw_tx = client
|
|
|
|
.get_transaction(Request::new(TxFilter {
|
|
|
|
block: None,
|
|
|
|
index: 0,
|
|
|
|
hash: input.hash().to_vec(),
|
|
|
|
}))
|
|
|
|
.await?
|
|
|
|
.into_inner();
|
|
|
|
let consensus_branch_id = get_branch(network, raw_tx.height as u32);
|
|
|
|
let tx = Transaction::read(&*raw_tx.data, consensus_branch_id)?;
|
|
|
|
let tx_data = tx.into_data();
|
|
|
|
let transparent_bundle = tx_data
|
|
|
|
.transparent_bundle()
|
|
|
|
.ok_or(anyhow!("No transparent bundle"))?;
|
|
|
|
let value = i64::from(transparent_bundle.vout[input.n() as usize].value);
|
|
|
|
in_value += value;
|
|
|
|
}
|
|
|
|
tx.timestamp = heights[&tx.height];
|
|
|
|
tx.in_value = in_value as u64;
|
|
|
|
tx.inputs.clear();
|
|
|
|
}
|
|
|
|
Ok(txs)
|
|
|
|
}
|
|
|
|
|
2023-02-13 19:54:39 -08:00
|
|
|
pub async fn get_taddr_tx_count(
|
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
|
|
|
address: &str,
|
|
|
|
) -> anyhow::Result<u32> {
|
|
|
|
let req = TransparentAddressBlockFilter {
|
|
|
|
address: address.to_string(),
|
|
|
|
range: None,
|
|
|
|
};
|
|
|
|
let rep = client
|
|
|
|
.get_taddress_txids(Request::new(req))
|
|
|
|
.await?
|
|
|
|
.into_inner();
|
|
|
|
let count = rep.count().await;
|
|
|
|
Ok(count as u32)
|
|
|
|
}
|
|
|
|
|
2021-11-11 17:39:50 -08:00
|
|
|
pub async fn get_utxos(
|
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
2022-06-08 05:48:16 -07:00
|
|
|
t_address: &str,
|
2021-11-11 17:39:50 -08:00
|
|
|
) -> anyhow::Result<Vec<GetAddressUtxosReply>> {
|
2022-06-08 05:48:16 -07:00
|
|
|
let req = GetAddressUtxosArg {
|
|
|
|
addresses: vec![t_address.to_string()],
|
|
|
|
start_height: 0,
|
|
|
|
max_entries: 0,
|
|
|
|
};
|
|
|
|
let utxo_rep = client
|
|
|
|
.get_address_utxos(Request::new(req))
|
|
|
|
.await?
|
|
|
|
.into_inner();
|
|
|
|
Ok(utxo_rep.address_utxos)
|
2021-07-09 06:33:05 -07:00
|
|
|
}
|
|
|
|
|
2022-06-07 09:58:24 -07:00
|
|
|
pub async fn scan_transparent_accounts(
|
|
|
|
network: &Network,
|
|
|
|
client: &mut CompactTxStreamerClient<Channel>,
|
|
|
|
gap_limit: usize,
|
2022-12-30 07:29:59 -08:00
|
|
|
) -> anyhow::Result<Vec<TBalance>> {
|
2022-06-08 05:48:16 -07:00
|
|
|
let c = CoinConfig::get_active();
|
2022-06-07 09:58:24 -07:00
|
|
|
let mut addresses = vec![];
|
2022-06-08 05:48:16 -07:00
|
|
|
let db = c.db()?;
|
2022-09-04 04:19:49 -07:00
|
|
|
let account_data = db.get_account_info(c.id_account)?;
|
|
|
|
let AccountData {
|
|
|
|
seed, mut aindex, ..
|
|
|
|
} = account_data;
|
2022-06-07 09:58:24 -07:00
|
|
|
if let Some(seed) = seed {
|
|
|
|
let mut gap = 0;
|
|
|
|
while gap < gap_limit {
|
2022-09-04 04:19:49 -07:00
|
|
|
let bip44_path = format!("m/44'/{}'/0'/0/{}", network.coin_type(), aindex);
|
|
|
|
log::info!("{} {}", aindex, bip44_path);
|
2022-06-07 09:58:24 -07:00
|
|
|
let (_, address) = derive_tkeys(network, &seed, &bip44_path)?;
|
|
|
|
let balance = get_taddr_balance(client, &address).await?;
|
|
|
|
if balance > 0 {
|
|
|
|
addresses.push(TBalance {
|
2022-09-04 04:19:49 -07:00
|
|
|
index: aindex,
|
2022-06-07 09:58:24 -07:00
|
|
|
address,
|
|
|
|
balance,
|
|
|
|
});
|
|
|
|
gap = 0;
|
|
|
|
} else {
|
2023-02-13 19:54:39 -08:00
|
|
|
let tx_count = get_taddr_tx_count(client, &address).await?;
|
|
|
|
if tx_count != 0 {
|
|
|
|
gap = 0;
|
|
|
|
} else {
|
|
|
|
gap += 1;
|
|
|
|
}
|
2022-06-07 09:58:24 -07:00
|
|
|
}
|
2022-09-04 04:19:49 -07:00
|
|
|
aindex += 1;
|
2022-06-07 09:58:24 -07:00
|
|
|
}
|
|
|
|
}
|
2022-12-30 07:29:59 -08:00
|
|
|
Ok(addresses)
|
2022-06-07 09:58:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn derive_tkeys(
|
|
|
|
network: &Network,
|
|
|
|
phrase: &str,
|
|
|
|
path: &str,
|
|
|
|
) -> anyhow::Result<(String, String)> {
|
2023-03-06 02:52:00 -08:00
|
|
|
let (phrase, password) = split_key(phrase);
|
|
|
|
let mnemonic = Mnemonic::from_phrase(&phrase, Language::English)?;
|
|
|
|
let seed = Seed::new(&mnemonic, &password);
|
2022-07-16 20:23:56 -07:00
|
|
|
let ext = ExtendedPrivKey::derive(seed.as_bytes(), path)
|
|
|
|
.map_err(|_| anyhow!("Invalid derivation path"))?;
|
|
|
|
let secret_key = SecretKey::from_slice(&ext.secret())?;
|
2022-07-23 06:25:08 -07:00
|
|
|
derive_from_secretkey(network, &secret_key)
|
|
|
|
}
|
|
|
|
|
2023-02-21 00:36:29 -08:00
|
|
|
pub fn parse_seckey(key: &str) -> anyhow::Result<SecretKey> {
|
2022-07-23 06:25:08 -07:00
|
|
|
let (_, sk) = key.from_base58check().map_err(|_| anyhow!("Invalid key"))?;
|
|
|
|
let sk = &sk[0..sk.len() - 1]; // remove compressed pub key marker
|
|
|
|
let secret_key = SecretKey::from_slice(&sk)?;
|
2023-02-21 00:36:29 -08:00
|
|
|
Ok(secret_key)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn derive_taddr(network: &Network, key: &str) -> anyhow::Result<(SecretKey, String)> {
|
|
|
|
let secret_key = parse_seckey(key)?;
|
|
|
|
let (_, addr) = derive_from_secretkey(network, &secret_key)?;
|
|
|
|
Ok((secret_key, addr))
|
2022-07-23 06:25:08 -07:00
|
|
|
}
|
|
|
|
|
2022-07-26 19:11:36 -07:00
|
|
|
pub fn derive_from_secretkey(
|
|
|
|
network: &Network,
|
|
|
|
sk: &SecretKey,
|
|
|
|
) -> anyhow::Result<(String, String)> {
|
2022-07-23 06:25:08 -07:00
|
|
|
let secp = Secp256k1::<All>::new();
|
|
|
|
let pub_key = PublicKey::from_secret_key(&secp, &sk);
|
2021-07-09 06:33:05 -07:00
|
|
|
let pub_key = pub_key.serialize();
|
|
|
|
let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key));
|
|
|
|
let address = TransparentAddress::PublicKey(pub_key.into());
|
2021-07-16 01:42:29 -07:00
|
|
|
let address = encode_transparent_address(
|
2022-03-07 06:47:06 -08:00
|
|
|
&network.b58_pubkey_address_prefix(),
|
|
|
|
&network.b58_script_address_prefix(),
|
2021-07-16 01:42:29 -07:00
|
|
|
&address,
|
|
|
|
);
|
2022-07-23 06:25:08 -07:00
|
|
|
let sk = sk.display_secret().to_string();
|
2021-07-09 06:33:05 -07:00
|
|
|
Ok((sk, address))
|
|
|
|
}
|
2022-06-07 09:58:24 -07:00
|
|
|
|
2023-02-21 00:36:29 -08:00
|
|
|
pub async fn sweep_tkey(
|
|
|
|
last_height: u32,
|
|
|
|
sk: &str,
|
|
|
|
pool: u8,
|
|
|
|
confirmations: u32,
|
|
|
|
) -> anyhow::Result<String> {
|
|
|
|
let c = CoinConfig::get_active();
|
|
|
|
let network = c.chain.network();
|
|
|
|
let (seckey, from_address) = derive_taddr(network, sk)?;
|
|
|
|
|
|
|
|
let (checkpoint_height, to_address) = {
|
|
|
|
let db = c.db().unwrap();
|
|
|
|
let checkpoint_height = get_checkpoint_height(&db, last_height, confirmations)?;
|
|
|
|
|
|
|
|
let to_address = match pool {
|
|
|
|
0 => db.get_taddr(c.id_account)?,
|
|
|
|
1 => {
|
|
|
|
let AccountData { address, .. } = db.get_account_info(c.id_account)?;
|
|
|
|
Some(address)
|
|
|
|
}
|
|
|
|
2 => {
|
|
|
|
let okeys = db.get_orchard(c.id_account)?;
|
|
|
|
okeys.map(|okeys| {
|
|
|
|
let address = okeys.get_address(0);
|
|
|
|
orchard_as_unified(network, &address).encode()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
let to_address = to_address.ok_or(anyhow!("Account has no address of this type"))?;
|
|
|
|
(checkpoint_height, to_address)
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut client = c.connect_lwd().await?;
|
|
|
|
let utxos = get_utxos(&mut client, &from_address).await?;
|
|
|
|
let balance = utxos.iter().map(|utxo| utxo.value_zat).sum::<i64>();
|
|
|
|
println!("balance {}", balance);
|
|
|
|
let utxos: Vec<_> = utxos
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(i, utxo)| UTXO {
|
|
|
|
id: i as u32,
|
|
|
|
source: Source::Transparent {
|
|
|
|
txid: utxo.txid.clone().try_into().unwrap(),
|
|
|
|
index: utxo.index as u32,
|
|
|
|
},
|
|
|
|
amount: utxo.value_zat as u64,
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
let recipient = RecipientMemo {
|
|
|
|
address: to_address,
|
|
|
|
amount: balance as u64,
|
|
|
|
fee_included: true,
|
|
|
|
memo: Memo::default(),
|
|
|
|
max_amount_per_note: 0,
|
|
|
|
};
|
|
|
|
println!("build_tx_plan_with_utxos");
|
|
|
|
let tx_plan = build_tx_plan_with_utxos(
|
|
|
|
c.coin,
|
|
|
|
c.id_account,
|
|
|
|
checkpoint_height,
|
|
|
|
last_height + EXPIRY_HEIGHT_OFFSET,
|
|
|
|
slice::from_ref(&recipient),
|
|
|
|
&utxos,
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
let skeys = SecretKeys {
|
|
|
|
transparent: Some(seckey),
|
|
|
|
sapling: None,
|
|
|
|
orchard: None,
|
|
|
|
};
|
|
|
|
println!("build_tx");
|
|
|
|
let tx = build_tx(network, &skeys, &tx_plan, OsRng)?;
|
|
|
|
println!("broadcast_tx");
|
|
|
|
let txid = broadcast_tx(&tx).await?;
|
|
|
|
Ok(txid)
|
|
|
|
}
|
|
|
|
|
2022-06-07 09:58:24 -07:00
|
|
|
pub struct TBalance {
|
|
|
|
pub index: u32,
|
|
|
|
pub address: String,
|
|
|
|
pub balance: u64,
|
|
|
|
}
|