Sweep taddr
This commit is contained in:
parent
36c85febad
commit
cda240c8e9
|
@ -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);
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
15
src/chain.rs
15
src/chain.rs
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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| {
|
||||
|
|
102
src/taddr.rs
102
src/taddr.rs
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue