Sweep taddr

This commit is contained in:
Hanh 2023-02-21 18:36:29 +10:00
parent 36c85febad
commit cda240c8e9
9 changed files with 201 additions and 42 deletions

View File

@ -14,6 +14,8 @@ typedef char bool;
typedef void *DartPostCObjectFnType;
#define EXPIRY_HEIGHT_OFFSET 50
#define QR_DATA_SIZE 256
#define MAX_ATTEMPTS 10
@ -230,6 +232,13 @@ struct CResult_____c_char sign_and_broadcast(uint8_t coin, uint32_t account, cha
struct CResult_____c_char broadcast_tx(char *tx_str);
bool is_valid_tkey(char *sk);
struct CResult_____c_char sweep_tkey(uint32_t last_height,
char *sk,
uint8_t pool,
uint32_t confirmations);
struct CResult_u32 get_activation_date(void);
struct CResult_u32 get_block_by_time(uint32_t time);

View File

@ -182,7 +182,7 @@ pub fn import_transparent_key(coin: u8, id_account: u32, path: &str) -> anyhow::
pub fn import_transparent_secret_key(coin: u8, id_account: u32, sk: &str) -> anyhow::Result<()> {
let c = CoinConfig::get(coin);
let db = c.db()?;
let (sk, addr) = derive_taddr(c.chain.network(), sk)?;
let (_, addr) = derive_taddr(c.chain.network(), sk)?;
db.store_transparent_key(id_account, &sk, &addr)?;
Ok(())
}

View File

@ -581,6 +581,25 @@ pub async unsafe extern "C" fn broadcast_tx(tx_str: *mut c_char) -> CResult<*mut
to_cresult_str(res.await)
}
#[no_mangle]
pub unsafe extern "C" fn is_valid_tkey(sk: *mut c_char) -> bool {
from_c_str!(sk);
crate::taddr::parse_seckey(&sk).is_ok()
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn sweep_tkey(
last_height: u32,
sk: *mut c_char,
pool: u8,
confirmations: u32,
) -> CResult<*mut c_char> {
from_c_str!(sk);
let txid = crate::taddr::sweep_tkey(last_height, &sk, pool, confirmations).await;
to_cresult_str(txid)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn get_activation_date() -> CResult<u32> {

View File

@ -2,9 +2,10 @@ use crate::api::account::get_unified_address;
use crate::api::recipient::RecipientMemo;
use crate::api::sync::get_latest_height;
pub use crate::broadcast_tx;
use crate::note_selection::{FeeFlat, Order};
use crate::chain::{get_checkpoint_height, EXPIRY_HEIGHT_OFFSET};
use crate::note_selection::{FeeFlat, Order, UTXO};
use crate::{
build_tx, fetch_utxos, get_secret_keys, note_selection, AccountData, CoinConfig, DbAdapter,
build_tx, fetch_utxos, get_secret_keys, note_selection, AccountData, CoinConfig,
TransactionBuilderConfig, TransactionBuilderError, TransactionPlan, TxBuilderContext,
MAX_ATTEMPTS,
};
@ -18,15 +19,13 @@ use zcash_primitives::transaction::builder::Progress;
#[allow(dead_code)]
type PaymentProgressCallback = Box<dyn Fn(Progress) + Send + Sync>;
const EXPIRY_HEIGHT_OFFSET: u32 = 50;
pub async fn build_tx_plan(
pub async fn build_tx_plan_with_utxos(
coin: u8,
account: u32,
last_height: u32,
checkpoint_height: u32,
expiry_height: u32,
recipients: &[RecipientMemo],
excluded_flags: u8,
confirmations: u32,
utxos: &[UTXO],
) -> note_selection::Result<TransactionPlan> {
let c = CoinConfig::get(coin);
let network = c.chain.network();
@ -41,12 +40,10 @@ pub async fn build_tx_plan(
}
}
let (fvk, checkpoint_height, expiry_height) = {
let fvk = {
let db = c.db()?;
let AccountData { fvk, .. } = db.get_account_info(account)?;
let checkpoint_height = get_checkpoint_height(&db, last_height, confirmations)?;
let latest_height = get_latest_height().await?;
(fvk, checkpoint_height, latest_height + EXPIRY_HEIGHT_OFFSET)
fvk
};
let change_address = get_unified_address(coin, account, 7)?;
let context = TxBuilderContext::from_height(coin, checkpoint_height)?;
@ -73,10 +70,9 @@ pub async fn build_tx_plan(
}
orders.last_mut().unwrap().take_fee = r.fee_included;
}
let utxos = fetch_utxos(coin, account, checkpoint_height, excluded_flags).await?;
let config = TransactionBuilderConfig::new(&change_address);
let tx_plan = crate::note_selection::build_tx_plan::<FeeFlat>(
let tx_plan = note_selection::build_tx_plan::<FeeFlat>(
network,
&fvk,
checkpoint_height,
@ -89,6 +85,34 @@ pub async fn build_tx_plan(
Ok(tx_plan)
}
pub async fn build_tx_plan(
coin: u8,
account: u32,
last_height: u32,
recipients: &[RecipientMemo],
excluded_flags: u8,
confirmations: u32,
) -> note_selection::Result<TransactionPlan> {
let (checkpoint_height, expiry_height) = {
let c = CoinConfig::get(coin);
let db = c.db()?;
let checkpoint_height = get_checkpoint_height(&db, last_height, confirmations)?;
let latest_height = get_latest_height().await?;
(checkpoint_height, latest_height + EXPIRY_HEIGHT_OFFSET)
};
let utxos = fetch_utxos(coin, account, checkpoint_height, excluded_flags).await?;
let tx_plan = build_tx_plan_with_utxos(
coin,
account,
checkpoint_height,
expiry_height,
recipients,
&utxos,
)
.await?;
Ok(tx_plan)
}
pub fn sign_plan(coin: u8, account: u32, tx_plan: &TransactionPlan) -> anyhow::Result<Vec<u8>> {
let c = CoinConfig::get(coin);
let fvk = {
@ -220,15 +244,3 @@ fn mark_spent(coin: u8, ids: &[u32]) -> anyhow::Result<()> {
db.tx_mark_spend(ids)?;
Ok(())
}
fn get_checkpoint_height(
db: &DbAdapter,
last_height: u32,
confirmations: u32,
) -> anyhow::Result<u32> {
let anchor_height = last_height.saturating_sub(confirmations);
let checkpoint_height = db
.get_checkpoint_height(anchor_height)?
.unwrap_or_else(|| db.sapling_activation_height()); // get the latest checkpoint before the requested anchor height
Ok(checkpoint_height)
}

View File

@ -2,6 +2,7 @@ use crate::db::AccountViewKey;
use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient;
use crate::lw_rpc::*;
use crate::scan::Blocks;
use crate::DbAdapter;
use ff::PrimeField;
use futures::{future, FutureExt};
use rand::prelude::SliceRandom;
@ -604,3 +605,17 @@ pub async fn get_best_server(servers: &str) -> anyhow::Result<String> {
struct Servers {
urls: Vec<String>,
}
pub const EXPIRY_HEIGHT_OFFSET: u32 = 50;
pub fn get_checkpoint_height(
db: &DbAdapter,
last_height: u32,
confirmations: u32,
) -> anyhow::Result<u32> {
let anchor_height = last_height.saturating_sub(confirmations);
let checkpoint_height = db
.get_checkpoint_height(anchor_height)?
.unwrap_or_else(|| db.sapling_activation_height()); // get the latest checkpoint before the requested anchor height
Ok(checkpoint_height)
}

View File

@ -3,7 +3,7 @@ pub use crate::note_selection::types::{
TransactionReport, UTXO,
};
pub use crate::note_selection::TransactionBuilderError::TxTooComplex;
pub use builder::{build_tx, get_secret_keys, TxBuilderContext};
pub use builder::{build_tx, get_secret_keys, SecretKeys, TxBuilderContext};
pub use fee::{FeeCalculator, FeeFlat, FeeZIP327};
pub use optimize::build_tx_plan;
use std::str::FromStr;

View File

@ -33,7 +33,7 @@ use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey};
pub struct SecretKeys {
pub transparent: Option<SecretKey>,
pub sapling: ExtendedSpendingKey,
pub sapling: Option<ExtendedSpendingKey>,
pub orchard: Option<SpendingKey>,
}
@ -82,8 +82,11 @@ pub fn build_tx(
TransparentAddress::PublicKey(pub_key.into())
});
let sapling_fvk = ExtendedFullViewingKey::from(&skeys.sapling);
let sapling_ovk = sapling_fvk.fvk.ovk;
let sapling_fvk = skeys
.sapling
.as_ref()
.map(|sk| ExtendedFullViewingKey::from(sk));
let sapling_ovk = sapling_fvk.as_ref().map(|efvk| efvk.fvk.ovk.clone());
let okeys = skeys.orchard.map(|sk| {
let orchard_fvk = FullViewingKey::from(&sk);
@ -120,12 +123,23 @@ pub fn build_tx(
..
} => {
let diversifier = Diversifier(*diversifier);
let sapling_address = sapling_fvk.fvk.vk.to_payment_address(diversifier).unwrap();
let sapling_address = sapling_fvk
.as_ref()
.ok_or(anyhow!("No sapling key"))?
.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)?;
builder.add_sapling_spend(
skeys.sapling.clone().ok_or(anyhow!("No Sapling Key"))?,
diversifier,
note,
merkle_path,
)?;
}
Source::Orchard {
id_note,
@ -171,7 +185,7 @@ pub fn build_tx(
Destination::Sapling(addr) => {
let sapling_address = PaymentAddress::from_bytes(addr).unwrap();
builder.add_sapling_output(
Some(sapling_ovk),
sapling_ovk,
sapling_address,
value,
output.memo.clone(),
@ -297,7 +311,7 @@ pub fn get_secret_keys(coin: u8, account: u32) -> anyhow::Result<SecretKeys> {
let sk = SecretKeys {
transparent: transparent_sk,
sapling: sapling_sk,
sapling: Some(sapling_sk),
orchard: orchard_sk,
};
Ok(sk)

View File

@ -33,7 +33,7 @@ async fn get_transparent_utxos(coin: u8, account: u32) -> anyhow::Result<Vec<UTX
};
if let Some(taddr) = taddr {
let mut client = coin.connect_lwd().await?;
let utxos = crate::taddr::get_utxos(&mut client, &taddr, account).await?;
let utxos = crate::taddr::get_utxos(&mut client, &taddr).await?;
let utxos: Vec<_> = utxos
.iter()
.map(|utxo| {

View File

@ -1,13 +1,20 @@
use crate::api::payment_v2::build_tx_plan_with_utxos;
use crate::api::recipient::RecipientMemo;
use crate::chain::{get_checkpoint_height, EXPIRY_HEIGHT_OFFSET};
use crate::coinconfig::CoinConfig;
use crate::db::AccountData;
use crate::note_selection::{SecretKeys, Source, UTXO};
use crate::unified::orchard_as_unified;
use crate::{
AddressList, CompactTxStreamerClient, GetAddressUtxosArg, GetAddressUtxosReply,
TransparentAddressBlockFilter,
broadcast_tx, build_tx, AddressList, CompactTxStreamerClient, GetAddressUtxosArg,
GetAddressUtxosReply, TransparentAddressBlockFilter,
};
use anyhow::anyhow;
use base58check::FromBase58Check;
use bip39::{Language, Mnemonic, Seed};
use core::slice;
use futures::StreamExt;
use rand::rngs::OsRng;
use ripemd::{Digest, Ripemd160};
use secp256k1::{All, PublicKey, Secp256k1, SecretKey};
use sha2::Sha256;
@ -17,6 +24,7 @@ use tonic::Request;
use zcash_client_backend::encoding::encode_transparent_address;
use zcash_primitives::consensus::{Network, Parameters};
use zcash_primitives::legacy::TransparentAddress;
use zcash_primitives::memo::Memo;
pub async fn get_taddr_balance(
client: &mut CompactTxStreamerClient<Channel>,
@ -51,7 +59,6 @@ pub async fn get_taddr_tx_count(
pub async fn get_utxos(
client: &mut CompactTxStreamerClient<Channel>,
t_address: &str,
_account: u32,
) -> anyhow::Result<Vec<GetAddressUtxosReply>> {
let req = GetAddressUtxosArg {
addresses: vec![t_address.to_string()],
@ -118,12 +125,17 @@ pub fn derive_tkeys(
derive_from_secretkey(network, &secret_key)
}
pub fn derive_taddr(network: &Network, key: &str) -> anyhow::Result<(String, String)> {
pub fn parse_seckey(key: &str) -> anyhow::Result<SecretKey> {
let (_, sk) = key.from_base58check().map_err(|_| anyhow!("Invalid key"))?;
let sk = &sk[0..sk.len() - 1]; // remove compressed pub key marker
log::info!("sk {}", hex::encode(&sk));
let secret_key = SecretKey::from_slice(&sk)?;
derive_from_secretkey(network, &secret_key)
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))
}
pub fn derive_from_secretkey(
@ -144,6 +156,84 @@ pub fn derive_from_secretkey(
Ok((sk, address))
}
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)
}
pub struct TBalance {
pub index: u32,
pub address: String,