This commit is contained in:
Hanh 2022-11-18 09:02:03 +08:00
parent 74a6310784
commit ac71c40218
11 changed files with 267 additions and 241 deletions

View File

@ -13,6 +13,11 @@ typedef char bool;
#endif
typedef void *DartPostCObjectFnType;
typedef struct CResult {
char value;
char *error;
} CResult;
#define QR_DATA_SIZE 256
#define MAX_ATTEMPTS 10
@ -34,21 +39,11 @@ typedef struct CResult_u8 {
char *error;
} CResult_u8;
typedef struct CResult_i64 {
int64_t value;
char *error;
} CResult_i64;
typedef struct CResult_u64 {
uint64_t value;
char *error;
} CResult_u64;
typedef struct CResult {
char value; // dummy
char *error;
} CResult;
void dummy_export(void);
void dart_post_cobject(DartPostCObjectFnType ptr);
@ -69,6 +64,10 @@ char *get_lwd_url(uint8_t coin);
void reset_app(void);
void mempool_run(int64_t port);
void mempool_set_active(uint8_t coin, uint32_t id_account);
struct CResult_u32 new_account(uint8_t coin, char *name, char *data, int32_t index);
void new_sub_account(char *name, int32_t index, uint32_t count);
@ -105,12 +104,6 @@ struct CResult_u32 rewind_to(uint32_t height);
void rescan_from(uint32_t height);
struct CResult_i64 mempool_sync(void);
void mempool_reset(void);
int64_t get_mempool_balance(void);
struct CResult_u64 get_taddr_balance(uint8_t coin, uint32_t id_account);
struct CResult_____c_char shield_taddr(uint8_t coin, uint32_t account, uint32_t confirmations);

View File

@ -12,3 +12,8 @@ typedef char bool;
#endif
#endif
typedef void *DartPostCObjectFnType;
typedef struct CResult {
char value;
char *error;
} CResult;

View File

@ -2,7 +2,6 @@ pub mod account;
pub mod contact;
pub mod fullbackup;
pub mod historical_prices;
pub mod mempool;
pub mod message;
pub mod recipient;
// pub mod payment;

View File

@ -1,4 +1,4 @@
use crate::coinconfig::{init_coin, migrate_coin, CoinConfig};
use crate::coinconfig::{init_coin, migrate_coin, CoinConfig, MEMPOOL, MEMPOOL_RUNNER};
use crate::note_selection::TransactionReport;
use crate::{ChainError, TransactionPlan, Tx};
use allo_isolate::{ffi, IntoDart};
@ -6,7 +6,6 @@ use android_logger::Config;
use anyhow::anyhow;
use lazy_static::lazy_static;
use log::Level;
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::sync::Mutex;
@ -17,10 +16,6 @@ static mut POST_COBJ: Option<ffi::DartPostCObjectFnType> = None;
const MAX_COINS: u8 = 3;
lazy_static! {
static ref LAST_ERROR: Mutex<RefCell<String>> = Mutex::new(RefCell::new(String::new()));
}
#[no_mangle]
pub unsafe extern "C" fn dummy_export() {}
@ -121,8 +116,9 @@ pub unsafe extern "C" fn set_active(active: u8) {
}
#[no_mangle]
pub unsafe extern "C" fn set_active_account(coin: u8, id: u32) {
crate::coinconfig::set_active_account(coin, id);
#[tokio::main]
pub async unsafe extern "C" fn set_active_account(coin: u8, id: u32) {
crate::coinconfig::set_active_account(coin, id).await;
}
#[no_mangle]
@ -148,6 +144,31 @@ pub unsafe extern "C" fn reset_app() {
log_error(res())
}
#[no_mangle]
#[tokio::main]
pub async unsafe extern "C" fn mempool_run(port: i64) {
let mut mempool_runner = MEMPOOL_RUNNER.lock().unwrap();
let mempool = mempool_runner
.run(move |balance: i64| {
let mut balance = balance.into_dart();
if port != 0 {
if let Some(p) = POST_COBJ {
p(port, &mut balance);
}
}
})
.await;
let _ = MEMPOOL.fill(mempool);
log::info!("end mempool_start");
}
#[no_mangle]
#[tokio::main]
pub async unsafe extern "C" fn mempool_set_active(coin: u8, id_account: u32) {
let mempool = MEMPOOL.borrow().unwrap();
mempool.set_active(coin, id_account).await;
}
#[no_mangle]
pub unsafe extern "C" fn new_account(
coin: u8,
@ -261,8 +282,6 @@ pub async unsafe extern "C" fn warp(
.await;
log::info!("Sync finished");
crate::api::mempool::scan().await?;
match result {
Ok(_) => Ok(0),
Err(err) => {
@ -374,27 +393,6 @@ pub async unsafe extern "C" fn rescan_from(height: u32) {
log_error(res)
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn mempool_sync() -> CResult<i64> {
let res = crate::api::mempool::scan().await;
to_cresult(res)
}
#[no_mangle]
pub unsafe extern "C" fn mempool_reset() {
let c = CoinConfig::get_active();
let mut mempool = c.mempool.lock().unwrap();
log_error(mempool.clear());
}
#[no_mangle]
pub unsafe extern "C" fn get_mempool_balance() -> i64 {
let c = CoinConfig::get_active();
let mempool = c.mempool.lock().unwrap();
mempool.get_unconfirmed_balance()
}
#[tokio::main]
#[no_mangle]
pub async unsafe extern "C" fn get_taddr_balance(coin: u8, id_account: u32) -> CResult<u64> {

View File

@ -1,33 +0,0 @@
//! Access to server mempool
use crate::api::sync::get_latest_height;
use anyhow::anyhow;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_primitives::consensus::Parameters;
use crate::coinconfig::CoinConfig;
use crate::db::AccountData;
/// Scan the mempool and return the unconfirmed balance
pub async fn scan() -> anyhow::Result<i64> {
let c = CoinConfig::get_active();
let AccountData { fvk, .. } = c.db()?.get_account_info(c.id_account)?;
let height = get_latest_height().await?;
let mut mempool = c.mempool.lock().unwrap();
let current_height = c.height;
if height != current_height {
CoinConfig::set_height(height);
mempool.clear()?;
}
let fvk = decode_extended_full_viewing_key(
c.chain.network().hrp_sapling_extended_full_viewing_key(),
&fvk,
)
.map_err(|_| anyhow!("Decode error"))?;
let mut client = c.connect_lwd().await?;
mempool
.update(&mut client, height, &fvk.fvk.vk.ivk())
.await?;
Ok(mempool.get_unconfirmed_balance())
}

View File

@ -286,6 +286,7 @@ pub struct DecryptedNote {
pub output_index: usize,
}
#[allow(dead_code)]
pub fn to_output_description(co: &CompactSaplingOutput) -> CompactOutputDescription {
let cmu: [u8; 32] = co.cmu.clone().try_into().unwrap();
let cmu = bls12_381::Scalar::from_repr(cmu).unwrap();

View File

@ -1,5 +1,5 @@
use crate::fountain::FountainCodes;
use crate::mempool::MemPool;
use crate::mempool::{MemPool, MemPoolRunner};
use crate::{connect_lightwalletd, CompactTxStreamerClient, DbAdapter};
use anyhow::anyhow;
use lazy_static::lazy_static;
@ -19,6 +19,8 @@ lazy_static! {
];
pub static ref PROVER: AtomicLazyCell<LocalTxProver> = AtomicLazyCell::new();
pub static ref RAPTORQ: Mutex<FountainCodes> = Mutex::new(FountainCodes::new());
pub static ref MEMPOOL: AtomicLazyCell<MemPool> = AtomicLazyCell::new();
pub static ref MEMPOOL_RUNNER: Mutex<MemPoolRunner> = Mutex::new(MemPoolRunner::new());
}
pub static ACTIVE_COIN: AtomicU8 = AtomicU8::new(0);
@ -29,14 +31,11 @@ pub fn set_active(active: u8) {
}
/// Set the active account for a given coin
pub fn set_active_account(coin: u8, id: u32) {
let mempool = {
let mut c = COIN_CONFIG[coin as usize].lock().unwrap();
c.id_account = id;
c.mempool.clone()
};
let mut mempool = mempool.lock().unwrap();
let _ = mempool.clear();
pub async fn set_active_account(coin: u8, id: u32) {
let mut c = COIN_CONFIG[coin as usize].lock().unwrap();
c.id_account = id;
let mempool = MEMPOOL.borrow().unwrap();
mempool.set_active(coin, id).await;
}
/// Set the lightwalletd url for a given coin
@ -77,7 +76,6 @@ pub struct CoinConfig {
pub height: u32,
pub lwd_url: Option<String>,
pub db_path: Option<String>,
pub mempool: Arc<Mutex<MemPool>>,
pub db: Option<Arc<Mutex<DbAdapter>>>,
pub chain: &'static (dyn CoinChain + Send),
}
@ -93,7 +91,6 @@ impl CoinConfig {
lwd_url: None,
db_path: None,
db: None,
mempool: Arc::new(Mutex::new(MemPool::new(coin))),
chain,
}
}
@ -137,10 +134,6 @@ impl CoinConfig {
c.height = height;
}
pub fn mempool(&self) -> MutexGuard<MemPool> {
self.mempool.lock().unwrap()
}
pub fn db(&self) -> anyhow::Result<MutexGuard<DbAdapter>> {
let db = self.db.as_ref().unwrap();
let db = db.lock().unwrap();

View File

@ -4,11 +4,11 @@ use crate::note_selection::{Source, UTXO};
use crate::orchard::{derive_orchard_keys, OrchardKeyBytes, OrchardViewKey};
use crate::prices::Quote;
use crate::sapling::SaplingViewKey;
use crate::sync;
use crate::sync::tree::{CTree, TreeCheckpoint};
use crate::taddr::{derive_tkeys, TBalance};
use crate::transaction::{GetTransactionDetailRequest, TransactionDetails};
use crate::unified::UnifiedAddressType;
use crate::{sync, Hash};
use orchard::keys::FullViewingKey;
use rusqlite::Error::QueryReturnedNoRows;
use rusqlite::{params, Connection, OptionalExtension, Transaction};
@ -555,7 +555,7 @@ impl DbAdapter {
&self,
account: u32,
unspent_only: bool,
) -> anyhow::Result<HashMap<Vec<u8>, u64>> {
) -> anyhow::Result<HashMap<Hash, u64>> {
let mut sql = "SELECT value, nf FROM received_notes WHERE account = ?1".to_string();
if unspent_only {
sql += " AND (spent IS NULL OR spent = 0)";
@ -564,9 +564,9 @@ impl DbAdapter {
let nfs_res = statement.query_map(params![account], |row| {
let amount: i64 = row.get(0)?;
let nf: Vec<u8> = row.get(1)?;
Ok((amount, nf))
Ok((amount, nf.try_into().unwrap()))
})?;
let mut nfs: HashMap<Vec<u8>, u64> = HashMap::new();
let mut nfs: HashMap<Hash, u64> = HashMap::new();
for n in nfs_res {
let n = n?;
nfs.insert(n.1, n.0 as u64);
@ -1369,8 +1369,7 @@ pub struct AccountData {
#[cfg(test)]
mod tests {
use crate::db::{DbAdapter, ReceivedNote, DEFAULT_DB_PATH};
use crate::sync::{CTree, Witness};
use crate::db::{DbAdapter, DEFAULT_DB_PATH};
use zcash_params::coin::CoinType;
#[test]

View File

@ -1,116 +1,228 @@
use crate::chain::to_output_description;
use crate::{CompactTx, CompactTxStreamerClient, Exclude};
use crate::{AccountData, Empty, Hash, RawTransaction};
use orchard::keys::{FullViewingKey, IncomingViewingKey, Scope};
use orchard::note_encryption::OrchardDomain;
use std::collections::HashMap;
use tonic::transport::Channel;
use tokio::runtime::Runtime;
use tokio::sync::mpsc::{Receiver, Sender};
use tonic::Request;
use zcash_client_backend::encoding::decode_extended_full_viewing_key;
use zcash_note_encryption::try_note_decryption;
use crate::coinconfig::CoinConfig;
use zcash_primitives::consensus::BlockHeight;
use zcash_primitives::consensus::{BlockHeight, Network, NetworkUpgrade, Parameters};
use zcash_primitives::sapling::note_encryption::{
try_sapling_compact_note_decryption, PreparedIncomingViewingKey,
try_sapling_note_decryption, PreparedIncomingViewingKey,
};
use zcash_primitives::sapling::SaplingIvk;
use zcash_primitives::transaction::Transaction;
const DEFAULT_EXCLUDE_LEN: u8 = 1;
struct MemPoolImpl {
network: Network,
nfs: HashMap<Hash, u64>,
balance: i64,
pivk: PreparedIncomingViewingKey,
oivk: Option<IncomingViewingKey>,
}
struct MemPoolTransacton {
#[allow(dead_code)]
balance: i64, // negative if spent
exclude_len: u8,
#[derive(Debug)]
pub enum MemPoolMsg {
Active(u8, u32),
Subscribe(u8, u32),
Balance(u8, u32, i64),
Close(u8, u32),
}
struct MemPoolHandler {
coin: u8,
id_account: u32,
tx_mesg: Sender<MemPoolMsg>,
}
impl MemPoolHandler {
pub fn new(coin: u8, id_account: u32, tx_mesg: Sender<MemPoolMsg>) -> Self {
MemPoolHandler {
coin,
id_account,
tx_mesg,
}
}
pub async fn subscribe(&self) -> anyhow::Result<()> {
let tx_mesg = self.tx_mesg.clone();
let coin = self.coin;
let id_account = self.id_account;
let c = CoinConfig::get(self.coin);
let mut client = c.connect_lwd().await?;
let (nfs, sapling_ivk, orchard_ivk) = {
let db = c.db()?;
let nfs = db.get_nullifier_amounts(id_account, true)?;
let network = c.chain.network();
let AccountData { fvk, .. } = db.get_account_info(id_account)?;
let fvk = decode_extended_full_viewing_key(
network.hrp_sapling_extended_full_viewing_key(),
&fvk,
)
.unwrap();
let sapling_ivk = fvk.fvk.vk.ivk();
let orchard_ivk = db.get_orchard(id_account)?.map(|k| {
let fvk = FullViewingKey::from_bytes(&k.fvk).unwrap();
fvk.to_ivk(Scope::External)
});
(nfs, sapling_ivk, orchard_ivk)
};
let mut mempool_impl = MemPoolImpl::new(c.chain.network(), nfs, sapling_ivk, orchard_ivk);
let mut mempool_stream = client
.get_mempool_stream(Request::new(Empty {}))
.await?
.into_inner();
tokio::spawn(async move {
while let Some(raw_tx) = mempool_stream.message().await? {
let balance = mempool_impl.scan_transaction(&raw_tx)?;
let _ = tx_mesg
.send(MemPoolMsg::Balance(coin, id_account, balance))
.await;
}
let _ = tx_mesg.send(MemPoolMsg::Close(coin, id_account)).await;
Ok::<_, anyhow::Error>(())
});
Ok(())
}
}
pub struct MemPool {
coin: u8,
transactions: HashMap<Vec<u8>, MemPoolTransacton>,
nfs: HashMap<Vec<u8>, u64>,
balance: i64,
tx_mesg: Sender<MemPoolMsg>,
}
impl MemPool {
pub fn new(coin: u8) -> MemPool {
MemPool {
coin,
transactions: HashMap::new(),
nfs: HashMap::new(),
balance: 0,
}
pub fn new(tx_mesg: Sender<MemPoolMsg>) -> Self {
MemPool { tx_mesg }
}
pub fn get_unconfirmed_balance(&self) -> i64 {
self.balance
}
pub fn clear(&mut self) -> anyhow::Result<()> {
let c = CoinConfig::get(self.coin);
self.nfs = c.db()?.get_nullifier_amounts(c.id_account, true)?;
self.transactions.clear();
self.balance = 0;
Ok(())
}
pub async fn update(
&mut self,
client: &mut CompactTxStreamerClient<Channel>,
height: u32,
ivk: &SaplingIvk,
) -> anyhow::Result<()> {
let filter: Vec<_> = self
.transactions
.iter()
.map(|(hash, tx)| {
let mut hash = hash.clone();
hash.truncate(tx.exclude_len as usize);
hash
})
.collect();
let exclude = Exclude { txid: filter };
let mut txs = client
.get_mempool_tx(Request::new(exclude))
.await?
.into_inner();
while let Some(tx) = txs.message().await? {
match self.transactions.get_mut(&*tx.hash) {
Some(tx) => {
tx.exclude_len += 1; // server sent us the same tx: make the filter more specific
}
None => {
let balance = self.scan_transaction(height, &tx, ivk);
let mempool_tx = MemPoolTransacton {
balance,
exclude_len: DEFAULT_EXCLUDE_LEN,
};
self.balance += balance;
self.transactions.insert(tx.hash.clone(), mempool_tx);
}
}
}
Ok(())
}
fn scan_transaction(&self, height: u32, tx: &CompactTx, ivk: &SaplingIvk) -> i64 {
let c = CoinConfig::get_active();
let mut balance = 0i64;
for cs in tx.spends.iter() {
if let Some(&value) = self.nfs.get(&*cs.nf) {
// nf recognized -> value is spent
balance -= value as i64;
}
}
let pivk = PreparedIncomingViewingKey::new(ivk);
for co in tx.outputs.iter() {
let od = to_output_description(co);
if let Some((note, _)) = try_sapling_compact_note_decryption(
c.chain.network(),
BlockHeight::from_u32(height),
&pivk,
&od,
) {
balance += note.value as i64; // value is incoming
}
}
balance
pub async fn set_active(&self, coin: u8, id_account: u32) {
let _ = self
.tx_mesg
.send(MemPoolMsg::Active(coin, id_account))
.await;
}
}
pub struct MemPoolRunner {
runtime: Runtime,
}
impl MemPoolRunner {
pub fn new() -> Self {
MemPoolRunner {
runtime: Runtime::new().unwrap(),
}
}
pub async fn run<F: Fn(i64) + Send + Sync + 'static>(&mut self, f: F) -> MemPool {
let (tx_mesg, rx_mesg) = tokio::sync::mpsc::channel::<MemPoolMsg>(8);
let mempool = MemPool::new(tx_mesg.clone());
self.runtime.spawn(async move {
run_mempool_loop(tx_mesg, rx_mesg, f).await.unwrap();
});
mempool
}
}
pub async fn run_mempool_loop<F: Fn(i64) + Send + Sync + 'static>(
tx_mesg: Sender<MemPoolMsg>,
mut rx_mesg: Receiver<MemPoolMsg>,
f: F,
) -> anyhow::Result<()> {
log::info!("MEMPOOL run");
let mut active_coin = 0;
let mut active_account = 0;
while let Some(message) = rx_mesg.recv().await {
match message {
MemPoolMsg::Active(coin, id_account) => {
if coin != active_coin || id_account != active_account {
active_coin = coin;
active_account = id_account;
let _ = tx_mesg.send(MemPoolMsg::Subscribe(coin, id_account)).await;
}
}
MemPoolMsg::Subscribe(coin, id_account) => {
let mempool_handler = MemPoolHandler::new(coin, id_account, tx_mesg.clone());
mempool_handler.subscribe().await?;
}
MemPoolMsg::Balance(coin, id_account, balance) => {
if coin == active_coin && id_account == active_account {
f(balance);
}
}
MemPoolMsg::Close(coin, id_account) => {
if coin == active_coin && id_account == active_account {
let _ = tx_mesg.send(MemPoolMsg::Subscribe(coin, id_account)).await;
}
}
}
}
Ok(())
}
impl MemPoolImpl {
pub fn new(
network: &Network,
nfs: HashMap<Hash, u64>,
sivk: SaplingIvk,
oivk: Option<IncomingViewingKey>,
) -> Self {
let pivk = PreparedIncomingViewingKey::new(&sivk);
MemPoolImpl {
network: network.clone(),
nfs,
balance: 0,
pivk,
oivk,
}
}
fn scan_transaction(&mut self, tx: &RawTransaction) -> anyhow::Result<i64> {
let height = tx.height as u32;
let mut balance = 0i64;
let consensus_branch_id = self.network.branch_id(NetworkUpgrade::Nu5);
let tx = Transaction::read(&tx.data[..], consensus_branch_id)?;
log::info!("Mempool TXID {}", tx.txid());
if let Some(sapling_bundle) = tx.sapling_bundle() {
for cs in sapling_bundle.shielded_spends.iter() {
let nf = cs.nullifier.0;
if let Some(&value) = self.nfs.get(&nf) {
// nf recognized -> value is spent
balance -= value as i64;
}
}
for co in sapling_bundle.shielded_outputs.iter() {
// let od = to_output_description(co);
if let Some((note, _, _)) = try_sapling_note_decryption(
&self.network,
BlockHeight::from_u32(height),
&self.pivk,
co,
) {
balance += note.value as i64; // value is incoming
}
}
}
if let Some(orchard_bundle) = tx.orchard_bundle() {
if let Some(ref oivk) = self.oivk {
for a in orchard_bundle.actions().iter() {
let nf = a.nullifier().to_bytes();
if let Some(&value) = self.nfs.get(&nf) {
// nf recognized -> value is spent
balance -= value as i64;
}
let domain = OrchardDomain::for_action(a);
if let Some((note, _, _)) = try_note_decryption(&domain, oivk, a) {
balance += note.value().inner() as i64; // value is incoming
}
}
}
}
self.balance += balance;
Ok(self.balance)
}
}

View File

@ -735,7 +735,7 @@ fn test_tx_plan() {
let tx_plan = build_tx_plan::<FeeZIP327>(
"",
0,
&[Hash::default(); 2],
&Hash::default(),
&utxos,
&orders,
&TransactionBuilderConfig {

View File

@ -108,44 +108,3 @@ impl<N: Parameters> TrialDecrypter<N, OrchardDomain, OrchardViewKey, DecryptedOr
vtx.actions.iter().map(|co| co.into()).collect()
}
}
#[test]
pub fn test_decrypt() -> anyhow::Result<()> {
// let mut nullifier = hex::decode("951ab285b0f4df3ff24f24470dbb8bafa3b5caeeb204fc4465f7ea9c3d5a980a").unwrap();
// let mut epk = hex::decode("182d698c3bb8b168d5f9420f1c2e32d94b4dbc0826181c1783ea47fedd31b710").unwrap();
// let mut cmx = hex::decode("df45e00eb39e4c281e2804a366d3010b7f663724472d12637e0a749e6ce22719").unwrap();
// let ciphertext = hex::decode("d9bc6ee09b0afde5dd69bfdf4b667a38da3e1084e84eb6752d54800b9f5110203b60496ab5313dba3f2acb9ef30bcaf68fbfcc59").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 {
coin_type: CoinType::Zcash,
db_path: "./zec.db".to_string(),
};
let db = db_builder.build()?;
let keys = db.get_orchard_fvks()?.first().unwrap().clone();
let fvk = keys.fvk;
let output = CompactOutputBytes {
nullifier: nullifier.clone().try_into().unwrap(),
epk: epk.try_into().unwrap(),
cmx: cmx.try_into().unwrap(),
ciphertext: ciphertext.try_into().unwrap(),
};
let domain = OrchardDomain::for_nullifier(
orchard::note::Nullifier::from_bytes(&nullifier.try_into().unwrap()).unwrap(),
);
let r = zcash_note_encryption::try_compact_note_decryption(
&domain,
&fvk.to_ivk(Scope::External),
&output,
);
println!("{:?}", r);
Ok(())
}