diff --git a/applications/tari_base_node/Cargo.toml b/applications/tari_base_node/Cargo.toml index bfffbfeabe..1eced936f6 100644 --- a/applications/tari_base_node/Cargo.toml +++ b/applications/tari_base_node/Cargo.toml @@ -19,7 +19,7 @@ tari_service_framework = { version = "^0.0", path = "../../base_layer/service_fr tari_shutdown = { path = "../../infrastructure/shutdown", version = "^0.0" } tari_mmr = { path = "../../base_layer/mmr", version = "^0.1" } tari_wallet = { path = "../../base_layer/wallet", version = "^0.1" } -tari_broadcast_channel = "^0.1" +tari_broadcast_channel = "^0.2" tari_crypto = { version = "^0.3" } structopt = { version = "0.3.13", default_features = false } diff --git a/applications/tari_base_node/src/builder.rs b/applications/tari_base_node/src/builder.rs index b7ae1cd7f2..2af3529c13 100644 --- a/applications/tari_base_node/src/builder.rs +++ b/applications/tari_base_node/src/builder.rs @@ -492,7 +492,7 @@ where //---------------------------------- Base Node --------------------------------------------// - let (publisher, base_node_subscriptions) = pubsub_connector(handle.clone(), 100); + let (publisher, base_node_subscriptions) = pubsub_connector(handle.clone(), 100, 1); let base_node_subscriptions = Arc::new(base_node_subscriptions); create_peer_db_folder(&config.peer_db_path)?; let (base_node_comms, base_node_dht) = setup_base_node_comms(base_node_identity, config, publisher).await?; @@ -515,7 +515,7 @@ where debug!(target: LOG_TARGET, "Base node service registration complete."); //---------------------------------- Wallet --------------------------------------------// - let (publisher, wallet_subscriptions) = pubsub_connector(handle.clone(), 1000); + let (publisher, wallet_subscriptions) = pubsub_connector(handle.clone(), 1000, 2); let wallet_subscriptions = Arc::new(wallet_subscriptions); create_peer_db_folder(&config.wallet_peer_db_path)?; let (wallet_comms, wallet_dht) = setup_wallet_comms( diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index 6a71fe47d2..f365c1d127 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -27,8 +27,8 @@ tari_common = { version= "^0.1", path = "../../common"} tari_service_framework = { version = "^0.0", path = "../service_framework"} tari_p2p = { version = "^0.1", path = "../../base_layer/p2p" } tari_comms_dht = { version = "^0.1", path = "../../comms/dht"} -tari_broadcast_channel = "^0.1" -tari_pubsub = "^0.1" +tari_broadcast_channel = "^0.2" +tari_pubsub = "^0.2" tari_shutdown = { version = "^0.0", path = "../../infrastructure/shutdown" } tari_mmr = { version = "^0.1", path = "../../base_layer/mmr", optional = true } randomx-rs = { version = "0.2.1", optional = true } diff --git a/base_layer/core/src/base_node/chain_metadata_service/initializer.rs b/base_layer/core/src/base_node/chain_metadata_service/initializer.rs index 12bc464acc..7b26e1042d 100644 --- a/base_layer/core/src/base_node/chain_metadata_service/initializer.rs +++ b/base_layer/core/src/base_node/chain_metadata_service/initializer.rs @@ -46,7 +46,7 @@ impl ServiceInitializer for ChainMetadataServiceInitializer { shutdown: ShutdownSignal, ) -> Self::Future { - let (publisher, subscriber) = broadcast_channel::bounded(BROADCAST_EVENT_BUFFER_SIZE); + let (publisher, subscriber) = broadcast_channel::bounded(BROADCAST_EVENT_BUFFER_SIZE, 5); let handle = ChainMetadataHandle::new(subscriber); handles_fut.register(handle); diff --git a/base_layer/core/src/base_node/chain_metadata_service/service.rs b/base_layer/core/src/base_node/chain_metadata_service/service.rs index 7e51826b47..b76d023210 100644 --- a/base_layer/core/src/base_node/chain_metadata_service/service.rs +++ b/base_layer/core/src/base_node/chain_metadata_service/service.rs @@ -303,7 +303,7 @@ mod test { let (base_node, mut base_node_receiver) = create_base_node_nci(); - let (publisher, _subscriber) = broadcast_channel::bounded(1); + let (publisher, _subscriber) = broadcast_channel::bounded(1, 106); let mut service = ChainMetadataService::new(liveness_handle, base_node, publisher); let mut proto_chain_metadata = create_sample_proto_chain_metadata(); @@ -347,7 +347,7 @@ mod test { let (base_node, _) = create_base_node_nci(); - let (publisher, _subscriber) = broadcast_channel::bounded(1); + let (publisher, _subscriber) = broadcast_channel::bounded(1, 107); let mut service = ChainMetadataService::new(liveness_handle, base_node, publisher); // To prevent the chain metadata buffer being flushed after receiving a single pong event, @@ -377,7 +377,7 @@ mod test { }; let (base_node, _) = create_base_node_nci(); - let (publisher, _subscriber) = broadcast_channel::bounded(1); + let (publisher, _subscriber) = broadcast_channel::bounded(1, 108); let mut service = ChainMetadataService::new(liveness_handle, base_node, publisher); let sample_event = LivenessEvent::ReceivedPong(Box::new(pong_event)); @@ -400,7 +400,7 @@ mod test { }; let (base_node, _) = create_base_node_nci(); - let (publisher, _subscriber) = broadcast_channel::bounded(1); + let (publisher, _subscriber) = broadcast_channel::bounded(1, 109); let mut service = ChainMetadataService::new(liveness_handle, base_node, publisher); let sample_event = LivenessEvent::ReceivedPong(Box::new(pong_event)); diff --git a/base_layer/core/src/base_node/state_machine.rs b/base_layer/core/src/base_node/state_machine.rs index 97a26b0488..7843bf5618 100644 --- a/base_layer/core/src/base_node/state_machine.rs +++ b/base_layer/core/src/base_node/state_machine.rs @@ -87,8 +87,8 @@ impl BaseNodeStateMachine { shutdown_signal: ShutdownSignal, ) -> Self { - let (event_sender, event_receiver): (Publisher<_>, Subscriber<_>) = bounded(10); - let (status_event_publisher, status_event_subscriber): (Publisher<_>, Subscriber<_>) = bounded(1); // only latest update is important + let (event_sender, event_receiver): (Publisher<_>, Subscriber<_>) = bounded(10, 3); + let (status_event_publisher, status_event_subscriber): (Publisher<_>, Subscriber<_>) = bounded(1, 4); // only latest update is important Self { db: db.clone(), local_node_interface: local_node_interface.clone(), diff --git a/base_layer/core/src/base_node/states/block_sync.rs b/base_layer/core/src/base_node/states/block_sync.rs index b9c403302a..fb3e680bcb 100644 --- a/base_layer/core/src/base_node/states/block_sync.rs +++ b/base_layer/core/src/base_node/states/block_sync.rs @@ -719,7 +719,7 @@ async fn exclude_sync_peer(sync_peers: &mut Vec, sync_peer: NodeId) -> R Ok(()) } -// Ban and disconnect the provided sync peer. +// Ban and disconnect the provided sync peer if this node is online async fn ban_sync_peer_if_online( shared: &mut BaseNodeStateMachine, sync_peers: &mut Vec, @@ -746,7 +746,6 @@ async fn ban_sync_peer( ) -> Result<(), BlockSyncError> { info!(target: LOG_TARGET, "Banning peer {} from local node.", sync_peer); - sync_peers.retain(|p| *p != sync_peer); shared.connectivity.ban_peer(sync_peer.clone(), ban_duration).await?; exclude_sync_peer(sync_peers, sync_peer).await } diff --git a/base_layer/core/src/mempool/service/initializer.rs b/base_layer/core/src/mempool/service/initializer.rs index 4b7dfbda20..9a4df0cc03 100644 --- a/base_layer/core/src/mempool/service/initializer.rs +++ b/base_layer/core/src/mempool/service/initializer.rs @@ -160,7 +160,7 @@ where T: BlockchainBackend + 'static let (outbound_tx_sender_service, outbound_tx_stream) = futures_mpsc_channel_unbounded(); let (outbound_request_sender_service, outbound_request_stream) = reply_channel::unbounded(); let (local_request_sender_service, local_request_stream) = reply_channel::unbounded(); - let (mempool_state_event_publisher, mempool_state_event_subscriber) = bounded(100); + let (mempool_state_event_publisher, mempool_state_event_subscriber) = bounded(100, 6); let outbound_mp_interface = OutboundMempoolServiceInterface::new(outbound_request_sender_service, outbound_tx_sender_service); let local_mp_interface = LocalMempoolService::new(local_request_sender_service, mempool_state_event_subscriber); diff --git a/base_layer/core/src/mempool/service/local_service.rs b/base_layer/core/src/mempool/service/local_service.rs index 3e3c9cbd1d..20433b0c6c 100644 --- a/base_layer/core/src/mempool/service/local_service.rs +++ b/base_layer/core/src/mempool/service/local_service.rs @@ -129,7 +129,7 @@ mod test { #[tokio_macros::test] async fn mempool_stats() { - let (_, event_subscriber) = bounded(100); + let (_, event_subscriber) = bounded(100, 110); let (tx, rx) = unbounded(); let mut service = LocalMempoolService::new(tx, event_subscriber); task::spawn(mock_handler(rx)); @@ -140,7 +140,7 @@ mod test { #[tokio_macros::test] async fn mempool_stats_from_multiple() { - let (_, event_subscriber) = bounded(100); + let (_, event_subscriber) = bounded(100, 111); let (tx, rx) = unbounded(); let mut service = LocalMempoolService::new(tx, event_subscriber); let mut service2 = service.clone(); diff --git a/base_layer/core/tests/helpers/chain_metadata.rs b/base_layer/core/tests/helpers/chain_metadata.rs index be72550821..091f28ecc6 100644 --- a/base_layer/core/tests/helpers/chain_metadata.rs +++ b/base_layer/core/tests/helpers/chain_metadata.rs @@ -42,7 +42,7 @@ pub struct MockChainMetadata { impl MockChainMetadata { pub fn new() -> Self { - let (publisher, subscriber) = bounded(10); + let (publisher, subscriber) = bounded(10, 114); Self { publisher, subscriber } } diff --git a/base_layer/core/tests/helpers/nodes.rs b/base_layer/core/tests/helpers/nodes.rs index 3e5b40befa..bd11c3f216 100644 --- a/base_layer/core/tests/helpers/nodes.rs +++ b/base_layer/core/tests/helpers/nodes.rs @@ -448,7 +448,7 @@ fn setup_base_node_services( CommsNode, ) { - let (publisher, subscription_factory) = pubsub_connector(runtime.handle().clone(), 100); + let (publisher, subscription_factory) = pubsub_connector(runtime.handle().clone(), 100, 104); let subscription_factory = Arc::new(subscription_factory); let (comms, dht) = runtime.block_on(setup_comms_services(node_identity, peers, publisher, data_path)); diff --git a/base_layer/core/tests/mining.rs b/base_layer/core/tests/mining.rs index 71d539efe6..25fa9c8542 100644 --- a/base_layer/core/tests/mining.rs +++ b/base_layer/core/tests/mining.rs @@ -102,7 +102,7 @@ fn mining() { let shutdown = Shutdown::new(); let mut miner = Miner::new(shutdown.to_signal(), consensus_manager, &alice_node.local_nci, 1); miner.enable_mining_flag().store(true, Ordering::Relaxed); - let (mut state_event_sender, state_event_receiver): (Publisher<_>, Subscriber<_>) = bounded(1); + let (mut state_event_sender, state_event_receiver): (Publisher<_>, Subscriber<_>) = bounded(1, 112); miner.subscribe_to_node_state_events(state_event_receiver); miner.subscribe_to_mempool_state_events(alice_node.local_mp_interface.get_mempool_state_event_stream()); let miner_utxo_stream = miner.get_utxo_receiver_channel().fuse(); diff --git a/base_layer/core/tests/wallet.rs b/base_layer/core/tests/wallet.rs index d06e8d7c79..c618db9531 100644 --- a/base_layer/core/tests/wallet.rs +++ b/base_layer/core/tests/wallet.rs @@ -301,7 +301,7 @@ fn wallet_base_node_integration_test() { let mut shutdown = Shutdown::new(); let mut miner = Miner::new(shutdown.to_signal(), consensus_manager, &base_node.local_nci, 1); miner.enable_mining_flag().store(true, Ordering::Relaxed); - let (mut state_event_sender, state_event_receiver): (Publisher<_>, Subscriber<_>) = bounded(1); + let (mut state_event_sender, state_event_receiver): (Publisher<_>, Subscriber<_>) = bounded(1, 113); miner.subscribe_to_node_state_events(state_event_receiver); miner.subscribe_to_mempool_state_events(base_node.local_mp_interface.get_mempool_state_event_stream()); let miner_utxo_stream = miner.get_utxo_receiver_channel().fuse(); diff --git a/base_layer/mmr/src/merkle_checkpoint.rs b/base_layer/mmr/src/merkle_checkpoint.rs index b7a6f654f9..80b920ec31 100644 --- a/base_layer/mmr/src/merkle_checkpoint.rs +++ b/base_layer/mmr/src/merkle_checkpoint.rs @@ -105,6 +105,12 @@ impl MerkleCheckPoint { self.prev_accumulated_nodes_added_count + self.nodes_added.len() as u32 } + /// Merge the provided Merkle checkpoint into the current checkpoint. + pub fn append(&mut self, mut cp: MerkleCheckPoint) { + self.nodes_added.append(&mut cp.nodes_added); + self.nodes_deleted.or_inplace(&cp.nodes_deleted); + } + /// Break a checkpoint up into its constituent parts pub fn into_parts(self) -> (Vec, Bitmap) { (self.nodes_added, self.nodes_deleted) diff --git a/base_layer/mmr/src/mmr_cache.rs b/base_layer/mmr/src/mmr_cache.rs index 27bf71ff3c..3637bcff2e 100644 --- a/base_layer/mmr/src/mmr_cache.rs +++ b/base_layer/mmr/src/mmr_cache.rs @@ -166,6 +166,16 @@ where Ok(()) } + /// Inform the MmrCache that the first N checkpoints have been merged to allow the base and current indices to be + /// updated. + pub fn checkpoints_merged(&mut self, num_merged: usize) -> Result<(), MerkleMountainRangeError> { + if let Some(num_reverse) = num_merged.checked_sub(1) { + self.base_cp_index = self.base_cp_index.saturating_sub(num_reverse); + self.curr_cp_index = self.curr_cp_index.saturating_sub(num_reverse); + } + self.update() + } + /// This function updates the state of the MMR cache based on the current state of the shared checkpoints. pub fn update(&mut self) -> Result<(), MerkleMountainRangeError> { let cp_count = self diff --git a/base_layer/mmr/tests/mmr_cache.rs b/base_layer/mmr/tests/mmr_cache.rs index 9d91252532..57fa3c416f 100644 --- a/base_layer/mmr/tests/mmr_cache.rs +++ b/base_layer/mmr/tests/mmr_cache.rs @@ -157,3 +157,76 @@ fn multiple_rewinds() { assert!(mmr_cache.update().is_ok()); assert_eq!(mmr_cache.get_mmr_only_root(), Ok(combine_hashes(&[&h1h2]).clone())); } + +#[test] +fn checkpoint_merging() { + let config = MmrCacheConfig { rewind_hist_len: 2 }; + let mut checkpoint_db = MemBackendVec::::new(); + let mut mmr_cache = MmrCache::::new(Vec::new(), checkpoint_db.clone(), config).unwrap(); + + let h1 = int_to_hash(1); + let h2 = int_to_hash(2); + let h3 = int_to_hash(3); + let h4 = int_to_hash(4); + let h5 = int_to_hash(5); + let h6 = int_to_hash(6); + let ha = combine_hashes(&[&h1, &h2]); + let hb = combine_hashes(&[&h3, &h4]); + let hc = combine_hashes(&[&h5, &h6]); + let hahb = combine_hashes(&[&ha, &hb]); + let cp4_mmr_only_root = combine_hashes(&[&hahb]); + let cp6_mmr_only_root = combine_hashes(&[&hahb, &hc]); + let cp1 = MerkleCheckPoint::new(vec![h1.clone()], Bitmap::create(), 0); + let cp2 = MerkleCheckPoint::new(vec![h2.clone()], Bitmap::create(), 0); + let cp3 = MerkleCheckPoint::new(vec![h3.clone()], Bitmap::create(), 0); + let cp4 = MerkleCheckPoint::new(vec![h4.clone()], Bitmap::create(), 0); + let cp5 = MerkleCheckPoint::new(vec![h5.clone()], Bitmap::create(), 0); + let cp6 = MerkleCheckPoint::new(vec![h6.clone()], Bitmap::create(), 0); + + checkpoint_db.push(cp1).unwrap(); + assert!(mmr_cache.update().is_ok()); + checkpoint_db.push(cp2).unwrap(); + assert!(mmr_cache.update().is_ok()); + checkpoint_db.push(cp3).unwrap(); + assert!(mmr_cache.update().is_ok()); + checkpoint_db.push(cp4).unwrap(); + assert!(mmr_cache.update().is_ok()); + assert_eq!(mmr_cache.get_mmr_only_root(), Ok(cp4_mmr_only_root.clone())); + + let mut merged_cp = checkpoint_db.get(0).unwrap().unwrap(); + merged_cp.append(checkpoint_db.get(1).unwrap().unwrap()); + assert!(checkpoint_db.shift(2).is_ok()); + assert!(checkpoint_db.push_front(merged_cp).is_ok()); + assert_eq!(checkpoint_db.len(), Ok(3)); + assert!(mmr_cache.checkpoints_merged(2).is_ok()); + assert_eq!(mmr_cache.get_mmr_only_root(), Ok(cp4_mmr_only_root.clone())); + + checkpoint_db.push(cp5).unwrap(); + assert!(mmr_cache.update().is_ok()); + checkpoint_db.push(cp6).unwrap(); + assert!(mmr_cache.update().is_ok()); + assert_eq!(mmr_cache.get_mmr_only_root(), Ok(cp6_mmr_only_root.clone())); + + let mut merged_cp = checkpoint_db.get(0).unwrap().unwrap(); + merged_cp.append(checkpoint_db.get(1).unwrap().unwrap()); + merged_cp.append(checkpoint_db.get(2).unwrap().unwrap()); + assert!(checkpoint_db.shift(3).is_ok()); + assert!(checkpoint_db.push_front(merged_cp).is_ok()); + assert_eq!(checkpoint_db.len(), Ok(3)); + assert!(mmr_cache.checkpoints_merged(3).is_ok()); + assert_eq!(mmr_cache.get_mmr_only_root(), Ok(cp6_mmr_only_root.clone())); + + // Recreate the MmrCache from the altered checkpoints + let mut mmr_cache = MmrCache::::new(Vec::new(), checkpoint_db.clone(), config).unwrap(); + assert_eq!(mmr_cache.get_mmr_only_root(), Ok(cp6_mmr_only_root.clone())); + + // Replace all existing checkpoints with a single accumulated checkpoint + let mut merged_cp = checkpoint_db.get(0).unwrap().unwrap(); + merged_cp.append(checkpoint_db.get(1).unwrap().unwrap()); + merged_cp.append(checkpoint_db.get(2).unwrap().unwrap()); + assert!(checkpoint_db.shift(3).is_ok()); + assert!(checkpoint_db.push_front(merged_cp).is_ok()); + assert_eq!(checkpoint_db.len(), Ok(1)); + assert!(mmr_cache.checkpoints_merged(3).is_ok()); + assert_eq!(mmr_cache.get_mmr_only_root(), Ok(cp6_mmr_only_root.clone())); +} diff --git a/base_layer/p2p/Cargo.toml b/base_layer/p2p/Cargo.toml index 008bdac60f..1aca93785a 100644 --- a/base_layer/p2p/Cargo.toml +++ b/base_layer/p2p/Cargo.toml @@ -13,11 +13,11 @@ edition = "2018" test-mocks = [] [dependencies] -tari_broadcast_channel = "^0.1" +tari_broadcast_channel = "^0.2" tari_comms = { version = "^0.1", path = "../../comms"} tari_comms_dht = { version = "^0.1", path = "../../comms/dht"} tari_crypto = { version = "^0.3" } -tari_pubsub = "^0.1" +tari_pubsub = "^0.2" tari_service_framework = { version = "^0.0", path = "../service_framework"} tari_shutdown = { version = "^0.0", path="../../infrastructure/shutdown" } tari_storage = {version = "^0.1", path = "../../infrastructure/storage"} diff --git a/base_layer/p2p/examples/pingpong.rs b/base_layer/p2p/examples/pingpong.rs index b97fc1a5be..c6147605fc 100644 --- a/base_layer/p2p/examples/pingpong.rs +++ b/base_layer/p2p/examples/pingpong.rs @@ -143,7 +143,7 @@ mod pingpong { let datastore_path = TempDir::new(random_string(8).as_str()).unwrap(); - let (publisher, subscription_factory) = pubsub_connector(rt.handle().clone(), 100); + let (publisher, subscription_factory) = pubsub_connector(rt.handle().clone(), 100, 105); let subscription_factory = Arc::new(subscription_factory); let transport_type = if is_tor_enabled { diff --git a/base_layer/p2p/src/comms_connector/pubsub.rs b/base_layer/p2p/src/comms_connector/pubsub.rs index dd7bfe20b4..4977829968 100644 --- a/base_layer/p2p/src/comms_connector/pubsub.rs +++ b/base_layer/p2p/src/comms_connector/pubsub.rs @@ -25,7 +25,7 @@ use crate::{comms_connector::InboundDomainConnector, tari_message::TariMessageTy use futures::{channel::mpsc, FutureExt, SinkExt, StreamExt}; use log::*; use std::sync::Arc; -use tari_pubsub::{pubsub_channel, TopicPayload, TopicSubscriptionFactory}; +use tari_pubsub::{pubsub_channel_with_id, TopicPayload, TopicSubscriptionFactory}; use tokio::runtime::Handle; const LOG_TARGET: &str = "comms::middleware::pubsub"; @@ -35,8 +35,13 @@ pub type PubsubDomainConnector = InboundDomainConnector>; /// Connects `InboundDomainConnector` to a `tari_pubsub::TopicPublisher` through a buffered channel -pub fn pubsub_connector(executor: Handle, buf_size: usize) -> (PubsubDomainConnector, SubscriptionFactory) { - let (publisher, subscription_factory) = pubsub_channel(buf_size); +pub fn pubsub_connector( + executor: Handle, + buf_size: usize, + buf_id: usize, +) -> (PubsubDomainConnector, SubscriptionFactory) +{ + let (publisher, subscription_factory) = pubsub_channel_with_id(buf_size, buf_id); let (sender, receiver) = mpsc::channel(buf_size); // Spawn a task which forwards messages from the pubsub service to the TopicPublisher diff --git a/base_layer/p2p/src/initialization.rs b/base_layer/p2p/src/initialization.rs index a383d66994..8e10017d51 100644 --- a/base_layer/p2p/src/initialization.rs +++ b/base_layer/p2p/src/initialization.rs @@ -125,6 +125,7 @@ where .take(8) .collect::() }; + std::fs::create_dir_all(data_path).unwrap(); let datastore = LMDBBuilder::new() .set_path(data_path) .set_environment_size(50) diff --git a/base_layer/p2p/tests/services/liveness.rs b/base_layer/p2p/tests/services/liveness.rs index 1b2e0e0a41..4fbb880e72 100644 --- a/base_layer/p2p/tests/services/liveness.rs +++ b/base_layer/p2p/tests/services/liveness.rs @@ -48,7 +48,7 @@ pub async fn setup_liveness_service( ) -> (LivenessHandle, CommsNode, Dht) { let rt_handle = runtime::Handle::current(); - let (publisher, subscription_factory) = pubsub_connector(rt_handle.clone(), 100); + let (publisher, subscription_factory) = pubsub_connector(rt_handle.clone(), 100, 101); let subscription_factory = Arc::new(subscription_factory); let (comms, dht) = setup_comms_services(node_identity.clone(), peers, publisher, data_path).await; diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index d529f69a81..368b3b7334 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -11,13 +11,13 @@ test_harness = ["tari_test_utils"] c_integration = [] [dependencies] -tari_broadcast_channel = "^0.1" +tari_broadcast_channel = "^0.2" tari_comms = { path = "../../comms", version = "^0.1"} tari_comms_dht = { path = "../../comms/dht", version = "^0.1"} tari_crypto = { version = "^0.3" } tari_key_manager = {path = "../key_manager", version = "^0.0"} tari_p2p = {path = "../p2p", version = "^0.1"} -tari_pubsub = "^0.1" +tari_pubsub = "^0.2" tari_service_framework = { version = "^0.0", path = "../service_framework"} tari_shutdown = { path = "../../infrastructure/shutdown", version = "^0.0"} tari_storage = { version = "^0.1", path = "../../infrastructure/storage"} diff --git a/base_layer/wallet/src/output_manager_service/mod.rs b/base_layer/wallet/src/output_manager_service/mod.rs index 764cfafd71..3bbc969155 100644 --- a/base_layer/wallet/src/output_manager_service/mod.rs +++ b/base_layer/wallet/src/output_manager_service/mod.rs @@ -112,7 +112,7 @@ where T: OutputManagerBackend + 'static let base_node_response_stream = self.base_node_response_stream(); let (sender, receiver) = reply_channel::unbounded(); - let (publisher, subscriber) = bounded(100); + let (publisher, subscriber) = bounded(100, 7); let oms_handle = OutputManagerHandle::new(sender, subscriber); diff --git a/base_layer/wallet/src/text_message_service/mod.rs b/base_layer/wallet/src/text_message_service/mod.rs index e793559a45..2df642f57f 100644 --- a/base_layer/wallet/src/text_message_service/mod.rs +++ b/base_layer/wallet/src/text_message_service/mod.rs @@ -104,7 +104,7 @@ impl ServiceInitializer for TextMessageServiceInitializer { .expect("text message service initializer already called"); let (sender, receiver) = reply_channel::unbounded(); - let (publisher, subscriber) = bounded(100); + let (publisher, subscriber) = bounded(100, 117); let text_message_stream = self.text_message_stream(); let text_message_ack_stream = self.text_message_ack_stream(); diff --git a/base_layer/wallet/src/transaction_service/mod.rs b/base_layer/wallet/src/transaction_service/mod.rs index 2d41453b36..812a766f74 100644 --- a/base_layer/wallet/src/transaction_service/mod.rs +++ b/base_layer/wallet/src/transaction_service/mod.rs @@ -49,10 +49,7 @@ use tari_core::{ use tari_p2p::{ comms_connector::PeerMessage, domain_message::DomainMessage, - services::{ - liveness::LivenessHandle, - utils::{map_decode, ok_or_skip_result}, - }, + services::utils::{map_decode, ok_or_skip_result}, tari_message::TariMessageType, }; use tari_pubsub::TopicSubscriptionFactory; @@ -178,9 +175,6 @@ where T: TransactionBackend + Clone + 'static let output_manager_service = handles .get_handle::() .expect("Output Manager Service handle required for TransactionService"); - let liveness_service = handles - .get_handle::() - .expect("LivenessHandle handle required for Transaction Service"); let service = TransactionService::new( config, @@ -193,7 +187,6 @@ where T: TransactionBackend + Clone + 'static base_node_response_stream, output_manager_service, outbound_message_service, - liveness_service, publisher, node_identity, factories, diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs index 21c4a4ef7a..1d4322d192 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs @@ -261,6 +261,8 @@ where TBackend: TransactionBackend + Clone + 'static )); }, }; + + #[allow(clippy::single_match)] match completed_tx.status { TransactionStatus::Completed => match ts { // Getting this response means the Mempool Rejected this transaction so it will be diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_chain_monitoring_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_chain_monitoring_protocol.rs index 02f9e6aa07..c94e9ed193 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_chain_monitoring_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_chain_monitoring_protocol.rs @@ -308,6 +308,8 @@ where TBackend: TransactionBackend + Clone + 'static )); }, }; + + #[allow(clippy::single_match)] match completed_tx.status { TransactionStatus::Broadcast => match ts { // Getting this response means the Mempool Rejected this transaction so it will be 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 a851ecd17a..d6e2f398bb 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 @@ -20,13 +20,495 @@ // 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. -// pub struct TransactionReceiveProtocol { -// id: u64, -// db: TransactionDatabase, -// output_manager_service: OutputManagerHandle, -// outbound_message_service: OutboundMessageRequester, -// event_publisher: Publisher, -// node_identity: Arc, -// factories: CryptoFactories, -// transaction_finalized_channel: Receiver, -// } +use crate::{ + output_manager_service::TxId, + transaction_service::{ + error::{TransactionServiceError, TransactionServiceProtocolError}, + handle::TransactionEvent, + service::TransactionServiceResources, + storage::database::{CompletedTransaction, InboundTransaction, TransactionBackend, TransactionStatus}, + }, +}; +use chrono::Utc; +use futures::{ + channel::{mpsc, oneshot}, + future::FutureExt, + StreamExt, +}; +use log::*; +use rand::rngs::OsRng; +use std::sync::Arc; +use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; +use tari_comms_dht::{ + domain_message::OutboundDomainMessage, + envelope::NodeDestination, + outbound::{MessageSendStates, OutboundEncryption, SendMessageResponse}, +}; +use tari_core::transactions::{ + transaction::{OutputFeatures, Transaction}, + transaction_protocol::{proto, recipient::RecipientState, sender::TransactionSenderMessage}, + types::PrivateKey, + ReceiverTransactionProtocol, +}; +use tari_crypto::keys::SecretKey; +use tari_p2p::tari_message::TariMessageType; + +const LOG_TARGET: &str = "wallet::transaction_service::protocols::receive_protocol"; + +#[derive(Debug, PartialEq)] +pub enum TransactionReceiveProtocolStage { + Initial, + WaitForFinalize, +} + +pub struct TransactionReceiveProtocol +where TBackend: TransactionBackend + Clone + 'static +{ + id: u64, + source_pubkey: CommsPublicKey, + sender_message: TransactionSenderMessage, + stage: TransactionReceiveProtocolStage, + resources: TransactionServiceResources, + transaction_finalize_receiver: Option>, + cancellation_receiver: Option>, +} + +impl TransactionReceiveProtocol +where TBackend: TransactionBackend + Clone + 'static +{ + pub fn new( + id: u64, + source_pubkey: CommsPublicKey, + sender_message: TransactionSenderMessage, + stage: TransactionReceiveProtocolStage, + resources: TransactionServiceResources, + transaction_finalize_receiver: mpsc::Receiver<(CommsPublicKey, TxId, Transaction)>, + cancellation_receiver: oneshot::Receiver<()>, + ) -> Self + { + Self { + id, + source_pubkey, + sender_message, + stage, + resources, + transaction_finalize_receiver: Some(transaction_finalize_receiver), + cancellation_receiver: Some(cancellation_receiver), + } + } + + pub async fn execute(mut self) -> Result { + info!( + target: LOG_TARGET, + "Starting Transaction Receive protocol for TxId: {} at Stage {:?}", self.id, self.stage + ); + + match self.stage { + TransactionReceiveProtocolStage::Initial => { + self.accept_transaction().await?; + self.wait_for_finalization().await?; + }, + TransactionReceiveProtocolStage::WaitForFinalize => { + self.wait_for_finalization().await?; + }, + } + + Ok(self.id) + } + + async fn accept_transaction(&mut self) -> Result<(), TransactionServiceProtocolError> { + // Currently we will only reply to a Single sender transaction protocol + if let TransactionSenderMessage::Single(data) = self.sender_message.clone() { + // Check this is not a repeat message i.e. tx_id doesn't already exist in our pending or completed + // transactions + if self + .resources + .db + .transaction_exists(data.tx_id) + .await + .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))? + { + trace!( + target: LOG_TARGET, + "Received Transaction (TxId: {}) already present in database.", + data.tx_id, + ); + return Err(TransactionServiceProtocolError::new( + self.id, + TransactionServiceError::RepeatedMessageError, + )); + } + + let amount = data.amount; + + let spending_key = self + .resources + .output_manager_service + .get_recipient_spending_key(data.tx_id, data.amount) + .await + .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; + let nonce = PrivateKey::random(&mut OsRng); + + let rtp = ReceiverTransactionProtocol::new( + self.sender_message.clone(), + nonce, + spending_key, + OutputFeatures::default(), + &self.resources.factories, + ); + let recipient_reply = rtp + .get_signed_data() + .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))? + .clone(); + + let mut store_and_forward_send_result = false; + let mut direct_send_result = false; + + let tx_id = recipient_reply.tx_id; + let proto_message: proto::RecipientSignedMessage = recipient_reply.into(); + match self + .resources + .outbound_message_service + .send_direct( + self.source_pubkey.clone(), + OutboundEncryption::None, + OutboundDomainMessage::new(TariMessageType::ReceiverPartialTransactionReply, proto_message.clone()), + ) + .await + { + Ok(result) => match result { + SendMessageResponse::Queued(send_states) => { + if self.wait_on_dial(send_states).await { + direct_send_result = true; + } else { + store_and_forward_send_result = self + .send_transaction_reply_store_and_forward( + tx_id, + self.source_pubkey.clone(), + proto_message.clone(), + ) + .await + .map_err(|e| TransactionServiceProtocolError::new(self.id, e))?; + } + }, + SendMessageResponse::Failed => { + error!( + target: LOG_TARGET, + "Transaction Reply Send Direct for TxID: {} failed", self.id + ); + store_and_forward_send_result = self + .send_transaction_reply_store_and_forward( + tx_id, + self.source_pubkey.clone(), + proto_message.clone(), + ) + .await + .map_err(|e| TransactionServiceProtocolError::new(self.id, e))?; + }, + SendMessageResponse::PendingDiscovery(rx) => { + store_and_forward_send_result = self + .send_transaction_reply_store_and_forward( + tx_id, + self.source_pubkey.clone(), + proto_message.clone(), + ) + .await + .map_err(|e| TransactionServiceProtocolError::new(self.id, e))?; + // now wait for discovery to complete + match rx.await { + Ok(send_msg_response) => { + if let SendMessageResponse::Queued(send_states) = send_msg_response { + debug!("Discovery of {} completed for TxID: {}", self.source_pubkey, self.id); + direct_send_result = self.wait_on_dial(send_states).await; + } + }, + Err(e) => { + error!( + target: LOG_TARGET, + "Error waiting for Discovery while sending message to TxId: {} {:?}", self.id, e + ); + }, + } + }, + }, + Err(e) => { + error!(target: LOG_TARGET, "Direct Transaction Reply Send failed: {:?}", e); + }, + } + + // Otherwise add it to our pending transaction list and return reply + let inbound_transaction = InboundTransaction::new( + tx_id, + self.source_pubkey.clone(), + amount, + rtp.clone(), + TransactionStatus::Pending, + data.message.clone(), + Utc::now().naive_utc(), + ); + self.resources + .db + .add_pending_inbound_transaction(tx_id, inbound_transaction.clone()) + .await + .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; + + if !direct_send_result && !store_and_forward_send_result { + warn!( + target: LOG_TARGET, + "Transaction with TX_ID = {} received from {}. Reply could not be sent!", tx_id, self.source_pubkey, + ); + } else { + info!( + target: LOG_TARGET, + "Transaction with TX_ID = {} received from {}. Reply Sent", tx_id, self.source_pubkey, + ); + } + + info!( + target: LOG_TARGET, + "Transaction (TX_ID: {}) - Amount: {} - Message: {}", tx_id, amount, data.message, + ); + + let _ = self + .resources + .event_publisher + .send(Arc::new(TransactionEvent::ReceivedTransaction(tx_id))) + .map_err(|e| { + trace!(target: LOG_TARGET, "Error sending event due to no subscribers: {:?}", e); + e + }); + Ok(()) + } else { + Err(TransactionServiceProtocolError::new( + self.id, + TransactionServiceError::InvalidStateError, + )) + } + } + + async fn send_transaction_reply_store_and_forward( + &mut self, + tx_id: TxId, + source_pubkey: CommsPublicKey, + msg: proto::RecipientSignedMessage, + ) -> Result + { + match self + .resources + .outbound_message_service + .broadcast( + NodeDestination::NodeId(Box::new(NodeId::from_key(&source_pubkey)?)), + OutboundEncryption::EncryptFor(Box::new(source_pubkey.clone())), + vec![], + OutboundDomainMessage::new(TariMessageType::ReceiverPartialTransactionReply, msg), + ) + .await + { + Ok(result) => match result.resolve_ok().await { + None => { + error!( + target: LOG_TARGET, + "Sending Transaction Reply (TxId: {}) to neighbours for Store and Forward failed", tx_id, + ); + }, + Some(tags) if !tags.is_empty() => { + info!( + target: LOG_TARGET, + "Sending Transaction Reply (TxId: {}) to Neighbours for Store and Forward successful with \ + Message Tags: {:?}", + tx_id, + tags, + ); + }, + Some(_) => { + error!( + target: LOG_TARGET, + "Sending Transaction Reply to Neighbours for Store and Forward for TX_ID: {} was unsuccessful \ + and no messages were sent.", + tx_id, + ); + }, + }, + Err(e) => { + error!( + target: LOG_TARGET, + "Sending Transaction Reply (TxId: {}) to neighbours for Store and Forward failed: {:?}", tx_id, e, + ); + }, + }; + + Ok(true) + } + + /// This function contains the logic to wait on a dial and send of a queued message + async fn wait_on_dial(&self, send_states: MessageSendStates) -> bool { + if send_states.len() == 1 { + debug!( + target: LOG_TARGET, + "Transaction Reply (TxId: {}) Direct Send to {} queued with Message {}", + self.id, + self.source_pubkey, + send_states[0].tag, + ); + let (sent, failed) = send_states + .wait_n_timeout(self.resources.config.direct_send_timeout, 1) + .await; + if !sent.is_empty() { + info!( + target: LOG_TARGET, + "Direct Send process of Transaction Reply TX_ID: {} was successful with Message: {}", + self.id, + sent[0] + ); + true + } else { + if failed.is_empty() { + error!( + target: LOG_TARGET, + "Direct Send process for Transaction Reply TX_ID: {} timed out", self.id + ); + } else { + error!( + target: LOG_TARGET, + "Direct Send process for Transaction Reply TX_ID: {} and Message {} was unsuccessful and no \ + message was sent", + self.id, + failed[0] + ); + } + false + } + } else { + error!( + target: LOG_TARGET, + "Transaction Reply Send Direct for TxID: {} failed", self.id + ); + false + } + } + + async fn wait_for_finalization(&mut self) -> Result<(), TransactionServiceProtocolError> { + let mut receiver = self + .transaction_finalize_receiver + .take() + .ok_or_else(|| TransactionServiceProtocolError::new(self.id, TransactionServiceError::InvalidStateError))?; + + let mut cancellation_receiver = self + .cancellation_receiver + .take() + .ok_or_else(|| TransactionServiceProtocolError::new(self.id, TransactionServiceError::InvalidStateError))? + .fuse(); + + let inbound_tx = match self.resources.db.get_pending_inbound_transaction(self.id).await { + Ok(tx) => tx, + Err(_e) => { + warn!( + target: LOG_TARGET, + "TxId for received Finalized Transaction does not exist in Pending Inbound Transactions, could be \ + a repeat Store and Forward message" + ); + return Ok(()); + }, + }; + + #[allow(unused_assignments)] + let mut incoming_finalized_transaction = None; + loop { + loop { + futures::select! { + (spk, tx_id, tx) = receiver.select_next_some() => { + incoming_finalized_transaction = Some(tx); + if inbound_tx.source_public_key != spk { + warn!( + target: LOG_TARGET, + "Finalized Transaction did not come from the expected Public Key" + ); + } else if tx_id != inbound_tx.tx_id || tx_id != self.id { + debug!(target: LOG_TARGET, "Finalized Transaction does not have the correct TxId"); + } else { + break; + } + }, + _ = cancellation_receiver => { + info!(target: LOG_TARGET, "Cancelling Transaction Receive Protocol for TxId: {}", self.id); + return Err(TransactionServiceProtocolError::new( + self.id, + TransactionServiceError::TransactionCancelled, + )); + } + } + } + + let finalized_transaction: Transaction = incoming_finalized_transaction.ok_or_else(|| { + TransactionServiceProtocolError::new(self.id, TransactionServiceError::TransactionCancelled) + })?; + + info!( + target: LOG_TARGET, + "Finalized Transaction with TX_ID = {} received from {}", + self.id, + self.source_pubkey.clone() + ); + + let rtp_output = match inbound_tx.receiver_protocol.state.clone() { + RecipientState::Finalized(s) => s.output, + RecipientState::Failed(_) => { + warn!( + target: LOG_TARGET, + "Finalized Transaction TxId: {} is not in the correct state to be completed", self.id + ); + return Err(TransactionServiceProtocolError::new( + self.id, + TransactionServiceError::InvalidStateError, + )); + }, + }; + + let finalized_outputs = finalized_transaction.body.outputs(); + + if finalized_outputs.iter().find(|o| o == &&rtp_output).is_none() { + warn!( + target: LOG_TARGET, + "Finalized Transaction does not contain the Receiver's output" + ); + continue; + } + + let completed_transaction = CompletedTransaction::new( + self.id, + self.source_pubkey.clone(), + self.resources.node_identity.public_key().clone(), + inbound_tx.amount, + finalized_transaction.body.get_total_fee(), + finalized_transaction.clone(), + TransactionStatus::Completed, + inbound_tx.message.clone(), + inbound_tx.timestamp, + ); + + self.resources + .db + .complete_inbound_transaction(self.id, completed_transaction.clone()) + .await + .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; + + info!( + target: LOG_TARGET, + "Inbound Transaction with TX_ID = {} from {} moved to Completed Transactions", + self.id, + self.source_pubkey.clone() + ); + + let _ = self + .resources + .event_publisher + .send(Arc::new(TransactionEvent::ReceivedFinalizedTransaction(self.id))) + .map_err(|e| { + trace!(target: LOG_TARGET, "Error sending event, no subscribers: {:?}", e); + e + }); + break; + } + Ok(()) + } +} 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 1d4c055c91..771812dcd9 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 @@ -41,16 +41,16 @@ use tari_comms_dht::{ }; use tari_core::transactions::{ tari_amount::MicroTari, - transaction::{KernelFeatures, TransactionError}, + transaction::{KernelFeatures, Transaction, TransactionError}, transaction_protocol::{proto, recipient::RecipientSignedMessage, sender::SingleRoundSenderData}, SenderTransactionProtocol, }; -use tari_p2p::{services::liveness::LivenessEvent, tari_message::TariMessageType}; +use tari_p2p::tari_message::TariMessageType; const LOG_TARGET: &str = "wallet::transaction_service::protocols::send_protocol"; #[derive(Debug, PartialEq)] -pub enum TransactionProtocolStage { +pub enum TransactionSendProtocolStage { Initial, WaitForReply, } @@ -63,7 +63,7 @@ where TBackend: TransactionBackend + Clone + 'static amount: MicroTari, message: String, sender_protocol: SenderTransactionProtocol, - stage: TransactionProtocolStage, + stage: TransactionSendProtocolStage, resources: TransactionServiceResources, transaction_reply_receiver: Option>, cancellation_receiver: Option>, @@ -82,7 +82,7 @@ where TBackend: TransactionBackend + Clone + 'static amount: MicroTari, message: String, sender_protocol: SenderTransactionProtocol, - stage: TransactionProtocolStage, + stage: TransactionSendProtocolStage, ) -> Self { Self { @@ -101,80 +101,94 @@ where TBackend: TransactionBackend + Clone + 'static /// Execute the Transaction Send Protocol as an async task. pub async fn execute(mut self) -> Result { info!( - "Starting Transaction Send protocol for TxId: {} at Stage {:?}", - self.id, self.stage + target: LOG_TARGET, + "Starting Transaction Send protocol for TxId: {} at Stage {:?}", self.id, self.stage ); - // Only Send the transaction if the protocol stage is Initial. If the protocol is started in a later stage - // ignore this - if self.stage == TransactionProtocolStage::Initial { - if !self.sender_protocol.is_single_round_message_ready() { - error!(target: LOG_TARGET, "Sender Transaction Protocol is in an invalid state"); - return Err(TransactionServiceProtocolError::new( - self.id, - TransactionServiceError::InvalidStateError, - )); - } - - let msg = self - .sender_protocol - .build_single_round_message() - .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; - let tx_id = msg.tx_id; + match self.stage { + TransactionSendProtocolStage::Initial => { + self.initial_send_transaction().await?; + self.wait_for_reply().await?; + }, + TransactionSendProtocolStage::WaitForReply => { + self.wait_for_reply().await?; + }, + } - if tx_id != self.id { - return Err(TransactionServiceProtocolError::new( - self.id, - TransactionServiceError::InvalidStateError, - )); - } + Ok(self.id) + } - let SendResult { - direct_send_result, - store_and_forward_send_result, - } = self.send_transaction(msg).await?; + async fn initial_send_transaction(&mut self) -> Result<(), TransactionServiceProtocolError> { + if !self.sender_protocol.is_single_round_message_ready() { + error!(target: LOG_TARGET, "Sender Transaction Protocol is in an invalid state"); + return Err(TransactionServiceProtocolError::new( + self.id, + TransactionServiceError::InvalidStateError, + )); + } - if !direct_send_result && !store_and_forward_send_result { - return Err(TransactionServiceProtocolError::new( - self.id, - TransactionServiceError::OutboundSendFailure, - )); - } + let msg = self + .sender_protocol + .build_single_round_message() + .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; + let tx_id = msg.tx_id; - self.resources - .output_manager_service - .confirm_pending_transaction(self.id) - .await - .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; - - let fee = self - .sender_protocol - .get_fee_amount() - .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; - let outbound_tx = OutboundTransaction::new( - tx_id, - self.dest_pubkey.clone(), - self.amount, - fee, - self.sender_protocol.clone(), - TransactionStatus::Pending, - self.message.clone(), - Utc::now().naive_utc(), - direct_send_result, - ); + if tx_id != self.id { + return Err(TransactionServiceProtocolError::new( + self.id, + TransactionServiceError::InvalidStateError, + )); + } - self.resources - .db - .add_pending_outbound_transaction(outbound_tx.tx_id, outbound_tx) - .await - .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; + let SendResult { + direct_send_result, + store_and_forward_send_result, + } = self.send_transaction(msg).await?; - info!( - target: LOG_TARGET, - "Pending Outbound Transaction TxId: {:?} added. Waiting for Reply or Cancellation", self.id, - ); + if !direct_send_result && !store_and_forward_send_result { + return Err(TransactionServiceProtocolError::new( + self.id, + TransactionServiceError::OutboundSendFailure, + )); } + self.resources + .output_manager_service + .confirm_pending_transaction(self.id) + .await + .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; + + let fee = self + .sender_protocol + .get_fee_amount() + .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; + let outbound_tx = OutboundTransaction::new( + tx_id, + self.dest_pubkey.clone(), + self.amount, + fee, + self.sender_protocol.clone(), + TransactionStatus::Pending, + self.message.clone(), + Utc::now().naive_utc(), + direct_send_result, + ); + + self.resources + .db + .add_pending_outbound_transaction(outbound_tx.tx_id, outbound_tx) + .await + .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; + + info!( + target: LOG_TARGET, + "Pending Outbound Transaction TxId: {:?} added. Waiting for Reply or Cancellation", self.id, + ); + + Ok(()) + } + + async fn wait_for_reply(&mut self) -> Result<(), TransactionServiceProtocolError> { // Waiting for Transaction Reply let tx_id = self.id; let mut receiver = self @@ -195,8 +209,6 @@ where TBackend: TransactionBackend + Clone + 'static .await .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; - let mut direct_send_result = outbound_tx.direct_send_success; - if !outbound_tx.sender_protocol.is_collecting_single_signature() { error!(target: LOG_TARGET, "Pending Transaction not in correct state"); return Err(TransactionServiceProtocolError::new( @@ -205,18 +217,6 @@ where TBackend: TransactionBackend + Clone + 'static )); } - // Add receiver to Liveness Service to monitor for liveness - let mut liveness_event_stream = self.resources.liveness_service.get_event_stream_fused(); - let destination_node_id = NodeId::from_key(&self.dest_pubkey) - .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; - if !direct_send_result { - self.resources - .liveness_service - .add_node_id(destination_node_id.clone()) - .await - .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; - } - #[allow(unused_assignments)] let mut reply = None; loop { @@ -236,25 +236,6 @@ where TBackend: TransactionBackend + Clone + 'static break; } }, - liveness_event = liveness_event_stream.select_next_some() => { - if let Ok(event) = liveness_event { - if let LivenessEvent::ReceivedPong(pong_event) = (*event).clone() { - if !direct_send_result && pong_event.node_id == destination_node_id { - debug!(target: LOG_TARGET, "Pong message received from counter-party before Transaction Reply is received, resending transaction TxId: {}", self.id); - let msg = self.sender_protocol.get_single_round_message().map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; - // If a direct send attempt is successful then stop resending on Pong and remove an instance of this node_id to be monitored in liveness - if self.send_transaction_direct_only(msg).await? { - direct_send_result = true; - self.resources - .liveness_service - .remove_node_id(destination_node_id.clone()) - .await - .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; - } - } - } - } - }, _ = cancellation_receiver => { info!(target: LOG_TARGET, "Cancelling Transaction Send Protocol for TxId: {}", self.id); return Err(TransactionServiceProtocolError::new( @@ -315,11 +296,6 @@ where TBackend: TransactionBackend + Clone + 'static "Transaction Recipient Reply for TX_ID = {} received", tx_id, ); - let finalized_transaction_message = proto::TransactionFinalizedMessage { - tx_id, - transaction: Some(tx.clone().into()), - }; - let _ = self .resources .event_publisher @@ -333,75 +309,9 @@ where TBackend: TransactionBackend + Clone + 'static e }); - match self - .resources - .outbound_message_service - .send_direct( - outbound_tx.destination_public_key.clone(), - OutboundEncryption::None, - OutboundDomainMessage::new( - TariMessageType::TransactionFinalized, - finalized_transaction_message.clone(), - ), - ) - .await - { - Ok(result) => match result.resolve_ok().await { - None => { - self.send_transaction_finalized_message_store_and_forward(finalized_transaction_message.clone()) - .await? - }, - Some(send_states) => { - if send_states.len() == 1 { - let msg_tag = send_states[0].tag; - debug!( - target: LOG_TARGET, - "Transaction Finalized (TxId: {}) Direct Send to {} queued with {}", - self.id, - self.dest_pubkey, - &msg_tag, - ); - match send_states.wait_single().await { - true => { - info!( - target: LOG_TARGET, - "Direct Send of Transaction Finalized message for TX_ID: {} was successful ({})", - self.id, - msg_tag - ); - }, - false => { - error!( - target: LOG_TARGET, - "Direct Send of Transaction Finalized message for TX_ID: {} was unsuccessful and \ - no message was sent", - self.id - ); - self.send_transaction_finalized_message_store_and_forward( - finalized_transaction_message.clone(), - ) - .await? - }, - } - } else { - error!( - target: LOG_TARGET, - "Transaction Finalized message Send Direct for TxID: {} failed", self.id - ); - self.send_transaction_finalized_message_store_and_forward(finalized_transaction_message.clone()) - .await? - } - }, - }, - Err(e) => { - return Err(TransactionServiceProtocolError::new( - self.id, - TransactionServiceError::from(e), - )) - }, - } + self.send_transaction_finalized_message(tx.clone()).await?; - Ok(self.id) + Ok(()) } /// Attempt to send the transaction to the recipient both directly and via Store-and-forward. If both fail to send @@ -434,7 +344,7 @@ where TBackend: TransactionBackend + Clone + 'static { Ok(result) => match result { SendMessageResponse::Queued(send_states) => { - if self.wait_on_dial(send_states).await { + if self.wait_on_dial(send_states, "Transaction").await { direct_send_result = true; } else { store_and_forward_send_result = self.send_transaction_store_and_forward(msg.clone()).await?; @@ -451,12 +361,11 @@ where TBackend: TransactionBackend + Clone + 'static store_and_forward_send_result = self.send_transaction_store_and_forward(msg.clone()).await?; // now wait for discovery to complete match rx.await { - Ok(send_msg_response) => match send_msg_response { - SendMessageResponse::Queued(send_states) => { + Ok(send_msg_response) => { + if let SendMessageResponse::Queued(send_states) = send_msg_response { debug!("Discovery of {} completed for TxID: {}", self.dest_pubkey, self.id); - direct_send_result = self.wait_on_dial(send_states).await; - }, - _ => (), + direct_send_result = self.wait_on_dial(send_states, "Transaction").await; + } }, Err(e) => { error!( @@ -509,34 +418,36 @@ where TBackend: TransactionBackend + Clone + 'static } /// This function contains the logic to wait on a dial and send of a queued message - async fn wait_on_dial(&self, send_states: MessageSendStates) -> bool { + async fn wait_on_dial(&self, send_states: MessageSendStates, message: &str) -> bool { if send_states.len() == 1 { debug!( target: LOG_TARGET, - "Transaction (TxId: {}) Direct Send to {} queued with Message {}", + "{} (TxId: {}) Direct Send to {} queued with Message {}", + message, self.id, self.dest_pubkey, send_states[0].tag, ); let (sent, failed) = send_states - .wait_n_timeout(self.resources.config.direct_send_timeout.clone(), 1) + .wait_n_timeout(self.resources.config.direct_send_timeout, 1) .await; if !sent.is_empty() { info!( target: LOG_TARGET, - "Direct Send process for TX_ID: {} was successful with Message: {}", self.id, sent[0] + "Direct Send process for {} TX_ID: {} was successful with Message: {}", message, self.id, sent[0] ); true } else { if failed.is_empty() { error!( target: LOG_TARGET, - "Direct Send process for TX_ID: {} timed out", self.id + "Direct Send process for {} TX_ID: {} timed out", message, self.id ); } else { error!( target: LOG_TARGET, - "Direct Send process for TX_ID: {} and Message {} was unsuccessful and no message was sent", + "Direct Send process for {} TX_ID: {} and Message {} was unsuccessful and no message was sent", + message, self.id, failed[0] ); @@ -546,7 +457,7 @@ where TBackend: TransactionBackend + Clone + 'static } else { error!( target: LOG_TARGET, - "Transaction Send Direct for TxID: {} failed", self.id + "{} Send Direct for TxID: {} failed", message, self.id ); false } @@ -634,64 +545,90 @@ where TBackend: TransactionBackend + Clone + 'static } } - /// Contains all the logic to send a transaction to the recipient only directly - /// # Arguments - /// `msg`: The transaction data message to be sent - async fn send_transaction_direct_only( + async fn send_transaction_finalized_message( &mut self, - msg: SingleRoundSenderData, - ) -> Result + transaction: Transaction, + ) -> Result<(), TransactionServiceProtocolError> { - let proto_message = proto::TransactionSenderMessage::single(msg.clone().into()); - - info!( - target: LOG_TARGET, - "Attempting to resend Transaction (TxId: {}) to recipient with Node Id: {} directly only.", - self.id, - self.dest_pubkey, - ); - + let finalized_transaction_message = proto::TransactionFinalizedMessage { + tx_id: self.id, + transaction: Some(transaction.clone().into()), + }; + let mut store_and_forward_send_result = false; + let mut direct_send_result = false; match self .resources .outbound_message_service .send_direct( self.dest_pubkey.clone(), OutboundEncryption::None, - OutboundDomainMessage::new(TariMessageType::SenderPartialTransaction, proto_message.clone()), + OutboundDomainMessage::new( + TariMessageType::TransactionFinalized, + finalized_transaction_message.clone(), + ), ) .await { - Ok(result) => match result.resolve_ok().await { - Some(send_states) if send_states.len() == 1 => { - if self.wait_on_dial(send_states).await { - if let Err(e) = self.resources.db.mark_direct_send_success(self.id).await { - error!(target: LOG_TARGET, "Error updating database: {:?}", e); - } + Ok(result) => match result { + SendMessageResponse::Queued(send_states) => { + if self.wait_on_dial(send_states, "Finalized Transaction").await { + direct_send_result = true; + } else { + store_and_forward_send_result = self + .send_transaction_finalized_message_store_and_forward(finalized_transaction_message.clone()) + .await?; } }, - _ => { + SendMessageResponse::Failed => { error!( target: LOG_TARGET, - "Transaction Send Direct for TxID: {} failed", self.id + "Finalized Transaction Send Direct for TxID: {} failed", self.id ); - return Ok(false); + store_and_forward_send_result = self + .send_transaction_finalized_message_store_and_forward(finalized_transaction_message.clone()) + .await?; + }, + SendMessageResponse::PendingDiscovery(rx) => { + store_and_forward_send_result = self + .send_transaction_finalized_message_store_and_forward(finalized_transaction_message.clone()) + .await?; + // now wait for discovery to complete + match rx.await { + Ok(send_msg_response) => { + if let SendMessageResponse::Queued(send_states) = send_msg_response { + debug!("Discovery of {} completed for TxID: {}", self.dest_pubkey, self.id); + direct_send_result = self.wait_on_dial(send_states, "Finalized Transaction").await; + } + }, + Err(e) => { + error!( + target: LOG_TARGET, + "Error waiting for Discovery while sending message to TxId: {} {:?}", self.id, e + ); + }, + } }, }, Err(e) => { - error!( - target: LOG_TARGET, - "Transaction Direct Send for TxID: {} failed: {:?}", self.id, e - ); - return Ok(false); + return Err(TransactionServiceProtocolError::new( + self.id, + TransactionServiceError::from(e), + )) }, } - Ok(true) + if !direct_send_result && !store_and_forward_send_result { + return Err(TransactionServiceProtocolError::new( + self.id, + TransactionServiceError::OutboundSendFailure, + )); + } + Ok(()) } async fn send_transaction_finalized_message_store_and_forward( &mut self, msg: proto::TransactionFinalizedMessage, - ) -> Result<(), TransactionServiceProtocolError> + ) -> Result { match self .resources @@ -741,7 +678,7 @@ where TBackend: TransactionBackend + Clone + 'static }, }; - Ok(()) + Ok(true) } } diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 6be8f0a458..fa3a27d428 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -29,15 +29,10 @@ use crate::{ protocols::{ transaction_broadcast_protocol::TransactionBroadcastProtocol, transaction_chain_monitoring_protocol::TransactionChainMonitoringProtocol, - transaction_send_protocol::{TransactionProtocolStage, TransactionSendProtocol}, - }, - storage::database::{ - CompletedTransaction, - InboundTransaction, - TransactionBackend, - TransactionDatabase, - TransactionStatus, + transaction_receive_protocol::{TransactionReceiveProtocol, TransactionReceiveProtocolStage}, + transaction_send_protocol::{TransactionSendProtocol, TransactionSendProtocolStage}, }, + storage::database::{CompletedTransaction, TransactionBackend, TransactionDatabase, TransactionStatus}, }, }; use chrono::Utc; @@ -56,15 +51,8 @@ use std::{ convert::{TryFrom, TryInto}, sync::Arc, }; -use tari_comms::{ - peer_manager::{NodeId, NodeIdentity}, - types::CommsPublicKey, -}; -use tari_comms_dht::{ - domain_message::OutboundDomainMessage, - envelope::NodeDestination, - outbound::{OutboundEncryption, OutboundMessageRequester}, -}; +use tari_comms::{peer_manager::NodeIdentity, types::CommsPublicKey}; +use tari_comms_dht::outbound::OutboundMessageRequester; #[cfg(feature = "test_harness")] use tari_core::transactions::{tari_amount::uT, types::BlindingFactor}; use tari_core::{ @@ -72,18 +60,12 @@ use tari_core::{ mempool::{proto::mempool as MempoolProto, service::MempoolServiceResponse}, transactions::{ tari_amount::MicroTari, - transaction::{OutputFeatures, Transaction}, - transaction_protocol::{ - proto, - recipient::{RecipientSignedMessage, RecipientState}, - sender::TransactionSenderMessage, - }, + transaction::Transaction, + transaction_protocol::{proto, recipient::RecipientSignedMessage, sender::TransactionSenderMessage}, types::{CryptoFactories, PrivateKey}, - ReceiverTransactionProtocol, }, }; -use tari_crypto::keys::SecretKey; -use tari_p2p::{domain_message::DomainMessage, services::liveness::LivenessHandle, tari_message::TariMessageType}; +use tari_p2p::domain_message::DomainMessage; use tari_service_framework::{reply_channel, reply_channel::Receiver}; #[cfg(feature = "test_harness")] use tokio::sync::broadcast; @@ -119,9 +101,7 @@ where TBackend: TransactionBackend + Clone + 'static { config: TransactionServiceConfig, db: TransactionDatabase, - outbound_message_service: OutboundMessageRequester, output_manager_service: OutputManagerHandle, - liveness_service: LivenessHandle, transaction_stream: Option, transaction_reply_stream: Option, transaction_finalized_stream: Option, @@ -132,13 +112,14 @@ where TBackend: TransactionBackend + Clone + 'static >, event_publisher: TransactionEventSender, node_identity: Arc, - factories: CryptoFactories, base_node_public_key: Option, service_resources: TransactionServiceResources, pending_transaction_reply_senders: HashMap>, mempool_response_senders: HashMap>, base_node_response_senders: HashMap>, send_transaction_cancellation_senders: HashMap>, + finalized_transaction_senders: HashMap>, + receiver_transaction_cancellation_senders: HashMap>, } #[allow(clippy::too_many_arguments)] @@ -166,7 +147,6 @@ where base_node_response_stream: BNResponseStream, output_manager_service: OutputManagerHandle, outbound_message_service: OutboundMessageRequester, - liveness_service: LivenessHandle, event_publisher: TransactionEventSender, node_identity: Arc, factories: CryptoFactories, @@ -177,19 +157,16 @@ where let service_resources = TransactionServiceResources { db: db.clone(), output_manager_service: output_manager_service.clone(), - outbound_message_service: outbound_message_service.clone(), - liveness_service: liveness_service.clone(), + outbound_message_service, event_publisher: event_publisher.clone(), node_identity: node_identity.clone(), - factories: factories.clone(), + factories, config: config.clone(), }; TransactionService { config, db, - outbound_message_service, output_manager_service, - liveness_service, transaction_stream: Some(transaction_stream), transaction_reply_stream: Some(transaction_reply_stream), transaction_finalized_stream: Some(transaction_finalized_stream), @@ -198,13 +175,14 @@ where request_stream: Some(request_stream), event_publisher, node_identity, - factories, base_node_public_key: None, service_resources, pending_transaction_reply_senders: HashMap::new(), mempool_response_senders: HashMap::new(), base_node_response_senders: HashMap::new(), send_transaction_cancellation_senders: HashMap::new(), + finalized_transaction_senders: HashMap::new(), + receiver_transaction_cancellation_senders: HashMap::new(), } } @@ -251,6 +229,10 @@ where JoinHandle>, > = FuturesUnordered::new(); + let mut receive_transaction_protocol_handles: FuturesUnordered< + JoinHandle>, + > = FuturesUnordered::new(); + let mut transaction_broadcast_protocol_handles: FuturesUnordered< JoinHandle>, > = FuturesUnordered::new(); @@ -266,7 +248,9 @@ where request_context = request_stream.select_next_some() => { trace!(target: LOG_TARGET, "Handling Service API Request"); let (request, reply_tx) = request_context.split(); - let _ = reply_tx.send(self.handle_request(request, &mut send_transaction_protocol_handles, + let _ = reply_tx.send(self.handle_request(request, + &mut send_transaction_protocol_handles, + &mut receive_transaction_protocol_handles, &mut transaction_broadcast_protocol_handles, &mut transaction_chain_monitoring_protocol_handles).await.or_else(|resp| { error!(target: LOG_TARGET, "Error handling request: {:?}", resp); @@ -276,14 +260,13 @@ where Err(resp) }); }, - // Incoming messages from the Comms layer + // Incoming Transaction messages from the Comms layer msg = transaction_stream.select_next_some() => { let (origin_public_key, inner_msg) = msg.clone().into_origin_and_inner(); trace!(target: LOG_TARGET, "Handling Transaction Message, Trace: {}", msg.dht_header.message_tag); let result = self.accept_transaction(origin_public_key, inner_msg, - msg.dht_header.message_tag.as_value()).await; - + msg.dht_header.message_tag.as_value(), &mut receive_transaction_protocol_handles).await; match result { Err(TransactionServiceError::RepeatedMessageError) => { @@ -299,7 +282,7 @@ where _ => (), } }, - // Incoming messages from the Comms layer + // Incoming Transaction Reply messages from the Comms layer msg = transaction_reply_stream.select_next_some() => { let (origin_public_key, inner_msg) = msg.clone().into_origin_and_inner(); trace!(target: LOG_TARGET, "Handling Transaction Reply Message, Trace: {}", msg.dht_header.message_tag); @@ -308,7 +291,7 @@ where match result { Err(TransactionServiceError::TransactionDoesNotExistError) => { debug!(target: LOG_TARGET, "Unable to handle incoming Transaction Reply message from NodeId: \ - {} due to Transaction not Existing. This usually means the message was a repeated message \ + {} due to Transaction not existing. This usually means the message was a repeated message \ from Store and Forward, Trace: {}", self.node_identity.node_id().short_str(), msg.dht_header.message_tag); }, @@ -322,22 +305,28 @@ where Ok(_) => (), } }, - // Incoming messages from the Comms layer + // Incoming Finalized Transaction messages from the Comms layer msg = transaction_finalized_stream.select_next_some() => { let (origin_public_key, inner_msg) = msg.clone().into_origin_and_inner(); trace!(target: LOG_TARGET, "Handling Transaction Finalized Message, Trace: {}", msg.dht_header.message_tag.as_value()); - let result = self.accept_finalized_transaction(origin_public_key, inner_msg, - &mut transaction_broadcast_protocol_handles).await.or_else(|err| { - error!(target: LOG_TARGET, "Failed to handle incoming Transaction Finalized message: {:?} \ - for NodeID: {}, Trace: {}", err , self.node_identity.node_id().short_str(), - msg.dht_header.message_tag.as_value()); - Err(err) - }); + let result = self.accept_finalized_transaction(origin_public_key, inner_msg, ).await; - if result.is_err() { - let _ = self.event_publisher.send(Arc::new(TransactionEvent::Error("Error handling Transaction \ - Finalized message".to_string(),))); + match result { + Err(TransactionServiceError::TransactionDoesNotExistError) => { + debug!(target: LOG_TARGET, "Unable to handle incoming Finalized Transaction message from NodeId: \ + {} due to Transaction not existing. This usually means the message was a repeated message \ + from Store and Forward, Trace: {}", self.node_identity.node_id().short_str(), + msg.dht_header.message_tag); + }, + Err(e) => { + error!(target: LOG_TARGET, "Failed to handle incoming Transaction Finalized message: {:?} \ + for NodeID: {}, Trace: {}", e , self.node_identity.node_id().short_str(), + msg.dht_header.message_tag.as_value()); + let _ = self.event_publisher.send(Arc::new(TransactionEvent::Error("Error handling Transaction \ + Finalized message".to_string(),))); + }, + Ok(_) => () } }, // Incoming messages from the Comms layer @@ -369,6 +358,14 @@ where Err(e) => error!(target: LOG_TARGET, "Error resolving Send Transaction Protocol: {:?}", e), }; } + join_result = receive_transaction_protocol_handles.select_next_some() => { + trace!(target: LOG_TARGET, "Receive Transaction Protocol has ended with result {:?}", join_result); + match join_result { + Ok(join_result_inner) => self.complete_receive_transaction_protocol(join_result_inner, + &mut transaction_broadcast_protocol_handles).await, + Err(e) => error!(target: LOG_TARGET, "Error resolving Send Transaction Protocol: {:?}", e), + }; + } join_result = transaction_broadcast_protocol_handles.select_next_some() => { trace!(target: LOG_TARGET, "Transaction Broadcast protocol has ended with result {:?}", join_result); match join_result { @@ -399,6 +396,9 @@ where &mut self, request: TransactionServiceRequest, send_transaction_join_handles: &mut FuturesUnordered>>, + receive_transaction_join_handles: &mut FuturesUnordered< + JoinHandle>, + >, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>, >, @@ -458,6 +458,7 @@ where transaction_broadcast_join_handles, chain_monitoring_join_handles, send_transaction_join_handles, + receive_transaction_join_handles, ) .await .map(|_| TransactionServiceResponse::BaseNodePublicKeySet), @@ -522,6 +523,7 @@ where let (tx_reply_sender, tx_reply_receiver) = mpsc::channel(100); let (cancellation_sender, cancellation_receiver) = oneshot::channel(); self.pending_transaction_reply_senders.insert(tx_id, tx_reply_sender); + self.send_transaction_cancellation_senders .insert(tx_id, cancellation_sender); let protocol = TransactionSendProtocol::new( @@ -533,7 +535,7 @@ where amount, message, sender_protocol, - TransactionProtocolStage::Initial, + TransactionSendProtocolStage::Initial, ); let join_handle = tokio::spawn(protocol.execute()); @@ -579,10 +581,8 @@ where >, ) { - let tx_id; match join_result { Ok(id) => { - tx_id = id; let _ = self.pending_transaction_reply_senders.remove(&id); let _ = self.send_transaction_cancellation_senders.remove(&id); let _ = self @@ -602,7 +602,6 @@ where ); }, Err(TransactionServiceProtocolError { id, error }) => { - tx_id = id; let _ = self.pending_transaction_reply_senders.remove(&id); let _ = self.send_transaction_cancellation_senders.remove(&id); error!( @@ -614,40 +613,9 @@ where .send(Arc::new(TransactionEvent::Error(format!("{:?}", error)))); }, } - // Find counter-party's public key to be removed from liveness service monitoring - let node_id; - if let Ok(pub_key) = self - .db - .get_pending_transaction_counterparty_pub_key_by_tx_id(tx_id) - .await - { - match NodeId::from_key(&pub_key) { - Ok(n) => node_id = n, - Err(_) => return, - } - } else { - match self.db.get_completed_transaction(tx_id).await { - Ok(tx) => { - if &tx.source_public_key == self.node_identity.public_key() { - match NodeId::from_key(&tx.destination_public_key) { - Ok(n) => node_id = n, - Err(_) => return, - } - } else { - match NodeId::from_key(&tx.source_public_key) { - Ok(n) => node_id = n, - Err(_) => return, - } - } - }, - _ => return, - } - } - // Attempt to remove this node_id from the nodes to be monitored. Error squashed - let _ = self.liveness_service.remove_node_id(node_id).await; } - /// Cancel a pending outbound transaction + /// Cancel a pending transaction async fn cancel_transaction(&mut self, tx_id: TxId) -> Result<(), TransactionServiceError> { self.db.cancel_pending_transaction(tx_id).await.map_err(|e| { error!( @@ -664,13 +632,18 @@ where } let _ = self.pending_transaction_reply_senders.remove(&tx_id); + if let Some(cancellation_sender) = self.receiver_transaction_cancellation_senders.remove(&tx_id) { + let _ = cancellation_sender.send(()); + } + let _ = self.finalized_transaction_senders.remove(&tx_id); + let _ = self .event_publisher .send(Arc::new(TransactionEvent::TransactionCancelled(tx_id))) .map_err(|e| { trace!( target: LOG_TARGET, - "Error sending event, usually because there are no subscribers: {:?}", + "Error sending event because there are no subscribers: {:?}", e ); e @@ -681,6 +654,7 @@ where Ok(()) } + #[allow(clippy::map_entry)] async fn restart_all_send_transaction_protocols( &mut self, join_handles: &mut FuturesUnordered>>, @@ -707,7 +681,7 @@ where tx.amount, tx.message, tx.sender_protocol, - TransactionProtocolStage::WaitForReply, + TransactionSendProtocolStage::WaitForReply, ); let join_handle = tokio::spawn(protocol.execute()); @@ -727,6 +701,7 @@ where source_pubkey: CommsPublicKey, sender_message: proto::TransactionSenderMessage, traced_message_tag: u64, + join_handles: &mut FuturesUnordered>>, ) -> Result<(), TransactionServiceError> { let sender_message: TransactionSenderMessage = sender_message @@ -742,237 +717,51 @@ where source_pubkey, traced_message_tag ); - // Check this is not a repeat message i.e. tx_id doesn't already exist in our pending or completed - // transactions - if self.db.transaction_exists(data.tx_id).await? { + + if self.finalized_transaction_senders.contains_key(&data.tx_id) || + self.receiver_transaction_cancellation_senders.contains_key(&data.tx_id) + { trace!( target: LOG_TARGET, - "Transaction (TxId: {}) already present in database, Trace: {}.", + "Transaction (TxId: {}) has already been received, this is probably a repeated message, Trace: {}.", data.tx_id, traced_message_tag ); return Err(TransactionServiceError::RepeatedMessageError); } - let amount = data.amount; - - let spending_key = self - .output_manager_service - .get_recipient_spending_key(data.tx_id, data.amount) - .await?; - let nonce = PrivateKey::random(&mut OsRng); + let (tx_finalized_sender, tx_finalized_receiver) = mpsc::channel(100); + let (cancellation_sender, cancellation_receiver) = oneshot::channel(); + self.finalized_transaction_senders + .insert(data.tx_id, tx_finalized_sender); + self.receiver_transaction_cancellation_senders + .insert(data.tx_id, cancellation_sender); - let rtp = ReceiverTransactionProtocol::new( - sender_message, - nonce, - spending_key, - OutputFeatures::default(), - &self.factories, - ); - let recipient_reply = rtp.get_signed_data()?.clone(); - - let tx_id = recipient_reply.tx_id; - let proto_message: proto::RecipientSignedMessage = recipient_reply.into(); - match self - .outbound_message_service - .send_direct( - source_pubkey.clone(), - OutboundEncryption::None, - OutboundDomainMessage::new(TariMessageType::ReceiverPartialTransactionReply, proto_message.clone()), - ) - .await? - .resolve_ok() - .await - { - None => { - debug!( - target: LOG_TARGET, - "Transaction Reply (TxId: {}) Direct Send to {} not possible, attempting Store and Forward, \ - Trace: {}", - tx_id, - source_pubkey, - traced_message_tag, - ); - self.send_transaction_reply_store_and_forward( - tx_id, - source_pubkey.clone(), - proto_message.clone(), - traced_message_tag, - ) - .await?; - }, - Some(send_states) => { - if send_states.len() == 1 { - debug!( - target: LOG_TARGET, - "Transaction Reply (TxId: {}) Direct Send to {} queued with Message Tag: {}, Trace: {}", - tx_id, - source_pubkey, - send_states[0].tag, - traced_message_tag, - ); - match send_states.wait_single().await { - true => { - info!( - target: LOG_TARGET, - "Direct Send of Transaction Reply message for TX_ID: {} was successful, Trace: {}", - tx_id, - traced_message_tag - ); - }, - false => { - error!( - target: LOG_TARGET, - "Direct Send of Transaction Reply message for TX_ID: {} was unsuccessful and no \ - message was sent, attempting Store and Forward, Trace: {}", - tx_id, - traced_message_tag - ); - self.send_transaction_reply_store_and_forward( - tx_id, - source_pubkey.clone(), - proto_message.clone(), - traced_message_tag, - ) - .await? - }, - } - } else { - error!( - target: LOG_TARGET, - "Transaction Reply message Direct Send for TxID: {} failed, attempting Store and Forward, \ - Trace: {}", - tx_id, - traced_message_tag - ); - self.send_transaction_reply_store_and_forward( - tx_id, - source_pubkey.clone(), - proto_message.clone(), - traced_message_tag, - ) - .await? - } - }, - } - - // Otherwise add it to our pending transaction list and return reply - let inbound_transaction = InboundTransaction::new( - tx_id, - source_pubkey.clone(), - amount, - rtp.clone(), - TransactionStatus::Pending, - data.message.clone(), - Utc::now().naive_utc(), - ); - self.db - .add_pending_inbound_transaction(tx_id, inbound_transaction.clone()) - .await?; - - info!( - target: LOG_TARGET, - "Transaction with TX_ID = {} received from {}, Trace: {}. Reply Sent", - tx_id, + let protocol = TransactionReceiveProtocol::new( + data.tx_id, source_pubkey, - traced_message_tag - ); - info!( - target: LOG_TARGET, - "Transaction (TX_ID: {}) - Amount: {} - Message: {}, Trace: {}", - tx_id, - amount, - data.message, - traced_message_tag + sender_message, + TransactionReceiveProtocolStage::Initial, + self.service_resources.clone(), + tx_finalized_receiver, + cancellation_receiver, ); - let _ = self - .event_publisher - .send(Arc::new(TransactionEvent::ReceivedTransaction(tx_id))) - .map_err(|e| { - trace!( - target: LOG_TARGET, - "Error sending event, usually because there are no subscribers: {:?}", - e - ); - e - }); + let join_handle = tokio::spawn(protocol.execute()); + join_handles.push(join_handle); + Ok(()) + } else { + Err(TransactionServiceError::InvalidStateError) } - Ok(()) } - async fn send_transaction_reply_store_and_forward( - &mut self, - tx_id: TxId, - source_pubkey: CommsPublicKey, - msg: proto::RecipientSignedMessage, - traced_message_tag: u64, - ) -> Result<(), TransactionServiceError> - { - match self - .outbound_message_service - .broadcast( - NodeDestination::NodeId(Box::new(NodeId::from_key(&source_pubkey)?)), - OutboundEncryption::EncryptFor(Box::new(source_pubkey.clone())), - vec![], - OutboundDomainMessage::new(TariMessageType::ReceiverPartialTransactionReply, msg), - ) - .await - { - Ok(result) => match result.resolve_ok().await { - None => { - error!( - target: LOG_TARGET, - "Sending Transaction Reply (TxId: {}) to neighbours for Store and Forward failed, Trace: {}", - tx_id, - traced_message_tag - ); - }, - Some(tags) if !tags.is_empty() => { - info!( - target: LOG_TARGET, - "Sending Transaction Reply (TxId: {}) to Neighbours for Store and Forward successful with \ - Message Tags: {:?}, Trace: {}", - tx_id, - tags, - traced_message_tag, - ); - }, - Some(_) => { - error!( - target: LOG_TARGET, - "Sending Transaction Reply to Neighbours for Store and Forward for TX_ID: {} was unsuccessful \ - and no messages were sent, Trace: {}", - tx_id, - traced_message_tag - ); - }, - }, - Err(e) => { - error!( - target: LOG_TARGET, - "Sending Transaction Reply (TxId: {}) to neighbours for Store and Forward failed: {:?}, Trace: {}", - tx_id, - e, - traced_message_tag - ); - }, - }; - - Ok(()) - } - - /// Accept a new transaction from a sender by handling a public SenderMessage. The reply is generated and sent. + /// Accept the public reply from a recipient and apply the reply to the relevant transaction protocol /// # Arguments - /// 'source_pubkey' - The pubkey from which the message was sent and to which the reply will be sent. - /// 'sender_message' - Message from a sender containing the setup of the transaction being sent to you + /// 'recipient_reply' - The public response from a recipient with data required to complete the transaction pub async fn accept_finalized_transaction( &mut self, source_pubkey: CommsPublicKey, finalized_transaction: proto::TransactionFinalizedMessage, - transaction_broadcast_join_handles: &mut FuturesUnordered< - JoinHandle>, - >, ) -> Result<(), TransactionServiceError> { let tx_id = finalized_transaction.tx_id; @@ -990,94 +779,95 @@ where ) })?; - let inbound_tx = match self.db.get_pending_inbound_transaction(tx_id).await { - Ok(tx) => tx, - Err(_e) => { - warn!( - target: LOG_TARGET, - "TxId for received Finalized Transaction does not exist in Pending Inbound Transactions, could be \ - a repeat Store and Forward message" - ); - return Ok(()); - }, - }; - - info!( - target: LOG_TARGET, - "Finalized Transaction with TX_ID = {} received from {}", - tx_id, - source_pubkey.clone() - ); - - if inbound_tx.source_public_key != source_pubkey { - error!( - target: LOG_TARGET, - "Finalized transaction Source Public Key does not correspond to stored value" - ); - return Err(TransactionServiceError::InvalidSourcePublicKey); - } - - let rtp_output = match inbound_tx.receiver_protocol.state { - RecipientState::Finalized(s) => s.output.clone(), - RecipientState::Failed(_) => return Err(TransactionServiceError::InvalidStateError), + let sender = match self.finalized_transaction_senders.get_mut(&tx_id) { + None => return Err(TransactionServiceError::TransactionDoesNotExistError), + Some(s) => s, }; - let finalized_outputs = transaction.body.outputs(); - - if finalized_outputs.iter().find(|o| o == &&rtp_output).is_none() { - error!( - target: LOG_TARGET, - "Finalized transaction not contain the Receiver's output" - ); - return Err(TransactionServiceError::ReceiverOutputNotFound); - } + sender + .send((source_pubkey, tx_id, transaction)) + .await + .map_err(|_| TransactionServiceError::ProtocolChannelError)?; - let completed_transaction = CompletedTransaction::new( - tx_id, - source_pubkey.clone(), - self.node_identity.public_key().clone(), - inbound_tx.amount, - transaction.body.get_total_fee(), - transaction.clone(), - TransactionStatus::Completed, - inbound_tx.message.clone(), - inbound_tx.timestamp, - ); + Ok(()) + } - self.db - .complete_inbound_transaction(tx_id, completed_transaction.clone()) - .await?; + /// Handle the final clean up after a Send Transaction protocol completes + async fn complete_receive_transaction_protocol( + &mut self, + join_result: Result, + transaction_broadcast_join_handles: &mut FuturesUnordered< + JoinHandle>, + >, + ) + { + match join_result { + Ok(id) => { + let _ = self.finalized_transaction_senders.remove(&id); + let _ = self.receiver_transaction_cancellation_senders.remove(&id); - info!( - target: LOG_TARGET, - "Inbound Transaction with TX_ID = {} from {} moved to Completed Transactions", - tx_id, - source_pubkey.clone() - ); + let _ = self + .broadcast_completed_transaction_to_mempool(id, transaction_broadcast_join_handles) + .await + .map_err(|e| { + warn!( + target: LOG_TARGET, + "Error broadcasting completed transaction TxId: {} to mempool: {:?}", id, e + ); + e + }); - // Logging this error here instead of propogating it up to the select! catchall which generates the Error Event. - let _ = self - .broadcast_completed_transaction_to_mempool(tx_id, transaction_broadcast_join_handles) - .await - .map_err(|e| { + trace!( + target: LOG_TARGET, + "Receive Transaction Protocol for TxId: {} completed successfully", + id + ); + }, + Err(TransactionServiceProtocolError { id, error }) => { + let _ = self.finalized_transaction_senders.remove(&id); + let _ = self.receiver_transaction_cancellation_senders.remove(&id); error!( target: LOG_TARGET, - "Error broadcasting completed transaction to mempool: {:?}", e + "Error completing Receive Transaction Protocol (Id: {}): {:?}", id, error ); - e - }); + let _ = self + .event_publisher + .send(Arc::new(TransactionEvent::Error(format!("{:?}", error)))); + }, + } + } - let _ = self - .event_publisher - .send(Arc::new(TransactionEvent::ReceivedFinalizedTransaction(tx_id))) - .map_err(|e| { - trace!( + async fn restart_all_receive_transaction_protocols( + &mut self, + join_handles: &mut FuturesUnordered>>, + ) -> Result<(), TransactionServiceError> + { + let inbound_txs = self.db.get_pending_inbound_transactions().await?; + for (tx_id, tx) in inbound_txs { + if !self.pending_transaction_reply_senders.contains_key(&tx_id) { + debug!( target: LOG_TARGET, - "Error sending event, usually because there are no subscribers: {:?}", - e + "Restarting listening for Transaction Finalize for Pending Inbound Transaction TxId: {}", tx_id ); - e - }); + let (tx_finalized_sender, tx_finalized_receiver) = mpsc::channel(100); + let (cancellation_sender, cancellation_receiver) = oneshot::channel(); + self.finalized_transaction_senders.insert(tx_id, tx_finalized_sender); + self.receiver_transaction_cancellation_senders + .insert(tx_id, cancellation_sender); + let protocol = TransactionReceiveProtocol::new( + tx_id, + tx.source_public_key, + TransactionSenderMessage::None, + TransactionReceiveProtocolStage::WaitForFinalize, + self.service_resources.clone(), + tx_finalized_receiver, + cancellation_receiver, + ); + + let join_handle = tokio::spawn(protocol.execute()); + join_handles.push(join_handle); + } + } Ok(()) } @@ -1091,6 +881,9 @@ where broadcast_join_handles: &mut FuturesUnordered>>, chain_monitoring_join_handles: &mut FuturesUnordered>>, send_transaction_join_handles: &mut FuturesUnordered>>, + receive_transaction_join_handles: &mut FuturesUnordered< + JoinHandle>, + >, ) -> Result<(), TransactionServiceError> { let startup_broadcast = self.base_node_public_key.is_none(); @@ -1130,6 +923,17 @@ where ); Err(resp) }); + + let _ = self + .restart_all_receive_transaction_protocols(receive_transaction_join_handles) + .await + .or_else(|resp| { + error!( + target: LOG_TARGET, + "Error restarting protocols for all pending inbound transactions: {:?}", resp + ); + Err(resp) + }); } Ok(()) } @@ -1502,6 +1306,8 @@ where /// the outputs #[cfg(feature = "test_harness")] pub async fn mine_transaction(&mut self, tx_id: TxId) -> Result<(), TransactionServiceError> { + use tari_core::transactions::transaction::OutputFeatures; + let completed_txs = self.db.get_completed_transactions().await?; let _found_tx = completed_txs.get(&tx_id).ok_or_else(|| { TransactionServiceError::TestHarnessError("Could not find Completed TX to mine.".to_string()) @@ -1519,8 +1325,10 @@ where .outputs_to_be_spent .iter() .map(|o| { - o.unblinded_output - .as_transaction_input(&self.factories.commitment, OutputFeatures::default()) + o.unblinded_output.as_transaction_input( + &self.service_resources.factories.commitment, + OutputFeatures::default(), + ) }) .collect(), pending_tx @@ -1528,7 +1336,7 @@ where .iter() .map(|o| { o.unblinded_output - .as_transaction_output(&self.factories) + .as_transaction_output(&self.service_resources.factories) .expect("Failed to convert to Transaction Output") }) .collect(), @@ -1568,14 +1376,16 @@ where service::OutputManagerService, storage::{database::OutputManagerDatabase, memory_db::OutputManagerMemoryDatabase}, }, - transaction_service::handle::TransactionServiceHandle, + transaction_service::{handle::TransactionServiceHandle, storage::database::InboundTransaction}, }; use futures::stream; use tari_broadcast_channel::bounded; + use tari_core::transactions::{transaction::OutputFeatures, ReceiverTransactionProtocol}; + use tari_crypto::keys::SecretKey; let (_sender, receiver) = reply_channel::unbounded(); let (tx, _rx) = mpsc::channel(20); - let (oms_event_publisher, _oms_event_subscriber) = bounded(100); + let (oms_event_publisher, _oms_event_subscriber) = bounded(100, 118); let (ts_request_sender, _ts_request_receiver) = reply_channel::unbounded(); let (event_publisher, _) = broadcast::channel(100); let ts_handle = TransactionServiceHandle::new(ts_request_sender, event_publisher.clone()); @@ -1588,12 +1398,12 @@ where stream::empty(), OutputManagerDatabase::new(OutputManagerMemoryDatabase::new()), oms_event_publisher, - self.factories.clone(), + self.service_resources.factories.clone(), ) .await?; use crate::testnet_utils::make_input; - let (_ti, uo) = make_input(&mut OsRng, amount + 1000 * uT, &self.factories); + let (_ti, uo) = make_input(&mut OsRng, amount + 1000 * uT, &self.service_resources.factories); fake_oms.add_output(uo).await?; @@ -1617,7 +1427,7 @@ where nonce, spending_key.clone(), OutputFeatures::default(), - &self.factories, + &self.service_resources.factories, ); let inbound_transaction = InboundTransaction::new( @@ -1697,7 +1507,6 @@ where TBackend: TransactionBackend + Clone + 'static pub db: TransactionDatabase, pub output_manager_service: OutputManagerHandle, pub outbound_message_service: OutboundMessageRequester, - pub liveness_service: LivenessHandle, pub event_publisher: TransactionEventSender, pub node_identity: Arc, pub factories: CryptoFactories, diff --git a/base_layer/wallet/src/transaction_service/storage/database.rs b/base_layer/wallet/src/transaction_service/storage/database.rs index 3bcf56fafb..4e5ada2945 100644 --- a/base_layer/wallet/src/transaction_service/storage/database.rs +++ b/base_layer/wallet/src/transaction_service/storage/database.rs @@ -478,9 +478,10 @@ where T: TransactionBackend + 'static ) -> Result { let db_clone = self.db.clone(); - let key = match cancelled { - true => DbKey::CancelledPendingOutboundTransaction(tx_id), - false => DbKey::PendingOutboundTransaction(tx_id), + let key = if cancelled { + DbKey::CancelledPendingOutboundTransaction(tx_id) + } else { + DbKey::PendingOutboundTransaction(tx_id) }; let t = tokio::task::spawn_blocking(move || match db_clone.fetch(&key) { Ok(None) => Err(TransactionStorageError::ValueNotFound(key)), @@ -516,9 +517,10 @@ where T: TransactionBackend + 'static ) -> Result { let db_clone = self.db.clone(); - let key = match cancelled { - true => DbKey::CancelledPendingInboundTransaction(tx_id), - false => DbKey::PendingInboundTransaction(tx_id), + let key = if cancelled { + DbKey::CancelledPendingInboundTransaction(tx_id) + } else { + DbKey::PendingInboundTransaction(tx_id) }; let t = tokio::task::spawn_blocking(move || match db_clone.fetch(&key) { Ok(None) => Err(TransactionStorageError::ValueNotFound(key)), @@ -554,9 +556,10 @@ where T: TransactionBackend + 'static ) -> Result { let db_clone = self.db.clone(); - let key = match cancelled { - true => DbKey::CancelledCompletedTransaction(tx_id), - false => DbKey::CompletedTransaction(tx_id), + let key = if cancelled { + DbKey::CancelledCompletedTransaction(tx_id) + } else { + DbKey::CompletedTransaction(tx_id) }; let t = tokio::task::spawn_blocking(move || match db_clone.fetch(&key) { Ok(None) => Err(TransactionStorageError::ValueNotFound(key)), @@ -588,9 +591,10 @@ where T: TransactionBackend + 'static { let db_clone = self.db.clone(); - let key = match cancelled { - true => DbKey::CancelledPendingInboundTransactions, - false => DbKey::PendingInboundTransactions, + let key = if cancelled { + DbKey::CancelledPendingInboundTransactions + } else { + DbKey::PendingInboundTransactions }; let t = tokio::task::spawn_blocking(move || match db_clone.fetch(&key) { @@ -628,9 +632,10 @@ where T: TransactionBackend + 'static { let db_clone = self.db.clone(); - let key = match cancelled { - true => DbKey::CancelledPendingOutboundTransactions, - false => DbKey::PendingOutboundTransactions, + let key = if cancelled { + DbKey::CancelledPendingOutboundTransactions + } else { + DbKey::PendingOutboundTransactions }; let t = tokio::task::spawn_blocking(move || match db_clone.fetch(&key) { @@ -681,9 +686,10 @@ where T: TransactionBackend + 'static { let db_clone = self.db.clone(); - let key = match cancelled { - true => DbKey::CancelledCompletedTransactions, - false => DbKey::CompletedTransactions, + let key = if cancelled { + DbKey::CancelledCompletedTransactions + } else { + DbKey::CompletedTransactions }; let t = tokio::task::spawn_blocking(move || match db_clone.fetch(&key) { diff --git a/base_layer/wallet/src/transaction_service/storage/memory_db.rs b/base_layer/wallet/src/transaction_service/storage/memory_db.rs index d2bdbe6e91..d9a4df908c 100644 --- a/base_layer/wallet/src/transaction_service/storage/memory_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/memory_db.rs @@ -74,7 +74,7 @@ impl TransactionMemoryDatabase { } } } - +#[allow(clippy::cognitive_complexity)] impl TransactionBackend for TransactionMemoryDatabase { fn fetch(&self, key: &DbKey) -> Result, TransactionStorageError> { let db = acquire_read_lock!(self.db); diff --git a/base_layer/wallet/src/util/emoji.rs b/base_layer/wallet/src/util/emoji.rs index 6fcb1fa3a3..bd843ad416 100644 --- a/base_layer/wallet/src/util/emoji.rs +++ b/base_layer/wallet/src/util/emoji.rs @@ -30,20 +30,20 @@ use tari_crypto::tari_utilities::{ }; const EMOJI: [char; 256] = [ - '😀', '😂', '🤣', '😉', '😊', '😎', '😍', '😘', '🤗', '🤩', '🤔', '🙄', '😮', '🤐', '😴', '😛', '🤤', '🙃', '🤑', - '😤', '😨', '🤯', '😬', '😱', '🤪', '😵', '😷', '🤢', '🤮', '🤠', '🤡', '🤫', '🤭', '🤓', '😈', '👻', '👽', '🤖', - '💩', '😺', '👶', '👩', '👨', '👮', '🤴', '👸', '🧜', '🙅', '🙋', '🤦', '🤷', '💇', '🏃', '💃', '🧗', '🛀', '🛌', - '👤', '🏄', '🚴', '🤹', '💏', '👪', '💪', '👈', '👍', '✋', '👊', '👐', '🙏', '🤝', '💅', '👂', '👀', '🧠', '👄', - '💔', '💖', '💙', '💌', '💤', '💣', '💥', '💦', '💨', '💫', '👔', '👕', '👖', '🧣', '🧤', '🧦', '👗', '👙', '👜', - '🎒', '👑', '🧢', '💍', '💎', '🐒', '🐶', '🦁', '🐴', '🦄', '🐮', '🐷', '🐑', '🐫', '🦒', '🐘', '🐭', '🐇', '🐔', - '🦆', '🐸', '🐍', '🐳', '🐚', '🦀', '🐌', '🦋', '🌸', '🌲', '🌵', '🍇', '🍉', '🍌', '🍎', '🍒', '🍓', '🥑', '🥕', - '🌽', '🍄', '🥜', '🍞', '🧀', '🍖', '🍔', '🍟', '🍕', '🍿', '🍦', '🍪', '🍰', '🍫', '🍬', '🍷', '🍺', '🍴', '🌍', - '🌋', '🏠', '⛺', '🎡', '🎢', '🎨', '🚂', '🚌', '🚑', '🚒', '🚔', '🚕', '🚜', '🚲', '⛽', '🚦', '🚧', '⛵', '🚢', - '🛫', '💺', '🚁', '🚀', '🛸', '🚪', '🚽', '🚿', '⌛', '⏰', '🕙', '🌛', '🌞', '⛅', '🌀', '🌈', '🌂', '🔥', '✨', - '🎈', '🎉', '🎀', '🎁', '🏆', '🏅', '⚽', '🏀', '🏈', '🎾', '🥊', '🎯', '⛳', '🎣', '🎮', '🎲', '🔈', '🔔', '🎶', - '🎤', '🎧', '📻', '🎸', '🎹', '🎺', '🎻', '🥁', '📱', '🔋', '💻', '📷', '🔍', '🔭', '📡', '💡', '🔦', '📖', '📚', - '📝', '📅', '📌', '📎', '🔒', '🔑', '🔨', '🏹', '🔧', '💉', '💊', '🏧', '⛔', '🚫', '✅', '❌', '❓', '❕', '💯', - '🆗', '🆘', '⬛', '🔶', '🔵', '🏁', '🚩', '🎌', '🏴', + '🌀', '🌂', '🌈', '🌊', '🌋', '🌍', '🌙', '🌝', '🌞', '🌟', '🌠', '🌰', '🌴', '🌵', '🌷', '🌸', '🌹', '🌻', '🌽', + '🍀', '🍁', '🍄', '🍅', '🍆', '🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🍎', '🍐', '🍑', '🍒', '🍓', '🍔', '🍕', + '🍗', '🍚', '🍞', '🍟', '🍠', '🍣', '🍦', '🍩', '🍪', '🍫', '🍬', '🍭', '🍯', '🍰', '🍳', '🍴', '🍵', '🍶', '🍷', + '🍸', '🍹', '🍺', '🍼', '🎀', '🎁', '🎂', '🎃', '🎄', '🎈', '🎉', '🎒', '🎓', '🎠', '🎡', '🎢', '🎣', '🎤', '🎥', + '🎧', '🎨', '🎩', '🎪', '🎬', '🎭', '🎮', '🎰', '🎱', '🎲', '🎳', '🎵', '🎷', '🎸', '🎹', '🎺', '🎻', '🎼', '🎽', + '🎾', '🎿', '🏀', '🏁', '🏂', '🏆', '🏈', '🏉', '🏠', '🏥', '🏦', '🏭', '🏰', '🐀', '🐉', '🐊', '🐌', '🐍', '🐎', + '🐐', '🐑', '🐓', '🐖', '🐗', '🐘', '🐙', '🐚', '🐛', '🐜', '🐝', '🐞', '🐢', '🐣', '🐨', '🐩', '🐪', '🐬', '🐭', + '🐮', '🐯', '🐰', '🐲', '🐳', '🐴', '🐵', '🐶', '🐷', '🐸', '🐺', '🐻', '🐼', '🐽', '🐾', '👀', '👅', '👑', '👒', + '👓', '👔', '👕', '👖', '👗', '👘', '👙', '👚', '👛', '👞', '👟', '👠', '👡', '👢', '👣', '👹', '👻', '👽', '👾', + '👿', '💀', '💄', '💈', '💉', '💊', '💋', '💌', '💍', '💎', '💐', '💔', '💕', '💘', '💡', '💣', '💤', '💦', '💨', + '💩', '💪', '💭', '💯', '💰', '💳', '💸', '💺', '💻', '💼', '📈', '📉', '📌', '📎', '📚', '📝', '📡', '📱', '📷', + '🔋', '🔌', '🔎', '🔑', '🔔', '🔥', '🔦', '🔧', '🔨', '🔩', '🔪', '🔫', '🔬', '🔭', '🔮', '🔱', '🗽', '😂', '😇', + '😈', '😉', '😍', '😎', '😷', '😹', '😻', '😿', '🙌', '🚀', '🚁', '🚂', '🚌', '🚑', '🚒', '🚓', '🚕', '🚗', '🚜', + '🚢', '🚦', '🚧', '🚨', '🚪', '🚲', '🚽', '🚿', '🛁', ]; lazy_static! { @@ -70,9 +70,9 @@ lazy_static! { /// ``` /// use tari_wallet::util::emoji::EmojiId; /// -/// assert!(EmojiId::is_valid("🐇💃😴🤩⚽🐍🍎🍫🤩🍓💔🐘🦄🍞🐇🌲🍇🎶🏠🧣🚢😈🐸👊🕙🤤💎🍓⛅👔🆗🏄👐")); +/// assert!(EmojiId::is_valid("🐍🍴🌷🌟💸🐓🐨🐽🌟🐪🎧🐊🏥🐲🐍🐜🐞📷👔🎸👾🍒🐑🎉💐🌹🏂🐪💘🎳🚢🍹🎒")); /// let eid = EmojiId::from_hex("70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a").unwrap(); -/// assert_eq!(eid.as_str(), "🐇💃😴🤩⚽🐍🍎🍫🤩🍓💔🐘🦄🍞🐇🌲🍇🎶🏠🧣🚢😈🐸👊🕙🤤💎🍓⛅👔🆗🏄👐"); +/// assert_eq!(eid.as_str(), "🐍🍴🌷🌟💸🐓🐨🐽🌟🐪🎧🐊🏥🐲🐍🐜🐞📷👔🎸👾🍒🐑🎉💐🌹🏂🐪💘🎳🚢🍹🎒"); /// ``` #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct EmojiId(String); @@ -170,7 +170,7 @@ mod test { let eid = EmojiId::from_hex("70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a").unwrap(); assert_eq!( eid.as_str(), - "🐇💃😴🤩⚽🐍🍎🍫🤩🍓💔🐘🦄🍞🐇🌲🍇🎶🏠🧣🚢😈🐸👊🕙🤤💎🍓⛅👔🆗🏄👐" + "🐍🍴🌷🌟💸🐓🐨🐽🌟🐪🎧🐊🏥🐲🐍🐜🐞📷👔🎸👾🍒🐑🎉💐🌹🏂🐪💘🎳🚢🍹🎒" ); assert_eq!(EmojiId::from_pubkey(&pubkey), eid); assert_eq!( @@ -178,7 +178,7 @@ mod test { "70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a" ); assert_eq!( - EmojiId::str_to_pubkey("🐇💃😴🤩⚽🐍🍎🍫🤩🍓💔🐘🦄🍞🐇🌲🍇🎶🏠🧣🚢😈🐸👊🕙🤤💎🍓⛅👔🆗🏄👐").unwrap(), + EmojiId::str_to_pubkey("🐍🍴🌷🌟💸🐓🐨🐽🌟🐪🎧🐊🏥🐲🐍🐜🐞📷👔🎸👾🍒🐑🎉💐🌹🏂🐪💘🎳🚢🍹🎒").unwrap(), pubkey ); } @@ -191,7 +191,7 @@ mod test { assert_eq!(EmojiId::is_valid(""), false, "Emoji ID too short"); assert_eq!(EmojiId::is_valid("😂"), false, "Emoji ID too short"); assert_eq!( - EmojiId::is_valid("🤩⚽🐍🍎🍫🤩🍓💔🐘🦄🍞🐇🌲🍇🎶🏠🧣🚢😈🐸👊🕙🤤💎🍓⛅👔🆗🏄👐"), + EmojiId::is_valid("🐍🍴🌷🌟💸🐓🐨🐽🌟🐪🎧🐊🏥🐲🐍🐜🐞📷👔🎸👾🍒🐑🎉💐🌹"), false, "Emoji ID too short" ); @@ -201,12 +201,12 @@ mod test { "Not emoji string" ); assert_eq!( - EmojiId::is_valid("🐇💃😴🤩⚽🐍🍎🍫🤩🍓💔🐘🦄🍞🐇🌲🍇🎶🏠🧣🚢😈🐸👊🕙🤤💎🍓⛅👔🆗🏄"), + EmojiId::is_valid("🐍🍴🌷🌟💸🐓🐨🐽🌟🐪🎧🐊🏥🐲🐍🐜🐞📷👔🎸👾🍒🐑🎉💐🌹🏂🐪💘🎳🚢🍹"), false, "No checksum" ); assert_eq!( - EmojiId::is_valid("🐇💃😴🤩⚽🐍🍎🍫🤩🍓💔🐘🦄🍞🐇🌲🍇🎶🏠🧣🚢😈🐸👊🕙🤤💎🍓⛅👔🆗🏄🎣"), + EmojiId::is_valid("🐍🍴🌷🌟💸🐓🐨🐽🌟🐪🎧🐊🏥🐲🐍🐜🐞📷👔🎸👾🍒🐑🎉💐🌹🏂🐪💘🎳🚢🍹💣"), false, "Wrong checksum" ); diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 8fae2abe81..e311bfce43 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -131,6 +131,7 @@ where let (publisher, subscription_factory) = pubsub_connector( runtime.handle().clone(), config.comms_config.max_concurrent_inbound_tasks, + 4, ); let subscription_factory = Arc::new(subscription_factory); diff --git a/base_layer/wallet/tests/output_manager_service/service.rs b/base_layer/wallet/tests/output_manager_service/service.rs index b5a5e6cc52..acfdacf353 100644 --- a/base_layer/wallet/tests/output_manager_service/service.rs +++ b/base_layer/wallet/tests/output_manager_service/service.rs @@ -98,7 +98,7 @@ pub fn setup_output_manager_service( let (outbound_message_requester, mock_outbound_service) = create_outbound_service_mock(20); let (oms_request_sender, oms_request_receiver) = reply_channel::unbounded(); let (base_node_response_sender, base_node_response_receiver) = mpsc::channel(20); - let (oms_event_publisher, oms_event_subscriber) = bounded(100); + let (oms_event_publisher, oms_event_subscriber) = bounded(100, 115); let (ts_request_sender, _ts_request_receiver) = reply_channel::unbounded(); let (event_publisher, _) = channel(100); diff --git a/base_layer/wallet/tests/text_message_service/mod.rs b/base_layer/wallet/tests/text_message_service/mod.rs index 8e9cb8baba..3d28f16c18 100644 --- a/base_layer/wallet/tests/text_message_service/mod.rs +++ b/base_layer/wallet/tests/text_message_service/mod.rs @@ -46,7 +46,7 @@ pub fn setup_text_message_service( database_path: String, ) -> (TextMessageHandle, CommsNode, Dht) { - let (publisher, subscription_factory) = pubsub_connector(runtime.executor(), 100); + let (publisher, subscription_factory) = pubsub_connector(runtime.executor(), 100, 102); let subscription_factory = Arc::new(subscription_factory); let (comms, dht) = setup_comms_services(runtime.executor(), Arc::new(node_identity.clone()), peers, publisher); diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index c5d95ffda5..0bf2276cd8 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -36,6 +36,7 @@ use prost::Message; use rand::rngs::OsRng; use std::{ convert::{TryFrom, TryInto}, + path::Path, sync::Arc, time::Duration, }; @@ -62,6 +63,7 @@ use tari_core::{ TxStorageResponse, }, transactions::{ + fee::Fee, proto::types::TransactionOutput as TransactionOutputProto, tari_amount::*, transaction::{KernelBuilder, KernelFeatures, OutputFeatures, Transaction, TransactionOutput, UnblindedOutput}, @@ -73,6 +75,7 @@ use tari_core::{ }; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, + common::Blake256, keys::{PublicKey as PK, SecretKey as SK}, }; use tari_p2p::{ @@ -83,18 +86,14 @@ use tari_p2p::{ liveness::{ mock::{create_p2p_liveness_mock, LivenessMockState}, LivenessConfig, - LivenessEvent, LivenessEventSender, LivenessHandle, LivenessInitializer, - LivenessRequest, - Metadata, - PingPongEvent, }, }, }; use tari_service_framework::{reply_channel, StackBuilder}; -use tari_test_utils::{collect_stream, paths::with_temp_dir}; +use tari_test_utils::paths::with_temp_dir; use tari_wallet::{ output_manager_service::{ config::OutputManagerServiceConfig, @@ -112,6 +111,8 @@ use tari_wallet::{ database::{ CompletedTransaction, DbKeyValuePair, + InboundTransaction, + OutboundTransaction, TransactionBackend, TransactionDatabase, TransactionStatus, @@ -141,23 +142,23 @@ fn create_runtime() -> Runtime { .unwrap() } -pub fn setup_transaction_service( +pub fn setup_transaction_service>( runtime: &mut Runtime, node_identity: Arc, peers: Vec>, factories: CryptoFactories, backend: T, - database_path: String, + database_path: P, discovery_request_timeout: Duration, ) -> (TransactionServiceHandle, OutputManagerHandle, CommsNode) { - let (publisher, subscription_factory) = pubsub_connector(runtime.handle().clone(), 100); + let (publisher, subscription_factory) = pubsub_connector(runtime.handle().clone(), 100, 103); let subscription_factory = Arc::new(subscription_factory); let (comms, dht) = runtime.block_on(setup_comms_services( node_identity, peers, publisher, - database_path, + database_path.as_ref().to_str().unwrap().to_owned(), discovery_request_timeout, )); @@ -224,7 +225,7 @@ pub fn setup_transaction_service_no_comms(al let factories = CryptoFactories::default(); let ( - alice_ts, + mut alice_ts, _alice_output_manager, alice_outbound_service, mut alice_tx_sender, @@ -940,15 +940,25 @@ fn finalize_tx_with_incorrect_pubkey(al ))) .unwrap(); + runtime.block_on(async { + let mut delay = delay_for(Duration::from_secs(15)).fuse(); + loop { + futures::select! { + event = alice_event_stream.select_next_some() => { + if let TransactionEvent::ReceivedFinalizedTransaction(_) = (*event.unwrap()).clone() { + assert!(false, "Should not have received finalized event!"); + } + }, + () = delay => { + break; + }, + } + } + }); + assert!(runtime - .block_on(async { collect_stream!(alice_event_stream, take = 2, timeout = Duration::from_secs(10)) }) - .iter() - .find(|i| if let TransactionEvent::Error(s) = &**(**i).as_ref().unwrap() { - s == &"Error handling Transaction Finalized message".to_string() - } else { - false - }) - .is_some()); + .block_on(alice_ts.get_completed_transaction(recipient_reply.tx_id)) + .is_err()); } #[test] @@ -978,7 +988,7 @@ fn finalize_tx_with_missing_output(alic let factories = CryptoFactories::default(); let ( - alice_ts, + mut alice_ts, _alice_output_manager, alice_outbound_service, mut alice_tx_sender, @@ -1046,15 +1056,25 @@ fn finalize_tx_with_missing_output(alic ))) .unwrap(); + runtime.block_on(async { + let mut delay = delay_for(Duration::from_secs(15)).fuse(); + loop { + futures::select! { + event = alice_event_stream.select_next_some() => { + if let TransactionEvent::ReceivedFinalizedTransaction(_) = (*event.unwrap()).clone() { + assert!(false, "Should not have received finalized event"); + } + }, + () = delay => { + break; + }, + } + } + }); + assert!(runtime - .block_on(async { collect_stream!(alice_event_stream, take = 2, timeout = Duration::from_secs(10)) }) - .iter() - .find(|i| if let TransactionEvent::Error(s) = &**(**i).as_ref().unwrap() { - s == &"Error handling Transaction Finalized message".to_string() - } else { - false - }) - .is_some()); + .block_on(alice_ts.get_completed_transaction(recipient_reply.tx_id)) + .is_err()); } #[test] @@ -1082,7 +1102,7 @@ fn finalize_tx_with_missing_output_sqlite_db() { #[test] fn discovery_async_return_test() { let db_tempdir = TempDir::new(random_string(8).as_str()).unwrap(); - let db_folder = db_tempdir.path().to_str().unwrap().to_string(); + let db_folder = db_tempdir.path(); let mut runtime = runtime::Builder::new() .basic_scheduler() @@ -1092,10 +1112,6 @@ fn discovery_async_return_test() { .unwrap(); let factories = CryptoFactories::default(); - let alice_backend = TransactionMemoryDatabase::new(); - let bob_backend = TransactionMemoryDatabase::new(); - let dave_backend = TransactionMemoryDatabase::new(); - // Alice's parameters let alice_node_identity = Arc::new( NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(), @@ -1111,55 +1127,33 @@ fn discovery_async_return_test() { NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(), ); - // Dave's parameters - let dave_node_identity = Arc::new( - NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(), - ); - log::info!( - "discovery_async_return_test: Alice: '{}', Bob: '{}', Carol: '{}', Dave: '{}'", + "discovery_async_return_test: Alice: '{}', Bob: '{}', Carol: '{}'", alice_node_identity.node_id().short_str(), bob_node_identity.node_id().short_str(), carol_node_identity.node_id().short_str(), - dave_node_identity.node_id().short_str() - ); - - let (mut alice_ts, mut alice_oms, alice_comms) = setup_transaction_service( - &mut runtime, - alice_node_identity.clone(), - vec![bob_node_identity.clone()], - factories.clone(), - alice_backend, - db_folder.clone(), - Duration::from_secs(20), ); - let mut alice_event_stream = alice_ts.get_event_stream_fused(); - let (_bob_ts, _bob_oms, bob_comms) = setup_transaction_service( + let (_carol_ts, _carol_oms, carol_comms) = setup_transaction_service( &mut runtime, - bob_node_identity.clone(), - vec![alice_node_identity.clone(), dave_node_identity.clone()], + carol_node_identity.clone(), + vec![], factories.clone(), - bob_backend, - db_folder.clone(), + TransactionMemoryDatabase::new(), + db_folder.join("carol"), Duration::from_secs(1), ); - let (_dave_ts, _dave_oms, dave_comms) = setup_transaction_service( + let (mut alice_ts, mut alice_oms, alice_comms) = setup_transaction_service( &mut runtime, - dave_node_identity.clone(), - vec![bob_node_identity.clone()], + alice_node_identity.clone(), + vec![carol_node_identity.clone()], factories.clone(), - dave_backend, - db_folder, - Duration::from_secs(1), + TransactionMemoryDatabase::new(), + db_folder.join("alice"), + Duration::from_secs(20), ); - // // Connect Dave to Bob - // let _ = runtime.block_on( - // dave_comms - // .connection_manager() - // .dial_peer(bob_node_identity.node_id().clone()), - // ); + let mut alice_event_stream = alice_ts.get_event_stream_fused(); let (_utxo, uo1a) = make_input(&mut OsRng, MicroTari(5500), &factories.commitment); runtime.block_on(alice_oms.add_output(uo1a)).unwrap(); @@ -1174,7 +1168,7 @@ fn discovery_async_return_test() { let tx_id = runtime .block_on(alice_ts.send_transaction( - carol_node_identity.public_key().clone(), + bob_node_identity.public_key().clone(), value_a_to_c_1, MicroTari::from(20), "Discovery Tx!".to_string(), @@ -1197,7 +1191,7 @@ fn discovery_async_return_test() { } }, () = delay => { - break; + panic!("Timeout while waiting for transaction to fail sending"); }, } } @@ -1207,7 +1201,7 @@ fn discovery_async_return_test() { let tx_id2 = runtime .block_on(alice_ts.send_transaction( - dave_node_identity.public_key().clone(), + carol_node_identity.public_key().clone(), value_a_to_c_1, MicroTari::from(20), "Discovery Tx2!".to_string(), @@ -1218,26 +1212,21 @@ fn discovery_async_return_test() { let mut success_tx_id = 0u64; runtime.block_on(async { let mut delay = delay_for(Duration::from_secs(60)).fuse(); - let mut success_count = 0; loop { futures::select! { event = alice_event_stream.select_next_some() => { if let TransactionEvent::TransactionDirectSendResult(tx_id, success) = &*event.unwrap() { - success_count+=1; - success_result = success.clone(); + success_result = *success; success_tx_id = *tx_id; - if success_count >= 1 { - break; - } + break; } }, () = delay => { - break; + panic!("Timeout while waiting for transaction to successfully be sent"); }, } } - assert!(success_count >= 1); }); assert_eq!(success_tx_id, tx_id2); @@ -1245,29 +1234,25 @@ fn discovery_async_return_test() { runtime.block_on(async { let mut delay = delay_for(Duration::from_secs(60)).fuse(); - let mut tx_reply = 0; loop { futures::select! { event = alice_event_stream.select_next_some() => { if let TransactionEvent::ReceivedTransactionReply(tx_id) = &*event.unwrap() { if tx_id == &tx_id2 { - tx_reply +=1; break; } } }, () = delay => { - break; + panic!("Timeout while Alice was waiting for a transaction reply"); }, } } - assert!(tx_reply >= 1); }); runtime.block_on(async move { alice_comms.shutdown().await; - bob_comms.shutdown().await; - dave_comms.shutdown().await; + carol_comms.shutdown().await; }); } @@ -2268,111 +2253,6 @@ fn query_all_completed_transactions_on_startup() { }); } -#[test] -#[ignore] -fn test_failed_tx_send_timeout() { - let temp_dir = TempDir::new(random_string(8).as_str()).unwrap(); - let mut runtime = create_runtime(); - - let factories = CryptoFactories::default(); - // Alice's parameters - let alice_node_identity = Arc::new( - NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(), - ); - - // Bob's parameters - let bob_node_identity = Arc::new( - NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(), - ); - - let base_node_identity = Arc::new( - NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(), - ); - - log::info!( - "manage_single_transaction: Alice: '{}', Bob: '{}', Base: '{}'", - alice_node_identity.node_id().short_str(), - bob_node_identity.node_id().short_str(), - base_node_identity.node_id().short_str() - ); - - let (mut alice_ts, mut alice_oms, _alice_comms) = setup_transaction_service( - &mut runtime, - alice_node_identity.clone(), - vec![bob_node_identity.clone()], - factories.clone(), - TransactionMemoryDatabase::new(), - temp_dir.path().to_str().unwrap().to_string().clone(), - Duration::from_secs(0), - ); - runtime - .block_on(alice_ts.set_base_node_public_key(base_node_identity.public_key().clone())) - .unwrap(); - - let (mut bob_ts, _bob_oms, bob_comms) = setup_transaction_service( - &mut runtime, - bob_node_identity.clone(), - vec![alice_node_identity.clone()], - factories.clone(), - TransactionMemoryDatabase::new(), - temp_dir.path().to_str().unwrap().to_string(), - Duration::from_secs(0), - ); - runtime - .block_on(bob_ts.set_base_node_public_key(base_node_identity.public_key().clone())) - .unwrap(); - - let _ = runtime.block_on( - bob_comms - .connection_manager() - .dial_peer(alice_node_identity.node_id().clone()), - ); - - runtime.block_on(bob_comms.shutdown()); - - let balance = 2500 * uT; - let value_sent = MicroTari::from(1000); - let (_utxo, uo1) = make_input(&mut OsRng, balance, &factories.commitment); - - runtime.block_on(alice_oms.add_output(uo1)).unwrap(); - let message = "TAKE MAH MONEYS!".to_string(); - runtime - .block_on(alice_ts.send_transaction( - bob_node_identity.public_key().clone(), - value_sent, - MicroTari::from(20), - message.clone(), - )) - .unwrap(); - - let mut alice_event_stream = alice_ts.get_event_stream_fused(); - - runtime.block_on(async { - let mut delay = delay_for(Duration::from_secs(180)).fuse(); - let mut returned = false; - let mut result = true; - loop { - futures::select! { - event = alice_event_stream.select_next_some() => { - if let TransactionEvent::TransactionDirectSendResult(_, success) = (*event.unwrap()).clone() { - returned = true; - result = success; - break; - } - }, - () = delay => { - break; - }, - } - } - assert!(returned, "Did not receive event"); - assert!(!result, "Send should have failed"); - }); - - let current_balance = runtime.block_on(alice_oms.get_balance()).unwrap(); - assert_eq!(current_balance.available_balance, balance); -} - #[test] fn transaction_cancellation_when_not_in_mempool() { let factories = CryptoFactories::default(); @@ -2775,204 +2655,6 @@ fn test_transaction_cancellation_sqlite_db() { test_transaction_cancellation(TransactionServiceSqliteDatabase::new(connection)); } -fn test_resend_of_tx_on_pong_event(backend: T) { - let factories = CryptoFactories::default(); - let mut runtime = Runtime::new().unwrap(); - - let alice_node_identity = - NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(); - - let bob_node_identity = - NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(); - - let base_node_identity = - NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(); - - let ( - mut alice_ts, - mut alice_output_manager, - alice_outbound_service, - mut _alice_tx_sender, - mut alice_tx_ack_sender, - _, - _, - _, - _alice_liveness_handle, - liveness_mock_state, - liveness_event_sender, - ) = setup_transaction_service_no_comms(&mut runtime, factories.clone(), backend, Some(Duration::from_secs(5))); - - let (mut bob_ts, _, bob_outbound_service, mut bob_tx_sender, _, _, _, _, _, _, _) = - setup_transaction_service_no_comms( - &mut runtime, - factories.clone(), - TransactionMemoryDatabase::new(), - Some(Duration::from_secs(20)), - ); - runtime - .block_on(bob_ts.set_base_node_public_key(base_node_identity.public_key().clone())) - .unwrap(); - - let alice_total_available = 250000 * uT; - let (_utxo, uo) = make_input(&mut OsRng, alice_total_available, &factories.commitment); - runtime.block_on(alice_output_manager.add_output(uo)).unwrap(); - - let amount_sent = 10000 * uT; - - alice_outbound_service.set_behaviour(MockBehaviour { - direct: ResponseType::Failed, - broadcast: ResponseType::Queued, - }); - - let tx_id = runtime - .block_on(alice_ts.send_transaction( - bob_node_identity.public_key().clone(), - amount_sent, - 100 * uT, - "Testing Message".to_string(), - )) - .unwrap(); - alice_outbound_service - .wait_call_count(1, Duration::from_secs(60)) - .unwrap(); - let _ = alice_outbound_service.pop_call().unwrap(); // burn send message - - alice_outbound_service.set_behaviour(MockBehaviour { - direct: ResponseType::Queued, - broadcast: ResponseType::Queued, - }); - - // Send the event but if the send protocol hasn't subscribed yet the send will error so wait a bit and try again. - for _ in 0..12 { - match liveness_event_sender.send(Arc::new(LivenessEvent::ReceivedPong(Box::new(PingPongEvent::new( - bob_node_identity.node_id().clone(), - None, - Metadata::new(), - true, - ))))) { - Ok(_) => { - break; - }, - Err(_) => { - runtime.block_on(async { delay_for(Duration::from_secs(5)).await }); - }, - } - } - - alice_outbound_service - .wait_call_count(1, Duration::from_secs(60)) - .unwrap(); - - let (_, body) = alice_outbound_service.pop_call().unwrap(); - - let envelope_body = EnvelopeBody::decode(body.to_vec().as_slice()).unwrap(); - let tx_sender_msg: TransactionSenderMessage = envelope_body - .decode_part::(1) - .unwrap() - .unwrap() - .try_into() - .unwrap(); - let msg_tx_id = match tx_sender_msg.clone() { - TransactionSenderMessage::Single(s) => s.tx_id, - _ => { - assert!(false, "Transaction is the not a single rounder sender variant"); - 0 - }, - }; - assert_eq!(tx_id, msg_tx_id); - - // Test that a second Pong doesn't evoke another message - liveness_event_sender - .send(Arc::new(LivenessEvent::ReceivedPong(Box::new(PingPongEvent::new( - bob_node_identity.node_id().clone(), - None, - Metadata::new(), - true, - ))))) - .unwrap(); - - for _ in 0..5 { - assert_eq!(alice_outbound_service.call_count(), 0, "Should be no more calls"); - runtime.block_on(async { delay_for(Duration::from_secs(5)).await }); - } - - // Send the reply - runtime - .block_on(bob_tx_sender.send(create_dummy_message( - tx_sender_msg.into(), - alice_node_identity.public_key(), - ))) - .unwrap(); - bob_outbound_service - .wait_call_count(1, Duration::from_secs(60)) - .unwrap(); - let (_, body) = bob_outbound_service.pop_call().unwrap(); - - let envelope_body = EnvelopeBody::decode(body.to_vec().as_slice()).unwrap(); - let tx_reply_msg: RecipientSignedMessage = envelope_body - .decode_part::(1) - .unwrap() - .unwrap() - .try_into() - .unwrap(); - - runtime - .block_on(alice_tx_ack_sender.send(create_dummy_message( - tx_reply_msg.into(), - bob_node_identity.public_key(), - ))) - .unwrap(); - - let _ = alice_outbound_service.wait_call_count(1, Duration::from_secs(60)); - let _ = alice_outbound_service.pop_call().unwrap(); // Burn finalize message - - for i in 1..=20 { - let call_count = liveness_mock_state.call_count(); - - if call_count == 2 { - let liveness_calls = liveness_mock_state.take_calls(); - let mut found_add = false; - let mut found_remove = false; - for c in liveness_calls { - match c { - LivenessRequest::AddNodeId(id) => { - found_add = true; - assert_eq!(&id, bob_node_identity.node_id()); - }, - LivenessRequest::RemoveNodeId(id) => { - found_remove = true; - assert_eq!(&id, bob_node_identity.node_id()); - }, - _ => (), - } - } - assert!(found_add, "Didn't find AddNodeId request"); - assert!(found_remove, "Didn't find RemoveNodeId request"); - break; - } - - runtime.block_on(async { delay_for(Duration::from_secs(5)).await }); - if i >= 20 { - assert!(false, "Did not get Liveness request"); - } - } -} - -#[test] -fn test_resend_of_tx_on_pong_event_memory_db() { - test_resend_of_tx_on_pong_event(TransactionMemoryDatabase::new()); -} - -#[test] -fn test_resend_of_tx_on_pong_event_sqlite_db() { - let db_name = format!("{}.sqlite3", random_string(8).as_str()); - let temp_dir = TempDir::new(random_string(8).as_str()).unwrap(); - let db_folder = temp_dir.path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}/{}", db_folder, db_name)).unwrap(); - - test_resend_of_tx_on_pong_event(TransactionServiceSqliteDatabase::new(connection)); -} - #[test] fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { let factories = CryptoFactories::default(); @@ -3407,3 +3089,176 @@ fn test_tx_direct_send_behaviour() { assert_eq!(saf_count, 1, "Should be 1 succeeded saf"); }); } + +#[test] +fn test_restarting_transaction_protocols() { + let mut runtime = Runtime::new().unwrap(); + let factories = CryptoFactories::default(); + + let alice_backend = TransactionMemoryDatabase::new(); + let bob_backend = TransactionMemoryDatabase::new(); + + let base_node_identity = Arc::new( + NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(), + ); + + let alice_identity = Arc::new( + NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(), + ); + + let bob_identity = Arc::new( + NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE).unwrap(), + ); + + // Bob is going to send a transaction to Alice + let alice = TestParams::new(&mut OsRng); + let bob = TestParams::new(&mut OsRng); + let (utxo, input) = make_input(&mut OsRng, MicroTari(2000), &factories.commitment); + let mut builder = SenderTransactionProtocol::builder(1); + let fee = Fee::calculate(MicroTari(20), 1, 1, 1); + builder + .with_lock_height(0) + .with_fee_per_gram(MicroTari(20)) + .with_offset(bob.offset.clone()) + .with_private_nonce(bob.nonce.clone()) + .with_input(utxo.clone(), input) + .with_amount(0, MicroTari(2000) - fee - MicroTari(10)); + let mut bob_stp = builder.build::(&factories).unwrap(); + let msg = bob_stp.build_single_round_message().unwrap(); + let bob_pre_finalize = bob_stp.clone(); + + let tx_id = msg.tx_id; + + let sender_info = TransactionSenderMessage::Single(Box::new(msg.clone())); + let receiver_protocol = ReceiverTransactionProtocol::new( + sender_info, + alice.nonce.clone(), + alice.spend_key.clone(), + OutputFeatures::default(), + &factories, + ); + + let alice_reply = receiver_protocol.get_signed_data().unwrap().clone(); + + bob_stp + .add_single_recipient_info(alice_reply.clone(), &factories.range_proof) + .unwrap(); + + match bob_stp.finalize(KernelFeatures::empty(), &factories) { + Ok(true) => (), + _ => assert!(false, "Should be able to finalize tx"), + }; + let tx = bob_stp.clone().get_transaction().unwrap().clone(); + + let inbound_tx = InboundTransaction { + tx_id, + source_public_key: bob_identity.public_key().clone(), + amount: msg.amount, + receiver_protocol, + status: TransactionStatus::Pending, + message: msg.message.clone(), + timestamp: Utc::now().naive_utc(), + cancelled: false, + direct_send_success: false, + }; + + alice_backend + .write(WriteOperation::Insert(DbKeyValuePair::PendingInboundTransaction( + tx_id, + Box::new(inbound_tx), + ))) + .unwrap(); + + let outbound_tx = OutboundTransaction { + tx_id, + destination_public_key: alice_identity.public_key().clone(), + amount: msg.amount, + fee, + sender_protocol: bob_pre_finalize, + status: TransactionStatus::Pending, + message: msg.message, + timestamp: Utc::now().naive_utc(), + cancelled: false, + direct_send_success: false, + }; + bob_backend + .write(WriteOperation::Insert(DbKeyValuePair::PendingOutboundTransaction( + tx_id, + Box::new(outbound_tx), + ))) + .unwrap(); + + // Test that Bob's node restarts the send protocol + let (mut bob_ts, _bob_oms, _bob_outbound_service, _, mut bob_tx_reply, _, _, _, _, _, _) = + setup_transaction_service_no_comms(&mut runtime, factories.clone(), bob_backend, None); + let mut bob_event_stream = bob_ts.get_event_stream_fused(); + + runtime + .block_on(bob_ts.set_base_node_public_key(base_node_identity.public_key().clone())) + .unwrap(); + + runtime + .block_on(bob_tx_reply.send(create_dummy_message(alice_reply.into(), &alice_identity.public_key()))) + .unwrap(); + + runtime.block_on(async { + let mut delay = delay_for(Duration::from_secs(15)).fuse(); + let mut received_reply = false; + loop { + futures::select! { + event = bob_event_stream.select_next_some() => { + if let TransactionEvent::ReceivedTransactionReply(id) = (*event.unwrap()).clone() { + assert_eq!(id, tx_id); + received_reply = true; + break; + } + }, + () = delay => { + break; + }, + } + } + assert!(received_reply, "Should have received tx reply"); + }); + + // Test Alice's node restarts the receive protocol + let (mut alice_ts, _alice_oms, _alice_outbound_service, _, _, mut alice_tx_finalized, _, _, _, _, _) = + setup_transaction_service_no_comms(&mut runtime, factories.clone(), alice_backend, None); + let mut alice_event_stream = alice_ts.get_event_stream_fused(); + + runtime + .block_on(alice_ts.set_base_node_public_key(base_node_identity.public_key().clone())) + .unwrap(); + + let finalized_transaction_message = proto::TransactionFinalizedMessage { + tx_id, + transaction: Some(tx.clone().into()), + }; + + runtime + .block_on(alice_tx_finalized.send(create_dummy_message( + finalized_transaction_message.clone(), + bob_identity.public_key(), + ))) + .unwrap(); + + runtime.block_on(async { + let mut delay = delay_for(Duration::from_secs(15)).fuse(); + let mut received_finalized = false; + loop { + futures::select! { + event = alice_event_stream.select_next_some() => { + if let TransactionEvent::ReceivedFinalizedTransaction(id) = (*event.unwrap()).clone() { + assert_eq!(id, tx_id); + received_finalized = true; + break; + } + }, + () = delay => { + break; + }, + } + } + assert!(received_finalized, "Should have received finalized tx"); + }); +} diff --git a/base_layer/wallet_ffi/Cargo.toml b/base_layer/wallet_ffi/Cargo.toml index bc0c0eaaaf..4d67520fab 100644 --- a/base_layer/wallet_ffi/Cargo.toml +++ b/base_layer/wallet_ffi/Cargo.toml @@ -20,7 +20,7 @@ tokio = "0.2.10" libc = "0.2.65" rand = "0.7.2" chrono = { version = "0.4.6", features = ["serde"]} -tari_broadcast_channel = "^0.1" +tari_broadcast_channel = "^0.2" derive-error = "0.0.4" log = "0.4.6" log4rs = {version = "0.8.3", features = ["console_appender", "file_appender", "file", "yaml_format"]} diff --git a/buildtools/install_tor_services.bat b/buildtools/install_tor_services.bat index 6fc49146f7..2a70489680 100644 --- a/buildtools/install_tor_services.bat +++ b/buildtools/install_tor_services.bat @@ -1,6 +1,9 @@ @rem Control variables @rem - Tor Services {Note: `powershell` cannot `expand-archive` to `C:\Program Files (x86)`} -@set tor_zip=tor-win32-0.4.2.7.zip +@rem - Download 'Windows Expert Bundle' at https://www.torproject.org/download/tor/ +https://www.torproject.org/dist/torbrowser/9.5/tor-win32-0.4.3.5.zip +@set tor_version=9.5 +@set tor_zip=tor-win32-0.4.3.5.zip @set tor_folder=%USERPROFILE%\.tor_services @set tor_runtime=tor.exe @@ -17,7 +20,7 @@ :INSTALL_TOR_SERVICES @rem Download install file -@powershell wget https://www.torproject.org/dist/torbrowser/9.0.9/%tor_zip% -outfile "%TEMP%\%tor_zip%" +@powershell wget https://www.torproject.org/dist/torbrowser/%tor_version%/%tor_zip% -outfile "%TEMP%\%tor_zip%" @rem Install @powershell expand-archive -Force -LiteralPath "%TEMP%\%tor_zip%" -DestinationPath "%tor_folder%" @rem Set Tari environment variables diff --git a/buildtools/windows_inno_installer.iss b/buildtools/windows_inno_installer.iss index 9e9b4fcbe2..ac2729c775 100644 --- a/buildtools/windows_inno_installer.iss +++ b/buildtools/windows_inno_installer.iss @@ -3,7 +3,7 @@ #define MyOrgName "Tari" #define MyAppName "Base Node" -#define MyAppVersion "0.2.4-0d60fff-release" +#define MyAppVersion "0.3.0-0f59836-release" #define MyAppPublisher "The Tari Development Community" #define MyAppURL "https://github.com/tari-project/tari" #define MyAppSupp "Tari Website" diff --git a/common/logging/log4rs-debug-sample.yml b/common/logging/log4rs-debug-sample.yml index eab4f140d4..08848f1aef 100644 --- a/common/logging/log4rs-debug-sample.yml +++ b/common/logging/log4rs-debug-sample.yml @@ -123,3 +123,9 @@ loggers: appenders: - other additive: false + # Route log events sent to the "pub_sub" logger to the "other" appender + tari_broadcast_channel: + level: debug + appenders: + - other + additive: false diff --git a/common/logging/log4rs-sample.yml b/common/logging/log4rs-sample.yml index ef8991e811..ab5b4b005a 100644 --- a/common/logging/log4rs-sample.yml +++ b/common/logging/log4rs-sample.yml @@ -122,3 +122,9 @@ loggers: appenders: - other additive: false + # Route log events sent to the "pub_sub" logger to the "other" appender + tari_broadcast_channel: + level: debug + appenders: + - other + additive: false diff --git a/comms/Cargo.toml b/comms/Cargo.toml index 567130eec1..a043ee35b2 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -19,7 +19,7 @@ blake2 = "0.8.1" bytes = { version = "0.5.x", features=["serde"] } chrono = { version = "0.4.6", features = ["serde"] } cidr = "0.1.0" -clear_on_drop = "0.2.3" +clear_on_drop = "=0.2.3" data-encoding = "2.2.0" derive-error = "0.0.4" digest = "0.8.0" diff --git a/comms/dht/src/actor.rs b/comms/dht/src/actor.rs index 145475c5b3..aa48227157 100644 --- a/comms/dht/src/actor.rs +++ b/comms/dht/src/actor.rs @@ -183,6 +183,7 @@ pub struct DhtActor { } impl DhtActor { + #[allow(clippy::too_many_arguments)] pub fn new( config: DhtConfig, conn: DbConnection, diff --git a/comms/dht/src/connectivity/mod.rs b/comms/dht/src/connectivity/mod.rs index 7737d42a41..37bcc36875 100644 --- a/comms/dht/src/connectivity/mod.rs +++ b/comms/dht/src/connectivity/mod.rs @@ -176,6 +176,22 @@ impl DhtConnectivity { Ok(()) } + async fn reinitialize_pools(&mut self) -> Result<(), DhtConnectivityError> { + info!( + target: LOG_TARGET, + "Reinitializing neighbour pool. Draining neighbour list (len={})", + self.neighbours.len(), + ); + for neighbour in self.neighbours.drain(..) { + self.connectivity.remove_peer(neighbour).await?; + } + + self.initialize_neighbours().await?; + self.refresh_random_pool().await?; + + Ok(()) + } + async fn handle_connectivity_event(&mut self, event: &ConnectivityEvent) -> Result<(), DhtConnectivityError> { use ConnectivityEvent::*; match event { @@ -201,6 +217,9 @@ impl DhtConnectivity { } } }, + ConnectivityStateOffline => { + self.reinitialize_pools().await?; + }, _ => {}, } @@ -208,52 +227,53 @@ impl DhtConnectivity { } async fn refresh_random_pool_if_required(&mut self) -> Result<(), DhtConnectivityError> { - if self.should_refresh_random_pool() { - let mut random_peers = self - .fetch_random_peers(self.config.num_random_nodes, &self.neighbours) - .await?; - if random_peers.is_empty() { - warn!( - target: LOG_TARGET, - "Unable to refresh random peer pool because there are insufficient known peers", - ); - } else { - let (keep, to_remove) = self - .random_pool - .iter() - .partition::, _>(|n| random_peers.contains(n)); - // Remove the peers that we want to keep from the `random_peers` to be added - random_peers.retain(|n| !keep.contains(&n)); - debug!( - target: LOG_TARGET, - "Adding new peers to random peer pool (#new = {}, #keeping = {}, #removing = {})", - random_peers.len(), - keep.len(), - to_remove.len() - ); - trace!( - target: LOG_TARGET, - "Random peers: Adding = {:?}, Removing = {:?}", - random_peers, - to_remove - ); - self.connectivity.add_managed_peers(random_peers).await?; - for n in to_remove { - self.connectivity.remove_peer(n.clone()).await?; - } - } - self.random_pool_last_refresh = Some(Instant::now()); + let should_refresh = self.config.num_random_nodes > 0 && + self.random_pool_last_refresh + .map(|instant| instant.elapsed() >= self.config.connectivity_random_pool_refresh) + .unwrap_or(true); + if should_refresh { + self.refresh_random_pool().await?; } Ok(()) } - #[inline] - fn should_refresh_random_pool(&self) -> bool { - self.config.num_random_nodes > 0 && - self.random_pool_last_refresh - .map(|instant| instant.elapsed() >= self.config.connectivity_random_pool_refresh) - .unwrap_or(true) + async fn refresh_random_pool(&mut self) -> Result<(), DhtConnectivityError> { + let mut random_peers = self + .fetch_random_peers(self.config.num_random_nodes, &self.neighbours) + .await?; + if random_peers.is_empty() { + warn!( + target: LOG_TARGET, + "Unable to refresh random peer pool because there are insufficient known peers", + ); + } else { + let (keep, to_remove) = self + .random_pool + .iter() + .partition::, _>(|n| random_peers.contains(n)); + // Remove the peers that we want to keep from the `random_peers` to be added + random_peers.retain(|n| !keep.contains(&n)); + debug!( + target: LOG_TARGET, + "Adding new peers to random peer pool (#new = {}, #keeping = {}, #removing = {})", + random_peers.len(), + keep.len(), + to_remove.len() + ); + trace!( + target: LOG_TARGET, + "Random peers: Adding = {:?}, Removing = {:?}", + random_peers, + to_remove + ); + self.connectivity.add_managed_peers(random_peers).await?; + for n in to_remove { + self.connectivity.remove_peer(n.clone()).await?; + } + } + self.random_pool_last_refresh = Some(Instant::now()); + Ok(()) } async fn handle_new_peer_connected(&mut self, conn: &PeerConnection) -> Result<(), DhtConnectivityError> { @@ -378,10 +398,11 @@ impl DhtConnectivity { d > dist }); - let mut removed_peer = None; - if self.neighbours.len() + 1 > self.config.num_neighbouring_nodes { - removed_peer = self.neighbours.pop(); - } + let removed_peer = if self.neighbours.len() + 1 > self.config.num_neighbouring_nodes { + self.neighbours.pop() + } else { + None + }; match pos { Some(idx) => { diff --git a/comms/dht/src/connectivity/test.rs b/comms/dht/src/connectivity/test.rs index c4e5b98adf..a73d6d7145 100644 --- a/comms/dht/src/connectivity/test.rs +++ b/comms/dht/src/connectivity/test.rs @@ -107,7 +107,7 @@ async fn initialize() { // Wait for calls to add peers async_assert!( - connectivity.call_count() >= 2, + connectivity.call_count().await >= 2, max_attempts = 20, interval = Duration::from_millis(10), ); @@ -146,7 +146,7 @@ async fn added_neighbours() { // Wait for calls to add peers async_assert!( - connectivity.call_count() >= 1, + connectivity.call_count().await >= 1, max_attempts = 20, interval = Duration::from_millis(10), ); @@ -158,7 +158,7 @@ async fn added_neighbours() { connectivity.publish_event(ConnectivityEvent::PeerConnected(conn)); async_assert!( - connectivity.call_count() >= 2, + connectivity.call_count().await >= 2, max_attempts = 20, interval = Duration::from_millis(10), ); @@ -173,6 +173,47 @@ async fn added_neighbours() { assert!(managed.contains(closer_peer.node_id())); } +#[tokio_macros::test_basic] +async fn reinitialize_pools_when_offline() { + let node_identity = make_node_identity(); + let node_identities = repeat_with(|| make_node_identity()).take(5).collect::>(); + // Closest to this node + let peers = node_identities.iter().map(|ni| ni.to_peer()).collect::>(); + + let config = DhtConfig { + num_neighbouring_nodes: 5, + num_random_nodes: 0, + ..Default::default() + }; + let (dht_connectivity, _, connectivity, _, _, _shutdown) = setup(config, node_identity, peers).await; + dht_connectivity.spawn(); + + // Wait for calls to add peers + async_assert!( + connectivity.call_count().await >= 1, + max_attempts = 20, + interval = Duration::from_millis(10), + ); + + let calls = connectivity.take_calls().await; + assert_eq!(count_string_occurrences(&calls, &["AddManagedPeers"]), 1); + + connectivity.publish_event(ConnectivityEvent::ConnectivityStateOffline); + + async_assert!( + connectivity.call_count().await >= 1, + max_attempts = 20, + interval = Duration::from_millis(10), + ); + let calls = connectivity.take_calls().await; + assert_eq!(count_string_occurrences(&calls, &["RemovePeer"]), 5); + assert_eq!(count_string_occurrences(&calls, &["AddManagedPeers"]), 1); + + // Check that the closer neighbour was added to managed peers + let managed = connectivity.get_managed_peers().await; + assert_eq!(managed.len(), 5); +} + #[tokio_macros::test_basic] async fn insert_neighbour() { let node_identity = make_node_identity(); diff --git a/comms/dht/src/outbound/broadcast.rs b/comms/dht/src/outbound/broadcast.rs index 908f41c5c0..cbcc99bab2 100644 --- a/comms/dht/src/outbound/broadcast.rs +++ b/comms/dht/src/outbound/broadcast.rs @@ -164,6 +164,7 @@ struct BroadcastTask { request: Option, target_network: Network, } +type FinalMessageParts = (Option>, Option, Bytes); impl BroadcastTask where S: Service @@ -462,7 +463,7 @@ where S: Service encryption: &OutboundEncryption, include_origin: bool, body: Bytes, - ) -> Result<(Option>, Option, Bytes), DhtOutboundError> + ) -> Result { match encryption { OutboundEncryption::EncryptFor(public_key) => { diff --git a/comms/dht/src/outbound/message_send_state.rs b/comms/dht/src/outbound/message_send_state.rs index 5bbeae7d95..967cb3566f 100644 --- a/comms/dht/src/outbound/message_send_state.rs +++ b/comms/dht/src/outbound/message_send_state.rs @@ -93,26 +93,19 @@ impl MessageSendStates { let mut unordered = self.into_futures_unordered(); let mut succeeded = Vec::new(); let mut failed = Vec::new(); - loop { - match unordered.next().await { - Some((tag, result)) => { - match result { - Ok(_) => { - count += 1; - succeeded.push(tag); - }, - Err(_) => { - failed.push(tag); - }, - } - if (count as f32) / (total as f32) >= threshold_perc { - break; - } + while let Some((tag, result)) = unordered.next().await { + match result { + Ok(_) => { + count += 1; + succeeded.push(tag); }, - None => { - break; + Err(_) => { + failed.push(tag); }, } + if (count as f32) / (total as f32) >= threshold_perc { + break; + } } (succeeded, failed) diff --git a/comms/dht/src/store_forward/database/mod.rs b/comms/dht/src/store_forward/database/mod.rs index 7787e64ae8..491c336f94 100644 --- a/comms/dht/src/store_forward/database/mod.rs +++ b/comms/dht/src/store_forward/database/mod.rs @@ -150,7 +150,7 @@ impl StoreAndForwardDatabase { .filter(|message| match message.destination_node_id { Some(ref dest_node_id) => match NodeId::from_hex(dest_node_id).ok() { Some(dest_node_id) => { - &dest_node_id == node_id || &dest_node_id.distance(node_id) <= &*dist_threshold + &dest_node_id == node_id || dest_node_id.distance(node_id) <= *dist_threshold }, None => false, }, diff --git a/comms/dht/src/store_forward/service.rs b/comms/dht/src/store_forward/service.rs index f8722e1f5f..3abd43f4be 100644 --- a/comms/dht/src/store_forward/service.rs +++ b/comms/dht/src/store_forward/service.rs @@ -169,6 +169,7 @@ pub struct StoreAndForwardService { } impl StoreAndForwardService { + #[allow(clippy::too_many_arguments)] pub fn new( config: DhtConfig, conn: DbConnection, @@ -298,6 +299,7 @@ impl StoreAndForwardService { return Ok(()); } + #[allow(clippy::single_match)] match event { PeerConnected(conn) => { // Whenever we connect to a peer, request SAF messages diff --git a/comms/src/builder/mod.rs b/comms/src/builder/mod.rs index 7c1f2077dd..edb1d1225e 100644 --- a/comms/src/builder/mod.rs +++ b/comms/src/builder/mod.rs @@ -326,7 +326,7 @@ where ) -> ConnectivityManager { ConnectivityManager { - config: self.connectivity_config.clone(), + config: self.connectivity_config, request_rx, event_tx, connection_manager: connection_manager_requester, diff --git a/comms/src/connectivity/connection_stats.rs b/comms/src/connectivity/connection_stats.rs index f9e2993d8f..8ccd19ad36 100644 --- a/comms/src/connectivity/connection_stats.rs +++ b/comms/src/connectivity/connection_stats.rs @@ -67,7 +67,7 @@ impl PeerConnectionStats { /// `last_connection_attempt` is not `Failed` pub fn last_failed_at(&self) -> Option { match &self.last_connection_attempt { - LastConnectionAttempt::Failed { failed_at, .. } => Some(failed_at.clone()), + LastConnectionAttempt::Failed { failed_at, .. } => Some(*failed_at), _ => None, } } diff --git a/comms/src/connectivity/selection.rs b/comms/src/connectivity/selection.rs index 6b013c9dc7..eae03eac67 100644 --- a/comms/src/connectivity/selection.rs +++ b/comms/src/connectivity/selection.rs @@ -56,7 +56,7 @@ pub fn select_closest<'a>(pool: &'a ConnectionPool, node_id: &NodeId, exclude: & pub fn select_random_nodes<'a>(pool: &'a ConnectionPool, n: usize, exclude: &[NodeId]) -> Vec<&'a PeerConnection> { let nodes = select_connected_nodes(pool, exclude); - nodes.choose_multiple(&mut OsRng, n).map(|c| *c).collect() + nodes.choose_multiple(&mut OsRng, n).cloned().collect() } #[cfg(test)] diff --git a/comms/src/peer_manager/manager.rs b/comms/src/peer_manager/manager.rs index ec012451b8..5d993843da 100644 --- a/comms/src/peer_manager/manager.rs +++ b/comms/src/peer_manager/manager.rs @@ -22,7 +22,6 @@ use crate::{ peer_manager::{ - connection_stats::PeerConnectionStats, node_id::{NodeDistance, NodeId}, peer::{Peer, PeerFlags}, peer_id::PeerId, @@ -31,7 +30,6 @@ use crate::{ PeerManagerError, PeerQuery, }, - protocol::ProtocolId, types::{CommsDatabase, CommsPublicKey}, }; use multiaddr::Multiaddr; @@ -64,33 +62,6 @@ impl PeerManager { self.peer_storage.write().await.add_peer(peer) } - /// Updates fields for a peer. Any fields set to Some(xx) will be updated. All None - /// fields will remain the same. - #[allow(clippy::too_many_arguments)] - pub async fn update_peer( - &self, - public_key: &CommsPublicKey, - net_addresses: Option>, - flags: Option, - #[allow(clippy::option_option)] banned_until: Option>, - #[allow(clippy::option_option)] is_offline: Option, - peer_features: Option, - connection_stats: Option, - supported_protocols: Option>, - ) -> Result<(), PeerManagerError> - { - self.peer_storage.write().await.update_peer( - public_key, - net_addresses, - flags, - banned_until, - is_offline, - peer_features, - connection_stats, - supported_protocols, - ) - } - /// The peer with the specified public_key will be removed from the PeerManager pub async fn delete_peer(&self, node_id: &NodeId) -> Result<(), PeerManagerError> { self.peer_storage.write().await.delete_peer(node_id) @@ -291,12 +262,12 @@ impl PeerManager { } /// Return some basic stats about the region around region_node_id - pub async fn get_region_stats<'a>( + pub async fn get_region_stats( &self, - region_node_id: &'a NodeId, + region_node_id: &NodeId, n: usize, features: PeerFeatures, - ) -> Result, PeerManagerError> + ) -> Result { self.peer_storage .read() diff --git a/comms/src/peer_manager/peer.rs b/comms/src/peer_manager/peer.rs index 78fc1d7b02..2b86d17ccc 100644 --- a/comms/src/peer_manager/peer.rs +++ b/comms/src/peer_manager/peer.rs @@ -158,14 +158,14 @@ impl Peer { self.id = Some(id); } + #[allow(clippy::option_option)] pub fn update( &mut self, net_addresses: Option>, flags: Option, - #[allow(clippy::option_option)] banned_until: Option>, - #[allow(clippy::option_option)] is_offline: Option, + banned_until: Option>, + is_offline: Option, features: Option, - connection_stats: Option, supported_protocols: Option>, ) { @@ -186,9 +186,6 @@ impl Peer { if let Some(new_features) = features { self.features = new_features; } - if let Some(connection_stats) = connection_stats { - self.connection_stats = connection_stats; - } if let Some(supported_protocols) = supported_protocols { self.supported_protocols = supported_protocols; } @@ -331,7 +328,6 @@ mod test { Some(Some(Duration::from_secs(1000))), None, Some(PeerFeatures::MESSAGE_PROPAGATION), - Some(PeerConnectionStats::new()), Some(vec![protocol::IDENTITY_PROTOCOL.clone()]), ); diff --git a/comms/src/peer_manager/peer_features.rs b/comms/src/peer_manager/peer_features.rs index 4e8cc3b58b..f69171c4fb 100644 --- a/comms/src/peer_manager/peer_features.rs +++ b/comms/src/peer_manager/peer_features.rs @@ -38,13 +38,13 @@ bitflags! { impl PeerFeatures { #[inline] - pub fn is_client(&self) -> bool { - self == &PeerFeatures::COMMUNICATION_CLIENT + pub fn is_client(self) -> bool { + self == PeerFeatures::COMMUNICATION_CLIENT } #[inline] - pub fn is_node(&self) -> bool { - self == &PeerFeatures::COMMUNICATION_NODE + pub fn is_node(self) -> bool { + self == PeerFeatures::COMMUNICATION_NODE } } diff --git a/comms/src/peer_manager/peer_storage.rs b/comms/src/peer_manager/peer_storage.rs index 17585b4af4..3708438819 100644 --- a/comms/src/peer_manager/peer_storage.rs +++ b/comms/src/peer_manager/peer_storage.rs @@ -23,7 +23,6 @@ use crate::{ consts::PEER_MANAGER_MAX_FLOOD_PEERS, peer_manager::{ - connection_stats::PeerConnectionStats, node_id::{NodeDistance, NodeId}, peer::{Peer, PeerFlags}, peer_id::{generate_peer_key, PeerId}, @@ -119,15 +118,15 @@ where DS: KeyValueStore /// Adds a peer to the routing table of the PeerManager if the peer does not already exist. When a peer already /// exist, the stored version will be replaced with the newly provided peer. #[allow(clippy::too_many_arguments)] + #[allow(clippy::option_option)] pub fn update_peer( &mut self, public_key: &CommsPublicKey, net_addresses: Option>, flags: Option, - #[allow(clippy::option_option)] banned_until: Option>, - #[allow(clippy::option_option)] is_offline: Option, + banned_until: Option>, + is_offline: Option, peer_features: Option, - connection_stats: Option, supported_protocols: Option>, ) -> Result<(), PeerManagerError> { @@ -147,7 +146,6 @@ where DS: KeyValueStore banned_until, is_offline, peer_features, - connection_stats, supported_protocols, ); @@ -506,12 +504,12 @@ where DS: KeyValueStore /// Return some basic stats for the region surrounding the region_node_id. The size of the local region is /// determined by the maximum distance of the n closest valid nodes. - pub fn get_region_stats<'a>( + pub fn get_region_stats( &self, - region_node_id: &'a NodeId, + region_node_id: &NodeId, n: usize, features: PeerFeatures, - ) -> Result, PeerManagerError> + ) -> Result { let mut peer_keys = Vec::new(); let mut valid_dists = Vec::new(); @@ -566,7 +564,7 @@ where DS: KeyValueStore let num_banned = banned_dists.into_iter().filter(|d| *d <= distance).count(); Ok(RegionStats { distance, - ref_node_id: region_node_id, + node_id: region_node_id.clone(), total, num_offline, num_banned, @@ -580,17 +578,17 @@ impl Into for PeerStorage { } } -pub struct RegionStats<'a> { +pub struct RegionStats { distance: NodeDistance, - ref_node_id: &'a NodeId, + node_id: NodeId, total: usize, num_offline: usize, num_banned: usize, } -impl RegionStats<'_> { +impl RegionStats { pub fn in_region(&self, node_id: &NodeId) -> bool { - node_id.distance(self.ref_node_id) <= self.distance + node_id.distance(&self.node_id) <= self.distance } pub fn offline_ratio(&self) -> f32 { @@ -602,7 +600,7 @@ impl RegionStats<'_> { } } -impl fmt::Display for RegionStats<'_> { +impl fmt::Display for RegionStats { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, diff --git a/comms/src/test_utils/mocks/connectivity_manager.rs b/comms/src/test_utils/mocks/connectivity_manager.rs index 454612e736..07a85927be 100644 --- a/comms/src/test_utils/mocks/connectivity_manager.rs +++ b/comms/src/test_utils/mocks/connectivity_manager.rs @@ -26,13 +26,7 @@ use crate::{ peer_manager::NodeId, }; use futures::{channel::mpsc, lock::Mutex, stream::Fuse, StreamExt}; -use std::{ - collections::HashMap, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, - }, -}; +use std::{collections::HashMap, sync::Arc}; use tokio::{sync::broadcast, task}; pub fn create_connectivity_mock() -> (ConnectivityRequester, ConnectivityManagerMock) { @@ -46,7 +40,6 @@ pub fn create_connectivity_mock() -> (ConnectivityRequester, ConnectivityManager #[derive(Debug, Clone)] pub struct ConnectivityManagerMockState { - call_count: Arc, calls: Arc>>, active_conns: Arc>>, selected_connections: Arc>>, @@ -57,7 +50,6 @@ pub struct ConnectivityManagerMockState { impl ConnectivityManagerMockState { pub fn new(event_tx: broadcast::Sender>) -> Self { Self { - call_count: Arc::new(AtomicUsize::new(0)), calls: Arc::new(Mutex::new(Vec::new())), event_tx, selected_connections: Arc::new(Mutex::new(Vec::new())), @@ -66,10 +58,6 @@ impl ConnectivityManagerMockState { } } - fn inc_call_count(&self) { - self.call_count.fetch_add(1, Ordering::SeqCst); - } - async fn add_call(&self, call_str: String) { self.calls.lock().await.push(call_str); } @@ -91,8 +79,8 @@ impl ConnectivityManagerMockState { } #[allow(dead_code)] - pub fn call_count(&self) -> usize { - self.call_count.load(Ordering::SeqCst) + pub async fn call_count(&self) -> usize { + self.calls.lock().await.len() } #[allow(dead_code)] @@ -139,7 +127,6 @@ impl ConnectivityManagerMock { async fn handle_request(&self, req: ConnectivityRequest) { use ConnectivityRequest::*; - self.state.inc_call_count(); self.state.add_call(format!("{:?}", req)).await; match req { DialPeer(node_id, reply_tx) => { diff --git a/comms/src/utils/cidr.rs b/comms/src/utils/cidr.rs index 29b8d4615e..d51971b3ed 100644 --- a/comms/src/utils/cidr.rs +++ b/comms/src/utils/cidr.rs @@ -22,7 +22,7 @@ use std::str::FromStr; -pub fn parse_cidrs<'a, I: IntoIterator, T: AsRef>(cidr_strs: I) -> Result, String> { +pub fn parse_cidrs, T: AsRef>(cidr_strs: I) -> Result, String> { let (success, failed) = cidr_strs .into_iter() .map(|s| ::cidr::AnyIpCidr::from_str(s.as_ref()))