diff --git a/src/api/handlers.rs b/src/api/handlers.rs index 352dfd14..fa64ba13 100644 --- a/src/api/handlers.rs +++ b/src/api/handlers.rs @@ -403,6 +403,23 @@ pub async fn get_total_supply( ) } +/// GET The last constructed transaction +pub async fn get_outgoing_txs( + route: &'static str, + db: WalletDb, + call_id: String, +) -> Result { + let r = CallResponse::new(route, &call_id); + + match db.get_outgoing_txs() { + Ok(tx) => r.into_ok( + "Successfully fetched last constructed transaction", + json_serialize_embed(tx), + ), + Err(_e) => r.into_err_internal(ApiErrorType::CannotAccessWallet), + } +} + //======= POST HANDLERS =======// /// Post to retrieve items from the blockchain db by hash key @@ -642,6 +659,7 @@ pub async fn post_update_running_total( _ => addresses, }; + debug!("Updating running total for addresses: {:?}", address_list); let request = UserRequest::UserApi(UserApiRequest::UpdateWalletFromUtxoSet { address_list: UtxoFetchType::AnyOf(address_list), }); diff --git a/src/api/routes.rs b/src/api/routes.rs index 0601f9c2..4b2c31c0 100644 --- a/src/api/routes.rs +++ b/src/api/routes.rs @@ -290,6 +290,30 @@ pub fn get_shared_config( .with(get_cors()) } +/// GET last constructed transaction +pub fn get_outgoing_txs( + dp: &mut DbgPaths, + db: WalletDb, + routes_pow: RoutesPoWInfo, + api_keys: ApiKeys, + cache: ReplyCache, +) -> impl Filter + Clone { + let route = "outgoing_transactions"; + warp_path(dp, route) + .and(warp::get()) + .and(auth_request(routes_pow, api_keys)) + .and(with_node_component(db)) + .and(with_node_component(cache)) + .and_then(move |call_id: String, db, cache| { + map_api_res_and_cache( + call_id.clone(), + cache, + handlers::get_outgoing_txs(route, db, call_id), + ) + }) + .with(get_cors()) +} + //======= POST ROUTES =======// // POST CORS @@ -817,6 +841,13 @@ pub fn user_node_routes( api_keys.clone(), cache.clone(), ) + .or(get_outgoing_txs( + dp, + db.clone(), + routes_pow_info.clone(), + api_keys.clone(), + cache.clone(), + )) .or(make_payment( dp, db.clone(), @@ -1167,6 +1198,13 @@ pub fn miner_node_with_user_routes( api_keys.clone(), cache.clone(), )) + .or(get_outgoing_txs( + dp, + db.clone(), + routes_pow_info.clone(), + api_keys.clone(), + cache.clone(), + )) // .or(make_ip_payment( // dp, // db.clone(), diff --git a/src/api/tests.rs b/src/api/tests.rs index d4952c4b..5ccc2c84 100644 --- a/src/api/tests.rs +++ b/src/api/tests.rs @@ -1747,6 +1747,7 @@ async fn test_post_update_running_total() { // Arrange // let (mut self_node, _self_socket) = new_self_node(NodeType::User).await; + let db = get_wallet_db("old_passphrase").await; let addresses = vec![COMMON_PUB_ADDR.to_string()]; @@ -1764,9 +1765,15 @@ async fn test_post_update_running_total() { let ks = to_api_keys(Default::default()); let cache = create_new_cache(CACHE_LIVE_TIME); - let filter = - routes::update_running_total(&mut dp(), self_node.clone(), Default::default(), ks, cache) - .recover(handle_rejection); + let filter = routes::update_running_total( + &mut dp(), + self_node.clone(), + db, + Default::default(), + ks, + cache, + ) + .recover(handle_rejection); let res = request.reply(&filter).await; // diff --git a/src/bin/node_settings_mainnet.toml b/src/bin/node_settings_mainnet.toml index a9775c5c..f79a5f54 100644 --- a/src/bin/node_settings_mainnet.toml +++ b/src/bin/node_settings_mainnet.toml @@ -27,4 +27,4 @@ address = "52.40.82.170:8080" address = "52.27.248.13:12340" [[user_nodes]] -address = "127.0.0.1:12342" +address = "127.0.0.1:12349" diff --git a/src/mempool.rs b/src/mempool.rs index 966156ed..7de26c89 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -1774,13 +1774,10 @@ impl MempoolNode { .await; } - if self.miners_changed { - info!( - "Change in no. of connected miners: {:?}", - self.get_connected_miners().await.len() - ); - self.miners_changed = false; - } + info!( + "Number of connected miners: {:?}", + self.get_connected_miners().await.len() + ); Ok(()) } diff --git a/src/user.rs b/src/user.rs index c8c99601..fec208c7 100644 --- a/src/user.rs +++ b/src/user.rs @@ -24,7 +24,8 @@ use tw_chain::primitives::block::Block; use tw_chain::primitives::druid::{DdeValues, DruidExpectation}; use tw_chain::primitives::transaction::{GenesisTxHashSpec, Transaction, TxIn, TxOut}; use tw_chain::utils::transaction_utils::{ - construct_item_create_tx, construct_rb_payments_send_tx, construct_rb_receive_payment_tx, construct_tx_core, construct_tx_ins_address, update_input_signatures, ReceiverInfo + construct_item_create_tx, construct_rb_payments_send_tx, construct_rb_receive_payment_tx, + construct_tx_core, construct_tx_ins_address, update_input_signatures, ReceiverInfo, }; use std::{collections::BTreeMap, error::Error, fmt, future::Future, net::SocketAddr}; @@ -1090,6 +1091,8 @@ impl UserNode { let key_material = self.wallet_db.get_key_material(&tx_ins); let final_tx_ins = update_input_signatures(&tx_ins, &tx_outs, &key_material); let payment_tx = construct_tx_core(final_tx_ins, tx_outs, None); + + self.wallet_db.set_last_construct_tx(payment_tx.clone()); self.next_payment = Some((peer, payment_tx)); Response { @@ -1723,8 +1726,15 @@ pub fn make_rb_payment_item_tx_and_response( genesis_hash: None, }; - let rb_receive_tx = - construct_rb_receive_payment_tx(tx_ins, tx_outs, None, sender_address, 0, dde_values, &BTreeMap::new()); + let rb_receive_tx = construct_rb_receive_payment_tx( + tx_ins, + tx_outs, + None, + sender_address, + 0, + dde_values, + &BTreeMap::new(), + ); let rb_payment_response = RbPaymentResponseData { receiver_address, @@ -1769,7 +1779,15 @@ pub fn make_rb_payment_send_transaction( asset: sender_asset, }; - construct_rb_payments_send_tx(tx_ins, tx_outs, None, receiver, 0, druid_values, &BTreeMap::new()) + construct_rb_payments_send_tx( + tx_ins, + tx_outs, + None, + receiver, + 0, + druid_values, + &BTreeMap::new(), + ) } fn make_transaction_gen(setup: UserAutoGenTxSetup) -> Option { diff --git a/src/wallet/fund_store.rs b/src/wallet/fund_store.rs index c92fc700..3dea0725 100644 --- a/src/wallet/fund_store.rs +++ b/src/wallet/fund_store.rs @@ -1,7 +1,7 @@ use crate::wallet::LockedCoinbase; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -use tracing::warn; +use tracing::{debug, warn}; use tw_chain::primitives::asset::{Asset, AssetValues}; use tw_chain::primitives::transaction::OutPoint; @@ -46,6 +46,13 @@ impl FundStore { &self.transactions } + pub fn reset(&mut self) { + self.running_total = Default::default(); + self.transactions = Default::default(); + self.transaction_pages = vec![BTreeMap::new()]; + self.spent_transactions = Default::default(); + } + /// Filters out locked coinbase transactions, updating the running total. /// /// Returns amount of filtered out coinbase transactions due to locktime @@ -122,7 +129,8 @@ impl FundStore { .insert(out_p.clone(), asset_to_save.clone()) { if old_amount != amount { - panic!("Try to insert existing transaction with different amount"); + warn!("Try to insert existing transaction with different amount"); + return; } } else { //Adds the entry to transaction pages @@ -131,6 +139,7 @@ impl FundStore { page.insert(out_p, asset_to_save.clone()); } self.running_total.update_add(&asset_to_save); + debug!("Running total after internal add: {:?}", self.running_total); } } diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index af34f53a..8533a2d8 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -33,6 +33,9 @@ pub const LOCKED_COINBASE_KEY: &str = "LockedCoinbaseKey"; /// Storage key for a &[u8] of the word 'MasterKeyStore' pub const MASTER_KEY_STORE_KEY: &str = "MasterKeyStore"; +/// Storage key for all outgoing transactions +pub const OUTGOING_TXS_KEY: &str = "OutgoingTxs"; + pub const DB_SPEC: SimpleDbSpec = SimpleDbSpec { db_path: WALLET_PATH, suffix: "", @@ -60,6 +63,7 @@ pub enum WalletDbError { InsufficientFundsError, MasterKeyRetrievalError, MasterKeyMissingError, + OutgoingTxMissingError, } impl fmt::Display for WalletDbError { @@ -74,6 +78,7 @@ impl fmt::Display for WalletDbError { Self::InsufficientFundsError => write!(f, "InsufficientFundsError"), Self::MasterKeyRetrievalError => write!(f, "MasterKeyRetrievalError"), Self::MasterKeyMissingError => write!(f, "MasterKeyMissingError"), + Self::OutgoingTxMissingError => write!(f, "OutgoingTxMissingError"), } } } @@ -90,6 +95,7 @@ impl error::Error for WalletDbError { Self::InsufficientFundsError => None, Self::MasterKeyRetrievalError => None, Self::MasterKeyMissingError => None, + Self::OutgoingTxMissingError => None, } } } @@ -182,6 +188,7 @@ pub struct WalletDb { locked_coinbase: LockedCoinbaseWithMutex, last_generated_address: Option, last_locked_coinbase_filter_b_num: Option, + last_constructed_tx: Option, } impl WalletDb { @@ -206,12 +213,13 @@ impl WalletDb { ui_feedback_tx: None, last_generated_address: None, last_locked_coinbase_filter_b_num: None, + last_constructed_tx: None, }) } /// Set the UI feedback channel /// - /// ## Arguments + /// ### Arguments /// * `tx` - The channel to send UI feedback messages to pub fn set_ui_feedback_tx(&mut self, tx: tokio::sync::mpsc::Sender) { self.ui_feedback_tx = Some(tx); @@ -219,13 +227,29 @@ impl WalletDb { /// Set locked coinbase value /// - /// ## Arguments + /// ### Arguments /// * `value` - The locked coinbase value pub async fn set_locked_coinbase(&mut self, value: LockedCoinbase) { let mut locked_coinbase = self.locked_coinbase.lock().await; *locked_coinbase = value; } + /// Set the latest constructed transaction + /// + /// ### Arguments + /// + /// * `tx` - The latest constructed transaction + pub fn set_last_construct_tx(&mut self, tx: Transaction) { + debug!("Last constructed tx set: {:?}", tx); + self.last_constructed_tx = Some(tx); + } + + /// Get the latest constructed transaction + pub fn get_last_constructed_tx(&self) -> Option { + debug!("Last constructed tx get: {:?}", self.last_constructed_tx); + self.last_constructed_tx.clone() + } + /// Get locked coinbase mutex #[allow(clippy::type_complexity)] pub fn get_locked_coinbase_mutex(&self) -> Arc> { @@ -237,14 +261,22 @@ impl WalletDb { self.locked_coinbase.lock().await.clone() } + /// Gets all outgoing transactions + pub fn get_outgoing_txs(&self) -> Result> { + let db = self.db.clone(); + let db = db.lock().unwrap(); + + get_outgoing_txs(&db) + } + /// Builds a key material map for a given set of transaction inputs - /// + /// /// ### Arguments - /// + /// /// * `tx_ins` - Transaction inputs pub fn get_key_material(&self, tx_ins: &[TxIn]) -> BTreeMap { let mut key_material = BTreeMap::new(); - + for tx_in in tx_ins { let out_p = &tx_in.previous_out; @@ -253,10 +285,9 @@ impl WalletDb { let address_store = self.get_address_store(&key_address); let public_key = address_store.public_key; let secret_key = address_store.secret_key; - + key_material.insert(out.clone(), (public_key, secret_key)); } - } key_material @@ -502,7 +533,7 @@ impl WalletDb { &mut self, payments: Vec<(OutPoint, Asset, String, u64)>, current_b_num: u64, - reset_db: bool + reset_db: bool, ) -> Result> { let db = self.db.clone(); let locked_coinbase = self.get_locked_coinbase().await.unwrap_or_default(); @@ -525,10 +556,22 @@ impl WalletDb { // Reset DB if needed if reset_db { + debug!("Resetting DB"); + debug!("Usable outpoints: {:?}", usable_outpoints); + debug!("Number of usable outpoints: {}", usable_outpoints.len()); let existing_tx = fund_store.transactions().clone(); - for (out_p, _) in &existing_tx { - if !usable_outpoints.contains(out_p) { - fund_store.spend_tx(out_p); + if usable_outpoints.len() == existing_tx.len() { + fund_store.reset(); + debug!( + "Current running total after reset: {:?}", + fund_store.running_total() + ); + } else { + for (out_p, _) in &existing_tx { + if !usable_outpoints.contains(out_p) { + fund_store.spend_tx(out_p); + debug!("Current running total: {:?}", fund_store.running_total()); + } } } } @@ -537,8 +580,12 @@ impl WalletDb { let key_address = key_address.clone(); let store = TransactionStore { key_address }; let asset_to_store = asset.clone().with_fixed_hash(out_p); + debug!("Storing asset: {:?}", asset_to_store); + fund_store.store_tx(out_p.clone(), asset_to_store); save_transaction_to_wallet(&mut batch, out_p, &store); + debug!("Running total: {:?}", fund_store.running_total()); + if *locktime > current_b_num { locked_coinbase.insert(out_p.t_hash.clone(), *locktime); } @@ -665,6 +712,14 @@ impl WalletDb { .save_usable_payments_to_wallet(payments, b_num, false) .await .unwrap(); + + let mut db = self.db.lock().unwrap(); + let mut batch = db.batch_writer(); + save_outgoing_tx_to_wallet(&db, &mut batch, (hash, transaction)); + + let batch = batch.done(); + db.write(batch).unwrap(); + tracing::debug!("store_payment_transactions: {:?}", our_payments); } @@ -1000,12 +1055,12 @@ pub fn save_address_store_to_wallet( pub fn get_transaction_store(db: &SimpleDb, out_p: &OutPoint) -> TransactionStore { match db.get_cf(DB_COL_DEFAULT, serialize(&out_p).unwrap()) { Ok(Some(store)) => deserialize(&store).unwrap(), - Ok(None) =>{ + Ok(None) => { warn!("Transaction not present in wallet: {:?}", out_p); TransactionStore { key_address: "".to_string(), } - } + } Err(e) => { warn!("Error accessing wallet: {:?}", e); TransactionStore { @@ -1032,6 +1087,34 @@ pub fn save_transaction_to_wallet( db.put_cf(DB_COL_DEFAULT, &key, &input); } +pub fn save_outgoing_tx_to_wallet( + db: &SimpleDb, + batch: &mut SimpleDbWriteBatch, + outgoing_tx: (String, Transaction), +) { + match get_outgoing_txs(db) { + Ok(mut outgoing_txs) => { + outgoing_txs.insert(outgoing_tx.0, outgoing_tx.1); + let store = serialize(&outgoing_txs).unwrap(); + batch.put_cf(DB_COL_DEFAULT, OUTGOING_TXS_KEY, &store); + } + Err(_) => { + let mut outgoing_txs = BTreeMap::new(); + outgoing_txs.insert(outgoing_tx.0, outgoing_tx.1); + let store = serialize(&outgoing_txs).unwrap(); + batch.put_cf(DB_COL_DEFAULT, OUTGOING_TXS_KEY, &store); + } + } +} + +pub fn get_outgoing_txs(db: &SimpleDb) -> Result> { + let store = db.get_cf(DB_COL_DEFAULT, OUTGOING_TXS_KEY)?; + let store = store.ok_or(WalletDbError::OutgoingTxMissingError)?; + let outgoing_tx: BTreeMap = deserialize(&store)?; + + Ok(outgoing_tx) +} + // Set a new master key store pub fn set_new_master_key_store( batch: &mut SimpleDbWriteBatch, @@ -1132,6 +1215,8 @@ pub fn fetch_inputs_for_payment_from_db( } let mut amount_made = Asset::default_of_type(&asset_required); + debug!("All transactions in store: {:?}", fund_store.transactions()); + // TODO: Check for spent outpoints before determining whether the wallet has enough if !fund_store.running_total().has_enough(&asset_required) { return Err(WalletDbError::InsufficientFundsError); @@ -1440,7 +1525,7 @@ mod tests { (out_p4, amount4, key_addr_non_existent, 0), ], Default::default(), - false + false, ) .await .unwrap();