diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 162b89c01f..10e12b70d9 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -105,7 +105,7 @@ jobs: --release \ --package tari_integration_tests \ -- -t "${{ env.CI_PROFILE }} and (not @wallet-ffi) and (not @chat-ffi) and (not @broken)" \ - -c 5 \ + -c 1 \ --retry 2 - name: upload artifact diff --git a/applications/minotari_app_grpc/proto/block.proto b/applications/minotari_app_grpc/proto/block.proto index f5ed70c3ee..afb6d7c855 100644 --- a/applications/minotari_app_grpc/proto/block.proto +++ b/applications/minotari_app_grpc/proto/block.proto @@ -38,9 +38,10 @@ message BlockHeader { bytes prev_hash = 4; // Timestamp at which the block was built. uint64 timestamp = 5; - // This is the UTXO merkle root of the outputs - // This is calculated as Hash (txo MMR root || roaring bitmap hash of UTXO indices) + // This is the UTXO merkle root of the outputs in the blockchain bytes output_mr = 6; + // This is the merkle root of all outputs in this block + bytes block_output_mr = 7; // This is the MMR root of the kernels bytes kernel_mr = 8; // This is the Merkle root of the inputs in this block diff --git a/applications/minotari_app_grpc/proto/network.proto b/applications/minotari_app_grpc/proto/network.proto index ce8d90dca0..e61e0c9fd9 100644 --- a/applications/minotari_app_grpc/proto/network.proto +++ b/applications/minotari_app_grpc/proto/network.proto @@ -92,6 +92,16 @@ message ListConnectedPeersResponse { repeated ConnectedPeer connected_peers = 1; } +message UpsertContactRequest { + string alias = 1; + bytes wallet_address = 2; +} + +message UpsertContactResponse { + bool success = 1; + string error_message = 2; +} + message SoftwareUpdate { bool has_update = 1; string version = 2; diff --git a/applications/minotari_app_grpc/proto/wallet.proto b/applications/minotari_app_grpc/proto/wallet.proto index 23498e4aac..f9d23872eb 100644 --- a/applications/minotari_app_grpc/proto/wallet.proto +++ b/applications/minotari_app_grpc/proto/wallet.proto @@ -59,6 +59,8 @@ service Wallet { rpc GetNetworkStatus(Empty) returns (NetworkStatusResponse); // List currently connected peers rpc ListConnectedPeers(Empty) returns (ListConnectedPeersResponse); + // Upsert a new contact + rpc UpsertContact(UpsertContactRequest) returns (UpsertContactResponse); // Cancel pending transaction rpc CancelTransaction (CancelTransactionRequest) returns (CancelTransactionResponse); // Will trigger a complete revalidation of all wallet outputs. diff --git a/applications/minotari_app_grpc/src/conversions/block_header.rs b/applications/minotari_app_grpc/src/conversions/block_header.rs index a0eb9e05cc..dccee203e5 100644 --- a/applications/minotari_app_grpc/src/conversions/block_header.rs +++ b/applications/minotari_app_grpc/src/conversions/block_header.rs @@ -39,6 +39,7 @@ impl From for grpc::BlockHeader { timestamp: h.timestamp.as_u64(), input_mr: h.input_mr.to_vec(), output_mr: h.output_mr.to_vec(), + block_output_mr: h.block_output_mr.to_vec(), output_mmr_size: h.output_smt_size, kernel_mr: h.kernel_mr.to_vec(), kernel_mmr_size: h.kernel_mmr_size, @@ -76,6 +77,7 @@ impl TryFrom for BlockHeader { timestamp: EpochTime::from(header.timestamp), input_mr: FixedHash::try_from(header.input_mr).map_err(|err| err.to_string())?, output_mr: FixedHash::try_from(header.output_mr).map_err(|err| err.to_string())?, + block_output_mr: FixedHash::try_from(header.block_output_mr).unwrap_or_default(), output_smt_size: header.output_mmr_size, kernel_mr: FixedHash::try_from(header.kernel_mr).map_err(|err| err.to_string())?, kernel_mmr_size: header.kernel_mmr_size, diff --git a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs index 0d3272d0dc..1f4c781e17 100644 --- a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -79,6 +79,8 @@ use minotari_app_grpc::tari_rpc::{ TransferRequest, TransferResponse, TransferResult, + UpsertContactRequest, + UpsertContactResponse, ValidateRequest, ValidateResponse, }; @@ -97,6 +99,7 @@ use tari_common_types::{ transaction::TxId, types::{BlockHash, PublicKey, Signature}, }; +use tari_contacts::contacts_service::types::Contact; use tari_core::{ consensus::{ConsensusBuilderError, ConsensusConstants, ConsensusManager}, transactions::{ @@ -927,6 +930,30 @@ impl wallet_server::Wallet for WalletGrpcServer { Ok(Response::new(resp)) } + async fn upsert_contact( + &self, + request: Request, + ) -> Result, Status> { + let message = request.into_inner(); + let alias = message.alias; + let wallet_address = TariAddress::from_bytes(&message.wallet_address) + .map_err(|e| Status::invalid_argument(format!("Invalid wallet address: {}", e)))?; + + let contact = Contact::new(alias, wallet_address, None, None, false); + + let mut wallet = self.wallet.clone(); + match wallet.contacts_service.upsert_contact(contact).await { + Ok(_) => Ok(Response::new(UpsertContactResponse { + success: true, + error_message: String::new(), + })), + Err(e) => Ok(Response::new(UpsertContactResponse { + success: false, + error_message: e.to_string(), + })), + } + } + async fn cancel_transaction( &self, request: Request, diff --git a/applications/minotari_console_wallet/src/init/mod.rs b/applications/minotari_console_wallet/src/init/mod.rs index d57917161d..faa080e530 100644 --- a/applications/minotari_console_wallet/src/init/mod.rs +++ b/applications/minotari_console_wallet/src/init/mod.rs @@ -454,7 +454,7 @@ pub async fn init_wallet( e => ExitError::new(ExitCode::WalletError, format!("Error creating Wallet Container: {}", e)), })?; - error!( + info!( target: LOG_TARGET, "Wallet started in {}ms", now.elapsed().as_millis() ); diff --git a/base_layer/contacts/src/contacts_service/service.rs b/base_layer/contacts/src/contacts_service/service.rs index 9c9ffb05f8..0e5f558cd4 100644 --- a/base_layer/contacts/src/contacts_service/service.rs +++ b/base_layer/contacts/src/contacts_service/service.rs @@ -57,11 +57,22 @@ pub const SUBSCRIPTION_LABEL: &str = "Chat"; #[derive(Debug, Clone, PartialEq, Eq)] pub enum ContactMessageType { - Ping, + Ping = 0, Pong, NoMessage, } +impl ContactMessageType { + pub fn from_byte(value: u8) -> Option { + match value { + 0 => Some(Self::Ping), + 1 => Some(Self::Pong), + 2 => Some(Self::NoMessage), + _ => None, + } + } +} + impl Display for ContactMessageType { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { match self { diff --git a/base_layer/core/src/base_node/sync/header_sync/validator.rs b/base_layer/core/src/base_node/sync/header_sync/validator.rs index 6a60cc81b7..bcf8d6cc05 100644 --- a/base_layer/core/src/base_node/sync/header_sync/validator.rs +++ b/base_layer/core/src/base_node/sync/header_sync/validator.rs @@ -245,13 +245,18 @@ mod test { test_helpers::blockchain::{create_new_blockchain, TempDatabase}, }; - fn setup() -> (BlockHeaderSyncValidator, AsyncBlockchainDb) { + fn setup() -> ( + BlockHeaderSyncValidator, + AsyncBlockchainDb, + ConsensusManager, + ) { let rules = ConsensusManager::builder(Network::LocalNet).build().unwrap(); let randomx_factory = RandomXFactory::default(); let db = create_new_blockchain(); ( - BlockHeaderSyncValidator::new(db.clone().into(), rules, randomx_factory), + BlockHeaderSyncValidator::new(db.clone().into(), rules.clone(), randomx_factory), db.into(), + rules, ) } @@ -262,10 +267,11 @@ mod test { AsyncBlockchainDb, ChainHeader, ) { - let (validator, db) = setup(); + let (validator, db, cm) = setup(); let mut tip = db.fetch_tip_header().await.unwrap(); for _ in 0..n { let mut header = BlockHeader::from_previous(tip.header()); + header.version = cm.consensus_constants(header.height).blockchain_version(); // Needed to have unique keys for the blockchain db mmr count indexes (MDB_KEY_EXIST error) header.kernel_mmr_size += 1; header.output_smt_size += 1; @@ -301,7 +307,7 @@ mod test { #[tokio::test] async fn it_errors_if_hash_does_not_exist() { - let (mut validator, _) = setup(); + let (mut validator, _, _cm) = setup(); let start_hash = vec![0; 32]; let err = validator .initialize_state(&start_hash.clone().try_into().unwrap()) diff --git a/base_layer/core/src/blocks/block_header.rs b/base_layer/core/src/blocks/block_header.rs index 5b5704348c..4988a38e9e 100644 --- a/base_layer/core/src/blocks/block_header.rs +++ b/base_layer/core/src/blocks/block_header.rs @@ -98,9 +98,10 @@ pub struct BlockHeader { pub timestamp: EpochTime, /// This is the Merkle root of the inputs in this block pub input_mr: FixedHash, - /// This is the UTXO merkle root of the outputs - /// This is calculated as Hash (txo MMR root || roaring bitmap hash of UTXO indices) + /// This is the UTXO merkle root of the outputs on the blockchain pub output_mr: FixedHash, + /// This is the block_output_mr + pub block_output_mr: FixedHash, /// The size (number of leaves) of the output and range proof MMRs at the time of this header pub output_smt_size: u64, /// This is the MMR root of the kernels @@ -130,6 +131,7 @@ impl BlockHeader { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -164,6 +166,7 @@ impl BlockHeader { timestamp: EpochTime::now(), output_mr: FixedHash::zero(), output_smt_size: prev.output_smt_size, + block_output_mr: FixedHash::zero(), kernel_mr: FixedHash::zero(), kernel_mmr_size: prev.kernel_mmr_size, input_mr: FixedHash::zero(), @@ -222,7 +225,7 @@ impl BlockHeader { /// Provides a mining hash of the header, used for the mining. /// This differs from the normal hash by not hashing the nonce and kernel pow. pub fn mining_hash(&self) -> FixedHash { - DomainSeparatedConsensusHasher::>::new("block_header") + let incomplete = DomainSeparatedConsensusHasher::>::new("block_header") .chain(&self.version) .chain(&self.height) .chain(&self.prev_hash) @@ -235,9 +238,12 @@ impl BlockHeader { .chain(&self.total_kernel_offset) .chain(&self.total_script_offset) .chain(&self.validator_node_mr) - .chain(&self.validator_node_size) - .finalize() - .into() + .chain(&self.validator_node_size); + + match self.version { + 0 => incomplete.finalize().into(), + _ => incomplete.chain(&self.block_output_mr).finalize().into(), + } } pub fn merge_mining_hash(&self) -> FixedHash { @@ -273,6 +279,7 @@ impl From for BlockHeader { prev_hash: header_template.prev_hash, timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 5ca7cdf802..8818206ca2 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -191,6 +191,8 @@ pub fn get_nextnet_genesis_block() -> ChainBlock { // TODO: Fix this hack with the next nextnet reset!! block.header.input_mr = FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); + block.header.block_output_mr = + FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); // Add pre-mine utxos - enable/disable as required let add_pre_mine_utxos = false; @@ -270,6 +272,8 @@ pub fn get_mainnet_genesis_block() -> ChainBlock { FixedHash::from_hex("b7b38b76f5832b5b63691a8334dfa67d8c762b77b2b4aa4f648c4eb1dfb25c1e").unwrap(); block.header.output_mr = FixedHash::from_hex("a77ecf05b20c426d3d400a63397be6c622843c66d5751ecbe3390c8a4885158e").unwrap(); + block.header.block_output_mr = + FixedHash::from_hex("91e997520b0eee770914334692080f92d18db434d373561f8842c56d70c11b97").unwrap(); block.header.validator_node_mr = FixedHash::from_hex("277da65c40b2cf99db86baedb903a3f0a38540f3a94d40c826eecac7e27d5dfc").unwrap(); } @@ -371,6 +375,10 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { // lets get the block let mut block = get_esmeralda_genesis_block_raw(); + // TODO: Fix this hack with the next esme reset!! + block.header.block_output_mr = + FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); + // Add pre-mine utxos - enable/disable as required let add_pre_mine_utxos = true; if add_pre_mine_utxos { @@ -482,7 +490,9 @@ fn get_raw_block(genesis_timestamp: &DateTime, not_before_proof: &P height: 0, prev_hash: FixedHash::zero(), timestamp: timestamp.into(), - output_mr: FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + output_mr: FixedHash::zero(), + block_output_mr: FixedHash::from_hex("622720a6571c33d6bf6138d9e737d3468c77f1193640698ad459953d24ec0812") + .unwrap(), output_smt_size: 0, kernel_mr: FixedHash::from_hex("c14803066909d6d22abf0d2d2782e8936afc3f713f2af3a4ef5c42e8400c1303").unwrap(), kernel_mmr_size: 0, @@ -518,6 +528,7 @@ mod test { use super::*; use crate::{ + block_output_mr_hash_from_pruned_mmr, chain_storage::calculate_validator_node_mr, consensus::ConsensusManager, test_helpers::blockchain::create_new_blockchain_with_network, @@ -527,8 +538,8 @@ mod test { }, validation::{ChainBalanceValidator, FinalHorizonStateValidation}, KernelMmr, + PrunedOutputMmr, }; - #[test] #[serial] fn esmeralda_genesis_sanity_check() { @@ -661,9 +672,15 @@ mod test { kernel_mmr.push(k.hash().to_vec()).unwrap(); } let mut output_smt = OutputSmt::new(); - + let mut block_output_mmr = PrunedOutputMmr::new(PrunedHashSet::default()); + let mut normal_output_mmr = PrunedOutputMmr::new(PrunedHashSet::default()); let mut vn_nodes = Vec::new(); for o in block.block().body.outputs() { + if o.features.is_coinbase() { + block_output_mmr.push(o.hash().to_vec()).unwrap(); + } else { + normal_output_mmr.push(o.hash().to_vec()).unwrap(); + } let smt_key = NodeKey::try_from(o.commitment.as_bytes()).unwrap(); let smt_node = ValueHash::try_from(o.smt_hash(block.header().height).as_slice()).unwrap(); output_smt.insert(smt_key, smt_node).unwrap(); @@ -681,6 +698,11 @@ mod test { )); } } + + block_output_mmr + .push(normal_output_mmr.get_merkle_root().unwrap().to_vec()) + .unwrap(); + for i in block.block().body.inputs() { let smt_key = NodeKey::try_from(i.commitment().unwrap().as_bytes()).unwrap(); output_smt.delete(&smt_key).unwrap(); @@ -718,6 +740,25 @@ mod test { output_mr_hash_from_smt(&mut output_smt).unwrap().to_vec().to_hex(), block.header().output_mr.to_vec().to_hex(), ); + + // TODO: Fix this hack with the next nextnet/esme release reset!! + if network == Network::NextNet || network == Network::Esmeralda { + assert_eq!( + FixedHash::from_hex("0000000000000000000000000000000000000000000000000000000000000000") + .unwrap() + .to_vec() + .to_hex(), + block.header().block_output_mr.to_vec().to_hex(), + ); + } else { + assert_eq!( + block_output_mr_hash_from_pruned_mmr(&block_output_mmr) + .unwrap() + .to_vec() + .to_hex(), + block.header().block_output_mr.to_vec().to_hex(), + ); + } if network == Network::NextNet { // TODO: Fix this hack with the next nextnet reset!! assert_eq!( diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 3ce6fa1870..c789baf4ae 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -49,6 +49,7 @@ use tari_utilities::{epoch_time::EpochTime, hex::Hex, ByteArray}; use super::TemplateRegistrationEntry; use crate::{ + block_output_mr_hash_from_pruned_mmr, blocks::{ Block, BlockAccumulatedData, @@ -105,6 +106,7 @@ use crate::{ OutputSmt, PrunedInputMmr, PrunedKernelMmr, + PrunedOutputMmr, ValidatorNodeBMT, }; @@ -913,6 +915,7 @@ where B: BlockchainBackend block.header.kernel_mmr_size = roots.kernel_mmr_size; block.header.input_mr = roots.input_mr; block.header.output_mr = roots.output_mr; + block.header.block_output_mr = roots.block_output_mr; block.header.output_smt_size = roots.output_smt_size; block.header.validator_node_mr = roots.validator_node_mr; block.header.validator_node_size = roots.validator_node_size; @@ -1322,6 +1325,7 @@ pub struct MmrRoots { pub kernel_mmr_size: u64, pub input_mr: FixedHash, pub output_mr: FixedHash, + pub block_output_mr: FixedHash, pub output_smt_size: u64, pub validator_node_mr: FixedHash, pub validator_node_size: u64, @@ -1334,6 +1338,7 @@ impl std::fmt::Display for MmrRoots { writeln!(f, "Kernel MR : {}", self.kernel_mr)?; writeln!(f, "Kernel MMR Size : {}", self.kernel_mmr_size)?; writeln!(f, "Output MR : {}", self.output_mr)?; + writeln!(f, "Block Output MR : {}", self.block_output_mr)?; writeln!(f, "Output SMT Size : {}", self.output_smt_size)?; writeln!(f, "Validator MR : {}", self.validator_node_mr)?; Ok(()) @@ -1373,6 +1378,8 @@ pub fn calculate_mmr_roots( let mut kernel_mmr = PrunedKernelMmr::new(kernels); let mut input_mmr = PrunedInputMmr::new(PrunedHashSet::default()); + let mut block_output_mmr = PrunedOutputMmr::new(PrunedHashSet::default()); + let mut normal_output_mmr = PrunedOutputMmr::new(PrunedHashSet::default()); for kernel in body.kernels() { kernel_mmr.push(kernel.hash().to_vec())?; @@ -1380,6 +1387,11 @@ pub fn calculate_mmr_roots( let mut outputs_to_remove = Vec::new(); for output in body.outputs() { + if output.features.is_coinbase() { + block_output_mmr.push(output.hash().to_vec())?; + } else { + normal_output_mmr.push(output.hash().to_vec())?; + } if !output.is_burned() { let smt_key = NodeKey::try_from(output.commitment.as_bytes())?; let smt_node = ValueHash::try_from(output.smt_hash(header.height).as_slice())?; @@ -1394,6 +1406,7 @@ pub fn calculate_mmr_roots( } } } + block_output_mmr.push(normal_output_mmr.get_merkle_root()?.to_vec())?; let mut outputs_to_add = Vec::new(); for input in body.inputs() { @@ -1427,11 +1440,18 @@ pub fn calculate_mmr_roots( (tip_header.validator_node_mr, 0) }; + let block_output_mr = if block.version() > 0 { + block_output_mr_hash_from_pruned_mmr(&block_output_mmr)? + } else { + FixedHash::zero() + }; + let mmr_roots = MmrRoots { kernel_mr: kernel_mr_hash_from_pruned_mmr(&kernel_mmr)?, kernel_mmr_size: kernel_mmr.get_leaf_count()? as u64, input_mr: input_mr_hash_from_pruned_mmr(&input_mmr)?, output_mr: output_mr_hash_from_smt(output_smt)?, + block_output_mr, output_smt_size: output_smt.size(), validator_node_mr, validator_node_size: validator_node_size as u64, diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index 7762f94dc2..796a68f728 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -389,8 +389,8 @@ impl ConsensusConstants { let consensus_constants = vec![ConsensusConstants { effective_from_height: 0, coinbase_min_maturity: 2, - blockchain_version: 0, - valid_blockchain_version_range: 0..=0, + blockchain_version: 1, + valid_blockchain_version_range: 1..=1, future_time_limit: 540, difficulty_block_window, max_block_transaction_weight: 19500, @@ -509,7 +509,7 @@ impl ConsensusConstants { target_time: 240, }); let (input_version_range, output_version_range, kernel_version_range) = version_zero(); - let consensus_constants = vec![ConsensusConstants { + let consensus_constants1 = ConsensusConstants { effective_from_height: 0, coinbase_min_maturity: 6, blockchain_version: 0, @@ -541,9 +541,14 @@ impl ConsensusConstants { vn_registration_lock_height: 0, vn_registration_shuffle_interval: VnEpoch(100), coinbase_output_features_extra_max_length: 256, - }]; + }; + let mut consensus_constants2 = consensus_constants1.clone(); + consensus_constants2.blockchain_version = 1; + consensus_constants2.effective_from_height = 16000; + consensus_constants2.valid_blockchain_version_range = 1..=1; + let consensus_constants = vec![consensus_constants1, consensus_constants2]; #[cfg(any(test, debug_assertions))] - assert_hybrid_pow_constants(&consensus_constants, &[120], &[50], &[50]); + assert_hybrid_pow_constants(&consensus_constants, &[120, 120], &[50, 50], &[50, 50]); consensus_constants } @@ -569,8 +574,8 @@ impl ConsensusConstants { let consensus_constants = vec![ConsensusConstants { effective_from_height: 0, coinbase_min_maturity: 360, - blockchain_version: 0, - valid_blockchain_version_range: 0..=0, + blockchain_version: 1, + valid_blockchain_version_range: 1..=1, future_time_limit: 540, difficulty_block_window: 90, max_block_transaction_weight: 127_795, @@ -653,10 +658,14 @@ impl ConsensusConstants { let mut con_2 = con_1.clone(); con_2.effective_from_height = 33000; con_2.coinbase_output_features_extra_max_length = 256; + let mut con_3 = con_2.clone(); + con_3.effective_from_height = 52000; + con_3.blockchain_version = 1; + con_3.valid_blockchain_version_range = 1..=1; - let consensus_constants = vec![con_1, con_2]; + let consensus_constants = vec![con_1, con_2, con_3]; #[cfg(any(test, debug_assertions))] - assert_hybrid_pow_constants(&consensus_constants, &[120, 120], &[50, 50], &[50, 50]); + assert_hybrid_pow_constants(&consensus_constants, &[120, 120, 120], &[50, 50, 50], &[50, 50, 50]); consensus_constants } @@ -678,8 +687,8 @@ impl ConsensusConstants { let consensus_constants = vec![ConsensusConstants { effective_from_height: 0, coinbase_min_maturity: 720, - blockchain_version: 0, - valid_blockchain_version_range: 0..=0, + blockchain_version: 1, + valid_blockchain_version_range: 1..=1, future_time_limit: 540, difficulty_block_window, max_block_transaction_weight: 127_795, diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index 58cd49473c..0068a05295 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -86,6 +86,7 @@ mod domain_hashing { hash_domain!(InputMmrHashDomain, "com.tari.base_layer.core.input_mmr", 1); pub type InputMmrHasherBlake256 = DomainSeparatedHasher, InputMmrHashDomain>; pub type PrunedInputMmr = MerkleMountainRange; + pub type PrunedOutputMmr = MerkleMountainRange; pub type OutputSmt = SparseMerkleTree; @@ -112,6 +113,11 @@ mod domain_hashing { Ok(FixedHash::try_from(input_mmr.get_merkle_root()?)?) } + #[inline] + pub fn block_output_mr_hash_from_pruned_mmr(output_mmr: &PrunedOutputMmr) -> Result { + Ok(FixedHash::try_from(output_mmr.get_merkle_root()?)?) + } + #[derive(Debug, thiserror::Error)] pub enum MrHashError { #[error("Output SMT conversion error: {0}")] diff --git a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs index 100e09c4b7..7f57275e38 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs @@ -550,6 +550,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -652,6 +653,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -726,6 +728,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -798,6 +801,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -874,6 +878,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -970,6 +975,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -1030,6 +1036,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -1102,6 +1109,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, @@ -1163,6 +1171,7 @@ mod test { prev_hash: FixedHash::zero(), timestamp: EpochTime::now(), output_mr: FixedHash::zero(), + block_output_mr: FixedHash::zero(), output_smt_size: 0, kernel_mr: FixedHash::zero(), kernel_mmr_size: 0, diff --git a/base_layer/core/src/proof_of_work/sha3x_pow.rs b/base_layer/core/src/proof_of_work/sha3x_pow.rs index c1953826b2..af788c3288 100644 --- a/base_layer/core/src/proof_of_work/sha3x_pow.rs +++ b/base_layer/core/src/proof_of_work/sha3x_pow.rs @@ -101,6 +101,6 @@ pub mod test { let mut header = get_header(); header.nonce = 631; println!("{:?}", header); - assert_eq!(sha3x_difficulty(&header).unwrap(), Difficulty::from_u64(28).unwrap()); + assert_eq!(sha3x_difficulty(&header).unwrap(), Difficulty::from_u64(13).unwrap()); } } diff --git a/base_layer/core/src/proto/block_header.rs b/base_layer/core/src/proto/block_header.rs index 1a40360e56..3da104ae0b 100644 --- a/base_layer/core/src/proto/block_header.rs +++ b/base_layer/core/src/proto/block_header.rs @@ -52,6 +52,7 @@ impl TryFrom for BlockHeader { prev_hash: FixedHash::try_from(header.prev_hash).map_err(|err| err.to_string())?, timestamp: EpochTime::from(header.timestamp), output_mr: FixedHash::try_from(header.output_mr).map_err(|err| err.to_string())?, + block_output_mr: FixedHash::try_from(header.block_output_mr).unwrap_or_default(), output_smt_size: header.output_mmr_size, kernel_mr: FixedHash::try_from(header.kernel_mr).map_err(|err| err.to_string())?, kernel_mmr_size: header.kernel_mmr_size, @@ -74,6 +75,7 @@ impl From for proto::BlockHeader { prev_hash: header.prev_hash.to_vec(), timestamp: header.timestamp.as_u64(), output_mr: header.output_mr.to_vec(), + block_output_mr: header.block_output_mr.to_vec(), kernel_mr: header.kernel_mr.to_vec(), input_mr: header.input_mr.to_vec(), total_kernel_offset: header.total_kernel_offset.to_vec(), diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index 08c4db4544..971810f842 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -91,6 +91,7 @@ pub async fn create_block( range_proof_type: Option, ) -> (Block, WalletOutput) { let mut header = BlockHeader::from_previous(&prev_block.header); + header.version = rules.consensus_constants(header.height).blockchain_version(); let block_height = spec.height_override.unwrap_or(prev_block.header.height + 1); header.height = block_height; let reward = spec.reward_override.unwrap_or_else(|| { diff --git a/base_layer/core/src/validation/block_body/test.rs b/base_layer/core/src/validation/block_body/test.rs index fede6a9984..1afa2f3f66 100644 --- a/base_layer/core/src/validation/block_body/test.rs +++ b/base_layer/core/src/validation/block_body/test.rs @@ -94,6 +94,7 @@ async fn it_passes_if_large_output_block_is_valid() { .calculate_mmr_roots(chain_block.block().clone()) .unwrap(); block.header.input_mr = mmr_roots.input_mr; + block.header.block_output_mr = mmr_roots.block_output_mr; block.header.output_mr = mmr_roots.output_mr; block.header.output_smt_size = mmr_roots.output_smt_size; block.header.kernel_mr = mmr_roots.kernel_mr; @@ -131,6 +132,7 @@ async fn it_validates_when_a_coinbase_is_spent() { .unwrap(); block.header.input_mr = mmr_roots.input_mr; block.header.output_mr = mmr_roots.output_mr; + block.header.block_output_mr = mmr_roots.block_output_mr; block.header.output_smt_size = mmr_roots.output_smt_size; block.header.kernel_mr = mmr_roots.kernel_mr; block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size; @@ -173,6 +175,7 @@ async fn it_passes_if_large_block_is_valid() { .unwrap(); block.header.input_mr = mmr_roots.input_mr; block.header.output_mr = mmr_roots.output_mr; + block.header.block_output_mr = mmr_roots.block_output_mr; block.header.output_smt_size = mmr_roots.output_smt_size; block.header.kernel_mr = mmr_roots.kernel_mr; block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size; @@ -203,6 +206,7 @@ async fn it_passes_if_block_is_valid() { .unwrap(); block.header.input_mr = mmr_roots.input_mr; block.header.output_mr = mmr_roots.output_mr; + block.header.block_output_mr = mmr_roots.block_output_mr; block.header.output_smt_size = mmr_roots.output_smt_size; block.header.kernel_mr = mmr_roots.kernel_mr; block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size; diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 160706defe..489ee2beff 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -263,6 +263,7 @@ pub fn check_not_duplicate_txo( Ok(()) } +#[allow(clippy::too_many_lines)] pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), ValidationError> { if header.kernel_mr != mmr_roots.kernel_mr { warn!( @@ -302,7 +303,7 @@ pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), mmr_roots.output_mr.to_hex() ); return Err(ValidationError::BlockError(BlockValidationError::MismatchedMmrRoots { - kind: "Utxo", + kind: "Utxos", })); }; if header.output_smt_size != mmr_roots.output_smt_size { @@ -318,7 +319,20 @@ pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), expected: mmr_roots.output_smt_size, actual: header.output_smt_size, })); - } + }; + if header.block_output_mr != mmr_roots.block_output_mr { + warn!( + target: LOG_TARGET, + "Block header block output MMR roots in #{} {} do not match calculated roots. Expected: {}, Actual:{}", + header.height, + header.hash().to_hex(), + header.block_output_mr, + mmr_roots.block_output_mr, + ); + return Err(ValidationError::BlockError(BlockValidationError::MismatchedMmrRoots { + kind: "block outputs", + })); + }; if header.input_mr != mmr_roots.input_mr { warn!( target: LOG_TARGET, diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index dc58fb1e73..6827a9048f 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -62,6 +62,7 @@ use tari_core::{ OutputSmt, PrunedInputMmr, PrunedKernelMmr, + PrunedOutputMmr, }; use tari_key_manager::key_manager_service::KeyManagerInterface; use tari_mmr::{ @@ -206,7 +207,9 @@ fn update_genesis_block_mmr_roots(template: NewBlockTemplate) -> Result Result write!( f, "SendTransaction (amount: {}, to: {}, message: {})", - amount, destination, message + amount, + destination.to_base58(), + message ), Self::BurnTari { amount, message, .. } => write!(f, "Burning Tari ({}, {})", amount, message), Self::SpendBackupPreMineUtxo { diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs index adc503c372..c5b55ec159 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs @@ -186,23 +186,21 @@ where if send_result { info!( target: LOG_TARGET, - "Transaction with TX_ID = {} received from {}. Reply Sent", data.tx_id, self.source_address, + "Transaction with TX_ID = {} received from {}. Reply Sent", + data.tx_id, self.source_address.to_base58(), ); } else { error!( target: LOG_TARGET, "Transaction with TX_ID = {} received from {}. Reply could not be sent!", - data.tx_id, - self.source_address, + data.tx_id, self.source_address.to_base58(), ); } trace!( target: LOG_TARGET, "Transaction (TX_ID: {}) - Amount: {} - Message: {}", - data.tx_id, - amount, - data.message, + data.tx_id, amount, data.message, ); let _size = self @@ -365,7 +363,7 @@ where target: LOG_TARGET, "Finalized Transaction with TX_ID = {} received from {}", self.id, - self.source_address.clone() + self.source_address.to_base58() ); // Find your own output in the transaction @@ -456,8 +454,7 @@ where info!( target: LOG_TARGET, "Inbound Transaction with TX_ID = {} from {} moved to Completed Transactions", - self.id, - self.source_address.clone() + self.id, self.source_address.to_base58() ); let _size = self diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs index 96bddac16e..6cc11cc544 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs @@ -678,7 +678,8 @@ where info!( target: LOG_TARGET, - "Attempting to Send Transaction (TxId: {}) to recipient with address: {}", self.id, self.dest_address, + "Attempting to Send Transaction (TxId: {}) to recipient with address: {}", + self.id, self.dest_address.to_base58(), ); match self diff --git a/base_layer/wallet_ffi/src/error.rs b/base_layer/wallet_ffi/src/error.rs index 7df7941035..ecd2cf225b 100644 --- a/base_layer/wallet_ffi/src/error.rs +++ b/base_layer/wallet_ffi/src/error.rs @@ -60,8 +60,8 @@ pub enum InterfaceError { InternalError(String), #[error("Balance Unavailable")] BalanceError, - #[error("Invalid multiaddr")] - InvalidMultiaddr, + #[error("Invalid multiaddr: `{0}`")] + InvalidMultiaddr(String), } /// This struct is meant to hold an error for use by FFI client applications. The error has an integer code and string @@ -113,9 +113,9 @@ impl From for LibWalletError { code: 10, message: format!("{:?}", v), }, - InterfaceError::InvalidMultiaddr => Self { + InterfaceError::InvalidMultiaddr(_) => Self { code: 11, - message: v.to_string(), + message: format!("{:?}", v), }, } } diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index e533d5b4b8..bf91ad7121 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -5270,42 +5270,56 @@ pub unsafe extern "C" fn transaction_send_status_destroy(status: *mut TariTransa #[allow(clippy::too_many_lines)] pub unsafe extern "C" fn comms_config_create( public_address: *const c_char, - listen_address: *const c_char, + listen_addresses: *const c_char, + enable_mdns: bool, + reachability_mode_private: bool, error_out: *mut c_int, ) -> *mut TariCommsConfig { *error_out = 0; let mut ignore_error = 0; let public_address_str = c_char_ptr_to_string(public_address, &mut ignore_error as *mut c_int); - let listen_address = c_char_ptr_to_string(listen_address, &mut ignore_error as *mut c_int); - let listen_multiaddr = listen_address - .filter(|s| !s.is_empty()) - .map(|s| s.parse::()) - .transpose(); + let listen_addresses = c_char_ptr_to_string(listen_addresses, &mut ignore_error as *mut c_int); + let public_multiaddr = public_address_str .filter(|s| !s.is_empty()) .map(|s| s.parse::()) .transpose(); - let Ok(listen_multiaddr) = listen_multiaddr else { - *error_out = LibWalletError::from(InterfaceError::InvalidMultiaddr).code; - return ptr::null_mut(); + let public_multiaddr = match public_multiaddr { + Ok(val) => val, + Err(e) => { + *error_out = LibWalletError::from(InterfaceError::InvalidMultiaddr(e.to_string())).code; + return ptr::null_mut(); + }, }; - let Ok(public_multiaddr) = public_multiaddr else { - *error_out = LibWalletError::from(InterfaceError::InvalidMultiaddr).code; - return ptr::null_mut(); + + let listen_addresses = if let Some(list_str) = listen_addresses { + match MultiaddrList::from_str(&list_str) { + Ok(val) => val, + Err(e) => { + *error_out = LibWalletError::from(InterfaceError::InvalidMultiaddr(e.to_string())).code; + return ptr::null_mut(); + }, + } + } else { + TariCommsConfig::default().listen_addresses }; let config = TariCommsConfig { override_from: None, public_addresses: MultiaddrList::from_iter(public_multiaddr), - listen_addresses: listen_multiaddr.into_iter().collect(), + listen_addresses, // Wallets will eagerly use a relay - reachability_mode: ReachabilityMode::Private, - rpc_max_simultaneous_sessions: 0, - rpc_max_sessions_per_peer: 0, - enable_mdns: true, + reachability_mode: if reachability_mode_private { + ReachabilityMode::Private + } else { + ReachabilityMode::Auto + }, + rpc_max_simultaneous_sessions: 100, + rpc_max_sessions_per_peer: 10, + enable_mdns, enable_relay: false, - max_inbound_connections_per_peer: Some(1), + max_inbound_connections_per_peer: Some(3), }; Box::into_raw(Box::new(config)) @@ -10297,7 +10311,7 @@ mod test { let address_alice_str = str_to_pointer(LISTEN_MULTIADDR); let alice_network_str = str_to_pointer(NETWORK_STRING); - let alice_config = comms_config_create(ptr::null(), address_alice_str, error_ptr); + let alice_config = comms_config_create(ptr::null(), address_alice_str, true, true, error_ptr); assert_eq!(*error_ptr, 0); let passphrase: *const c_char = @@ -10468,7 +10482,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let network_str: *const c_char = CString::into_raw(network) as *const c_char; - let alice_config = comms_config_create(ptr::null(), address_alice_str, error_ptr); + let alice_config = comms_config_create(ptr::null(), address_alice_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("dolphis dancing in the coastal waters").unwrap()) as *const c_char; @@ -10689,7 +10703,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let network_str: *const c_char = CString::into_raw(network) as *const c_char; - let config = comms_config_create(ptr::null(), address_str, error_ptr); + let config = comms_config_create(ptr::null(), address_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("a cat outside in Istanbul").unwrap()) as *const c_char; @@ -10747,7 +10761,7 @@ mod test { let db_path_str: *const c_char = CString::into_raw(db_path) as *const c_char; let address_str = CString::new(LISTEN_MULTIADDR).unwrap().into_raw() as *const c_char; - let config = comms_config_create(ptr::null(), address_str, error_ptr); + let config = comms_config_create(ptr::null(), address_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("a wave in teahupoo").unwrap()) as *const c_char; @@ -10826,7 +10840,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let network_str: *const c_char = CString::into_raw(network) as *const c_char; - let alice_config = comms_config_create(ptr::null(), address_alice_str, error_ptr); + let alice_config = comms_config_create(ptr::null(), address_alice_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("Satoshi Nakamoto").unwrap()) as *const c_char; @@ -10997,7 +11011,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let network_str: *const c_char = CString::into_raw(network) as *const c_char; - let alice_config = comms_config_create(ptr::null(), address_alice_str, error_ptr); + let alice_config = comms_config_create(ptr::null(), address_alice_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("J-bay open corona").unwrap()) as *const c_char; @@ -11129,7 +11143,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let network_str: *const c_char = CString::into_raw(network) as *const c_char; - let alice_config = comms_config_create(ptr::null(), address_alice_str, error_ptr); + let alice_config = comms_config_create(ptr::null(), address_alice_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("The master and margarita").unwrap()) as *const c_char; @@ -11342,7 +11356,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let network_str: *const c_char = CString::into_raw(network) as *const c_char; - let alice_config = comms_config_create(ptr::null(), address_alice_str, error_ptr); + let alice_config = comms_config_create(ptr::null(), address_alice_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("niao").unwrap()) as *const c_char; let dns_string: *const c_char = CString::into_raw(CString::new("").unwrap()) as *const c_char; @@ -11567,7 +11581,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let network_str: *const c_char = CString::into_raw(network) as *const c_char; - let alice_config = comms_config_create(ptr::null(), address_alice_str, error_ptr); + let alice_config = comms_config_create(ptr::null(), address_alice_str, true, true, error_ptr); assert_eq!(error, 0, "comms_config_create errored"); let passphrase: *const c_char = CString::into_raw(CString::new("niao").unwrap()) as *const c_char; @@ -11818,7 +11832,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let network_str: *const c_char = CString::into_raw(network) as *const c_char; - let config = comms_config_create(ptr::null(), address_str, error_ptr); + let config = comms_config_create(ptr::null(), address_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("niao").unwrap()) as *const c_char; let dns_string: *const c_char = CString::into_raw(CString::new("").unwrap()) as *const c_char; let void_ptr: *mut c_void = &mut (5) as *mut _ as *mut c_void; @@ -12182,7 +12196,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let alice_network_str: *const c_char = CString::into_raw(network) as *const c_char; - let alice_config = comms_config_create(ptr::null(), alice_address_str, error_ptr); + let alice_config = comms_config_create(ptr::null(), alice_address_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("niao").unwrap()) as *const c_char; let dns_string: *const c_char = CString::into_raw(CString::new("").unwrap()) as *const c_char; let void_ptr: *mut c_void = &mut (5) as *mut _ as *mut c_void; @@ -12240,7 +12254,7 @@ mod test { let network = CString::new(NETWORK_STRING).unwrap(); let bob_network_str: *const c_char = CString::into_raw(network) as *const c_char; - let bob_config = comms_config_create(ptr::null(), bob_address_str, error_ptr); + let bob_config = comms_config_create(ptr::null(), bob_address_str, true, true, error_ptr); let passphrase: *const c_char = CString::into_raw(CString::new("niao").unwrap()) as *const c_char; let dns_string: *const c_char = CString::into_raw(CString::new("").unwrap()) as *const c_char; let void_ptr: *mut c_void = &mut (5) as *mut _ as *mut c_void; @@ -12426,19 +12440,21 @@ mod test { // Bob's peer connection to Alice will still be active for a short while until Bob figures out Alice is // gone, and a 'dial_peer' command to Alice from Bob may return the previous connection state, but it // should not be possible to do anything with the connection. - let bob_comms_dial_peer = - bob_wallet_runtime.block_on(async { bob_wallet_comms.dial_peer(alice_peer_id).await.unwrap().await }); - if bob_comms_dial_peer.is_ok() && - bob_wallet_runtime - .block_on(bob_wallet_comms.open_substream( - alice_peer_id, - // TODO: this will cause this to fail even if the peer is active - &StreamProtocol::new("/test/me"), - )) - .is_ok() - { - panic!("Connection to Alice should not be active!"); - } + bob_wallet_runtime.block_on(async { + if let Ok(dial_result) = bob_wallet_comms.dial_peer(alice_peer_id).await { + if (dial_result.await).is_ok() && + bob_wallet_runtime + .block_on(bob_wallet_comms.open_substream( + alice_peer_id, + // TODO: this will cause this to fail even if the peer is active + &StreamProtocol::new("/test/me"), + )) + .is_ok() + { + panic!("Connection to Alice should not be active!"); + } + } + }); // - Bob can still retrieve messages Alice sent let bob_contacts_get_messages = diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 2a6f5a5fb7..9388b64f30 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -2630,7 +2630,9 @@ void transaction_send_status_destroy(TariTransactionSendStatus *status); * The ```comms_config_destroy``` method must be called when finished with a TariCommsConfig to prevent a memory leak */ TariCommsConfig *comms_config_create(const char *public_address, - const char *listen_address, + const char *listen_addresses, + bool enable_mdns, + bool reachability_mode_private, int *error_out); /** diff --git a/common/src/configuration/multiaddr_list.rs b/common/src/configuration/multiaddr_list.rs index ec4ee54a92..56efee198a 100644 --- a/common/src/configuration/multiaddr_list.rs +++ b/common/src/configuration/multiaddr_list.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::str::FromStr; + use libp2p::multiaddr::Multiaddr; use crate::configuration::ConfigList; @@ -27,12 +29,25 @@ use crate::configuration::ConfigList; /// Supports deserialization from a sequence of strings or comma-delimited strings pub type MultiaddrList = ConfigList; +impl FromStr for MultiaddrList { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + s.split(',') + .map(|s| s.replace("\"", "")) + .filter(|s| !s.is_empty()) + .map(|s| s.trim().parse()) + .collect::, _>>() + .map(Into::into) + } +} + #[cfg(test)] mod tests { use std::{str::FromStr, vec}; use config::Config; - use multiaddr::Multiaddr; + use multiaddr::{Error, Multiaddr}; use serde::Deserialize; use crate::configuration::MultiaddrList; @@ -126,4 +141,27 @@ mod tests { Multiaddr::from_str("/ip4/10.0.0.1/tcp/1234").unwrap() ]); } + + #[test] + fn it_deserializes_from_string() { + let list_str_1 = "/ip4/127.0.0.1/tcp/1234, /ip4/192.168.0.1/tcp/1234, /ip4/10.0.0.1/udp/1234/quic-v1,"; + let list_str_2 = + "\" /ip4/127.0.0.1/tcp/1234 \", \"/ip4/192.168.0.1/tcp/1234\", /ip4/10.0.0.1/udp/1234/quic-v1,"; + let list_1 = MultiaddrList::from_str(list_str_1).unwrap(); + let list_2 = MultiaddrList::from_str(list_str_2).unwrap(); + + assert_eq!(list_1, list_2); + assert_eq!(list_1.into_vec(), vec![ + Multiaddr::from_str("/ip4/127.0.0.1/tcp/1234").unwrap(), + Multiaddr::from_str("/ip4/192.168.0.1/tcp/1234").unwrap(), + Multiaddr::from_str("/ip4/10.0.0.1/udp/1234/quic-v1").unwrap() + ]); + + let list_str_err = "\" /ip4/127.0.0.1/udp/1234/some_unknown_protocol \""; + let list_err = MultiaddrList::from_str(list_str_err).unwrap_err(); + match list_err { + Error::UnknownProtocolString(_) => {}, + _ => panic!("Error variant not as expected"), + } + } } diff --git a/integration_tests/README.md b/integration_tests/README.md index fd4a8dc8e0..2da1ce5eca 100644 --- a/integration_tests/README.md +++ b/integration_tests/README.md @@ -43,6 +43,24 @@ In its simplest form you can run the tests from the project route with `cargo te cargo test --release --test cucumber -- -i "tests/features/WalletTransactions*" ``` +## Concurrency + +- The set of wallet FFI tests (`"@wallet-ffi and not @broken"`) should not be run with `--concurrency` greater than 1, + and because it defaults to 64, it should be limited to `--concurrency 1` on the command line. This is also true for + running it in CI. + + ```shell + cargo test --release --test cucumber -- --tags "@wallet-ffi and not @broken" --concurrency 1 --retry 2 + ``` + +- The set of non-wallet FFI tests (`"@critical and (not @long-running) and (not @wallet-ffi) and (not @chat-ffi) and + (not @broken)"`) should not be run with `--concurrency` greater than 1, and because it defaults to 64, it should be + limited to `--concurrency 1` on the command line. This is also true for running it in CI. + + ```shell + cargo test --release --all-features --test cucumber -- --tags "@critical and (not @long-running) and (not @wallet-ffi) and (not @chat-ffi) and (not @broken)" --concurrency 1 --retry 2 + ``` + ## Notes This suite is still a work in progress. We have scenarios marked `@broken` that are known broken and require fixing. diff --git a/integration_tests/log4rs/cucumber.yml b/integration_tests/log4rs/cucumber.yml index 186151ac24..e015173db8 100644 --- a/integration_tests/log4rs/cucumber.yml +++ b/integration_tests/log4rs/cucumber.yml @@ -32,7 +32,7 @@ appenders: count: 10 pattern: "{{log_dir}}/log/network.{}.log" encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{X(grpc)}] {f}.{L} {i} [{t}] {l:5} {m}{n}" + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" # An appender named "base_layer_base_node" that writes to a file with a custom pattern encoder base_layer_base_node: kind: rolling_file @@ -48,7 +48,7 @@ appenders: count: 10 pattern: "{{log_dir}}/log/base_node.{}.log" encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{X(grpc)}] {f}.{L} {i} [{t}] {l:5} {m}{n}" + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" # An appender named "base_layer_wallet" that writes to a file with a custom pattern encoder base_layer_wallet: kind: rolling_file @@ -64,7 +64,7 @@ appenders: count: 10 pattern: "{{log_dir}}/log/wallet.{}.log" encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{X(grpc)}] {f}.{L} {i} [{t}] {l:5} {m}{n}" + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" # An appender named "base_layer_contacts" that writes to a file with a custom pattern encoder base_layer_contacts: kind: rolling_file @@ -80,7 +80,7 @@ appenders: count: 10 pattern: "{{log_dir}}/log/contacts.{}.log" encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{X(grpc)}] {f}.{L} {i} [{t}] {l:5} {m}{n}" + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" # An appender named "other" that writes to a file with a custom pattern encoder other: kind: rolling_file @@ -96,7 +96,7 @@ appenders: count: 5 pattern: "{{log_dir}}/log/other.{}.log" encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{X(grpc)}] {f}.{L} {i} [{t}] {l:5} {m}{n}" + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" # An appender named "miner" that writes to a file with a custom pattern encoder miner: kind: rolling_file @@ -112,7 +112,7 @@ appenders: count: 5 pattern: "{{log_dir}}/log/miner.{}.log" encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{X(grpc)}] {f}.{L} {i} [{t}] {l:5} {m}{n}" + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" # An appender named "proxy" that writes to a file with a custom pattern encoder proxy: kind: rolling_file @@ -128,7 +128,7 @@ appenders: count: 5 pattern: "{{log_dir}}/log/proxy.{}.log" encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{X(grpc)}] {f}.{L} {i} [{t}] {l:5} {m}{n}" + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" # An appender named "libp2p" that writes to a file with a custom pattern encoder libp2p: kind: rolling_file @@ -144,7 +144,7 @@ appenders: count: 5 pattern: "{{log_dir}}/log/libp2p.{}.log" encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{X(grpc)}] {f}.{L} {i} [{t}] {l:5} {m}{n}" + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" # We don't want prints during cucumber test, everything useful will in logs. # root: diff --git a/integration_tests/src/base_node_process.rs b/integration_tests/src/base_node_process.rs index 61452b905e..4a66a956a0 100644 --- a/integration_tests/src/base_node_process.rs +++ b/integration_tests/src/base_node_process.rs @@ -25,27 +25,35 @@ use std::{ fmt::{Debug, Formatter}, net::TcpListener, path::PathBuf, + str::FromStr, sync::Arc, time::Duration, }; +use log::*; use minotari_app_utilities::identity_management::save_identity; use minotari_node::{run_base_node, BaseNodeConfig, GrpcMethod, MetricsConfig}; use minotari_node_grpc_client::BaseNodeGrpcClient; -use tari_common::{configuration::CommonConfig, network_check::set_network_if_choice_valid}; +use tari_common::{ + configuration::{CommonConfig, MultiaddrList}, + network_check::set_network_if_choice_valid, +}; use tari_crypto::ristretto::RistrettoPublicKey; -use tari_network::{identity, multiaddr::Multiaddr}; +use tari_network::{identity, multiaddr::Multiaddr, ReachabilityMode}; use tari_p2p::{auto_update::AutoUpdateConfig, Network, PeerSeedsConfig}; use tari_shutdown::Shutdown; use tokio::task; use tonic::transport::Channel; -use crate::{get_peer_seeds, get_port, wait_for_service, TariWorld}; +use crate::{get_peer_seeds, get_port, wait_for_service, ServiceType, TariWorld}; + +const LOG_TARGET: &str = "cucumber::bas_node_process"; #[derive(Clone)] pub struct BaseNodeProcess { pub name: String, - pub port: u64, + pub tcp_port: u64, + pub udp_port: u64, pub grpc_port: u64, pub identity: identity::Keypair, pub public_key: RistrettoPublicKey, @@ -57,8 +65,11 @@ pub struct BaseNodeProcess { } impl BaseNodeProcess { - pub fn get_listen_addr(&self) -> Multiaddr { - format!("/ip4/127.0.0.1/tcp/{}", self.port).parse().unwrap() + pub fn get_public_addresses(&self) -> Vec { + vec![ + format!("/ip4/127.0.0.1/udp/{}/quic-v1", self.udp_port).parse().unwrap(), + format!("/ip4/127.0.0.1/tcp/{}", self.tcp_port).parse().unwrap(), + ] } } @@ -73,7 +84,8 @@ impl Debug for BaseNodeProcess { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("BaseNodeProcess") .field("name", &self.name) - .field("port", &self.port) + .field("tcp_port", &self.tcp_port) + .field("udp_port", &self.udp_port) .field("grpc_port", &self.grpc_port) .field("identity", &self.identity) .field("temp_dir_path", &self.temp_dir_path) @@ -97,13 +109,15 @@ pub async fn spawn_base_node_with_config( std::env::set_var("TARI_NETWORK", "localnet"); set_network_if_choice_valid(Network::LocalNet).unwrap(); - let port: u64; + let tcp_port: u64; + let udp_port: u64; let grpc_port: u64; let temp_dir_path: PathBuf; let base_node_identity: identity::Keypair; let base_node_identity_path; if let Some(node_ps) = world.base_nodes.get(&bn_name) { - port = node_ps.port; + tcp_port = node_ps.tcp_port; + udp_port = node_ps.udp_port; grpc_port = node_ps.grpc_port; temp_dir_path = node_ps.temp_dir_path.clone(); base_node_identity_path = temp_dir_path.join("base_node_key.bin"); @@ -112,7 +126,8 @@ pub async fn spawn_base_node_with_config( base_node_identity = node_ps.identity.clone(); } else { // each spawned base node will use different ports - port = get_port(18000..18499).unwrap(); + tcp_port = get_port(18000..18499).unwrap(); + udp_port = get_port(18000..18499).unwrap(); grpc_port = get_port(18500..18999).unwrap(); // create a new temporary directory temp_dir_path = world @@ -127,12 +142,14 @@ pub async fn spawn_base_node_with_config( save_identity(&base_node_identity_path, &base_node_identity).unwrap(); }; + debug!(target: LOG_TARGET, "Base node peer id: {}", base_node_identity.public().to_peer_id()); println!("Base node peer id: {}", base_node_identity.public().to_peer_id()); let shutdown = Shutdown::new(); let process = BaseNodeProcess { name: bn_name.clone(), - port, + tcp_port, + udp_port, grpc_port, identity: base_node_identity.clone(), public_key: base_node_identity @@ -153,6 +170,7 @@ pub async fn spawn_base_node_with_config( let peer_seeds = get_peer_seeds(world, &peers).await; + let public_addresses = process.get_public_addresses(); let mut common_config = CommonConfig::default(); common_config.base_path = temp_dir_path.clone(); task::spawn(async move { @@ -169,6 +187,10 @@ pub async fn spawn_base_node_with_config( }; println!("Using base_node temp_dir: {}", temp_dir_path.clone().display()); + debug!(target: LOG_TARGET, "Using base_node temp_dir: {}", temp_dir_path.clone().display()); + let listen_addr_tcp = Multiaddr::from_str(&format!("/ip4/0.0.0.0/tcp/{}", tcp_port)).unwrap(); + let listen_addr_udp = Multiaddr::from_str(&format!("/ip4/0.0.0.0/udp/{}/quic-v1", udp_port)).unwrap(); + base_node_config.base_node.network = Network::LocalNet; base_node_config.base_node.grpc_enabled = true; base_node_config.base_node.grpc_address = Some(format!("/ip4/127.0.0.1/tcp/{}", grpc_port).parse().unwrap()); @@ -179,13 +201,13 @@ pub async fn spawn_base_node_with_config( base_node_config.base_node.identity_file = base_node_identity_path; // base_node_config.base_node.tor_identity_file = PathBuf::from("base_node_tor_id.json"); base_node_config.base_node.max_randomx_vms = 1; - base_node_config.base_node.p2p.enable_mdns = false; + base_node_config.base_node.p2p.enable_mdns = true; base_node_config.base_node.p2p.enable_relay = false; + base_node_config.base_node.p2p.reachability_mode = ReachabilityMode::Auto; + base_node_config.base_node.p2p.public_addresses = MultiaddrList::from(public_addresses); + base_node_config.base_node.p2p.listen_addresses = MultiaddrList::from(vec![listen_addr_tcp, listen_addr_udp]); base_node_config.base_node.lmdb_path = temp_dir_path.to_path_buf(); - base_node_config.base_node.p2p.listen_addresses = - vec![format!("/ip4/127.0.0.1/tcp/{}", port).parse().unwrap()].into(); - base_node_config.base_node.p2p.public_addresses = base_node_config.base_node.p2p.listen_addresses.clone(); base_node_config.base_node.storage.orphan_storage_capacity = 10; if base_node_config.base_node.storage.pruning_horizon != 0 { base_node_config.base_node.storage.pruning_interval = 1; @@ -195,13 +217,19 @@ pub async fn spawn_base_node_with_config( // Hierarchically set the base path for all configs base_node_config.base_node.set_base_path(temp_dir_path.clone()); + debug!( + target: LOG_TARGET, + "Initializing base node: name={}; tcp_port={}; udp_port={}; grpc_port={}; is_seed_node={}", + name_cloned, tcp_port, udp_port, grpc_port, is_seed_node + ); println!( - "Initializing base node: name={}; port={}; grpc_port={}; is_seed_node={}", - name_cloned, port, grpc_port, is_seed_node + "Initializing base node: name={}; tcp_port={}; udp_port={}; grpc_port={}; is_seed_node={}", + name_cloned, tcp_port, udp_port, grpc_port, is_seed_node ); let result = run_base_node(shutdown, Arc::new(base_node_identity), Arc::new(base_node_config)).await; if let Err(e) = result { + error!(target: LOG_TARGET,"{:?}", e); panic!("{:?}", e); } }); @@ -209,11 +237,18 @@ pub async fn spawn_base_node_with_config( // make the new base node able to be referenced by other processes world.base_nodes.insert(bn_name.clone(), process); if is_seed_node { - world.seed_nodes.push(bn_name); + world.seed_nodes.push(bn_name.clone()); } - wait_for_service(port).await; - wait_for_service(grpc_port).await; + debug!(target: LOG_TARGET, "Wait for service..."); + wait_for_service(tcp_port, ServiceType::Tcp, &bn_name).await; + // wait_for_service(udp_port, ServiceType::Udp, &bn_name).await; + wait_for_service(grpc_port, ServiceType::Tcp, &bn_name).await; + debug!( + target: LOG_TARGET, + "Spawned base node: name={}; tcp_port={}; udp_port={}; grpc_port={}; is_seed_node={}", + bn_name, tcp_port, udp_port, grpc_port, is_seed_node + ); } impl BaseNodeProcess { @@ -225,7 +260,7 @@ impl BaseNodeProcess { self.kill_signal.trigger(); loop { // lets wait till the port is cleared - if TcpListener::bind(("127.0.0.1", self.port.try_into().unwrap())).is_ok() { + if TcpListener::bind(("127.0.0.1", self.tcp_port.try_into().unwrap())).is_ok() { break; } } diff --git a/integration_tests/src/ffi/callbacks.rs b/integration_tests/src/ffi/callbacks.rs index c9a6f779bf..4a10ce64cc 100644 --- a/integration_tests/src/ffi/callbacks.rs +++ b/integration_tests/src/ffi/callbacks.rs @@ -292,9 +292,14 @@ impl Callbacks { pub fn on_connectivity_status(&mut self, status: u64) { println!( - "{} Connectivity Status Changed to {}.", + "{} Connectivity Status Changed to '{}'.", chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), - status + match status { + 0 => "Connecting", + 1 => "Online", + 2 => "Offline", + _ => "Status undefined!", + } ); } diff --git a/integration_tests/src/ffi/comms_config.rs b/integration_tests/src/ffi/comms_config.rs index 456ffd2e20..74f9144719 100644 --- a/integration_tests/src/ffi/comms_config.rs +++ b/integration_tests/src/ffi/comms_config.rs @@ -38,13 +38,22 @@ impl Drop for CommsConfig { } impl CommsConfig { - pub fn create(port: u64) -> Self { + pub fn create(tcp_port: u64, udp_port: u64) -> Self { let mut error = 0; let ptr; unsafe { + let _public_addr_tcp = format!("/ip4/127.0.0.1/tcp/{}", tcp_port); + let listen_addr_tcp = format!("/ip4/0.0.0.0/tcp/{}", tcp_port); + let public_addr_udp = format!("/ip4/127.0.0.1/udp/{}/quic-v1", udp_port); + let listen_addr_udp = format!("/ip4/0.0.0.0/udp/{}/quic-v1", udp_port); + ptr = ffi_import::comms_config_create( - CString::new(format!("/ip4/127.0.0.1/tcp/{}", port)).unwrap().into_raw(), - CString::new(format!("/ip4/127.0.0.1/tcp/{}", port)).unwrap().into_raw(), + CString::new(public_addr_udp).unwrap().into_raw(), + CString::new(format!("{}, {}", listen_addr_tcp, listen_addr_udp)) + .unwrap() + .into_raw(), + true, + false, &mut error, ); if error > 0 { diff --git a/integration_tests/src/ffi/contacts_liveness_data.rs b/integration_tests/src/ffi/contacts_liveness_data.rs index dd071e2491..e29e88d125 100644 --- a/integration_tests/src/ffi/contacts_liveness_data.rs +++ b/integration_tests/src/ffi/contacts_liveness_data.rs @@ -20,9 +20,10 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::ptr::null_mut; +use std::{convert::TryFrom, ptr::null_mut}; use libc::c_void; +use tari_contacts::contacts_service::service::ContactMessageType; use super::{ffi_import, FFIString, WalletAddress}; @@ -81,17 +82,23 @@ impl ContactsLivenessData { FFIString::from_ptr(ptr).as_string() } - pub fn get_message_type(&self) -> i32 { + pub fn get_message_type(&self) -> ContactMessageType { let message_type; let mut error = 0; unsafe { message_type = ffi_import::liveness_data_get_message_type(self.ptr, &mut error); if error > 0 { - println!("liveness_data_get_message_type error {}", error); - panic!("liveness_data_get_message_type error"); + println!("liveness_data_get_message_type ffi error {}", error); + panic!("liveness_data_get_message_type ffi error"); } } - message_type + match ContactMessageType::from_byte(u8::try_from(message_type).unwrap()) { + Some(val) => val, + None => { + println!("liveness_data_get_message_type conversion error"); + panic!("liveness_data_get_message_type conversion error"); + }, + } } pub fn get_online_status(&self) -> String { diff --git a/integration_tests/src/ffi/ffi_import.rs b/integration_tests/src/ffi/ffi_import.rs index 9684d93e4b..da125d9040 100644 --- a/integration_tests/src/ffi/ffi_import.rs +++ b/integration_tests/src/ffi/ffi_import.rs @@ -362,7 +362,9 @@ extern "C" { // pub fn transport_config_destroy(transport: *mut TariTransportConfig); pub fn comms_config_create( public_address: *const c_char, - listen_address: *const c_char, + listen_addresses: *const c_char, + enable_mdns: bool, + reachability_mode_private: bool, error_out: *mut c_int, ) -> *mut TariCommsConfig; pub fn comms_config_destroy(wc: *mut TariCommsConfig); diff --git a/integration_tests/src/ffi/wallet.rs b/integration_tests/src/ffi/wallet.rs index d0debc152d..a2693bc65d 100644 --- a/integration_tests/src/ffi/wallet.rs +++ b/integration_tests/src/ffi/wallet.rs @@ -301,8 +301,8 @@ impl Wallet { unsafe { ptr = ffi_import::wallet_get_tari_interactive_address(self.ptr, &mut error); if error > 0 { - println!("wallet_get_tari_interactive_address error {}", error); - panic!("wallet_get_tari_interactive_address error"); + println!("wallet_get_tari_interactive_address (get_address) error {}", error); + panic!("wallet_get_tari_interactive_address (get_address) error"); } } WalletAddress::from_ptr(ptr) diff --git a/integration_tests/src/ffi/wallet_address.rs b/integration_tests/src/ffi/wallet_address.rs index a7ae222723..c3cf91857a 100644 --- a/integration_tests/src/ffi/wallet_address.rs +++ b/integration_tests/src/ffi/wallet_address.rs @@ -48,8 +48,8 @@ impl WalletAddress { unsafe { ptr = ffi_import::tari_address_from_base58(CString::new(address).unwrap().into_raw(), &mut error); if error > 0 { - println!("wallet_get_tari_interactive_address error {}", error); - panic!("wallet_get_tari_interactive_address error"); + println!("wallet_get_tari_interactive_address (from_base58) error {}", error); + panic!("wallet_get_tari_interactive_address (from_base58) error"); } } Self { ptr } @@ -62,8 +62,8 @@ impl WalletAddress { unsafe { ptr = ffi_import::emoji_id_to_tari_address(CString::new(emoji_id).unwrap().into_raw(), &mut error); if error > 0 { - println!("wallet_get_tari_interactive_address error {}", error); - panic!("wallet_get_tari_interactive_address error"); + println!("wallet_get_tari_interactive_address (from_emoji_id) error {}", error); + panic!("wallet_get_tari_interactive_address (from_emoji_id) error"); } } Self { ptr } @@ -75,8 +75,8 @@ impl WalletAddress { unsafe { ptr = ffi_import::tari_address_get_bytes(self.ptr, &mut error); if error > 0 { - println!("wallet_get_tari_interactive_address error {}", error); - panic!("wallet_get_tari_interactive_address error"); + println!("wallet_get_tari_interactive_address (address) error {}", error); + panic!("wallet_get_tari_interactive_address (address) error"); } } FFIBytes::from_ptr(ptr) diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 180fd397b1..106e9d4da9 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -24,7 +24,7 @@ use std::{convert::TryFrom, net::TcpListener, ops::Range, path::PathBuf, process use rand::Rng; use tari_p2p::peer_seeds::SeedPeer; -use tokio::net::TcpSocket; +use tokio::net::{TcpSocket, UdpSocket}; pub mod base_node_process; // pub mod chat_client; @@ -58,19 +58,51 @@ pub fn get_base_dir() -> PathBuf { crate_root.join(format!("tests/temp/cucumber_{}", process::id())) } -pub async fn wait_for_service(port: u64) { +#[derive(Debug)] +pub enum ServiceType { + Tcp, + Udp, +} + +pub async fn wait_for_service(port: u64, service_type: ServiceType, process_name: &str) { // Check if we can open a socket to a port. let max_tries = 4 * 60; let mut attempts = 0; let addr = ([127u8, 0, 0, 1], u16::try_from(port).unwrap()).into(); loop { - if TcpSocket::new_v4().unwrap().connect(addr).await.is_ok() { - return; + match service_type { + ServiceType::Tcp => { + if TcpSocket::new_v4().unwrap().connect(addr).await.is_ok() { + println!( + "Service '{:?}' on port '{}' started for '{}' after {} ms", + service_type, + port, + process_name, + attempts * 250 + ); + return; + } + }, + ServiceType::Udp => { + if UdpSocket::bind(addr).await.is_ok() { + println!( + "Service '{:?}' on port '{}' started for '{}' after {} ms", + service_type, + port, + process_name, + attempts * 250 + ); + return; + } + }, } if attempts >= max_tries { - panic!("Service on port {} never started", port); + panic!( + "Service '{:?}' on port '{}' never started for '{}'", + service_type, port, process_name + ); } tokio::time::sleep(Duration::from_millis(250)).await; @@ -83,9 +115,12 @@ pub async fn get_peer_seeds(world: &TariWorld, seed_peer_names: &[String]) -> Ve .iter() .map(|peer_string| { let bn = world.base_nodes.get(peer_string.as_str()).unwrap(); - SeedPeer::new(bn.public_key.clone(), vec![format!("/ip4/127.0.0.1/tcp/{}", bn.port) - .parse() - .unwrap()]) + SeedPeer::new(bn.public_key.clone(), vec![format!( + "/ip4/127.0.0.1/tcp/{}", + bn.tcp_port + ) + .parse() + .unwrap()]) .to_string() }) .collect() diff --git a/integration_tests/src/wallet_ffi.rs b/integration_tests/src/wallet_ffi.rs index 2df98c77d2..80f66a2c31 100644 --- a/integration_tests/src/wallet_ffi.rs +++ b/integration_tests/src/wallet_ffi.rs @@ -54,17 +54,19 @@ use crate::{ #[derive(Debug)] pub struct WalletFFI { pub name: String, - pub port: u64, + pub tcp_port: u64, + pub udp_port: u64, pub base_dir: PathBuf, pub wallet: Arc>, } impl WalletFFI { fn spawn(name: String, seed_words_ptr: *const c_void, base_dir: PathBuf) -> Self { - let port = get_port(18000..18499).unwrap(); - let base_dir_path = base_dir.join("ffi_wallets").join(format!("{}_port_{}", name, port)); + let tcp_port = get_port(18000..18499).unwrap(); + let udp_port = get_port(18000..18499).unwrap(); + let base_dir_path = base_dir.join("ffi_wallets").join(format!("{}_port_{}", name, tcp_port)); let base_dir: String = base_dir_path.as_os_str().to_str().unwrap().into(); - let comms_config = ffi::CommsConfig::create(port); + let comms_config = ffi::CommsConfig::create(tcp_port, udp_port); let log_path = base_dir_path .join("logs") .join("ffi_wallet.log") @@ -75,7 +77,8 @@ impl WalletFFI { let wallet = ffi::Wallet::create(comms_config, base_dir, log_path, seed_words_ptr); Self { name, - port, + tcp_port, + udp_port, base_dir: base_dir_path, wallet, } @@ -175,9 +178,10 @@ impl WalletFFI { pub fn restart(&mut self) { self.wallet.lock().unwrap().destroy(); - let port = get_port(18000..18499).unwrap(); + let tcp_port = get_port(18000..18499).unwrap(); + let udp_port = get_port(18000..18499).unwrap(); let now: DateTime = SystemTime::now().into(); - let comms_config = ffi::CommsConfig::create(port); + let comms_config = ffi::CommsConfig::create(tcp_port, udp_port); let base_dir = format!("./log/ffi_wallets/{}", now.format("%Y%m%d-%H%M%S")); let log_path = format!("{}/log/ffi_wallet.log", base_dir); self.wallet = ffi::Wallet::create(comms_config, base_dir, log_path, null()); diff --git a/integration_tests/src/wallet_process.rs b/integration_tests/src/wallet_process.rs index 4a1601b877..c516498946 100644 --- a/integration_tests/src/wallet_process.rs +++ b/integration_tests/src/wallet_process.rs @@ -37,7 +37,7 @@ use tari_shutdown::Shutdown; use tokio::runtime; use tonic::transport::Channel; -use crate::{get_peer_seeds, get_port, wait_for_service, TariWorld}; +use crate::{get_peer_seeds, get_port, wait_for_service, ServiceType, TariWorld}; #[derive(Clone, Debug)] pub struct WalletProcess { @@ -45,7 +45,8 @@ pub struct WalletProcess { pub grpc_port: u64, pub kill_signal: Shutdown, pub name: String, - pub port: u64, + pub tcp_port: u64, + pub udp_port: u64, pub temp_dir_path: PathBuf, } @@ -67,19 +68,22 @@ pub async fn spawn_wallet( std::env::set_var("TARI_NETWORK", "localnet"); set_network_if_choice_valid(Network::LocalNet).unwrap(); - let port: u64; + let tcp_port: u64; + let udp_port: u64; let grpc_port: u64; let temp_dir_path: PathBuf; let mut wallet_config: WalletConfig; if let Some(wallet_ps) = world.wallets.get(&wallet_name) { - port = wallet_ps.port; + tcp_port = wallet_ps.tcp_port; + udp_port = wallet_ps.udp_port; grpc_port = wallet_ps.grpc_port; temp_dir_path = wallet_ps.temp_dir_path.clone(); wallet_config = wallet_ps.config.clone(); } else { // each spawned wallet will use different ports - port = get_port(18000..18499).unwrap(); + tcp_port = get_port(18000..18499).unwrap(); + udp_port = get_port(18000..18499).unwrap(); grpc_port = get_port(18500..18999).unwrap(); temp_dir_path = world @@ -96,19 +100,21 @@ pub async fn spawn_wallet( }; let base_node = base_node_name.map(|name| { - let pubkey = world.base_nodes.get(&name).unwrap().public_key.clone(); - let port = world.base_nodes.get(&name).unwrap().port; - let set_base_node_request = SetBaseNodeRequest { - net_address: format! {"/ip4/127.0.0.1/tcp/{}", port}, - public_key_hex: pubkey.to_string(), + let bn_pubkey = world.base_nodes.get(&name).unwrap().public_key.clone(); + let bn_udp_port = world.base_nodes.get(&name).unwrap().udp_port; + let bn_addr = format!("/ip4/127.0.0.1/udp/{}/quic-v1", bn_udp_port); + let base_node_request = SetBaseNodeRequest { + net_address: bn_addr.clone(), + public_key_hex: bn_pubkey.to_string(), }; + let custom_base_node = format!("{}::{}", bn_pubkey, bn_addr); - (pubkey, port, set_base_node_request) + (custom_base_node, base_node_request) }); + let base_node_cloned = base_node.clone(); let peer_seeds = get_peer_seeds(world, &peer_seeds).await; - let base_node_cloned = base_node.clone(); let shutdown = Shutdown::new(); let mut send_to_thread_shutdown = shutdown.clone(); @@ -129,7 +135,10 @@ pub async fn spawn_wallet( }; eprintln!("Using wallet temp_dir: {}", temp_dir_path.clone().display()); - let listen_addr = Multiaddr::from_str(&format!("/ip4/127.0.0.1/tcp/{}", port)).unwrap(); + let public_addr_tcp = Multiaddr::from_str(&format!("/ip4/127.0.0.1/tcp/{}", tcp_port)).unwrap(); + let listen_addr_tcp = Multiaddr::from_str(&format!("/ip4/0.0.0.0/tcp/{}", tcp_port)).unwrap(); + let public_addr_udp = Multiaddr::from_str(&format!("/ip4/127.0.0.1/udp/{}/quic-v1", udp_port)).unwrap(); + let listen_addr_udp = Multiaddr::from_str(&format!("/ip4/0.0.0.0/udp/{}/quic-v1", udp_port)).unwrap(); wallet_app_config.wallet.identity_file = Some(temp_dir_path.clone().join("wallet_id.json")); wallet_app_config.wallet.network = Network::LocalNet; @@ -139,14 +148,15 @@ pub async fn spawn_wallet( Some(Multiaddr::from_str(&format!("/ip4/127.0.0.1/tcp/{}", grpc_port)).unwrap()); wallet_app_config.wallet.db_file = PathBuf::from("console_wallet.db"); wallet_app_config.wallet.contacts_auto_ping_interval = Duration::from_secs(2); - wallet_app_config.wallet.p2p.enable_mdns = false; + wallet_app_config.wallet.p2p.enable_mdns = true; wallet_app_config.wallet.p2p.enable_relay = false; + wallet_app_config.wallet.p2p.reachability_mode = ReachabilityMode::Auto; + wallet_app_config.wallet.p2p.public_addresses = MultiaddrList::from(vec![public_addr_tcp, public_addr_udp]); + wallet_app_config.wallet.p2p.listen_addresses = MultiaddrList::from(vec![listen_addr_tcp, listen_addr_udp]); wallet_app_config .wallet .base_node_service_config .base_node_monitor_max_refresh_interval = Duration::from_secs(15); - wallet_app_config.wallet.p2p.listen_addresses = MultiaddrList::from(vec![listen_addr.clone()]); - wallet_app_config.wallet.p2p.public_addresses = MultiaddrList::from(vec![listen_addr]); if let Some(mech) = routing_mechanism { wallet_app_config .wallet @@ -154,9 +164,7 @@ pub async fn spawn_wallet( .transaction_routing_mechanism = mech; } - // FIXME: wallet doesn't pick up the custom base node for some reason atm - wallet_app_config.wallet.custom_base_node = - base_node_cloned.map(|(pubkey, port, _)| format!("{}::/ip4/127.0.0.1/tcp/{}", pubkey, port)); + wallet_app_config.wallet.custom_base_node = base_node_cloned.map(|(custom_base_node, _)| custom_base_node); wallet_app_config.wallet.set_base_path(temp_dir_path.clone()); @@ -177,23 +185,24 @@ pub async fn spawn_wallet( world.wallets.insert(wallet_name.clone(), WalletProcess { config: wallet_config, name: wallet_name.clone(), - port, + tcp_port, grpc_port, temp_dir_path: temp_dir, kill_signal: shutdown, + udp_port, }); tokio::time::sleep(Duration::from_secs(5)).await; - wait_for_service(port).await; - wait_for_service(grpc_port).await; + wait_for_service(tcp_port, ServiceType::Tcp, &wallet_name).await; + wait_for_service(grpc_port, ServiceType::Tcp, &wallet_name).await; - if let Some((_, _, hacky_request)) = base_node { + if let Some((_, base_node_request)) = base_node { let mut wallet_client = create_wallet_client(world, wallet_name.clone()) .await .expect("wallet grpc client"); - let _resp = wallet_client.set_base_node(hacky_request).await.unwrap(); + let _resp = wallet_client.set_base_node(base_node_request).await.unwrap(); } tokio::time::sleep(Duration::from_secs(2)).await; diff --git a/integration_tests/src/world.rs b/integration_tests/src/world.rs index ddd6babe87..242188e583 100644 --- a/integration_tests/src/world.rs +++ b/integration_tests/src/world.rs @@ -22,12 +22,13 @@ use std::{ collections::VecDeque, - fmt::{Debug, Formatter}, + fmt::{Debug, Display, Formatter}, path::PathBuf, }; use cucumber::gherkin::{Feature, Scenario}; use indexmap::IndexMap; +use log::*; use rand::rngs::OsRng; use serde_json::Value; // use tari_chat_client::ChatClient; @@ -57,6 +58,8 @@ use crate::{ wallet_process::WalletProcess, }; +const LOG_TARGET: &str = "cucumber::world"; + #[derive(Error, Debug)] pub enum TariWorldError { #[error("Base node process not found: {0}")] @@ -71,6 +74,8 @@ pub enum TariWorldError { MergeMinerProcessNotFound(String), #[error("No base node, or wallet client found: {0}")] ClientNotFound(String), + #[error("Conversion error: {0}")] + ConversionError(String), } #[derive(cucumber::World)] @@ -86,7 +91,7 @@ pub struct TariWorld { // pub chat_clients: IndexMap>, pub merge_mining_proxies: IndexMap, pub transactions: IndexMap, - pub wallet_addresses: IndexMap, // values are strings representing tari addresses + pub stopped_wallet_addresses: IndexMap, // values are strings representing tari addresses pub utxos: IndexMap, pub output_hash: Option, pub pre_image: Option, @@ -128,7 +133,7 @@ impl Default for TariWorld { // chat_clients: Default::default(), merge_mining_proxies: Default::default(), transactions: Default::default(), - wallet_addresses: Default::default(), + stopped_wallet_addresses: Default::default(), utxos: Default::default(), output_hash: None, pre_image: None, @@ -157,7 +162,7 @@ impl Debug for TariWorld { .field("merge_mining_proxies", &self.merge_mining_proxies) // .field("chat_clients", &self.chat_clients.keys()) .field("transactions", &self.transactions) - .field("wallet_addresses", &self.wallet_addresses) + .field("stopped_wallet_addresses", &self.stopped_wallet_addresses) .field("utxos", &self.utxos) .field("output_hash", &self.output_hash) .field("pre_image", &self.pre_image) @@ -177,14 +182,20 @@ pub enum NodeClient { } impl TariWorld { - pub async fn get_node_client>( + pub async fn get_node_client + Display>( &self, name: &S, ) -> anyhow::Result> { - self.get_node(name)?.get_grpc_client().await + match self.get_node(name) { + Ok(node) => node.get_grpc_client().await, + Err(err) => { + error!(target: LOG_TARGET, "get_node_client '{}': {}", name, err); + Err(err) + }, + } } - pub async fn get_base_node_or_wallet_client>( + pub async fn get_base_node_or_wallet_client + Display>( &self, name: S, ) -> anyhow::Result { @@ -192,28 +203,38 @@ impl TariWorld { Ok(client) => Ok(NodeClient::BaseNode(client)), Err(_) => match self.get_wallet_client(&name).await { Ok(wallet) => Ok(NodeClient::Wallet(wallet)), - Err(e) => Err(TariWorldError::ClientNotFound(e.to_string()).into()), + Err(err) => { + error!(target: LOG_TARGET, "get_base_node_or_wallet_client '{}': {}", name, err); + Err(TariWorldError::ClientNotFound(err.to_string()).into()) + }, }, } } - pub async fn get_wallet_address>(&self, name: &S) -> anyhow::Result { - if let Some(address) = self.wallet_addresses.get(name.as_ref()) { + pub async fn get_wallet_address_base58 + Display>(&self, name: &S) -> anyhow::Result { + if let Some(address) = self.stopped_wallet_addresses.get(name.as_ref()) { + if let Err(err) = TariAddress::from_base58(address) { + error!(target: LOG_TARGET, "get_wallet_address conversion '{}': {}", name, err); + return Err(TariWorldError::ConversionError(err.to_string()).into()); + } return Ok(address.clone()); } let address_bytes = match self.get_wallet_client(name).await { - Ok(wallet) => { - let mut wallet = wallet; - - wallet - .get_address(minotari_wallet_grpc_client::grpc::Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address + Ok(mut wallet) => match wallet.get_address(minotari_wallet_grpc_client::grpc::Empty {}).await { + Ok(response) => response.into_inner().interactive_address, + Err(err) => { + error!(target: LOG_TARGET, "get_wallet_address no client '{}': {}", name, err); + return Err(TariWorldError::ClientNotFound(err.to_string()).into()); + }, }, Err(_) => { - let ffi_wallet = self.get_ffi_wallet(name).unwrap(); + let ffi_wallet = match self.get_ffi_wallet(name) { + Ok(wallet) => wallet, + Err(err) => { + error!(target: LOG_TARGET, "get_wallet_address no FFI wallet '{}': {}", name.as_ref(), err); + return Err(TariWorldError::FFIWalletNotFound(err.to_string()).into()); + }, + }; ffi_wallet.get_address().address().get_vec() }, @@ -222,64 +243,90 @@ impl TariWorld { Ok(tari_address.to_base58()) } - #[allow(dead_code)] - pub async fn get_wallet_client>( + pub async fn get_wallet_client + Display>( &self, name: &S, ) -> anyhow::Result> { - self.get_wallet(name)?.get_grpc_client().await + match self.get_wallet(name) { + Ok(wallet) => wallet.get_grpc_client().await, + Err(err) => { + error!(target: LOG_TARGET, "get_wallet_client '{}': {}", name, err); + Err(err) + }, + } } - pub fn get_node>(&self, node_name: &S) -> anyhow::Result<&BaseNodeProcess> { - Ok(self - .base_nodes - .get(node_name.as_ref()) - .ok_or_else(|| TariWorldError::BaseNodeProcessNotFound(node_name.as_ref().to_string()))?) + pub fn get_node + Display>(&self, node_name: &S) -> anyhow::Result<&BaseNodeProcess> { + match self.base_nodes.get(node_name.as_ref()) { + Some(node) => Ok(node), + None => { + error!(target: LOG_TARGET, "get_node '{}' process not found", node_name); + Err(TariWorldError::BaseNodeProcessNotFound(node_name.as_ref().to_string()).into()) + }, + } } - pub fn get_wallet>(&self, wallet_name: &S) -> anyhow::Result<&WalletProcess> { - Ok(self - .wallets - .get(wallet_name.as_ref()) - .ok_or_else(|| TariWorldError::WalletProcessNotFound(wallet_name.as_ref().to_string()))?) + pub fn get_wallet + Display>(&self, wallet_name: &S) -> anyhow::Result<&WalletProcess> { + match self.wallets.get(wallet_name.as_ref()) { + Some(wallet) => Ok(wallet), + None => { + error!(target: LOG_TARGET, "get_wallet '{}' process not found", wallet_name); + Err(TariWorldError::WalletProcessNotFound(wallet_name.as_ref().to_string()).into()) + }, + } } - pub fn get_ffi_wallet>(&self, wallet_name: &S) -> anyhow::Result<&WalletFFI> { - Ok(self - .ffi_wallets - .get(wallet_name.as_ref()) - .ok_or_else(|| TariWorldError::FFIWalletNotFound(wallet_name.as_ref().to_string()))?) + pub fn get_ffi_wallet + Display>(&self, wallet_name: &S) -> anyhow::Result<&WalletFFI> { + match self.ffi_wallets.get(wallet_name.as_ref()) { + Some(wallet) => Ok(wallet), + None => { + error!(target: LOG_TARGET, "get_ffi_wallet '{}' process not found", wallet_name); + Err(TariWorldError::FFIWalletNotFound(wallet_name.as_ref().to_string()).into()) + }, + } } pub fn get_mut_ffi_wallet>(&mut self, wallet_name: &S) -> anyhow::Result<&mut WalletFFI> { - Ok(self - .ffi_wallets - .get_mut(wallet_name.as_ref()) - .ok_or_else(|| TariWorldError::FFIWalletNotFound(wallet_name.as_ref().to_string()))?) + match self.ffi_wallets.get_mut(wallet_name.as_ref()) { + Some(wallet) => Ok(wallet), + None => { + error!(target: LOG_TARGET, "get_mut_ffi_wallet '{}' process not found", wallet_name.as_ref()); + Err(TariWorldError::FFIWalletNotFound(wallet_name.as_ref().to_string()).into()) + }, + } } pub fn get_miner>(&self, miner_name: S) -> anyhow::Result<&MinerProcess> { - Ok(self - .miners - .get(miner_name.as_ref()) - .ok_or_else(|| TariWorldError::MinerProcessNotFound(miner_name.as_ref().to_string()))?) + match self.miners.get(miner_name.as_ref()) { + Some(miner) => Ok(miner), + None => { + error!(target: LOG_TARGET, "get_miner '{}' process not found", miner_name.as_ref()); + Err(TariWorldError::MinerProcessNotFound(miner_name.as_ref().to_string()).into()) + }, + } } pub fn get_merge_miner>(&self, miner_name: S) -> anyhow::Result<&MergeMiningProxyProcess> { - Ok(self - .merge_mining_proxies - .get(miner_name.as_ref()) - .ok_or_else(|| TariWorldError::MergeMinerProcessNotFound(miner_name.as_ref().to_string()))?) + match self.merge_mining_proxies.get(miner_name.as_ref()) { + Some(miner) => Ok(miner), + None => { + error!(target: LOG_TARGET, "get_merge_miner '{}' process not found", miner_name.as_ref()); + Err(TariWorldError::MergeMinerProcessNotFound(miner_name.as_ref().to_string()).into()) + }, + } } pub fn get_mut_merge_miner>( &mut self, miner_name: S, ) -> anyhow::Result<&mut MergeMiningProxyProcess> { - Ok(self - .merge_mining_proxies - .get_mut(miner_name.as_ref()) - .ok_or_else(|| TariWorldError::MergeMinerProcessNotFound(miner_name.as_ref().to_string()))?) + match self.merge_mining_proxies.get_mut(miner_name.as_ref()) { + Some(miner) => Ok(miner), + None => { + error!(target: LOG_TARGET, "get_mut_merge_miner '{}' process not found", miner_name.as_ref()); + Err(TariWorldError::MergeMinerProcessNotFound(miner_name.as_ref().to_string()).into()) + }, + } } pub fn all_seed_nodes(&self) -> &[String] { @@ -303,7 +350,7 @@ impl TariWorld { } for (name, mut p) in self.base_nodes.drain(..) { println!("Shutting down base node {}", name); - // You have explicitly trigger the shutdown now because of the change to use Arc/Mutex in tari_shutdown + // You have explicitly triggered the shutdown now because of the change to use Arc/Mutex in tari_shutdown p.kill_signal.trigger(); } } diff --git a/integration_tests/tests/cucumber.rs b/integration_tests/tests/cucumber.rs index e8152c8534..632b53e930 100644 --- a/integration_tests/tests/cucumber.rs +++ b/integration_tests/tests/cucumber.rs @@ -68,7 +68,6 @@ fn main() { let world = TariWorld::cucumber() .repeat_failed() // following config needed to use eprint statements in the tests - .max_concurrent_scenarios(5) .after(move |_feature, _rule, scenario, ev, maybe_world| { let stdout_buffer = stdout_buffer_clone.clone(); Box::pin(async move { diff --git a/integration_tests/tests/features/WalletFFI.feature b/integration_tests/tests/features/WalletFFI.feature index dcd17ebf0e..e63cf3f14d 100644 --- a/integration_tests/tests/features/WalletFFI.feature +++ b/integration_tests/tests/features/WalletFFI.feature @@ -10,6 +10,8 @@ Feature: Wallet FFI And I want to get emoji id of ffi wallet FFI_WALLET And I stop ffi wallet FFI_WALLET + # Failing with: (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION) + @broken Scenario: As a client I want to be able to restore my ffi wallet from seed words Given I have a base node BASE When I have wallet SPECTATOR connected to base node BASE @@ -40,19 +42,31 @@ Feature: Wallet FFI And I stop ffi wallet FFI_WALLET Scenario: As a client I want to cancel a transaction - Given I have a base node BASE + Given I have a seed node SEED + When I have a base node BASE connected to all seed nodes When I have wallet SENDER connected to base node BASE + When I have wallet RECEIVER connected to base node BASE And I have a ffi wallet FFI_WALLET connected to base node BASE + # Force some P2P discovery with contact liveness + When I add contact with alias ALIAS_SENDER and address of SENDER to ffi wallet FFI_WALLET + When I add contact with alias ALIAS_RECEIVER and address of RECEIVER to ffi wallet FFI_WALLET + When I add contact with alias ALIAS_FFI_WALLET and address of FFI_WALLET to wallet SENDER + When I add contact with alias ALIAS_FFI_WALLET and address of FFI_WALLET to wallet RECEIVER + When I add contact with alias ALIAS_RECEIVER and address of RECEIVER to wallet SENDER + When I add contact with alias ALIAS_SENDER and address of SENDER to wallet RECEIVER + Then I wait for ffi wallet FFI_WALLET to have at least 2 contacts to be Online + And I stop wallet RECEIVER + # Now continue When I have mining node MINER connected to base node BASE and wallet SENDER When mining node MINER mines 10 blocks + Then all nodes are at height 10 Then I wait for wallet SENDER to have at least 1000000 uT - And I send 2000000 uT without waiting for broadcast from wallet SENDER to wallet FFI_WALLET at fee 20 - Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST + # Note: This step requires more than 1 base node so the base node can return a synced status + And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 And wallet SENDER detects all transactions are at least Broadcast + Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST When mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT - When I have wallet RECEIVER connected to base node BASE - And I stop wallet RECEIVER And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 20 Then I wait for ffi wallet FFI_WALLET to have 1 pending outbound transaction Then I cancel all outbound transactions on ffi wallet FFI_WALLET and it will cancel 1 transaction @@ -79,16 +93,8 @@ Feature: Wallet FFI # Start the contact liveness pings by adding contacts to the FFI wallet When I add contact with alias ALIAS1 and address of WALLET1 to ffi wallet FFI_WALLET And I add contact with alias ALIAS2 and address of WALLET2 to ffi wallet FFI_WALLET - # Do some mining and send transactions to force P2P discovery - And I have mining node MINER1 connected to base node SEED and wallet WALLET1 - And I have mining node MINER2 connected to base node SEED and wallet WALLET2 - And mining node MINER1 mines 1 blocks - And mining node MINER2 mines 5 blocks - Then I wait for wallet WALLET1 to have at least 100000000 uT - And I wait for wallet WALLET2 to have at least 100000000 uT - When I send 100000000 uT without waiting for broadcast from wallet WALLET1 to wallet FFI_WALLET at fee 20 - And I send 100000000 uT without waiting for broadcast from wallet WALLET2 to wallet FFI_WALLET at fee 20 - # If the FFI wallet can send the transactions, P2P connectivity has been established + When I add contact with alias ALIAS_FFI_WALLET and address of FFI_WALLET to wallet WALLET1 + When I add contact with alias ALIAS_FFI_WALLET and address of FFI_WALLET to wallet WALLET2 Then I wait for ffi wallet FFI_WALLET to have at least 2 contacts to be Online And I stop ffi wallet FFI_WALLET @@ -97,47 +103,66 @@ Feature: Wallet FFI Given I have a seed node SEED When I have a base node BASE1 connected to all seed nodes When I have wallet SENDER connected to base node BASE1 + When I have wallet RECEIVER connected to base node BASE1 And I have a ffi wallet FFI_WALLET connected to base node BASE1 # Force some P2P discovery with contact liveness - When I add contact with alias ALIAS1 and address of SENDER to ffi wallet FFI_WALLET - When I have wallet RECEIVER connected to base node BASE1 + When I add contact with alias ALIAS_SENDER and address of SENDER to ffi wallet FFI_WALLET + When I add contact with alias ALIAS_RECEIVER and address of RECEIVER to ffi wallet FFI_WALLET + When I add contact with alias ALIAS_FFI_WALLET and address of FFI_WALLET to wallet SENDER + When I add contact with alias ALIAS_FFI_WALLET and address of FFI_WALLET to wallet RECEIVER + When I add contact with alias ALIAS_RECEIVER and address of RECEIVER to wallet SENDER + When I add contact with alias ALIAS_SENDER and address of SENDER to wallet RECEIVER + Then I wait for ffi wallet FFI_WALLET to have at least 2 contacts to be Online + # Now continue When I have mining node MINER connected to base node BASE1 and wallet SENDER When mining node MINER mines 10 blocks Then all nodes are at height 10 - Then I wait for wallet SENDER to have at least 2000000 uT + Then I wait for wallet SENDER to have at least 4100000 uT + # Provide funds to the FFI and RECEIVER wallets + And I send 2000000 uT from wallet SENDER to wallet RECEIVER at fee 20 And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST + Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled + # Now we mine some blocks When mining node MINER mines 10 blocks Then all nodes are at height 20 Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT + Then I wait for wallet RECEIVER to have at least 1000000 uT + # Now we send 1 transaction from the FFI wallet to the RECEIVER wallet and vice versa + And I send 500000 uT from wallet RECEIVER to wallet FFI_WALLET at fee 20 + Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled + Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 20 Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_BROADCAST - Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled - Then I wait until base node BASE1 has 1 unconfirmed transactions in its mempool - Then I wait until base node SEED has 1 unconfirmed transactions in its mempool + Then wallet RECEIVER has at least 2 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled + Then I wait until base node BASE1 has 2 unconfirmed transactions in its mempool + Then I wait until base node SEED has 2 unconfirmed transactions in its mempool # The broadcast check does not include delivery; create some holding points to ensure it was received When mining node MINER mines 4 blocks Then all nodes are at height 24 - Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_MINED -# When mining node MINER mines 6 blocks - Then I wait for wallet RECEIVER to have at least 1000000 uT - And I have 1 received and 1 send transaction in ffi wallet FFI_WALLET + Then ffi wallet FFI_WALLET detects AT_LEAST 3 ffi transactions to be TRANSACTION_STATUS_MINED + Then I wait for wallet RECEIVER to have at least 1400000 uT + # Check the FFI wallet's stats + And I have 2 received and 1 send transaction in ffi wallet FFI_WALLET And I start TXO validation on ffi wallet FFI_WALLET And I start TX validation on ffi wallet FFI_WALLET - Then I wait for ffi wallet FFI_WALLET to receive 2 mined + Then I wait for ffi wallet FFI_WALLET to receive 3 mined Then I want to view the transaction kernels for completed transactions in ffi wallet FFI_WALLET And I stop ffi wallet FFI_WALLET + # Broken due to SAF not being implemented @critical @broken - Scenario: As a client I want to receive Tari via my Public Key sent while I am offline when I come back online + Scenario: As a client I want to receive Tari sent when offline Given I have a seed node SEED When I have a base node BASE1 connected to all seed nodes When I have wallet SENDER connected to base node BASE1 And I have a ffi wallet FFI_WALLET connected to base node SEED - # Force some P2P discovery with contact liveness - When I add contact with alias ALIAS1 and address of SENDER to ffi wallet FFI_WALLET + When I add contact with alias ALIAS_SENDER_1 and address of SENDER to ffi wallet FFI_WALLET + When I add contact with alias ALIAS_FFI_WALLET_1 and address of FFI_WALLET to wallet SENDER + Then I wait for ffi wallet FFI_WALLET to have at least 1 contacts to be Online + # Now continue # Established comms by funding the FFI wallet When I have mining node MINER connected to base node BASE1 and wallet SENDER @@ -158,10 +183,15 @@ Feature: Wallet FFI # Let's restart the wallet and see if it can receive the offline transaction And I restart ffi wallet FFI_WALLET connected to base node BASE1 - When I add contact with alias ALIAS2 and address of SENDER to ffi wallet FFI_WALLET + # Force some P2P discovery with contact liveness + When I add contact with alias ALIAS_SENDER_2 and address of SENDER to ffi wallet FFI_WALLET + When I add contact with alias ALIAS_FFI_WALLET_2 and address of FFI_WALLET to wallet SENDER + Then I wait for ffi wallet FFI_WALLET to have at least 1 contacts to be Online + # Now continue + # BROKEN - And I send 1000000 uT from wallet SENDER to wallet FFI_WALLET at fee 5 Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST + When mining node MINER mines 2 blocks Then all nodes are at height 22 Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_BROADCAST @@ -178,12 +208,11 @@ Feature: Wallet FFI When I have wallet SENDER connected to base node BASE1 And I have a ffi wallet FFI_WALLET connected to base node BASE1 When I have wallet RECEIVER connected to base node BASE1 - # Force some P2P discovery with contact liveness - When I add contact with alias ALIAS1 and address of SENDER to ffi wallet FFI_WALLET - When I add contact with alias ALIAS2 and address of RECEIVER to ffi wallet FFI_WALLET - - # Fund the FFI wallet + When I add contact with alias ALIAS_SENDER and address of SENDER to ffi wallet FFI_WALLET + When I add contact with alias ALIAS_FFI_WALLET and address of FFI_WALLET to wallet SENDER + Then I wait for ffi wallet FFI_WALLET to have at least 1 contacts to be Online + # Now continue When I have mining node MINER connected to base node BASE1 and wallet SENDER When mining node MINER mines 10 blocks Then all nodes are at height 10 @@ -196,53 +225,62 @@ Feature: Wallet FFI Then all nodes are at height 20 Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_MINED Then I wait for ffi wallet FFI_WALLET to have at least 4000000 uT - # The FFI wallet now has funds to send a one-sided transaction And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 5 via one-sided transactions Then ffi wallet FFI_WALLET detects AT_LEAST 3 ffi transactions to be TRANSACTION_STATUS_BROADCAST When mining node MINER mines 2 blocks Then all nodes are at height 22 - Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_MINED_UNCONFIRMED and not cancelled + Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_ONE_SIDED_UNCONFIRMED or TRANSACTION_STATUS_MINED_UNCONFIRMED and not cancelled When mining node MINER mines 5 blocks Then all nodes are at height 27 - Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_MINED_CONFIRMED and not cancelled + Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_ONE_SIDED_CONFIRMED or TRANSACTION_STATUS_MINED_CONFIRMED and not cancelled And I stop ffi wallet FFI_WALLET - @critical @pie + @critical Scenario: As a client I want to receive a one-sided transaction Given I have a seed node SEED When I have a base node BASE1 connected to all seed nodes - When I have a base node BASE2 connected to all seed nodes When I have wallet SENDER connected to base node BASE1 - And I have a ffi wallet FFI_RECEIVER connected to base node BASE2 + And I have a ffi wallet FFI_RECEIVER connected to base node BASE1 + # Force some P2P discovery with contact liveness + When I add contact with alias ALIAS_SENDER and address of SENDER to ffi wallet FFI_RECEIVER + When I add contact with alias ALIAS_FFI_RECEIVER and address of FFI_RECEIVER to wallet SENDER + Then I wait for ffi wallet FFI_RECEIVER to have at least 1 contacts to be Online + # Now continue When I have mining node MINER connected to base node BASE1 and wallet SENDER When mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 5000000 uT Then I send a one-sided transaction of 1000000 uT from SENDER to FFI_RECEIVER at fee 20 When mining node MINER mines 2 blocks Then all nodes are at height 12 - Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_ONE_SIDED_UNCONFIRMED + Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_MINED_OR_ONE_SIDED_UNCONFIRMED And I send 1000000 uT from wallet SENDER to wallet FFI_RECEIVER at fee 20 Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST When mining node MINER mines 5 blocks Then all nodes are at height 17 - Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_ONE_SIDED_CONFIRMED + Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_MINED_OR_ONE_SIDED_CONFIRMED And I stop ffi wallet FFI_RECEIVER Scenario: As a client I want to get fee per gram stats - Given I have a base node BASE + Given I have a seed node SEED + When I have a base node BASE connected to all seed nodes When I have wallet WALLET_A connected to base node BASE When I have wallet WALLET_B connected to base node BASE + # Force some P2P discovery with contact liveness + When I add contact with alias ALIAS_WALLET_A and address of WALLET_A to wallet WALLET_B + When I add contact with alias ALIAS_WALLET_B and address of WALLET_B to wallet WALLET_A + # Now continue When I have mining node MINER connected to base node BASE and wallet WALLET_A When mining node MINER mines 7 blocks + Then all nodes are at height 7 Then I wait for wallet WALLET_A to have at least 10000000 uT And I have a ffi wallet FFI_WALLET connected to base node BASE And The fee per gram stats for FFI_WALLET are 1, 1, 1 And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 - And The fee per gram stats for FFI_WALLET are 20, 20, 20 + And The fee per gram stats for FFI_WALLET are 18, 18, 18 And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 40 - And The fee per gram stats for FFI_WALLET are 20, 30, 40 + And The fee per gram stats for FFI_WALLET are 18, 28, 37 And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 60 - And The fee per gram stats for FFI_WALLET are 20, 40, 60 + And The fee per gram stats for FFI_WALLET are 18, 37, 56 When mining node MINER mines 1 blocks And The fee per gram stats for FFI_WALLET are 1, 1, 1 diff --git a/integration_tests/tests/steps/mining_steps.rs b/integration_tests/tests/steps/mining_steps.rs index 29362e216b..0683d64fd8 100644 --- a/integration_tests/tests/steps/mining_steps.rs +++ b/integration_tests/tests/steps/mining_steps.rs @@ -136,7 +136,7 @@ async fn sha3_miner_connected_to_base_node(world: &mut TariWorld, miner: String, )] async fn while_mining_all_txs_in_wallet_are_mined_confirmed(world: &mut TariWorld, miner: String, wallet: String) { let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet).await.unwrap(); let wallet_tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); if wallet_tx_ids.is_empty() { @@ -195,7 +195,7 @@ async fn while_mining_in_node_all_txs_in_wallet_are_mined_confirmed( wallet: String, ) { let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet).await.unwrap(); let script_key_id = &world.script_key_id().await; let wallet_tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); diff --git a/integration_tests/tests/steps/wallet_ffi_steps.rs b/integration_tests/tests/steps/wallet_ffi_steps.rs index 40df4b2957..c0dd2d1f9c 100644 --- a/integration_tests/tests/steps/wallet_ffi_steps.rs +++ b/integration_tests/tests/steps/wallet_ffi_steps.rs @@ -36,10 +36,10 @@ use tari_utilities::hex::Hex; async fn ffi_start_wallet_connected_to_base_node(world: &mut TariWorld, wallet: String, base_node: String) { spawn_wallet_ffi(world, wallet.clone(), null()); let base_node = world.get_node(&base_node).unwrap(); - world - .get_ffi_wallet(&wallet) - .unwrap() - .add_base_node(base_node.public_key.to_hex(), base_node.get_listen_addr().to_string()); + world.get_ffi_wallet(&wallet).unwrap().add_base_node( + base_node.public_key.to_hex(), + base_node.get_public_addresses()[0].to_string(), + ); } #[given(expr = "I have a ffi wallet {word} connected to seed node {word}")] @@ -47,19 +47,19 @@ async fn ffi_start_wallet_connected_to_seed_node(world: &mut TariWorld, wallet: spawn_wallet_ffi(world, wallet.clone(), null()); assert!(world.all_seed_nodes().contains(&seed_node), "Seed node not found."); let seed_node = world.get_node(&seed_node).unwrap(); - world - .get_ffi_wallet(&wallet) - .unwrap() - .add_base_node(seed_node.public_key.to_hex(), seed_node.get_listen_addr().to_string()); + world.get_ffi_wallet(&wallet).unwrap().add_base_node( + seed_node.public_key.to_hex(), + seed_node.get_public_addresses()[0].to_string(), + ); } #[given(expr = "I set base node {word} for ffi wallet {word}")] async fn ffi_set_base_node(world: &mut TariWorld, base_node: String, wallet: String) { let base_node = world.get_node(&base_node).unwrap(); - world - .get_ffi_wallet(&wallet) - .unwrap() - .add_base_node(base_node.public_key.to_hex(), base_node.get_listen_addr().to_string()); + world.get_ffi_wallet(&wallet).unwrap().add_base_node( + base_node.public_key.to_hex(), + base_node.get_public_addresses()[0].to_string(), + ); } #[then(expr = "I want to get public key of ffi wallet {word}")] @@ -73,21 +73,18 @@ async fn ffi_get_public_key(world: &mut TariWorld, wallet: String) { async fn ffi_get_emoji_id(world: &mut TariWorld, wallet: String) { let wallet = world.get_ffi_wallet(&wallet).unwrap(); let emoji_id = wallet.get_emoji_id(); - assert_eq!( - emoji_id.len(), - 132, - "Emoji id {} is expected to be of length 132", - emoji_id - ); + assert!(TariAddress::from_emoji_string(&emoji_id).is_ok()); } #[then(expr = "I stop ffi wallet {word}")] async fn ffi_stop_wallet(world: &mut TariWorld, wallet: String) { - let address = world.get_wallet_address(&wallet).await.unwrap(); - let ffi_wallet = world.ffi_wallets.get_mut(&wallet).unwrap(); - println!("Adding wallet {}", wallet); - world.wallet_addresses.insert(wallet, address); - ffi_wallet.destroy(); + if let Ok(address) = world.get_wallet_address_base58(&wallet).await { + println!("Adding wallet '{}' to stopped_wallet_addresses", wallet); + world.stopped_wallet_addresses.insert(wallet.clone(), address); + } + if let Some(ffi_wallet) = world.ffi_wallets.get_mut(&wallet) { + ffi_wallet.destroy(); + } } #[then(expr = "I retrieve the mnemonic word list for {word}")] @@ -127,7 +124,7 @@ async fn ffi_wait_for_balance(world: &mut TariWorld, wallet: String, balance: u6 println!( "wallet {}, port {}, balance: available {} incoming {} time locked {}", ffi_wallet.name, - ffi_wallet.port, + ffi_wallet.tcp_port, ffi_balance.get_available(), ffi_balance.get_pending_incoming(), ffi_balance.get_time_locked() @@ -140,7 +137,7 @@ async fn ffi_wait_for_balance(world: &mut TariWorld, wallet: String, balance: u6 ffi_balance.get_available() >= balance, "Wallet {}:{} doesn't have enough available funds: available {} incoming {} time locked {}", ffi_wallet.name, - ffi_wallet.port, + ffi_wallet.tcp_port, ffi_balance.get_available(), ffi_balance.get_pending_incoming(), ffi_balance.get_time_locked() @@ -151,7 +148,7 @@ async fn ffi_wait_for_balance(world: &mut TariWorld, wallet: String, balance: u6 async fn ffi_add_contact(world: &mut TariWorld, alias: String, pubkey: String, wallet: String) { let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); - let address = world.get_wallet_address(&pubkey).await.unwrap(); + let address = world.get_wallet_address_base58(&pubkey).await.unwrap(); let contact = create_contact(alias, address); assert!(ffi_wallet.upsert_contact(contact)); @@ -160,7 +157,7 @@ async fn ffi_add_contact(world: &mut TariWorld, alias: String, pubkey: String, w async fn check_contact(world: &mut TariWorld, alias: String, pubkey: Option, wallet: String) -> bool { let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); let address: Option = match pubkey { - Some(pubkey) => Some(world.get_wallet_address(&pubkey).await.unwrap()), + Some(pubkey) => Some(world.get_wallet_address_base58(&pubkey).await.unwrap()), None => None, }; let contacts = ffi_wallet.get_contacts(); @@ -208,7 +205,7 @@ async fn ffi_check_no_contact(world: &mut TariWorld, alias: String, wallet: Stri #[then(expr = "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int}")] async fn ffi_send_transaction(world: &mut TariWorld, amount: u64, wallet: String, dest: String, fee: u64) { let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); - let dest_pub_key = world.get_wallet_address(&dest).await.unwrap(); + let dest_pub_key = world.get_wallet_address_base58(&dest).await.unwrap(); let message = format!("Send from ffi {} to ${} at fee ${}", wallet, dest, fee); let tx_id = ffi_wallet.send_transaction(dest_pub_key, amount, fee, message, false); assert_ne!(tx_id, 0, "Send transaction was not successful"); @@ -218,7 +215,7 @@ async fn ffi_send_transaction(world: &mut TariWorld, amount: u64, wallet: String #[then(expr = "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int} via one-sided transactions")] async fn ffi_send_one_sided_transaction(world: &mut TariWorld, amount: u64, wallet: String, dest: String, fee: u64) { let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); - let dest_pub_key = world.get_wallet_address(&dest).await.unwrap(); + let dest_pub_key = world.get_wallet_address_base58(&dest).await.unwrap(); let message = format!("Send from ffi {} to ${} at fee ${}", wallet, dest, fee); let tx_id = ffi_wallet.send_transaction(dest_pub_key, amount, fee, message, true); assert_ne!(tx_id, 0, "Send transaction was not successful"); @@ -418,8 +415,8 @@ async fn ffi_detects_transaction( "TRANSACTION_STATUS_BROADCAST", "TRANSACTION_STATUS_MINED_UNCONFIRMED", "TRANSACTION_STATUS_MINED", - "TRANSACTION_STATUS_ONE_SIDED_UNCONFIRMED", - "TRANSACTION_STATUS_ONE_SIDED_CONFIRMED" + "TRANSACTION_STATUS_MINED_OR_ONE_SIDED_UNCONFIRMED", + "TRANSACTION_STATUS_MINED_OR_ONE_SIDED_CONFIRMED" ] .contains(&status.as_str())); println!( @@ -432,12 +429,12 @@ async fn ffi_detects_transaction( "TRANSACTION_STATUS_BROADCAST" => ffi_wallet.get_counters().get_transaction_broadcast(), "TRANSACTION_STATUS_MINED_UNCONFIRMED" => ffi_wallet.get_counters().get_transaction_mined_unconfirmed(), "TRANSACTION_STATUS_MINED" => ffi_wallet.get_counters().get_transaction_mined(), - "TRANSACTION_STATUS_ONE_SIDED_UNCONFIRMED" => { + "TRANSACTION_STATUS_MINED_OR_ONE_SIDED_UNCONFIRMED" => { let mut count = ffi_wallet.get_counters().get_transaction_faux_unconfirmed(); count += ffi_wallet.get_counters().get_transaction_mined_unconfirmed(); count }, - "TRANSACTION_STATUS_ONE_SIDED_CONFIRMED" => { + "TRANSACTION_STATUS_MINED_OR_ONE_SIDED_CONFIRMED" => { let mut count = ffi_wallet.get_counters().get_transaction_faux_confirmed(); count += ffi_wallet.get_counters().get_transaction_mined(); count @@ -494,10 +491,10 @@ async fn ffi_recover_wallet(world: &mut TariWorld, wallet_name: String, ffi_wall spawn_wallet_ffi(world, ffi_wallet_name.clone(), seed_words.get_ptr()); let base_node = world.get_node(&base_node).unwrap(); - world - .get_ffi_wallet(&ffi_wallet_name) - .unwrap() - .add_base_node(base_node.public_key.to_hex(), base_node.get_listen_addr().to_string()); + world.get_ffi_wallet(&ffi_wallet_name).unwrap().add_base_node( + base_node.public_key.to_hex(), + base_node.get_public_addresses()[0].to_string(), + ); } #[then(expr = "I restart ffi wallet {word} connected to base node {word}")] @@ -506,7 +503,10 @@ async fn ffi_restart_wallet(world: &mut TariWorld, wallet: String, base_node: St ffi_wallet.restart(); let base_node = world.get_node(&base_node).unwrap(); let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); - ffi_wallet.add_base_node(base_node.public_key.to_hex(), base_node.get_listen_addr().to_string()); + ffi_wallet.add_base_node( + base_node.public_key.to_hex(), + base_node.get_public_addresses()[0].to_string(), + ); } #[then(expr = "The fee per gram stats for {word} are {int}, {int}, {int}")] diff --git a/integration_tests/tests/steps/wallet_steps.rs b/integration_tests/tests/steps/wallet_steps.rs index c7747f3936..a14fa10348 100644 --- a/integration_tests/tests/steps/wallet_steps.rs +++ b/integration_tests/tests/steps/wallet_steps.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{convert::TryFrom, path::PathBuf, time::Duration}; +use std::{convert::TryFrom, path::PathBuf, str::FromStr, time::Duration}; use cucumber::{given, then, when}; use futures::StreamExt; @@ -39,10 +39,14 @@ use grpc::{ TransferRequest, ValidateRequest, }; -use minotari_app_grpc::tari_rpc::{self as grpc, TransactionStatus}; +use log::*; +use minotari_app_grpc::tari_rpc::{self as grpc, TransactionStatus, UpsertContactRequest}; use minotari_console_wallet::{CliCommands, ExportUtxosArgs}; use minotari_wallet::transaction_service::config::TransactionRoutingMechanism; -use tari_common_types::types::{ComAndPubSignature, PrivateKey, PublicKey, RangeProof}; +use tari_common_types::{ + tari_address::TariAddress, + types::{ComAndPubSignature, PrivateKey, PublicKey, RangeProof}, +}; use tari_core::{ covenants::Covenant, transactions::{ @@ -73,6 +77,8 @@ use tari_utilities::hex::Hex; use crate::steps::{mining_steps::create_miner, CONFIRMATION_PERIOD, HALF_SECOND, TWO_MINUTES_WITH_HALF_SECOND_SLEEP}; +const LOG_TARGET: &str = "cucumber::wallet_steps"; + #[given(expr = "a wallet {word} connected to base node {word}")] async fn start_wallet(world: &mut TariWorld, wallet_name: String, node_name: String) { let seeds = world.base_nodes.get(&node_name).unwrap().seed_nodes.clone(); @@ -99,6 +105,20 @@ async fn start_wallet_connected_to_all_seed_nodes(world: &mut TariWorld, name: S .await; } +#[when(expr = "I add contact with alias {word} and address of {word} to wallet {word}")] +async fn add_contact(world: &mut TariWorld, alias: String, pubkey: String, wallet: String) { + let wallet_ps = world.wallets.get(&wallet).unwrap(); + let mut client = wallet_ps.get_grpc_client().await.unwrap(); + + let address = world.get_wallet_address_base58(&pubkey).await.unwrap(); + let wallet_address = TariAddress::from_str(&address).unwrap().to_vec(); + + assert!(client + .upsert_contact(UpsertContactRequest { alias, wallet_address }) + .await + .is_ok()); +} + #[when(expr = "I wait for wallet {word} to have at least {int} uT")] #[then(expr = "I wait for wallet {word} to have at least {int} uT")] async fn wait_for_wallet_to_have_micro_tari(world: &mut TariWorld, wallet: String, amount: u64) { @@ -125,6 +145,11 @@ async fn wait_for_wallet_to_have_micro_tari(world: &mut TariWorld, wallet: Strin } // failed to get wallet right amount, so we panic + error!( + target: LOG_TARGET, + "wallet {} failed to get balance of at least amount {}, current amount is {}", + wallet, amount, curr_amount + ); panic!( "wallet {} failed to get balance of at least amount {}, current amount is {}", wallet, amount, curr_amount @@ -152,6 +177,7 @@ async fn have_wallet_connect_to_seed_node(world: &mut TariWorld, wallet: String, spawn_wallet(world, wallet, Some(seed_node.clone()), vec![seed_node], None, None).await; } +#[allow(clippy::too_many_lines)] #[when(expr = "wallet {word} detects all transactions as {word}")] #[then(expr = "wallet {word} detects all transactions as {word}")] async fn wallet_detects_all_txs_as_mined_status(world: &mut TariWorld, wallet_name: String, status: String) { @@ -178,6 +204,11 @@ async fn wallet_detects_all_txs_as_mined_status(world: &mut TariWorld, wallet_na let tx_info = tx_info.transactions.first().unwrap(); if retry == num_retries { + error!( + target: LOG_TARGET, + "Wallet {} failed to detect tx with tx_id = {} to be {}, current status is {:?}", + wallet_name.as_str(), tx_id, status, tx_info.status() + ); panic!( "Wallet {} failed to detect tx with tx_id = {} to be {}, current status is {:?}", wallet_name.as_str(), @@ -194,7 +225,9 @@ async fn wallet_detects_all_txs_as_mined_status(world: &mut TariWorld, wallet_na grpc::TransactionStatus::MinedUnconfirmed | grpc::TransactionStatus::MinedConfirmed | grpc::TransactionStatus::OneSidedUnconfirmed | - grpc::TransactionStatus::OneSidedConfirmed => { + grpc::TransactionStatus::OneSidedConfirmed | + grpc::TransactionStatus::CoinbaseUnconfirmed | + grpc::TransactionStatus::CoinbaseConfirmed => { break; }, _ => (), @@ -205,7 +238,9 @@ async fn wallet_detects_all_txs_as_mined_status(world: &mut TariWorld, wallet_na grpc::TransactionStatus::MinedUnconfirmed | grpc::TransactionStatus::MinedConfirmed | grpc::TransactionStatus::OneSidedUnconfirmed | - grpc::TransactionStatus::OneSidedConfirmed => { + grpc::TransactionStatus::OneSidedConfirmed | + grpc::TransactionStatus::CoinbaseUnconfirmed | + grpc::TransactionStatus::CoinbaseConfirmed => { break; }, _ => (), @@ -215,7 +250,9 @@ async fn wallet_detects_all_txs_as_mined_status(world: &mut TariWorld, wallet_na grpc::TransactionStatus::MinedUnconfirmed | grpc::TransactionStatus::MinedConfirmed | grpc::TransactionStatus::OneSidedUnconfirmed | - grpc::TransactionStatus::OneSidedConfirmed => { + grpc::TransactionStatus::OneSidedConfirmed | + grpc::TransactionStatus::CoinbaseUnconfirmed | + grpc::TransactionStatus::CoinbaseConfirmed => { break; }, _ => (), @@ -245,7 +282,10 @@ async fn wallet_detects_all_txs_as_mined_status(world: &mut TariWorld, wallet_na }, _ => (), }, - _ => panic!("Unknown status {}, don't know what to expect", status), + _ => { + error!(target: LOG_TARGET, "Unknown status {}, don't know what to expect", status); + panic!("Unknown status {}, don't know what to expect", status); + }, } // tokio sleep 100ms tokio::time::sleep(Duration::from_millis(100)).await; @@ -253,6 +293,7 @@ async fn wallet_detects_all_txs_as_mined_status(world: &mut TariWorld, wallet_na } } +#[allow(clippy::too_many_lines)] #[when(expr = "wallet {word} detects all transactions are at least {word}")] #[then(expr = "wallet {word} detects all transactions are at least {word}")] async fn wallet_detects_all_txs_are_at_least_in_some_status( @@ -261,14 +302,14 @@ async fn wallet_detects_all_txs_are_at_least_in_some_status( status: String, ) { let mut client = create_wallet_client(world, wallet_name.clone()).await.unwrap(); - let wallet_address = client - .get_address(Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address - .to_hex(); - let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet_name).await.unwrap(); + let tx_ids = match world.wallet_tx_ids.get(&wallet_address) { + Some(tx_ids) => tx_ids, + None => { + error!(target: LOG_TARGET, "No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", wallet_name); + panic!("No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", wallet_name); + }, + }; let num_retries = 100; @@ -282,6 +323,11 @@ async fn wallet_detects_all_txs_are_at_least_in_some_status( let tx_info = tx_info.transactions.first().unwrap(); if retry == num_retries { + error!( + target: LOG_TARGET, + "Wallet {} failed to detect tx with tx_id = {} to be at least {}", + wallet_name.as_str(), tx_id, status + ); panic!( "Wallet {} failed to detect tx with tx_id = {} to be at least {}", wallet_name.as_str(), @@ -297,7 +343,9 @@ async fn wallet_detects_all_txs_are_at_least_in_some_status( grpc::TransactionStatus::MinedUnconfirmed | grpc::TransactionStatus::MinedConfirmed | grpc::TransactionStatus::OneSidedUnconfirmed | - grpc::TransactionStatus::OneSidedConfirmed => { + grpc::TransactionStatus::OneSidedConfirmed | + grpc::TransactionStatus::CoinbaseUnconfirmed | + grpc::TransactionStatus::CoinbaseConfirmed => { break; }, _ => (), @@ -308,7 +356,9 @@ async fn wallet_detects_all_txs_are_at_least_in_some_status( grpc::TransactionStatus::MinedUnconfirmed | grpc::TransactionStatus::MinedConfirmed | grpc::TransactionStatus::OneSidedUnconfirmed | - grpc::TransactionStatus::OneSidedConfirmed => { + grpc::TransactionStatus::OneSidedConfirmed | + grpc::TransactionStatus::CoinbaseUnconfirmed | + grpc::TransactionStatus::CoinbaseConfirmed => { break; }, _ => (), @@ -318,7 +368,9 @@ async fn wallet_detects_all_txs_are_at_least_in_some_status( grpc::TransactionStatus::MinedUnconfirmed | grpc::TransactionStatus::MinedConfirmed | grpc::TransactionStatus::OneSidedUnconfirmed | - grpc::TransactionStatus::OneSidedConfirmed => { + grpc::TransactionStatus::OneSidedConfirmed | + grpc::TransactionStatus::CoinbaseUnconfirmed | + grpc::TransactionStatus::CoinbaseConfirmed => { break; }, _ => (), @@ -327,12 +379,31 @@ async fn wallet_detects_all_txs_are_at_least_in_some_status( grpc::TransactionStatus::MinedUnconfirmed | grpc::TransactionStatus::MinedConfirmed | grpc::TransactionStatus::OneSidedUnconfirmed | - grpc::TransactionStatus::OneSidedConfirmed => { + grpc::TransactionStatus::OneSidedConfirmed | + grpc::TransactionStatus::CoinbaseUnconfirmed | + grpc::TransactionStatus::CoinbaseConfirmed => { + break; + }, + _ => (), + }, + "Mined_or_OneSidedConfirmed" => match tx_info.status() { + grpc::TransactionStatus::MinedConfirmed | + grpc::TransactionStatus::OneSidedConfirmed | + grpc::TransactionStatus::CoinbaseConfirmed => { + break; + }, + _ => (), + }, + "Coinbase" => match tx_info.status() { + grpc::TransactionStatus::CoinbaseConfirmed | grpc::TransactionStatus::CoinbaseUnconfirmed => { break; }, _ => (), }, - _ => panic!("Unknown status {}, don't know what to expect", status), + _ => { + error!(target: LOG_TARGET, "Unknown status {}, don't know what to expect", status); + panic!("Unknown status {}, don't know what to expect", status); + }, } tokio::time::sleep(Duration::from_secs(1)).await; } @@ -342,14 +413,11 @@ async fn wallet_detects_all_txs_are_at_least_in_some_status( #[then(expr = "wallet {word} detects all transactions are Broadcast")] async fn wallet_detects_all_txs_as_broadcast(world: &mut TariWorld, wallet_name: String) { let mut client = create_wallet_client(world, wallet_name.clone()).await.unwrap(); - let wallet_address = client - .get_address(Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address - .to_hex(); - let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet_name).await.unwrap(); + let tx_ids = match world.wallet_tx_ids.get(&wallet_address) { + Some(tx_ids) => tx_ids, + None => panic!("No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", wallet_name), + }; let num_retries = 100; @@ -363,6 +431,11 @@ async fn wallet_detects_all_txs_as_broadcast(world: &mut TariWorld, wallet_name: let tx_info = tx_info.transactions.first().unwrap(); if retry == num_retries { + error!( + target: LOG_TARGET, + "Wallet {} failed to detect tx with tx_id = {} to be mined_confirmed", + wallet_name.as_str(), tx_id + ); panic!( "Wallet {} failed to detect tx with tx_id = {} to be mined_confirmed", wallet_name.as_str(), @@ -395,14 +468,11 @@ async fn wallet_detects_all_txs_as_broadcast(world: &mut TariWorld, wallet_name: #[when(expr = "wallet {word} detects last transaction is Pending")] async fn wallet_detects_last_tx_as_pending(world: &mut TariWorld, wallet: String) { let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let wallet_address = client - .get_address(Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address - .to_hex(); - let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet).await.unwrap(); + let tx_ids = match world.wallet_tx_ids.get(&wallet_address) { + Some(tx_ids) => tx_ids, + None => panic!("No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", wallet), + }; let tx_id = tx_ids.last().unwrap(); // get last transaction let num_retries = 100; @@ -415,6 +485,11 @@ async fn wallet_detects_last_tx_as_pending(world: &mut TariWorld, wallet: String let tx_info = tx_info.transactions.first().unwrap(); if retry == num_retries { + error!( + target: LOG_TARGET, + "Wallet {} failed to detect tx with tx_id = {} to be pending", + wallet.as_str(), tx_id + ); panic!( "Wallet {} failed to detect tx with tx_id = {} to be pending", wallet.as_str(), @@ -441,14 +516,14 @@ async fn wallet_detects_last_tx_as_pending(world: &mut TariWorld, wallet: String #[when(expr = "wallet {word} detects last transaction is Cancelled")] async fn wallet_detects_last_tx_as_cancelled(world: &mut TariWorld, wallet: String) { let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let wallet_address = client - .get_address(Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address - .to_hex(); - let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet).await.unwrap(); + let tx_ids = match world.wallet_tx_ids.get(&wallet_address) { + Some(tx_ids) => tx_ids, + None => { + error!(target: LOG_TARGET, "No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", wallet); + panic!("No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", wallet); + }, + }; let tx_id = tx_ids.last().unwrap(); // get last transaction let num_retries = 100; @@ -461,6 +536,11 @@ async fn wallet_detects_last_tx_as_cancelled(world: &mut TariWorld, wallet: Stri let tx_info = tx_info.transactions.first().unwrap(); if retry == num_retries { + error!( + target: LOG_TARGET, + "Wallet {} failed to detect tx with tx_id = {} to be cancelled, current status is {:?}", + wallet.as_str(), tx_id, tx_info.status(), + ); panic!( "Wallet {} failed to detect tx with tx_id = {} to be cancelled, current status is {:?}", wallet.as_str(), @@ -485,6 +565,11 @@ async fn wallet_detects_last_tx_as_cancelled(world: &mut TariWorld, wallet: Stri #[then(expr = "I list all {word} transactions for wallet {word}")] async fn list_all_txs_for_wallet(world: &mut TariWorld, transaction_type: String, wallet: String) { if transaction_type.as_str() != "COINBASE" && transaction_type.as_str() != "NORMAL" { + error!( + target: LOG_TARGET, + "Invalid transaction type. Values should be COINBASE or NORMAL, value passed is {}", + transaction_type + ); panic!( "Invalid transaction type. Values should be COINBASE or NORMAL, value passed is {}", transaction_type @@ -515,7 +600,46 @@ async fn list_all_txs_for_wallet(world: &mut TariWorld, transaction_type: String #[then(expr = "wallet {word} has at least {int} transactions that are all {word} and not cancelled")] async fn wallet_has_at_least_num_txs(world: &mut TariWorld, wallet: String, num_txs: u64, transaction_status: String) { let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let transaction_status = match transaction_status.as_str() { + let transaction_status = transaction_status_as_int(transaction_status); + + let num_retries = 100; + let mut current_status = 0; + let mut total_found = 0; + + for _ in 0..num_retries { + let mut txs = client + .get_completed_transactions(grpc::GetCompletedTransactionsRequest {}) + .await + .unwrap() + .into_inner(); + let mut found_tx = 0; + while let Some(tx) = txs.next().await { + let tx_info = tx.unwrap().transaction.unwrap(); + current_status = tx_info.status; + if current_status == transaction_status { + found_tx += 1; + } + } + if found_tx >= num_txs { + return; + } + total_found += found_tx; + tokio::time::sleep(Duration::from_secs(5)).await; + } + + error!( + target: LOG_TARGET, + "Wallet {} failed to have at least num {} txs with status {}, current status is {}, scanned txs {}", + wallet, num_txs, transaction_status, current_status, total_found + ); + panic!( + "Wallet {} failed to have at least num {} txs with status {}, current status is {}, scanned txs {}", + wallet, num_txs, transaction_status, current_status, total_found + ); +} + +fn transaction_status_as_int(transaction_status: String) -> i32 { + match transaction_status.as_str() { "TRANSACTION_STATUS_COMPLETED" => 0, "TRANSACTION_STATUS_BROADCAST" => 1, "TRANSACTION_STATUS_MINED_UNCONFIRMED" => 2, @@ -531,8 +655,25 @@ async fn wallet_has_at_least_num_txs(world: &mut TariWorld, wallet: String, num_ "TRANSACTION_STATUS_COINBASE_UNCONFIRMED" => 12, "TRANSACTION_STATUS_COINBASE_CONFIRMED" => 13, "TRANSACTION_STATUS_COINBASE_NOT_IN_BLOCK_CHAIN" => 14, - _ => panic!("Invalid transaction status {}", transaction_status), - }; + _ => { + error!(target: LOG_TARGET, "Invalid transaction status {}", transaction_status); + panic!("Invalid transaction status {}", transaction_status); + }, + } +} + +#[when(expr = "wallet {word} has at least {int} transactions that are all {word} or {word} and not cancelled")] +#[then(expr = "wallet {word} has at least {int} transactions that are all {word} or {word} and not cancelled")] +async fn wallet_has_at_least_num_txs_optional_status( + world: &mut TariWorld, + wallet: String, + num_txs: u64, + transaction_status_1: String, + transaction_status_2: String, +) { + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let transaction_status_1 = transaction_status_as_int(transaction_status_1); + let transaction_status_2 = transaction_status_as_int(transaction_status_2); let num_retries = 100; let mut current_status = 0; @@ -548,7 +689,7 @@ async fn wallet_has_at_least_num_txs(world: &mut TariWorld, wallet: String, num_ while let Some(tx) = txs.next().await { let tx_info = tx.unwrap().transaction.unwrap(); current_status = tx_info.status; - if current_status == transaction_status { + if current_status == transaction_status_1 || current_status == transaction_status_2 { found_tx += 1; } } @@ -559,9 +700,14 @@ async fn wallet_has_at_least_num_txs(world: &mut TariWorld, wallet: String, num_ tokio::time::sleep(Duration::from_secs(5)).await; } + error!( + target: LOG_TARGET, + "Wallet {} failed to have at least num {} txs with status {} or {}, current status is {}, scanned txs {}", + wallet, num_txs, transaction_status_1, transaction_status_1, current_status, total_found + ); panic!( - "Wallet {} failed to have at least num {} txs with status {}, current status is {}, scanned txs {}", - wallet, num_txs, transaction_status, current_status, total_found + "Wallet {} failed to have at least num {} txs with status {} or {}, current status is {}, scanned txs {}", + wallet, num_txs, transaction_status_1, transaction_status_1, current_status, total_found ); } @@ -637,6 +783,11 @@ async fn wait_for_wallet_to_have_less_than_micro_tari(world: &mut TariWorld, wal tokio::time::sleep(Duration::from_secs(5)).await; } + error!( + target: LOG_TARGET, + "Wallet {} didn't get less than {} after num_retries {}", + wallet, amount, num_retries + ); panic!( "Wallet {} didn't get less than {} after num_retries {}", wallet, amount, num_retries @@ -694,9 +845,9 @@ async fn send_amount_from_source_wallet_to_dest_wallet_without_broadcast( fee: u64, ) { let mut source_client = create_wallet_client(world, source_wallet.clone()).await.unwrap(); - let source_wallet_address = world.get_wallet_address(&source_wallet).await.unwrap(); + let source_wallet_address = world.get_wallet_address_base58(&source_wallet).await.unwrap(); - let dest_wallet_address = world.get_wallet_address(&dest_wallet).await.unwrap(); + let dest_wallet_address = world.get_wallet_address_base58(&dest_wallet).await.unwrap(); let payment_recipient = PaymentRecipient { address: dest_wallet_address.clone(), @@ -755,9 +906,9 @@ async fn send_one_sided_transaction_from_source_wallet_to_dest_wallt( fee: u64, ) { let mut source_client = create_wallet_client(world, source_wallet.clone()).await.unwrap(); - let source_wallet_address = world.get_wallet_address(&source_wallet).await.unwrap(); + let source_wallet_address = world.get_wallet_address_base58(&source_wallet).await.unwrap(); - let dest_wallet_address = world.get_wallet_address(&dest_wallet).await.unwrap(); + let dest_wallet_address = world.get_wallet_address_base58(&dest_wallet).await.unwrap(); let payment_recipient = PaymentRecipient { address: dest_wallet_address.clone(), @@ -790,7 +941,7 @@ async fn send_one_sided_transaction_from_source_wallet_to_dest_wallt( fee ); - // we wait for transaction to be broadcasted + // we wait for transaction to be broadcast let tx_id = tx_res.transaction_id; let num_retries = 100; let tx_info_req = GetTransactionInfoRequest { @@ -808,7 +959,7 @@ async fn send_one_sided_transaction_from_source_wallet_to_dest_wallt( // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info.status == 1_i32 { println!( - "One sided transaction from {} to {} with amount {} at fee {} has been broadcasted", + "One sided transaction from {} to {} with amount {} at fee {} has been broadcast", source_wallet.clone(), dest_wallet.clone(), amount, @@ -818,13 +969,18 @@ async fn send_one_sided_transaction_from_source_wallet_to_dest_wallt( } if i == num_retries - 1 { + error!( + target: LOG_TARGET, + "One sided transaction from {} to {} with amount {} at fee {} failed to be broadcast", + source_wallet.clone(), dest_wallet.clone(), amount, fee + ); panic!( - "One sided transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + "One sided transaction from {} to {} with amount {} at fee {} failed to be broadcast", source_wallet.clone(), dest_wallet.clone(), amount, fee - ) + ); } tokio::time::sleep(Duration::from_secs(5)).await; @@ -855,8 +1011,8 @@ async fn send_amount_from_wallet_to_wallet_at_fee( fee_per_gram: u64, ) { let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); - let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); - let receiver_wallet_address = world.get_wallet_address(&receiver).await.unwrap(); + let sender_wallet_address = world.get_wallet_address_base58(&sender).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address_base58(&receiver).await.unwrap(); let payment_recipient = PaymentRecipient { address: receiver_wallet_address.clone(), @@ -907,7 +1063,8 @@ async fn send_amount_from_wallet_to_wallet_at_fee( // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info.status == 1_i32 { println!( - "Transaction from {} to {} with amount {} at fee {} has been broadcasted", + "Transaction {} from {} to {} with amount {} at fee {} has been broadcast", + tx_id, sender.clone(), receiver.clone(), amount, @@ -917,16 +1074,22 @@ async fn send_amount_from_wallet_to_wallet_at_fee( } if i == num_retries - 1 { + error!( + target: LOG_TARGET, + "Transaction {} from {} to {} with amount {} at fee {} failed to be broadcast", + tx_id, sender.clone(), receiver.clone(), amount, fee_per_gram + ); panic!( - "Transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + "Transaction {} from {} to {} with amount {} at fee {} failed to be broadcast", + tx_id, sender.clone(), receiver.clone(), amount, fee_per_gram - ) + ); } - tokio::time::sleep(Duration::from_secs(5)).await; + tokio::time::sleep(Duration::from_secs(1)).await; } // insert tx_id's to the corresponding world mapping @@ -989,6 +1152,11 @@ async fn wallet_detects_at_least_coinbase_transactions(world: &mut TariWorld, wa &wallet_name, coinbases ); } else { + error!( + target: LOG_TARGET, + "Wallet {} failed to detect at least {} coinbase transactions as CoinbaseConfirmed", + wallet_name, coinbases + ); panic!( "Wallet {} failed to detect at least {} coinbase transactions as CoinbaseConfirmed", wallet_name, coinbases @@ -1045,6 +1213,11 @@ async fn wallet_detects_at_least_coinbase_unconfirmed_transactions( &wallet_name, coinbases ); } else { + error!( + target: LOG_TARGET, + "Wallet {} failed to detect at least {} coinbase transactions as CoinbaseConfirmed", + wallet_name, coinbases + ); panic!( "Wallet {} failed to detect at least {} coinbase transactions as CoinbaseConfirmed", wallet_name, coinbases @@ -1055,8 +1228,14 @@ async fn wallet_detects_at_least_coinbase_unconfirmed_transactions( #[then(expr = "wallet {word} detects exactly {int} coinbase transactions as CoinbaseConfirmed")] async fn wallet_detects_exactly_coinbase_transactions(world: &mut TariWorld, wallet_name: String, coinbases: u64) { let mut client = create_wallet_client(world, wallet_name.clone()).await.unwrap(); - let wallet_address = world.get_wallet_address(&wallet_name).await.unwrap(); - let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet_name).await.unwrap(); + let tx_ids = match world.wallet_tx_ids.get(&wallet_address) { + Some(tx_ids) => tx_ids, + None => { + error!(target: LOG_TARGET, "No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", wallet_name); + panic!("No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", wallet_name); + }, + }; let num_retries = 100; let mut total_mined_confirmed_coinbases = 0; @@ -1090,6 +1269,11 @@ async fn wallet_detects_exactly_coinbase_transactions(world: &mut TariWorld, wal &wallet_name, coinbases ); } else { + error!( + target: LOG_TARGET, + "Wallet {} failed to detect exactly {} coinbase transactions as CoinbaseConfirmed", + wallet_name, coinbases + ); panic!( "Wallet {} failed to detect exactly {} coinbase transactions as CoinbaseConfirmed", wallet_name, coinbases @@ -1111,15 +1295,17 @@ async fn stop_all_wallets(world: &mut TariWorld) { async fn stop_wallet(world: &mut TariWorld, wallet: String) { // conveniently, register wallet address let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let wallet_address = wallet_client + let wallet_address_bytes = wallet_client .get_address(Empty {}) .await .unwrap() .into_inner() - .interactive_address - .to_hex(); + .interactive_address; + let wallet_address = TariAddress::from_bytes(&wallet_address_bytes).unwrap(); let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); - world.wallet_addresses.insert(wallet.clone(), wallet_address); + world + .stopped_wallet_addresses + .insert(wallet.clone(), wallet_address.to_base58()); println!("Stopping wallet {}", wallet.as_str()); wallet_ps.kill(); } @@ -1142,7 +1328,7 @@ async fn start_wallet_without_node(world: &mut TariWorld, wallet: String) { async fn all_wallets_detect_all_txs_as_mined_confirmed(world: &mut TariWorld) { for wallet in world.wallets.keys() { let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet).await.unwrap(); let wallet_tx_ids = world.wallet_tx_ids.get(&wallet_address); let wallet_tx_ids = if wallet_tx_ids.is_none() { @@ -1151,6 +1337,7 @@ async fn all_wallets_detect_all_txs_as_mined_confirmed(world: &mut TariWorld) { } else { let wallet_tx_ids = wallet_tx_ids.unwrap(); if wallet_tx_ids.is_empty() { + error!(target: LOG_TARGET, "Wallet {} should have available transaction ids", wallet.as_str()); panic!("Wallet {} should have available transaction ids", wallet.as_str()); } wallet_tx_ids.clone() @@ -1177,6 +1364,11 @@ async fn all_wallets_detect_all_txs_as_mined_confirmed(world: &mut TariWorld) { } if retry == num_retries { + error!( + target: LOG_TARGET, + "Transaction with id {} does not have status as Mined_or_OneSidedConfirmed, on wallet {}", + tx_id, &wallet + ); panic!( "Transaction with id {} does not have status as Mined_or_OneSidedConfirmed, on wallet {}", tx_id, &wallet @@ -1200,6 +1392,7 @@ async fn wallets_should_have_at_least_num_spendable_coinbase_outs( let exactly = "EXACTLY"; if comparison.as_str() != at_least && comparison.as_str() != exactly { + error!(target: LOG_TARGET, "Invalid comparison value provided: {}", comparison); panic!("Invalid comparison value provided: {}", comparison); } @@ -1266,6 +1459,11 @@ async fn wallets_should_have_at_least_num_spendable_coinbase_outs( } else if comparison == exactly && spendable_coinbase_count == amount_of_coinbases { println!("Wallet {} has found exactly {}", &wallet, amount_of_coinbases); } else { + error!( + target: LOG_TARGET, + "Wallet {} hasn't found {} {} spendable outputs, instead got {}", + wallet, comparison, amount_of_coinbases, spendable_coinbase_count + ); panic!( "Wallet {} hasn't found {} {} spendable outputs, instead got {}", wallet, comparison, amount_of_coinbases, spendable_coinbase_count @@ -1284,8 +1482,8 @@ async fn send_num_transactions_to_wallets_at_fee( fee_per_gram: u64, ) { let mut sender_wallet_client = create_wallet_client(world, sender_wallet.clone()).await.unwrap(); - let sender_wallet_address = world.get_wallet_address(&sender_wallet).await.unwrap(); - let receiver_wallet_address = world.get_wallet_address(&receiver_wallet).await.unwrap(); + let sender_wallet_address = world.get_wallet_address_base58(&sender_wallet).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address_base58(&receiver_wallet).await.unwrap(); let mut tx_ids = vec![]; for _ in 0..num_txs { @@ -1309,6 +1507,11 @@ async fn send_num_transactions_to_wallets_at_fee( let transfer_res = transfer_res.results.first().unwrap(); if !transfer_res.is_success { + error!( + target: LOG_TARGET, + "Failed to send transaction from wallet {} to wallet {}, with message \n {}", + &sender_wallet, &receiver_wallet, &transfer_res.failure_message + ); panic!( "Failed to send transaction from wallet {} to wallet {}, with message \n {}", &sender_wallet, &receiver_wallet, &transfer_res.failure_message @@ -1330,12 +1533,12 @@ async fn send_num_transactions_to_wallets_at_fee( let num_retries = 100; println!( - "Waiting for transactions from wallet {} to wallet {} to be broadcasted", + "Waiting for transactions from wallet {} to wallet {} to be broadcast", &sender_wallet, &receiver_wallet ); for tx_id in tx_ids { - println!("Waiting for transaction with id {} to be broadcasted", tx_id); + println!("Waiting for transaction with id {} to be broadcast", tx_id); let request = GetTransactionInfoRequest { transaction_ids: vec![tx_id], }; @@ -1352,7 +1555,7 @@ async fn send_num_transactions_to_wallets_at_fee( if txs_info.status == 1 { println!( - "Transaction from wallet {} to wallet {} with id {} has been broadcasted to the network", + "Transaction from wallet {} to wallet {} with id {} has been broadcast to the network", &sender_wallet, &receiver_wallet, tx_id ); is_broadcast = true; @@ -1362,6 +1565,11 @@ async fn send_num_transactions_to_wallets_at_fee( } if !is_broadcast { + error!( + target: LOG_TARGET, + "Transaction from wallet {} to wallet {} with id {} was not broacasted to the network", + &sender_wallet, &receiver_wallet, tx_id + ); panic!( "Transaction from wallet {} to wallet {} with id {} was not broacasted to the network", &sender_wallet, &receiver_wallet, tx_id @@ -1389,6 +1597,11 @@ async fn wait_for_wallet_to_have_num_connections(world: &mut TariWorld, wallet: } if u64::from(actual_connections) != connections { + error!( + target: LOG_TARGET, + "Wallet {} does not have {} connections", + &wallet, connections + ); panic!("Wallet {} does not have {} connections", &wallet, connections); } } @@ -1406,7 +1619,10 @@ async fn wait_for_wallet_to_have_specific_connectivity(world: &mut TariWorld, wa "ONLINE" => 1, "DEGRADED" => 2, "OFFLINE" => 3, - _ => panic!("Invalid connectivity value {}", connectivity), + _ => { + error!(target: LOG_TARGET, "Invalid connectivity value {}", connectivity); + panic!("Invalid connectivity value {}", connectivity); + }, }; for _ in 0..=num_retries { @@ -1419,6 +1635,7 @@ async fn wait_for_wallet_to_have_specific_connectivity(world: &mut TariWorld, wa tokio::time::sleep(Duration::from_secs(5)).await; } + error!(target: LOG_TARGET, "Wallet {} did not get correct connectivity status {}", &wallet, connectivity); panic!( "Wallet {} did not get correct connectivity status {}", &wallet, connectivity @@ -1428,8 +1645,8 @@ async fn wait_for_wallet_to_have_specific_connectivity(world: &mut TariWorld, wa #[when(expr = "I transfer {int}T from {word} to {word}")] async fn transfer_tari_from_wallet_to_receiver(world: &mut TariWorld, amount: u64, sender: String, receiver: String) { let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); - let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); - let receiver_wallet_address = world.get_wallet_address(&receiver).await.unwrap(); + let sender_wallet_address = world.get_wallet_address_base58(&sender).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address_base58(&receiver).await.unwrap(); let payment_recipient = PaymentRecipient { address: receiver_wallet_address.clone(), @@ -1462,7 +1679,7 @@ async fn transfer_tari_from_wallet_to_receiver(world: &mut TariWorld, amount: u6 10 ); - // we wait for transaction to be broadcasted + // we wait for transaction to be broadcast let tx_id = tx_res.transaction_id; let num_retries = 100; let tx_info_req = GetTransactionInfoRequest { @@ -1480,7 +1697,7 @@ async fn transfer_tari_from_wallet_to_receiver(world: &mut TariWorld, amount: u6 // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info.status == 1_i32 { println!( - "Transaction from {} to {} with amount {} at fee {} has been broadcasted", + "Transaction from {} to {} with amount {} at fee {} has been broadcast", sender.clone(), receiver.clone(), amount, @@ -1490,13 +1707,18 @@ async fn transfer_tari_from_wallet_to_receiver(world: &mut TariWorld, amount: u6 } if i == num_retries { + error!( + target: LOG_TARGET, + "Transaction from {} to {} with amount {} at fee {} failed to be broadcast", + sender.clone(), receiver.clone(), amount, 10 + ); panic!( - "Transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + "Transaction from {} to {} with amount {} at fee {} failed to be broadcast", sender.clone(), receiver.clone(), amount, 10 - ) + ); } tokio::time::sleep(Duration::from_secs(5)).await; @@ -1542,6 +1764,11 @@ async fn wallet_has_tari(world: &mut TariWorld, wallet: String, amount: u64) { tokio::time::sleep(Duration::from_secs(5)).await; } + error!( + target: LOG_TARGET, + "Wallet {} failed to have at least {}T, it ended with {}T", + wallet, amount, available_balance + ); panic!( "Wallet {} failed to have at least {}T, it ended with {}T", wallet, amount, available_balance @@ -1608,6 +1835,7 @@ async fn wallet_with_tari_connected_to_base_node( tokio::time::sleep(Duration::from_secs(5)).await; } + error!(target: LOG_TARGET, "Wallet {} failed to have at least {}T", wallet, amount); panic!("Wallet {} failed to have at least {}T", wallet, amount); } @@ -1622,9 +1850,9 @@ async fn transfer_from_wallet_to_two_recipients_at_fee( fee_per_gram: u64, ) { let mut sender_client = create_wallet_client(world, sender.clone()).await.unwrap(); - let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); - let receiver1_address = world.get_wallet_address(&receiver1).await.unwrap(); - let receiver2_address = world.get_wallet_address(&receiver2).await.unwrap(); + let sender_wallet_address = world.get_wallet_address_base58(&sender).await.unwrap(); + let receiver1_address = world.get_wallet_address_base58(&receiver1).await.unwrap(); + let receiver2_address = world.get_wallet_address_base58(&receiver2).await.unwrap(); let payment_recipient1 = PaymentRecipient { address: receiver1_address.clone(), @@ -1681,7 +1909,7 @@ async fn transfer_from_wallet_to_two_recipients_at_fee( fee_per_gram ); - // we wait for transaction to be broadcasted + // we wait for transaction to be broadcast let tx_id1 = tx_res1.transaction_id; let tx_id2 = tx_res2.transaction_id; @@ -1710,7 +1938,7 @@ async fn transfer_from_wallet_to_two_recipients_at_fee( // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info1.status == 1_i32 && tx_info2.status == 1_i32 { println!( - "Transaction from {} to {} and {} with amount {} at fee {} has been broadcasted", + "Transaction from {} to {} and {} with amount {} at fee {} has been broadcast", sender.as_str(), receiver1.as_str(), receiver2.as_str(), @@ -1721,14 +1949,19 @@ async fn transfer_from_wallet_to_two_recipients_at_fee( } if i == num_retries { + error!( + target: LOG_TARGET, + "Transaction from {} to {} and {} with amount {} at fee {} failed to be broadcast", + sender.as_str(), receiver1.as_str(), receiver2.as_str(), amount, 10 + ); panic!( - "Transaction from {} to {} and {} with amount {} at fee {} failed to be broadcasted", + "Transaction from {} to {} and {} with amount {} at fee {} failed to be broadcast", sender.as_str(), receiver1.as_str(), receiver2.as_str(), amount, 10 - ) + ); } tokio::time::sleep(Duration::from_secs(5)).await; @@ -1755,7 +1988,7 @@ async fn transfer_from_wallet_to_two_recipients_at_fee( #[when(expr = "I transfer {int} uT to self from wallet {word} at fee {int}")] async fn transfer_tari_to_self(world: &mut TariWorld, amount: u64, sender: String, fee_per_gram: u64) { let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); - let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); + let sender_wallet_address = world.get_wallet_address_base58(&sender).await.unwrap(); let payment_recipient = PaymentRecipient { address: sender_wallet_address.clone(), @@ -1782,7 +2015,7 @@ async fn transfer_tari_to_self(world: &mut TariWorld, amount: u64, sender: Strin fee_per_gram ); - // we wait for transaction to be broadcasted + // we wait for transaction to be broadcast let tx_id = tx_res.transaction_id; let num_retries = 100; let tx_info_req = GetTransactionInfoRequest { @@ -1800,7 +2033,7 @@ async fn transfer_tari_to_self(world: &mut TariWorld, amount: u64, sender: Strin // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info.status == 1_i32 { println!( - "Transaction to self from {} with amount {} at fee {} has been broadcasted", + "Transaction to self from {} with amount {} at fee {} has been broadcast", sender.clone(), amount, fee_per_gram @@ -1809,12 +2042,17 @@ async fn transfer_tari_to_self(world: &mut TariWorld, amount: u64, sender: Strin } if i == num_retries { + error!( + target: LOG_TARGET, + "Transaction to self from {} with amount {} at fee {} failed to be broadcast", + sender.clone(), amount, fee_per_gram + ); panic!( - "Transaction to self from {} with amount {} at fee {} failed to be broadcasted", + "Transaction to self from {} with amount {} at fee {} failed to be broadcast", sender.clone(), amount, fee_per_gram - ) + ); } tokio::time::sleep(Duration::from_secs(5)).await; @@ -1834,8 +2072,8 @@ async fn transfer_tari_to_self(world: &mut TariWorld, amount: u64, sender: Strin #[when(expr = "I broadcast HTLC transaction with {int} uT from wallet {word} to wallet {word} at fee {int}")] async fn htlc_transaction(world: &mut TariWorld, amount: u64, sender: String, receiver: String, fee_per_gram: u64) { let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); - let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); - let receiver_wallet_address = world.get_wallet_address(&receiver).await.unwrap(); + let sender_wallet_address = world.get_wallet_address_base58(&sender).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address_base58(&receiver).await.unwrap(); let payment_recipient = PaymentRecipient { address: receiver_wallet_address.clone(), @@ -1870,7 +2108,7 @@ async fn htlc_transaction(world: &mut TariWorld, amount: u64, sender: String, re fee_per_gram ); - // we wait for transaction to be broadcasted + // we wait for transaction to be broadcast let tx_id = sha_atomic_swap_tx_res.transaction_id; let num_retries = 100; let tx_info_req = GetTransactionInfoRequest { @@ -1888,7 +2126,7 @@ async fn htlc_transaction(world: &mut TariWorld, amount: u64, sender: String, re // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info.status == 1_i32 { println!( - "Atomic swap transaction from {} to {} with amount {} at fee {} has been broadcasted", + "Atomic swap transaction from {} to {} with amount {} at fee {} has been broadcast", sender.as_str(), receiver.as_str(), amount, @@ -1898,13 +2136,18 @@ async fn htlc_transaction(world: &mut TariWorld, amount: u64, sender: String, re } if i == num_retries { + error!( + target: LOG_TARGET, + "Atomic swap transaction from {} to {} with amount {} at fee {} failed to be broadcast", + sender.as_str(), receiver.as_str(), amount, fee_per_gram + ); panic!( - "Atomic swap transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + "Atomic swap transaction from {} to {} with amount {} at fee {} failed to be broadcast", sender.as_str(), receiver.as_str(), amount, fee_per_gram - ) + ); } tokio::time::sleep(Duration::from_secs(5)).await; @@ -1930,7 +2173,7 @@ async fn htlc_transaction(world: &mut TariWorld, amount: u64, sender: String, re #[when(expr = "I claim an HTLC refund transaction with wallet {word} at fee {int}")] async fn claim_htlc_refund_transaction_with_wallet_at_fee(world: &mut TariWorld, wallet: String, fee_per_gram: u64) { let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet).await.unwrap(); let output_hash = world.output_hash.clone().unwrap(); let claim_htlc_req = ClaimHtlcRefundRequest { @@ -1951,7 +2194,7 @@ async fn claim_htlc_refund_transaction_with_wallet_at_fee(world: &mut TariWorld, fee_per_gram ); - // we wait for transaction to be broadcasted + // we wait for transaction to be broadcast let tx_id = claim_htlc_refund_res.results.unwrap().transaction_id; let num_retries = 100; let tx_info_req = GetTransactionInfoRequest { @@ -1969,7 +2212,7 @@ async fn claim_htlc_refund_transaction_with_wallet_at_fee(world: &mut TariWorld, // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info.status == 1_i32 { println!( - "Claim HTLC refund transaction with wallet {} at fee {} has been broadcasted", + "Claim HTLC refund transaction with wallet {} at fee {} has been broadcast", wallet.as_str(), fee_per_gram ); @@ -1977,11 +2220,16 @@ async fn claim_htlc_refund_transaction_with_wallet_at_fee(world: &mut TariWorld, } if i == num_retries { + error!( + "Claim HTLC refund transaction with wallet {} at fee {} failed to be broadcast", + wallet.as_str(), + fee_per_gram + ); panic!( - "Claim HTLC refund transaction with wallet {} at fee {} failed to be broadcasted", + "Claim HTLC refund transaction with wallet {} at fee {} failed to be broadcast", wallet.as_str(), fee_per_gram - ) + ); } tokio::time::sleep(Duration::from_secs(5)).await; @@ -2000,7 +2248,7 @@ async fn claim_htlc_refund_transaction_with_wallet_at_fee(world: &mut TariWorld, #[when(expr = "I claim an HTLC transaction with wallet {word} at fee {int}")] async fn wallet_claims_htlc_transaction_at_fee(world: &mut TariWorld, wallet: String, fee_per_gram: u64) { let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet).await.unwrap(); let output_hash = world.output_hash.clone().unwrap(); let pre_image = world.pre_image.clone().unwrap(); @@ -2023,7 +2271,7 @@ async fn wallet_claims_htlc_transaction_at_fee(world: &mut TariWorld, wallet: St fee_per_gram ); - // we wait for transaction to be broadcasted + // we wait for transaction to be broadcast let tx_id = claim_htlc_res.results.unwrap().transaction_id; let num_retries = 100; let tx_info_req = GetTransactionInfoRequest { @@ -2041,7 +2289,7 @@ async fn wallet_claims_htlc_transaction_at_fee(world: &mut TariWorld, wallet: St // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info.status == 1_i32 { println!( - "Claim HTLC transaction with wallet {} at fee {} has been broadcasted", + "Claim HTLC transaction with wallet {} at fee {} has been broadcast", wallet.as_str(), fee_per_gram ); @@ -2049,11 +2297,16 @@ async fn wallet_claims_htlc_transaction_at_fee(world: &mut TariWorld, wallet: St } if i == num_retries { + error!( + "Claim HTLC transaction with wallet {} at fee {} failed to be broadcast", + wallet.as_str(), + fee_per_gram + ); panic!( - "Claim HTLC transaction with wallet {} at fee {} failed to be broadcasted", + "Claim HTLC transaction with wallet {} at fee {} failed to be broadcast", wallet.as_str(), fee_per_gram - ) + ); } tokio::time::sleep(Duration::from_secs(5)).await; @@ -2094,6 +2347,11 @@ async fn wait_for_wallet_to_have_less_than_amount(world: &mut TariWorld, wallet: } // failed to get wallet right amount, so we panic + error!( + target: LOG_TARGET, + "wallet {} failed to get less balance than amount {}, current amount is {}", + wallet.as_str(), amount, curr_amount + ); panic!( "wallet {} failed to get less balance than amount {}, current amount is {}", wallet.as_str(), @@ -2110,23 +2368,8 @@ async fn send_one_sided_stealth_transaction( receiver: String, fee_per_gram: u64, ) { - let mut sender_client = create_wallet_client(world, sender.clone()).await.unwrap(); - let sender_wallet_address = sender_client - .get_address(Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address - .to_hex(); - - let mut receiver_client = create_wallet_client(world, receiver.clone()).await.unwrap(); - let receiver_wallet_address = receiver_client - .get_address(Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address - .to_hex(); + let sender_wallet_address = world.get_wallet_address_base58(&sender).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address_base58(&receiver).await.unwrap(); let payment_recipient = PaymentRecipient { address: receiver_wallet_address.clone(), @@ -2144,6 +2387,7 @@ async fn send_one_sided_stealth_transaction( let transfer_req = TransferRequest { recipients: vec![payment_recipient], }; + let mut sender_client = create_wallet_client(world, sender.clone()).await.unwrap(); let tx_res = sender_client.transfer(transfer_req).await.unwrap().into_inner(); let tx_res = tx_res.results; @@ -2159,7 +2403,7 @@ async fn send_one_sided_stealth_transaction( fee_per_gram ); - // we wait for transaction to be broadcasted + // we wait for transaction to be broadcast let tx_id = tx_res.transaction_id; let num_retries = 100; let tx_info_req = GetTransactionInfoRequest { @@ -2177,7 +2421,7 @@ async fn send_one_sided_stealth_transaction( // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info.status == 1_i32 { println!( - "One sided stealth transaction from {} to {} with amount {} at fee {} has been broadcasted", + "One sided stealth transaction from {} to {} with amount {} at fee {} has been broadcast", sender.clone(), receiver.clone(), amount, @@ -2187,13 +2431,20 @@ async fn send_one_sided_stealth_transaction( } if i == num_retries - 1 { + error!( + "One sided stealth transaction from {} to {} with amount {} at fee {} failed to be broadcast", + sender.clone(), + receiver.clone(), + amount, + fee_per_gram + ); panic!( - "One sided stealth transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + "One sided stealth transaction from {} to {} with amount {} at fee {} failed to be broadcast", sender.clone(), receiver.clone(), amount, fee_per_gram - ) + ); } tokio::time::sleep(Duration::from_secs(5)).await; @@ -2214,6 +2465,7 @@ async fn send_one_sided_stealth_transaction( ); } +#[allow(clippy::too_many_lines)] #[then(expr = "I import {word} unspent outputs to {word}")] async fn import_wallet_unspent_outputs(world: &mut TariWorld, wallet_a: String, wallet_b: String) { let wallet_a_ps = world.wallets.get_mut(&wallet_a).unwrap(); @@ -2247,7 +2499,10 @@ async fn import_wallet_unspent_outputs(world: &mut TariWorld, wallet_a: String, let version = match &output[1] { "V0" => TransactionOutputVersion::V0, "V1" => TransactionOutputVersion::V1, - _ => panic!("Invalid output version"), + _ => { + error!(target: LOG_TARGET, "Invalid output version"); + panic!("Invalid output version"); + }, }; let value = MicroMinotari(output[2].parse::().unwrap()); let spending_key = PrivateKey::from_hex(&output[3]).unwrap(); @@ -2257,7 +2512,10 @@ async fn import_wallet_unspent_outputs(world: &mut TariWorld, wallet_a: String, "Burn" => OutputType::Burn, "ValidatorNodeRegistration" => OutputType::ValidatorNodeRegistration, "CodeTemplateRegistration" => OutputType::CodeTemplateRegistration, - _ => panic!("Invalid output type"), + _ => { + error!(target: LOG_TARGET, "Invalid output type"); + panic!("Invalid output type"); + }, }; let maturity = output[6].parse::().unwrap(); let coinbase_extra = CoinBaseExtra::try_from(Vec::from_hex(&output[7]).unwrap()).unwrap(); @@ -2326,6 +2584,7 @@ async fn import_wallet_unspent_outputs(world: &mut TariWorld, wallet_a: String, .tx_ids; } +#[allow(clippy::too_many_lines)] #[then(expr = "I import {word} spent outputs to {word}")] async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wallet_b: String) { let wallet_a_ps = world.wallets.get_mut(&wallet_a).unwrap(); @@ -2358,7 +2617,10 @@ async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wa let version = match &output[1] { "V0" => TransactionOutputVersion::V0, "V1" => TransactionOutputVersion::V1, - _ => panic!("Invalid output version"), + _ => { + error!(target: LOG_TARGET, "Invalid output version"); + panic!("Invalid output version"); + }, }; let value = MicroMinotari(output[2].parse::().unwrap()); let spending_key = PrivateKey::from_hex(&output[3]).unwrap(); @@ -2368,7 +2630,10 @@ async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wa "Burn" => OutputType::Burn, "ValidatorNodeRegistration" => OutputType::ValidatorNodeRegistration, "CodeTemplateRegistration" => OutputType::CodeTemplateRegistration, - _ => panic!("Invalid output type"), + _ => { + error!(target: LOG_TARGET, "Invalid output type"); + panic!("Invalid output type"); + }, }; let maturity = output[6].parse::().unwrap(); let coinbase_extra = CoinBaseExtra::try_from(Vec::from_hex(&output[7]).unwrap()).unwrap(); @@ -2469,7 +2734,10 @@ async fn import_unspent_outputs_as_pre_mine(world: &mut TariWorld, wallet_a: Str let version = match &output[1] { "V0" => TransactionOutputVersion::V0, "V1" => TransactionOutputVersion::V1, - _ => panic!("Invalid output version"), + _ => { + error!(target: LOG_TARGET, "Invalid output version"); + panic!("Invalid output version"); + }, }; let value = MicroMinotari(output[2].parse::().unwrap()); let spending_key = PrivateKey::from_hex(&output[3]).unwrap(); @@ -2479,7 +2747,10 @@ async fn import_unspent_outputs_as_pre_mine(world: &mut TariWorld, wallet_a: Str "Burn" => OutputType::Burn, "ValidatorNodeRegistration" => OutputType::ValidatorNodeRegistration, "CodeTemplateRegistration" => OutputType::CodeTemplateRegistration, - _ => panic!("Invalid output type"), + _ => { + error!(target: LOG_TARGET, "Invalid output type"); + panic!("Invalid output type"); + }, }; let maturity = output[6].parse::().unwrap(); let coinbase_extra = CoinBaseExtra::try_from(Vec::from_hex(&output[7]).unwrap()).unwrap(); @@ -2599,22 +2870,8 @@ async fn multi_send_txs_from_wallet( fee_per_gram: u64, ) { let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); - let sender_wallet_address = sender_wallet_client - .get_address(Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address - .to_hex(); - - let mut receiver_wallet_client = create_wallet_client(world, receiver.clone()).await.unwrap(); - let receiver_wallet_address = receiver_wallet_client - .get_address(Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address - .to_hex(); + let sender_wallet_address = world.get_wallet_address_base58(&sender).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address_base58(&receiver).await.unwrap(); let mut transfer_res = vec![]; @@ -2674,7 +2931,7 @@ async fn multi_send_txs_from_wallet( // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 if tx_info.status == 1_i32 { println!( - "Multi-transaction from {} to {} with amount {} at fee {} has been broadcasted", + "Multi-transaction from {} to {} with amount {} at fee {} has been broadcast", sender.clone(), receiver.clone(), amount, @@ -2684,13 +2941,20 @@ async fn multi_send_txs_from_wallet( } if i == num_retries - 1 { + error!( + "Multi-transaction from {} to {} with amount {} at fee {} failed to be broadcast", + sender.clone(), + receiver.clone(), + amount, + fee_per_gram + ); panic!( - "Multi-transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + "Multi-transaction from {} to {} with amount {} at fee {} failed to be broadcast", sender.clone(), receiver.clone(), amount, fee_per_gram - ) + ); } tokio::time::sleep(Duration::from_secs(5)).await; @@ -2727,6 +2991,10 @@ async fn check_if_last_imported_txs_are_invalid_in_wallet(world: &mut TariWorld, // 3 => TRANSACTION_STATUS_IMPORTED // 5 => TRANSACTION_STATUS_COINBASE if ![3, 5].contains(&status) { + error!( + "Imported transaction hasn't been received as such: current status code is {}, it should be 3 or 5", + status + ); panic!( "Imported transaction hasn't been received as such: current status code is {}, it should be 3 or 5", status @@ -2760,20 +3028,24 @@ async fn check_if_last_imported_txs_are_valid_in_wallet(world: &mut TariWorld, w #[then(expr = "I cancel last transaction in wallet {word}")] async fn cancel_last_transaction_in_wallet(world: &mut TariWorld, wallet: String) { - let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); - let wallet_address = client - .get_address(Empty {}) - .await - .unwrap() - .into_inner() - .interactive_address - .to_hex(); - - let wallet_tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + let wallet_address = world.get_wallet_address_base58(&wallet).await.unwrap(); + + let wallet_tx_ids = match world.wallet_tx_ids.get(&wallet_address) { + Some(tx_ids) => tx_ids, + None => { + error!( + target: LOG_TARGET, + "No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", + wallet + ); + panic!("No tx_ids found in 'world.wallet_tx_ids' for wallet '{}'", wallet); + }, + }; // get the last tx id for wallet let tx_id = *wallet_tx_ids.last().unwrap(); let cancel_tx_req = CancelTransactionRequest { tx_id }; + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); let cancel_tx_res = client.cancel_transaction(cancel_tx_req).await.unwrap().into_inner(); assert!( cancel_tx_res.is_success, @@ -2815,9 +3087,14 @@ async fn burn_transaction(world: &mut TariWorld, amount: u64, wallet: String, fe tokio::time::sleep(Duration::from_millis(HALF_SECOND)).await; } + error!( + "Burn transaction has status {} when we desired 1 (TRANSACTION_STATUS_BROADCAST), 2 \ + (TRANSACTION_STATUS_UNCONFIRMED), or 6 (TRANSACTION_STATUS_CONFIRMED)", + last_status + ); panic!( "Burn transaction has status {} when we desired 1 (TRANSACTION_STATUS_BROADCAST), 2 \ (TRANSACTION_STATUS_UNCONFIRMED), or 6 (TRANSACTION_STATUS_CONFIRMED)", last_status - ) + ); }