From 6d4d9d4b0dbfc9f8c1c491ba8956e2a3778b3e41 Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Tue, 9 Nov 2021 07:37:07 +0200 Subject: [PATCH 01/46] ci: switch off cargo multiplexing for build binaries action (#3540) --- .github/workflows/base_node_binaries.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/base_node_binaries.yml b/.github/workflows/base_node_binaries.yml index e1bc225ae9..0da2411273 100644 --- a/.github/workflows/base_node_binaries.yml +++ b/.github/workflows/base_node_binaries.yml @@ -48,6 +48,7 @@ jobs: run: | echo "VBRANCH=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV echo "VSHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "CARGO_HTTP_MULTIPLEXING=false" >> $GITHUB_ENV - name: Default Destination Folder run: | From 23e868a8a4d2d8b673e4bd3df9fb9f4d33d191d9 Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Tue, 9 Nov 2021 10:07:07 +0400 Subject: [PATCH 02/46] fix: add decision step between header sync and pruned/archival (#3546) Description --- - removes pruned metadata sync logic from listening state - adds `DecideNextSync` step that is responsible for finding a suitable peer for block/pruned sync - chain metadata service includes peer latency - DRY'd up chain metadata service ping/pong event handling - take latency into account when selecting a sync peer - header sync orders the peers in ascending latency order Motivation and Context --- When transitioning to header sync, the listening state would filter out peers that have a suitable horizon height. However any peer regardless of pruned height can provide headers for header sync. How Has This Been Tested? --- Sync tests Manually --- .../chain_metadata_service/handle.rs | 8 +- .../chain_metadata_service/service.rs | 83 ++++------- .../state_machine_service/state_machine.rs | 21 ++- .../states/block_sync.rs | 30 ++-- .../states/events_and_states.rs | 40 +++--- .../states/header_sync.rs | 16 ++- .../states/horizon_state_sync.rs | 26 +++- .../states/horizon_state_sync/error.rs | 9 +- .../horizon_state_synchronization.rs | 38 ++--- .../state_machine_service/states/listening.rs | 83 ++++++----- .../state_machine_service/states/mod.rs | 5 +- .../states/sync_decide.rs | 130 ++++++++++++++++++ .../base_node/sync/block_sync/synchronizer.rs | 31 ++--- .../sync/header_sync/synchronizer.rs | 49 ++----- .../core/tests/helpers/chain_metadata.rs | 4 +- base_layer/p2p/src/services/liveness/state.rs | 4 + 16 files changed, 349 insertions(+), 228 deletions(-) create mode 100644 base_layer/core/src/base_node/state_machine_service/states/sync_decide.rs diff --git a/base_layer/core/src/base_node/chain_metadata_service/handle.rs b/base_layer/core/src/base_node/chain_metadata_service/handle.rs index 67680b6a67..ae8778becc 100644 --- a/base_layer/core/src/base_node/chain_metadata_service/handle.rs +++ b/base_layer/core/src/base_node/chain_metadata_service/handle.rs @@ -32,13 +32,15 @@ use tokio::sync::broadcast; pub struct PeerChainMetadata { node_id: NodeId, chain_metadata: ChainMetadata, + latency: Option, } impl PeerChainMetadata { - pub fn new(node_id: NodeId, chain_metadata: ChainMetadata) -> Self { + pub fn new(node_id: NodeId, chain_metadata: ChainMetadata, latency: Option) -> Self { Self { node_id, chain_metadata, + latency, } } @@ -49,6 +51,10 @@ impl PeerChainMetadata { pub fn claimed_chain_metadata(&self) -> &ChainMetadata { &self.chain_metadata } + + pub fn latency(&self) -> Option { + self.latency + } } impl Display for PeerChainMetadata { 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 ad5050a2eb..f2879115b8 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 @@ -38,9 +38,8 @@ use tari_common_types::chain_metadata::ChainMetadata; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityRequester}, message::MessageExt, - peer_manager::NodeId, }; -use tari_p2p::services::liveness::{LivenessEvent, LivenessHandle, Metadata, MetadataKey}; +use tari_p2p::services::liveness::{LivenessEvent, LivenessHandle, MetadataKey, PingPongEvent}; use tokio::sync::broadcast; const NUM_ROUNDS_NETWORK_SILENCE: u16 = 3; @@ -164,8 +163,10 @@ impl ChainMetadataService { event.node_id ); self.number_of_rounds_no_pings = 0; - self.collect_chain_state_from_ping(&event.node_id, &event.metadata)?; - self.send_chain_metadata_to_event_publisher().await?; + if event.metadata.has(MetadataKey::ChainMetadata) { + self.collect_chain_state_from_ping_pong(event)?; + self.send_chain_metadata_to_event_publisher().await?; + } }, // Received a pong, check if our neighbour sent it and it contains ChainMetadata LivenessEvent::ReceivedPong(event) => { @@ -175,8 +176,10 @@ impl ChainMetadataService { event.node_id ); self.number_of_rounds_no_pings = 0; - self.collect_chain_state_from_pong(&event.node_id, &event.metadata)?; - self.send_chain_metadata_to_event_publisher().await?; + if event.metadata.has(MetadataKey::ChainMetadata) { + self.collect_chain_state_from_ping_pong(event)?; + self.send_chain_metadata_to_event_publisher().await?; + } }, // New ping round has begun LivenessEvent::PingRoundBroadcast(num_peers) => { @@ -231,52 +234,18 @@ impl ChainMetadataService { } } - fn collect_chain_state_from_ping( - &mut self, - node_id: &NodeId, - metadata: &Metadata, - ) -> Result<(), ChainMetadataSyncError> { - if let Some(chain_metadata_bytes) = metadata.get(MetadataKey::ChainMetadata) { - let chain_metadata = proto::ChainMetadata::decode(chain_metadata_bytes.as_slice())?; - let chain_metadata = ChainMetadata::try_from(chain_metadata) - .map_err(|err| ChainMetadataSyncError::ReceivedInvalidChainMetadata(node_id.clone(), err))?; - debug!( - target: LOG_TARGET, - "Received chain metadata from NodeId '{}' #{}, Acc_diff {}", - node_id, - chain_metadata.height_of_longest_chain(), - chain_metadata.accumulated_difficulty().to_formatted_string(&Locale::en), - ); - - if let Some(pos) = self - .peer_chain_metadata - .iter() - .position(|peer_chainstate| peer_chainstate.node_id() == node_id) - { - self.peer_chain_metadata.remove(pos); - } - - self.peer_chain_metadata - .push(PeerChainMetadata::new(node_id.clone(), chain_metadata)); - } - Ok(()) - } - - fn collect_chain_state_from_pong( - &mut self, - node_id: &NodeId, - metadata: &Metadata, - ) -> Result<(), ChainMetadataSyncError> { - let chain_metadata_bytes = metadata + fn collect_chain_state_from_ping_pong(&mut self, event: &PingPongEvent) -> Result<(), ChainMetadataSyncError> { + let chain_metadata_bytes = event + .metadata .get(MetadataKey::ChainMetadata) .ok_or(ChainMetadataSyncError::NoChainMetadata)?; let chain_metadata = ChainMetadata::try_from(proto::ChainMetadata::decode(chain_metadata_bytes.as_slice())?) - .map_err(|err| ChainMetadataSyncError::ReceivedInvalidChainMetadata(node_id.clone(), err))?; + .map_err(|err| ChainMetadataSyncError::ReceivedInvalidChainMetadata(event.node_id.clone(), err))?; debug!( target: LOG_TARGET, "Received chain metadata from NodeId '{}' #{}, Acc_diff {}", - node_id, + event.node_id, chain_metadata.height_of_longest_chain(), chain_metadata.accumulated_difficulty().to_formatted_string(&Locale::en), ); @@ -284,13 +253,16 @@ impl ChainMetadataService { if let Some(pos) = self .peer_chain_metadata .iter() - .position(|peer_chainstate| peer_chainstate.node_id() == node_id) + .position(|peer_chainstate| *peer_chainstate.node_id() == event.node_id) { self.peer_chain_metadata.remove(pos); } - self.peer_chain_metadata - .push(PeerChainMetadata::new(node_id.clone(), chain_metadata)); + self.peer_chain_metadata.push(PeerChainMetadata::new( + event.node_id.clone(), + chain_metadata, + event.latency, + )); Ok(()) } } @@ -301,13 +273,17 @@ mod test { use crate::base_node::comms_interface::{CommsInterfaceError, NodeCommsRequest, NodeCommsResponse}; use futures::StreamExt; use std::convert::TryInto; - use tari_comms::test_utils::{ - mocks::{create_connectivity_mock, ConnectivityManagerMockState}, - node_identity::build_many_node_identities, + use tari_comms::{ + peer_manager::NodeId, + test_utils::{ + mocks::{create_connectivity_mock, ConnectivityManagerMockState}, + node_identity::build_many_node_identities, + }, }; use tari_p2p::services::liveness::{ mock::{create_p2p_liveness_mock, LivenessMockState}, LivenessRequest, + Metadata, PingPongEvent, }; use tari_service_framework::reply_channel; @@ -465,9 +441,8 @@ mod test { }; let sample_event = LivenessEvent::ReceivedPong(Box::new(pong_event)); - let err = service.handle_liveness_event(&sample_event).await.unwrap_err(); - unpack_enum!(ChainMetadataSyncError::NoChainMetadata = err); - assert_eq!(service.peer_chain_metadata.len(), 0); + service.handle_liveness_event(&sample_event).await.unwrap(); + assert!(service.peer_chain_metadata.is_empty()); } #[tokio::test] diff --git a/base_layer/core/src/base_node/state_machine_service/state_machine.rs b/base_layer/core/src/base_node/state_machine_service/state_machine.rs index ab534cda52..95232568e2 100644 --- a/base_layer/core/src/base_node/state_machine_service/state_machine.rs +++ b/base_layer/core/src/base_node/state_machine_service/state_machine.rs @@ -138,22 +138,20 @@ impl BaseNodeStateMachine { use self::{BaseNodeState::*, StateEvent::*, SyncStatus::*}; match (state, event) { (Starting(s), Initialized) => Listening(s.into()), - (HeaderSync(_), HeadersSynchronized(conn)) => { - if self.config.pruning_horizon > 0 { - HorizonStateSync(states::HorizonStateSync::with_peer(conn)) - } else { - BlockSync(states::BlockSync::with_peer(conn)) - } - }, + (Listening(_), FallenBehind(Lagging(_, sync_peers))) => HeaderSync(sync_peers.into()), (HeaderSync(s), HeaderSyncFailed) => Waiting(s.into()), - (HeaderSync(s), Continue) => Listening(s.into()), - (HeaderSync(s), NetworkSilence) => Listening(s.into()), + (HeaderSync(s), Continue | NetworkSilence) => Listening(s.into()), + (HeaderSync(s), HeadersSynchronized(_)) => DecideNextSync(s.into()), + + (DecideNextSync(_), ProceedToHorizonSync(peer)) => HorizonStateSync(peer.into()), + (DecideNextSync(s), Continue) => Listening(s.into()), (HorizonStateSync(s), HorizonStateSynchronized) => BlockSync(s.into()), (HorizonStateSync(s), HorizonStateSyncFailure) => Waiting(s.into()), + + (DecideNextSync(_), ProceedToBlockSync(peer)) => BlockSync(peer.into()), (BlockSync(s), BlocksSynchronized) => Listening(s.into()), (BlockSync(s), BlockSyncFailed) => Waiting(s.into()), - (Listening(_), FallenBehind(Lagging(_, sync_peers))) => HeaderSync(sync_peers.into()), - (Listening(_), FallenBehind(LaggingBehindHorizon(_, sync_peers))) => HeaderSync(sync_peers.into()), + (Waiting(s), Continue) => Listening(s.into()), (_, FatalError(s)) => Shutdown(states::Shutdown::with_reason(s)), (_, UserQuit) => Shutdown(states::Shutdown::with_reason("Shutdown initiated by user".to_string())), @@ -240,6 +238,7 @@ impl BaseNodeStateMachine { match state { Starting(s) => s.next_event(shared_state).await, HeaderSync(s) => s.next_event(shared_state).await, + DecideNextSync(s) => s.next_event(shared_state).await, HorizonStateSync(s) => s.next_event(shared_state).await, BlockSync(s) => s.next_event(shared_state).await, Listening(s) => s.next_event(shared_state).await, diff --git a/base_layer/core/src/base_node/state_machine_service/states/block_sync.rs b/base_layer/core/src/base_node/state_machine_service/states/block_sync.rs index 96e2a1f2c6..9addd1ab08 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/block_sync.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/block_sync.rs @@ -24,7 +24,7 @@ use crate::{ base_node::{ comms_interface::BlockEvent, state_machine_service::states::{BlockSyncInfo, HorizonStateSync, StateEvent, StateInfo, StatusInfo}, - sync::BlockSynchronizer, + sync::{BlockSynchronizer, SyncPeer}, BaseNodeStateMachine, }, chain_storage::{BlockAddResult, BlockchainBackend}, @@ -32,24 +32,19 @@ use crate::{ use log::*; use randomx_rs::RandomXFlag; use std::time::Instant; -use tari_comms::PeerConnection; const LOG_TARGET: &str = "c::bn::block_sync"; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct BlockSync { - sync_peer: Option, + sync_peer: SyncPeer, is_synced: bool, } impl BlockSync { - pub fn new() -> Self { - Default::default() - } - - pub fn with_peer(sync_peer: PeerConnection) -> Self { + pub fn new(sync_peer: SyncPeer) -> Self { Self { - sync_peer: Some(sync_peer), + sync_peer, is_synced: false, } } @@ -62,7 +57,7 @@ impl BlockSync { shared.config.block_sync_config.clone(), shared.db.clone(), shared.connectivity.clone(), - self.sync_peer.take(), + self.sync_peer.clone(), shared.sync_validators.block_body.clone(), ); @@ -122,7 +117,16 @@ impl BlockSync { } impl From for BlockSync { - fn from(_: HorizonStateSync) -> Self { - BlockSync::new() + fn from(sync: HorizonStateSync) -> Self { + BlockSync::new(sync.into_sync_peer()) + } +} + +impl From for BlockSync { + fn from(sync_peer: SyncPeer) -> Self { + Self { + sync_peer, + is_synced: false, + } } } diff --git a/base_layer/core/src/base_node/state_machine_service/states/events_and_states.rs b/base_layer/core/src/base_node/state_machine_service/states/events_and_states.rs index 080fd9df21..c85f8403d7 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/events_and_states.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/events_and_states.rs @@ -23,6 +23,7 @@ use crate::base_node::{ state_machine_service::states::{ BlockSync, + DecideNextSync, HeaderSync, HorizonStateSync, Listening, @@ -36,12 +37,13 @@ use crate::base_node::{ use randomx_rs::RandomXFlag; use std::fmt::{Display, Error, Formatter}; use tari_common_types::chain_metadata::ChainMetadata; -use tari_comms::{peer_manager::NodeId, PeerConnection}; +use tari_comms::peer_manager::NodeId; #[derive(Debug)] pub enum BaseNodeState { Starting(Starting), HeaderSync(HeaderSync), + DecideNextSync(DecideNextSync), HorizonStateSync(HorizonStateSync), BlockSync(BlockSync), // The best network chain metadata @@ -54,8 +56,10 @@ pub enum BaseNodeState { #[derive(Debug, Clone, PartialEq)] pub enum StateEvent { Initialized, - HeadersSynchronized(PeerConnection), + HeadersSynchronized(SyncPeer), HeaderSyncFailed, + ProceedToHorizonSync(SyncPeer), + ProceedToBlockSync(SyncPeer), HorizonStateSynchronized, HorizonStateSyncFailure, BlocksSynchronized, @@ -81,8 +85,6 @@ impl From for StateEvent { pub enum SyncStatus { // We are behind the chain tip. Lagging(ChainMetadata, Vec), - // We are behind the pruning horizon. - LaggingBehindHorizon(ChainMetadata, Vec), UpToDate, } @@ -107,13 +109,6 @@ impl Display for SyncStatus { m.height_of_longest_chain(), m.accumulated_difficulty(), ), - LaggingBehindHorizon(m, v) => write!( - f, - "Lagging behind pruning horizon ({} peer(s), Network height: #{}, Difficulty: {})", - v.len(), - m.height_of_longest_chain(), - m.accumulated_difficulty(), - ), UpToDate => f.write_str("UpToDate"), } } @@ -123,18 +118,20 @@ impl Display for StateEvent { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { use StateEvent::*; match self { - Initialized => f.write_str("Initialized"), - BlocksSynchronized => f.write_str("Synchronised Blocks"), - HeadersSynchronized(conn) => write!(f, "Headers Synchronized from peer `{}`", conn.peer_node_id()), - HeaderSyncFailed => f.write_str("Header Synchronization Failed"), - HorizonStateSynchronized => f.write_str("Horizon State Synchronized"), - HorizonStateSyncFailure => f.write_str("Horizon State Synchronization Failed"), - BlockSyncFailed => f.write_str("Block Synchronization Failed"), + Initialized => write!(f, "Initialized"), + BlocksSynchronized => write!(f, "Synchronised Blocks"), + HeadersSynchronized(peer) => write!(f, "Headers Synchronized from peer `{}`", peer), + HeaderSyncFailed => write!(f, "Header Synchronization Failed"), + ProceedToHorizonSync(_) => write!(f, "Proceed to horizon sync"), + ProceedToBlockSync(_) => write!(f, "Proceed to block sync"), + HorizonStateSynchronized => write!(f, "Horizon State Synchronized"), + HorizonStateSyncFailure => write!(f, "Horizon State Synchronization Failed"), + BlockSyncFailed => write!(f, "Block Synchronization Failed"), FallenBehind(s) => write!(f, "Fallen behind main chain - {}", s), - NetworkSilence => f.write_str("Network Silence"), - Continue => f.write_str("Continuing"), + NetworkSilence => write!(f, "Network Silence"), + Continue => write!(f, "Continuing"), FatalError(e) => write!(f, "Fatal Error - {}", e), - UserQuit => f.write_str("User Termination"), + UserQuit => write!(f, "User Termination"), } } } @@ -145,6 +142,7 @@ impl Display for BaseNodeState { let s = match self { Starting(_) => "Initializing", HeaderSync(_) => "Synchronizing block headers", + DecideNextSync(_) => "Deciding next sync", HorizonStateSync(_) => "Synchronizing horizon state", BlockSync(_) => "Synchronizing blocks", Listening(_) => "Listening", diff --git a/base_layer/core/src/base_node/state_machine_service/states/header_sync.rs b/base_layer/core/src/base_node/state_machine_service/states/header_sync.rs index 44fc90b19f..08c602cc66 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/header_sync.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/header_sync.rs @@ -30,7 +30,7 @@ use crate::{ chain_storage::BlockchainBackend, }; use log::*; -use std::time::Instant; +use std::{cmp::Ordering, time::Instant}; const LOG_TARGET: &str = "c::bn::header_sync"; @@ -41,7 +41,15 @@ pub struct HeaderSync { } impl HeaderSync { - pub fn new(sync_peers: Vec) -> Self { + pub fn new(mut sync_peers: Vec) -> Self { + // Sort by latency lowest to highest + sync_peers.sort_by(|a, b| match (a.latency(), b.latency()) { + (None, None) => Ordering::Equal, + // No latency goes to the end + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (Some(la), Some(lb)) => la.cmp(&lb), + }); Self { sync_peers, is_synced: false, @@ -52,6 +60,10 @@ impl HeaderSync { self.is_synced } + pub fn into_sync_peers(self) -> Vec { + self.sync_peers + } + pub async fn next_event( &mut self, shared: &mut BaseNodeStateMachine, diff --git a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync.rs b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync.rs index c1b8412028..ca2161e033 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync.rs @@ -29,9 +29,12 @@ use log::*; pub use error::HorizonSyncError; use horizon_state_synchronization::HorizonStateSynchronization; -use tari_comms::PeerConnection; -use crate::{base_node::BaseNodeStateMachine, chain_storage::BlockchainBackend, transactions::CryptoFactories}; +use crate::{ + base_node::{sync::SyncPeer, BaseNodeStateMachine}, + chain_storage::BlockchainBackend, + transactions::CryptoFactories, +}; use super::{ events_and_states::{HorizonSyncInfo, HorizonSyncStatus}, @@ -51,14 +54,18 @@ const LOG_TARGET: &str = "c::bn::state_machine_service::states::horizon_state_sy #[derive(Clone, Debug)] pub struct HorizonStateSync { - sync_peer: PeerConnection, + sync_peer: SyncPeer, } impl HorizonStateSync { - pub fn with_peer(sync_peer: PeerConnection) -> Self { + pub fn new(sync_peer: SyncPeer) -> Self { Self { sync_peer } } + pub fn into_sync_peer(self) -> SyncPeer { + self.sync_peer + } + pub async fn next_event( &mut self, shared: &mut BaseNodeStateMachine, @@ -83,12 +90,11 @@ impl HorizonStateSync { return StateEvent::HorizonStateSynchronized; } - let info = HorizonSyncInfo::new(vec![self.sync_peer.peer_node_id().clone()], HorizonSyncStatus::Starting); + let info = HorizonSyncInfo::new(vec![self.sync_peer.node_id().clone()], HorizonSyncStatus::Starting); shared.set_state_info(StateInfo::HorizonSync(info)); let prover = CryptoFactories::default().range_proof; - let mut horizon_state = - HorizonStateSynchronization::new(shared, self.sync_peer.clone(), horizon_sync_height, &prover); + let mut horizon_state = HorizonStateSynchronization::new(shared, &self.sync_peer, horizon_sync_height, &prover); match horizon_state.synchronize().await { Ok(()) => { @@ -102,3 +108,9 @@ impl HorizonStateSync { } } } + +impl From for HorizonStateSync { + fn from(sync_peer: SyncPeer) -> Self { + Self { sync_peer } + } +} diff --git a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/error.rs b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/error.rs index 4669bdce09..adb65def9f 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/error.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/error.rs @@ -27,7 +27,10 @@ use crate::{ validation::ValidationError, }; use std::num::TryFromIntError; -use tari_comms::protocol::rpc::{RpcError, RpcStatus}; +use tari_comms::{ + connectivity::ConnectivityError, + protocol::rpc::{RpcError, RpcStatus}, +}; use tari_mmr::error::MerkleMountainRangeError; use thiserror::Error; use tokio::task; @@ -36,8 +39,6 @@ use tokio::task; pub enum HorizonSyncError { #[error("Peer sent an invalid response: {0}")] IncorrectResponse(String), - // #[error("Exceeded maximum sync attempts")] - // MaxSyncAttemptsReached, #[error("Chain storage error: {0}")] ChainStorageError(#[from] ChainStorageError), #[error("Comms interface error: {0}")] @@ -67,6 +68,8 @@ pub enum HorizonSyncError { ConversionError(String), #[error("MerkleMountainRangeError: {0}")] MerkleMountainRangeError(#[from] MerkleMountainRangeError), + #[error("Connectivity Error: {0}")] + ConnectivityError(#[from] ConnectivityError), } impl From for HorizonSyncError { diff --git a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs index 7e10d59745..52c1556bb6 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs @@ -27,7 +27,7 @@ use crate::{ states::events_and_states::{HorizonSyncInfo, HorizonSyncStatus, StateInfo}, BaseNodeStateMachine, }, - sync::rpc, + sync::{rpc, SyncPeer}, }, blocks::BlockHeader, chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, ChainStorageError, MmrTree, PrunedOutput}, @@ -49,7 +49,6 @@ use std::{ sync::Arc, }; use tari_common_types::types::{HashDigest, RangeProofService}; -use tari_comms::PeerConnection; use tari_crypto::{ commitment::HomomorphicCommitment, tari_utilities::{hex::Hex, Hashable}, @@ -60,7 +59,7 @@ const LOG_TARGET: &str = "c::bn::state_machine_service::states::horizon_state_sy pub struct HorizonStateSynchronization<'a, B: BlockchainBackend> { shared: &'a mut BaseNodeStateMachine, - sync_peer: PeerConnection, + sync_peer: &'a SyncPeer, horizon_sync_height: u64, prover: &'a RangeProofService, num_kernels: u64, @@ -70,7 +69,7 @@ pub struct HorizonStateSynchronization<'a, B: BlockchainBackend> { impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { pub fn new( shared: &'a mut BaseNodeStateMachine, - sync_peer: PeerConnection, + sync_peer: &'a SyncPeer, horizon_sync_height: u64, prover: &'a RangeProofService, ) -> Self { @@ -97,7 +96,12 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { } })?; - let mut client = self.sync_peer.connect_rpc::().await?; + let mut connection = self + .shared + .connectivity + .dial_peer(self.sync_peer.node_id().clone()) + .await?; + let mut client = connection.connect_rpc::().await?; match self.begin_sync(&mut client, &header).await { Ok(_) => match self.finalize_horizon_sync().await { @@ -142,7 +146,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { } let info = HorizonSyncInfo::new( - vec![self.sync_peer.peer_node_id().clone()], + vec![self.sync_peer.node_id().clone()], HorizonSyncStatus::Kernels(local_num_kernels, remote_num_kernels), ); self.shared.set_state_info(StateInfo::HorizonSync(info)); @@ -158,9 +162,8 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { let latency = client.get_last_request_latency().await?; debug!( target: LOG_TARGET, - "Initiating kernel sync with peer `{}` `{}` (latency = {}ms)", - self.sync_peer.peer_node_id(), - self.sync_peer.address(), + "Initiating kernel sync with peer `{}` (latency = {}ms)", + self.sync_peer.node_id(), latency.unwrap_or_default().as_millis() ); @@ -236,7 +239,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { if mmr_position % 100 == 0 || mmr_position == self.num_kernels { let info = HorizonSyncInfo::new( - vec![self.sync_peer.peer_node_id().clone()], + vec![self.sync_peer.node_id().clone()], HorizonSyncStatus::Kernels(mmr_position, self.num_kernels), ); self.shared.set_state_info(StateInfo::HorizonSync(info)); @@ -267,7 +270,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { } let info = HorizonSyncInfo::new( - vec![self.sync_peer.peer_node_id().clone()], + vec![self.sync_peer.node_id().clone()], HorizonSyncStatus::Outputs(local_num_outputs, self.num_outputs), ); self.shared.set_state_info(StateInfo::HorizonSync(info)); @@ -288,7 +291,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { debug!( target: LOG_TARGET, "Initiating output sync with peer `{}` (latency = {}ms)", - self.sync_peer.peer_node_id(), + self.sync_peer.node_id(), latency.unwrap_or_default().as_millis() ); @@ -425,14 +428,14 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { peer {}", diff_bitmap.len(), MAX_DIFF_BITMAP_BYTE_LEN, - self.sync_peer.peer_node_id() + self.sync_peer.node_id() ))); } let diff_bitmap = Bitmap::try_deserialize(&diff_bitmap).ok_or_else(|| { HorizonSyncError::IncorrectResponse(format!( "Peer {} sent an invalid difference bitmap", - self.sync_peer.peer_node_id() + self.sync_peer.node_id() )) })?; @@ -498,7 +501,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { if mmr_position % 100 == 0 || mmr_position == self.num_outputs { let info = HorizonSyncInfo::new( - vec![self.sync_peer.peer_node_id().clone()], + vec![self.sync_peer.node_id().clone()], HorizonSyncStatus::Outputs(mmr_position, self.num_outputs), ); self.shared.set_state_info(StateInfo::HorizonSync(info)); @@ -518,10 +521,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { async fn finalize_horizon_sync(&mut self) -> Result<(), HorizonSyncError> { debug!(target: LOG_TARGET, "Validating horizon state"); - let info = HorizonSyncInfo::new( - vec![self.sync_peer.peer_node_id().clone()], - HorizonSyncStatus::Finalizing, - ); + let info = HorizonSyncInfo::new(vec![self.sync_peer.node_id().clone()], HorizonSyncStatus::Finalizing); self.shared.set_state_info(StateInfo::HorizonSync(info)); let header = self.db().fetch_chain_header(self.horizon_sync_height).await?; diff --git a/base_layer/core/src/base_node/state_machine_service/states/listening.rs b/base_layer/core/src/base_node/state_machine_service/states/listening.rs index b0899a1978..7afea9bd4a 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/listening.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/listening.rs @@ -24,7 +24,16 @@ use crate::{ base_node::{ chain_metadata_service::{ChainMetadataEvent, PeerChainMetadata}, state_machine_service::{ - states::{BlockSync, HeaderSync, StateEvent, StateEvent::FatalError, StateInfo, SyncStatus, Waiting}, + states::{ + BlockSync, + DecideNextSync, + HeaderSync, + StateEvent, + StateEvent::FatalError, + StateInfo, + SyncStatus, + Waiting, + }, BaseNodeStateMachine, }, }, @@ -156,7 +165,7 @@ impl Listening { // Find the best network metadata and set of sync peers with the best tip. let best_metadata = match best_claimed_metadata(&peer_metadata_list) { - Some(m) => m.clone(), + Some(m) => m, None => { debug!( target: LOG_TARGET, @@ -196,11 +205,18 @@ impl Listening { // If we have configured sync peers, they are already filtered at this point let sync_peers = if configured_sync_peers.is_empty() { - select_sync_peers(&local, &best_metadata, &peer_metadata_list) + select_sync_peers(&best_metadata, &peer_metadata_list) } else { peer_metadata_list }; + let local = match shared.db.get_chain_metadata().await { + Ok(m) => m, + Err(e) => { + return FatalError(format!("Could not get local blockchain metadata. {}", e)); + }, + }; + let sync_mode = determine_sync_mode( shared.config.blocks_behind_before_considered_lagging, &local, @@ -258,10 +274,15 @@ impl From for Listening { } } +impl From for Listening { + fn from(_: DecideNextSync) -> Self { + Self { is_synced: false } + } +} + // Finds the set of sync peers that have the best tip on their main chain and have all the data required to update the // local node. fn select_sync_peers<'a>( - local_metadata: &ChainMetadata, best_metadata: &ChainMetadata, peer_metadata_list: &[&'a PeerChainMetadata], ) -> Vec<&'a PeerChainMetadata> { @@ -269,16 +290,7 @@ fn select_sync_peers<'a>( .iter() // Check if the peer can provide blocks higher than the local tip height .filter(|peer| { - let chain_metadata = peer.claimed_chain_metadata(); - - // If we synced from this peer, do they claim to be able to provide full blocks up until our pruned height? - let our_pruned_height_from_peer = local_metadata.horizon_block(chain_metadata.height_of_longest_chain()); - let their_pruned_height= chain_metadata.pruned_height(); - if our_pruned_height_from_peer < their_pruned_height { - return false; - } - - chain_metadata.best_block() == best_metadata.best_block() + peer.claimed_chain_metadata().best_block() == best_metadata.best_block() }) // &T is a Copy type .copied() @@ -303,7 +315,7 @@ fn best_claimed_metadata<'a>(metadata_list: &[&'a PeerChainMetadata]) -> Option< fn determine_sync_mode( blocks_behind_before_considered_lagging: u64, local: &ChainMetadata, - network: ChainMetadata, + network: &ChainMetadata, sync_peers: Vec<&PeerChainMetadata>, ) -> SyncStatus { use SyncStatus::*; @@ -322,7 +334,6 @@ fn determine_sync_mode( network_tip_height, network_tip_accum_difficulty.to_formatted_string(&Locale::en), ); - let network_horizon_block = local.horizon_block(network_tip_height); // This is to test the block propagation by delaying lagging. if local_tip_height + blocks_behind_before_considered_lagging > network_tip_height && @@ -338,13 +349,11 @@ fn determine_sync_mode( }; let sync_peers = sync_peers.into_iter().cloned().collect(); - if local_tip_height < network_horizon_block { - debug!(target: LOG_TARGET, "Lagging behind horizon"); - LaggingBehindHorizon(network, sync_peers) - } else { - debug!(target: LOG_TARGET, "Lagging"); - Lagging(network, sync_peers) - } + debug!( + target: LOG_TARGET, + "Lagging (local height = {}, network height = {})", local_tip_height, network_tip_height + ); + Lagging(network.clone(), sync_peers) } else { info!( target: LOG_TARGET, @@ -383,9 +392,8 @@ mod test { let best_network_metadata = best_claimed_metadata(&peer_metadata_list); assert!(best_network_metadata.is_none()); let best_network_metadata = ChainMetadata::empty(); - let local_metadata = ChainMetadata::new(network_tip_height, Vec::new(), 501, 0, 0); assert_eq!(best_network_metadata, ChainMetadata::new(0, Vec::new(), 0, 0, 0)); - let sync_peers = select_sync_peers(&local_metadata, &best_network_metadata, &peer_metadata_list); + let sync_peers = select_sync_peers(&best_network_metadata, &peer_metadata_list); assert_eq!(sync_peers.len(), 0); let node_id1 = random_node_id(); @@ -397,6 +405,7 @@ mod test { let peer1 = PeerChainMetadata::new( node_id1.clone(), ChainMetadata::new(network_tip_height, block_hash1.clone(), 0, 0, accumulated_difficulty1), + None, ); // Pruning horizon is to short to sync from @@ -409,6 +418,7 @@ mod test { 5000 - 500, accumulated_difficulty1, ), + None, ); let peer3 = PeerChainMetadata::new( @@ -420,6 +430,7 @@ mod test { 5000 - 1440, accumulated_difficulty1, ), + None, ); let peer4 = PeerChainMetadata::new( node_id4, @@ -430,6 +441,7 @@ mod test { 5000 - 2880, accumulated_difficulty2, ), + None, ); // Node running a fork let peer5 = PeerChainMetadata::new( @@ -441,6 +453,7 @@ mod test { 5000 - 2880, accumulated_difficulty1, ), + None, ); peer_metadata_list.push(&peer1); peer_metadata_list.push(&peer2); @@ -452,8 +465,8 @@ mod test { assert_eq!(best_network_metadata.height_of_longest_chain(), network_tip_height); assert_eq!(best_network_metadata.best_block(), &block_hash1); assert_eq!(best_network_metadata.accumulated_difficulty(), accumulated_difficulty1); - let sync_peers = select_sync_peers(&local_metadata, best_network_metadata, &peer_metadata_list); - assert_eq!(sync_peers.len(), 3); + let sync_peers = select_sync_peers(best_network_metadata, &peer_metadata_list); + assert_eq!(sync_peers.len(), 4); sync_peers.iter().find(|p| *p.node_id() == node_id1).unwrap(); sync_peers.iter().find(|p| *p.node_id() == node_id3).unwrap(); sync_peers.iter().find(|p| *p.node_id() == node_id5).unwrap(); @@ -462,41 +475,41 @@ mod test { #[test] fn sync_mode_selection() { let local = ChainMetadata::new(0, Vec::new(), 0, 0, 500_000); - match determine_sync_mode(0, &local, local.clone(), vec![]) { + match determine_sync_mode(0, &local, &local, vec![]) { SyncStatus::UpToDate => {}, _ => panic!(), } let network = ChainMetadata::new(0, Vec::new(), 0, 0, 499_000); - match determine_sync_mode(0, &local, network, vec![]) { + match determine_sync_mode(0, &local, &network, vec![]) { SyncStatus::UpToDate => {}, _ => panic!(), } let network = ChainMetadata::new(0, Vec::new(), 0, 0, 500_001); - match determine_sync_mode(0, &local, network.clone(), vec![]) { + match determine_sync_mode(0, &local, &network, vec![]) { SyncStatus::Lagging(n, _) => assert_eq!(n, network), _ => panic!(), } let local = ChainMetadata::new(100, Vec::new(), 50, 50, 500_000); let network = ChainMetadata::new(150, Vec::new(), 0, 0, 500_001); - match determine_sync_mode(0, &local, network.clone(), vec![]) { + match determine_sync_mode(0, &local, &network, vec![]) { SyncStatus::Lagging(n, _) => assert_eq!(n, network), _ => panic!(), } let local = ChainMetadata::new(0, Vec::new(), 50, 50, 500_000); let network = ChainMetadata::new(100, Vec::new(), 0, 0, 500_001); - match determine_sync_mode(0, &local, network.clone(), vec![]) { - SyncStatus::LaggingBehindHorizon(n, _) => assert_eq!(n, network), + match determine_sync_mode(0, &local, &network, vec![]) { + SyncStatus::Lagging(n, _) => assert_eq!(n, network), _ => panic!(), } let local = ChainMetadata::new(99, Vec::new(), 50, 50, 500_000); let network = ChainMetadata::new(150, Vec::new(), 0, 0, 500_001); - match determine_sync_mode(0, &local, network.clone(), vec![]) { - SyncStatus::LaggingBehindHorizon(n, _) => assert_eq!(n, network), + match determine_sync_mode(0, &local, &network, vec![]) { + SyncStatus::Lagging(n, _) => assert_eq!(n, network), _ => panic!(), } } diff --git a/base_layer/core/src/base_node/state_machine_service/states/mod.rs b/base_layer/core/src/base_node/state_machine_service/states/mod.rs index ebc82ee7dc..c111149f0b 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/mod.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/mod.rs @@ -69,9 +69,8 @@ pub use helpers::SyncPeerConfig; mod header_sync; pub use header_sync::HeaderSync; -// TODO: Consolidate with header sync -// mod horizon_header_sync; -// pub use horizon_header_sync::HorizonHeaderSync; +mod sync_decide; +pub use sync_decide::DecideNextSync; mod horizon_state_sync; pub use horizon_state_sync::{HorizonStateSync, HorizonSyncConfig}; diff --git a/base_layer/core/src/base_node/state_machine_service/states/sync_decide.rs b/base_layer/core/src/base_node/state_machine_service/states/sync_decide.rs new file mode 100644 index 0000000000..fb7c85673b --- /dev/null +++ b/base_layer/core/src/base_node/state_machine_service/states/sync_decide.rs @@ -0,0 +1,130 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + base_node::{ + state_machine_service::{ + states::{HeaderSync, StateEvent}, + BaseNodeStateMachine, + }, + sync::SyncPeer, + }, + chain_storage::BlockchainBackend, +}; +use log::*; + +const LOG_TARGET: &str = "c::bn::state_machine_service::states::sync_decide"; + +#[derive(Clone, Debug, PartialEq)] +pub struct DecideNextSync { + sync_peers: Vec, +} + +impl DecideNextSync { + pub async fn next_event(&mut self, shared: &BaseNodeStateMachine) -> StateEvent { + use StateEvent::*; + let local_metadata = match shared.db.get_chain_metadata().await { + Ok(m) => m, + Err(e) => { + return FatalError(format!("Could not get local blockchain metadata. {}", e)); + }, + }; + + debug!( + target: LOG_TARGET, + "Selecting a suitable sync peer from {} peer(s)", + self.sync_peers.len() + ); + + if shared.config.pruning_horizon > 0 { + // Filter sync peers that claim to be able to provide full blocks up until our pruned height + let sync_peers_iter = self.sync_peers.iter().filter(|sync_peer| { + let chain_metadata = sync_peer.claimed_chain_metadata(); + let our_pruned_height_from_peer = + local_metadata.horizon_block(chain_metadata.height_of_longest_chain()); + let their_pruned_height = chain_metadata.pruned_height(); + our_pruned_height_from_peer >= their_pruned_height + }); + + match find_best_latency(sync_peers_iter) { + Some(sync_peer) => ProceedToHorizonSync(sync_peer), + None => Continue, + } + } else { + // Filter sync peers that are able to provide full blocks from our current tip + let sync_peers_iter = self.sync_peers.iter().filter(|sync_peer| { + sync_peer.claimed_chain_metadata().pruning_horizon() <= local_metadata.height_of_longest_chain() + }); + + match find_best_latency(sync_peers_iter) { + Some(sync_peer) => ProceedToBlockSync(sync_peer), + None => Continue, + } + } + } +} + +/// Find the peer with the best latency +fn find_best_latency<'a, I: IntoIterator>(iter: I) -> Option { + iter.into_iter() + .fold(Option::<&'a SyncPeer>::None, |current, sync_peer| match current { + Some(p) => match (p.latency(), sync_peer.latency()) { + (Some(_), None) => Some(p), + (None, Some(_)) => Some(sync_peer), + (Some(current), Some(latency)) if current > latency => Some(sync_peer), + (Some(_), Some(_)) | (None, None) => current, + }, + None => Some(sync_peer), + }) + .cloned() +} + +impl From for DecideNextSync { + fn from(sync: HeaderSync) -> Self { + Self { + sync_peers: sync.into_sync_peers(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use rand::{rngs::OsRng, seq::SliceRandom}; + use tari_common_types::chain_metadata::ChainMetadata; + + mod find_best_latency { + use super::*; + + #[test] + fn it_selects_the_best_latency() { + let peers = (0..10) + .map(|i| SyncPeer::new(Default::default(), ChainMetadata::empty(), Some(i))) + .chain(Some(SyncPeer::new(Default::default(), ChainMetadata::empty(), None))) + .collect::>(); + let mut shuffled = peers.clone(); + shuffled.shuffle(&mut OsRng); + let selected = find_best_latency(shuffled.iter()); + assert_eq!(selected, peers.first().cloned()); + } + } +} diff --git a/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs index 7cf95f2d4f..b17da13bc3 100644 --- a/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs @@ -23,7 +23,7 @@ use super::error::BlockSyncError; use crate::{ base_node::{ - sync::{hooks::Hooks, rpc}, + sync::{hooks::Hooks, rpc, SyncPeer}, BlockSyncConfig, }, blocks::{Block, ChainBlock}, @@ -41,11 +41,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use tari_comms::{ - connectivity::{ConnectivityRequester, ConnectivitySelection}, - peer_manager::NodeId, - PeerConnection, -}; +use tari_comms::{connectivity::ConnectivityRequester, peer_manager::NodeId, PeerConnection}; use tracing; const LOG_TARGET: &str = "c::bn::block_sync"; @@ -54,7 +50,7 @@ pub struct BlockSynchronizer { config: BlockSyncConfig, db: AsyncBlockchainDb, connectivity: ConnectivityRequester, - sync_peer: Option, + sync_peer: SyncPeer, block_validator: Arc, hooks: Hooks, } @@ -64,7 +60,7 @@ impl BlockSynchronizer { config: BlockSyncConfig, db: AsyncBlockchainDb, connectivity: ConnectivityRequester, - sync_peer: Option, + sync_peer: SyncPeer, block_validator: Arc, ) -> Self { Self { @@ -89,7 +85,7 @@ impl BlockSynchronizer { #[tracing::instrument(skip(self), err)] pub async fn synchronize(&mut self) -> Result<(), BlockSyncError> { - let peer_conn = self.get_next_sync_peer().await?; + let peer_conn = self.connect_to_sync_peer().await?; let node_id = peer_conn.peer_node_id().clone(); info!( target: LOG_TARGET, @@ -108,20 +104,9 @@ impl BlockSynchronizer { } } - async fn get_next_sync_peer(&mut self) -> Result { - match self.sync_peer { - Some(ref peer) => Ok(peer.clone()), - None => { - let mut peers = self - .connectivity - .select_connections(ConnectivitySelection::random_nodes(1, vec![])) - .await?; - if peers.is_empty() { - return Err(BlockSyncError::NoSyncPeers); - } - Ok(peers.remove(0)) - }, - } + async fn connect_to_sync_peer(&mut self) -> Result { + let connection = self.connectivity.dial_peer(self.sync_peer.node_id().clone()).await?; + Ok(connection) } async fn attempt_block_sync(&mut self, mut conn: PeerConnection) -> Result<(), BlockSyncError> { diff --git a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs index c5a69a4ea0..4ff044218f 100644 --- a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs @@ -34,7 +34,7 @@ use crate::{ tari_utilities::{hex::Hex, Hashable}, validation::ValidationError, }; -use futures::{future, stream::FuturesUnordered, StreamExt, TryFutureExt}; +use futures::StreamExt; use log::*; use std::{ convert::TryFrom, @@ -97,25 +97,25 @@ impl<'a, B: BlockchainBackend + 'static> HeaderSynchronizer<'a, B> { self.hooks.add_on_rewind_hook(hook); } - pub async fn synchronize(&mut self) -> Result { + pub async fn synchronize(&mut self) -> Result { debug!(target: LOG_TARGET, "Starting header sync.",); self.hooks.call_on_starting_hook(); - let sync_peers = self.select_sync_peers().await?; info!( target: LOG_TARGET, "Synchronizing headers ({} candidate peers selected)", - sync_peers.len() + self.sync_peers.len() ); - for (sync_peer, peer_conn) in sync_peers { + for sync_peer in self.sync_peers { + let peer_conn = self.dial_sync_peer(sync_peer).await?; let node_id = peer_conn.peer_node_id().clone(); debug!( target: LOG_TARGET, "Attempting to synchronize headers with `{}`", node_id ); - match self.attempt_sync(&sync_peer, peer_conn.clone()).await { - Ok(()) => return Ok(peer_conn), + match self.attempt_sync(sync_peer, peer_conn).await { + Ok(()) => return Ok(sync_peer.clone()), // Try another peer Err(err @ BlockHeaderSyncError::NotInSync) => { warn!(target: LOG_TARGET, "{}", err); @@ -169,36 +169,17 @@ impl<'a, B: BlockchainBackend + 'static> HeaderSynchronizer<'a, B> { Err(BlockHeaderSyncError::SyncFailedAllPeers) } - async fn select_sync_peers(&self) -> Result, BlockHeaderSyncError> { - let tasks = self - .sync_peers - .iter() - .cloned() - .map(|sync_peer| { - debug!(target: LOG_TARGET, "Dialing {} sync peer", sync_peer.node_id()); - self.connectivity - .dial_peer(sync_peer.node_id().clone()) - .and_then(|conn| future::ready(Ok((sync_peer, conn)))) - }) - .collect::>(); - - let connections = tasks - .filter_map(|r| match r { - Ok((sync_peer, conn)) => future::ready(Some((sync_peer, conn))), - Err(err) => { - debug!(target: LOG_TARGET, "Failed to dial sync peer: {}", err); - future::ready(None) - }, - }) - .collect::>() - .await; + async fn dial_sync_peer(&self, sync_peer: &SyncPeer) -> Result { + let timer = Instant::now(); + debug!(target: LOG_TARGET, "Dialing {} sync peer", sync_peer.node_id()); + let conn = self.connectivity.dial_peer(sync_peer.node_id().clone()).await?; info!( target: LOG_TARGET, - "Successfully dialed {} of {} sync peer(s)", - connections.len(), - self.sync_peers.len() + "Successfully dialed sync peer {} in {:.2?}", + sync_peer, + timer.elapsed() ); - Ok(connections) + Ok(conn) } async fn ban_peer_long(&mut self, node_id: NodeId, reason: BanReason) -> Result<(), BlockHeaderSyncError> { diff --git a/base_layer/core/tests/helpers/chain_metadata.rs b/base_layer/core/tests/helpers/chain_metadata.rs index f55365ddf4..df5c1adbac 100644 --- a/base_layer/core/tests/helpers/chain_metadata.rs +++ b/base_layer/core/tests/helpers/chain_metadata.rs @@ -64,7 +64,7 @@ impl MockChainMetadata { id: &NodeId, metadata: &ChainMetadata, ) -> Result> { - let data = PeerChainMetadata::new(id.clone(), metadata.clone()); + let data = PeerChainMetadata::new(id.clone(), metadata.clone(), None); self.publish_event(ChainMetadataEvent::PeerChainMetadataReceived(vec![data])) } } @@ -75,5 +75,5 @@ pub fn random_peer_metadata(height: u64, difficulty: u128) -> PeerChainMetadata let id = NodeId::from_key(&key); let block_hash = Blake256::digest(id.as_bytes()).to_vec(); let metadata = ChainMetadata::new(height, block_hash, 2800, 0, difficulty); - PeerChainMetadata::new(id, metadata) + PeerChainMetadata::new(id, metadata, None) } diff --git a/base_layer/p2p/src/services/liveness/state.rs b/base_layer/p2p/src/services/liveness/state.rs index f61f14a295..d8b5b29d20 100644 --- a/base_layer/p2p/src/services/liveness/state.rs +++ b/base_layer/p2p/src/services/liveness/state.rs @@ -50,6 +50,10 @@ impl Metadata { pub fn get(&self, key: MetadataKey) -> Option<&Vec> { self.inner.get(&(key as i32)) } + + pub fn has(&self, key: MetadataKey) -> bool { + self.inner.contains_key(&(key as i32)) + } } impl From>> for Metadata { From d8e0cedc19ee008a8dd937347d7e2fc5e7fc4c3f Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Tue, 9 Nov 2021 09:20:51 +0200 Subject: [PATCH 03/46] feat: add ffi get mnemonic wordlist (#3538) Description --- Added an FFI method to get the entire mnemonic word list for a given language. Motivation and Context --- See above. How Has This Been Tested? --- - Added a unit test to test this functionality. - Added a cucumber test to verify the mnemonic word list is retrieved. --- Cargo.lock | 29 +- base_layer/key_manager/Cargo.toml | 2 + base_layer/key_manager/src/mnemonic.rs | 40 +- base_layer/wallet_ffi/src/enums.rs | 1 + base_layer/wallet_ffi/src/error.rs | 6 + base_layer/wallet_ffi/src/lib.rs | 153 +- base_layer/wallet_ffi/wallet.h | 3 + integration_tests/features/WalletFFI.feature | 12 + .../features/support/ffi_steps.js | 10 + integration_tests/helpers/ffi/ffiInterface.js | 17 + integration_tests/helpers/ffi/seedWords.js | 50 + integration_tests/helpers/ffi/wallet.js | 1 - integration_tests/helpers/walletClient.js | 4 +- integration_tests/helpers/walletFFIClient.js | 4 + integration_tests/package-lock.json | 4935 ++--------------- 15 files changed, 731 insertions(+), 4536 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc43d83396..0c0388b1e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4175,6 +4175,15 @@ version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89a286a7e3b5720b9a477b23253bc50debac207c8d21505f8e70b36792f11b5" +[[package]] +name = "strum" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e" +dependencies = [ + "strum_macros 0.22.0", +] + [[package]] name = "strum_macros" version = "0.17.1" @@ -4211,6 +4220,18 @@ dependencies = [ "syn 1.0.81", ] +[[package]] +name = "strum_macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb" +dependencies = [ + "heck", + "proc-macro2 1.0.32", + "quote 1.0.10", + "syn 1.0.81", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4310,7 +4331,7 @@ dependencies = [ "rand 0.8.4", "serde_json", "structopt", - "strum", + "strum 0.19.5", "strum_macros 0.19.4", "tari_common", "tari_common_types", @@ -4340,7 +4361,7 @@ dependencies = [ "regex", "rustyline", "rustyline-derive", - "strum", + "strum 0.19.5", "strum_macros 0.18.0", "tari_app_grpc", "tari_app_utilities", @@ -4553,7 +4574,7 @@ dependencies = [ "regex", "rpassword", "rustyline", - "strum", + "strum 0.19.5", "strum_macros 0.19.4", "tari_app_grpc", "tari_app_utilities", @@ -4683,6 +4704,8 @@ dependencies = [ "digest", "rand 0.8.4", "sha2", + "strum 0.22.0", + "strum_macros 0.22.0", "tari_crypto", "thiserror", ] diff --git a/base_layer/key_manager/Cargo.toml b/base_layer/key_manager/Cargo.toml index 878b1d1ef4..237d5791c2 100644 --- a/base_layer/key_manager/Cargo.toml +++ b/base_layer/key_manager/Cargo.toml @@ -20,6 +20,8 @@ crc32fast = "1.2.1" rand = "0.8" digest = "0.9.0" thiserror = "1.0.26" +strum_macros = "0.22" +strum = { version = "0.22", features = ["derive"] } [dev-dependencies] sha2 = "0.9.8" diff --git a/base_layer/key_manager/src/mnemonic.rs b/base_layer/key_manager/src/mnemonic.rs index 14a1994fb9..4226c8c250 100644 --- a/base_layer/key_manager/src/mnemonic.rs +++ b/base_layer/key_manager/src/mnemonic.rs @@ -26,13 +26,14 @@ use crate::{ mnemonic_wordlists::*, }; use std::slice::Iter; +use strum_macros::{Display, EnumString}; use tari_crypto::tari_utilities::bit::*; /// The Mnemonic system simplifies the encoding and decoding of a secret key into and from a Mnemonic word sequence /// It can autodetect the language of the Mnemonic word sequence // TODO: Develop a language autodetection mechanism to distinguish between ChineseTraditional and ChineseSimplified -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, EnumString, Display)] pub enum MnemonicLanguage { ChineseSimplified, English, @@ -67,6 +68,19 @@ impl MnemonicLanguage { ]; MNEMONIC_LANGUAGES.iter() } + + /// Returns the mnemonic word list count for the specified language + pub fn word_count(language: &MnemonicLanguage) -> usize { + match language { + MnemonicLanguage::ChineseSimplified => MNEMONIC_CHINESE_SIMPLIFIED_WORDS.len(), + MnemonicLanguage::English => MNEMONIC_ENGLISH_WORDS.len(), + MnemonicLanguage::French => MNEMONIC_FRENCH_WORDS.len(), + MnemonicLanguage::Italian => MNEMONIC_ITALIAN_WORDS.len(), + MnemonicLanguage::Japanese => MNEMONIC_JAPANESE_WORDS.len(), + MnemonicLanguage::Korean => MNEMONIC_KOREAN_WORDS.len(), + MnemonicLanguage::Spanish => MNEMONIC_SPANISH_WORDS.len(), + } + } } /// Finds and returns the index of a specific word in a mnemonic word list defined by the specified language @@ -193,6 +207,7 @@ mod test { use super::*; use crate::mnemonic; use rand::{self, rngs::OsRng}; + use std::str::FromStr; use tari_crypto::{keys::SecretKey, ristretto::RistrettoSecretKey, tari_utilities::byte_array::ByteArray}; #[test] @@ -211,6 +226,29 @@ mod test { } } + #[test] + fn test_string_to_enum_conversion() { + let my_enum = MnemonicLanguage::from_str("ChineseSimplified").unwrap(); + assert_eq!(my_enum, MnemonicLanguage::ChineseSimplified); + let my_enum = MnemonicLanguage::from_str("English").unwrap(); + assert_eq!(my_enum, MnemonicLanguage::English); + let my_enum = MnemonicLanguage::from_str("French").unwrap(); + assert_eq!(my_enum, MnemonicLanguage::French); + let my_enum = MnemonicLanguage::from_str("Italian").unwrap(); + assert_eq!(my_enum, MnemonicLanguage::Italian); + let my_enum = MnemonicLanguage::from_str("Japanese").unwrap(); + assert_eq!(my_enum, MnemonicLanguage::Japanese); + let my_enum = MnemonicLanguage::from_str("Korean").unwrap(); + assert_eq!(my_enum, MnemonicLanguage::Korean); + let my_enum = MnemonicLanguage::from_str("Spanish").unwrap(); + assert_eq!(my_enum, MnemonicLanguage::Spanish); + let my_language = "TariVerse"; + match MnemonicLanguage::from_str(my_language) { + Ok(_) => panic!("Language '{}' is not a member of 'MnemonicLanguage'!", my_language), + Err(e) => assert_eq!(e, strum::ParseError::VariantNotFound), + } + } + #[test] fn test_language_detection() { // Test valid Mnemonic words diff --git a/base_layer/wallet_ffi/src/enums.rs b/base_layer/wallet_ffi/src/enums.rs index 8abc0d3c0a..4d54dfc38e 100644 --- a/base_layer/wallet_ffi/src/enums.rs +++ b/base_layer/wallet_ffi/src/enums.rs @@ -26,4 +26,5 @@ pub enum SeedWordPushResult { SuccessfulPush, SeedPhraseComplete, InvalidSeedPhrase, + InvalidObject, } diff --git a/base_layer/wallet_ffi/src/error.rs b/base_layer/wallet_ffi/src/error.rs index 13ed469f11..fb9a8b09da 100644 --- a/base_layer/wallet_ffi/src/error.rs +++ b/base_layer/wallet_ffi/src/error.rs @@ -52,6 +52,8 @@ pub enum InterfaceError { NetworkError(String), #[error("Emoji ID is invalid")] InvalidEmojiId, + #[error("An error has occurred due to an invalid argument: `{0}`")] + InvalidArgument(String), } /// This struct is meant to hold an error for use by FFI client applications. The error has an integer code and string @@ -90,6 +92,10 @@ impl From for LibWalletError { code: 6, message: format!("{:?}", v), }, + InterfaceError::InvalidArgument(_) => Self { + code: 7, + message: format!("{:?}", v), + }, } } } diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 326314bee8..ea9753b0c9 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -217,6 +217,7 @@ pub struct TariContacts(Vec); pub type TariContact = tari_wallet::contacts_service::storage::database::Contact; pub type TariCompletedTransaction = tari_wallet::transaction_service::storage::models::CompletedTransaction; pub type TariBalance = tari_wallet::output_manager_service::service::Balance; +pub type TariMnemonicLanguage = tari_key_manager::mnemonic::MnemonicLanguage; pub struct TariCompletedTransactions(Vec); @@ -844,6 +845,7 @@ pub unsafe extern "C" fn private_key_from_hex(key: *const c_char, error_out: *mu } /// -------------------------------------------------------------------------------------------- /// + /// ----------------------------------- Seed Words ----------------------------------------------/// /// Create an empty instance of TariSeedWords @@ -861,6 +863,77 @@ pub unsafe extern "C" fn seed_words_create() -> *mut TariSeedWords { Box::into_raw(Box::new(TariSeedWords(Vec::new()))) } +/// Create a TariSeedWords instance containing the entire mnemonic wordlist for the requested language +/// +/// ## Arguments +/// `language` - The required language as a string +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `TariSeedWords` - Returns the TariSeedWords instance containing the entire mnemonic wordlist for the +/// requested language. +/// +/// # Safety +/// The `seed_words_destroy` method must be called when finished with a TariSeedWords instance from rust to prevent a +/// memory leak +#[no_mangle] +pub unsafe extern "C" fn seed_words_get_mnemonic_word_list_for_language( + language: *const c_char, + error_out: *mut c_int, +) -> *mut TariSeedWords { + use tari_key_manager::mnemonic_wordlists; + + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + let mut mnemonic_word_list_vec = Vec::new(); + if language.is_null() { + error = LibWalletError::from(InterfaceError::NullError("mnemonic wordlist".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + } else { + let not_supported; + let language_string = match CStr::from_ptr(language).to_str() { + Ok(str) => str, + Err(e) => { + not_supported = e.to_string(); + not_supported.as_str() + }, + }; + let mnemonic_word_list = match TariMnemonicLanguage::from_str(language_string) { + Ok(language) => match language { + TariMnemonicLanguage::ChineseSimplified => mnemonic_wordlists::MNEMONIC_CHINESE_SIMPLIFIED_WORDS, + TariMnemonicLanguage::English => mnemonic_wordlists::MNEMONIC_ENGLISH_WORDS, + TariMnemonicLanguage::French => mnemonic_wordlists::MNEMONIC_FRENCH_WORDS, + TariMnemonicLanguage::Italian => mnemonic_wordlists::MNEMONIC_ITALIAN_WORDS, + TariMnemonicLanguage::Japanese => mnemonic_wordlists::MNEMONIC_JAPANESE_WORDS, + TariMnemonicLanguage::Korean => mnemonic_wordlists::MNEMONIC_KOREAN_WORDS, + TariMnemonicLanguage::Spanish => mnemonic_wordlists::MNEMONIC_SPANISH_WORDS, + }, + Err(_) => { + error!( + target: LOG_TARGET, + "Mnemonic wordlist - '{}' language not supported", language_string + ); + error = LibWalletError::from(InterfaceError::InvalidArgument(format!( + "mnemonic wordlist - '{}' language not supported", + language_string + ))) + .code; + ptr::swap(error_out, &mut error as *mut c_int); + [""; 2048] + }, + }; + info!( + target: LOG_TARGET, + "Retrieved mnemonic wordlist for'{}'", language_string + ); + mnemonic_word_list_vec = mnemonic_word_list.to_vec().iter().map(|s| s.to_string()).collect(); + } + + Box::into_raw(Box::new(TariSeedWords(mnemonic_word_list_vec))) +} + /// Gets the length of TariSeedWords /// /// ## Arguments @@ -915,7 +988,7 @@ pub unsafe extern "C" fn seed_words_get_at( ptr::swap(error_out, &mut error as *mut c_int); } else { let len = (*seed_words).0.len(); - if position > len as u32 { + if position >= len as u32 { error = LibWalletError::from(InterfaceError::PositionInvalidError).code; ptr::swap(error_out, &mut error as *mut c_int); } else { @@ -967,6 +1040,27 @@ pub unsafe extern "C" fn seed_words_push_word( } // Check word is from a word list + match MnemonicLanguage::from(word_string.as_str()) { + Ok(language) => { + if (*seed_words).0.len() >= MnemonicLanguage::word_count(&language) { + let error_msg = "Invalid seed words object, i.e. the entire mnemonic word list, is being used"; + log::error!(target: LOG_TARGET, "{}", error_msg); + error = LibWalletError::from(InterfaceError::InvalidArgument(error_msg.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return SeedWordPushResult::InvalidObject as u8; + } + }, + Err(e) => { + log::error!( + target: LOG_TARGET, + "{} is not a valid mnemonic seed word ({:?})", + word_string, + e + ); + return SeedWordPushResult::InvalidSeedWord as u8; + }, + } + if MnemonicLanguage::from(word_string.as_str()).is_err() { log::error!(target: LOG_TARGET, "{} is not a valid mnemonic seed word", word_string); return SeedWordPushResult::InvalidSeedWord as u8; @@ -5288,6 +5382,7 @@ mod test { use tempfile::tempdir; use tari_common_types::{emoji, transaction::TransactionStatus}; + use tari_key_manager::{mnemonic::MnemonicLanguage, mnemonic_wordlists}; use tari_test_utils::random; use tari_wallet::storage::sqlite_utilities::run_migration_and_create_sqlite_connection; @@ -6218,6 +6313,62 @@ mod test { } } + #[test] + pub fn test_mnemonic_word_lists() { + unsafe { + let mut error = 0; + let error_ptr = &mut error as *mut c_int; + + for language in MnemonicLanguage::iterator() { + let language_str: *const c_char = + CString::into_raw(CString::new(language.to_string()).unwrap()) as *const c_char; + let mnemonic_wordlist_ffi = seed_words_get_mnemonic_word_list_for_language(language_str, error_ptr); + assert_eq!(error, 0); + let mnemonic_wordlist = match *(language) { + TariMnemonicLanguage::ChineseSimplified => mnemonic_wordlists::MNEMONIC_CHINESE_SIMPLIFIED_WORDS, + TariMnemonicLanguage::English => mnemonic_wordlists::MNEMONIC_ENGLISH_WORDS, + TariMnemonicLanguage::French => mnemonic_wordlists::MNEMONIC_FRENCH_WORDS, + TariMnemonicLanguage::Italian => mnemonic_wordlists::MNEMONIC_ITALIAN_WORDS, + TariMnemonicLanguage::Japanese => mnemonic_wordlists::MNEMONIC_JAPANESE_WORDS, + TariMnemonicLanguage::Korean => mnemonic_wordlists::MNEMONIC_KOREAN_WORDS, + TariMnemonicLanguage::Spanish => mnemonic_wordlists::MNEMONIC_SPANISH_WORDS, + }; + // Compare from Rust's perspective + assert_eq!( + (*mnemonic_wordlist_ffi).0, + mnemonic_wordlist + .to_vec() + .iter() + .map(|s| s.to_string()) + .collect::>() + ); + // Compare from C's perspective + let count = seed_words_get_length(mnemonic_wordlist_ffi, error_ptr); + assert_eq!(error, 0); + for i in 0..count { + // Compare each word in the list + let mnemonic_word_ffi = CString::from_raw(seed_words_get_at(mnemonic_wordlist_ffi, i, error_ptr)); + assert_eq!(error, 0); + assert_eq!( + mnemonic_word_ffi.to_str().unwrap().to_string(), + mnemonic_wordlist[i as usize].to_string() + ); + } + // Try to wrongfully add a new seed word onto the mnemonic wordlist seed words object + let w = CString::new(mnemonic_wordlist[188]).unwrap(); + let w_str: *const c_char = CString::into_raw(w) as *const c_char; + seed_words_push_word(mnemonic_wordlist_ffi, w_str, error_ptr); + assert_eq!( + seed_words_push_word(mnemonic_wordlist_ffi, w_str, error_ptr), + SeedWordPushResult::InvalidObject as u8 + ); + assert_ne!(error, 0); + // Clear memory + seed_words_destroy(mnemonic_wordlist_ffi); + } + } + } + #[test] pub fn test_seed_words() { unsafe { diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index f0a69519c3..376462987f 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -163,6 +163,9 @@ void private_key_destroy(struct TariPrivateKey *pk); // Create an empty instance of TariSeedWords struct TariSeedWords *seed_words_create(); +// Create a TariSeedWords instance containing the entire mnemonic wordlist for the requested language +struct TariSeedWords *seed_words_get_mnemonic_word_list_for_language(const char *language, int *error_out); + // Get the number of seed words in the provided collection unsigned int seed_words_get_length(struct TariSeedWords *seed_words, int *error_out); diff --git a/integration_tests/features/WalletFFI.feature b/integration_tests/features/WalletFFI.feature index 43fd2b0698..79f66144ab 100644 --- a/integration_tests/features/WalletFFI.feature +++ b/integration_tests/features/WalletFFI.feature @@ -30,6 +30,18 @@ Feature: Wallet FFI And I wait for ffi wallet FFI_WALLET to have at least 1000000 uT And I stop ffi wallet FFI_WALLET + Scenario: As a client I want to retrieve the mnemonic word list for a given language + Given I have a base node BASE + And I have a ffi wallet FFI_WALLET connected to base node BASE + Then I retrieve the mnemonic word list for CHINESE_SIMPLIFIED from ffi wallet FFI_WALLET + Then I retrieve the mnemonic word list for ENGLISH from ffi wallet FFI_WALLET + Then I retrieve the mnemonic word list for FRENCH from ffi wallet FFI_WALLET + Then I retrieve the mnemonic word list for ITALIAN from ffi wallet FFI_WALLET + Then I retrieve the mnemonic word list for JAPANESE from ffi wallet FFI_WALLET + Then I retrieve the mnemonic word list for KOREAN from ffi wallet FFI_WALLET + Then I retrieve the mnemonic word list for SPANISH from ffi wallet FFI_WALLET + And I stop ffi wallet FFI_WALLET + Scenario: As a client I want to set the base node Given I have a base node BASE1 Given I have a base node BASE2 diff --git a/integration_tests/features/support/ffi_steps.js b/integration_tests/features/support/ffi_steps.js index e4ad54cbe6..c0f73e82cb 100644 --- a/integration_tests/features/support/ffi_steps.js +++ b/integration_tests/features/support/ffi_steps.js @@ -226,6 +226,16 @@ Then( } ); +Then( + "I retrieve the mnemonic word list for {word} from ffi wallet {word}", + async function (language, walletName) { + const wallet = this.getWallet(walletName); + const mnemonicWordList = wallet.getMnemonicWordListForLanguage(language); + console.log("Mnemonic word list for", language, ":", mnemonicWordList); + expect(mnemonicWordList.length).to.equal(2048); + } +); + Then( "Check callbacks for finished inbound tx on ffi wallet {word}", async function (walletName) { diff --git a/integration_tests/helpers/ffi/ffiInterface.js b/integration_tests/helpers/ffi/ffiInterface.js index cae9624528..1d47cb7c83 100644 --- a/integration_tests/helpers/ffi/ffiInterface.js +++ b/integration_tests/helpers/ffi/ffiInterface.js @@ -111,6 +111,10 @@ class InterfaceFFI { private_key_from_hex: [this.ptr, [this.string, this.intPtr]], private_key_destroy: [this.void, [this.ptr]], seed_words_create: [this.ptr, []], + seed_words_get_mnemonic_word_list_for_language: [ + this.ptr, + [this.string, this.intPtr], + ], seed_words_get_length: [this.uint, [this.ptr, this.intPtr]], seed_words_get_at: [this.stringPtr, [this.ptr, this.uint, this.intPtr]], seed_words_push_word: [this.uchar, [this.ptr, this.string, this.intPtr]], @@ -661,6 +665,19 @@ class InterfaceFFI { return this.fn.seed_words_create(); } + static seedWordsGetMnemonicWordListForLanguage(language) { + let error = this.initError(); + let result = this.fn.seed_words_get_mnemonic_word_list_for_language( + language, + error + ); + this.checkErrorResult( + error, + `seed_words_get_mnemonic_word_list_for_language` + ); + return result; + } + static seedWordsGetLength(ptr) { let error = this.initError(); let result = this.fn.seed_words_get_length(ptr, error); diff --git a/integration_tests/helpers/ffi/seedWords.js b/integration_tests/helpers/ffi/seedWords.js index 65cd734897..75b503652c 100644 --- a/integration_tests/helpers/ffi/seedWords.js +++ b/integration_tests/helpers/ffi/seedWords.js @@ -1,5 +1,30 @@ const InterfaceFFI = require("./ffiInterface"); const utf8 = require("utf8"); +const { expect } = require("chai"); + +function mnemonicLanguageStepId() { + return [ + "CHINESE_SIMPLIFIED", + "ENGLISH", + "FRENCH", + "ITALIAN", + "JAPANESE", + "KOREAN", + "SPANISH", + ]; +} + +function mnemonicLanguageText() { + return [ + "ChineseSimplified", + "English", + "French", + "Italian", + "Japanese", + "Korean", + "Spanish", + ]; +} class SeedWords { ptr; @@ -27,6 +52,31 @@ class SeedWords { return seed_words; } + static getMnemonicWordListForLanguage(language) { + const index = mnemonicLanguageStepId().indexOf(language); + if (index < 0) { + console.log( + "Mnemonic Language", + language, + "not recognized. Select from:\n", + mnemonicLanguageStepId() + ); + expect(index < 0).to.equal(false); + } + const seed_words = new SeedWords(); + seed_words.pointerAssign( + InterfaceFFI.seedWordsGetMnemonicWordListForLanguage( + utf8.encode(mnemonicLanguageText()[index]) + ) + ); + const mnemonicWords = []; + for (let i = 0; i < seed_words.getLength(); i++) { + mnemonicWords.push(seed_words.getAt(i)); + } + seed_words.destroy(); + return mnemonicWords; + } + getLength() { return InterfaceFFI.seedWordsGetLength(this.ptr); } diff --git a/integration_tests/helpers/ffi/wallet.js b/integration_tests/helpers/ffi/wallet.js index dfdf817e9b..2ce779e0b0 100644 --- a/integration_tests/helpers/ffi/wallet.js +++ b/integration_tests/helpers/ffi/wallet.js @@ -10,7 +10,6 @@ const Contacts = require("./contacts"); const Balance = require("./balance"); const utf8 = require("utf8"); - class Wallet { ptr; log_path = ""; diff --git a/integration_tests/helpers/walletClient.js b/integration_tests/helpers/walletClient.js index 5fb69c8080..c30d497b13 100644 --- a/integration_tests/helpers/walletClient.js +++ b/integration_tests/helpers/walletClient.js @@ -82,8 +82,8 @@ class WalletClient { const transactions = []; for (let i = 0; i < data.length; i++) { if ( - transactionStatus().indexOf(data[i].status) == 6 && - data[i].valid == true + transactionStatus().indexOf(data[i].status) === 6 && + data[i].valid === true ) { transactions.push(data[i]); } diff --git a/integration_tests/helpers/walletFFIClient.js b/integration_tests/helpers/walletFFIClient.js index 68441f390d..0929f96aaf 100644 --- a/integration_tests/helpers/walletFFIClient.js +++ b/integration_tests/helpers/walletFFIClient.js @@ -86,6 +86,10 @@ class WalletFFIClient { return this.wallet.getContacts(); } + getMnemonicWordListForLanguage(language) { + return SeedWords.getMnemonicWordListForLanguage(language); + } + getCompletedTxs() { return this.wallet.getCompletedTransactions(); } diff --git a/integration_tests/package-lock.json b/integration_tests/package-lock.json index 88c1c66b32..69893aa509 100644 --- a/integration_tests/package-lock.json +++ b/integration_tests/package-lock.json @@ -1,4430 +1,12 @@ { "name": "integration_tests", "version": "1.0.0", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "integration_tests", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@cucumber/pretty-formatter": "^1.0.0-alpha.1", - "archiver": "^5.3.0", - "axios": "^0.21.4", - "clone-deep": "^4.0.1", - "csv-parser": "^3.0.0", - "dateformat": "^3.0.3", - "glob": "^7.2.0", - "hex64": "^0.4.0", - "jszip": "^3.7.1", - "nvm": "^0.0.4", - "sha3": "^2.1.3", - "synchronized-promise": "^0.3.1", - "tari_crypto": "^0.9.1", - "utf8": "^3.0.0", - "wallet-grpc-client": "file:../clients/wallet_grpc_client" - }, - "devDependencies": { - "@babel/core": "^7.15.8", - "@babel/eslint-parser": "^7.15.8", - "@babel/eslint-plugin": "^7.14.5", - "@cucumber/cucumber": "^8.0.0-rc.1", - "@cucumber/pretty-formatter": "^1.0.0-alpha.1", - "@grpc/grpc-js": "^1.4.1", - "@grpc/proto-loader": "^0.5.5", - "blakejs": "^1.1.0", - "chai": "^4.2.0", - "cucumber-html-reporter": "^5.5.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^3.4.1", - "eslint-plugin-promise": "^4.3.1", - "ffi-napi": "^4.0.3", - "grpc-promise": "^1.4.0", - "husky": "^6.0.0", - "prettier": "^2.4.1", - "ref-napi": "^3.0.3" - } - }, - "../clients/wallet_grpc_client": { - "name": "@tari/wallet-grpc-client", - "version": "0.0.1", - "dependencies": { - "@grpc/grpc-js": "^1.3.6", - "@grpc/proto-loader": "^0.5.5", - "grpc-promise": "^1.4.0" - } - }, - "../clients/wallet_grpc_client/node_modules/@grpc/grpc-js": { - "version": "1.3.6", - "integrity": "sha512-v7+LQFbqZKmd/Tvf5/j1Xlbq6jXL/4d+gUtm2TNX4QiEC3ELWADmGr2dGlUyLl6aKTuYfsN72vAsO5zmavYkEg==", - "dependencies": { - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "../clients/wallet_grpc_client/node_modules/@grpc/proto-loader": { - "version": "0.5.6", - "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - }, - "engines": { - "node": ">=6" - } - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/base64": { - "version": "1.1.2", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/float": { - "version": "1.0.2", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/path": { - "version": "1.1.2", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/pool": { - "version": "1.1.0", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" - }, - "../clients/wallet_grpc_client/node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" - }, - "../clients/wallet_grpc_client/node_modules/@types/long": { - "version": "4.0.1", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, - "../clients/wallet_grpc_client/node_modules/@types/node": { - "version": "16.3.2", - "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==" - }, - "../clients/wallet_grpc_client/node_modules/grpc-promise": { - "version": "1.4.0", - "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==" - }, - "../clients/wallet_grpc_client/node_modules/lodash.camelcase": { - "version": "4.3.0", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, - "../clients/wallet_grpc_client/node_modules/long": { - "version": "4.0.0", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "../clients/wallet_grpc_client/node_modules/protobufjs": { - "version": "6.11.2", - "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.15.8", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.15.0", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.15.8", - "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.15.8", - "@babel/generator": "^7.15.8", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.8", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.8", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/eslint-parser": { - "version": "7.15.8", - "integrity": "sha512-fYP7QFngCvgxjUuw8O057SVH5jCXsbFFOoE77CFDcvzwBVgTOkMD/L4mIC5Ud1xf8chK/no2fRbSSn1wvNmKuQ==", - "dev": true, - "dependencies": { - "eslint-scope": "^5.1.1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.11.0", - "eslint": ">=7.5.0" - } - }, - "node_modules/@babel/eslint-plugin": { - "version": "7.14.5", - "integrity": "sha512-nzt/YMnOOIRikvSn2hk9+W2omgJBy6U8TN0R+WTTmqapA+HnZTuviZaketdTE9W7/k/+E/DfZlt1ey1NSE39pg==", - "dev": true, - "dependencies": { - "eslint-rule-composer": "^0.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/eslint-parser": ">=7.11.0", - "eslint": ">=7.5.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.15.8", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.15.4", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.15.4", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.15.4", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.15.4", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.15.4", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.15.8", - "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.15.4", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.15.4", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.15.4", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.15.4", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.15.8", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.15.4", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.15.4", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.15.6", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@cucumber/create-meta": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@cucumber/create-meta/-/create-meta-6.0.1.tgz", - "integrity": "sha512-oaNFVBAfduO0GJ1xUtgfGZHvg6+CH56DYaGWPAraayLxvtsQwaOnBYMzzaccGHty/Q6sksQ+IIZK3SuGkTmdvg==", - "dev": true, - "dependencies": { - "@cucumber/messages": "^17.0.1" - } - }, - "node_modules/@cucumber/cucumber": { - "version": "8.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@cucumber/cucumber/-/cucumber-8.0.0-rc.1.tgz", - "integrity": "sha512-NULLICYYNr0bSig2V/JiHzjONBONPeAo9/iBVoag4P7/GB4ZIeRGaBVzl7FaG8C3blx0AfTh3zfn9h+flFawQA==", - "dev": true, - "dependencies": { - "@cucumber/create-meta": "6.0.1", - "@cucumber/cucumber-expressions": "^14.0.0", - "@cucumber/gherkin": "^22.0.0", - "@cucumber/gherkin-streams": "^4.0.0", - "@cucumber/html-formatter": "^17.0.0", - "@cucumber/messages": "^17.1.1", - "@cucumber/tag-expressions": "^4.1.0", - "assertion-error-formatter": "^3.0.0", - "capital-case": "^1.0.4", - "cli-table3": "^0.6.0", - "colors": "^1.4.0", - "commander": "^8.0.0", - "duration": "^0.2.2", - "durations": "^3.4.2", - "figures": "^3.2.0", - "glob": "^7.1.6", - "indent-string": "^4.0.0", - "is-stream": "^2.0.0", - "knuth-shuffle-seeded": "^1.0.6", - "mz": "^2.7.0", - "progress": "^2.0.3", - "resolve": "^1.19.0", - "resolve-pkg": "^2.0.0", - "stack-chain": "^2.0.0", - "stacktrace-js": "^2.0.2", - "string-argv": "^0.3.1", - "tmp": "^0.2.1", - "util-arity": "^1.1.0", - "verror": "^1.10.0" - }, - "bin": { - "cucumber-js": "bin/cucumber-js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cucumber/cucumber-expressions": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/cucumber-expressions/-/cucumber-expressions-14.0.0.tgz", - "integrity": "sha512-QiuFBrj4dZRc1Igvp2/nOjUNFyDtO7uHTrzgY9DbwzebYAYOvM6CKGOSxSuPUzxowuc1nuRkzJfFUI1kHaZgPQ==", - "dev": true, - "dependencies": { - "regexp-match-indices": "1.0.2" - } - }, - "node_modules/@cucumber/gherkin": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-22.0.0.tgz", - "integrity": "sha512-D5OghXE8kkZm7pcwo8TvQMgrrXGMXEjERdKLU0T7dQIbc6k0BmMX8dTRh2cwAjH8c7vhwdd0qLU8FPQgGGj+bg==", - "dev": true, - "dependencies": { - "@cucumber/message-streams": "^3.0.0", - "@cucumber/messages": "^17.1.1" - } - }, - "node_modules/@cucumber/gherkin-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/gherkin-streams/-/gherkin-streams-4.0.0.tgz", - "integrity": "sha512-b/guGNeuxr3ghoJOK47QpLhwa2BOdRq+cs2hBYulMLPTiVfwvRBiZlq7P6xdjR9dIpUKBSpzYR6NwaLMgV5DTg==", - "dev": true, - "dependencies": { - "@cucumber/gherkin": "^21.0.0", - "@cucumber/message-streams": "^3.0.0", - "@cucumber/messages": "^17.1.0", - "commander": "8.1.0", - "source-map-support": "0.5.19" - }, - "bin": { - "gherkin-javascript": "bin/gherkin" - } - }, - "node_modules/@cucumber/gherkin-streams/node_modules/@cucumber/gherkin": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-21.0.0.tgz", - "integrity": "sha512-S6YFmTg56iEn563ReePL6Sygb77vwYrGHEr7NwuLIgg20Hi1pp7P80BAYVYNRgU7nK9vG2II9O6kaZbiOXF/5g==", - "dev": true, - "dependencies": { - "@cucumber/message-streams": "^3.0.0", - "@cucumber/messages": "^17.1.0" - } - }, - "node_modules/@cucumber/gherkin-streams/node_modules/commander": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz", - "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cucumber/html-formatter": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/html-formatter/-/html-formatter-17.0.0.tgz", - "integrity": "sha512-yegA8LY1HYUONyMtTvAYj+aG4zc/6WRtKQxqJahjcdmjgXWcL1BTe8y0lw4BFVqFjaZNI9onOM5KDnMHDm3J/w==", - "dev": true, - "dependencies": { - "@cucumber/messages": "^17.1.0", - "commander": "8.1.0", - "source-map-support": "0.5.19" - }, - "bin": { - "cucumber-html-formatter": "bin/cucumber-html-formatter.js" - } - }, - "node_modules/@cucumber/html-formatter/node_modules/commander": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz", - "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cucumber/message-streams": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/message-streams/-/message-streams-3.0.0.tgz", - "integrity": "sha512-ABx91nKUebV8mLmpf7BsB3bmQ57CDAfj2EIZswThz+nJHYPAFlZ1JewI6ykFsR9RzJ7/QhgQs0KHeQh7nH/u1Q==", - "dev": true, - "dependencies": { - "@cucumber/messages": "^17.0.0" - } - }, - "node_modules/@cucumber/messages": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-17.1.1.tgz", - "integrity": "sha512-KQMn2Ag+1g1CXp/zKQ7LLqmuHjuQwuXw0N2u5SrDk8r72zPt36SxmDSJK7w6HiFTI+3p5ZuzwLi4S5jop3Tx4g==", - "dev": true, - "dependencies": { - "@types/uuid": "8.3.1", - "class-transformer": "0.4.0", - "reflect-metadata": "0.1.13", - "uuid": "8.3.2" - } - }, - "node_modules/@cucumber/messages/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@cucumber/pretty-formatter": { - "version": "1.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@cucumber/pretty-formatter/-/pretty-formatter-1.0.0-alpha.1.tgz", - "integrity": "sha512-emVFdRkEFAqksd3X9cMWn7cOE2fIPB0aTwZAFZPMO55ZRf+7IaZ7VlEY2Pd5qPxhTXNmyZvaBf6AOoZmx47pmA==", - "dev": true, - "dependencies": { - "ansi-styles": "^5.0.0", - "cli-table3": "^0.6.0", - "figures": "^3.2.0", - "ts-dedent": "^2.0.0" - }, - "peerDependencies": { - "@cucumber/cucumber": ">=7.0.0", - "@cucumber/messages": "*" - } - }, - "node_modules/@cucumber/pretty-formatter/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@cucumber/tag-expressions": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@cucumber/tag-expressions/-/tag-expressions-4.1.0.tgz", - "integrity": "sha512-chTnjxV3vryL75N90wJIMdMafXmZoO2JgNJLYpsfcALL2/IQrRiny3vM9DgD5RDCSt1LNloMtb7rGey9YWxCsA==", - "dev": true - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.11.0", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.4.1.tgz", - "integrity": "sha512-/chkA48TdAvATHA7RXJPeHQLdfFhpu51974s8htjO/XTDHA41j5+SkR5Io+lr9XsLmkZD6HxLyRAFGmA9wjO2w==", - "dev": true, - "dependencies": { - "@grpc/proto-loader": "^0.6.4", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.6.tgz", - "integrity": "sha512-cdMaPZ8AiFz6ua6PUbP+LKbhwJbFXnrQ/mlnKGUyzDUZ3wp7vPLksnmLCBX6SHgSmjX7CbNVNLFYD5GmmjO4GQ==", - "dev": true, - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.1.1" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.5.6", - "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", - "dev": true, - "dependencies": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", - "dev": true - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "dev": true - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", - "dev": true - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "dev": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", - "dev": true - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", - "dev": true - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", - "dev": true - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", - "dev": true - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", - "dev": true - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "node_modules/@types/long": { - "version": "4.0.1", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", - "dev": true - }, - "node_modules/@types/node": { - "version": "16.10.3", - "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", - "dev": true - }, - "node_modules/@types/uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", - "dev": true - }, - "node_modules/acorn": { - "version": "7.4.1", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true - }, - "node_modules/archiver": { - "version": "5.3.0", - "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", - "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.0", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "2.3.7", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/archiver-utils/node_modules/string_decoder": { - "version": "1.1.1", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-includes": { - "version": "3.1.4", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.2.5", - "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/assertion-error-formatter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/assertion-error-formatter/-/assertion-error-formatter-3.0.0.tgz", - "integrity": "sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==", - "dev": true, - "dependencies": { - "diff": "^4.0.1", - "pad-right": "^0.2.2", - "repeat-string": "^1.6.1" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.1", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" - }, - "node_modules/axios": { - "version": "0.21.4", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bindings": { - "version": "1.5.0", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/blakejs": { - "version": "1.1.1", - "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/browserslist": { - "version": "4.17.3", - "integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001264", - "electron-to-chromium": "^1.3.857", - "escalade": "^3.1.1", - "node-releases": "^1.1.77", - "picocolors": "^0.2.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/call-bind": { - "version": "1.0.2", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001265", - "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/capital-case": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", - "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, - "node_modules/chai": { - "version": "4.3.4", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/class-transformer": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.4.0.tgz", - "integrity": "sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==", - "dev": true - }, - "node_modules/cli-table3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", - "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "colors": "^1.1.2" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/compress-commons": { - "version": "4.1.1", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "node_modules/crc-32": { - "version": "1.2.0", - "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", - "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" - }, - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc32-stream": { - "version": "4.0.2", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csv-parser": { - "version": "3.0.0", - "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "csv-parser": "bin/csv-parser" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cucumber-html-reporter": { - "version": "5.5.0", - "integrity": "sha512-kF7vIwvTe7we7Wp/5uNZVZk+Ryozb688LpNvCNhou6N0RmLYPqaoV2aiN8GIB94JUBpribtlq6kDkEUHwxBVeQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "find": "^0.3.0", - "fs-extra": "^8.1.0", - "js-base64": "^2.3.2", - "jsonfile": "^5.0.0", - "lodash": "^4.17.11", - "node-emoji": "^1.10.0", - "open": "^6.4.0", - "uuid": "^3.3.3" - } - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/dateformat": { - "version": "3.0.3", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "engines": { - "node": "*" - } - }, - "node_modules/deasync": { - "version": "0.1.23", - "integrity": "sha512-CGZSokFwidI50GOAmkz/7z3QdMzTQqAiUOzt95PuhKgi6VVztn9D03ZCzzi93uUWlp/v6A9osvNWpIvqHvKjTA==", - "hasInstallScript": true, - "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^1.7.1" - }, - "engines": { - "node": ">=0.11.0" - } - }, - "node_modules/deasync/node_modules/node-addon-api": { - "version": "1.7.2", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" - }, - "node_modules/debug": { - "version": "4.3.2", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-eql": { - "version": "3.0.1", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/define-properties": { - "version": "1.1.3", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/duration": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz", - "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.46" - } - }, - "node_modules/durations": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/durations/-/durations-3.4.2.tgz", - "integrity": "sha512-V/lf7y33dGaypZZetVI1eu7BmvkbC4dItq12OElLRpKuaU5JxQstV2zHwLv8P7cNbQ+KL1WD80zMCTx5dNC4dg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.3.864", - "integrity": "sha512-v4rbad8GO6/yVI92WOeU9Wgxc4NA0n4f6P1FvZTY+jyY7JHEhw3bduYu60v3Q1h81Cg6eo4ApZrFPuycwd5hGw==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/error-stack-parser": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", - "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", - "dev": true, - "dependencies": { - "stackframe": "^1.1.1" - } - }, - "node_modules/es-abstract": { - "version": "1.19.1", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", - "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dev": true, - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.3.0", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-config-standard": { - "version": "16.0.3", - "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peerDependencies": { - "eslint": "^7.12.1", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1 || ^5.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz", - "integrity": "sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "find-up": "^2.1.0", - "pkg-dir": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz", - "integrity": "sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.0", - "has": "^1.0.3", - "is-core-module": "^2.7.0", - "is-glob": "^4.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.5", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/eslint-plugin-es": { - "version": "3.0.1", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-node/node_modules/ignore": { - "version": "5.1.8", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "3.4.1", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "eslint": ">=5.0.0", - "prettier": ">=1.13.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-promise": { - "version": "4.3.1", - "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-rule-composer": { - "version": "0.3.0", - "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.11.0", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.5", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/exit-on-epipe": { - "version": "1.0.1", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", - "dev": true, - "dependencies": { - "type": "^2.5.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", - "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", - "dev": true - }, - "node_modules/extsprintf": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", - "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/ffi-napi": { - "version": "4.0.3", - "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "debug": "^4.1.1", - "get-uv-event-loop-napi-h": "^1.0.5", - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.1", - "ref-napi": "^2.0.1 || ^3.0.2", - "ref-struct-di": "^1.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "node_modules/find": { - "version": "0.3.0", - "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", - "dev": true, - "dependencies": { - "traverse-chain": "~0.1.0" - } - }, - "node_modules/find-up": { - "version": "2.1.0", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.2", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.14.4", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-extra/node_modules/jsonfile": { - "version": "4.0.0", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-symbol-from-current-process-h": { - "version": "1.0.2", - "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==", - "dev": true - }, - "node_modules/get-uv-event-loop-napi-h": { - "version": "1.0.6", - "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", - "dev": true, - "dependencies": { - "get-symbol-from-current-process-h": "^1.0.1" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - }, - "node_modules/grpc-promise": { - "version": "1.4.0", - "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hex64": { - "version": "0.4.0", - "integrity": "sha1-rRB4rIHVfXLeYjKxADvE9vsCh8A=", - "bin": { - "hex64": "bin/hex64" - } - }, - "node_modules/husky": { - "version": "6.0.0", - "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", - "dev": true, - "bin": { - "husky": "lib/bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "4.0.6", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.4", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.7.0", - "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number-object": { - "version": "1.0.6", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.1", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.1", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/isexe": { - "version": "2.0.0", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-base64": { - "version": "2.6.4", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.0", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "5.0.0", - "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", - "dev": true, - "dependencies": { - "universalify": "^0.1.2" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jszip": { - "version": "3.7.1", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" - } - }, - "node_modules/jszip/node_modules/readable-stream": { - "version": "2.3.7", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/jszip/node_modules/string_decoder": { - "version": "1.1.1", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/knuth-shuffle-seeded": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", - "integrity": "sha1-AfG2VzOqdUDuCNiwF0Fk0iCB5OE=", - "dev": true, - "dependencies": { - "seed-random": "~2.2.0" - } - }, - "node_modules/lazystream": { - "version": "1.0.0", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lazystream/node_modules/readable-stream": { - "version": "2.3.7", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/lazystream/node_modules/string_decoder": { - "version": "1.1.1", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/locate-path": { - "version": "2.0.0", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", - "dev": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/lodash.union": { - "version": "4.6.0", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" - }, - "node_modules/long": { - "version": "4.0.0", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "dev": true - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "node_modules/ms": { - "version": "2.1.2", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-addon-api": { - "version": "3.2.1", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true - }, - "node_modules/node-emoji": { - "version": "1.11.0", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/node-gyp-build": { - "version": "4.3.0", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-releases": { - "version": "1.1.77", - "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nvm": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/nvm/-/nvm-0.0.4.tgz", - "integrity": "sha1-OKF46dMbKDUIyS0VydqGHRqSELw=", - "deprecated": "This is NOT the correct nvm. Visit https://nvm.sh and use the curl command to install it.", - "bin": { - "nvm": "bin/nvm" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.11.0", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.1.5", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "6.4.0", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "dev": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "1.3.0", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-locate": { - "version": "2.0.0", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-try": { - "version": "1.0.0", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pad-right": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/pad-right/-/pad-right-0.2.2.tgz", - "integrity": "sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q=", - "dev": true, - "dependencies": { - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "3.0.0", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/pathval": { - "version": "1.1.1", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/picocolors": { - "version": "0.2.1", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "node_modules/pkg-dir": { - "version": "2.0.0", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "dependencies": { - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.4.1", - "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/printj": { - "version": "1.1.2", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", - "bin": { - "printj": "bin/printj.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/progress": { - "version": "2.0.3", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/protobufjs": { - "version": "6.11.2", - "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdir-glob": { - "version": "1.1.1", - "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", - "dependencies": { - "minimatch": "^3.0.4" - } - }, - "node_modules/ref-napi": { - "version": "3.0.3", - "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "debug": "^4.1.1", - "get-symbol-from-current-process-h": "^1.0.2", - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.1" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/ref-struct-di": { - "version": "1.1.1", - "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", - "dev": true, - "dependencies": { - "debug": "^3.1.0" - } - }, - "node_modules/ref-struct-di/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true - }, - "node_modules/regexp-match-indices": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regexp-match-indices/-/regexp-match-indices-1.0.2.tgz", - "integrity": "sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==", - "dev": true, - "dependencies": { - "regexp-tree": "^0.1.11" - } - }, - "node_modules/regexp-tree": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", - "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", - "dev": true, - "bin": { - "regexp-tree": "bin/regexp-tree" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.20.0", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-2.0.0.tgz", - "integrity": "sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-pkg/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/seed-random": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", - "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=", - "dev": true - }, - "node_modules/semver": { - "version": "6.3.0", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sha3": { - "version": "2.1.4", - "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", - "dependencies": { - "buffer": "6.0.3" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.5.7", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/stack-chain": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-2.0.0.tgz", - "integrity": "sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==", - "dev": true - }, - "node_modules/stack-generator": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", - "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", - "dev": true, - "dependencies": { - "stackframe": "^1.1.1" - } - }, - "node_modules/stackframe": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", - "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", - "dev": true - }, - "node_modules/stacktrace-gps": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz", - "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", - "dev": true, - "dependencies": { - "source-map": "0.5.6", - "stackframe": "^1.1.1" - } - }, - "node_modules/stacktrace-gps/node_modules/source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stacktrace-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", - "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", - "dev": true, - "dependencies": { - "error-stack-parser": "^2.0.6", - "stack-generator": "^2.0.5", - "stacktrace-gps": "^3.0.4" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/synchronized-promise": { - "version": "0.3.1", - "integrity": "sha512-Iy+JzrERSUrwpOHUDku8HHIddk8V6iLG9bPIzboP2i5RYkn2eSmRB8waSaX7Rc/+DUUsnFsoOHrmniwOp9BOgw==", - "dependencies": { - "deasync": "^0.1.15" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/table": { - "version": "6.7.2", - "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.6.3", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tari_crypto": { - "version": "0.9.1", - "integrity": "sha512-K7LAtwQQKCeTH5CyyO8d/TiPDEePRaJ4e6+hrxpWv6jlkkAiS4m6csBuVqpSjyAlKeP8cQJpUQX2n22akOuZVg==" - }, - "node_modules/text-table": { - "version": "0.2.0", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "dev": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/traverse-chain": { - "version": "0.1.0", - "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", - "dev": true - }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "dev": true, - "engines": { - "node": ">=6.10" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.11.0", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/upper-case-first": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utf8": { - "version": "3.0.0", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" - }, - "node_modules/util-arity": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", - "integrity": "sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=", - "dev": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/uuid": { - "version": "3.4.0", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/wallet-grpc-client": { - "resolved": "../clients/wallet_grpc_client", - "link": true - }, - "node_modules/which": { - "version": "2.0.2", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/zip-stream": { - "version": "4.1.0", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "dependencies": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - } - }, "dependencies": { "@babel/code-frame": { "version": "7.15.8", + "resolved": false, "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { @@ -4433,11 +15,13 @@ }, "@babel/compat-data": { "version": "7.15.0", + "resolved": false, "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", "dev": true }, "@babel/core": { "version": "7.15.8", + "resolved": false, "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", "dev": true, "requires": { @@ -4460,6 +44,7 @@ }, "@babel/eslint-parser": { "version": "7.15.8", + "resolved": false, "integrity": "sha512-fYP7QFngCvgxjUuw8O057SVH5jCXsbFFOoE77CFDcvzwBVgTOkMD/L4mIC5Ud1xf8chK/no2fRbSSn1wvNmKuQ==", "dev": true, "requires": { @@ -4470,6 +55,7 @@ }, "@babel/eslint-plugin": { "version": "7.14.5", + "resolved": false, "integrity": "sha512-nzt/YMnOOIRikvSn2hk9+W2omgJBy6U8TN0R+WTTmqapA+HnZTuviZaketdTE9W7/k/+E/DfZlt1ey1NSE39pg==", "dev": true, "requires": { @@ -4478,6 +64,7 @@ }, "@babel/generator": { "version": "7.15.8", + "resolved": false, "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", "dev": true, "requires": { @@ -4488,6 +75,7 @@ }, "@babel/helper-compilation-targets": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", "dev": true, "requires": { @@ -4499,6 +87,7 @@ }, "@babel/helper-function-name": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", "dev": true, "requires": { @@ -4509,6 +98,7 @@ }, "@babel/helper-get-function-arity": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", "dev": true, "requires": { @@ -4517,6 +107,7 @@ }, "@babel/helper-hoist-variables": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", "dev": true, "requires": { @@ -4525,6 +116,7 @@ }, "@babel/helper-member-expression-to-functions": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", "dev": true, "requires": { @@ -4533,6 +125,7 @@ }, "@babel/helper-module-imports": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", "dev": true, "requires": { @@ -4541,6 +134,7 @@ }, "@babel/helper-module-transforms": { "version": "7.15.8", + "resolved": false, "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", "dev": true, "requires": { @@ -4556,6 +150,7 @@ }, "@babel/helper-optimise-call-expression": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", "dev": true, "requires": { @@ -4564,6 +159,7 @@ }, "@babel/helper-replace-supers": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", "dev": true, "requires": { @@ -4575,6 +171,7 @@ }, "@babel/helper-simple-access": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", "dev": true, "requires": { @@ -4583,6 +180,7 @@ }, "@babel/helper-split-export-declaration": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", "dev": true, "requires": { @@ -4591,16 +189,19 @@ }, "@babel/helper-validator-identifier": { "version": "7.15.7", + "resolved": false, "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, "@babel/helper-validator-option": { "version": "7.14.5", + "resolved": false, "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", "dev": true }, "@babel/helpers": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", "dev": true, "requires": { @@ -4611,6 +212,7 @@ }, "@babel/highlight": { "version": "7.14.5", + "resolved": false, "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { @@ -4621,11 +223,13 @@ }, "@babel/parser": { "version": "7.15.8", + "resolved": false, "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", "dev": true }, "@babel/template": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", "dev": true, "requires": { @@ -4636,6 +240,7 @@ }, "@babel/traverse": { "version": "7.15.4", + "resolved": false, "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", "dev": true, "requires": { @@ -4652,6 +257,7 @@ }, "@babel/types": { "version": "7.15.6", + "resolved": false, "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", "dev": true, "requires": { @@ -4807,7 +413,6 @@ "version": "1.0.0-alpha.1", "resolved": "https://registry.npmjs.org/@cucumber/pretty-formatter/-/pretty-formatter-1.0.0-alpha.1.tgz", "integrity": "sha512-emVFdRkEFAqksd3X9cMWn7cOE2fIPB0aTwZAFZPMO55ZRf+7IaZ7VlEY2Pd5qPxhTXNmyZvaBf6AOoZmx47pmA==", - "dev": true, "requires": { "ansi-styles": "^5.0.0", "cli-table3": "^0.6.0", @@ -4818,8 +423,7 @@ "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" } } }, @@ -4831,6 +435,7 @@ }, "@eslint/eslintrc": { "version": "0.4.3", + "resolved": false, "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { @@ -4847,6 +452,7 @@ "dependencies": { "globals": { "version": "13.11.0", + "resolved": false, "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", "dev": true, "requires": { @@ -4855,6 +461,7 @@ }, "type-fest": { "version": "0.20.2", + "resolved": false, "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } @@ -4887,6 +494,7 @@ }, "@grpc/proto-loader": { "version": "0.5.6", + "resolved": false, "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", "dev": true, "requires": { @@ -4896,6 +504,7 @@ }, "@humanwhocodes/config-array": { "version": "0.5.0", + "resolved": false, "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "requires": { @@ -4906,31 +515,37 @@ }, "@humanwhocodes/object-schema": { "version": "1.2.0", + "resolved": false, "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, "@protobufjs/aspromise": { "version": "1.1.2", + "resolved": false, "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", "dev": true }, "@protobufjs/base64": { "version": "1.1.2", + "resolved": false, "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "dev": true }, "@protobufjs/codegen": { "version": "2.0.4", + "resolved": false, "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", "dev": true }, "@protobufjs/eventemitter": { "version": "1.1.0", + "resolved": false, "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", "dev": true }, "@protobufjs/fetch": { "version": "1.1.0", + "resolved": false, "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", "dev": true, "requires": { @@ -4940,41 +555,49 @@ }, "@protobufjs/float": { "version": "1.0.2", + "resolved": false, "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", "dev": true }, "@protobufjs/inquire": { "version": "1.1.0", + "resolved": false, "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", "dev": true }, "@protobufjs/path": { "version": "1.1.2", + "resolved": false, "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", "dev": true }, "@protobufjs/pool": { "version": "1.1.0", + "resolved": false, "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", "dev": true }, "@protobufjs/utf8": { "version": "1.1.0", + "resolved": false, "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", "dev": true }, "@types/json5": { "version": "0.0.29", + "resolved": false, "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/long": { "version": "4.0.1", + "resolved": false, "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", "dev": true }, "@types/node": { "version": "16.10.3", + "resolved": false, "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", "dev": true }, @@ -4986,17 +609,19 @@ }, "acorn": { "version": "7.4.1", + "resolved": false, "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-jsx": { "version": "5.3.2", + "resolved": false, "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "ajv": { "version": "6.12.6", + "resolved": false, "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { @@ -5008,16 +633,18 @@ }, "ansi-colors": { "version": "4.1.1", + "resolved": false, "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-regex": { "version": "5.0.1", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "resolved": false, + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "3.2.1", + "resolved": false, "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { @@ -5026,12 +653,13 @@ }, "any-promise": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "resolved": false, "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", "dev": true }, "archiver": { "version": "5.3.0", + "resolved": false, "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", "requires": { "archiver-utils": "^2.1.0", @@ -5045,6 +673,7 @@ }, "archiver-utils": { "version": "2.1.0", + "resolved": false, "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "requires": { "glob": "^7.1.4", @@ -5061,6 +690,7 @@ "dependencies": { "readable-stream": { "version": "2.3.7", + "resolved": false, "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", @@ -5074,6 +704,7 @@ }, "string_decoder": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -5083,6 +714,7 @@ }, "argparse": { "version": "1.0.10", + "resolved": false, "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { @@ -5091,6 +723,7 @@ }, "array-includes": { "version": "3.1.4", + "resolved": false, "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", "dev": true, "requires": { @@ -5103,6 +736,7 @@ }, "array.prototype.flat": { "version": "1.2.5", + "resolved": false, "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", "dev": true, "requires": { @@ -5113,18 +747,19 @@ }, "assert-plus": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "resolved": false, "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "assertion-error": { "version": "1.1.0", + "resolved": false, "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "assertion-error-formatter": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/assertion-error-formatter/-/assertion-error-formatter-3.0.0.tgz", + "resolved": false, "integrity": "sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==", "dev": true, "requires": { @@ -5135,15 +770,18 @@ }, "astral-regex": { "version": "2.0.0", + "resolved": false, "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "async": { "version": "3.2.1", + "resolved": false, "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" }, "axios": { "version": "0.21.4", + "resolved": false, "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "requires": { "follow-redirects": "^1.14.0" @@ -5151,14 +789,17 @@ }, "balanced-match": { "version": "1.0.2", + "resolved": false, "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", + "resolved": false, "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bindings": { "version": "1.5.0", + "resolved": false, "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "requires": { "file-uri-to-path": "1.0.0" @@ -5166,6 +807,7 @@ }, "bl": { "version": "4.1.0", + "resolved": false, "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "requires": { "buffer": "^5.5.0", @@ -5175,6 +817,7 @@ "dependencies": { "buffer": { "version": "5.7.1", + "resolved": false, "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "requires": { "base64-js": "^1.3.1", @@ -5185,11 +828,13 @@ }, "blakejs": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==", "dev": true }, "brace-expansion": { "version": "1.1.11", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", @@ -5198,6 +843,7 @@ }, "browserslist": { "version": "4.17.3", + "resolved": false, "integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==", "dev": true, "requires": { @@ -5210,6 +856,7 @@ }, "buffer": { "version": "6.0.3", + "resolved": false, "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "requires": { "base64-js": "^1.3.1", @@ -5218,6 +865,7 @@ }, "buffer-crc32": { "version": "0.2.13", + "resolved": false, "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, "buffer-from": { @@ -5228,6 +876,7 @@ }, "call-bind": { "version": "1.0.2", + "resolved": false, "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, "requires": { @@ -5237,11 +886,13 @@ }, "callsites": { "version": "3.1.0", + "resolved": false, "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "caniuse-lite": { "version": "1.0.30001265", + "resolved": false, "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", "dev": true }, @@ -5258,6 +909,7 @@ }, "chai": { "version": "4.3.4", + "resolved": false, "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, "requires": { @@ -5271,6 +923,7 @@ }, "chalk": { "version": "2.4.2", + "resolved": false, "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { @@ -5281,6 +934,7 @@ }, "check-error": { "version": "1.0.2", + "resolved": false, "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, @@ -5294,7 +948,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", - "dev": true, "requires": { "colors": "^1.1.2", "object-assign": "^4.1.0", @@ -5314,6 +967,7 @@ }, "clone-deep": { "version": "4.0.1", + "resolved": false, "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "requires": { "is-plain-object": "^2.0.4", @@ -5323,6 +977,7 @@ }, "color-convert": { "version": "1.9.3", + "resolved": false, "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { @@ -5331,14 +986,14 @@ }, "color-name": { "version": "1.1.3", + "resolved": false, "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "colors": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true + "resolved": false, + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" }, "commander": { "version": "8.3.0", @@ -5348,6 +1003,7 @@ }, "compress-commons": { "version": "4.1.1", + "resolved": false, "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", "requires": { "buffer-crc32": "^0.2.13", @@ -5358,10 +1014,12 @@ }, "concat-map": { "version": "0.0.1", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "convert-source-map": { "version": "1.8.0", + "resolved": false, "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { @@ -5370,10 +1028,12 @@ }, "core-util-is": { "version": "1.0.2", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "crc-32": { "version": "1.2.0", + "resolved": false, "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", "requires": { "exit-on-epipe": "~1.0.1", @@ -5382,6 +1042,7 @@ }, "crc32-stream": { "version": "4.0.2", + "resolved": false, "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", "requires": { "crc-32": "^1.2.0", @@ -5390,6 +1051,7 @@ }, "cross-spawn": { "version": "7.0.3", + "resolved": false, "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { @@ -5400,6 +1062,7 @@ }, "csv-parser": { "version": "3.0.0", + "resolved": false, "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", "requires": { "minimist": "^1.2.0" @@ -5407,6 +1070,7 @@ }, "cucumber-html-reporter": { "version": "5.5.0", + "resolved": false, "integrity": "sha512-kF7vIwvTe7we7Wp/5uNZVZk+Ryozb688LpNvCNhou6N0RmLYPqaoV2aiN8GIB94JUBpribtlq6kDkEUHwxBVeQ==", "dev": true, "requires": { @@ -5423,7 +1087,7 @@ }, "d": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "resolved": false, "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", "dev": true, "requires": { @@ -5433,10 +1097,12 @@ }, "dateformat": { "version": "3.0.3", + "resolved": false, "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" }, "deasync": { "version": "0.1.23", + "resolved": false, "integrity": "sha512-CGZSokFwidI50GOAmkz/7z3QdMzTQqAiUOzt95PuhKgi6VVztn9D03ZCzzi93uUWlp/v6A9osvNWpIvqHvKjTA==", "requires": { "bindings": "^1.5.0", @@ -5445,12 +1111,14 @@ "dependencies": { "node-addon-api": { "version": "1.7.2", + "resolved": false, "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" } } }, "debug": { "version": "4.3.2", + "resolved": false, "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { @@ -5459,6 +1127,7 @@ }, "deep-eql": { "version": "3.0.1", + "resolved": false, "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { @@ -5467,11 +1136,13 @@ }, "deep-is": { "version": "0.1.4", + "resolved": false, "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "define-properties": { "version": "1.1.3", + "resolved": false, "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { @@ -5480,12 +1151,13 @@ }, "diff": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "resolved": false, "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, "doctrine": { "version": "3.0.0", + "resolved": false, "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { @@ -5494,7 +1166,7 @@ }, "duration": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz", + "resolved": false, "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", "dev": true, "requires": { @@ -5510,16 +1182,18 @@ }, "electron-to-chromium": { "version": "1.3.864", + "resolved": false, "integrity": "sha512-v4rbad8GO6/yVI92WOeU9Wgxc4NA0n4f6P1FvZTY+jyY7JHEhw3bduYu60v3Q1h81Cg6eo4ApZrFPuycwd5hGw==", "dev": true }, "emoji-regex": { "version": "8.0.0", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "resolved": false, + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "end-of-stream": { "version": "1.4.4", + "resolved": false, "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "requires": { "once": "^1.4.0" @@ -5527,6 +1201,7 @@ }, "enquirer": { "version": "2.3.6", + "resolved": false, "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { @@ -5535,7 +1210,7 @@ }, "error-stack-parser": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", + "resolved": false, "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", "dev": true, "requires": { @@ -5544,6 +1219,7 @@ }, "es-abstract": { "version": "1.19.1", + "resolved": false, "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", "dev": true, "requires": { @@ -5571,6 +1247,7 @@ }, "es-to-primitive": { "version": "1.2.1", + "resolved": false, "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { @@ -5581,7 +1258,7 @@ }, "es5-ext": { "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "resolved": false, "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dev": true, "requires": { @@ -5592,7 +1269,7 @@ }, "es6-iterator": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "resolved": false, "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { @@ -5603,7 +1280,7 @@ }, "es6-symbol": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "resolved": false, "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { @@ -5613,16 +1290,18 @@ }, "escalade": { "version": "3.1.1", + "resolved": false, "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "resolved": false, + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { "version": "7.32.0", + "resolved": false, "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "requires": { @@ -5670,6 +1349,7 @@ "dependencies": { "@babel/code-frame": { "version": "7.12.11", + "resolved": false, "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "requires": { @@ -5678,6 +1358,7 @@ }, "ansi-styles": { "version": "4.3.0", + "resolved": false, "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { @@ -5686,6 +1367,7 @@ }, "chalk": { "version": "4.1.2", + "resolved": false, "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { @@ -5695,6 +1377,7 @@ }, "color-convert": { "version": "2.0.1", + "resolved": false, "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { @@ -5703,16 +1386,19 @@ }, "color-name": { "version": "1.1.4", + "resolved": false, "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "escape-string-regexp": { "version": "4.0.0", + "resolved": false, "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "globals": { "version": "13.11.0", + "resolved": false, "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", "dev": true, "requires": { @@ -5721,11 +1407,13 @@ }, "has-flag": { "version": "4.0.0", + "resolved": false, "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "semver": { "version": "7.3.5", + "resolved": false, "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { @@ -5734,6 +1422,7 @@ }, "supports-color": { "version": "7.2.0", + "resolved": false, "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { @@ -5742,6 +1431,7 @@ }, "type-fest": { "version": "0.20.2", + "resolved": false, "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } @@ -5749,18 +1439,19 @@ }, "eslint-config-prettier": { "version": "8.3.0", + "resolved": false, "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true, - "requires": {} + "dev": true }, "eslint-config-standard": { "version": "16.0.3", + "resolved": false, "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "dev": true, - "requires": {} + "dev": true }, "eslint-import-resolver-node": { "version": "0.3.6", + "resolved": false, "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", "dev": true, "requires": { @@ -5770,6 +1461,7 @@ "dependencies": { "debug": { "version": "3.2.7", + "resolved": false, "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { @@ -5791,6 +1483,7 @@ "dependencies": { "debug": { "version": "3.2.7", + "resolved": false, "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { @@ -5822,6 +1515,7 @@ "dependencies": { "debug": { "version": "2.6.9", + "resolved": false, "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { @@ -5830,6 +1524,7 @@ }, "doctrine": { "version": "2.1.0", + "resolved": false, "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { @@ -5838,6 +1533,7 @@ }, "ms": { "version": "2.0.0", + "resolved": false, "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } @@ -5845,6 +1541,7 @@ }, "eslint-plugin-node": { "version": "11.1.0", + "resolved": false, "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "requires": { @@ -5858,6 +1555,7 @@ "dependencies": { "eslint-plugin-es": { "version": "3.0.1", + "resolved": false, "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "requires": { @@ -5867,6 +1565,7 @@ }, "ignore": { "version": "5.1.8", + "resolved": false, "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true } @@ -5874,6 +1573,7 @@ }, "eslint-plugin-prettier": { "version": "3.4.1", + "resolved": false, "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", "dev": true, "requires": { @@ -5882,16 +1582,19 @@ }, "eslint-plugin-promise": { "version": "4.3.1", + "resolved": false, "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", "dev": true }, "eslint-rule-composer": { "version": "0.3.0", + "resolved": false, "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", "dev": true }, "eslint-scope": { "version": "5.1.1", + "resolved": false, "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { @@ -5901,6 +1604,7 @@ }, "eslint-utils": { "version": "2.1.0", + "resolved": false, "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { @@ -5909,6 +1613,7 @@ "dependencies": { "eslint-visitor-keys": { "version": "1.3.0", + "resolved": false, "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true } @@ -5916,11 +1621,13 @@ }, "eslint-visitor-keys": { "version": "2.1.0", + "resolved": false, "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, "espree": { "version": "7.3.1", + "resolved": false, "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { @@ -5931,6 +1638,7 @@ "dependencies": { "eslint-visitor-keys": { "version": "1.3.0", + "resolved": false, "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true } @@ -5938,11 +1646,13 @@ }, "esprima": { "version": "4.0.1", + "resolved": false, "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { "version": "1.4.0", + "resolved": false, "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { @@ -5951,6 +1661,7 @@ "dependencies": { "estraverse": { "version": "5.2.0", + "resolved": false, "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } @@ -5958,6 +1669,7 @@ }, "esrecurse": { "version": "4.3.0", + "resolved": false, "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { @@ -5966,6 +1678,7 @@ "dependencies": { "estraverse": { "version": "5.2.0", + "resolved": false, "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } @@ -5973,21 +1686,24 @@ }, "estraverse": { "version": "4.3.0", + "resolved": false, "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { "version": "2.0.3", + "resolved": false, "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "exit-on-epipe": { "version": "1.0.1", + "resolved": false, "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" }, "ext": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "resolved": false, "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", "dev": true, "requires": { @@ -5996,7 +1712,7 @@ "dependencies": { "type": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "resolved": false, "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", "dev": true } @@ -6004,32 +1720,37 @@ }, "extsprintf": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", + "resolved": false, "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=", "dev": true }, "fast-deep-equal": { "version": "3.1.3", + "resolved": false, "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-diff": { "version": "1.2.0", + "resolved": false, "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", + "resolved": false, "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-levenshtein": { "version": "2.0.6", + "resolved": false, "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "ffi-napi": { "version": "4.0.3", + "resolved": false, "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", "dev": true, "requires": { @@ -6043,15 +1764,15 @@ }, "figures": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "resolved": false, "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, "requires": { "escape-string-regexp": "^1.0.5" } }, "file-entry-cache": { "version": "6.0.1", + "resolved": false, "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { @@ -6060,10 +1781,12 @@ }, "file-uri-to-path": { "version": "1.0.0", + "resolved": false, "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "find": { "version": "0.3.0", + "resolved": false, "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", "dev": true, "requires": { @@ -6072,6 +1795,7 @@ }, "find-up": { "version": "2.1.0", + "resolved": false, "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { @@ -6080,6 +1804,7 @@ }, "flat-cache": { "version": "3.0.4", + "resolved": false, "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { @@ -6089,19 +1814,23 @@ }, "flatted": { "version": "3.2.2", + "resolved": false, "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "dev": true }, "follow-redirects": { "version": "1.14.4", + "resolved": false, "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" }, "fs-constants": { "version": "1.0.0", + "resolved": false, "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { "version": "8.1.0", + "resolved": false, "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { @@ -6112,6 +1841,7 @@ "dependencies": { "jsonfile": { "version": "4.0.0", + "resolved": false, "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { @@ -6122,20 +1852,24 @@ }, "fs.realpath": { "version": "1.0.0", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "functional-red-black-tree": { "version": "1.0.1", + "resolved": false, "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, "gensync": { "version": "1.0.0-beta.2", + "resolved": false, "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, @@ -6147,11 +1881,13 @@ }, "get-func-name": { "version": "2.0.0", + "resolved": false, "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, "get-intrinsic": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { @@ -6162,6 +1898,7 @@ }, "get-symbol-description": { "version": "1.0.0", + "resolved": false, "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", "dev": true, "requires": { @@ -6171,11 +1908,13 @@ }, "get-symbol-from-current-process-h": { "version": "1.0.2", + "resolved": false, "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==", "dev": true }, "get-uv-event-loop-napi-h": { "version": "1.0.6", + "resolved": false, "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", "dev": true, "requires": { @@ -6184,6 +1923,7 @@ }, "glob": { "version": "7.2.0", + "resolved": false, "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", @@ -6196,6 +1936,7 @@ }, "glob-parent": { "version": "5.1.2", + "resolved": false, "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { @@ -6204,20 +1945,24 @@ }, "globals": { "version": "11.12.0", + "resolved": false, "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, "graceful-fs": { "version": "4.2.8", + "resolved": false, "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, "grpc-promise": { "version": "1.4.0", + "resolved": false, "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==", "dev": true }, "has": { "version": "1.0.3", + "resolved": false, "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { @@ -6226,21 +1971,25 @@ }, "has-bigints": { "version": "1.0.1", + "resolved": false, "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", "dev": true }, "has-flag": { "version": "3.0.0", + "resolved": false, "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { "version": "1.0.2", + "resolved": false, "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, "has-tostringtag": { "version": "1.0.0", + "resolved": false, "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "dev": true, "requires": { @@ -6249,28 +1998,34 @@ }, "hex64": { "version": "0.4.0", + "resolved": false, "integrity": "sha1-rRB4rIHVfXLeYjKxADvE9vsCh8A=" }, "husky": { "version": "6.0.0", + "resolved": false, "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", "dev": true }, "ieee754": { "version": "1.2.1", + "resolved": false, "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "4.0.6", + "resolved": false, "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "immediate": { "version": "3.0.6", + "resolved": false, "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "import-fresh": { "version": "3.3.0", + "resolved": false, "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { @@ -6280,17 +2035,19 @@ }, "imurmurhash": { "version": "0.1.4", + "resolved": false, "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "indent-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "resolved": false, "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, "inflight": { "version": "1.0.6", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", @@ -6299,10 +2056,12 @@ }, "inherits": { "version": "2.0.4", + "resolved": false, "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "internal-slot": { "version": "1.0.3", + "resolved": false, "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "dev": true, "requires": { @@ -6313,6 +2072,7 @@ }, "is-bigint": { "version": "1.0.4", + "resolved": false, "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "requires": { @@ -6321,6 +2081,7 @@ }, "is-boolean-object": { "version": "1.1.2", + "resolved": false, "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "requires": { @@ -6330,11 +2091,13 @@ }, "is-callable": { "version": "1.2.4", + "resolved": false, "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "dev": true }, "is-core-module": { "version": "2.7.0", + "resolved": false, "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", "dev": true, "requires": { @@ -6343,6 +2106,7 @@ }, "is-date-object": { "version": "1.0.5", + "resolved": false, "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "requires": { @@ -6351,17 +2115,18 @@ }, "is-extglob": { "version": "2.1.1", + "resolved": false, "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { "version": "4.0.3", + "resolved": false, "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { @@ -6370,11 +2135,13 @@ }, "is-negative-zero": { "version": "2.0.1", + "resolved": false, "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", "dev": true }, "is-number-object": { "version": "1.0.6", + "resolved": false, "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", "dev": true, "requires": { @@ -6383,6 +2150,7 @@ }, "is-plain-object": { "version": "2.0.4", + "resolved": false, "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "requires": { "isobject": "^3.0.1" @@ -6390,6 +2158,7 @@ }, "is-regex": { "version": "1.1.4", + "resolved": false, "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "requires": { @@ -6399,17 +2168,19 @@ }, "is-shared-array-buffer": { "version": "1.0.1", + "resolved": false, "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", "dev": true }, "is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "resolved": false, "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, "is-string": { "version": "1.0.7", + "resolved": false, "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "requires": { @@ -6418,6 +2189,7 @@ }, "is-symbol": { "version": "1.0.4", + "resolved": false, "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "requires": { @@ -6426,6 +2198,7 @@ }, "is-weakref": { "version": "1.0.1", + "resolved": false, "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", "dev": true, "requires": { @@ -6434,34 +2207,41 @@ }, "is-wsl": { "version": "1.1.0", + "resolved": false, "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, "isarray": { "version": "1.0.0", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", + "resolved": false, "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isobject": { "version": "3.0.1", + "resolved": false, "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "js-base64": { "version": "2.6.4", + "resolved": false, "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "dev": true }, "js-tokens": { "version": "4.0.0", + "resolved": false, "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { "version": "3.14.1", + "resolved": false, "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { @@ -6471,21 +2251,25 @@ }, "jsesc": { "version": "2.5.2", + "resolved": false, "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "json-schema-traverse": { "version": "0.4.1", + "resolved": false, "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", + "resolved": false, "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, "json5": { "version": "2.2.0", + "resolved": false, "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { @@ -6494,6 +2278,7 @@ }, "jsonfile": { "version": "5.0.0", + "resolved": false, "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", "dev": true, "requires": { @@ -6503,6 +2288,7 @@ }, "jszip": { "version": "3.7.1", + "resolved": false, "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", "requires": { "lie": "~3.3.0", @@ -6513,6 +2299,7 @@ "dependencies": { "readable-stream": { "version": "2.3.7", + "resolved": false, "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", @@ -6526,6 +2313,7 @@ }, "string_decoder": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -6535,11 +2323,12 @@ }, "kind-of": { "version": "6.0.3", + "resolved": false, "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "knuth-shuffle-seeded": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", + "resolved": false, "integrity": "sha1-AfG2VzOqdUDuCNiwF0Fk0iCB5OE=", "dev": true, "requires": { @@ -6548,6 +2337,7 @@ }, "lazystream": { "version": "1.0.0", + "resolved": false, "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "requires": { "readable-stream": "^2.0.5" @@ -6555,6 +2345,7 @@ "dependencies": { "readable-stream": { "version": "2.3.7", + "resolved": false, "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", @@ -6568,6 +2359,7 @@ }, "string_decoder": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -6577,6 +2369,7 @@ }, "levn": { "version": "0.4.1", + "resolved": false, "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { @@ -6586,6 +2379,7 @@ }, "lie": { "version": "3.3.0", + "resolved": false, "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "requires": { "immediate": "~3.0.5" @@ -6593,6 +2387,7 @@ }, "locate-path": { "version": "2.0.0", + "resolved": false, "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { @@ -6602,51 +2397,62 @@ }, "lodash": { "version": "4.17.21", + "resolved": false, "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.camelcase": { "version": "4.3.0", + "resolved": false, "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, "lodash.clonedeep": { "version": "4.5.0", + "resolved": false, "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, "lodash.defaults": { "version": "4.2.0", + "resolved": false, "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, "lodash.difference": { "version": "4.5.0", + "resolved": false, "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" }, "lodash.flatten": { "version": "4.4.0", + "resolved": false, "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" }, "lodash.isplainobject": { "version": "4.0.6", + "resolved": false, "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.merge": { "version": "4.6.2", + "resolved": false, "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, "lodash.truncate": { "version": "4.4.2", + "resolved": false, "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, "lodash.union": { "version": "4.6.0", + "resolved": false, "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "long": { "version": "4.0.0", + "resolved": false, "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", "dev": true }, @@ -6661,6 +2467,7 @@ }, "lru-cache": { "version": "6.0.0", + "resolved": false, "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { @@ -6669,6 +2476,7 @@ }, "minimatch": { "version": "3.0.4", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" @@ -6676,16 +2484,18 @@ }, "minimist": { "version": "1.2.5", + "resolved": false, "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "ms": { "version": "2.1.2", + "resolved": false, "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "mz": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "resolved": false, "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, "requires": { @@ -6696,12 +2506,13 @@ }, "natural-compare": { "version": "1.4.0", + "resolved": false, "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "next-tick": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "resolved": false, "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, @@ -6717,11 +2528,13 @@ }, "node-addon-api": { "version": "3.2.1", + "resolved": false, "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true }, "node-emoji": { "version": "1.11.0", + "resolved": false, "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", "dev": true, "requires": { @@ -6730,16 +2543,19 @@ }, "node-gyp-build": { "version": "4.3.0", + "resolved": false, "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", "dev": true }, "node-releases": { "version": "1.1.77", + "resolved": false, "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", "dev": true }, "normalize-path": { "version": "3.0.0", + "resolved": false, "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "nvm": { @@ -6749,22 +2565,24 @@ }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-inspect": { "version": "1.11.0", + "resolved": false, "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", "dev": true }, "object-keys": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, "object.assign": { "version": "4.1.2", + "resolved": false, "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, "requires": { @@ -6776,6 +2594,7 @@ }, "object.values": { "version": "1.1.5", + "resolved": false, "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, "requires": { @@ -6786,6 +2605,7 @@ }, "once": { "version": "1.4.0", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" @@ -6793,6 +2613,7 @@ }, "open": { "version": "6.4.0", + "resolved": false, "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", "dev": true, "requires": { @@ -6801,6 +2622,7 @@ }, "optionator": { "version": "0.9.1", + "resolved": false, "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { @@ -6814,6 +2636,7 @@ }, "p-limit": { "version": "1.3.0", + "resolved": false, "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { @@ -6822,6 +2645,7 @@ }, "p-locate": { "version": "2.0.0", + "resolved": false, "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { @@ -6830,12 +2654,13 @@ }, "p-try": { "version": "1.0.0", + "resolved": false, "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "pad-right": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/pad-right/-/pad-right-0.2.2.tgz", + "resolved": false, "integrity": "sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q=", "dev": true, "requires": { @@ -6844,10 +2669,12 @@ }, "pako": { "version": "1.0.11", + "resolved": false, "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parent-module": { "version": "1.0.1", + "resolved": false, "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { @@ -6856,35 +2683,42 @@ }, "path-exists": { "version": "3.0.0", + "resolved": false, "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "path-is-absolute": { "version": "1.0.1", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", + "resolved": false, "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { "version": "1.0.7", + "resolved": false, "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "pathval": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, "picocolors": { "version": "0.2.1", + "resolved": false, "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", "dev": true }, "pkg-dir": { "version": "2.0.0", + "resolved": false, "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { @@ -6893,16 +2727,19 @@ }, "prelude-ls": { "version": "1.2.1", + "resolved": false, "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prettier": { "version": "2.4.1", + "resolved": false, "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", "dev": true }, "prettier-linter-helpers": { "version": "1.0.0", + "resolved": false, "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, "requires": { @@ -6911,19 +2748,23 @@ }, "printj": { "version": "1.1.2", + "resolved": false, "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" }, "process-nextick-args": { "version": "2.0.1", + "resolved": false, "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", + "resolved": false, "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "protobufjs": { "version": "6.11.2", + "resolved": false, "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "dev": true, "requires": { @@ -6944,11 +2785,13 @@ }, "punycode": { "version": "2.1.1", + "resolved": false, "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "readable-stream": { "version": "3.6.0", + "resolved": false, "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", @@ -6958,6 +2801,7 @@ }, "readdir-glob": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", "requires": { "minimatch": "^3.0.4" @@ -6965,6 +2809,7 @@ }, "ref-napi": { "version": "3.0.3", + "resolved": false, "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", "dev": true, "requires": { @@ -6976,6 +2821,7 @@ }, "ref-struct-di": { "version": "1.1.1", + "resolved": false, "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", "dev": true, "requires": { @@ -6984,6 +2830,7 @@ "dependencies": { "debug": { "version": "3.2.7", + "resolved": false, "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { @@ -7015,12 +2862,13 @@ }, "regexpp": { "version": "3.2.0", + "resolved": false, "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "repeat-string": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "resolved": false, "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, @@ -7032,11 +2880,13 @@ }, "require-from-string": { "version": "2.0.2", + "resolved": false, "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "resolve": { "version": "1.20.0", + "resolved": false, "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { @@ -7046,6 +2896,7 @@ }, "resolve-from": { "version": "4.0.0", + "resolved": false, "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, @@ -7068,6 +2919,7 @@ }, "rimraf": { "version": "3.0.2", + "resolved": false, "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { @@ -7076,25 +2928,29 @@ }, "safe-buffer": { "version": "5.1.2", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "seed-random": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", + "resolved": false, "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=", "dev": true }, "semver": { "version": "6.3.0", + "resolved": false, "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "set-immediate-shim": { "version": "1.0.1", + "resolved": false, "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" }, "sha3": { "version": "2.1.4", + "resolved": false, "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", "requires": { "buffer": "6.0.3" @@ -7102,6 +2958,7 @@ }, "shallow-clone": { "version": "3.0.1", + "resolved": false, "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "requires": { "kind-of": "^6.0.2" @@ -7109,6 +2966,7 @@ }, "shebang-command": { "version": "2.0.0", + "resolved": false, "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { @@ -7117,11 +2975,13 @@ }, "shebang-regex": { "version": "3.0.0", + "resolved": false, "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "side-channel": { "version": "1.0.4", + "resolved": false, "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "requires": { @@ -7132,6 +2992,7 @@ }, "slice-ansi": { "version": "4.0.0", + "resolved": false, "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { @@ -7142,6 +3003,7 @@ "dependencies": { "ansi-styles": { "version": "4.3.0", + "resolved": false, "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { @@ -7150,6 +3012,7 @@ }, "color-convert": { "version": "2.0.1", + "resolved": false, "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { @@ -7158,6 +3021,7 @@ }, "color-name": { "version": "1.1.4", + "resolved": false, "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } @@ -7165,6 +3029,7 @@ }, "source-map": { "version": "0.5.7", + "resolved": false, "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, @@ -7188,18 +3053,19 @@ }, "sprintf-js": { "version": "1.0.3", + "resolved": false, "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "stack-chain": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-2.0.0.tgz", + "resolved": false, "integrity": "sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==", "dev": true }, "stack-generator": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", + "resolved": false, "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", "dev": true, "requires": { @@ -7208,13 +3074,13 @@ }, "stackframe": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", + "resolved": false, "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", "dev": true }, "stacktrace-gps": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz", + "resolved": false, "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", "dev": true, "requires": { @@ -7224,7 +3090,7 @@ "dependencies": { "source-map": { "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "resolved": false, "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true } @@ -7232,7 +3098,7 @@ }, "stacktrace-js": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "resolved": false, "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", "dev": true, "requires": { @@ -7241,22 +3107,9 @@ "stacktrace-gps": "^3.0.4" } }, - "string_decoder": { - "version": "1.3.0", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, "string-argv": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "resolved": false, "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", "dev": true }, @@ -7264,7 +3117,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7273,6 +3125,7 @@ }, "string.prototype.trimend": { "version": "1.0.4", + "resolved": false, "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, "requires": { @@ -7282,6 +3135,7 @@ }, "string.prototype.trimstart": { "version": "1.0.4", + "resolved": false, "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, "requires": { @@ -7289,26 +3143,44 @@ "define-properties": "^1.1.3" } }, + "string_decoder": { + "version": "1.3.0", + "resolved": false, + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": false, + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "strip-ansi": { "version": "6.0.1", + "resolved": false, "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } }, "strip-bom": { "version": "3.0.0", + "resolved": false, "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, "strip-json-comments": { "version": "3.1.1", + "resolved": false, "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { "version": "5.5.0", + "resolved": false, "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { @@ -7317,6 +3189,7 @@ }, "synchronized-promise": { "version": "0.3.1", + "resolved": false, "integrity": "sha512-Iy+JzrERSUrwpOHUDku8HHIddk8V6iLG9bPIzboP2i5RYkn2eSmRB8waSaX7Rc/+DUUsnFsoOHrmniwOp9BOgw==", "requires": { "deasync": "^0.1.15" @@ -7324,6 +3197,7 @@ }, "table": { "version": "6.7.2", + "resolved": false, "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", "dev": true, "requires": { @@ -7337,6 +3211,7 @@ "dependencies": { "ajv": { "version": "8.6.3", + "resolved": false, "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", "dev": true, "requires": { @@ -7348,6 +3223,7 @@ }, "json-schema-traverse": { "version": "1.0.0", + "resolved": false, "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true } @@ -7355,6 +3231,7 @@ }, "tar-stream": { "version": "2.2.0", + "resolved": false, "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "requires": { "bl": "^4.0.3", @@ -7366,16 +3243,18 @@ }, "tari_crypto": { "version": "0.9.1", + "resolved": false, "integrity": "sha512-K7LAtwQQKCeTH5CyyO8d/TiPDEePRaJ4e6+hrxpWv6jlkkAiS4m6csBuVqpSjyAlKeP8cQJpUQX2n22akOuZVg==" }, "text-table": { "version": "0.2.0", + "resolved": false, "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, "thenify": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "resolved": false, "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, "requires": { @@ -7384,7 +3263,7 @@ }, "thenify-all": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "resolved": false, "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", "dev": true, "requires": { @@ -7402,22 +3281,24 @@ }, "to-fast-properties": { "version": "2.0.0", + "resolved": false, "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "traverse-chain": { "version": "0.1.0", + "resolved": false, "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", "dev": true }, "ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "dev": true + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==" }, "tsconfig-paths": { "version": "3.11.0", + "resolved": false, "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", "dev": true, "requires": { @@ -7429,6 +3310,7 @@ "dependencies": { "json5": { "version": "1.0.1", + "resolved": false, "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { @@ -7445,12 +3327,13 @@ }, "type": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "resolved": false, "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, "type-check": { "version": "0.4.0", + "resolved": false, "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { @@ -7459,11 +3342,13 @@ }, "type-detect": { "version": "4.0.8", + "resolved": false, "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "unbox-primitive": { "version": "1.0.1", + "resolved": false, "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", "dev": true, "requires": { @@ -7475,6 +3360,7 @@ }, "universalify": { "version": "0.1.2", + "resolved": false, "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, @@ -7489,6 +3375,7 @@ }, "uri-js": { "version": "4.4.1", + "resolved": false, "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { @@ -7497,31 +3384,35 @@ }, "utf8": { "version": "3.0.0", + "resolved": false, "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" }, "util-arity": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", + "resolved": false, "integrity": "sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=", "dev": true }, "util-deprecate": { "version": "1.0.2", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { "version": "3.4.0", + "resolved": false, "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, "v8-compile-cache": { "version": "2.3.0", + "resolved": false, "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "verror": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "resolved": false, "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { @@ -7540,86 +3431,68 @@ "dependencies": { "@grpc/grpc-js": { "version": "1.3.6", - "integrity": "sha512-v7+LQFbqZKmd/Tvf5/j1Xlbq6jXL/4d+gUtm2TNX4QiEC3ELWADmGr2dGlUyLl6aKTuYfsN72vAsO5zmavYkEg==", "requires": { "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { "version": "0.5.6", - "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", "requires": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" } }, "@protobufjs/aspromise": { - "version": "1.1.2", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + "version": "1.1.2" }, "@protobufjs/base64": { - "version": "1.1.2", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + "version": "1.1.2" }, "@protobufjs/codegen": { - "version": "2.0.4", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + "version": "2.0.4" }, "@protobufjs/eventemitter": { - "version": "1.1.0", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + "version": "1.1.0" }, "@protobufjs/fetch": { "version": "1.1.0", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "@protobufjs/float": { - "version": "1.0.2", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + "version": "1.0.2" }, "@protobufjs/inquire": { - "version": "1.1.0", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + "version": "1.1.0" }, "@protobufjs/path": { - "version": "1.1.2", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + "version": "1.1.2" }, "@protobufjs/pool": { - "version": "1.1.0", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + "version": "1.1.0" }, "@protobufjs/utf8": { - "version": "1.1.0", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + "version": "1.1.0" }, "@types/long": { - "version": "4.0.1", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + "version": "4.0.1" }, "@types/node": { - "version": "16.3.2", - "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==" + "version": "16.3.2" }, "grpc-promise": { - "version": "1.4.0", - "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==" + "version": "1.4.0" }, "lodash.camelcase": { - "version": "4.3.0", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + "version": "4.3.0" }, "long": { - "version": "4.0.0", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "4.0.0" }, "protobufjs": { "version": "6.11.2", - "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -7640,6 +3513,7 @@ }, "which": { "version": "2.0.2", + "resolved": false, "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { @@ -7648,6 +3522,7 @@ }, "which-boxed-primitive": { "version": "1.0.2", + "resolved": false, "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, "requires": { @@ -7660,6 +3535,7 @@ }, "word-wrap": { "version": "1.2.3", + "resolved": false, "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, @@ -7702,6 +3578,7 @@ }, "wrappy": { "version": "1.0.2", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "y18n": { @@ -7712,6 +3589,7 @@ }, "yallist": { "version": "4.0.0", + "resolved": false, "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, @@ -7738,6 +3616,7 @@ }, "zip-stream": { "version": "4.1.0", + "resolved": false, "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", "requires": { "archiver-utils": "^2.1.0", From 9064b830c04000683aecf7b2972ffeabe5d90f08 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Tue, 9 Nov 2021 10:58:10 +0200 Subject: [PATCH 04/46] feat: optimize transaction validation for wallet (#3537) Description --- Optimized the transaction validation memory footprint by restricting all queries to the database to only return the information that is needed. This holds two considerable advantages for databases with many or large transactions: - The memory footprint is reduced. - Overall performance is increased. Motivation and Context --- See above. How Has This Been Tested? --- Unit tests Cucumber tests System-level tests with a large wallet --- .../src/chain_storage/blockchain_database.rs | 2 +- .../core/src/transactions/aggregated_body.rs | 2 +- .../core/src/transactions/transaction.rs | 4 +- .../down.sql | 48 +++++ .../up.sql | 5 + base_layer/wallet/src/schema.rs | 2 + .../wallet/src/transaction_service/error.rs | 4 + .../transaction_validation_protocol.rs | 117 ++++++----- .../transaction_service/storage/database.rs | 19 +- .../src/transaction_service/storage/models.rs | 32 ++- .../transaction_service/storage/sqlite_db.rs | 192 ++++++++++++++++-- .../tests/transaction_service/service.rs | 10 +- .../tests/transaction_service/storage.rs | 7 +- comms/dht/src/crypt.rs | 2 +- 14 files changed, 354 insertions(+), 92 deletions(-) create mode 100644 base_layer/wallet/migrations/2021-10-28-164318_add_transaction_signature/down.sql create mode 100644 base_layer/wallet/migrations/2021-10-28-164318_add_transaction_signature/up.sql diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 52c1ce2099..5b3b07abf8 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -1141,7 +1141,7 @@ pub fn calculate_mmr_roots(db: &T, block: &Block) -> Resul output_mmr.compress(); - // TODO: #testnetreset clean up this code + // TODO: #testnet_reset clean up this code let input_mr = if header.version == 1 { MutableMmr::::new(input_mmr.get_pruned_hash_set()?, Bitmap::create())?.get_merkle_root()? } else { diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index a7f3134678..45a30c730b 100644 --- a/base_layer/core/src/transactions/aggregated_body.rs +++ b/base_layer/core/src/transactions/aggregated_body.rs @@ -221,7 +221,7 @@ impl AggregateBody { } self.inputs.sort(); self.outputs.sort(); - // TODO: #testnetreset clean up this code + // TODO: #testnet_reset clean up this code if version <= 1 { self.kernels.sort_by(|a, b| a.deprecated_cmp(b)); } else { diff --git a/base_layer/core/src/transactions/transaction.rs b/base_layer/core/src/transactions/transaction.rs index 9c59bbaddf..9b806a73f0 100644 --- a/base_layer/core/src/transactions/transaction.rs +++ b/base_layer/core/src/transactions/transaction.rs @@ -779,7 +779,7 @@ impl TransactionOutput { Challenge::new() .chain(public_commitment_nonce.as_bytes()) .chain(script.as_bytes()) - // TODO: Use consensus encoded bytes #testnet reset + // TODO: Use consensus encoded bytes #testnet_reset .chain(features.to_v1_bytes()) .chain(sender_offset_public_key.as_bytes()) .chain(commitment.as_bytes()) @@ -887,7 +887,7 @@ impl TransactionOutput { impl Hashable for TransactionOutput { fn hash(&self) -> Vec { HashDigest::new() - // TODO: use consensus encoding #testnetreset + // TODO: use consensus encoding #testnet_reset .chain(self.features.to_v1_bytes()) .chain(self.commitment.as_bytes()) // .chain(range proof) // See docs as to why we exclude this diff --git a/base_layer/wallet/migrations/2021-10-28-164318_add_transaction_signature/down.sql b/base_layer/wallet/migrations/2021-10-28-164318_add_transaction_signature/down.sql new file mode 100644 index 0000000000..e6f0ea5bae --- /dev/null +++ b/base_layer/wallet/migrations/2021-10-28-164318_add_transaction_signature/down.sql @@ -0,0 +1,48 @@ +PRAGMA foreign_keys=OFF; +ALTER TABLE completed_transactions + RENAME TO completed_transactions_old; +CREATE TABLE completed_transactions ( + tx_id INTEGER PRIMARY KEY NOT NULL, + source_public_key BLOB NOT NULL, + destination_public_key BLOB NOT NULL, + amount INTEGER NOT NULL, + fee INTEGER NOT NULL, + transaction_protocol TEXT NOT NULL, + status INTEGER NOT NULL, + message TEXT NOT NULL, + timestamp DATETIME NOT NULL, + cancelled INTEGER NOT NULL DEFAULT 0, + direction INTEGER NULL DEFAULT NULL, + coinbase_block_height INTEGER NULL DEFAULT NULL, + send_count INTEGER NOT NULL DEFAULT 0, + last_send_timestamp DATETIME NULL DEFAULT NULL, + valid INTEGER NOT NULL DEFAULT 0, + confirmations INTEGER NULL DEFAULT NULL, + mined_height BIGINT NULL DEFAULT NULL, + mined_in_block BLOB NULL DEFAULT NULL + +); +INSERT INTO completed_transactions (tx_id, source_public_key, destination_public_key, amount, fee, transaction_protocol, + status, message, timestamp, cancelled, direction, coinbase_block_height, send_count, + last_send_timestamp, valid, confirmations) +SELECT tx_id, + source_public_key, + destination_public_key, + amount, + fee, + transaction_protocol, + status, + message, + timestamp, + cancelled, + direction, + coinbase_block_height, + send_count, + last_send_timestamp, + valid, + confirmations, + mined_height, + mined_in_block +FROM completed_transactions_old; +DROP TABLE completed_transactions_old; +PRAGMA foreign_keys=ON; diff --git a/base_layer/wallet/migrations/2021-10-28-164318_add_transaction_signature/up.sql b/base_layer/wallet/migrations/2021-10-28-164318_add_transaction_signature/up.sql new file mode 100644 index 0000000000..179e7b39bb --- /dev/null +++ b/base_layer/wallet/migrations/2021-10-28-164318_add_transaction_signature/up.sql @@ -0,0 +1,5 @@ +ALTER TABLE completed_transactions + ADD transaction_signature_nonce BLOB NOT NULL DEFAULT 0; + +ALTER TABLE completed_transactions + ADD transaction_signature_key BLOB NOT NULL DEFAULT 0; diff --git a/base_layer/wallet/src/schema.rs b/base_layer/wallet/src/schema.rs index 269b965f5d..570ae8cae1 100644 --- a/base_layer/wallet/src/schema.rs +++ b/base_layer/wallet/src/schema.rs @@ -25,6 +25,8 @@ table! { confirmations -> Nullable, mined_height -> Nullable, mined_in_block -> Nullable, + transaction_signature_nonce -> Binary, + transaction_signature_key -> Binary, } } diff --git a/base_layer/wallet/src/transaction_service/error.rs b/base_layer/wallet/src/transaction_service/error.rs index 17f3b324a8..bf7077ec72 100644 --- a/base_layer/wallet/src/transaction_service/error.rs +++ b/base_layer/wallet/src/transaction_service/error.rs @@ -166,6 +166,10 @@ pub enum TransactionKeyError { Source(ByteArrayError), #[error("Invalid destination PublicKey")] Destination(ByteArrayError), + #[error("Invalid transaction signature nonce")] + SignatureNonce(ByteArrayError), + #[error("Invalid transaction signature key")] + SignatureKey(ByteArrayError), } #[derive(Debug, Error)] diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs index fff77520b0..eff2c9f0cc 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs @@ -29,7 +29,7 @@ use crate::{ handle::{TransactionEvent, TransactionEventSender}, storage::{ database::{TransactionBackend, TransactionDatabase}, - models::CompletedTransaction, + sqlite_db::UnconfirmedTransactionInfo, }, }, }; @@ -39,7 +39,10 @@ use std::{ convert::{TryFrom, TryInto}, sync::Arc, }; -use tari_common_types::{transaction::TransactionStatus, types::BlockHash}; +use tari_common_types::{ + transaction::{TransactionStatus, TxId}, + types::BlockHash, +}; use tari_comms::protocol::rpc::{RpcError::RequestFailed, RpcStatusCode::NotFound}; use tari_core::{ base_node::{ @@ -61,6 +64,7 @@ pub struct TransactionValidationProtocol TransactionValidationProtocol @@ -99,14 +103,15 @@ where target: LOG_TARGET, "Checking if transactions have been mined since last we checked" ); - let unmined_transactions = self + // Fetch completed but unconfirmed transactions that were not imported + let unconfirmed_transactions = self .db - .fetch_unconfirmed_transactions() + .fetch_unconfirmed_transactions_info() .await .for_protocol(self.operation_id) .unwrap(); - for batch in unmined_transactions.chunks(self.config.max_tx_query_batch_size) { + for batch in unconfirmed_transactions.chunks(self.config.max_tx_query_batch_size) { let (mined, unmined, tip_info) = self .query_base_node_for_transactions(batch, &mut *base_node_wallet_client) .await @@ -117,22 +122,28 @@ where mined.len(), unmined.len() ); - for (tx, mined_height, mined_in_block, num_confirmations) in &mined { - info!(target: LOG_TARGET, "Updating transaction {} as mined", tx.tx_id); - self.update_transaction_as_mined(tx, mined_in_block, *mined_height, *num_confirmations) - .await?; + for (mined_tx, mined_height, mined_in_block, num_confirmations) in &mined { + info!(target: LOG_TARGET, "Updating transaction {} as mined", mined_tx.tx_id); + self.update_transaction_as_mined( + mined_tx.tx_id, + &mined_tx.status, + mined_in_block, + *mined_height, + *num_confirmations, + ) + .await?; } if let Some((tip_height, tip_block)) = tip_info { - for tx in &unmined { + for unmined_tx in &unmined { // Treat coinbases separately - if tx.is_coinbase() { - if tx.coinbase_block_height.unwrap_or_default() <= tip_height { - info!(target: LOG_TARGET, "Updated coinbase {} as abandoned", tx.tx_id); + if unmined_tx.is_coinbase() { + if unmined_tx.coinbase_block_height.unwrap_or_default() <= tip_height { + info!(target: LOG_TARGET, "Updated coinbase {} as abandoned", unmined_tx.tx_id); self.update_coinbase_as_abandoned( - tx, + unmined_tx.tx_id, &tip_block, tip_height, - tip_height.saturating_sub(tx.coinbase_block_height.unwrap_or_default()), + tip_height.saturating_sub(unmined_tx.coinbase_block_height.unwrap_or_default()), ) .await?; } else { @@ -140,13 +151,17 @@ where target: LOG_TARGET, "Coinbase not found, but it is for a block that is not yet in the chain. Coinbase \ height: {}, tip height:{}", - tx.coinbase_block_height.unwrap_or_default(), + unmined_tx.coinbase_block_height.unwrap_or_default(), tip_height ); } } else { - info!(target: LOG_TARGET, "Updated transaction {} as unmined", tx.tx_id); - self.update_transaction_as_unmined(tx).await?; + info!( + target: LOG_TARGET, + "Updated transaction {} as unmined", unmined_tx.tx_id + ); + self.update_transaction_as_unmined(unmined_tx.tx_id, &unmined_tx.status) + .await?; } } } @@ -216,7 +231,8 @@ where .map(|k| k.excess.to_hex()) .unwrap() ); - self.update_transaction_as_unmined(&last_mined_transaction).await?; + self.update_transaction_as_unmined(last_mined_transaction.tx_id, &last_mined_transaction.status) + .await?; } else { info!( target: LOG_TARGET, @@ -230,12 +246,12 @@ where async fn query_base_node_for_transactions( &self, - batch: &[CompletedTransaction], + batch: &[UnconfirmedTransactionInfo], base_node_client: &mut BaseNodeWalletRpcClient, ) -> Result< ( - Vec<(CompletedTransaction, u64, BlockHash, u64)>, - Vec, + Vec<(UnconfirmedTransactionInfo, u64, BlockHash, u64)>, + Vec, Option<(u64, BlockHash)>, ), TransactionServiceError, @@ -244,10 +260,10 @@ where let mut unmined = vec![]; let mut batch_signatures = HashMap::new(); - for tx in batch.iter() { - // Imported transactions do not have a signature - if let Some(sig) = tx.transaction.first_kernel_excess_sig() { - batch_signatures.insert(sig.clone(), tx); + for tx_info in batch.iter() { + // Imported transactions do not have a signature; this is represented by the default signature in info + if tx_info.signature != Signature::default() { + batch_signatures.insert(tx_info.signature.clone(), tx_info); } } @@ -259,7 +275,7 @@ where info!( target: LOG_TARGET, "Asking base node for location of {} transactions by excess signature", - batch.len() + batch_signatures.len() ); let batch_response = base_node_client @@ -275,16 +291,16 @@ where let response = TxQueryBatchResponse::try_from(response_proto) .map_err(TransactionServiceError::ProtobufConversionError)?; let sig = response.signature; - if let Some(completed_tx) = batch_signatures.get(&sig) { + if let Some(unconfirmed_tx) = batch_signatures.get(&sig) { if response.location == TxLocation::Mined { mined.push(( - (*completed_tx).clone(), + (*unconfirmed_tx).clone(), response.block_height, response.block_hash.unwrap(), response.confirmations, )); } else { - unmined.push((*completed_tx).clone()); + unmined.push((*unconfirmed_tx).clone()); } } } @@ -333,14 +349,15 @@ where #[allow(clippy::ptr_arg)] async fn update_transaction_as_mined( &mut self, - tx: &CompletedTransaction, + tx_id: TxId, + status: &TransactionStatus, mined_in_block: &BlockHash, mined_height: u64, num_confirmations: u64, ) -> Result<(), TransactionServiceProtocolError> { self.db .set_transaction_mined_height( - tx.tx_id, + tx_id, true, mined_height, mined_in_block.clone(), @@ -351,23 +368,20 @@ where .for_protocol(self.operation_id)?; if num_confirmations >= self.config.num_confirmations_required { - self.publish_event(TransactionEvent::TransactionMined { - tx_id: tx.tx_id, - is_valid: true, - }) + self.publish_event(TransactionEvent::TransactionMined { tx_id, is_valid: true }) } else { self.publish_event(TransactionEvent::TransactionMinedUnconfirmed { - tx_id: tx.tx_id, + tx_id, num_confirmations, is_valid: true, }) } - if tx.status == TransactionStatus::Coinbase { - if let Err(e) = self.output_manager_handle.set_coinbase_abandoned(tx.tx_id, false).await { + if *status == TransactionStatus::Coinbase { + if let Err(e) = self.output_manager_handle.set_coinbase_abandoned(tx_id, false).await { warn!( target: LOG_TARGET, - "Could not mark coinbase output for TxId: {} as not abandoned: {}", tx.tx_id, e + "Could not mark coinbase output for TxId: {} as not abandoned: {}", tx_id, e ); }; } @@ -378,14 +392,14 @@ where #[allow(clippy::ptr_arg)] async fn update_coinbase_as_abandoned( &mut self, - tx: &CompletedTransaction, + tx_id: TxId, mined_in_block: &BlockHash, mined_height: u64, num_confirmations: u64, ) -> Result<(), TransactionServiceProtocolError> { self.db .set_transaction_mined_height( - tx.tx_id, + tx_id, false, mined_height, mined_in_block.clone(), @@ -395,37 +409,38 @@ where .await .for_protocol(self.operation_id)?; - if let Err(e) = self.output_manager_handle.set_coinbase_abandoned(tx.tx_id, true).await { + if let Err(e) = self.output_manager_handle.set_coinbase_abandoned(tx_id, true).await { warn!( target: LOG_TARGET, - "Could not mark coinbase output for TxId: {} as abandoned: {}", tx.tx_id, e + "Could not mark coinbase output for TxId: {} as abandoned: {}", tx_id, e ); }; - self.publish_event(TransactionEvent::TransactionCancelled(tx.tx_id)); + self.publish_event(TransactionEvent::TransactionCancelled(tx_id)); Ok(()) } async fn update_transaction_as_unmined( &mut self, - tx: &CompletedTransaction, + tx_id: TxId, + status: &TransactionStatus, ) -> Result<(), TransactionServiceProtocolError> { self.db - .set_transaction_as_unmined(tx.tx_id) + .set_transaction_as_unmined(tx_id) .await .for_protocol(self.operation_id)?; - if tx.status == TransactionStatus::Coinbase { - if let Err(e) = self.output_manager_handle.set_coinbase_abandoned(tx.tx_id, false).await { + if *status == TransactionStatus::Coinbase { + if let Err(e) = self.output_manager_handle.set_coinbase_abandoned(tx_id, false).await { warn!( target: LOG_TARGET, - "Could not mark coinbase output for TxId: {} as not abandoned: {}", tx.tx_id, e + "Could not mark coinbase output for TxId: {} as not abandoned: {}", tx_id, e ); }; } - self.publish_event(TransactionEvent::TransactionBroadcast(tx.tx_id)); + self.publish_event(TransactionEvent::TransactionBroadcast(tx_id)); Ok(()) } } diff --git a/base_layer/wallet/src/transaction_service/storage/database.rs b/base_layer/wallet/src/transaction_service/storage/database.rs index b8ab7a7a65..8d97ebe971 100644 --- a/base_layer/wallet/src/transaction_service/storage/database.rs +++ b/base_layer/wallet/src/transaction_service/storage/database.rs @@ -28,7 +28,10 @@ use aes_gcm::Aes256Gcm; use chrono::Utc; use log::*; -use crate::transaction_service::storage::{models::WalletTransaction, sqlite_db::InboundTransactionSenderInfo}; +use crate::transaction_service::storage::{ + models::WalletTransaction, + sqlite_db::{InboundTransactionSenderInfo, UnconfirmedTransactionInfo}, +}; use std::{ collections::HashMap, fmt, @@ -54,7 +57,8 @@ pub trait TransactionBackend: Send + Sync + Clone { fn fetch_last_mined_transaction(&self) -> Result, TransactionStorageError>; - fn fetch_unconfirmed_transactions(&self) -> Result, TransactionStorageError>; + /// Light weight method to retrieve pertinent unconfirmed transactions info from completed transactions + fn fetch_unconfirmed_transactions_info(&self) -> Result, TransactionStorageError>; fn get_transactions_to_be_broadcast(&self) -> Result, TransactionStorageError>; @@ -131,9 +135,9 @@ pub trait TransactionBackend: Send + Sync + Clone { ) -> Result<(), TransactionStorageError>; /// Clears the mined block and height of a transaction fn set_transaction_as_unmined(&self, tx_id: TxId) -> Result<(), TransactionStorageError>; - /// Mark all transactions as unvalidated + /// Reset optional 'mined height' and 'mined in block' fields to nothing fn mark_all_transactions_as_unvalidated(&self) -> Result<(), TransactionStorageError>; - /// Get transaction sender info for all pending inbound transactions + /// Light weight method to retrieve pertinent transaction sender info for all pending inbound transactions fn get_pending_inbound_transaction_sender_info( &self, ) -> Result, TransactionStorageError>; @@ -431,8 +435,11 @@ where T: TransactionBackend + 'static self.db.fetch_last_mined_transaction() } - pub async fn fetch_unconfirmed_transactions(&self) -> Result, TransactionStorageError> { - self.db.fetch_unconfirmed_transactions() + /// Light weight method to return completed but unconfirmed transactions that were not imported + pub async fn fetch_unconfirmed_transactions_info( + &self, + ) -> Result, TransactionStorageError> { + self.db.fetch_unconfirmed_transactions_info() } /// This method returns all completed transactions that must be broadcast diff --git a/base_layer/wallet/src/transaction_service/storage/models.rs b/base_layer/wallet/src/transaction_service/storage/models.rs index 2125081a0b..a0925364a8 100644 --- a/base_layer/wallet/src/transaction_service/storage/models.rs +++ b/base_layer/wallet/src/transaction_service/storage/models.rs @@ -24,7 +24,7 @@ use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus, TxId}, - types::{BlockHash, PrivateKey}, + types::{BlockHash, PrivateKey, Signature}, }; use tari_comms::types::CommsPublicKey; use tari_core::transactions::{ @@ -138,6 +138,7 @@ pub struct CompletedTransaction { pub send_count: u32, pub last_send_timestamp: Option, pub valid: bool, + pub transaction_signature: Signature, pub confirmations: Option, pub mined_height: Option, pub mined_in_block: Option, @@ -158,6 +159,11 @@ impl CompletedTransaction { direction: TransactionDirection, coinbase_block_height: Option, ) -> Self { + let transaction_signature = if let Some(excess_sig) = transaction.first_kernel_excess_sig() { + excess_sig.clone() + } else { + Signature::default() + }; Self { tx_id, source_public_key, @@ -174,6 +180,7 @@ impl CompletedTransaction { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature, confirmations: None, mined_height: None, mined_in_block: None, @@ -181,7 +188,11 @@ impl CompletedTransaction { } pub fn is_coinbase(&self) -> bool { - self.coinbase_block_height.is_some() + if let Some(height) = self.coinbase_block_height { + height > 0 + } else { + false + } } } @@ -224,6 +235,19 @@ impl From for OutboundTransaction { impl From for CompletedTransaction { fn from(tx: OutboundTransaction) -> Self { + let transaction = if tx.sender_protocol.is_finalized() { + match tx.sender_protocol.get_transaction() { + Ok(tx) => tx.clone(), + Err(_) => Transaction::new(vec![], vec![], vec![], PrivateKey::default(), PrivateKey::default()), + } + } else { + Transaction::new(vec![], vec![], vec![], PrivateKey::default(), PrivateKey::default()) + }; + let transaction_signature = if let Some(excess_sig) = transaction.first_kernel_excess_sig() { + excess_sig.clone() + } else { + Signature::default() + }; Self { tx_id: tx.tx_id, source_public_key: Default::default(), @@ -234,12 +258,13 @@ impl From for CompletedTransaction { message: tx.message, timestamp: tx.timestamp, cancelled: tx.cancelled, - transaction: Transaction::new(vec![], vec![], vec![], PrivateKey::default(), PrivateKey::default()), + transaction, direction: TransactionDirection::Outbound, coinbase_block_height: None, send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature, confirmations: None, mined_height: None, mined_in_block: None, @@ -265,6 +290,7 @@ impl From for CompletedTransaction { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: Signature::default(), confirmations: None, mined_height: None, mined_in_block: None, diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index bc72c94366..307eee85fe 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -53,7 +53,7 @@ use tari_common_types::{ TransactionStatus, TxId, }, - types::{BlockHash, PublicKey}, + types::{BlockHash, PrivateKey, PublicKey, Signature}, }; use tari_comms::types::CommsPublicKey; use tari_core::transactions::tari_amount::MicroTari; @@ -75,10 +75,75 @@ pub struct TransactionServiceSqliteDatabase { impl TransactionServiceSqliteDatabase { pub fn new(database_connection: WalletDbConnection, cipher: Option) -> Self { - Self { + let mut new_self = Self { database_connection, cipher: Arc::new(RwLock::new(cipher)), + }; + + // TODO: Remove this call for the next #testnet_reset + match new_self.add_transaction_signature_for_legacy_transactions() { + Ok(count) => { + if count > 0 { + info!( + target: LOG_TARGET, + "Updated transaction signatures for {} legacy transactions", count + ); + } + }, + Err(e) => warn!( + target: LOG_TARGET, + "Legacy transaction signatures could not be updated: {:?}", e + ), + }; + + new_self + } + + // TODO: Remove this function for the next #testnet_reset + fn add_transaction_signature_for_legacy_transactions(&mut self) -> Result { + let conn = self.database_connection.acquire_lock(); + let txs_sql = completed_transactions::table + .filter( + completed_transactions::transaction_signature_nonce + .eq(completed_transactions::transaction_signature_key), + ) + .load::(&*conn)?; + + let mut count = 0u32; + if !txs_sql.is_empty() { + info!( + target: LOG_TARGET, + "Updating transaction signatures for {} legacy transactions...", + txs_sql.len() + ); + for mut tx_sql in txs_sql { + self.decrypt_if_necessary(&mut tx_sql)?; + let tx = CompletedTransaction::try_from(tx_sql.clone())?; + let (transaction_signature_nonce, transaction_signature_key) = + if let Some(transaction_signature) = tx.transaction.first_kernel_excess_sig() { + ( + Some(transaction_signature.get_public_nonce().as_bytes().to_vec()), + Some(transaction_signature.get_signature().as_bytes().to_vec()), + ) + } else { + ( + Some(Signature::default().get_public_nonce().as_bytes().to_vec()), + Some(Signature::default().get_signature().as_bytes().to_vec()), + ) + }; + tx_sql.update( + UpdateCompletedTransactionSql { + transaction_signature_nonce, + transaction_signature_key, + ..Default::default() + }, + &(*conn), + )?; + count += 1; + } } + + Ok(count) } fn insert(&self, kvp: DbKeyValuePair, conn: MutexGuard) -> Result<(), TransactionStorageError> { @@ -1042,34 +1107,28 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { Ok(result) } - fn fetch_unconfirmed_transactions(&self) -> Result, TransactionStorageError> { + fn fetch_unconfirmed_transactions_info(&self) -> Result, TransactionStorageError> { let start = Instant::now(); let conn = self.database_connection.acquire_lock(); let acquire_lock = start.elapsed(); - let txs = completed_transactions::table - .filter( - completed_transactions::mined_height - .is_null() - .or(completed_transactions::status.eq(TransactionStatus::MinedUnconfirmed as i32)), - ) - .filter(completed_transactions::cancelled.eq(false as i32)) - .order_by(completed_transactions::tx_id) - .load::(&*conn)?; - - let mut result = vec![]; - for mut tx in txs { - self.decrypt_if_necessary(&mut tx)?; - result.push(tx.try_into()?); + let mut tx_info: Vec = vec![]; + match UnconfirmedTransactionInfoSql::fetch_unconfirmed_transactions_info(&*conn) { + Ok(info) => { + for item in info { + tx_info.push(UnconfirmedTransactionInfo::try_from(item)?); + } + }, + Err(e) => return Err(e), } trace!( target: LOG_TARGET, - "sqlite profile - fetch_unconfirmed_transactions: lock {} + db_op {} = {} ms", + "sqlite profile - fetch_unconfirmed_transactions_info: lock {} + db_op {} = {} ms", acquire_lock.as_millis(), (start.elapsed() - acquire_lock).as_millis(), start.elapsed().as_millis() ); - Ok(result) + Ok(tx_info) } fn get_transactions_to_be_broadcast(&self) -> Result, TransactionStorageError> { @@ -1591,6 +1650,8 @@ struct CompletedTransactionSql { confirmations: Option, mined_height: Option, mined_in_block: Option>, + transaction_signature_nonce: Vec, + transaction_signature_key: Vec, } impl CompletedTransactionSql { @@ -1807,6 +1868,8 @@ impl TryFrom for CompletedTransactionSql { confirmations: c.confirmations.map(|ic| ic as i64), mined_height: c.mined_height.map(|ic| ic as i64), mined_in_block: c.mined_in_block, + transaction_signature_nonce: c.transaction_signature.get_public_nonce().to_vec(), + transaction_signature_key: c.transaction_signature.get_signature().to_vec(), }) } } @@ -1827,6 +1890,13 @@ impl TryFrom for CompletedTransaction { type Error = CompletedTransactionConversionError; fn try_from(c: CompletedTransactionSql) -> Result { + let transaction_signature = match PublicKey::from_vec(&c.transaction_signature_nonce) { + Ok(public_nonce) => match PrivateKey::from_vec(&c.transaction_signature_key) { + Ok(signature) => Signature::new(public_nonce, signature), + Err(_) => Signature::default(), + }, + Err(_) => Signature::default(), + }; Ok(Self { tx_id: c.tx_id as u64, source_public_key: PublicKey::from_vec(&c.source_public_key).map_err(TransactionKeyError::Source)?, @@ -1844,6 +1914,7 @@ impl TryFrom for CompletedTransaction { send_count: c.send_count as u32, last_send_timestamp: c.last_send_timestamp, valid: c.valid != 0, + transaction_signature, confirmations: c.confirmations.map(|ic| ic as u64), mined_height: c.mined_height.map(|ic| ic as u64), mined_in_block: c.mined_in_block, @@ -1865,6 +1936,77 @@ pub struct UpdateCompletedTransactionSql { confirmations: Option>, mined_height: Option>, mined_in_block: Option>>, + transaction_signature_nonce: Option>, + transaction_signature_key: Option>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct UnconfirmedTransactionInfo { + pub tx_id: TxId, + pub signature: Signature, + pub status: TransactionStatus, + pub coinbase_block_height: Option, +} + +impl UnconfirmedTransactionInfo { + pub fn is_coinbase(&self) -> bool { + if let Some(height) = self.coinbase_block_height { + height > 0 + } else { + false + } + } +} + +impl TryFrom for UnconfirmedTransactionInfo { + type Error = TransactionStorageError; + + fn try_from(i: UnconfirmedTransactionInfoSql) -> Result { + Ok(Self { + tx_id: i.tx_id as u64, + signature: Signature::new( + PublicKey::from_vec(&i.transaction_signature_nonce)?, + PrivateKey::from_vec(&i.transaction_signature_key)?, + ), + status: TransactionStatus::try_from(i.status)?, + coinbase_block_height: i.coinbase_block_height.map(|b| b as u64), + }) + } +} + +#[derive(Clone, Queryable)] +pub struct UnconfirmedTransactionInfoSql { + pub tx_id: i64, + pub status: i32, + pub transaction_signature_nonce: Vec, + pub transaction_signature_key: Vec, + pub coinbase_block_height: Option, +} + +impl UnconfirmedTransactionInfoSql { + /// This method returns completed but unconfirmed transactions + pub fn fetch_unconfirmed_transactions_info( + conn: &SqliteConnection, + ) -> Result, TransactionStorageError> { + // TODO: Should we not return cancelled transactions as well and handle it upstream? It could be mined. + let query_result = completed_transactions::table + .select(( + completed_transactions::tx_id, + completed_transactions::status, + completed_transactions::transaction_signature_nonce, + completed_transactions::transaction_signature_key, + completed_transactions::coinbase_block_height, + )) + .filter( + completed_transactions::mined_height + .is_null() + .or(completed_transactions::status.eq(TransactionStatus::MinedUnconfirmed as i32)), + ) + .filter(completed_transactions::cancelled.eq(false as i32)) + .order_by(completed_transactions::tx_id) + .load::(&*conn)?; + Ok(query_result) + } } #[cfg(test)] @@ -1887,7 +2029,7 @@ mod test { use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus}, - types::{HashDigest, PrivateKey, PublicKey}, + types::{HashDigest, PrivateKey, PublicKey, Signature}, }; use tari_core::transactions::{ tari_amount::MicroTari, @@ -2094,6 +2236,7 @@ mod test { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, @@ -2114,6 +2257,7 @@ mod test { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, @@ -2243,6 +2387,7 @@ mod test { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, @@ -2264,6 +2409,7 @@ mod test { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, @@ -2275,7 +2421,7 @@ mod test { destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), amount, fee: MicroTari::from(100), - transaction: tx, + transaction: tx.clone(), status: TransactionStatus::Coinbase, message: "Hey!".to_string(), timestamp: Utc::now().naive_utc(), @@ -2285,6 +2431,7 @@ mod test { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, @@ -2396,6 +2543,7 @@ mod test { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: Signature::default(), confirmations: None, mined_height: None, mined_in_block: None, @@ -2478,6 +2626,7 @@ mod test { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: Signature::default(), confirmations: None, mined_height: None, mined_in_block: None, @@ -2562,6 +2711,7 @@ mod test { send_count: 0, last_send_timestamp: None, valid, + transaction_signature: Signature::default(), confirmations: None, mined_height: None, mined_in_block: None, diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index d6134ccbc6..32ac55970c 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -1816,6 +1816,7 @@ fn test_power_mode_updates() { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, @@ -1827,7 +1828,7 @@ fn test_power_mode_updates() { destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), amount: 6000 * uT, fee: MicroTari::from(200), - transaction: tx, + transaction: tx.clone(), status: TransactionStatus::Completed, message: "Yo!".to_string(), timestamp: Utc::now().naive_utc(), @@ -1837,6 +1838,7 @@ fn test_power_mode_updates() { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, @@ -4980,7 +4982,7 @@ fn broadcast_all_completed_transactions_on_startup() { destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), amount: 5000 * uT, fee: MicroTari::from(20), - transaction: tx, + transaction: tx.clone(), status: TransactionStatus::Completed, message: "Yo!".to_string(), timestamp: Utc::now().naive_utc(), @@ -4990,6 +4992,7 @@ fn broadcast_all_completed_transactions_on_startup() { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, @@ -5125,7 +5128,7 @@ fn dont_broadcast_invalid_transactions() { destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), amount: 5000 * uT, fee: MicroTari::from(20), - transaction: tx, + transaction: tx.clone(), status: TransactionStatus::Completed, message: "Yo!".to_string(), timestamp: Utc::now().naive_utc(), @@ -5135,6 +5138,7 @@ fn dont_broadcast_invalid_transactions() { send_count: 0, last_send_timestamp: None, valid: false, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, diff --git a/base_layer/wallet/tests/transaction_service/storage.rs b/base_layer/wallet/tests/transaction_service/storage.rs index 0e9480035e..da77f6bad5 100644 --- a/base_layer/wallet/tests/transaction_service/storage.rs +++ b/base_layer/wallet/tests/transaction_service/storage.rs @@ -28,7 +28,7 @@ use chrono::Utc; use rand::rngs::OsRng; use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus}, - types::{HashDigest, PrivateKey, PublicKey}, + types::{HashDigest, PrivateKey, PublicKey, Signature}, }; use tari_core::transactions::{ tari_amount::{uT, MicroTari}, @@ -266,6 +266,7 @@ pub fn test_db_backend(backend: T) { send_count: 0, last_send_timestamp: None, valid: true, + transaction_signature: tx.first_kernel_excess_sig().unwrap_or(&Signature::default()).clone(), confirmations: None, mined_height: None, mined_in_block: None, @@ -533,7 +534,7 @@ pub fn test_db_backend(backend: T) { panic!("Should have found cancelled outbound tx"); } - let unmined_txs = runtime.block_on(db.fetch_unconfirmed_transactions()).unwrap(); + let unmined_txs = runtime.block_on(db.fetch_unconfirmed_transactions_info()).unwrap(); assert_eq!(unmined_txs.len(), 4); @@ -541,7 +542,7 @@ pub fn test_db_backend(backend: T) { .block_on(db.set_transaction_as_unmined(completed_txs[0].tx_id)) .unwrap(); - let unmined_txs = runtime.block_on(db.fetch_unconfirmed_transactions()).unwrap(); + let unmined_txs = runtime.block_on(db.fetch_unconfirmed_transactions_info()).unwrap(); assert_eq!(unmined_txs.len(), 5); } diff --git a/comms/dht/src/crypt.rs b/comms/dht/src/crypt.rs index c34b96f2b2..d6ea8c2978 100644 --- a/comms/dht/src/crypt.rs +++ b/comms/dht/src/crypt.rs @@ -105,7 +105,7 @@ pub fn create_origin_mac_challenge_parts( body: &[u8], ) -> Challenge { let mut mac_challenge = Challenge::new(); - // TODO: #testnetreset remove conditional + // TODO: #testnet_reset remove conditional if protocol_version.as_major() > 1 { mac_challenge.update(&protocol_version.to_bytes()); mac_challenge.update(destination.to_inner_bytes().as_slice()); From 413757bcea5474524b18a860a95df255cbe95d33 Mon Sep 17 00:00:00 2001 From: David Main <51991544+StriderDM@users.noreply.github.com> Date: Tue, 9 Nov 2021 12:30:00 +0200 Subject: [PATCH 05/46] refactor!: remove outdated wallet_ffi balance methods (#3528) Description Removed outdated balance methods in wallet_ffi Updated wallet.h Updated cucumber tests Motivation and Context Removal of outdated methods Merge https://github.com/tari-project/tari/pull/3547 first. How Has This Been Tested? nvm use 12.22.6 && node_modules/.bin/cucumber-js --profile "ci" --tags "not @long-running and not @broken and @wallet-ffi" --- base_layer/wallet/src/wallet.rs | 2 +- base_layer/wallet_ffi/src/error.rs | 6 + base_layer/wallet_ffi/src/lib.rs | 146 ++++-------------- base_layer/wallet_ffi/wallet.h | 12 +- integration_tests/features/WalletFFI.feature | 2 +- .../features/support/ffi_steps.js | 2 +- integration_tests/helpers/ffi/ffiInterface.js | 38 +---- integration_tests/helpers/ffi/wallet.js | 50 ++++-- integration_tests/helpers/walletFFIClient.js | 4 + 9 files changed, 91 insertions(+), 171 deletions(-) diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 0f3ee66fda..f184cadc42 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -266,7 +266,7 @@ where /// This method consumes the wallet so that the handles are dropped which will result in the services async loops /// exiting. pub async fn wait_until_shutdown(self) { - self.comms.clone().wait_until_shutdown().await; + self.comms.to_owned().wait_until_shutdown().await; } /// This function will set the base node that the wallet uses to broadcast transactions, monitor outputs, and diff --git a/base_layer/wallet_ffi/src/error.rs b/base_layer/wallet_ffi/src/error.rs index fb9a8b09da..37349dd778 100644 --- a/base_layer/wallet_ffi/src/error.rs +++ b/base_layer/wallet_ffi/src/error.rs @@ -54,6 +54,8 @@ pub enum InterfaceError { InvalidEmojiId, #[error("An error has occurred due to an invalid argument: `{0}`")] InvalidArgument(String), + #[error("Balance Unavailable")] + BalanceError, } /// This struct is meant to hold an error for use by FFI client applications. The error has an integer code and string @@ -96,6 +98,10 @@ impl From for LibWalletError { code: 7, message: format!("{:?}", v), }, + InterfaceError::BalanceError => Self { + code: 8, + message: "Balance Unavailable".to_string(), + }, } } } diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index ea9753b0c9..6fb0da6528 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -3050,6 +3050,39 @@ pub unsafe extern "C" fn wallet_create( } } +/// Retrieves the balance from a wallet +/// +/// ## Arguments +/// `wallet` - The TariWallet pointer. +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// ## Returns +/// `*mut Balance` - Returns the pointer to the TariBalance or null if error occurs +/// +/// # Safety +/// The ```balance_destroy``` method must be called when finished with a TariBalance to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn wallet_get_balance(wallet: *mut TariWallet, error_out: *mut c_int) -> *mut TariBalance { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if wallet.is_null() { + error = LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + let balance = (*wallet) + .runtime + .block_on((*wallet).wallet.output_manager_service.get_balance()); + match balance { + Ok(balance) => Box::into_raw(Box::new(balance)), + Err(_) => { + error = LibWalletError::from(InterfaceError::BalanceError).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + /// Signs a message using the public key of the TariWallet /// /// ## Arguments @@ -3457,119 +3490,6 @@ pub unsafe extern "C" fn balance_destroy(balance: *mut TariBalance) { } } -/// Gets the available balance from a TariWallet. This is the balance the user can spend. -/// -/// ## Arguments -/// `wallet` - The TariWallet pointer -/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions -/// as an out parameter. -/// -/// ## Returns -/// `c_ulonglong` - The available balance, 0 if wallet is null -/// -/// # Safety -/// None -#[no_mangle] -pub unsafe extern "C" fn wallet_get_available_balance(wallet: *mut TariWallet, error_out: *mut c_int) -> c_ulonglong { - let mut error = 0; - ptr::swap(error_out, &mut error as *mut c_int); - if wallet.is_null() { - error = LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return 0; - } - - match (*wallet) - .runtime - .block_on((*wallet).wallet.output_manager_service.get_balance()) - { - Ok(b) => c_ulonglong::from(b.available_balance), - Err(e) => { - error = LibWalletError::from(WalletError::OutputManagerError(e)).code; - ptr::swap(error_out, &mut error as *mut c_int); - 0 - }, - } -} - -/// Gets the incoming balance from a `TariWallet`. This is the uncleared balance of Tari that is -/// expected to come into the `TariWallet` but is not yet spendable. -/// -/// ## Arguments -/// `wallet` - The TariWallet pointer -/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions -/// as an out parameter. -/// -/// ## Returns -/// `c_ulonglong` - The incoming balance, 0 if wallet is null -/// -/// # Safety -/// None -#[no_mangle] -pub unsafe extern "C" fn wallet_get_pending_incoming_balance( - wallet: *mut TariWallet, - error_out: *mut c_int, -) -> c_ulonglong { - let mut error = 0; - ptr::swap(error_out, &mut error as *mut c_int); - if wallet.is_null() { - error = LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return 0; - } - - match (*wallet) - .runtime - .block_on((*wallet).wallet.output_manager_service.get_balance()) - { - Ok(b) => c_ulonglong::from(b.pending_incoming_balance), - Err(e) => { - error = LibWalletError::from(WalletError::OutputManagerError(e)).code; - ptr::swap(error_out, &mut error as *mut c_int); - 0 - }, - } -} - -/// Gets the outgoing balance from a `TariWallet`. This is the uncleared balance of Tari that has -/// been spent -/// -/// ## Arguments -/// `wallet` - The TariWallet pointer -/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions -/// as an out parameter. -/// -/// ## Returns -/// `c_ulonglong` - The outgoing balance, 0 if wallet is null -/// -/// # Safety -/// None -#[no_mangle] -pub unsafe extern "C" fn wallet_get_pending_outgoing_balance( - wallet: *mut TariWallet, - error_out: *mut c_int, -) -> c_ulonglong { - let mut error = 0; - ptr::swap(error_out, &mut error as *mut c_int); - if wallet.is_null() { - error = LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return 0; - } - - match (*wallet) - .runtime - .block_on((*wallet).wallet.output_manager_service.get_balance()) - { - Ok(b) => c_ulonglong::from(b.pending_outgoing_balance), - Err(e) => { - error = LibWalletError::from(WalletError::OutputManagerError(e)).code; - ptr::swap(error_out, &mut error as *mut c_int); - 0 - }, - } -} - /// Sends a TariPendingOutboundTransaction /// /// ## Arguments diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 376462987f..7b0ee7ca16 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -477,6 +477,9 @@ struct TariWallet *wallet_create(struct TariCommsConfig *config, bool *recovery_in_progress, int *error_out); +// Gets the balance +struct TariBalance *wallet_get_balance(struct TariWallet *wallet, int *error_out); + // Signs a message char *wallet_sign_message(struct TariWallet *wallet, const char *msg, int *error_out); @@ -504,15 +507,6 @@ unsigned long long balance_get_pending_incoming(struct TariBalance *balance, int // Gets the available balance from a TariBalance unsigned long long balance_get_pending_outgoing(struct TariBalance *balance, int *error_out); -// Gets the available balance from a TariWallet -unsigned long long wallet_get_available_balance(struct TariWallet *wallet, int *error_out); - -// Gets the incoming balance from a TariWallet -unsigned long long wallet_get_pending_incoming_balance(struct TariWallet *wallet, int *error_out); - -// Gets the outgoing balance from a TariWallet -unsigned long long wallet_get_pending_outgoing_balance(struct TariWallet *wallet, int *error_out); - // Get a fee estimate from a TariWallet for a given amount unsigned long long wallet_get_fee_estimate(struct TariWallet *wallet, unsigned long long amount, unsigned long long fee_per_gram, unsigned long long num_kernels, unsigned long long num_outputs, int *error_out); diff --git a/integration_tests/features/WalletFFI.feature b/integration_tests/features/WalletFFI.feature index 79f66144ab..79b23f241d 100644 --- a/integration_tests/features/WalletFFI.feature +++ b/integration_tests/features/WalletFFI.feature @@ -156,7 +156,7 @@ Feature: Wallet FFI # Scenario: As a client I want feedback about my connection status to the specifed Base Node # Scenario: As a client I want feedback about the wallet restoration process - # As a client I want to be able to restore my wallet from seed words + # It's a subtest of "As a client I want to be able to restore my wallet from seed words" # Scenario: As a client I want feedback about TXO and TX validation processes # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" diff --git a/integration_tests/features/support/ffi_steps.js b/integration_tests/features/support/ffi_steps.js index c0f73e82cb..6c083b01d1 100644 --- a/integration_tests/features/support/ffi_steps.js +++ b/integration_tests/features/support/ffi_steps.js @@ -444,7 +444,7 @@ Then( 120 ); - let balance = wallet.getBalance().available; + let balance = wallet.pollBalance().available; if (!(balance >= amount)) { console.log("Balance not adequate!"); diff --git a/integration_tests/helpers/ffi/ffiInterface.js b/integration_tests/helpers/ffi/ffiInterface.js index 1d47cb7c83..78430ac90e 100644 --- a/integration_tests/helpers/ffi/ffiInterface.js +++ b/integration_tests/helpers/ffi/ffiInterface.js @@ -294,6 +294,7 @@ class InterfaceFFI { this.intPtr, ], ], + wallet_get_balance: [this.ptr, [this.ptr, this.intPtr]], wallet_sign_message: [ this.stringPtr, [this.ptr, this.string, this.intPtr], @@ -312,15 +313,6 @@ class InterfaceFFI { balance_get_time_locked: [this.ulonglong, [this.ptr, this.intPtr]], balance_get_pending_incoming: [this.ulonglong, [this.ptr, this.intPtr]], balance_get_pending_outgoing: [this.ulonglong, [this.ptr, this.intPtr]], - wallet_get_available_balance: [this.ulonglong, [this.ptr, this.intPtr]], - wallet_get_pending_incoming_balance: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - wallet_get_pending_outgoing_balance: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], wallet_get_fee_estimate: [ this.ulonglong, [ @@ -1211,6 +1203,13 @@ class InterfaceFFI { return result; } + static walletGetBalance(ptr) { + let error = this.initError(); + let result = this.fn.wallet_get_balance(ptr, error); + this.checkErrorResult(error, `walletGetBalance`); + return result; + } + static walletGetPublicKey(ptr) { let error = this.initError(); let result = this.fn.wallet_get_public_key(ptr, error); @@ -1292,27 +1291,6 @@ class InterfaceFFI { return result; } - static walletGetAvailableBalance(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_available_balance(ptr, error); - this.checkErrorResult(error, `walletGetAvailableBalance`); - return result; - } - - static walletGetPendingIncomingBalance(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_pending_incoming_balance(ptr, error); - this.checkErrorResult(error, `walletGetPendingIncomingBalance`); - return result; - } - - static walletGetPendingOutgoingBalance(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_pending_outgoing_balance(ptr, error); - this.checkErrorResult(error, `walletGetPendingOutgoingBalance`); - return result; - } - static walletGetFeeEstimate( ptr, amount, diff --git a/integration_tests/helpers/ffi/wallet.js b/integration_tests/helpers/ffi/wallet.js index 2ce779e0b0..0797ad81d5 100644 --- a/integration_tests/helpers/ffi/wallet.js +++ b/integration_tests/helpers/ffi/wallet.js @@ -10,8 +10,17 @@ const Contacts = require("./contacts"); const Balance = require("./balance"); const utf8 = require("utf8"); + +class WalletBalance { + available = 0; + timeLocked = 0; + pendingIn = 0; + pendingOut = 0; +} + class Wallet { ptr; + balance = new WalletBalance(); log_path = ""; receivedTransaction = 0; receivedTransactionReply = 0; @@ -269,8 +278,16 @@ class Wallet { onBalanceUpdated = (ptr) => { let b = new Balance(); b.pointerAssign(ptr); + this.balance.available = b.getAvailable(); + this.balance.timeLocked = b.getTimeLocked(); + this.balance.pendingIn = b.getPendingIncoming(); + this.balance.pendingOut = b.getPendingOutgoing(); console.log( - `${new Date().toISOString()} callbackBalanceUpdated: available = ${b.getAvailable()}, time locked = ${b.getTimeLocked()} pending incoming = ${b.getPendingIncoming()} pending outgoing = ${b.getPendingOutgoing()}` + `${new Date().toISOString()} callbackBalanceUpdated: available = ${ + this.balance.available + }, time locked = ${this.balance.timeLocked} pending incoming = ${ + this.balance.pendingIn + } pending outgoing = ${this.balance.pendingOut}` ); b.destroy(); }; @@ -320,6 +337,22 @@ class Wallet { return result; } + getBalance() { + return this.balance; + } + + pollBalance() { + let b = new Balance(); + let ptr = InterfaceFFI.walletGetBalance(this.ptr); + b.pointerAssign(ptr); + this.balance.available = b.getAvailable(); + this.balance.timeLocked = b.getTimeLocked(); + this.balance.pendingIn = b.getPendingIncoming(); + this.balance.pendingOut = b.getPendingOutgoing(); + b.destroy(); + return this.balance; + } + getEmojiId() { let ptr = InterfaceFFI.walletGetPublicKey(this.ptr); let pk = new PublicKey(); @@ -329,21 +362,6 @@ class Wallet { return result; } - getBalance() { - let available = InterfaceFFI.walletGetAvailableBalance(this.ptr); - let pendingIncoming = InterfaceFFI.walletGetPendingIncomingBalance( - this.ptr - ); - let pendingOutgoing = InterfaceFFI.walletGetPendingOutgoingBalance( - this.ptr - ); - return { - pendingIn: pendingIncoming, - pendingOut: pendingOutgoing, - available: available, - }; - } - addBaseNodePeer(public_key_hex, address) { let public_key = PublicKey.fromHexString(utf8.encode(public_key_hex)); let result = InterfaceFFI.walletAddBaseNodePeer( diff --git a/integration_tests/helpers/walletFFIClient.js b/integration_tests/helpers/walletFFIClient.js index 0929f96aaf..592ddaff6c 100644 --- a/integration_tests/helpers/walletFFIClient.js +++ b/integration_tests/helpers/walletFFIClient.js @@ -74,6 +74,10 @@ class WalletFFIClient { return this.wallet.getBalance(); } + pollBalance() { + return this.wallet.pollBalance(); + } + addBaseNodePeer(public_key_hex, address) { return this.wallet.addBaseNodePeer(public_key_hex, address); } From 739303adbc51b47e00150041c31eeeaad2c9fa8f Mon Sep 17 00:00:00 2001 From: Mike the Tike Date: Tue, 9 Nov 2021 13:33:44 +0200 Subject: [PATCH 06/46] v0.21.0 --- Cargo.lock | 98 ++++++++++++------- applications/tari_app_grpc/Cargo.toml | 4 +- applications/tari_app_utilities/Cargo.toml | 2 +- applications/tari_base_node/Cargo.toml | 2 +- applications/tari_console_wallet/Cargo.toml | 4 +- .../tari_merge_mining_proxy/Cargo.toml | 2 +- applications/tari_mining_node/Cargo.toml | 2 +- applications/test_faucet/Cargo.toml | 2 +- base_layer/common_types/Cargo.toml | 2 +- base_layer/core/Cargo.toml | 30 +++--- base_layer/key_manager/Cargo.toml | 2 +- base_layer/mmr/Cargo.toml | 4 +- base_layer/p2p/Cargo.toml | 18 ++-- base_layer/service_framework/Cargo.toml | 6 +- base_layer/tari_stratum_ffi/Cargo.toml | 4 +- base_layer/wallet/Cargo.toml | 26 ++--- base_layer/wallet_ffi/Cargo.toml | 22 ++--- changelog.md | 29 ++++++ common/Cargo.toml | 6 +- comms/Cargo.toml | 10 +- comms/dht/Cargo.toml | 14 +-- comms/rpc_macros/Cargo.toml | 6 +- infrastructure/derive/Cargo.toml | 2 +- infrastructure/shutdown/Cargo.toml | 2 +- infrastructure/storage/Cargo.toml | 2 +- infrastructure/test_utils/Cargo.toml | 2 +- package-lock.json | 2 +- scripts/update_crate_metadata.sh | 1 + 28 files changed, 183 insertions(+), 123 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c0388b1e8..441c5071b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -937,28 +937,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f4919d60f26ae233e14233cc39746c8c8bb8cd7b05840ace83604917b51b6c7" dependencies = [ "bitflags 1.3.2", - "crossterm_winapi", + "crossterm_winapi 0.6.2", "lazy_static 1.4.0", "libc", "mio", "parking_lot 0.10.2", - "signal-hook", + "signal-hook 0.1.17", "winapi 0.3.9", ] [[package]] name = "crossterm" -version = "0.18.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb" +checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" dependencies = [ "bitflags 1.3.2", - "crossterm_winapi", - "lazy_static 1.4.0", + "crossterm_winapi 0.8.0", "libc", "mio", "parking_lot 0.11.2", - "signal-hook", + "signal-hook 0.3.10", + "signal-hook-mio", "winapi 0.3.9", ] @@ -971,6 +971,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "crossterm_winapi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -4035,6 +4044,27 @@ dependencies = [ "signal-hook-registry", ] +[[package]] +name = "signal-hook" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" +dependencies = [ + "libc", + "mio", + "signal-hook 0.3.10", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -4306,7 +4336,7 @@ dependencies = [ [[package]] name = "tari_app_grpc" -version = "0.13.0" +version = "0.21.0" dependencies = [ "chrono", "prost", @@ -4321,7 +4351,7 @@ dependencies = [ [[package]] name = "tari_app_utilities" -version = "0.13.0" +version = "0.21.0" dependencies = [ "config", "dirs-next 1.0.2", @@ -4346,7 +4376,7 @@ dependencies = [ [[package]] name = "tari_base_node" -version = "0.13.0" +version = "0.21.0" dependencies = [ "anyhow", "bincode", @@ -4404,7 +4434,7 @@ dependencies = [ [[package]] name = "tari_common" -version = "0.13.0" +version = "0.21.0" dependencies = [ "anyhow", "config", @@ -4434,7 +4464,7 @@ dependencies = [ [[package]] name = "tari_common_types" -version = "0.13.0" +version = "0.21.0" dependencies = [ "digest", "futures 0.3.17", @@ -4448,7 +4478,7 @@ dependencies = [ [[package]] name = "tari_comms" -version = "0.13.0" +version = "0.21.0" dependencies = [ "anyhow", "async-trait", @@ -4497,7 +4527,7 @@ dependencies = [ [[package]] name = "tari_comms_dht" -version = "0.13.0" +version = "0.21.0" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -4544,7 +4574,7 @@ dependencies = [ [[package]] name = "tari_comms_rpc_macros" -version = "0.13.0" +version = "0.21.0" dependencies = [ "futures 0.3.17", "proc-macro2 1.0.32", @@ -4559,7 +4589,7 @@ dependencies = [ [[package]] name = "tari_console_wallet" -version = "0.13.0" +version = "0.21.0" dependencies = [ "bitflags 1.3.2", "chrono", @@ -4601,7 +4631,7 @@ dependencies = [ [[package]] name = "tari_core" -version = "0.13.0" +version = "0.21.0" dependencies = [ "async-trait", "bincode", @@ -4682,7 +4712,7 @@ dependencies = [ [[package]] name = "tari_infra_derive" -version = "0.13.0" +version = "0.21.0" dependencies = [ "blake2", "proc-macro2 0.4.30", @@ -4692,7 +4722,7 @@ dependencies = [ [[package]] name = "tari_key_manager" -version = "0.13.0" +version = "0.21.0" dependencies = [ "argon2", "arrayvec 0.7.1", @@ -4712,7 +4742,7 @@ dependencies = [ [[package]] name = "tari_merge_mining_proxy" -version = "0.13.0" +version = "0.21.0" dependencies = [ "anyhow", "bincode", @@ -4749,7 +4779,7 @@ dependencies = [ [[package]] name = "tari_mining_node" -version = "0.13.0" +version = "0.21.0" dependencies = [ "bufstream", "chrono", @@ -4779,7 +4809,7 @@ dependencies = [ [[package]] name = "tari_mmr" -version = "0.13.0" +version = "0.21.0" dependencies = [ "bincode", "blake2", @@ -4798,7 +4828,7 @@ dependencies = [ [[package]] name = "tari_p2p" -version = "0.13.0" +version = "0.21.0" dependencies = [ "anyhow", "bytes 0.5.6", @@ -4842,7 +4872,7 @@ dependencies = [ [[package]] name = "tari_service_framework" -version = "0.13.0" +version = "0.21.0" dependencies = [ "anyhow", "async-trait", @@ -4859,7 +4889,7 @@ dependencies = [ [[package]] name = "tari_shutdown" -version = "0.13.0" +version = "0.21.0" dependencies = [ "futures 0.3.17", "tokio 1.13.0", @@ -4867,7 +4897,7 @@ dependencies = [ [[package]] name = "tari_storage" -version = "0.13.0" +version = "0.21.0" dependencies = [ "bincode", "bytes 0.5.6", @@ -4885,7 +4915,7 @@ dependencies = [ [[package]] name = "tari_stratum_ffi" -version = "0.13.0" +version = "0.21.0" dependencies = [ "hex", "libc", @@ -4938,7 +4968,7 @@ dependencies = [ [[package]] name = "tari_test_utils" -version = "0.13.0" +version = "0.21.0" dependencies = [ "futures 0.3.17", "futures-test", @@ -4969,7 +4999,7 @@ dependencies = [ [[package]] name = "tari_wallet" -version = "0.13.0" +version = "0.21.0" dependencies = [ "aes-gcm 0.8.0", "argon2", @@ -5014,7 +5044,7 @@ dependencies = [ [[package]] name = "tari_wallet_ffi" -version = "0.20.1" +version = "0.21.0" dependencies = [ "chrono", "env_logger 0.7.1", @@ -5067,7 +5097,7 @@ dependencies = [ [[package]] name = "test_faucet" -version = "0.13.0" +version = "0.21.0" dependencies = [ "rand 0.8.4", "serde 1.0.130", @@ -5741,13 +5771,13 @@ dependencies = [ [[package]] name = "tui" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4e6c82bb967df89f20b875fa8835fab5d5622c6a5efa574a1f0b6d0aa6e8f6" +checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23" dependencies = [ "bitflags 1.3.2", "cassowary", - "crossterm 0.18.2", + "crossterm 0.20.0", "unicode-segmentation", "unicode-width", ] diff --git a/applications/tari_app_grpc/Cargo.toml b/applications/tari_app_grpc/Cargo.toml index 9030fc789f..5a56dd41a6 100644 --- a/applications/tari_app_grpc/Cargo.toml +++ b/applications/tari_app_grpc/Cargo.toml @@ -4,11 +4,11 @@ authors = ["The Tari Development Community"] description = "This crate is to provide a single source for all cross application grpc files and conversions to and from tari::core" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [dependencies] -tari_common_types = { version = "^0.13", path = "../../base_layer/common_types"} +tari_common_types = { version = "^0.21", path = "../../base_layer/common_types"} tari_core = { path = "../../base_layer/core"} tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } tari_comms = { path = "../../comms"} diff --git a/applications/tari_app_utilities/Cargo.toml b/applications/tari_app_utilities/Cargo.toml index 88082894d3..09f49767a5 100644 --- a/applications/tari_app_utilities/Cargo.toml +++ b/applications/tari_app_utilities/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_app_utilities" -version = "0.13.0" +version = "0.21.0" authors = ["The Tari Development Community"] edition = "2018" diff --git a/applications/tari_base_node/Cargo.toml b/applications/tari_base_node/Cargo.toml index 0fa8b5f63f..5002470f7e 100644 --- a/applications/tari_base_node/Cargo.toml +++ b/applications/tari_base_node/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "The tari full base node implementation" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [dependencies] diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index ee6e7749a1..ea49dc33cc 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_console_wallet" -version = "0.13.0" +version = "0.21.0" authors = ["The Tari Development Community"] edition = "2018" @@ -50,7 +50,7 @@ default-features = false features = ["transactions", "mempool_proto", "base_node_proto"] [dependencies.tui] -version = "^0.13" +version = "0.16" default-features = false features = ["crossterm"] diff --git a/applications/tari_merge_mining_proxy/Cargo.toml b/applications/tari_merge_mining_proxy/Cargo.toml index 8f1f1c3458..bdcc5dc430 100644 --- a/applications/tari_merge_mining_proxy/Cargo.toml +++ b/applications/tari_merge_mining_proxy/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "The tari merge miner proxy for xmrig" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [features] diff --git a/applications/tari_mining_node/Cargo.toml b/applications/tari_mining_node/Cargo.toml index 03e870733a..e8431f0540 100644 --- a/applications/tari_mining_node/Cargo.toml +++ b/applications/tari_mining_node/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "The tari mining node implementation" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [dependencies] diff --git a/applications/test_faucet/Cargo.toml b/applications/test_faucet/Cargo.toml index 57d2d538bc..62e13c4707 100644 --- a/applications/test_faucet/Cargo.toml +++ b/applications/test_faucet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_faucet" -version = "0.13.0" +version = "0.21.0" authors = ["The Tari Development Community"] edition = "2018" diff --git a/base_layer/common_types/Cargo.toml b/base_layer/common_types/Cargo.toml index 6e1c32d1fc..6e61345a94 100644 --- a/base_layer/common_types/Cargo.toml +++ b/base_layer/common_types/Cargo.toml @@ -3,7 +3,7 @@ name = "tari_common_types" authors = ["The Tari Development Community"] description = "Tari cryptocurrency common types" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [dependencies] diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index ccb8091e64..854d341a71 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [features] @@ -18,18 +18,18 @@ base_node_proto = [] avx2 = ["tari_crypto/avx2"] [dependencies] -tari_common = { version = "^0.13", path = "../../common" } -tari_common_types = { version = "^0.13", path = "../../base_layer/common_types" } -tari_comms = { version = "^0.13", path = "../../comms" } -tari_comms_dht = { version = "^0.13", path = "../../comms/dht" } -tari_comms_rpc_macros = { version = "^0.13", path = "../../comms/rpc_macros" } +tari_common = { version = "^0.21", path = "../../common" } +tari_common_types = { version = "^0.21", path = "../../base_layer/common_types" } +tari_comms = { version = "^0.21", path = "../../comms" } +tari_comms_dht = { version = "^0.21", path = "../../comms/dht" } +tari_comms_rpc_macros = { version = "^0.21", path = "../../comms/rpc_macros" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } -tari_mmr = { version = "^0.13", path = "../../base_layer/mmr", optional = true } -tari_p2p = { version = "^0.13", path = "../../base_layer/p2p" } -tari_service_framework = { version = "^0.13", path = "../service_framework" } -tari_shutdown = { version = "^0.13", path = "../../infrastructure/shutdown" } -tari_storage = { version = "^0.13", path = "../../infrastructure/storage" } -tari_test_utils = { version = "^0.13", path = "../../infrastructure/test_utils" } +tari_mmr = { version = "^0.21", path = "../../base_layer/mmr", optional = true } +tari_p2p = { version = "^0.21", path = "../../base_layer/p2p" } +tari_service_framework = { version = "^0.21", path = "../service_framework" } +tari_shutdown = { version = "^0.21", path = "../../infrastructure/shutdown" } +tari_storage = { version = "^0.21", path = "../../infrastructure/storage" } +tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils" } async-trait = "0.1.50" bincode = "1.1.4" @@ -69,12 +69,12 @@ ttl_cache = "0.5.1" uint = { version = "0.9", default-features = false } [dev-dependencies] -tari_p2p = { version = "^0.13", path = "../../base_layer/p2p", features = ["test-mocks"] } -tari_test_utils = { version = "^0.13", path = "../../infrastructure/test_utils" } +tari_p2p = { version = "^0.21", path = "../../base_layer/p2p", features = ["test-mocks"] } +tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils" } config = { version = "0.9.3" } env_logger = "0.7.0" tempfile = "3.1.0" [build-dependencies] -tari_common = { version = "^0.13", path = "../../common", features = ["build"] } +tari_common = { version = "^0.21", path = "../../common", features = ["build"] } diff --git a/base_layer/key_manager/Cargo.toml b/base_layer/key_manager/Cargo.toml index 237d5791c2..97b2d67334 100644 --- a/base_layer/key_manager/Cargo.toml +++ b/base_layer/key_manager/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "Tari cryptocurrency wallet key management" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [dependencies] diff --git a/base_layer/mmr/Cargo.toml b/base_layer/mmr/Cargo.toml index dbdbcdc7bb..a61138f80b 100644 --- a/base_layer/mmr/Cargo.toml +++ b/base_layer/mmr/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "A Merkle Mountain Range implementation" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [features] @@ -24,7 +24,7 @@ criterion = { version="0.2", optional = true } [dev-dependencies] rand="0.8.0" blake2 = "0.9.0" -tari_infra_derive= { path = "../../infrastructure/derive", version = "^0.13" } +tari_infra_derive= { path = "../../infrastructure/derive", version = "^0.21" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } serde_json = "1.0" bincode = "1.1" diff --git a/base_layer/p2p/Cargo.toml b/base_layer/p2p/Cargo.toml index 50e07834cd..abb9aa0e88 100644 --- a/base_layer/p2p/Cargo.toml +++ b/base_layer/p2p/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_p2p" -version = "0.13.0" +version = "0.21.0" authors = ["The Tari Development community"] description = "Tari base layer-specific peer-to-peer communication features" repository = "https://github.com/tari-project/tari" @@ -10,13 +10,13 @@ license = "BSD-3-Clause" edition = "2018" [dependencies] -tari_comms = { version = "^0.13", path = "../../comms" } -tari_comms_dht = { version = "^0.13", path = "../../comms/dht" } -tari_common = { version = "^0.13", path = "../../common" } +tari_comms = { version = "^0.21", path = "../../comms" } +tari_comms_dht = { version = "^0.21", path = "../../comms/dht" } +tari_common = { version = "^0.21", path = "../../common" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } -tari_service_framework = { version = "^0.13", path = "../service_framework" } -tari_shutdown = { version = "^0.13", path = "../../infrastructure/shutdown" } -tari_storage = { version = "^0.13", path = "../../infrastructure/storage" } +tari_service_framework = { version = "^0.21", path = "../service_framework" } +tari_shutdown = { version = "^0.21", path = "../../infrastructure/shutdown" } +tari_storage = { version = "^0.21", path = "../../infrastructure/storage" } tari_utilities = "^0.3" anyhow = "1.0.32" @@ -43,7 +43,7 @@ rustls = "0.19.1" webpki = "0.21" [dev-dependencies] -tari_test_utils = { version = "^0.13", path = "../../infrastructure/test_utils" } +tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils" } clap = "2.33.0" env_logger = "0.6.2" @@ -58,7 +58,7 @@ features = ["console_appender", "file_appender", "file", "yaml_format"] default-features = false [build-dependencies] -tari_common = { version = "^0.13", path = "../../common", features = ["build"] } +tari_common = { version = "^0.21", path = "../../common", features = ["build"] } [features] test-mocks = [] diff --git a/base_layer/service_framework/Cargo.toml b/base_layer/service_framework/Cargo.toml index deda000475..b51333a151 100644 --- a/base_layer/service_framework/Cargo.toml +++ b/base_layer/service_framework/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_service_framework" -version = "0.13.0" +version = "0.21.0" authors = ["The Tari Development Community"] description = "The Tari communication stack service framework" repository = "https://github.com/tari-project/tari" @@ -10,7 +10,7 @@ license = "BSD-3-Clause" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tari_shutdown = { version = "^0.13", path = "../../infrastructure/shutdown" } +tari_shutdown = { version = "^0.21", path = "../../infrastructure/shutdown" } anyhow = "1.0.32" async-trait = "0.1.50" @@ -21,7 +21,7 @@ tokio = { version = "1.11", features = ["rt"] } tower-service = { version = "0.3.0" } [dev-dependencies] -tari_test_utils = { version = "^0.13", path = "../../infrastructure/test_utils" } +tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils" } tokio = { version = "1.11", features = ["rt-multi-thread", "macros", "time"] } futures-test = { version = "0.3.3" } diff --git a/base_layer/tari_stratum_ffi/Cargo.toml b/base_layer/tari_stratum_ffi/Cargo.toml index d43973f591..b6b5effbd4 100644 --- a/base_layer/tari_stratum_ffi/Cargo.toml +++ b/base_layer/tari_stratum_ffi/Cargo.toml @@ -3,11 +3,11 @@ name = "tari_stratum_ffi" authors = ["The Tari Development Community"] description = "Tari cryptocurrency miningcore C FFI bindings" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [dependencies] -tari_comms = { version = "^0.13", path = "../../comms" } +tari_comms = { version = "^0.21", path = "../../comms" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } tari_common = { path = "../../common" } tari_app_grpc = { path = "../../applications/tari_app_grpc" } diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index d03160b50f..6b1fefdcc0 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -3,20 +3,20 @@ name = "tari_wallet" authors = ["The Tari Development Community"] description = "Tari cryptocurrency wallet library" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [dependencies] tari_common = { path = "../../common" } -tari_common_types = { version = "^0.13", path = "../../base_layer/common_types" } -tari_comms = { version = "^0.13", path = "../../comms" } -tari_comms_dht = { version = "^0.13", path = "../../comms/dht" } +tari_common_types = { version = "^0.21", path = "../../base_layer/common_types" } +tari_comms = { version = "^0.21", path = "../../comms" } +tari_comms_dht = { version = "^0.21", path = "../../comms/dht" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } -tari_key_manager = { version = "^0.13", path = "../key_manager" } -tari_p2p = { version = "^0.13", path = "../p2p", features = ["auto-update"] } -tari_service_framework = { version = "^0.13", path = "../service_framework" } -tari_shutdown = { version = "^0.13", path = "../../infrastructure/shutdown" } -tari_storage = { version = "^0.13", path = "../../infrastructure/storage" } +tari_key_manager = { version = "^0.21", path = "../key_manager" } +tari_p2p = { version = "^0.21", path = "../p2p", features = ["auto-update"] } +tari_service_framework = { version = "^0.21", path = "../service_framework" } +tari_shutdown = { version = "^0.21", path = "../../infrastructure/shutdown" } +tari_storage = { version = "^0.21", path = "../../infrastructure/storage" } aes-gcm = "^0.8" async-trait = "0.1.50" @@ -46,14 +46,14 @@ tower = "0.3.0-alpha.2" [dependencies.tari_core] path = "../../base_layer/core" -version = "^0.13" +version = "^0.21" default-features = false features = ["transactions", "mempool_proto", "base_node_proto", ] [dev-dependencies] -tari_p2p = { version = "^0.13", path = "../p2p", features = ["test-mocks"] } -tari_comms_dht = { version = "^0.13", path = "../../comms/dht", features = ["test-mocks"] } -tari_test_utils = { version = "^0.13", path = "../../infrastructure/test_utils" } +tari_p2p = { version = "^0.21", path = "../p2p", features = ["test-mocks"] } +tari_comms_dht = { version = "^0.21", path = "../../comms/dht", features = ["test-mocks"] } +tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils" } env_logger = "0.7.1" prost = "0.8.0" diff --git a/base_layer/wallet_ffi/Cargo.toml b/base_layer/wallet_ffi/Cargo.toml index b0ee0dec22..d426e42697 100644 --- a/base_layer/wallet_ffi/Cargo.toml +++ b/base_layer/wallet_ffi/Cargo.toml @@ -3,18 +3,18 @@ name = "tari_wallet_ffi" authors = ["The Tari Development Community"] description = "Tari cryptocurrency wallet C FFI bindings" license = "BSD-3-Clause" -version = "0.20.1" +version = "0.21.0" edition = "2018" [dependencies] -tari_comms = { version = "^0.13", path = "../../comms", features = ["c_integration"]} -tari_comms_dht = { version = "^0.13", path = "../../comms/dht", default-features = false } +tari_comms = { version = "^0.21", path = "../../comms", features = ["c_integration"]} +tari_comms_dht = { version = "^0.21", path = "../../comms/dht", default-features = false } tari_common_types = {path="../common_types"} tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } -tari_key_manager = { version = "^0.13", path = "../key_manager" } -tari_p2p = { version = "^0.13", path = "../p2p" } -tari_wallet = { version = "^0.13", path = "../wallet", features = ["c_integration"]} -tari_shutdown = { version = "^0.13", path = "../../infrastructure/shutdown" } +tari_key_manager = { version = "^0.21", path = "../key_manager" } +tari_p2p = { version = "^0.21", path = "../p2p" } +tari_wallet = { version = "^0.21", path = "../wallet", features = ["c_integration"]} +tari_shutdown = { version = "^0.21", path = "../../infrastructure/shutdown" } tari_utilities = "^0.3" chrono = { version = "0.4.6", features = ["serde"]} @@ -38,7 +38,7 @@ security-framework = "2.4.2" [dependencies.tari_core] path = "../../base_layer/core" -version = "^0.13" +version = "^0.21" default-features = false features = ["transactions"] @@ -49,7 +49,7 @@ crate-type = ["staticlib","cdylib"] tempfile = "3.1.0" lazy_static = "1.3.0" env_logger = "0.7.1" -tari_key_manager = { version = "^0.13", path = "../key_manager" } -tari_common_types = { version = "^0.13", path = "../../base_layer/common_types"} -tari_test_utils = { version = "^0.13", path = "../../infrastructure/test_utils"} +tari_key_manager = { version = "^0.21", path = "../key_manager" } +tari_common_types = { version = "^0.21", path = "../../base_layer/common_types"} +tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils"} tari_service_framework = { path = "../../base_layer/service_framework" } diff --git a/changelog.md b/changelog.md index 2db52cd666..45f5bf0402 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,34 @@ # Changelog + +## [0.21.0](https://github.com/tari-project/tari/compare/v0.13.0...v0.21.0) (2021-11-09) + + +### ⚠ BREAKING CHANGES + +* remove outdated wallet_ffi balance methods (#3528) +* **rpc:** read from substream while streaming to check for interruptions (#3548) + +### Features + +* add ffi get mnemonic wordlist ([#3538](https://github.com/tari-project/tari/issues/3538)) ([d8e0ced](https://github.com/tari-project/tari/commit/d8e0cedc19ee008a8dd937347d7e2fc5e7fc4c3f)) +* optimize transaction validation for wallet ([#3537](https://github.com/tari-project/tari/issues/3537)) ([9064b83](https://github.com/tari-project/tari/commit/9064b830c04000683aecf7b2972ffeabe5d90f08)) + + +### Bug Fixes + +* add check for old db encryption and provide warning ([#3549](https://github.com/tari-project/tari/issues/3549)) ([69bbbdf](https://github.com/tari-project/tari/commit/69bbbdfd87fae56d31bcd342fe4dc5c84086402e)) +* add decision step between header sync and pruned/archival ([#3546](https://github.com/tari-project/tari/issues/3546)) ([23e868a](https://github.com/tari-project/tari/commit/23e868a8a4d2d8b673e4bd3df9fb9f4d33d191d9)) +* check for previously cancelled completed txn before accepting a repeat message ([#3542](https://github.com/tari-project/tari/issues/3542)) ([911b83b](https://github.com/tari-project/tari/commit/911b83b675816cd41b4e40e0f001bca6f7037369)) +* prevent race condition between block propagation and sync ([#3536](https://github.com/tari-project/tari/issues/3536)) ([6bbb654](https://github.com/tari-project/tari/commit/6bbb65453ed5d8969e0e659fd855d5183262c6d6)) +* remove dns resolver config from cucumber tests, use default ([#3547](https://github.com/tari-project/tari/issues/3547)) ([e17ee64](https://github.com/tari-project/tari/commit/e17ee645add6c3030d1198d8efe96149fffbb7b6)) +* **rpc:** read from substream while streaming to check for interruptions ([#3548](https://github.com/tari-project/tari/issues/3548)) ([9194501](https://github.com/tari-project/tari/commit/919450186f70f3c00ade937a76288ce00ef2175c)) +* update the seed words used in the Daily tests ([#3545](https://github.com/tari-project/tari/issues/3545)) ([7696840](https://github.com/tari-project/tari/commit/76968400fbb1560d11f3beeecb6d1bb5ba60433b)) +* use tcp tls backend for peer seed DNS resolution ([#3544](https://github.com/tari-project/tari/issues/3544)) ([5b38909](https://github.com/tari-project/tari/commit/5b389098aa0aab9dd723213a29aeebe22e4d9bb6)) + + +* remove outdated wallet_ffi balance methods ([#3528](https://github.com/tari-project/tari/issues/3528)) ([413757b](https://github.com/tari-project/tari/commit/413757bcea5474524b18a860a95df255cbe95d33)) + ## [0.13.0](https://github.com/tari-project/tari/compare/v0.12.0...v0.13.0) (2021-11-04) diff --git a/common/Cargo.toml b/common/Cargo.toml index 070fa014e8..8dd805cedd 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [features] @@ -25,7 +25,7 @@ log4rs = { version = "1.0.0", default_features= false, features = ["config_parsi multiaddr={version = "0.13.0"} sha2 = "0.9.5" path-clean = "0.1.0" -tari_storage = { version = "^0.13", path = "../infrastructure/storage"} +tari_storage = { version = "^0.21", path = "../infrastructure/storage"} tracing = "0.1.26" tracing-opentelemetry = "0.15.0" tracing-subscriber = "0.2.20" @@ -41,6 +41,6 @@ toml = { version = "0.5", optional = true } thiserror = "1.0.29" [dev-dependencies] -tari_test_utils = { version = "^0.13", path = "../infrastructure/test_utils"} +tari_test_utils = { version = "^0.21", path = "../infrastructure/test_utils"} tempfile = "3.1.0" anyhow = "1.0" diff --git a/comms/Cargo.toml b/comms/Cargo.toml index fc61ef9998..b27d6707ac 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -6,14 +6,14 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [dependencies] tari_common = { path = "../common" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } -tari_storage = { version = "^0.13", path = "../infrastructure/storage" } -tari_shutdown = { version = "^0.13", path = "../infrastructure/shutdown" } +tari_storage = { version = "^0.21", path = "../infrastructure/storage" } +tari_shutdown = { version = "^0.21", path = "../infrastructure/shutdown" } anyhow = "1.0.32" async-trait = "0.1.36" @@ -55,7 +55,7 @@ opentelemetry-jaeger = { version = "0.15", features = ["rt-tokio"] } tower-make = { version = "0.3.0", optional = true } [dev-dependencies] -tari_test_utils = { version = "^0.13", path = "../infrastructure/test_utils" } +tari_test_utils = { version = "^0.21", path = "../infrastructure/test_utils" } tari_comms_rpc_macros = { version = "*", path = "./rpc_macros" } env_logger = "0.7.0" @@ -63,7 +63,7 @@ serde_json = "1.0.39" tempfile = "3.1.0" [build-dependencies] -tari_common = { version = "^0.13", path = "../common", features = ["build"] } +tari_common = { version = "^0.21", path = "../common", features = ["build"] } [features] c_integration = [] diff --git a/comms/dht/Cargo.toml b/comms/dht/Cargo.toml index ed218f4db1..fb207792fa 100644 --- a/comms/dht/Cargo.toml +++ b/comms/dht/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_comms_dht" -version = "0.13.0" +version = "0.21.0" authors = ["The Tari Development Community"] description = "Tari comms DHT module" repository = "https://github.com/tari-project/tari" @@ -10,12 +10,12 @@ license = "BSD-3-Clause" edition = "2018" [dependencies] -tari_comms = { version = "^0.13", path = "../", features = ["rpc"] } -tari_comms_rpc_macros = { version = "^0.13", path = "../rpc_macros" } +tari_comms = { version = "^0.21", path = "../", features = ["rpc"] } +tari_comms_rpc_macros = { version = "^0.21", path = "../rpc_macros" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } tari_utilities = { version = "^0.3" } -tari_shutdown = { version = "^0.13", path = "../../infrastructure/shutdown" } -tari_storage = { version = "^0.13", path = "../../infrastructure/storage" } +tari_shutdown = { version = "^0.21", path = "../../infrastructure/shutdown" } +tari_storage = { version = "^0.21", path = "../../infrastructure/storage" } anyhow = "1.0.32" bitflags = "1.2.0" @@ -43,7 +43,7 @@ ttl_cache = "0.5.1" pin-project = "0.4" [dev-dependencies] -tari_test_utils = { version = "^0.13", path = "../../infrastructure/test_utils" } +tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils" } env_logger = "0.7.0" futures-test = { version = "0.3.5" } @@ -60,7 +60,7 @@ futures-util = "^0.3.1" lazy_static = "1.4.0" [build-dependencies] -tari_common = { version = "^0.13", path = "../../common" } +tari_common = { version = "^0.21", path = "../../common" } [features] test-mocks = [] diff --git a/comms/rpc_macros/Cargo.toml b/comms/rpc_macros/Cargo.toml index 615d77eb3f..63944e9712 100644 --- a/comms/rpc_macros/Cargo.toml +++ b/comms/rpc_macros/Cargo.toml @@ -6,21 +6,21 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [lib] proc-macro = true [dependencies] -tari_comms = { version = "^0.13", path = "../", features = ["rpc"] } +tari_comms = { version = "^0.21", path = "../", features = ["rpc"] } proc-macro2 = "1.0.24" quote = "1.0.7" syn = { version = "1.0.38", features = ["fold"] } [dev-dependencies] -tari_test_utils = { version = "^0.13", path = "../../infrastructure/test_utils" } +tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils" } futures = "0.3.5" prost = "0.8.0" diff --git a/infrastructure/derive/Cargo.toml b/infrastructure/derive/Cargo.toml index 5289e18ee9..619deeea72 100644 --- a/infrastructure/derive/Cargo.toml +++ b/infrastructure/derive/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [lib] diff --git a/infrastructure/shutdown/Cargo.toml b/infrastructure/shutdown/Cargo.toml index 927abb2817..60f35c24ff 100644 --- a/infrastructure/shutdown/Cargo.toml +++ b/infrastructure/shutdown/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/infrastructure/storage/Cargo.toml b/infrastructure/storage/Cargo.toml index c4f86b5e95..29a00c9766 100644 --- a/infrastructure/storage/Cargo.toml +++ b/infrastructure/storage/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.13.0" +version = "0.21.0" edition = "2018" [dependencies] diff --git a/infrastructure/test_utils/Cargo.toml b/infrastructure/test_utils/Cargo.toml index 5c7509772f..083b0754f3 100644 --- a/infrastructure/test_utils/Cargo.toml +++ b/infrastructure/test_utils/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tari_test_utils" description = "Utility functions used in Tari test functions" -version = "0.13.0" +version = "0.21.0" authors = ["The Tari Development Community"] edition = "2018" license = "BSD-3-Clause" diff --git a/package-lock.json b/package-lock.json index cd0632366a..127e50bdb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,4 +1,4 @@ { "lockfileVersion": 1, - "version": "0.13.0" + "version": "0.14.0" } diff --git a/scripts/update_crate_metadata.sh b/scripts/update_crate_metadata.sh index 8a5580a199..2749cd23e3 100755 --- a/scripts/update_crate_metadata.sh +++ b/scripts/update_crate_metadata.sh @@ -40,6 +40,7 @@ function update_versions { base_layer/p2p base_layer/service_framework base_layer/wallet + base_layer/wallet_ffi base_layer/tari_stratum_ffi common comms From 0ae5fbd561373acd6a2a6be4cf9f9a967b049c27 Mon Sep 17 00:00:00 2001 From: Mike the Tike Date: Tue, 9 Nov 2021 15:16:33 +0200 Subject: [PATCH 07/46] test: various cucumber fixes (#3519) Description --- * improved reorg test to ensure max difficulty * reduced various warn logs to info or debug * allowed dns_seeds to be passed as a comma separated string when passed as an ENV var Motivation and Context --- The Mass Multiple Reorg cucumber test was flaky because it did not use explicit difficulties in the chains, so it had vague statements "All chains are on the same tip" How Has This Been Tested? --- Tested locally --- common/src/configuration/global.rs | 16 ++++++--- comms/dht/src/actor.rs | 2 +- comms/dht/src/connectivity/mod.rs | 6 ++-- integration_tests/features/Reorgs.feature | 31 ++++++++--------- integration_tests/features/support/steps.js | 38 +++++++++++++-------- integration_tests/features/support/world.js | 20 +++++++++-- integration_tests/helpers/config.js | 1 + 7 files changed, 72 insertions(+), 42 deletions(-) diff --git a/common/src/configuration/global.rs b/common/src/configuration/global.rs index dbd93d5602..958c1d40f1 100644 --- a/common/src/configuration/global.rs +++ b/common/src/configuration/global.rs @@ -386,11 +386,17 @@ fn convert_node_config( .map_err(|e| ConfigurationError::new(key, &e.to_string()))?; let key = "common.dns_seeds"; - let dns_seeds = optional(cfg.get_array(key))? - .unwrap_or_default() - .into_iter() - .map(|v| v.into_str().unwrap()) - .collect::>(); + let dns_seeds = match cfg.get_array(key) { + Ok(seeds) => seeds.into_iter().map(|v| v.into_str().unwrap()).collect(), + Err(..) => optional(cfg.get_str(key))? + .map(|s| { + s.split(',') + .map(|v| v.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect() + }) + .unwrap_or_default(), + }; // Peer DB path let peer_db_path = data_dir.join("peer_db"); diff --git a/comms/dht/src/actor.rs b/comms/dht/src/actor.rs index 7de0af251a..3d93e3abc8 100644 --- a/comms/dht/src/actor.rs +++ b/comms/dht/src/actor.rs @@ -561,7 +561,7 @@ impl DhtActor { }; if connections.is_empty() { - warn!( + info!( target: LOG_TARGET, "Propagation requested but there are no node peer connections available" ); diff --git a/comms/dht/src/connectivity/mod.rs b/comms/dht/src/connectivity/mod.rs index 223da00f0b..a5c3c63085 100644 --- a/comms/dht/src/connectivity/mod.rs +++ b/comms/dht/src/connectivity/mod.rs @@ -330,7 +330,7 @@ impl DhtConnectivity { .fetch_random_peers(self.config.num_random_nodes, &self.neighbours) .await?; if random_peers.is_empty() { - warn!( + info!( target: LOG_TARGET, "Unable to refresh random peer pool because there are insufficient known peers", ); @@ -489,7 +489,7 @@ impl DhtConnectivity { self.connectivity.request_many_dials(vec![new_peer]).await?; }, None => { - warn!( + debug!( target: LOG_TARGET, "Unable to fetch new random peer to replace disconnected peer '{}' because not enough peers \ are known. Random pool size is {}.", @@ -516,7 +516,7 @@ impl DhtConnectivity { self.connectivity.request_many_dials(vec![node_id]).await?; }, None => { - warn!( + info!( target: LOG_TARGET, "Unable to fetch new neighbouring peer to replace disconnected peer '{}'. Neighbour pool size \ is {}.", diff --git a/integration_tests/features/Reorgs.feature b/integration_tests/features/Reorgs.feature index 715a00fd11..cbeac43c7b 100644 --- a/integration_tests/features/Reorgs.feature +++ b/integration_tests/features/Reorgs.feature @@ -140,23 +140,23 @@ Feature: Reorgs Scenario Outline: Massive multiple reorg # # Chain 1a: - # Mine X1 blocks (orphan_storage_capacity default set to 10) + # Mine X1 blocks # Given I have a seed node SEED_A1 # Add multiple base nodes to ensure more robust comms And I have a base node NODE_A1 connected to seed SEED_A1 And I have a base node NODE_A2 connected to seed SEED_A1 - When I mine blocks on SEED_A1 + When I mine blocks on SEED_A1 with difficulty 1 Then all nodes are on the same chain at height # # Chain 1b: - # Mine Y1 blocks (orphan_storage_capacity default set to 10) + # Mine Y1 blocks # And I have a seed node SEED_A2 # Add multiple base nodes to ensure more robust comms And I have a base node NODE_A3 connected to seed SEED_A2 And I have a base node NODE_A4 connected to seed SEED_A2 - When I mine blocks on SEED_A2 + When I mine blocks on SEED_A2 with difficulty 1 Then node NODE_A3 is at height Then node NODE_A4 is at height # @@ -167,17 +167,16 @@ Feature: Reorgs And I connect node SEED_A1 to node SEED_A2 Then node SEED_A1 is in state LISTENING Then node SEED_A2 is in state LISTENING - When I mine 10 blocks on SEED_A1 - Then all nodes are on the same chain tip + Then all nodes are on the same chain at height # # Chain 2a: - # Mine X2 blocks (orphan_storage_capacity default set to 10) + # Mine X2 blocks # Given I have a seed node SEED_B1 # Add multiple base nodes to ensure more robust comms And I have a base node NODE_B1 connected to seed SEED_B1 And I have a base node NODE_B2 connected to seed SEED_B1 - When I mine blocks on SEED_B1 + When I mine blocks on SEED_B1 with difficulty 1 Then node NODE_B1 is at height Then node NODE_B2 is at height # @@ -188,7 +187,7 @@ Feature: Reorgs # Add multiple base nodes to ensure more robust comms And I have a base node NODE_B3 connected to seed SEED_B2 And I have a base node NODE_B4 connected to seed SEED_B2 - When I mine blocks on SEED_B2 + When I mine blocks on SEED_B2 with difficulty 1 Then node NODE_B3 is at height Then node NODE_B4 is at height # @@ -199,20 +198,18 @@ Feature: Reorgs And I connect node SEED_B1 to node SEED_B2 Then node SEED_B2 is in state LISTENING Then node SEED_B1 is in state LISTENING - When I mine 10 blocks on SEED_B1 - Then node SEED_B2 is at the same height as node SEED_B1 - Then node NODE_B1 is at the same height as node SEED_B1 - Then node NODE_B2 is at the same height as node SEED_B1 - Then node NODE_B3 is at the same height as node SEED_B1 - Then node NODE_B4 is at the same height as node SEED_B1 + Then node SEED_B2 is at height + Then node NODE_B1 is at height + Then node NODE_B2 is at height + Then node NODE_B3 is at height + Then node NODE_B4 is at height # # Connect Chain 1 and 2 # And I connect node NODE_A1 to node NODE_B1 And I connect node NODE_A3 to node NODE_B3 And I connect node SEED_A1 to node SEED_B1 - When I mine 10 blocks on SEED_A1 - Then all nodes are on the same chain tip + Then all nodes are on the same chain at height Examples: | X1 | Y1 | X2 | Y2 | diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index 31bbe9b822..b791d70c9c 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -250,7 +250,9 @@ Given( const nodeA = this.getNode(nodeNameA); const nodeB = this.getNode(nodeNameB); nodeA.setPeerSeeds([nodeB.peerAddress()]); + console.log("Stopping node"); await this.stopNode(nodeNameA); + console.log("Starting node"); await this.startNode(nodeNameA); } ); @@ -684,17 +686,7 @@ Given( Given( /I have mining node (.*) connected to base node (.*) and wallet (.*)/, async function (miner, node, wallet) { - const baseNode = this.getNode(node); - const walletNode = await this.getOrCreateWallet(wallet); - const miningNode = new MiningNodeProcess( - miner, - baseNode.getGrpcAddress(), - this.getClient(node), - walletNode.getGrpcAddress(), - this.logFilePathMiningNode, - true - ); - this.addMiningNode(miner, miningNode); + await this.createMiningNode(miner, node, wallet); } ); @@ -1338,13 +1330,31 @@ When( } ); +When( + /I mine (.*) blocks on (.*) with difficulty (.*)/, + { timeout: 20 * 1000 }, + async function (numBlocks, node, difficulty) { + const miner = await this.createMiningNode("temp", node, "temp"); + await miner.init( + parseInt(numBlocks), + null, + parseInt(difficulty), + parseInt(difficulty), + false, + null + ); + await miner.startNew(); + } +); + When( /mining node (.*) mines (\d+) blocks$/, { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; dynamic time out below limits actual time async function (miner, numBlocks) { - const miningNode = this.getMiningNode(miner); - // Don't wait for sync before mining - await miningNode.init(numBlocks, null, 1, 100000, false, null); + const miningNode = this.getMiningNode(miner);d + // Don't wait for sync before mining. Also use a max difficulty of 1, since most tests assume + // that 1 block = 1 difficulty + await miningNode.init(numBlocks, null, 1, 1, false, null); await withTimeout( (10 + parseInt(numBlocks) * 1) * 1000, await miningNode.startNew() diff --git a/integration_tests/features/support/world.js b/integration_tests/features/support/world.js index 7a81b62154..dcf2b00149 100644 --- a/integration_tests/features/support/world.js +++ b/integration_tests/features/support/world.js @@ -268,6 +268,21 @@ class CustomWorld { return miner; } + async createMiningNode(name, node, wallet) { + const baseNode = this.getNode(node); + const walletNode = await this.getOrCreateWallet(wallet); + const miningNode = new MiningNodeProcess( + name, + baseNode.getGrpcAddress(), + this.getClient(node), + walletNode.getGrpcAddress(), + this.logFilePathMiningNode, + true + ); + this.addMiningNode(name, miningNode); + return miningNode; + } + getWallet(name) { const wallet = this.wallets[name] || this.walletsFFI[name]; if (!wallet) { @@ -467,11 +482,12 @@ function attachLogs(path, context) { archive.pipe(zipFile); glob(path + "/**/*.log", {}, function (err, files) { - console.log(files); for (let i = 0; i < files.length; i++) { // Append the file name at the bottom fs.appendFileSync(files[i], `>>>> End of ${files[i]}`); - archive.append(fs.createReadStream(files[i]), { name: files[i] }); + archive.append(fs.createReadStream(files[i]), { + name: files[i].replace("./temp", ""), + }); } archive.finalize().then(function () { context.attach( diff --git a/integration_tests/helpers/config.js b/integration_tests/helpers/config.js index 5aa40ab6e1..a022190d3f 100644 --- a/integration_tests/helpers/config.js +++ b/integration_tests/helpers/config.js @@ -84,6 +84,7 @@ function baseEnvs(peerSeeds = [], forceSyncPeers = []) { TARI_BASE_NODE__LOCALNET__GRPC_ENABLED: true, TARI_BASE_NODE__LOCALNET__ENABLE_WALLET: false, TARI_COMMON__DNS_SEEDS_USE_DNSSEC: "false", + TARI_COMMON__DNS_SEEDS: "", TARI_BASE_NODE__LOCALNET__BLOCK_SYNC_STRATEGY: "ViaBestChainMetadata", TARI_BASE_NODE__LOCALNET__ORPHAN_DB_CLEAN_OUT_THRESHOLD: "0", TARI_BASE_NODE__LOCALNET__MAX_RANDOMX_VMS: "1", From d211031cfa44ad498706db84e8a919b9babaf422 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Tue, 9 Nov 2021 16:46:08 +0300 Subject: [PATCH 08/46] fix: use time crate instead of chrono (#3527) Description --- Replaces `chrono` crate to `time` where possible. Motivation and Context --- `chrono` crate depends on `time`, but is not actively maintained. There is a known security issue: https://github.com/chronotope/chrono/pull/578 How Has This Been Tested? --- In progress... --- Cargo.lock | 46 ++++++++-------- applications/tari_console_wallet/Cargo.toml | 4 +- applications/tari_console_wallet/README.md | 4 +- .../src/automation/command_parser.rs | 10 ++-- .../src/automation/commands.rs | 14 ++--- .../src/automation/error.rs | 6 +- .../tari_console_wallet/src/recovery.rs | 6 +- .../src/ui/components/notification_tab.rs | 29 ++++++---- .../src/ui/components/transactions_tab.rs | 55 ++++++++++++++----- .../src/ui/state/app_state.rs | 14 +++-- 10 files changed, 111 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 441c5071b3..7250677e8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -605,21 +605,10 @@ dependencies = [ "num-integer", "num-traits 0.2.14", "serde 1.0.130", - "time", + "time 0.1.44", "winapi 0.3.9", ] -[[package]] -name = "chrono-english" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be5180df5f7c41fc2416bc038bc8d78d44db8136c415b94ccbc95f523dc38e9" -dependencies = [ - "chrono", - "scanlex", - "time", -] - [[package]] name = "cidr" version = "0.1.1" @@ -1921,7 +1910,7 @@ dependencies = [ "log 0.3.9", "mime 0.2.6", "num_cpus", - "time", + "time 0.1.44", "traitobject", "typeable", "unicase", @@ -3777,12 +3766,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scanlex" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "088c5d71572124929ea7549a8ce98e1a6fd33d0a38367b09027b382e67c033db" - [[package]] name = "schannel" version = "0.1.19" @@ -4591,9 +4574,8 @@ dependencies = [ name = "tari_console_wallet" version = "0.21.0" dependencies = [ + "anyhow", "bitflags 1.3.2", - "chrono", - "chrono-english", "crossterm 0.17.7", "futures 0.3.17", "log 0.4.14", @@ -4619,6 +4601,7 @@ dependencies = [ "tari_shutdown", "tari_wallet", "thiserror", + "time 0.3.4", "tokio 1.13.0", "tonic", "tracing", @@ -4802,7 +4785,7 @@ dependencies = [ "tari_core", "tari_crypto", "thiserror", - "time", + "time 0.1.44", "tokio 1.13.0", "tonic", ] @@ -5037,7 +5020,7 @@ dependencies = [ "tari_test_utils", "tempfile", "thiserror", - "time", + "time 0.1.44", "tokio 1.13.0", "tower 0.3.1", ] @@ -5191,6 +5174,23 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "time" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99beeb0daeac2bd1e86ac2c21caddecb244b39a093594da1a661ec2060c7aedd" +dependencies = [ + "itoa", + "libc", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" + [[package]] name = "tiny-keccak" version = "2.0.2" diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index ea49dc33cc..1881c45448 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -17,9 +17,8 @@ tari_app_grpc = { path = "../tari_app_grpc" } tari_shutdown = { path = "../../infrastructure/shutdown" } tari_key_manager = { path = "../../base_layer/key_manager" } +anyhow = "1.0.44" bitflags = "1.2.1" -chrono = { version = "0.4.6", features = ["serde"] } -chrono-english = "0.1" futures = { version = "^0.3.16", default-features = false, features = ["alloc"] } crossterm = { version = "0.17" } rand = "0.8" @@ -35,6 +34,7 @@ strum_macros = "^0.19" tokio = { version = "1.11", features = ["signal"] } thiserror = "1.0.26" tonic = "0.5.2" +time = { version = "0.3.4", features = ["formatting", "local-offset", "macros", "parsing"] } tracing = "0.1.26" tracing-opentelemetry = "0.15.0" diff --git a/applications/tari_console_wallet/README.md b/applications/tari_console_wallet/README.md index 0b8f0bd2a5..8842d68005 100644 --- a/applications/tari_console_wallet/README.md +++ b/applications/tari_console_wallet/README.md @@ -72,12 +72,14 @@ Make it rain! Send many transactions to a public key or emoji id. `` can be `negotiated` or `one_sided` +`` is a string in RFC3339 format or `now` + example: ``` $ tari_console_wallet --command "make-it-rain 1 10 8000 100 now c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 negotiated makin it rain yo" -1. make-it-rain 1 10 8000 µT 100 µT 2021-03-26 10:03:30.459157 UTC c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 negotiated makin it rain yo +1. make-it-rain 1 10 8000 µT 100 µT 2021-03-26T10:03:30Z c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 negotiated makin it rain yo Monitoring 10 sent transactions to Broadcast stage... Done! All transactions monitored to Broadcast stage. diff --git a/applications/tari_console_wallet/src/automation/command_parser.rs b/applications/tari_console_wallet/src/automation/command_parser.rs index d8eca4a212..b7cc3327e0 100644 --- a/applications/tari_console_wallet/src/automation/command_parser.rs +++ b/applications/tari_console_wallet/src/automation/command_parser.rs @@ -22,8 +22,6 @@ use crate::automation::{commands::WalletCommand, error::ParseError}; -use chrono::{DateTime, Utc}; -use chrono_english::{parse_date_string, Dialect}; use core::str::SplitWhitespace; use std::{ fmt::{Display, Formatter}, @@ -34,6 +32,7 @@ use tari_comms::multiaddr::Multiaddr; use tari_common_types::types::PublicKey; use tari_core::transactions::tari_amount::MicroTari; +use time::{format_description::well_known::Rfc3339, OffsetDateTime}; #[derive(Debug)] pub struct ParsedCommand { @@ -78,7 +77,7 @@ pub enum ParsedArgument { Text(String), Float(f64), Int(u64), - Date(DateTime), + Date(OffsetDateTime), OutputToCSVFile(String), CSVFileName(String), Address(Multiaddr), @@ -216,11 +215,10 @@ fn parse_make_it_rain(mut args: SplitWhitespace) -> Result, // start time utc or 'now' let start_time = args.next().ok_or_else(|| ParseError::Empty("start time".to_string()))?; - let now = Utc::now(); let start_time = if start_time != "now" { - parse_date_string(start_time, now, Dialect::Uk).map_err(ParseError::Date)? + OffsetDateTime::parse(start_time, &Rfc3339)? } else { - now + OffsetDateTime::now_utc() }; parsed_args.push(ParsedArgument::Date(start_time)); diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 34301b3d45..5706df5087 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -30,7 +30,6 @@ use std::{ time::{Duration, Instant}, }; -use chrono::{DateTime, Utc}; use futures::FutureExt; use strum_macros::{Display, EnumIter, EnumString}; use tari_crypto::ristretto::pedersen::PedersenCommitmentFactory; @@ -59,6 +58,7 @@ use tari_wallet::{ transaction_service::handle::{TransactionEvent, TransactionServiceHandle}, WalletSqlite, }; +use time::OffsetDateTime; use tokio::{ sync::{broadcast, mpsc}, time::{sleep, timeout}, @@ -274,7 +274,7 @@ pub async fn make_it_rain( }?; let start_time = match args[4].clone() { - Date(dt) => Ok(dt as DateTime), + Date(dt) => Ok(dt), _ => Err(CommandError::Argument), }?; @@ -296,13 +296,13 @@ pub async fn make_it_rain( // We are spawning this command in parallel, thus not collecting transaction IDs tokio::task::spawn(async move { // Wait until specified test start time - let now = Utc::now(); + let now = OffsetDateTime::now_utc(); let delay_ms = if start_time > now { println!( "`make-it-rain` scheduled to start at {}: msg \"{}\"", start_time, message ); - (start_time - now).num_milliseconds() as u64 + (start_time - now).whole_milliseconds() as u64 } else { 0 }; @@ -314,7 +314,7 @@ pub async fn make_it_rain( sleep(Duration::from_millis(delay_ms)).await; let num_txs = (txps * duration as f64) as usize; - let started_at = Utc::now(); + let started_at = OffsetDateTime::now_utc(); struct TransactionSendStats { i: usize, @@ -348,7 +348,7 @@ pub async fn make_it_rain( ParsedArgument::Text(message.clone()), ]; // Manage transaction submission rate - let actual_ms = (Utc::now() - started_at).num_milliseconds(); + let actual_ms = (OffsetDateTime::now_utc() - started_at).whole_milliseconds() as i64; let target_ms = (i as f64 / (txps / 1000.0)) as i64; if target_ms - actual_ms > 0 { // Maximum delay between Txs set to 120 s @@ -420,7 +420,7 @@ pub async fn make_it_rain( num_txs, transaction_type, message, - Utc::now() + OffsetDateTime::now_utc(), ); }); diff --git a/applications/tari_console_wallet/src/automation/error.rs b/applications/tari_console_wallet/src/automation/error.rs index 483d2ae371..ead7e548f4 100644 --- a/applications/tari_console_wallet/src/automation/error.rs +++ b/applications/tari_console_wallet/src/automation/error.rs @@ -22,7 +22,6 @@ use std::num::{ParseFloatError, ParseIntError}; -use chrono_english::DateError; use log::*; use tari_common::exit_codes::ExitCodes; use tari_core::transactions::{ @@ -35,6 +34,7 @@ use tari_wallet::{ transaction_service::error::TransactionServiceError, }; use thiserror::Error; +use time::error::ComponentRange; use tokio::task::JoinError; pub const LOG_TARGET: &str = "wallet::automation::error"; @@ -88,7 +88,9 @@ pub enum ParseError { #[error("Failed to parse int.")] Int(#[from] ParseIntError), #[error("Failed to parse date. {0}")] - Date(#[from] DateError), + Date(#[from] time::error::Parse), + #[error("Failed to convert time. {0}")] + TimeRange(#[from] ComponentRange), #[error("Failed to parse a net address.")] Address, #[error("Invalid combination of arguments ({0}).")] diff --git a/applications/tari_console_wallet/src/recovery.rs b/applications/tari_console_wallet/src/recovery.rs index d3c247977a..2b2ff73aa6 100644 --- a/applications/tari_console_wallet/src/recovery.rs +++ b/applications/tari_console_wallet/src/recovery.rs @@ -20,7 +20,6 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use chrono::offset::Local; use futures::FutureExt; use log::*; use rustyline::Editor; @@ -36,6 +35,7 @@ use tari_wallet::{ use crate::wallet_modes::PeerConfig; use tari_key_manager::cipher_seed::CipherSeed; +use time::OffsetDateTime; use tokio::sync::broadcast; pub const LOG_TARGET: &str = "wallet::recovery"; @@ -126,14 +126,14 @@ pub async fn wallet_recovery(wallet: &WalletSqlite, base_node_config: &PeerConfi debug!( target: LOG_TARGET, "{}: Recovery process {}% complete ({} of {} utxos).", - Local::now(), + OffsetDateTime::now_local().unwrap(), percentage_progress, current, total ); println!( "{}: Recovery process {}% complete ({} of {} utxos).", - Local::now(), + OffsetDateTime::now_local().unwrap(), percentage_progress, current, total diff --git a/applications/tari_console_wallet/src/ui/components/notification_tab.rs b/applications/tari_console_wallet/src/ui/components/notification_tab.rs index 80a6685879..00a115f93f 100644 --- a/applications/tari_console_wallet/src/ui/components/notification_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/notification_tab.rs @@ -9,7 +9,9 @@ // notification, the UI should go there if I click on it. use crate::ui::{components::Component, state::AppState}; +use anyhow::Error; use tari_comms::runtime::Handle; +use time::{error::Format, format_description::FormatItem, macros::format_description}; use tui::{ backend::Backend, layout::{Constraint, Layout, Rect}, @@ -19,6 +21,8 @@ use tui::{ Frame, }; +const DT_FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day] [hour]-[minute]-[second] "); + pub struct NotificationTab {} impl NotificationTab { @@ -26,7 +30,7 @@ impl NotificationTab { Self {} } - fn draw_notifications(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) + fn draw_notifications(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) -> Result<(), Error> where B: Backend { let block = Block::default().borders(Borders::ALL).title(Span::styled( "Notifications", @@ -37,22 +41,21 @@ impl NotificationTab { .constraints([Constraint::Min(42)].as_ref()) .margin(1) .split(area); - let mut text: Vec = app_state + let text = app_state .get_notifications() .iter() + .rev() .map(|(time, line)| { - Spans::from(vec![ - Span::styled( - time.format("%Y-%m-%d %H:%M:%S ").to_string(), - Style::default().fg(Color::LightGreen), - ), + Ok(Spans::from(vec![ + Span::styled(time.format(&DT_FORMAT)?, Style::default().fg(Color::LightGreen)), Span::raw(line), - ]) + ])) }) - .collect(); - text.reverse(); - let paragraph = Paragraph::new(text.clone()).wrap(Wrap { trim: true }); + .collect::, Format>>() + .unwrap(); + let paragraph = Paragraph::new(text).wrap(Wrap { trim: true }); f.render_widget(paragraph, notifications_area[0]); + Ok(()) } } @@ -61,7 +64,9 @@ impl Component for NotificationTab { let areas = Layout::default() .constraints([Constraint::Min(42)].as_ref()) .split(area); - self.draw_notifications(f, areas[0], app_state); + if let Err(err) = self.draw_notifications(f, areas[0], app_state) { + log::error!("Notification tab rendering failed: {}", err); + } } fn on_tick(&mut self, app_state: &mut AppState) { diff --git a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs index 2c9c49f8b6..9642abb085 100644 --- a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs @@ -6,8 +6,9 @@ use crate::ui::{ widgets::{draw_dialog, MultiColumnList, WindowedListState}, MAX_WIDTH, }; -use chrono::{DateTime, Local}; +use anyhow::Error; use tari_common_types::transaction::{TransactionDirection, TransactionStatus}; +use time::{format_description::FormatItem, macros::format_description, UtcOffset}; use tokio::runtime::Handle; use tui::{ backend::Backend, @@ -18,6 +19,8 @@ use tui::{ Frame, }; +const DT_FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); + pub struct TransactionsTab { balance: Balance, selected_tx_list: SelectedTransactionList, @@ -41,7 +44,7 @@ impl TransactionsTab { } } - fn draw_transaction_lists(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) + fn draw_transaction_lists(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) -> Result<(), Error> where B: Backend { let (pending_constraint, completed_constraint) = if app_state.get_pending_txs().is_empty() { self.selected_tx_list = SelectedTransactionList::CompletedTxs; @@ -66,12 +69,20 @@ impl TransactionsTab { .title(Span::styled("(P)ending Transactions", style)); f.render_widget(block, list_areas[0]); - self.draw_pending_transactions(f, list_areas[0], app_state); - self.draw_completed_transactions(f, list_areas[1], app_state); + self.draw_pending_transactions(f, list_areas[0], app_state)?; + self.draw_completed_transactions(f, list_areas[1], app_state)?; + Ok(()) } - fn draw_pending_transactions(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) - where B: Backend { + fn draw_pending_transactions( + &mut self, + f: &mut Frame, + area: Rect, + app_state: &AppState, + ) -> Result<(), Error> + where + B: Backend, + { // Pending Transactions self.pending_list_state.set_num_items(app_state.get_pending_txs().len()); let mut pending_list_state = self @@ -114,9 +125,10 @@ impl TransactionsTab { }; column1_items.push(ListItem::new(Span::styled(format!("{}", t.amount), amount_style))); } - let local_time = DateTime::::from_utc(t.timestamp, Local::now().offset().to_owned()); + let offset = UtcOffset::current_local_offset()?; + let local_time = t.timestamp.replace_offset(offset); column2_items.push(ListItem::new(Span::styled( - format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")), + local_time.format(&DT_FORMAT)?, Style::default().fg(text_color), ))); column3_items.push(ListItem::new(Span::styled( @@ -134,10 +146,18 @@ impl TransactionsTab { .add_column(Some("Local Date/Time"), Some(20), column2_items) .add_column(Some("Message"), None, column3_items); column_list.render(f, area, &mut pending_list_state); + Ok(()) } - fn draw_completed_transactions(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) - where B: Backend { + fn draw_completed_transactions( + &mut self, + f: &mut Frame, + area: Rect, + app_state: &AppState, + ) -> Result<(), Error> + where + B: Backend, + { // Completed Transactions let style = if self.selected_tx_list == SelectedTransactionList::CompletedTxs { Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD) @@ -203,9 +223,10 @@ impl TransactionsTab { let amount_style = Style::default().fg(color); column1_items.push(ListItem::new(Span::styled(format!("{}", t.amount), amount_style))); } - let local_time = DateTime::::from_utc(t.timestamp, Local::now().offset().to_owned()); + let offset = UtcOffset::current_local_offset()?; + let local_time = t.timestamp.replace_offset(offset); column2_items.push(ListItem::new(Span::styled( - format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")), + local_time.format(&DT_FORMAT)?, Style::default().fg(text_color), ))); let status = if (t.cancelled || !t.valid) && t.status == TransactionStatus::Coinbase { @@ -232,6 +253,7 @@ impl TransactionsTab { .add_column(Some("Status"), None, column3_items); column_list.render(f, area, &mut completed_list_state); + Ok(()) } fn draw_detailed_transaction(&self, f: &mut Frame, area: Rect, app_state: &AppState) @@ -344,9 +366,10 @@ impl TransactionsTab { }; let status = Span::styled(status_msg, Style::default().fg(Color::White)); let message = Span::styled(tx.message.as_str(), Style::default().fg(Color::White)); - let local_time = DateTime::::from_utc(tx.timestamp, Local::now().offset().to_owned()); + // TODO: Get Local from UTC + let local_time = tx.timestamp; let timestamp = Span::styled( - format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")), + format!("{}", local_time.format(&DT_FORMAT).unwrap()), Style::default().fg(Color::White), ); let excess = Span::styled(tx.excess_signature.as_str(), Style::default().fg(Color::White)); @@ -446,7 +469,9 @@ impl Component for TransactionsTab { let instructions = Paragraph::new(Spans::from(span_vec)).wrap(Wrap { trim: true }); f.render_widget(instructions, areas[1]); - self.draw_transaction_lists(f, areas[2], app_state); + if let Err(err) = self.draw_transaction_lists(f, areas[2], app_state) { + log::error!("Can't draw transactions list: {}", err); + } self.draw_detailed_transaction(f, areas[3], app_state); if let Some(msg) = self.error_message.clone() { diff --git a/applications/tari_console_wallet/src/ui/state/app_state.rs b/applications/tari_console_wallet/src/ui/state/app_state.rs index 466f091fe3..bdf08f8a7c 100644 --- a/applications/tari_console_wallet/src/ui/state/app_state.rs +++ b/applications/tari_console_wallet/src/ui/state/app_state.rs @@ -27,11 +27,11 @@ use std::{ }; use bitflags::bitflags; -use chrono::{DateTime, Local, NaiveDateTime}; use log::*; use qrcode::{render::unicode, QrCode}; use tari_crypto::{ristretto::RistrettoPublicKey, tari_utilities::hex::Hex}; use tari_p2p::auto_update::SoftwareUpdaterHandle; +use time::OffsetDateTime; use tokio::{ sync::{watch, RwLock}, task, @@ -442,7 +442,7 @@ impl AppState { self.completed_tx_filter.toggle(TransactionFilter::ABANDONED_COINBASES); } - pub fn get_notifications(&self) -> &Vec<(DateTime, String)> { + pub fn get_notifications(&self) -> &[(OffsetDateTime, String)] { &self.cached_data.notifications } @@ -847,7 +847,9 @@ impl AppStateInner { } pub fn add_notification(&mut self, notification: String) { - self.data.notifications.push((Local::now(), notification)); + self.data + .notifications + .push((OffsetDateTime::now_local().unwrap(), notification)); self.data.new_notification_count += 1; self.updated = true; } @@ -873,7 +875,7 @@ pub struct CompletedTransactionInfo { pub maturity: u64, pub status: TransactionStatus, pub message: String, - pub timestamp: NaiveDateTime, + pub timestamp: OffsetDateTime, pub cancelled: bool, pub direction: TransactionDirection, pub valid: bool, @@ -911,7 +913,7 @@ impl CompletedTransactionInfo { .unwrap_or(0), status: tx.status, message: tx.message, - timestamp: tx.timestamp, + timestamp: OffsetDateTime::from_unix_timestamp(tx.timestamp.timestamp()).unwrap(), cancelled: tx.cancelled, direction: tx.direction, valid: tx.valid, @@ -938,7 +940,7 @@ struct AppStateData { base_node_previous: Peer, base_node_list: Vec<(String, Peer)>, base_node_peer_custom: Option, - notifications: Vec<(DateTime, String)>, + notifications: Vec<(OffsetDateTime, String)>, new_notification_count: u32, } From f8185e94083722f18d0ca6a7cc12630bae94292e Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Tue, 9 Nov 2021 20:12:27 +0200 Subject: [PATCH 09/46] ci: fix libwallet build (#3553) * ci: fix libwallet build * update fork and add workflow dispatch --- .github/workflows/libwallet.yml | 10 ++++++++-- rust-toolchain.toml | 14 +++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/libwallet.yml b/.github/workflows/libwallet.yml index b2df3d7182..a9c92f523d 100644 --- a/.github/workflows/libwallet.yml +++ b/.github/workflows/libwallet.yml @@ -9,6 +9,12 @@ on: - "libwallet-*" schedule: - cron: "05 00 * * *" + workflow_dispatch: + inputs: + customTag: + description: "Development Tag" + required: true + default: "development-tag" jobs: android: runs-on: ubuntu-latest @@ -18,7 +24,7 @@ jobs: # Build and package the libraries - name: Build libwallet id: build-libwallet - uses: tari-project/action-buildlibs@v0.3.2 + uses: tari-project/action-buildlibs@v0.3.3 with: platforms: "x86_64-linux-android;aarch64-linux-android;armv7-linux-androideabi" level: "24" @@ -31,7 +37,7 @@ jobs: # Copy tarballs to S3 - name: Sync to S3 if: ${{ startsWith(github.ref, 'refs/tags/v') }} - continue-on-error: true # Don't break if s3 upload fails + continue-on-error: true # Don't break if s3 upload fails uses: jakejarvis/s3-sync-action@v0.5.1 with: args: --acl public-read --follow-symlinks diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0d2a95711f..20c6ecf071 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,13 @@ -# Please update all references in the codebase! -# and increment this number: -# Hours spent updating the Rust Toolchain = 1 +# So, you want to update the Rust toolchain... + +# Besides making sure the code compiles (duh) and the tests/clippy pass (obviously) +# Please note that you'll have to update all the toolchain references in the codebase! +# AND build new docker images for: +# - rust_tari-build-with-deps +# - rust-ndk +# AND update the action-buildlibs dockerfile. + + +# Hours spent updating the Rust Toolchain = 4 [toolchain] channel = "nightly-2021-09-18" From b3e71398537882c8b9a59bf089fd67422f46704f Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Wed, 10 Nov 2021 09:30:24 +0200 Subject: [PATCH 10/46] chore: fix dev broken cucumber (#3554) Description --- fix cucumber tests on dev. --- integration_tests/features/support/steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index b791d70c9c..9320964ed8 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -1351,7 +1351,7 @@ When( /mining node (.*) mines (\d+) blocks$/, { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; dynamic time out below limits actual time async function (miner, numBlocks) { - const miningNode = this.getMiningNode(miner);d + const miningNode = this.getMiningNode(miner); // Don't wait for sync before mining. Also use a max difficulty of 1, since most tests assume // that 1 block = 1 difficulty await miningNode.init(numBlocks, null, 1, 1, false, null); From 5be3b9d36f5b810df6c63d97d60d4177d6484d40 Mon Sep 17 00:00:00 2001 From: Mike the Tike Date: Wed, 10 Nov 2021 10:08:07 +0200 Subject: [PATCH 11/46] refactor: split sqlitedb file (#3541) Splits the sqlitedb file into a mod with individual files to help manage them better. This has been done on the validator-node branch so doing this on development will make merges easier --- .../{sqlite_db.rs => sqlite_db/mod.rs} | 543 +----------------- .../storage/sqlite_db/new_output_sql.rs | 132 +++++ .../storage/sqlite_db/output_sql.rs | 492 ++++++++++++++++ 3 files changed, 631 insertions(+), 536 deletions(-) rename base_layer/wallet/src/output_manager_service/storage/{sqlite_db.rs => sqlite_db/mod.rs} (75%) create mode 100644 base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs create mode 100644 base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs similarity index 75% rename from base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs rename to base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs index afd94398ce..c2af0f7973 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs @@ -38,7 +38,7 @@ use crate::{ }; use aes_gcm::Aes256Gcm; use chrono::{NaiveDateTime, Utc}; -use diesel::{prelude::*, result::Error as DieselError, sql_query, SqliteConnection}; +use diesel::{prelude::*, result::Error as DieselError, SqliteConnection}; use log::*; use std::{ convert::{TryFrom, TryInto}, @@ -47,18 +47,10 @@ use std::{ }; use tari_common_types::{ transaction::TxId, - types::{ComSignature, Commitment, PrivateKey, PublicKey}, -}; -use tari_core::{ - tari_utilities::hash::Hashable, - transactions::{ - tari_amount::MicroTari, - transaction::{OutputFeatures, OutputFlags, TransactionOutput, UnblindedOutput}, - CryptoFactories, - }, + types::{Commitment, PrivateKey}, }; +use tari_core::transactions::transaction::TransactionOutput; use tari_crypto::{ - commitment::HomomorphicCommitmentFactory, script::{ExecutionStack, TariScript}, tari_utilities::{ hex::{from_hex, Hex}, @@ -67,6 +59,10 @@ use tari_crypto::{ }; use tari_key_manager::cipher_seed::CipherSeed; use tokio::time::Instant; +mod new_output_sql; +pub use new_output_sql::NewOutputSql; +mod output_sql; +pub use output_sql::OutputSql; const LOG_TARGET: &str = "wallet::output_manager_service::database::sqlite_db"; @@ -1134,531 +1130,6 @@ impl TryFrom for OutputStatus { } } -/// This struct represents an Output in the Sql database. A distinct struct is required to define the Sql friendly -/// equivalent datatypes for the members. -#[derive(Clone, Debug, Insertable, PartialEq)] -#[table_name = "outputs"] -struct NewOutputSql { - commitment: Option>, - spending_key: Vec, - value: i64, - flags: i32, - maturity: i64, - status: i32, - hash: Option>, - script: Vec, - input_data: Vec, - script_private_key: Vec, - sender_offset_public_key: Vec, - metadata_signature_nonce: Vec, - metadata_signature_u_key: Vec, - metadata_signature_v_key: Vec, - received_in_tx_id: Option, - coinbase_block_height: Option, -} - -impl NewOutputSql { - pub fn new( - output: DbUnblindedOutput, - status: OutputStatus, - received_in_tx_id: Option, - coinbase_block_height: Option, - ) -> Result { - Ok(Self { - commitment: Some(output.commitment.to_vec()), - spending_key: output.unblinded_output.spending_key.to_vec(), - value: (u64::from(output.unblinded_output.value)) as i64, - flags: output.unblinded_output.features.flags.bits() as i32, - maturity: output.unblinded_output.features.maturity as i64, - status: status as i32, - received_in_tx_id: received_in_tx_id.map(|i| i as i64), - hash: Some(output.hash), - script: output.unblinded_output.script.as_bytes(), - input_data: output.unblinded_output.input_data.as_bytes(), - script_private_key: output.unblinded_output.script_private_key.to_vec(), - sender_offset_public_key: output.unblinded_output.sender_offset_public_key.to_vec(), - metadata_signature_nonce: output.unblinded_output.metadata_signature.public_nonce().to_vec(), - metadata_signature_u_key: output.unblinded_output.metadata_signature.u().to_vec(), - metadata_signature_v_key: output.unblinded_output.metadata_signature.v().to_vec(), - coinbase_block_height: coinbase_block_height.map(|bh| bh as i64), - }) - } - - /// Write this struct to the database - pub fn commit(&self, conn: &SqliteConnection) -> Result<(), OutputManagerStorageError> { - diesel::insert_into(outputs::table).values(self.clone()).execute(conn)?; - Ok(()) - } -} - -impl Encryptable for NewOutputSql { - fn encrypt(&mut self, cipher: &Aes256Gcm) -> Result<(), String> { - self.spending_key = encrypt_bytes_integral_nonce(cipher, self.spending_key.clone())?; - self.script_private_key = encrypt_bytes_integral_nonce(cipher, self.script_private_key.clone())?; - Ok(()) - } - - fn decrypt(&mut self, cipher: &Aes256Gcm) -> Result<(), String> { - self.spending_key = decrypt_bytes_integral_nonce(cipher, self.spending_key.clone())?; - self.script_private_key = decrypt_bytes_integral_nonce(cipher, self.script_private_key.clone())?; - Ok(()) - } -} - -#[derive(Clone, Debug, Queryable, Identifiable, PartialEq)] -#[table_name = "outputs"] -struct OutputSql { - id: i32, // Auto inc primary key - commitment: Option>, - spending_key: Vec, - value: i64, - flags: i32, - maturity: i64, - status: i32, - hash: Option>, - script: Vec, - input_data: Vec, - script_private_key: Vec, - sender_offset_public_key: Vec, - metadata_signature_nonce: Vec, - metadata_signature_u_key: Vec, - metadata_signature_v_key: Vec, - mined_height: Option, - mined_in_block: Option>, - mined_mmr_position: Option, - marked_deleted_at_height: Option, - marked_deleted_in_block: Option>, - received_in_tx_id: Option, - spent_in_tx_id: Option, - coinbase_block_height: Option, -} - -impl OutputSql { - /// Return all outputs - pub fn index(conn: &SqliteConnection) -> Result, OutputManagerStorageError> { - Ok(outputs::table.load::(conn)?) - } - - /// Return all outputs with a given status - pub fn index_status( - status: OutputStatus, - conn: &SqliteConnection, - ) -> Result, OutputManagerStorageError> { - Ok(outputs::table.filter(outputs::status.eq(status as i32)).load(conn)?) - } - - /// Return all unspent outputs that have a maturity above the provided chain tip - pub fn index_time_locked(tip: u64, conn: &SqliteConnection) -> Result, OutputManagerStorageError> { - Ok(outputs::table - .filter(outputs::status.eq(OutputStatus::Unspent as i32)) - .filter(outputs::maturity.gt(tip as i64)) - .load(conn)?) - } - - pub fn index_unconfirmed(conn: &SqliteConnection) -> Result, OutputManagerStorageError> { - Ok(outputs::table - .filter( - outputs::status - .eq(OutputStatus::UnspentMinedUnconfirmed as i32) - .or(outputs::mined_in_block.is_null()), - ) - .order(outputs::id.asc()) - .load(conn)?) - } - - pub fn index_marked_deleted_in_block_is_null( - conn: &SqliteConnection, - ) -> Result, OutputManagerStorageError> { - Ok(outputs::table - // Return outputs not marked as deleted or confirmed - .filter(outputs::marked_deleted_in_block.is_null().or(outputs::status.eq(OutputStatus::SpentMinedUnconfirmed as i32))) - // Only return mined - .filter(outputs::mined_in_block.is_not_null()) - .order(outputs::id.asc()) - .load(conn)?) - } - - pub fn first_by_mined_height_desc(conn: &SqliteConnection) -> Result, OutputManagerStorageError> { - Ok(outputs::table - .filter(outputs::mined_height.is_not_null()) - .order(outputs::mined_height.desc()) - .first(conn) - .optional()?) - } - - pub fn first_by_marked_deleted_height_desc( - conn: &SqliteConnection, - ) -> Result, OutputManagerStorageError> { - Ok(outputs::table - .filter(outputs::marked_deleted_at_height.is_not_null()) - .order(outputs::marked_deleted_at_height.desc()) - .first(conn) - .optional()?) - } - - /// Find a particular Output, if it exists - pub fn find(spending_key: &[u8], conn: &SqliteConnection) -> Result { - Ok(outputs::table - .filter(outputs::spending_key.eq(spending_key)) - .first::(conn)?) - } - - /// Return the available, time locked, pending incoming and pending outgoing balance - pub fn get_balance( - current_tip_for_time_lock_calculation: Option, - conn: &SqliteConnection, - ) -> Result { - #[derive(QueryableByName, Clone)] - struct BalanceQueryResult { - #[sql_type = "diesel::sql_types::BigInt"] - amount: i64, - #[sql_type = "diesel::sql_types::Text"] - category: String, - } - let balance_query_result = if let Some(current_tip) = current_tip_for_time_lock_calculation { - let balance_query = sql_query( - "SELECT coalesce(sum(value), 0) as amount, 'available_balance' as category \ - FROM outputs WHERE status = ? \ - UNION ALL \ - SELECT coalesce(sum(value), 0) as amount, 'time_locked_balance' as category \ - FROM outputs WHERE status = ? AND maturity > ? \ - UNION ALL \ - SELECT coalesce(sum(value), 0) as amount, 'pending_incoming_balance' as category \ - FROM outputs WHERE status = ? OR status = ? OR status = ? \ - UNION ALL \ - SELECT coalesce(sum(value), 0) as amount, 'pending_outgoing_balance' as category \ - FROM outputs WHERE status = ? OR status = ? OR status = ?", - ) - // available_balance - .bind::(OutputStatus::Unspent as i32) - // time_locked_balance - .bind::(OutputStatus::Unspent as i32) - .bind::(current_tip as i64) - // pending_incoming_balance - .bind::(OutputStatus::EncumberedToBeReceived as i32) - .bind::(OutputStatus::ShortTermEncumberedToBeReceived as i32) - .bind::(OutputStatus::UnspentMinedUnconfirmed as i32) - // pending_outgoing_balance - .bind::(OutputStatus::EncumberedToBeSpent as i32) - .bind::(OutputStatus::ShortTermEncumberedToBeSpent as i32) - .bind::(OutputStatus::SpentMinedUnconfirmed as i32); - balance_query.load::(conn)? - } else { - let balance_query = sql_query( - "SELECT coalesce(sum(value), 0) as amount, 'available_balance' as category \ - FROM outputs WHERE status = ? \ - UNION ALL \ - SELECT coalesce(sum(value), 0) as amount, 'pending_incoming_balance' as category \ - FROM outputs WHERE status = ? OR status = ? OR status = ? \ - UNION ALL \ - SELECT coalesce(sum(value), 0) as amount, 'pending_outgoing_balance' as category \ - FROM outputs WHERE status = ? OR status = ? OR status = ?", - ) - // available_balance - .bind::(OutputStatus::Unspent as i32) - // pending_incoming_balance - .bind::(OutputStatus::EncumberedToBeReceived as i32) - .bind::(OutputStatus::ShortTermEncumberedToBeReceived as i32) - .bind::(OutputStatus::UnspentMinedUnconfirmed as i32) - // pending_outgoing_balance - .bind::(OutputStatus::EncumberedToBeSpent as i32) - .bind::(OutputStatus::ShortTermEncumberedToBeSpent as i32) - .bind::(OutputStatus::SpentMinedUnconfirmed as i32); - balance_query.load::(conn)? - }; - let mut available_balance = None; - let mut time_locked_balance = Some(None); - let mut pending_incoming_balance = None; - let mut pending_outgoing_balance = None; - for balance in balance_query_result.clone() { - match balance.category.as_str() { - "available_balance" => available_balance = Some(MicroTari::from(balance.amount as u64)), - "time_locked_balance" => time_locked_balance = Some(Some(MicroTari::from(balance.amount as u64))), - "pending_incoming_balance" => pending_incoming_balance = Some(MicroTari::from(balance.amount as u64)), - "pending_outgoing_balance" => pending_outgoing_balance = Some(MicroTari::from(balance.amount as u64)), - _ => { - return Err(OutputManagerStorageError::UnexpectedResult( - "Unexpected category in balance query".to_string(), - )) - }, - } - } - - Ok(Balance { - available_balance: available_balance.ok_or_else(|| { - OutputManagerStorageError::UnexpectedResult("Available balance could not be calculated".to_string()) - })?, - time_locked_balance: time_locked_balance.ok_or_else(|| { - OutputManagerStorageError::UnexpectedResult("Time locked balance could not be calculated".to_string()) - })?, - pending_incoming_balance: pending_incoming_balance.ok_or_else(|| { - OutputManagerStorageError::UnexpectedResult( - "Pending incoming balance could not be calculated".to_string(), - ) - })?, - pending_outgoing_balance: pending_outgoing_balance.ok_or_else(|| { - OutputManagerStorageError::UnexpectedResult( - "Pending outgoing balance could not be calculated".to_string(), - ) - })?, - }) - } - - pub fn find_by_commitment( - commitment: &[u8], - conn: &SqliteConnection, - ) -> Result { - Ok(outputs::table - .filter(outputs::commitment.eq(commitment)) - .first::(conn)?) - } - - pub fn find_by_commitment_and_cancelled( - commitment: &[u8], - cancelled: bool, - conn: &SqliteConnection, - ) -> Result { - let cancelled_flag = OutputStatus::CancelledInbound as i32; - - let mut request = outputs::table.filter(outputs::commitment.eq(commitment)).into_boxed(); - if cancelled { - request = request.filter(outputs::status.eq(cancelled_flag)) - } else { - request = request.filter(outputs::status.ne(cancelled_flag)) - }; - - Ok(request.first::(conn)?) - } - - pub fn find_by_tx_id_and_status( - tx_id: TxId, - status: OutputStatus, - conn: &SqliteConnection, - ) -> Result, OutputManagerStorageError> { - Ok(outputs::table - .filter( - outputs::received_in_tx_id - .eq(Some(tx_id as i64)) - .or(outputs::spent_in_tx_id.eq(Some(tx_id as i64))), - ) - .filter(outputs::status.eq(status as i32)) - .load(conn)?) - } - - /// Find outputs via tx_id that are encumbered. Any outputs that are encumbered cannot be marked as spent. - pub fn find_by_tx_id_and_encumbered( - tx_id: TxId, - conn: &SqliteConnection, - ) -> Result, OutputManagerStorageError> { - Ok(outputs::table - .filter( - outputs::received_in_tx_id - .eq(Some(tx_id as i64)) - .or(outputs::spent_in_tx_id.eq(Some(tx_id as i64))), - ) - .filter( - outputs::status - .eq(OutputStatus::EncumberedToBeReceived as i32) - .or(outputs::status.eq(OutputStatus::EncumberedToBeSpent as i32)) - .or(outputs::status.eq(OutputStatus::ShortTermEncumberedToBeReceived as i32)) - .or(outputs::status.eq(OutputStatus::ShortTermEncumberedToBeSpent as i32)), - ) - .load(conn)?) - } - - /// Find a particular Output, if it exists and is in the specified Spent state - pub fn find_status( - spending_key: &[u8], - status: OutputStatus, - conn: &SqliteConnection, - ) -> Result { - Ok(outputs::table - .filter(outputs::status.eq(status as i32)) - .filter(outputs::spending_key.eq(spending_key)) - .first::(conn)?) - } - - /// Find a particular Output, if it exists and is in the specified Spent state - pub fn find_pending_coinbase_at_block_height( - block_height: u64, - conn: &SqliteConnection, - ) -> Result { - Ok(outputs::table - .filter(outputs::status.ne(OutputStatus::Unspent as i32)) - .filter(outputs::coinbase_block_height.eq(block_height as i64)) - .first::(conn)?) - } - - pub fn delete(&self, conn: &SqliteConnection) -> Result<(), OutputManagerStorageError> { - let num_deleted = - diesel::delete(outputs::table.filter(outputs::spending_key.eq(&self.spending_key))).execute(conn)?; - - if num_deleted == 0 { - return Err(OutputManagerStorageError::ValuesNotFound); - } - - Ok(()) - } - - pub fn update( - &self, - updated_output: UpdateOutput, - conn: &SqliteConnection, - ) -> Result { - diesel::update(outputs::table.filter(outputs::id.eq(&self.id))) - .set(UpdateOutputSql::from(updated_output)) - .execute(conn) - .num_rows_affected_or_not_found(1)?; - - OutputSql::find(&self.spending_key, conn) - } - - /// Update the changed fields of this record after encryption/decryption is performed - pub fn update_encryption(&self, conn: &SqliteConnection) -> Result<(), OutputManagerStorageError> { - let _ = self.update( - UpdateOutput { - spending_key: Some(self.spending_key.clone()), - script_private_key: Some(self.script_private_key.clone()), - ..Default::default() - }, - conn, - )?; - Ok(()) - } -} - -/// Conversion from an DbUnblindedOutput to the Sql datatype form -impl TryFrom for DbUnblindedOutput { - type Error = OutputManagerStorageError; - - fn try_from(o: OutputSql) -> Result { - let unblinded_output = UnblindedOutput::new( - MicroTari::from(o.value as u64), - PrivateKey::from_vec(&o.spending_key).map_err(|_| { - error!( - target: LOG_TARGET, - "Could not create PrivateKey from stored bytes, They might be encrypted" - ); - OutputManagerStorageError::ConversionError - })?, - OutputFeatures { - flags: OutputFlags::from_bits(o.flags as u8).ok_or(OutputManagerStorageError::ConversionError)?, - maturity: o.maturity as u64, - }, - TariScript::from_bytes(o.script.as_slice())?, - ExecutionStack::from_bytes(o.input_data.as_slice())?, - PrivateKey::from_vec(&o.script_private_key).map_err(|_| { - error!( - target: LOG_TARGET, - "Could not create PrivateKey from stored bytes, They might be encrypted" - ); - OutputManagerStorageError::ConversionError - })?, - PublicKey::from_vec(&o.sender_offset_public_key).map_err(|_| { - error!( - target: LOG_TARGET, - "Could not create PublicKey from stored bytes, They might be encrypted" - ); - OutputManagerStorageError::ConversionError - })?, - ComSignature::new( - Commitment::from_vec(&o.metadata_signature_nonce).map_err(|_| { - error!( - target: LOG_TARGET, - "Could not create PublicKey from stored bytes, They might be encrypted" - ); - OutputManagerStorageError::ConversionError - })?, - PrivateKey::from_vec(&o.metadata_signature_u_key).map_err(|_| { - error!( - target: LOG_TARGET, - "Could not create PrivateKey from stored bytes, They might be encrypted" - ); - OutputManagerStorageError::ConversionError - })?, - PrivateKey::from_vec(&o.metadata_signature_v_key).map_err(|_| { - error!( - target: LOG_TARGET, - "Could not create PrivateKey from stored bytes, They might be encrypted" - ); - OutputManagerStorageError::ConversionError - })?, - ), - ); - - let hash = match o.hash { - None => { - let factories = CryptoFactories::default(); - unblinded_output.as_transaction_output(&factories)?.hash() - }, - Some(v) => v, - }; - let commitment = match o.commitment { - None => { - let factories = CryptoFactories::default(); - factories - .commitment - .commit(&unblinded_output.spending_key, &unblinded_output.value.into()) - }, - Some(c) => Commitment::from_vec(&c)?, - }; - - Ok(Self { - commitment, - unblinded_output, - hash, - mined_height: o.mined_height.map(|mh| mh as u64), - mined_in_block: o.mined_in_block, - mined_mmr_position: o.mined_mmr_position.map(|mp| mp as u64), - marked_deleted_at_height: o.marked_deleted_at_height.map(|d| d as u64), - marked_deleted_in_block: o.marked_deleted_in_block, - }) - } -} - -impl Encryptable for OutputSql { - fn encrypt(&mut self, cipher: &Aes256Gcm) -> Result<(), String> { - self.spending_key = encrypt_bytes_integral_nonce(cipher, self.spending_key.clone())?; - self.script_private_key = encrypt_bytes_integral_nonce(cipher, self.script_private_key.clone())?; - Ok(()) - } - - fn decrypt(&mut self, cipher: &Aes256Gcm) -> Result<(), String> { - self.spending_key = decrypt_bytes_integral_nonce(cipher, self.spending_key.clone())?; - self.script_private_key = decrypt_bytes_integral_nonce(cipher, self.script_private_key.clone())?; - Ok(()) - } -} - -impl From for NewOutputSql { - fn from(o: OutputSql) -> Self { - Self { - commitment: o.commitment, - spending_key: o.spending_key, - value: o.value, - flags: o.flags, - maturity: o.maturity, - status: o.status, - hash: o.hash, - script: o.script, - input_data: o.input_data, - script_private_key: o.script_private_key, - sender_offset_public_key: o.sender_offset_public_key, - metadata_signature_nonce: o.metadata_signature_nonce, - metadata_signature_u_key: o.metadata_signature_u_key, - metadata_signature_v_key: o.metadata_signature_v_key, - received_in_tx_id: o.received_in_tx_id, - coinbase_block_height: o.coinbase_block_height, - } - } -} - -impl PartialEq for OutputSql { - fn eq(&self, other: &NewOutputSql) -> bool { - &NewOutputSql::from(self.clone()) == other - } -} - /// These are the fields that can be updated for an Output #[derive(Default)] pub struct UpdateOutput { diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs new file mode 100644 index 0000000000..c69ec2e93f --- /dev/null +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs @@ -0,0 +1,132 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use crate::{ + output_manager_service::{ + error::OutputManagerStorageError, + storage::{ + models::{DbUnblindedOutput, OutputStatus}, + sqlite_db::OutputSql, + }, + }, + schema::outputs, + util::encryption::{decrypt_bytes_integral_nonce, encrypt_bytes_integral_nonce, Encryptable}, +}; +use aes_gcm::Aes256Gcm; + +use diesel::{prelude::*, SqliteConnection}; + +use tari_common_types::transaction::TxId; +use tari_crypto::tari_utilities::ByteArray; + +/// This struct represents an Output in the Sql database. A distinct struct is required to define the Sql friendly +/// equivalent datatypes for the members. +#[derive(Clone, Debug, Insertable, PartialEq)] +#[table_name = "outputs"] +pub struct NewOutputSql { + pub commitment: Option>, + pub spending_key: Vec, + pub value: i64, + pub flags: i32, + pub maturity: i64, + pub status: i32, + pub hash: Option>, + pub script: Vec, + pub input_data: Vec, + pub script_private_key: Vec, + pub sender_offset_public_key: Vec, + pub metadata_signature_nonce: Vec, + pub metadata_signature_u_key: Vec, + pub metadata_signature_v_key: Vec, + pub received_in_tx_id: Option, + pub coinbase_block_height: Option, +} + +impl NewOutputSql { + pub fn new( + output: DbUnblindedOutput, + status: OutputStatus, + received_in_tx_id: Option, + coinbase_block_height: Option, + ) -> Result { + Ok(Self { + commitment: Some(output.commitment.to_vec()), + spending_key: output.unblinded_output.spending_key.to_vec(), + value: (u64::from(output.unblinded_output.value)) as i64, + flags: output.unblinded_output.features.flags.bits() as i32, + maturity: output.unblinded_output.features.maturity as i64, + status: status as i32, + received_in_tx_id: received_in_tx_id.map(|i| i as i64), + hash: Some(output.hash), + script: output.unblinded_output.script.as_bytes(), + input_data: output.unblinded_output.input_data.as_bytes(), + script_private_key: output.unblinded_output.script_private_key.to_vec(), + sender_offset_public_key: output.unblinded_output.sender_offset_public_key.to_vec(), + metadata_signature_nonce: output.unblinded_output.metadata_signature.public_nonce().to_vec(), + metadata_signature_u_key: output.unblinded_output.metadata_signature.u().to_vec(), + metadata_signature_v_key: output.unblinded_output.metadata_signature.v().to_vec(), + coinbase_block_height: coinbase_block_height.map(|bh| bh as i64), + }) + } + + /// Write this struct to the database + pub fn commit(&self, conn: &SqliteConnection) -> Result<(), OutputManagerStorageError> { + diesel::insert_into(outputs::table).values(self.clone()).execute(conn)?; + Ok(()) + } +} + +impl Encryptable for NewOutputSql { + fn encrypt(&mut self, cipher: &Aes256Gcm) -> Result<(), String> { + self.spending_key = encrypt_bytes_integral_nonce(cipher, self.spending_key.clone())?; + self.script_private_key = encrypt_bytes_integral_nonce(cipher, self.script_private_key.clone())?; + Ok(()) + } + + fn decrypt(&mut self, cipher: &Aes256Gcm) -> Result<(), String> { + self.spending_key = decrypt_bytes_integral_nonce(cipher, self.spending_key.clone())?; + self.script_private_key = decrypt_bytes_integral_nonce(cipher, self.script_private_key.clone())?; + Ok(()) + } +} + +impl From for NewOutputSql { + fn from(o: OutputSql) -> Self { + Self { + commitment: o.commitment, + spending_key: o.spending_key, + value: o.value, + flags: o.flags, + maturity: o.maturity, + status: o.status, + hash: o.hash, + script: o.script, + input_data: o.input_data, + script_private_key: o.script_private_key, + sender_offset_public_key: o.sender_offset_public_key, + metadata_signature_nonce: o.metadata_signature_nonce, + metadata_signature_u_key: o.metadata_signature_u_key, + metadata_signature_v_key: o.metadata_signature_v_key, + received_in_tx_id: o.received_in_tx_id, + coinbase_block_height: o.coinbase_block_height, + } + } +} diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs new file mode 100644 index 0000000000..c1bb5925cb --- /dev/null +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs @@ -0,0 +1,492 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + output_manager_service::{ + error::OutputManagerStorageError, + service::Balance, + storage::{ + models::{DbUnblindedOutput, OutputStatus}, + sqlite_db::{NewOutputSql, UpdateOutput, UpdateOutputSql}, + }, + }, + schema::outputs, + util::{ + diesel_ext::ExpectedRowsExtension, + encryption::{decrypt_bytes_integral_nonce, encrypt_bytes_integral_nonce, Encryptable}, + }, +}; +use aes_gcm::Aes256Gcm; + +use diesel::{prelude::*, sql_query, SqliteConnection}; +use log::*; +use std::convert::TryFrom; +use tari_common_types::{ + transaction::TxId, + types::{ComSignature, Commitment, PrivateKey, PublicKey}, +}; +use tari_core::{ + tari_utilities::hash::Hashable, + transactions::{ + tari_amount::MicroTari, + transaction::{OutputFeatures, OutputFlags, UnblindedOutput}, + CryptoFactories, + }, +}; +use tari_crypto::{ + commitment::HomomorphicCommitmentFactory, + script::{ExecutionStack, TariScript}, + tari_utilities::ByteArray, +}; + +const LOG_TARGET: &str = "wallet::output_manager_service::database::sqlite_db"; + +#[derive(Clone, Debug, Queryable, Identifiable, PartialEq)] +#[table_name = "outputs"] +pub struct OutputSql { + pub id: i32, // Auto inc primary key + pub commitment: Option>, + pub spending_key: Vec, + pub value: i64, + pub flags: i32, + pub maturity: i64, + pub status: i32, + pub hash: Option>, + pub script: Vec, + pub input_data: Vec, + pub script_private_key: Vec, + pub sender_offset_public_key: Vec, + pub metadata_signature_nonce: Vec, + pub metadata_signature_u_key: Vec, + pub metadata_signature_v_key: Vec, + pub mined_height: Option, + pub mined_in_block: Option>, + pub mined_mmr_position: Option, + pub marked_deleted_at_height: Option, + pub marked_deleted_in_block: Option>, + pub received_in_tx_id: Option, + pub spent_in_tx_id: Option, + pub coinbase_block_height: Option, +} + +impl OutputSql { + /// Return all outputs + pub fn index(conn: &SqliteConnection) -> Result, OutputManagerStorageError> { + Ok(outputs::table.load::(conn)?) + } + + /// Return all outputs with a given status + pub fn index_status( + status: OutputStatus, + conn: &SqliteConnection, + ) -> Result, OutputManagerStorageError> { + Ok(outputs::table.filter(outputs::status.eq(status as i32)).load(conn)?) + } + + /// Return all unspent outputs that have a maturity above the provided chain tip + pub fn index_time_locked(tip: u64, conn: &SqliteConnection) -> Result, OutputManagerStorageError> { + Ok(outputs::table + .filter(outputs::status.eq(OutputStatus::Unspent as i32)) + .filter(outputs::maturity.gt(tip as i64)) + .load(conn)?) + } + + pub fn index_unconfirmed(conn: &SqliteConnection) -> Result, OutputManagerStorageError> { + Ok(outputs::table + .filter( + outputs::status + .eq(OutputStatus::UnspentMinedUnconfirmed as i32) + .or(outputs::mined_in_block.is_null()), + ) + .order(outputs::id.asc()) + .load(conn)?) + } + + pub fn index_marked_deleted_in_block_is_null( + conn: &SqliteConnection, + ) -> Result, OutputManagerStorageError> { + Ok(outputs::table + // Return outputs not marked as deleted or confirmed + .filter(outputs::marked_deleted_in_block.is_null().or(outputs::status.eq(OutputStatus::SpentMinedUnconfirmed as i32))) + // Only return mined + .filter(outputs::mined_in_block.is_not_null()) + .order(outputs::id.asc()) + .load(conn)?) + } + + pub fn first_by_mined_height_desc(conn: &SqliteConnection) -> Result, OutputManagerStorageError> { + Ok(outputs::table + .filter(outputs::mined_height.is_not_null()) + .order(outputs::mined_height.desc()) + .first(conn) + .optional()?) + } + + pub fn first_by_marked_deleted_height_desc( + conn: &SqliteConnection, + ) -> Result, OutputManagerStorageError> { + Ok(outputs::table + .filter(outputs::marked_deleted_at_height.is_not_null()) + .order(outputs::marked_deleted_at_height.desc()) + .first(conn) + .optional()?) + } + + /// Find a particular Output, if it exists + pub fn find(spending_key: &[u8], conn: &SqliteConnection) -> Result { + Ok(outputs::table + .filter(outputs::spending_key.eq(spending_key)) + .first::(conn)?) + } + + /// Return the available, time locked, pending incoming and pending outgoing balance + pub fn get_balance( + current_tip_for_time_lock_calculation: Option, + conn: &SqliteConnection, + ) -> Result { + #[derive(QueryableByName, Clone)] + struct BalanceQueryResult { + #[sql_type = "diesel::sql_types::BigInt"] + amount: i64, + #[sql_type = "diesel::sql_types::Text"] + category: String, + } + let balance_query_result = if let Some(current_tip) = current_tip_for_time_lock_calculation { + let balance_query = sql_query( + "SELECT coalesce(sum(value), 0) as amount, 'available_balance' as category \ + FROM outputs WHERE status = ? \ + UNION ALL \ + SELECT coalesce(sum(value), 0) as amount, 'time_locked_balance' as category \ + FROM outputs WHERE status = ? AND maturity > ? \ + UNION ALL \ + SELECT coalesce(sum(value), 0) as amount, 'pending_incoming_balance' as category \ + FROM outputs WHERE status = ? OR status = ? OR status = ? \ + UNION ALL \ + SELECT coalesce(sum(value), 0) as amount, 'pending_outgoing_balance' as category \ + FROM outputs WHERE status = ? OR status = ? OR status = ?", + ) + // available_balance + .bind::(OutputStatus::Unspent as i32) + // time_locked_balance + .bind::(OutputStatus::Unspent as i32) + .bind::(current_tip as i64) + // pending_incoming_balance + .bind::(OutputStatus::EncumberedToBeReceived as i32) + .bind::(OutputStatus::ShortTermEncumberedToBeReceived as i32) + .bind::(OutputStatus::UnspentMinedUnconfirmed as i32) + // pending_outgoing_balance + .bind::(OutputStatus::EncumberedToBeSpent as i32) + .bind::(OutputStatus::ShortTermEncumberedToBeSpent as i32) + .bind::(OutputStatus::SpentMinedUnconfirmed as i32); + balance_query.load::(conn)? + } else { + let balance_query = sql_query( + "SELECT coalesce(sum(value), 0) as amount, 'available_balance' as category \ + FROM outputs WHERE status = ? \ + UNION ALL \ + SELECT coalesce(sum(value), 0) as amount, 'pending_incoming_balance' as category \ + FROM outputs WHERE status = ? OR status = ? OR status = ? \ + UNION ALL \ + SELECT coalesce(sum(value), 0) as amount, 'pending_outgoing_balance' as category \ + FROM outputs WHERE status = ? OR status = ? OR status = ?", + ) + // available_balance + .bind::(OutputStatus::Unspent as i32) + // pending_incoming_balance + .bind::(OutputStatus::EncumberedToBeReceived as i32) + .bind::(OutputStatus::ShortTermEncumberedToBeReceived as i32) + .bind::(OutputStatus::UnspentMinedUnconfirmed as i32) + // pending_outgoing_balance + .bind::(OutputStatus::EncumberedToBeSpent as i32) + .bind::(OutputStatus::ShortTermEncumberedToBeSpent as i32) + .bind::(OutputStatus::SpentMinedUnconfirmed as i32); + balance_query.load::(conn)? + }; + let mut available_balance = None; + let mut time_locked_balance = Some(None); + let mut pending_incoming_balance = None; + let mut pending_outgoing_balance = None; + for balance in balance_query_result.clone() { + match balance.category.as_str() { + "available_balance" => available_balance = Some(MicroTari::from(balance.amount as u64)), + "time_locked_balance" => time_locked_balance = Some(Some(MicroTari::from(balance.amount as u64))), + "pending_incoming_balance" => pending_incoming_balance = Some(MicroTari::from(balance.amount as u64)), + "pending_outgoing_balance" => pending_outgoing_balance = Some(MicroTari::from(balance.amount as u64)), + _ => { + return Err(OutputManagerStorageError::UnexpectedResult( + "Unexpected category in balance query".to_string(), + )) + }, + } + } + + Ok(Balance { + available_balance: available_balance.ok_or_else(|| { + OutputManagerStorageError::UnexpectedResult("Available balance could not be calculated".to_string()) + })?, + time_locked_balance: time_locked_balance.ok_or_else(|| { + OutputManagerStorageError::UnexpectedResult("Time locked balance could not be calculated".to_string()) + })?, + pending_incoming_balance: pending_incoming_balance.ok_or_else(|| { + OutputManagerStorageError::UnexpectedResult( + "Pending incoming balance could not be calculated".to_string(), + ) + })?, + pending_outgoing_balance: pending_outgoing_balance.ok_or_else(|| { + OutputManagerStorageError::UnexpectedResult( + "Pending outgoing balance could not be calculated".to_string(), + ) + })?, + }) + } + + pub fn find_by_commitment( + commitment: &[u8], + conn: &SqliteConnection, + ) -> Result { + Ok(outputs::table + .filter(outputs::commitment.eq(commitment)) + .first::(conn)?) + } + + pub fn find_by_commitment_and_cancelled( + commitment: &[u8], + cancelled: bool, + conn: &SqliteConnection, + ) -> Result { + let cancelled_flag = OutputStatus::CancelledInbound as i32; + + let mut request = outputs::table.filter(outputs::commitment.eq(commitment)).into_boxed(); + if cancelled { + request = request.filter(outputs::status.eq(cancelled_flag)) + } else { + request = request.filter(outputs::status.ne(cancelled_flag)) + }; + + Ok(request.first::(conn)?) + } + + pub fn find_by_tx_id_and_status( + tx_id: TxId, + status: OutputStatus, + conn: &SqliteConnection, + ) -> Result, OutputManagerStorageError> { + Ok(outputs::table + .filter( + outputs::received_in_tx_id + .eq(Some(tx_id as i64)) + .or(outputs::spent_in_tx_id.eq(Some(tx_id as i64))), + ) + .filter(outputs::status.eq(status as i32)) + .load(conn)?) + } + + /// Find outputs via tx_id that are encumbered. Any outputs that are encumbered cannot be marked as spent. + pub fn find_by_tx_id_and_encumbered( + tx_id: TxId, + conn: &SqliteConnection, + ) -> Result, OutputManagerStorageError> { + Ok(outputs::table + .filter( + outputs::received_in_tx_id + .eq(Some(tx_id as i64)) + .or(outputs::spent_in_tx_id.eq(Some(tx_id as i64))), + ) + .filter( + outputs::status + .eq(OutputStatus::EncumberedToBeReceived as i32) + .or(outputs::status.eq(OutputStatus::EncumberedToBeSpent as i32)) + .or(outputs::status.eq(OutputStatus::ShortTermEncumberedToBeReceived as i32)) + .or(outputs::status.eq(OutputStatus::ShortTermEncumberedToBeSpent as i32)), + ) + .load(conn)?) + } + + /// Find a particular Output, if it exists and is in the specified Spent state + pub fn find_status( + spending_key: &[u8], + status: OutputStatus, + conn: &SqliteConnection, + ) -> Result { + Ok(outputs::table + .filter(outputs::status.eq(status as i32)) + .filter(outputs::spending_key.eq(spending_key)) + .first::(conn)?) + } + + /// Find a particular Output, if it exists and is in the specified Spent state + pub fn find_pending_coinbase_at_block_height( + block_height: u64, + conn: &SqliteConnection, + ) -> Result { + Ok(outputs::table + .filter(outputs::status.ne(OutputStatus::Unspent as i32)) + .filter(outputs::coinbase_block_height.eq(block_height as i64)) + .first::(conn)?) + } + + pub fn delete(&self, conn: &SqliteConnection) -> Result<(), OutputManagerStorageError> { + let num_deleted = + diesel::delete(outputs::table.filter(outputs::spending_key.eq(&self.spending_key))).execute(conn)?; + + if num_deleted == 0 { + return Err(OutputManagerStorageError::ValuesNotFound); + } + + Ok(()) + } + + pub fn update( + &self, + updated_output: UpdateOutput, + conn: &SqliteConnection, + ) -> Result { + diesel::update(outputs::table.filter(outputs::id.eq(&self.id))) + .set(UpdateOutputSql::from(updated_output)) + .execute(conn) + .num_rows_affected_or_not_found(1)?; + + OutputSql::find(&self.spending_key, conn) + } + + /// Update the changed fields of this record after encryption/decryption is performed + pub fn update_encryption(&self, conn: &SqliteConnection) -> Result<(), OutputManagerStorageError> { + let _ = self.update( + UpdateOutput { + spending_key: Some(self.spending_key.clone()), + script_private_key: Some(self.script_private_key.clone()), + ..Default::default() + }, + conn, + )?; + Ok(()) + } +} + +/// Conversion from an DbUnblindedOutput to the Sql datatype form +impl TryFrom for DbUnblindedOutput { + type Error = OutputManagerStorageError; + + fn try_from(o: OutputSql) -> Result { + let unblinded_output = UnblindedOutput::new( + MicroTari::from(o.value as u64), + PrivateKey::from_vec(&o.spending_key).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create PrivateKey from stored bytes, They might be encrypted" + ); + OutputManagerStorageError::ConversionError + })?, + OutputFeatures { + flags: OutputFlags::from_bits(o.flags as u8).ok_or(OutputManagerStorageError::ConversionError)?, + maturity: o.maturity as u64, + }, + TariScript::from_bytes(o.script.as_slice())?, + ExecutionStack::from_bytes(o.input_data.as_slice())?, + PrivateKey::from_vec(&o.script_private_key).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create PrivateKey from stored bytes, They might be encrypted" + ); + OutputManagerStorageError::ConversionError + })?, + PublicKey::from_vec(&o.sender_offset_public_key).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create PublicKey from stored bytes, They might be encrypted" + ); + OutputManagerStorageError::ConversionError + })?, + ComSignature::new( + Commitment::from_vec(&o.metadata_signature_nonce).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create PublicKey from stored bytes, They might be encrypted" + ); + OutputManagerStorageError::ConversionError + })?, + PrivateKey::from_vec(&o.metadata_signature_u_key).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create PrivateKey from stored bytes, They might be encrypted" + ); + OutputManagerStorageError::ConversionError + })?, + PrivateKey::from_vec(&o.metadata_signature_v_key).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create PrivateKey from stored bytes, They might be encrypted" + ); + OutputManagerStorageError::ConversionError + })?, + ), + ); + + let hash = match o.hash { + None => { + let factories = CryptoFactories::default(); + unblinded_output.as_transaction_output(&factories)?.hash() + }, + Some(v) => v, + }; + let commitment = match o.commitment { + None => { + let factories = CryptoFactories::default(); + factories + .commitment + .commit(&unblinded_output.spending_key, &unblinded_output.value.into()) + }, + Some(c) => Commitment::from_vec(&c)?, + }; + + Ok(Self { + commitment, + unblinded_output, + hash, + mined_height: o.mined_height.map(|mh| mh as u64), + mined_in_block: o.mined_in_block, + mined_mmr_position: o.mined_mmr_position.map(|mp| mp as u64), + marked_deleted_at_height: o.marked_deleted_at_height.map(|d| d as u64), + marked_deleted_in_block: o.marked_deleted_in_block, + }) + } +} + +impl Encryptable for OutputSql { + fn encrypt(&mut self, cipher: &Aes256Gcm) -> Result<(), String> { + self.spending_key = encrypt_bytes_integral_nonce(cipher, self.spending_key.clone())?; + self.script_private_key = encrypt_bytes_integral_nonce(cipher, self.script_private_key.clone())?; + Ok(()) + } + + fn decrypt(&mut self, cipher: &Aes256Gcm) -> Result<(), String> { + self.spending_key = decrypt_bytes_integral_nonce(cipher, self.spending_key.clone())?; + self.script_private_key = decrypt_bytes_integral_nonce(cipher, self.script_private_key.clone())?; + Ok(()) + } +} + +impl PartialEq for OutputSql { + fn eq(&self, other: &NewOutputSql) -> bool { + &NewOutputSql::from(self.clone()) == other + } +} From 9f8e2899922c0eec9167ed4f49c5d4c161330221 Mon Sep 17 00:00:00 2001 From: Cayle Sharrock Date: Wed, 10 Nov 2021 14:07:59 +0000 Subject: [PATCH 12/46] feat: add support for MultiAddr in RPC config (#3557) Motivation and Context --- Robust networking support in docker configurations requires us to be able to specify the gRPC address of things like the node and wallet in terms of domains instead of raw IP addresses. This adds support for MultiAddr specification of these addresses while still being 100% backwards compatible. Also included in #3534 How Has This Been Tested? --- No new tests added. --- Cargo.lock | 3 ++ applications/tari_base_node/src/main.rs | 10 ++++- .../tari_console_wallet/src/wallet_modes.rs | 12 +++--- .../tari_merge_mining_proxy/Cargo.toml | 1 + .../tari_merge_mining_proxy/src/main.rs | 4 +- .../tari_merge_mining_proxy/src/proxy.rs | 18 +++++--- applications/tari_mining_node/Cargo.toml | 1 + applications/tari_mining_node/src/config.rs | 19 ++++----- applications/tari_mining_node/src/errors.rs | 2 + applications/tari_mining_node/src/main.rs | 8 ++-- .../tari_stratum_transcoder/Cargo.toml | 1 + .../tari_stratum_transcoder/src/main.rs | 4 +- .../tari_stratum_transcoder/src/proxy.rs | 17 +++++--- common/src/configuration/global.rs | 41 +++++++++++-------- 14 files changed, 84 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7250677e8b..26d72745ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4748,6 +4748,7 @@ dependencies = [ "tari_app_grpc", "tari_app_utilities", "tari_common", + "tari_comms", "tari_core", "tari_crypto", "tari_utilities", @@ -4782,6 +4783,7 @@ dependencies = [ "tari_app_grpc", "tari_app_utilities", "tari_common", + "tari_comms", "tari_core", "tari_crypto", "thiserror", @@ -4936,6 +4938,7 @@ dependencies = [ "structopt", "tari_app_grpc", "tari_common", + "tari_comms", "tari_core", "tari_crypto", "tari_utilities", diff --git a/applications/tari_base_node/src/main.rs b/applications/tari_base_node/src/main.rs index a7561a6f37..c658bf74e5 100644 --- a/applications/tari_base_node/src/main.rs +++ b/applications/tari_base_node/src/main.rs @@ -114,7 +114,11 @@ use tari_app_utilities::{ utilities::setup_runtime, }; use tari_common::{configuration::bootstrap::ApplicationType, exit_codes::ExitCodes, ConfigBootstrap, GlobalConfig}; -use tari_comms::{peer_manager::PeerFeatures, tor::HiddenServiceControllerError}; +use tari_comms::{ + peer_manager::PeerFeatures, + tor::HiddenServiceControllerError, + utils::multiaddr::multiaddr_to_socketaddr, +}; use tari_core::chain_storage::ChainStorageError; use tari_shutdown::{Shutdown, ShutdownSignal}; use tokio::{ @@ -228,7 +232,9 @@ async fn run_node(node_config: Arc, bootstrap: ConfigBootstrap) -> if node_config.grpc_enabled { // Go, GRPC, go go let grpc = crate::grpc::base_node_grpc_server::BaseNodeGrpcServer::from_base_node_context(&ctx); - task::spawn(run_grpc(grpc, node_config.grpc_base_node_address, shutdown.to_signal())); + let socket_addr = multiaddr_to_socketaddr(&node_config.grpc_base_node_address) + .map_err(|e| ExitCodes::ConfigError(e.to_string()))?; + task::spawn(run_grpc(grpc, socket_addr, shutdown.to_signal())); } // Run, node, run! diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index 2193021b02..1caa9c979a 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -30,9 +30,9 @@ use crate::{ }; use log::*; use rand::{rngs::OsRng, seq::SliceRandom}; -use std::{fs, io::Stdout, net::SocketAddr, path::PathBuf}; +use std::{fs, io::Stdout, path::PathBuf}; use tari_common::{exit_codes::ExitCodes, ConfigBootstrap, GlobalConfig}; -use tari_comms::peer_manager::Peer; +use tari_comms::{multiaddr::Multiaddr, peer_manager::Peer, utils::multiaddr::multiaddr_to_socketaddr}; use tari_wallet::WalletSqlite; use tokio::runtime::Handle; use tonic::transport::Server; @@ -213,7 +213,7 @@ pub fn tui_mode(config: WalletModeConfig, mut wallet: WalletSqlite) -> Result<() .. } = config; let grpc = WalletGrpcServer::new(wallet.clone()); - handle.spawn(run_grpc(grpc, global_config.grpc_console_wallet_address)); + handle.spawn(run_grpc(grpc, global_config.grpc_console_wallet_address.clone())); let notifier = Notifier::new(notify_script, handle.clone(), wallet.clone()); @@ -294,12 +294,12 @@ pub fn grpc_mode(config: WalletModeConfig, wallet: WalletSqlite) -> Result<(), E Ok(()) } -async fn run_grpc(grpc: WalletGrpcServer, grpc_console_wallet_address: SocketAddr) -> Result<(), String> { +async fn run_grpc(grpc: WalletGrpcServer, grpc_console_wallet_address: Multiaddr) -> Result<(), String> { info!(target: LOG_TARGET, "Starting GRPC on {}", grpc_console_wallet_address); - + let socket = multiaddr_to_socketaddr(&grpc_console_wallet_address).map_err(|e| e.to_string())?; Server::builder() .add_service(tari_app_grpc::tari_rpc::wallet_server::WalletServer::new(grpc)) - .serve(grpc_console_wallet_address) + .serve(socket) .await .map_err(|e| format!("GRPC server returned error:{}", e))?; info!(target: LOG_TARGET, "Stopping GRPC"); diff --git a/applications/tari_merge_mining_proxy/Cargo.toml b/applications/tari_merge_mining_proxy/Cargo.toml index bdcc5dc430..aadc3bf6dc 100644 --- a/applications/tari_merge_mining_proxy/Cargo.toml +++ b/applications/tari_merge_mining_proxy/Cargo.toml @@ -14,6 +14,7 @@ envlog = ["env_logger"] [dependencies] tari_app_grpc = { path = "../tari_app_grpc" } tari_common = { path = "../../common" } +tari_comms = { path = "../../comms" } tari_core = { path = "../../base_layer/core", default-features = false, features = ["transactions"] } tari_app_utilities = { path = "../tari_app_utilities" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } diff --git a/applications/tari_merge_mining_proxy/src/main.rs b/applications/tari_merge_mining_proxy/src/main.rs index 0df27490bb..b58d74d5d7 100644 --- a/applications/tari_merge_mining_proxy/src/main.rs +++ b/applications/tari_merge_mining_proxy/src/main.rs @@ -40,7 +40,7 @@ use crate::{block_template_data::BlockTemplateRepository, error::MmProxyError}; use futures::future; use hyper::{service::make_service_fn, Server}; use proxy::{MergeMiningProxyConfig, MergeMiningProxyService}; -use std::convert::Infallible; +use std::convert::{Infallible, TryFrom}; use tari_app_grpc::tari_rpc as grpc; use tari_app_utilities::initialization::init_configuration; use tari_common::configuration::bootstrap::ApplicationType; @@ -50,7 +50,7 @@ use tokio::time::Duration; async fn main() -> Result<(), anyhow::Error> { let (_, config, _) = init_configuration(ApplicationType::MergeMiningProxy)?; - let config = MergeMiningProxyConfig::from(config); + let config = MergeMiningProxyConfig::try_from(config)?; let addr = config.proxy_host_address; let client = reqwest::Client::builder() .connect_timeout(Duration::from_secs(5)) diff --git a/applications/tari_merge_mining_proxy/src/proxy.rs b/applications/tari_merge_mining_proxy/src/proxy.rs index ff31d8e8f9..f3a9ad5a96 100644 --- a/applications/tari_merge_mining_proxy/src/proxy.rs +++ b/applications/tari_merge_mining_proxy/src/proxy.rs @@ -34,6 +34,7 @@ use reqwest::{ResponseBuilderExt, Url}; use serde_json as json; use std::{ cmp, + convert::TryFrom, future::Future, net::SocketAddr, pin::Pin, @@ -46,6 +47,7 @@ use std::{ }; use tari_app_grpc::tari_rpc as grpc; use tari_common::{configuration::Network, GlobalConfig}; +use tari_comms::utils::multiaddr::multiaddr_to_socketaddr; use tari_core::proof_of_work::{monero_rx, monero_rx::FixedByteArray}; use tari_utilities::hex::Hex; use tracing::{debug, error, info, instrument, trace, warn}; @@ -70,20 +72,24 @@ pub struct MergeMiningProxyConfig { pub wait_for_initial_sync_at_startup: bool, } -impl From for MergeMiningProxyConfig { - fn from(config: GlobalConfig) -> Self { - Self { +impl TryFrom for MergeMiningProxyConfig { + type Error = std::io::Error; + + fn try_from(config: GlobalConfig) -> Result { + let grpc_base_node_address = multiaddr_to_socketaddr(&config.grpc_base_node_address)?; + let grpc_console_wallet_address = multiaddr_to_socketaddr(&config.grpc_console_wallet_address)?; + Ok(Self { network: config.network, monerod_url: config.monerod_url, monerod_username: config.monerod_username, monerod_password: config.monerod_password, monerod_use_auth: config.monerod_use_auth, - grpc_base_node_address: config.grpc_base_node_address, - grpc_console_wallet_address: config.grpc_console_wallet_address, + grpc_base_node_address, + grpc_console_wallet_address, proxy_host_address: config.proxy_host_address, proxy_submit_to_origin: config.proxy_submit_to_origin, wait_for_initial_sync_at_startup: config.wait_for_initial_sync_at_startup, - } + }) } } diff --git a/applications/tari_mining_node/Cargo.toml b/applications/tari_mining_node/Cargo.toml index e8431f0540..5ff573f720 100644 --- a/applications/tari_mining_node/Cargo.toml +++ b/applications/tari_mining_node/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" [dependencies] tari_core = { path = "../../base_layer/core", default-features = false } tari_common = { path = "../../common" } +tari_comms = { path = "../../comms" } tari_app_utilities = { path = "../tari_app_utilities"} tari_app_grpc = { path = "../tari_app_grpc" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } diff --git a/applications/tari_mining_node/src/config.rs b/applications/tari_mining_node/src/config.rs index 6b46e8e195..69f618f919 100644 --- a/applications/tari_mining_node/src/config.rs +++ b/applications/tari_mining_node/src/config.rs @@ -40,11 +40,10 @@ use serde::{Deserialize, Serialize}; use std::time::Duration; use tari_app_grpc::tari_rpc::{pow_algo::PowAlgos, NewBlockTemplateRequest, PowAlgo}; use tari_common::{GlobalConfig, NetworkConfigPath}; +use tari_comms::utils::multiaddr::multiaddr_to_socketaddr; #[derive(Serialize, Deserialize, Debug)] pub struct MinerConfig { - pub base_node_grpc_address: Option, - pub wallet_grpc_address: Option, pub num_mining_threads: usize, pub mine_on_tip_only: bool, pub proof_of_work_algo: ProofOfWork, @@ -68,8 +67,6 @@ impl NetworkConfigPath for MinerConfig { impl Default for MinerConfig { fn default() -> Self { Self { - base_node_grpc_address: None, - wallet_grpc_address: None, num_mining_threads: num_cpus::get(), mine_on_tip_only: true, proof_of_work_algo: ProofOfWork::Sha3, @@ -82,16 +79,14 @@ impl Default for MinerConfig { } impl MinerConfig { - pub fn base_node_addr(&self, global: &GlobalConfig) -> String { - self.base_node_grpc_address - .clone() - .unwrap_or_else(|| format!("http://{}", global.grpc_base_node_address)) + pub fn base_node_addr(&self, global: &GlobalConfig) -> Result { + let socket = multiaddr_to_socketaddr(&global.grpc_base_node_address)?; + Ok(format!("http://{}", socket)) } - pub fn wallet_addr(&self, global: &GlobalConfig) -> String { - self.wallet_grpc_address - .clone() - .unwrap_or_else(|| format!("http://{}", global.grpc_console_wallet_address)) + pub fn wallet_addr(&self, global: &GlobalConfig) -> Result { + let socket = multiaddr_to_socketaddr(&global.grpc_console_wallet_address)?; + Ok(format!("http://{}", socket)) } pub fn pow_algo_request(&self) -> NewBlockTemplateRequest { diff --git a/applications/tari_mining_node/src/errors.rs b/applications/tari_mining_node/src/errors.rs index d8aed339d9..9dc4c304da 100644 --- a/applications/tari_mining_node/src/errors.rs +++ b/applications/tari_mining_node/src/errors.rs @@ -24,6 +24,8 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum MinerError { + #[error("I/O error")] + IOError(#[from] std::io::Error), #[error("GRPC error: {0}")] GrpcStatus(#[from] tonic::Status), #[error("Connection error: {0}")] diff --git a/applications/tari_mining_node/src/main.rs b/applications/tari_mining_node/src/main.rs index 568bc67eb9..92ee565015 100644 --- a/applications/tari_mining_node/src/main.rs +++ b/applications/tari_mining_node/src/main.rs @@ -223,12 +223,12 @@ async fn connect( config: &MinerConfig, global: &GlobalConfig, ) -> Result<(BaseNodeClient, WalletClient), MinerError> { - let base_node_addr = config.base_node_addr(global); + let base_node_addr = config.base_node_addr(global)?; info!(target: LOG_TARGET, "Connecting to base node at {}", base_node_addr); - let node_conn = BaseNodeClient::connect(base_node_addr.clone()).await?; - let wallet_addr = config.wallet_addr(global); + let node_conn = BaseNodeClient::connect(base_node_addr).await?; + let wallet_addr = config.wallet_addr(global)?; info!(target: LOG_TARGET, "Connecting to wallet at {}", wallet_addr); - let wallet_conn = WalletClient::connect(wallet_addr.clone()).await?; + let wallet_conn = WalletClient::connect(wallet_addr).await?; Ok((node_conn, wallet_conn)) } diff --git a/applications/tari_stratum_transcoder/Cargo.toml b/applications/tari_stratum_transcoder/Cargo.toml index f02b8c5ba0..dc25e5d987 100644 --- a/applications/tari_stratum_transcoder/Cargo.toml +++ b/applications/tari_stratum_transcoder/Cargo.toml @@ -12,6 +12,7 @@ default = [] envlog = ["env_logger"] [dependencies] +tari_comms = { path = "../../comms" } tari_app_grpc = { path = "../tari_app_grpc" } tari_common = { path = "../../common" } tari_core = { path = "../../base_layer/core", default-features = false, features = ["transactions"] } diff --git a/applications/tari_stratum_transcoder/src/main.rs b/applications/tari_stratum_transcoder/src/main.rs index f742c92d6d..61e0da2d88 100644 --- a/applications/tari_stratum_transcoder/src/main.rs +++ b/applications/tari_stratum_transcoder/src/main.rs @@ -35,7 +35,7 @@ use crate::error::StratumTranscoderProxyError; use futures::future; use hyper::{service::make_service_fn, Server}; use proxy::{StratumTranscoderProxyConfig, StratumTranscoderProxyService}; -use std::convert::Infallible; +use std::convert::{Infallible, TryFrom}; use structopt::StructOpt; use tari_app_grpc::tari_rpc as grpc; use tari_common::{configuration::bootstrap::ApplicationType, ConfigBootstrap, GlobalConfig}; @@ -45,7 +45,7 @@ use tokio::time::Duration; async fn main() -> Result<(), StratumTranscoderProxyError> { let config = initialize()?; - let config = StratumTranscoderProxyConfig::from(config); + let config = StratumTranscoderProxyConfig::try_from(config)?; let addr = config.transcoder_host_address; let client = reqwest::Client::builder() .connect_timeout(Duration::from_secs(5)) diff --git a/applications/tari_stratum_transcoder/src/proxy.rs b/applications/tari_stratum_transcoder/src/proxy.rs index eac834d6e0..c3ce03ee56 100644 --- a/applications/tari_stratum_transcoder/src/proxy.rs +++ b/applications/tari_stratum_transcoder/src/proxy.rs @@ -44,6 +44,7 @@ use std::{ }; use tari_app_grpc::{tari_rpc as grpc, tari_rpc::GetCoinbaseRequest}; use tari_common::{configuration::Network, GlobalConfig}; +use tari_comms::utils::multiaddr::multiaddr_to_socketaddr; use tari_core::blocks::{Block, NewBlockTemplate}; use tari_utilities::{hex::Hex, message_format::MessageFormat}; use tracing::{debug, error}; @@ -58,14 +59,18 @@ pub struct StratumTranscoderProxyConfig { pub transcoder_host_address: SocketAddr, } -impl From for StratumTranscoderProxyConfig { - fn from(config: GlobalConfig) -> Self { - Self { +impl TryFrom for StratumTranscoderProxyConfig { + type Error = std::io::Error; + + fn try_from(config: GlobalConfig) -> Result { + let grpc_base_node_address = multiaddr_to_socketaddr(&config.grpc_base_node_address)?; + let grpc_console_wallet_address = multiaddr_to_socketaddr(&config.grpc_console_wallet_address)?; + Ok(Self { network: config.network, - grpc_base_node_address: config.grpc_base_node_address, - grpc_console_wallet_address: config.grpc_console_wallet_address, + grpc_base_node_address, + grpc_console_wallet_address, transcoder_host_address: config.transcoder_host_address, - } + }) } } diff --git a/common/src/configuration/global.rs b/common/src/configuration/global.rs index 958c1d40f1..b35004b990 100644 --- a/common/src/configuration/global.rs +++ b/common/src/configuration/global.rs @@ -27,7 +27,6 @@ use crate::{ ConfigurationError, }; use config::{Config, ConfigError, Environment}; -use multiaddr::Multiaddr; use std::{ convert::TryInto, fmt, @@ -35,9 +34,13 @@ use std::{ net::SocketAddr, num::{NonZeroU16, TryFromIntError}, path::PathBuf, + prelude::rust_2021::FromIterator, str::FromStr, time::Duration, }; + +use multiaddr::{Error, Multiaddr, Protocol}; + use tari_storage::lmdb_store::LMDBConfig; const DB_INIT_DEFAULT_MB: usize = 1000; @@ -74,8 +77,8 @@ pub struct GlobalConfig { pub base_node_identity_file: PathBuf, pub public_address: Multiaddr, pub grpc_enabled: bool, - pub grpc_base_node_address: SocketAddr, - pub grpc_console_wallet_address: SocketAddr, + pub grpc_base_node_address: Multiaddr, + pub grpc_console_wallet_address: Multiaddr, pub peer_seeds: Vec, pub dns_seeds: Vec, pub dns_seeds_name_server: DnsNameServer, @@ -192,7 +195,7 @@ fn convert_node_config( return Err(ConfigurationError::new( &key, &format!("DB initial size must be at least {} MB.", DB_INIT_MIN_MB), - )) + )); }, Ok(mb) => mb as usize, Err(e) => match e { @@ -207,7 +210,7 @@ fn convert_node_config( return Err(ConfigurationError::new( &key, &format!("DB grow size must be at least {} MB.", DB_GROW_SIZE_MIN_MB), - )) + )); }, Ok(mb) => mb as usize, Err(e) => match e { @@ -225,19 +228,19 @@ fn convert_node_config( "DB resize threshold must be at least {} MB.", DB_RESIZE_THRESHOLD_MIN_MB ), - )) + )); }, Ok(mb) if mb as usize >= grow_size_mb => { return Err(ConfigurationError::new( &key, "DB resize threshold must be less than grow size.", - )) + )); }, Ok(mb) if mb as usize >= init_size_mb => { return Err(ConfigurationError::new( &key, "DB resize threshold must be less than init size.", - )) + )); }, Ok(mb) => mb as usize, Err(e) => match e { @@ -341,20 +344,13 @@ fn convert_node_config( let key = config_string("base_node", net_str, "grpc_base_node_address"); let grpc_base_node_address = cfg .get_str(&key) - .map_err(|e| ConfigurationError::new(&key, &e.to_string())) - .and_then(|addr| { - addr.parse::() - .map_err(|e| ConfigurationError::new(&key, &e.to_string())) - })?; + .map(|addr| socket_or_multi(&addr).map_err(|e| ConfigurationError::new(&key, &e.to_string())))??; let key = config_string("base_node", net_str, "grpc_console_wallet_address"); let grpc_console_wallet_address = cfg .get_str(&key) .map_err(|e| ConfigurationError::new(&key, &e.to_string())) - .and_then(|addr| { - addr.parse::() - .map_err(|e| ConfigurationError::new(&key, &e.to_string())) - })?; + .map(|addr| socket_or_multi(&addr).map_err(|e| ConfigurationError::new(&key, &e.to_string())))??; // Peer and DNS seeds let key = "common.peer_seeds"; @@ -976,6 +972,17 @@ fn parse_key_value(s: &str, split_chr: char) -> (String, Option<&str>) { ) } +/// Interpret a string as either a socket address (first) or a multiaddr format string. +/// If the former, it gets converted into a MultiAddr before being returned. +pub fn socket_or_multi(addr: &str) -> Result { + addr.parse::() + .map(|socket| match socket { + SocketAddr::V4(ip4) => Multiaddr::from_iter([Protocol::Ip4(*ip4.ip()), Protocol::Tcp(ip4.port())]), + SocketAddr::V6(ip6) => Multiaddr::from_iter([Protocol::Ip6(*ip6.ip()), Protocol::Tcp(ip6.port())]), + }) + .or_else(|_| addr.parse::()) +} + impl FromStr for TorControlAuthentication { type Err = String; From e0f21876278702aa43096b04aa9e701f0942be67 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Thu, 11 Nov 2021 13:45:55 +0200 Subject: [PATCH 13/46] fix: stop leak of value of recovered output (#3558) Description --- Updated the recovery of an output to not reuse the blinding factor to use as thescript_key, but rather generate a new key. Motivation and Context --- If the blinding factor is reused as the script_key, on spending you will reveal k.G, this makes guessing v.H trivial and this leaks the k.G value. How Has This Been Tested? --- --- .../recovery/standard_outputs_recoverer.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index f3386a30ea..33838c0d22 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -23,13 +23,17 @@ use std::sync::Arc; use log::*; -use tari_crypto::{inputs, keys::PublicKey as PublicKeyTrait, tari_utilities::hex::Hex}; - -use tari_common_types::types::PublicKey; +use rand::rngs::OsRng; +use tari_common_types::types::{PrivateKey, PublicKey}; use tari_core::transactions::{ transaction::{TransactionOutput, UnblindedOutput}, CryptoFactories, }; +use tari_crypto::{ + inputs, + keys::{PublicKey as PublicKeyTrait, SecretKey}, + tari_utilities::hex::Hex, +}; use crate::output_manager_service::{ error::{OutputManagerError, OutputManagerStorageError}, @@ -91,13 +95,17 @@ where TBackend: OutputManagerBackend + 'static }) .map( |(output, features, script, sender_offset_public_key, metadata_signature)| { + // Todo we need to look here that we might want to fail a specific output and not recover it as this + // will only work if the script is a Nop script. If this is not a Nop script the recovered input + // will not be spendable. + let script_key = PrivateKey::random(&mut OsRng); UnblindedOutput::new( output.committed_value, output.blinding_factor.clone(), features, script, - inputs!(PublicKey::from_secret_key(&output.blinding_factor)), - output.blinding_factor, + inputs!(PublicKey::from_secret_key(&script_key)), + script_key, sender_offset_public_key, metadata_signature, ) From 96b33174b492be7aeecd7ae0dd4705b9391971e6 Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Thu, 11 Nov 2021 14:33:39 +0200 Subject: [PATCH 14/46] ci: add workflow dispatch to libwallet build action (#3556) Co-authored-by: mergequeue[bot] <48659329+mergequeue[bot]@users.noreply.github.com> --- .github/workflows/libwallet.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/libwallet.yml b/.github/workflows/libwallet.yml index a9c92f523d..02cf3fe03a 100644 --- a/.github/workflows/libwallet.yml +++ b/.github/workflows/libwallet.yml @@ -1,20 +1,16 @@ # Build a new set of libraries when a new tag containing 'libwallet' is pushed name: Build libwallet + on: push: branches: - "libwallet-*" - # - development tags: - "libwallet-*" - schedule: - - cron: "05 00 * * *" - workflow_dispatch: - inputs: - customTag: - description: "Development Tag" - required: true - default: "development-tag" + schedule: + - cron: "05 00 * * *" + workflow_dispatch: + jobs: android: runs-on: ubuntu-latest From ec677987a712c934168040da07f31fc744f66f71 Mon Sep 17 00:00:00 2001 From: Cayle Sharrock Date: Thu, 11 Nov 2021 13:07:24 +0000 Subject: [PATCH 15/46] feat: one-click installer - cli edition (#3534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Tari Launchpad - CLI edition # Code changes - Updated configuration for gRPC endpoints to accept MultAddr format in addition to SocketAddr. Changes should be 100% backwards compatible. a.k.a. _Tari one-click miner_. Currently this only works on MacOS and Linux. Contributions for `quick_start.bat` are welcomed. ## Prerequisites 1. [Docker](https://docs.docker.com/get-docker/) 2. Set up some environment variables: - `DATA_FOLDER` (required). The path to store the Tari configuration files and logs. You can create multiple network setups, each in their own sandbox. - `TARI_NETWORK` (optional). Default=weatherwax. Specify the Tari network to connect to. - `START_TOR` (optional). Default=1. Whether we should start a Tor instance - `START_BASE_NODE` (optional). Default=1. Whether we should start a base node instance - `START_WALLET` (optional). Default=1. Whether we should start a wallet instance (GRPC only) - `START_MINER` (optional). Default=1. Whether we should fire up a SHA_3 solo miner. - `USE_OWN_MODEROD` (optional). Default=0. Whether we should start and sync our own Monero node. - `START_MONERO_MM` (optional). Default=1. Whether we should start merge-mining on Monero. - `TARI_WALLET_PASSWORD` (optional). Highly recommended. Default: tari. The password for your Tari wallet. - `TARI_WALLET__WEATHERWAX__TOR_CONTROL_AUTH` (optional). Default: tari. The password for the Tor control auth. - `TARI_MONERO_WALLET_ADDRESS` (optional). Though you'll donate funds to some random address if you don't set it. ## Quick Start `./quick-start.sh` And you should be golden. ## Interacting with the wallet and base node. Any Tari client that supports gRPC can be connected to the node setup. With the default configuration, the base node will be at `http://127.0.0.1:18142` and the wallet will be at `http://127.0.0.1:18143`. [Kreya](https://kreya.app/) is a fairly user-friendly gRPC client that can give you a rudimentary UI to the docker rig out of the box: #### Getting block information ![node_blocks](https://user-images.githubusercontent.com/7573551/140189524-f5557221-4811-4fcf-a28e-0302988d2998.jpg) #### Wallet - whoami ![wallet_id](https://user-images.githubusercontent.com/7573551/140189544-4259e837-b7d4-4b14-9322-259556332e94.jpg) #### Wallet - send transactions ![wallet_send](https://user-images.githubusercontent.com/7573551/140189563-3e5d951a-d6e7-4a69-9bed-d0ed783fae1c.jpg) ## Viewing logs and configuration files Logs and configuration files are stored in `{DATA_FOLDER}/{app}/log/*.log`. So assuming your data folder is `/tmp/tari1/, the generated file structure looks like: ```text /tmp/tari1$ ll -R drwxr-xr-x user user 128 B Wed Nov 3 15:56:53 2021  base_node/ drwxr-xr-x user user 96 B Wed Nov 3 15:56:53 2021  config/ drwxr-xr-x user user 128 B Mon Nov 1 12:55:50 2021  mm_proxy/ drwxr-xr-x user user 128 B Mon Nov 1 12:55:58 2021  sha3_miner/ drwxr-xr-x user user 64 B Wed Nov 3 15:56:53 2021  tor/ drwxr-xr-x user user 192 B Mon Nov 1 12:56:17 2021  wallet/ drwxr-xr-x user user 96 B Mon Nov 1 12:20:31 2021  xmrig/ ./base_node: drwxr-xr-x user user 128 B Fri Oct 29 17:44:56 2021  config/ drwxr-xr-x user user 320 B Wed Nov 3 15:40:55 2021  log/ ./base_node/config: .rw-r--r-- user user 312 B Mon Nov 1 20:09:38 2021  base_node_id.json .rw-r--r-- user user 211 B Mon Nov 1 20:09:38 2021  base_node_tor.json ./base_node/log: .rw-r--r-- user user 3.3 MB Wed Nov 3 15:54:20 2021  core.log .rw-r--r-- user user 1 MB Wed Nov 3 15:53:10 2021  network.log .rw-r--r-- user user 0 B Mon Nov 1 13:01:33 2021  other.log ./config: .rw-r--r-- user user 4.1 KB Mon Nov 1 20:12:10 2021  log4rs.yml ./mm_proxy: drwxr-xr-x user user 128 B Mon Nov 1 12:53:33 2021  config/ drwxr-xr-x user user 352 B Wed Nov 3 14:10:04 2021  log/ ./mm_proxy/config: .rw-r--r-- user user 8.7 KB Mon Nov 1 12:53:33 2021  config.toml .rw-r--r-- user user 1.1 KB Mon Nov 1 12:53:33 2021  log4rs.yml ./mm_proxy/log: .rw-r--r-- user user 8.4 MB Wed Nov 3 15:53:53 2021  core.log .rw-r--r-- user user 0 B Mon Nov 1 13:01:38 2021  network.log .rw-r--r-- user user 0 B Mon Nov 1 13:01:38 2021  other.log ./sha3_miner: drwxr-xr-x user user 192 B Mon Nov 1 13:01:36 2021  log/ ./sha3_miner/log: .rw-r--r-- user user 677.7 KB Wed Nov 3 15:49:31 2021  core.log .rw-r--r-- user user 0 B Mon Nov 1 13:01:36 2021  network.log .rw-r--r-- user user 0 B Mon Nov 1 13:01:36 2021  other.log ./wallet: drwxr-xr-x user user 192 B Mon Nov 1 12:56:17 2021  ./ drwxr-xr-x user user 288 B Wed Nov 3 15:56:53 2021  ../ drwxr-xr-x user user 256 B Wed Nov 3 15:23:19 2021  log/ drwxr-xr-x user user 128 B Wed Nov 3 15:54:10 2021  weatherwax/ ./wallet/log: .rw-r--r-- user user 564.2 KB Wed Nov 3 15:54:20 2021  core.log .rw-r--r-- user user 280.3 KB Wed Nov 3 15:54:08 2021  network.log .rw-r--r-- user user 0 B Mon Nov 1 13:01:24 2021  other.log ./xmrig: drwxr-xr-x user user 96 B Mon Nov 1 12:20:31 2021  ./ drwxr-xr-x user user 288 B Wed Nov 3 15:56:53 2021  ../ .rw-r--r-- user user 212.9 KB Wed Nov 3 15:53:53 2021  xmrig.log ``` You can edit the network configuration (requires a container restart) and log configuration (takes effect within 30s) by editing the files in the top-level `config` folder. ## Current shortcomings and TODOs * It's a CLI only. Next step is to build a nice UI wrapper around this. * TODO - Windows support. * The blockchain data is stores in docker volumes, and not on the host machine directly. This is due to crippling performance limitations one suffers when mounting host file system from Windows or MacOS into docker containers. This isn't a big drawback, since you seldom want or need to access the raw blockchain database files anyway. They're [still accessible](#accessing-blockchain-data). But **ensure that you reserve enough space to store the Tari, and optionally, Monero blockchains inside the Docker VM**. * TODO - Multi-network sandboxing. Sandboxing currently doesn't work across different networks (e.g. weatherwax and mainnet). This should be fixed soon. * Local MoneroD support. The merge-miner proxy doesn't actually use the local MoneroD container if it is running. This is left as a TODO for the UI version. * Tor control password. For now, you set this manually. In the UI edition, this will be ephemeral and configured automatically. * GRPC addresses. The code only supports IPv4 addresses as gRPC configuration parameters. This makes the docker networking fragile since the IP addresses must be guessed. TODO - accept DNS names in gRPC configs to make this more robust. ### Accessing blockchain data The blockchain data is stored in a docker volume for performance reasons. If you need to back up or access the LMDB a blockchain data, you can use something like this to extract it to the host filesystem: `docker run --rm -v $(pwd):/backup -v blockchain:/blockchain ubuntu tar czvf /backup/backup.tar.gz /blockchain` --- .dockerignore | 4 + Cargo.lock | 7 + Cargo.toml | 1 + applications/deps_only/Cargo.toml | 9 + applications/deps_only/src/main.rs | 8 + applications/launchpad/docker_rig/README.md | 217 ++++++++++++ .../launchpad/docker_rig/base_node.Dockerfile | 61 ++++ applications/launchpad/docker_rig/config.toml | 325 ++++++++++++++++++ .../docker_rig/console_wallet.Dockerfile | 36 +- .../launchpad/docker_rig/docker-compose.yml | 184 ++++++++++ .../launchpad/docker_rig/img/node_blocks.jpg | Bin 0 -> 38176 bytes .../launchpad/docker_rig/img/wallet_id.jpg | Bin 0 -> 26185 bytes .../launchpad/docker_rig/img/wallet_send.jpg | Bin 0 -> 53703 bytes applications/launchpad/docker_rig/log4rs.yml | 162 +++++++++ .../launchpad/docker_rig/mm_proxy.Dockerfile | 51 +++ .../launchpad/docker_rig/monerod.Dockerfile | 35 ++ .../launchpad/docker_rig/push_bundle.sh | 62 ++++ .../launchpad/docker_rig/quick_start.sh | 90 +++++ .../docker_rig/sha3_miner.Dockerfile | 40 +-- .../launchpad/docker_rig/start_tari_app.sh | 38 ++ .../launchpad}/docker_rig/tor.Dockerfile | 5 +- .../launchpad}/docker_rig/torrc | 2 +- .../launchpad/docker_rig/xmrig.Dockerfile | 46 +++ applications/tari_console_wallet/Cargo.toml | 2 +- .../tari_console_wallet/src/wallet_modes.rs | 4 +- .../core/src/chain_storage/lmdb_db/lmdb_db.rs | 4 +- .../tests/output_manager_service/service.rs | 2 +- buildtools/docker_rig/README.md | 70 ---- buildtools/docker_rig/docker-compose.yml | 93 ----- buildtools/docker_rig/start_base_node.sh | 56 --- buildtools/docker_rig/start_wallet.sh | 46 --- common/src/configuration/bootstrap.rs | 6 +- common/src/configuration/global.rs | 1 - integration_tests/features/Reorgs.feature | 8 +- integration_tests/features/support/steps.js | 4 +- integration_tests/features/support/world.js | 3 +- integration_tests/helpers/walletProcess.js | 5 +- integration_tests/package-lock.json | 306 +++++++++-------- integration_tests/package.json | 12 +- 39 files changed, 1533 insertions(+), 472 deletions(-) create mode 100644 applications/deps_only/Cargo.toml create mode 100644 applications/deps_only/src/main.rs create mode 100644 applications/launchpad/docker_rig/README.md create mode 100644 applications/launchpad/docker_rig/base_node.Dockerfile create mode 100644 applications/launchpad/docker_rig/config.toml rename buildtools/docker_rig/base_node.Dockerfile => applications/launchpad/docker_rig/console_wallet.Dockerfile (53%) create mode 100644 applications/launchpad/docker_rig/docker-compose.yml create mode 100644 applications/launchpad/docker_rig/img/node_blocks.jpg create mode 100644 applications/launchpad/docker_rig/img/wallet_id.jpg create mode 100644 applications/launchpad/docker_rig/img/wallet_send.jpg create mode 100644 applications/launchpad/docker_rig/log4rs.yml create mode 100644 applications/launchpad/docker_rig/mm_proxy.Dockerfile create mode 100644 applications/launchpad/docker_rig/monerod.Dockerfile create mode 100755 applications/launchpad/docker_rig/push_bundle.sh create mode 100755 applications/launchpad/docker_rig/quick_start.sh rename buildtools/docker_rig/console_wallet.Dockerfile => applications/launchpad/docker_rig/sha3_miner.Dockerfile (52%) create mode 100755 applications/launchpad/docker_rig/start_tari_app.sh rename {buildtools => applications/launchpad}/docker_rig/tor.Dockerfile (70%) rename {buildtools => applications/launchpad}/docker_rig/torrc (64%) create mode 100644 applications/launchpad/docker_rig/xmrig.Dockerfile delete mode 100644 buildtools/docker_rig/README.md delete mode 100644 buildtools/docker_rig/docker-compose.yml delete mode 100755 buildtools/docker_rig/start_base_node.sh delete mode 100755 buildtools/docker_rig/start_wallet.sh diff --git a/.dockerignore b/.dockerignore index 3d1d7cfbad..a4a771cb62 100644 --- a/.dockerignore +++ b/.dockerignore @@ -40,3 +40,7 @@ base_layer/wallet_ffi/build.config base_layer/wallet_ffi/.cargo/config keys.json + +# Ignore docker files +applications/launchpad/docker_rig/*.Dockerfile +applications/launchpad/docker_rig/docker-compose.yml diff --git a/Cargo.lock b/Cargo.lock index 26d72745ec..b756e0b2ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1122,6 +1122,13 @@ dependencies = [ "thiserror", ] +[[package]] +name = "deps_only" +version = "0.1.0" +dependencies = [ + "log 0.4.14", +] + [[package]] name = "derivative" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index ac268c2585..25f6c96222 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "infrastructure/shutdown", "infrastructure/storage", "infrastructure/test_utils", + "applications/deps_only", # "applications/installer", "applications/tari_base_node", "applications/tari_console_wallet", diff --git a/applications/deps_only/Cargo.toml b/applications/deps_only/Cargo.toml new file mode 100644 index 0000000000..775d00bf2e --- /dev/null +++ b/applications/deps_only/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "deps_only" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4.14" \ No newline at end of file diff --git a/applications/deps_only/src/main.rs b/applications/deps_only/src/main.rs new file mode 100644 index 0000000000..b8b24d892b --- /dev/null +++ b/applications/deps_only/src/main.rs @@ -0,0 +1,8 @@ +//! This is a dummy application that does nothing useful. BUT building it will download and compile all the crates if +//! you run `cargo build --bin deps_only` from the project root. This is very handy for creating a common image in +//! docker that provides a common shared set of compiled libraries, which can be cached and used to build a variety +//! of other app-specific images + +fn main() { + log::info!("Hi Tari. This app does nothing except build all the dependencies"); +} diff --git a/applications/launchpad/docker_rig/README.md b/applications/launchpad/docker_rig/README.md new file mode 100644 index 0000000000..76b3089faf --- /dev/null +++ b/applications/launchpad/docker_rig/README.md @@ -0,0 +1,217 @@ +# Tari Launchpad - CLI edition + +a.k.a. _Tari one-click miner_. + +Currently this only works on MacOS and Linux. +Contributions for `quick_start.bat` are welcomed. + +## Prerequisites + +1. [Docker](https://docs.docker.com/get-docker/) +2. Set up some environment variables: + - `DATA_FOLDER` (required). The path to store the Tari configuration files and logs. You can create multiple + network setups, each in their own sandbox. + - `TARI_NETWORK` (optional). Default=weatherwax. Specify the Tari network to connect to. + - `START_TOR` (optional). Default=1. Whether we should start a Tor instance + - `START_BASE_NODE` (optional). Default=1. Whether we should start a base node instance + - `START_WALLET` (optional). Default=1. Whether we should start a wallet instance (GRPC only) + - `START_MINER` (optional). Default=1. Whether we should fire up a SHA_3 solo miner. + - `USE_OWN_MODEROD` (optional). Default=0. Whether we should start and sync our own Monero node. + - `START_MONERO_MM` (optional). Default=1. Whether we should start merge-mining on Monero. + + - `TARI_WALLET_PASSWORD` (optional). Highly recommended. Default: tari. The password for your Tari wallet. + - `TARI_WALLET__WEATHERWAX__TOR_CONTROL_AUTH` (optional). Default: tari. The password for the Tor control auth. + - `TARI_MONERO_WALLET_ADDRESS` (optional). Though you'll donate funds to some random address if you don't set it. + +## Quick Start + +`./quick-start.sh` + +And you should be golden. + +## Interacting with the wallet and base node. + +Any Tari client that supports gRPC can be connected to the node setup. With the default configuration, the base node +will be at `http://127.0.0.1:18142` and the wallet will be at `http://127.0.0.1:18143`. + +[Kreya](https://kreya.app/) is a fairly user-friendly gRPC client that can give you a rudimentary UI to the docker rig +out of the box: + +#### Getting block information + +![get_blocks](img/node_blocks.jpg) + +#### Wallet - whoami + +![whoami](img/wallet_id.jpg) + +#### Wallet - send transactions + +![send](img/wallet_send.jpg) + +## Viewing logs and configuration files + +Logs and configuration files are stored in `{DATA_FOLDER}/{app}/log/*.log`. + +So assuming your data folder is `/tmp/tari1/, the generated file structure looks like: + +```text +/tmp/tari1$ ll -R +drwxr-xr-x user user 128 B Wed Nov 3 15:56:53 2021  base_node/ +drwxr-xr-x user user 96 B Wed Nov 3 15:56:53 2021  config/ +drwxr-xr-x user user 128 B Mon Nov 1 12:55:50 2021  mm_proxy/ +drwxr-xr-x user user 128 B Mon Nov 1 12:55:58 2021  sha3_miner/ +drwxr-xr-x user user 64 B Wed Nov 3 15:56:53 2021  tor/ +drwxr-xr-x user user 192 B Mon Nov 1 12:56:17 2021  wallet/ +drwxr-xr-x user user 96 B Mon Nov 1 12:20:31 2021  xmrig/ + +./base_node: +drwxr-xr-x user user 128 B Fri Oct 29 17:44:56 2021  config/ +drwxr-xr-x user user 320 B Wed Nov 3 15:40:55 2021  log/ + +./base_node/config: +.rw-r--r-- user user 312 B Mon Nov 1 20:09:38 2021  base_node_id.json +.rw-r--r-- user user 211 B Mon Nov 1 20:09:38 2021  base_node_tor.json + +./base_node/log: +.rw-r--r-- user user 3.3 MB Wed Nov 3 15:54:20 2021  core.log +.rw-r--r-- user user 1 MB Wed Nov 3 15:53:10 2021  network.log +.rw-r--r-- user user 0 B Mon Nov 1 13:01:33 2021  other.log + +./config: +.rw-r--r-- user user 4.1 KB Mon Nov 1 20:12:10 2021  log4rs.yml + +./mm_proxy: +drwxr-xr-x user user 128 B Mon Nov 1 12:53:33 2021  config/ +drwxr-xr-x user user 352 B Wed Nov 3 14:10:04 2021  log/ + +./mm_proxy/config: +.rw-r--r-- user user 8.7 KB Mon Nov 1 12:53:33 2021  config.toml +.rw-r--r-- user user 1.1 KB Mon Nov 1 12:53:33 2021  log4rs.yml + +./mm_proxy/log: +.rw-r--r-- user user 8.4 MB Wed Nov 3 15:53:53 2021  core.log +.rw-r--r-- user user 0 B Mon Nov 1 13:01:38 2021  network.log +.rw-r--r-- user user 0 B Mon Nov 1 13:01:38 2021  other.log + +./sha3_miner: +drwxr-xr-x user user 192 B Mon Nov 1 13:01:36 2021  log/ + +./sha3_miner/log: +.rw-r--r-- user user 677.7 KB Wed Nov 3 15:49:31 2021  core.log +.rw-r--r-- user user 0 B Mon Nov 1 13:01:36 2021  network.log +.rw-r--r-- user user 0 B Mon Nov 1 13:01:36 2021  other.log + +./wallet: +drwxr-xr-x user user 192 B Mon Nov 1 12:56:17 2021  ./ +drwxr-xr-x user user 288 B Wed Nov 3 15:56:53 2021  ../ +drwxr-xr-x user user 256 B Wed Nov 3 15:23:19 2021  log/ +drwxr-xr-x user user 128 B Wed Nov 3 15:54:10 2021  weatherwax/ + +./wallet/log: +.rw-r--r-- user user 564.2 KB Wed Nov 3 15:54:20 2021  core.log +.rw-r--r-- user user 280.3 KB Wed Nov 3 15:54:08 2021  network.log +.rw-r--r-- user user 0 B Mon Nov 1 13:01:24 2021  other.log + +./xmrig: +drwxr-xr-x user user 96 B Mon Nov 1 12:20:31 2021  ./ +drwxr-xr-x user user 288 B Wed Nov 3 15:56:53 2021  ../ +.rw-r--r-- user user 212.9 KB Wed Nov 3 15:53:53 2021  xmrig.log +``` + +You can edit the network configuration (requires a container restart) and log configuration (takes effect within 30s) +by editing the files in the top-level `config` folder. + + + +## Current shortcomings and TODOs + +* It's a CLI only. Next step is to build a nice UI wrapper around this. + +* TODO - Windows support. + +* The blockchain data is stores in docker volumes, and not on the host machine directly. This is due to crippling performance +limitations one suffers when mounting host file system from Windows or MacOS into docker containers. +This isn't a big drawback, since you seldom want or need to access the raw blockchain database files anyway. Are they're +[still accessible](#accessing-blockchain-data). But **ensure that you reserve enough space to store the Tari, and optionally, +Monero blockchains inside the Docker VM**. + +* TODO - Multi-network sandboxing. Sandboxing currently doesn't work across different networks (e.g. weatherwax and mainnet). This should be fixed soon. + +* Local MoneroD support. The merge-miner proxy doesn't actually use the local MoneroD container if it is running. This + is left as a TODO for the UI version. + +* Tor control password. For now, you set this manually. In the UI edition, this will be ephemeral and configured automatically. + +* GRPC addresses. The code only supports IPv4 addresses as gRPC configuration parameters. This makes the docker networking + fragile since the IP addresses must be guessed. TODO - accept DNS names in gRPC configs to make this more robust. + + + + +### Accessing blockchain data + +The blockchain data is stored in a docker volume for performance reasons. If you need to back up or access the LMDB +a blockchain data, you can use something like this to extract it to the host filesystem: + +`docker run --rm -v $(pwd):/backup -v blockchain:/blockchain ubuntu tar czvf /backup/backup.tar.gz /blockchain` + + +## Layout + + +-----------------------+ + | | ++---->| Console Wallet +------------------+ +| | | | +| +----------+------------+ | +| | | +| | gRPC | +| | | +| | | +| +----------v------------+ +------v-----+ +| | | Socks5 | | +| | Base Node +---------->| Tor |----> Network +| | | | | +| +----------^------------+ +------------+ +| | +| | +| | +| +----------+------------+ +| | | ++-----+ SHA3-Miner | +| | | +| +-----------------------+ +| +| +| +| +-----------------------+ +| | | ++-----+ XMRRig etc | + | | + +-----------------------+ + +#### Notes + +Building docker images: + +``` +cd buildtools/docker_rig +docker build -t quay.io/tarilabs/tor:latest -f base_node.Dockerfile . +docker build -t quay.io/tarilabs/tari_base_node:latest -f base_node.Dockerfile ../../ +``` + +Base node/Wallet config for using the Tor docker container: + +```toml +tcp_listener_address = "/ip4/0.0.0.0/tcp/18189" +transport = "tor" +tor_control_address = "/dns4/tor/tcp/9051" +tor_control_auth = "password=asdf" # replace with your configured password +tor_onion_port = 18141 +tor_forward_address = "/ip4/0.0.0.0/tcp/18189" +tor_socks_address_override="/dns4/tor/tcp/9050" +``` + +When attaching to a running container: + +To detach the tty without exiting the shell/program, use the escape sequence ^P^Q (Ctrl+P followed by Ctrl+Q). diff --git a/applications/launchpad/docker_rig/base_node.Dockerfile b/applications/launchpad/docker_rig/base_node.Dockerfile new file mode 100644 index 0000000000..14ac002eff --- /dev/null +++ b/applications/launchpad/docker_rig/base_node.Dockerfile @@ -0,0 +1,61 @@ +FROM quay.io/tarilabs/rust_tari-build-with-deps:nightly-2021-09-18 as builder + +WORKDIR /tari + +# Adding only necessary things up front and copying the entrypoint script last +# to take advantage of layer caching in docker +ADD Cargo.lock . +ADD Cargo.toml . +ADD applications applications +ADD base_layer base_layer +ADD common common +ADD comms comms +ADD infrastructure infrastructure +ADD meta meta +ADD rust-toolchain.toml . + +ARG ARCH=native +ARG FEATURES=avx2 +ENV RUSTFLAGS="-C target_cpu=$ARCH" +ENV ROARING_ARCH=$ARCH +ENV CARGO_HTTP_MULTIPLEXING=false + +# Caches downloads across docker builds +RUN cargo build --bin deps_only --release + +RUN cargo build --bin tari_base_node --release --features $FEATURES --locked + +# Create a base minimal image for the executables +FROM quay.io/bitnami/minideb:bullseye as base +# Disable Prompt During Packages Installation +ARG DEBIAN_FRONTEND=noninteractive +RUN apt update && apt -y install \ + apt-transport-https \ + bash \ + ca-certificates \ + curl \ + gpg \ + iputils-ping \ + less \ + libreadline8 \ + libreadline-dev \ + libsqlite3-0 \ + openssl \ + telnet + +RUN groupadd -g 1000 tari && useradd -s /bin/bash -u 1000 -g 1000 tari + +RUN mkdir -p "/var/tari/base_node/weatherwax" \ + && mkdir -p "/var/tari/base_node/igor" \ + && mkdir -p "/var/tari/base_node/mainnet" \ + && chown -R tari.tari "/var/tari/base_node" + +USER tari + +ENV APP_NAME=base_node APP_EXEC=tari_base_node + +COPY --from=builder /tari/target/release/$APP_EXEC /usr/bin/ +COPY applications/launchpad/docker_rig/start_tari_app.sh /usr/bin/start_tari_app.sh + +ENTRYPOINT [ "start_tari_app.sh", "-c", "/var/tari/config/config.toml", "-b", "/var/tari/base_node" ] +# CMD [ "--non-interactive-mode" ] diff --git a/applications/launchpad/docker_rig/config.toml b/applications/launchpad/docker_rig/config.toml new file mode 100644 index 0000000000..20f902026b --- /dev/null +++ b/applications/launchpad/docker_rig/config.toml @@ -0,0 +1,325 @@ +######################################################################################################################## +# # +# Common Configuration Options # +# # +######################################################################################################################## + +[common] +# Select the network to connect to. Valid options are: +# mainnet - the "real" Tari network (default) +# weatherwax - the Tari testnet +network = "weatherwax" + +# When first logging onto the Tari network, you need to find a few peers to bootstrap the process. In the absence of +# any servers, this is a little more challenging than usual. Our best strategy is just to try and connect to the peers +# you knew about last time you ran the software. But what about when you run the software for the first time? That's +# where this allowlist comes in. It's a list of known Tari nodes that are likely to be around for a long time and that +# new nodes can use to introduce themselves to the network. +# peer_seeds = ["public_key1::address1", "public_key2::address2",... ] +peer_seeds = [ + # weatherwax + "98bc76afc1c35ad4651bdc9ef57bbe0655a2ea3cd86c0e19b5fd5890546eb040::/onion3/33izgtjkrlxhxybj6luqowkpiy2wvte43osejnbqyieqtdfhovzghxad:18141", #jozi + "9a26e910288213d649b26f9a7a7ee51fe2b2a67ff7d42334523463bf4be94312::/onion3/56kq54ylttnbl5ikotqex3oqvtzlxdpn7zlx4v56rvzf4kq7eezlclid:18141", #london + "6afd5b3c7772ad7d4bb26e0c19668fe04f2d68f99de9e132bee50a6c1846946d::/onion3/may4ajbmcn4dlnzf6fanvqlklxzqiw6qwu6ywqwkjc3bb354rc2i5wid:18141", #ncal + "8e7beec9becdc44fe6015a00d97a77fa3dbafe65127dcc988df6326bd9fd040d::/onion3/3pise36l4imoopsbjic5rtw67adx7rms6w5pgjmccpdwiqx66j7oqcqd:18141", #nvir + "80bb590d943a46e63ae79af5dc2c7d35a3dcd7922c182b28f619dc4cfc366f44::/onion3/oaxwahri7r3h5qjlcdbveyjmg4jsttausik66bicmhixft73nmvecdad:18141", #oregon + "981cc8cd1e4fe2f99ea1bd3e0ab1e7821ca0bfab336a4967cfec053fee86254c::/onion3/7hxpnxrxycdfevirddau7ybofwedaamjrg2ijm57k2kevh5q46ixamid:18141", #seoul + "f2ce179fb733725961a5f7e1e45dacdd443dd43ba6237438d6abe344fb717058::/onion3/nvgdmjf4wucgatz7vemzvi2u4sw5o4gyzwuikagpepoj4w7mkii47zid:18141", #stockholm + "909c0160f4d8e815aba5c2bbccfcceb448877e7b38759fb160f3e9494484d515::/onion3/qw5uxv533sqdn2qoncfyqo35dgecy4rt4x27rexi2her6q6pcpxbm4qd:18141", #sydney + # igor + "8e7eb81e512f3d6347bf9b1ca9cd67d2c8e29f2836fc5bd608206505cc72af34::/onion3/l4wouomx42nezhzexjdzfh7pcou5l7df24ggmwgekuih7tkv2rsaokqd:18141", + "00b35047a341401bcd336b2a3d564280a72f6dc72ec4c739d30c502acce4e803::/onion3/ojhxd7z6ga7qrvjlr3px66u7eiwasmffnuklscbh5o7g6wrbysj45vid:18141", + "40a9d8573745072534bce7d0ecafe882b1c79570375a69841c08a98dee9ecb5f::/onion3/io37fylc2pupg4cte4siqlsmuszkeythgjsxs2i3prm6jyz2dtophaad:18141", + "126c7ee64f71aca36398b977dd31fbbe9f9dad615df96473fb655bef5709c540::/onion3/6ilmgndocop7ybgmcvivbdsetzr5ggj4hhsivievoa2dx2b43wqlrlid:18141", +] + +# DNS seeds +# The DNS records in these hostnames should provide TXT records as per https://github.com/tari-project/tari/pull/2319 +# Enter a domain name for the TXT records: seeds.tari.com +dns_seeds =["seeds.weatherwax.tari.com"] +# The name server used to resolve DNS seeds (Default: "1.1.1.1:53") +# dns_seeds_name_server = "1.1.1.1:53" +# Servers addresses, majority of them have to agree. +# autoupdate_dns_hosts = [#server1, #server2, ...] +# Set to true to only accept DNS records that pass DNSSEC validation (Default: true) +dns_seeds_use_dnssec = false + +# Tari is a 100% peer-to-peer network, so there are no servers to hold messages for you while you're offline. +# Instead, we rely on our peers to hold messages for us while we're offline. This settings sets maximum size of the +# message cache that for holding our peers' messages, in MB. +#message_cache_size = 10 + +# When storing messages for peers, hold onto them for at most this long before discarding them. The default is 1440 +# minutes = or 24 hrs. +#message_cache_ttl = 1440 + +# If peer nodes spam you with messages, or are otherwise badly behaved, they will be added to your denylist and banned +# You can set a time limit to release that ban (in minutes), or otherwise ban them for life (-1). The default is to +# ban them for 10 days. +#denylist_ban_period = 1440 + +# The number of liveness sessions to allow. Liveness sessions can be established by liveness monitors over TCP by +# sending a 0x50 (P) as the first byte. Any messages sent must be followed by newline message no longer than +# 50 characters. That message will be echoed back. +#liveness_max_sessions = 0 +#liveness_allowlist_cidrs = ["127.0.0.1/32"] + +# The buffer size constants for the publish/subscribe connector channel, connecting comms messages to the domain layer: +# - Buffer size for the base node (min value = 30, default value = 1500). +#buffer_size_base_node = 1500 +# - Buffer size for the console wallet (min value = 300, default value = 50000). +#buffer_size_console_wallet = 50000 +# The rate limit constants for the publish/subscribe connector channel, i.e. maximum amount of inbound messages to +# accept - any rate attemting to exceed this limit will be throttled. +# - Rate limit for the base node (min value = 5, default value = 1000). +#buffer_rate_limit_base_node = 1000 +# - Rate limit for the console wallet (min value = 5, default value = 1000). +buffer_rate_limit_console_wallet = 1000 +# The message deduplication persistent cache size - messages with these hashes in the cache will only be processed once. +# The cache will also be trimmed down to size periodically (min value = 0, default value = 2500). +dedup_cache_capacity = 25000 + +# The timeout (s) for requesting blocks from a peer during blockchain sync (min value = 10 s, default value = 150 s). +#fetch_blocks_timeout = 150 + +# The timeout (s) for requesting UTXOs from a base node (min value = 10 s, default value = 600 s). +#fetch_utxos_timeout = 600 + +# The timeout (s) for requesting other base node services (min value = 10 s, default value = 180 s). +#service_request_timeout = 180 + +# The maximum simultaneous comms RPC sessions allowed (default value = 1000). Setting this to -1 will allow unlimited +# sessions. +rpc_max_simultaneous_sessions = 10000 + +# Auto Update +# +# This interval in seconds to check for software updates. Setting this to 0 disables checking. +# auto_update.check_interval = 300 +# Customize the hosts that are used to check for updates. These hosts must contain update information in DNS TXT records. +# auto_update.dns_hosts = ["updates.taripulse.com"] +# Customize the location of the update SHA hashes and maintainer-signed signature. +# auto_update.hashes_url = "https://
/hashes.txt" +# auto_update.hashes_sig_url = "https://
/hashes.txt.sig" + + +######################################################################################################################## +# # +# The Tari Network Configuration File # +# # +######################################################################################################################## + +# This file carries all the configuration options for running Tari-related nodes and infrastructure in one single +# file. As you'll notice, almost all configuraton options are commented out. This is because they are either not +# needed, are for advanced users that know what they want to tweak, or are already set at their default values. If +# things are working fine, then there's no need to change anything here. +# +# Each major section is clearly marked so that you can quickly find the section you're looking for. This first +# section holds configuration options that are common to all sections. + +######################################################################################################################## +# # +# Base Node Configuration Options # +# # +######################################################################################################################## + +# If you are not running a Tari Base node, you can simply leave everything in this section commented out. Base nodes +# help maintain the security of the Tari token and are the surest way to preserve your privacy and be 100% sure that +# no-one is cheating you out of your money. + +[base_node] +# network = "weatherwax" + +# Configuration options for testnet Weatherwax +[base_node.weatherwax] +# The type of database backend to use. Currently supported options are "memory" and "lmdb". LMDB is recommnded for +# almost all use cases. +db_type = "lmdb" + +# db config defaults +# db_init_size_mb = 1000 +# db_grow_size_mb = 500 +# db_resize_threshold_mb = 100 + +# The maximum number of orphans that can be stored in the Orphan block pool. Default value is "720". +#orphan_storage_capacity = 720 +# The size that the orphan pool will be allowed to grow before it is cleaned out, with threshold being tested every +# time before fetch and add blocks. Default value is "0", which indicates the orphan pool will not be cleaned out. +#orphan_db_clean_out_threshold = 0 +# The pruning horizon that indicates how many full blocks without pruning must be kept by the base node. Default value +# is "0", which indicates an archival node without any pruning. +#pruning_horizon = 0 + +# The amount of messages that will be permitted in the flood ban timespan of 100s (Default weatherwax = 1000, +# default mainnet = 10000) +flood_ban_max_msg_count = 10000 + +# The relative path to store persistent data +data_dir = "/blockchain/weatherwax" + +# This allowlist provides a method to force syncing from any known nodes you may choose, for example if you have a +# couple of nodes that you always want to have in sync. +# force_sync_peers = ["public_key1::address1", "public_key2::address2",... ] +force_sync_peers = [ + #my known peer 1 + #"public_key1::address1", + #my known peer 2 + #"public_key1::address1", +] + +# Determines the method of syncing blocks when the node is lagging. If you are not struggling with syncing, then +# it is recommended to leave this setting as it. Available values are ViaBestChainMetadata and ViaRandomPeer. +#block_sync_strategy="ViaBestChainMetadata" + +# Configure the maximum number of threads available for base node operation. These threads are spawned lazily, so a higher +# number is recommended. +# max_threads = 512 + +# The number of threads to spawn and keep active at all times. The default is the number of cores available on this node. +# core_threads = + +# The node's publicly-accessible hostname. This is the host name that is advertised on the network so that +# peers can find you. +# _NOTE_: If using the `tor` transport type, public_address will be ignored and an onion address will be +# automatically configured +#public_address = "/ip4/172.2.3.4/tcp/18189" + +# do we allow test addresses to be accpted like 127.0.0.1 +allow_test_addresses = false + +# Enable the gRPC server for the base node. Set this to true if you want to enable third-party wallet software +grpc_enabled = true +# The socket to expose for the gRPC base node server. This value is ignored if grpc_enabled is false. +# Valid values here are IPv4 and IPv6 TCP sockets, local unix sockets (e.g. "ipc://base-node-gprc.sock.100") +grpc_base_node_address = "0.0.0.0:18142" +# The socket to expose for the gRPC wallet server. This value is ignored if grpc_enabled is false. +# Valid values here are IPv4 and IPv6 TCP sockets, local unix sockets (e.g. "ipc://base-node-gprc.sock.100") +grpc_console_wallet_address = "0.0.0.0:18143" + +# A path to the file that stores your node identity and secret key +base_node_identity_file = "config/base_node_id.json" + +# A path to the file that stores your console wallet's node identity and secret key +console_wallet_identity_file = "config/console_wallet_id.json" + +# -------------- Transport configuration -------------- +# Use TCP to connect to the Tari network. This transport can only communicate with TCP/IP addresses, so peers with +# e.g. tor onion addresses will not be contactable. +#transport = "tcp" +# The address and port to listen for peer connections over TCP. +#tcp_listener_address = "/ip4/0.0.0.0/tcp/18189" +# Configures a tor proxy used to connect to onion addresses. All other traffic uses direct TCP connections. +# This setting is optional however, if it is not specified, this node will not be able to connect to nodes that +# only advertise an onion address. +#tcp_tor_socks_address = "/ip4/127.0.0.1/tcp/36050" +#tcp_tor_socks_auth = "none" + +transport = "tor" +tor_control_address = "/dns4/tor/tcp/9051" +# Overridden by the docker environment variables +tor_control_auth = "password=tari" +tor_onion_port = 18141 +tor_forward_address = "/dns4/base_node/tcp/18189" +tor_socks_address_override="/dns4/tor/tcp/9050" +base_node_tor_identity_file = "config/base_node_tor.json" +console_wallet_tor_identity_file = "config/console_wallet_tor.json" + +# Optionally bind an additional TCP socket for inbound Tari P2P protocol commms. +# Use cases include: +# - allowing wallets to locally connect to their base node, rather than through tor, when used in conjunction with `tor_proxy_bypass_addresses` +# - multiple P2P addresses, one public over DNS and one private over TOR +# - a "bridge" between TOR and TCP-only nodes +# auxilary_tcp_listener_address = "/ip4/127.0.0.1/tcp/9998" + +# When these addresses are encountered when dialing another peer, the tor proxy is bypassed and the connection is made +# direcly over TCP. /ip4, /ip6, /dns, /dns4 and /dns6 are supported. +# tor_proxy_bypass_addresses = ["/dns4/my-foo-base-node/tcp/9998"] +# When using the tor transport and set to true, outbound TCP connections bypass the tor proxy. Defaults to false for better privacy +# tor_proxy_bypass_for_outbound_tcp = false; + +######################################################################################################################## +# # +# Mempool Configuration Options # +# # +######################################################################################################################## + +[mempool.weatherwax] +# Configuration options for testnet Igor +[base_node.igor] +# The type of database backend to use. Currently supported options are "memory" and "lmdb". LMDB is recommnded for +# almost all use cases. +db_type = "lmdb" + +# db config defaults +# db_init_size_mb = 1000 +# db_grow_size_mb = 500 +# db_resize_threshold_mb = 100 +# orphan_storage_capacity = 720 +# The size that the orphan pool will be allowed to grow before it is cleaned out, with threshold being tested every +# time before fetch and add blocks. Default value is "0", which indicates the orphan pool will not be cleaned out. +#orphan_db_clean_out_threshold = 0 +# The pruning horizon that indicates how many full blocks without pruning must be kept by the base node. Default value +# is "0", which indicates an archival node without any pruning. +#pruning_horizon = 0 + +# The amount of messages that will be permitted in the flood ban timespan of 100s (Default weatherwax = 1000, +# default mainnet = 10000) +flood_ban_max_msg_count = 10000 + +# The relative path to store persistent data +data_dir = "/blockchain/igor" +# max_threads = 512 +# core_threads = + +# The node's publicly-accessible hostname. This is the host name that is advertised on the network so that +# peers can find you. +# _NOTE_: If using the `tor` transport type, public_address will be ignored and an onion address will be +# automatically configured +#public_address = "/ip4/172.2.3.4/tcp/18189" + +# do we allow test addresses to be accpted like 127.0.0.1 +allow_test_addresses = false + +# Enable the gRPC server for the base node. Set this to true if you want to enable third-party wallet software +grpc_enabled = true +grpc_base_node_address = "127.0.0.1:18142" +grpc_console_wallet_address = "127.0.0.1:18143" +base_node_identity_file = "config/igor/base_node_id.json" +console_wallet_identity_file = "config/igor/console_wallet_id.json" + +# -------------- Transport configuration -------------- +#transport = "tcp" +#tcp_listener_address = "/ip4/0.0.0.0/tcp/18189" +#tcp_tor_socks_address = "/ip4/127.0.0.1/tcp/36050" +#tcp_tor_socks_auth = "none" + +transport = "tor" +tor_control_address = "/dns4/tor/tcp/9051" +# Overridden by the docker environment variables +tor_control_auth = "password=tari" +tor_onion_port = 18141 +tor_forward_address = "/dns4/base_node/tcp/18189" +tor_socks_address_override="/dns4/tor/tcp/9050" +base_node_tor_identity_file = "config/igor/base_node_tor.json" +console_wallet_tor_identity_file = "config/igor/console_wallet_tor.json" +# Optionally bind an additional TCP socket for inbound Tari P2P protocol commms. +# Use cases include: +# - allowing wallets to locally connect to their base node, rather than through tor, when used in conjunction with `tor_proxy_bypass_addresses` +# - multiple P2P addresses, one public over DNS and one private over TOR +# - a "bridge" between TOR and TCP-only nodes +# auxilary_tcp_listener_address = "/ip4/127.0.0.1/tcp/9998" + +# When these addresses are encountered when dialing another peer, the tor proxy is bypassed and the connection is made +# direcly over TCP. /ip4, /ip6, /dns, /dns4 and /dns6 are supported. +# tor_proxy_bypass_addresses = ["/dns4/my-foo-base-node/tcp/9998"] +# When using the tor transport and set to true, outbound TCP connections bypass the tor proxy. Defaults to false for better privacy +# tor_proxy_bypass_for_outbound_tcp = false; + +######################################################################################################################## +# # +# Mempool Configuration Options # +# # +######################################################################################################################## +[mempool.igor] + + diff --git a/buildtools/docker_rig/base_node.Dockerfile b/applications/launchpad/docker_rig/console_wallet.Dockerfile similarity index 53% rename from buildtools/docker_rig/base_node.Dockerfile rename to applications/launchpad/docker_rig/console_wallet.Dockerfile index 43d7288b9d..0fc7c456a8 100644 --- a/buildtools/docker_rig/base_node.Dockerfile +++ b/applications/launchpad/docker_rig/console_wallet.Dockerfile @@ -1,4 +1,4 @@ -FROM quay.io/tarilabs/rust_tari-build-with-deps:nightly-2021-08-18 as builder +FROM quay.io/tarilabs/rust_tari-build-with-deps:nightly-2021-09-18 as builder WORKDIR /tari @@ -12,20 +12,21 @@ ADD common common ADD comms comms ADD infrastructure infrastructure ADD meta meta -ADD rust-toolchain . +ADD rust-toolchain.toml . -# RUN rustup component add rustfmt --toolchain nightly-2020-08-13-x86_64-unknown-linux-gnu -#ARG TBN_ARCH=native -ARG TBN_ARCH=x86-64 -#ARG TBN_FEATURES=avx2 -ARG TBN_FEATURES=safe -ENV RUSTFLAGS="-C target_cpu=$TBN_ARCH" -ENV ROARING_ARCH=$TBN_ARCH +ARG ARCH=native +ARG FEATURES=avx2 +ENV RUSTFLAGS="-C target_cpu=$ARCH" +ENV ROARING_ARCH=$ARCH +ENV CARGO_HTTP_MULTIPLEXING=false -RUN cargo build --bin tari_base_node --release --features $TBN_FEATURES --locked +# Caches downloads across docker builds +RUN cargo build --bin deps_only --release + +RUN cargo build --bin tari_console_wallet --release --features $FEATURES --locked # Create a base minimal image for the executables -FROM quay.io/bitnami/minideb:buster as base +FROM quay.io/bitnami/minideb:bullseye as base # Disable Prompt During Packages Installation ARG DEBIAN_FRONTEND=noninteractive RUN apt update && apt -y install \ @@ -36,18 +37,19 @@ RUN apt update && apt -y install \ gpg \ iputils-ping \ less \ - libreadline7 \ + libreadline8 \ libreadline-dev \ libsqlite3-0 \ openssl \ telnet -# Now create a new image with only the essentials and throw everything else away -FROM base -ENV APP_NAME=base_node APP_EXEC=tari_base_node +RUN groupadd -g 1000 tari && useradd -s /bin/bash -u 1000 -g 1000 tari +USER tari + +ENV APP_NAME=wallet APP_EXEC=tari_console_wallet COPY --from=builder /tari/target/release/$APP_EXEC /usr/bin/ -COPY buildtools/docker_rig/start_base_node.sh /usr/bin/start_tari_app.sh +COPY applications/launchpad/docker_rig/start_tari_app.sh /usr/bin/start_tari_app.sh -ENTRYPOINT [ "start_tari_app.sh", "-c", "/var/tari/config/config.toml", "-b", "/var/tari/base_node" ] +ENTRYPOINT [ "start_tari_app.sh", "-c", "/var/tari/config/config.toml", "-b", "/var/tari/wallet" ] # CMD [ "--non-interactive-mode" ] diff --git a/applications/launchpad/docker_rig/docker-compose.yml b/applications/launchpad/docker_rig/docker-compose.yml new file mode 100644 index 0000000000..e062458ffa --- /dev/null +++ b/applications/launchpad/docker_rig/docker-compose.yml @@ -0,0 +1,184 @@ +version: "3.9" +services: + tor: + image: quay.io/tarilabs/tor:latest + build: + context: . + dockerfile: tor.Dockerfile +# volumes: +# - ${DATA_FOLDER}/tor:/etc/tor/ +# ports: +# - 9050:9050 +# - 9051:9051 + + + wallet: + image: quay.io/tarilabs/tari_console_wallet:latest + build: + context: ./../../.. + dockerfile: applications/launchpad/docker_rig/console_wallet.Dockerfile + args: + ARCH: native + FEATURES: avx2 + environment: + TARI_LOG_CONFIGURATION: "/var/tari/config/log4rs.yml" + APP_NAME: wallet + APP_EXEC: tari_console_wallet + WAIT_FOR_TOR: ${WAIT_FOR_TOR:-0} + SHELL: "/bin/bash" + TERM: "linux" + TARI_WALLET_PASSWORD: ${TARI_WALLET_PASSWORD:-tari} + TARI_NETWORK: ${TARI_NETWORK:-weatherwax} + TARI_WALLET__WEATHERWAX__TOR_CONTROL_AUTH: "password=${TOR_CONTROL_AUTH:-tari}" + TARI_WALLET__WEATHERWAX__TOR_CONTROL_ADDRESS: "/dns4/tor/tcp/9051" + TARI_WALLET__WEATHERWAX__TOR_SOCKS_ADDRESS_OVERRIDE: "/dns4/tor/tcp/9050" + TARI_WALLET__WEATHERWAX__TOR_FORWARD_ADDRESS: "/dns4/wallet/tcp/18188" + TARI_WALLET__WEATHERWAX__TCP_LISTENER_ADDRESS: "/dns4/wallet/tcp/18188" + TARI_BASE_NODE__WEATHERWAX__GRPC_CONSOLE_WALLET_ADDRESS: "0.0.0.0:18143" + command: ["--non-interactive"] + ports: + - 18188:18188 + - 18143:18143 + depends_on: + - tor + volumes: + - ${DATA_FOLDER}:/var/tari/ + #stdin_open: true + #tty: true + + + base_node: + image: quay.io/tarilabs/tari_base_node:latest + build: + context: ./../../.. + dockerfile: applications/launchpad/docker_rig/base_node.Dockerfile + args: + ARCH: native + FEATURES: avx2 + environment: + TARI_LOG_CONFIGURATION: "/var/tari/config/log4rs.yml" + APP_NAME: base_node + APP_EXEC: tari_base_node + WAIT_FOR_TOR: ${WAIT_FOR_TOR:-0} + TARI_NETWORK: ${TARI_NETWORK} + TARI_BASE_NODE__WEATHERWAX__TOR_CONTROL_AUTH: "password=${TOR_CONTROL_AUTH:-tari}" + TARI_BASE_NODE__WEATHERWAX__DATA_DIR: "/blockchain/weatherwax" + TARI_BASE_NODE__IGOR__DATA_DIR: "/blockchain/igor" + TARI_BASE_NODE__MAINNET__DATA_DIR: "/blockchain/mainnet" + TARI_BASE_NODE__WEATHERWAX__TOR_CONTROL_ADDRESS: "/dns4/tor/tcp/9051" + TARI_BASE_NODE__WEATHERWAX__TOR_SOCKS_ADDRESS_OVERRIDE: "/dns4/tor/tcp/9050" + TARI_BASE_NODE__WEATHERWAX__TOR_FORWARD_ADDRESS: "/dns4/base_node/tcp/18189" + TARI_BASE_NODE__WEATHERWAX__TCP_LISTENER_ADDRESS: "/dns4/base_node/tcp/18189" + TARI_BASE_NODE__WEATHERWAX__GRPC_BASE_NODE_ADDRESS: "0.0.0.0:18142" + TARI_BASE_NODE__WEATHERWAX__GRPC_ENABLED: "1" + ports: + - 18189:18189 + - 18142:18142 + command: ["--non-interactive"] + depends_on: + - tor + volumes: + - ${DATA_FOLDER}:/var/tari/ + - blockchain:/blockchain/${TARI_NETWORK} + stdin_open: true + tty: true + + + sha3_miner: + image: quay.io/tarilabs/tari_sha3_miner:latest + build: + context: ./../../.. + dockerfile: applications/launchpad/docker_rig/sha3_miner.Dockerfile + args: + ARCH: native + FEATURES: avx2 + environment: + TARI_LOG_CONFIGURATION: "/var/tari/config/log4rs.yml" + APP_NAME: sha3_miner + APP_EXEC: tari_mining_node + WAIT_FOR_TOR: 0 + TARI_NETWORK: ${TARI_NETWORK} + TARI_MINING_NODE__NUM_MINING_THREADS: 2 + TARI_MINING_NODE__MINE_ON_TIP_ONLY: 1 + TARI_BASE_NODE__WEATHERWAX__GRPC_BASE_NODE_ADDRESS: "/dns4/base_node/tcp/18142" + TARI_BASE_NODE__WEATHERWAX__GRPC_CONSOLE_WALLET_ADDRESS: "/dns4/wallet/tcp/18143" + command: [ ] + depends_on: + - base_node + - wallet + volumes: + - ${DATA_FOLDER}:/var/tari/ + + + xmrig: + image: quay.io/tarilabs/xmrig:latest + build: + context: . + dockerfile: xmrig.Dockerfile + #command: ["--url", "mm_proxy:18081", "--user", "${TARI_MONERO_WALLET_ADDRESS:-859CW1aiA8gUmAaTipKsYrF5r83MesesSjWoJSSRL6nnfi5LqBssxJmg7BzhgXYcjcPARM7bBvFR9H5dJdi6w93eKA53v8G}", "--coin", "monero", "--daemon", "--log-file=/var/xmrig/xmrig.log"] + command: ["--url", "mm_proxy:18081", "--user", "${TARI_MONERO_WALLET_ADDRESS:-5AJ8FwQge4UjT9Gbj4zn7yYcnpVQzzkqr636pKto59jQcu85CFsuYVeFgbhUdRpiPjUCkA4sQtWApUzCyTMmSigFG2hDo48}", "--coin", "monero", "--daemon", "--log-file=/var/xmrig/xmrig.log", "--verbose"] + depends_on: + - mm_proxy + volumes: + - ${DATA_FOLDER}/xmrig:/var/xmrig/ + + + monerod: + image: quay.io/tarilabs/monerod:latest + build: + context: . + dockerfile: monerod.Dockerfile + volumes: + - monero-blockchain:/home/monero/.bitmonero + - ${DATA_FOLDER}/monerod:/home/monerod + command: + - "--non-interactive" + - "--restricted-rpc" + - "--rpc-bind-ip=0.0.0.0" + - "--confirm-external-bind" + - "--enable-dns-blocklist" + - "--log-file=/home/monerod/monerod.log" + - "--fast-block-sync=1" + - "--prune-blockchain" + - "--${MONERO_NETWORK:-mainnet}" + + mm_proxy: + image: quay.io/tarilabs/tari_mm_proxy:latest + build: + context: ./../../.. + dockerfile: applications/launchpad/docker_rig/mm_proxy.Dockerfile + args: + ARCH: native + FEATURES: avx2 + environment: + RUST_LOG: debug + TARI_LOG_CONFIGURATION: "/var/tari/config/log4rs.yml" + APP_NAME: mm_proxy + APP_EXEC: tari_merge_mining_proxy + WAIT_FOR_TOR: 0 + TARI_NETWORK: ${TARI_NETWORK} + TARI_BASE_NODE__WEATHERWAX__GRPC_BASE_NODE_ADDRESS: "/dns4/base_node/tcp/18142" + TARI_BASE_NODE__WEATHERWAX__GRPC_CONSOLE_WALLET_ADDRESS: "/dns4/wallet/tcp/18143" + TARI_MERGE_MINING_PROXY__WEATHERWAX__MONEROD_URL: ${TARI_MONEROD_URL:-http://monero-stagenet.exan.tech:38081} + TARI_MERGE_MINING_PROXY__WEATHERWAX__MONEROD_USERNAME: ${TARI_MONEROD_USERNAME} + TARI_MERGE_MINING_PROXY__WEATHERWAX__MONEROD_PASSWORD: ${TARI_MONEROD_PASSWORD} + TARI_MERGE_MINING_PROXY__WEATHERWAX__MONEROD_USE_AUTH: ${TARI_MONEROD_USE_AUTH:-0} + TARI_MERGE_MINING_PROXY__WEATHERWAX__PROXY_HOST_ADDRESS: "0.0.0.0:18081" + depends_on: + - base_node + - wallet + command: [ ] + volumes: + - ${DATA_FOLDER}:/var/tari/ +# pool-worker: +# pool-operator: + +volumes: + # The blockchain data is stored in a docker volume for performance reasons. If you need to back up or access the LMDB + # blockchain data, you can use something like + # `docker run --rm -v $(pwd):/backup -v blockchain:/blockchain ubuntu tar czvf /backup/backup.tar.gz /blockchain` + blockchain: + monero-blockchain: + + + diff --git a/applications/launchpad/docker_rig/img/node_blocks.jpg b/applications/launchpad/docker_rig/img/node_blocks.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bf481b296c3ca56f9f4ec746fb16f7066d65261c GIT binary patch literal 38176 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�l|8xL+M579`QN}o`cv(S6SO^PokxDRLXwCHw( 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

C$hX(mD9hPk%JI zulF1ZzTCnT@!S1lm#@g<5++mf@ z9sM!mIpr+xMsr)@b9n?6ar=~ZuC=vvTU~-VkN=p_+-(uVB;(j$>6p%>OSpfv|KLrP zGdqNb(1`@O&GJ~FquaSW-=b;E?ZvR4=IZi9&l_s}m1Whjq4x=Nz=S=^%rnD7DJ`gV zMd_6)r$VqwV|r6ub#)W06?SRp@wpm0+@{X`(p*T#p}bcF@xVYZB`rjofJKj5H~w3( z45^sNxKA#uE-pa$xgKUdY#1H?qHV^G?cS)= zfI3Bh(D@m#W?mzJG%q=b)nT|~;I>7Y`^##N*9yBVYKq|}F~GES zNRk=wRx(S48FzOS4FtclyHs#}F#Xg%_5GX^6>`&i_u@D;^jTOXePn#DScqezVA52Y zTqqo0gaBtiBr0m$_tw9n=L{cswDWFyvO!o_jjx#=yw`*GUaa{IQ2G87?nzW>!IZC-m%uL0bw=b{jazcrhXkq@=B93T593!qeh?>Jfp0rhR?HtX`3#lGs}jr z^pCIV6qG+!M}4Yn0?$)N257e?ZGc}IaS>wQLb{CCHkYoWg~omJJYYX~f&E7*WG!z` zdz-%#TU9o1alMKK(CQR~p;>S0J7)N*il(fDTEK`ry5#1=PqQ7xp&j6w+Rg zWoD7Bs0XMbp5o$S-kv#Xo8LBg>FCZ-J-)qInI9&pH?tD&&DxaB#}Ew>#I~kH*drcT zIP~t;E3fV<6RS3@?~GOf-tvzKbb~+lI^+B#%54xarb&Q_ zvn=Q=Z}XN_1aB3c9G^86u?AvxP^l%1@R=mNqy6F7c3!fPwW?ugj%R5%HO0M;5@2g( zwFUjx_zy>S-U`nVgf_&QXBWooiH=TJhE?L`udafLxUy~TsoA~a!-@I>D)Ogi+`A6A z;5#K^->ofxn~z!Q#$i7lb}nDjFAv@28Oc$6)tBm?ZpO#;?}5f26RqHLn`-B2J@$^_ za7-0qrxC$J3LvzmZ9T-6j@mhUst#4j4-&cmR*!A|m)K0}?&;c_WqB%P#(oo+Taz7p z5{KE7TFE@Yx~@Yx;NO-+gH{I@|CD@=kLVYOj~$ z3DPy-<24R(6s8$Imvf#HaE#XZ9JMSNhzn~HOfv_X4JedrPeSl5-JW`6`)po-V_wXr>{ z1pHb#@o>&6d>)Yh;7p}kT!hd<&38v+bt;|W%!FvmW>%Ra)5$;NQ&Ud##X;1Dugm1+ zmCu98L5sWN0FY-*VeUjpQ?76PS#mbt{roLE>95C+zL~r>YdTc}k1N?1zMbXcC@C<` zE@farqDNa$BGK?{h>qA(l+;t(w zZoXU@8mK>lB2f!XttV97etd3~@Tco!bN)PFL_kyo3biGoG{dAFy zu5_*8$X$3tGvWo$3z$bq0Z!J96Eb9K+jv-EFYmNsKD^?P<7Lyq?qn7?ZlzdW)3Q00 zZ*Q#0hjugud+R$wd2T9YSb@UJ*(}8{lfQ`C(V(R!26ij&n(5=l4NKS{eQ`QD=x`l>?65qf-v63SZ&B<*;pP{yfX=x?GT36cdV~j2AL?8{ z{2ur#MTKUsFjdp+m!Xo%hX1_tYjT?q^@b>I(H@hXfd?xoNFI*^6^$6UVw5(=(z^U> zz@&agg0q^s`pIL^e<9TWz0f}sS6W&b<5_{^ZH7}}yJ0gMlZrlqdKdKa(cqW%<$`Iu z4P#bZo_^4}rD!H$6KZR*u;IKY&kGCZ_(p5j8dj^@R;Ke&&jc=9QqF5BO+GgXPVzDtOZSz13Hi!NX5{LWBnyhs=u60ZJao6_k1+O62eISkRL9!F8{UK_ z=FsC?N{1}lqn@(eDp*LAudIt`Nij1dn&7+QjZE@i(&}`a2YkEvUvWHsdIhP5q%0$5 z>w#rP`XKK88T!vy~5jFQ?=A)>B z1=|t{MYhN&!)%6tZ?@u#(%x1DO^ptY%~vPWX0ze^JHKIZfBxo^27R#l+)3a5(5p!~ z)_P){c4+S)W#4I9JCmjvFl=68rgnCg-gxV(FiO3HdGw@~8wdtU<*^UW=;`ReHZF8T z`88Ze8p5EVF_Ailg8)Y&!(NWLUzr2;W174t-wXxZqmK`MeR}h-^PZKeQC&Qk{TiQ$ z=hv0MxCfi@`k);=dfd^w;^(n^U1qJJ)b36-c~jab_c8=U+CyCrY|;5d!vwX^Yo)B6 z6~=Y1ZLD)3f6&r6+rH$0nbzuh8@?$d3)bG4brHs&y^n^y7CXDLI6I8mQnvf`zLH@2D6}fb7O1Mv&it%61INlqsCG)c{6L&X zN|V0lXw|ckuBgl?0rlKg%^qkNAk$bZ�T=&zhDwBR_w-qLf*841NTe!XDDu0;$~V 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 literal 0 HcmV?d00001 diff --git a/applications/launchpad/docker_rig/img/wallet_id.jpg b/applications/launchpad/docker_rig/img/wallet_id.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2d7a4d6f2ee6747d50cd191f890ec87d751f0516 GIT binary patch literal 26185 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%0DS29{cr5alH zenZJZBPmdsDSb~iDo!~+l|5K;R`F;?W$u(LHslGXf~V}{tM0M}BPr;|dAc2mX_p*( zj#)3P%Ae=p$Wrm7)lCt!`sa|h1{p|J+iD{zViC@)*L92%RC3=kcSQJCnyRSd-h4xG zpjBgOd*vVkqO`Fx40$b+T;?xP;-&6q^6DIzdY3$w&$zAi4LDmSLF7&1m7Nvzm$&}~ zF{!^A!<8K2gk{~m&746sT_}6mo_XV~u5TreQ%LZr6_~CyCGagF-|mEITXE6%`CSpq zKQF=_QV)FT9OXV0c-G|BWJzr-H3c*Nw%4q{KNr8PJJ||ywjLBUFa$N0RsQ70rN8oz ze}LD&2EZG1#`hd95+YoL)4qY>nnao!utTN1(rtn2S2d)_w5_ za3YLeP8*3sX!&!w!O&)&AYTw(KS@cLSDd4z^J7BWG~Xz|mV=VnNZt`~e)<6sci}9s zGJA91mK%TQ&|g+5=cg&FJUZ{dVadtpf6=vTZ$x}xYVO-#Kdl<~bTk`Ly*wEce|7cq zw#hzBI?P$!YRD1iDITlv6MjMw_YB(26g4gj;v%A)z-B0VqFrW53h2ahNRFymt3JbQ zWni{|>c_nJ6@^daJqKIU$#1MJP9FDbTnKeZ8xTh#|`Azo3 zoQ1ikdhW#ANKNc0IV?K$A<8!l?la}fi9}>ZnYHb{&M-NrbzNV-R7(0Wl-~)a>DL(C)MOgK0*;&lj<31+!qQz_D?Z&3X%gN7f z_yu`qD*4pb(`J~UKTurjIvjm3>I^2!h@!5~!M7y}s|7rsD^xY3{%S8Yl}0WW`yuM% z$a+F9DdCMV5V{Nt@A%TxFu;#dwlAb5W4G?-eRa*j)3JTLP8KgN(#l0qr(qJakyibD zDw6gatXL%+r_=8_c929B`qtJgl?_jqvMPNCn=x(;5l@zXHDy;jT3kvTsfc`M;xlQ0 zXCZdFw@IX>p-Bgp`id*c3)>HJwaC$D);&ac%&kD2#t+rbRt!2tS9$zSW*RB=`s`+N z+G;Es4cS^rU~4rApb-o7tvwu-^aN5E z{E4Fo6-xXu6R`KFVJ@on*61J$vTAVK&fg0-8x>~i>pK|OEQ=W8jTnuH03oCX<{2$o z^I2}t&m8->u{A{l*vC2shgS?pRw})@#IrK; z6Z_fZoyi6)TV-SZF3;y6cbs$lv}@hGSy>|n_-)3M@!S#r!>gfvzeb)7SJ3N(4b;?-lqEME!St3lVKte z5d&s|n9(dfkLVWxCSOISQo%i08NOpxWd`{qjaovkmDV`>4}=d|hwNm}h*C%M)>+CL zZ6;GwyVfT^?@ttZJ9EHW2Q|!EyK2!54Ke_bOq_=m9S&Gq>C4{`W;nX!C$&cn+qW;W zNXJvVl+`MdSDJl7%BOnXvZd}QK=&v=z~Qn}LG0TMtUqP1-ELHFwCyNmhJL8UFWDi2 zOJ!zwMn@~2kH@k7{G1d^=S^UcLX|q8==fpIb94dFm1qfeTEJ5Yr2F9+YhAzCV5PrW zrq&(PBXTqOdf#umc`;cE{IpMCbzRK zQ~y9Q_L>AOZkanO3GZ1?SQS(W-&_mcwR5$; z3t9Jk294P%PR^It*G*;xg#-KsQBdV`)PJ@#rX>f|fu9#&otNCR;N9i_ff6XLo|A;f|C}ijPfjLm!H2`XG$DnM#OXbF z-^*VJ>6u%SMy95QJBNwZj}oh_p|7suA8Nx|NO{WlWF$JEa0VC)-U3?Dk+TzD<*QjW z)8e*^mGSuD&f+6-|FG<6n`OwTctvRZ(o$+>TsYO;_m{6@!he*(MBoY&-^SoYd6s~u z4@fKNHVqeQcUQrBkr}@3Rv|!EkhfucbB}U z;9(4fabFrjsU+>FcTLM~h~1S2anPjSi$d!af680;&F`u&mtu`BG%xq-^Vk^9974tF zVBMR9p`qwKUk7nN!0_N|@{hB;t;XXg2ihlNce6X&MFoHQ23m4 zsI%40ew>-iiO>4BP}5eo2GP52m@LGy-fJE5YNs9Af`H93q7eE5H9kXfa3U(NCPuS6 zcy@gvSi*&Adg`0**nf}I{^1jh-KF~I4nhH66YrKFa3_;QzhELz=R2ecuyG24!woCa zoR((aWtK@!cC7mKn^kM1$0kHWf#W30_KVSyZFEXPv-wn!VAa=kdh<-d%W5IFvdLb` zmuCX0c=Eju`(nAm{y;e-G*;J;wy1BDqLx{DQalG*;iEU-K8MM{@p|MyDY#&emYK&fRp;k zllIVTfav4xVr&nxDo}JMHbO_%EAU=8W6cE1I7c0KpzYvuL8qf%?LU?U{%i_y$J=;$ zHNPe2jLtpYWJxFJt9(_CBtJYFBXG>BD72k%_88)sE;}*;&86%@uIQ_zaR<*@qG@@u zgAnj~BVfW%u&+ei>%QI9xgX0ddD0=fLn}UBe(^#cas1_G%}6EuVU_gtbz02rDF(H` z{##5^RV`q z&t`(|Hmr!ZBy!-29;e2bz(;`&%lU+AQf$JvI0AQES-wBL)yB(2w?Y2I=cif5W$SyJ zr^>64CIvb2kgIlMo(Y1{_CjpJ*N*B6lWUE2k<{B|5CbN2{G-w?W#t~zG1I(vxKcb$ z&z*P`9n=(^cne?SG!XN(Nw64dFrZ@5yFQrsG3Yr{y2c;lc6vBbf#WfBAh;>G85$-` zwID0WmX>HDBI8s1$POFNmfYS?5rEz?i#?%MK2_J4aX__N2Kef74_G1E&hDLNXf?5& zPQsB+r+TTrui}u##tH%^Q&u~A8z!q2HPtmga_n;1GWVSXi{Vv}WY!mHfYKuD~{8=(-o<=R$~ zQdG)ts8PQ?R)l>}sw~IkboSy^U5#EBGHm+Sb-mwN6QvUm6@lH7nF)()Im$S4pYl=S zSR3TzYkwpCPR?f0e*>xjhU-}|g~R2~p44Ay#@Yh5XjnJ-DWYR5&ZfG${>wPWIb5Q} zO`YJ&_`t*)NqAU7$~pysNX@5ALU!wNHppum&ptG5bF;Z^FPMqT7|qg%|IUe{<~6Zg zGaYd<;{22LRPJ#@f+IDj)$>=l4asqc!+xQlvAm5XTE6j823Iu4&l(n!axF4lNZ)fV zR>j+qi|fwcL+O6%08Md7KCK68!2{Uo+m?ke$b)-d&(sCQ4p1n`tDc|SI_KI!3vg=BT& z0BrPXN&Uw9fJa};<6T!F)3fy>{yytGllzw|>)F@un$TOkE>!l>#kl8UF_l2v<*MXx z8*n!_6b1Bvbu1fipuWp^J>0-${dl*R^WHcv#CCLjD$BR3e`u}H>?7Z)lYpt95`aa+ zQ)LJBlESsE_ga_ZyUjgMxI^Zy(GOkr>!)i-q=0W71Z3)}$x;L7b13e@>FL?qntG`* z%9~2VVEY-aC)1X|e_L4$E?;$6?pw2e)Aw_4QCq=ti?(Jn(^IW1pV5vv85fDhj3Ym7 zqL@=1J9j7wkt8yc34k*u@uhLAV-LpVw$Y&aC~`8uGQWsz1+`wR87uzC3vqr~6U)on zT$|0VzN-N=nAjzUl`-T$PQsI%2pSN#xT=;@EXwJ%ENvKT)J)abkAjY)w+BnQ5#QQp zS(PJRoam+|_4|TZwZhhW6qKP0Kv)SNt_y2=k|J1TPEP!Xe$x*k4> z@woobs!LQzFj<%}bbT<$xVD8y=sIdf_q0V?E2sh=R}K{A>z_~rhBbx*9%^s ze~|N97o+Ohe+o8TE41k97P?ikRQ~EA*F~|r%UYtMPrM?t(QbaVJGfq(%dmqS0T0^5reWF@CzbGDvYx{2=|Bqh@sgkdY zajfp($$n`RWHHE|F@sOb8obX{<*SAoXsCpKrNXGpnM0;1t`0RnN;g>)k4+eGVh-;K z>4d^9c9%?5a`wL0VKfM*_rA0!mgx$E=dad%ebrb)Qw2c&zG475)uKU03ksOzQBThh0TUl z3h>#O*|58OY|YiRUTeJuo?L29U4E}U@qVBMJ|`?;ffK{fmzNTOM1O~2&`Ut~@MQTW zVB$mAn3yyPvJrbY7z21Yg!U|=0)u5XTeY=z3do7JSVghCMW1u(zwP(msk+>cejpKk z6c$%6ZFgy@<(mi^b-Q7+Zyk};S|mc!6@506z4$5#x%ubIN)ityJIeCHDHZH$N zNfiBv*K_vymoqSAml>kO&QN!!WOta(e{n=q`G+~k?tYo)yI-PkjS*!J)qcL}u8ePw zDI6K2J(GOb>{ZyYdK?>8-av1A*nj~i*^5$^htl^+ zt{~H@oi2diM%h^~=+Gg@A(Kk7Qusr>Q=yK~?fQ5&;PUo-`=h7T=L`>h;khSh_D>=|>9&%1;v8h@!lt5$GOVFCMB>-#-1Yx%IV?N(D`cPB+dhcMYJoe=2sQ&@S+#&wz zUBzKzs=P*oWie+ixrS>IqfpdWJ`@rp^+V+=Lp$S)y@?FI9HT82zt{I-^2 z#%@~b1)%5lFp*cNIvD5g^9v9QKk74oybx}xt37wgvp=FP_iGUU&}re}&B2D1@`j6# zD6|kULy~J=)X1PK9UJwJd+)D)8ye?5ZT9frIEf}UJp8Gs85+phlup)P{%9SvlY_Gk zDA2q%)H@gpo-M<8_1h}644UQ2e_KSitk;Qj&i*htz)46cEt?ZW=;Z^)X zA7SjiZdosh zVq~^F*ru`I7-vf$hl0(Q37G0Okdkr~Bzv?&l6bWqI8>-cuyrb;R9~=R0kM7u|F|}_ zVeyE)_>n~6Mapr4%bdDHH{{5Ud2>x7yHlWV?u-W4>;%k^i)CaMYe$i2(La4uWPGc~ z6u#&%!M%=u`bTyJ{SmN|BrJH;WW7w%12Hj&}ILua=C^d39~>2S5@_ z;s<8!4a>oD{EebH2B%PGBfiNfHz!@3{51otC$uUsqu4_UcXj6OBIEnObc$Xo`1^RZ zl{xhJpdT(rR!%3^BlWbx0 zWRIh$$jM&)q>@yps-aRQOXR@37=tnT%vJ*6Q9>Hs|5PVne)<+AiAsif^P%k%XOH!I zPj)8LhDJYL`a557fl7NoR)`z;TTL^3=m>q4-{-< zRcOp%_566PFtPiEn(b5egdzoC#w-M!R99AVu6=$Z^!b0;Gv7kBTfNb;8@8oD&j`n1 z|7g?dMIRt{2`(30l2xvaR;XMqeO{-E4gvO?|Hef37L0fyCbo@4yi>gbn_oFe#V5UA zR2C*OOnBxSr=c!UUx6LeYFkCBK=Iygdi|iS;3SFpuyL7p8@vB?QOJ=03;wvFujIVF z&tj>$VW4%1ViurI?wiUaqBodgVKrHVbX@$Se2G0nBX-Jf&HZOjY24x;cXQ7>hIs@+ zD3pDip*}azy^bwC8X7(#Zw^T`*X5r8W5VW|C{;n{9|eo9=Vn%*rtKK7&$>gi*2hLv zSc2uuYwJw-sYTK8s9a|(B5jJSQ$YNj64W3E^lu+bLJDGB_YCdV9o}YB`Fie57BG{o zr1X|`SiKxSvF-($DaBPmO+o389f1h}dt(n4gY2VM9O}=@$+m08<1~_{#3)wnLzt`8 z;TAD&4Am2z&E!I)IZ(+7koFoiMerNdTST&}E1M`!OiM{_p4aeqo|xWZ->3sCoXx!E zKTvK{21pdcQZO30Zk6~(H19{rk41Q)=@pX51!coa;O-xLY z{v94}8#?{4Wx+u5zU*fv9z~$CYsB3c7R@3F_^!81=HzK)Y7f2m>|@>Fi4R@f1y&Tj zvDI}ZwvD2FBcczZO?+~{jWYs(AkR=n=4}+$Mzr%bt0S+puIbvJpQX%Swfhg$;2>B$ zS-^F|42MF_qkVQ-f$-$uH^kqM4~qFk9Vfm_A9br(Sr6IwDg~5%k5*;&^QpIuMfefF z%TXB_4oSgI7>vdQnW(!?p`_!KQvl1|s@tPVPlPh_#PQoasL1YhQtw-a{AKjT=FI7~ zXuE>nzo(zk_B<|57{r`-l> zcZM8OySm049`1rDVTGL<`$nn;E~!?Q)J^Y)sQrA84KxPPlmfa*UZU7;4Jr~6-AY!{ z989df{R73K@@hV9NV#`gDg9Zo260D|z0TS?j{Osk%SK?$xI>VeDwH|oljeZ_A&t19nNT(jB^Rumh z2Q;=63`-IZze`?^03SCF#gy7zZpnDsbFTG*v?LLeaCF=Mikqco*QoD4Yd=b+L#<9@ zxjN6?OqnehW+3rrJ3x-s1-n_Hj_^|tZCNybb5k1MUzdG~S(p4`M=}R4%EHe42TBPZ zyy8Fwqn#q8M=9c@v0qB^twnuaBRwJwZi^-4fY}y@P?yi{F2u^{Gkp&z1_T4M6mAXOa*+;`&tuPP!ZIhN7mb^ELwx z+tVa45aAi-q{#5zNo82u>Tk(VNp>5MX=@q*gFt8TH5=HH^|>U)l(K9d;fDn~!P3=} z%Vp@^O2X-q>)SnpDJ}SOCp7~Z89AD?bX7|#;6K>~e_zO7?Dp7?(6?&z(IKC@a;H2k zZH0gj=(Q`%r%V(I2;%m675{VX#(f_y;2rCf+7I=BaBJe|S{_H5Celg@MVm58f5^!^ z4^U}i5i_r9^?a{xB;HorYx>l)H+d5L5{?4ZsU}PwC~G_L!s4bB?7e%%yROqCi^l-? zn5vcn%`_}Vt5rZ-*yR6=r2Ff$izW_x2;Rar5-H$?BUthY;M5n7q&jn$0u0xhuwwch zIrp?4b0u17VJbFBVGPOc3lW^DI|?j|#m6e`k-4p_N#P@WW*B|e$z*eYTJLq?gkGO<B#vH5T&Ywdv57OcX3+c9S zIIGi~wme-1K}18II1JPwgb6cU!Kb6Dwd5a;E;IzKn47|W2Em{aeaTXW4dOcK`GF-_ zl877mQ>^8C8o{~G%|soTF`dy6)lYn~T>_|ee!Cse{O9;hO>GgC=t1rL-tbohFIPH* z_OupKA)~EETj){w1i2Q3q4C^uZAbawnc&H#%RzC*UZc*O%6cOkuDgK&Lby=f89zn& z;Dq+n!hLUT5HD8V#pGA5bv1lvm7jkjC4#4TGeIQrV+|JtiJzmb+x6&*Eukj38b}2P_HIH()vVt<{qY zAs+5H#ij_)S#>yHnW9o8A3y(|Y`kj7-`JX{dZ~{yl*JxD=z@^e74i;GY1xD7dqN9U zM!poR8lsX+e4CQlBoKt06%P0J8!g5(e{dGG2d*T14 z#rd7m`)j!MFYofa&~4!yAr%C`esRk47&6e_2x*6W5 zPRF9@t6`aGajqGsh=?H=vPHLzGo*c{8kWBTO4rP;(TIY%mh8uy;hmv8kyr`O17hvulq8YaDKap}7P%|?!_kRrkcOoZUq1^%wS}lL=Ptx$`M2W# z;?E*qSA36i-XC!r(;PdoZVLeU1+j=KWbzr{FBUrrCHJ!cXA~4g!Tw&DESzVH`Uz{# z-sKKcKf{3+v|5DZ(tV|ILA?|Q#>x>8gfaqw|9;_r8Q|YZuK#RP|0x`y<);Vb?$K|$ z`qn99$P!G6B){P88Zt%DS3PQ9=UMK}p5Z zaygRZW$)Z_5Hb64Ub@Uj?D?3KJ+RvkSyD{^d2v0H2z}kS7ToYfy)nrAA?fh(Ktjfl znEb0=3df!#yDWJVKi0$La?xjNSuyH;=(g(@ckfwL3J+!#ZIfJIGQeP=U zLKheDNRlM}K+8!ZD|V#KtEqE007LUYZ^M12)Jx-aR2 zs`TGB1`mnTUgNB<_C-O8H)|sfQ5z(ffkDs!Q1oMyt(@DZvBFYew7L5-&Kw03X;4)H zY?-7-Ii>LG5_-)y^+Go4Z1-isiR=3-4>wN)DznYWC80)8 z`MQcXl-hqW~4!ktctN?W9Vn4!CO}@i0h^SUYj423H_Zb9d*io_A%&dvzKI;4koEiYl zJzWyN#5}p{bx6T;YQYeHP8G@1n9PM8!=T{6uHEO_a#|Zgi+<>L%}+N|HFxkX(Kq}| zZ_@(eqduX&$V5$NvesQ1nKOq?Gqe-+{rB00D(?Qa<`YzI;VaX>`iMFlP)|#eqnue_->uYh5P2W)9=dw@w(!})B4=)7ZksI_0P3?-2c75VLmyPuJ@P&}v7G2islLQ< z0)-PFT)Sb9J-AOt0Z-=URK2_nxYHMG*w_S-Q>$#Y0c>2Q`hO-P`RYgweE90m@!LYy zi%!YHHbg!PsatOFWtlQGO4xr|A_mGy!?fsjUZ9dUM9{5F+8HC%Wdgey z>w4`iqn!;N8({E_)+fGExX%y#-bhTHf1E-nZIi;VnxE5$rKz&R*bP^BjEzan!N@?d zF!gLaVo-pbV{MB?fq@s)G5RugB(vdOi%cttQI=H^jRIdbg7-Ocr~T^M_{~}TABgAA zWbJ<@z~7eBUF7MKQTU@ZIch#u-ejd}V}p$P>etdXk{6%4GRLt^AFXv$J*>kH( zy!!+lhQhet;=^dTCw;11(ixJ2B1rj#@nv_I-miRRe)<$Yi^S+HeLc}9PL-a_DM1;u zMxtVt7O7q2F!Lch3%ATsOtLu-xg5bZs-9)xYe$CqCB}SS80>{23kEb(+Iz|PoUrrg zP&*?ajTvvq+UdO4K|_jSA{>+x0Ln=ycewRexRLO)NzUL-Q%s|!V#ji9M3S(xvSLns zbP00jYb&?rM#}2{?e6~huzteB^3G>bm$0Xc5qB_b!9r{TErfSH^aU};YxV?8J|V9$ z_ZSV3+1o@af+|`Cr=oYeb0YKd&Yr^8k-OM_8TtYW>gr2ITTn#baIc^7rVaRL`Yp zL_JgfxNg^|Zm1vyjn7oItClZD51J7-_b6M8%4h4*_%8e;cATP5Twvp$xHBgzFfe%fvVNpL3vZ;tSYz}y%lt3hFmwz2h{MKG}qpkyCMmrJ5tiw5^?JlQd&M6MkNizOI z_}kN)$akL%YXO*i-J}(G!bdR7r%=piC#}ScS03BF@qv9m4zsVr<#?g8TDl0_m|H z(b1E{Rt0pDVbJ6$av*U@05(kGgo44rw}_?am-qDl(2d7i6b~0WI*xasv8oa=c|l9f zsR3^K1H}pluNc9o$f8pzp%Te#?mg!0d%o6=fS3?=^#kqL_mYujRYs@G;3MZ!s_ z^Z~{W_oy(137Q^1={>$Aw>VvSY)SQ2hyqGn%+Q+U>`;a!*T#|PQn1}&?`6CEHO&2u zR_cVaiq+&}@~>>_zCmjf0|o|BJQ*KpC9-v>s}o67bX#8zB$IGX z-Y6+Js9H1epw*&g)+iS_7VhT+?pD>Ir^(0BcQ}gkaD4!$!8J5!`&ChQ6tDq{iv

+pkY_m1M5iG8a(fohiVm`+8Hu>@BVb<(VNy*t`H3;2t2-(oByT_jKGu@EXQay*T`JPN4-xWV4kP9Pk+%V}JV7 zg35uT0j^b?_lG#%9?9NMsB5Ve<-n<8TUT-z!WsZZ!! /home/xmrig/.xmrig.json + +ENTRYPOINT [ "/usr/bin/xmrig" ] diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index 1881c45448..51fc346d9d 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -55,4 +55,4 @@ default-features = false features = ["crossterm"] [features] -avx2 = [] +avx2 = ["tari_core/avx2", "tari_crypto/avx2", "tari_wallet/avx2", "tari_comms/avx2", "tari_comms_dht/avx2", "tari_p2p/avx2", "tari_key_manager/avx2"] diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index 1caa9c979a..2803d8f9fb 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -285,12 +285,12 @@ pub fn grpc_mode(config: WalletModeConfig, wallet: WalletSqlite) -> Result<(), E let WalletModeConfig { global_config, handle, .. } = config; - println!("Starting grpc server"); + info!(target: LOG_TARGET, "Starting grpc server"); let grpc = WalletGrpcServer::new(wallet); handle .block_on(run_grpc(grpc, global_config.grpc_console_wallet_address)) .map_err(ExitCodes::GrpcError)?; - println!("Shutting down"); + info!(target: LOG_TARGET, "Shutting down"); Ok(()) } diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index 0b8e6333af..3b80e8dacf 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -115,7 +115,8 @@ const LMDB_DB_ORPHAN_PARENT_MAP_INDEX: &str = "orphan_parent_map_index"; pub fn create_lmdb_database>(path: P, config: LMDBConfig) -> Result { let flags = db::CREATE; - let _ = std::fs::create_dir_all(&path); + debug!(target: LOG_TARGET, "Creating LMDB database at {:?}", path.as_ref()); + std::fs::create_dir_all(&path)?; let file_lock = acquire_exclusive_file_lock(&path.as_ref().to_path_buf())?; @@ -145,6 +146,7 @@ pub fn create_lmdb_database>(path: P, config: LMDBConfig) -> Resu .add_database(LMDB_DB_ORPHAN_PARENT_MAP_INDEX, flags | db::DUPSORT) .build() .map_err(|err| ChainStorageError::CriticalError(format!("Could not create LMDB store:{}", err)))?; + debug!(target: LOG_TARGET, "LMDB database creation successful"); LMDBDatabase::new(lmdb_store, file_lock) } diff --git a/base_layer/wallet/tests/output_manager_service/service.rs b/base_layer/wallet/tests/output_manager_service/service.rs index 30ef5c1920..b99ed40b30 100644 --- a/base_layer/wallet/tests/output_manager_service/service.rs +++ b/base_layer/wallet/tests/output_manager_service/service.rs @@ -60,7 +60,7 @@ use tari_crypto::{ }; use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic}; use tari_p2p::Network; -use tari_service_framework::reply_channel; +use tari_service_framework::{reply_channel, reply_channel::SenderService}; use tari_shutdown::Shutdown; use tari_wallet::{ base_node_service::{ diff --git a/buildtools/docker_rig/README.md b/buildtools/docker_rig/README.md deleted file mode 100644 index 1e38bb60ed..0000000000 --- a/buildtools/docker_rig/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# The complete Docker guide to Tari - -## Quick Start - -Install docker - -Run the universal installer -Select 'docker' as the system layout -Configure the containers to run -Other config options incl tor password -"Go!" - -## Layout - - +-----------------------+ - | | -+---->| Console Wallet +------------------+ -| | | | -| +----------+------------+ | -| | | -| | gRPC | -| | | -| | | -| +----------v------------+ +------v-----+ -| | | Socks5 | | -| | Base Node +---------->| Tor |----> Network -| | | | | -| +----------^------------+ +------------+ -| | -| | -| | -| +----------+------------+ -| | | -+-----+ SHA3-Miner | -| | | -| +-----------------------+ -| -| -| -| +-----------------------+ -| | | -+-----+ XMRRig etc | - | | - +-----------------------+ - -#### Notes - -Building docker images: - -``` -cd buildtools/docker_rig -docker build -t quay.io/tarilabs/tor:latest -f base_node.Dockerfile . -docker build -t quay.io/tarilabs/tari_base_node:latest -f base_node.Dockerfile ../../ -``` - -Base node/Wallet config for using the Tor docker container: - -```toml -tcp_listener_address = "/ip4/0.0.0.0/tcp/18189" -transport = "tor" -tor_control_address = "/dns4/tor/tcp/9051" -tor_control_auth = "password=asdf" # replace with your configured password -tor_onion_port = 18141 -tor_forward_address = "/ip4/0.0.0.0/tcp/18189" -tor_socks_address_override="/dns4/tor/tcp/9050" -``` - -When attaching to a running container: - -To detach the tty without exiting the shell/program, use the escape sequence ^P^Q (Ctrl+P followed by Ctrl+Q). diff --git a/buildtools/docker_rig/docker-compose.yml b/buildtools/docker_rig/docker-compose.yml deleted file mode 100644 index ff149cce2c..0000000000 --- a/buildtools/docker_rig/docker-compose.yml +++ /dev/null @@ -1,93 +0,0 @@ -version: "3.9" -services: - tor: - image: quay.io/tarilabs/tor:latest - build: - context: . - dockerfile: tor.Dockerfile - ports: - - 9050:9050 - - 9051:9051 - wallet: - image: quay.io/tarilabs/tari_console_wallet:latest - build: - context: ./../.. - dockerfile: buildtools/docker_rig/console_wallet.Dockerfile - args: - ARG WALLET_ARCH: x86-64 - ports: - - 18188:18188 - environment: - TARI_LOG_CONFIGURATION: "/var/tari/config/log4rs.yml" - APP_NAME: wallet - APP_EXEC: tari_console_wallet - CREATE_CONFIG: 1 - CREATE_ID: 1 - WAIT_FOR_TOR: 60 - TARI_NETWORK: weatherwax - SHELL: "/bin/bash" - TERM: "linux" - PASSWORD: "asdf" - TARI_WALLET__WEATHERWAX__TOR_CONTROL_AUTH: "password=asdf" - TARI_WALLET__WEATHERWAX__TOR_CONTROL_ADDRESS: "/dns4/tor/tcp/9051" - TARI_WALLET__WEATHERWAX__TOR_SOCKS_ADDRESS_OVERRIDE: "/dns4/tor/tcp/9050" - TARI_WALLET__WEATHERWAX__TOR_FORWARD_ADDRESS: "/ip4/0.0.0.0/tcp/18188" - TARI_WALLET__WEATHERWAX__TCP_LISTENER_ADDRESS: "/ip4/0.0.0.0/tcp/18188" - command: [] - depends_on: - - tor - volumes: - - $HOME/.tari/config:/var/tari/config - - $HOME/.tari/wallet:/var/tari/wallet - - $HOME/.tari/wallet/log:/var/tari/log - stdin_open: true - tty: true - base_node: - image: quay.io/tarilabs/tari_base_node:latest - build: - context: ./../.. - dockerfile: buildtools/docker_rig/base_node.Dockerfile - args: - ARG WALLET_ARCH: x86-64 - environment: - TARI_LOG_CONFIGURATION: "/var/tari/config/log4rs.yml" - APP_NAME: base_node - APP_EXEC: tari_base_node - CREATE_CONFIG: 1 - CREATE_ID: 1 - WAIT_FOR_TOR: 60 - TARI_NETWORK: weatherwax - TARI_BASE_NODE__WEATHERWAX__TOR_CONTROL_AUTH: "password=asdf" - TARI_BASE_NODE__WEATHERWAX__TOR_CONTROL_ADDRESS: "/dns4/tor/tcp/9051" - TARI_BASE_NODE__WEATHERWAX__TOR_SOCKS_ADDRESS_OVERRIDE: "/dns4/tor/tcp/9050" - TARI_BASE_NODE__WEATHERWAX__TOR_FORWARD_ADDRESS: "/ip4/0.0.0.0/tcp/18189" - TARI_BASE_NODE__WEATHERWAX__TCP_LISTENER_ADDRESS: "/ip4/0.0.0.0/tcp/18189" - ports: - - 18189:18189 - command: [] - depends_on: - - tor - volumes: - - $HOME/.tari/config:/var/tari/config - - $HOME/.tari/base_node:/var/tari/base_node - - $HOME/.tari/base_node/log:/var/tari/log - stdin_open: true - tty: true -# xmrig: -# sha3-miner: -# pool-worker: -# pool-operator: - -#volumes: -# config: -# driver: local -# driver_opts: -# o: bind -# type: none -# device: $HOME/.tari/config -# data: -# driver: local -# driver_opts: -# o: bind -# type: none -# device: $HOME/.tari/data diff --git a/buildtools/docker_rig/start_base_node.sh b/buildtools/docker_rig/start_base_node.sh deleted file mode 100755 index 6d6235bd97..0000000000 --- a/buildtools/docker_rig/start_base_node.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash -# -# Docker Start Script for Tari applications -# The docker compose environment should set the following envars -# - APP_NAME - the name of the app to run. This var is used to set the location of log files, and app-specific config -# - APP_EXEC - the name of the application executable. Just the name is enough, since the Dockerfile will put it in /usr/bin -# - CREATE_CONFIG - set to 1 if we should write a default config file if one is missing. -# - CREATE_ID - set to 1 if we should create an id file for this application if one is missing. It will be called -# {network}_{app_name}_id.json -# - WAIT_FOR_TOR - set to 1 to place a 30 second delay at the beginning of this script. -# - TARI_NETWORK - the Tari network to configure the docker rig for -# - -APP_NAME=${APP_NAME:-base_node} -APP_EXEC=${APP_EXEC:-tari_base_node} -CREATE_CONFIG=${CREATE_CONFIG:-0} -CREATE_ID=${CREATE_ID:-0} -WAIT_FOR_TOR=${WAIT_FOR_TOR:-0} -NETWORK=${TARI_NETWORK:-weatherwax} -TARI_BASE=/var/tari/$APP_NAME -CONFIG=/var/tari/config - -echo "Starting $APP_NAME with following docker environment:" -echo "executable: $APP_EXEC" -echo "network: $NETWORK" -echo "CREATE_CONFIG: $CREATE_CONFIG" -echo "CREATE_ID: $CREATE_ID" -echo "WAIT_FOR_TOR: $WAIT_FOR_TOR" -echo "base folder (in container): $TARI_BASE" -echo "config folder (in container): $CONFIG" - -if [[ $WAIT_FOR_TOR != 0 ]]; then - echo "Waiting $WAIT_FOR_TOR seconds for Tor to start up" - sleep "$WAIT_FOR_TOR" -fi - -cd "$TARI_BASE" || exit 1 - -ARGS=() -if [[ $CREATE_CONFIG == 1 && ! -f $CONFIG/config.toml ]]; then - echo "Creating config file." - ARGS+=("--init") -fi - -ID_FILENAME=${NETWORK}_${APP_NAME}_id.json -if [[ $CREATE_ID && ! -f $ID_FILENAME ]]; then - echo "Creating network identity file ($ID_FILENAME)." - ARGS+=("--create-id") -fi - -if [ -n "${ARGS[0]}" ]; then - echo "Initializing." - $APP_EXEC -b "$TARI_BASE" -c "$CONFIG/config.toml" "${ARGS[@]}" || exit 1 -fi - -$APP_EXEC "$@" diff --git a/buildtools/docker_rig/start_wallet.sh b/buildtools/docker_rig/start_wallet.sh deleted file mode 100755 index 8cebe3949b..0000000000 --- a/buildtools/docker_rig/start_wallet.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -# -# Docker Start Script for Tari applications -# The docker compose environment should set the following envars -# - APP_NAME - the name of the app to run. This var is used to set the location of log files, and app-specific config -# - APP_EXEC - the name of the application executable. Just the name is enough, since the Dockerfile will put it in /usr/bin -# - CREATE_CONFIG - set to 1 if we should write a default config file if one is missing. -# - CREATE_ID - set to 1 if we should create an id file for this application if one is missing. It will be called -# {network}_{app_name}_id.json -# - WAIT_FOR_TOR - set to 1 to place a 30 second delay at the beginning of this script. -# - TARI_NETWORK - the Tari network to configure the docker rig for -# - -APP_NAME=${APP_NAME:-wallet} -APP_EXEC=${APP_EXEC:-tari_console_wallet} -CREATE_CONFIG=${CREATE_CONFIG:-0} -CREATE_ID=${CREATE_ID:-0} -WAIT_FOR_TOR=${WAIT_FOR_TOR:-0} -NETWORK=${TARI_NETWORK:-weatherwax} -TARI_BASE=/var/tari/$APP_NAME -CONFIG=/var/tari/config - -echo "Starting $APP_NAME with following docker environment:" -echo "executable: $APP_EXEC" -echo "network: $NETWORK" -echo "CREATE_CONFIG: $CREATE_CONFIG" -echo "CREATE_ID: $CREATE_ID" -echo "WAIT_FOR_TOR: $WAIT_FOR_TOR" -echo "base folder (in container): $TARI_BASE" -echo "config folder (in container): $CONFIG" -echo "wallet password: $PASSWORD" # delete this - -if [[ $WAIT_FOR_TOR != 0 ]]; then - echo "Waiting $WAIT_FOR_TOR seconds for Tor to start up" - sleep "$WAIT_FOR_TOR" -fi - -cd "$TARI_BASE" || exit 1 - -if [[ $CREATE_CONFIG == 1 && ! -f $CONFIG/config.toml ]]; then - $APP_EXEC --init --password "$PASSWORD" "$@" -else - $APP_EXEC --password "$PASSWORD" "$@" -fi - -# $APP_EXEC "$INIT" --password "$PASSWORD" "$@" diff --git a/common/src/configuration/bootstrap.rs b/common/src/configuration/bootstrap.rs index 472624ed4b..a93439b0cf 100644 --- a/common/src/configuration/bootstrap.rs +++ b/common/src/configuration/bootstrap.rs @@ -116,8 +116,10 @@ pub struct ConfigBootstrap { /// This will clean out the orphans db at startup #[structopt(long, alias = "clean_orphans_db")] pub clean_orphans_db: bool, - /// Supply the password for the console wallet - #[structopt(long)] + /// Supply the password for the console wallet. It's very bad security practice to provide the password on the + /// command line, since it's visible using `ps ax` from anywhere on the system, so always use the env var where + /// possible. + #[structopt(long, env = "TARI_WALLET_PASSWORD")] pub password: Option, /// Change the password for the console wallet #[structopt(long, alias = "update-password")] diff --git a/common/src/configuration/global.rs b/common/src/configuration/global.rs index b35004b990..cb6a4dc13b 100644 --- a/common/src/configuration/global.rs +++ b/common/src/configuration/global.rs @@ -149,7 +149,6 @@ impl GlobalConfig { let env = Environment::with_prefix("tari").separator("__"); cfg.merge(env) .map_err(|e| ConfigurationError::new("environment variable", &e.to_string()))?; - let network = one_of::(&cfg, &[ &format!("{}.network", application.as_config_str()), "common.network", diff --git a/integration_tests/features/Reorgs.feature b/integration_tests/features/Reorgs.feature index cbeac43c7b..4003cabb07 100644 --- a/integration_tests/features/Reorgs.feature +++ b/integration_tests/features/Reorgs.feature @@ -146,7 +146,7 @@ Feature: Reorgs # Add multiple base nodes to ensure more robust comms And I have a base node NODE_A1 connected to seed SEED_A1 And I have a base node NODE_A2 connected to seed SEED_A1 - When I mine blocks on SEED_A1 with difficulty 1 + When I mine blocks with difficulty 1 on SEED_A1 Then all nodes are on the same chain at height # # Chain 1b: @@ -156,7 +156,7 @@ Feature: Reorgs # Add multiple base nodes to ensure more robust comms And I have a base node NODE_A3 connected to seed SEED_A2 And I have a base node NODE_A4 connected to seed SEED_A2 - When I mine blocks on SEED_A2 with difficulty 1 + When I mine blocks with difficulty 1 on SEED_A2 Then node NODE_A3 is at height Then node NODE_A4 is at height # @@ -176,7 +176,7 @@ Feature: Reorgs # Add multiple base nodes to ensure more robust comms And I have a base node NODE_B1 connected to seed SEED_B1 And I have a base node NODE_B2 connected to seed SEED_B1 - When I mine blocks on SEED_B1 with difficulty 1 + When I mine blocks with difficulty 1 on SEED_B1 Then node NODE_B1 is at height Then node NODE_B2 is at height # @@ -187,7 +187,7 @@ Feature: Reorgs # Add multiple base nodes to ensure more robust comms And I have a base node NODE_B3 connected to seed SEED_B2 And I have a base node NODE_B4 connected to seed SEED_B2 - When I mine blocks on SEED_B2 with difficulty 1 + When I mine blocks with difficulty 1 on SEED_B2 Then node NODE_B3 is at height Then node NODE_B4 is at height # diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index 9320964ed8..47241ef1f8 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -1331,9 +1331,9 @@ When( ); When( - /I mine (.*) blocks on (.*) with difficulty (.*)/, + /I mine (.*) blocks with difficulty (.*) on (.*)/, { timeout: 20 * 1000 }, - async function (numBlocks, node, difficulty) { + async function (numBlocks, difficulty, node) { const miner = await this.createMiningNode("temp", node, "temp"); await miner.init( parseInt(numBlocks), diff --git a/integration_tests/features/support/world.js b/integration_tests/features/support/world.js index dcf2b00149..667d0c7b19 100644 --- a/integration_tests/features/support/world.js +++ b/integration_tests/features/support/world.js @@ -75,8 +75,8 @@ class CustomWorld { } async createAndAddNode(name, addresses) { + console.log(`Creating node ${name} connected to ${addresses}`); const node = this.createNode(name); - console.log(`Creating node ${name} with ${addresses}`); if (addresses) { if (Array.isArray(addresses)) { node.setPeerSeeds(addresses); @@ -102,6 +102,7 @@ class CustomWorld { } async createAndAddWallet(name, nodeAddresses, options = {}) { + console.log(`Creating wallet ${name} connected to ${nodeAddresses}`); const wallet = new WalletProcess( name, false, diff --git a/integration_tests/helpers/walletProcess.js b/integration_tests/helpers/walletProcess.js index 45c0a111a7..21149cfc5d 100644 --- a/integration_tests/helpers/walletProcess.js +++ b/integration_tests/helpers/walletProcess.js @@ -60,7 +60,7 @@ class WalletProcess { } run(cmd, args, saveFile, input_buffer, output) { - return new Promise((resolve, reject) => { + let thePromise = new Promise((resolve, reject) => { if (!fs.existsSync(this.baseDir)) { fs.mkdirSync(this.baseDir, { recursive: true }); fs.mkdirSync(this.baseDir + "/log", { recursive: true }); @@ -142,10 +142,11 @@ class WalletProcess { resolve(ps); } }); - expect(ps.error).to.be.undefined; this.ps = ps; + resolve(ps); }); + return thePromise; } async startNew() { diff --git a/integration_tests/package-lock.json b/integration_tests/package-lock.json index 69893aa509..16ee31915f 100644 --- a/integration_tests/package-lock.json +++ b/integration_tests/package-lock.json @@ -5,35 +5,48 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.15.8", - "resolved": false, - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", "dev": true, "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.16.0" + }, + "dependencies": { + "@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + } } }, "@babel/compat-data": { - "version": "7.15.0", - "resolved": false, - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.0.tgz", + "integrity": "sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==", "dev": true }, "@babel/core": { - "version": "7.15.8", - "resolved": false, - "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.15.8", - "@babel/generator": "^7.15.8", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.8", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.8", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.0.tgz", + "integrity": "sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.0", + "@babel/helper-compilation-targets": "^7.16.0", + "@babel/helper-module-transforms": "^7.16.0", + "@babel/helpers": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -43,9 +56,9 @@ } }, "@babel/eslint-parser": { - "version": "7.15.8", - "resolved": false, - "integrity": "sha512-fYP7QFngCvgxjUuw8O057SVH5jCXsbFFOoE77CFDcvzwBVgTOkMD/L4mIC5Ud1xf8chK/no2fRbSSn1wvNmKuQ==", + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.16.3.tgz", + "integrity": "sha512-iB4ElZT0jAt7PKVaeVulOECdGe6UnmA/O0P9jlF5g5GBOwDVbna8AXhHRu4s27xQf6OkveyA8iTDv1jHdDejgQ==", "dev": true, "requires": { "eslint-scope": "^5.1.1", @@ -63,128 +76,128 @@ } }, "@babel/generator": { - "version": "7.15.8", - "resolved": false, - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", + "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", "dev": true, "requires": { - "@babel/types": "^7.15.6", + "@babel/types": "^7.16.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", + "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", + "@babel/compat-data": "^7.16.0", "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", + "browserslist": "^4.17.5", "semver": "^6.3.0" } }, "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", + "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-get-function-arity": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", + "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.0" } }, "@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", + "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.0" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz", + "integrity": "sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.0" } }, "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", + "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.0" } }, "@babel/helper-module-transforms": { - "version": "7.15.8", - "resolved": false, - "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz", + "integrity": "sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-replace-supers": "^7.16.0", + "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", + "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.0" } }, "@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz", + "integrity": "sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/helper-member-expression-to-functions": "^7.16.0", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", + "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.0" } }, "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", + "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" + "@babel/types": "^7.16.0" } }, "@babel/helper-validator-identifier": { @@ -200,14 +213,14 @@ "dev": true }, "@babel/helpers": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.3.tgz", + "integrity": "sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w==", "dev": true, "requires": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.3", + "@babel/types": "^7.16.0" } }, "@babel/highlight": { @@ -222,46 +235,46 @@ } }, "@babel/parser": { - "version": "7.15.8", - "resolved": false, - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.3.tgz", + "integrity": "sha512-dcNwU1O4sx57ClvLBVFbEgx0UZWfd0JQX5X6fxFRCLHelFBGXFfSz6Y0FAq2PEwUqlqLkdVjVr4VASEOuUnLJw==", "dev": true }, "@babel/template": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", + "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", "dev": true, "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" + "@babel/code-frame": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/types": "^7.16.0" } }, "@babel/traverse": { - "version": "7.15.4", - "resolved": false, - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz", + "integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.0", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/parser": "^7.16.3", + "@babel/types": "^7.16.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.15.6", - "resolved": false, - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-validator-identifier": "^7.15.7", "to-fast-properties": "^2.0.0" } }, @@ -468,9 +481,9 @@ } }, "@grpc/grpc-js": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.4.1.tgz", - "integrity": "sha512-/chkA48TdAvATHA7RXJPeHQLdfFhpu51974s8htjO/XTDHA41j5+SkR5Io+lr9XsLmkZD6HxLyRAFGmA9wjO2w==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.4.4.tgz", + "integrity": "sha512-a6222b7Dl6fIlMgzVl7e+NiRoLiZFbpcwvBH2Oli56Bn7W4/3Ld+86hK4ffPn5rx2DlDidmIcvIJiOQXyhv9gA==", "dev": true, "requires": { "@grpc/proto-loader": "^0.6.4", @@ -842,16 +855,16 @@ } }, "browserslist": { - "version": "4.17.3", - "resolved": false, - "integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==", + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.6.tgz", + "integrity": "sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001264", - "electron-to-chromium": "^1.3.857", + "caniuse-lite": "^1.0.30001274", + "electron-to-chromium": "^1.3.886", "escalade": "^3.1.1", - "node-releases": "^1.1.77", - "picocolors": "^0.2.1" + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" } }, "buffer": { @@ -891,9 +904,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001265", - "resolved": false, - "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", + "version": "1.0.30001279", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz", + "integrity": "sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ==", "dev": true }, "capital-case": { @@ -1181,9 +1194,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.864", - "resolved": false, - "integrity": "sha512-v4rbad8GO6/yVI92WOeU9Wgxc4NA0n4f6P1FvZTY+jyY7JHEhw3bduYu60v3Q1h81Cg6eo4ApZrFPuycwd5hGw==", + "version": "1.3.894", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.894.tgz", + "integrity": "sha512-WY8pA4irAZ4cm/Pr7YFPtPLVqj3nU6d0SbfoHF6M7HZNONfPdAnYAarumqQ75go2LuN72uO9wGuCEqnfya/ytg==", "dev": true }, "emoji-regex": { @@ -1493,9 +1506,9 @@ } }, "eslint-plugin-import": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz", - "integrity": "sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==", + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz", + "integrity": "sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg==", "dev": true, "requires": { "array-includes": "^3.1.4", @@ -1503,9 +1516,9 @@ "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.0", + "eslint-module-utils": "^2.7.1", "has": "^1.0.3", - "is-core-module": "^2.7.0", + "is-core-module": "^2.8.0", "is-glob": "^4.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.5", @@ -1531,6 +1544,15 @@ "esutils": "^2.0.2" } }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "ms": { "version": "2.0.0", "resolved": false, @@ -2548,9 +2570,9 @@ "dev": true }, "node-releases": { - "version": "1.1.77", - "resolved": false, - "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", "dev": true }, "normalize-path": { @@ -2711,9 +2733,9 @@ "dev": true }, "picocolors": { - "version": "0.2.1", - "resolved": false, - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, "pkg-dir": { diff --git a/integration_tests/package.json b/integration_tests/package.json index 74a0c8b512..7c040ec972 100644 --- a/integration_tests/package.json +++ b/integration_tests/package.json @@ -14,20 +14,20 @@ "author": "The Tari Project", "license": "ISC", "devDependencies": { - "@babel/core": "^7.15.8", - "@babel/eslint-parser": "^7.15.8", + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", "@babel/eslint-plugin": "^7.14.5", - "@grpc/grpc-js": "^1.4.1", + "@cucumber/cucumber": "^8.0.0-rc.1", + "@cucumber/pretty-formatter": "^1.0.0-alpha.1", + "@grpc/grpc-js": "^1.4.4", "@grpc/proto-loader": "^0.5.5", "blakejs": "^1.1.0", "chai": "^4.2.0", - "@cucumber/cucumber": "^8.0.0-rc.1", "cucumber-html-reporter": "^5.5.0", - "@cucumber/pretty-formatter": "^1.0.0-alpha.1", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.25.2", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.1", "eslint-plugin-promise": "^4.3.1", From 23e83988cb8fe99babd0a96686602added75011a Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Fri, 12 Nov 2021 10:42:55 +0300 Subject: [PATCH 16/46] fix: avoid implicit using of the time crate (#3562) Description --- This PR removes implicit `time` crate dependency where possible. Motivation and Context --- `chrono` crate uses a deprecated version of the `time` crate that has security issues. As a result of the investigation, it was found that `chrono` supports `time` for compatibility, but it's not required for most features and could be easily deactivated (by deactivating default features). All `time` relations removed except `tari_app_grpc` that depends on `tari_utilities` that also needed the similar fix. How Has This Been Tested? --- CI --- Cargo.lock | 203 ++++++------------ applications/tari_app_grpc/Cargo.toml | 2 +- applications/tari_base_node/Cargo.toml | 2 +- applications/tari_console_wallet/Cargo.toml | 4 +- .../src/automation/command_parser.rs | 8 +- .../src/automation/commands.rs | 12 +- .../src/automation/error.rs | 5 +- .../tari_console_wallet/src/recovery.rs | 6 +- .../src/ui/components/notification_tab.rs | 23 +- .../src/ui/components/transactions_tab.rs | 55 ++--- .../src/ui/state/app_state.rs | 14 +- .../tari_merge_mining_proxy/Cargo.toml | 4 +- .../src/common/json_rpc.rs | 1 + applications/tari_mining_node/Cargo.toml | 7 +- .../src/stratum/controller.rs | 24 ++- .../tari_stratum_transcoder/Cargo.toml | 4 +- .../src/common/json_rpc.rs | 1 + base_layer/core/Cargo.toml | 2 +- base_layer/key_manager/Cargo.toml | 2 +- base_layer/p2p/Cargo.toml | 2 +- base_layer/wallet/Cargo.toml | 3 +- .../src/output_manager_service/error.rs | 5 - .../wallet/src/transaction_service/error.rs | 12 +- .../wallet/src/transaction_service/mod.rs | 1 + .../protocols/transaction_receive_protocol.rs | 11 +- .../protocols/transaction_send_protocol.rs | 11 +- .../wallet/src/transaction_service/service.rs | 3 +- .../wallet/src/transaction_service/utc.rs | 30 +++ base_layer/wallet_ffi/Cargo.toml | 2 +- comms/Cargo.toml | 2 +- comms/dht/Cargo.toml | 2 +- 31 files changed, 184 insertions(+), 279 deletions(-) create mode 100644 base_layer/wallet/src/transaction_service/utc.rs diff --git a/Cargo.lock b/Cargo.lock index b756e0b2ab..dcb87c9737 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,16 +243,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder", - "safemem", -] - [[package]] name = "base64" version = "0.10.1" @@ -274,6 +264,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64-compat" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +dependencies = [ + "byteorder", +] + [[package]] name = "base64ct" version = "1.1.1" @@ -313,7 +312,7 @@ dependencies = [ "env_logger 0.8.4", "lazy_static 1.4.0", "lazycell", - "log 0.4.14", + "log", "peeking_take_while", "proc-macro2 1.0.32", "quote 1.0.10", @@ -518,7 +517,7 @@ dependencies = [ "clap", "heck", "indexmap", - "log 0.4.14", + "log", "proc-macro2 1.0.32", "quote 1.0.10", "serde 1.0.130", @@ -605,7 +604,7 @@ dependencies = [ "num-integer", "num-traits 0.2.14", "serde 1.0.130", - "time 0.1.44", + "time", "winapi 0.3.9", ] @@ -1348,7 +1347,7 @@ checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" dependencies = [ "atty", "humantime 1.3.0", - "log 0.4.14", + "log", "regex", "termcolor", ] @@ -1361,7 +1360,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime 1.3.0", - "log 0.4.14", + "log", "regex", "termcolor", ] @@ -1374,7 +1373,7 @@ checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "atty", "humantime 2.1.0", - "log 0.4.14", + "log", "regex", "termcolor", ] @@ -1775,7 +1774,7 @@ dependencies = [ "bitflags 1.3.2", "libc", "libgit2-sys", - "log 0.4.14", + "log", "openssl-probe", "openssl-sys", "url 1.7.2", @@ -1905,25 +1904,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "hyper" -version = "0.10.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" -dependencies = [ - "base64 0.9.3", - "httparse", - "language-tags", - "log 0.3.9", - "mime 0.2.6", - "num_cpus", - "time 0.1.44", - "traitobject", - "typeable", - "unicase", - "url 1.7.2", -] - [[package]] name = "hyper" version = "0.14.14" @@ -1954,7 +1934,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.14", + "hyper", "pin-project-lite 0.2.7", "tokio 1.13.0", "tokio-io-timeout", @@ -1967,7 +1947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.1.0", - "hyper 0.14.14", + "hyper", "native-tls", "tokio 1.13.0", "tokio-native-tls", @@ -2087,11 +2067,11 @@ dependencies = [ [[package]] name = "jsonrpc" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436f3455a8a4e9c7b14de9f1206198ee5d0bdc2db1b560339d2141093d7dd389" +checksum = "ad24d69a8a0698db8ffb9048e937e8ae3ee3bc45772a5d7b6979b1d2d5b6a9f7" dependencies = [ - "hyper 0.10.16", + "base64-compat", "serde 1.0.130", "serde_derive", "serde_json", @@ -2113,12 +2093,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "lazy_static" version = "0.2.11" @@ -2276,15 +2250,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -dependencies = [ - "log 0.4.14", -] - [[package]] name = "log" version = "0.4.14" @@ -2313,7 +2278,7 @@ dependencies = [ "fnv", "humantime 1.3.0", "libc", - "log 0.4.14", + "log", "log-mdc", "serde 1.0.130", "serde-value 0.5.3", @@ -2337,7 +2302,7 @@ dependencies = [ "fnv", "humantime 2.1.0", "libc", - "log 0.4.14", + "log", "log-mdc", "parking_lot 0.11.2", "regex", @@ -2425,15 +2390,6 @@ dependencies = [ "syn 1.0.81", ] -[[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" -dependencies = [ - "log 0.3.9", -] - [[package]] name = "mime" version = "0.3.16" @@ -2457,7 +2413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", - "log 0.4.14", + "log", "miow", "ntapi", "winapi 0.3.9", @@ -2546,7 +2502,7 @@ checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" dependencies = [ "lazy_static 1.4.0", "libc", - "log 0.4.14", + "log", "openssl", "openssl-probe", "openssl-sys", @@ -3040,7 +2996,7 @@ dependencies = [ "generic-array", "hex", "lazy_static 1.4.0", - "log 0.4.14", + "log", "md-5", "nom 4.2.3", "num-bigint-dig", @@ -3248,7 +3204,7 @@ dependencies = [ "bytes 1.1.0", "heck", "itertools 0.10.1", - "log 0.4.14", + "log", "multimap", "petgraph", "prost", @@ -3568,13 +3524,13 @@ dependencies = [ "futures-util", "http", "http-body", - "hyper 0.14.14", + "hyper", "hyper-tls", "ipnet", "js-sys", "lazy_static 1.4.0", - "log 0.4.14", - "mime 0.3.16", + "log", + "mime", "native-tls", "percent-encoding 2.1.0", "pin-project-lite 0.2.7", @@ -3712,7 +3668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.0", - "log 0.4.14", + "log", "ring", "sct", "webpki", @@ -3730,7 +3686,7 @@ dependencies = [ "dirs-next 2.0.0", "fd-lock", "libc", - "log 0.4.14", + "log", "memchr", "nix", "radix_trie", @@ -4346,7 +4302,7 @@ dependencies = [ "config", "dirs-next 1.0.2", "futures 0.3.17", - "log 0.4.14", + "log", "qrcode", "rand 0.8.4", "serde_json", @@ -4374,7 +4330,7 @@ dependencies = [ "config", "either", "futures 0.3.17", - "log 0.4.14", + "log", "num_cpus", "opentelemetry", "opentelemetry-jaeger", @@ -4431,7 +4387,7 @@ dependencies = [ "dirs-next 1.0.2", "get_if_addrs", "git2", - "log 0.4.14", + "log", "log4rs 1.0.0", "multiaddr", "opentelemetry", @@ -4484,7 +4440,7 @@ dependencies = [ "futures 0.3.17", "lazy_static 1.4.0", "lmdb-zero", - "log 0.4.14", + "log", "multiaddr", "nom 5.1.2", "openssl-sys", @@ -4535,7 +4491,7 @@ dependencies = [ "lazy_static 1.4.0", "libsqlite3-sys", "lmdb-zero", - "log 0.4.14", + "log", "petgraph", "pin-project 0.4.28", "prost", @@ -4581,11 +4537,11 @@ dependencies = [ name = "tari_console_wallet" version = "0.21.0" dependencies = [ - "anyhow", "bitflags 1.3.2", + "chrono", "crossterm 0.17.7", "futures 0.3.17", - "log 0.4.14", + "log", "opentelemetry", "opentelemetry-jaeger", "qrcode", @@ -4608,7 +4564,6 @@ dependencies = [ "tari_shutdown", "tari_wallet", "thiserror", - "time 0.3.4", "tokio 1.13.0", "tonic", "tracing", @@ -4641,7 +4596,7 @@ dependencies = [ "integer-encoding 3.0.2", "lazy_static 1.4.0", "lmdb-zero", - "log 0.4.14", + "log", "monero", "newtype-ops", "num", @@ -4744,9 +4699,9 @@ dependencies = [ "futures 0.3.17", "futures-test", "hex", - "hyper 0.14.14", + "hyper", "jsonrpc", - "log 0.4.14", + "log", "rand 0.8.4", "reqwest", "serde 1.0.130", @@ -4778,7 +4733,7 @@ dependencies = [ "futures 0.3.17", "hex", "jsonrpc", - "log 0.4.14", + "log", "native-tls", "num_cpus", "prost-types", @@ -4794,7 +4749,6 @@ dependencies = [ "tari_core", "tari_crypto", "thiserror", - "time 0.1.44", "tokio 1.13.0", "tonic", ] @@ -4808,7 +4762,7 @@ dependencies = [ "criterion", "croaring", "digest", - "log 0.4.14", + "log", "rand 0.8.4", "serde 1.0.130", "serde_json", @@ -4832,7 +4786,7 @@ dependencies = [ "futures-timer", "lazy_static 1.4.0", "lmdb-zero", - "log 0.4.14", + "log", "log4rs 0.8.3", "pgp", "prost", @@ -4870,7 +4824,7 @@ dependencies = [ "async-trait", "futures 0.3.17", "futures-test", - "log 0.4.14", + "log", "tari_shutdown", "tari_test_utils", "thiserror", @@ -4895,7 +4849,7 @@ dependencies = [ "bytes 0.5.6", "env_logger 0.6.2", "lmdb-zero", - "log 0.4.14", + "log", "rand 0.8.4", "rmp", "rmp-serde", @@ -4935,9 +4889,9 @@ dependencies = [ "futures 0.3.17", "futures-test", "hex", - "hyper 0.14.14", + "hyper", "jsonrpc", - "log 0.4.14", + "log", "rand 0.7.3", "reqwest", "serde 1.0.130", @@ -5010,7 +4964,7 @@ dependencies = [ "futures 0.3.17", "libsqlite3-sys", "lmdb-zero", - "log 0.4.14", + "log", "log4rs 1.0.0", "prost", "rand 0.8.4", @@ -5030,7 +4984,6 @@ dependencies = [ "tari_test_utils", "tempfile", "thiserror", - "time 0.1.44", "tokio 1.13.0", "tower 0.3.1", ] @@ -5044,7 +4997,7 @@ dependencies = [ "futures 0.3.17", "lazy_static 1.4.0", "libc", - "log 0.4.14", + "log", "log4rs 1.0.0", "rand 0.8.4", "security-framework", @@ -5168,7 +5121,7 @@ checksum = "0c6d965454947cc7266d22716ebfd07b18d84ebaf35eec558586bbb2a8cb6b5b" dependencies = [ "byteorder", "integer-encoding 1.1.7", - "log 0.4.14", + "log", "ordered-float 1.1.1", "threadpool", ] @@ -5184,23 +5137,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "time" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99beeb0daeac2bd1e86ac2c21caddecb244b39a093594da1a661ec2060c7aedd" -dependencies = [ - "itoa", - "libc", - "time-macros", -] - -[[package]] -name = "time-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" - [[package]] name = "tiny-keccak" version = "2.0.2" @@ -5355,7 +5291,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "log 0.4.14", + "log", "pin-project-lite 0.2.7", "tokio 1.13.0", ] @@ -5393,7 +5329,7 @@ dependencies = [ "h2", "http", "http-body", - "hyper 0.14.14", + "hyper", "hyper-timeout", "percent-encoding 2.1.0", "pin-project 1.0.8", @@ -5513,7 +5449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cc79fc3afd07492b7966d7efa7c6c50f8ed58d768a6075dd7ae6591c5d2017b" dependencies = [ "futures-core", - "log 0.4.14", + "log", "pin-project 0.4.28", "tokio 0.2.25", "tower-discover", @@ -5605,7 +5541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", - "log 0.4.14", + "log", "pin-project-lite 0.2.7", "tracing-attributes", "tracing-core", @@ -5648,7 +5584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" dependencies = [ "lazy_static 1.4.0", - "log 0.4.14", + "log", "tracing-core", ] @@ -5715,7 +5651,7 @@ dependencies = [ "futures-channel", "futures-util", "lazy_static 1.4.0", - "log 0.4.14", + "log", "radix_trie", "rand 0.8.4", "ring", @@ -5742,7 +5678,7 @@ dependencies = [ "idna 0.2.3", "ipnet", "lazy_static 1.4.0", - "log 0.4.14", + "log", "rand 0.8.4", "ring", "rustls", @@ -5803,12 +5739,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "typeable" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" - [[package]] name = "typemap" version = "0.3.3" @@ -5842,15 +5772,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unicase" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -dependencies = [ - "version_check 0.1.5", -] - [[package]] name = "unicode-bidi" version = "0.3.7" @@ -5997,7 +5918,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.14", + "log", "try-lock", ] @@ -6031,7 +5952,7 @@ checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", "lazy_static 1.4.0", - "log 0.4.14", + "log", "proc-macro2 1.0.32", "quote 1.0.10", "syn 1.0.81", @@ -6198,7 +6119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ "futures 0.3.17", - "log 0.4.14", + "log", "nohash-hasher", "parking_lot 0.11.2", "rand 0.8.4", diff --git a/applications/tari_app_grpc/Cargo.toml b/applications/tari_app_grpc/Cargo.toml index 5a56dd41a6..fff5cfc6b5 100644 --- a/applications/tari_app_grpc/Cargo.toml +++ b/applications/tari_app_grpc/Cargo.toml @@ -13,7 +13,7 @@ tari_core = { path = "../../base_layer/core"} tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } tari_comms = { path = "../../comms"} -chrono = "0.4.6" +chrono = { version = "0.4.19", default-features = false } prost = "0.8" prost-types = "0.8" tonic = "0.5.2" diff --git a/applications/tari_base_node/Cargo.toml b/applications/tari_base_node/Cargo.toml index 5002470f7e..d43d833537 100644 --- a/applications/tari_base_node/Cargo.toml +++ b/applications/tari_base_node/Cargo.toml @@ -23,7 +23,7 @@ tari_shutdown = { path = "../../infrastructure/shutdown" } anyhow = "1.0.32" bincode = "1.3.1" -chrono = "0.4" +chrono = { version = "0.4.19", default-features = false } config = { version = "0.9.3" } either = "1.6.1" futures = { version = "^0.3.16", default-features = false, features = ["alloc"] } diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index 51fc346d9d..263c4588d7 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -17,7 +17,7 @@ tari_app_grpc = { path = "../tari_app_grpc" } tari_shutdown = { path = "../../infrastructure/shutdown" } tari_key_manager = { path = "../../base_layer/key_manager" } -anyhow = "1.0.44" +chrono = { version = "0.4.19", default-features = false } bitflags = "1.2.1" futures = { version = "^0.3.16", default-features = false, features = ["alloc"] } crossterm = { version = "0.17" } @@ -34,8 +34,6 @@ strum_macros = "^0.19" tokio = { version = "1.11", features = ["signal"] } thiserror = "1.0.26" tonic = "0.5.2" -time = { version = "0.3.4", features = ["formatting", "local-offset", "macros", "parsing"] } - tracing = "0.1.26" tracing-opentelemetry = "0.15.0" tracing-subscriber = "0.2.20" diff --git a/applications/tari_console_wallet/src/automation/command_parser.rs b/applications/tari_console_wallet/src/automation/command_parser.rs index b7cc3327e0..942c3ccb4a 100644 --- a/applications/tari_console_wallet/src/automation/command_parser.rs +++ b/applications/tari_console_wallet/src/automation/command_parser.rs @@ -22,6 +22,7 @@ use crate::automation::{commands::WalletCommand, error::ParseError}; +use chrono::{DateTime, Utc}; use core::str::SplitWhitespace; use std::{ fmt::{Display, Formatter}, @@ -32,7 +33,6 @@ use tari_comms::multiaddr::Multiaddr; use tari_common_types::types::PublicKey; use tari_core::transactions::tari_amount::MicroTari; -use time::{format_description::well_known::Rfc3339, OffsetDateTime}; #[derive(Debug)] pub struct ParsedCommand { @@ -77,7 +77,7 @@ pub enum ParsedArgument { Text(String), Float(f64), Int(u64), - Date(OffsetDateTime), + Date(DateTime), OutputToCSVFile(String), CSVFileName(String), Address(Multiaddr), @@ -216,9 +216,9 @@ fn parse_make_it_rain(mut args: SplitWhitespace) -> Result, // start time utc or 'now' let start_time = args.next().ok_or_else(|| ParseError::Empty("start time".to_string()))?; let start_time = if start_time != "now" { - OffsetDateTime::parse(start_time, &Rfc3339)? + DateTime::parse_from_rfc3339(start_time)?.with_timezone(&Utc) } else { - OffsetDateTime::now_utc() + Utc::now() }; parsed_args.push(ParsedArgument::Date(start_time)); diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 5706df5087..40090d6d51 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -21,6 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::error::CommandError; +use chrono::Utc; use log::*; use std::{ convert::TryFrom, @@ -58,7 +59,6 @@ use tari_wallet::{ transaction_service::handle::{TransactionEvent, TransactionServiceHandle}, WalletSqlite, }; -use time::OffsetDateTime; use tokio::{ sync::{broadcast, mpsc}, time::{sleep, timeout}, @@ -296,13 +296,13 @@ pub async fn make_it_rain( // We are spawning this command in parallel, thus not collecting transaction IDs tokio::task::spawn(async move { // Wait until specified test start time - let now = OffsetDateTime::now_utc(); + let now = Utc::now(); let delay_ms = if start_time > now { println!( "`make-it-rain` scheduled to start at {}: msg \"{}\"", start_time, message ); - (start_time - now).whole_milliseconds() as u64 + (start_time - now).num_milliseconds() as u64 } else { 0 }; @@ -314,7 +314,7 @@ pub async fn make_it_rain( sleep(Duration::from_millis(delay_ms)).await; let num_txs = (txps * duration as f64) as usize; - let started_at = OffsetDateTime::now_utc(); + let started_at = Utc::now(); struct TransactionSendStats { i: usize, @@ -348,7 +348,7 @@ pub async fn make_it_rain( ParsedArgument::Text(message.clone()), ]; // Manage transaction submission rate - let actual_ms = (OffsetDateTime::now_utc() - started_at).whole_milliseconds() as i64; + let actual_ms = (Utc::now() - started_at).num_milliseconds(); let target_ms = (i as f64 / (txps / 1000.0)) as i64; if target_ms - actual_ms > 0 { // Maximum delay between Txs set to 120 s @@ -420,7 +420,7 @@ pub async fn make_it_rain( num_txs, transaction_type, message, - OffsetDateTime::now_utc(), + Utc::now(), ); }); diff --git a/applications/tari_console_wallet/src/automation/error.rs b/applications/tari_console_wallet/src/automation/error.rs index ead7e548f4..f174e1cd59 100644 --- a/applications/tari_console_wallet/src/automation/error.rs +++ b/applications/tari_console_wallet/src/automation/error.rs @@ -34,7 +34,6 @@ use tari_wallet::{ transaction_service::error::TransactionServiceError, }; use thiserror::Error; -use time::error::ComponentRange; use tokio::task::JoinError; pub const LOG_TARGET: &str = "wallet::automation::error"; @@ -88,9 +87,7 @@ pub enum ParseError { #[error("Failed to parse int.")] Int(#[from] ParseIntError), #[error("Failed to parse date. {0}")] - Date(#[from] time::error::Parse), - #[error("Failed to convert time. {0}")] - TimeRange(#[from] ComponentRange), + Date(#[from] chrono::ParseError), #[error("Failed to parse a net address.")] Address, #[error("Invalid combination of arguments ({0}).")] diff --git a/applications/tari_console_wallet/src/recovery.rs b/applications/tari_console_wallet/src/recovery.rs index 2b2ff73aa6..d3c247977a 100644 --- a/applications/tari_console_wallet/src/recovery.rs +++ b/applications/tari_console_wallet/src/recovery.rs @@ -20,6 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use chrono::offset::Local; use futures::FutureExt; use log::*; use rustyline::Editor; @@ -35,7 +36,6 @@ use tari_wallet::{ use crate::wallet_modes::PeerConfig; use tari_key_manager::cipher_seed::CipherSeed; -use time::OffsetDateTime; use tokio::sync::broadcast; pub const LOG_TARGET: &str = "wallet::recovery"; @@ -126,14 +126,14 @@ pub async fn wallet_recovery(wallet: &WalletSqlite, base_node_config: &PeerConfi debug!( target: LOG_TARGET, "{}: Recovery process {}% complete ({} of {} utxos).", - OffsetDateTime::now_local().unwrap(), + Local::now(), percentage_progress, current, total ); println!( "{}: Recovery process {}% complete ({} of {} utxos).", - OffsetDateTime::now_local().unwrap(), + Local::now(), percentage_progress, current, total diff --git a/applications/tari_console_wallet/src/ui/components/notification_tab.rs b/applications/tari_console_wallet/src/ui/components/notification_tab.rs index 00a115f93f..271cdeee6e 100644 --- a/applications/tari_console_wallet/src/ui/components/notification_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/notification_tab.rs @@ -9,9 +9,7 @@ // notification, the UI should go there if I click on it. use crate::ui::{components::Component, state::AppState}; -use anyhow::Error; use tari_comms::runtime::Handle; -use time::{error::Format, format_description::FormatItem, macros::format_description}; use tui::{ backend::Backend, layout::{Constraint, Layout, Rect}, @@ -21,8 +19,6 @@ use tui::{ Frame, }; -const DT_FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day] [hour]-[minute]-[second] "); - pub struct NotificationTab {} impl NotificationTab { @@ -30,7 +26,7 @@ impl NotificationTab { Self {} } - fn draw_notifications(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) -> Result<(), Error> + fn draw_notifications(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) where B: Backend { let block = Block::default().borders(Borders::ALL).title(Span::styled( "Notifications", @@ -46,16 +42,17 @@ impl NotificationTab { .iter() .rev() .map(|(time, line)| { - Ok(Spans::from(vec![ - Span::styled(time.format(&DT_FORMAT)?, Style::default().fg(Color::LightGreen)), + Spans::from(vec![ + Span::styled( + time.format("%Y-%m-%d %H:%M:%S ").to_string(), + Style::default().fg(Color::LightGreen), + ), Span::raw(line), - ])) + ]) }) - .collect::, Format>>() - .unwrap(); + .collect::>(); let paragraph = Paragraph::new(text).wrap(Wrap { trim: true }); f.render_widget(paragraph, notifications_area[0]); - Ok(()) } } @@ -64,9 +61,7 @@ impl Component for NotificationTab { let areas = Layout::default() .constraints([Constraint::Min(42)].as_ref()) .split(area); - if let Err(err) = self.draw_notifications(f, areas[0], app_state) { - log::error!("Notification tab rendering failed: {}", err); - } + self.draw_notifications(f, areas[0], app_state); } fn on_tick(&mut self, app_state: &mut AppState) { diff --git a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs index 9642abb085..2c9c49f8b6 100644 --- a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs @@ -6,9 +6,8 @@ use crate::ui::{ widgets::{draw_dialog, MultiColumnList, WindowedListState}, MAX_WIDTH, }; -use anyhow::Error; +use chrono::{DateTime, Local}; use tari_common_types::transaction::{TransactionDirection, TransactionStatus}; -use time::{format_description::FormatItem, macros::format_description, UtcOffset}; use tokio::runtime::Handle; use tui::{ backend::Backend, @@ -19,8 +18,6 @@ use tui::{ Frame, }; -const DT_FORMAT: &[FormatItem<'static>] = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); - pub struct TransactionsTab { balance: Balance, selected_tx_list: SelectedTransactionList, @@ -44,7 +41,7 @@ impl TransactionsTab { } } - fn draw_transaction_lists(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) -> Result<(), Error> + fn draw_transaction_lists(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) where B: Backend { let (pending_constraint, completed_constraint) = if app_state.get_pending_txs().is_empty() { self.selected_tx_list = SelectedTransactionList::CompletedTxs; @@ -69,20 +66,12 @@ impl TransactionsTab { .title(Span::styled("(P)ending Transactions", style)); f.render_widget(block, list_areas[0]); - self.draw_pending_transactions(f, list_areas[0], app_state)?; - self.draw_completed_transactions(f, list_areas[1], app_state)?; - Ok(()) + self.draw_pending_transactions(f, list_areas[0], app_state); + self.draw_completed_transactions(f, list_areas[1], app_state); } - fn draw_pending_transactions( - &mut self, - f: &mut Frame, - area: Rect, - app_state: &AppState, - ) -> Result<(), Error> - where - B: Backend, - { + fn draw_pending_transactions(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) + where B: Backend { // Pending Transactions self.pending_list_state.set_num_items(app_state.get_pending_txs().len()); let mut pending_list_state = self @@ -125,10 +114,9 @@ impl TransactionsTab { }; column1_items.push(ListItem::new(Span::styled(format!("{}", t.amount), amount_style))); } - let offset = UtcOffset::current_local_offset()?; - let local_time = t.timestamp.replace_offset(offset); + let local_time = DateTime::::from_utc(t.timestamp, Local::now().offset().to_owned()); column2_items.push(ListItem::new(Span::styled( - local_time.format(&DT_FORMAT)?, + format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")), Style::default().fg(text_color), ))); column3_items.push(ListItem::new(Span::styled( @@ -146,18 +134,10 @@ impl TransactionsTab { .add_column(Some("Local Date/Time"), Some(20), column2_items) .add_column(Some("Message"), None, column3_items); column_list.render(f, area, &mut pending_list_state); - Ok(()) } - fn draw_completed_transactions( - &mut self, - f: &mut Frame, - area: Rect, - app_state: &AppState, - ) -> Result<(), Error> - where - B: Backend, - { + fn draw_completed_transactions(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) + where B: Backend { // Completed Transactions let style = if self.selected_tx_list == SelectedTransactionList::CompletedTxs { Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD) @@ -223,10 +203,9 @@ impl TransactionsTab { let amount_style = Style::default().fg(color); column1_items.push(ListItem::new(Span::styled(format!("{}", t.amount), amount_style))); } - let offset = UtcOffset::current_local_offset()?; - let local_time = t.timestamp.replace_offset(offset); + let local_time = DateTime::::from_utc(t.timestamp, Local::now().offset().to_owned()); column2_items.push(ListItem::new(Span::styled( - local_time.format(&DT_FORMAT)?, + format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")), Style::default().fg(text_color), ))); let status = if (t.cancelled || !t.valid) && t.status == TransactionStatus::Coinbase { @@ -253,7 +232,6 @@ impl TransactionsTab { .add_column(Some("Status"), None, column3_items); column_list.render(f, area, &mut completed_list_state); - Ok(()) } fn draw_detailed_transaction(&self, f: &mut Frame, area: Rect, app_state: &AppState) @@ -366,10 +344,9 @@ impl TransactionsTab { }; let status = Span::styled(status_msg, Style::default().fg(Color::White)); let message = Span::styled(tx.message.as_str(), Style::default().fg(Color::White)); - // TODO: Get Local from UTC - let local_time = tx.timestamp; + let local_time = DateTime::::from_utc(tx.timestamp, Local::now().offset().to_owned()); let timestamp = Span::styled( - format!("{}", local_time.format(&DT_FORMAT).unwrap()), + format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")), Style::default().fg(Color::White), ); let excess = Span::styled(tx.excess_signature.as_str(), Style::default().fg(Color::White)); @@ -469,9 +446,7 @@ impl Component for TransactionsTab { let instructions = Paragraph::new(Spans::from(span_vec)).wrap(Wrap { trim: true }); f.render_widget(instructions, areas[1]); - if let Err(err) = self.draw_transaction_lists(f, areas[2], app_state) { - log::error!("Can't draw transactions list: {}", err); - } + self.draw_transaction_lists(f, areas[2], app_state); self.draw_detailed_transaction(f, areas[3], app_state); if let Some(msg) = self.error_message.clone() { diff --git a/applications/tari_console_wallet/src/ui/state/app_state.rs b/applications/tari_console_wallet/src/ui/state/app_state.rs index bdf08f8a7c..783693f525 100644 --- a/applications/tari_console_wallet/src/ui/state/app_state.rs +++ b/applications/tari_console_wallet/src/ui/state/app_state.rs @@ -27,11 +27,11 @@ use std::{ }; use bitflags::bitflags; +use chrono::{DateTime, Local, NaiveDateTime}; use log::*; use qrcode::{render::unicode, QrCode}; use tari_crypto::{ristretto::RistrettoPublicKey, tari_utilities::hex::Hex}; use tari_p2p::auto_update::SoftwareUpdaterHandle; -use time::OffsetDateTime; use tokio::{ sync::{watch, RwLock}, task, @@ -442,7 +442,7 @@ impl AppState { self.completed_tx_filter.toggle(TransactionFilter::ABANDONED_COINBASES); } - pub fn get_notifications(&self) -> &[(OffsetDateTime, String)] { + pub fn get_notifications(&self) -> &[(DateTime, String)] { &self.cached_data.notifications } @@ -847,9 +847,7 @@ impl AppStateInner { } pub fn add_notification(&mut self, notification: String) { - self.data - .notifications - .push((OffsetDateTime::now_local().unwrap(), notification)); + self.data.notifications.push((Local::now(), notification)); self.data.new_notification_count += 1; self.updated = true; } @@ -875,7 +873,7 @@ pub struct CompletedTransactionInfo { pub maturity: u64, pub status: TransactionStatus, pub message: String, - pub timestamp: OffsetDateTime, + pub timestamp: NaiveDateTime, pub cancelled: bool, pub direction: TransactionDirection, pub valid: bool, @@ -913,7 +911,7 @@ impl CompletedTransactionInfo { .unwrap_or(0), status: tx.status, message: tx.message, - timestamp: OffsetDateTime::from_unix_timestamp(tx.timestamp.timestamp()).unwrap(), + timestamp: tx.timestamp, cancelled: tx.cancelled, direction: tx.direction, valid: tx.valid, @@ -940,7 +938,7 @@ struct AppStateData { base_node_previous: Peer, base_node_list: Vec<(String, Peer)>, base_node_peer_custom: Option, - notifications: Vec<(OffsetDateTime, String)>, + notifications: Vec<(DateTime, String)>, new_notification_count: u32, } diff --git a/applications/tari_merge_mining_proxy/Cargo.toml b/applications/tari_merge_mining_proxy/Cargo.toml index aadc3bf6dc..b8649c0133 100644 --- a/applications/tari_merge_mining_proxy/Cargo.toml +++ b/applications/tari_merge_mining_proxy/Cargo.toml @@ -23,14 +23,14 @@ tari_utilities = "^0.3" anyhow = "1.0.40" bincode = "1.3.1" bytes = "1.1" -chrono = "0.4.19" +chrono = { version = "0.4.6", default-features = false } config = { version = "0.9.3" } derive-error = "0.0.4" env_logger = { version = "0.7.1", optional = true } futures = "0.3.5" hex = "0.4.2" hyper = "0.14.12" -jsonrpc = "0.11.0" +jsonrpc = "0.12.0" log = { version = "0.4.8", features = ["std"] } rand = "0.8" reqwest = { version = "0.11.4", features = ["json"] } diff --git a/applications/tari_merge_mining_proxy/src/common/json_rpc.rs b/applications/tari_merge_mining_proxy/src/common/json_rpc.rs index 95c7a3c159..ebf55b7edf 100644 --- a/applications/tari_merge_mining_proxy/src/common/json_rpc.rs +++ b/applications/tari_merge_mining_proxy/src/common/json_rpc.rs @@ -52,6 +52,7 @@ pub fn standard_error_response( err: jsonrpc::error::StandardError, data: Option, ) -> json::Value { + let data = data.and_then(|value| json::value::to_raw_value(&value).ok()); let err = jsonrpc::error::standard_error(err, data); json!({ "id": req_id.unwrap_or(-1), diff --git a/applications/tari_mining_node/Cargo.toml b/applications/tari_mining_node/Cargo.toml index 5ff573f720..9b9632f225 100644 --- a/applications/tari_mining_node/Cargo.toml +++ b/applications/tari_mining_node/Cargo.toml @@ -25,15 +25,14 @@ serde = { version = "1.0", default_features = false, features = ["derive"] } tonic = { version = "0.5.2", features = ["transport"] } tokio = { version = "1.11", default_features = false, features = ["rt-multi-thread"] } thiserror = "1.0" -jsonrpc = "0.11.0" +jsonrpc = "0.12.0" reqwest = { version = "0.11", features = [ "json"] } serde_json = "1.0.57" native-tls = "0.2" bufstream = "0.1" -time = "0.1" -chrono = "0.4" +chrono = { version = "0.4.19", default-features = false } hex = "0.4.2" [dev-dependencies] prost-types = "0.8" -chrono = "0.4" +chrono = { version = "0.4.19", default-features = false } diff --git a/applications/tari_mining_node/src/stratum/controller.rs b/applications/tari_mining_node/src/stratum/controller.rs index 288e94157e..557abd3514 100644 --- a/applications/tari_mining_node/src/stratum/controller.rs +++ b/applications/tari_mining_node/src/stratum/controller.rs @@ -28,6 +28,7 @@ use std::{ io::{BufRead, ErrorKind, Write}, sync::{mpsc, Arc, RwLock}, thread, + time::{Duration, Instant}, }; pub const LOG_TARGET: &str = "tari_mining_node::miner::stratum::controller"; @@ -312,12 +313,12 @@ impl Controller { #[allow(clippy::cognitive_complexity)] pub fn run(mut self) { - let server_read_interval = 1; - let server_retry_interval = 5; - let mut next_server_read = time::get_time().sec + server_read_interval; - let mut next_server_retry = time::get_time().sec; + let server_read_interval = Duration::from_secs(1); + let server_retry_interval = Duration::from_secs(5); + let mut next_server_read = Instant::now() + server_read_interval; + let mut next_server_retry = Instant::now(); // Request the first job template - thread::sleep(std::time::Duration::from_secs(1)); + thread::sleep(Duration::from_secs(1)); let mut was_disconnected = true; loop { // Check our connection status, and try to correct if possible @@ -326,11 +327,12 @@ impl Controller { let _ = self.send_miner_stop(); } was_disconnected = true; - if time::get_time().sec > next_server_retry { + if Instant::now() > next_server_retry { if self.try_connect().is_err() { let status = format!( "Connection Status: Can't establish server connection to {}. Will retry every {} seconds", - self.server_url, server_retry_interval + self.server_url, + server_retry_interval.as_secs() ); warn!("{}", status); self.stream = None; @@ -338,7 +340,7 @@ impl Controller { let status = format!("Connection Status: Connected to server at {}.", self.server_url); info!(target: LOG_TARGET, "{}", status); } - next_server_retry = time::get_time().sec + server_retry_interval; + next_server_retry = Instant::now() + server_retry_interval; if self.stream.is_none() { thread::sleep(std::time::Duration::from_secs(1)); continue; @@ -352,7 +354,7 @@ impl Controller { let _ = self.send_miner_resume(); } // read messages from server - if time::get_time().sec > next_server_read { + if Instant::now() > next_server_read { match self.read_message() { Ok(Some(m)) => { // figure out what kind of message, @@ -396,7 +398,7 @@ impl Controller { continue; }, } - next_server_read = time::get_time().sec + server_read_interval; + next_server_read = Instant::now() + server_read_interval; } } @@ -418,7 +420,7 @@ impl Controller { self.stream = None; } } - thread::sleep(std::time::Duration::from_millis(10)); + thread::sleep(Duration::from_millis(10)); } // loop } } diff --git a/applications/tari_stratum_transcoder/Cargo.toml b/applications/tari_stratum_transcoder/Cargo.toml index dc25e5d987..dd16df3b0c 100644 --- a/applications/tari_stratum_transcoder/Cargo.toml +++ b/applications/tari_stratum_transcoder/Cargo.toml @@ -21,14 +21,14 @@ tari_utilities = "^0.3" bincode = "1.3.1" bytes = "0.5" -chrono = "0.4.19" +chrono = { version = "0.4.19", default-features = false } config = { version = "0.9.3" } derive-error = "0.0.4" env_logger = { version = "0.7.1", optional = true } futures = "0.3.5" hex = "0.4.2" hyper = "0.14.12" -jsonrpc = "0.11.0" +jsonrpc = "0.12.0" log = { version = "0.4.8", features = ["std"] } rand = "0.7.2" reqwest = { version = "0.11", features = ["json"] } diff --git a/applications/tari_stratum_transcoder/src/common/json_rpc.rs b/applications/tari_stratum_transcoder/src/common/json_rpc.rs index 976b421b61..61c51f30b4 100644 --- a/applications/tari_stratum_transcoder/src/common/json_rpc.rs +++ b/applications/tari_stratum_transcoder/src/common/json_rpc.rs @@ -33,6 +33,7 @@ pub fn standard_error_response( err: jsonrpc::error::StandardError, data: Option, ) -> json::Value { + let data = data.and_then(|value| json::value::to_raw_value(&value).ok()); let err = jsonrpc::error::standard_error(err, data); json!({ "id": req_id.unwrap_or(-1), diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index 854d341a71..aaf400f5e8 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -36,7 +36,7 @@ bincode = "1.1.4" bitflags = "1.0.4" blake2 = "^0.9.0" bytes = "0.5" -chrono = { version = "0.4.6", features = ["serde"] } +chrono = { version = "0.4.19", default-features = false, features = ["serde"] } croaring = { version = "=0.4.5", optional = true } decimal-rs = "0.1.20" derive_more = "0.99.16" diff --git a/base_layer/key_manager/Cargo.toml b/base_layer/key_manager/Cargo.toml index 97b2d67334..3c3c69ab64 100644 --- a/base_layer/key_manager/Cargo.toml +++ b/base_layer/key_manager/Cargo.toml @@ -14,7 +14,7 @@ arrayvec = "0.7.1" argon2 = { version = "0.2", features = ["std"] } blake2 = "0.9.1" chacha20 = "0.7.1" -chrono = { version = "0.4.6", features = ["serde"] } +chrono = { version = "0.4.19", default-features = false, features = ["serde"] } clear_on_drop = "=0.2.4" crc32fast = "1.2.1" rand = "0.8" diff --git a/base_layer/p2p/Cargo.toml b/base_layer/p2p/Cargo.toml index abb9aa0e88..f8ef9e7c04 100644 --- a/base_layer/p2p/Cargo.toml +++ b/base_layer/p2p/Cargo.toml @@ -21,7 +21,7 @@ tari_utilities = "^0.3" anyhow = "1.0.32" bytes = "0.5" -chrono = { version = "0.4.6", features = ["serde"] } +chrono = { version = "0.4.19", default-features = false, features = ["serde"] } fs2 = "0.3.0" futures = { version = "^0.3.1" } lmdb-zero = "0.4.4" diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index 6b1fefdcc0..5e1d89ca2d 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -23,7 +23,7 @@ async-trait = "0.1.50" argon2 = "0.2" bincode = "1.3.1" blake2 = "0.9.0" -chrono = { version = "0.4.6", features = ["serde"] } +chrono = { version = "0.4.19", default-features = false, features = ["serde"] } clear_on_drop = "=0.2.4" crossbeam-channel = "0.3.8" diesel = { version = "1.4.7", features = ["sqlite", "serde_json", "chrono"] } @@ -40,7 +40,6 @@ serde = { version = "1.0.89", features = ["derive"] } serde_json = "1.0.39" tempfile = "3.1.0" thiserror = "1.0.26" -time = { version = "0.1.39" } tokio = { version = "1.11", features = ["sync", "macros"] } tower = "0.3.0-alpha.2" diff --git a/base_layer/wallet/src/output_manager_service/error.rs b/base_layer/wallet/src/output_manager_service/error.rs index f82092d9c7..25322205d7 100644 --- a/base_layer/wallet/src/output_manager_service/error.rs +++ b/base_layer/wallet/src/output_manager_service/error.rs @@ -34,7 +34,6 @@ use tari_crypto::{script::ScriptError, tari_utilities::ByteArrayError}; use tari_key_manager::error::{KeyManagerError, MnemonicError}; use tari_service_framework::reply_channel::TransportChannelError; use thiserror::Error; -use time::OutOfRangeError; #[derive(Debug, Error)] pub enum OutputManagerError { @@ -46,8 +45,6 @@ pub enum OutputManagerError { TransactionProtocolError(#[from] TransactionProtocolError), #[error("Transport channel error: `{0}`")] TransportChannelError(#[from] TransportChannelError), - #[error("Out of range error: `{0}`")] - OutOfRangeError(#[from] OutOfRangeError), #[error("Output manager storage error: `{0}`")] OutputManagerStorageError(#[from] OutputManagerStorageError), #[error("Mnemonic error: `{0}`")] @@ -145,8 +142,6 @@ pub enum OutputManagerStorageError { OutputAlreadySpent, #[error("Key Manager not initialized")] KeyManagerNotInitialized, - #[error("Out of range error: `{0}`")] - OutOfRangeError(#[from] OutOfRangeError), #[error("R2d2 error")] R2d2Error, #[error("Transaction error: `{0}`")] diff --git a/base_layer/wallet/src/transaction_service/error.rs b/base_layer/wallet/src/transaction_service/error.rs index bf7077ec72..b4b21b5465 100644 --- a/base_layer/wallet/src/transaction_service/error.rs +++ b/base_layer/wallet/src/transaction_service/error.rs @@ -23,7 +23,10 @@ use crate::{ error::WalletStorageError, output_manager_service::error::OutputManagerError, - transaction_service::storage::{database::DbKey, sqlite_db::CompletedTransactionConversionError}, + transaction_service::{ + storage::{database::DbKey, sqlite_db::CompletedTransactionConversionError}, + utc::NegativeDurationError, + }, }; use diesel::result::Error as DieselError; use futures::channel::oneshot::Canceled; @@ -36,7 +39,6 @@ use tari_crypto::tari_utilities::ByteArrayError; use tari_p2p::services::liveness::error::LivenessError; use tari_service_framework::reply_channel::TransportChannelError; use thiserror::Error; -use time::OutOfRangeError; use tokio::sync::broadcast::error::RecvError; #[derive(Debug, Error)] @@ -113,8 +115,8 @@ pub enum TransactionServiceError { TransactionError(#[from] TransactionError), #[error("Conversion error: `{0}`")] ConversionError(#[from] TransactionConversionError), - #[error("duration::OutOfRangeError: {0}")] - DurationOutOfRange(#[from] OutOfRangeError), + #[error("duration::NegativeDurationError: {0}")] + DurationOutOfRange(#[from] NegativeDurationError), #[error("Node ID error: `{0}`")] NodeIdError(#[from] NodeIdError), #[error("Broadcast recv error: `{0}`")] @@ -191,7 +193,7 @@ pub enum TransactionStorageError { #[error("Transaction direction error: `{0}`")] TransactionDirectionError(#[from] TransactionDirectionError), #[error("Error converting a type: `{0}`")] - OutOfRangeError(#[from] OutOfRangeError), + NegativeDurationError(#[from] NegativeDurationError), #[error("Error converting a type: `{0}`")] ConversionError(#[from] TransactionConversionError), #[error("Completed transaction conversion error: `{0}`")] diff --git a/base_layer/wallet/src/transaction_service/mod.rs b/base_layer/wallet/src/transaction_service/mod.rs index 3d17a1b564..253a9392cb 100644 --- a/base_layer/wallet/src/transaction_service/mod.rs +++ b/base_layer/wallet/src/transaction_service/mod.rs @@ -65,6 +65,7 @@ pub mod protocols; pub mod service; pub mod storage; pub mod tasks; +mod utc; const LOG_TARGET: &str = "wallet::transaction_service"; const SUBSCRIPTION_LABEL: &str = "Transaction Service"; 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 8709acb120..ca75e3145c 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 @@ -29,6 +29,7 @@ use crate::transaction_service::{ models::{CompletedTransaction, InboundTransaction}, }, tasks::send_transaction_reply::send_transaction_reply, + utc::utc_duration_since, }; use chrono::Utc; use futures::future::FutureExt; @@ -236,10 +237,7 @@ where }; // Determine the time remaining before this transaction times out - let elapsed_time = Utc::now() - .naive_utc() - .signed_duration_since(inbound_tx.timestamp) - .to_std() + let elapsed_time = utc_duration_since(&inbound_tx.timestamp) .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?; let timeout_duration = match self @@ -261,10 +259,7 @@ where let resend = match inbound_tx.last_send_timestamp { None => true, Some(timestamp) => { - let elapsed_time = Utc::now() - .naive_utc() - .signed_duration_since(timestamp) - .to_std() + let elapsed_time = utc_duration_since(×tamp) .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?; elapsed_time > self.resources.config.transaction_resend_period }, 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 e967a51044..774351782e 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 @@ -36,6 +36,7 @@ use crate::{ send_transaction_cancelled::send_transaction_cancelled_message, wait_on_dial::wait_on_dial, }, + utc::utc_duration_since, }, }; use chrono::Utc; @@ -325,10 +326,7 @@ where } // Determine the time remaining before this transaction times out - let elapsed_time = Utc::now() - .naive_utc() - .signed_duration_since(outbound_tx.timestamp) - .to_std() + let elapsed_time = utc_duration_since(&outbound_tx.timestamp) .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?; let timeout_duration = match self @@ -350,10 +348,7 @@ where let resend = match outbound_tx.last_send_timestamp { None => true, Some(timestamp) => { - let elapsed_time = Utc::now() - .naive_utc() - .signed_duration_since(timestamp) - .to_std() + let elapsed_time = utc_duration_since(×tamp) .map_err(|e| TransactionServiceProtocolError::new(self.id, e.into()))?; elapsed_time > self.resources.config.transaction_resend_period }, diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 84e0fe4484..f7771ab403 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -44,6 +44,7 @@ use crate::{ send_transaction_cancelled::send_transaction_cancelled_message, send_transaction_reply::send_transaction_reply, }, + utc::utc_duration_since, }, types::HashDigest, util::watch::Watch, @@ -1267,7 +1268,7 @@ where } // Check if the last reply is beyond the resend cooldown if let Some(timestamp) = inbound_tx.last_send_timestamp { - let elapsed_time = Utc::now().naive_utc().signed_duration_since(timestamp).to_std()?; + let elapsed_time = utc_duration_since(×tamp)?; if elapsed_time < self.resources.config.resend_response_cooldown { trace!( target: LOG_TARGET, diff --git a/base_layer/wallet/src/transaction_service/utc.rs b/base_layer/wallet/src/transaction_service/utc.rs new file mode 100644 index 0000000000..c81b80b6cc --- /dev/null +++ b/base_layer/wallet/src/transaction_service/utc.rs @@ -0,0 +1,30 @@ +use chrono::{NaiveDateTime, Utc}; +use std::time::Duration; +use thiserror::Error; + +/// The error happens when a duration is negative. +#[derive(Debug, Error)] +#[error("Diration is negative: {ms} ms")] +pub struct NegativeDurationError { + ms: i64, +} + +/// The function compared two non-leap UTC timestamps. +/// 1. `chrono` uses `SystemTime` and will never +/// produce leap-seconds. +/// 2. `chrono` supports leap seconds that can be read from +/// the string format (as `60` second), because it's required by the standard (ISO 8601). +/// 3. Leap-second handled automatically by NTP and we +/// could ignore it as soon as `chrono` doesn't handle +/// them accurately. No guarantees and only the one +/// second handeled. +pub fn utc_duration_since(since: &NaiveDateTime) -> Result { + let now_ms = Utc::now().naive_utc().timestamp_millis(); + let since_ms = since.timestamp_millis(); + let ms = now_ms - since_ms; + if ms >= 0 { + Ok(Duration::from_millis(ms as u64)) + } else { + Err(NegativeDurationError { ms }) + } +} diff --git a/base_layer/wallet_ffi/Cargo.toml b/base_layer/wallet_ffi/Cargo.toml index d426e42697..1402cd3e14 100644 --- a/base_layer/wallet_ffi/Cargo.toml +++ b/base_layer/wallet_ffi/Cargo.toml @@ -17,7 +17,7 @@ tari_wallet = { version = "^0.21", path = "../wallet", features = ["c_integratio tari_shutdown = { version = "^0.21", path = "../../infrastructure/shutdown" } tari_utilities = "^0.3" -chrono = { version = "0.4.6", features = ["serde"]} +chrono = { version = "0.4.19", default-features = false, features = ["serde"] } futures = { version = "^0.3.1", features =["compat", "std"]} libc = "0.2.65" log = "0.4.6" diff --git a/comms/Cargo.toml b/comms/Cargo.toml index b27d6707ac..612524729c 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -20,7 +20,7 @@ async-trait = "0.1.36" bitflags = "1.0.4" blake2 = "0.9.0" bytes = { version = "1", features = ["serde"] } -chrono = { version = "0.4.6", features = ["serde"] } +chrono = { version = "0.4.19", default-features = false, features = ["serde"] } cidr = "0.1.0" clear_on_drop = "=0.2.4" data-encoding = "2.2.0" diff --git a/comms/dht/Cargo.toml b/comms/dht/Cargo.toml index fb207792fa..485873484a 100644 --- a/comms/dht/Cargo.toml +++ b/comms/dht/Cargo.toml @@ -21,7 +21,7 @@ anyhow = "1.0.32" bitflags = "1.2.0" bytes = "0.5" chacha20 = "0.7.1" -chrono = "0.4.9" +chrono = { version = "0.4.19", default-features = false } diesel = { version = "1.4.7", features = ["sqlite", "serde_json", "chrono", "numeric"] } diesel_migrations = "1.4.0" libsqlite3-sys = { version = ">=0.8.0, <0.13.0", features = ["bundled"], optional = true } From 82cbad1fafe3387f0e1f672a02982faa451dec3f Mon Sep 17 00:00:00 2001 From: Martin Stefcek <35243812+Cifko@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:32:21 +0100 Subject: [PATCH 17/46] chore: add node id/public key to log mdc (#3559) Description --- Add node id and public key to the logs, using mdc. Since you have to propagate the mdc to every thread, I'm not 100% sure it's everywhere. But it can be easily extended if we found it missing somewhere. Also there is single point of creation of the mdc data. But I propagate everything, so if we want something else in the mdc, it will be one line change. How Has This Been Tested? --- Manually. --- Cargo.lock | 12 +++++--- applications/tari_base_node/Cargo.toml | 1 + applications/tari_base_node/src/main.rs | 3 ++ base_layer/core/Cargo.toml | 1 + .../state_machine_service/initializer.rs | 3 ++ .../state_machine_service/state_machine.rs | 3 ++ .../states/block_sync.rs | 4 +++ .../states/header_sync.rs | 6 ++++ .../state_machine_service/states/listening.rs | 6 ++++ base_layer/core/src/chain_storage/async_db.rs | 8 ++++- base_layer/core/src/mempool/async_mempool.rs | 29 ++++++++++++++----- .../src/mempool/sync_protocol/initializer.rs | 5 ++++ common/logging/log4rs_sample_base_node.yml | 5 ++-- comms/Cargo.toml | 1 + comms/dht/Cargo.toml | 1 + comms/dht/src/connectivity/mod.rs | 4 +++ comms/dht/src/discovery/service.rs | 3 ++ comms/src/connectivity/manager.rs | 7 ++++- 18 files changed, 85 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dcb87c9737..62e66d73f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4330,7 +4330,8 @@ dependencies = [ "config", "either", "futures 0.3.17", - "log", + "log 0.4.14", + "log-mdc", "num_cpus", "opentelemetry", "opentelemetry-jaeger", @@ -4440,7 +4441,8 @@ dependencies = [ "futures 0.3.17", "lazy_static 1.4.0", "lmdb-zero", - "log", + "log 0.4.14", + "log-mdc", "multiaddr", "nom 5.1.2", "openssl-sys", @@ -4491,7 +4493,8 @@ dependencies = [ "lazy_static 1.4.0", "libsqlite3-sys", "lmdb-zero", - "log", + "log 0.4.14", + "log-mdc", "petgraph", "pin-project 0.4.28", "prost", @@ -4596,7 +4599,8 @@ dependencies = [ "integer-encoding 3.0.2", "lazy_static 1.4.0", "lmdb-zero", - "log", + "log 0.4.14", + "log-mdc", "monero", "newtype-ops", "num", diff --git a/applications/tari_base_node/Cargo.toml b/applications/tari_base_node/Cargo.toml index d43d833537..268ff90f15 100644 --- a/applications/tari_base_node/Cargo.toml +++ b/applications/tari_base_node/Cargo.toml @@ -28,6 +28,7 @@ config = { version = "0.9.3" } either = "1.6.1" futures = { version = "^0.3.16", default-features = false, features = ["alloc"] } log = { version = "0.4.8", features = ["std"] } +log-mdc = "0.1.0" num_cpus = "1" regex = "1" rustyline = "9.0" diff --git a/applications/tari_base_node/src/main.rs b/applications/tari_base_node/src/main.rs index c658bf74e5..47f12b3a90 100644 --- a/applications/tari_base_node/src/main.rs +++ b/applications/tari_base_node/src/main.rs @@ -174,6 +174,9 @@ async fn run_node(node_config: Arc, bootstrap: ConfigBootstrap) -> PeerFeatures::COMMUNICATION_NODE, )?; + log_mdc::insert("node-public-key", node_identity.public_key().to_string()); + log_mdc::insert("node-id", node_identity.node_id().to_string()); + // Exit if create_id or init arguments were run if bootstrap.create_id { info!( diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index aaf400f5e8..0d8f4f8801 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -48,6 +48,7 @@ integer-encoding = "3.0.2" lazy_static = "1.4.0" lmdb-zero = "0.4.4" log = "0.4" +log-mdc = "0.1.0" monero = { version = "^0.13.0", features = ["serde_support"], optional = true } newtype-ops = "0.1.4" num = "0.3" diff --git a/base_layer/core/src/base_node/state_machine_service/initializer.rs b/base_layer/core/src/base_node/state_machine_service/initializer.rs index b3dea2f6af..04a66f073c 100644 --- a/base_layer/core/src/base_node/state_machine_service/initializer.rs +++ b/base_layer/core/src/base_node/state_machine_service/initializer.rs @@ -93,7 +93,10 @@ where B: BlockchainBackend + 'static let db = self.db.clone(); let config = self.config.clone(); + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); context.spawn_when_ready(move |handles| async move { + log_mdc::extend(mdc); let chain_metadata_service = handles.expect_handle::(); let node_local_interface = handles.expect_handle::(); let connectivity = handles.expect_handle::(); diff --git a/base_layer/core/src/base_node/state_machine_service/state_machine.rs b/base_layer/core/src/base_node/state_machine_service/state_machine.rs index 95232568e2..4bb5aa7ccb 100644 --- a/base_layer/core/src/base_node/state_machine_service/state_machine.rs +++ b/base_layer/core/src/base_node/state_machine_service/state_machine.rs @@ -218,7 +218,10 @@ impl BaseNodeStateMachine { let next_state_future = self.next_state_event(&mut state); // Get the next `StateEvent`, returning a `UserQuit` state event if the interrupt signal is triggered + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); let next_event = select_next_state_event(interrupt_signal, next_state_future).await; + log_mdc::extend(mdc); // Publish the event on the event bus let _ = self.event_publisher.send(Arc::new(next_event.clone())); trace!( diff --git a/base_layer/core/src/base_node/state_machine_service/states/block_sync.rs b/base_layer/core/src/base_node/state_machine_service/states/block_sync.rs index 9addd1ab08..ddfdd28a7d 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/block_sync.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/block_sync.rs @@ -98,13 +98,17 @@ impl BlockSync { }); let timer = Instant::now(); + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); match synchronizer.synchronize().await { Ok(()) => { + log_mdc::extend(mdc); info!(target: LOG_TARGET, "Blocks synchronized in {:.0?}", timer.elapsed()); self.is_synced = true; StateEvent::BlocksSynchronized }, Err(err) => { + log_mdc::extend(mdc); warn!(target: LOG_TARGET, "Block sync failed: {}", err); StateEvent::BlockSyncFailed }, diff --git a/base_layer/core/src/base_node/state_machine_service/states/header_sync.rs b/base_layer/core/src/base_node/state_machine_service/states/header_sync.rs index 08c602cc66..6aa5999bd2 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/header_sync.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/header_sync.rs @@ -111,22 +111,28 @@ impl HeaderSync { }); let timer = Instant::now(); + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); match synchronizer.synchronize().await { Ok(sync_peer) => { + log_mdc::extend(mdc); info!(target: LOG_TARGET, "Headers synchronized in {:.0?}", timer.elapsed()); self.is_synced = true; StateEvent::HeadersSynchronized(sync_peer) }, Err(err @ BlockHeaderSyncError::SyncFailedAllPeers) => { + log_mdc::extend(mdc); warn!(target: LOG_TARGET, "{}. Continuing...", err); StateEvent::Continue }, Err(err @ BlockHeaderSyncError::NetworkSilence) => { + log_mdc::extend(mdc); warn!(target: LOG_TARGET, "{}", err); self.is_synced = true; StateEvent::NetworkSilence }, Err(err) => { + log_mdc::extend(mdc); debug!(target: LOG_TARGET, "Header sync failed: {}", err); StateEvent::HeaderSyncFailed }, diff --git a/base_layer/core/src/base_node/state_machine_service/states/listening.rs b/base_layer/core/src/base_node/state_machine_service/states/listening.rs index 7afea9bd4a..88fec981d8 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/listening.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/listening.rs @@ -112,8 +112,11 @@ impl Listening { info!(target: LOG_TARGET, "Listening for chain metadata updates"); shared.set_state_info(StateInfo::Listening(ListeningInfo::new(self.is_synced))); let mut time_since_better_block = None; + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); loop { let metadata_event = shared.metadata_event_stream.recv().await; + log_mdc::extend(mdc.clone()); match metadata_event.as_ref().map(|v| v.deref()) { Ok(ChainMetadataEvent::NetworkSilence) => { debug!("NetworkSilence event received"); @@ -139,6 +142,7 @@ impl Listening { .peer_manager .set_peer_metadata(peer.node_id(), 1, peer_data.to_bytes()) .await; + log_mdc::extend(mdc.clone()); } let configured_sync_peers = &shared.config.block_sync_config.sync_peers; @@ -182,6 +186,7 @@ impl Listening { return FatalError(format!("Could not get local blockchain metadata. {}", e)); }, }; + log_mdc::extend(mdc.clone()); // If this node is just one block behind, wait for block propagation before // rushing to sync mode @@ -216,6 +221,7 @@ impl Listening { return FatalError(format!("Could not get local blockchain metadata. {}", e)); }, }; + log_mdc::extend(mdc.clone()); let sync_mode = determine_sync_mode( shared.config.blocks_behind_before_considered_lagging, diff --git a/base_layer/core/src/chain_storage/async_db.rs b/base_layer/core/src/chain_storage/async_db.rs index bf7c9b4a06..269e096da5 100644 --- a/base_layer/core/src/chain_storage/async_db.rs +++ b/base_layer/core/src/chain_storage/async_db.rs @@ -93,8 +93,11 @@ macro_rules! make_async_fn { $(#[$outer])* pub async fn $fn(&self) -> Result<$rtype, ChainStorageError> { let db = self.db.clone(); + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); tokio::task::spawn_blocking(move || { - trace_log($name, move || db.$fn()) + log_mdc::extend(mdc.clone()); + trace_log($name, move || db.$fn()) }) .await? } @@ -107,7 +110,10 @@ macro_rules! make_async_fn { $(#[$outer])* pub async fn $fn$(< $( $lt $( : $clt )? ),+ +Sync+Send + 'static >)?(&self, $($param: $ptype),+) -> Result<$rtype, ChainStorageError> { let db = self.db.clone(); + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); tokio::task::spawn_blocking(move || { + log_mdc::extend(mdc.clone()); trace_log($name, move || db.$fn($($param),+)) }) .await? diff --git a/base_layer/core/src/mempool/async_mempool.rs b/base_layer/core/src/mempool/async_mempool.rs index 9a9b0a5f8a..d99da619f1 100644 --- a/base_layer/core/src/mempool/async_mempool.rs +++ b/base_layer/core/src/mempool/async_mempool.rs @@ -31,25 +31,38 @@ use tari_common_types::types::Signature; macro_rules! make_async { ($fn:ident($($param1:ident:$ptype1:ty,$param2:ident:$ptype2:ty),+) -> $rtype:ty) => { pub async fn $fn(mp: Mempool, $($param1: $ptype1, $param2: $ptype2),+) -> Result<$rtype, MempoolError> { - tokio::task::spawn_blocking(move || mp.$fn($($param1,$param2),+)) - .await - .or_else(|err| Err(MempoolError::BlockingTaskSpawnError(err.to_string()))) - .and_then(|inner_result| inner_result) + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); + tokio::task::spawn_blocking(move || { + log_mdc::extend(mdc.clone()); + mp.$fn($($param1,$param2),+) + }) + .await + .or_else(|err| Err(MempoolError::BlockingTaskSpawnError(err.to_string()))) + .and_then(|inner_result| inner_result) } }; ($fn:ident($($param:ident:$ptype:ty),+) -> $rtype:ty) => { pub async fn $fn(mp: Mempool, $($param: $ptype),+) -> Result<$rtype, MempoolError> { - tokio::task::spawn_blocking(move || mp.$fn($($param),+)) - .await - .or_else(|err| Err(MempoolError::BlockingTaskSpawnError(err.to_string()))) - .and_then(|inner_result| inner_result) + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); + tokio::task::spawn_blocking(move || { + log_mdc::extend(mdc.clone()); + mp.$fn($($param),+) + }) + .await + .or_else(|err| Err(MempoolError::BlockingTaskSpawnError(err.to_string()))) + .and_then(|inner_result| inner_result) } }; ($fn:ident() -> $rtype:ty) => { pub async fn $fn(mp: Mempool) -> Result<$rtype, MempoolError> { + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); tokio::task::spawn_blocking(move || { + log_mdc::extend(mdc.clone()); mp.$fn() }) .await diff --git a/base_layer/core/src/mempool/sync_protocol/initializer.rs b/base_layer/core/src/mempool/sync_protocol/initializer.rs index 9af871121d..964522c637 100644 --- a/base_layer/core/src/mempool/sync_protocol/initializer.rs +++ b/base_layer/core/src/mempool/sync_protocol/initializer.rs @@ -74,7 +74,10 @@ impl ServiceInitializer for MempoolSyncInitializer { let mempool = self.mempool.clone(); let notif_rx = self.notif_rx.take().unwrap(); + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); context.spawn_until_shutdown(move |handles| async move { + log_mdc::extend(mdc.clone()); let state_machine = handles.expect_handle::(); let connectivity = handles.expect_handle::(); // Ensure that we get an subscription ASAP so that we don't miss any connectivity events @@ -84,6 +87,7 @@ impl ServiceInitializer for MempoolSyncInitializer { if !status_watch.borrow().bootstrapped { debug!(target: LOG_TARGET, "Waiting for node to bootstrap..."); while status_watch.changed().await.is_ok() { + log_mdc::extend(mdc.clone()); if status_watch.borrow().bootstrapped { debug!(target: LOG_TARGET, "Node bootstrapped. Starting mempool sync protocol"); break; @@ -94,6 +98,7 @@ impl ServiceInitializer for MempoolSyncInitializer { ); sleep(Duration::from_secs(1)).await; } + log_mdc::extend(mdc.clone()); } MempoolSyncProtocol::new(config, notif_rx, connectivity_event_subscription, mempool) diff --git a/common/logging/log4rs_sample_base_node.yml b/common/logging/log4rs_sample_base_node.yml index 17781c077b..dee1bc500a 100644 --- a/common/logging/log4rs_sample_base_node.yml +++ b/common/logging/log4rs_sample_base_node.yml @@ -18,8 +18,7 @@ appenders: encoder: pattern: "{d(%H:%M)} {h({l}):5} {m}{n}" filters: - - - kind: threshold + - kind: threshold level: warn # An appender named "network" that writes to a file with a custom pattern encoder @@ -54,7 +53,7 @@ appenders: count: 5 pattern: "log/base-node/base_layer.{}.log" encoder: - pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] [Thread:{I}] {l:5} {m}{n}" + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] [Thread:{I}] [{X(node-public-key)},{X(node-id)}] {l:5} {m}{n}" # An appender named "other" that writes to a file with a custom pattern encoder other: diff --git a/comms/Cargo.toml b/comms/Cargo.toml index 612524729c..710d4746ac 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -29,6 +29,7 @@ futures = { version = "^0.3", features = ["async-await"] } lazy_static = "1.3.0" lmdb-zero = "0.4.4" log = { version = "0.4.0", features = ["std"] } +log-mdc = "0.1.0" multiaddr = { version = "0.13.0" } nom = { version = "5.1.0", features = ["std"], default-features = false } openssl-sys = { version = "0.9.66", features = ["vendored"], optional = true } diff --git a/comms/dht/Cargo.toml b/comms/dht/Cargo.toml index 485873484a..80739d331f 100644 --- a/comms/dht/Cargo.toml +++ b/comms/dht/Cargo.toml @@ -28,6 +28,7 @@ libsqlite3-sys = { version = ">=0.8.0, <0.13.0", features = ["bundled"], optiona digest = "0.9.0" futures = { version = "^0.3.1" } log = "0.4.8" +log-mdc = "0.1.0" prost = "=0.8.0" prost-types = "=0.8.0" rand = "0.8" diff --git a/comms/dht/src/connectivity/mod.rs b/comms/dht/src/connectivity/mod.rs index a5c3c63085..a26fe6b3b0 100644 --- a/comms/dht/src/connectivity/mod.rs +++ b/comms/dht/src/connectivity/mod.rs @@ -117,9 +117,13 @@ impl DhtConnectivity { pub fn spawn(mut self) -> JoinHandle> { // Listen to events as early as possible let connectivity_events = self.connectivity.get_event_subscription(); + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); task::spawn(async move { + log_mdc::extend(mdc.clone()); debug!(target: LOG_TARGET, "Waiting for connectivity manager to start"); let _ = self.connectivity.wait_started().await; + log_mdc::extend(mdc.clone()); match self.run(connectivity_events).await { Ok(_) => Ok(()), Err(err) => { diff --git a/comms/dht/src/discovery/service.rs b/comms/dht/src/discovery/service.rs index 3dcf615742..50d6f68b79 100644 --- a/comms/dht/src/discovery/service.rs +++ b/comms/dht/src/discovery/service.rs @@ -92,7 +92,10 @@ impl DhtDiscoveryService { } pub fn spawn(self) { + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); task::spawn(async move { + log_mdc::extend(mdc); info!(target: LOG_TARGET, "Discovery service started"); self.run().await }); diff --git a/comms/src/connectivity/manager.rs b/comms/src/connectivity/manager.rs index 9d045df380..e827d8caae 100644 --- a/comms/src/connectivity/manager.rs +++ b/comms/src/connectivity/manager.rs @@ -149,7 +149,12 @@ struct ConnectivityManagerActor { impl ConnectivityManagerActor { pub fn spawn(self) -> JoinHandle<()> { - task::spawn(Self::run(self)) + let mut mdc = vec![]; + log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); + task::spawn(async { + log_mdc::extend(mdc); + Self::run(self).await + }) } #[tracing::instrument(name = "connectivity_manager_actor::run", skip(self))] From e177d373b675784d8967bafe65f021ca41e435bc Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Fri, 12 Nov 2021 14:04:26 +0200 Subject: [PATCH 18/46] test: fix cucumber console wallet startup (#3564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description --- - Fixed an issue with the wallet startup promise not resolving consistently (_... a broken fix introduced by me in #3534_). - Added explicit startup markers (`Tari Console Wallet running...`) in the console wallet that will print to stdout for the sake of cucumber tests. These are scraped to indicate the wallet startup state. - Improved flaky test `Scenario: As a user I want to discover-peer via command line` - Added variable time out settings for `wallet.runCommand(command)` for the different `As a user I ... via command line` steps according to the higher-level logic. Motivation and Context --- Cucumber tests were not running successfully. How Has This Been Tested? --- Cucumber: - `npm test -- --profile ci --name "via command line"` - `npm test -- --tags "not @long-running and not @broken"` Sample stdout output: ``` rust Installing new config file at C:\Users\pluto\Documents\Code\@tari-project\integration_tests\temp\base_nodes\202111121208\Wallet5529-WALLET\config\config.toml Initializing logging according to "C:\\Users\\pluto\\Documents\\Code\\@tari-project\\integration_tests\\log4rs\\wallet.yml" Tari Console Wallet running... (gRPC mode started) Initializing logging according to "C:\\Users\\pluto\\Documents\\Code\\@tari-project\\integration_tests\\log4rs\\wallet.yml" Tari Console Wallet running... (Command mode started) ============== Command Runner ============== 1. discover-peer e88343a9b0d734a0ed98e0e3b558493d91220216599b676de12c76a2ce7ef369 Waiting for connectivity... ✅ 🌎 Peer discovery started. ⚡️ Discovery succeeded in 77ms. [05696c85b9a0ab4b] PK=e88343a9b0d734a0ed98e0e3b558493d91220216599b676de12c76a2ce7ef369 (/ip4/127.0.0.1/tcp/5537) - . Type: BASE_NODE. User agent: tari/basenode/0.21.0. Last connected at 2021-11-12 10:08:51. Tari Console Wallet running... (Command mode completed) Tari Console Wallet running... (gRPC mode started) >>>> End of ./temp/base_nodes/202111121208/Wallet5529-WALLET/log/stdout.log ``` ``` rust Installing new config file at C:\Users\pluto\Documents\Code\@tari-project\integration_tests\temp\base_nodes\202111121210\Wallet5586-WALLET\config\config.toml Initializing logging according to "C:\\Users\\pluto\\Documents\\Code\\@tari-project\\integration_tests\\log4rs\\wallet.yml" Tari Console Wallet running... (gRPC mode started) Initializing logging according to "C:\\Users\\pluto\\Documents\\Code\\@tari-project\\integration_tests\\log4rs\\wallet.yml" Tari Console Wallet running... (Command mode started) ============== Command Runner ============== 1. set-custom-base-node a637ce92c0e07ff9fc39a023a1b6530d1873be782cc27e258995af27bb5d3101 /ip4/127.0.0.1/tcp/5581 Setting base node peer... a637ce92c0e07ff9fc39a023a1b6530d1873be782cc27e258995af27bb5d3101::/ip4/127.0.0.1/tcp/5581 Custom base node peer saved in wallet database. Tari Console Wallet running... (Command mode completed) Tari Console Wallet running... (gRPC mode started) Initializing logging according to "C:\\Users\\pluto\\Documents\\Code\\@tari-project\\integration_tests\\log4rs\\wallet.yml" Tari Console Wallet running... (Command mode started) ============== Command Runner ============== 1. clear-custom-base-node Custom base node peer cleared from wallet database. Tari Console Wallet running... (Command mode completed) Tari Console Wallet running... (gRPC mode started) >>>> End of ./temp/base_nodes/202111121210/Wallet5586-WALLET/log/stdout.log ``` --- .../tari_console_wallet/src/wallet_modes.rs | 40 +++++++++++++++++++ integration_tests/features/WalletCli.feature | 2 +- integration_tests/features/support/steps.js | 21 ++++++---- integration_tests/helpers/walletProcess.js | 19 +++++---- 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index 2803d8f9fb..a0fcaced5d 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -132,9 +132,18 @@ pub fn command_mode(config: WalletModeConfig, wallet: WalletSqlite, command: Str global_config, handle, .. } = config.clone(); let commands = vec![parse_command(&command)?]; + + // Do not remove this println! + const CUCUMBER_TEST_MARKER_A: &str = "Tari Console Wallet running... (Command mode started)"; + println!("{}", CUCUMBER_TEST_MARKER_A); + info!(target: LOG_TARGET, "Starting wallet command mode"); handle.block_on(command_runner(commands, wallet.clone(), global_config))?; + // Do not remove this println! + const CUCUMBER_TEST_MARKER_B: &str = "Tari Console Wallet running... (Command mode completed)"; + println!("{}", CUCUMBER_TEST_MARKER_B); + info!(target: LOG_TARGET, "Completed wallet command mode"); wallet_or_exit(config, wallet) @@ -164,9 +173,17 @@ pub fn script_mode(config: WalletModeConfig, wallet: WalletSqlite, path: PathBuf } println!("{} commands parsed successfully.", commands.len()); + // Do not remove this println! + const CUCUMBER_TEST_MARKER_A: &str = "Tari Console Wallet running... (Script mode started)"; + println!("{}", CUCUMBER_TEST_MARKER_A); + println!("Starting the command runner!"); handle.block_on(command_runner(commands, wallet.clone(), global_config))?; + // Do not remove this println! + const CUCUMBER_TEST_MARKER_B: &str = "Tari Console Wallet running... (Script mode completed)"; + println!("{}", CUCUMBER_TEST_MARKER_B); + info!(target: LOG_TARGET, "Completed wallet script mode"); wallet_or_exit(config, wallet) @@ -238,6 +255,10 @@ pub fn tui_mode(config: WalletModeConfig, mut wallet: WalletSqlite) -> Result<() info!(target: LOG_TARGET, "Starting app"); + // Do not remove this println! + const CUCUMBER_TEST_MARKER: &str = "Tari Console Wallet running... (TUI mode started)"; + println!("{}", CUCUMBER_TEST_MARKER); + { let _enter = handle.enter(); ui::run(app)?; @@ -258,6 +279,11 @@ pub fn recovery_mode(config: WalletModeConfig, wallet: WalletSqlite) -> Result<( wallet_mode, .. } = config.clone(); + + // Do not remove this println! + const CUCUMBER_TEST_MARKER_A: &str = "Tari Console Wallet running... (Recovery mode started)"; + println!("{}", CUCUMBER_TEST_MARKER_A); + println!("Starting recovery..."); match handle.block_on(wallet_recovery(&wallet, &base_node_config)) { Ok(_) => println!("Wallet recovered!"), @@ -272,6 +298,10 @@ pub fn recovery_mode(config: WalletModeConfig, wallet: WalletSqlite) -> Result<( }, } + // Do not remove this println! + const CUCUMBER_TEST_MARKER_B: &str = "Tari Console Wallet running... (Recovery mode completed)"; + println!("{}", CUCUMBER_TEST_MARKER_B); + println!("Starting TUI."); match wallet_mode { @@ -287,6 +317,7 @@ pub fn grpc_mode(config: WalletModeConfig, wallet: WalletSqlite) -> Result<(), E } = config; info!(target: LOG_TARGET, "Starting grpc server"); let grpc = WalletGrpcServer::new(wallet); + handle .block_on(run_grpc(grpc, global_config.grpc_console_wallet_address)) .map_err(ExitCodes::GrpcError)?; @@ -295,6 +326,10 @@ pub fn grpc_mode(config: WalletModeConfig, wallet: WalletSqlite) -> Result<(), E } async fn run_grpc(grpc: WalletGrpcServer, grpc_console_wallet_address: Multiaddr) -> Result<(), String> { + // Do not remove this println! + const CUCUMBER_TEST_MARKER_A: &str = "Tari Console Wallet running... (gRPC mode started)"; + println!("{}", CUCUMBER_TEST_MARKER_A); + info!(target: LOG_TARGET, "Starting GRPC on {}", grpc_console_wallet_address); let socket = multiaddr_to_socketaddr(&grpc_console_wallet_address).map_err(|e| e.to_string())?; Server::builder() @@ -302,6 +337,11 @@ async fn run_grpc(grpc: WalletGrpcServer, grpc_console_wallet_address: Multiaddr .serve(socket) .await .map_err(|e| format!("GRPC server returned error:{}", e))?; + + // Do not remove this println! + const CUCUMBER_TEST_MARKER_B: &str = "Tari Console Wallet running... (gRPC mode completed)"; + println!("{}", CUCUMBER_TEST_MARKER_B); + info!(target: LOG_TARGET, "Stopping GRPC"); Ok(()) } diff --git a/integration_tests/features/WalletCli.feature b/integration_tests/features/WalletCli.feature index 0978bb96c6..6ab1d20cdf 100644 --- a/integration_tests/features/WalletCli.feature +++ b/integration_tests/features/WalletCli.feature @@ -126,9 +126,9 @@ Feature: Wallet CLI @flaky Scenario: As a user I want to discover-peer via command line Given I have a seed node SEED + And I have wallet WALLET connected to seed node SEED And I have a base node BASE1 connected to seed SEED And I have a base node BASE2 connected to seed SEED - And I have wallet WALLET connected to base node BASE1 And I discover peer BASE2 on wallet WALLET via command line Then WALLET is connected to BASE2 diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index 47241ef1f8..b9a0591fc0 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -3490,6 +3490,7 @@ Given( async function wallet_run_command( wallet, command, + timeOutSeconds = 15, message = "", printMessage = true ) { @@ -3511,7 +3512,7 @@ async function wallet_run_command( return true; }, true, - 45 * 1000, + timeOutSeconds * 1000, 5 * 1000, 5 ); @@ -3523,7 +3524,7 @@ Then( { timeout: 180 * 1000 }, async function (name, amount) { let wallet = this.getWallet(name); - let output = await wallet_run_command(wallet, "get-balance"); + let output = await wallet_run_command(wallet, "get-balance", 180); let parse = output.buffer.match(/Available balance: (\d*.\d*) T/); expect(parse, "Parsing the output buffer failed").to.not.be.null; expect(parseFloat(parse[1])).to.be.greaterThanOrEqual(amount / 1000000); @@ -3538,7 +3539,8 @@ When( let dest_pubkey = this.getWalletPubkey(receiver); await wallet_run_command( wallet, - `send-tari ${amount} ${dest_pubkey} test message` + `send-tari ${amount} ${dest_pubkey} test message`, + 180 ); // await wallet.sendTari(dest_pubkey, amount, "test message"); } @@ -3552,7 +3554,8 @@ When( let dest_pubkey = this.getWalletPubkey(receiver); await wallet_run_command( wallet, - `send-one-sided ${amount} ${dest_pubkey} test message` + `send-one-sided ${amount} ${dest_pubkey} test message`, + 180 ); // await wallet.sendOneSided(dest_pubkey, amount, "test message"); } @@ -3566,7 +3569,8 @@ Then( let dest_pubkey = this.getWalletPubkey(receiver); await wallet_run_command( wallet, - `make-it-rain ${freq} ${duration} ${amount} ${amount_inc} now ${dest_pubkey} negotiated test message` + `make-it-rain ${freq} ${duration} ${amount} ${amount_inc} now ${dest_pubkey} negotiated test message`, + 300 ); } ); @@ -3589,7 +3593,8 @@ When( let wallet = this.getWallet(name); await wallet_run_command( wallet, - `coin-split ${amount_per_coin} ${number_of_coins}` + `coin-split ${amount_per_coin} ${number_of_coins}`, + 180 ); } ); @@ -3600,7 +3605,7 @@ When( async function (node, name) { let wallet = this.getWallet(name); let peer = this.getNode(node).peerAddress().split("::")[0]; - let output = await wallet_run_command(wallet, `discover-peer ${peer}`); + let output = await wallet_run_command(wallet, `discover-peer ${peer}`, 120); let parse = output.buffer.match(/Discovery succeeded/); expect(parse, "Parsing the output buffer failed").to.not.be.null; } @@ -3613,7 +3618,7 @@ When( await sleep(5000); let wallet = this.getWallet(name); let pubkey = this.getNode(who).peerAddress().split("::")[0]; - let output = await wallet_run_command(wallet, `whois ${pubkey}`); + let output = await wallet_run_command(wallet, `whois ${pubkey}`, 20); let parse = output.buffer.match(/Public Key: (.+)\n/); expect(parse, "Parsing the output buffer failed").to.not.be.null; expect(parse[1]).to.be.equal(pubkey); diff --git a/integration_tests/helpers/walletProcess.js b/integration_tests/helpers/walletProcess.js index 21149cfc5d..549b4993d2 100644 --- a/integration_tests/helpers/walletProcess.js +++ b/integration_tests/helpers/walletProcess.js @@ -59,7 +59,7 @@ class WalletProcess { this.peerSeeds = addresses; } - run(cmd, args, saveFile, input_buffer, output) { + run(cmd, args, saveFile, input_buffer, output, waitForCommand) { let thePromise = new Promise((resolve, reject) => { if (!fs.existsSync(this.baseDir)) { fs.mkdirSync(this.baseDir, { recursive: true }); @@ -110,16 +110,20 @@ class WalletProcess { ps.stdin.write(input_buffer); } ps.stdout.on("data", (data) => { - //console.log(`stdout: ${data}`); + //console.log(`\nstdout: ${data}`); if (output !== undefined && output.buffer !== undefined) { output.buffer += data; } fs.appendFileSync(`${this.baseDir}/log/stdout.log`, data.toString()); if ( - (!this.recoverWallet && - data.toString().match(/Starting grpc server/)) || - (this.recoverWallet && - data.toString().match(/Initializing logging according/)) + (!waitForCommand && + data.toString().match(/Tari Console Wallet running/i)) || + (waitForCommand && + data + .toString() + .match( + /(?=.*Tari Console Wallet running)(?=.*Command mode completed)/gim + )) ) { resolve(ps); } @@ -144,7 +148,6 @@ class WalletProcess { }); expect(ps.error).to.be.undefined; this.ps = ps; - resolve(ps); }); return thePromise; } @@ -244,7 +247,7 @@ class WalletProcess { } let output = { buffer: "" }; // In case we killed the wallet fast send enter. Because it will ask for the logs again (e.g. whois test) - await this.run(await this.compile(), args, true, "\n", output); + await this.run(await this.compile(), args, true, "\n", output, true); return output; } From f013e191672e8b690147c11a657ba8e7d4172f55 Mon Sep 17 00:00:00 2001 From: Mike the Tike Date: Fri, 12 Nov 2021 14:40:19 +0200 Subject: [PATCH 19/46] test: reduce cucumber ci to critical only (#3566) Hopefully by reducing this CI step to critical only, we'll have more green ticks for the check and will have less to maintain regularly. Ideally we would want to run all of the tests, but because some of the non-critical ones fail in a flaky manner, devs have started ignoring this step and making it required takes too long. --- .circleci/config.yml | 2 +- Cargo.lock | 2 +- applications/tari_base_node/src/main.rs | 1 + applications/tari_console_wallet/src/main.rs | 1 + .../src/base_node/state_machine_service/states/listening.rs | 2 +- .../recovery/standard_outputs_recoverer.rs | 2 +- 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e274bcd9b3..f502eb02da 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -129,7 +129,7 @@ commands: - run: name: Run cucumber scenarios no_output_timeout: 20m - command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --profile "ci" --tags "not @long-running and not @broken and not @wallet-ffi" --format json:cucumber_output/tests.cucumber --exit --retry 3 --retry-tag-filter "@flaky and not @broken" + command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --profile "ci" --tags "@critical and not @long-running and not @broken and not @wallet-ffi" --format json:cucumber_output/tests.cucumber --exit --retry 3 --retry-tag-filter "@flaky and not @broken" - run: name: Generate report command: cd integration_tests && node ./generate_report.js diff --git a/Cargo.lock b/Cargo.lock index 62e66d73f2..e128344af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1125,7 +1125,7 @@ dependencies = [ name = "deps_only" version = "0.1.0" dependencies = [ - "log 0.4.14", + "log", ] [[package]] diff --git a/applications/tari_base_node/src/main.rs b/applications/tari_base_node/src/main.rs index 47f12b3a90..ba0d3e3ea0 100644 --- a/applications/tari_base_node/src/main.rs +++ b/applications/tari_base_node/src/main.rs @@ -26,6 +26,7 @@ #![deny(unused_must_use)] #![deny(unreachable_patterns)] #![deny(unknown_lints)] +#![deny(clippy::needless_borrow)] /// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣶⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ /// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣾⣿⡿⠋⠀⠀⠀⠀⠉⠛⠿⣿⣿⣶⣤⣀⠀⠀⠀⠀⠀⠀⢰⣿⣾⣾⣾⣾⣾⣾⣾⣾⣾⣿⠀⠀⠀⣾⣾⣾⡀⠀⠀⠀⠀⢰⣾⣾⣾⣾⣿⣶⣶⡀⠀⠀⠀⢸⣾⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/applications/tari_console_wallet/src/main.rs b/applications/tari_console_wallet/src/main.rs index c463d83c0d..86b6eb41a9 100644 --- a/applications/tari_console_wallet/src/main.rs +++ b/applications/tari_console_wallet/src/main.rs @@ -5,6 +5,7 @@ #![deny(unused_must_use)] #![deny(unreachable_patterns)] #![deny(unknown_lints)] +#![deny(clippy::redundant_clone)] #![recursion_limit = "1024"] use crate::{recovery::get_seed_from_seed_words, wallet_modes::WalletModeConfig}; use init::{ diff --git a/base_layer/core/src/base_node/state_machine_service/states/listening.rs b/base_layer/core/src/base_node/state_machine_service/states/listening.rs index 88fec981d8..32c52396ab 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/listening.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/listening.rs @@ -210,7 +210,7 @@ impl Listening { // If we have configured sync peers, they are already filtered at this point let sync_peers = if configured_sync_peers.is_empty() { - select_sync_peers(&best_metadata, &peer_metadata_list) + select_sync_peers(best_metadata, &peer_metadata_list) } else { peer_metadata_list }; diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index 33838c0d22..650a6f2bef 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -101,7 +101,7 @@ where TBackend: OutputManagerBackend + 'static let script_key = PrivateKey::random(&mut OsRng); UnblindedOutput::new( output.committed_value, - output.blinding_factor.clone(), + output.blinding_factor, features, script, inputs!(PublicKey::from_secret_key(&script_key)), From 11b8afa31abe7e64ff366f8e83e478b017a86da5 Mon Sep 17 00:00:00 2001 From: Philip Robinson Date: Mon, 15 Nov 2021 09:14:24 +0200 Subject: [PATCH 20/46] feat: trigger time lock balance update when block received (#3567) Description --- This PR update the console wallet event monitor to trigger a balance refresh when a new block is received IF there is a timelocked balanced that could potentially changed based on the block height. How Has This Been Tested? --- cargo test --- Cargo.lock | 8 ++++---- .../tari_console_wallet/src/ui/state/app_state.rs | 9 +++++++++ .../src/ui/state/wallet_event_monitor.rs | 6 ++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e128344af5..5a48731351 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4330,7 +4330,7 @@ dependencies = [ "config", "either", "futures 0.3.17", - "log 0.4.14", + "log", "log-mdc", "num_cpus", "opentelemetry", @@ -4441,7 +4441,7 @@ dependencies = [ "futures 0.3.17", "lazy_static 1.4.0", "lmdb-zero", - "log 0.4.14", + "log", "log-mdc", "multiaddr", "nom 5.1.2", @@ -4493,7 +4493,7 @@ dependencies = [ "lazy_static 1.4.0", "libsqlite3-sys", "lmdb-zero", - "log 0.4.14", + "log", "log-mdc", "petgraph", "pin-project 0.4.28", @@ -4599,7 +4599,7 @@ dependencies = [ "integer-encoding 3.0.2", "lazy_static 1.4.0", "lmdb-zero", - "log 0.4.14", + "log", "log-mdc", "monero", "newtype-ops", diff --git a/applications/tari_console_wallet/src/ui/state/app_state.rs b/applications/tari_console_wallet/src/ui/state/app_state.rs index 783693f525..9f6abcddaa 100644 --- a/applications/tari_console_wallet/src/ui/state/app_state.rs +++ b/applications/tari_console_wallet/src/ui/state/app_state.rs @@ -693,6 +693,15 @@ impl AppStateInner { Ok(()) } + pub fn has_time_locked_balance(&self) -> bool { + if let Some(time_locked_balance) = self.data.balance.time_locked_balance { + if time_locked_balance > MicroTari::from(0) { + return true; + } + } + false + } + pub async fn refresh_balance(&mut self, balance: Balance) -> Result<(), UiError> { self.data.balance = balance; self.updated = true; diff --git a/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs b/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs index 4f9e8da173..dca471ee9b 100644 --- a/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs +++ b/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs @@ -260,6 +260,12 @@ impl WalletEventMonitor { if let Err(e) = inner.refresh_base_node_state(state).await { warn!(target: LOG_TARGET, "Error refresh app_state: {}", e); } + + if inner.has_time_locked_balance() { + if let Err(e) = self.balance_enquiry_debounce_tx.send(()) { + warn!(target: LOG_TARGET, "Error refresh app_state: {}", e); + } + } } async fn trigger_base_node_peer_refresh(&mut self, peer: Peer) { From 8d22164ca10a4493fe73f66d13edf9ddd57cc6d1 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Mon, 15 Nov 2021 11:36:58 +0200 Subject: [PATCH 21/46] feat: implement multiple read single write for sqlite (#3568) Description --- - Implemented a generic system-wide pool connection manager for diesel SQLite connections that uses the modern Write-ahead Log (WAL) SQLite3 database mode to speed up database operations (_see https://www.sqlite.org/wal.html_) with a configurable pool size. - Changed the Wallet's SQLite database connection to make use of the pool connection manager. - Refined SQLite profiling trace logs to only log an entry if the total time is > 1ms - this cut down on noisy entries as most database calls are now < 1ms. - Notable SQLite pragma settings: - [PRAGMA schema.journal_mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) = `WAL` - [PRAGMA schema.locking_mode](https://www.sqlite.org/pragma.html#pragma_locking_mode) = `NORMAL ` - [PRAGMA schema.synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous) = `| 2 | FULL` - [PRAGMA wal_autocheckpoint](https://www.sqlite.org/pragma.html#pragma_wal_autocheckpoint) = `1000` (the default) Motivation and Context --- The wallet's database connection was not optimal. How Has This Been Tested? --- - Unit tests - Cucumber tests (`npm test -- --tags "not @long-running and not @broken"`) - System-level tests with positive results all around (see discussion): - Configuration: Base node + console wallet + merge mining proxy + XMRig -> Mining - Test 1: Moderate coin split (22 transactions producing 3124 outputs) - Test 2: Moderate stress test (2x simultaneous `make-it-rain` transactions from one sender to two receiving wallets of 250 transactions each) The graphs below show the SQLite WAL mode profiling for the above two tests (sender wallet only) with a pool size of 16 for all read and write database operations where the total time (acquire lock time + db operation time) was larger than 1ms. The wallet in question had a communications breakdown to its connected base node for a while after all transactions were completed and when it eventually gained an RPC connection validation protocols for all 500 transactions queried the base node as fast as possible, which panned out in some of those protocols waiting for a pooled connection. The actual time db operations will wait for a write operation to conclude is managed by SQLite and bunched within the `db op time` measurement. ![image](https://user-images.githubusercontent.com/39146854/141734409-34b268d1-0b6c-4cc9-91d1-6933b244ee3d.png) ![image](https://user-images.githubusercontent.com/39146854/141733462-583eb0eb-e7ed-4c8c-bfb3-48e3acd4e800.png) --- Cargo.lock | 31 + Cargo.toml | 1 + .../tari_console_wallet/src/init/mod.rs | 5 +- base_layer/wallet/Cargo.toml | 1 + .../wallet/src/contacts_service/error.rs | 6 +- .../src/contacts_service/storage/sqlite_db.rs | 40 +- base_layer/wallet/src/error.rs | 11 +- .../src/output_manager_service/error.rs | 6 +- .../storage/sqlite_db/mod.rs | 725 +++++++------- base_layer/wallet/src/storage/database.rs | 2 +- base_layer/wallet/src/storage/sqlite_db.rs | 193 ++-- .../mod.rs} | 47 +- .../sqlite_utilities/wallet_db_connection.rs | 53 ++ base_layer/wallet/src/test_utils.rs | 15 +- .../wallet/src/transaction_service/error.rs | 4 +- .../transaction_service/storage/sqlite_db.rs | 892 ++++++++++-------- .../tests/output_manager_service/service.rs | 2 +- base_layer/wallet/tests/support/data.rs | 2 +- .../tests/transaction_service/service.rs | 63 +- .../tests/transaction_service/storage.rs | 4 +- .../transaction_protocols.rs | 2 +- base_layer/wallet/tests/wallet/mod.rs | 16 +- base_layer/wallet_ffi/src/lib.rs | 10 +- common/src/configuration/global.rs | 5 + common_sqlite/Cargo.toml | 14 + common_sqlite/src/connection_options.rs | 63 ++ common_sqlite/src/error.rs | 35 + common_sqlite/src/lib.rs | 25 + common_sqlite/src/sqlite_connection_pool.rs | 142 +++ 29 files changed, 1471 insertions(+), 944 deletions(-) rename base_layer/wallet/src/storage/{sqlite_utilities.rs => sqlite_utilities/mod.rs} (88%) create mode 100644 base_layer/wallet/src/storage/sqlite_utilities/wallet_db_connection.rs create mode 100644 common_sqlite/Cargo.toml create mode 100644 common_sqlite/src/connection_options.rs create mode 100644 common_sqlite/src/error.rs create mode 100644 common_sqlite/src/lib.rs create mode 100644 common_sqlite/src/sqlite_connection_pool.rs diff --git a/Cargo.lock b/Cargo.lock index 5a48731351..6d6a9ec468 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1213,6 +1213,7 @@ dependencies = [ "num-bigint 0.2.6", "num-integer", "num-traits 0.2.14", + "r2d2", "serde_json", ] @@ -3276,6 +3277,17 @@ dependencies = [ "proc-macro2 1.0.32", ] +[[package]] +name = "r2d2" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f" +dependencies = [ + "log", + "parking_lot 0.11.2", + "scheduled-thread-pool", +] + [[package]] name = "radix_trie" version = "0.2.1" @@ -3739,6 +3751,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f74fd1204073fa02d5d5d68bec8021be4c38690b61264b2fdb48083d0e7d7" +dependencies = [ + "parking_lot 0.11.2", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -4409,6 +4430,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tari_common_sqlite" +version = "0.21.0" +dependencies = [ + "diesel", + "log", + "thiserror", +] + [[package]] name = "tari_common_types" version = "0.21.0" @@ -4975,6 +5005,7 @@ dependencies = [ "serde 1.0.130", "serde_json", "tari_common", + "tari_common_sqlite", "tari_common_types", "tari_comms", "tari_comms_dht", diff --git a/Cargo.toml b/Cargo.toml index 25f6c96222..ad0d9caf5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "comms", "comms/dht", "comms/rpc_macros", + "common_sqlite", "infrastructure/shutdown", "infrastructure/storage", "infrastructure/test_utils", diff --git a/applications/tari_console_wallet/src/init/mod.rs b/applications/tari_console_wallet/src/init/mod.rs index 9a7ae20df7..0cb9d96338 100644 --- a/applications/tari_console_wallet/src/init/mod.rs +++ b/applications/tari_console_wallet/src/init/mod.rs @@ -272,7 +272,7 @@ pub async fn init_wallet( // test encryption by initializing with no passphrase... let db_path = config.console_wallet_db_file.clone(); - let result = initialize_sqlite_database_backends(db_path.clone(), None); + let result = initialize_sqlite_database_backends(db_path.clone(), None, config.wallet_connection_manager_pool_size); let (backends, wallet_encrypted) = match result { Ok(backends) => { // wallet is not encrypted @@ -281,7 +281,8 @@ pub async fn init_wallet( Err(WalletStorageError::NoPasswordError) => { // get supplied or prompt password let passphrase = get_or_prompt_password(arg_password.clone(), config.console_wallet_password.clone())?; - let backends = initialize_sqlite_database_backends(db_path, passphrase)?; + let backends = + initialize_sqlite_database_backends(db_path, passphrase, config.wallet_connection_manager_pool_size)?; (backends, true) }, diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index 5e1d89ca2d..407f9a80e5 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -17,6 +17,7 @@ tari_p2p = { version = "^0.21", path = "../p2p", features = ["auto-update"] } tari_service_framework = { version = "^0.21", path = "../service_framework" } tari_shutdown = { version = "^0.21", path = "../../infrastructure/shutdown" } tari_storage = { version = "^0.21", path = "../../infrastructure/storage" } +tari_common_sqlite = { path = "../../common_sqlite" } aes-gcm = "^0.8" async-trait = "0.1.50" diff --git a/base_layer/wallet/src/contacts_service/error.rs b/base_layer/wallet/src/contacts_service/error.rs index e1e31b90c6..b06a02f9c0 100644 --- a/base_layer/wallet/src/contacts_service/error.rs +++ b/base_layer/wallet/src/contacts_service/error.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::contacts_service::storage::database::DbKey; +use crate::{contacts_service::storage::database::DbKey, error::WalletStorageError}; use diesel::result::Error as DieselError; use tari_service_framework::reply_channel::TransportChannelError; use thiserror::Error; @@ -50,8 +50,8 @@ pub enum ContactsServiceStorageError { ValueNotFound(DbKey), #[error("Unexpected result error: `{0}`")] UnexpectedResult(String), - #[error("R2d2 error")] - R2d2Error, + #[error("Diesel R2d2 error: `{0}`")] + DieselR2d2Error(#[from] WalletStorageError), #[error("Diesel error: `{0}`")] DieselError(#[from] DieselError), #[error("Diesel connection error: `{0}`")] diff --git a/base_layer/wallet/src/contacts_service/storage/sqlite_db.rs b/base_layer/wallet/src/contacts_service/storage/sqlite_db.rs index b5f22c48bb..cdc01e3956 100644 --- a/base_layer/wallet/src/contacts_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/contacts_service/storage/sqlite_db.rs @@ -20,19 +20,22 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::convert::TryFrom; + +use diesel::{prelude::*, result::Error as DieselError, SqliteConnection}; +use tari_crypto::tari_utilities::ByteArray; + +use tari_common_types::types::PublicKey; + use crate::{ contacts_service::{ error::ContactsServiceStorageError, storage::database::{Contact, ContactsBackend, DbKey, DbKeyValuePair, DbValue, WriteOperation}, }, schema::contacts, - storage::sqlite_utilities::WalletDbConnection, + storage::sqlite_utilities::wallet_db_connection::WalletDbConnection, util::diesel_ext::ExpectedRowsExtension, }; -use diesel::{prelude::*, result::Error as DieselError, SqliteConnection}; -use std::convert::TryFrom; -use tari_common_types::types::PublicKey; -use tari_crypto::tari_utilities::ByteArray; /// A Sqlite backend for the Output Manager Service. The Backend is accessed via a connection pool to the Sqlite file. #[derive(Clone)] @@ -47,10 +50,10 @@ impl ContactsServiceSqliteDatabase { impl ContactsBackend for ContactsServiceSqliteDatabase { fn fetch(&self, key: &DbKey) -> Result, ContactsServiceStorageError> { - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let result = match key { - DbKey::Contact(pk) => match ContactSql::find(&pk.to_vec(), &(*conn)) { + DbKey::Contact(pk) => match ContactSql::find(&pk.to_vec(), &conn) { Ok(c) => Some(DbValue::Contact(Box::new(Contact::try_from(c)?))), Err(ContactsServiceStorageError::DieselError(DieselError::NotFound)) => None, Err(e) => return Err(e), @@ -67,13 +70,13 @@ impl ContactsBackend for ContactsServiceSqliteDatabase { } fn write(&self, op: WriteOperation) -> Result, ContactsServiceStorageError> { - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; match op { WriteOperation::Upsert(kvp) => match kvp { - DbKeyValuePair::Contact(k, c) => match ContactSql::find(&k.to_vec(), &(*conn)) { + DbKeyValuePair::Contact(k, c) => match ContactSql::find(&k.to_vec(), &conn) { Ok(found_c) => { - let _ = found_c.update(UpdateContact { alias: Some(c.alias) }, &(*conn))?; + let _ = found_c.update(UpdateContact { alias: Some(c.alias) }, &conn)?; }, Err(_) => { ContactSql::from(c).commit(&conn)?; @@ -81,7 +84,7 @@ impl ContactsBackend for ContactsServiceSqliteDatabase { }, }, WriteOperation::Remove(k) => match k { - DbKey::Contact(k) => match ContactSql::find(&k.to_vec(), &(*conn)) { + DbKey::Contact(k) => match ContactSql::find(&k.to_vec(), &conn) { Ok(c) => { c.delete(&conn)?; return Ok(Some(DbValue::Contact(Box::new(Contact::try_from(c)?)))); @@ -181,20 +184,23 @@ pub struct UpdateContact { #[cfg(test)] mod test { - use crate::contacts_service::storage::{ - database::Contact, - sqlite_db::{ContactSql, UpdateContact}, - }; + use std::convert::TryFrom; + use diesel::{Connection, SqliteConnection}; use rand::rngs::OsRng; - use std::convert::TryFrom; - use tari_common_types::types::{PrivateKey, PublicKey}; use tari_crypto::{ keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, tari_utilities::ByteArray, }; + + use tari_common_types::types::{PrivateKey, PublicKey}; use tari_test_utils::{paths::with_temp_dir, random::string}; + use crate::contacts_service::storage::{ + database::Contact, + sqlite_db::{ContactSql, UpdateContact}, + }; + #[test] fn test_crud() { with_temp_dir(|dir_path| { diff --git a/base_layer/wallet/src/error.rs b/base_layer/wallet/src/error.rs index e007a95144..30321105c2 100644 --- a/base_layer/wallet/src/error.rs +++ b/base_layer/wallet/src/error.rs @@ -32,6 +32,7 @@ use diesel::result::Error as DieselError; use log::SetLoggerError; use serde_json::Error as SerdeJsonError; use tari_common::exit_codes::ExitCodes; +use tari_common_sqlite::error::SqliteStorageError; use tari_comms::{ connectivity::ConnectivityError, multiaddr, @@ -113,8 +114,8 @@ pub enum WalletStorageError { DbPathDoesNotExist, #[error("Serde json error: `{0}`")] SerdeJsonError(#[from] SerdeJsonError), - #[error("R2d2 error")] - R2d2Error, + #[error("Diesel R2d2 error: `{0}`")] + DieselR2d2Error(#[from] SqliteStorageError), #[error("Diesel error: `{0}`")] DieselError(#[from] DieselError), #[error("Diesel connection error: `{0}`")] @@ -169,3 +170,9 @@ impl From for ExitCodes { } } } + +impl PartialEq for WalletStorageError { + fn eq(&self, other: &Self) -> bool { + self == other + } +} diff --git a/base_layer/wallet/src/output_manager_service/error.rs b/base_layer/wallet/src/output_manager_service/error.rs index 25322205d7..2d287bda0c 100644 --- a/base_layer/wallet/src/output_manager_service/error.rs +++ b/base_layer/wallet/src/output_manager_service/error.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::base_node_service::error::BaseNodeServiceError; +use crate::{base_node_service::error::BaseNodeServiceError, error::WalletStorageError}; use diesel::result::Error as DieselError; use tari_common::exit_codes::ExitCodes; use tari_comms::{connectivity::ConnectivityError, peer_manager::node_id::NodeIdError, protocol::rpc::RpcError}; @@ -142,8 +142,8 @@ pub enum OutputManagerStorageError { OutputAlreadySpent, #[error("Key Manager not initialized")] KeyManagerNotInitialized, - #[error("R2d2 error")] - R2d2Error, + #[error("Diesel R2d2 error: `{0}`")] + DieselR2d2Error(#[from] WalletStorageError), #[error("Transaction error: `{0}`")] TransactionError(#[from] TransactionError), #[error("Diesel error: `{0}`")] diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs index c2af0f7973..fdd8661e9d 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs @@ -20,6 +20,34 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::{ + convert::{TryFrom, TryInto}, + str::from_utf8, + sync::{Arc, RwLock}, +}; + +use aes_gcm::Aes256Gcm; +use chrono::{NaiveDateTime, Utc}; +use diesel::{prelude::*, result::Error as DieselError, SqliteConnection}; +use log::*; +use tari_crypto::{ + script::{ExecutionStack, TariScript}, + tari_utilities::{ + hex::{from_hex, Hex}, + ByteArray, + }, +}; +use tokio::time::Instant; + +pub use new_output_sql::NewOutputSql; +pub use output_sql::OutputSql; +use tari_common_types::{ + transaction::TxId, + types::{Commitment, PrivateKey}, +}; +use tari_core::transactions::transaction::TransactionOutput; +use tari_key_manager::cipher_seed::CipherSeed; + use crate::{ output_manager_service::{ error::OutputManagerStorageError, @@ -30,39 +58,15 @@ use crate::{ }, }, schema::{key_manager_states, known_one_sided_payment_scripts, outputs}, - storage::sqlite_utilities::WalletDbConnection, + storage::sqlite_utilities::wallet_db_connection::WalletDbConnection, util::{ diesel_ext::ExpectedRowsExtension, encryption::{decrypt_bytes_integral_nonce, encrypt_bytes_integral_nonce, Encryptable}, }, }; -use aes_gcm::Aes256Gcm; -use chrono::{NaiveDateTime, Utc}; -use diesel::{prelude::*, result::Error as DieselError, SqliteConnection}; -use log::*; -use std::{ - convert::{TryFrom, TryInto}, - str::from_utf8, - sync::{Arc, RwLock}, -}; -use tari_common_types::{ - transaction::TxId, - types::{Commitment, PrivateKey}, -}; -use tari_core::transactions::transaction::TransactionOutput; -use tari_crypto::{ - script::{ExecutionStack, TariScript}, - tari_utilities::{ - hex::{from_hex, Hex}, - ByteArray, - }, -}; -use tari_key_manager::cipher_seed::CipherSeed; -use tokio::time::Instant; + mod new_output_sql; -pub use new_output_sql::NewOutputSql; mod output_sql; -pub use output_sql::OutputSql; const LOG_TARGET: &str = "wallet::output_manager_service::database::sqlite_db"; @@ -102,23 +106,23 @@ impl OutputManagerSqliteDatabase { fn insert(&self, key_value_pair: DbKeyValuePair, conn: &SqliteConnection) -> Result<(), OutputManagerStorageError> { match key_value_pair { DbKeyValuePair::UnspentOutput(c, o) => { - if OutputSql::find_by_commitment_and_cancelled(&c.to_vec(), false, &(*conn)).is_ok() { + if OutputSql::find_by_commitment_and_cancelled(&c.to_vec(), false, conn).is_ok() { return Err(OutputManagerStorageError::DuplicateOutput); } let mut new_output = NewOutputSql::new(*o, OutputStatus::Unspent, None, None)?; self.encrypt_if_necessary(&mut new_output)?; - new_output.commit(&(*conn))? + new_output.commit(conn)? }, DbKeyValuePair::UnspentOutputWithTxId(c, (tx_id, o)) => { - if OutputSql::find_by_commitment_and_cancelled(&c.to_vec(), false, &(*conn)).is_ok() { + if OutputSql::find_by_commitment_and_cancelled(&c.to_vec(), false, conn).is_ok() { return Err(OutputManagerStorageError::DuplicateOutput); } let mut new_output = NewOutputSql::new(*o, OutputStatus::Unspent, Some(tx_id), None)?; self.encrypt_if_necessary(&mut new_output)?; - new_output.commit(&(*conn))? + new_output.commit(conn)? }, DbKeyValuePair::OutputToBeReceived(c, (tx_id, o, coinbase_block_height)) => { - if OutputSql::find_by_commitment_and_cancelled(&c.to_vec(), false, &(*conn)).is_ok() { + if OutputSql::find_by_commitment_and_cancelled(&c.to_vec(), false, conn).is_ok() { return Err(OutputManagerStorageError::DuplicateOutput); } let mut new_output = NewOutputSql::new( @@ -128,17 +132,17 @@ impl OutputManagerSqliteDatabase { coinbase_block_height, )?; self.encrypt_if_necessary(&mut new_output)?; - new_output.commit(&(*conn))? + new_output.commit(conn)? }, DbKeyValuePair::KeyManagerState(km) => { let mut km_sql = NewKeyManagerStateSql::from(km); self.encrypt_if_necessary(&mut km_sql)?; - km_sql.commit(&(*conn))? + km_sql.commit(conn)? }, DbKeyValuePair::KnownOneSidedPaymentScripts(script) => { let mut script_sql = KnownOneSidedPaymentScriptSql::from(script); self.encrypt_if_necessary(&mut script_sql)?; - script_sql.commit(&(*conn))? + script_sql.commit(conn)? }, } Ok(()) @@ -149,11 +153,11 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { #[allow(clippy::cognitive_complexity)] fn fetch(&self, key: &DbKey) -> Result, OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let result = match key { - DbKey::SpentOutput(k) => match OutputSql::find_status(&k.to_vec(), OutputStatus::Spent, &(*conn)) { + DbKey::SpentOutput(k) => match OutputSql::find_status(&k.to_vec(), OutputStatus::Spent, &conn) { Ok(mut o) => { self.decrypt_if_necessary(&mut o)?; Some(DbValue::SpentOutput(Box::new(DbUnblindedOutput::try_from(o)?))) @@ -166,7 +170,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { None }, }, - DbKey::UnspentOutput(k) => match OutputSql::find_status(&k.to_vec(), OutputStatus::Unspent, &(*conn)) { + DbKey::UnspentOutput(k) => match OutputSql::find_status(&k.to_vec(), OutputStatus::Unspent, &conn) { Ok(mut o) => { self.decrypt_if_necessary(&mut o)?; Some(DbValue::UnspentOutput(Box::new(DbUnblindedOutput::try_from(o)?))) @@ -180,7 +184,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { }, }, DbKey::AnyOutputByCommitment(commitment) => { - match OutputSql::find_by_commitment(&commitment.to_vec(), &(*conn)) { + match OutputSql::find_by_commitment(&commitment.to_vec(), &conn) { Ok(mut o) => { self.decrypt_if_necessary(&mut o)?; Some(DbValue::SpentOutput(Box::new(DbUnblindedOutput::try_from(o)?))) @@ -195,7 +199,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } }, DbKey::OutputsByTxIdAndStatus(tx_id, status) => { - let mut outputs = OutputSql::find_by_tx_id_and_status(*tx_id, *status, &(*conn))?; + let mut outputs = OutputSql::find_by_tx_id_and_status(*tx_id, *status, &conn)?; for o in outputs.iter_mut() { self.decrypt_if_necessary(o)?; } @@ -207,7 +211,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) }, DbKey::UnspentOutputs => { - let mut outputs = OutputSql::index_status(OutputStatus::Unspent, &(*conn))?; + let mut outputs = OutputSql::index_status(OutputStatus::Unspent, &conn)?; for o in outputs.iter_mut() { self.decrypt_if_necessary(o)?; } @@ -220,7 +224,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) }, DbKey::SpentOutputs => { - let mut outputs = OutputSql::index_status(OutputStatus::Spent, &(*conn))?; + let mut outputs = OutputSql::index_status(OutputStatus::Spent, &conn)?; for o in outputs.iter_mut() { self.decrypt_if_necessary(o)?; } @@ -233,7 +237,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) }, DbKey::TimeLockedUnspentOutputs(tip) => { - let mut outputs = OutputSql::index_time_locked(*tip, &(*conn))?; + let mut outputs = OutputSql::index_time_locked(*tip, &conn)?; for o in outputs.iter_mut() { self.decrypt_if_necessary(o)?; } @@ -246,7 +250,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) }, DbKey::KeyManagerState => { - match KeyManagerStateSql::get_state(&(*conn)).ok() { + match KeyManagerStateSql::get_state(&conn).ok() { None => None, Some(mut km) => { self.decrypt_if_necessary(&mut km)?; @@ -260,7 +264,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } }, DbKey::InvalidOutputs => { - let mut outputs = OutputSql::index_status(OutputStatus::Invalid, &(*conn))?; + let mut outputs = OutputSql::index_status(OutputStatus::Invalid, &conn)?; for o in outputs.iter_mut() { self.decrypt_if_necessary(o)?; } @@ -273,7 +277,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) }, DbKey::KnownOneSidedPaymentScripts => { - let mut known_one_sided_payment_scripts = KnownOneSidedPaymentScriptSql::index(&(*conn))?; + let mut known_one_sided_payment_scripts = KnownOneSidedPaymentScriptSql::index(&conn)?; for script in known_one_sided_payment_scripts.iter_mut() { self.decrypt_if_necessary(script)?; } @@ -286,33 +290,37 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )) }, }; - trace!( - target: LOG_TARGET, - "sqlite profile - fetch '{}': lock {} + db_op {} = {} ms", - key, - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - fetch '{}': lock {} + db_op {} = {} ms", + key, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(result) } fn fetch_mined_unspent_outputs(&self) -> Result, OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - let mut outputs = OutputSql::index_marked_deleted_in_block_is_null(&(*conn))?; + let mut outputs = OutputSql::index_marked_deleted_in_block_is_null(&conn)?; for output in outputs.iter_mut() { self.decrypt_if_necessary(output)?; } - trace!( - target: LOG_TARGET, - "sqlite profile - fetch_mined_unspent_outputs: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_mined_unspent_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } outputs .into_iter() @@ -322,19 +330,21 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { fn fetch_unconfirmed_outputs(&self) -> Result, OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - let mut outputs = OutputSql::index_unconfirmed(&(*conn))?; + let mut outputs = OutputSql::index_unconfirmed(&conn)?; for output in outputs.iter_mut() { self.decrypt_if_necessary(output)?; } - trace!( - target: LOG_TARGET, - "sqlite profile - fetch_unconfirmed_outputs: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_unconfirmed_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } outputs .into_iter() @@ -344,7 +354,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { fn write(&self, op: WriteOperation) -> Result, OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); match op { @@ -352,17 +362,19 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { WriteOperation::Remove(k) => match k { DbKey::AnyOutputByCommitment(commitment) => { // Used by coinbase when mining. - match OutputSql::find_by_commitment(&commitment.to_vec(), &(*conn)) { + match OutputSql::find_by_commitment(&commitment.to_vec(), &conn) { Ok(mut o) => { - o.delete(&(*conn))?; + o.delete(&conn)?; self.decrypt_if_necessary(&mut o)?; - trace!( - target: LOG_TARGET, - "sqlite profile - write Remove: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - write Remove: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } return Ok(Some(DbValue::AnyOutput(Box::new(DbUnblindedOutput::try_from(o)?)))); }, Err(e) => { @@ -384,20 +396,22 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { DbKey::OutputsByTxIdAndStatus(_, _) => return Err(OutputManagerStorageError::OperationNotSupported), }, } - trace!( - target: LOG_TARGET, - "sqlite profile - write Insert: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - write Insert: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(None) } fn fetch_pending_incoming_outputs(&self) -> Result, OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut outputs = OutputSql::index_status(OutputStatus::EncumberedToBeReceived, &conn)?; @@ -409,13 +423,15 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { for o in outputs.iter_mut() { self.decrypt_if_necessary(o)?; } - trace!( - target: LOG_TARGET, - "sqlite profile - fetch_pending_incoming_outputs: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_pending_incoming_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } outputs .iter() .map(|o| DbUnblindedOutput::try_from(o.clone())) @@ -431,7 +447,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { confirmed: bool, ) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let status = if confirmed { OutputStatus::Unspent as i32 @@ -450,22 +466,24 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { outputs::mined_mmr_position.eq(mmr_position as i64), outputs::status.eq(status), )) - .execute(&(*conn)) + .execute(&conn) .num_rows_affected_or_not_found(1)?; - trace!( - target: LOG_TARGET, - "sqlite profile - set_received_output_mined_height: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - set_received_output_mined_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn set_output_to_unmined(&self, hash: Vec) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); // Only allow updating of non-deleted utxos diesel::update(outputs::table.filter(outputs::hash.eq(hash).and(outputs::marked_deleted_at_height.is_null()))) @@ -475,22 +493,24 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { outputs::mined_mmr_position.eq::>(None), outputs::status.eq(OutputStatus::Invalid as i32), )) - .execute(&(*conn)) + .execute(&conn) .num_rows_affected_or_not_found(1)?; - trace!( - target: LOG_TARGET, - "sqlite profile - set_output_to_unmined: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - set_output_to_unmined: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn set_outputs_to_be_revalidated(&self) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); // Only update non-deleted utxos let result = diesel::update(outputs::table.filter(outputs::marked_deleted_at_height.is_null())) @@ -499,16 +519,18 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { outputs::mined_in_block.eq::>>(None), outputs::mined_mmr_position.eq::>(None), )) - .execute(&(*conn))?; + .execute(&conn)?; trace!(target: LOG_TARGET, "rows updated: {:?}", result); - trace!( - target: LOG_TARGET, - "sqlite profile - set_outputs_to_be_revalidated: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - set_outputs_to_be_revalidated: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -521,7 +543,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { confirmed: bool, ) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let status = if confirmed { OutputStatus::Spent as i32 @@ -543,22 +565,24 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { outputs::marked_deleted_in_block.eq(mark_deleted_in_block), outputs::status.eq(status), )) - .execute(&(*conn)) + .execute(&conn) .num_rows_affected_or_not_found(1)?; - trace!( - target: LOG_TARGET, - "sqlite profile - mark_output_as_spent: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - mark_output_as_spent: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn mark_output_as_unspent(&self, hash: Vec) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); debug!(target: LOG_TARGET, "mark_output_as_unspent({})", hash.to_hex()); @@ -575,15 +599,17 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { outputs::marked_deleted_in_block.eq::>>(None), outputs::status.eq(OutputStatus::Unspent as i32), )) - .execute(&(*conn)) + .execute(&conn) .num_rows_affected_or_not_found(1)?; - trace!( - target: LOG_TARGET, - "sqlite profile - mark_output_as_unspent: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - mark_output_as_unspent: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -595,12 +621,12 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { outputs_to_receive: &[DbUnblindedOutput], ) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut outputs_to_be_spent = Vec::with_capacity(outputs_to_send.len()); for i in outputs_to_send { - let output = OutputSql::find_by_commitment_and_cancelled(i.commitment.as_bytes(), false, &(*conn))?; + let output = OutputSql::find_by_commitment_and_cancelled(i.commitment.as_bytes(), false, &conn)?; if output.status != (OutputStatus::Unspent as i32) { return Err(OutputManagerStorageError::OutputAlreadySpent); } @@ -614,7 +640,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { spent_in_tx_id: Some(Some(tx_id)), ..Default::default() }, - &(*conn), + &conn, )?; } @@ -626,22 +652,24 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { None, )?; self.encrypt_if_necessary(&mut new_output)?; - new_output.commit(&(*conn))?; + new_output.commit(&conn)?; + } + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - short_term_encumber_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); } - trace!( - target: LOG_TARGET, - "sqlite profile - short_term_encumber_outputs: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); Ok(()) } fn confirm_encumbered_outputs(&self, tx_id: TxId) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let outputs_to_be_received = @@ -652,7 +680,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { status: Some(OutputStatus::EncumberedToBeReceived), ..Default::default() }, - &(*conn), + &conn, )?; } @@ -664,23 +692,25 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { status: Some(OutputStatus::EncumberedToBeSpent), ..Default::default() }, - &(*conn), + &conn, )?; } - trace!( - target: LOG_TARGET, - "sqlite profile - confirm_encumbered_outputs: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - confirm_encumbered_outputs: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn clear_short_term_encumberances(&self) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let outputs_to_be_received = OutputSql::index_status(OutputStatus::ShortTermEncumberedToBeReceived, &conn)?; @@ -690,7 +720,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { status: Some(OutputStatus::CancelledInbound), ..Default::default() }, - &(*conn), + &conn, )?; } @@ -701,33 +731,37 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { status: Some(OutputStatus::Unspent), ..Default::default() }, - &(*conn), + &conn, )?; } - trace!( - target: LOG_TARGET, - "sqlite profile - clear_short_term_encumberances: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - clear_short_term_encumberances: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn get_last_mined_output(&self) -> Result, OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - let output = OutputSql::first_by_mined_height_desc(&(*conn))?; - trace!( - target: LOG_TARGET, - "sqlite profile - get_last_mined_output: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + let output = OutputSql::first_by_mined_height_desc(&conn)?; + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - get_last_mined_output: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } match output { Some(mut o) => { self.decrypt_if_necessary(&mut o)?; @@ -739,17 +773,19 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { fn get_last_spent_output(&self) -> Result, OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - let output = OutputSql::first_by_marked_deleted_height_desc(&(*conn))?; - trace!( - target: LOG_TARGET, - "sqlite profile - get_last_spent_output: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + let output = OutputSql::first_by_marked_deleted_height_desc(&conn)?; + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - get_last_spent_output: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } match output { Some(mut o) => { self.decrypt_if_necessary(&mut o)?; @@ -764,23 +800,25 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { current_tip_for_time_lock_calculation: Option, ) -> Result { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - let result = OutputSql::get_balance(current_tip_for_time_lock_calculation, &(*conn)); - trace!( - target: LOG_TARGET, - "sqlite profile - get_balance: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + let result = OutputSql::get_balance(current_tip_for_time_lock_calculation, &conn); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - get_balance: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } result } fn cancel_pending_transaction(&self, tx_id: TxId) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let outputs = OutputSql::find_by_tx_id_and_encumbered(tx_id, &conn)?; @@ -796,7 +834,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { status: Some(OutputStatus::CancelledInbound), ..Default::default() }, - &(*conn), + &conn, )?; } else if output.spent_in_tx_id == Some(tx_id as i64) { output.update( @@ -805,58 +843,64 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { spent_in_tx_id: Some(None), ..Default::default() }, - &(*conn), + &conn, )?; } } - trace!( - target: LOG_TARGET, - "sqlite profile - cancel_pending_transaction: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - cancel_pending_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn increment_key_index(&self) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - KeyManagerStateSql::increment_index(&(*conn))?; - trace!( - target: LOG_TARGET, - "sqlite profile - increment_key_index: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + KeyManagerStateSql::increment_index(&conn)?; + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - increment_key_index: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn set_key_index(&self, index: u64) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - KeyManagerStateSql::set_index(index, &(*conn))?; - trace!( - target: LOG_TARGET, - "sqlite profile - set_key_index: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + KeyManagerStateSql::set_index(index, &conn)?; + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - set_key_index: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn update_output_metadata_signature(&self, output: &TransactionOutput) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let db_output = OutputSql::find_by_commitment_and_cancelled(&output.commitment.to_vec(), false, &conn)?; db_output.update( @@ -865,22 +909,24 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { metadata_signature_u_key: Some(output.metadata_signature.u().to_vec()), ..Default::default() }, - &(*conn), + &conn, )?; - trace!( - target: LOG_TARGET, - "sqlite profile - update_output_metadata_signature: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - update_output_metadata_signature: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn revalidate_unspent_output(&self, commitment: &Commitment) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let output = OutputSql::find_by_commitment_and_cancelled(&commitment.to_vec(), false, &conn)?; @@ -892,15 +938,17 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { status: Some(OutputStatus::Unspent), ..Default::default() }, - &(*conn), + &conn, )?; - trace!( - target: LOG_TARGET, - "sqlite profile - revalidate_unspent_output: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - revalidate_unspent_output: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -912,7 +960,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut outputs = OutputSql::index(&conn)?; @@ -963,13 +1011,15 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } (*current_cipher) = Some(cipher); - trace!( - target: LOG_TARGET, - "sqlite profile - apply_encryption: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - apply_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -982,7 +1032,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { return Ok(()); }; let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut outputs = OutputSql::index(&conn)?; @@ -1009,13 +1059,15 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { // Now that all the decryption has been completed we can safely remove the cipher fully let _ = (*current_cipher).take(); - trace!( - target: LOG_TARGET, - "sqlite profile - remove_encryption: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - remove_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -1024,26 +1076,28 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { block_height: u64, ) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let output = OutputSql::find_pending_coinbase_at_block_height(block_height, &conn)?; output.delete(&conn)?; - trace!( - target: LOG_TARGET, - "sqlite profile - clear_pending_coinbase_transaction_at_block_height: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - clear_pending_coinbase_transaction_at_block_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn set_coinbase_abandoned(&self, tx_id: TxId, abandoned: bool) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); if abandoned { @@ -1059,7 +1113,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { ), ) .set((outputs::status.eq(OutputStatus::AbandonedCoinbase as i32),)) - .execute(&(*conn)) + .execute(&conn) .num_rows_affected_or_not_found(1)?; } else { let output = OutputSql::find_by_tx_id_and_status(tx_id, OutputStatus::AbandonedCoinbase, &conn)?; @@ -1073,20 +1127,22 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { )?; } }; - trace!( - target: LOG_TARGET, - "sqlite profile - set_coinbase_abandoned: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - set_coinbase_abandoned: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn reinstate_cancelled_inbound_output(&self, tx_id: TxId) -> Result<(), OutputManagerStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let outputs = OutputSql::find_by_tx_id_and_status(tx_id, OutputStatus::CancelledInbound, &conn)?; @@ -1096,16 +1152,18 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { status: Some(OutputStatus::EncumberedToBeReceived), ..Default::default() }, - &(*conn), + &conn, )?; } - trace!( - target: LOG_TARGET, - "sqlite profile - reinstate_cancelled_inbound_output: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - reinstate_cancelled_inbound_output: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } } @@ -1493,27 +1551,6 @@ impl Encryptable for KnownOneSidedPaymentScriptSql { #[cfg(test)] mod test { - use std::convert::TryFrom; - - use aes_gcm::{ - aead::{generic_array::GenericArray, NewAead}, - Aes256Gcm, - }; - use diesel::{Connection, SqliteConnection}; - use rand::{rngs::OsRng, RngCore}; - use tari_crypto::script; - use tempfile::tempdir; - - use tari_common_types::types::CommitmentFactory; - use tari_core::transactions::{ - tari_amount::MicroTari, - test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, - transaction::{OutputFeatures, TransactionInput, UnblindedOutput}, - CryptoFactories, - }; - use tari_key_manager::cipher_seed::CipherSeed; - use tari_test_utils::random; - use crate::{ output_manager_service::storage::{ database::{DbKey, KeyManagerState, OutputManagerBackend}, @@ -1528,9 +1565,28 @@ mod test { UpdateOutput, }, }, - storage::sqlite_utilities::WalletDbConnection, + storage::sqlite_utilities::wallet_db_connection::WalletDbConnection, util::encryption::Encryptable, }; + use aes_gcm::{ + aead::{generic_array::GenericArray, NewAead}, + Aes256Gcm, + }; + use diesel::{Connection, SqliteConnection}; + use rand::{rngs::OsRng, RngCore}; + use std::{convert::TryFrom, time::Duration}; + use tari_common_sqlite::sqlite_connection_pool::SqliteConnectionPool; + use tari_common_types::types::CommitmentFactory; + use tari_core::transactions::{ + tari_amount::MicroTari, + test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, + transaction::{OutputFeatures, TransactionInput, UnblindedOutput}, + CryptoFactories, + }; + use tari_crypto::script; + use tari_key_manager::cipher_seed::CipherSeed; + use tari_test_utils::random; + use tempfile::tempdir; pub fn make_input(val: MicroTari) -> (TransactionInput, UnblindedOutput) { let test_params = TestParamsHelpers::new(); @@ -1784,33 +1840,42 @@ mod test { let db_path = format!("{}{}", db_folder, db_name); embed_migrations!("./migrations"); - let conn = SqliteConnection::establish(&db_path).unwrap_or_else(|_| panic!("Error connecting to {}", db_path)); + let mut pool = SqliteConnectionPool::new(db_path.clone(), 1, true, true, Duration::from_secs(60)); + pool.create_pool() + .unwrap_or_else(|_| panic!("Error connecting to {}", db_path)); + // Note: For this test the connection pool is setup with a pool size of one; the pooled connection must go out + // of scope to be released once obtained otherwise subsequent calls to obtain a pooled connection will fail . + { + let conn = pool + .get_pooled_connection() + .unwrap_or_else(|_| panic!("Error connecting to {}", db_path)); + + embedded_migrations::run_with_output(&conn, &mut std::io::stdout()).expect("Migration failed"); + let factories = CryptoFactories::default(); + + let starting_state = KeyManagerState { + seed: CipherSeed::new(), + branch_seed: "boop boop".to_string(), + primary_key_index: 1, + }; + + let _state_sql = NewKeyManagerStateSql::from(starting_state).commit(&conn).unwrap(); - embedded_migrations::run_with_output(&conn, &mut std::io::stdout()).expect("Migration failed"); - let factories = CryptoFactories::default(); - - let starting_state = KeyManagerState { - seed: CipherSeed::new(), - branch_seed: "boop boop".to_string(), - primary_key_index: 1, - }; - - let _state_sql = NewKeyManagerStateSql::from(starting_state).commit(&conn).unwrap(); - - let (_, uo) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); - let output = NewOutputSql::new(uo, OutputStatus::Unspent, None, None).unwrap(); - output.commit(&conn).unwrap(); + let (_, uo) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let output = NewOutputSql::new(uo, OutputStatus::Unspent, None, None).unwrap(); + output.commit(&conn).unwrap(); - let (_, uo2) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); - let uo2 = DbUnblindedOutput::from_unblinded_output(uo2, &factories).unwrap(); - let output2 = NewOutputSql::new(uo2, OutputStatus::Unspent, None, None).unwrap(); - output2.commit(&conn).unwrap(); + let (_, uo2) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); + let uo2 = DbUnblindedOutput::from_unblinded_output(uo2, &factories).unwrap(); + let output2 = NewOutputSql::new(uo2, OutputStatus::Unspent, None, None).unwrap(); + output2.commit(&conn).unwrap(); + } let key = GenericArray::from_slice(b"an example very very secret key."); let cipher = Aes256Gcm::new(key); - let connection = WalletDbConnection::new(conn, None); + let connection = WalletDbConnection::new(pool, None); let db1 = OutputManagerSqliteDatabase::new(connection.clone(), Some(cipher.clone())); assert!(db1.apply_encryption(cipher.clone()).is_err()); diff --git a/base_layer/wallet/src/storage/database.rs b/base_layer/wallet/src/storage/database.rs index b645df3685..b0f9021f3b 100644 --- a/base_layer/wallet/src/storage/database.rs +++ b/base_layer/wallet/src/storage/database.rs @@ -373,7 +373,7 @@ mod test { let db_name = format!("{}.sqlite3", string(8).as_str()); let db_folder = tempdir().unwrap().path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name)).unwrap(); + let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name), 16).unwrap(); let db = WalletDatabase::new(WalletSqliteDatabase::new(connection, None).unwrap()); diff --git a/base_layer/wallet/src/storage/sqlite_db.rs b/base_layer/wallet/src/storage/sqlite_db.rs index fef5e0100e..27295998fa 100644 --- a/base_layer/wallet/src/storage/sqlite_db.rs +++ b/base_layer/wallet/src/storage/sqlite_db.rs @@ -20,15 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - error::WalletStorageError, - schema::{client_key_values, wallet_settings}, - storage::{ - database::{DbKey, DbKeyValuePair, DbValue, WalletBackend, WriteOperation}, - sqlite_utilities::WalletDbConnection, - }, - util::encryption::{decrypt_bytes_integral_nonce, encrypt_bytes_integral_nonce, Encryptable, AES_NONCE_BYTES}, +use std::{ + str::{from_utf8, FromStr}, + sync::{Arc, RwLock}, }; + use aes_gcm::{ aead::{generic_array::GenericArray, Aead}, Aes256Gcm, @@ -40,19 +36,26 @@ use argon2::{ }; use diesel::{prelude::*, SqliteConnection}; use log::*; -use std::{ - str::{from_utf8, FromStr}, - sync::{Arc, RwLock}, -}; -use tari_common_types::chain_metadata::ChainMetadata; -use tari_comms::{multiaddr::Multiaddr, peer_manager::PeerFeatures, tor::TorIdentity}; use tari_crypto::tari_utilities::{ hex::{from_hex, Hex}, message_format::MessageFormat, }; -use tari_key_manager::cipher_seed::CipherSeed; use tokio::time::Instant; +use tari_common_types::chain_metadata::ChainMetadata; +use tari_comms::{multiaddr::Multiaddr, peer_manager::PeerFeatures, tor::TorIdentity}; +use tari_key_manager::cipher_seed::CipherSeed; + +use crate::{ + error::WalletStorageError, + schema::{client_key_values, wallet_settings}, + storage::{ + database::{DbKey, DbKeyValuePair, DbValue, WalletBackend, WriteOperation}, + sqlite_utilities::wallet_db_connection::WalletDbConnection, + }, + util::encryption::{decrypt_bytes_integral_nonce, encrypt_bytes_integral_nonce, Encryptable, AES_NONCE_BYTES}, +}; + const LOG_TARGET: &str = "wallet::storage::sqlite_db"; /// A Sqlite backend for the Output Manager Service. The Backend is accessed via a connection pool to the Sqlite file. @@ -208,7 +211,7 @@ impl WalletSqliteDatabase { fn insert_key_value_pair(&self, kvp: DbKeyValuePair) -> Result, WalletStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let kvp_text; match kvp { @@ -237,13 +240,15 @@ impl WalletSqliteDatabase { self.encrypt_if_necessary(&mut client_key_value)?; client_key_value.set(&conn)?; - trace!( - target: LOG_TARGET, - "sqlite profile - insert_key_value_pair 'ClientKeyValue': lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - insert_key_value_pair 'ClientKeyValue': lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } return Ok(value_to_return.map(|v| DbValue::ClientValue(v.value))); }, @@ -256,20 +261,22 @@ impl WalletSqliteDatabase { WalletSettingSql::new(DbKey::CommsFeatures.to_string(), cf.bits().to_string()).set(&conn)?; }, } - trace!( - target: LOG_TARGET, - "sqlite profile - insert_key_value_pair '{}': lock {} + db_op {} = {} ms", - kvp_text, - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - insert_key_value_pair '{}': lock {} + db_op {} = {} ms", + kvp_text, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(None) } fn remove_key(&self, k: DbKey) -> Result, WalletStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); match k { DbKey::MasterSeed => { @@ -299,14 +306,16 @@ impl WalletSqliteDatabase { return Err(WalletStorageError::OperationNotSupported); }, }; - trace!( - target: LOG_TARGET, - "sqlite profile - remove_key '{}': lock {} + db_op {} = {} ms", - k, - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - remove_key '{}': lock {} + db_op {} = {} ms", + k, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(None) } @@ -319,7 +328,7 @@ impl WalletSqliteDatabase { impl WalletBackend for WalletSqliteDatabase { fn fetch(&self, key: &DbKey) -> Result, WalletStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let result = match key { @@ -338,14 +347,16 @@ impl WalletBackend for WalletSqliteDatabase { DbKey::PassphraseHash => WalletSettingSql::get(key.to_string(), &conn)?.map(DbValue::PassphraseHash), DbKey::EncryptionSalt => WalletSettingSql::get(key.to_string(), &conn)?.map(DbValue::EncryptionSalt), }; - trace!( - target: LOG_TARGET, - "sqlite profile - fetch '{}': lock {} + db_op {} = {} ms", - key, - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - fetch '{}': lock {} + db_op {} = {} ms", + key, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(result) } @@ -364,7 +375,7 @@ impl WalletBackend for WalletSqliteDatabase { } let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); // Check if there is an existing passphrase applied @@ -424,13 +435,15 @@ impl WalletBackend for WalletSqliteDatabase { } (*current_cipher) = Some(cipher.clone()); - trace!( - target: LOG_TARGET, - "sqlite profile - apply_encryption: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - apply_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(cipher) } @@ -443,7 +456,7 @@ impl WalletBackend for WalletSqliteDatabase { return Ok(()); }; let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let master_seed_str = match WalletSettingSql::get(DbKey::MasterSeed.to_string(), &conn)? { None => return Err(WalletStorageError::ValueNotFound(DbKey::MasterSeed)), @@ -482,13 +495,15 @@ impl WalletBackend for WalletSqliteDatabase { // Now that all the decryption has been completed we can safely remove the cipher fully let _ = (*current_cipher).take(); - trace!( - target: LOG_TARGET, - "sqlite profile - remove_encryption: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - remove_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -504,7 +519,7 @@ fn check_db_encryption_status( passphrase: Option, ) -> Result, WalletStorageError> { let start = Instant::now(); - let conn = database_connection.acquire_lock(); + let conn = database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let db_passphrase_hash = WalletSettingSql::get(DbKey::PassphraseHash.to_string(), &conn)?; @@ -592,13 +607,15 @@ fn check_db_encryption_status( }, }; } - trace!( - target: LOG_TARGET, - "sqlite profile - check_db_encryption_status: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - check_db_encryption_status: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(cipher) } @@ -707,26 +724,28 @@ impl Encryptable for ClientKeyValueSql { #[cfg(test)] mod test { + use tari_crypto::tari_utilities::hex::Hex; + use tempfile::tempdir; + + use tari_key_manager::cipher_seed::CipherSeed; + use tari_test_utils::random::string; + use crate::storage::{ database::{DbKey, DbValue, WalletBackend}, sqlite_db::{ClientKeyValueSql, WalletSettingSql, WalletSqliteDatabase}, sqlite_utilities::run_migration_and_create_sqlite_connection, }; - use tari_crypto::tari_utilities::hex::Hex; - use tari_key_manager::cipher_seed::CipherSeed; - use tari_test_utils::random::string; - use tempfile::tempdir; #[test] fn test_unencrypted_secret_public_key_setting() { let db_name = format!("{}.sqlite3", string(8).as_str()); let tempdir = tempdir().unwrap(); let db_folder = tempdir.path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name)).unwrap(); + let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name), 16).unwrap(); let secret_seed1 = CipherSeed::new(); { - let conn = connection.acquire_lock(); + let conn = connection.get_pooled_connection().unwrap(); WalletSettingSql::new( DbKey::MasterSeed.to_string(), secret_seed1.encipher(None).unwrap().to_hex(), @@ -746,7 +765,7 @@ mod test { let secret_seed2 = CipherSeed::new(); { - let conn = connection.acquire_lock(); + let conn = connection.get_pooled_connection().unwrap(); db.set_master_seed(&secret_seed2, &conn).unwrap(); } @@ -762,7 +781,7 @@ mod test { let db_name = format!("{}.sqlite3", string(8).as_str()); let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name)).unwrap(); + let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name), 16).unwrap(); let passphrase = "an example very very secret key.".to_string(); @@ -770,7 +789,7 @@ mod test { let seed = CipherSeed::new(); { - let conn = connection.acquire_lock(); + let conn = connection.get_pooled_connection().unwrap(); WalletSettingSql::new(DbKey::MasterSeed.to_string(), seed.encipher(None).unwrap().to_hex()) .set(&conn) .unwrap(); @@ -792,7 +811,7 @@ mod test { let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); let db_path = format!("{}/{}", db_folder, db_name); - let connection = run_migration_and_create_sqlite_connection(&db_path).unwrap(); + let connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); let seed = CipherSeed::new(); let key_values = vec![ @@ -802,7 +821,7 @@ mod test { ]; let db = WalletSqliteDatabase::new(connection.clone(), None).unwrap(); { - let conn = connection.acquire_lock(); + let conn = connection.get_pooled_connection().unwrap(); db.set_master_seed(&seed, &conn).unwrap(); for kv in key_values.iter() { kv.set(&conn).unwrap(); @@ -839,7 +858,7 @@ mod test { assert_eq!(seed, read_seed2); { - let conn = connection.acquire_lock(); + let conn = connection.get_pooled_connection().unwrap(); let secret_key_str = WalletSettingSql::get(DbKey::MasterSeed.to_string(), &conn) .unwrap() .unwrap(); @@ -877,8 +896,8 @@ mod test { let db_name = format!("{}.sqlite3", string(8).as_str()); let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name)).unwrap(); - let conn = connection.acquire_lock(); + let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name), 16).unwrap(); + let conn = connection.get_pooled_connection().unwrap(); let key1 = "key1".to_string(); let value1 = "value1".to_string(); diff --git a/base_layer/wallet/src/storage/sqlite_utilities.rs b/base_layer/wallet/src/storage/sqlite_utilities/mod.rs similarity index 88% rename from base_layer/wallet/src/storage/sqlite_utilities.rs rename to base_layer/wallet/src/storage/sqlite_utilities/mod.rs index 3d39d1d761..944c076e81 100644 --- a/base_layer/wallet/src/storage/sqlite_utilities.rs +++ b/base_layer/wallet/src/storage/sqlite_utilities/mod.rs @@ -27,38 +27,24 @@ use crate::{ storage::{database::WalletDatabase, sqlite_db::WalletSqliteDatabase}, transaction_service::storage::sqlite_db::TransactionServiceSqliteDatabase, }; -use diesel::{Connection, ExpressionMethods, QueryDsl, SqliteConnection}; +use diesel::{ExpressionMethods, QueryDsl, SqliteConnection}; use fs2::FileExt; use log::*; use std::{ fs::File, path::{Path, PathBuf}, - sync::{Arc, Mutex, MutexGuard}, + time::Duration, }; +use tari_common_sqlite::sqlite_connection_pool::SqliteConnectionPool; +pub use wallet_db_connection::WalletDbConnection; -const LOG_TARGET: &str = "wallet::storage:sqlite_utilities"; - -#[derive(Clone)] -pub struct WalletDbConnection { - pub connection: Arc>, - _file_lock: Arc>, -} - -impl WalletDbConnection { - pub fn new(connection: SqliteConnection, file_lock: Option) -> Self { - Self { - connection: Arc::new(Mutex::new(connection)), - _file_lock: Arc::new(file_lock), - } - } +pub(crate) mod wallet_db_connection; - pub fn acquire_lock(&self) -> MutexGuard { - acquire_lock!(self.connection) - } -} +const LOG_TARGET: &str = "wallet::storage:sqlite_utilities"; pub fn run_migration_and_create_sqlite_connection>( db_path: P, + sqlite_pool_size: usize, ) -> Result { let file_lock = acquire_exclusive_file_lock(&db_path.as_ref().to_path_buf())?; @@ -66,8 +52,16 @@ pub fn run_migration_and_create_sqlite_connection>( .as_ref() .to_str() .ok_or(WalletStorageError::InvalidUnicodePath)?; - let connection = SqliteConnection::establish(path_str)?; - connection.execute("PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 60000;")?; + + let mut pool = SqliteConnectionPool::new( + String::from(path_str), + sqlite_pool_size, + true, + true, + Duration::from_secs(60), + ); + pool.create_pool()?; + let connection = pool.get_pooled_connection()?; check_for_incompatible_db_encryption(&connection)?; @@ -75,7 +69,7 @@ pub fn run_migration_and_create_sqlite_connection>( embedded_migrations::run(&connection) .map_err(|err| WalletStorageError::DatabaseMigrationError(format!("Database migration failed {}", err)))?; - Ok(WalletDbConnection::new(connection, Some(file_lock))) + Ok(WalletDbConnection::new(pool, Some(file_lock))) } /// This function will copy a wallet database to the provided path and then clear the Master Private Key from the @@ -94,7 +88,7 @@ pub async fn partial_wallet_backup>(current_db: P, backup_path: P .map_err(|_| WalletStorageError::FileError("Could not copy database file for backup".to_string()))?; // open a connection and clear the Master Secret Key - let connection = run_migration_and_create_sqlite_connection(backup_path)?; + let connection = run_migration_and_create_sqlite_connection(backup_path, 16)?; let db = WalletDatabase::new(WalletSqliteDatabase::new(connection, None)?); db.clear_master_seed().await?; @@ -136,6 +130,7 @@ pub fn acquire_exclusive_file_lock(db_path: &Path) -> Result, + sqlite_pool_size: usize, ) -> Result< ( WalletSqliteDatabase, @@ -145,7 +140,7 @@ pub fn initialize_sqlite_database_backends( ), WalletStorageError, > { - let connection = run_migration_and_create_sqlite_connection(&db_path).map_err(|e| { + let connection = run_migration_and_create_sqlite_connection(&db_path, sqlite_pool_size).map_err(|e| { error!( target: LOG_TARGET, "Error creating Sqlite Connection in Wallet: {:?}", e diff --git a/base_layer/wallet/src/storage/sqlite_utilities/wallet_db_connection.rs b/base_layer/wallet/src/storage/sqlite_utilities/wallet_db_connection.rs new file mode 100644 index 0000000000..25a5fb45ed --- /dev/null +++ b/base_layer/wallet/src/storage/sqlite_utilities/wallet_db_connection.rs @@ -0,0 +1,53 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{fs::File, sync::Arc}; + +use crate::error::WalletStorageError; +use diesel::{ + r2d2::{ConnectionManager, PooledConnection}, + SqliteConnection, +}; +use tari_common_sqlite::sqlite_connection_pool::SqliteConnectionPool; + +#[derive(Clone)] +pub struct WalletDbConnection { + pool: SqliteConnectionPool, + _file_lock: Arc>, +} + +impl WalletDbConnection { + pub fn new(pool: SqliteConnectionPool, file_lock: Option) -> Self { + Self { + pool, + _file_lock: Arc::new(file_lock), + } + } + + pub fn get_pooled_connection( + &self, + ) -> Result>, WalletStorageError> { + self.pool + .get_pooled_connection() + .map_err(WalletStorageError::DieselR2d2Error) + } +} diff --git a/base_layer/wallet/src/test_utils.rs b/base_layer/wallet/src/test_utils.rs index ef9c83f0ca..7b43836bd9 100644 --- a/base_layer/wallet/src/test_utils.rs +++ b/base_layer/wallet/src/test_utils.rs @@ -20,13 +20,19 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::storage::sqlite_utilities::{run_migration_and_create_sqlite_connection, WalletDbConnection}; use core::iter; -use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use std::path::Path; + +use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; +use tempfile::{tempdir, TempDir}; + use tari_common::configuration::Network; use tari_core::consensus::{ConsensusConstants, ConsensusManager}; -use tempfile::{tempdir, TempDir}; + +use crate::storage::sqlite_utilities::{ + run_migration_and_create_sqlite_connection, + wallet_db_connection::WalletDbConnection, +}; pub fn random_string(len: usize) -> String { iter::repeat(()) @@ -49,7 +55,8 @@ pub fn make_wallet_database_connection(path: Option) -> (WalletDbConnect let db_path = Path::new(&path_string).join(db_name); let connection = - run_migration_and_create_sqlite_connection(&db_path.to_str().expect("Should be able to make path")).unwrap(); + run_migration_and_create_sqlite_connection(&db_path.to_str().expect("Should be able to make path"), 16) + .unwrap(); (connection, temp_dir) } diff --git a/base_layer/wallet/src/transaction_service/error.rs b/base_layer/wallet/src/transaction_service/error.rs index b4b21b5465..94541e42aa 100644 --- a/base_layer/wallet/src/transaction_service/error.rs +++ b/base_layer/wallet/src/transaction_service/error.rs @@ -200,8 +200,8 @@ pub enum TransactionStorageError { CompletedConversionError(#[from] CompletedTransactionConversionError), #[error("Serde json error: `{0}`")] SerdeJsonError(#[from] SerdeJsonError), - #[error("R2d2 error")] - R2d2Error, + #[error("Diesel R2d2 error: `{0}`")] + DieselR2d2Error(#[from] WalletStorageError), #[error("Diesel error: `{0}`")] DieselError(#[from] DieselError), #[error("Diesel connection error: `{0}`")] diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index 307eee85fe..52dcf91cd4 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -20,31 +20,24 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - schema::{completed_transactions, inbound_transactions, outbound_transactions}, - storage::sqlite_utilities::WalletDbConnection, - transaction_service::{ - error::{TransactionKeyError, TransactionStorageError}, - storage::{ - database::{DbKey, DbKeyValuePair, DbValue, TransactionBackend, WriteOperation}, - models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, - }, - }, - util::{ - diesel_ext::ExpectedRowsExtension, - encryption::{decrypt_bytes_integral_nonce, encrypt_bytes_integral_nonce, Encryptable}, - }, +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, + str::from_utf8, + sync::{Arc, RwLock}, }; + use aes_gcm::{self, Aes256Gcm}; use chrono::{NaiveDateTime, Utc}; use diesel::{prelude::*, result::Error as DieselError, SqliteConnection}; use log::*; -use std::{ - collections::HashMap, - convert::{TryFrom, TryInto}, - str::from_utf8, - sync::{Arc, MutexGuard, RwLock}, +use tari_crypto::tari_utilities::{ + hex::{from_hex, Hex}, + ByteArray, }; +use thiserror::Error; +use tokio::time::Instant; + use tari_common_types::{ transaction::{ TransactionConversionError, @@ -57,12 +50,22 @@ use tari_common_types::{ }; use tari_comms::types::CommsPublicKey; use tari_core::transactions::tari_amount::MicroTari; -use tari_crypto::tari_utilities::{ - hex::{from_hex, Hex}, - ByteArray, + +use crate::{ + schema::{completed_transactions, inbound_transactions, outbound_transactions}, + storage::sqlite_utilities::wallet_db_connection::WalletDbConnection, + transaction_service::{ + error::{TransactionKeyError, TransactionStorageError}, + storage::{ + database::{DbKey, DbKeyValuePair, DbValue, TransactionBackend, WriteOperation}, + models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, + }, + }, + util::{ + diesel_ext::ExpectedRowsExtension, + encryption::{decrypt_bytes_integral_nonce, encrypt_bytes_integral_nonce, Encryptable}, + }, }; -use thiserror::Error; -use tokio::time::Instant; const LOG_TARGET: &str = "wallet::transaction_service::database::sqlite_db"; @@ -101,7 +104,7 @@ impl TransactionServiceSqliteDatabase { // TODO: Remove this function for the next #testnet_reset fn add_transaction_signature_for_legacy_transactions(&mut self) -> Result { - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let txs_sql = completed_transactions::table .filter( completed_transactions::transaction_signature_nonce @@ -137,7 +140,7 @@ impl TransactionServiceSqliteDatabase { transaction_signature_key, ..Default::default() }, - &(*conn), + &conn, )?; count += 1; } @@ -146,48 +149,43 @@ impl TransactionServiceSqliteDatabase { Ok(count) } - fn insert(&self, kvp: DbKeyValuePair, conn: MutexGuard) -> Result<(), TransactionStorageError> { + fn insert(&self, kvp: DbKeyValuePair, conn: &SqliteConnection) -> Result<(), TransactionStorageError> { match kvp { DbKeyValuePair::PendingOutboundTransaction(k, v) => { - if OutboundTransactionSql::find_by_cancelled(k, false, &(*conn)).is_ok() { + if OutboundTransactionSql::find_by_cancelled(k, false, conn).is_ok() { return Err(TransactionStorageError::DuplicateOutput); } let mut o = OutboundTransactionSql::try_from(*v)?; self.encrypt_if_necessary(&mut o)?; - o.commit(&(*conn))?; + o.commit(conn)?; }, DbKeyValuePair::PendingInboundTransaction(k, v) => { - if InboundTransactionSql::find_by_cancelled(k, false, &(*conn)).is_ok() { + if InboundTransactionSql::find_by_cancelled(k, false, conn).is_ok() { return Err(TransactionStorageError::DuplicateOutput); } let mut i = InboundTransactionSql::try_from(*v)?; self.encrypt_if_necessary(&mut i)?; - i.commit(&(*conn))?; + i.commit(conn)?; }, DbKeyValuePair::CompletedTransaction(k, v) => { - if CompletedTransactionSql::find_by_cancelled(k, false, &(*conn)).is_ok() { + if CompletedTransactionSql::find_by_cancelled(k, false, conn).is_ok() { return Err(TransactionStorageError::DuplicateOutput); } let mut c = CompletedTransactionSql::try_from(*v)?; self.encrypt_if_necessary(&mut c)?; - c.commit(&(*conn))?; + c.commit(conn)?; }, } Ok(()) } - fn remove( - &self, - key: DbKey, - conn: MutexGuard, - ) -> Result, TransactionStorageError> { + fn remove(&self, key: DbKey, conn: &SqliteConnection) -> Result, TransactionStorageError> { match key { - DbKey::PendingOutboundTransaction(k) => match OutboundTransactionSql::find_by_cancelled(k, false, &(*conn)) - { + DbKey::PendingOutboundTransaction(k) => match OutboundTransactionSql::find_by_cancelled(k, false, conn) { Ok(mut v) => { - v.delete(&(*conn))?; + v.delete(conn)?; self.decrypt_if_necessary(&mut v)?; Ok(Some(DbValue::PendingOutboundTransaction(Box::new( OutboundTransaction::try_from(v)?, @@ -198,9 +196,9 @@ impl TransactionServiceSqliteDatabase { ), Err(e) => Err(e), }, - DbKey::PendingInboundTransaction(k) => match InboundTransactionSql::find_by_cancelled(k, false, &(*conn)) { + DbKey::PendingInboundTransaction(k) => match InboundTransactionSql::find_by_cancelled(k, false, conn) { Ok(mut v) => { - v.delete(&(*conn))?; + v.delete(conn)?; self.decrypt_if_necessary(&mut v)?; Ok(Some(DbValue::PendingInboundTransaction(Box::new( InboundTransaction::try_from(v)?, @@ -211,9 +209,9 @@ impl TransactionServiceSqliteDatabase { ), Err(e) => Err(e), }, - DbKey::CompletedTransaction(k) => match CompletedTransactionSql::find_by_cancelled(k, false, &(*conn)) { + DbKey::CompletedTransaction(k) => match CompletedTransactionSql::find_by_cancelled(k, false, conn) { Ok(mut v) => { - v.delete(&(*conn))?; + v.delete(conn)?; self.decrypt_if_necessary(&mut v)?; Ok(Some(DbValue::CompletedTransaction(Box::new( CompletedTransaction::try_from(v)?, @@ -231,9 +229,9 @@ impl TransactionServiceSqliteDatabase { DbKey::CancelledPendingInboundTransactions => Err(TransactionStorageError::OperationNotSupported), DbKey::CancelledCompletedTransactions => Err(TransactionStorageError::OperationNotSupported), DbKey::CancelledPendingOutboundTransaction(k) => { - match OutboundTransactionSql::find_by_cancelled(k, true, &(*conn)) { + match OutboundTransactionSql::find_by_cancelled(k, true, conn) { Ok(mut v) => { - v.delete(&(*conn))?; + v.delete(conn)?; self.decrypt_if_necessary(&mut v)?; Ok(Some(DbValue::PendingOutboundTransaction(Box::new( OutboundTransaction::try_from(v)?, @@ -246,9 +244,9 @@ impl TransactionServiceSqliteDatabase { } }, DbKey::CancelledPendingInboundTransaction(k) => { - match InboundTransactionSql::find_by_cancelled(k, true, &(*conn)) { + match InboundTransactionSql::find_by_cancelled(k, true, conn) { Ok(mut v) => { - v.delete(&(*conn))?; + v.delete(conn)?; self.decrypt_if_necessary(&mut v)?; Ok(Some(DbValue::PendingInboundTransaction(Box::new( InboundTransaction::try_from(v)?, @@ -286,36 +284,32 @@ impl TransactionServiceSqliteDatabase { impl TransactionBackend for TransactionServiceSqliteDatabase { fn fetch(&self, key: &DbKey) -> Result, TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let result = match key { - DbKey::PendingOutboundTransaction(t) => { - match OutboundTransactionSql::find_by_cancelled(*t, false, &(*conn)) { - Ok(mut o) => { - self.decrypt_if_necessary(&mut o)?; + DbKey::PendingOutboundTransaction(t) => match OutboundTransactionSql::find_by_cancelled(*t, false, &conn) { + Ok(mut o) => { + self.decrypt_if_necessary(&mut o)?; - Some(DbValue::PendingOutboundTransaction(Box::new( - OutboundTransaction::try_from(o)?, - ))) - }, - Err(TransactionStorageError::DieselError(DieselError::NotFound)) => None, - Err(e) => return Err(e), - } + Some(DbValue::PendingOutboundTransaction(Box::new( + OutboundTransaction::try_from(o)?, + ))) + }, + Err(TransactionStorageError::DieselError(DieselError::NotFound)) => None, + Err(e) => return Err(e), }, - DbKey::PendingInboundTransaction(t) => { - match InboundTransactionSql::find_by_cancelled(*t, false, &(*conn)) { - Ok(mut i) => { - self.decrypt_if_necessary(&mut i)?; - Some(DbValue::PendingInboundTransaction(Box::new( - InboundTransaction::try_from(i)?, - ))) - }, - Err(TransactionStorageError::DieselError(DieselError::NotFound)) => None, - Err(e) => return Err(e), - } + DbKey::PendingInboundTransaction(t) => match InboundTransactionSql::find_by_cancelled(*t, false, &conn) { + Ok(mut i) => { + self.decrypt_if_necessary(&mut i)?; + Some(DbValue::PendingInboundTransaction(Box::new( + InboundTransaction::try_from(i)?, + ))) + }, + Err(TransactionStorageError::DieselError(DieselError::NotFound)) => None, + Err(e) => return Err(e), }, - DbKey::CompletedTransaction(t) => match CompletedTransactionSql::find(*t, &(*conn)) { + DbKey::CompletedTransaction(t) => match CompletedTransactionSql::find(*t, &conn) { Ok(mut c) => { self.decrypt_if_necessary(&mut c)?; Some(DbValue::CompletedTransaction(Box::new(CompletedTransaction::try_from( @@ -326,7 +320,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { Err(e) => return Err(e), }, DbKey::AnyTransaction(t) => { - match OutboundTransactionSql::find(*t, &(*conn)) { + match OutboundTransactionSql::find(*t, &conn) { Ok(mut o) => { self.decrypt_if_necessary(&mut o)?; @@ -337,7 +331,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { Err(TransactionStorageError::DieselError(DieselError::NotFound)) => (), Err(e) => return Err(e), }; - match InboundTransactionSql::find(*t, &(*conn)) { + match InboundTransactionSql::find(*t, &conn) { Ok(mut i) => { self.decrypt_if_necessary(&mut i)?; return Ok(Some(DbValue::WalletTransaction(Box::new( @@ -347,7 +341,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { Err(TransactionStorageError::DieselError(DieselError::NotFound)) => (), Err(e) => return Err(e), }; - match CompletedTransactionSql::find(*t, &(*conn)) { + match CompletedTransactionSql::find(*t, &conn) { Ok(mut c) => { self.decrypt_if_necessary(&mut c)?; return Ok(Some(DbValue::WalletTransaction(Box::new( @@ -362,7 +356,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, DbKey::PendingOutboundTransactions => { let mut result = HashMap::new(); - for o in OutboundTransactionSql::index_by_cancelled(&(*conn), false)?.iter_mut() { + for o in OutboundTransactionSql::index_by_cancelled(&conn, false)?.iter_mut() { self.decrypt_if_necessary(o)?; result.insert(o.tx_id as u64, OutboundTransaction::try_from((*o).clone())?); } @@ -371,7 +365,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, DbKey::PendingInboundTransactions => { let mut result = HashMap::new(); - for i in InboundTransactionSql::index_by_cancelled(&(*conn), false)?.iter_mut() { + for i in InboundTransactionSql::index_by_cancelled(&conn, false)?.iter_mut() { self.decrypt_if_necessary(i)?; result.insert(i.tx_id as u64, InboundTransaction::try_from((*i).clone())?); } @@ -380,7 +374,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, DbKey::CompletedTransactions => { let mut result = HashMap::new(); - for c in CompletedTransactionSql::index_by_cancelled(&(*conn), false)?.iter_mut() { + for c in CompletedTransactionSql::index_by_cancelled(&conn, false)?.iter_mut() { self.decrypt_if_necessary(c)?; result.insert(c.tx_id as u64, CompletedTransaction::try_from((*c).clone())?); } @@ -389,7 +383,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, DbKey::CancelledPendingOutboundTransactions => { let mut result = HashMap::new(); - for o in OutboundTransactionSql::index_by_cancelled(&(*conn), true)?.iter_mut() { + for o in OutboundTransactionSql::index_by_cancelled(&conn, true)?.iter_mut() { self.decrypt_if_necessary(o)?; result.insert(o.tx_id as u64, OutboundTransaction::try_from((*o).clone())?); } @@ -398,7 +392,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, DbKey::CancelledPendingInboundTransactions => { let mut result = HashMap::new(); - for i in InboundTransactionSql::index_by_cancelled(&(*conn), true)?.iter_mut() { + for i in InboundTransactionSql::index_by_cancelled(&conn, true)?.iter_mut() { self.decrypt_if_necessary(i)?; result.insert(i.tx_id as u64, InboundTransaction::try_from((*i).clone())?); } @@ -407,7 +401,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, DbKey::CancelledCompletedTransactions => { let mut result = HashMap::new(); - for c in CompletedTransactionSql::index_by_cancelled(&(*conn), true)?.iter_mut() { + for c in CompletedTransactionSql::index_by_cancelled(&conn, true)?.iter_mut() { self.decrypt_if_necessary(c)?; result.insert(c.tx_id as u64, CompletedTransaction::try_from((*c).clone())?); } @@ -415,7 +409,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { Some(DbValue::CompletedTransactions(result)) }, DbKey::CancelledPendingOutboundTransaction(t) => { - match OutboundTransactionSql::find_by_cancelled(*t, true, &(*conn)) { + match OutboundTransactionSql::find_by_cancelled(*t, true, &conn) { Ok(mut o) => { self.decrypt_if_necessary(&mut o)?; @@ -428,7 +422,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { } }, DbKey::CancelledPendingInboundTransaction(t) => { - match InboundTransactionSql::find_by_cancelled(*t, true, &(*conn)) { + match InboundTransactionSql::find_by_cancelled(*t, true, &conn) { Ok(mut i) => { self.decrypt_if_necessary(&mut i)?; Some(DbValue::PendingInboundTransaction(Box::new( @@ -440,31 +434,29 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { } }, }; - trace!( - target: LOG_TARGET, - "sqlite profile - fetch '{}': lock {} + db_op {} = {} ms", - key, - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - fetch '{}': lock {} + db_op {} = {} ms", + key, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(result) } fn contains(&self, key: &DbKey) -> Result { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let result = match key { - DbKey::PendingOutboundTransaction(k) => { - OutboundTransactionSql::find_by_cancelled(*k, false, &(*conn)).is_ok() - }, - DbKey::PendingInboundTransaction(k) => { - InboundTransactionSql::find_by_cancelled(*k, false, &(*conn)).is_ok() - }, - DbKey::CompletedTransaction(k) => CompletedTransactionSql::find(*k, &(*conn)).is_ok(), + DbKey::PendingOutboundTransaction(k) => OutboundTransactionSql::find_by_cancelled(*k, false, &conn).is_ok(), + DbKey::PendingInboundTransaction(k) => InboundTransactionSql::find_by_cancelled(*k, false, &conn).is_ok(), + DbKey::CompletedTransaction(k) => CompletedTransactionSql::find(*k, &conn).is_ok(), DbKey::PendingOutboundTransactions => false, DbKey::PendingInboundTransactions => false, DbKey::CompletedTransactions => false, @@ -472,72 +464,78 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { DbKey::CancelledPendingInboundTransactions => false, DbKey::CancelledCompletedTransactions => false, DbKey::CancelledPendingOutboundTransaction(k) => { - OutboundTransactionSql::find_by_cancelled(*k, true, &(*conn)).is_ok() + OutboundTransactionSql::find_by_cancelled(*k, true, &conn).is_ok() }, DbKey::CancelledPendingInboundTransaction(k) => { - InboundTransactionSql::find_by_cancelled(*k, true, &(*conn)).is_ok() + InboundTransactionSql::find_by_cancelled(*k, true, &conn).is_ok() }, DbKey::AnyTransaction(k) => { - CompletedTransactionSql::find(*k, &(*conn)).is_ok() || - InboundTransactionSql::find(*k, &(*conn)).is_ok() || - OutboundTransactionSql::find(*k, &(*conn)).is_ok() + CompletedTransactionSql::find(*k, &conn).is_ok() || + InboundTransactionSql::find(*k, &conn).is_ok() || + OutboundTransactionSql::find(*k, &conn).is_ok() }, }; - trace!( - target: LOG_TARGET, - "sqlite profile - contains '{}': lock {} + db_op {} = {} ms", - key, - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - contains '{}': lock {} + db_op {} = {} ms", + key, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(result) } fn write(&self, op: WriteOperation) -> Result, TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let key_text; let result = match op { WriteOperation::Insert(kvp) => { key_text = "Insert"; - self.insert(kvp, conn).map(|_| None) + self.insert(kvp, &conn).map(|_| None) }, WriteOperation::Remove(key) => { key_text = "Remove"; - self.remove(key, conn) + self.remove(key, &conn) }, }; - trace!( - target: LOG_TARGET, - "sqlite profile - write '{}': lock {} + db_op {} = {} ms", - key_text, - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - write '{}': lock {} + db_op {} = {} ms", + key_text, + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } result } fn transaction_exists(&self, tx_id: u64) -> Result { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - let result = OutboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() || - InboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() || - CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok(); - trace!( - target: LOG_TARGET, - "sqlite profile - transaction_exists: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + let result = OutboundTransactionSql::find_by_cancelled(tx_id, false, &conn).is_ok() || + InboundTransactionSql::find_by_cancelled(tx_id, false, &conn).is_ok() || + CompletedTransactionSql::find_by_cancelled(tx_id, false, &conn).is_ok(); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - transaction_exists: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(result) } @@ -546,31 +544,37 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { tx_id: u64, ) -> Result { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - if let Ok(mut outbound_tx_sql) = OutboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { + if let Ok(mut outbound_tx_sql) = OutboundTransactionSql::find_by_cancelled(tx_id, false, &conn) { self.decrypt_if_necessary(&mut outbound_tx_sql)?; let outbound_tx = OutboundTransaction::try_from(outbound_tx_sql)?; - trace!( - target: LOG_TARGET, - "sqlite profile - get_pending_transaction_counterparty_pub_key_by_tx_id: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - get_pending_transaction_counterparty_pub_key_by_tx_id: lock {} + db_op {} = {} \ + ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } return Ok(outbound_tx.destination_public_key); } - if let Ok(mut inbound_tx_sql) = InboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { + if let Ok(mut inbound_tx_sql) = InboundTransactionSql::find_by_cancelled(tx_id, false, &conn) { self.decrypt_if_necessary(&mut inbound_tx_sql)?; let inbound_tx = InboundTransaction::try_from(inbound_tx_sql)?; - trace!( - target: LOG_TARGET, - "sqlite profile - get_pending_transaction_counterparty_pub_key_by_tx_id: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - get_pending_transaction_counterparty_pub_key_by_tx_id: lock {} + db_op {} = {} \ + ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } return Ok(inbound_tx.source_public_key); } @@ -581,9 +585,9 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { &self, tx_id: TxId, ) -> Result, TransactionStorageError> { - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; - match OutboundTransactionSql::find_by_cancelled(tx_id, true, &(*conn)) { + match OutboundTransactionSql::find_by_cancelled(tx_id, true, &conn) { Ok(mut o) => { self.decrypt_if_necessary(&mut o)?; @@ -594,7 +598,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { Err(TransactionStorageError::DieselError(DieselError::NotFound)) => (), Err(e) => return Err(e), }; - match InboundTransactionSql::find_by_cancelled(tx_id, true, &(*conn)) { + match InboundTransactionSql::find_by_cancelled(tx_id, true, &conn) { Ok(mut i) => { self.decrypt_if_necessary(&mut i)?; return Ok(Some(WalletTransaction::PendingInbound(InboundTransaction::try_from( @@ -604,7 +608,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { Err(TransactionStorageError::DieselError(DieselError::NotFound)) => (), Err(e) => return Err(e), }; - match CompletedTransactionSql::find_by_cancelled(tx_id, true, &(*conn)) { + match CompletedTransactionSql::find_by_cancelled(tx_id, true, &conn) { Ok(mut c) => { self.decrypt_if_necessary(&mut c)?; return Ok(Some(WalletTransaction::Completed(CompletedTransaction::try_from(c)?))); @@ -621,19 +625,19 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { completed_transaction: CompletedTransaction, ) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - if CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() { + if CompletedTransactionSql::find_by_cancelled(tx_id, false, &conn).is_ok() { return Err(TransactionStorageError::TransactionAlreadyExists); } - match OutboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { + match OutboundTransactionSql::find_by_cancelled(tx_id, false, &conn) { Ok(v) => { let mut completed_tx_sql = CompletedTransactionSql::try_from(completed_transaction)?; self.encrypt_if_necessary(&mut completed_tx_sql)?; - v.delete(&(*conn))?; - completed_tx_sql.commit(&(*conn))?; + v.delete(&conn)?; + completed_tx_sql.commit(&conn)?; }, Err(TransactionStorageError::DieselError(DieselError::NotFound)) => { return Err(TransactionStorageError::ValueNotFound( @@ -642,13 +646,15 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; - trace!( - target: LOG_TARGET, - "sqlite profile - complete_outbound_transaction: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - complete_outbound_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -658,19 +664,19 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { completed_transaction: CompletedTransaction, ) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - if CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)).is_ok() { + if CompletedTransactionSql::find_by_cancelled(tx_id, false, &conn).is_ok() { return Err(TransactionStorageError::TransactionAlreadyExists); } - match InboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { + match InboundTransactionSql::find_by_cancelled(tx_id, false, &conn) { Ok(v) => { let mut completed_tx_sql = CompletedTransactionSql::try_from(completed_transaction)?; self.encrypt_if_necessary(&mut completed_tx_sql)?; - v.delete(&(*conn))?; - completed_tx_sql.commit(&(*conn))?; + v.delete(&conn)?; + completed_tx_sql.commit(&conn)?; }, Err(TransactionStorageError::DieselError(DieselError::NotFound)) => { return Err(TransactionStorageError::ValueNotFound( @@ -679,22 +685,24 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; - trace!( - target: LOG_TARGET, - "sqlite profile - complete_inbound_transaction: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - complete_inbound_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn broadcast_completed_transaction(&self, tx_id: u64) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - match CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { + match CompletedTransactionSql::find_by_cancelled(tx_id, false, &conn) { Ok(v) => { if TransactionStatus::try_from(v.status)? == TransactionStatus::Completed { v.update( @@ -702,7 +710,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { status: Some(TransactionStatus::Broadcast as i32), ..Default::default() }, - &(*conn), + &conn, )?; } }, @@ -713,23 +721,25 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; - trace!( - target: LOG_TARGET, - "sqlite profile - broadcast_completed_transaction: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - broadcast_completed_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn reject_completed_transaction(&self, tx_id: u64) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - match CompletedTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { + match CompletedTransactionSql::find_by_cancelled(tx_id, false, &conn) { Ok(v) => { - v.reject(&(*conn))?; + v.reject(&conn)?; }, Err(TransactionStorageError::DieselError(DieselError::NotFound)) => { return Err(TransactionStorageError::ValueNotFound(DbKey::CompletedTransaction( @@ -738,13 +748,15 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; - trace!( - target: LOG_TARGET, - "sqlite profile - reject_completed_transaction: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - reject_completed_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -754,16 +766,16 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { cancelled: bool, ) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - match InboundTransactionSql::find(tx_id, &(*conn)) { + match InboundTransactionSql::find(tx_id, &conn) { Ok(v) => { - v.set_cancelled(cancelled, &(*conn))?; + v.set_cancelled(cancelled, &conn)?; }, Err(_) => { - match OutboundTransactionSql::find(tx_id, &(*conn)) { + match OutboundTransactionSql::find(tx_id, &conn) { Ok(v) => { - v.set_cancelled(cancelled, &(*conn))?; + v.set_cancelled(cancelled, &conn)?; }, Err(TransactionStorageError::DieselError(DieselError::NotFound)) => { return Err(TransactionStorageError::ValuesNotFound); @@ -772,21 +784,23 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }; }, }; - trace!( - target: LOG_TARGET, - "sqlite profile - set_pending_transaction_cancellation_status: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - set_pending_transaction_cancellation_status: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn mark_direct_send_success(&self, tx_id: u64) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - match InboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { + match InboundTransactionSql::find_by_cancelled(tx_id, false, &conn) { Ok(v) => { v.update( UpdateInboundTransactionSql { @@ -796,11 +810,11 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { send_count: None, last_send_timestamp: None, }, - &(*conn), + &conn, )?; }, Err(_) => { - match OutboundTransactionSql::find_by_cancelled(tx_id, false, &(*conn)) { + match OutboundTransactionSql::find_by_cancelled(tx_id, false, &conn) { Ok(v) => { v.update( UpdateOutboundTransactionSql { @@ -810,7 +824,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { send_count: None, last_send_timestamp: None, }, - &(*conn), + &conn, )?; }, Err(TransactionStorageError::DieselError(DieselError::NotFound)) => { @@ -820,13 +834,15 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }; }, }; - trace!( - target: LOG_TARGET, - "sqlite profile - mark_direct_send_success: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - mark_direct_send_success: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -838,7 +854,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { } let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut inbound_txs = InboundTransactionSql::index(&conn)?; @@ -890,13 +906,15 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { } (*current_cipher) = Some(cipher); - trace!( - target: LOG_TARGET, - "sqlite profile - apply_encryption: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - apply_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -910,7 +928,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { return Ok(()); }; let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut inbound_txs = InboundTransactionSql::index(&conn)?; @@ -938,33 +956,37 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { // Now that all the decryption has been completed we can safely remove the cipher fully let _ = (*current_cipher).take(); - trace!( - target: LOG_TARGET, - "sqlite profile - remove_encryption: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - remove_encryption: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn cancel_coinbase_transaction_at_block_height(&self, block_height: u64) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let coinbase_txs = CompletedTransactionSql::index_coinbase_at_block_height(block_height as i64, &conn)?; for c in coinbase_txs.iter() { c.cancel(&conn)?; } - trace!( - target: LOG_TARGET, - "sqlite profile - cancel_coinbase_transaction_at_block_height: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - cancel_coinbase_transaction_at_block_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -975,7 +997,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { amount: MicroTari, ) -> Result, TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut coinbase_txs = CompletedTransactionSql::index_coinbase_at_block_height(block_height as i64, &conn)?; @@ -986,20 +1008,22 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { return Ok(Some(completed_tx)); } } - trace!( - target: LOG_TARGET, - "sqlite profile - find_coinbase_transaction_at_block_height: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - find_coinbase_transaction_at_block_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(None) } fn increment_send_count(&self, tx_id: u64) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); if let Ok(tx) = CompletedTransactionSql::find(tx_id, &conn) { @@ -1030,13 +1054,15 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { } else { return Err(TransactionStorageError::ValuesNotFound); } - trace!( - target: LOG_TARGET, - "sqlite profile - increment_send_count: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - increment_send_count: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -1051,9 +1077,9 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { is_confirmed: bool, ) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - match CompletedTransactionSql::find(tx_id, &(*conn)) { + match CompletedTransactionSql::find(tx_id, &conn) { Ok(v) => { v.update_mined_height( is_valid, @@ -1061,7 +1087,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { mined_in_block, num_confirmations, is_confirmed, - &(*conn), + &conn, )?; }, Err(TransactionStorageError::DieselError(DieselError::NotFound)) => { @@ -1071,19 +1097,21 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; - trace!( - target: LOG_TARGET, - "sqlite profile - update_mined_height: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - update_mined_height: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn fetch_last_mined_transaction(&self) -> Result, TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let tx = completed_transactions::table .filter(completed_transactions::mined_height.is_not_null()) @@ -1097,19 +1125,21 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, None => None, }; - trace!( - target: LOG_TARGET, - "sqlite profile - fetch_last_mined_transaction: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_last_mined_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(result) } fn fetch_unconfirmed_transactions_info(&self) -> Result, TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut tx_info: Vec = vec![]; match UnconfirmedTransactionInfoSql::fetch_unconfirmed_transactions_info(&*conn) { @@ -1120,20 +1150,22 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), } - trace!( - target: LOG_TARGET, - "sqlite profile - fetch_unconfirmed_transactions_info: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_unconfirmed_transactions_info: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(tx_info) } fn get_transactions_to_be_broadcast(&self) -> Result, TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let txs = completed_transactions::table .filter(completed_transactions::valid.eq(true as i32)) @@ -1156,46 +1188,50 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { self.decrypt_if_necessary(&mut tx)?; result.push(tx.try_into()?); } - trace!( - target: LOG_TARGET, - "sqlite profile - get_transactions_to_be_broadcast: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - get_transactions_to_be_broadcast: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(result) } fn mark_all_transactions_as_unvalidated(&self) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let result = diesel::update(completed_transactions::table.filter(completed_transactions::cancelled.eq(0))) .set(( completed_transactions::mined_height.eq::>(None), completed_transactions::mined_in_block.eq::>>(None), )) - .execute(&(*conn))?; + .execute(&conn)?; trace!(target: LOG_TARGET, "rows updated: {:?}", result); - trace!( - target: LOG_TARGET, - "sqlite profile - set_transactions_to_be_revalidated: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - set_transactions_to_be_revalidated: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } fn set_transaction_as_unmined(&self, tx_id: u64) -> Result<(), TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); - match CompletedTransactionSql::find(tx_id, &(*conn)) { + match CompletedTransactionSql::find(tx_id, &conn) { Ok(v) => { - v.set_as_unmined(&(*conn))?; + v.set_as_unmined(&conn)?; }, Err(TransactionStorageError::DieselError(DieselError::NotFound)) => { return Err(TransactionStorageError::ValueNotFound(DbKey::CompletedTransaction( @@ -1204,13 +1240,15 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), }; - trace!( - target: LOG_TARGET, - "sqlite profile - set_transaction_as_unmined: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - set_transaction_as_unmined: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(()) } @@ -1218,10 +1256,10 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { &self, ) -> Result, TransactionStorageError> { let start = Instant::now(); - let conn = self.database_connection.acquire_lock(); + let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut sender_info: Vec = vec![]; - match InboundTransactionSenderInfoSql::get_pending_inbound_transaction_sender_info(&(*conn)) { + match InboundTransactionSenderInfoSql::get_pending_inbound_transaction_sender_info(&conn) { Ok(info) => { for item in info { sender_info.push(InboundTransactionSenderInfo::try_from(item)?); @@ -1229,13 +1267,15 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { }, Err(e) => return Err(e), } - trace!( - target: LOG_TARGET, - "sqlite profile - get_pending_inbound_transaction_sender_info: lock {} + db_op {} = {} ms", - acquire_lock.as_millis(), - (start.elapsed() - acquire_lock).as_millis(), - start.elapsed().as_millis() - ); + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - get_pending_inbound_transaction_sender_info: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } Ok(sender_info) } } @@ -2011,8 +2051,22 @@ impl UnconfirmedTransactionInfoSql { #[cfg(test)] mod test { - use std::convert::TryFrom; - + use crate::{ + storage::sqlite_utilities::wallet_db_connection::WalletDbConnection, + test_utils::create_consensus_constants, + transaction_service::storage::{ + database::{DbKey, TransactionBackend}, + models::{CompletedTransaction, InboundTransaction, OutboundTransaction}, + sqlite_db::{ + CompletedTransactionSql, + InboundTransactionSenderInfo, + InboundTransactionSql, + OutboundTransactionSql, + TransactionServiceSqliteDatabase, + }, + }, + util::encryption::Encryptable, + }; use aes_gcm::{ aead::{generic_array::GenericArray, NewAead}, Aes256Gcm, @@ -2020,13 +2074,8 @@ mod test { use chrono::Utc; use diesel::{Connection, SqliteConnection}; use rand::rngs::OsRng; - use tari_crypto::{ - keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, - script, - script::{ExecutionStack, TariScript}, - }; - use tempfile::tempdir; - + use std::{convert::TryFrom, time::Duration}; + use tari_common_sqlite::sqlite_connection_pool::SqliteConnectionPool; use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus}, types::{HashDigest, PrivateKey, PublicKey, Signature}, @@ -2040,24 +2089,13 @@ mod test { ReceiverTransactionProtocol, SenderTransactionProtocol, }; - use tari_test_utils::random::string; - - use crate::{ - storage::sqlite_utilities::WalletDbConnection, - test_utils::create_consensus_constants, - transaction_service::storage::{ - database::{DbKey, TransactionBackend}, - models::{CompletedTransaction, InboundTransaction, OutboundTransaction}, - sqlite_db::{ - CompletedTransactionSql, - InboundTransactionSenderInfo, - InboundTransactionSql, - OutboundTransactionSql, - TransactionServiceSqliteDatabase, - }, - }, - util::encryption::Encryptable, + use tari_crypto::{ + keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, + script, + script::{ExecutionStack, TariScript}, }; + use tari_test_utils::random::string; + use tempfile::tempdir; #[test] fn test_crud() { @@ -2567,77 +2605,86 @@ mod test { let db_path = format!("{}{}", db_folder, db_name); embed_migrations!("./migrations"); - let conn = SqliteConnection::establish(&db_path).unwrap_or_else(|_| panic!("Error connecting to {}", db_path)); - - embedded_migrations::run_with_output(&conn, &mut std::io::stdout()).expect("Migration failed"); - - let inbound_tx = InboundTransaction { - tx_id: 1, - source_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), - amount: MicroTari::from(100), - receiver_protocol: ReceiverTransactionProtocol::new_placeholder(), - status: TransactionStatus::Pending, - message: "Yo!".to_string(), - timestamp: Utc::now().naive_utc(), - cancelled: false, - direct_send_success: false, - send_count: 0, - last_send_timestamp: None, - }; - let inbound_tx_sql = InboundTransactionSql::try_from(inbound_tx).unwrap(); - inbound_tx_sql.commit(&conn).unwrap(); + let mut pool = SqliteConnectionPool::new(db_path.clone(), 1, true, true, Duration::from_secs(60)); + pool.create_pool() + .unwrap_or_else(|_| panic!("Error connecting to {}", db_path)); + // Note: For this test the connection pool is setup with a pool size of one; the pooled connection must go out + // of scope to be released once obtained otherwise subsequent calls to obtain a pooled connection will fail . + { + let conn = pool + .get_pooled_connection() + .unwrap_or_else(|_| panic!("Error connecting to {}", db_path)); + + embedded_migrations::run_with_output(&conn, &mut std::io::stdout()).expect("Migration failed"); + + let inbound_tx = InboundTransaction { + tx_id: 1, + source_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + amount: MicroTari::from(100), + receiver_protocol: ReceiverTransactionProtocol::new_placeholder(), + status: TransactionStatus::Pending, + message: "Yo!".to_string(), + timestamp: Utc::now().naive_utc(), + cancelled: false, + direct_send_success: false, + send_count: 0, + last_send_timestamp: None, + }; + let inbound_tx_sql = InboundTransactionSql::try_from(inbound_tx).unwrap(); + inbound_tx_sql.commit(&conn).unwrap(); - let outbound_tx = OutboundTransaction { - tx_id: 2u64, - destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), - amount: MicroTari::from(100), - fee: MicroTari::from(10), - sender_protocol: SenderTransactionProtocol::new_placeholder(), - status: TransactionStatus::Pending, - message: "Yo!".to_string(), - timestamp: Utc::now().naive_utc(), - cancelled: false, - direct_send_success: false, - send_count: 0, - last_send_timestamp: None, - }; - let outbound_tx_sql = OutboundTransactionSql::try_from(outbound_tx).unwrap(); - outbound_tx_sql.commit(&conn).unwrap(); + let outbound_tx = OutboundTransaction { + tx_id: 2u64, + destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + amount: MicroTari::from(100), + fee: MicroTari::from(10), + sender_protocol: SenderTransactionProtocol::new_placeholder(), + status: TransactionStatus::Pending, + message: "Yo!".to_string(), + timestamp: Utc::now().naive_utc(), + cancelled: false, + direct_send_success: false, + send_count: 0, + last_send_timestamp: None, + }; + let outbound_tx_sql = OutboundTransactionSql::try_from(outbound_tx).unwrap(); + outbound_tx_sql.commit(&conn).unwrap(); - let completed_tx = CompletedTransaction { - tx_id: 3, - source_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), - destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), - amount: MicroTari::from(100), - fee: MicroTari::from(100), - transaction: Transaction::new( - vec![], - vec![], - vec![], - PrivateKey::random(&mut OsRng), - PrivateKey::random(&mut OsRng), - ), - status: TransactionStatus::MinedUnconfirmed, - message: "Yo!".to_string(), - timestamp: Utc::now().naive_utc(), - cancelled: false, - direction: TransactionDirection::Unknown, - coinbase_block_height: None, - send_count: 0, - last_send_timestamp: None, - valid: true, - transaction_signature: Signature::default(), - confirmations: None, - mined_height: None, - mined_in_block: None, - }; - let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx).unwrap(); - completed_tx_sql.commit(&conn).unwrap(); + let completed_tx = CompletedTransaction { + tx_id: 3, + source_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + destination_public_key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + amount: MicroTari::from(100), + fee: MicroTari::from(100), + transaction: Transaction::new( + vec![], + vec![], + vec![], + PrivateKey::random(&mut OsRng), + PrivateKey::random(&mut OsRng), + ), + status: TransactionStatus::MinedUnconfirmed, + message: "Yo!".to_string(), + timestamp: Utc::now().naive_utc(), + cancelled: false, + direction: TransactionDirection::Unknown, + coinbase_block_height: None, + send_count: 0, + last_send_timestamp: None, + valid: true, + transaction_signature: Signature::default(), + confirmations: None, + mined_height: None, + mined_in_block: None, + }; + let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx).unwrap(); + completed_tx_sql.commit(&conn).unwrap(); + } let key = GenericArray::from_slice(b"an example very very secret key."); let cipher = Aes256Gcm::new(key); - let connection = WalletDbConnection::new(conn, None); + let connection = WalletDbConnection::new(pool, None); let db1 = TransactionServiceSqliteDatabase::new(connection.clone(), Some(cipher.clone())); assert!(db1.apply_encryption(cipher.clone()).is_err()); @@ -2669,7 +2716,14 @@ mod test { let db_path = format!("{}{}", db_folder, db_name); embed_migrations!("./migrations"); - let conn = SqliteConnection::establish(&db_path).unwrap_or_else(|_| panic!("Error connecting to {}", db_path)); + // Note: For this test the connection pool is setup with a pool size of 2; a pooled connection must go out + // of scope to be released once obtained otherwise subsequent calls to obtain a pooled connection will fail . + let mut pool = SqliteConnectionPool::new(db_path.clone(), 2, true, true, Duration::from_secs(60)); + pool.create_pool() + .unwrap_or_else(|_| panic!("Error connecting to {}", db_path)); + let conn = pool + .get_pooled_connection() + .unwrap_or_else(|_| panic!("Error connecting to {}", db_path)); embedded_migrations::run_with_output(&conn, &mut std::io::stdout()).expect("Migration failed"); @@ -2731,7 +2785,7 @@ mod test { } } - let connection = WalletDbConnection::new(conn, None); + let connection = WalletDbConnection::new(pool, None); let db1 = TransactionServiceSqliteDatabase::new(connection, None); let txn_list = db1.get_transactions_to_be_broadcast().unwrap(); diff --git a/base_layer/wallet/tests/output_manager_service/service.rs b/base_layer/wallet/tests/output_manager_service/service.rs index b99ed40b30..30ef5c1920 100644 --- a/base_layer/wallet/tests/output_manager_service/service.rs +++ b/base_layer/wallet/tests/output_manager_service/service.rs @@ -60,7 +60,7 @@ use tari_crypto::{ }; use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic}; use tari_p2p::Network; -use tari_service_framework::{reply_channel, reply_channel::SenderService}; +use tari_service_framework::reply_channel; use tari_shutdown::Shutdown; use tari_wallet::{ base_node_service::{ diff --git a/base_layer/wallet/tests/support/data.rs b/base_layer/wallet/tests/support/data.rs index 2f5681f765..400c49c206 100644 --- a/base_layer/wallet/tests/support/data.rs +++ b/base_layer/wallet/tests/support/data.rs @@ -50,7 +50,7 @@ pub fn get_temp_sqlite_database_connection() -> (WalletDbConnection, TempDir) { let db_folder = db_tempdir.path().to_str().unwrap().to_string(); let db_path = format!("{}/{}", db_folder, db_name); // let db_path = "/tmp/test.sqlite3".to_string(); - let connection = run_migration_and_create_sqlite_connection(&db_path).unwrap(); + let connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); (connection, db_tempdir) } diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index 32ac55970c..05cb742165 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -20,11 +20,14 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::support::{ - comms_and_services::{create_dummy_message, get_next_memory_address, setup_comms_services}, - comms_rpc::{connect_rpc_client, BaseNodeWalletRpcMockService, BaseNodeWalletRpcMockState}, - utils::{make_input, TestParams}, +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, + path::Path, + sync::Arc, + time::Duration, }; + use chrono::{Duration as ChronoDuration, Utc}; use futures::{ channel::{mpsc, mpsc::Sender}, @@ -33,13 +36,22 @@ use futures::{ }; use prost::Message; use rand::{rngs::OsRng, RngCore}; -use std::{ - collections::HashMap, - convert::{TryFrom, TryInto}, - path::Path, - sync::Arc, - time::Duration, +use tari_crypto::{ + commitment::HomomorphicCommitmentFactory, + common::Blake256, + inputs, + keys::{PublicKey as PK, SecretKey as SK}, + script, + script::{ExecutionStack, TariScript}, }; +use tempfile::tempdir; +use tokio::{ + runtime, + runtime::{Builder, Runtime}, + sync::{broadcast, broadcast::channel}, + time::sleep, +}; + use tari_common_types::{ chain_metadata::ChainMetadata, transaction::{TransactionDirection, TransactionStatus}, @@ -87,14 +99,6 @@ use tari_core::{ SenderTransactionProtocol, }, }; -use tari_crypto::{ - commitment::HomomorphicCommitmentFactory, - common::Blake256, - inputs, - keys::{PublicKey as PK, SecretKey as SK}, - script, - script::{ExecutionStack, TariScript}, -}; use tari_key_manager::cipher_seed::CipherSeed; use tari_p2p::{comms_connector::pubsub_connector, domain_message::DomainMessage, Network}; use tari_service_framework::{reply_channel, RegisterHandle, StackBuilder}; @@ -145,12 +149,11 @@ use tari_wallet::{ }, types::HashDigest, }; -use tempfile::tempdir; -use tokio::{ - runtime, - runtime::{Builder, Runtime}, - sync::{broadcast, broadcast::channel}, - time::sleep, + +use crate::support::{ + comms_and_services::{create_dummy_message, get_next_memory_address, setup_comms_services}, + comms_rpc::{connect_rpc_client, BaseNodeWalletRpcMockService, BaseNodeWalletRpcMockState}, + utils::{make_input, TestParams}, }; fn create_runtime() -> Runtime { @@ -1243,7 +1246,7 @@ fn test_accepting_unknown_tx_id_and_malformed_reply() { let path_string = temp_dir.path().to_str().unwrap().to_string(); let alice_db_name = format!("{}.sqlite3", random::string(8).as_str()); let alice_db_path = format!("{}/{}", path_string, alice_db_name); - let connection_alice = run_migration_and_create_sqlite_connection(&alice_db_path).unwrap(); + let connection_alice = run_migration_and_create_sqlite_connection(&alice_db_path, 16).unwrap(); let bob_node_identity = NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE); @@ -1351,8 +1354,8 @@ fn finalize_tx_with_incorrect_pubkey() { let alice_db_path = format!("{}/{}", path_string, alice_db_name); let bob_db_name = format!("{}.sqlite3", random::string(8).as_str()); let bob_db_path = format!("{}/{}", path_string, bob_db_name); - let connection_alice = run_migration_and_create_sqlite_connection(&alice_db_path).unwrap(); - let connection_bob = run_migration_and_create_sqlite_connection(&bob_db_path).unwrap(); + let connection_alice = run_migration_and_create_sqlite_connection(&alice_db_path, 16).unwrap(); + let connection_bob = run_migration_and_create_sqlite_connection(&bob_db_path, 16).unwrap(); let ( mut alice_ts, @@ -1479,8 +1482,8 @@ fn finalize_tx_with_missing_output() { let alice_db_path = format!("{}/{}", path_string, alice_db_name); let bob_db_name = format!("{}.sqlite3", random::string(8).as_str()); let bob_db_path = format!("{}/{}", path_string, bob_db_name); - let connection_alice = run_migration_and_create_sqlite_connection(&alice_db_path).unwrap(); - let connection_bob = run_migration_and_create_sqlite_connection(&bob_db_path).unwrap(); + let connection_alice = run_migration_and_create_sqlite_connection(&alice_db_path, 16).unwrap(); + let connection_bob = run_migration_and_create_sqlite_connection(&bob_db_path, 16).unwrap(); let ( mut alice_ts, @@ -5105,7 +5108,7 @@ fn dont_broadcast_invalid_transactions() { let temp_dir = tempdir().unwrap(); let db_name = format!("{}.sqlite3", random::string(8).as_str()); let db_path = format!("{}/{}", temp_dir.path().to_str().unwrap(), db_name); - let connection = run_migration_and_create_sqlite_connection(&db_path).unwrap(); + let connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); let backend = TransactionServiceSqliteDatabase::new(connection.clone(), None); let kernel = KernelBuilder::new() diff --git a/base_layer/wallet/tests/transaction_service/storage.rs b/base_layer/wallet/tests/transaction_service/storage.rs index da77f6bad5..e14e28b8d0 100644 --- a/base_layer/wallet/tests/transaction_service/storage.rs +++ b/base_layer/wallet/tests/transaction_service/storage.rs @@ -552,7 +552,7 @@ pub fn test_transaction_service_sqlite_db() { let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); let db_path = format!("{}/{}", db_folder, db_name); - let connection = run_migration_and_create_sqlite_connection(&db_path).unwrap(); + let connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); test_db_backend(TransactionServiceSqliteDatabase::new(connection, None)); } @@ -563,7 +563,7 @@ pub fn test_transaction_service_sqlite_db_encrypted() { let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); let db_path = format!("{}/{}", db_folder, db_name); - let connection = run_migration_and_create_sqlite_connection(&db_path).unwrap(); + let connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); let key = GenericArray::from_slice(b"an example very very secret key."); let cipher = Aes256Gcm::new(key); diff --git a/base_layer/wallet/tests/transaction_service/transaction_protocols.rs b/base_layer/wallet/tests/transaction_service/transaction_protocols.rs index ac3dd8eebd..102c744769 100644 --- a/base_layer/wallet/tests/transaction_service/transaction_protocols.rs +++ b/base_layer/wallet/tests/transaction_service/transaction_protocols.rs @@ -134,7 +134,7 @@ pub async fn setup( let db_name = format!("{}.sqlite3", random::string(8).as_str()); let temp_dir = tempdir().unwrap(); let db_folder = temp_dir.path().to_str().unwrap().to_string(); - let db_connection = run_migration_and_create_sqlite_connection(&format!("{}/{}", db_folder, db_name)).unwrap(); + let db_connection = run_migration_and_create_sqlite_connection(&format!("{}/{}", db_folder, db_name), 16).unwrap(); let db = TransactionDatabase::new(TransactionServiceSqliteDatabase::new(db_connection, None)); diff --git a/base_layer/wallet/tests/wallet/mod.rs b/base_layer/wallet/tests/wallet/mod.rs index 291c193409..e2a2df6da2 100644 --- a/base_layer/wallet/tests/wallet/mod.rs +++ b/base_layer/wallet/tests/wallet/mod.rs @@ -136,7 +136,7 @@ async fn create_wallet( .with_extension("sqlite3"); let (wallet_backend, transaction_backend, output_manager_backend, contacts_backend) = - initialize_sqlite_database_backends(sql_database_path, passphrase).unwrap(); + initialize_sqlite_database_backends(sql_database_path, passphrase, 16).unwrap(); let transaction_service_config = TransactionServiceConfig { resend_response_cooldown: Duration::from_secs(1), @@ -312,7 +312,7 @@ async fn test_wallet() { alice_wallet.wait_until_shutdown().await; let connection = - run_migration_and_create_sqlite_connection(¤t_wallet_path).expect("Could not open Sqlite db"); + run_migration_and_create_sqlite_connection(¤t_wallet_path, 16).expect("Could not open Sqlite db"); if WalletSqliteDatabase::new(connection.clone(), None).is_ok() { panic!("Should not be able to instantiate encrypted wallet without cipher"); @@ -348,7 +348,7 @@ async fn test_wallet() { alice_wallet.wait_until_shutdown().await; let connection = - run_migration_and_create_sqlite_connection(¤t_wallet_path).expect("Could not open Sqlite db"); + run_migration_and_create_sqlite_connection(¤t_wallet_path, 16).expect("Could not open Sqlite db"); let db = WalletSqliteDatabase::new(connection, None).expect( "Should be able to instantiate db with cipher", @@ -386,12 +386,12 @@ async fn test_wallet() { .unwrap(); let connection = - run_migration_and_create_sqlite_connection(¤t_wallet_path).expect("Could not open Sqlite db"); + run_migration_and_create_sqlite_connection(¤t_wallet_path, 16).expect("Could not open Sqlite db"); let wallet_db = WalletDatabase::new(WalletSqliteDatabase::new(connection.clone(), None).unwrap()); let master_seed = wallet_db.get_master_seed().await.unwrap(); assert!(master_seed.is_some()); // Checking that the backup has had its Comms Private Key is cleared. - let connection = run_migration_and_create_sqlite_connection(&backup_wallet_path).expect( + let connection = run_migration_and_create_sqlite_connection(&backup_wallet_path, 16).expect( "Could not open Sqlite db", ); @@ -764,14 +764,14 @@ fn test_db_file_locking() { let db_tempdir = tempdir().unwrap(); let wallet_path = db_tempdir.path().join("alice_db").with_extension("sqlite3"); - let connection = run_migration_and_create_sqlite_connection(&wallet_path).expect("Could not open Sqlite db"); + let connection = run_migration_and_create_sqlite_connection(&wallet_path, 16).expect("Could not open Sqlite db"); - match run_migration_and_create_sqlite_connection(&wallet_path) { + match run_migration_and_create_sqlite_connection(&wallet_path, 16) { Err(WalletStorageError::CannotAcquireFileLock) => {}, _ => panic!("Should not be able to acquire file lock"), } drop(connection); - assert!(run_migration_and_create_sqlite_connection(&wallet_path).is_ok()); + assert!(run_migration_and_create_sqlite_connection(&wallet_path, 16).is_ok()); } diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 6fb0da6528..c783a1147a 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -2933,7 +2933,7 @@ pub unsafe extern "C" fn wallet_create( debug!(target: LOG_TARGET, "Running Wallet database migrations"); let (wallet_backend, transaction_backend, output_manager_backend, contacts_backend) = - match initialize_sqlite_database_backends(sql_database_path, passphrase_option) { + match initialize_sqlite_database_backends(sql_database_path, passphrase_option, 16) { Ok((w, t, o, c)) => (w, t, o, c), Err(e) => { error = LibWalletError::from(WalletError::WalletStorageError(e)).code; @@ -5781,7 +5781,7 @@ mod test { let runtime = Runtime::new().unwrap(); let connection = - run_migration_and_create_sqlite_connection(&sql_database_path).expect("Could not open Sqlite db"); + run_migration_and_create_sqlite_connection(&sql_database_path, 16).expect("Could not open Sqlite db"); let wallet_backend = WalletDatabase::new(WalletSqliteDatabase::new(connection, None).unwrap()); let stored_seed = runtime.block_on(wallet_backend.get_master_seed()).unwrap(); @@ -5816,7 +5816,7 @@ mod test { wallet_destroy(alice_wallet); let connection = - run_migration_and_create_sqlite_connection(&sql_database_path).expect("Could not open Sqlite db"); + run_migration_and_create_sqlite_connection(&sql_database_path, 16).expect("Could not open Sqlite db"); let wallet_backend = WalletDatabase::new(WalletSqliteDatabase::new(connection, None).unwrap()); let stored_seed1 = runtime.block_on(wallet_backend.get_master_seed()).unwrap().unwrap(); @@ -5853,7 +5853,7 @@ mod test { wallet_destroy(alice_wallet2); let connection = - run_migration_and_create_sqlite_connection(&sql_database_path).expect("Could not open Sqlite db"); + run_migration_and_create_sqlite_connection(&sql_database_path, 16).expect("Could not open Sqlite db"); let wallet_backend = WalletDatabase::new(WalletSqliteDatabase::new(connection, None).unwrap()); let stored_seed2 = runtime.block_on(wallet_backend.get_master_seed()).unwrap().unwrap(); @@ -5872,7 +5872,7 @@ mod test { let sql_database_path = alice_temp_dir.path().join("backup").with_extension("sqlite3"); let connection = - run_migration_and_create_sqlite_connection(&sql_database_path).expect("Could not open Sqlite db"); + run_migration_and_create_sqlite_connection(&sql_database_path, 16).expect("Could not open Sqlite db"); let wallet_backend = WalletDatabase::new(WalletSqliteDatabase::new(connection, None).unwrap()); let stored_seed = runtime.block_on(wallet_backend.get_master_seed()).unwrap(); diff --git a/common/src/configuration/global.rs b/common/src/configuration/global.rs index cb6a4dc13b..75c728ebd6 100644 --- a/common/src/configuration/global.rs +++ b/common/src/configuration/global.rs @@ -112,6 +112,7 @@ pub struct GlobalConfig { pub transaction_event_channel_size: usize, pub base_node_event_channel_size: usize, pub output_manager_event_channel_size: usize, + pub wallet_connection_manager_pool_size: usize, pub console_wallet_password: Option, pub wallet_command_send_wait_stage: String, pub wallet_command_send_wait_timeout: u64, @@ -486,6 +487,9 @@ fn convert_node_config( let key = "wallet.base_node_event_channel_size"; let base_node_event_channel_size = optional(cfg.get_int(key))?.unwrap_or(250) as usize; + let key = "wallet.connection_manager_pool_size"; + let wallet_connection_manager_pool_size = optional(cfg.get_int(key))?.unwrap_or(16) as usize; + let key = "wallet.output_manager_event_channel_size"; let output_manager_event_channel_size = optional(cfg.get_int(key))?.unwrap_or(250) as usize; @@ -759,6 +763,7 @@ fn convert_node_config( transaction_num_confirmations_required, transaction_event_channel_size, base_node_event_channel_size, + wallet_connection_manager_pool_size, output_manager_event_channel_size, console_wallet_password, wallet_command_send_wait_stage, diff --git a/common_sqlite/Cargo.toml b/common_sqlite/Cargo.toml new file mode 100644 index 0000000000..5f08e511e2 --- /dev/null +++ b/common_sqlite/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tari_common_sqlite" +authors = ["The Tari Development Community"] +description = "Tari cryptocurrency wallet library" +license = "BSD-3-Clause" +version = "0.21.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +diesel = { version = "1.4.7", features = ["sqlite", "r2d2"] } +log = "0.4.6" +thiserror = "1.0.26" diff --git a/common_sqlite/src/connection_options.rs b/common_sqlite/src/connection_options.rs new file mode 100644 index 0000000000..8e181e5fdf --- /dev/null +++ b/common_sqlite/src/connection_options.rs @@ -0,0 +1,63 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use core::{ + option::{Option, Option::Some}, + result::{Result, Result::Ok}, + time::Duration, +}; +use diesel::{connection::SimpleConnection, SqliteConnection}; + +#[derive(Debug, Clone)] +pub struct ConnectionOptions { + enable_wal: bool, + enable_foreign_keys: bool, + busy_timeout: Option, +} + +impl ConnectionOptions { + pub fn new(enable_wal: bool, enable_foreign_keys: bool, busy_timeout: Duration) -> Self { + Self { + enable_wal, + enable_foreign_keys, + busy_timeout: Some(busy_timeout), + } + } +} + +impl diesel::r2d2::CustomizeConnection for ConnectionOptions { + fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> { + (|| { + if self.enable_wal { + conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;")?; + } + if self.enable_foreign_keys { + conn.batch_execute("PRAGMA foreign_keys = ON;")?; + } + if let Some(d) = self.busy_timeout { + conn.batch_execute(&format!("PRAGMA busy_timeout = {};", d.as_millis()))?; + } + Ok(()) + })() + .map_err(diesel::r2d2::Error::QueryError) + } +} diff --git a/common_sqlite/src/error.rs b/common_sqlite/src/error.rs new file mode 100644 index 0000000000..c47859ce16 --- /dev/null +++ b/common_sqlite/src/error.rs @@ -0,0 +1,35 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SqliteStorageError { + #[error("Diesel R2d2 error")] + DieselR2d2Error(String), +} + +impl PartialEq for SqliteStorageError { + fn eq(&self, other: &Self) -> bool { + self == other + } +} diff --git a/common_sqlite/src/lib.rs b/common_sqlite/src/lib.rs new file mode 100644 index 0000000000..8f0cf8e37a --- /dev/null +++ b/common_sqlite/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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. + +mod connection_options; +pub mod error; +pub mod sqlite_connection_pool; diff --git a/common_sqlite/src/sqlite_connection_pool.rs b/common_sqlite/src/sqlite_connection_pool.rs new file mode 100644 index 0000000000..f21f7f498e --- /dev/null +++ b/common_sqlite/src/sqlite_connection_pool.rs @@ -0,0 +1,142 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{connection_options::ConnectionOptions, error::SqliteStorageError}; +use core::{ + option::Option::{None, Some}, + result::{ + Result, + Result::{Err, Ok}, + }, + time::Duration, +}; +use diesel::{ + r2d2::{ConnectionManager, Pool, PooledConnection}, + SqliteConnection, +}; +use log::*; + +const LOG_TARGET: &str = "common_sqlite::sqlite_connection_pool"; + +#[derive(Clone)] +pub struct SqliteConnectionPool { + pool: Option>>, + db_path: String, + pool_size: usize, + connection_options: ConnectionOptions, +} + +impl SqliteConnectionPool { + pub fn new( + db_path: String, + pool_size: usize, + enable_wal: bool, + enable_foreign_keys: bool, + busy_timeout: Duration, + ) -> Self { + Self { + pool: None, + db_path, + pool_size, + connection_options: ConnectionOptions::new(enable_wal, enable_foreign_keys, busy_timeout), + } + } + + /// Create an sqlite connection pool managed by the pool connection manager + pub fn create_pool(&mut self) -> Result<(), SqliteStorageError> { + if self.pool.is_none() { + let pool = Pool::builder() + .max_size(self.pool_size as u32) + .connection_customizer(Box::new(self.connection_options.clone())) + .build(ConnectionManager::::new(self.db_path.as_str())) + .map_err(|e| SqliteStorageError::DieselR2d2Error(e.to_string())); + self.pool = Some(pool?); + } else { + warn!( + target: LOG_TARGET, + "Connection pool for {} already exists", self.db_path + ); + } + Ok(()) + } + + /// Return a pooled sqlite connection managed by the pool connection manager, waits for at most the configured + /// connection timeout before returning an error. + pub fn get_pooled_connection( + &self, + ) -> Result>, SqliteStorageError> { + if let Some(pool) = self.pool.clone() { + pool.get().map_err(|e| { + warn!( + target: LOG_TARGET, + "Connection pool state {:?}: {}", + pool.state(), + e.to_string() + ); + SqliteStorageError::DieselR2d2Error(e.to_string()) + }) + } else { + Err(SqliteStorageError::DieselR2d2Error("Pool does not exist".to_string())) + } + } + + /// Return a pooled sqlite connection managed by the pool connection manager, waits for at most supplied + /// connection timeout before returning an error. + pub fn get_pooled_connection_timeout( + &self, + timeout: Duration, + ) -> Result>, SqliteStorageError> { + if let Some(pool) = self.pool.clone() { + pool.get_timeout(timeout).map_err(|e| { + warn!( + target: LOG_TARGET, + "Connection pool state {:?}: {}", + pool.state(), + e.to_string() + ); + SqliteStorageError::DieselR2d2Error(e.to_string()) + }) + } else { + Err(SqliteStorageError::DieselR2d2Error("Pool does not exist".to_string())) + } + } + + /// Return a pooled sqlite connection managed by the pool connection manager, returns None if there are no idle + /// connections available in the pool. This method will not block waiting to establish a new connection. + pub fn try_get_pooled_connection( + &self, + ) -> Result>>, SqliteStorageError> { + if let Some(pool) = self.pool.clone() { + let connection = pool.try_get(); + if connection.is_none() { + warn!( + target: LOG_TARGET, + "No connections available, pool state {:?}", + pool.state() + ); + }; + Ok(connection) + } else { + Err(SqliteStorageError::DieselR2d2Error("Pool does not exist".to_string())) + } + } +} From 433bc46e3d5cd488ec0f29fef6059594cf0cf3e3 Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Mon, 15 Nov 2021 17:33:43 +0400 Subject: [PATCH 22/46] feat: implement prometheus metrics for base node (#3563) Description --- - Instrument tari_comms for prometheus metrics - add `metrics` feature to base node and tari_comms - add prometheus metrics scrape server - add prometheus push client - add configuration for metrics `common.metrics` - add `get-network-stats` command - fix bug in value for last request latency Motivation and Context --- Increase base node visibility ![image](https://user-images.githubusercontent.com/1057902/141313369-46060af6-5f27-4ca5-846c-998248996fbd.png) **Dependencies** - `warp` for http metric pull server - `reqwest` for metric push client - `prometheus` for instrumentation How Has This Been Tested? --- Manually - run prometheus ```docker run --rm -it -p 9090:9090 -v `pwd`/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus``` - run base node(s) with `--metrics-bind-addr 127.0.0.1:5544` - visit `localhost:9090` ```yaml # prometheus.yml scrape_configs: - job_name: 'node1' scrape_interval: 5s metrics_path: "/metrics" static_configs: - targets: ['host.docker.internal:5544'] labels: node: 'node1' ``` --- Cargo.lock | 199 +++++++++++++++++- Cargo.toml | 1 + applications/tari_base_node/Cargo.toml | 17 +- .../tari_base_node/src/command_handler.rs | 51 +++++ applications/tari_base_node/src/main.rs | 20 +- .../tari_base_node/src/metrics/builder.rs | 84 ++++++++ .../tari_base_node/src/metrics/mod.rs | 71 +++++++ .../tari_base_node/src/metrics/push.rs | 60 ++++++ .../tari_base_node/src/metrics/server.rs | 68 ++++++ applications/tari_base_node/src/parser.rs | 7 + common/src/configuration/bootstrap.rs | 9 + common/src/configuration/global.rs | 30 +++ comms/Cargo.toml | 11 +- comms/rpc_macros/src/generator.rs | 2 +- comms/src/connection_manager/listener.rs | 24 +-- .../src/connection_manager/peer_connection.rs | 10 +- comms/src/connection_manager/types.rs | 8 + comms/src/connectivity/connection_pool.rs | 44 ++-- comms/src/connectivity/manager.rs | 31 ++- comms/src/connectivity/metrics.rs | 54 +++++ comms/src/connectivity/mod.rs | 3 + comms/src/peer_manager/peer_features.rs | 8 + comms/src/protocol/rpc/client/metrics.rs | 90 ++++++++ .../protocol/rpc/{client.rs => client/mod.rs} | 84 ++++++-- .../rpc/{client_pool.rs => client/pool.rs} | 2 +- comms/src/protocol/rpc/client/tests.rs | 173 +++++++++++++++ comms/src/protocol/rpc/message.rs | 4 + comms/src/protocol/rpc/mod.rs | 13 +- comms/src/protocol/rpc/server/metrics.rs | 90 ++++++++ comms/src/protocol/rpc/server/mod.rs | 39 +++- comms/src/protocol/rpc/test/client_pool.rs | 2 +- .../src/protocol/rpc/test/greeting_service.rs | 8 +- comms/src/protocol/rpc/test/mock.rs | 2 +- comms/src/protocol/rpc/test/mod.rs | 5 +- infrastructure/metrics/Cargo.toml | 14 ++ infrastructure/metrics/src/lib.rs | 89 ++++++++ infrastructure/shutdown/src/lib.rs | 6 +- 37 files changed, 1339 insertions(+), 94 deletions(-) create mode 100644 applications/tari_base_node/src/metrics/builder.rs create mode 100644 applications/tari_base_node/src/metrics/mod.rs create mode 100644 applications/tari_base_node/src/metrics/push.rs create mode 100644 applications/tari_base_node/src/metrics/server.rs create mode 100644 comms/src/connectivity/metrics.rs create mode 100644 comms/src/protocol/rpc/client/metrics.rs rename comms/src/protocol/rpc/{client.rs => client/mod.rs} (93%) rename comms/src/protocol/rpc/{client_pool.rs => client/pool.rs} (99%) create mode 100644 comms/src/protocol/rpc/client/tests.rs create mode 100644 comms/src/protocol/rpc/server/metrics.rs create mode 100644 infrastructure/metrics/Cargo.toml create mode 100644 infrastructure/metrics/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 6d6a9ec468..bd118b3ed5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1826,6 +1826,31 @@ dependencies = [ "num-traits 0.2.14", ] +[[package]] +name = "headers" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c4eb0471fcb85846d8b0690695ef354f9afb11cb03cac2e1d7c9253351afb0" +dependencies = [ + "base64 0.13.0", + "bitflags 1.3.2", + "bytes 1.1.0", + "headers-core", + "http", + "httpdate", + "mime", + "sha-1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.3.3" @@ -2397,6 +2422,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -2495,6 +2530,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand 0.8.4", + "safemem", + "tempfile", + "twoway", +] + [[package]] name = "native-tls" version = "0.2.8" @@ -2802,6 +2855,20 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "opentelemetry-http" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50ceb0b0e8b75cb3e388a2571a807c8228dabc5d6670f317b6eb21301095373" +dependencies = [ + "async-trait", + "bytes 1.1.0", + "futures-util", + "http", + "opentelemetry", + "reqwest", +] + [[package]] name = "opentelemetry-jaeger" version = "0.15.0" @@ -2809,9 +2876,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db22f492873ea037bc267b35a0e8e4fb846340058cb7c864efe3d0bf23684593" dependencies = [ "async-trait", + "headers", + "http", "lazy_static 1.4.0", "opentelemetry", + "opentelemetry-http", "opentelemetry-semantic-conventions", + "reqwest", "thiserror", "thrift", "tokio 1.13.0", @@ -3186,6 +3257,21 @@ dependencies = [ "unicode-xid 0.2.2", ] +[[package]] +name = "prometheus" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f64969ffd5dd8f39bd57a68ac53c163a095ed9d0fb707146da1b27025a3504" +dependencies = [ + "cfg-if 1.0.0", + "fnv", + "lazy_static 1.4.0", + "memchr", + "parking_lot 0.11.2", + "protobuf", + "thiserror", +] + [[package]] name = "prost" version = "0.8.0" @@ -3237,6 +3323,12 @@ dependencies = [ "prost", ] +[[package]] +name = "protobuf" +version = "2.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c327e191621a2158159df97cdbc2e7074bb4e940275e35abf38eb3d2595754" + [[package]] name = "qrcode" version = "0.12.0" @@ -3760,6 +3852,12 @@ dependencies = [ "parking_lot 0.11.2", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -4357,6 +4455,7 @@ dependencies = [ "opentelemetry", "opentelemetry-jaeger", "regex", + "reqwest", "rustyline", "rustyline-derive", "strum 0.19.5", @@ -4369,6 +4468,7 @@ dependencies = [ "tari_comms_dht", "tari_core", "tari_crypto", + "tari_metrics", "tari_mmr", "tari_p2p", "tari_service_framework", @@ -4379,6 +4479,7 @@ dependencies = [ "tracing", "tracing-opentelemetry", "tracing-subscriber", + "warp", ] [[package]] @@ -4475,9 +4576,8 @@ dependencies = [ "log-mdc", "multiaddr", "nom 5.1.2", + "once_cell", "openssl-sys", - "opentelemetry", - "opentelemetry-jaeger", "pin-project 1.0.8", "prost", "rand 0.8.4", @@ -4488,6 +4588,7 @@ dependencies = [ "tari_common", "tari_comms_rpc_macros", "tari_crypto", + "tari_metrics", "tari_shutdown", "tari_storage", "tari_test_utils", @@ -4757,6 +4858,14 @@ dependencies = [ "url 2.2.2", ] +[[package]] +name = "tari_metrics" +version = "0.1.0" +dependencies = [ + "once_cell", + "prometheus", +] + [[package]] name = "tari_mining_node" version = "0.21.0" @@ -5316,6 +5425,19 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project 1.0.8", + "tokio 1.13.0", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.6.9" @@ -5763,6 +5885,25 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes 1.1.0", + "http", + "httparse", + "log", + "rand 0.8.4", + "sha-1", + "thiserror", + "url 2.2.2", + "utf-8", +] + [[package]] name = "twofish" version = "0.5.0" @@ -5774,6 +5915,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typemap" version = "0.3.3" @@ -5807,6 +5957,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check 0.9.3", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -5906,6 +6065,12 @@ dependencies = [ "percent-encoding 2.1.0", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8parse" version = "0.2.0" @@ -5957,6 +6122,36 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes 1.1.0", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multipart", + "percent-encoding 2.1.0", + "pin-project 1.0.8", + "scoped-tls", + "serde 1.0.130", + "serde_json", + "serde_urlencoded", + "tokio 1.13.0", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index ad0d9caf5c..385dbaf1dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "comms/dht", "comms/rpc_macros", "common_sqlite", + "infrastructure/metrics", "infrastructure/shutdown", "infrastructure/storage", "infrastructure/test_utils", diff --git a/applications/tari_base_node/Cargo.toml b/applications/tari_base_node/Cargo.toml index 268ff90f15..23c2c4916e 100644 --- a/applications/tari_base_node/Cargo.toml +++ b/applications/tari_base_node/Cargo.toml @@ -12,7 +12,7 @@ tari_app_grpc = { path = "../tari_app_grpc" } tari_app_utilities = { path = "../tari_app_utilities" } tari_common = { path = "../../common" } tari_comms = { path = "../../comms", features = ["rpc"] } -tari_common_types = {path = "../../base_layer/common_types"} +tari_common_types = { path = "../../base_layer/common_types" } tari_comms_dht = { path = "../../comms/dht" } tari_core = { path = "../../base_layer/core", default-features = false, features = ["transactions"] } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } @@ -39,15 +39,22 @@ thiserror = "^1.0.26" tokio = { version = "1.11", features = ["signal"] } tonic = "0.5.2" tracing = "0.1.26" + +# network tracing, rt-tokio for async batch export +opentelemetry = { version = "0.16", default-features = false, features = ["trace", "rt-tokio"] } +opentelemetry-jaeger = { version = "0.15", features = ["rt-tokio", "collector_client", "reqwest_collector_client"] } tracing-opentelemetry = "0.15.0" tracing-subscriber = "0.2.20" -# network tracing, rt-tokio for async batch export -opentelemetry = { version = "0.16", default-features = false, features = ["trace","rt-tokio"] } -opentelemetry-jaeger = { version="0.15", features=["rt-tokio"]} +# Metrics +tari_metrics = { path = "../../infrastructure/metrics", optional = true } +warp = { version = "0.3.1", optional = true } +reqwest = { version = "0.11.4", default-features = false, optional = true } [features] -avx2 = ["tari_core/avx2", "tari_crypto/avx2", "tari_p2p/avx2", "tari_comms/avx2", "tari_comms_dht/avx2"] +default = ["metrics"] +avx2 = ["tari_core/avx2", "tari_crypto/avx2", "tari_p2p/avx2", "tari_comms/avx2", "tari_comms_dht/avx2"] +metrics = ["warp", "reqwest", "tari_metrics", "tari_comms/metrics"] safe = [] diff --git a/applications/tari_base_node/src/command_handler.rs b/applications/tari_base_node/src/command_handler.rs index 8db816de57..5037f5555b 100644 --- a/applications/tari_base_node/src/command_handler.rs +++ b/applications/tari_base_node/src/command_handler.rs @@ -1199,6 +1199,57 @@ impl CommandHandler { } }); } + + #[cfg(not(feature = "metrics"))] + pub fn get_network_stats(&self) { + println!( + "Metrics are not enabled in this binary. Recompile Tari base node with `--features metrics` to enable \ + them." + ); + } + + #[cfg(feature = "metrics")] + pub fn get_network_stats(&self) { + use tari_metrics::proto::MetricType; + let metric_families = tari_metrics::get_default_registry().gather(); + let metric_family_iter = metric_families + .into_iter() + .filter(|family| family.get_name().starts_with("tari_comms")); + + // TODO: Make this useful + let mut table = Table::new(); + table.set_titles(vec!["name", "type", "value"]); + for family in metric_family_iter { + let field_type = family.get_field_type(); + let name = family.get_name(); + for metric in family.get_metric() { + let value = match field_type { + MetricType::COUNTER => metric.get_counter().get_value(), + MetricType::GAUGE => metric.get_gauge().get_value(), + MetricType::SUMMARY => { + let summary = metric.get_summary(); + summary.get_sample_sum() / summary.get_sample_count() as f64 + }, + MetricType::UNTYPED => metric.get_untyped().get_value(), + MetricType::HISTOGRAM => { + let histogram = metric.get_histogram(); + histogram.get_sample_sum() / histogram.get_sample_count() as f64 + }, + }; + + let field_type = match field_type { + MetricType::COUNTER => "COUNTER", + MetricType::GAUGE => "GAUGE", + MetricType::SUMMARY => "SUMMARY", + MetricType::UNTYPED => "UNTYPED", + MetricType::HISTOGRAM => "HISTOGRAM", + }; + + table.add_row(row![name, field_type, value]); + } + } + table.print_stdout(); + } } async fn fetch_banned_peers(pm: &PeerManager) -> Result, PeerManagerError> { diff --git a/applications/tari_base_node/src/main.rs b/applications/tari_base_node/src/main.rs index ba0d3e3ea0..e5817d1268 100644 --- a/applications/tari_base_node/src/main.rs +++ b/applications/tari_base_node/src/main.rs @@ -95,6 +95,9 @@ mod recovery; mod status_line; mod utils; +#[cfg(feature = "metrics")] +mod metrics; + use crate::command_handler::{CommandHandler, StatusOutput}; use futures::{pin_mut, FutureExt}; use log::*; @@ -164,9 +167,13 @@ fn main_inner() -> Result<(), ExitCodes> { /// Sets up the base node and runs the cli_loop async fn run_node(node_config: Arc, bootstrap: ConfigBootstrap) -> Result<(), ExitCodes> { + // This is the main and only shutdown trigger for the system. + let shutdown = Shutdown::new(); + if bootstrap.tracing_enabled { enable_tracing(); } + // Load or create the Node identity let node_identity = setup_node_identity( &node_config.base_node_identity_file, @@ -175,6 +182,17 @@ async fn run_node(node_config: Arc, bootstrap: ConfigBootstrap) -> PeerFeatures::COMMUNICATION_NODE, )?; + #[cfg(feature = "metrics")] + { + metrics::install( + ApplicationType::BaseNode, + &node_identity, + &node_config, + &bootstrap, + shutdown.to_signal(), + ); + } + log_mdc::insert("node-public-key", node_identity.public_key().to_string()); log_mdc::insert("node-id", node_identity.node_id().to_string()); @@ -187,8 +205,6 @@ async fn run_node(node_config: Arc, bootstrap: ConfigBootstrap) -> ); return Ok(()); } - // This is the main and only shutdown trigger for the system. - let shutdown = Shutdown::new(); if bootstrap.rebuild_db { info!(target: LOG_TARGET, "Node is in recovery mode, entering recovery"); diff --git a/applications/tari_base_node/src/metrics/builder.rs b/applications/tari_base_node/src/metrics/builder.rs new file mode 100644 index 0000000000..7db6ce2aa7 --- /dev/null +++ b/applications/tari_base_node/src/metrics/builder.rs @@ -0,0 +1,84 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use super::{push, server}; +use reqwest::{IntoUrl, Url}; +use std::{ + net::{SocketAddr, ToSocketAddrs}, + time::Duration, +}; +use tari_shutdown::ShutdownSignal; +use tokio::task; + +#[derive(Debug, Clone)] +pub struct MetricsBuilder { + listener_addr: Option, + push_endpoint: Option, + push_interval: Duration, +} + +impl MetricsBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn with_scrape_server(mut self, address: T) -> Self { + let listener_addr = address + .to_socket_addrs() + .expect("bad metric server bind address") + .next() + .unwrap(); + self.listener_addr = Some(listener_addr); + self + } + + pub fn with_push_gateway(mut self, endpoint: T) -> Self { + let endpoint = endpoint.into_url().unwrap(); + self.push_endpoint = Some(endpoint); + self + } + + pub fn spawn_services(self, shutdown: ShutdownSignal) { + let registry = tari_metrics::get_default_registry(); + + if let Some(listener_addr) = self.listener_addr { + task::spawn( + shutdown + .clone() + .select(Box::pin(server::start(listener_addr, registry.clone()))), + ); + } + + if let Some(endpoint) = self.push_endpoint { + task::spawn(shutdown.select(Box::pin(push::start(endpoint, self.push_interval, registry)))); + } + } +} + +impl Default for MetricsBuilder { + fn default() -> Self { + Self { + listener_addr: None, + push_endpoint: None, + push_interval: Duration::from_secs(10), + } + } +} diff --git a/applications/tari_base_node/src/metrics/mod.rs b/applications/tari_base_node/src/metrics/mod.rs new file mode 100644 index 0000000000..ebe4e5414f --- /dev/null +++ b/applications/tari_base_node/src/metrics/mod.rs @@ -0,0 +1,71 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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. + +mod builder; +mod push; +mod server; + +use builder::MetricsBuilder; +use std::collections::HashMap; +use tari_common::{configuration::bootstrap::ApplicationType, ConfigBootstrap, GlobalConfig}; +use tari_comms::NodeIdentity; +use tari_metrics::Registry; +use tari_shutdown::ShutdownSignal; + +pub fn install( + application: ApplicationType, + identity: &NodeIdentity, + config: &GlobalConfig, + bootstrap: &ConfigBootstrap, + shutdown: ShutdownSignal, +) { + let metrics_registry = create_metrics_registry(application, identity); + tari_metrics::set_default_registry(metrics_registry); + + let mut metrics = MetricsBuilder::new(); + if let Some(addr) = bootstrap + .metrics_server_bind_addr + .as_ref() + .or_else(|| config.metrics.prometheus_scraper_bind_addr.as_ref()) + { + metrics = metrics.with_scrape_server(addr); + } + if let Some(endpoint) = bootstrap + .metrics_push_endpoint + .as_ref() + .or_else(|| config.metrics.prometheus_push_endpoint.as_ref()) + { + // http://localhost:9091/metrics/job/base-node + metrics = metrics.with_push_gateway(endpoint); + } + metrics.spawn_services(shutdown); +} + +fn create_metrics_registry(application: ApplicationType, identity: &NodeIdentity) -> Registry { + let mut labels = HashMap::with_capacity(4); + labels.insert("app".to_string(), application.as_config_str().to_string()); + labels.insert("node_role".to_string(), identity.features().as_role_str().to_string()); + labels.insert("node_id".to_string(), identity.node_id().to_string()); + labels.insert("node_public_key".to_string(), identity.public_key().to_string()); + + tari_metrics::Registry::new_custom(Some("tari".to_string()), Some(labels)).unwrap() +} diff --git a/applications/tari_base_node/src/metrics/push.rs b/applications/tari_base_node/src/metrics/push.rs new file mode 100644 index 0000000000..2ca900d17a --- /dev/null +++ b/applications/tari_base_node/src/metrics/push.rs @@ -0,0 +1,60 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use log::*; +use reqwest::{Client, Url}; +use std::time::{Duration, Instant}; +use tari_metrics::{Encoder, Registry, TextEncoder}; +use tokio::{time, time::MissedTickBehavior}; + +const LOG_TARGET: &str = "base_node::metrics::push"; + +pub async fn start(endpoint: Url, push_interval: Duration, registry: Registry) { + let mut interval = time::interval(push_interval); + interval.set_missed_tick_behavior(MissedTickBehavior::Delay); + + loop { + interval.tick().await; + + if let Err(err) = push_metrics(®istry, endpoint.clone()).await { + error!(target: LOG_TARGET, "{}", err); + } + } +} + +async fn push_metrics(registry: &Registry, endpoint: Url) -> Result<(), anyhow::Error> { + let client = Client::new(); + let timer = Instant::now(); + let encoder = TextEncoder::new(); + let mut buffer = Vec::new(); + let metrics = registry.gather(); + encoder.encode(&metrics, &mut buffer)?; + let raw_metrics_data = String::from_utf8(buffer)?; + client.post(endpoint).body(raw_metrics_data).send().await?; + debug!( + target: LOG_TARGET, + "POSTed {} metrics in {:.2?}", + metrics.len(), + timer.elapsed() + ); + Ok(()) +} diff --git a/applications/tari_base_node/src/metrics/server.rs b/applications/tari_base_node/src/metrics/server.rs new file mode 100644 index 0000000000..bee29bb861 --- /dev/null +++ b/applications/tari_base_node/src/metrics/server.rs @@ -0,0 +1,68 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use log::*; +use std::{convert::Infallible, net::SocketAddr, string::FromUtf8Error}; +use tari_metrics::{Encoder, Registry, TextEncoder}; +use tokio::{task, task::JoinError}; +use warp::{reject::Reject, Filter, Rejection, Reply}; + +const LOG_TARGET: &str = "app::metrics_server"; + +pub async fn start(listen_addr: SocketAddr, registry: Registry) { + let route = warp::path!("metrics") + .and(with(registry)) + .and_then(metrics_text_handler); + + info!(target: LOG_TARGET, "Metrics server started on {}", listen_addr); + let routes = route.with(warp::log("metrics_server")); + warp::serve(routes).run(listen_addr).await; +} + +async fn metrics_text_handler(registry: Registry) -> Result { + task::spawn_blocking::<_, Result<_, Error>>(move || { + let encoder = TextEncoder::new(); + let mut buffer = Vec::new(); + encoder.encode(®istry.gather(), &mut buffer)?; + let encoded = String::from_utf8(buffer)?; + Ok(encoded) + }) + .await + .map_err(Error::BlockingThreadFailed)? + .map_err(Into::into) +} + +fn with(t: T) -> impl Filter + Clone { + warp::any().map(move || t.clone()) +} + +#[derive(Debug, thiserror::Error)] +enum Error { + #[error("Failed to encode: {0}")] + PrometheusEncodeFailed(#[from] tari_metrics::Error), + #[error("Failed to encode: {0}")] + FromUtf8(#[from] FromUtf8Error), + #[error("Failed to spawn blocking thread: {0}")] + BlockingThreadFailed(#[from] JoinError), +} + +impl Reject for Error {} diff --git a/applications/tari_base_node/src/parser.rs b/applications/tari_base_node/src/parser.rs index 1f08265d5b..7ef6717dd2 100644 --- a/applications/tari_base_node/src/parser.rs +++ b/applications/tari_base_node/src/parser.rs @@ -84,6 +84,7 @@ pub enum BaseNodeCommand { GetMempoolState, Whoami, GetStateInfo, + GetNetworkStats, Quit, Exit, } @@ -265,6 +266,9 @@ impl Parser { Whoami => { self.command_handler.whoami(); }, + GetNetworkStats => { + self.command_handler.get_network_stats(); + }, Exit | Quit => { println!("Shutting down..."); info!( @@ -412,6 +416,9 @@ impl Parser { address" ); }, + GetNetworkStats => { + println!("Displays network stats"); + }, Exit | Quit => { println!("Exits the base node"); }, diff --git a/common/src/configuration/bootstrap.rs b/common/src/configuration/bootstrap.rs index a93439b0cf..083b70a9bb 100644 --- a/common/src/configuration/bootstrap.rs +++ b/common/src/configuration/bootstrap.rs @@ -66,6 +66,7 @@ use std::{ fmt, fmt::{Display, Formatter}, io, + net::SocketAddr, path::{Path, PathBuf}, str::FromStr, }; @@ -153,6 +154,12 @@ pub struct ConfigBootstrap { /// Supply a network (overrides existing configuration) #[structopt(long, alias = "network")] pub network: Option, + /// Metrics server bind address (prometheus pull) + #[structopt(long, alias = "metrics-bind-addr")] + pub metrics_server_bind_addr: Option, + /// Metrics push endpoint (prometheus push) + #[structopt(long)] + pub metrics_push_endpoint: Option, } fn normalize_path(path: PathBuf) -> PathBuf { @@ -189,6 +196,8 @@ impl Default for ConfigBootstrap { miner_max_diff: None, tracing_enabled: false, network: None, + metrics_server_bind_addr: None, + metrics_push_endpoint: None, } } } diff --git a/common/src/configuration/global.rs b/common/src/configuration/global.rs index 75c728ebd6..990b9acd13 100644 --- a/common/src/configuration/global.rs +++ b/common/src/configuration/global.rs @@ -141,6 +141,7 @@ pub struct GlobalConfig { pub mining_wallet_address: String, pub mining_worker_name: String, pub base_node_bypass_range_proof_verification: bool, + pub metrics: MetricsConfig, } impl GlobalConfig { @@ -706,6 +707,8 @@ fn convert_node_config( .filter(|c| c.is_alphanumeric()) .collect::(); + let metrics = MetricsConfig::from_config(&cfg)?; + Ok(GlobalConfig { autoupdate_check_interval, autoupdate_dns_hosts, @@ -793,6 +796,7 @@ fn convert_node_config( mining_wallet_address, mining_worker_name, base_node_bypass_range_proof_verification, + metrics, }) } @@ -1079,3 +1083,29 @@ pub enum CommsTransport { listener_address: Multiaddr, }, } + +#[derive(Debug, Clone)] +pub struct MetricsConfig { + pub prometheus_scraper_bind_addr: Option, + pub prometheus_push_endpoint: Option, +} + +impl MetricsConfig { + fn from_config(cfg: &Config) -> Result { + let key = "common.metrics.server_bind_address"; + let prometheus_scraper_bind_addr = optional(cfg.get_str(key))? + .map(|s| { + s.parse() + .map_err(|_| ConfigurationError::new(key, "Invalid metrics server socket address")) + }) + .transpose()?; + + let key = "common.metrics.push_endpoint"; + let prometheus_push_endpoint = optional(cfg.get_str(key))?; + + Ok(Self { + prometheus_scraper_bind_addr, + prometheus_push_endpoint, + }) + } +} diff --git a/comms/Cargo.toml b/comms/Cargo.toml index 710d4746ac..380184bb05 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -26,12 +26,13 @@ clear_on_drop = "=0.2.4" data-encoding = "2.2.0" digest = "0.9.0" futures = { version = "^0.3", features = ["async-await"] } -lazy_static = "1.3.0" +lazy_static = "1.4.0" lmdb-zero = "0.4.4" log = { version = "0.4.0", features = ["std"] } log-mdc = "0.1.0" multiaddr = { version = "0.13.0" } nom = { version = "5.1.0", features = ["std"], default-features = false } +once_cell = "1.8.0" openssl-sys = { version = "0.9.66", features = ["vendored"], optional = true } pin-project = "1.0.8" prost = "=0.8.0" @@ -48,13 +49,12 @@ tracing = "0.1.26" tracing-futures = "0.2.5" yamux = "=0.9.0" -# network tracing, rt-tokio for async batch export -opentelemetry = { version = "0.16", default-features = false, features = ["trace", "rt-tokio"] } -opentelemetry-jaeger = { version = "0.15", features = ["rt-tokio"] } - # RPC dependencies tower-make = { version = "0.3.0", optional = true } +# Metrics +tari_metrics = { path = "../infrastructure/metrics" } + [dev-dependencies] tari_test_utils = { version = "^0.21", path = "../infrastructure/test_utils" } tari_comms_rpc_macros = { version = "*", path = "./rpc_macros" } @@ -69,4 +69,5 @@ tari_common = { version = "^0.21", path = "../common", features = ["build"] } [features] c_integration = [] avx2 = ["tari_crypto/avx2"] +metrics = [] rpc = ["tower-make"] diff --git a/comms/rpc_macros/src/generator.rs b/comms/rpc_macros/src/generator.rs index 1a5ed3e023..312e318284 100644 --- a/comms/rpc_macros/src/generator.rs +++ b/comms/rpc_macros/src/generator.rs @@ -197,7 +197,7 @@ impl RpcCodeGenerator { pub async fn connect(framed: #dep_mod::CanonicalFraming) -> Result where TSubstream: #dep_mod::AsyncRead + #dep_mod::AsyncWrite + Unpin + Send + #dep_mod::StreamId + 'static { use #dep_mod::NamedProtocolService; - let inner = #dep_mod::RpcClient::connect(Default::default(), framed, Self::PROTOCOL_NAME.into()).await?; + let inner = #dep_mod::RpcClient::connect(Default::default(), Default::default(), framed, Self::PROTOCOL_NAME.into()).await?; Ok(Self { inner }) } diff --git a/comms/src/connection_manager/listener.rs b/comms/src/connection_manager/listener.rs index 21c3771610..c79e192795 100644 --- a/comms/src/connection_manager/listener.rs +++ b/comms/src/connection_manager/listener.rs @@ -41,7 +41,6 @@ use crate::{ protocol::ProtocolId, runtime, transports::Transport, - types::CommsPublicKey, utils::multiaddr::multiaddr_to_socketaddr, PeerManager, }; @@ -335,21 +334,22 @@ where } async fn remote_public_key_from_socket(socket: TTransport::Output, noise_config: NoiseConfig) -> String { - let public_key: Option = match time::timeout( + let noise_socket = time::timeout( Duration::from_secs(30), noise_config.upgrade_socket(socket, ConnectionDirection::Inbound), ) - .await - .map_err(|_| ConnectionManagerError::NoiseProtocolTimeout) - { - Ok(Ok(noise_socket)) => { - match noise_socket - .get_remote_public_key() - .ok_or(ConnectionManagerError::InvalidStaticPublicKey) - { - Ok(pk) => Some(pk), - _ => None, + .await; + + let public_key = match noise_socket { + Ok(Ok(mut noise_socket)) => { + let pk = noise_socket.get_remote_public_key(); + if let Err(err) = noise_socket.shutdown().await { + debug!( + target: LOG_TARGET, + "IO error when closing socket after invalid wire format: {}", err + ); } + pk }, _ => None, }; diff --git a/comms/src/connection_manager/peer_connection.rs b/comms/src/connection_manager/peer_connection.rs index 37b05b4cd3..3b407e0b69 100644 --- a/comms/src/connection_manager/peer_connection.rs +++ b/comms/src/connection_manager/peer_connection.rs @@ -22,12 +22,12 @@ #[cfg(feature = "rpc")] use crate::protocol::rpc::{ + pool::RpcClientPool, + pool::RpcPoolClient, NamedProtocolService, RpcClient, RpcClientBuilder, - RpcClientPool, RpcError, - RpcPoolClient, RPC_MAX_FRAME_SIZE, }; @@ -242,7 +242,11 @@ impl PeerConnection { self.peer_node_id ); let framed = self.open_framed_substream(&protocol, RPC_MAX_FRAME_SIZE).await?; - builder.with_protocol_id(protocol).connect(framed).await + builder + .with_protocol_id(protocol) + .with_node_id(self.peer_node_id.clone()) + .connect(framed) + .await } /// Creates a new RpcClientPool that can be shared between tasks. The client pool will lazily establish up to diff --git a/comms/src/connection_manager/types.rs b/comms/src/connection_manager/types.rs index c92b2b717a..8751c1e87c 100644 --- a/comms/src/connection_manager/types.rs +++ b/comms/src/connection_manager/types.rs @@ -35,6 +35,14 @@ impl ConnectionDirection { is_fn!(is_inbound, ConnectionDirection::Inbound); is_fn!(is_outbound, ConnectionDirection::Outbound); + + pub fn as_str(&self) -> &'static str { + use ConnectionDirection::*; + match self { + Inbound => "inbound", + Outbound => "outbound", + } + } } impl fmt::Display for ConnectionDirection { diff --git a/comms/src/connectivity/connection_pool.rs b/comms/src/connectivity/connection_pool.rs index 1b22de0d34..556e34e592 100644 --- a/comms/src/connectivity/connection_pool.rs +++ b/comms/src/connectivity/connection_pool.rs @@ -207,49 +207,41 @@ impl ConnectionPool { } pub fn count_connected_nodes(&self) -> usize { - self.connections - .values() - .filter(|c| { - c.status() == ConnectionStatus::Connected && - c.connection() - .filter(|c| c.is_connected() && c.peer_features().is_node()) - .is_some() - }) - .count() + self.count_filtered(|c| { + c.status() == ConnectionStatus::Connected && + c.connection() + .filter(|c| c.is_connected() && c.peer_features().is_node()) + .is_some() + }) } pub fn count_connected_clients(&self) -> usize { - self.connections - .values() - .filter(|c| { - c.status() == ConnectionStatus::Connected && - c.connection() - .filter(|c| c.is_connected() && c.peer_features().is_client()) - .is_some() - }) - .count() + self.count_filtered(|c| { + c.status() == ConnectionStatus::Connected && + c.connection() + .filter(|c| c.is_connected() && c.peer_features().is_client()) + .is_some() + }) } pub fn count_connected(&self) -> usize { - self.connections - .values() - .filter(|c| c.status() == ConnectionStatus::Connected || c.status() == ConnectionStatus::Connecting) - .count() + self.count_filtered(|c| c.is_connected()) } pub fn count_failed(&self) -> usize { - self.count_status(ConnectionStatus::Failed) + self.count_filtered(|c| c.status() == ConnectionStatus::Failed) } pub fn count_disconnected(&self) -> usize { - self.count_status(ConnectionStatus::Disconnected) + self.count_filtered(|c| c.status() == ConnectionStatus::Disconnected) } pub fn count_entries(&self) -> usize { self.connections.len() } - fn count_status(&self, status: ConnectionStatus) -> usize { - self.connections.values().filter(|c| c.status() == status).count() + pub(in crate::connectivity) fn count_filtered

(&self, mut predicate: P) -> usize + where P: FnMut(&PeerConnectionState) -> bool { + self.connections.values().filter(|c| (predicate)(*c)).count() } } diff --git a/comms/src/connectivity/manager.rs b/comms/src/connectivity/manager.rs index e827d8caae..d8043050e7 100644 --- a/comms/src/connectivity/manager.rs +++ b/comms/src/connectivity/manager.rs @@ -26,6 +26,7 @@ use super::{ error::ConnectivityError, requester::{ConnectivityEvent, ConnectivityRequest}, selection::ConnectivitySelection, + ConnectivityEventTx, }; use crate::{ connection_manager::{ @@ -34,7 +35,6 @@ use crate::{ ConnectionManagerEvent, ConnectionManagerRequester, }, - connectivity::ConnectivityEventTx, peer_manager::NodeId, runtime::task, utils::datetime::format_duration, @@ -91,6 +91,8 @@ impl ConnectivityManager { node_identity: self.node_identity, pool: ConnectionPool::new(), shutdown_signal: self.shutdown_signal, + #[cfg(feature = "metrics")] + uptime: Instant::now(), } .spawn() } @@ -145,6 +147,8 @@ struct ConnectivityManagerActor { connection_stats: HashMap, pool: ConnectionPool, shutdown_signal: ShutdownSignal, + #[cfg(feature = "metrics")] + uptime: Instant, } impl ConnectivityManagerActor { @@ -324,6 +328,7 @@ impl ConnectivityManagerActor { self.reap_inactive_connections().await; } self.update_connectivity_status(); + self.update_connectivity_metrics(); Ok(()) } @@ -505,6 +510,10 @@ impl ConnectivityManagerActor { _ => {}, } }, + #[cfg(feature = "metrics")] + NewInboundSubstream(node_id, protocol, _) => { + super::metrics::substream_request_count(node_id, protocol).inc(); + }, _ => {}, } @@ -577,6 +586,7 @@ impl ConnectivityManagerActor { } self.update_connectivity_status(); + self.update_connectivity_metrics(); Ok(()) } @@ -639,6 +649,25 @@ impl ConnectivityManagerActor { } } + #[cfg(not(feature = "metrics"))] + fn update_connectivity_metrics(&mut self) {} + + #[cfg(feature = "metrics")] + fn update_connectivity_metrics(&mut self) { + use super::metrics; + use std::convert::TryFrom; + + let total = self.pool.count_connected() as i64; + let num_inbound = self.pool.count_filtered(|state| match state.connection() { + Some(conn) => conn.is_connected() && conn.direction().is_inbound(), + None => false, + }) as i64; + + metrics::connections(ConnectionDirection::Inbound).set(num_inbound); + metrics::connections(ConnectionDirection::Outbound).set(total - num_inbound); + metrics::uptime().set(i64::try_from(self.uptime.elapsed().as_secs()).unwrap_or(i64::MAX)); + } + fn transition(&mut self, next_status: ConnectivityStatus, required_num_peers: usize) { use ConnectivityStatus::*; if self.status != next_status { diff --git a/comms/src/connectivity/metrics.rs b/comms/src/connectivity/metrics.rs new file mode 100644 index 0000000000..56bd8f781d --- /dev/null +++ b/comms/src/connectivity/metrics.rs @@ -0,0 +1,54 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{connection_manager::ConnectionDirection, peer_manager::NodeId, protocol::ProtocolId}; +use once_cell::sync::Lazy; +use tari_metrics::{IntGauge, IntGaugeVec}; + +pub fn connections(direction: ConnectionDirection) -> IntGauge { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_gauge_vec("comms::connections", "Number of active connections by direction", &[ + "direction", + ]) + .unwrap() + }); + + METER.with_label_values(&[direction.as_str()]) +} + +pub fn substream_request_count(peer: &NodeId, protocol: &ProtocolId) -> IntGauge { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_gauge_vec("comms::substream_request_count", "Number of substream requests", &[ + "peer", "protocol", + ]) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn uptime() -> IntGauge { + static METER: Lazy = + Lazy::new(|| tari_metrics::register_int_gauge("comms::uptime", "Comms uptime").unwrap()); + + METER.clone() +} diff --git a/comms/src/connectivity/mod.rs b/comms/src/connectivity/mod.rs index 550244b3ee..046fb3ff22 100644 --- a/comms/src/connectivity/mod.rs +++ b/comms/src/connectivity/mod.rs @@ -34,6 +34,9 @@ mod manager; pub(crate) use manager::ConnectivityManager; pub use manager::ConnectivityStatus; +#[cfg(feature = "metrics")] +mod metrics; + mod requester; pub(crate) use requester::ConnectivityRequest; pub use requester::{ConnectivityEvent, ConnectivityEventRx, ConnectivityEventTx, ConnectivityRequester}; diff --git a/comms/src/peer_manager/peer_features.rs b/comms/src/peer_manager/peer_features.rs index f69171c4fb..63bb489fea 100644 --- a/comms/src/peer_manager/peer_features.rs +++ b/comms/src/peer_manager/peer_features.rs @@ -46,6 +46,14 @@ impl PeerFeatures { pub fn is_node(self) -> bool { self == PeerFeatures::COMMUNICATION_NODE } + + pub fn as_role_str(self) -> &'static str { + match self { + PeerFeatures::COMMUNICATION_NODE => "node", + PeerFeatures::COMMUNICATION_CLIENT => "client", + _ => "unknown", + } + } } impl Default for PeerFeatures { diff --git a/comms/src/protocol/rpc/client/metrics.rs b/comms/src/protocol/rpc/client/metrics.rs new file mode 100644 index 0000000000..ee77a6b91f --- /dev/null +++ b/comms/src/protocol/rpc/client/metrics.rs @@ -0,0 +1,90 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{peer_manager::NodeId, protocol::ProtocolId}; +use once_cell::sync::Lazy; +use tari_metrics::{Histogram, HistogramVec, IntGauge, IntGaugeVec}; + +pub fn sessions_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_gauge_vec( + "comms::rpc::client::num_sessions", + "The number of active clients per node per protocol", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn handshake_errors(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_gauge_vec( + "comms::rpc::client::handshake_errors", + "The number of handshake errors per node per protocol", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn request_response_latency(node_id: &NodeId, protocol: &ProtocolId) -> Histogram { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_histogram_vec( + "comms::rpc::client::request_response_latency", + "A histogram of request to first response latency", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn outbound_request_bytes(node_id: &NodeId, protocol: &ProtocolId) -> Histogram { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_histogram_vec( + "comms::rpc::client::outbound_request_bytes", + "Avg. request bytes per node per protocol", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn inbound_response_bytes(node_id: &NodeId, protocol: &ProtocolId) -> Histogram { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_histogram_vec( + "comms::rpc::client::inbound_response_bytes", + "Avg. response bytes per peer per protocol", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} diff --git a/comms/src/protocol/rpc/client.rs b/comms/src/protocol/rpc/client/mod.rs similarity index 93% rename from comms/src/protocol/rpc/client.rs rename to comms/src/protocol/rpc/client/mod.rs index d3cf5b9df5..bdaa57c368 100644 --- a/comms/src/protocol/rpc/client.rs +++ b/comms/src/protocol/rpc/client/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2020, The Tari Project +// Copyright 2021, The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // following conditions are met: @@ -20,10 +20,18 @@ // 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 mod pool; + +#[cfg(test)] +mod tests; + +mod metrics; + use super::message::RpcMethod; use crate::{ framing::CanonicalFraming, message::MessageExt, + peer_manager::NodeId, proto, protocol::{ rpc, @@ -87,6 +95,7 @@ impl RpcClient { /// Create a new RpcClient using the given framed substream and perform the RPC handshake. pub async fn connect( config: RpcClientConfig, + node_id: NodeId, framed: CanonicalFraming, protocol_name: ProtocolId, ) -> Result @@ -103,9 +112,17 @@ impl RpcClient { let span = span!(Level::TRACE, "start_rpc_worker"); span.follows_from(tracing_id); - RpcClientWorker::new(config, request_rx, framed, ready_tx, protocol_name, shutdown_signal) - .run() - .instrument(span) + RpcClientWorker::new( + config, + node_id, + request_rx, + framed, + ready_tx, + protocol_name, + shutdown_signal, + ) + .run() + .instrument(span) }); ready_rx .await @@ -184,6 +201,7 @@ impl fmt::Debug for RpcClient { pub struct RpcClientBuilder { config: RpcClientConfig, protocol_id: Option, + node_id: Option, _client: PhantomData, } @@ -192,6 +210,7 @@ impl Default for RpcClientBuilder { Self { config: Default::default(), protocol_id: None, + node_id: None, _client: PhantomData, } } @@ -236,6 +255,12 @@ impl RpcClientBuilder { self.protocol_id = Some(protocol_id); self } + + /// Set the node_id for logging/metrics purposes + pub fn with_node_id(mut self, node_id: NodeId) -> Self { + self.node_id = Some(node_id); + self + } } impl RpcClientBuilder @@ -246,6 +271,7 @@ where TClient: From + NamedProtocolService where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId + 'static { RpcClient::connect( self.config, + self.node_id.unwrap_or_default(), framed, self.protocol_id .as_ref() @@ -363,6 +389,7 @@ impl Service> for ClientConnector { struct RpcClientWorker { config: RpcClientConfig, + node_id: NodeId, request_rx: mpsc::Receiver, framed: CanonicalFraming, // Request ids are limited to u16::MAX because varint encoding is used over the wire and the magnitude of the value @@ -379,6 +406,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId { pub(self) fn new( config: RpcClientConfig, + node_id: NodeId, request_rx: mpsc::Receiver, framed: CanonicalFraming, ready_tx: oneshot::Sender>, @@ -387,6 +415,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId ) -> Self { Self { config, + node_id, request_rx, framed, next_request_id: 0, @@ -439,6 +468,8 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId }, } + let session_counter = metrics::sessions_counter(&self.node_id, &self.protocol_id); + session_counter.inc(); loop { tokio::select! { biased; @@ -458,6 +489,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId } } } + session_counter.dec(); if let Err(err) = self.framed.close().await { debug!( @@ -556,6 +588,8 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId request: BaseRequest, reply: oneshot::Sender, RpcStatus>>>, ) -> Result<(), RpcError> { + metrics::outbound_request_bytes(&self.node_id, &self.protocol_id).observe(request.get_ref().len() as f64); + let request_id = self.next_request_id(); let method = request.method.into(); let req = proto::rpc::RpcRequest { @@ -568,7 +602,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId debug!(target: LOG_TARGET, "Sending request: {}", req); - let start = Instant::now(); + let mut timer = Some(Instant::now()); if reply.is_closed() { event!(Level::WARN, "Client request was cancelled before request was sent"); warn!( @@ -590,6 +624,8 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId return Ok(()); } + let latency = metrics::request_response_latency(&self.node_id, &self.protocol_id); + let mut metrics_timer = Some(latency.start_timer()); if let Err(err) = self.send_request(req).await { warn!(target: LOG_TARGET, "{}", err); let _ = response_tx.send(Err(err.into())); @@ -610,27 +646,28 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId let resp = match self.read_response(request_id).await { Ok(resp) => { - let latency = start.elapsed(); + if let Some(t) = timer.take() { + self.last_request_latency = Some(t.elapsed()); + } event!(Level::TRACE, "Message received"); trace!( target: LOG_TARGET, - "Received response ({} byte(s)) from request #{} (protocol = {}, method={}) in {:.0?}", + "Received response ({} byte(s)) from request #{} (protocol = {}, method={})", resp.payload.len(), request_id, self.protocol_name(), method, - latency ); - self.last_request_latency = Some(latency); + + if let Some(t) = metrics_timer.take() { + t.observe_duration(); + } resp }, Err(RpcError::ReplyTimeout) => { debug!( target: LOG_TARGET, - "Request {} (method={}) timed out after {:.0?}", - request_id, - method, - start.elapsed() + "Request {} (method={}) timed out", request_id, method, ); event!(Level::ERROR, "Response timed out"); if !response_tx.is_closed() { @@ -641,10 +678,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId Err(RpcError::ClientClosed) => { debug!( target: LOG_TARGET, - "Request {} (method={}) was closed after {:.0?} (read_reply)", - request_id, - method, - start.elapsed() + "Request {} (method={}) was closed (read_reply)", request_id, method, ); self.request_rx.close(); break; @@ -652,10 +686,9 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId Err(err) => { event!( Level::WARN, - "Request {} (method={}) returned an error after {:.0?}: {}", + "Request {} (method={}) returned an error: {}", request_id, method, - start.elapsed(), err ); return Err(err); @@ -733,7 +766,11 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId let mut num_ignored = 0; let resp = loop { match reader.read_response().await { - Ok(resp) => break resp, + Ok(resp) => { + metrics::inbound_response_bytes(&self.node_id, &self.protocol_id) + .observe(reader.bytes_read() as f64); + break resp; + }, Err(RpcError::ResponseIdDidNotMatchRequest { actual, expected }) if actual.saturating_add(1) == request_id => { @@ -792,6 +829,7 @@ struct RpcResponseReader<'a, TSubstream> { framed: &'a mut CanonicalFraming, config: RpcClientConfig, request_id: u16, + bytes_read: usize, } impl<'a, TSubstream> RpcResponseReader<'a, TSubstream> @@ -802,9 +840,14 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin framed, config, request_id, + bytes_read: 0, } } + pub fn bytes_read(&self) -> usize { + self.bytes_read + } + pub async fn read_response(&mut self) -> Result { let mut resp = self.next().await?; self.check_response(&resp)?; @@ -833,6 +876,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin let msg = self.next().await?; last_chunk_flags = RpcMessageFlags::from_bits_truncate(msg.flags as u8); last_chunk_size = msg.payload.len(); + self.bytes_read += last_chunk_size; self.check_response(&resp)?; resp.payload.extend(msg.payload); chunk_count += 1; diff --git a/comms/src/protocol/rpc/client_pool.rs b/comms/src/protocol/rpc/client/pool.rs similarity index 99% rename from comms/src/protocol/rpc/client_pool.rs rename to comms/src/protocol/rpc/client/pool.rs index 7cf99ed419..af57d3422b 100644 --- a/comms/src/protocol/rpc/client_pool.rs +++ b/comms/src/protocol/rpc/client/pool.rs @@ -69,7 +69,7 @@ where T: RpcPoolClient + From + NamedProtocolService + Clone } #[derive(Clone)] -pub(super) struct LazyPool { +pub(crate) struct LazyPool { connection: PeerConnection, clients: Vec>, client_config: RpcClientBuilder, diff --git a/comms/src/protocol/rpc/client/tests.rs b/comms/src/protocol/rpc/client/tests.rs new file mode 100644 index 0000000000..0dc4a4c595 --- /dev/null +++ b/comms/src/protocol/rpc/client/tests.rs @@ -0,0 +1,173 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + connection_manager::PeerConnection, + protocol::{ + rpc::{ + test::{ + greeting_service::{GreetingClient, GreetingServer, GreetingService}, + mock::create_mocked_rpc_context, + }, + NamedProtocolService, + RpcServer, + }, + ProtocolEvent, + ProtocolId, + ProtocolNotification, + }, + runtime, + runtime::task, + test_utils::mocks::{new_peer_connection_mock_pair, PeerConnectionMockState}, +}; +use tari_shutdown::Shutdown; +use tari_test_utils::{async_assert_eventually, unpack_enum}; +use tokio::sync::mpsc; + +async fn setup(num_concurrent_sessions: usize) -> (PeerConnection, PeerConnectionMockState, Shutdown) { + let (conn1, conn1_state, conn2, conn2_state) = new_peer_connection_mock_pair().await; + let (notif_tx, notif_rx) = mpsc::channel(1); + let shutdown = Shutdown::new(); + let (context, _) = create_mocked_rpc_context(); + + task::spawn( + RpcServer::builder() + .with_maximum_simultaneous_sessions(num_concurrent_sessions) + .finish() + .add_service(GreetingServer::new(GreetingService::default())) + .serve(notif_rx, context), + ); + + task::spawn(async move { + while let Some(stream) = conn2_state.next_incoming_substream().await { + notif_tx + .send(ProtocolNotification::new( + ProtocolId::from_static(GreetingClient::PROTOCOL_NAME), + ProtocolEvent::NewInboundSubstream(conn2.peer_node_id().clone(), stream), + )) + .await + .unwrap(); + } + }); + + (conn1, conn1_state, shutdown) +} + +mod lazy_pool { + use super::*; + use crate::protocol::rpc::client::pool::{LazyPool, RpcClientPoolError}; + + #[runtime::test] + async fn it_connects_lazily() { + let (conn, mock_state, _shutdown) = setup(2).await; + let mut pool = LazyPool::::new(conn, 2, Default::default()); + assert_eq!(mock_state.num_open_substreams(), 0); + let _conn1 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 1); + let _conn2 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 2); + } + + #[runtime::test] + async fn it_reuses_unused_connections() { + let (conn, mock_state, _shutdown) = setup(2).await; + let mut pool = LazyPool::::new(conn, 2, Default::default()); + let _ = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(pool.refresh_num_active_connections(), 1); + async_assert_eventually!(mock_state.num_open_substreams(), expect = 1); + let _ = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(pool.refresh_num_active_connections(), 1); + async_assert_eventually!(mock_state.num_open_substreams(), expect = 1); + } + + #[runtime::test] + async fn it_reuses_least_used_connections() { + let (conn, mock_state, _shutdown) = setup(2).await; + let mut pool = LazyPool::::new(conn, 2, Default::default()); + let conn1 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 1); + let conn2 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 2); + let conn3 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(conn3.lease_count(), 2); + assert!((conn1.lease_count() == 1) ^ (conn2.lease_count() == 1)); + assert_eq!(mock_state.num_open_substreams(), 2); + let conn4 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(conn4.lease_count(), 2); + assert_eq!(mock_state.num_open_substreams(), 2); + + assert_eq!(conn1.lease_count(), 2); + assert_eq!(conn2.lease_count(), 2); + assert_eq!(conn3.lease_count(), 2); + } + + #[runtime::test] + async fn it_reuses_used_connections_if_necessary() { + let (conn, mock_state, _shutdown) = setup(2).await; + let mut pool = LazyPool::::new(conn, 1, Default::default()); + let conn1 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 1); + let conn2 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 1); + drop(conn1); + drop(conn2); + } + + #[runtime::test] + async fn it_gracefully_handles_insufficient_server_sessions() { + let (conn, mock_state, _shutdown) = setup(1).await; + let mut pool = LazyPool::::new(conn, 2, Default::default()); + let conn1 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 1); + let conn2 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 1); + assert_eq!(conn1.lease_count(), 2); + assert_eq!(conn2.lease_count(), 2); + } + + #[runtime::test] + async fn it_prunes_disconnected_sessions() { + let (conn, mock_state, _shutdown) = setup(2).await; + let mut pool = LazyPool::::new(conn, 2, Default::default()); + let mut client1 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 1); + let _client2 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(mock_state.num_open_substreams(), 2); + client1.close().await; + drop(client1); + async_assert_eventually!(mock_state.num_open_substreams(), expect = 1); + assert_eq!(pool.refresh_num_active_connections(), 1); + let _client3 = pool.get_least_used_or_connect().await.unwrap(); + assert_eq!(pool.refresh_num_active_connections(), 2); + assert_eq!(mock_state.num_open_substreams(), 2); + } + + #[runtime::test] + async fn it_fails_when_peer_connected_disconnects() { + let (mut peer_conn, _, _shutdown) = setup(2).await; + let mut pool = LazyPool::::new(peer_conn.clone(), 2, Default::default()); + let mut _conn1 = pool.get_least_used_or_connect().await.unwrap(); + peer_conn.disconnect().await.unwrap(); + let err = pool.get_least_used_or_connect().await.unwrap_err(); + unpack_enum!(RpcClientPoolError::PeerConnectionDropped { .. } = err); + } +} diff --git a/comms/src/protocol/rpc/message.rs b/comms/src/protocol/rpc/message.rs index 099a70331b..9bc7154fbd 100644 --- a/comms/src/protocol/rpc/message.rs +++ b/comms/src/protocol/rpc/message.rs @@ -132,6 +132,10 @@ impl BaseRequest { message: f(self.message), } } + + pub fn get_ref(&self) -> &T { + &self.message + } } #[derive(Debug, Clone)] diff --git a/comms/src/protocol/rpc/mod.rs b/comms/src/protocol/rpc/mod.rs index fb3fee42f6..5ab25dda01 100644 --- a/comms/src/protocol/rpc/mod.rs +++ b/comms/src/protocol/rpc/mod.rs @@ -64,7 +64,13 @@ mod server; pub use server::{mock, NamedProtocolService, RpcServer, RpcServerError, RpcServerHandle}; mod client; -pub use client::{RpcClient, RpcClientBuilder, RpcClientConfig}; +pub use client::{ + pool, + pool::{RpcClientLease, RpcClientPool, RpcClientPoolError, RpcPoolClient}, + RpcClient, + RpcClientBuilder, + RpcClientConfig, +}; mod either; @@ -77,9 +83,6 @@ pub use error::RpcError; mod handshake; pub use handshake::{Handshake, RpcHandshakeError}; -mod client_pool; -pub use client_pool::{RpcClientLease, RpcClientPool, RpcClientPoolError, RpcPoolClient}; - mod status; pub use status::{RpcStatus, RpcStatusCode}; @@ -91,8 +94,8 @@ pub mod __macro_reexports { framing::CanonicalFraming, protocol::{ rpc::{ - client_pool::RpcPoolClient, message::{Request, Response}, + pool::RpcPoolClient, server::{NamedProtocolService, RpcServerError}, Body, ClientStreaming, diff --git a/comms/src/protocol/rpc/server/metrics.rs b/comms/src/protocol/rpc/server/metrics.rs new file mode 100644 index 0000000000..cb0c1d7d2a --- /dev/null +++ b/comms/src/protocol/rpc/server/metrics.rs @@ -0,0 +1,90 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{peer_manager::NodeId, protocol::ProtocolId}; +use once_cell::sync::Lazy; +use tari_metrics::{Histogram, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec}; + +pub fn sessions_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_gauge_vec( + "comms::rpc::server::num_sessions", + "The number of active server sessions per node per protocol", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn handshake_error_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_gauge_vec( + "comms::rpc::server::handshake_errors", + "The number of handshake errors per node per protocol", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn error_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::rpc::server::error_count", + "The number of RPC errors per node per protocol", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn inbound_requests_bytes(node_id: &NodeId, protocol: &ProtocolId) -> Histogram { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_histogram_vec( + "comms::rpc::server::inbound_request_bytes", + "Avg. request bytes per node per protocol", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn outbound_response_bytes(node_id: &NodeId, protocol: &ProtocolId) -> Histogram { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_histogram_vec( + "comms::rpc::server::outbound_response_bytes", + "Avg. response bytes per peer per protocol", + &["peer", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} diff --git a/comms/src/protocol/rpc/server/mod.rs b/comms/src/protocol/rpc/server/mod.rs index 974db6136d..31fa62ee5a 100644 --- a/comms/src/protocol/rpc/server/mod.rs +++ b/comms/src/protocol/rpc/server/mod.rs @@ -30,6 +30,8 @@ mod handle; pub use handle::RpcServerHandle; use handle::RpcServerRequest; +mod metrics; + pub mod mock; mod router; @@ -73,6 +75,7 @@ use std::{ task::Poll, time::{Duration, Instant}, }; +use tari_metrics::IntCounter; use tokio::{sync::mpsc, time}; use tokio_stream::Stream; use tower::Service; @@ -308,8 +311,15 @@ where ); let framed = framing::canonical(substream, RPC_MAX_FRAME_SIZE); - match self.try_initiate_service(notification.protocol, node_id, framed).await { + match self + .try_initiate_service(notification.protocol.clone(), &node_id, framed) + .await + { Ok(_) => {}, + Err(err @ RpcServerError::HandshakeError(_)) => { + debug!(target: LOG_TARGET, "{}", err); + metrics::handshake_error_counter(&node_id, ¬ification.protocol).inc(); + }, Err(err) => { debug!(target: LOG_TARGET, "Unable to spawn RPC service: {}", err); }, @@ -324,7 +334,7 @@ where async fn try_initiate_service( &mut self, protocol: ProtocolId, - node_id: NodeId, + node_id: &NodeId, mut framed: CanonicalFraming, ) -> Result<(), RpcServerError> { let mut handshake = Handshake::new(&mut framed).with_timeout(self.config.handshake_timeout); @@ -373,8 +383,14 @@ where self.comms_provider.clone(), ); + let node_id = node_id.clone(); self.executor - .try_spawn(service.start()) + .try_spawn(async move { + let sessions_counter = metrics::sessions_counter(&node_id, &service.protocol); + sessions_counter.inc(); + service.start().await; + sessions_counter.dec(); + }) .map_err(|_| RpcServerError::MaximumSessionsReached)?; Ok(()) @@ -389,6 +405,7 @@ struct ActivePeerRpcService { framed: CanonicalFraming, comms_provider: TCommsProvider, logging_context_string: Arc, + error_counter: IntCounter, } impl ActivePeerRpcService @@ -404,6 +421,7 @@ where framed: CanonicalFraming, comms_provider: TCommsProvider, ) -> Self { + let error_counter = metrics::error_counter(&node_id, &protocol); Self { logging_context_string: Arc::new(format!( "stream_id: {}, peer: {}, protocol: {}", @@ -418,6 +436,7 @@ where service, framed, comms_provider, + error_counter, } } @@ -427,6 +446,7 @@ where "({}) Rpc server started.", self.logging_context_string, ); if let Err(err) = self.run().await { + self.error_counter.inc(); error!( target: LOG_TARGET, "({}) Rpc server exited with an error: {}", self.logging_context_string, err @@ -435,10 +455,12 @@ where } async fn run(&mut self) -> Result<(), RpcServerError> { + let request_bytes = metrics::inbound_requests_bytes(&self.node_id, &self.protocol); while let Some(result) = self.framed.next().await { match result { Ok(frame) => { let start = Instant::now(); + request_bytes.observe(frame.len() as f64); if let Err(err) = self.handle_request(frame.freeze()).await { if let Err(err) = self.framed.close().await { error!( @@ -448,6 +470,7 @@ where err ); } + self.error_counter.inc(); return Err(err); } let elapsed = start.elapsed(); @@ -466,6 +489,7 @@ where "({}) Failed to close substream after socket error: {}", self.logging_context_string, err ); } + self.error_counter.inc(); return Err(err.into()); }, } @@ -501,6 +525,7 @@ where payload: status.to_details_bytes(), }; self.framed.send(bad_request.to_encoded_bytes().into()).await?; + self.error_counter.inc(); return Ok(()); } @@ -554,6 +579,7 @@ where self.node_id, self.protocol_name() ); + self.error_counter.inc(); return Ok(()); }, }; @@ -577,6 +603,7 @@ where payload: err.to_details_bytes(), }; + self.error_counter.inc(); self.framed.send(resp.to_encoded_bytes().into()).await?; }, } @@ -594,6 +621,7 @@ where deadline: Duration, body: Response, ) -> Result<(), RpcServerError> { + let response_bytes = metrics::outbound_response_bytes(&self.node_id, &self.protocol); trace!(target: LOG_TARGET, "Service call succeeded"); let mut stream = body .into_message() @@ -604,7 +632,8 @@ where loop { // Check if the client interrupted the outgoing stream if let Err(err) = self.check_interruptions().await { - warn!(target: LOG_TARGET, "{}", err); + // Debug level because there are many valid reasons to interrupt a stream + debug!(target: LOG_TARGET, "Stream interrupted: {}", err); break; } @@ -616,6 +645,7 @@ where ); match time::timeout(deadline, next_item).await { Ok(Some(msg)) => { + response_bytes.observe(msg.len() as f64); debug!( target: LOG_TARGET, "({}) Sending body len = {}", @@ -637,6 +667,7 @@ where deadline ); + self.error_counter.inc(); break; }, } diff --git a/comms/src/protocol/rpc/test/client_pool.rs b/comms/src/protocol/rpc/test/client_pool.rs index 7263b32d49..0dc4a4c595 100644 --- a/comms/src/protocol/rpc/test/client_pool.rs +++ b/comms/src/protocol/rpc/test/client_pool.rs @@ -74,7 +74,7 @@ async fn setup(num_concurrent_sessions: usize) -> (PeerConnection, PeerConnectio mod lazy_pool { use super::*; - use crate::protocol::rpc::client_pool::{LazyPool, RpcClientPoolError}; + use crate::protocol::rpc::client::pool::{LazyPool, RpcClientPoolError}; #[runtime::test] async fn it_connects_lazily() { diff --git a/comms/src/protocol/rpc/test/greeting_service.rs b/comms/src/protocol/rpc/test/greeting_service.rs index 9153db59bf..d974f433cc 100644 --- a/comms/src/protocol/rpc/test/greeting_service.rs +++ b/comms/src/protocol/rpc/test/greeting_service.rs @@ -398,7 +398,13 @@ impl __rpc_deps::NamedProtocolService for GreetingClient { impl GreetingClient { pub async fn connect(framed: __rpc_deps::CanonicalFraming) -> Result { - let inner = __rpc_deps::RpcClient::connect(Default::default(), framed, Self::PROTOCOL_NAME.into()).await?; + let inner = __rpc_deps::RpcClient::connect( + Default::default(), + Default::default(), + framed, + Self::PROTOCOL_NAME.into(), + ) + .await?; Ok(Self { inner }) } diff --git a/comms/src/protocol/rpc/test/mock.rs b/comms/src/protocol/rpc/test/mock.rs index fb615c3d60..fb229579d3 100644 --- a/comms/src/protocol/rpc/test/mock.rs +++ b/comms/src/protocol/rpc/test/mock.rs @@ -170,7 +170,7 @@ impl From for MockRpcClient { } } -pub(super) fn create_mocked_rpc_context() -> (RpcCommsBackend, ConnectivityManagerMockState) { +pub(crate) fn create_mocked_rpc_context() -> (RpcCommsBackend, ConnectivityManagerMockState) { let (connectivity, mock) = create_connectivity_mock(); let mock_state = mock.get_shared_state(); mock.spawn(); diff --git a/comms/src/protocol/rpc/test/mod.rs b/comms/src/protocol/rpc/test/mod.rs index 873db4c044..206e84c7cb 100644 --- a/comms/src/protocol/rpc/test/mod.rs +++ b/comms/src/protocol/rpc/test/mod.rs @@ -20,9 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -mod client_pool; mod comms_integration; -mod greeting_service; +pub(super) mod greeting_service; mod handshake; -mod mock; +pub(super) mod mock; mod smoke; diff --git a/infrastructure/metrics/Cargo.toml b/infrastructure/metrics/Cargo.toml new file mode 100644 index 0000000000..c4111b1ef6 --- /dev/null +++ b/infrastructure/metrics/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tari_metrics" +description = "Tari metrics" +version = "0.1.0" +edition = "2021" +authors = ["The Tari Development Community"] +repository = "https://github.com/tari-project/tari" +homepage = "https://tari.com" +readme = "README.md" +license = "BSD-3-Clause" + +[dependencies] +once_cell = "1.8.0" +prometheus = "0.13.0" diff --git a/infrastructure/metrics/src/lib.rs b/infrastructure/metrics/src/lib.rs new file mode 100644 index 0000000000..c9d82abff3 --- /dev/null +++ b/infrastructure/metrics/src/lib.rs @@ -0,0 +1,89 @@ +use once_cell::sync::Lazy; +use prometheus::opts; +use std::sync::{Arc, RwLock}; + +pub use prometheus::{ + core::Collector, + proto, + Counter, + CounterVec, + Encoder, + Error, + Gauge, + GaugeVec, + Histogram, + HistogramOpts, + HistogramTimer, + HistogramVec, + IntCounter, + IntCounterVec, + IntGauge, + IntGaugeVec, + Registry, + TextEncoder, +}; + +static DEFAULT_REGISTRY: Lazy>> = Lazy::new(|| Arc::new(RwLock::new(Registry::default()))); + +/// Sets the global default registry. +/// +/// This should be set once by the application. Libraries should never set this. +pub fn set_default_registry(registry: Registry) { + *DEFAULT_REGISTRY.write().unwrap() = registry; +} + +pub fn get_default_registry() -> Registry { + DEFAULT_REGISTRY.read().unwrap().clone() +} + +pub fn register(c: C) -> prometheus::Result<()> { + get_default_registry().register(Box::new(c)) +} + +pub fn register_gauge(name: &str, help: &str) -> prometheus::Result { + let gauge = prometheus::Gauge::new(name, help)?; + register(gauge.clone())?; + Ok(gauge) +} + +pub fn register_gauge_vec(name: &str, help: &str, label_names: &[&str]) -> prometheus::Result { + let gauge = prometheus::GaugeVec::new(opts!(name, help), label_names)?; + register(gauge.clone())?; + Ok(gauge) +} + +pub fn register_int_gauge_vec(name: &str, help: &str, label_names: &[&str]) -> prometheus::Result { + let gauge = prometheus::IntGaugeVec::new(opts!(name, help), label_names)?; + register(gauge.clone())?; + Ok(gauge) +} + +pub fn register_int_counter(name: &str, help: &str) -> prometheus::Result { + let gauge = prometheus::IntCounter::new(name, help)?; + register(gauge.clone())?; + Ok(gauge) +} + +pub fn register_int_counter_vec(name: &str, help: &str, label_names: &[&str]) -> prometheus::Result { + let gauge = prometheus::IntCounterVec::new(opts!(name, help), label_names)?; + register(gauge.clone())?; + Ok(gauge) +} + +pub fn register_int_gauge(name: &str, help: &str) -> prometheus::Result { + let gauge = prometheus::IntGauge::new(name, help)?; + register(gauge.clone())?; + Ok(gauge) +} + +pub fn register_histogram(name: &str, help: &str) -> prometheus::Result { + let gauge = prometheus::Histogram::with_opts(HistogramOpts::new(name, help))?; + register(gauge.clone())?; + Ok(gauge) +} + +pub fn register_histogram_vec(name: &str, help: &str, label_names: &[&str]) -> prometheus::Result { + let gauge = prometheus::HistogramVec::new(HistogramOpts::new(name, help), label_names)?; + register(gauge.clone())?; + Ok(gauge) +} diff --git a/infrastructure/shutdown/src/lib.rs b/infrastructure/shutdown/src/lib.rs index 5afd56a239..7c2afc6989 100644 --- a/infrastructure/shutdown/src/lib.rs +++ b/infrastructure/shutdown/src/lib.rs @@ -30,7 +30,7 @@ pub mod oneshot_trigger; use crate::oneshot_trigger::OneshotSignal; -use futures::future::FusedFuture; +use futures::{future, future::FusedFuture}; use std::{ future::Future, pin::Pin, @@ -82,6 +82,10 @@ impl ShutdownSignal { pub fn wait(&mut self) -> &mut Self { self } + + pub fn select(self, other: T) -> future::Select { + future::select(self, other) + } } impl Future for ShutdownSignal { From a1855065e0f2aaabbb6d7e508f71d6d0eaf6acd5 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Tue, 16 Nov 2021 08:33:42 +0200 Subject: [PATCH 23/46] feat: add atomic swap htlc sending and claiming (#3552) Description --- This Pr adds in the ability to send and claim HTLC transactions. This is added to the console wallet UI and GRPC commands. UI: `tari_console_wallet --command "init-sha-atomic-swap "` This is used to initialize an atomic swap for the XTR side `tari_console_wallet --command "finalise-sha-atomic-swap "` This is used to finalize and claim an HTLC transaction for the XTR side. GRPC: `rpc SendShaAtomicSwapTransaction(SendShaAtomicSwapRequest) returns (SendShaAtomicSwapResponse);` This is used to initialize an atomic swap for the XTR side `rpc ClaimShaAtomicSwapTransaction(ClaimShaAtomicSwapRequest) returns (ClaimShaAtomicSwapResponse);` This is used to finalize and claim an HTLC transaction for the XTR side. Motivation and Context --- This is the first step in allowing Atomic swaps for Tari and coins that have SHA256 as a pre_image. Currently, the available commands in this PR and tools available we can do the happy path of an atomic swap for: Bitcoin Bitcoin Cash (Bitcoin ABC, Bitcoin Unlimited, Bitcoin XT) Decred Litecoin Monacoin Particl Qtum Vertcoin Viacoin Zcoin How Has This Been Tested? --- Added unit and cucumber tests for the Tari side. Did a manual XTR BTC atomic swap XTR UTXO hash: `186a95239d16ebf800cb3b7991521585b569253255002b009b16f11faab8d115` BTC tx_ids: `1f770ce82aeb1f6c0d0345a6a88f1b3eb2e226595063491c93ad442625f678ae` `2b6e2e2566577e8e52da685e2abb4252b98fc4a60dc9a1b09a56e6b003900bc9` --- Cargo.lock | 3 + applications/tari_app_grpc/proto/wallet.proto | 28 +- .../tari_app_utilities/src/utilities.rs | 9 +- applications/tari_console_wallet/Cargo.toml | 2 + .../src/automation/command_parser.rs | 52 +- .../src/automation/commands.rs | 87 +- .../src/automation/error.rs | 15 +- .../src/grpc/wallet_grpc_server.rs | 120 +- base_layer/wallet/Cargo.toml | 1 + .../src/output_manager_service/handle.rs | 34 +- .../wallet/src/output_manager_service/mod.rs | 9 + .../src/output_manager_service/service.rs | 144 +- .../wallet/src/transaction_service/handle.rs | 41 +- .../wallet/src/transaction_service/service.rs | 161 +- base_layer/wallet/src/wallet.rs | 1 + .../tests/output_manager_service/service.rs | 29 +- .../tests/transaction_service/service.rs | 152 +- clients/wallet_grpc_client/index.js | 2 + docs/book.toml | 4 +- docs/src/btc_atomic_swap.md | 192 + .../features/WalletTransfer.feature | 15 + integration_tests/features/support/steps.js | 159 + integration_tests/helpers/walletClient.js | 8 + integration_tests/package-lock.json | 4931 +++++++++++++++-- 24 files changed, 5740 insertions(+), 459 deletions(-) create mode 100644 docs/src/btc_atomic_swap.md diff --git a/Cargo.lock b/Cargo.lock index bd118b3ed5..d674d81979 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4674,6 +4674,7 @@ dependencies = [ "bitflags 1.3.2", "chrono", "crossterm 0.17.7", + "digest", "futures 0.3.17", "log", "opentelemetry", @@ -4683,6 +4684,7 @@ dependencies = [ "regex", "rpassword", "rustyline", + "sha2", "strum 0.19.5", "strum_macros 0.19.4", "tari_app_grpc", @@ -5113,6 +5115,7 @@ dependencies = [ "rand 0.8.4", "serde 1.0.130", "serde_json", + "sha2", "tari_common", "tari_common_sqlite", "tari_common_types", diff --git a/applications/tari_app_grpc/proto/wallet.proto b/applications/tari_app_grpc/proto/wallet.proto index f00fe34239..4aee184a72 100644 --- a/applications/tari_app_grpc/proto/wallet.proto +++ b/applications/tari_app_grpc/proto/wallet.proto @@ -56,6 +56,10 @@ service Wallet { rpc CancelTransaction (CancelTransactionRequest) returns (CancelTransactionResponse); // Will triggger a complete revalidation of all wallet outputs. rpc RevalidateAllTransactions (RevalidateRequest) returns (RevalidateResponse); + // This will send a XTR SHA Atomic swap transaction + rpc SendShaAtomicSwapTransaction(SendShaAtomicSwapRequest) returns (SendShaAtomicSwapResponse); + // This will claim a XTR SHA Atomic swap transaction + rpc ClaimShaAtomicSwapTransaction(ClaimShaAtomicSwapRequest) returns (ClaimShaAtomicSwapResponse); } message GetVersionRequest { } @@ -76,6 +80,10 @@ message TransferRequest { repeated PaymentRecipient recipients = 1; } +message SendShaAtomicSwapRequest { + PaymentRecipient recipient = 1; +} + message PaymentRecipient { string address = 1; uint64 amount = 2; @@ -92,6 +100,14 @@ message TransferResponse { repeated TransferResult results = 1; } +message SendShaAtomicSwapResponse { + uint64 transaction_id = 1; + string pre_image = 2; + string output_hash = 3; + bool is_success = 4; + string failure_message = 5; +} + message TransferResult { string address = 1; uint64 transaction_id = 2; @@ -99,6 +115,16 @@ message TransferResult { string failure_message = 4; } +message ClaimShaAtomicSwapRequest{ + string output = 1; + string pre_image = 2; + uint64 fee_per_gram = 3; +} + +message ClaimShaAtomicSwapResponse { + TransferResult results = 1; +} + message GetTransactionInfoRequest { repeated uint64 transaction_ids = 1; } @@ -204,4 +230,4 @@ message CancelTransactionResponse { message RevalidateRequest{} -message RevalidateResponse{} \ No newline at end of file +message RevalidateResponse{} diff --git a/applications/tari_app_utilities/src/utilities.rs b/applications/tari_app_utilities/src/utilities.rs index 8392e20e45..899b68172d 100644 --- a/applications/tari_app_utilities/src/utilities.rs +++ b/applications/tari_app_utilities/src/utilities.rs @@ -23,9 +23,8 @@ use futures::future::Either; use log::*; use std::sync::Arc; -use tokio::{runtime, runtime::Runtime}; - use tari_common::{CommsTransport, GlobalConfig, SocksAuthentication, TorControlAuthentication}; +use tari_common_types::types::BlockHash; use tari_comms::{ peer_manager::NodeId, socks, @@ -37,6 +36,7 @@ use tari_comms::{ }; use tari_core::tari_utilities::hex::Hex; use tari_p2p::transport::{TorConfig, TransportType}; +use tokio::{runtime, runtime::Runtime}; use crate::identity_management::load_from_json; use tari_common_types::emoji::EmojiId; @@ -175,6 +175,11 @@ pub fn parse_emoji_id_or_public_key(key: &str) -> Option { .ok() } +/// Returns a hash from a hex string +pub fn parse_hash(hash_string: &str) -> Option { + BlockHash::from_hex(hash_string).ok() +} + /// Returns a CommsPublicKey from either a emoji id, a public key or node id pub fn parse_emoji_id_or_public_key_or_node_id(key: &str) -> Option> { parse_emoji_id_or_public_key(key) diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index 263c4588d7..7378ae8ed2 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -17,6 +17,8 @@ tari_app_grpc = { path = "../tari_app_grpc" } tari_shutdown = { path = "../../infrastructure/shutdown" } tari_key_manager = { path = "../../base_layer/key_manager" } +sha2 = "0.9.5" +digest = "0.9.0" chrono = { version = "0.4.19", default-features = false } bitflags = "1.2.1" futures = { version = "^0.3.16", default-features = false, features = ["alloc"] } diff --git a/applications/tari_console_wallet/src/automation/command_parser.rs b/applications/tari_console_wallet/src/automation/command_parser.rs index 942c3ccb4a..89c90c6aac 100644 --- a/applications/tari_console_wallet/src/automation/command_parser.rs +++ b/applications/tari_console_wallet/src/automation/command_parser.rs @@ -21,20 +21,20 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::automation::{commands::WalletCommand, error::ParseError}; - use chrono::{DateTime, Utc}; use core::str::SplitWhitespace; use std::{ fmt::{Display, Formatter}, str::FromStr, }; -use tari_app_utilities::utilities::parse_emoji_id_or_public_key; -use tari_comms::multiaddr::Multiaddr; +use tari_app_utilities::utilities::{parse_emoji_id_or_public_key, parse_hash}; use tari_common_types::types::PublicKey; +use tari_comms::multiaddr::Multiaddr; use tari_core::transactions::tari_amount::MicroTari; +use tari_crypto::tari_utilities::hex::Hex; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ParsedCommand { pub command: WalletCommand, pub args: Vec, @@ -57,6 +57,8 @@ impl Display for ParsedCommand { SetBaseNode => "set-base-node", SetCustomBaseNode => "set-custom-base-node", ClearCustomBaseNode => "clear-custom-base-node", + InitShaAtomicSwap => "init-sha-atomic-swap", + FinaliseShaAtomicSwap => "finalise-sha-atomic-swap", }; let args = self @@ -82,6 +84,7 @@ pub enum ParsedArgument { CSVFileName(String), Address(Multiaddr), Negotiated(bool), + Hash(Vec), } impl Display for ParsedArgument { @@ -98,6 +101,7 @@ impl Display for ParsedArgument { CSVFileName(v) => write!(f, "{}", v.to_string()), Address(v) => write!(f, "{}", v.to_string()), Negotiated(v) => write!(f, "{}", v.to_string()), + Hash(v) => write!(f, "{}", v.to_hex()), } } } @@ -124,6 +128,8 @@ pub fn parse_command(command: &str) -> Result { SetBaseNode => parse_public_key_and_address(args)?, SetCustomBaseNode => parse_public_key_and_address(args)?, ClearCustomBaseNode => Vec::new(), + InitShaAtomicSwap => parse_init_sha_atomic_swap(args)?, + FinaliseShaAtomicSwap => parse_finalise_sha_atomic_swap(args)?, }; Ok(ParsedCommand { command, args }) @@ -175,6 +181,44 @@ fn parse_public_key_and_address(mut args: SplitWhitespace) -> Result Result, ParseError> { + let mut parsed_args = Vec::new(); + + // amount + let amount = args.next().ok_or_else(|| ParseError::Empty("amount".to_string()))?; + let amount = MicroTari::from_str(amount)?; + parsed_args.push(ParsedArgument::Amount(amount)); + + // public key/emoji id + let pubkey = args + .next() + .ok_or_else(|| ParseError::Empty("public key or emoji id".to_string()))?; + let pubkey = parse_emoji_id_or_public_key(pubkey).ok_or(ParseError::PublicKey)?; + parsed_args.push(ParsedArgument::PublicKey(pubkey)); + // message + let message = args.collect::>().join(" "); + parsed_args.push(ParsedArgument::Text(message)); + + Ok(parsed_args) +} + +fn parse_finalise_sha_atomic_swap(mut args: SplitWhitespace) -> Result, ParseError> { + let mut parsed_args = Vec::new(); + // hash + let hash = args + .next() + .ok_or_else(|| ParseError::Empty("Output hash".to_string()))?; + let hash = parse_hash(hash).ok_or(ParseError::Hash)?; + parsed_args.push(ParsedArgument::Hash(hash)); + + // public key + let pre_image = args.next().ok_or_else(|| ParseError::Empty("public key".to_string()))?; + let pre_image = parse_emoji_id_or_public_key(pre_image).ok_or(ParseError::PublicKey)?; + parsed_args.push(ParsedArgument::PublicKey(pre_image)); + + Ok(parsed_args) +} + fn parse_make_it_rain(mut args: SplitWhitespace) -> Result, ParseError> { let mut parsed_args = Vec::new(); diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 40090d6d51..58a7d80a31 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -22,7 +22,9 @@ use super::error::CommandError; use chrono::Utc; +use digest::Digest; use log::*; +use sha2::Sha256; use std::{ convert::TryFrom, fs::File, @@ -30,6 +32,7 @@ use std::{ str::FromStr, time::{Duration, Instant}, }; +use tari_crypto::tari_utilities::{ByteArray, Hashable}; use futures::FutureExt; use strum_macros::{Display, EnumIter, EnumString}; @@ -51,7 +54,7 @@ use tari_core::{ tari_utilities::hex::Hex, transactions::{ tari_amount::{uT, MicroTari, Tari}, - transaction::UnblindedOutput, + transaction::{TransactionOutput, UnblindedOutput}, }, }; use tari_wallet::{ @@ -83,6 +86,8 @@ pub enum WalletCommand { SetBaseNode, SetCustomBaseNode, ClearCustomBaseNode, + InitShaAtomicSwap, + FinaliseShaAtomicSwap, } #[derive(Debug, EnumString, PartialEq, Clone)] @@ -124,6 +129,31 @@ fn get_transaction_parameters( Ok((fee_per_gram, amount, dest_pubkey, message)) } +fn get_init_sha_atomic_swap_parameters( + args: Vec, +) -> Result<(MicroTari, MicroTari, PublicKey, String), CommandError> { + // TODO: Consolidate "fee per gram" in codebase + let fee_per_gram = 25 * uT; + + use ParsedArgument::*; + let amount = match args[0].clone() { + Amount(mtari) => Ok(mtari), + _ => Err(CommandError::Argument), + }?; + + let dest_pubkey = match args[1].clone() { + PublicKey(key) => Ok(key), + _ => Err(CommandError::Argument), + }?; + + let message = match args[2].clone() { + Text(msg) => Ok(msg), + _ => Err(CommandError::Argument), + }?; + + Ok((fee_per_gram, amount, dest_pubkey, message)) +} + /// Send a normal negotiated transaction to a recipient pub async fn send_tari( mut wallet_transaction_service: TransactionServiceHandle, @@ -136,6 +166,45 @@ pub async fn send_tari( .map_err(CommandError::TransactionServiceError) } +/// publishes a tari-SHA atomic swap HTLC transaction +pub async fn init_sha_atomic_swap( + mut wallet_transaction_service: TransactionServiceHandle, + args: Vec, +) -> Result<(TxId, PublicKey, TransactionOutput), CommandError> { + let (fee_per_gram, amount, dest_pubkey, message) = get_init_sha_atomic_swap_parameters(args)?; + + let (tx_id, pre_image, output) = wallet_transaction_service + .send_sha_atomic_swap_transaction(dest_pubkey, amount, fee_per_gram, message) + .await + .map_err(CommandError::TransactionServiceError)?; + Ok((tx_id, pre_image, output)) +} + +/// claims a tari-SHA atomic swap HTLC transaction +pub async fn finalise_sha_atomic_swap( + mut output_service: OutputManagerHandle, + mut transaction_service: TransactionServiceHandle, + args: Vec, +) -> Result { + use ParsedArgument::*; + let output = match args[0].clone() { + Hash(output) => Ok(output), + _ => Err(CommandError::Argument), + }?; + + let pre_image = match args[1].clone() { + PublicKey(key) => Ok(key), + _ => Err(CommandError::Argument), + }?; + let (tx_id, fee, amount, tx) = output_service + .create_claim_sha_atomic_swap_transaction(output, pre_image, MicroTari(25)) + .await?; + transaction_service + .submit_transaction(tx_id, tx, fee, amount, "Claimed HTLC atomic swap".into()) + .await?; + Ok(tx_id) +} + /// Send a one-sided transaction to a recipient pub async fn send_one_sided( mut wallet_transaction_service: TransactionServiceHandle, @@ -674,6 +743,22 @@ pub async fn command_runner( .await?; println!("Custom base node peer cleared from wallet database."); }, + InitShaAtomicSwap => { + let (tx_id, pre_image, output) = + init_sha_atomic_swap(transaction_service.clone(), parsed.clone().args).await?; + debug!(target: LOG_TARGET, "tari HTLC tx_id {}", tx_id); + let hash: [u8; 32] = Sha256::digest(pre_image.as_bytes()).into(); + println!("pre_image hex: {}", pre_image.to_hex()); + println!("pre_image hash: {}", hash.to_hex()); + println!("Output hash: {}", output.hash().to_hex()); + tx_ids.push(tx_id); + }, + FinaliseShaAtomicSwap => { + let tx_id = + finalise_sha_atomic_swap(output_service.clone(), transaction_service.clone(), parsed.args).await?; + debug!(target: LOG_TARGET, "claiming tari HTLC tx_id {}", tx_id); + tx_ids.push(tx_id); + }, } } diff --git a/applications/tari_console_wallet/src/automation/error.rs b/applications/tari_console_wallet/src/automation/error.rs index f174e1cd59..e3600782fc 100644 --- a/applications/tari_console_wallet/src/automation/error.rs +++ b/applications/tari_console_wallet/src/automation/error.rs @@ -24,9 +24,12 @@ use std::num::{ParseFloatError, ParseIntError}; use log::*; use tari_common::exit_codes::ExitCodes; -use tari_core::transactions::{ - tari_amount::{MicroTariError, TariConversionError}, - transaction::TransactionError, +use tari_core::{ + tari_utilities::hex::HexError, + transactions::{ + tari_amount::{MicroTariError, TariConversionError}, + transaction::TransactionError, + }, }; use tari_wallet::{ error::{WalletError, WalletStorageError}, @@ -63,6 +66,10 @@ pub enum CommandError { WalletError(#[from] WalletError), #[error("Wallet storage error `{0}`")] WalletStorageError(#[from] WalletStorageError), + #[error("Hex error `{0}`")] + HexError(#[from] HexError), + #[error("Error `{0}`")] + ShaError(String), } impl From for ExitCodes { @@ -80,6 +87,8 @@ pub enum ParseError { MicroTariAmount(#[from] MicroTariError), #[error("Failed to parse public key or emoji id.")] PublicKey, + #[error("Failed to parse hash")] + Hash, #[error("Failed to parse a missing {0}")] Empty(String), #[error("Failed to parse float.")] diff --git a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs index 4ac00be1e1..129cf06da8 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -7,6 +7,8 @@ use tari_app_grpc::{ tari_rpc::{ payment_recipient::PaymentType, wallet_server, + ClaimShaAtomicSwapRequest, + ClaimShaAtomicSwapResponse, CoinSplitRequest, CoinSplitResponse, GetBalanceRequest, @@ -25,6 +27,8 @@ use tari_app_grpc::{ ImportUtxosResponse, RevalidateRequest, RevalidateResponse, + SendShaAtomicSwapRequest, + SendShaAtomicSwapResponse, TransactionDirection, TransactionInfo, TransactionStatus, @@ -33,17 +37,19 @@ use tari_app_grpc::{ TransferResult, }, }; -use tari_common_types::types::Signature; +use tari_common_types::types::{BlockHash, Signature}; use tari_comms::{types::CommsPublicKey, CommsNode}; use tari_core::{ tari_utilities::{hex::Hex, ByteArray}, transactions::{tari_amount::MicroTari, transaction::UnblindedOutput}, }; +use tari_crypto::tari_utilities::Hashable; use tari_wallet::{ output_manager_service::handle::OutputManagerHandle, transaction_service::{handle::TransactionServiceHandle, storage::models}, WalletSqlite, }; + use tokio::task; use tonic::{Request, Response, Status}; @@ -156,6 +162,118 @@ impl wallet_server::Wallet for WalletGrpcServer { } } + async fn send_sha_atomic_swap_transaction( + &self, + request: Request, + ) -> Result, Status> { + let message = request + .into_inner() + .recipient + .ok_or_else(|| Status::internal("Request is malformed".to_string()))?; + let address = CommsPublicKey::from_hex(&message.address) + .map_err(|_| Status::internal("Destination address is malformed".to_string()))?; + + let mut transaction_service = self.get_transaction_service(); + let response = match transaction_service + .send_sha_atomic_swap_transaction( + address.clone(), + message.amount.into(), + message.fee_per_gram.into(), + message.message, + ) + .await + { + Ok((tx_id, pre_image, output)) => { + debug!( + target: LOG_TARGET, + "Transaction broadcast: {}, preimage_hex: {}, hash {}", + tx_id, + pre_image.to_hex(), + output.to_string() + ); + SendShaAtomicSwapResponse { + transaction_id: tx_id, + pre_image: pre_image.to_hex(), + output_hash: output.hash().to_hex(), + is_success: true, + failure_message: Default::default(), + } + }, + Err(e) => { + warn!( + target: LOG_TARGET, + "Failed to send Sha - XTR atomic swap for address `{}`: {}", address, e + ); + SendShaAtomicSwapResponse { + transaction_id: Default::default(), + pre_image: "".to_string(), + output_hash: "".to_string(), + is_success: false, + failure_message: e.to_string(), + } + }, + }; + + Ok(Response::new(response)) + } + + async fn claim_sha_atomic_swap_transaction( + &self, + request: Request, + ) -> Result, Status> { + let message = request.into_inner(); + let pre_image = CommsPublicKey::from_hex(&message.pre_image) + .map_err(|_| Status::internal("pre_image is malformed".to_string()))?; + let output = BlockHash::from_hex(&message.output) + .map_err(|_| Status::internal("Output hash is malformed".to_string()))?; + + let mut transaction_service = self.get_transaction_service(); + let mut output_manager_service = self.get_output_manager_service(); + let response = match output_manager_service + .create_claim_sha_atomic_swap_transaction(output, pre_image, message.fee_per_gram.into()) + .await + { + Ok((tx_id, fee, amount, tx)) => { + match transaction_service + .submit_transaction( + tx_id, + tx, + fee, + amount, + "Claiming HTLC transaction with pre-image".to_string(), + ) + .await + { + Ok(()) => TransferResult { + address: Default::default(), + transaction_id: tx_id, + is_success: true, + failure_message: Default::default(), + }, + Err(e) => TransferResult { + address: Default::default(), + transaction_id: Default::default(), + is_success: false, + failure_message: e.to_string(), + }, + } + }, + Err(e) => { + warn!(target: LOG_TARGET, "Failed to claim SHA - XTR atomic swap: {}", e); + TransferResult { + address: Default::default(), + transaction_id: Default::default(), + is_success: false, + failure_message: e.to_string(), + } + }, + }; + + Ok(Response::new(ClaimShaAtomicSwapResponse { + results: Some(response), + })) + } + async fn transfer(&self, request: Request) -> Result, Status> { let message = request.into_inner(); let recipients = message diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index 407f9a80e5..33b4d75f2e 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -24,6 +24,7 @@ async-trait = "0.1.50" argon2 = "0.2" bincode = "1.3.1" blake2 = "0.9.0" +sha2 = "0.9.5" chrono = { version = "0.4.19", default-features = false, features = ["serde"] } clear_on_drop = "=0.2.4" crossbeam-channel = "0.3.8" diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index a669c0ff79..ff483c8f3b 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -27,7 +27,10 @@ use crate::output_manager_service::{ }; use aes_gcm::Aes256Gcm; use std::{fmt, sync::Arc}; -use tari_common_types::{transaction::TxId, types::PublicKey}; +use tari_common_types::{ + transaction::TxId, + types::{HashOutput, PublicKey}, +}; use tari_core::transactions::{ tari_amount::MicroTari, transaction::{Transaction, TransactionOutput, UnblindedOutput}, @@ -73,6 +76,7 @@ pub enum OutputManagerRequest { AddKnownOneSidedPaymentScript(KnownOneSidedPaymentScript), ReinstateCancelledInboundTx(TxId), SetCoinbaseAbandoned(TxId, bool), + CreateClaimShaAtomicSwapTransaction(HashOutput, PublicKey, MicroTari), } impl fmt::Display for OutputManagerRequest { @@ -120,6 +124,13 @@ impl fmt::Display for OutputManagerRequest { AddKnownOneSidedPaymentScript(_) => write!(f, "AddKnownOneSidedPaymentScript"), ReinstateCancelledInboundTx(_) => write!(f, "ReinstateCancelledInboundTx"), SetCoinbaseAbandoned(_, _) => write!(f, "SetCoinbaseAbandoned"), + CreateClaimShaAtomicSwapTransaction(output, pre_image, fee_per_gram) => write!( + f, + "ClaimShaAtomicSwap(output hash: {}, pre_image: {}, fee_per_gram: {} )", + output.to_hex(), + pre_image, + fee_per_gram, + ), } } } @@ -153,6 +164,7 @@ pub enum OutputManagerResponse { AddKnownOneSidedPaymentScript, ReinstatedCancelledInboundTx, CoinbaseAbandonedSet, + ClaimShaAtomicSwapTransaction((u64, MicroTari, MicroTari, Transaction)), } pub type OutputManagerEventSender = broadcast::Sender>; @@ -425,6 +437,26 @@ impl OutputManagerHandle { } } + pub async fn create_claim_sha_atomic_swap_transaction( + &mut self, + output: HashOutput, + pre_image: PublicKey, + fee_per_gram: MicroTari, + ) -> Result<(u64, MicroTari, MicroTari, Transaction), OutputManagerError> { + match self + .handle + .call(OutputManagerRequest::CreateClaimShaAtomicSwapTransaction( + output, + pre_image, + fee_per_gram, + )) + .await?? + { + OutputManagerResponse::ClaimShaAtomicSwapTransaction(ct) => Ok(ct), + _ => Err(OutputManagerError::UnexpectedApiResponse), + } + } + pub async fn apply_encryption(&mut self, cipher: Aes256Gcm) -> Result<(), OutputManagerError> { match self .handle diff --git a/base_layer/wallet/src/output_manager_service/mod.rs b/base_layer/wallet/src/output_manager_service/mod.rs index d4d4a80727..c933dca22c 100644 --- a/base_layer/wallet/src/output_manager_service/mod.rs +++ b/base_layer/wallet/src/output_manager_service/mod.rs @@ -30,6 +30,10 @@ use crate::{ storage::database::{OutputManagerBackend, OutputManagerDatabase}, }, }; +use tari_comms::NodeIdentity; + +use std::sync::Arc; + use futures::future; use log::*; pub(crate) use master_key_manager::MasterKeyManager; @@ -64,6 +68,7 @@ where T: OutputManagerBackend factories: CryptoFactories, network: NetworkConsensus, master_seed: CipherSeed, + node_identity: Arc, } impl OutputManagerServiceInitializer @@ -75,6 +80,7 @@ where T: OutputManagerBackend + 'static factories: CryptoFactories, network: NetworkConsensus, master_seed: CipherSeed, + node_identity: Arc, ) -> Self { Self { config, @@ -82,6 +88,7 @@ where T: OutputManagerBackend + 'static factories, network, master_seed, + node_identity, } } } @@ -112,6 +119,7 @@ where T: OutputManagerBackend + 'static let config = self.config.clone(); let constants = self.network.create_consensus_constants().pop().unwrap(); let master_seed = self.master_seed.clone(); + let node_identity = self.node_identity.clone(); context.spawn_when_ready(move |handles| async move { let base_node_service_handle = handles.expect_handle::(); let connectivity = handles.expect_handle::(); @@ -127,6 +135,7 @@ where T: OutputManagerBackend + 'static base_node_service_handle, connectivity, master_seed, + node_identity, ) .await .expect("Could not initialize Output Manager Service") diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 26f83a0a98..e47cbe9db1 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -43,14 +43,15 @@ use diesel::result::{DatabaseErrorKind, Error as DieselError}; use futures::{pin_mut, StreamExt}; use log::*; use rand::{rngs::OsRng, RngCore}; -use std::{cmp::Ordering, fmt, fmt::Display, sync::Arc}; +use std::{cmp::Ordering, convert::TryInto, fmt, fmt::Display, sync::Arc}; use tari_common_types::{ transaction::TxId, - types::{PrivateKey, PublicKey}, + types::{HashOutput, PrivateKey, PublicKey}, }; -use tari_comms::types::CommsPublicKey; +use tari_comms::{types::CommsPublicKey, NodeIdentity}; use tari_core::{ consensus::{ConsensusConstants, ConsensusEncodingSized, ConsensusEncodingWrapper}, + proto::base_node::FetchMatchingUtxos, transactions::{ fee::Fee, tari_amount::MicroTari, @@ -85,6 +86,7 @@ pub struct OutputManagerService { Option>>, base_node_service: BaseNodeServiceHandle, last_seen_tip_height: Option, + node_identity: Arc, } impl OutputManagerService @@ -107,6 +109,7 @@ where base_node_service: BaseNodeServiceHandle, connectivity: TWalletConnectivity, master_seed: CipherSeed, + node_identity: Arc, ) -> Result { // Clear any encumberances for transactions that were being negotiated but did not complete to become official // Pending Transactions. @@ -130,6 +133,7 @@ where request_stream: Some(request_stream), base_node_service, last_seen_tip_height: None, + node_identity, }) } @@ -331,9 +335,30 @@ where .set_coinbase_abandoned(tx_id, abandoned) .await .map(|_| OutputManagerResponse::CoinbaseAbandonedSet), + OutputManagerRequest::CreateClaimShaAtomicSwapTransaction(output_hash, pre_image, fee_per_gram) => { + self.claim_sha_atomic_swap_with_hash(output_hash, pre_image, fee_per_gram) + .await + }, } } + async fn claim_sha_atomic_swap_with_hash( + &mut self, + output_hash: HashOutput, + pre_image: PublicKey, + fee_per_gram: MicroTari, + ) -> Result { + let output = self + .fetch_outputs_from_node(vec![output_hash]) + .await? + .pop() + .ok_or_else(|| OutputManagerError::ServiceError("Output not found".to_string()))?; + + self.create_claim_sha_atomic_swap_transaction(output, pre_image, fee_per_gram) + .await + .map(OutputManagerResponse::ClaimShaAtomicSwapTransaction) + } + fn handle_base_node_service_event(&mut self, event: Arc) { match (*event).clone() { BaseNodeEvent::BaseNodeStateChanged(state) => { @@ -1159,6 +1184,119 @@ where Ok((tx_id, tx, fee, utxos_total_value)) } + async fn fetch_outputs_from_node( + &mut self, + hashes: Vec, + ) -> Result, OutputManagerError> { + // lets get the output from the blockchain + let req = FetchMatchingUtxos { output_hashes: hashes }; + let results: Vec = self + .resources + .connectivity + .obtain_base_node_wallet_rpc_client() + .await + .ok_or_else(|| { + OutputManagerError::InvalidResponseError("Could not connect to base node rpc client".to_string()) + })? + .fetch_matching_utxos(req) + .await? + .outputs + .into_iter() + .filter_map(|o| match o.try_into() { + Ok(output) => Some(output), + _ => None, + }) + .collect(); + Ok(results) + } + + pub async fn create_claim_sha_atomic_swap_transaction( + &mut self, + output: TransactionOutput, + pre_image: PublicKey, + fee_per_gram: MicroTari, + ) -> Result<(u64, MicroTari, MicroTari, Transaction), OutputManagerError> { + let spending_key = PrivateKey::from_bytes( + CommsPublicKey::shared_secret( + self.node_identity.as_ref().secret_key(), + &output.sender_offset_public_key, + ) + .as_bytes(), + )?; + let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spending_key))?; + let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; + let rewound = + output.full_rewind_range_proof(&self.resources.factories.range_proof, &rewind_key, &blinding_key)?; + + let rewound_output = UnblindedOutput::new( + rewound.committed_value, + rewound.blinding_factor.clone(), + output.features, + output.script, + inputs!(pre_image), + self.node_identity.as_ref().secret_key().clone(), + output.sender_offset_public_key, + output.metadata_signature, + ); + let amount = rewound.committed_value; + + let offset = PrivateKey::random(&mut OsRng); + let nonce = PrivateKey::random(&mut OsRng); + let message = "SHA-XTR atomic swap".to_string(); + + // Create builder with no recipients (other than ourselves) + let mut builder = SenderTransactionProtocol::builder(0, self.resources.consensus_constants.clone()); + builder + .with_lock_height(0) + .with_fee_per_gram(fee_per_gram) + .with_offset(offset.clone()) + .with_private_nonce(nonce.clone()) + .with_message(message) + .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) + .with_input( + rewound_output.as_transaction_input(&self.resources.factories.commitment)?, + rewound_output, + ); + + let mut outputs = Vec::new(); + + let (spending_key, script_private_key) = self + .resources + .master_key_manager + .get_next_spend_and_script_key() + .await?; + builder.with_change_secret(spending_key); + builder.with_rewindable_outputs(self.resources.master_key_manager.rewind_data().clone()); + builder.with_change_script( + script!(Nop), + inputs!(PublicKey::from_secret_key(&script_private_key)), + script_private_key, + ); + + let factories = CryptoFactories::default(); + let mut stp = builder + .build::(&self.resources.factories) + .map_err(|e| OutputManagerError::BuildError(e.message))?; + + let tx_id = stp.get_tx_id()?; + + let unblinded_output = stp.get_change_unblinded_output()?.ok_or_else(|| { + OutputManagerError::BuildError("There should be a change output metadata signature available".to_string()) + })?; + let change_output = DbUnblindedOutput::from_unblinded_output(unblinded_output, &self.resources.factories)?; + outputs.push(change_output); + + trace!(target: LOG_TARGET, "Claiming HTLC with transaction ({}).", tx_id); + self.resources.db.encumber_outputs(tx_id, Vec::new(), outputs).await?; + self.confirm_encumberance(tx_id).await?; + let fee = stp.get_fee_amount()?; + trace!(target: LOG_TARGET, "Finalize send-to-self transaction ({}).", tx_id); + stp.finalize(KernelFeatures::empty(), &factories)?; + let tx = stp.take_transaction()?; + + Ok((tx_id, fee, amount - fee, tx)) + } + /// Persist a one-sided payment script for a Comms Public/Private key. These are the scripts that this wallet knows /// to look for when scanning for one-sided payments async fn add_known_script(&mut self, known_script: KnownOneSidedPaymentScript) -> Result<(), OutputManagerError> { diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index c8ed08aebd..7a34b0bd7d 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -33,6 +33,9 @@ use tari_service_framework::reply_channel::SenderService; use tokio::sync::broadcast; use tower::Service; +use tari_common_types::types::PublicKey; +use tari_core::transactions::transaction::TransactionOutput; + /// API Request enum #[allow(clippy::large_enum_variant)] pub enum TransactionServiceRequest { @@ -46,9 +49,10 @@ pub enum TransactionServiceRequest { GetAnyTransaction(TxId), SendTransaction(CommsPublicKey, MicroTari, MicroTari, String), SendOneSidedTransaction(CommsPublicKey, MicroTari, MicroTari, String), + SendShaAtomicSwapTransaction(CommsPublicKey, MicroTari, MicroTari, String), CancelTransaction(TxId), ImportUtxo(MicroTari, CommsPublicKey, String, Option), - SubmitCoinSplitTransaction(TxId, Transaction, MicroTari, MicroTari, String), + SubmitTransactionToSelf(TxId, Transaction, MicroTari, MicroTari, String), SetLowPowerMode, SetNormalPowerMode, ApplyEncryption(Box), @@ -76,6 +80,9 @@ impl fmt::Display for TransactionServiceRequest { Self::SendOneSidedTransaction(k, v, _, msg) => { f.write_str(&format!("SendOneSidedTransaction (to {}, {}, {})", k, v, msg)) }, + Self::SendShaAtomicSwapTransaction(k, v, _, msg) => { + f.write_str(&format!("SendShaAtomicSwapTransaction (to {}, {}, {})", k, v, msg)) + }, Self::CancelTransaction(t) => f.write_str(&format!("CancelTransaction ({})", t)), Self::ImportUtxo(v, k, msg, maturity) => f.write_str(&format!( "ImportUtxo (from {}, {}, {} with maturity: {})", @@ -84,9 +91,7 @@ impl fmt::Display for TransactionServiceRequest { msg, maturity.unwrap_or(0) )), - Self::SubmitCoinSplitTransaction(tx_id, _, _, _, _) => { - f.write_str(&format!("SubmitTransaction ({})", tx_id)) - }, + Self::SubmitTransactionToSelf(tx_id, _, _, _, _) => f.write_str(&format!("SubmitTransaction ({})", tx_id)), Self::SetLowPowerMode => f.write_str("SetLowPowerMode "), Self::SetNormalPowerMode => f.write_str("SetNormalPowerMode"), Self::ApplyEncryption(_) => f.write_str("ApplyEncryption"), @@ -128,6 +133,7 @@ pub enum TransactionServiceResponse { NumConfirmationsSet, ValidationStarted(u64), CompletedTransactionValidityChanged, + ShaAtomicSwapTransactionSent(Box<(TxId, PublicKey, TransactionOutput)>), } /// Events that can be published on the Text Message Service Event Stream @@ -381,7 +387,7 @@ impl TransactionServiceHandle { ) -> Result<(), TransactionServiceError> { match self .handle - .call(TransactionServiceRequest::SubmitCoinSplitTransaction( + .call(TransactionServiceRequest::SubmitTransactionToSelf( tx_id, tx, fee, amount, message, )) .await?? @@ -512,4 +518,29 @@ impl TransactionServiceHandle { _ => Err(TransactionServiceError::UnexpectedApiResponse), } } + + pub async fn send_sha_atomic_swap_transaction( + &mut self, + dest_pubkey: CommsPublicKey, + amount: MicroTari, + fee_per_gram: MicroTari, + message: String, + ) -> Result<(TxId, PublicKey, TransactionOutput), TransactionServiceError> { + match self + .handle + .call(TransactionServiceRequest::SendShaAtomicSwapTransaction( + dest_pubkey, + amount, + fee_per_gram, + message, + )) + .await?? + { + TransactionServiceResponse::ShaAtomicSwapTransactionSent(boxed) => { + let (tx_id, pre_image, output) = *boxed; + Ok((tx_id, pre_image, output)) + }, + _ => Err(TransactionServiceError::UnexpectedApiResponse), + } + } } diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index f7771ab403..076c9bc489 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -50,11 +50,13 @@ use crate::{ util::watch::Watch, utxo_scanner_service::utxo_scanning::RECOVERY_KEY, }; + use chrono::{NaiveDateTime, Utc}; use digest::Digest; use futures::{pin_mut, stream::FuturesUnordered, Stream, StreamExt}; use log::*; use rand::{rngs::OsRng, RngCore}; +use sha2::Sha256; use std::{ collections::{HashMap, HashSet}, convert::TryInto, @@ -63,7 +65,7 @@ use std::{ }; use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus, TxId}, - types::PrivateKey, + types::{PrivateKey, PublicKey}, }; use tari_comms::{peer_manager::NodeIdentity, types::CommsPublicKey}; use tari_comms_dht::outbound::OutboundMessageRequester; @@ -72,7 +74,7 @@ use tari_core::{ proto::base_node as base_node_proto, transactions::{ tari_amount::MicroTari, - transaction::{KernelFeatures, OutputFeatures, Transaction}, + transaction::{KernelFeatures, OutputFeatures, Transaction, TransactionOutput}, transaction_protocol::{ proto, recipient::RecipientSignedMessage, @@ -83,7 +85,11 @@ use tari_core::{ ReceiverTransactionProtocol, }, }; -use tari_crypto::{keys::DiffieHellmanSharedSecret, script, tari_utilities::ByteArray}; +use tari_crypto::{ + keys::{DiffieHellmanSharedSecret, PublicKey as PKtrait}, + script, + tari_utilities::ByteArray, +}; use tari_p2p::domain_message::DomainMessage; use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tari_shutdown::ShutdownSignal; @@ -552,6 +558,18 @@ where ) .await .map(TransactionServiceResponse::TransactionSent), + TransactionServiceRequest::SendShaAtomicSwapTransaction(dest_pubkey, amount, fee_per_gram, message) => { + Ok(TransactionServiceResponse::ShaAtomicSwapTransactionSent( + self.send_sha_atomic_swap_transaction( + dest_pubkey, + amount, + fee_per_gram, + message, + transaction_broadcast_join_handles, + ) + .await?, + )) + }, TransactionServiceRequest::CancelTransaction(tx_id) => self .cancel_pending_transaction(tx_id) .await @@ -597,8 +615,8 @@ where .add_utxo_import_transaction(value, source_public_key, message, maturity) .await .map(TransactionServiceResponse::UtxoImported), - TransactionServiceRequest::SubmitCoinSplitTransaction(tx_id, tx, fee, amount, message) => self - .submit_coin_split_transaction(transaction_broadcast_join_handles, tx_id, tx, fee, amount, message) + TransactionServiceRequest::SubmitTransactionToSelf(tx_id, tx, fee, amount, message) => self + .submit_transaction_to_self(transaction_broadcast_join_handles, tx_id, tx, fee, amount, message) .await .map(|_| TransactionServiceResponse::TransactionSubmitted), TransactionServiceRequest::GenerateCoinbaseTransaction(reward, fees, block_height) => self @@ -781,6 +799,137 @@ where Ok(()) } + /// broadcasts a SHA-XTR atomic swap transaction + /// # Arguments + /// 'dest_pubkey': The Comms pubkey of the recipient node + /// 'amount': The amount of Tari to send to the recipient + /// 'fee_per_gram': The amount of fee per transaction gram to be included in transaction + pub async fn send_sha_atomic_swap_transaction( + &mut self, + dest_pubkey: CommsPublicKey, + amount: MicroTari, + fee_per_gram: MicroTari, + message: String, + transaction_broadcast_join_handles: &mut FuturesUnordered< + JoinHandle>, + >, + ) -> Result, TransactionServiceError> { + let tx_id = OsRng.next_u64(); + // this can be anything, so lets generate a random private key + let pre_image = PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)); + let hash: [u8; 32] = Sha256::digest(pre_image.as_bytes()).into(); + + // lets make the unlock height a day from now, 2 min blocks * 30 blocks per hour * 24 hours + let height = self.last_seen_tip_height.unwrap_or(0) + (24 * 30); + + // lets create the HTLC script + let script = script!(HashSha256 PushHash(Box::new(hash)) Equal IfThen PushPubKey(Box::new(dest_pubkey.clone())) Else CheckHeightVerify(height) PushPubKey(Box::new(self.node_identity.public_key().clone())) EndIf); + + // Prepare sender part of the transaction + let mut stp = self + .output_manager_service + .prepare_transaction_to_send(tx_id, amount, fee_per_gram, None, message.clone(), script.clone()) + .await?; + + // This call is needed to advance the state from `SingleRoundMessageReady` to `SingleRoundMessageReady`, + // but the returned value is not used + let _ = stp + .build_single_round_message() + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + + self.output_manager_service + .confirm_pending_transaction(tx_id) + .await + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + + // Prepare receiver part of the transaction + + // Diffie-Hellman shared secret `k_Ob * K_Sb = K_Ob * k_Sb` results in a public key, which is converted to + // bytes to enable conversion into a private key to be used as the spending key + let sender_offset_private_key = stp + .get_recipient_sender_offset_private_key(0) + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + // TODO: Add a standardized Diffie-Hellman method to the tari_crypto library that will return a private key, + // TODO: then come back and use it here. + let spending_key = PrivateKey::from_bytes( + CommsPublicKey::shared_secret(&sender_offset_private_key.clone(), &dest_pubkey.clone()).as_bytes(), + ) + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + + let sender_message = TransactionSenderMessage::new_single_round_message(stp.get_single_round_message()?); + let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spending_key))?; + let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; + let rewind_data = RewindData { + rewind_key: rewind_key.clone(), + rewind_blinding_key: blinding_key.clone(), + proof_message: [0u8; 21], + }; + + let rtp = ReceiverTransactionProtocol::new_with_rewindable_output( + sender_message, + PrivateKey::random(&mut OsRng), + spending_key, + OutputFeatures::default(), + &self.resources.factories, + &rewind_data, + ); + + let recipient_reply = rtp.get_signed_data()?.clone(); + let output = recipient_reply.output.clone(); + + // Start finalizing + + stp.add_single_recipient_info(recipient_reply, &self.resources.factories.range_proof) + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + + // Finalize + + stp.finalize(KernelFeatures::empty(), &self.resources.factories) + .map_err(|e| { + error!( + target: LOG_TARGET, + "Transaction (TxId: {}) could not be finalized. Failure error: {:?}", tx_id, e, + ); + TransactionServiceProtocolError::new(tx_id, e.into()) + })?; + info!(target: LOG_TARGET, "Finalized one-side transaction TxId: {}", tx_id); + + // This event being sent is important, but not critical to the protocol being successful. Send only fails if + // there are no subscribers. + let _ = self + .event_publisher + .send(Arc::new(TransactionEvent::TransactionCompletedImmediately(tx_id))); + + // Broadcast one-sided transaction + + let tx = stp + .get_transaction() + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + let fee = stp + .get_fee_amount() + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + + self.submit_transaction( + transaction_broadcast_join_handles, + CompletedTransaction::new( + tx_id, + self.resources.node_identity.public_key().clone(), + dest_pubkey.clone(), + amount, + fee, + tx.clone(), + TransactionStatus::Completed, + message.clone(), + Utc::now().naive_utc(), + TransactionDirection::Outbound, + None, + ), + ) + .await?; + + Ok(Box::new((tx_id, pre_image, output))) + } + /// Sends a one side payment transaction to a recipient /// # Arguments /// 'dest_pubkey': The Comms pubkey of the recipient node @@ -1829,7 +1978,7 @@ where /// Submit a completed coin split transaction to the Transaction Manager. This is different from /// `submit_transaction` in that it will expose less information about the completed transaction. - pub async fn submit_coin_split_transaction( + pub async fn submit_transaction_to_self( &mut self, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>, diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index f184cadc42..1b71f2744d 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -171,6 +171,7 @@ where factories.clone(), config.network, master_seed, + node_identity.clone(), )) .add_initializer(TransactionServiceInitializer::new( config.transaction_service_config.unwrap_or_default(), diff --git a/base_layer/wallet/tests/output_manager_service/service.rs b/base_layer/wallet/tests/output_manager_service/service.rs index 30ef5c1920..892c99c5c4 100644 --- a/base_layer/wallet/tests/output_manager_service/service.rs +++ b/base_layer/wallet/tests/output_manager_service/service.rs @@ -197,6 +197,7 @@ async fn setup_output_manager_service( basenode_service_handle, wallet_connectivity_mock.clone(), cipher_seed, + server_node_identity.clone(), ) .await .unwrap(); @@ -219,6 +220,7 @@ async fn setup_output_manager_service( pub async fn setup_oms_with_bn_state( backend: T, height: Option, + node_identity: Arc, ) -> ( OutputManagerHandle, Shutdown, @@ -263,6 +265,7 @@ pub async fn setup_oms_with_bn_state( base_node_service_handle.clone(), connectivity, CipherSeed::new(), + node_identity, ) .await .unwrap(); @@ -370,10 +373,14 @@ async fn fee_estimate() { async fn test_utxo_selection_no_chain_metadata() { let factories = CryptoFactories::default(); let (connection, _tempdir) = get_temp_sqlite_database_connection(); - + let server_node_identity = build_node_identity(PeerFeatures::COMMUNICATION_NODE); // no chain metadata - let (mut oms, _shutdown, _, _, _) = - setup_oms_with_bn_state(OutputManagerSqliteDatabase::new(connection, None), None).await; + let (mut oms, _shutdown, _, _, _) = setup_oms_with_bn_state( + OutputManagerSqliteDatabase::new(connection, None), + None, + server_node_identity, + ) + .await; let fee_calc = Fee::new(*create_consensus_constants(0).transaction_weight()); // no utxos - not enough funds @@ -468,9 +475,14 @@ async fn test_utxo_selection_with_chain_metadata() { let factories = CryptoFactories::default(); let (connection, _tempdir) = get_temp_sqlite_database_connection(); + let server_node_identity = build_node_identity(PeerFeatures::COMMUNICATION_NODE); // setup with chain metadata at a height of 6 - let (mut oms, _shutdown, _, _, _) = - setup_oms_with_bn_state(OutputManagerSqliteDatabase::new(connection, None), Some(6)).await; + let (mut oms, _shutdown, _, _, _) = setup_oms_with_bn_state( + OutputManagerSqliteDatabase::new(connection, None), + Some(6), + server_node_identity, + ) + .await; let fee_calc = Fee::new(*create_consensus_constants(0).transaction_weight()); // no utxos - not enough funds @@ -1679,6 +1691,7 @@ async fn test_oms_key_manager_discrepancy() { let master_seed1 = CipherSeed::new(); + let server_node_identity = build_node_identity(PeerFeatures::COMMUNICATION_NODE); let output_manager_service = OutputManagerService::new( OutputManagerServiceConfig::default(), oms_request_receiver, @@ -1690,6 +1703,7 @@ async fn test_oms_key_manager_discrepancy() { basenode_service_handle.clone(), wallet_connectivity.clone(), master_seed1.clone(), + server_node_identity, ) .await .unwrap(); @@ -1697,6 +1711,8 @@ async fn test_oms_key_manager_discrepancy() { drop(output_manager_service); let (_oms_request_sender2, oms_request_receiver2) = reply_channel::unbounded(); + + let server_node_identity2 = build_node_identity(PeerFeatures::COMMUNICATION_NODE); let output_manager_service2 = OutputManagerService::new( OutputManagerServiceConfig::default(), oms_request_receiver2, @@ -1708,6 +1724,7 @@ async fn test_oms_key_manager_discrepancy() { basenode_service_handle.clone(), wallet_connectivity.clone(), master_seed1, + server_node_identity2, ) .await .expect("Should be able to make a new OMS with same master key"); @@ -1715,6 +1732,7 @@ async fn test_oms_key_manager_discrepancy() { let (_oms_request_sender3, oms_request_receiver3) = reply_channel::unbounded(); let master_seed2 = CipherSeed::new(); + let server_node_identity3 = build_node_identity(PeerFeatures::COMMUNICATION_NODE); let output_manager_service3 = OutputManagerService::new( OutputManagerServiceConfig::default(), oms_request_receiver3, @@ -1726,6 +1744,7 @@ async fn test_oms_key_manager_discrepancy() { basenode_service_handle, wallet_connectivity, master_seed2, + server_node_identity3, ) .await; diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index 05cb742165..d9b058ad49 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -209,6 +209,7 @@ pub fn setup_transaction_service>( factories.clone(), Network::Weatherwax.into(), CipherSeed::new(), + comms.node_identity(), )) .add_initializer(TransactionServiceInitializer::new( TransactionServiceConfig { @@ -241,6 +242,7 @@ pub fn setup_transaction_service>( connectivity_service_handle, ) } + /// This utility function creates a Transaction service without using the Service Framework Stack and exposes all the /// streams for testing purposes. #[allow(clippy::type_complexity)] @@ -330,7 +332,6 @@ pub fn setup_transaction_service_no_comms( ); let ts_db = TransactionDatabase::new(TransactionServiceSqliteDatabase::new(db_connection.clone(), None)); let oms_db = OutputManagerDatabase::new(OutputManagerSqliteDatabase::new(db_connection, None)); - let output_manager_service = runtime .block_on(OutputManagerService::new( OutputManagerServiceConfig::default(), @@ -343,6 +344,7 @@ pub fn setup_transaction_service_no_comms( basenode_service_handle.clone(), wallet_connectivity.clone(), CipherSeed::new(), + server_node_identity.clone(), )) .unwrap(); @@ -376,11 +378,7 @@ pub fn setup_transaction_service_no_comms( outbound_message_requester, wallet_connectivity.clone(), event_publisher, - Arc::new(NodeIdentity::random( - &mut OsRng, - get_next_memory_address(), - PeerFeatures::COMMUNICATION_NODE, - )), + server_node_identity.clone(), factories, shutdown.to_signal(), basenode_service_handle, @@ -911,6 +909,148 @@ fn recover_one_sided_transaction() { }); } +#[test] +fn test_htlc_send_and_claim() { + 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, + )); + + let base_node_identity = Arc::new(NodeIdentity::random( + &mut OsRng, + get_next_memory_address(), + PeerFeatures::COMMUNICATION_NODE, + )); + + log::info!( + "manage_single_transaction: Alice: '{}', Base: '{}'", + alice_node_identity.node_id().short_str(), + base_node_identity.node_id().short_str() + ); + + let temp_dir = tempdir().unwrap(); + let temp_dir_bob = tempdir().unwrap(); + let database_path = temp_dir.path().to_str().unwrap().to_string(); + let path_string = temp_dir_bob.path().to_str().unwrap().to_string(); + let bob_db_name = format!("{}.sqlite3", random::string(8).as_str()); + let bob_db_path = format!("{}/{}", path_string, bob_db_name); + + let (db_connection, _tempdir) = make_wallet_database_connection(Some(database_path.clone())); + let bob_connection = run_migration_and_create_sqlite_connection(&bob_db_path, 16).unwrap(); + + let shutdown = Shutdown::new(); + let (alice_ts, alice_oms, _alice_comms, mut alice_connectivity) = setup_transaction_service( + &mut runtime, + alice_node_identity, + vec![], + factories.clone(), + db_connection, + database_path, + Duration::from_secs(0), + shutdown.to_signal(), + ); + + let ( + mut bob_ts, + mut bob_oms, + _, + _, + _, + _, + _, + _, + _shutdown, + _, + bob_node_identity, + bob_node_mock, + _, + _, + _rpc_server_connection, + ) = setup_transaction_service_no_comms(&mut runtime, factories.clone(), bob_connection, None); + + log::info!( + "manage_single_transaction: Bob: '{}'", + bob_node_identity.node_id().short_str(), + ); + + let mut alice_event_stream = alice_ts.get_event_stream(); + + alice_connectivity.set_base_node(base_node_identity.to_peer()); + + let initial_wallet_value = 2500.into(); + let (_utxo, uo1) = make_input(&mut OsRng, initial_wallet_value, &factories.commitment); + let mut alice_oms_clone = alice_oms.clone(); + runtime.block_on(async move { alice_oms_clone.add_output(uo1).await.unwrap() }); + + let message = "".to_string(); + let value = 1000.into(); + let mut alice_ts_clone = alice_ts.clone(); + let bob_pubkey = bob_node_identity.public_key().clone(); + let (tx_id, pre_image, output) = runtime.block_on(async move { + alice_ts_clone + .send_sha_atomic_swap_transaction(bob_pubkey, value, 20.into(), message.clone()) + .await + .expect("Alice sending HTLC transaction") + }); + + let mut alice_ts_clone2 = alice_ts.clone(); + let mut alice_oms_clone = alice_oms.clone(); + runtime.block_on(async move { + let completed_tx = alice_ts_clone2 + .get_completed_transaction(tx_id) + .await + .expect("Could not find completed HTLC tx"); + + let fees = completed_tx.fee; + + assert_eq!( + alice_oms_clone.get_balance().await.unwrap().pending_incoming_balance, + initial_wallet_value - value - fees + ); + }); + + runtime.block_on(async { + let delay = sleep(Duration::from_secs(30)); + tokio::pin!(delay); + loop { + tokio::select! { + event = alice_event_stream.recv() => { + if let TransactionEvent::TransactionCompletedImmediately(id) = &*event.unwrap() { + if id == &tx_id { + break; + } + } + }, + () = &mut delay => { + break; + }, + } + } + }); + let hash = output.hash(); + bob_node_mock.set_utxos(vec![output]); + runtime.block_on(async move { + let (tx_id_htlc, htlc_fee, htlc_amount, tx) = bob_oms + .create_claim_sha_atomic_swap_transaction(hash, pre_image, 20.into()) + .await + .unwrap(); + + bob_ts + .submit_transaction(tx_id_htlc, tx, htlc_fee, htlc_amount, "".to_string()) + .await + .unwrap(); + assert_eq!( + bob_oms.get_balance().await.unwrap().pending_incoming_balance, + htlc_amount + ); + }); +} + #[test] fn send_one_sided_transaction_to_self() { let mut runtime = create_runtime(); diff --git a/clients/wallet_grpc_client/index.js b/clients/wallet_grpc_client/index.js index 41065483d7..17a5e17293 100644 --- a/clients/wallet_grpc_client/index.js +++ b/clients/wallet_grpc_client/index.js @@ -42,6 +42,8 @@ function Client(address) { "cancelTransaction", "checkForUpdates", "revalidateAllTransactions", + "SendShaAtomicSwapTransaction", + "claimShaAtomicSwapTransaction", ]; this.waitForReady = (...args) => { diff --git a/docs/book.toml b/docs/book.toml index afa110f9b9..68f5cb637f 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -1,5 +1,5 @@ [book] -authors = ["Philip Robinson", "Byron Hambly"] +authors = ["Philip Robinson", "Byron Hambly", "SW van Heerden"] language = "en" multilingual = false src = "src" @@ -7,4 +7,4 @@ title = "Tari Protocol Developer Documentation" [output.html] mathjax-support=true -theme = "src/theme" \ No newline at end of file +theme = "src/theme" diff --git a/docs/src/btc_atomic_swap.md b/docs/src/btc_atomic_swap.md new file mode 100644 index 0000000000..8075c6e91b --- /dev/null +++ b/docs/src/btc_atomic_swap.md @@ -0,0 +1,192 @@ +# BTC Atomic swap + +This is a short document detailing how to do a Bitcoin - Tari atomic swap + +## BTC setup + +The following dependencies are required: + +* Tari wallet + +* Bitcoind (only for BTC atomic swaps, replace with other wallets for other coins) + +* Golang + +* decred atomic swap cli program + + +### Mac install instructions + +Install Bitcoind, follow these steps: + +Install go: + +Clone: + +### Win install instructions + +Install Bitcoind, follow these steps: + +Install go: + +Clone: + +### Unix install instructions + +Install Bitcoind, follow these steps: + +Install go: + +Clone: + +### Setup instructions + +Start bitcoin core with: +```cli,ignore +./src/bitcoind -daemon -testnet +``` + +Create a wallet with: +```cli,ignore +bitcoin-cli --testnet -named createwallet wallet_name=mywallet load_on_startup=true descriptors=false +``` +`mywallet` can be any name you want to name your wallet. +Ensure that `descriptors` is set to false. + +Create a bitcoin.conf file with an RPC username and RPC password set. +Example bitcoin.conf file: +Ensure this file is in the correct location: + +Install the desired Atomic swap tool from the Decred repo with: +```cli,ignore +cd cmd/ +go build . +``` +Replace `` with the desired tool folder, for example: `btcatomicswap` for bitcoin. + +### Atomic swap process + +This example will use bitcoin but can be any of the other coins supported by decred atomic swap ci programs. +Alice starts the process by sending a tari HTLC transaction with: + +```cli,ignore +tari_console_wallet --command "init-sha-atomic-swap " +``` +`` is the Tari amount that is swapped +`` is the public key used by Bob's wallet +`` is the desired Tari transaction message + +This will print out the following information: +```cli,ignore +pre_image hex: +pre_image hash: +Output hash: +``` +`pre_image hex:` is the hex of the actual pre-image that is required to claim the BTC transaction. +`pre_image hash:` is the hex that Bob must use or this HTLC transaction +`Output hash:` is the hash of the output that contains the HTLC script + +Alice must generate a new BTC address with: + +```cli,ignore +bitcoin-cli --testnet getnewaddress "" "legacy" +``` + +Bob can then create the BTC HTLC transaction with: +```cli,ignore +./btcatomicswap --testnet --rpcuser= --rpcpass= participate +``` +`` This is the chosen RPC username to connect the bitcoin wallet as specified in the bitcoin.conf file +`` This is the chosen RPC password to connect the bitcoin wallet as specified in the bitcoin.conf file +`` This is Alice's BTC address +`` This is the amount of BTC Alice wants for the XTR +`` This is the pre_image hash `pre_image hash` that Alice got when creating the XTR HTLC transaction and gave to Bob + +This returns the following: +```cli,ignore +Contract fee: +Refund fee: + +Contract (): + + +Contract transaction (): + + +Refund transaction (): + + +Publish contract transaction? [y/N] +``` +Selecting `y` will publish the transaction. + + +After Bob publishes the transaction Alice can verify the transaction with: +```cli,ignore +./btcatomicswap --testnet --rpcuser= --rpcpass= auditcontract +``` +`` This is the hex of the BTC contract +``This is the hex of the BTC transaction + +This will return: +```cli,ignore +Contract address: +Contract value: +Recipient address: +Author's refund address: + +Secret hash: +``` +Alice can verify the details and if she is happy, she can claim her BTC transaction with: + +```cli,ignore +./btcatomicswap --testnet --rpcuser= --rpcpass= redeem +``` +`` This is the hex of the BTC contract +``This is the hex of the BTC transaction +`` This is the pre_image of the hex Alice got when she published the XTR HTLC transaction. + +This will return: +```cli,ignore +Redeem fee: + +Redeem transaction (): + + +Publish redeem transaction? [y/N] +``` +Selecting y, will publish the transaction. + +After Alice publishes the transaction Bob can either get the transaction hex from Alice or by calling the bitcoin cli +```cli, ignore +bitcoin-cli --testnet getrawtransaction false +``` +`` The redeem transaction id. +`` The block hash that has the mined redeem transaction in it. + +Bob can then get the pre_image by calling: +```cli,ignore +./btcatomicswap --testnet --rpcuser= --rpcpass= extractsecret +``` +`` the hex of the redeem transaction. +`` the hex of the hashed pre_image + +this returns: +```cli,ignore +Secret: +``` + +Bob can then claim the XTR with: +```cli,ignore +tari_console_wallet --command "finalise-sha-atomic-swap " +``` +`` is the hash of the XTR output that contains the HTLC script + +### Ref guide to documentation + +* Tari atomic swap RFC: + +* Bitcoind RPC interface: + +* Decred Atomic swap tool readme: diff --git a/integration_tests/features/WalletTransfer.feature b/integration_tests/features/WalletTransfer.feature index ed333460c0..47dd6c5914 100644 --- a/integration_tests/features/WalletTransfer.feature +++ b/integration_tests/features/WalletTransfer.feature @@ -34,3 +34,18 @@ Feature: Wallet Transfer And I mine 5 blocks on NODE Then all nodes are at height 15 Then all wallets detect all transactions as Mined_Confirmed + + Scenario: As a wallet I want to create a HTLC transaction + Given I have a seed node NODE + # Add a 2nd node otherwise initial sync will not succeed + And I have 1 base nodes connected to all seed nodes + And I have wallet WALLET_A connected to all seed nodes + And I have wallet WALLET_B connected to all seed nodes + And I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 10 blocks + Then I wait for wallet WALLET_A to have at least 10000000000 uT + When I broadcast HTLC transaction with 5000000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 + And mining node MINER mines 6 blocks + And I claim an HTLC transaction with wallet WALLET_B at fee 20 + And mining node MINER mines 6 blocks + Then I wait for wallet WALLET_B to have at least 4000000000 uT \ No newline at end of file diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index b9a0591fc0..bf8ad4fa54 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -1790,6 +1790,165 @@ When( } ); +When( + /I broadcast HTLC transaction with (.*) uT from wallet (.*) to wallet (.*) at fee (.*)/, + { timeout: 25 * 5 * 1000 }, + async function (tariAmount, source, dest, feePerGram) { + const sourceClient = await this.getWallet(source).connectClient(); + const destClient = await this.getWallet(dest).connectClient(); + + const sourceInfo = await sourceClient.identify(); + const destInfo = await destClient.identify(); + console.log("Starting HTLC transaction of", tariAmount, "to", dest); + let success = false; + let retries = 1; + const retries_limit = 25; + while (!success && retries <= retries_limit) { + await waitFor( + async () => { + try { + this.lastResult = await sourceClient.sendHtlc({ + recipient: { + address: destInfo.public_key, + amount: tariAmount, + fee_per_gram: feePerGram, + message: "msg", + }, + }); + } catch (error) { + console.log(error); + return false; + } + return true; + }, + true, + 20 * 1000, + 5 * 1000, + 5 + ); + + success = this.lastResult.is_success; + if (!success) { + const wait_seconds = 5; + console.log( + " " + + lastResult.failure_message + + ", trying again after " + + wait_seconds + + "s (" + + retries + + " of " + + retries_limit + + ")" + ); + await sleep(wait_seconds * 1000); + retries++; + } + } + if (success) { + this.addTransaction( + sourceInfo.public_key, + this.lastResult.transaction_id + ); + this.addTransaction(destInfo.public_key, this.lastResult.transaction_id); + } + expect(success).to.equal(true); + //lets now wait for this transaction to be at least broadcast before we continue. + await waitFor( + async () => + sourceClient.isTransactionAtLeastBroadcast( + this.lastResult.transaction_id + ), + true, + 60 * 1000, + 5 * 1000, + 5 + ); + let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( + this.lastResult.transaction_id + ); + expect(transactionPending).to.equal(true); + } +); + +When( + /I claim an HTLC transaction with wallet (.*) at fee (.*)/, + { timeout: 25 * 5 * 1000 }, + async function (source, feePerGram) { + const sourceClient = await this.getWallet(source).connectClient(); + + const sourceInfo = await sourceClient.identify(); + console.log("Claiming HTLC transaction of", source); + let success = false; + let retries = 1; + const retries_limit = 25; + while (!success && retries <= retries_limit) { + await waitFor( + async () => { + try { + this.lastResult = await sourceClient.claimHtlc({ + output: this.lastResult.output_hash, + pre_image: this.lastResult.pre_image, + fee_per_gram: feePerGram, + }); + } catch (error) { + console.log(error); + return false; + } + return true; + }, + true, + 20 * 1000, + 5 * 1000, + 5 + ); + + success = this.lastResult.results.is_success; + if (!success) { + const wait_seconds = 5; + console.log( + " " + + lastResult.results.failure_message + + ", trying again after " + + wait_seconds + + "s (" + + retries + + " of " + + retries_limit + + ")" + ); + await sleep(wait_seconds * 1000); + retries++; + } + } + + if (success) { + this.addTransaction( + sourceInfo.public_key, + this.lastResult.results.transaction_id + ); + } + expect(success).to.equal(true); + //lets now wait for this transaction to be at least broadcast before we continue. + await waitFor( + async () => + sourceClient.isTransactionAtLeastBroadcast( + this.lastResult.results.transaction_id + ), + true, + 60 * 1000, + 5 * 1000, + 5 + ); + + let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( + this.lastResult.results.transaction_id + ); + + expect(transactionPending).to.equal(true); + } +); + When( /I send(.*) uT without waiting for broadcast from wallet (.*) to wallet (.*) at fee (.*)/, { timeout: 20 * 1000 }, diff --git a/integration_tests/helpers/walletClient.js b/integration_tests/helpers/walletClient.js index c30d497b13..9bbd06c639 100644 --- a/integration_tests/helpers/walletClient.js +++ b/integration_tests/helpers/walletClient.js @@ -148,6 +148,14 @@ class WalletClient { return await this.client.transfer(args); } + async sendHtlc(args) { + return await this.client.SendShaAtomicSwapTransaction(args); + } + + async claimHtlc(args) { + return await this.client.claimShaAtomicSwapTransaction(args); + } + async importUtxos(outputs) { return await this.client.importUtxos({ outputs: outputs, diff --git a/integration_tests/package-lock.json b/integration_tests/package-lock.json index 16ee31915f..059a7730c4 100644 --- a/integration_tests/package-lock.json +++ b/integration_tests/package-lock.json @@ -1,8 +1,4424 @@ { "name": "integration_tests", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "integration_tests", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@cucumber/pretty-formatter": "^1.0.0-alpha.1", + "archiver": "^5.3.0", + "axios": "^0.21.4", + "clone-deep": "^4.0.1", + "csv-parser": "^3.0.0", + "dateformat": "^3.0.3", + "glob": "^7.2.0", + "hex64": "^0.4.0", + "jszip": "^3.7.1", + "nvm": "^0.0.4", + "sha3": "^2.1.3", + "synchronized-promise": "^0.3.1", + "tari_crypto": "^0.9.1", + "utf8": "^3.0.0", + "wallet-grpc-client": "file:../clients/wallet_grpc_client" + }, + "devDependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@babel/eslint-plugin": "^7.14.5", + "@cucumber/cucumber": "^8.0.0-rc.1", + "@cucumber/pretty-formatter": "^1.0.0-alpha.1", + "@grpc/grpc-js": "^1.4.4", + "@grpc/proto-loader": "^0.5.5", + "blakejs": "^1.1.0", + "chai": "^4.2.0", + "cucumber-html-reporter": "^5.5.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-config-standard": "^16.0.2", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-promise": "^4.3.1", + "ffi-napi": "^4.0.3", + "grpc-promise": "^1.4.0", + "husky": "^6.0.0", + "prettier": "^2.4.1", + "ref-napi": "^3.0.3" + } + }, + "../clients/wallet_grpc_client": { + "name": "@tari/wallet-grpc-client", + "version": "0.0.1", + "dependencies": { + "@grpc/grpc-js": "^1.3.6", + "@grpc/proto-loader": "^0.5.5", + "grpc-promise": "^1.4.0" + } + }, + "../clients/wallet_grpc_client/node_modules/@grpc/grpc-js": { + "version": "1.3.6", + "integrity": "sha512-v7+LQFbqZKmd/Tvf5/j1Xlbq6jXL/4d+gUtm2TNX4QiEC3ELWADmGr2dGlUyLl6aKTuYfsN72vAsO5zmavYkEg==", + "dependencies": { + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "../clients/wallet_grpc_client/node_modules/@grpc/proto-loader": { + "version": "0.5.6", + "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + }, + "engines": { + "node": ">=6" + } + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/base64": { + "version": "1.1.2", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/float": { + "version": "1.0.2", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/path": { + "version": "1.1.2", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/pool": { + "version": "1.1.0", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "../clients/wallet_grpc_client/node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "../clients/wallet_grpc_client/node_modules/@types/long": { + "version": "4.0.1", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "../clients/wallet_grpc_client/node_modules/@types/node": { + "version": "16.3.2", + "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==" + }, + "../clients/wallet_grpc_client/node_modules/grpc-promise": { + "version": "1.4.0", + "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==" + }, + "../clients/wallet_grpc_client/node_modules/lodash.camelcase": { + "version": "4.3.0", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "../clients/wallet_grpc_client/node_modules/long": { + "version": "4.0.0", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "../clients/wallet_grpc_client/node_modules/protobufjs": { + "version": "6.11.2", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.0.tgz", + "integrity": "sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.0.tgz", + "integrity": "sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.0", + "@babel/helper-compilation-targets": "^7.16.0", + "@babel/helper-module-transforms": "^7.16.0", + "@babel/helpers": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.16.3.tgz", + "integrity": "sha512-iB4ElZT0jAt7PKVaeVulOECdGe6UnmA/O0P9jlF5g5GBOwDVbna8AXhHRu4s27xQf6OkveyA8iTDv1jHdDejgQ==", + "dev": true, + "dependencies": { + "eslint-scope": "^5.1.1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/eslint-plugin": { + "version": "7.14.5", + "integrity": "sha512-nzt/YMnOOIRikvSn2hk9+W2omgJBy6U8TN0R+WTTmqapA+HnZTuviZaketdTE9W7/k/+E/DfZlt1ey1NSE39pg==", + "dev": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/eslint-parser": ">=7.11.0", + "eslint": ">=7.5.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", + "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", + "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.16.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", + "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", + "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", + "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz", + "integrity": "sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", + "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz", + "integrity": "sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-replace-supers": "^7.16.0", + "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", + "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz", + "integrity": "sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.16.0", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", + "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", + "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.15.7", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.3.tgz", + "integrity": "sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.3", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.3.tgz", + "integrity": "sha512-dcNwU1O4sx57ClvLBVFbEgx0UZWfd0JQX5X6fxFRCLHelFBGXFfSz6Y0FAq2PEwUqlqLkdVjVr4VASEOuUnLJw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", + "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz", + "integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.0", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/parser": "^7.16.3", + "@babel/types": "^7.16.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.15.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cucumber/create-meta": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@cucumber/create-meta/-/create-meta-6.0.1.tgz", + "integrity": "sha512-oaNFVBAfduO0GJ1xUtgfGZHvg6+CH56DYaGWPAraayLxvtsQwaOnBYMzzaccGHty/Q6sksQ+IIZK3SuGkTmdvg==", + "dev": true, + "dependencies": { + "@cucumber/messages": "^17.0.1" + } + }, + "node_modules/@cucumber/cucumber": { + "version": "8.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@cucumber/cucumber/-/cucumber-8.0.0-rc.1.tgz", + "integrity": "sha512-NULLICYYNr0bSig2V/JiHzjONBONPeAo9/iBVoag4P7/GB4ZIeRGaBVzl7FaG8C3blx0AfTh3zfn9h+flFawQA==", + "dev": true, + "dependencies": { + "@cucumber/create-meta": "6.0.1", + "@cucumber/cucumber-expressions": "^14.0.0", + "@cucumber/gherkin": "^22.0.0", + "@cucumber/gherkin-streams": "^4.0.0", + "@cucumber/html-formatter": "^17.0.0", + "@cucumber/messages": "^17.1.1", + "@cucumber/tag-expressions": "^4.1.0", + "assertion-error-formatter": "^3.0.0", + "capital-case": "^1.0.4", + "cli-table3": "^0.6.0", + "colors": "^1.4.0", + "commander": "^8.0.0", + "duration": "^0.2.2", + "durations": "^3.4.2", + "figures": "^3.2.0", + "glob": "^7.1.6", + "indent-string": "^4.0.0", + "is-stream": "^2.0.0", + "knuth-shuffle-seeded": "^1.0.6", + "mz": "^2.7.0", + "progress": "^2.0.3", + "resolve": "^1.19.0", + "resolve-pkg": "^2.0.0", + "stack-chain": "^2.0.0", + "stacktrace-js": "^2.0.2", + "string-argv": "^0.3.1", + "tmp": "^0.2.1", + "util-arity": "^1.1.0", + "verror": "^1.10.0" + }, + "bin": { + "cucumber-js": "bin/cucumber-js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cucumber/cucumber-expressions": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/cucumber-expressions/-/cucumber-expressions-14.0.0.tgz", + "integrity": "sha512-QiuFBrj4dZRc1Igvp2/nOjUNFyDtO7uHTrzgY9DbwzebYAYOvM6CKGOSxSuPUzxowuc1nuRkzJfFUI1kHaZgPQ==", + "dev": true, + "dependencies": { + "regexp-match-indices": "1.0.2" + } + }, + "node_modules/@cucumber/gherkin": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-22.0.0.tgz", + "integrity": "sha512-D5OghXE8kkZm7pcwo8TvQMgrrXGMXEjERdKLU0T7dQIbc6k0BmMX8dTRh2cwAjH8c7vhwdd0qLU8FPQgGGj+bg==", + "dev": true, + "dependencies": { + "@cucumber/message-streams": "^3.0.0", + "@cucumber/messages": "^17.1.1" + } + }, + "node_modules/@cucumber/gherkin-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin-streams/-/gherkin-streams-4.0.0.tgz", + "integrity": "sha512-b/guGNeuxr3ghoJOK47QpLhwa2BOdRq+cs2hBYulMLPTiVfwvRBiZlq7P6xdjR9dIpUKBSpzYR6NwaLMgV5DTg==", + "dev": true, + "dependencies": { + "@cucumber/gherkin": "^21.0.0", + "@cucumber/message-streams": "^3.0.0", + "@cucumber/messages": "^17.1.0", + "commander": "8.1.0", + "source-map-support": "0.5.19" + }, + "bin": { + "gherkin-javascript": "bin/gherkin" + } + }, + "node_modules/@cucumber/gherkin-streams/node_modules/@cucumber/gherkin": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-21.0.0.tgz", + "integrity": "sha512-S6YFmTg56iEn563ReePL6Sygb77vwYrGHEr7NwuLIgg20Hi1pp7P80BAYVYNRgU7nK9vG2II9O6kaZbiOXF/5g==", + "dev": true, + "dependencies": { + "@cucumber/message-streams": "^3.0.0", + "@cucumber/messages": "^17.1.0" + } + }, + "node_modules/@cucumber/gherkin-streams/node_modules/commander": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz", + "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cucumber/html-formatter": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/html-formatter/-/html-formatter-17.0.0.tgz", + "integrity": "sha512-yegA8LY1HYUONyMtTvAYj+aG4zc/6WRtKQxqJahjcdmjgXWcL1BTe8y0lw4BFVqFjaZNI9onOM5KDnMHDm3J/w==", + "dev": true, + "dependencies": { + "@cucumber/messages": "^17.1.0", + "commander": "8.1.0", + "source-map-support": "0.5.19" + }, + "bin": { + "cucumber-html-formatter": "bin/cucumber-html-formatter.js" + } + }, + "node_modules/@cucumber/html-formatter/node_modules/commander": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz", + "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cucumber/message-streams": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/message-streams/-/message-streams-3.0.0.tgz", + "integrity": "sha512-ABx91nKUebV8mLmpf7BsB3bmQ57CDAfj2EIZswThz+nJHYPAFlZ1JewI6ykFsR9RzJ7/QhgQs0KHeQh7nH/u1Q==", + "dev": true, + "dependencies": { + "@cucumber/messages": "^17.0.0" + } + }, + "node_modules/@cucumber/messages": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-17.1.1.tgz", + "integrity": "sha512-KQMn2Ag+1g1CXp/zKQ7LLqmuHjuQwuXw0N2u5SrDk8r72zPt36SxmDSJK7w6HiFTI+3p5ZuzwLi4S5jop3Tx4g==", + "dev": true, + "dependencies": { + "@types/uuid": "8.3.1", + "class-transformer": "0.4.0", + "reflect-metadata": "0.1.13", + "uuid": "8.3.2" + } + }, + "node_modules/@cucumber/messages/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@cucumber/pretty-formatter": { + "version": "1.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@cucumber/pretty-formatter/-/pretty-formatter-1.0.0-alpha.1.tgz", + "integrity": "sha512-emVFdRkEFAqksd3X9cMWn7cOE2fIPB0aTwZAFZPMO55ZRf+7IaZ7VlEY2Pd5qPxhTXNmyZvaBf6AOoZmx47pmA==", + "dev": true, + "dependencies": { + "ansi-styles": "^5.0.0", + "cli-table3": "^0.6.0", + "figures": "^3.2.0", + "ts-dedent": "^2.0.0" + }, + "peerDependencies": { + "@cucumber/cucumber": ">=7.0.0", + "@cucumber/messages": "*" + } + }, + "node_modules/@cucumber/pretty-formatter/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@cucumber/tag-expressions": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@cucumber/tag-expressions/-/tag-expressions-4.1.0.tgz", + "integrity": "sha512-chTnjxV3vryL75N90wJIMdMafXmZoO2JgNJLYpsfcALL2/IQrRiny3vM9DgD5RDCSt1LNloMtb7rGey9YWxCsA==", + "dev": true + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.11.0", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.4.4.tgz", + "integrity": "sha512-a6222b7Dl6fIlMgzVl7e+NiRoLiZFbpcwvBH2Oli56Bn7W4/3Ld+86hK4ffPn5rx2DlDidmIcvIJiOQXyhv9gA==", + "dev": true, + "dependencies": { + "@grpc/proto-loader": "^0.6.4", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.6.tgz", + "integrity": "sha512-cdMaPZ8AiFz6ua6PUbP+LKbhwJbFXnrQ/mlnKGUyzDUZ3wp7vPLksnmLCBX6SHgSmjX7CbNVNLFYD5GmmjO4GQ==", + "dev": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.1.1" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.5.6", + "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", + "dev": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.0", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "dev": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "dev": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "dev": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "dev": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "dev": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "dev": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "dev": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.1", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.10.3", + "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", + "dev": true + }, + "node_modules/acorn": { + "version": "7.4.1", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "node_modules/archiver": { + "version": "5.3.0", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.7", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-includes": { + "version": "3.1.4", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.2.5", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/assertion-error-formatter": { + "version": "3.0.0", + "integrity": "sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==", + "dev": true, + "dependencies": { + "diff": "^4.0.1", + "pad-right": "^0.2.2", + "repeat-string": "^1.6.1" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.1", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + }, + "node_modules/axios": { + "version": "0.21.4", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bindings": { + "version": "1.5.0", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/blakejs": { + "version": "1.1.1", + "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.18.0.tgz", + "integrity": "sha512-ER2M0g5iAR84fS/zjBDqEgU6iO5fS9JI2EkHr5zxDxYEFk3LjhU9Vpp/INb6RMQphxko7PDV1FH38H/qVP5yCA==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001280", + "electron-to-chromium": "^1.3.896", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.2", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001280", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001280.tgz", + "integrity": "sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/chai": { + "version": "4.3.4", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/class-transformer": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.4.0.tgz", + "integrity": "sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==", + "dev": true + }, + "node_modules/cli-table3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", + "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "^1.1.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/compress-commons": { + "version": "4.1.1", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/crc-32": { + "version": "1.2.0", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.2", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csv-parser": { + "version": "3.0.0", + "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "csv-parser": "bin/csv-parser" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cucumber-html-reporter": { + "version": "5.5.0", + "integrity": "sha512-kF7vIwvTe7we7Wp/5uNZVZk+Ryozb688LpNvCNhou6N0RmLYPqaoV2aiN8GIB94JUBpribtlq6kDkEUHwxBVeQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "find": "^0.3.0", + "fs-extra": "^8.1.0", + "js-base64": "^2.3.2", + "jsonfile": "^5.0.0", + "lodash": "^4.17.11", + "node-emoji": "^1.10.0", + "open": "^6.4.0", + "uuid": "^3.3.3" + } + }, + "node_modules/d": { + "version": "1.0.1", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "engines": { + "node": "*" + } + }, + "node_modules/deasync": { + "version": "0.1.23", + "integrity": "sha512-CGZSokFwidI50GOAmkz/7z3QdMzTQqAiUOzt95PuhKgi6VVztn9D03ZCzzi93uUWlp/v6A9osvNWpIvqHvKjTA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^1.7.1" + }, + "engines": { + "node": ">=0.11.0" + } + }, + "node_modules/deasync/node_modules/node-addon-api": { + "version": "1.7.2", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" + }, + "node_modules/debug": { + "version": "4.3.2", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.1.3", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/duration": { + "version": "0.2.2", + "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.46" + } + }, + "node_modules/durations": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/durations/-/durations-3.4.2.tgz", + "integrity": "sha512-V/lf7y33dGaypZZetVI1eu7BmvkbC4dItq12OElLRpKuaU5JxQstV2zHwLv8P7cNbQ+KL1WD80zMCTx5dNC4dg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.896", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.896.tgz", + "integrity": "sha512-NcGkBVXePiuUrPLV8IxP43n1EOtdg+dudVjrfVEUd/bOqpQUFZ2diL5PPYzbgEhZFEltdXV3AcyKwGnEQ5lhMA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/error-stack-parser": { + "version": "2.0.6", + "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "dev": true, + "dependencies": { + "stackframe": "^1.1.1" + } + }, + "node_modules/es-abstract": { + "version": "1.19.1", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.53", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.3.0", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-standard": { + "version": "16.0.3", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^7.12.1", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1 || ^5.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz", + "integrity": "sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0", + "pkg-dir": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz", + "integrity": "sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.1", + "has": "^1.0.3", + "is-core-module": "^2.8.0", + "is-glob": "^4.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.5", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.11.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-plugin-es": { + "version": "3.0.1", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-node/node_modules/ignore": { + "version": "5.1.8", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-promise": { + "version": "4.3.1", + "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.11.0", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.5", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ext": { + "version": "1.6.0", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "dev": true, + "dependencies": { + "type": "^2.5.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.5.0", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", + "dev": true + }, + "node_modules/extsprintf": { + "version": "1.4.0", + "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/ffi-napi": { + "version": "4.0.3", + "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "debug": "^4.1.1", + "get-uv-event-loop-napi-h": "^1.0.5", + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.1", + "ref-napi": "^2.0.1 || ^3.0.2", + "ref-struct-di": "^1.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/find": { + "version": "0.3.0", + "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", + "dev": true, + "dependencies": { + "traverse-chain": "~0.1.0" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.2", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.14.4", + "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-extra/node_modules/jsonfile": { + "version": "4.0.0", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-from-current-process-h": { + "version": "1.0.2", + "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==", + "dev": true + }, + "node_modules/get-uv-event-loop-napi-h": { + "version": "1.0.6", + "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", + "dev": true, + "dependencies": { + "get-symbol-from-current-process-h": "^1.0.1" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.8", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + }, + "node_modules/grpc-promise": { + "version": "1.4.0", + "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hex64": { + "version": "0.4.0", + "integrity": "sha1-rRB4rIHVfXLeYjKxADvE9vsCh8A=", + "bin": { + "hex64": "bin/hex64" + } + }, + "node_modules/husky": { + "version": "6.0.0", + "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "4.0.6", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.6", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.1", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.1", + "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-base64": { + "version": "2.6.4", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.0", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "5.0.0", + "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", + "dev": true, + "dependencies": { + "universalify": "^0.1.2" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jszip": { + "version": "3.7.1", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.7", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/knuth-shuffle-seeded": { + "version": "1.0.6", + "integrity": "sha1-AfG2VzOqdUDuCNiwF0Fk0iCB5OE=", + "dev": true, + "dependencies": { + "seed-random": "~2.2.0" + } + }, + "node_modules/lazystream": { + "version": "1.0.0", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.7", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, + "node_modules/long": { + "version": "4.0.0", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/ms": { + "version": "2.1.2", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/next-tick": { + "version": "1.0.0", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.3.0", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", + "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nvm": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/nvm/-/nvm-0.0.4.tgz", + "integrity": "sha1-OKF46dMbKDUIyS0VydqGHRqSELw=", + "deprecated": "This is NOT the correct nvm. Visit https://nvm.sh and use the curl command to install it.", + "bin": { + "nvm": "bin/nvm" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.11.0", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "6.4.0", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pad-right": { + "version": "0.2.2", + "integrity": "sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q=", + "dev": true, + "dependencies": { + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/pkg-dir": { + "version": "2.0.0", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "dependencies": { + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.4.1", + "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/printj": { + "version": "1.1.2", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.3", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/protobufjs": { + "version": "6.11.2", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.1", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/ref-napi": { + "version": "3.0.3", + "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "debug": "^4.1.1", + "get-symbol-from-current-process-h": "^1.0.2", + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.1" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/ref-struct-di": { + "version": "1.1.1", + "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", + "dev": true, + "dependencies": { + "debug": "^3.1.0" + } + }, + "node_modules/ref-struct-di/node_modules/debug": { + "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "node_modules/regexp-match-indices": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regexp-match-indices/-/regexp-match-indices-1.0.2.tgz", + "integrity": "sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==", + "dev": true, + "dependencies": { + "regexp-tree": "^0.1.11" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", + "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", + "dev": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-2.0.0.tgz", + "integrity": "sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/seed-random": { + "version": "2.2.0", + "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=", + "dev": true + }, + "node_modules/semver": { + "version": "6.3.0", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sha3": { + "version": "2.1.4", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", + "dependencies": { + "buffer": "6.0.3" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stack-chain": { + "version": "2.0.0", + "integrity": "sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==", + "dev": true + }, + "node_modules/stack-generator": { + "version": "2.0.5", + "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "dev": true, + "dependencies": { + "stackframe": "^1.1.1" + } + }, + "node_modules/stackframe": { + "version": "1.2.0", + "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", + "dev": true + }, + "node_modules/stacktrace-gps": { + "version": "3.0.4", + "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", + "dev": true, + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.1.1" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "dev": true, + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-argv": { + "version": "0.3.1", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/synchronized-promise": { + "version": "0.3.1", + "integrity": "sha512-Iy+JzrERSUrwpOHUDku8HHIddk8V6iLG9bPIzboP2i5RYkn2eSmRB8waSaX7Rc/+DUUsnFsoOHrmniwOp9BOgw==", + "dependencies": { + "deasync": "^0.1.15" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/table": { + "version": "6.7.2", + "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.6.3", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tari_crypto": { + "version": "0.9.1", + "integrity": "sha512-K7LAtwQQKCeTH5CyyO8d/TiPDEePRaJ4e6+hrxpWv6jlkkAiS4m6csBuVqpSjyAlKeP8cQJpUQX2n22akOuZVg==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/traverse-chain": { + "version": "0.1.0", + "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", + "dev": true + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true, + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.11.0", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.1", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/type": { + "version": "1.2.0", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8": { + "version": "3.0.0", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + }, + "node_modules/util-arity": { + "version": "1.1.0", + "integrity": "sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/uuid": { + "version": "3.4.0", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/verror": { + "version": "1.10.0", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/wallet-grpc-client": { + "resolved": "../clients/wallet_grpc_client", + "link": true + }, + "node_modules/which": { + "version": "2.0.2", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/zip-stream": { + "version": "4.1.0", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "dependencies": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.16.0", @@ -11,19 +4427,6 @@ "dev": true, "requires": { "@babel/highlight": "^7.16.0" - }, - "dependencies": { - "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - } } }, "@babel/compat-data": { @@ -68,7 +4471,6 @@ }, "@babel/eslint-plugin": { "version": "7.14.5", - "resolved": false, "integrity": "sha512-nzt/YMnOOIRikvSn2hk9+W2omgJBy6U8TN0R+WTTmqapA+HnZTuviZaketdTE9W7/k/+E/DfZlt1ey1NSE39pg==", "dev": true, "requires": { @@ -202,13 +4604,12 @@ }, "@babel/helper-validator-identifier": { "version": "7.15.7", - "resolved": false, "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, "@babel/helper-validator-option": { "version": "7.14.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", "dev": true }, @@ -224,12 +4625,12 @@ } }, "@babel/highlight": { - "version": "7.14.5", - "resolved": false, - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.15.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } @@ -426,6 +4827,7 @@ "version": "1.0.0-alpha.1", "resolved": "https://registry.npmjs.org/@cucumber/pretty-formatter/-/pretty-formatter-1.0.0-alpha.1.tgz", "integrity": "sha512-emVFdRkEFAqksd3X9cMWn7cOE2fIPB0aTwZAFZPMO55ZRf+7IaZ7VlEY2Pd5qPxhTXNmyZvaBf6AOoZmx47pmA==", + "dev": true, "requires": { "ansi-styles": "^5.0.0", "cli-table3": "^0.6.0", @@ -436,7 +4838,8 @@ "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true } } }, @@ -448,7 +4851,6 @@ }, "@eslint/eslintrc": { "version": "0.4.3", - "resolved": false, "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { @@ -465,7 +4867,6 @@ "dependencies": { "globals": { "version": "13.11.0", - "resolved": false, "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", "dev": true, "requires": { @@ -474,7 +4875,6 @@ }, "type-fest": { "version": "0.20.2", - "resolved": false, "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } @@ -507,7 +4907,6 @@ }, "@grpc/proto-loader": { "version": "0.5.6", - "resolved": false, "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", "dev": true, "requires": { @@ -517,7 +4916,6 @@ }, "@humanwhocodes/config-array": { "version": "0.5.0", - "resolved": false, "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "requires": { @@ -528,37 +4926,31 @@ }, "@humanwhocodes/object-schema": { "version": "1.2.0", - "resolved": false, "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, "@protobufjs/aspromise": { "version": "1.1.2", - "resolved": false, "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", "dev": true }, "@protobufjs/base64": { "version": "1.1.2", - "resolved": false, "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "dev": true }, "@protobufjs/codegen": { "version": "2.0.4", - "resolved": false, "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", "dev": true }, "@protobufjs/eventemitter": { "version": "1.1.0", - "resolved": false, "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", "dev": true }, "@protobufjs/fetch": { "version": "1.1.0", - "resolved": false, "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", "dev": true, "requires": { @@ -568,49 +4960,41 @@ }, "@protobufjs/float": { "version": "1.0.2", - "resolved": false, "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", "dev": true }, "@protobufjs/inquire": { "version": "1.1.0", - "resolved": false, "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", "dev": true }, "@protobufjs/path": { "version": "1.1.2", - "resolved": false, "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", "dev": true }, "@protobufjs/pool": { "version": "1.1.0", - "resolved": false, "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", "dev": true }, "@protobufjs/utf8": { "version": "1.1.0", - "resolved": false, "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", "dev": true }, "@types/json5": { "version": "0.0.29", - "resolved": false, "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/long": { "version": "4.0.1", - "resolved": false, "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", "dev": true }, "@types/node": { "version": "16.10.3", - "resolved": false, "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", "dev": true }, @@ -622,19 +5006,17 @@ }, "acorn": { "version": "7.4.1", - "resolved": false, "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-jsx": { "version": "5.3.2", - "resolved": false, "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv": { "version": "6.12.6", - "resolved": false, "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { @@ -646,18 +5028,16 @@ }, "ansi-colors": { "version": "4.1.1", - "resolved": false, "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-regex": { "version": "5.0.1", - "resolved": false, - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "ansi-styles": { "version": "3.2.1", - "resolved": false, "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { @@ -666,13 +5046,11 @@ }, "any-promise": { "version": "1.3.0", - "resolved": false, "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", "dev": true }, "archiver": { "version": "5.3.0", - "resolved": false, "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", "requires": { "archiver-utils": "^2.1.0", @@ -686,7 +5064,6 @@ }, "archiver-utils": { "version": "2.1.0", - "resolved": false, "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "requires": { "glob": "^7.1.4", @@ -703,7 +5080,6 @@ "dependencies": { "readable-stream": { "version": "2.3.7", - "resolved": false, "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", @@ -717,7 +5093,6 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -727,7 +5102,6 @@ }, "argparse": { "version": "1.0.10", - "resolved": false, "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { @@ -736,7 +5110,6 @@ }, "array-includes": { "version": "3.1.4", - "resolved": false, "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", "dev": true, "requires": { @@ -749,7 +5122,6 @@ }, "array.prototype.flat": { "version": "1.2.5", - "resolved": false, "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", "dev": true, "requires": { @@ -760,19 +5132,16 @@ }, "assert-plus": { "version": "1.0.0", - "resolved": false, "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "assertion-error": { "version": "1.1.0", - "resolved": false, "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "assertion-error-formatter": { "version": "3.0.0", - "resolved": false, "integrity": "sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==", "dev": true, "requires": { @@ -783,18 +5152,15 @@ }, "astral-regex": { "version": "2.0.0", - "resolved": false, "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "async": { "version": "3.2.1", - "resolved": false, "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" }, "axios": { "version": "0.21.4", - "resolved": false, "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "requires": { "follow-redirects": "^1.14.0" @@ -802,17 +5168,14 @@ }, "balanced-match": { "version": "1.0.2", - "resolved": false, "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", - "resolved": false, "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bindings": { "version": "1.5.0", - "resolved": false, "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "requires": { "file-uri-to-path": "1.0.0" @@ -820,7 +5183,6 @@ }, "bl": { "version": "4.1.0", - "resolved": false, "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "requires": { "buffer": "^5.5.0", @@ -830,7 +5192,6 @@ "dependencies": { "buffer": { "version": "5.7.1", - "resolved": false, "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "requires": { "base64-js": "^1.3.1", @@ -841,13 +5202,11 @@ }, "blakejs": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==", "dev": true }, "brace-expansion": { "version": "1.1.11", - "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", @@ -855,13 +5214,13 @@ } }, "browserslist": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.6.tgz", - "integrity": "sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.18.0.tgz", + "integrity": "sha512-ER2M0g5iAR84fS/zjBDqEgU6iO5fS9JI2EkHr5zxDxYEFk3LjhU9Vpp/INb6RMQphxko7PDV1FH38H/qVP5yCA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001274", - "electron-to-chromium": "^1.3.886", + "caniuse-lite": "^1.0.30001280", + "electron-to-chromium": "^1.3.896", "escalade": "^3.1.1", "node-releases": "^2.0.1", "picocolors": "^1.0.0" @@ -869,7 +5228,6 @@ }, "buffer": { "version": "6.0.3", - "resolved": false, "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "requires": { "base64-js": "^1.3.1", @@ -878,7 +5236,6 @@ }, "buffer-crc32": { "version": "0.2.13", - "resolved": false, "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, "buffer-from": { @@ -889,7 +5246,6 @@ }, "call-bind": { "version": "1.0.2", - "resolved": false, "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, "requires": { @@ -899,14 +5255,13 @@ }, "callsites": { "version": "3.1.0", - "resolved": false, "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "caniuse-lite": { - "version": "1.0.30001279", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz", - "integrity": "sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ==", + "version": "1.0.30001280", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001280.tgz", + "integrity": "sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA==", "dev": true }, "capital-case": { @@ -922,7 +5277,6 @@ }, "chai": { "version": "4.3.4", - "resolved": false, "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, "requires": { @@ -936,7 +5290,6 @@ }, "chalk": { "version": "2.4.2", - "resolved": false, "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { @@ -947,7 +5300,6 @@ }, "check-error": { "version": "1.0.2", - "resolved": false, "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, @@ -961,6 +5313,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", + "dev": true, "requires": { "colors": "^1.1.2", "object-assign": "^4.1.0", @@ -980,7 +5333,6 @@ }, "clone-deep": { "version": "4.0.1", - "resolved": false, "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "requires": { "is-plain-object": "^2.0.4", @@ -990,7 +5342,6 @@ }, "color-convert": { "version": "1.9.3", - "resolved": false, "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { @@ -999,14 +5350,13 @@ }, "color-name": { "version": "1.1.3", - "resolved": false, "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "colors": { "version": "1.4.0", - "resolved": false, - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true }, "commander": { "version": "8.3.0", @@ -1016,7 +5366,6 @@ }, "compress-commons": { "version": "4.1.1", - "resolved": false, "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", "requires": { "buffer-crc32": "^0.2.13", @@ -1027,12 +5376,10 @@ }, "concat-map": { "version": "0.0.1", - "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "convert-source-map": { "version": "1.8.0", - "resolved": false, "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { @@ -1041,12 +5388,10 @@ }, "core-util-is": { "version": "1.0.2", - "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "crc-32": { "version": "1.2.0", - "resolved": false, "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", "requires": { "exit-on-epipe": "~1.0.1", @@ -1055,7 +5400,6 @@ }, "crc32-stream": { "version": "4.0.2", - "resolved": false, "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", "requires": { "crc-32": "^1.2.0", @@ -1064,7 +5408,6 @@ }, "cross-spawn": { "version": "7.0.3", - "resolved": false, "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { @@ -1075,7 +5418,6 @@ }, "csv-parser": { "version": "3.0.0", - "resolved": false, "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", "requires": { "minimist": "^1.2.0" @@ -1083,7 +5425,6 @@ }, "cucumber-html-reporter": { "version": "5.5.0", - "resolved": false, "integrity": "sha512-kF7vIwvTe7we7Wp/5uNZVZk+Ryozb688LpNvCNhou6N0RmLYPqaoV2aiN8GIB94JUBpribtlq6kDkEUHwxBVeQ==", "dev": true, "requires": { @@ -1100,7 +5441,6 @@ }, "d": { "version": "1.0.1", - "resolved": false, "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", "dev": true, "requires": { @@ -1110,12 +5450,10 @@ }, "dateformat": { "version": "3.0.3", - "resolved": false, "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" }, "deasync": { "version": "0.1.23", - "resolved": false, "integrity": "sha512-CGZSokFwidI50GOAmkz/7z3QdMzTQqAiUOzt95PuhKgi6VVztn9D03ZCzzi93uUWlp/v6A9osvNWpIvqHvKjTA==", "requires": { "bindings": "^1.5.0", @@ -1124,14 +5462,12 @@ "dependencies": { "node-addon-api": { "version": "1.7.2", - "resolved": false, "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==" } } }, "debug": { "version": "4.3.2", - "resolved": false, "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { @@ -1140,7 +5476,6 @@ }, "deep-eql": { "version": "3.0.1", - "resolved": false, "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { @@ -1149,13 +5484,11 @@ }, "deep-is": { "version": "0.1.4", - "resolved": false, "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "define-properties": { "version": "1.1.3", - "resolved": false, "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { @@ -1164,13 +5497,11 @@ }, "diff": { "version": "4.0.2", - "resolved": false, "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, "doctrine": { "version": "3.0.0", - "resolved": false, "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { @@ -1179,7 +5510,6 @@ }, "duration": { "version": "0.2.2", - "resolved": false, "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", "dev": true, "requires": { @@ -1194,19 +5524,18 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.894", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.894.tgz", - "integrity": "sha512-WY8pA4irAZ4cm/Pr7YFPtPLVqj3nU6d0SbfoHF6M7HZNONfPdAnYAarumqQ75go2LuN72uO9wGuCEqnfya/ytg==", + "version": "1.3.896", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.896.tgz", + "integrity": "sha512-NcGkBVXePiuUrPLV8IxP43n1EOtdg+dudVjrfVEUd/bOqpQUFZ2diL5PPYzbgEhZFEltdXV3AcyKwGnEQ5lhMA==", "dev": true }, "emoji-regex": { "version": "8.0.0", - "resolved": false, - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "end-of-stream": { "version": "1.4.4", - "resolved": false, "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "requires": { "once": "^1.4.0" @@ -1214,7 +5543,6 @@ }, "enquirer": { "version": "2.3.6", - "resolved": false, "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { @@ -1223,7 +5551,6 @@ }, "error-stack-parser": { "version": "2.0.6", - "resolved": false, "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", "dev": true, "requires": { @@ -1232,7 +5559,6 @@ }, "es-abstract": { "version": "1.19.1", - "resolved": false, "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", "dev": true, "requires": { @@ -1260,7 +5586,6 @@ }, "es-to-primitive": { "version": "1.2.1", - "resolved": false, "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { @@ -1271,7 +5596,6 @@ }, "es5-ext": { "version": "0.10.53", - "resolved": false, "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dev": true, "requires": { @@ -1282,7 +5606,6 @@ }, "es6-iterator": { "version": "2.0.3", - "resolved": false, "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { @@ -1293,7 +5616,6 @@ }, "es6-symbol": { "version": "3.1.3", - "resolved": false, "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { @@ -1303,18 +5625,16 @@ }, "escalade": { "version": "3.1.1", - "resolved": false, "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", - "resolved": false, - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "eslint": { "version": "7.32.0", - "resolved": false, "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "requires": { @@ -1362,7 +5682,6 @@ "dependencies": { "@babel/code-frame": { "version": "7.12.11", - "resolved": false, "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "requires": { @@ -1371,7 +5690,6 @@ }, "ansi-styles": { "version": "4.3.0", - "resolved": false, "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { @@ -1380,7 +5698,6 @@ }, "chalk": { "version": "4.1.2", - "resolved": false, "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { @@ -1390,7 +5707,6 @@ }, "color-convert": { "version": "2.0.1", - "resolved": false, "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { @@ -1399,19 +5715,16 @@ }, "color-name": { "version": "1.1.4", - "resolved": false, "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "escape-string-regexp": { "version": "4.0.0", - "resolved": false, "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "globals": { "version": "13.11.0", - "resolved": false, "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", "dev": true, "requires": { @@ -1420,13 +5733,11 @@ }, "has-flag": { "version": "4.0.0", - "resolved": false, "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "semver": { "version": "7.3.5", - "resolved": false, "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { @@ -1435,7 +5746,6 @@ }, "supports-color": { "version": "7.2.0", - "resolved": false, "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { @@ -1444,7 +5754,6 @@ }, "type-fest": { "version": "0.20.2", - "resolved": false, "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } @@ -1452,19 +5761,18 @@ }, "eslint-config-prettier": { "version": "8.3.0", - "resolved": false, "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true + "dev": true, + "requires": {} }, "eslint-config-standard": { "version": "16.0.3", - "resolved": false, "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "dev": true + "dev": true, + "requires": {} }, "eslint-import-resolver-node": { "version": "0.3.6", - "resolved": false, "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", "dev": true, "requires": { @@ -1474,7 +5782,6 @@ "dependencies": { "debug": { "version": "3.2.7", - "resolved": false, "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { @@ -1496,7 +5803,6 @@ "dependencies": { "debug": { "version": "3.2.7", - "resolved": false, "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { @@ -1528,7 +5834,6 @@ "dependencies": { "debug": { "version": "2.6.9", - "resolved": false, "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { @@ -1537,25 +5842,14 @@ }, "doctrine": { "version": "2.1.0", - "resolved": false, "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { "esutils": "^2.0.2" } }, - "is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, "ms": { "version": "2.0.0", - "resolved": false, "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } @@ -1563,7 +5857,6 @@ }, "eslint-plugin-node": { "version": "11.1.0", - "resolved": false, "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "requires": { @@ -1577,7 +5870,6 @@ "dependencies": { "eslint-plugin-es": { "version": "3.0.1", - "resolved": false, "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "requires": { @@ -1587,7 +5879,6 @@ }, "ignore": { "version": "5.1.8", - "resolved": false, "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true } @@ -1595,7 +5886,6 @@ }, "eslint-plugin-prettier": { "version": "3.4.1", - "resolved": false, "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", "dev": true, "requires": { @@ -1604,19 +5894,16 @@ }, "eslint-plugin-promise": { "version": "4.3.1", - "resolved": false, "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", "dev": true }, "eslint-rule-composer": { "version": "0.3.0", - "resolved": false, "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", "dev": true }, "eslint-scope": { "version": "5.1.1", - "resolved": false, "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { @@ -1626,7 +5913,6 @@ }, "eslint-utils": { "version": "2.1.0", - "resolved": false, "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { @@ -1635,7 +5921,6 @@ "dependencies": { "eslint-visitor-keys": { "version": "1.3.0", - "resolved": false, "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true } @@ -1643,13 +5928,11 @@ }, "eslint-visitor-keys": { "version": "2.1.0", - "resolved": false, "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, "espree": { "version": "7.3.1", - "resolved": false, "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { @@ -1660,7 +5943,6 @@ "dependencies": { "eslint-visitor-keys": { "version": "1.3.0", - "resolved": false, "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true } @@ -1668,13 +5950,11 @@ }, "esprima": { "version": "4.0.1", - "resolved": false, "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { "version": "1.4.0", - "resolved": false, "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { @@ -1683,7 +5963,6 @@ "dependencies": { "estraverse": { "version": "5.2.0", - "resolved": false, "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } @@ -1691,7 +5970,6 @@ }, "esrecurse": { "version": "4.3.0", - "resolved": false, "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { @@ -1700,7 +5978,6 @@ "dependencies": { "estraverse": { "version": "5.2.0", - "resolved": false, "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } @@ -1708,24 +5985,20 @@ }, "estraverse": { "version": "4.3.0", - "resolved": false, "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { "version": "2.0.3", - "resolved": false, "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "exit-on-epipe": { "version": "1.0.1", - "resolved": false, "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" }, "ext": { "version": "1.6.0", - "resolved": false, "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", "dev": true, "requires": { @@ -1734,7 +6007,6 @@ "dependencies": { "type": { "version": "2.5.0", - "resolved": false, "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", "dev": true } @@ -1742,37 +6014,31 @@ }, "extsprintf": { "version": "1.4.0", - "resolved": false, "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=", "dev": true }, "fast-deep-equal": { "version": "3.1.3", - "resolved": false, "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-diff": { "version": "1.2.0", - "resolved": false, "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", - "resolved": false, "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-levenshtein": { "version": "2.0.6", - "resolved": false, "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "ffi-napi": { "version": "4.0.3", - "resolved": false, "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", "dev": true, "requires": { @@ -1786,15 +6052,14 @@ }, "figures": { "version": "3.2.0", - "resolved": false, "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, "requires": { "escape-string-regexp": "^1.0.5" } }, "file-entry-cache": { "version": "6.0.1", - "resolved": false, "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { @@ -1803,12 +6068,10 @@ }, "file-uri-to-path": { "version": "1.0.0", - "resolved": false, "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "find": { "version": "0.3.0", - "resolved": false, "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", "dev": true, "requires": { @@ -1817,7 +6080,6 @@ }, "find-up": { "version": "2.1.0", - "resolved": false, "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { @@ -1826,7 +6088,6 @@ }, "flat-cache": { "version": "3.0.4", - "resolved": false, "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { @@ -1836,23 +6097,19 @@ }, "flatted": { "version": "3.2.2", - "resolved": false, "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", "dev": true }, "follow-redirects": { "version": "1.14.4", - "resolved": false, "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" }, "fs-constants": { "version": "1.0.0", - "resolved": false, "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { "version": "8.1.0", - "resolved": false, "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { @@ -1863,7 +6120,6 @@ "dependencies": { "jsonfile": { "version": "4.0.0", - "resolved": false, "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { @@ -1874,24 +6130,20 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "functional-red-black-tree": { "version": "1.0.1", - "resolved": false, "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, "gensync": { "version": "1.0.0-beta.2", - "resolved": false, "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, @@ -1903,13 +6155,11 @@ }, "get-func-name": { "version": "2.0.0", - "resolved": false, "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, "get-intrinsic": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { @@ -1920,7 +6170,6 @@ }, "get-symbol-description": { "version": "1.0.0", - "resolved": false, "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", "dev": true, "requires": { @@ -1930,13 +6179,11 @@ }, "get-symbol-from-current-process-h": { "version": "1.0.2", - "resolved": false, "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==", "dev": true }, "get-uv-event-loop-napi-h": { "version": "1.0.6", - "resolved": false, "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", "dev": true, "requires": { @@ -1945,7 +6192,6 @@ }, "glob": { "version": "7.2.0", - "resolved": false, "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", @@ -1958,7 +6204,6 @@ }, "glob-parent": { "version": "5.1.2", - "resolved": false, "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { @@ -1967,24 +6212,21 @@ }, "globals": { "version": "11.12.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, "graceful-fs": { "version": "4.2.8", - "resolved": false, "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, "grpc-promise": { "version": "1.4.0", - "resolved": false, "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==", "dev": true }, "has": { "version": "1.0.3", - "resolved": false, "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { @@ -1993,25 +6235,21 @@ }, "has-bigints": { "version": "1.0.1", - "resolved": false, "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", "dev": true }, "has-flag": { "version": "3.0.0", - "resolved": false, "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { "version": "1.0.2", - "resolved": false, "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, "has-tostringtag": { "version": "1.0.0", - "resolved": false, "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "dev": true, "requires": { @@ -2020,34 +6258,28 @@ }, "hex64": { "version": "0.4.0", - "resolved": false, "integrity": "sha1-rRB4rIHVfXLeYjKxADvE9vsCh8A=" }, "husky": { "version": "6.0.0", - "resolved": false, "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", "dev": true }, "ieee754": { "version": "1.2.1", - "resolved": false, "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "4.0.6", - "resolved": false, "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "immediate": { "version": "3.0.6", - "resolved": false, "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "import-fresh": { "version": "3.3.0", - "resolved": false, "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { @@ -2057,19 +6289,16 @@ }, "imurmurhash": { "version": "0.1.4", - "resolved": false, "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "indent-string": { "version": "4.0.0", - "resolved": false, "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, "inflight": { "version": "1.0.6", - "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", @@ -2078,12 +6307,10 @@ }, "inherits": { "version": "2.0.4", - "resolved": false, "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "internal-slot": { "version": "1.0.3", - "resolved": false, "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "dev": true, "requires": { @@ -2094,7 +6321,6 @@ }, "is-bigint": { "version": "1.0.4", - "resolved": false, "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "requires": { @@ -2103,7 +6329,6 @@ }, "is-boolean-object": { "version": "1.1.2", - "resolved": false, "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "requires": { @@ -2113,14 +6338,13 @@ }, "is-callable": { "version": "1.2.4", - "resolved": false, "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "dev": true }, "is-core-module": { - "version": "2.7.0", - "resolved": false, - "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", "dev": true, "requires": { "has": "^1.0.3" @@ -2128,7 +6352,6 @@ }, "is-date-object": { "version": "1.0.5", - "resolved": false, "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "requires": { @@ -2137,18 +6360,17 @@ }, "is-extglob": { "version": "2.1.1", - "resolved": false, "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-glob": { "version": "4.0.3", - "resolved": false, "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { @@ -2157,13 +6379,11 @@ }, "is-negative-zero": { "version": "2.0.1", - "resolved": false, "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", "dev": true }, "is-number-object": { "version": "1.0.6", - "resolved": false, "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", "dev": true, "requires": { @@ -2172,7 +6392,6 @@ }, "is-plain-object": { "version": "2.0.4", - "resolved": false, "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "requires": { "isobject": "^3.0.1" @@ -2180,7 +6399,6 @@ }, "is-regex": { "version": "1.1.4", - "resolved": false, "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "requires": { @@ -2190,19 +6408,16 @@ }, "is-shared-array-buffer": { "version": "1.0.1", - "resolved": false, "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", "dev": true }, "is-stream": { "version": "2.0.1", - "resolved": false, "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, "is-string": { "version": "1.0.7", - "resolved": false, "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "requires": { @@ -2211,7 +6426,6 @@ }, "is-symbol": { "version": "1.0.4", - "resolved": false, "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "requires": { @@ -2220,7 +6434,6 @@ }, "is-weakref": { "version": "1.0.1", - "resolved": false, "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", "dev": true, "requires": { @@ -2229,41 +6442,34 @@ }, "is-wsl": { "version": "1.1.0", - "resolved": false, "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, "isarray": { "version": "1.0.0", - "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", - "resolved": false, "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isobject": { "version": "3.0.1", - "resolved": false, "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "js-base64": { "version": "2.6.4", - "resolved": false, "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", "dev": true }, "js-tokens": { "version": "4.0.0", - "resolved": false, "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { "version": "3.14.1", - "resolved": false, "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { @@ -2273,25 +6479,22 @@ }, "jsesc": { "version": "2.5.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "json-schema-traverse": { "version": "0.4.1", - "resolved": false, "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": false, "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, "json5": { "version": "2.2.0", - "resolved": false, "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { @@ -2300,7 +6503,6 @@ }, "jsonfile": { "version": "5.0.0", - "resolved": false, "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", "dev": true, "requires": { @@ -2310,7 +6512,6 @@ }, "jszip": { "version": "3.7.1", - "resolved": false, "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", "requires": { "lie": "~3.3.0", @@ -2321,7 +6522,6 @@ "dependencies": { "readable-stream": { "version": "2.3.7", - "resolved": false, "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", @@ -2335,7 +6535,6 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -2345,12 +6544,10 @@ }, "kind-of": { "version": "6.0.3", - "resolved": false, "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "knuth-shuffle-seeded": { "version": "1.0.6", - "resolved": false, "integrity": "sha1-AfG2VzOqdUDuCNiwF0Fk0iCB5OE=", "dev": true, "requires": { @@ -2359,7 +6556,6 @@ }, "lazystream": { "version": "1.0.0", - "resolved": false, "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "requires": { "readable-stream": "^2.0.5" @@ -2367,7 +6563,6 @@ "dependencies": { "readable-stream": { "version": "2.3.7", - "resolved": false, "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", @@ -2381,7 +6576,6 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -2391,7 +6585,6 @@ }, "levn": { "version": "0.4.1", - "resolved": false, "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { @@ -2401,7 +6594,6 @@ }, "lie": { "version": "3.3.0", - "resolved": false, "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "requires": { "immediate": "~3.0.5" @@ -2409,7 +6601,6 @@ }, "locate-path": { "version": "2.0.0", - "resolved": false, "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { @@ -2419,62 +6610,51 @@ }, "lodash": { "version": "4.17.21", - "resolved": false, "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.camelcase": { "version": "4.3.0", - "resolved": false, "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, "lodash.clonedeep": { "version": "4.5.0", - "resolved": false, "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, "lodash.defaults": { "version": "4.2.0", - "resolved": false, "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, "lodash.difference": { "version": "4.5.0", - "resolved": false, "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" }, "lodash.flatten": { "version": "4.4.0", - "resolved": false, "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" }, "lodash.isplainobject": { "version": "4.0.6", - "resolved": false, "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.merge": { "version": "4.6.2", - "resolved": false, "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, "lodash.truncate": { "version": "4.4.2", - "resolved": false, "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, "lodash.union": { "version": "4.6.0", - "resolved": false, "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "long": { "version": "4.0.0", - "resolved": false, "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", "dev": true }, @@ -2489,7 +6669,6 @@ }, "lru-cache": { "version": "6.0.0", - "resolved": false, "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { @@ -2498,7 +6677,6 @@ }, "minimatch": { "version": "3.0.4", - "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" @@ -2506,18 +6684,15 @@ }, "minimist": { "version": "1.2.5", - "resolved": false, "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "ms": { "version": "2.1.2", - "resolved": false, "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "mz": { "version": "2.7.0", - "resolved": false, "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, "requires": { @@ -2528,13 +6703,11 @@ }, "natural-compare": { "version": "1.4.0", - "resolved": false, "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "next-tick": { "version": "1.0.0", - "resolved": false, "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, @@ -2550,13 +6723,11 @@ }, "node-addon-api": { "version": "3.2.1", - "resolved": false, "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true }, "node-emoji": { "version": "1.11.0", - "resolved": false, "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", "dev": true, "requires": { @@ -2565,7 +6736,6 @@ }, "node-gyp-build": { "version": "4.3.0", - "resolved": false, "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", "dev": true }, @@ -2577,7 +6747,6 @@ }, "normalize-path": { "version": "3.0.0", - "resolved": false, "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "nvm": { @@ -2587,24 +6756,21 @@ }, "object-assign": { "version": "4.1.1", - "resolved": false, - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, "object-inspect": { "version": "1.11.0", - "resolved": false, "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", "dev": true }, "object-keys": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, "object.assign": { "version": "4.1.2", - "resolved": false, "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, "requires": { @@ -2616,7 +6782,6 @@ }, "object.values": { "version": "1.1.5", - "resolved": false, "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, "requires": { @@ -2627,7 +6792,6 @@ }, "once": { "version": "1.4.0", - "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" @@ -2635,7 +6799,6 @@ }, "open": { "version": "6.4.0", - "resolved": false, "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", "dev": true, "requires": { @@ -2644,7 +6807,6 @@ }, "optionator": { "version": "0.9.1", - "resolved": false, "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { @@ -2658,7 +6820,6 @@ }, "p-limit": { "version": "1.3.0", - "resolved": false, "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { @@ -2667,7 +6828,6 @@ }, "p-locate": { "version": "2.0.0", - "resolved": false, "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { @@ -2676,13 +6836,11 @@ }, "p-try": { "version": "1.0.0", - "resolved": false, "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "pad-right": { "version": "0.2.2", - "resolved": false, "integrity": "sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q=", "dev": true, "requires": { @@ -2691,12 +6849,10 @@ }, "pako": { "version": "1.0.11", - "resolved": false, "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parent-module": { "version": "1.0.1", - "resolved": false, "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { @@ -2705,30 +6861,25 @@ }, "path-exists": { "version": "3.0.0", - "resolved": false, "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", - "resolved": false, "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { "version": "1.0.7", - "resolved": false, "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "pathval": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, @@ -2740,7 +6891,6 @@ }, "pkg-dir": { "version": "2.0.0", - "resolved": false, "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { @@ -2749,19 +6899,16 @@ }, "prelude-ls": { "version": "1.2.1", - "resolved": false, "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prettier": { "version": "2.4.1", - "resolved": false, "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", "dev": true }, "prettier-linter-helpers": { "version": "1.0.0", - "resolved": false, "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, "requires": { @@ -2770,23 +6917,19 @@ }, "printj": { "version": "1.1.2", - "resolved": false, "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" }, "process-nextick-args": { "version": "2.0.1", - "resolved": false, "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", - "resolved": false, "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "protobufjs": { "version": "6.11.2", - "resolved": false, "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "dev": true, "requires": { @@ -2807,13 +6950,11 @@ }, "punycode": { "version": "2.1.1", - "resolved": false, "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "readable-stream": { "version": "3.6.0", - "resolved": false, "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", @@ -2823,7 +6964,6 @@ }, "readdir-glob": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", "requires": { "minimatch": "^3.0.4" @@ -2831,7 +6971,6 @@ }, "ref-napi": { "version": "3.0.3", - "resolved": false, "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", "dev": true, "requires": { @@ -2843,7 +6982,6 @@ }, "ref-struct-di": { "version": "1.1.1", - "resolved": false, "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", "dev": true, "requires": { @@ -2852,7 +6990,6 @@ "dependencies": { "debug": { "version": "3.2.7", - "resolved": false, "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { @@ -2884,13 +7021,11 @@ }, "regexpp": { "version": "3.2.0", - "resolved": false, "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "repeat-string": { "version": "1.6.1", - "resolved": false, "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, @@ -2902,13 +7037,11 @@ }, "require-from-string": { "version": "2.0.2", - "resolved": false, "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "resolve": { "version": "1.20.0", - "resolved": false, "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { @@ -2918,7 +7051,6 @@ }, "resolve-from": { "version": "4.0.0", - "resolved": false, "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, @@ -2941,7 +7073,6 @@ }, "rimraf": { "version": "3.0.2", - "resolved": false, "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { @@ -2950,29 +7081,24 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "seed-random": { "version": "2.2.0", - "resolved": false, "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=", "dev": true }, "semver": { "version": "6.3.0", - "resolved": false, "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "set-immediate-shim": { "version": "1.0.1", - "resolved": false, "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" }, "sha3": { "version": "2.1.4", - "resolved": false, "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", "requires": { "buffer": "6.0.3" @@ -2980,7 +7106,6 @@ }, "shallow-clone": { "version": "3.0.1", - "resolved": false, "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "requires": { "kind-of": "^6.0.2" @@ -2988,7 +7113,6 @@ }, "shebang-command": { "version": "2.0.0", - "resolved": false, "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { @@ -2997,13 +7121,11 @@ }, "shebang-regex": { "version": "3.0.0", - "resolved": false, "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "side-channel": { "version": "1.0.4", - "resolved": false, "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "requires": { @@ -3014,7 +7136,6 @@ }, "slice-ansi": { "version": "4.0.0", - "resolved": false, "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { @@ -3025,7 +7146,6 @@ "dependencies": { "ansi-styles": { "version": "4.3.0", - "resolved": false, "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { @@ -3034,7 +7154,6 @@ }, "color-convert": { "version": "2.0.1", - "resolved": false, "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { @@ -3043,7 +7162,6 @@ }, "color-name": { "version": "1.1.4", - "resolved": false, "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } @@ -3051,7 +7169,7 @@ }, "source-map": { "version": "0.5.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, @@ -3075,19 +7193,16 @@ }, "sprintf-js": { "version": "1.0.3", - "resolved": false, "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "stack-chain": { "version": "2.0.0", - "resolved": false, "integrity": "sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==", "dev": true }, "stack-generator": { "version": "2.0.5", - "resolved": false, "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", "dev": true, "requires": { @@ -3096,13 +7211,11 @@ }, "stackframe": { "version": "1.2.0", - "resolved": false, "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", "dev": true }, "stacktrace-gps": { "version": "3.0.4", - "resolved": false, "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", "dev": true, "requires": { @@ -3112,7 +7225,6 @@ "dependencies": { "source-map": { "version": "0.5.6", - "resolved": false, "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true } @@ -3120,7 +7232,6 @@ }, "stacktrace-js": { "version": "2.0.2", - "resolved": false, "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", "dev": true, "requires": { @@ -3129,9 +7240,21 @@ "stacktrace-gps": "^3.0.4" } }, + "string_decoder": { + "version": "1.3.0", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "string-argv": { "version": "0.3.1", - "resolved": false, "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", "dev": true }, @@ -3139,6 +7262,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -3147,7 +7271,6 @@ }, "string.prototype.trimend": { "version": "1.0.4", - "resolved": false, "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, "requires": { @@ -3157,7 +7280,6 @@ }, "string.prototype.trimstart": { "version": "1.0.4", - "resolved": false, "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, "requires": { @@ -3165,44 +7287,26 @@ "define-properties": "^1.1.3" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": false, - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": false, - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, "strip-ansi": { "version": "6.0.1", - "resolved": false, "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } }, "strip-bom": { "version": "3.0.0", - "resolved": false, "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, "strip-json-comments": { "version": "3.1.1", - "resolved": false, "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { "version": "5.5.0", - "resolved": false, "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { @@ -3211,7 +7315,6 @@ }, "synchronized-promise": { "version": "0.3.1", - "resolved": false, "integrity": "sha512-Iy+JzrERSUrwpOHUDku8HHIddk8V6iLG9bPIzboP2i5RYkn2eSmRB8waSaX7Rc/+DUUsnFsoOHrmniwOp9BOgw==", "requires": { "deasync": "^0.1.15" @@ -3219,7 +7322,6 @@ }, "table": { "version": "6.7.2", - "resolved": false, "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", "dev": true, "requires": { @@ -3233,7 +7335,6 @@ "dependencies": { "ajv": { "version": "8.6.3", - "resolved": false, "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", "dev": true, "requires": { @@ -3245,7 +7346,6 @@ }, "json-schema-traverse": { "version": "1.0.0", - "resolved": false, "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true } @@ -3253,7 +7353,6 @@ }, "tar-stream": { "version": "2.2.0", - "resolved": false, "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "requires": { "bl": "^4.0.3", @@ -3265,18 +7364,15 @@ }, "tari_crypto": { "version": "0.9.1", - "resolved": false, "integrity": "sha512-K7LAtwQQKCeTH5CyyO8d/TiPDEePRaJ4e6+hrxpWv6jlkkAiS4m6csBuVqpSjyAlKeP8cQJpUQX2n22akOuZVg==" }, "text-table": { "version": "0.2.0", - "resolved": false, "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, "thenify": { "version": "3.3.1", - "resolved": false, "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "dev": true, "requires": { @@ -3285,7 +7381,6 @@ }, "thenify-all": { "version": "1.6.0", - "resolved": false, "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", "dev": true, "requires": { @@ -3303,24 +7398,23 @@ }, "to-fast-properties": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "traverse-chain": { "version": "0.1.0", - "resolved": false, "integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=", "dev": true }, "ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==" + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true }, "tsconfig-paths": { "version": "3.11.0", - "resolved": false, "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", "dev": true, "requires": { @@ -3332,7 +7426,6 @@ "dependencies": { "json5": { "version": "1.0.1", - "resolved": false, "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { @@ -3349,13 +7442,11 @@ }, "type": { "version": "1.2.0", - "resolved": false, "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, "type-check": { "version": "0.4.0", - "resolved": false, "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { @@ -3364,13 +7455,11 @@ }, "type-detect": { "version": "4.0.8", - "resolved": false, "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "unbox-primitive": { "version": "1.0.1", - "resolved": false, "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", "dev": true, "requires": { @@ -3382,7 +7471,6 @@ }, "universalify": { "version": "0.1.2", - "resolved": false, "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, @@ -3397,7 +7485,6 @@ }, "uri-js": { "version": "4.4.1", - "resolved": false, "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { @@ -3406,35 +7493,29 @@ }, "utf8": { "version": "3.0.0", - "resolved": false, "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" }, "util-arity": { "version": "1.1.0", - "resolved": false, "integrity": "sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA=", "dev": true }, "util-deprecate": { "version": "1.0.2", - "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { "version": "3.4.0", - "resolved": false, "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, "v8-compile-cache": { "version": "2.3.0", - "resolved": false, "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "verror": { "version": "1.10.0", - "resolved": false, "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { @@ -3453,68 +7534,86 @@ "dependencies": { "@grpc/grpc-js": { "version": "1.3.6", + "integrity": "sha512-v7+LQFbqZKmd/Tvf5/j1Xlbq6jXL/4d+gUtm2TNX4QiEC3ELWADmGr2dGlUyLl6aKTuYfsN72vAsO5zmavYkEg==", "requires": { "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { "version": "0.5.6", + "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", "requires": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" } }, "@protobufjs/aspromise": { - "version": "1.1.2" + "version": "1.1.2", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" }, "@protobufjs/base64": { - "version": "1.1.2" + "version": "1.1.2", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "@protobufjs/codegen": { - "version": "2.0.4" + "version": "2.0.4", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "@protobufjs/eventemitter": { - "version": "1.1.0" + "version": "1.1.0", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" }, "@protobufjs/fetch": { "version": "1.1.0", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "@protobufjs/float": { - "version": "1.0.2" + "version": "1.0.2", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" }, "@protobufjs/inquire": { - "version": "1.1.0" + "version": "1.1.0", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" }, "@protobufjs/path": { - "version": "1.1.2" + "version": "1.1.2", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" }, "@protobufjs/pool": { - "version": "1.1.0" + "version": "1.1.0", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" }, "@protobufjs/utf8": { - "version": "1.1.0" + "version": "1.1.0", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@types/long": { - "version": "4.0.1" + "version": "4.0.1", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/node": { - "version": "16.3.2" + "version": "16.3.2", + "integrity": "sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==" }, "grpc-promise": { - "version": "1.4.0" + "version": "1.4.0", + "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==" }, "lodash.camelcase": { - "version": "4.3.0" + "version": "4.3.0", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, "long": { - "version": "4.0.0" + "version": "4.0.0", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "protobufjs": { "version": "6.11.2", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -3535,7 +7634,6 @@ }, "which": { "version": "2.0.2", - "resolved": false, "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { @@ -3544,7 +7642,6 @@ }, "which-boxed-primitive": { "version": "1.0.2", - "resolved": false, "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, "requires": { @@ -3557,7 +7654,6 @@ }, "word-wrap": { "version": "1.2.3", - "resolved": false, "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, @@ -3600,7 +7696,6 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "y18n": { @@ -3611,7 +7706,6 @@ }, "yallist": { "version": "4.0.0", - "resolved": false, "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, @@ -3638,7 +7732,6 @@ }, "zip-stream": { "version": "4.1.0", - "resolved": false, "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", "requires": { "archiver-utils": "^2.1.0", From 51b6121c236265f2b587923a747cd648fa895acb Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Tue, 16 Nov 2021 09:39:27 +0200 Subject: [PATCH 24/46] test: make monerod stagenet usage resilient (#3572) Description --- Made `monerod` stagenet usage resilient for use in cucumber by testing publically available URLs from a pre-defined list before assigning the first available and responsive one to use. Flaky URLs have been pushed to the back of the list. Motivation and Context --- Cucumber merge mining tests failed because the currently hard-coded `monerod` was not available. How Has This Been Tested? --- Tested merge mining in cucumber, All the responsive URLs in the list have been tested to work with the merge mining proxy. Sample console output below: ``` rust >> Warn: `monerod` at http://3.104.4.129:18081 is not available! >> Info: `monerod` at http://singapore.node.xmr.pm:38081 is responsive and available ``` --- integration_tests/features/support/steps.js | 1 + .../helpers/mergeMiningProxyProcess.js | 80 ++++++++++++++++++- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index bf8ad4fa54..51d721570c 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -631,6 +631,7 @@ When( Given( /I have a merge mining proxy (.*) connected to (.*) and (.*) with default config/, + { timeout: 120 * 1000 }, // The timeout must make provision for testing the monerod URL /get_height response async function (mmProxy, node, wallet) { const baseNode = this.getNode(node); const walletNode = this.getWallet(wallet); diff --git a/integration_tests/helpers/mergeMiningProxyProcess.js b/integration_tests/helpers/mergeMiningProxyProcess.js index 7d29b8d866..fe0b87159a 100644 --- a/integration_tests/helpers/mergeMiningProxyProcess.js +++ b/integration_tests/helpers/mergeMiningProxyProcess.js @@ -6,6 +6,8 @@ const { spawn } = require("child_process"); const { expect } = require("chai"); const MergeMiningProxyClient = require("./mergeMiningProxyClient"); const { createEnv } = require("./config"); +require("https"); +require("http"); let outputProcess; @@ -38,7 +40,7 @@ class MergeMiningProxyProcess { // console.log("MergeMiningProxyProcess init - assign server GRPC:", this.grpcPort); } - run(cmd, args) { + run(cmd, args, monerodUrl) { return new Promise((resolve, reject) => { if (!fs.existsSync(this.baseDir)) { fs.mkdirSync(this.baseDir, { recursive: true }); @@ -65,8 +67,7 @@ class MergeMiningProxyProcess { const extraEnvs = { TARI_MERGE_MINING_PROXY__LOCALNET__PROXY_SUBMIT_TO_ORIGIN: this.submitOrigin, - TARI_MERGE_MINING_PROXY__LOCALNET__monerod_url: - "http://3.104.4.129:18081", + TARI_MERGE_MINING_PROXY__LOCALNET__monerod_url: monerodUrl, }; const completeEnvs = { ...envs, ...extraEnvs }; const ps = spawn(cmd, args, { @@ -104,13 +105,84 @@ class MergeMiningProxyProcess { }); } + async testWebsite(protocol, address, port, path) { + const url = protocol + "://" + address + ":" + port; + const webRequest = require(protocol); + + let request; + let thePromise; + const displayData = false; + try { + thePromise = await new Promise((resolve, reject) => { + request = webRequest + .get(url + path, (resp) => { + let data = ""; + // Read all data chunks until the end + resp.on("data", (chunk) => { + data += chunk; + }); + // Finish when complete response has been received + resp.on("end", () => { + if (displayData) { + console.log(data); // `data` is 'used' here to keep eslint happy + } + return resolve(true); + }); + }) + .on("error", () => { + return reject(false); + }); + }); + console.log( + " >> Info: `monerod` at", + url, + "is responsive and available" + ); + } catch { + console.log(" >> Warn: `monerod` at", url, "is not available!"); + } + request.end(); + + return thePromise; + } + + async getMoneroStagenetUrl() { + // See: https://monero.fail/?nettype=stagenet + const monerodUrl = [ + ["http", "singapore.node.xmr.pm", "38081"], + ["http", "stagenet.xmr-tw.org", "38081"], + ["http", "xmr-lux.boldsuck.org", "38081"], + ["http", "monero-stagenet.exan.tech", "38081"], + ["http", "3.104.4.129", "18081"], // flaky + ["http", "stagenet.community.xmr.to", "38081"], // flaky + ["http", "super.fast.node.xmr.pm", "38089"], // flaky + ]; + let url; + for (let i = 0; i < monerodUrl.length; i++) { + let availble = await this.testWebsite( + monerodUrl[i][0], + monerodUrl[i][1], + monerodUrl[i][2], + "/get_height" + ); + if (availble) { + url = + monerodUrl[i][0] + "://" + monerodUrl[i][1] + ":" + monerodUrl[i][2]; + break; + } + } + return url; + } + async startNew() { await this.init(); const args = ["--base-path", ".", "--init"]; if (this.logFilePath) { args.push("--log-config", this.logFilePath); } - return await this.run(await this.compile(), args, true); + + let url = await this.getMoneroStagenetUrl(); + return await this.run(await this.compile(), args, url); } async compile() { From ccf1da02dcbf99fe1872deec73f424b4328c70e0 Mon Sep 17 00:00:00 2001 From: David Main <51991544+StriderDM@users.noreply.github.com> Date: Tue, 16 Nov 2021 10:33:40 +0200 Subject: [PATCH 25/46] feat: get fee for transactions for stratum transcoder (#3571) Description --- Adds the ability for a fee to be requested for a transaction from the console wallet via the tari stratum transcoder. It makes use of the existing grpc call get_transaction_info and essentially returns a (TxId, Fee) collection of json objects. Motivation and Context --- Primarily used in the Tari implementation for Miningcore. How Has This Been Tested? --- Manually. --- .../tari_stratum_transcoder/src/proxy.rs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/applications/tari_stratum_transcoder/src/proxy.rs b/applications/tari_stratum_transcoder/src/proxy.rs index c3ce03ee56..613d3ca66b 100644 --- a/applications/tari_stratum_transcoder/src/proxy.rs +++ b/applications/tari_stratum_transcoder/src/proxy.rs @@ -480,6 +480,64 @@ impl InnerService { proxy::json_response(StatusCode::OK, &json_response) } + async fn handle_get_fee( + &self, + request: Request, + ) -> Result, StratumTranscoderProxyError> { + let request = request.body(); + let transactions = match request["params"]["transactions"].as_array() { + Some(v) => v, + None => { + return proxy::json_response( + StatusCode::OK, + &json_rpc::error_response( + request["id"].as_i64(), + 1, + "`transactions` field is empty or an invalid type for transfer request.", + None, + ), + ) + }, + }; + + let mut grpc_transaction_info = Vec::new(); + for transaction in transactions.iter() { + grpc_transaction_info.push( + transaction["transaction_id"] + .as_str() + .unwrap() + .to_string() + .parse::() + .unwrap(), + ); + } + + let mut client = self.wallet_client.clone(); + + let transaction_info_results = client + .get_transaction_info(grpc::GetTransactionInfoRequest { + transaction_ids: grpc_transaction_info, + }) + .await? + .into_inner(); + let info_results = &transaction_info_results.transactions; + + let mut results = Vec::new(); + for info_result in info_results.iter() { + let result = json!({ + "transaction_id": info_result.tx_id, + "fee": info_result.fee, + }); + results.push(result.as_object().unwrap().clone()); + } + + let json_response = json!({ + "jsonrpc": "2.0", + "result": {"fee_results" : results}, + }); + proxy::json_response(StatusCode::OK, &json_response) + } + async fn handle_transfer( &self, request: Request, @@ -586,6 +644,7 @@ impl InnerService { "getlastblockheader" | "get_last_block_header" => self.handle_get_last_block_header(request).await, "transfer" => self.handle_transfer(request).await, "getbalance" | "get_balance" => self.handle_get_balance(request).await, + "getfee" | "get_fee" => self.handle_get_fee(request).await, _ => { let request = request.body(); proxy_resp = Response::new(standard_error_response( From eb8b8152ecc4cd9ccf49a7fe23fe0e2c77ff2c63 Mon Sep 17 00:00:00 2001 From: Stanimal Date: Tue, 16 Nov 2021 21:30:38 +0400 Subject: [PATCH 26/46] fix: remove delay from last request latency call --- Cargo.lock | 4 -- .../horizon_state_synchronization.rs | 4 +- .../sync/header_sync/synchronizer.rs | 2 +- .../wallet/src/base_node_service/monitor.rs | 2 +- .../src/utxo_scanner_service/utxo_scanning.rs | 2 +- .../tests/transaction_service/service.rs | 8 ++-- common/Cargo.toml | 6 --- comms/rpc_macros/src/generator.rs | 4 +- comms/src/protocol/rpc/client/mod.rs | 40 +++++++++---------- comms/src/protocol/rpc/client/tests.rs | 34 +++++++++++++++- .../src/protocol/rpc/test/greeting_service.rs | 4 +- comms/src/protocol/rpc/test/smoke.rs | 2 +- 12 files changed, 66 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d674d81979..06ba280191 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4513,8 +4513,6 @@ dependencies = [ "log", "log4rs 1.0.0", "multiaddr", - "opentelemetry", - "opentelemetry-jaeger", "path-clean", "prost-build", "serde 1.0.130", @@ -4527,8 +4525,6 @@ dependencies = [ "thiserror", "toml 0.5.8", "tracing", - "tracing-opentelemetry", - "tracing-subscriber", ] [[package]] diff --git a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs index 52c1556bb6..75ff753d50 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs @@ -159,7 +159,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { remote_num_kernels - local_num_kernels, ); - let latency = client.get_last_request_latency().await?; + let latency = client.get_last_request_latency(); debug!( target: LOG_TARGET, "Initiating kernel sync with peer `{}` (latency = {}ms)", @@ -287,7 +287,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { let end = remote_num_outputs; let end_hash = to_header.hash(); - let latency = client.get_last_request_latency().await?; + let latency = client.get_last_request_latency(); debug!( target: LOG_TARGET, "Initiating output sync with peer `{}` (latency = {}ms)", diff --git a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs index 4ff044218f..ca594087b6 100644 --- a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs @@ -218,7 +218,7 @@ impl<'a, B: BlockchainBackend + 'static> HeaderSynchronizer<'a, B> { mut conn: PeerConnection, ) -> Result<(), BlockHeaderSyncError> { let mut client = conn.connect_rpc::().await?; - let latency = client.get_last_request_latency().await?; + let latency = client.get_last_request_latency(); debug!( target: LOG_TARGET, "Initiating header sync with peer `{}` (sync latency = {}ms)", diff --git a/base_layer/wallet/src/base_node_service/monitor.rs b/base_layer/wallet/src/base_node_service/monitor.rs index 11003eff8b..9a7df7630b 100644 --- a/base_layer/wallet/src/base_node_service/monitor.rs +++ b/base_layer/wallet/src/base_node_service/monitor.rs @@ -150,7 +150,7 @@ where timer.elapsed().as_millis() ); - let latency = match client.get_last_request_latency().await? { + let latency = match client.get_last_request_latency() { Some(latency) => latency, None => continue, }; diff --git a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs index d5afe96fd1..ed21d9d029 100644 --- a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs +++ b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs @@ -268,7 +268,7 @@ where TBackend: WalletBackend + 'static .connect_rpc_using_builder(BaseNodeSyncRpcClient::builder().with_deadline(Duration::from_secs(60))) .await?; - let latency = client.get_last_request_latency().await?; + let latency = client.get_last_request_latency(); self.publish_event(UtxoScannerEvent::ConnectedToBaseNode( peer.clone(), latency.unwrap_or_default(), diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index d9b058ad49..eb0593bb67 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -944,7 +944,7 @@ fn test_htlc_send_and_claim() { let bob_connection = run_migration_and_create_sqlite_connection(&bob_db_path, 16).unwrap(); let shutdown = Shutdown::new(); - let (alice_ts, alice_oms, _alice_comms, mut alice_connectivity) = setup_transaction_service( + let (mut alice_ts, mut alice_oms, _alice_comms, mut alice_connectivity) = setup_transaction_service( &mut runtime, alice_node_identity, vec![], @@ -998,10 +998,8 @@ fn test_htlc_send_and_claim() { .expect("Alice sending HTLC transaction") }); - let mut alice_ts_clone2 = alice_ts.clone(); - let mut alice_oms_clone = alice_oms.clone(); runtime.block_on(async move { - let completed_tx = alice_ts_clone2 + let completed_tx = alice_ts .get_completed_transaction(tx_id) .await .expect("Could not find completed HTLC tx"); @@ -1009,7 +1007,7 @@ fn test_htlc_send_and_claim() { let fees = completed_tx.fee; assert_eq!( - alice_oms_clone.get_balance().await.unwrap().pending_incoming_balance, + alice_oms.get_balance().await.unwrap().pending_incoming_balance, initial_wallet_value - value - fees ); }); diff --git a/common/Cargo.toml b/common/Cargo.toml index 8dd805cedd..67cb608d9b 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -27,12 +27,6 @@ sha2 = "0.9.5" path-clean = "0.1.0" tari_storage = { version = "^0.21", path = "../infrastructure/storage"} tracing = "0.1.26" -tracing-opentelemetry = "0.15.0" -tracing-subscriber = "0.2.20" - -# network tracing, rt-tokio for async batch export -opentelemetry = { version = "0.16", default-features = false, features = ["trace","rt-tokio"] } -opentelemetry-jaeger = { version="0.15", features=["rt-tokio"]} anyhow = { version = "1.0", optional = true } git2 = { version = "0.8", optional = true } diff --git a/comms/rpc_macros/src/generator.rs b/comms/rpc_macros/src/generator.rs index 312e318284..d3fe17df88 100644 --- a/comms/rpc_macros/src/generator.rs +++ b/comms/rpc_macros/src/generator.rs @@ -208,8 +208,8 @@ impl RpcCodeGenerator { #client_methods - pub async fn get_last_request_latency(&mut self) -> Result, #dep_mod::RpcError> { - self.inner.get_last_request_latency().await + pub fn get_last_request_latency(&mut self) -> Option { + self.inner.get_last_request_latency() } pub async fn ping(&mut self) -> Result { diff --git a/comms/src/protocol/rpc/client/mod.rs b/comms/src/protocol/rpc/client/mod.rs index bdaa57c368..52abf1049f 100644 --- a/comms/src/protocol/rpc/client/mod.rs +++ b/comms/src/protocol/rpc/client/mod.rs @@ -73,7 +73,7 @@ use std::{ use tari_shutdown::{Shutdown, ShutdownSignal}; use tokio::{ io::{AsyncRead, AsyncWrite}, - sync::{mpsc, oneshot, Mutex}, + sync::{mpsc, oneshot, watch, Mutex}, time, }; use tower::{Service, ServiceExt}; @@ -105,7 +105,8 @@ impl RpcClient { let (request_tx, request_rx) = mpsc::channel(1); let shutdown = Shutdown::new(); let shutdown_signal = shutdown.to_signal(); - let connector = ClientConnector::new(request_tx, shutdown); + let (last_request_latency_tx, last_request_latency_rx) = watch::channel(None); + let connector = ClientConnector::new(request_tx, last_request_latency_rx, shutdown); let (ready_tx, ready_rx) = oneshot::channel(); let tracing_id = tracing::Span::current().id(); task::spawn({ @@ -116,6 +117,7 @@ impl RpcClient { config, node_id, request_rx, + last_request_latency_tx, framed, ready_tx, protocol_name, @@ -172,7 +174,7 @@ impl RpcClient { } /// Return the latency of the last request - pub fn get_last_request_latency(&mut self) -> impl Future, RpcError>> + '_ { + pub fn get_last_request_latency(&mut self) -> Option { self.connector.get_last_request_latency() } @@ -315,13 +317,19 @@ impl Default for RpcClientConfig { #[derive(Clone)] pub struct ClientConnector { inner: mpsc::Sender, + last_request_latency_rx: watch::Receiver>, shutdown: Arc>, } impl ClientConnector { - pub(self) fn new(sender: mpsc::Sender, shutdown: Shutdown) -> Self { + pub(self) fn new( + sender: mpsc::Sender, + last_request_latency_rx: watch::Receiver>, + shutdown: Shutdown, + ) -> Self { Self { inner: sender, + last_request_latency_rx, shutdown: Arc::new(Mutex::new(shutdown)), } } @@ -331,14 +339,8 @@ impl ClientConnector { lock.trigger(); } - pub async fn get_last_request_latency(&mut self) -> Result, RpcError> { - let (reply, reply_rx) = oneshot::channel(); - self.inner - .send(ClientRequest::GetLastRequestLatency(reply)) - .await - .map_err(|_| RpcError::ClientClosed)?; - - reply_rx.await.map_err(|_| RpcError::RequestCancelled) + pub fn get_last_request_latency(&mut self) -> Option { + *self.last_request_latency_rx.borrow() } pub async fn send_ping(&mut self) -> Result { @@ -391,12 +393,12 @@ struct RpcClientWorker { config: RpcClientConfig, node_id: NodeId, request_rx: mpsc::Receiver, + last_request_latency_tx: watch::Sender>, framed: CanonicalFraming, // Request ids are limited to u16::MAX because varint encoding is used over the wire and the magnitude of the value // sent determines the byte size. A u16 will be more than enough for the purpose next_request_id: u16, ready_tx: Option>>, - last_request_latency: Option, protocol_id: ProtocolId, shutdown_signal: ShutdownSignal, } @@ -404,10 +406,12 @@ struct RpcClientWorker { impl RpcClientWorker where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId { + #[allow(clippy::too_many_arguments)] pub(self) fn new( config: RpcClientConfig, node_id: NodeId, request_rx: mpsc::Receiver, + last_request_latency_tx: watch::Sender>, framed: CanonicalFraming, ready_tx: oneshot::Sender>, protocol_id: ProtocolId, @@ -420,7 +424,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId framed, next_request_id: 0, ready_tx: Some(ready_tx), - last_request_latency: None, + last_request_latency_tx, protocol_id, shutdown_signal, } @@ -454,7 +458,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId self.protocol_name(), latency ); - self.last_request_latency = Some(latency); + let _ = self.last_request_latency_tx.send(Some(latency)); if let Some(r) = self.ready_tx.take() { let _ = r.send(Ok(())); } @@ -514,9 +518,6 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId SendRequest { request, reply } => { self.do_request_response(request, reply).await?; }, - GetLastRequestLatency(reply) => { - let _ = reply.send(self.last_request_latency); - }, SendPing(reply) => { self.do_ping_pong(reply).await?; }, @@ -647,7 +648,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId let resp = match self.read_response(request_id).await { Ok(resp) => { if let Some(t) = timer.take() { - self.last_request_latency = Some(t.elapsed()); + let _ = self.last_request_latency_tx.send(Some(t.elapsed())); } event!(Level::TRACE, "Message received"); trace!( @@ -821,7 +822,6 @@ pub enum ClientRequest { request: BaseRequest, reply: oneshot::Sender, RpcStatus>>>, }, - GetLastRequestLatency(oneshot::Sender>), SendPing(oneshot::Sender>), } diff --git a/comms/src/protocol/rpc/client/tests.rs b/comms/src/protocol/rpc/client/tests.rs index 0dc4a4c595..d12faeb7d4 100644 --- a/comms/src/protocol/rpc/client/tests.rs +++ b/comms/src/protocol/rpc/client/tests.rs @@ -25,7 +25,7 @@ use crate::{ protocol::{ rpc::{ test::{ - greeting_service::{GreetingClient, GreetingServer, GreetingService}, + greeting_service::{GreetingClient, GreetingServer, GreetingService, SlowStreamRequest}, mock::create_mocked_rpc_context, }, NamedProtocolService, @@ -39,9 +39,11 @@ use crate::{ runtime::task, test_utils::mocks::{new_peer_connection_mock_pair, PeerConnectionMockState}, }; +use std::{env, time::Duration}; use tari_shutdown::Shutdown; use tari_test_utils::{async_assert_eventually, unpack_enum}; use tokio::sync::mpsc; +use tokio_stream::StreamExt; async fn setup(num_concurrent_sessions: usize) -> (PeerConnection, PeerConnectionMockState, Shutdown) { let (conn1, conn1_state, conn2, conn2_state) = new_peer_connection_mock_pair().await; @@ -171,3 +173,33 @@ mod lazy_pool { unpack_enum!(RpcClientPoolError::PeerConnectionDropped { .. } = err); } } + +mod last_request_latency { + use super::*; + + #[runtime::test] + async fn it_returns_the_latency_until_the_first_response() { + let (mut conn, _, _shutdown) = setup(1).await; + + let mut client = conn.connect_rpc::().await.unwrap(); + + let resp = client + .slow_stream(SlowStreamRequest { + num_items: 100, + item_size: 10, + delay_ms: 10, + }) + .await + .unwrap(); + + resp.collect::>().await.into_iter().for_each(|r| { + r.unwrap(); + }); + + let latency = client.get_last_request_latency().unwrap(); + // CI could be really slow, so to prevent flakiness exclude the assert + if env::var("CI").is_err() { + assert!(latency < Duration::from_millis(100)); + } + } +} diff --git a/comms/src/protocol/rpc/test/greeting_service.rs b/comms/src/protocol/rpc/test/greeting_service.rs index d974f433cc..00d3aad61e 100644 --- a/comms/src/protocol/rpc/test/greeting_service.rs +++ b/comms/src/protocol/rpc/test/greeting_service.rs @@ -447,8 +447,8 @@ impl GreetingClient { self.inner.server_streaming(request, 8).await } - pub async fn get_last_request_latency(&mut self) -> Result, RpcError> { - self.inner.get_last_request_latency().await + pub fn get_last_request_latency(&mut self) -> Option { + self.inner.get_last_request_latency() } pub async fn ping(&mut self) -> Result { diff --git a/comms/src/protocol/rpc/test/smoke.rs b/comms/src/protocol/rpc/test/smoke.rs index 8ca1bfce52..0fc1f05256 100644 --- a/comms/src/protocol/rpc/test/smoke.rs +++ b/comms/src/protocol/rpc/test/smoke.rs @@ -135,7 +135,7 @@ async fn request_response_errors_and_streaming() { .unwrap(); // Latency is available "for free" as part of the connect protocol - assert!(client.get_last_request_latency().await.unwrap().is_some()); + assert!(client.get_last_request_latency().is_some()); let resp = client .say_hello(SayHelloRequest { From d073f50baa7e6ecf8cf16733ce80b13efea6553c Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Wed, 17 Nov 2021 12:04:04 +0200 Subject: [PATCH 27/46] docs: rfc 0250_Covenants (#3574) Description --- Adds RFC for covenants on Tari [Rendered](https://demo.hedgedoc.org/s/Vwn0ZtWsS) Motivation and Context --- Allows sidechain checkpointing transactions and many other use-cases. --- RFC/src/BaseLayerExtensions.md | 4 +- RFC/src/RFC-0250_Covenants.md | 468 +++++++++++++++++++++++++++++++++ RFC/src/SUMMARY.md | 1 + 3 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 RFC/src/RFC-0250_Covenants.md diff --git a/RFC/src/BaseLayerExtensions.md b/RFC/src/BaseLayerExtensions.md index 32d4c415af..d799f1dca4 100644 --- a/RFC/src/BaseLayerExtensions.md +++ b/RFC/src/BaseLayerExtensions.md @@ -5,5 +5,7 @@ Tari Base layer token and Digital Asset network. * [RFC-0220: Asset Checkpoints](RFC-0220_AssetCheckpoints.md) * [RFC-0230: Time related transactions](RFC-0230_HTLC.md) +* [RFC-0201: TariScript](RFC-0201_TariScript.md) +* [RFC-0250: Covenants](RFC-0250_Covenants.md) * [RFC-0322: Validator Node Registration](RFC-0322_VNRegistration.md) -* [RFC-0322: Asset Registration](RFC-0341_AssetRegistration.md) +* [RFC-0341: Asset Registration](RFC-0341_AssetRegistration.md) diff --git a/RFC/src/RFC-0250_Covenants.md b/RFC/src/RFC-0250_Covenants.md new file mode 100644 index 0000000000..d2e90367db --- /dev/null +++ b/RFC/src/RFC-0250_Covenants.md @@ -0,0 +1,468 @@ +# RFC-0250/Covenants + +## Covenants + +![status: draft](theme/images/status-draft.svg) + +**Maintainer(s)**: [Stanley Bondi](https://github.com/sdbondi) + +# Licence + +[The 3-Clause BSD Licence](https://opensource.org/licenses/BSD-3-Clause). + +Copyright 2021 The Tari Development Community + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of this document must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 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. + +## Language + +The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT +RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in +[BCP 14](https://tools.ietf.org/html/bcp14) (covering RFC2119 and RFC8174) when, and only when, they appear in all +capitals, as shown here. + +## Disclaimer + +This document and its content are intended for information purposes only and may be subject to change or update without +notice. + +This document may include preliminary concepts that may or may not be in the process of being developed by the Tari +community. The release of this document is intended solely for review and discussion by the community of the +technological merits of the potential system outlined herein. + +## Goals + +This Request for Comment (RFC) presents a proposal for introducing _covenants_ into the Tari base layer protocol. Tari +Covenents aims to provide restrictions on the _future_ spending of subsequent transactions to enable a number of powerful +use-cases, such as +- [vaults] +- side-chain checkpointing transactions, +- commission on NFT transfers, and +- many others not thought of here. + +## Related Requests for Comment + +- [RFC-0200: Base Layer Extensions](BaseLayerExtensions.md) +- [RFC-0300: The Tari Digital Assets Network](RFC-0300_DAN.md) + +## Introduction + +The Tari protocol already provides programmable consensus, through [TariScript], that restricts whether a [UTXO] +may be included as an input to a transaction (a.k.a spent). The scope of information [TariScript] is inherently limited, +by the [TariScript Opcodes] and the input data provided by a spender. Once the requirements of the script are met, +a spender may generate [UTXO]s of their choosing, within the constraints of [MimbleWimble]. + +This RFC aims to expand the capabilities of Tari protocol by adding _additional requirements_, called covenants +that allow the owner(s) of a [UTXO] to control the composition of a _subsequent_ transaction. + +Covenants are not a new idea and have been proposed and implemented in various forms by others. + +For example, +- [Bitcoin-NG covenents] put forward the `CheckOutputVerify` script opcode. +- [Handshake] has implemented covenants to add the [UTXO] state of their auctioning process. +- [Elements Covenants] + +## Covenants in MimbleWimble + +In block chains like Bitcoin, a block contains discrete transactions containing inputs and outputs. A covenant +in Bitcoin would be able to interrogate those outputs _belonging to the input_ to ensure that they adhere to rules. + +In [MimbleWimble], the body of a block and transaction can be expressed in an identical data structure. This +is indeed the case in the [Tari codebase], which defines a structure called `AggregateBody` containing inputs +and outputs (and kernels) for transactions and blocks. This is innate to [MimbleWimble], so even if we were +to put a "box" around these inputs/outputs there is nothing to stop someone from including inputs and +outputs from other boxes as long as balance is maintained. + +This results in an interesting dilemma: how do we allow rules that dictate how future outputs look only armed with +the knowledge that the rule must apply to one or more outputs? + +In this RFC, we propose a covenant scheme that allows the [UTXO] originator to express a _filter_ that must be +satisfied for a subsequent spending transaction to be considered valid. + +## Assumptions + +The following assumptions are made: +1. Duplicate commitments within a block are disallowed by consensus _prior_ to covenant execution, +2. all outputs in the output set are valid, and +3. all inputs are valid spends, save for covenent checks. + +## Protocol modifications + +Modifications to the existing protocol and consensus are as follows: + +- the covenant is recorded in the transaction [UTXO], +- the covenant is committed to in the output and input hashes to prevent malleability, +- transactions with covenants entering the mempool MUST be validated, and +- each covenant in a block must be validated before being included in the block chain. + +### Transaction input and output changes + +A `covenant` field would need to be added to the `TransactionOutput` and `TransactionInput` structs +and committed to in their hashes. + +### Covenant definition + +We define a clear notation for covenants that mirrors the [miniscript] project. + +#### Execution Context and Scope + +Covenants execute within a limited read-only context and scope. This is both to reduce complexity (and therefore +the possibility of bugs) and maintain reasonable performance. + +A covenant's context is limited to: +- a immutable reference to the current input, +- a vector of immutable _references_ to outputs in the current block/transaction (called, the output set), +- the current input's mined height, and +- the current block height. + +Each output's covenant is executed with this context, filtering on the output set and returning the result. +The output set given to each covenant at execution MUST be the same set for all covenants and MUST never be +influenced by other covenants. The stateless and immutable nature of this scheme has the benefit of being +able to execute covenants in parallel. + +A covenant passes if at least one output in the set is matched. Allowing more than one output to match allows for +covenants that restrict the characteristics of multiple outputs. A covenant that matches zero outputs _fails_ +which invalidates the transaction/block. + +If a covenant is empty (zero bytes) the `identity` operation is implied and therefore, no actual execution need occur. + +#### Argument types + +```rust,ignore +enum CovenantArg { + // byte code: 0x01 + // data size: 32 bytes + Hash([u8; 32]), + // byte code: 0x02 + // data size: 32 bytes + PublicKey(RistrettoPublicKey), + // byte code: 0x03 + // data size: 32 bytes + Commitment(PedersonCommitment), + // byte code: 0x04 + // data size: 64 bytes + Signature(Signature), + // byte code: 0x05 + // data size: variable + Script(TariScript), + // byte code: 0x06 + // data size: variable + Covenant(Covenant), + // byte code: 0x07 + // data size: variable + VarInt(VarInt), + // byte code: 0x08 + // data size: 1 byte + Field(FieldKey), + // byte code: 0x09 + // data size: variable + Fields(Vec), +} +``` + +##### Output field tags + +Fields from each output in the output set may be brought into a covenant filter. +The available fields are defined as follows: + + +| Tag Name | Byte Code | Returns | +|---------------------------------------|---------------|---------------------------------------| +| `field::commitment` | 0x00 | output.commitment | +| `field::script` | 0x01 | output.script | +| `field::sender_offset_public_key` | 0x02 | output.sender_offset_public_key | +| `field::covenant` | 0x03 | output.covenant | +| `field::features` | 0x04 | output.features | +| `field::features_flags` | 0x05 | output.features.flags | +| `field::features_maturity` | 0x06 | output.features.maturity | +| `field::features_unique_id` | 0x07 | output.features.unique_id | +| `field::features_parent_public_key` | 0x08 | output.features.parent_public_key | +| `field::features_metadata` | 0x09 | output.features.metadata | + +Each field tag returns a consensus encoded byte representation of the value contained in the field. +How those bytes are interpreted depends on the covenant. For instance, `filter_fields_hashed_eq` will +concatenate the bytes and hash the result whereas `filter_field_int_eq` will interpret the bytes as a +little-endian 64-byte unsigned integer. + +#### Set operations + +##### identity() + +The output set is returned unaltered. This rule is implicit for an empty (0 byte) covenant. + +```yaml +op_byte: 0x20 +args: [] +``` + +##### and(A, B) + +The intersection (\\(A \cap B\\)) of the resulting output set for covenant rules \\(A\\) and \\(B\\). + +```yaml +op_byte: 0x21 +args: [Covenant, Covenant] +``` + +##### or(A, B) + +The union (\\(A \cup B\\)) of the resulting output set for covenant rules \\(A\\) and \\(B\\). + +```yaml +op_byte: 0x22 +args: [Covenant, Covenant] +``` + +##### xor(A, B) + +The symmetric difference (\\(A \triangle B\\)) of the resulting output set for covenant rules \\(A\\) and \\(B\\). +This is, outputs that match either \\(A\\) or \\(B\\) but not both. + +```yaml +op_byte: 0x23 +args: [Covenant, Covenant] +``` + +##### not(A) + +Returns the compliment of `A`. That is, all the elements of `A` are removed from the +resultant output set. + +```yaml +op_byte: 0x24 +args: [Covenant] +``` + +##### empty() + +Returns an empty set. This will always fail and, if used alone, prevents the UTXO from ever being spent. +A more useful reason to use `empty` is in conjunction a conditional e.g. `if_else(Condition(older_rel(10)), A, empty)` + +```yaml +op_byte: 0x25 +args: [] +``` + +#### Filters + +##### filter_output_hash_eq(hash) + +Filters for a single output that matches the hash. This filter only returns zero or one outputs. + +```yaml +op_byte: 0x30 +args: [Hash] +``` + +##### filter_fields_preserved(fields) + +Filter for outputs where all given fields in the input are preserved in the output. + +```yaml +op_byte: 0x31 +args: [Fields] +``` + +##### filter_field_int_eq(field, int) + +Filters for outputs whose field value matches the given integer value. If the given field cannot be cast +to an unsigned 64-bit integer, the transaction/block is rejected. + +```yaml +op_byte: 0x32 +args: [Field, VarInt] +``` + +##### filter_fields_hashed_eq(fields, hash) + +```yaml +op_byte: 0x33 +args: [Fields, VarInt] +``` + +##### filter_relative_height(height) + +Checks the block height that current [UTXO] (i.e. the current input) was mined plus `height` is greater than or +equal to the current block height. If so, the `identity()` is returned, otherwise `empty()`. + +```yaml +op_byte: 0x34 +args: [VarInt] +``` + +#### Encoding / Decoding + +Convenants can be encoded to/decoded from bytes as a token stream. Each token is consumed and interpreted serially +before being executed. + +For instance, + +``` +xor( + filter_output_hash_eq(Hash(0e0411c70df0ea4243a363fcbf161ebe6e2c1f074faf1c6a316a386823c3753c)), + filter_relative_height(10), +) +``` + +is represented in hex bytes as `23 30 01 a8b3f48e39449e89f7ff699b3eb2b080a2479b09a600a19d8ba48d765fe5d47d 35 07 0a`. +Let's unpack that as follows: +``` +23 // xor - consume two covenant args +30 // filter_output_hash_eq - consume a hash arg +01 // 32-byte hash +a8b3f48e39449e89f7ff699b3eb2b080a2479b09a600a19d8ba48d765fe5d47d // data +// end filter_output_hash_eq +35 // 2nd covenant - filter_relative_height +07 // varint +0A // 10 +// end varint, filter_relative_height, xor +``` + +Some functions can take any number of arguments, such as `filter_fields_hashed_eq` which defines the `Fields` type. +This type is encoded first by its byte code `34` followed by a varint encoded number that indicates the number +of field identifiers to consume. To mitigate misuse, the maximum allowed arguments are limited. + +### Covenant Validation + +A covenant and therefore the block/transaction MUST be regarded as invalid if: + +1. an unrecognised byte code is encountered +2. the end of the byte stream is reached unexpectedly +3. there are bytes remaining on the stream after interpreting +4. an invalid argument type is encountered +5. the `Fields` type encounters more than 9 arguments (i.e. the number of fields tags available) +6. the depth of the calls exceeds 16. + +### Consensus changes + +The covenant is executed once all other validations, including [TariScript], are complete. This ensures that +invalid transactions in a block cannot influence the results. + +## Considerations + +### Complexity + +This introduces additional validation complexity. We avoid stacks, loop, and conditionals (covenants are basically +one conditional), there are overheads both in terms of complexity and performance as a trade-off for the +power given by covenants. + +The worst case complexity for covenant validation is `O(num_inputs*num_outputs)`, although as mentioned above +validation for each input can be executed in parallel. To compensate for the additional workload the network +encounters, use of covenants should incur heavily-weighted fees to discourage needlessly using them. + +### Cut-through + +The same arguments made in the [TariScript RFC](./RFC-0201_TariScript.md#cut-through) for the need to prevent +[cut-through] apply to covenants. + +### Chain analysis + +The same arguments made in the [TariScript RFC](./RFC-0201_TariScript.md#fodder-for-chain-analysis) apply. + +### Security + +As all outputs in a block are in the scope of an input to be checked, any unrelated/malicious output in a block +_could_ pass an unrelated covenant rule if given the chance. A secure covenant is one that _uniquely_ identifies +one or more outputs. + +## Examples + +### Now or never + +Spend within 10 blocks or burn + +``` +not(filter_relative_height(10)) +``` + +Note, this covenant may be valid when submitted to the mempool, but invalid by the time it is put in a block for +the miner. + +### NFT transfer + +Output features as detailed in [RFC-310-AssetImplementation] (early draft stages, still to be finalised) contain the +NFT details. This covenant preserves both the covenant protecting the token, and the token itself. + +``` +filter_fields_preserved([field::features, field::covenant]) +``` + +### Side-chain checkpointing + +``` +and( + filter_field_int_eq(field::feature_flags, 16) // SIDECHAIN CHECKPOINT = 16 + filter_fields_preserved([field::features, field::covenant, field::script]) +) +``` + +### Restrict spending to a particular commitment if not spent within 100 blocks + +``` +or( + not(filter_relative_height(100)), + filter_fields_hashed_eq([field::commmitment], Hash(xxxx)) +) +``` + +### Output must preserve covenant, features and script or be burnt + +``` +xor( + filter_fields_preserved([field::features, field::covenant, field::script]), + and( + filter_field_int_eq(field::features_flags, 128), // FLAG_BURN = 128 + filter_fields_hashed_eq([field::commitment, field::script], Hash(...)), + ), +) +``` + +### Commission for NFT transfer + +``` +// Must be different outputs +xor( + and( + // Relavant input fields preserved in subsequent output + filter_fields_preserved([fields::features, fields::covenant, fields::script]), + // The spender must obtain the covenent for the subsequent output + filter_fields_hashed_eq([fields::covenant], Hash(xxxx)), + ), + // The spender must obtain and submit the output that matches this hash + filter_output_hash_eq(Hash(xxxx)), +) +``` + +### Other potential covenants + +- `filter_script_eq(script)` +- `filter_covenant_eq(covenant)` +- `filter_script_match()` +- `filter_covenant_match()` + +[commitment]: (./Glossary.md#commitment) +[Tari codebase]: (https://github.com/tari-project/tari) +[Handshake]: https://handshake.org/files/handshake.txt +[cut-through]: https://tlu.tarilabs.com/protocols/grin-protocol-overview/MainReport.html#cut-through +[RFC-0310_AssetImplementation]: https://github.com/tari-project/tari/pull/3340 +[Bitcoin-NG covenants](https://maltemoeser.de/paper/covenants.pdf) +[UTXO](./Glossary.md#unspent-transaction-outputs) +[miniscript](https://medium.com/blockstream/miniscript-bitcoin-scripting-3aeff3853620) +[Elements Covenants](https://blockstream.com/2016/11/02/en-covenants-in-elements-alpha/) +[vaults](https://hackingdistributed.com/2016/02/26/how-to-implement-secure-bitcoin-vaults/) diff --git a/RFC/src/SUMMARY.md b/RFC/src/SUMMARY.md index 7075ac1276..57ef0b72a3 100644 --- a/RFC/src/SUMMARY.md +++ b/RFC/src/SUMMARY.md @@ -27,6 +27,7 @@ - [RFC-0202: TariScript Opcodes](RFC-0202_TariScriptOpcodes.md) - [RFC-0230: Hash time locked contracts](RFC-0230_HTLC.md) - [RFC-0240: Atomic swap](RFC-0240_AtomicSwap.md) + - [RFC-0250: Covenants](RFC-0250_Covenants.md) - [RFC-0322: Validator Node Registration](RFC-0322_VNRegistration.md) - [RFC-0341: Asset registration](RFC-0341_AssetRegistration.md) - [RFC-0300: The Digital Assets Network](RFC-0300_DAN.md) From c286d408f5419620a63783c1ae9fe4d9f5cd68d2 Mon Sep 17 00:00:00 2001 From: Philip Robinson Date: Wed, 17 Nov 2021 12:37:28 +0200 Subject: [PATCH 28/46] =?UTF-8?q?feat(wallet):=20import=20utxo=E2=80=99s?= =?UTF-8?q?=20as=20EncumberedToBeReceived=20rather=20than=20Unspent=20(#35?= =?UTF-8?q?75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description --- When the Wallet imports a UTXO it would set its status to Unspent making it immediately spendable. However, imported outputs are missing a number of pieces of data from the chain that a traditionally received output would have. Completing a validation cycle fills in this data. However, this caused some issues in the mobile clients when importing a UTXO and quickly spending it before the first validation was complete. There were unintended edge cases when a validation completed during an active transaction negotiation. This PR solves this problem by importing utxo’s into the wallet via the Wallet.import_utxo(…) method in the EncumberedToBeReceived status and triggering a new validation immediately. This stops the output from being selected for a transaction before the validation completes which should happen quickly if the wallet is connected to a base node. How Has This Been Tested? --- cargo test --- .../src/output_manager_service/handle.rs | 19 +++++++++++++++ .../src/output_manager_service/service.rs | 22 ++++++++++++++++- .../storage/database.rs | 15 ++++++++++++ .../storage/sqlite_db/mod.rs | 24 +++++++++++++++++++ base_layer/wallet/src/wallet.rs | 2 +- base_layer/wallet/tests/wallet/mod.rs | 4 +--- base_layer/wallet_ffi/src/lib.rs | 20 +++++++++++++++- 7 files changed, 100 insertions(+), 6 deletions(-) diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index ff483c8f3b..710f52307a 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -48,6 +48,7 @@ pub enum OutputManagerRequest { GetBalance, AddOutput(Box), AddOutputWithTxId((TxId, Box)), + AddUnvalidatedOutput((TxId, Box)), UpdateOutputMetadataSignature(Box), GetRecipientTransaction(TransactionSenderMessage), GetCoinbaseTransaction((u64, MicroTari, MicroTari, u64)), @@ -131,6 +132,9 @@ impl fmt::Display for OutputManagerRequest { pre_image, fee_per_gram, ), + OutputManagerRequest::AddUnvalidatedOutput((t, v)) => { + write!(f, "AddUnvalidatedOutput ({}: {})", t, v.value) + }, } } } @@ -234,6 +238,21 @@ impl OutputManagerHandle { } } + pub async fn add_unvalidated_output( + &mut self, + tx_id: TxId, + output: UnblindedOutput, + ) -> Result<(), OutputManagerError> { + match self + .handle + .call(OutputManagerRequest::AddUnvalidatedOutput((tx_id, Box::new(output)))) + .await?? + { + OutputManagerResponse::OutputAdded => Ok(()), + _ => Err(OutputManagerError::UnexpectedApiResponse), + } + } + pub async fn update_output_metadata_signature( &mut self, output: TransactionOutput, diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index e47cbe9db1..e4738d8d09 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -339,6 +339,10 @@ where self.claim_sha_atomic_swap_with_hash(output_hash, pre_image, fee_per_gram) .await }, + OutputManagerRequest::AddUnvalidatedOutput((tx_id, uo)) => self + .add_unvalidated_output(tx_id, *uo) + .await + .map(|_| OutputManagerResponse::OutputAdded), } } @@ -417,7 +421,7 @@ where self.validate_outputs() } - /// Add an unblinded output to the unspent outputs list + /// Add an unblinded output to the outputs table and marks is as `Unspent`. pub async fn add_output(&mut self, tx_id: Option, output: UnblindedOutput) -> Result<(), OutputManagerError> { debug!( target: LOG_TARGET, @@ -431,6 +435,22 @@ where Ok(()) } + /// Add an unblinded output to the outputs table and marks is as `EncumberedToBeReceived`. This is so that it will + /// require a successful validation to confirm that it indeed spendable. + pub async fn add_unvalidated_output( + &mut self, + tx_id: TxId, + output: UnblindedOutput, + ) -> Result<(), OutputManagerError> { + debug!( + target: LOG_TARGET, + "Add unvalidated output of value {} to Output Manager", output.value + ); + let output = DbUnblindedOutput::from_unblinded_output(output, &self.resources.factories)?; + self.resources.db.add_unvalidated_output(tx_id, output).await?; + Ok(()) + } + /// Update an output's metadata signature, akin to 'finalize output' pub async fn update_output_metadata_signature( &mut self, diff --git a/base_layer/wallet/src/output_manager_service/storage/database.rs b/base_layer/wallet/src/output_manager_service/storage/database.rs index 127472d2d9..72caec2822 100644 --- a/base_layer/wallet/src/output_manager_service/storage/database.rs +++ b/base_layer/wallet/src/output_manager_service/storage/database.rs @@ -128,6 +128,8 @@ pub trait OutputManagerBackend: Send + Sync + Clone { &self, current_tip_for_time_lock_calculation: Option, ) -> Result; + /// Import unvalidated output + fn add_unvalidated_output(&self, output: DbUnblindedOutput, tx_id: TxId) -> Result<(), OutputManagerStorageError>; } /// Holds the state of the KeyManager being used by the Output Manager Service @@ -264,6 +266,19 @@ where T: OutputManagerBackend + 'static Ok(()) } + pub async fn add_unvalidated_output( + &self, + tx_id: TxId, + output: DbUnblindedOutput, + ) -> Result<(), OutputManagerStorageError> { + let db_clone = self.db.clone(); + tokio::task::spawn_blocking(move || db_clone.add_unvalidated_output(output, tx_id)) + .await + .map_err(|err| OutputManagerStorageError::BlockingTaskSpawnError(err.to_string()))??; + + Ok(()) + } + pub async fn add_output_to_be_received( &self, tx_id: TxId, diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs index fdd8661e9d..974c4a57fc 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs @@ -1166,6 +1166,30 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } Ok(()) } + + fn add_unvalidated_output(&self, output: DbUnblindedOutput, tx_id: TxId) -> Result<(), OutputManagerStorageError> { + let start = Instant::now(); + let conn = self.database_connection.get_pooled_connection()?; + let acquire_lock = start.elapsed(); + + if OutputSql::find_by_commitment_and_cancelled(&output.commitment.to_vec(), false, &conn).is_ok() { + return Err(OutputManagerStorageError::DuplicateOutput); + } + let mut new_output = NewOutputSql::new(output, OutputStatus::EncumberedToBeReceived, Some(tx_id), None)?; + self.encrypt_if_necessary(&mut new_output)?; + new_output.commit(&conn)?; + + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - reinstate_cancelled_inbound_output: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } + Ok(()) + } } impl TryFrom for OutputStatus { diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 1b71f2744d..1ea209b069 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -373,7 +373,7 @@ where .await?; self.output_manager_service - .add_output_with_tx_id(tx_id, unblinded_output.clone()) + .add_unvalidated_output(tx_id, unblinded_output.clone()) .await?; info!( diff --git a/base_layer/wallet/tests/wallet/mod.rs b/base_layer/wallet/tests/wallet/mod.rs index e2a2df6da2..3dcc79190b 100644 --- a/base_layer/wallet/tests/wallet/mod.rs +++ b/base_layer/wallet/tests/wallet/mod.rs @@ -744,7 +744,7 @@ async fn test_import_utxo() { let balance = alice_wallet.output_manager_service.get_balance().await.unwrap(); - assert_eq!(balance.available_balance, 20000 * uT); + assert_eq!(balance.pending_incoming_balance, 20000 * uT); let completed_tx = alice_wallet .transaction_service @@ -755,8 +755,6 @@ async fn test_import_utxo() { .expect("Tx should be in collection"); assert_eq!(completed_tx.amount, 20000 * uT); - let stored_utxo = alice_wallet.output_manager_service.get_unspent_outputs().await.unwrap()[0].clone(); - assert_eq!(stored_utxo, utxo); } #[test] diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index c783a1147a..fbd9046d76 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -4392,7 +4392,25 @@ pub unsafe extern "C" fn wallet_import_utxo( &(*spending_key).clone(), &Default::default(), )) { - Ok(tx_id) => tx_id, + Ok(tx_id) => { + if let Err(e) = (*wallet) + .runtime + .block_on((*wallet).wallet.output_manager_service.validate_txos()) + { + error = LibWalletError::from(WalletError::OutputManagerError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + if let Err(e) = (*wallet) + .runtime + .block_on((*wallet).wallet.transaction_service.validate_transactions()) + { + error = LibWalletError::from(WalletError::TransactionServiceError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + tx_id + }, Err(e) => { error = LibWalletError::from(e).code; ptr::swap(error_out, &mut error as *mut c_int); From 81f8c379d03fec18165d309d8a43d5c3cafa692d Mon Sep 17 00:00:00 2001 From: Mike the Tike Date: Wed, 17 Nov 2021 14:13:06 +0200 Subject: [PATCH 29/46] ci: split cucumber job into two (#3583) Split circle ci job into two smaller jobs --- .circleci/config.yml | 232 ++++-------------- .../features/WalletTransactions.feature | 2 +- 2 files changed, 44 insertions(+), 190 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f502eb02da..bb6fa80f20 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,97 +4,52 @@ defaults: rust_image: &rust_image quay.io/tarilabs/rust_tari-build-with-deps:nightly-2021-09-18 commands: - test: - description: Run the tests - parameters: - release: - description: Set this to true to compile in release mode. - type: boolean - default: false + cucumber-js: + description: Run cucumber scenarios steps: - run: - name: Calculate dependencies - command: | - rustc --version >rust-version - test -e Cargo.lock || cargo generate-lockfile + name: node -v + command: node -v - run: - name: Install cargo2junit - command: cargo install cargo2junit - - restore_cache: - keys: - - v6-cargo-cache-{{arch}}-{{checksum "rust-version"}}-<>-{{checksum "Cargo.lock"}} + name: npm ci + command: cd integration_tests && npm ci - run: - name: Run tests - command: mkdir -p testresults && cargo test --workspace --all-features -v --jobs=3 <<#parameters.release>>--release<> -- -Z unstable-options --format json --report-time | cargo2junit > testresults/results.xml - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: v6-cargo-cache-{{arch}}-{{checksum "rust-version"}}-<>-{{checksum "Cargo.lock"}} - - store_test_results: - path: testresults - - store_artifacts: - path: testresults - build: - description: Build - parameters: - release: - description: Set this to true to compile in release mode. - type: boolean - default: false - steps: + name: Check formatting + command: cd integration_tests && npm run check-fmt - run: - name: Calculate dependencies - command: | - rustc --version >rust-version - test -e Cargo.lock || cargo generate-lockfile - - restore_cache: - keys: - - v6-cargo-cache-{{arch}}-{{checksum "rust-version"}}-<>-{{checksum "Cargo.lock"}} + name: Check eslint + command: cd integration_tests && npm run lint - run: - name: Build - command: cargo build -v --all --all-features --jobs=3 <<#parameters.release>>--release<> + name: Build base node + command: cargo build --release --bin tari_base_node - run: - name: Build Wallet - command: cargo build -p tari_wallet <<#parameters.release>>--release<> - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: v6-cargo-cache-{{arch}}-{{checksum "rust-version"}}-<>-{{checksum "Cargo.lock"}} - clippy: - description: cargo clippy - steps: + name: Build wallet + command: cargo build --release --bin tari_console_wallet - run: - name: Calculate dependencies - command: | - rustc --version >rust-version - test -e Cargo.lock || cargo generate-lockfile - - restore_cache: - keys: - - v6-cargo-cache-{{arch}}-{{checksum "rust-version"}}-{{checksum "Cargo.lock"}} + name: Build mmproxy + command: cargo build --release --bin tari_merge_mining_proxy - run: - name: Cargo fmt - command: | - TOOLCHAIN=$(awk '/channel = /{print $NF}' rust-toolchain.toml) - rustup component add --toolchain $TOOLCHAIN rustfmt - cargo fmt --all -- --check + name: Build mining_node + command: cargo build --release --bin tari_mining_node - run: - name: Run clippy (main source) - command: | - TOOLCHAIN=$(awk '/channel = /{print $NF}' rust-toolchain.toml) - rustup component add --toolchain $TOOLCHAIN clippy - cargo clippy -- -D warnings -W clippy::cognitive_complexity + name: Build stratum_transcoder + command: cargo build --release --bin tari_stratum_transcoder - run: - name: Run clippy (all targets) - command: cargo clippy --all-targets -- -D warnings - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: v6-cargo-cache-{{arch}}-{{checksum "rust-version"}}-{{checksum "Cargo.lock"}} - cucumber-js: - description: Run cucumber scenarios + name: Run cucumber scenarios + no_output_timeout: 20m + command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --profile "ci" --tags "@critical and not @long-running and not @broken and not @wallet-ffi" --format json:cucumber_output/tests.cucumber --exit --retry 3 --retry-tag-filter "@flaky and not @broken" + - run: + name: Generate report + command: cd integration_tests && node ./generate_report.js + when: always + - store_test_results: + path: integration_tests/cucumber_output + - store_artifacts: + path: integration_tests/cucumber_output + - store_artifacts: + path: integration_tests/temp/reports + cucumber-js-ffi: + description: Run cucumber scenarios (FFI) steps: - run: name: node -v @@ -102,9 +57,6 @@ commands: - run: name: npm ci command: cd integration_tests && npm ci - - run: - name: Check formatting - command: cd integration_tests && npm run check-fmt - run: name: Check eslint command: cd integration_tests && npm run lint @@ -126,14 +78,6 @@ commands: - run: name: Build stratum_transcoder command: cargo build --release --bin tari_stratum_transcoder - - run: - name: Run cucumber scenarios - no_output_timeout: 20m - command: cd integration_tests && mkdir -p cucumber_output && node_modules/.bin/cucumber-js --profile "ci" --tags "@critical and not @long-running and not @broken and not @wallet-ffi" --format json:cucumber_output/tests.cucumber --exit --retry 3 --retry-tag-filter "@flaky and not @broken" - - run: - name: Generate report - command: cd integration_tests && node ./generate_report.js - when: always # Below step requires NodeJS v12 to run correctly, see explanation in WalletFFI.feature - run: name: Run FFI wallet library cucumber scenarios @@ -154,30 +98,6 @@ commands: path: integration_tests/temp/reports jobs: - test-docs: - docker: - - image: *rust_image - steps: - - checkout - - run: - name: RFC documentation - command: | - cargo install mdbook --version ^0.4 - cd RFC && mdbook test && mdbook build - - - persist_to_workspace: - root: . - paths: book - - test-tari-release: - docker: - - image: *rust_image - resource_class: medium - steps: - - checkout - - test: - release: true - run-integration-tests: docker: - image: *rust_image @@ -188,64 +108,16 @@ jobs: - checkout - cucumber-js - build-tari-release: - docker: - - image: *rust_image - resource_class: medium - steps: - - checkout - - build: - release: true - - clippy: + run-ffi-integration-tests: docker: - image: *rust_image resource_class: medium + environment: + CARGO_HTTP_MULTIPLEXING: false steps: - checkout - - clippy + - cucumber-js-ffi - deploy-docs: - docker: - - image: quay.io/tarilabs/git-ssh-client:0.2-alpine - steps: - - checkout - - attach_workspace: - at: . - - add_ssh_keys: - fingerprints: - - "a6:a6:e2:be:a3:94:3e:4c:9d:51:25:f6:98:f9:0c:a4" - - run: - name: Deploy docs to gh-pages branch - command: | - DEST_BRANCH=gh-pages - DEST_PATH=book/ - - if [[ ! -d $DEST_PATH ]]; then - echo "$DEST_PATH directory not found!" - exit 1 - fi - - TMP_DIR=$(mktemp -d /tmp/ghpages_XXXXXX) - - echo "Copying book files to temporary location $TMP_DIR" - cp -R $DEST_PATH/* $DEST_PATH/.nojekyll $TMP_DIR - - REMOTE=$(git remote get-url origin) - - cd $TMP_DIR - - git config --global user.email "ci-build@tari.com" - git config --global user.name "ci-build" - - git init - git checkout -b $DEST_BRANCH - git remote add origin $REMOTE - git add --all . - git commit -m "[skip ci] Update RFC docs" - git push origin $DEST_BRANCH --force - - echo "Published." workflows: version: 2 @@ -255,25 +127,7 @@ workflows: filters: branches: ignore: gh-pages - # - test-docs: - # filters: - # branches: - # ignore: gh-pages - # - deploy-docs: - # requires: - # - test-docs - # filters: - # branches: - # only: development - # - build-tari-release: - # filters: - # branches: - # ignore: gh-pages - # - test-tari-release: - # filters: - # branches: - # ignore: gh-pages - # - clippy: - # filters: - # branches: - # ignore: gh-pages + - run-ffi-integration-tests: + filters: + branches: + ignore: gh-pages \ No newline at end of file diff --git a/integration_tests/features/WalletTransactions.feature b/integration_tests/features/WalletTransactions.feature index 96d7378903..a279582e1b 100644 --- a/integration_tests/features/WalletTransactions.feature +++ b/integration_tests/features/WalletTransactions.feature @@ -1,7 +1,7 @@ @wallet-transact @wallet Feature: Wallet Transactions - @critical + @critical @flaky Scenario: Wallet sending and receiving one-sided transactions Given I have a seed node NODE And I have 1 base nodes connected to all seed nodes From 291380457ba28c6208d2d1ac97757e8cfa8df85c Mon Sep 17 00:00:00 2001 From: Philip Robinson Date: Wed, 17 Nov 2021 14:49:58 +0200 Subject: [PATCH 30/46] feat: add error codes to LibWallet for CipherSeed errors (#3578) Description --- This PR adds explicit error codes to the LibWallet FFI interface to return the errors that can occur when invalid seed words are used during wallet recovery. The new error codes are: - Code 429: KeyManagerError::InvalidData - Code 430: KeyManagerError::VersionMismatch - Code 431: KeyManagerError::DecryptionFailed - Code 432: KeyManagerError::CrcError How Has This Been Tested? --- cargo test --- base_layer/wallet_ffi/src/error.rs | 19 +++++++++++++++++-- base_layer/wallet_ffi/src/lib.rs | 3 ++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/base_layer/wallet_ffi/src/error.rs b/base_layer/wallet_ffi/src/error.rs index 37349dd778..e842530d11 100644 --- a/base_layer/wallet_ffi/src/error.rs +++ b/base_layer/wallet_ffi/src/error.rs @@ -26,14 +26,13 @@ use tari_crypto::{ signatures::SchnorrSignatureError, tari_utilities::{hex::HexError, ByteArrayError}, }; -use tari_key_manager::error::MnemonicError; +use tari_key_manager::error::{KeyManagerError, MnemonicError}; use tari_wallet::{ contacts_service::error::{ContactsServiceError, ContactsServiceStorageError}, error::{WalletError, WalletStorageError}, output_manager_service::error::{OutputManagerError, OutputManagerStorageError}, transaction_service::error::{TransactionServiceError, TransactionStorageError}, }; - use thiserror::Error; const LOG_TARGET: &str = "wallet_ffi::error"; @@ -286,6 +285,22 @@ impl From for LibWalletError { code: 428, message: format!("{:?}", w), }, + WalletError::KeyManagerError(KeyManagerError::InvalidData) => Self { + code: 429, + message: format!("{:?}", w), + }, + WalletError::KeyManagerError(KeyManagerError::VersionMismatch) => Self { + code: 430, + message: format!("{:?}", w), + }, + WalletError::KeyManagerError(KeyManagerError::DecryptionFailed) => Self { + code: 431, + message: format!("{:?}", w), + }, + WalletError::KeyManagerError(KeyManagerError::CrcError) => Self { + code: 432, + message: format!("{:?}", w), + }, // This is the catch all error code. Any error that is not explicitly mapped above will be given this code _ => Self { code: 999, diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index fbd9046d76..43006072de 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -1071,7 +1071,8 @@ pub unsafe extern "C" fn seed_words_push_word( return if let Err(e) = CipherSeed::from_mnemonic(&(*seed_words).0, None) { log::error!( target: LOG_TARGET, - "Problem building valid private seed from seed phrase" + "Problem building valid private seed from seed phrase: {}", + e ); error = LibWalletError::from(WalletError::KeyManagerError(e)).code; ptr::swap(error_out, &mut error as *mut c_int); From 294f45ea8b084a9c55daeea4a778cd5bd3f8be79 Mon Sep 17 00:00:00 2001 From: Mike the Tike Date: Wed, 17 Nov 2021 15:05:04 +0200 Subject: [PATCH 31/46] v0.21.1 --- Cargo.lock | 48 +++++++++---------- applications/tari_app_grpc/Cargo.toml | 2 +- applications/tari_app_utilities/Cargo.toml | 2 +- applications/tari_base_node/Cargo.toml | 2 +- applications/tari_console_wallet/Cargo.toml | 4 +- .../tari_merge_mining_proxy/Cargo.toml | 2 +- applications/tari_mining_node/Cargo.toml | 2 +- applications/test_faucet/Cargo.toml | 2 +- base_layer/common_types/Cargo.toml | 2 +- base_layer/core/Cargo.toml | 2 +- base_layer/key_manager/Cargo.toml | 2 +- base_layer/mmr/Cargo.toml | 2 +- base_layer/p2p/Cargo.toml | 2 +- base_layer/service_framework/Cargo.toml | 2 +- base_layer/tari_stratum_ffi/Cargo.toml | 2 +- base_layer/wallet/Cargo.toml | 2 +- base_layer/wallet_ffi/Cargo.toml | 2 +- changelog.md | 22 +++++++++ common/Cargo.toml | 2 +- comms/Cargo.toml | 2 +- comms/dht/Cargo.toml | 2 +- comms/rpc_macros/Cargo.toml | 2 +- infrastructure/derive/Cargo.toml | 2 +- infrastructure/shutdown/Cargo.toml | 2 +- infrastructure/storage/Cargo.toml | 2 +- infrastructure/test_utils/Cargo.toml | 2 +- package-lock.json | 2 +- 27 files changed, 72 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d674d81979..3e8078ceae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4401,7 +4401,7 @@ dependencies = [ [[package]] name = "tari_app_grpc" -version = "0.21.0" +version = "0.21.1" dependencies = [ "chrono", "prost", @@ -4416,7 +4416,7 @@ dependencies = [ [[package]] name = "tari_app_utilities" -version = "0.21.0" +version = "0.21.1" dependencies = [ "config", "dirs-next 1.0.2", @@ -4441,7 +4441,7 @@ dependencies = [ [[package]] name = "tari_base_node" -version = "0.21.0" +version = "0.21.1" dependencies = [ "anyhow", "bincode", @@ -4503,7 +4503,7 @@ dependencies = [ [[package]] name = "tari_common" -version = "0.21.0" +version = "0.21.1" dependencies = [ "anyhow", "config", @@ -4542,7 +4542,7 @@ dependencies = [ [[package]] name = "tari_common_types" -version = "0.21.0" +version = "0.21.1" dependencies = [ "digest", "futures 0.3.17", @@ -4556,7 +4556,7 @@ dependencies = [ [[package]] name = "tari_comms" -version = "0.21.0" +version = "0.21.1" dependencies = [ "anyhow", "async-trait", @@ -4606,7 +4606,7 @@ dependencies = [ [[package]] name = "tari_comms_dht" -version = "0.21.0" +version = "0.21.1" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -4654,7 +4654,7 @@ dependencies = [ [[package]] name = "tari_comms_rpc_macros" -version = "0.21.0" +version = "0.21.1" dependencies = [ "futures 0.3.17", "proc-macro2 1.0.32", @@ -4669,7 +4669,7 @@ dependencies = [ [[package]] name = "tari_console_wallet" -version = "0.21.0" +version = "0.21.1" dependencies = [ "bitflags 1.3.2", "chrono", @@ -4712,7 +4712,7 @@ dependencies = [ [[package]] name = "tari_core" -version = "0.21.0" +version = "0.21.1" dependencies = [ "async-trait", "bincode", @@ -4794,7 +4794,7 @@ dependencies = [ [[package]] name = "tari_infra_derive" -version = "0.21.0" +version = "0.21.1" dependencies = [ "blake2", "proc-macro2 0.4.30", @@ -4804,7 +4804,7 @@ dependencies = [ [[package]] name = "tari_key_manager" -version = "0.21.0" +version = "0.21.1" dependencies = [ "argon2", "arrayvec 0.7.1", @@ -4824,7 +4824,7 @@ dependencies = [ [[package]] name = "tari_merge_mining_proxy" -version = "0.21.0" +version = "0.21.1" dependencies = [ "anyhow", "bincode", @@ -4870,7 +4870,7 @@ dependencies = [ [[package]] name = "tari_mining_node" -version = "0.21.0" +version = "0.21.1" dependencies = [ "bufstream", "chrono", @@ -4900,7 +4900,7 @@ dependencies = [ [[package]] name = "tari_mmr" -version = "0.21.0" +version = "0.21.1" dependencies = [ "bincode", "blake2", @@ -4919,7 +4919,7 @@ dependencies = [ [[package]] name = "tari_p2p" -version = "0.21.0" +version = "0.21.1" dependencies = [ "anyhow", "bytes 0.5.6", @@ -4963,7 +4963,7 @@ dependencies = [ [[package]] name = "tari_service_framework" -version = "0.21.0" +version = "0.21.1" dependencies = [ "anyhow", "async-trait", @@ -4980,7 +4980,7 @@ dependencies = [ [[package]] name = "tari_shutdown" -version = "0.21.0" +version = "0.21.1" dependencies = [ "futures 0.3.17", "tokio 1.13.0", @@ -4988,7 +4988,7 @@ dependencies = [ [[package]] name = "tari_storage" -version = "0.21.0" +version = "0.21.1" dependencies = [ "bincode", "bytes 0.5.6", @@ -5006,7 +5006,7 @@ dependencies = [ [[package]] name = "tari_stratum_ffi" -version = "0.21.0" +version = "0.21.1" dependencies = [ "hex", "libc", @@ -5060,7 +5060,7 @@ dependencies = [ [[package]] name = "tari_test_utils" -version = "0.21.0" +version = "0.21.1" dependencies = [ "futures 0.3.17", "futures-test", @@ -5091,7 +5091,7 @@ dependencies = [ [[package]] name = "tari_wallet" -version = "0.21.0" +version = "0.21.1" dependencies = [ "aes-gcm 0.8.0", "argon2", @@ -5137,7 +5137,7 @@ dependencies = [ [[package]] name = "tari_wallet_ffi" -version = "0.21.0" +version = "0.21.1" dependencies = [ "chrono", "env_logger 0.7.1", @@ -5190,7 +5190,7 @@ dependencies = [ [[package]] name = "test_faucet" -version = "0.21.0" +version = "0.21.1" dependencies = [ "rand 0.8.4", "serde 1.0.130", diff --git a/applications/tari_app_grpc/Cargo.toml b/applications/tari_app_grpc/Cargo.toml index fff5cfc6b5..f3e7262c06 100644 --- a/applications/tari_app_grpc/Cargo.toml +++ b/applications/tari_app_grpc/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "This crate is to provide a single source for all cross application grpc files and conversions to and from tari::core" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/applications/tari_app_utilities/Cargo.toml b/applications/tari_app_utilities/Cargo.toml index 09f49767a5..48c8f0484f 100644 --- a/applications/tari_app_utilities/Cargo.toml +++ b/applications/tari_app_utilities/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_app_utilities" -version = "0.21.0" +version = "0.21.1" authors = ["The Tari Development Community"] edition = "2018" diff --git a/applications/tari_base_node/Cargo.toml b/applications/tari_base_node/Cargo.toml index 23c2c4916e..440ee59ea8 100644 --- a/applications/tari_base_node/Cargo.toml +++ b/applications/tari_base_node/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "The tari full base node implementation" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index 7378ae8ed2..e7827d6e9d 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_console_wallet" -version = "0.21.0" +version = "0.21.1" authors = ["The Tari Development Community"] edition = "2018" @@ -50,7 +50,7 @@ default-features = false features = ["transactions", "mempool_proto", "base_node_proto"] [dependencies.tui] -version = "0.16" +version = "^0.16" default-features = false features = ["crossterm"] diff --git a/applications/tari_merge_mining_proxy/Cargo.toml b/applications/tari_merge_mining_proxy/Cargo.toml index b8649c0133..fc058caee9 100644 --- a/applications/tari_merge_mining_proxy/Cargo.toml +++ b/applications/tari_merge_mining_proxy/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "The tari merge miner proxy for xmrig" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [features] diff --git a/applications/tari_mining_node/Cargo.toml b/applications/tari_mining_node/Cargo.toml index 9b9632f225..8d3dadd3db 100644 --- a/applications/tari_mining_node/Cargo.toml +++ b/applications/tari_mining_node/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "The tari mining node implementation" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/applications/test_faucet/Cargo.toml b/applications/test_faucet/Cargo.toml index 62e13c4707..1e5ee98504 100644 --- a/applications/test_faucet/Cargo.toml +++ b/applications/test_faucet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_faucet" -version = "0.21.0" +version = "0.21.1" authors = ["The Tari Development Community"] edition = "2018" diff --git a/base_layer/common_types/Cargo.toml b/base_layer/common_types/Cargo.toml index 6e61345a94..a15d105675 100644 --- a/base_layer/common_types/Cargo.toml +++ b/base_layer/common_types/Cargo.toml @@ -3,7 +3,7 @@ name = "tari_common_types" authors = ["The Tari Development Community"] description = "Tari cryptocurrency common types" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index 0d8f4f8801..3f9b98c03c 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [features] diff --git a/base_layer/key_manager/Cargo.toml b/base_layer/key_manager/Cargo.toml index 3c3c69ab64..18c64eb6d5 100644 --- a/base_layer/key_manager/Cargo.toml +++ b/base_layer/key_manager/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "Tari cryptocurrency wallet key management" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/base_layer/mmr/Cargo.toml b/base_layer/mmr/Cargo.toml index a61138f80b..62663d291f 100644 --- a/base_layer/mmr/Cargo.toml +++ b/base_layer/mmr/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "A Merkle Mountain Range implementation" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [features] diff --git a/base_layer/p2p/Cargo.toml b/base_layer/p2p/Cargo.toml index f8ef9e7c04..e3a7b6f07d 100644 --- a/base_layer/p2p/Cargo.toml +++ b/base_layer/p2p/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_p2p" -version = "0.21.0" +version = "0.21.1" authors = ["The Tari Development community"] description = "Tari base layer-specific peer-to-peer communication features" repository = "https://github.com/tari-project/tari" diff --git a/base_layer/service_framework/Cargo.toml b/base_layer/service_framework/Cargo.toml index b51333a151..bc288cf93e 100644 --- a/base_layer/service_framework/Cargo.toml +++ b/base_layer/service_framework/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_service_framework" -version = "0.21.0" +version = "0.21.1" authors = ["The Tari Development Community"] description = "The Tari communication stack service framework" repository = "https://github.com/tari-project/tari" diff --git a/base_layer/tari_stratum_ffi/Cargo.toml b/base_layer/tari_stratum_ffi/Cargo.toml index b6b5effbd4..805be3e4b9 100644 --- a/base_layer/tari_stratum_ffi/Cargo.toml +++ b/base_layer/tari_stratum_ffi/Cargo.toml @@ -3,7 +3,7 @@ name = "tari_stratum_ffi" authors = ["The Tari Development Community"] description = "Tari cryptocurrency miningcore C FFI bindings" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index 33b4d75f2e..13c6e527e3 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -3,7 +3,7 @@ name = "tari_wallet" authors = ["The Tari Development Community"] description = "Tari cryptocurrency wallet library" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/base_layer/wallet_ffi/Cargo.toml b/base_layer/wallet_ffi/Cargo.toml index 1402cd3e14..49ddf8016e 100644 --- a/base_layer/wallet_ffi/Cargo.toml +++ b/base_layer/wallet_ffi/Cargo.toml @@ -3,7 +3,7 @@ name = "tari_wallet_ffi" authors = ["The Tari Development Community"] description = "Tari cryptocurrency wallet C FFI bindings" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/changelog.md b/changelog.md index 45f5bf0402..d2f69f8cfd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,28 @@ # Changelog +### [0.21.1](https://github.com/tari-project/tari/compare/v0.21.0...v0.21.1) (2021-11-17) + + +### Features + +* add atomic swap htlc sending and claiming ([#3552](https://github.com/tari-project/tari/issues/3552)) ([a185506](https://github.com/tari-project/tari/commit/a1855065e0f2aaabbb6d7e508f71d6d0eaf6acd5)) +* add error codes to LibWallet for CipherSeed errors ([#3578](https://github.com/tari-project/tari/issues/3578)) ([2913804](https://github.com/tari-project/tari/commit/291380457ba28c6208d2d1ac97757e8cfa8df85c)) +* add support for MultiAddr in RPC config ([#3557](https://github.com/tari-project/tari/issues/3557)) ([9f8e289](https://github.com/tari-project/tari/commit/9f8e2899922c0eec9167ed4f49c5d4c161330221)) +* get fee for transactions for stratum transcoder ([#3571](https://github.com/tari-project/tari/issues/3571)) ([ccf1da0](https://github.com/tari-project/tari/commit/ccf1da02dcbf99fe1872deec73f424b4328c70e0)) +* implement multiple read single write for sqlite ([#3568](https://github.com/tari-project/tari/issues/3568)) ([8d22164](https://github.com/tari-project/tari/commit/8d22164ca10a4493fe73f66d13edf9ddd57cc6d1)) +* implement prometheus metrics for base node ([#3563](https://github.com/tari-project/tari/issues/3563)) ([433bc46](https://github.com/tari-project/tari/commit/433bc46e3d5cd488ec0f29fef6059594cf0cf3e3)) +* one-click installer - cli edition ([#3534](https://github.com/tari-project/tari/issues/3534)) ([ec67798](https://github.com/tari-project/tari/commit/ec677987a712c934168040da07f31fc744f66f71)) +* trigger time lock balance update when block received ([#3567](https://github.com/tari-project/tari/issues/3567)) ([11b8afa](https://github.com/tari-project/tari/commit/11b8afa31abe7e64ff366f8e83e478b017a86da5)) +* **wallet:** import utxo’s as EncumberedToBeReceived rather than Unspent ([#3575](https://github.com/tari-project/tari/issues/3575)) ([c286d40](https://github.com/tari-project/tari/commit/c286d408f5419620a63783c1ae9fe4d9f5cd68d2)) + + +### Bug Fixes + +* avoid implicit using of the time crate ([#3562](https://github.com/tari-project/tari/issues/3562)) ([23e8398](https://github.com/tari-project/tari/commit/23e83988cb8fe99babd0a96686602added75011a)) +* stop leak of value of recovered output ([#3558](https://github.com/tari-project/tari/issues/3558)) ([e0f2187](https://github.com/tari-project/tari/commit/e0f21876278702aa43096b04aa9e701f0942be67)) +* use time crate instead of chrono ([#3527](https://github.com/tari-project/tari/issues/3527)) ([d211031](https://github.com/tari-project/tari/commit/d211031cfa44ad498706db84e8a919b9babaf422)) + ## [0.21.0](https://github.com/tari-project/tari/compare/v0.13.0...v0.21.0) (2021-11-09) diff --git a/common/Cargo.toml b/common/Cargo.toml index 8dd805cedd..4ed687e6da 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [features] diff --git a/comms/Cargo.toml b/comms/Cargo.toml index 380184bb05..091275ca46 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/comms/dht/Cargo.toml b/comms/dht/Cargo.toml index 80739d331f..a02696fdbb 100644 --- a/comms/dht/Cargo.toml +++ b/comms/dht/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_comms_dht" -version = "0.21.0" +version = "0.21.1" authors = ["The Tari Development Community"] description = "Tari comms DHT module" repository = "https://github.com/tari-project/tari" diff --git a/comms/rpc_macros/Cargo.toml b/comms/rpc_macros/Cargo.toml index 63944e9712..52a73902a4 100644 --- a/comms/rpc_macros/Cargo.toml +++ b/comms/rpc_macros/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [lib] diff --git a/infrastructure/derive/Cargo.toml b/infrastructure/derive/Cargo.toml index 619deeea72..6d7208e964 100644 --- a/infrastructure/derive/Cargo.toml +++ b/infrastructure/derive/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [lib] diff --git a/infrastructure/shutdown/Cargo.toml b/infrastructure/shutdown/Cargo.toml index 60f35c24ff..4a32a93b4b 100644 --- a/infrastructure/shutdown/Cargo.toml +++ b/infrastructure/shutdown/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/infrastructure/storage/Cargo.toml b/infrastructure/storage/Cargo.toml index 29a00c9766..42590b5b0d 100644 --- a/infrastructure/storage/Cargo.toml +++ b/infrastructure/storage/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.0" +version = "0.21.1" edition = "2018" [dependencies] diff --git a/infrastructure/test_utils/Cargo.toml b/infrastructure/test_utils/Cargo.toml index 083b0754f3..0fb9b1f8b2 100644 --- a/infrastructure/test_utils/Cargo.toml +++ b/infrastructure/test_utils/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tari_test_utils" description = "Utility functions used in Tari test functions" -version = "0.21.0" +version = "0.21.1" authors = ["The Tari Development Community"] edition = "2018" license = "BSD-3-Clause" diff --git a/package-lock.json b/package-lock.json index 127e50bdb2..fd3ecdaa80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,4 +1,4 @@ { "lockfileVersion": 1, - "version": "0.14.0" + "version": "0.21.1" } From e191e27ed79aff5cfe4d76effe77473a03eb31f6 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Wed, 17 Nov 2021 15:46:44 +0200 Subject: [PATCH 32/46] feat: improve wallet connectivity status for console wallet (#3577) Description --- - Improved base node connection status feedback to the console wallet UI. - Added disconnect logic for the wallet connectivity service to enable closing bad base node connections if RPC connections cannot be obtained, which will force the connection cycle to restart from scratch, rather than just waiting for the connection to become alive again. - ~~Disabled obtaining latency information after obtaining tip information in the wallet's base node monitor, as it sometimes introduces huge additional delays.~~ ~~_**Note:** Fixing this behaviour is marked for another PR._~~ _**(Fixed in PR #3579)**_ Motivation and Context --- - Connectivity status was not always current in the console wallet under certain edge cases, observed when switching base nodes. - Retrying to obtain an RPC connection from a bad base node connection is not efficient; this sometimes results in the base node staying offline for extended (> 30 minutes) durations. How Has This Been Tested? --- - System-level testing: - Switching base nodes and observing the connection status while (a) mining and (b) monitoring transactions to be mined. - Stress testing `make-it-rain`. - Stress testing `coin-split`. - Stress testing simaltaneous `make-it-rain` and `coin-split`. See sample profiling measurements below depicting the added delay sometimes introduced by obtaining latency information, _**without the fix mentioned above**_: ``` rust 2021-11-16 14:18:07.490078500 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain RPC client 160 ms 2021-11-16 14:18:08.062610200 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain tip info in 572 ms 2021-11-16 14:18:08.062646600 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain latency info in 0 ms 2021-11-16 14:18:08.062944600 [wallet::base_node_service::chain_metadata_monitor] DEBUG Base node ece4c616a156c1e4fe34d94d8c Tip: 52127 (Synced) Latency: 572 ms 2021-11-16 14:18:37.491546100 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain RPC client 0 ms 2021-11-16 14:20:28.971178900 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain tip info in 111479 ms 2021-11-16 14:25:07.794774200 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain latency info in 278823 ms 2021-11-16 14:25:07.795167000 [wallet::base_node_service::chain_metadata_monitor] DEBUG Base node ece4c616a156c1e4fe34d94d8c Tip: 52127 (Synced) Latency: 11011 ms 2021-11-16 14:25:26.789903600 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain RPC client 0 ms 2021-11-16 14:30:35.914526200 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain tip info in 309124 ms 2021-11-16 14:34:04.569556900 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain latency info in 208654 ms 2021-11-16 14:34:04.569945400 [wallet::base_node_service::chain_metadata_monitor] DEBUG Base node ece4c616a156c1e4fe34d94d8c Tip: 52130 (Synced) Latency: 9699 ms 2021-11-16 14:34:24.885048300 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain RPC client 0 ms 2021-11-16 14:34:44.260586300 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain tip info in 19375 ms 2021-11-16 14:34:44.260734900 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain latency info in 0 ms 2021-11-16 14:34:44.261005000 [wallet::base_node_service::chain_metadata_monitor] DEBUG Base node ece4c616a156c1e4fe34d94d8c Tip: 52133 (Synced) Latency: 10712 ms 2021-11-16 14:35:03.552244200 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain RPC client 0 ms 2021-11-16 14:35:22.417825800 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain tip info in 18865 ms 2021-11-16 14:35:22.417869200 [wallet::base_node_service::chain_metadata_monitor] TRACE Obtain latency info in 0 ms 2021-11-16 14:35:22.418075300 [wallet::base_node_service::chain_metadata_monitor] DEBUG Base node ece4c616a156c1e4fe34d94d8c Tip: 52134 (Synced) Latency: 18865 ms ``` --- .../wallet/src/base_node_service/monitor.rs | 37 +++++++++---------- .../src/connectivity_service/service.rs | 26 ++++++++++++- .../tests/transaction_service/service.rs | 4 +- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/base_layer/wallet/src/base_node_service/monitor.rs b/base_layer/wallet/src/base_node_service/monitor.rs index 11003eff8b..9ef2452086 100644 --- a/base_layer/wallet/src/base_node_service/monitor.rs +++ b/base_layer/wallet/src/base_node_service/monitor.rs @@ -119,7 +119,7 @@ where .obtain_base_node_wallet_rpc_client() .await .ok_or(BaseNodeMonitorError::NodeShuttingDown)?; - debug!( + trace!( target: LOG_TARGET, "Obtain RPC client {} ms", timer.elapsed().as_millis() @@ -144,36 +144,27 @@ where .and_then(|metadata| { ChainMetadata::try_from(metadata).map_err(BaseNodeMonitorError::InvalidBaseNodeResponse) })?; - debug!( + trace!( target: LOG_TARGET, - "get_tip_info took {} ms", + "Obtain tip info in {} ms", timer.elapsed().as_millis() ); + let timer = Instant::now(); let latency = match client.get_last_request_latency().await? { Some(latency) => latency, None => continue, }; - - let is_synced = tip_info.is_synced; - debug!( - target: LOG_TARGET, - "Base node {} Tip: {} ({}) Latency: {} ms", - base_node_id, - chain_metadata.height_of_longest_chain(), - if is_synced { "Synced" } else { "Syncing..." }, - latency.as_millis() - ); - - let timer = Instant::now(); - self.db.set_chain_metadata(chain_metadata.clone()).await?; trace!( target: LOG_TARGET, - "Update metadata in db {} ms", + "Obtain latency info in {} ms", timer.elapsed().as_millis() ); - let timer = Instant::now(); + self.db.set_chain_metadata(chain_metadata.clone()).await?; + + let is_synced = tip_info.is_synced; + let height_of_longest_chain = chain_metadata.height_of_longest_chain(); self.map_state(move |_| BaseNodeState { chain_metadata: Some(chain_metadata), is_synced: Some(is_synced), @@ -181,7 +172,15 @@ where latency: Some(latency), }) .await; - trace!(target: LOG_TARGET, "Publish event {} ms", timer.elapsed().as_millis()); + + debug!( + target: LOG_TARGET, + "Base node {} Tip: {} ({}) Latency: {} ms", + base_node_id, + height_of_longest_chain, + if is_synced { "Synced" } else { "Syncing..." }, + latency.as_millis() + ); let delay = time::sleep(self.interval.saturating_sub(latency)); if interrupt(base_node_watch.changed(), delay).await.is_none() { diff --git a/base_layer/wallet/src/connectivity_service/service.rs b/base_layer/wallet/src/connectivity_service/service.rs index a3df1874b4..d558389747 100644 --- a/base_layer/wallet/src/connectivity_service/service.rs +++ b/base_layer/wallet/src/connectivity_service/service.rs @@ -88,6 +88,7 @@ impl WalletConnectivityService { debug!(target: LOG_TARGET, "Wallet connectivity service has started."); let mut check_connection = time::interval_at(time::Instant::now() + Duration::from_secs(5), Duration::from_secs(5)); + self.set_online_status(OnlineStatus::Offline); check_connection.set_missed_tick_behavior(MissedTickBehavior::Delay); loop { tokio::select! { @@ -116,6 +117,7 @@ impl WalletConnectivityService { if let Some(pool) = self.pools.as_ref() { if !pool.base_node_wallet_rpc_client.is_connected().await { debug!(target: LOG_TARGET, "Peer connection lost. Attempting to reconnect..."); + self.set_online_status(OnlineStatus::Offline); self.setup_base_node_connection().await; } } @@ -155,6 +157,9 @@ impl WalletConnectivityService { target: LOG_TARGET, "Base node connection failed: {}. Reconnecting...", e ); + if let Some(node_id) = self.current_base_node() { + self.disconnect_base_node(node_id).await; + }; self.pending_requests.push(reply.into()); }, }, @@ -185,6 +190,9 @@ impl WalletConnectivityService { target: LOG_TARGET, "Base node connection failed: {}. Reconnecting...", e ); + if let Some(node_id) = self.current_base_node() { + self.disconnect_base_node(node_id).await; + }; self.pending_requests.push(reply.into()); }, }, @@ -205,6 +213,14 @@ impl WalletConnectivityService { self.base_node_watch.borrow().as_ref().map(|p| p.node_id.clone()) } + async fn disconnect_base_node(&mut self, node_id: NodeId) { + if let Ok(Some(connection)) = self.connectivity.get_connection(node_id.clone()).await { + if connection.clone().disconnect().await.is_ok() { + debug!(target: LOG_TARGET, "Disconnected base node peer {}", node_id); + } + }; + } + async fn setup_base_node_connection(&mut self) { self.pools = None; loop { @@ -231,15 +247,18 @@ impl WalletConnectivityService { }, Ok(false) => { // Retry with updated peer + self.disconnect_base_node(node_id).await; + time::sleep(self.config.base_node_monitor_refresh_interval).await; continue; }, Err(e) => { - if self.current_base_node() != Some(node_id) { + if self.current_base_node() != Some(node_id.clone()) { self.set_online_status(OnlineStatus::Connecting); } else { self.set_online_status(OnlineStatus::Offline); } warn!(target: LOG_TARGET, "{}", e); + self.disconnect_base_node(node_id).await; time::sleep(self.config.base_node_monitor_refresh_interval).await; continue; }, @@ -254,7 +273,10 @@ impl WalletConnectivityService { async fn try_setup_rpc_pool(&mut self, peer: NodeId) -> Result { let conn = match self.try_dial_peer(peer.clone()).await? { Some(c) => c, - None => return Ok(false), + None => { + warn!(target: LOG_TARGET, "Could not dial base node peer {}", peer); + return Ok(false); + }, }; debug!( target: LOG_TARGET, diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index d9b058ad49..acebd6425a 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -998,8 +998,8 @@ fn test_htlc_send_and_claim() { .expect("Alice sending HTLC transaction") }); - let mut alice_ts_clone2 = alice_ts.clone(); - let mut alice_oms_clone = alice_oms.clone(); + let mut alice_ts_clone2 = alice_ts; + let mut alice_oms_clone = alice_oms; runtime.block_on(async move { let completed_tx = alice_ts_clone2 .get_completed_transaction(tx_id) From 337bc6f1b11abc0f53cdc3a82a0aa5110e1fe856 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Wed, 17 Nov 2021 19:16:38 +0200 Subject: [PATCH 33/46] feat: add atomic swap refund transaction handling (#3573) Description --- This Pr adds the following things: Atomic swap refund transactions Proper handling of the script context Transaction spend priority SQL query for filtering locked outputs Fix broken get_balance tests Motivation and Context --- One of the features that are required for atomic swaps is to be able to process refunds. This PR now allows atomic swap refunds to be processed after the time lock of the HTLC has passed. This can happen in a manual way where the user manually asks the console-wallet via the CLI, or if the time lock has passed the wallet will automatically spend this UTXO first. One of the missing features of TariScript is the `scriptContext`, which allows scripts to validate the blockchain state such as block height that is required in this case. This is now piped in anywhere where the script is validated if possible. If the state cannot be gathered, the state is filled in with default values which is how it operated up to now. This adds in a transaction spend priority that can allow UTXO to be given a priority to be spent. This is the case for high-risk UTXO such as HTLC which more than one party can claim. Manual claim for these transactions is still advised as they can be claimed as soon as possible, but this adds a second automated state where if you want to spend some UTXO at least pick the ones that you need to spend more urgently than the other ones. The current `select_utxos` function uses a rust function to filter the maturity of the UTXOs to only select UTXOs it can spend. This PR completely swaps out the selection to be based completely on SQL queries which should be much faster. All current get_balance tests are broken in where the available and time-locked balance is asserted to be the same. This is not due to the function being broken but rather the test and the testing code. What happened is that tests use a tip_height of `u64::MAX` but this is too large as the database uses `i64::MAX`. The problem comes when `u64::MAX` is cast as `i64` as this is equal to `-1` and when the query `FROM outputs WHERE status = ? AND maturity > ? AND script_lock_height > ?` is run, it select everything as the tip is now `-1`. This ensures that the testing tip is selected as `i64::MAX` to let the queries run correctly. How Has This Been Tested? --- Added new tests. --- applications/tari_app_grpc/proto/types.proto | 2 + applications/tari_app_grpc/proto/wallet.proto | 13 +- .../src/conversions/unblinded_output.rs | 2 + applications/tari_base_node/src/builder.rs | 1 + .../src/automation/command_parser.rs | 14 + .../src/automation/commands.rs | 27 ++ .../src/grpc/wallet_grpc_server.rs | 56 +++- .../unconfirmed_pool/unconfirmed_pool.rs | 8 +- .../core/src/transactions/aggregated_body.rs | 15 +- .../core/src/transactions/coinbase_builder.rs | 7 +- .../core/src/transactions/test_helpers.rs | 14 +- .../core/src/transactions/transaction.rs | 50 +++- .../transaction_protocol/sender.rs | 50 ++-- .../transaction_initializer.rs | 34 ++- .../block_validators/async_validator.rs | 11 +- base_layer/core/src/validation/helpers.rs | 2 + .../src/validation/transaction_validators.rs | 23 +- base_layer/core/tests/mempool.rs | 4 +- base_layer/core/tests/node_comms_interface.rs | 1 + .../down.sql | 0 .../up.sql | 31 +++ .../mock_base_node_service.rs | 2 +- .../src/output_manager_service/handle.rs | 66 +++-- .../recovery/standard_outputs_recoverer.rs | 4 +- .../src/output_manager_service/service.rs | 247 ++++++++++++------ .../storage/database.rs | 51 +++- .../output_manager_service/storage/models.rs | 32 +++ .../storage/sqlite_db/mod.rs | 62 ++++- .../storage/sqlite_db/output_sql.rs | 71 ++++- base_layer/wallet/src/schema.rs | 3 + .../protocols/transaction_receive_protocol.rs | 15 +- .../protocols/transaction_send_protocol.rs | 18 +- .../wallet/src/transaction_service/service.rs | 96 ++++--- .../transaction_service/storage/sqlite_db.rs | 2 +- base_layer/wallet/src/wallet.rs | 7 +- .../tests/output_manager_service/service.rs | 117 +++++---- .../tests/output_manager_service/storage.rs | 12 +- base_layer/wallet/tests/support/comms_rpc.rs | 2 +- .../tests/transaction_service/service.rs | 102 ++++---- .../tests/transaction_service/storage.rs | 2 +- base_layer/wallet/tests/wallet/mod.rs | 7 +- base_layer/wallet_ffi/src/lib.rs | 2 + clients/wallet_grpc_client/index.js | 1 + .../features/WalletTransfer.feature | 20 +- integration_tests/features/support/steps.js | 80 +++++- integration_tests/helpers/walletClient.js | 4 + 46 files changed, 1054 insertions(+), 336 deletions(-) create mode 100644 base_layer/wallet/migrations/2021-11-11-094000_add_script_lock_height/down.sql create mode 100644 base_layer/wallet/migrations/2021-11-11-094000_add_script_lock_height/up.sql diff --git a/applications/tari_app_grpc/proto/types.proto b/applications/tari_app_grpc/proto/types.proto index 49306660b7..34db0289e5 100644 --- a/applications/tari_app_grpc/proto/types.proto +++ b/applications/tari_app_grpc/proto/types.proto @@ -288,6 +288,8 @@ message UnblindedOutput { bytes sender_offset_public_key = 8; // UTXO signature with the script offset private key, k_O ComSignature metadata_signature = 9; + // The minimum height the script allows this output to be spent + uint64 script_lock_height = 10; } // ----------------------------- Network Types ----------------------------- // diff --git a/applications/tari_app_grpc/proto/wallet.proto b/applications/tari_app_grpc/proto/wallet.proto index 4aee184a72..99319145e8 100644 --- a/applications/tari_app_grpc/proto/wallet.proto +++ b/applications/tari_app_grpc/proto/wallet.proto @@ -54,12 +54,14 @@ service Wallet { rpc ListConnectedPeers(Empty) returns (ListConnectedPeersResponse); // Cancel pending transaction rpc CancelTransaction (CancelTransactionRequest) returns (CancelTransactionResponse); - // Will triggger a complete revalidation of all wallet outputs. + // Will trigger a complete revalidation of all wallet outputs. rpc RevalidateAllTransactions (RevalidateRequest) returns (RevalidateResponse); // This will send a XTR SHA Atomic swap transaction rpc SendShaAtomicSwapTransaction(SendShaAtomicSwapRequest) returns (SendShaAtomicSwapResponse); // This will claim a XTR SHA Atomic swap transaction rpc ClaimShaAtomicSwapTransaction(ClaimShaAtomicSwapRequest) returns (ClaimShaAtomicSwapResponse); + // This will claim a HTLC refund transaction + rpc ClaimHtlcRefundTransaction(ClaimHtlcRefundRequest) returns (ClaimHtlcRefundResponse); } message GetVersionRequest { } @@ -125,6 +127,15 @@ message ClaimShaAtomicSwapResponse { TransferResult results = 1; } +message ClaimHtlcRefundRequest{ + string output_hash = 1; + uint64 fee_per_gram = 2; +} + +message ClaimHtlcRefundResponse { + TransferResult results = 1; +} + message GetTransactionInfoRequest { repeated uint64 transaction_ids = 1; } diff --git a/applications/tari_app_grpc/src/conversions/unblinded_output.rs b/applications/tari_app_grpc/src/conversions/unblinded_output.rs index bf9efa58bc..f452928e12 100644 --- a/applications/tari_app_grpc/src/conversions/unblinded_output.rs +++ b/applications/tari_app_grpc/src/conversions/unblinded_output.rs @@ -49,6 +49,7 @@ impl From for grpc::UnblindedOutput { signature_u: Vec::from(output.metadata_signature.u().as_bytes()), signature_v: Vec::from(output.metadata_signature.v().as_bytes()), }), + script_lock_height: output.script_lock_height, } } } @@ -91,6 +92,7 @@ impl TryFrom for UnblindedOutput { script_private_key, sender_offset_public_key, metadata_signature, + script_lock_height: output.script_lock_height, }) } } diff --git a/applications/tari_base_node/src/builder.rs b/applications/tari_base_node/src/builder.rs index ca7d6d2cab..bdacc47765 100644 --- a/applications/tari_base_node/src/builder.rs +++ b/applications/tari_base_node/src/builder.rs @@ -246,6 +246,7 @@ async fn build_node_context( Box::new(TxInternalConsistencyValidator::new( factories.clone(), config.base_node_bypass_range_proof_verification, + blockchain_db.clone(), )), Box::new(TxInputAndMaturityValidator::new(blockchain_db.clone())), Box::new(TxConsensusValidator::new(blockchain_db.clone())), diff --git a/applications/tari_console_wallet/src/automation/command_parser.rs b/applications/tari_console_wallet/src/automation/command_parser.rs index 89c90c6aac..0ed8acf4a6 100644 --- a/applications/tari_console_wallet/src/automation/command_parser.rs +++ b/applications/tari_console_wallet/src/automation/command_parser.rs @@ -59,6 +59,7 @@ impl Display for ParsedCommand { ClearCustomBaseNode => "clear-custom-base-node", InitShaAtomicSwap => "init-sha-atomic-swap", FinaliseShaAtomicSwap => "finalise-sha-atomic-swap", + ClaimShaAtomicSwapRefund => "claim-sha-atomic-swap-refund", }; let args = self @@ -130,6 +131,7 @@ pub fn parse_command(command: &str) -> Result { ClearCustomBaseNode => Vec::new(), InitShaAtomicSwap => parse_init_sha_atomic_swap(args)?, FinaliseShaAtomicSwap => parse_finalise_sha_atomic_swap(args)?, + ClaimShaAtomicSwapRefund => parse_claim_htlc_refund_refund(args)?, }; Ok(ParsedCommand { command, args }) @@ -219,6 +221,18 @@ fn parse_finalise_sha_atomic_swap(mut args: SplitWhitespace) -> Result Result, ParseError> { + let mut parsed_args = Vec::new(); + // hash + let hash = args + .next() + .ok_or_else(|| ParseError::Empty("Output hash".to_string()))?; + let hash = parse_hash(hash).ok_or(ParseError::Hash)?; + parsed_args.push(ParsedArgument::Hash(hash)); + + Ok(parsed_args) +} + fn parse_make_it_rain(mut args: SplitWhitespace) -> Result, ParseError> { let mut parsed_args = Vec::new(); diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 58a7d80a31..8468ec8a18 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -88,6 +88,7 @@ pub enum WalletCommand { ClearCustomBaseNode, InitShaAtomicSwap, FinaliseShaAtomicSwap, + ClaimShaAtomicSwapRefund, } #[derive(Debug, EnumString, PartialEq, Clone)] @@ -205,6 +206,27 @@ pub async fn finalise_sha_atomic_swap( Ok(tx_id) } +/// claims a HTLC refund transaction +pub async fn claim_htlc_refund( + mut output_service: OutputManagerHandle, + mut transaction_service: TransactionServiceHandle, + args: Vec, +) -> Result { + use ParsedArgument::*; + let output = match args[0].clone() { + Hash(output) => Ok(output), + _ => Err(CommandError::Argument), + }?; + + let (tx_id, fee, amount, tx) = output_service + .create_htlc_refund_transaction(output, MicroTari(25)) + .await?; + transaction_service + .submit_transaction(tx_id, tx, fee, amount, "Claimed HTLC refund".into()) + .await?; + Ok(tx_id) +} + /// Send a one-sided transaction to a recipient pub async fn send_one_sided( mut wallet_transaction_service: TransactionServiceHandle, @@ -759,6 +781,11 @@ pub async fn command_runner( debug!(target: LOG_TARGET, "claiming tari HTLC tx_id {}", tx_id); tx_ids.push(tx_id); }, + ClaimShaAtomicSwapRefund => { + let tx_id = claim_htlc_refund(output_service.clone(), transaction_service.clone(), parsed.args).await?; + debug!(target: LOG_TARGET, "claiming tari HTLC tx_id {}", tx_id); + tx_ids.push(tx_id); + }, } } diff --git a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs index 129cf06da8..18e19d6a18 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -7,6 +7,8 @@ use tari_app_grpc::{ tari_rpc::{ payment_recipient::PaymentType, wallet_server, + ClaimHtlcRefundRequest, + ClaimHtlcRefundResponse, ClaimShaAtomicSwapRequest, ClaimShaAtomicSwapResponse, CoinSplitRequest, @@ -189,7 +191,7 @@ impl wallet_server::Wallet for WalletGrpcServer { "Transaction broadcast: {}, preimage_hex: {}, hash {}", tx_id, pre_image.to_hex(), - output.to_string() + output.hash().to_hex() ); SendShaAtomicSwapResponse { transaction_id: tx_id, @@ -226,7 +228,7 @@ impl wallet_server::Wallet for WalletGrpcServer { .map_err(|_| Status::internal("pre_image is malformed".to_string()))?; let output = BlockHash::from_hex(&message.output) .map_err(|_| Status::internal("Output hash is malformed".to_string()))?; - + debug!(target: LOG_TARGET, "Trying to claim HTLC with hash {}", output.to_hex()); let mut transaction_service = self.get_transaction_service(); let mut output_manager_service = self.get_output_manager_service(); let response = match output_manager_service @@ -274,6 +276,56 @@ impl wallet_server::Wallet for WalletGrpcServer { })) } + async fn claim_htlc_refund_transaction( + &self, + request: Request, + ) -> Result, Status> { + let message = request.into_inner(); + let output = BlockHash::from_hex(&message.output_hash) + .map_err(|_| Status::internal("Output hash is malformed".to_string()))?; + + let mut transaction_service = self.get_transaction_service(); + let mut output_manager_service = self.get_output_manager_service(); + debug!(target: LOG_TARGET, "Trying to claim HTLC with hash {}", output.to_hex()); + let response = match output_manager_service + .create_htlc_refund_transaction(output, message.fee_per_gram.into()) + .await + { + Ok((tx_id, fee, amount, tx)) => { + match transaction_service + .submit_transaction(tx_id, tx, fee, amount, "Creating HTLC refund transaction".to_string()) + .await + { + Ok(()) => TransferResult { + address: Default::default(), + transaction_id: tx_id, + is_success: true, + failure_message: Default::default(), + }, + Err(e) => TransferResult { + address: Default::default(), + transaction_id: Default::default(), + is_success: false, + failure_message: e.to_string(), + }, + } + }, + Err(e) => { + warn!(target: LOG_TARGET, "Failed to claim HTLC refund transaction: {}", e); + TransferResult { + address: Default::default(), + transaction_id: Default::default(), + is_success: false, + failure_message: e.to_string(), + } + }, + }; + + Ok(Response::new(ClaimHtlcRefundResponse { + results: Some(response), + })) + } + async fn transfer(&self, request: Request) -> Result, Status> { let message = request.into_inner(); let recipients = message diff --git a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs index a97d1e5773..5e979b296b 100644 --- a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs +++ b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs @@ -596,8 +596,12 @@ mod test { .unwrap(); let factories = CryptoFactories::default(); - let mut stx_protocol = stx_builder.build::(&factories).unwrap(); - stx_protocol.finalize(KernelFeatures::empty(), &factories).unwrap(); + let mut stx_protocol = stx_builder + .build::(&factories, None, Some(u64::MAX)) + .unwrap(); + stx_protocol + .finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) + .unwrap(); let tx3 = stx_protocol.get_transaction().unwrap().clone(); diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index 45a30c730b..f32fa6d4b8 100644 --- a/base_layer/core/src/transactions/aggregated_body.rs +++ b/base_layer/core/src/transactions/aggregated_body.rs @@ -41,12 +41,14 @@ use log::*; use serde::{Deserialize, Serialize}; use std::{ cmp::max, + convert::TryInto, fmt::{Display, Error, Formatter}, }; use tari_common_types::types::{ BlindingFactor, Commitment, CommitmentFactory, + HashOutput, PrivateKey, PublicKey, RangeProofService, @@ -55,6 +57,7 @@ use tari_crypto::{ commitment::HomomorphicCommitmentFactory, keys::PublicKey as PublicKeyTrait, ristretto::pedersen::PedersenCommitment, + script::ScriptContext, tari_utilities::hex::Hex, }; @@ -342,6 +345,7 @@ impl AggregateBody { /// This function does NOT check that inputs come from the UTXO set /// The reward is the total amount of Tari rewarded for this block (block reward + total fees), this should be 0 /// for a transaction + #[allow(clippy::too_many_arguments)] pub fn validate_internal_consistency( &self, tx_offset: &BlindingFactor, @@ -349,6 +353,8 @@ impl AggregateBody { bypass_range_proof_verification: bool, total_reward: MicroTari, factories: &CryptoFactories, + prev_header: Option, + height: Option, ) -> Result<(), TransactionError> { self.verify_kernel_signatures()?; @@ -361,7 +367,7 @@ impl AggregateBody { self.verify_metadata_signatures()?; let script_offset_g = PublicKey::from_secret_key(script_offset); - self.validate_script_offset(script_offset_g, &factories.commitment) + self.validate_script_offset(script_offset_g, &factories.commitment, prev_header, height) } pub fn dissolve(self) -> (Vec, Vec, Vec) { @@ -425,12 +431,17 @@ impl AggregateBody { &self, script_offset: PublicKey, factory: &CommitmentFactory, + prev_header: Option, + height: Option, ) -> Result<(), TransactionError> { trace!(target: LOG_TARGET, "Checking script offset"); // lets count up the input script public keys let mut input_keys = PublicKey::default(); + let prev_hash: [u8; 32] = prev_header.unwrap_or_default().as_slice().try_into().unwrap_or([0; 32]); + let height = height.unwrap_or_default(); for input in &self.inputs { - input_keys = input_keys + input.run_and_verify_script(factory)?; + let context = ScriptContext::new(height, &prev_hash, &input.commitment); + input_keys = input_keys + input.run_and_verify_script(factory, Some(context))?; } // Now lets gather the output public keys and hashes. diff --git a/base_layer/core/src/transactions/coinbase_builder.rs b/base_layer/core/src/transactions/coinbase_builder.rs index 3c75d7fa84..c34153c2bd 100644 --- a/base_layer/core/src/transactions/coinbase_builder.rs +++ b/base_layer/core/src/transactions/coinbase_builder.rs @@ -208,6 +208,7 @@ impl CoinbaseBuilder { script_private_key, sender_offset_public_key, metadata_sig, + 0, ); let output = if let Some(rewind_data) = self.rewind_data.as_ref() { unblinded_output @@ -235,7 +236,7 @@ impl CoinbaseBuilder { .with_reward(total_reward) .with_kernel(kernel); let tx = builder - .build(&self.factories) + .build(&self.factories, None, Some(height)) .map_err(|e| CoinbaseBuildError::BuildError(e.to_string()))?; Ok((tx, unblinded_output)) } @@ -525,7 +526,9 @@ mod test { &PrivateKey::default(), false, block_reward, - &factories + &factories, + None, + Some(u64::MAX) ), Ok(()) ); diff --git a/base_layer/core/src/transactions/test_helpers.rs b/base_layer/core/src/transactions/test_helpers.rs index 4fe0be260f..7a8c9dbf76 100644 --- a/base_layer/core/src/transactions/test_helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -155,6 +155,7 @@ impl TestParams { self.script_private_key.clone(), self.sender_offset_public_key.clone(), metadata_signature, + 0, ) } @@ -444,8 +445,10 @@ pub fn create_transaction_with( stx_builder.with_output(utxo, script_offset_pvt_key).unwrap(); }); - let mut stx_protocol = stx_builder.build::(&factories).unwrap(); - stx_protocol.finalize(KernelFeatures::empty(), &factories).unwrap(); + let mut stx_protocol = stx_builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + stx_protocol + .finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) + .unwrap(); stx_protocol.take_transaction().unwrap() } @@ -513,7 +516,7 @@ pub fn spend_utxos(schema: TransactionSchema) -> (Transaction, Vec(&factories).unwrap(); + let mut stx_protocol = stx_builder.build::(&factories, None, Some(u64::MAX)).unwrap(); let change = stx_protocol.get_change_amount().unwrap(); // The change output is assigned its own random script offset private key let change_sender_offset_public_key = stx_protocol.get_change_sender_offset_public_key().unwrap().unwrap(); @@ -539,9 +542,12 @@ pub fn spend_utxos(schema: TransactionSchema) -> (Transaction, Vec UnblindedOutput { UnblindedOutput { value, @@ -326,6 +331,7 @@ impl UnblindedOutput { script_private_key, sender_offset_public_key, metadata_signature, + script_lock_height, } } @@ -534,8 +540,9 @@ impl TransactionInput { /// This will run the script contained in the TransactionInput, returning either a script error or the resulting /// public key. - pub fn run_script(&self) -> Result { - match self.script.execute(&self.input_data)? { + pub fn run_script(&self, context: Option) -> Result { + let context = context.unwrap_or_default(); + match self.script.execute_with_context(&self.input_data, &context)? { StackItem::PublicKey(pubkey) => Ok(pubkey), _ => Err(TransactionError::ScriptExecutionError( "The script executed successfully but it did not leave a public key on the stack".to_string(), @@ -569,8 +576,12 @@ impl TransactionInput { /// This will run the script and verify the script signature. If its valid, it will return the resulting public key /// from the script. - pub fn run_and_verify_script(&self, factory: &CommitmentFactory) -> Result { - let key = self.run_script()?; + pub fn run_and_verify_script( + &self, + factory: &CommitmentFactory, + context: Option, + ) -> Result { + let key = self.run_script(context)?; self.validate_script_signature(&key, factory)?; Ok(key) } @@ -1236,6 +1247,8 @@ impl Transaction { bypass_range_proof_verification: bool, factories: &CryptoFactories, reward: Option, + prev_header: Option, + height: Option, ) -> Result<(), TransactionError> { let reward = reward.unwrap_or_else(|| 0 * uT); self.body.validate_internal_consistency( @@ -1244,6 +1257,8 @@ impl Transaction { bypass_range_proof_verification, reward, factories, + prev_header, + height, ) } @@ -1382,11 +1397,16 @@ impl TransactionBuilder { } /// Build the transaction. - pub fn build(self, factories: &CryptoFactories) -> Result { + pub fn build( + self, + factories: &CryptoFactories, + prev_header: Option, + height: Option, + ) -> Result { if let (Some(script_offset), Some(offset)) = (self.script_offset, self.offset) { let (i, o, k) = self.body.dissolve(); let tx = Transaction::new(i, o, k, offset, script_offset); - tx.validate_internal_consistency(true, factories, self.reward)?; + tx.validate_internal_consistency(true, factories, self.reward, prev_header, height)?; Ok(tx) } else { Err(TransactionError::ValidationError( @@ -1639,7 +1659,9 @@ mod test { let (tx, _, _) = test_helpers::create_tx(5000.into(), 3.into(), 1, 2, 1, 4); let factories = CryptoFactories::default(); - assert!(tx.validate_internal_consistency(false, &factories, None).is_ok()); + assert!(tx + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .is_ok()); } #[test] @@ -1652,7 +1674,9 @@ mod test { assert_eq!(tx.body.kernels().len(), 1); let factories = CryptoFactories::default(); - assert!(tx.validate_internal_consistency(false, &factories, None).is_ok()); + assert!(tx + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .is_ok()); let schema = txn_schema!(from: vec![outputs[1].clone()], to: vec![1 * T, 2 * T]); let (tx2, _outputs, _) = test_helpers::spend_utxos(schema); @@ -1683,11 +1707,13 @@ mod test { } // Validate basis transaction where cut-through has not been applied. - assert!(tx3.validate_internal_consistency(false, &factories, None).is_ok()); + assert!(tx3 + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .is_ok()); // tx3_cut_through has manual cut-through, it should not be possible so this should fail assert!(tx3_cut_through - .validate_internal_consistency(false, &factories, None) + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) .is_err()); } @@ -1725,7 +1751,9 @@ mod test { tx.body.inputs_mut()[0].input_data = stack; let factories = CryptoFactories::default(); - let err = tx.validate_internal_consistency(false, &factories, None).unwrap_err(); + let err = tx + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .unwrap_err(); assert!(matches!(err, TransactionError::InvalidSignatureError(_))); } diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index b38577a2ad..3d626270ac 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -50,7 +50,15 @@ use crate::{ use digest::Digest; use serde::{Deserialize, Serialize}; use std::fmt; -use tari_common_types::types::{BlindingFactor, ComSignature, PrivateKey, PublicKey, RangeProofService, Signature}; +use tari_common_types::types::{ + BlindingFactor, + ComSignature, + HashOutput, + PrivateKey, + PublicKey, + RangeProofService, + Signature, +}; use tari_crypto::{ keys::PublicKey as PublicKeyTrait, ristretto::pedersen::{PedersenCommitment, PedersenCommitmentFactory}, @@ -98,6 +106,8 @@ pub(super) struct RawTransactionInfo { pub recipient_info: RecipientInfo, pub signatures: Vec, pub message: String, + pub height: Option, + pub prev_header: Option, } #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] @@ -487,7 +497,9 @@ impl SenderTransactionProtocol { .with_signature(&s_agg) .build()?; tx_builder.with_kernel(kernel); - tx_builder.build(factories).map_err(TPE::from) + tx_builder + .build(factories, info.prev_header.clone(), info.height) + .map_err(TPE::from) } /// Performs sanity checks on the collected transaction pieces prior to building the final Transaction instance @@ -543,7 +555,13 @@ impl SenderTransactionProtocol { /// formally validate the transaction terms (no inflation, signature matches etc). If any step fails, /// the transaction protocol moves to Failed state and we are done; you can't rescue the situation. The function /// returns `Ok(false)` in this instance. - pub fn finalize(&mut self, features: KernelFeatures, factories: &CryptoFactories) -> Result<(), TPE> { + pub fn finalize( + &mut self, + features: KernelFeatures, + factories: &CryptoFactories, + prev_header: Option, + height: Option, + ) -> Result<(), TPE> { // Create the final aggregated signature, moving to the Failed state if anything goes wrong match &mut self.state { SenderState::Finalizing(_) => { @@ -566,7 +584,7 @@ impl SenderTransactionProtocol { } let transaction = result.unwrap(); let result = transaction - .validate_internal_consistency(true, factories, None) + .validate_internal_consistency(true, factories, None, prev_header, height) .map_err(TPE::TransactionBuildError); if let Err(e) = result { self.state = SenderState::Failed(e.clone()); @@ -840,10 +858,10 @@ mod test { p2.sender_offset_private_key.clone(), ) .unwrap(); - let mut sender = builder.build::(&factories).unwrap(); + let mut sender = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); assert!(!sender.is_failed()); assert!(sender.is_finalizing()); - match sender.finalize(KernelFeatures::empty(), &factories) { + match sender.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) { Ok(_) => (), Err(e) => panic!("{:?}", e), } @@ -874,7 +892,7 @@ mod test { .with_change_script(script, ExecutionStack::default(), PrivateKey::default()) // A little twist: Check the case where the change is less than the cost of another output .with_amount(0, MicroTari(1200) - fee - MicroTari(10)); - let mut alice = builder.build::(&factories).unwrap(); + let mut alice = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); assert!(alice.is_single_round_message_ready()); let msg = alice.build_single_round_message().unwrap(); // Send message down the wire....and wait for response @@ -892,7 +910,7 @@ mod test { .unwrap(); // Transaction should be complete assert!(alice.is_finalizing()); - match alice.finalize(KernelFeatures::empty(), &factories) { + match alice.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) { Ok(_) => (), Err(e) => panic!("{:?}", e), }; @@ -939,7 +957,7 @@ mod test { ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()) .with_amount(0, MicroTari(5000)); - let mut alice = builder.build::(&factories).unwrap(); + let mut alice = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); assert!(alice.is_single_round_message_ready()); let msg = alice.build_single_round_message().unwrap(); println!( @@ -973,7 +991,7 @@ mod test { .unwrap(); // Transaction should be complete assert!(alice.is_finalizing()); - match alice.finalize(KernelFeatures::empty(), &factories) { + match alice.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) { Ok(_) => (), Err(e) => panic!("{:?}", e), }; @@ -987,7 +1005,7 @@ mod test { assert_eq!(tx.body.outputs().len(), 2); assert!(tx .clone() - .validate_internal_consistency(false, &factories, None) + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) .is_ok()); } @@ -1019,7 +1037,7 @@ mod test { ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()) .with_amount(0, (2u64.pow(32) + 1).into()); - let mut alice = builder.build::(&factories).unwrap(); + let mut alice = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); assert!(alice.is_single_round_message_ready()); let msg = alice.build_single_round_message().unwrap(); // Send message down the wire....and wait for response @@ -1062,7 +1080,7 @@ mod test { ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); // Verify that the initial 'fee greater than amount' check rejects the transaction when it is constructed - match builder.build::(&factories) { + match builder.build::(&factories, None, Some(u64::MAX)) { Ok(_) => panic!("'BuildError(\"Fee is greater than amount\")' not caught"), Err(e) => assert_eq!(e.message, "Fee is greater than amount".to_string()), }; @@ -1095,7 +1113,7 @@ mod test { ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); // Test if the transaction passes the initial 'fee greater than amount' check when it is constructed - match builder.build::(&factories) { + match builder.build::(&factories, None, Some(u64::MAX)) { Ok(_) => {}, Err(e) => panic!("Unexpected error: {:?}", e), }; @@ -1145,7 +1163,7 @@ mod test { PrivateKey::random(&mut OsRng), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let mut alice = builder.build::(&factories).unwrap(); + let mut alice = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); assert!(alice.is_single_round_message_ready()); let msg = alice.build_single_round_message().unwrap(); @@ -1173,7 +1191,7 @@ mod test { .unwrap(); // Transaction should be complete assert!(alice.is_finalizing()); - match alice.finalize(KernelFeatures::empty(), &factories) { + match alice.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) { Ok(_) => (), Err(e) => panic!("{:?}", e), }; diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index dc445627d2..7657275782 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -49,7 +49,7 @@ use std::{ collections::HashMap, fmt::{Debug, Error, Formatter}, }; -use tari_common_types::types::{BlindingFactor, PrivateKey, PublicKey}; +use tari_common_types::types::{BlindingFactor, HashOutput, PrivateKey, PublicKey}; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, keys::{PublicKey as PublicKeyTrait, SecretKey}, @@ -381,6 +381,7 @@ impl SenderTransactionInitializer { .clone(), PublicKey::from_secret_key(&change_sender_offset_private_key), metadata_signature, + 0, ); Ok((fee_with_change, v, Some(change_unblinded_output))) }, @@ -421,7 +422,12 @@ impl SenderTransactionInitializer { /// error (so that you can continue building) along with a string listing the missing fields. /// If all the input data is present, but one or more fields are invalid, the function will return a /// `SenderTransactionProtocol` instance in the Failed state. - pub fn build(mut self, factories: &CryptoFactories) -> Result { + pub fn build( + mut self, + factories: &CryptoFactories, + prev_header: Option, + height: Option, + ) -> Result { // Compile a list of all data that is missing let mut message = Vec::new(); Self::check_value("Missing Lock Height", &self.lock_height, &mut message); @@ -615,6 +621,8 @@ impl SenderTransactionInitializer { recipient_info, signatures: Vec::new(), message: self.message.unwrap_or_else(|| "".to_string()), + prev_header, + height, }; let state = SenderState::Initializing(Box::new(sender_info)); @@ -662,7 +670,7 @@ mod test { let p = TestParams::new(); // Start the builder let builder = SenderTransactionInitializer::new(0, create_consensus_constants(0)); - let err = builder.build::(&factories).unwrap_err(); + let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); let script = script!(Nop); // We should have a bunch of fields missing still, but we can recover and continue assert_eq!( @@ -699,12 +707,12 @@ mod test { .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let expected_fee = builder.fee().calculate(MicroTari(20), 1, 1, 2, 0); // We needed a change input, so this should fail - let err = builder.build::(&factories).unwrap_err(); + let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); assert_eq!(err.message, "Change spending key was not provided"); // Ok, give them a change output let mut builder = err.builder; builder.with_change_secret(p.change_spend_key); - let result = builder.build::(&factories).unwrap(); + let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); // Peek inside and check the results if let SenderState::Finalizing(info) = result.into_state() { assert_eq!(info.num_recipients, 0, "Number of receivers"); @@ -746,7 +754,7 @@ mod test { .with_input(utxo, input) .with_fee_per_gram(MicroTari(4)) .with_prevent_fee_gt_amount(false); - let result = builder.build::(&factories).unwrap(); + let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); // Peek inside and check the results if let SenderState::Finalizing(info) = result.into_state() { assert_eq!(info.num_recipients, 0, "Number of receivers"); @@ -797,7 +805,7 @@ mod test { .with_input(utxo, input) .with_fee_per_gram(MicroTari(1)) .with_prevent_fee_gt_amount(false); - let result = builder.build::(&factories).unwrap(); + let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); // Peek inside and check the results if let SenderState::Finalizing(info) = result.into_state() { assert_eq!(info.num_recipients, 0, "Number of receivers"); @@ -839,7 +847,7 @@ mod test { let (utxo, input) = create_test_input(MicroTari(50), 0, &factories.commitment); builder.with_input(utxo, input); } - let err = builder.build::(&factories).unwrap_err(); + let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); assert_eq!(err.message, "Too many inputs in transaction"); } @@ -872,7 +880,7 @@ mod test { PrivateKey::random(&mut OsRng), ); // .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let err = builder.build::(&factories).unwrap_err(); + let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); assert_eq!(err.message, "Fee is less than the minimum"); } @@ -904,7 +912,7 @@ mod test { PrivateKey::random(&mut OsRng), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let err = builder.build::(&factories).unwrap_err(); + let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); assert_eq!( err.message, "You are spending (471 µT) more than you're providing (400 µT)." @@ -948,7 +956,7 @@ mod test { PrivateKey::random(&mut OsRng), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let result = builder.build::(&factories).unwrap(); + let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); // Peek inside and check the results if let SenderState::Failed(TransactionProtocolError::UnsupportedError(s)) = result.into_state() { assert_eq!(s, "Multiple recipients are not supported yet") @@ -996,7 +1004,7 @@ mod test { PrivateKey::random(&mut OsRng), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let result = builder.build::(&factories).unwrap(); + let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); // Peek inside and check the results if let SenderState::SingleRoundMessageReady(info) = result.into_state() { assert_eq!(info.num_recipients, 1, "Number of receivers"); @@ -1047,7 +1055,7 @@ mod test { PrivateKey::random(&mut OsRng), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let result = builder.build::(&factories); + let result = builder.build::(&factories, None, Some(u64::MAX)); match result { Ok(_) => panic!("Range proof should have failed to verify"), diff --git a/base_layer/core/src/validation/block_validators/async_validator.rs b/base_layer/core/src/validation/block_validators/async_validator.rs index 4acab94d65..d8f5cb9dfe 100644 --- a/base_layer/core/src/validation/block_validators/async_validator.rs +++ b/base_layer/core/src/validation/block_validators/async_validator.rs @@ -41,9 +41,9 @@ use crate::{ use async_trait::async_trait; use futures::{stream::FuturesUnordered, StreamExt}; use log::*; -use std::{cmp, cmp::Ordering, thread, time::Instant}; +use std::{cmp, cmp::Ordering, convert::TryInto, thread, time::Instant}; use tari_common_types::types::{Commitment, HashOutput, PublicKey}; -use tari_crypto::commitment::HomomorphicCommitmentFactory; +use tari_crypto::{commitment::HomomorphicCommitmentFactory, script::ScriptContext}; use tokio::task; /// This validator checks whether a block satisfies consensus rules. @@ -229,12 +229,15 @@ impl BlockValidator { let block_height = header.height; let commitment_factory = self.factories.commitment.clone(); let db = self.db.inner().clone(); + let prev_hash: [u8; 32] = header.prev_hash.as_slice().try_into().unwrap_or([0; 32]); + let height = header.height; task::spawn_blocking(move || { let timer = Instant::now(); let mut aggregate_input_key = PublicKey::default(); let mut commitment_sum = Commitment::default(); let mut not_found_inputs = Vec::new(); let db = db.db_read_access()?; + for (i, input) in inputs.iter().enumerate() { // Check for duplicates and/or incorrect sorting if i > 0 && input <= &inputs[i - 1] { @@ -268,8 +271,10 @@ impl BlockValidator { // Once we've found unknown inputs, the aggregate data will be discarded and there is no reason to run // the tari script if not_found_inputs.is_empty() { + let context = ScriptContext::new(height, &prev_hash, &input.commitment); // lets count up the input script public keys - aggregate_input_key = aggregate_input_key + input.run_and_verify_script(&commitment_factory)?; + aggregate_input_key = + aggregate_input_key + input.run_and_verify_script(&commitment_factory, Some(context))?; commitment_sum = &commitment_sum + &input.commitment; } } diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index ef099efe4a..f8d54f88a6 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -233,6 +233,8 @@ pub fn check_accounting_balance( bypass_range_proof_verification, total_coinbase, factories, + Some(block.header.prev_hash.clone()), + Some(block.header.height), ) .map_err(|err| { warn!( diff --git a/base_layer/core/src/validation/transaction_validators.rs b/base_layer/core/src/validation/transaction_validators.rs index 169b29c923..0d5b23d80f 100644 --- a/base_layer/core/src/validation/transaction_validators.rs +++ b/base_layer/core/src/validation/transaction_validators.rs @@ -41,24 +41,35 @@ pub const LOG_TARGET: &str = "c::val::transaction_validators"; /// 1. Range proofs of the outputs are valid /// /// This function does NOT check that inputs come from the UTXO set -pub struct TxInternalConsistencyValidator { +pub struct TxInternalConsistencyValidator { + db: BlockchainDatabase, factories: CryptoFactories, bypass_range_proof_verification: bool, } -impl TxInternalConsistencyValidator { - pub fn new(factories: CryptoFactories, bypass_range_proof_verification: bool) -> Self { +impl TxInternalConsistencyValidator { + pub fn new(factories: CryptoFactories, bypass_range_proof_verification: bool, db: BlockchainDatabase) -> Self { Self { + db, factories, bypass_range_proof_verification, } } } -impl MempoolTransactionValidation for TxInternalConsistencyValidator { +impl MempoolTransactionValidation for TxInternalConsistencyValidator { fn validate(&self, tx: &Transaction) -> Result<(), ValidationError> { - tx.validate_internal_consistency(self.bypass_range_proof_verification, &self.factories, None) - .map_err(ValidationError::TransactionError)?; + let db = self.db.db_read_access()?; + let tip = db.fetch_chain_metadata()?; + + tx.validate_internal_consistency( + self.bypass_range_proof_verification, + &self.factories, + None, + Some(tip.best_block().clone()), + Some(tip.height_of_longest_chain()), + ) + .map_err(ValidationError::TransactionError)?; Ok(()) } } diff --git a/base_layer/core/tests/mempool.rs b/base_layer/core/tests/mempool.rs index eeed7ad193..444ac1f815 100644 --- a/base_layer/core/tests/mempool.rs +++ b/base_layer/core/tests/mempool.rs @@ -1066,7 +1066,9 @@ async fn consensus_validation_large_tx() { // make sure the tx was correctly made and is valid let factories = CryptoFactories::default(); - assert!(tx.validate_internal_consistency(true, &factories, None).is_ok()); + assert!(tx + .validate_internal_consistency(true, &factories, None, None, Some(u64::MAX)) + .is_ok()); let weighting = constants.transaction_weight(); let weight = tx.calculate_weight(weighting); diff --git a/base_layer/core/tests/node_comms_interface.rs b/base_layer/core/tests/node_comms_interface.rs index b5461f4f02..9e1bce86f1 100644 --- a/base_layer/core/tests/node_comms_interface.rs +++ b/base_layer/core/tests/node_comms_interface.rs @@ -354,6 +354,7 @@ async fn inbound_fetch_blocks_before_horizon_height() { key, PublicKey::from_secret_key(&offset), metadata_signature, + 0, ); let mut txn = DbTransaction::new(); txn.insert_utxo(utxo.clone(), block0.hash().clone(), 0, 4002); diff --git a/base_layer/wallet/migrations/2021-11-11-094000_add_script_lock_height/down.sql b/base_layer/wallet/migrations/2021-11-11-094000_add_script_lock_height/down.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/base_layer/wallet/migrations/2021-11-11-094000_add_script_lock_height/up.sql b/base_layer/wallet/migrations/2021-11-11-094000_add_script_lock_height/up.sql new file mode 100644 index 0000000000..da064ece7d --- /dev/null +++ b/base_layer/wallet/migrations/2021-11-11-094000_add_script_lock_height/up.sql @@ -0,0 +1,31 @@ +-- Copyright 2021. The Tari Project +-- +-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +-- following conditions are met: +-- +-- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +-- disclaimer. +-- +-- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +-- following disclaimer in the documentation and/or other materials provided with the distribution. +-- +-- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +-- products derived from this software without specific prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +-- 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. + +ALTER TABLE outputs + ADD script_lock_height UNSIGNED BIGINT NOT NULL DEFAULT 0; + +ALTER TABLE outputs + ADD spending_priority UNSIGNED Integer NOT NULL DEFAULT 500; + +ALTER TABLE known_one_sided_payment_scripts + ADD script_lock_height UNSIGNED BIGINT NOT NULL DEFAULT 0; + diff --git a/base_layer/wallet/src/base_node_service/mock_base_node_service.rs b/base_layer/wallet/src/base_node_service/mock_base_node_service.rs index ff12309823..e7de7a9d6e 100644 --- a/base_layer/wallet/src/base_node_service/mock_base_node_service.rs +++ b/base_layer/wallet/src/base_node_service/mock_base_node_service.rs @@ -94,7 +94,7 @@ impl MockBaseNodeService { } pub fn set_default_base_node_state(&mut self) { - let metadata = ChainMetadata::new(u64::MAX, Vec::new(), 0, 0, 0); + let metadata = ChainMetadata::new(i64::MAX as u64, Vec::new(), 0, 0, 0); self.state = BaseNodeState { chain_metadata: Some(metadata), is_synced: Some(true), diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 710f52307a..37afdbd641 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -23,7 +23,7 @@ use crate::output_manager_service::{ error::OutputManagerError, service::Balance, - storage::models::KnownOneSidedPaymentScript, + storage::models::{KnownOneSidedPaymentScript, SpendingPriority}, }; use aes_gcm::Aes256Gcm; use std::{fmt, sync::Arc}; @@ -46,9 +46,9 @@ use tower::Service; /// API Request enum pub enum OutputManagerRequest { GetBalance, - AddOutput(Box), - AddOutputWithTxId((TxId, Box)), - AddUnvalidatedOutput((TxId, Box)), + AddOutput((Box, Option)), + AddOutputWithTxId((TxId, Box, Option)), + AddUnvalidatedOutput((TxId, Box, Option)), UpdateOutputMetadataSignature(Box), GetRecipientTransaction(TransactionSenderMessage), GetCoinbaseTransaction((u64, MicroTari, MicroTari, u64)), @@ -78,6 +78,7 @@ pub enum OutputManagerRequest { ReinstateCancelledInboundTx(TxId), SetCoinbaseAbandoned(TxId, bool), CreateClaimShaAtomicSwapTransaction(HashOutput, PublicKey, MicroTari), + CreateHtlcRefundTransaction(HashOutput, MicroTari), } impl fmt::Display for OutputManagerRequest { @@ -85,8 +86,11 @@ impl fmt::Display for OutputManagerRequest { use OutputManagerRequest::*; match self { GetBalance => write!(f, "GetBalance"), - AddOutput(v) => write!(f, "AddOutput ({})", v.value), - AddOutputWithTxId((t, v)) => write!(f, "AddOutputWithTxId ({}: {})", t, v.value), + AddOutput((v, _)) => write!(f, "AddOutput ({})", v.value), + AddOutputWithTxId((t, v, _)) => write!(f, "AddOutputWithTxId ({}: {})", t, v.value), + AddUnvalidatedOutput((t, v, _)) => { + write!(f, "AddUnvalidatedOutput ({}: {})", t, v.value) + }, UpdateOutputMetadataSignature(v) => write!( f, "UpdateOutputMetadataSignature ({}, {}, {})", @@ -132,9 +136,12 @@ impl fmt::Display for OutputManagerRequest { pre_image, fee_per_gram, ), - OutputManagerRequest::AddUnvalidatedOutput((t, v)) => { - write!(f, "AddUnvalidatedOutput ({}: {})", t, v.value) - }, + CreateHtlcRefundTransaction(output, fee_per_gram) => write!( + f, + "CreateHtlcRefundTransaction(output hash: {}, , fee_per_gram: {} )", + output.to_hex(), + fee_per_gram, + ), } } } @@ -168,7 +175,7 @@ pub enum OutputManagerResponse { AddKnownOneSidedPaymentScript, ReinstatedCancelledInboundTx, CoinbaseAbandonedSet, - ClaimShaAtomicSwapTransaction((u64, MicroTari, MicroTari, Transaction)), + ClaimHtlcTransaction((u64, MicroTari, MicroTari, Transaction)), } pub type OutputManagerEventSender = broadcast::Sender>; @@ -212,10 +219,14 @@ impl OutputManagerHandle { self.event_stream_sender.subscribe() } - pub async fn add_output(&mut self, output: UnblindedOutput) -> Result<(), OutputManagerError> { + pub async fn add_output( + &mut self, + output: UnblindedOutput, + spend_priority: Option, + ) -> Result<(), OutputManagerError> { match self .handle - .call(OutputManagerRequest::AddOutput(Box::new(output))) + .call(OutputManagerRequest::AddOutput((Box::new(output), spend_priority))) .await?? { OutputManagerResponse::OutputAdded => Ok(()), @@ -227,10 +238,15 @@ impl OutputManagerHandle { &mut self, tx_id: TxId, output: UnblindedOutput, + spend_priority: Option, ) -> Result<(), OutputManagerError> { match self .handle - .call(OutputManagerRequest::AddOutputWithTxId((tx_id, Box::new(output)))) + .call(OutputManagerRequest::AddOutputWithTxId(( + tx_id, + Box::new(output), + spend_priority, + ))) .await?? { OutputManagerResponse::OutputAdded => Ok(()), @@ -242,10 +258,15 @@ impl OutputManagerHandle { &mut self, tx_id: TxId, output: UnblindedOutput, + spend_priority: Option, ) -> Result<(), OutputManagerError> { match self .handle - .call(OutputManagerRequest::AddUnvalidatedOutput((tx_id, Box::new(output)))) + .call(OutputManagerRequest::AddUnvalidatedOutput(( + tx_id, + Box::new(output), + spend_priority, + ))) .await?? { OutputManagerResponse::OutputAdded => Ok(()), @@ -456,6 +477,21 @@ impl OutputManagerHandle { } } + pub async fn create_htlc_refund_transaction( + &mut self, + output: HashOutput, + fee_per_gram: MicroTari, + ) -> Result<(u64, MicroTari, MicroTari, Transaction), OutputManagerError> { + match self + .handle + .call(OutputManagerRequest::CreateHtlcRefundTransaction(output, fee_per_gram)) + .await?? + { + OutputManagerResponse::ClaimHtlcTransaction(ct) => Ok(ct), + _ => Err(OutputManagerError::UnexpectedApiResponse), + } + } + pub async fn create_claim_sha_atomic_swap_transaction( &mut self, output: HashOutput, @@ -471,7 +507,7 @@ impl OutputManagerHandle { )) .await?? { - OutputManagerResponse::ClaimShaAtomicSwapTransaction(ct) => Ok(ct), + OutputManagerResponse::ClaimHtlcTransaction(ct) => Ok(ct), _ => Err(OutputManagerError::UnexpectedApiResponse), } } diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index 650a6f2bef..b7b35ae8f7 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -93,6 +93,7 @@ where TBackend: OutputManagerBackend + 'static ) }) }) + //Todo this needs some investigation. We assume Nop script here and recovery here might create an unspendable output if the script does not equal Nop. .map( |(output, features, script, sender_offset_public_key, metadata_signature)| { // Todo we need to look here that we might want to fail a specific output and not recover it as this @@ -108,6 +109,7 @@ where TBackend: OutputManagerBackend + 'static script_key, sender_offset_public_key, metadata_signature, + 0, ) }, ) @@ -117,7 +119,7 @@ where TBackend: OutputManagerBackend + 'static self.update_outputs_script_private_key_and_update_key_manager_index(output) .await?; - let db_output = DbUnblindedOutput::from_unblinded_output(output.clone(), &self.factories)?; + let db_output = DbUnblindedOutput::from_unblinded_output(output.clone(), &self.factories, None)?; let output_hex = db_output.commitment.to_hex(); if let Err(e) = self.db.add_unspent_output(db_output).await { match e { diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index e4738d8d09..2572402e69 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -31,7 +31,7 @@ use crate::{ resources::OutputManagerResources, storage::{ database::{OutputManagerBackend, OutputManagerDatabase}, - models::{DbUnblindedOutput, KnownOneSidedPaymentScript}, + models::{DbUnblindedOutput, KnownOneSidedPaymentScript, SpendingPriority}, }, tasks::TxoValidationTask, MasterKeyManager, @@ -43,7 +43,7 @@ use diesel::result::{DatabaseErrorKind, Error as DieselError}; use futures::{pin_mut, StreamExt}; use log::*; use rand::{rngs::OsRng, RngCore}; -use std::{cmp::Ordering, convert::TryInto, fmt, fmt::Display, sync::Arc}; +use std::{convert::TryInto, fmt, fmt::Display, sync::Arc}; use tari_common_types::{ transaction::TxId, types::{HashOutput, PrivateKey, PublicKey}, @@ -187,12 +187,16 @@ where ) -> Result { trace!(target: LOG_TARGET, "Handling Service Request: {}", request); match request { - OutputManagerRequest::AddOutput(uo) => self - .add_output(None, *uo) + OutputManagerRequest::AddOutput((uo, spend_priority)) => self + .add_output(None, *uo, spend_priority) .await .map(|_| OutputManagerResponse::OutputAdded), - OutputManagerRequest::AddOutputWithTxId((tx_id, uo)) => self - .add_output(Some(tx_id), *uo) + OutputManagerRequest::AddOutputWithTxId((tx_id, uo, spend_priority)) => self + .add_output(Some(tx_id), *uo, spend_priority) + .await + .map(|_| OutputManagerResponse::OutputAdded), + OutputManagerRequest::AddUnvalidatedOutput((tx_id, uo, spend_priority)) => self + .add_unvalidated_output(tx_id, *uo, spend_priority) .await .map(|_| OutputManagerResponse::OutputAdded), OutputManagerRequest::UpdateOutputMetadataSignature(uo) => self @@ -339,10 +343,10 @@ where self.claim_sha_atomic_swap_with_hash(output_hash, pre_image, fee_per_gram) .await }, - OutputManagerRequest::AddUnvalidatedOutput((tx_id, uo)) => self - .add_unvalidated_output(tx_id, *uo) + OutputManagerRequest::CreateHtlcRefundTransaction(output, fee_per_gram) => self + .create_htlc_refund_transaction(output, fee_per_gram) .await - .map(|_| OutputManagerResponse::OutputAdded), + .map(OutputManagerResponse::ClaimHtlcTransaction), } } @@ -360,7 +364,7 @@ where self.create_claim_sha_atomic_swap_transaction(output, pre_image, fee_per_gram) .await - .map(OutputManagerResponse::ClaimShaAtomicSwapTransaction) + .map(OutputManagerResponse::ClaimHtlcTransaction) } fn handle_base_node_service_event(&mut self, event: Arc) { @@ -422,12 +426,23 @@ where } /// Add an unblinded output to the outputs table and marks is as `Unspent`. - pub async fn add_output(&mut self, tx_id: Option, output: UnblindedOutput) -> Result<(), OutputManagerError> { + pub async fn add_output( + &mut self, + tx_id: Option, + output: UnblindedOutput, + spend_priority: Option, + ) -> Result<(), OutputManagerError> { debug!( target: LOG_TARGET, "Add output of value {} to Output Manager", output.value ); - let output = DbUnblindedOutput::from_unblinded_output(output, &self.resources.factories)?; + + let output = DbUnblindedOutput::from_unblinded_output(output, &self.resources.factories, spend_priority)?; + debug!( + target: LOG_TARGET, + "saving output of hash {} to Output Manager", + output.hash.to_hex() + ); match tx_id { None => self.resources.db.add_unspent_output(output).await?, Some(t) => self.resources.db.add_unspent_output_with_tx_id(t, output).await?, @@ -441,12 +456,13 @@ where &mut self, tx_id: TxId, output: UnblindedOutput, + spend_priority: Option, ) -> Result<(), OutputManagerError> { debug!( target: LOG_TARGET, "Add unvalidated output of value {} to Output Manager", output.value ); - let output = DbUnblindedOutput::from_unblinded_output(output, &self.resources.factories)?; + let output = DbUnblindedOutput::from_unblinded_output(output, &self.resources.factories, spend_priority)?; self.resources.db.add_unvalidated_output(tx_id, output).await?; Ok(()) } @@ -513,8 +529,10 @@ where &single_round_sender_data.sender_offset_public_key.clone(), &single_round_sender_data.public_commitment_nonce.clone(), )?, + 0, ), &self.resources.factories, + None, )?; self.resources @@ -648,7 +666,7 @@ where } let stp = builder - .build::(&self.resources.factories) + .build::(&self.resources.factories, None, self.last_seen_tip_height) .map_err(|e| OutputManagerError::BuildError(e.message))?; // If a change output was created add it to the pending_outputs list. @@ -662,6 +680,7 @@ where change_output.push(DbUnblindedOutput::from_unblinded_output( unblinded_output, &self.resources.factories, + None, )?); } @@ -710,7 +729,7 @@ where .with_rewind_data(self.resources.master_key_manager.rewind_data().clone()) .build_with_reward(&self.resources.consensus_constants, reward)?; - let output = DbUnblindedOutput::from_unblinded_output(unblinded_output, &self.resources.factories)?; + let output = DbUnblindedOutput::from_unblinded_output(unblinded_output, &self.resources.factories, None)?; // Clear any existing pending coinbase transactions for this blockheight if they exist if let Err(e) = self @@ -815,8 +834,10 @@ where script_private_key, PublicKey::from_secret_key(&sender_offset_private_key), metadata_signature, + 0, ), &self.resources.factories, + None, )?; builder .with_output(utxo.unblinded_output.clone(), sender_offset_private_key.clone()) @@ -841,7 +862,7 @@ where let factories = CryptoFactories::default(); let mut stp = builder - .build::(&self.resources.factories) + .build::(&self.resources.factories, None, self.last_seen_tip_height) .map_err(|e| OutputManagerError::BuildError(e.message))?; if input_selection.requires_change_output() { @@ -850,7 +871,8 @@ where "There should be a change output metadata signature available".to_string(), ) })?; - let change_output = DbUnblindedOutput::from_unblinded_output(unblinded_output, &self.resources.factories)?; + let change_output = + DbUnblindedOutput::from_unblinded_output(unblinded_output, &self.resources.factories, None)?; outputs.push(change_output); } @@ -866,7 +888,7 @@ where self.confirm_encumberance(tx_id).await?; let fee = stp.get_fee_amount()?; trace!(target: LOG_TARGET, "Finalize send-to-self transaction ({}).", tx_id); - stp.finalize(KernelFeatures::empty(), &factories)?; + stp.finalize(KernelFeatures::empty(), &factories, None, self.last_seen_tip_height)?; let tx = stp.take_transaction()?; Ok((fee, tx)) @@ -922,78 +944,40 @@ where let mut fee_with_change = MicroTari::from(0); let fee_calc = self.get_fee_calc(); - let uo = self.resources.db.fetch_sorted_unspent_outputs().await?; - // Attempt to get the chain tip height let chain_metadata = self.base_node_service.get_chain_metadata().await?; let (connected, tip_height) = match &chain_metadata { - Some(metadata) => (true, metadata.height_of_longest_chain()), - None => (false, 0), + Some(metadata) => (true, Some(metadata.height_of_longest_chain())), + None => (false, None), }; // If no strategy was specified and no metadata is available, then make sure to use MaturitythenSmallest let strategy = match (strategy, connected) { - (Some(s), _) => Some(s), - (None, false) => Some(UTXOSelectionStrategy::MaturityThenSmallest), - (None, true) => None, // use the selection heuristic next - }; - - // If we know the chain height then filter out unspendable UTXOs - let num_utxos = uo.len(); - let uo = if connected { - let mature_utxos = uo - .into_iter() - .filter(|u| u.unblinded_output.features.maturity <= tip_height) - .collect::>(); - - trace!( - target: LOG_TARGET, - "Some UTXOs have not matured yet at height {}, filtered {} UTXOs", - tip_height, - num_utxos - mature_utxos.len() - ); - - mature_utxos - } else { - uo + (Some(s), _) => s, + (None, false) => UTXOSelectionStrategy::MaturityThenSmallest, + (None, true) => UTXOSelectionStrategy::Default, // use the selection heuristic next }; // Heuristic for selection strategy: Default to MaturityThenSmallest, but if the amount is greater than // the largest UTXO, use Largest UTXOs first. - let strategy = match (strategy, uo.is_empty()) { - (Some(s), _) => s, - (None, true) => UTXOSelectionStrategy::Smallest, - (None, false) => { - let largest_utxo = &uo[uo.len() - 1]; - if amount > largest_utxo.unblinded_output.value { - UTXOSelectionStrategy::Largest - } else { - UTXOSelectionStrategy::MaturityThenSmallest - } - }, - }; + // let strategy = match (strategy, uo.is_empty()) { + // (Some(s), _) => s, + // (None, true) => UTXOSelectionStrategy::Smallest, + // (None, false) => { + // let largest_utxo = &uo[uo.len() - 1]; + // if amount > largest_utxo.unblinded_output.value { + // UTXOSelectionStrategy::Largest + // } else { + // UTXOSelectionStrategy::MaturityThenSmallest + // } + // }, + // }; debug!(target: LOG_TARGET, "select_utxos selection strategy: {}", strategy); - - let uo = match strategy { - UTXOSelectionStrategy::Smallest => uo, - UTXOSelectionStrategy::MaturityThenSmallest => { - let mut uo = uo; - uo.sort_by(|a, b| { - match a - .unblinded_output - .features - .maturity - .cmp(&b.unblinded_output.features.maturity) - { - Ordering::Equal => a.unblinded_output.value.cmp(&b.unblinded_output.value), - Ordering::Less => Ordering::Less, - Ordering::Greater => Ordering::Greater, - } - }); - uo - }, - UTXOSelectionStrategy::Largest => uo.into_iter().rev().collect(), - }; + let uo = self + .resources + .db + .fetch_unspent_outputs_for_spending(strategy, amount, tip_height) + .await?; trace!(target: LOG_TARGET, "We found {} UTXOs to select from", uo.len()); // Assumes that default Outputfeatures are used for change utxo @@ -1143,8 +1127,10 @@ where script_private_key, sender_offset_public_key, metadata_signature, + 0, ), &self.resources.factories, + None, )?; builder .with_output(utxo.unblinded_output.clone(), sender_offset_private_key) @@ -1169,7 +1155,7 @@ where let factories = CryptoFactories::default(); let mut stp = builder - .build::(&self.resources.factories) + .build::(&self.resources.factories, None, self.last_seen_tip_height) .map_err(|e| OutputManagerError::BuildError(e.message))?; // The Transaction Protocol built successfully so we will pull the unspent outputs out of the unspent list and // store them until the transaction times out OR is confirmed @@ -1189,6 +1175,7 @@ where outputs.push(DbUnblindedOutput::from_unblinded_output( unblinded_output, &self.resources.factories, + None, )?); } @@ -1198,7 +1185,7 @@ where .await?; self.confirm_encumberance(tx_id).await?; trace!(target: LOG_TARGET, "Finalize coin split transaction ({}).", tx_id); - stp.finalize(KernelFeatures::empty(), &factories)?; + stp.finalize(KernelFeatures::empty(), &factories, None, self.last_seen_tip_height)?; let fee = stp.get_fee_amount()?; let tx = stp.take_transaction()?; Ok((tx_id, tx, fee, utxos_total_value)) @@ -1257,6 +1244,9 @@ where self.node_identity.as_ref().secret_key().clone(), output.sender_offset_public_key, output.metadata_signature, + // Although the technically the script does have a script lock higher than 0, this does not apply to to us + // as we are claiming the Hashed part which has a 0 time lock + 0, ); let amount = rewound.committed_value; @@ -1295,7 +1285,7 @@ where let factories = CryptoFactories::default(); let mut stp = builder - .build::(&self.resources.factories) + .build::(&self.resources.factories, None, self.last_seen_tip_height) .map_err(|e| OutputManagerError::BuildError(e.message))?; let tx_id = stp.get_tx_id()?; @@ -1303,7 +1293,8 @@ where let unblinded_output = stp.get_change_unblinded_output()?.ok_or_else(|| { OutputManagerError::BuildError("There should be a change output metadata signature available".to_string()) })?; - let change_output = DbUnblindedOutput::from_unblinded_output(unblinded_output, &self.resources.factories)?; + let change_output = + DbUnblindedOutput::from_unblinded_output(unblinded_output, &self.resources.factories, None)?; outputs.push(change_output); trace!(target: LOG_TARGET, "Claiming HTLC with transaction ({}).", tx_id); @@ -1311,12 +1302,88 @@ where self.confirm_encumberance(tx_id).await?; let fee = stp.get_fee_amount()?; trace!(target: LOG_TARGET, "Finalize send-to-self transaction ({}).", tx_id); - stp.finalize(KernelFeatures::empty(), &factories)?; + stp.finalize(KernelFeatures::empty(), &factories, None, self.last_seen_tip_height)?; let tx = stp.take_transaction()?; Ok((tx_id, fee, amount - fee, tx)) } + pub async fn create_htlc_refund_transaction( + &mut self, + output_hash: HashOutput, + fee_per_gram: MicroTari, + ) -> Result<(u64, MicroTari, MicroTari, Transaction), OutputManagerError> { + let output = self + .resources + .db + .get_unspent_output(output_hash) + .await? + .unblinded_output; + + let amount = output.value; + + let offset = PrivateKey::random(&mut OsRng); + let nonce = PrivateKey::random(&mut OsRng); + let message = "SHA-XTR atomic refund".to_string(); + + // Create builder with no recipients (other than ourselves) + let mut builder = SenderTransactionProtocol::builder(0, self.resources.consensus_constants.clone()); + builder + .with_lock_height(0) + .with_fee_per_gram(fee_per_gram) + .with_offset(offset.clone()) + .with_private_nonce(nonce.clone()) + .with_message(message) + .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) + .with_input( + output.as_transaction_input(&self.resources.factories.commitment)?, + output, + ); + + let mut outputs = Vec::new(); + + let (spending_key, script_private_key) = self + .resources + .master_key_manager + .get_next_spend_and_script_key() + .await?; + builder.with_change_secret(spending_key); + builder.with_rewindable_outputs(self.resources.master_key_manager.rewind_data().clone()); + builder.with_change_script( + script!(Nop), + inputs!(PublicKey::from_secret_key(&script_private_key)), + script_private_key, + ); + + let factories = CryptoFactories::default(); + println!("he`"); + let mut stp = builder + .build::(&self.resources.factories, None, self.last_seen_tip_height) + .map_err(|e| OutputManagerError::BuildError(e.message))?; + + let tx_id = stp.get_tx_id()?; + + let unblinded_output = stp.get_change_unblinded_output()?.ok_or_else(|| { + OutputManagerError::BuildError("There should be a change output metadata signature available".to_string()) + })?; + + let change_output = + DbUnblindedOutput::from_unblinded_output(unblinded_output, &self.resources.factories, None)?; + outputs.push(change_output); + + trace!(target: LOG_TARGET, "Claiming HTLC refund with transaction ({}).", tx_id); + + let fee = stp.get_fee_amount()?; + + stp.finalize(KernelFeatures::empty(), &factories, None, self.last_seen_tip_height)?; + + let tx = stp.take_transaction()?; + + self.resources.db.encumber_outputs(tx_id, Vec::new(), outputs).await?; + self.confirm_encumberance(tx_id).await?; + Ok((tx_id, fee, amount - fee, tx)) + } + /// Persist a one-sided payment script for a Comms Public/Private key. These are the scripts that this wallet knows /// to look for when scanning for one-sided payments async fn add_known_script(&mut self, known_script: KnownOneSidedPaymentScript) -> Result<(), OutputManagerError> { @@ -1370,9 +1437,13 @@ where known_one_sided_payment_scripts[i].private_key.clone(), output.sender_offset_public_key, output.metadata_signature, + known_one_sided_payment_scripts[i].script_lock_height, ); - let db_output = - DbUnblindedOutput::from_unblinded_output(rewound_output.clone(), &self.resources.factories)?; + let db_output = DbUnblindedOutput::from_unblinded_output( + rewound_output.clone(), + &self.resources.factories, + None, + )?; let output_hex = output.commitment.to_hex(); match self.resources.db.add_unspent_output(db_output).await { @@ -1410,7 +1481,7 @@ where /// Different UTXO selection strategies for choosing which UTXO's are used to fulfill a transaction /// TODO Investigate and implement more optimal strategies -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum UTXOSelectionStrategy { // Start from the smallest UTXOs and work your way up until the amount is covered. Main benefit // is removing small UTXOs from the blockchain, con is that it costs more in fees @@ -1419,6 +1490,9 @@ pub enum UTXOSelectionStrategy { MaturityThenSmallest, // A strategy that selects the largest UTXOs first. Preferred when the amount is large Largest, + // Heuristic for selection strategy: MaturityThenSmallest, but if the amount is greater than + // the largest UTXO, use Largest UTXOs first + Default, } impl Display for UTXOSelectionStrategy { @@ -1427,6 +1501,7 @@ impl Display for UTXOSelectionStrategy { UTXOSelectionStrategy::Smallest => write!(f, "Smallest"), UTXOSelectionStrategy::MaturityThenSmallest => write!(f, "MaturityThenSmallest"), UTXOSelectionStrategy::Largest => write!(f, "Largest"), + UTXOSelectionStrategy::Default => write!(f, "Default"), } } } diff --git a/base_layer/wallet/src/output_manager_service/storage/database.rs b/base_layer/wallet/src/output_manager_service/storage/database.rs index 72caec2822..78c40bfb40 100644 --- a/base_layer/wallet/src/output_manager_service/storage/database.rs +++ b/base_layer/wallet/src/output_manager_service/storage/database.rs @@ -25,6 +25,9 @@ use crate::output_manager_service::{ service::Balance, storage::models::{DbUnblindedOutput, KnownOneSidedPaymentScript, OutputStatus}, }; +use tari_crypto::tari_utilities::hex::Hex; + +use crate::output_manager_service::service::UTXOSelectionStrategy; use aes_gcm::Aes256Gcm; use log::*; use std::{ @@ -35,7 +38,7 @@ use tari_common_types::{ transaction::TxId, types::{BlindingFactor, Commitment, HashOutput}, }; -use tari_core::transactions::transaction::TransactionOutput; +use tari_core::transactions::{tari_amount::MicroTari, transaction::TransactionOutput}; use tari_key_manager::cipher_seed::CipherSeed; const LOG_TARGET: &str = "wallet::output_manager_service::database"; @@ -130,6 +133,13 @@ pub trait OutputManagerBackend: Send + Sync + Clone { ) -> Result; /// Import unvalidated output fn add_unvalidated_output(&self, output: DbUnblindedOutput, tx_id: TxId) -> Result<(), OutputManagerStorageError>; + + fn fetch_unspent_outputs_for_spending( + &self, + strategy: UTXOSelectionStrategy, + amount: u64, + current_tip_height: Option, + ) -> Result, OutputManagerStorageError>; } /// Holds the state of the KeyManager being used by the Output Manager Service @@ -144,6 +154,7 @@ pub struct KeyManagerState { pub enum DbKey { SpentOutput(BlindingFactor), UnspentOutput(BlindingFactor), + UnspentOutputHash(HashOutput), AnyOutputByCommitment(Commitment), TimeLockedUnspentOutputs(u64), UnspentOutputs, @@ -387,6 +398,22 @@ where T: OutputManagerBackend + 'static Ok(uo) } + /// Retrieves UTXOs than can be spent, sorted by priority, then value from smallest to largest. + pub async fn fetch_unspent_outputs_for_spending( + &self, + strategy: UTXOSelectionStrategy, + amount: MicroTari, + tip_height: Option, + ) -> Result, OutputManagerStorageError> { + let db_clone = self.db.clone(); + let utxos = tokio::task::spawn_blocking(move || { + db_clone.fetch_unspent_outputs_for_spending(strategy, amount.as_u64(), tip_height) + }) + .await + .map_err(|err| OutputManagerStorageError::BlockingTaskSpawnError(err.to_string()))??; + Ok(utxos) + } + pub async fn fetch_spent_outputs(&self) -> Result, OutputManagerStorageError> { let db_clone = self.db.clone(); @@ -516,6 +543,27 @@ where T: OutputManagerBackend + 'static Ok(scripts) } + pub async fn get_unspent_output(&self, output: HashOutput) -> Result { + let db_clone = self.db.clone(); + + let uo = tokio::task::spawn_blocking( + move || match db_clone.fetch(&DbKey::UnspentOutputHash(output.clone())) { + Ok(None) => log_error( + DbKey::UnspentOutputHash(output.clone()), + OutputManagerStorageError::UnexpectedResult( + "Could not retrieve unspent output: ".to_string() + &output.to_hex(), + ), + ), + Ok(Some(DbValue::UnspentOutput(uo))) => Ok(uo), + Ok(Some(other)) => unexpected_result(DbKey::UnspentOutputHash(output), other), + Err(e) => log_error(DbKey::UnspentOutputHash(output), e), + }, + ) + .await + .map_err(|err| OutputManagerStorageError::BlockingTaskSpawnError(err.to_string()))??; + Ok(*uo) + } + pub async fn get_last_mined_output(&self) -> Result, OutputManagerStorageError> { self.db.get_last_mined_output() } @@ -630,6 +678,7 @@ impl Display for DbKey { match self { DbKey::SpentOutput(_) => f.write_str(&"Spent Output Key".to_string()), DbKey::UnspentOutput(_) => f.write_str(&"Unspent Output Key".to_string()), + DbKey::UnspentOutputHash(_) => f.write_str(&"Unspent Output Hash Key".to_string()), DbKey::UnspentOutputs => f.write_str(&"Unspent Outputs Key".to_string()), DbKey::SpentOutputs => f.write_str(&"Spent Outputs Key".to_string()), DbKey::KeyManagerState => f.write_str(&"Key Manager State".to_string()), diff --git a/base_layer/wallet/src/output_manager_service/storage/models.rs b/base_layer/wallet/src/output_manager_service/storage/models.rs index 198ad10054..7f0a8805c9 100644 --- a/base_layer/wallet/src/output_manager_service/storage/models.rs +++ b/base_layer/wallet/src/output_manager_service/storage/models.rs @@ -39,12 +39,14 @@ pub struct DbUnblindedOutput { pub mined_mmr_position: Option, pub marked_deleted_at_height: Option, pub marked_deleted_in_block: Option, + pub spend_priority: SpendingPriority, } impl DbUnblindedOutput { pub fn from_unblinded_output( output: UnblindedOutput, factory: &CryptoFactories, + spend_priority: Option, ) -> Result { let tx_out = output.as_transaction_output(factory)?; Ok(DbUnblindedOutput { @@ -56,6 +58,7 @@ impl DbUnblindedOutput { mined_mmr_position: None, marked_deleted_at_height: None, marked_deleted_in_block: None, + spend_priority: spend_priority.unwrap_or(SpendingPriority::Normal), }) } @@ -63,6 +66,7 @@ impl DbUnblindedOutput { output: UnblindedOutput, factory: &CryptoFactories, rewind_data: &RewindData, + spend_priority: Option, ) -> Result { let tx_out = output.as_rewindable_transaction_output(factory, rewind_data)?; Ok(DbUnblindedOutput { @@ -74,6 +78,7 @@ impl DbUnblindedOutput { mined_mmr_position: None, marked_deleted_at_height: None, marked_deleted_in_block: None, + spend_priority: spend_priority.unwrap_or(SpendingPriority::Normal), }) } } @@ -104,12 +109,39 @@ impl Ord for DbUnblindedOutput { impl Eq for DbUnblindedOutput {} +#[derive(Debug, Clone)] +pub enum SpendingPriority { + Normal, + HtlcSpendAsap, + Unknown, +} + +impl From for SpendingPriority { + fn from(value: u32) -> Self { + match value { + 100 => SpendingPriority::HtlcSpendAsap, + 500 => SpendingPriority::Normal, + _ => SpendingPriority::Unknown, + } + } +} + +impl From for u32 { + fn from(value: SpendingPriority) -> Self { + match value { + SpendingPriority::HtlcSpendAsap => 100, + SpendingPriority::Normal | SpendingPriority::Unknown => 500, + } + } +} + #[derive(Debug, Clone)] pub struct KnownOneSidedPaymentScript { pub script_hash: Vec, pub private_key: PrivateKey, pub script: TariScript, pub input: ExecutionStack, + pub script_lock_height: u64, } impl PartialEq for KnownOneSidedPaymentScript { diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs index 974c4a57fc..78a7fcddaf 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs @@ -51,7 +51,7 @@ use tari_key_manager::cipher_seed::CipherSeed; use crate::{ output_manager_service::{ error::OutputManagerStorageError, - service::Balance, + service::{Balance, UTXOSelectionStrategy}, storage::{ database::{DbKey, DbKeyValuePair, DbValue, KeyManagerState, OutputManagerBackend, WriteOperation}, models::{DbUnblindedOutput, KnownOneSidedPaymentScript, OutputStatus}, @@ -183,6 +183,19 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { None }, }, + DbKey::UnspentOutputHash(hash) => match OutputSql::find_by_hash(hash, OutputStatus::Unspent, &(*conn)) { + Ok(mut o) => { + self.decrypt_if_necessary(&mut o)?; + Some(DbValue::UnspentOutput(Box::new(DbUnblindedOutput::try_from(o)?))) + }, + Err(e) => { + match e { + OutputManagerStorageError::DieselError(DieselError::NotFound) => (), + e => return Err(e), + }; + None + }, + }, DbKey::AnyOutputByCommitment(commitment) => { match OutputSql::find_by_commitment(&commitment.to_vec(), &conn) { Ok(mut o) => { @@ -386,6 +399,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } }, DbKey::SpentOutput(_s) => return Err(OutputManagerStorageError::OperationNotSupported), + DbKey::UnspentOutputHash(_h) => return Err(OutputManagerStorageError::OperationNotSupported), DbKey::UnspentOutput(_k) => return Err(OutputManagerStorageError::OperationNotSupported), DbKey::UnspentOutputs => return Err(OutputManagerStorageError::OperationNotSupported), DbKey::SpentOutputs => return Err(OutputManagerStorageError::OperationNotSupported), @@ -1190,6 +1204,37 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { } Ok(()) } + + /// Retrieves UTXOs than can be spent, sorted by priority, then value from smallest to largest. + fn fetch_unspent_outputs_for_spending( + &self, + strategy: UTXOSelectionStrategy, + amount: u64, + tip_height: Option, + ) -> Result, OutputManagerStorageError> { + let start = Instant::now(); + let conn = self.database_connection.get_pooled_connection()?; + let acquire_lock = start.elapsed(); + let tip = match tip_height { + Some(v) => v as i64, + None => i64::MAX, + }; + let mut outputs = OutputSql::fetch_unspent_outputs_for_spending(strategy, amount, tip, &conn)?; + for o in outputs.iter_mut() { + self.decrypt_if_necessary(o)?; + } + trace!( + target: LOG_TARGET, + "sqlite profile - fetch_unspent_outputs_for_spending: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + outputs + .iter() + .map(|o| DbUnblindedOutput::try_from(o.clone())) + .collect::, _>>() + } } impl TryFrom for OutputStatus { @@ -1436,6 +1481,7 @@ pub struct KnownOneSidedPaymentScriptSql { pub private_key: Vec, pub script: Vec, pub input: Vec, + pub script_lock_height: i64, } /// These are the fields that can be updated for an Output @@ -1536,11 +1582,13 @@ impl TryFrom for KnownOneSidedPaymentScript { error!(target: LOG_TARGET, "Could not create execution stack from stored bytes"); OutputManagerStorageError::ConversionError })?; + let script_lock_height = o.script_lock_height as u64; Ok(KnownOneSidedPaymentScript { script_hash, private_key, script, input, + script_lock_height, }) } } @@ -1548,6 +1596,7 @@ impl TryFrom for KnownOneSidedPaymentScript { /// Conversion from an KnownOneSidedPaymentScriptSQL to the datatype form impl From for KnownOneSidedPaymentScriptSql { fn from(known_script: KnownOneSidedPaymentScript) -> Self { + let script_lock_height = known_script.script_lock_height as i64; let script_hash = known_script.script_hash; let private_key = known_script.private_key.as_bytes().to_vec(); let script = known_script.script.as_bytes().to_vec(); @@ -1557,6 +1606,7 @@ impl From for KnownOneSidedPaymentScriptSql { private_key, script, input, + script_lock_height, } } } @@ -1644,7 +1694,7 @@ mod test { for _i in 0..2 { let (_, uo) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); let o = NewOutputSql::new(uo, OutputStatus::Unspent, None, None).unwrap(); outputs.push(o.clone()); outputs_unspent.push(o.clone()); @@ -1653,7 +1703,7 @@ mod test { for _i in 0..3 { let (_, uo) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); let o = NewOutputSql::new(uo, OutputStatus::Spent, None, None).unwrap(); outputs.push(o.clone()); outputs_spent.push(o.clone()); @@ -1768,7 +1818,7 @@ mod test { let factories = CryptoFactories::default(); let (_, uo) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); let output = NewOutputSql::new(uo, OutputStatus::Unspent, None, None).unwrap(); let key = GenericArray::from_slice(b"an example very very secret key."); @@ -1886,12 +1936,12 @@ mod test { let _state_sql = NewKeyManagerStateSql::from(starting_state).commit(&conn).unwrap(); let (_, uo) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); let output = NewOutputSql::new(uo, OutputStatus::Unspent, None, None).unwrap(); output.commit(&conn).unwrap(); let (_, uo2) = make_input(MicroTari::from(100 + OsRng.next_u64() % 1000)); - let uo2 = DbUnblindedOutput::from_unblinded_output(uo2, &factories).unwrap(); + let uo2 = DbUnblindedOutput::from_unblinded_output(uo2, &factories, None).unwrap(); let output2 = NewOutputSql::new(uo2, OutputStatus::Unspent, None, None).unwrap(); output2.commit(&conn).unwrap(); } diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs index c1bb5925cb..f02317119a 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs @@ -37,6 +37,7 @@ use crate::{ }; use aes_gcm::Aes256Gcm; +use crate::output_manager_service::service::UTXOSelectionStrategy; use diesel::{prelude::*, sql_query, SqliteConnection}; use log::*; use std::convert::TryFrom; @@ -86,6 +87,8 @@ pub struct OutputSql { pub received_in_tx_id: Option, pub spent_in_tx_id: Option, pub coinbase_block_height: Option, + pub script_lock_height: i64, + pub spend_priority: i32, } impl OutputSql { @@ -102,6 +105,55 @@ impl OutputSql { Ok(outputs::table.filter(outputs::status.eq(status as i32)).load(conn)?) } + /// Retrieves UTXOs than can be spent, sorted by priority, then value from smallest to largest. + pub fn fetch_unspent_outputs_for_spending( + mut strategy: UTXOSelectionStrategy, + amount: u64, + tip_height: i64, + conn: &SqliteConnection, + ) -> Result, OutputManagerStorageError> { + if strategy == UTXOSelectionStrategy::Default { + // lets get the max value for all utxos + let max: Vec = outputs::table + .filter(outputs::status.eq(OutputStatus::Unspent as i32)) + .filter(outputs::script_lock_height.le(tip_height)) + .filter(outputs::maturity.le(tip_height)) + .order(outputs::value.desc()) + .select(outputs::value) + .limit(1) + .load(conn)?; + if max.is_empty() { + strategy = UTXOSelectionStrategy::Smallest + } else if amount > max[0] as u64 { + strategy = UTXOSelectionStrategy::Largest + } else { + strategy = UTXOSelectionStrategy::MaturityThenSmallest + } + } + + let mut query = outputs::table + .into_boxed() + .filter(outputs::status.eq(OutputStatus::Unspent as i32)) + .filter(outputs::script_lock_height.le(tip_height)) + .filter(outputs::maturity.le(tip_height)) + .order_by(outputs::spending_priority.asc()); + match strategy { + UTXOSelectionStrategy::Smallest => { + query = query.then_order_by(outputs::value.asc()); + }, + UTXOSelectionStrategy::MaturityThenSmallest => { + query = query + .then_order_by(outputs::maturity.asc()) + .then_order_by(outputs::value.asc()); + }, + UTXOSelectionStrategy::Largest => { + query = query.then_order_by(outputs::value.desc()); + }, + UTXOSelectionStrategy::Default => {}, + }; + Ok(query.load(conn)?) + } + /// Return all unspent outputs that have a maturity above the provided chain tip pub fn index_time_locked(tip: u64, conn: &SqliteConnection) -> Result, OutputManagerStorageError> { Ok(outputs::table @@ -176,7 +228,7 @@ impl OutputSql { FROM outputs WHERE status = ? \ UNION ALL \ SELECT coalesce(sum(value), 0) as amount, 'time_locked_balance' as category \ - FROM outputs WHERE status = ? AND maturity > ? \ + FROM outputs WHERE status = ? AND maturity > ? OR script_lock_height > ? \ UNION ALL \ SELECT coalesce(sum(value), 0) as amount, 'pending_incoming_balance' as category \ FROM outputs WHERE status = ? OR status = ? OR status = ? \ @@ -189,6 +241,7 @@ impl OutputSql { // time_locked_balance .bind::(OutputStatus::Unspent as i32) .bind::(current_tip as i64) + .bind::(current_tip as i64) // pending_incoming_balance .bind::(OutputStatus::EncumberedToBeReceived as i32) .bind::(OutputStatus::ShortTermEncumberedToBeReceived as i32) @@ -333,6 +386,18 @@ impl OutputSql { .first::(conn)?) } + /// Find a particular Output, if it exists and is in the specified Spent state + pub fn find_by_hash( + hash: &[u8], + status: OutputStatus, + conn: &SqliteConnection, + ) -> Result { + Ok(outputs::table + .filter(outputs::status.eq(status as i32)) + .filter(outputs::hash.eq(Some(hash))) + .first::(conn)?) + } + /// Find a particular Output, if it exists and is in the specified Spent state pub fn find_pending_coinbase_at_block_height( block_height: u64, @@ -439,6 +504,7 @@ impl TryFrom for DbUnblindedOutput { OutputManagerStorageError::ConversionError })?, ), + o.script_lock_height as u64, ); let hash = match o.hash { @@ -457,7 +523,7 @@ impl TryFrom for DbUnblindedOutput { }, Some(c) => Commitment::from_vec(&c)?, }; - + let spend_priority = (o.spend_priority as u32).into(); Ok(Self { commitment, unblinded_output, @@ -467,6 +533,7 @@ impl TryFrom for DbUnblindedOutput { mined_mmr_position: o.mined_mmr_position.map(|mp| mp as u64), marked_deleted_at_height: o.marked_deleted_at_height.map(|d| d as u64), marked_deleted_in_block: o.marked_deleted_in_block, + spend_priority, }) } } diff --git a/base_layer/wallet/src/schema.rs b/base_layer/wallet/src/schema.rs index 570ae8cae1..383dc93f7b 100644 --- a/base_layer/wallet/src/schema.rs +++ b/base_layer/wallet/src/schema.rs @@ -68,6 +68,7 @@ table! { private_key -> Binary, script -> Binary, input -> Binary, + script_lock_height -> BigInt, } } @@ -112,6 +113,8 @@ table! { received_in_tx_id -> Nullable, spent_in_tx_id -> Nullable, coinbase_block_height -> Nullable, + script_lock_height -> BigInt, + spending_priority -> Integer, } } 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 ca75e3145c..c02f61e2a2 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 @@ -40,6 +40,7 @@ use tari_comms::types::CommsPublicKey; use tokio::sync::{mpsc, oneshot}; use crate::connectivity_service::WalletConnectivityInterface; +use tari_common_types::types::HashOutput; use tari_core::transactions::{ transaction::Transaction, transaction_protocol::{recipient::RecipientState, sender::TransactionSenderMessage}, @@ -64,6 +65,8 @@ pub struct TransactionReceiveProtocol { resources: TransactionServiceResources, transaction_finalize_receiver: Option>, cancellation_receiver: Option>, + prev_header: Option, + height: Option, } impl TransactionReceiveProtocol @@ -79,6 +82,8 @@ where resources: TransactionServiceResources, transaction_finalize_receiver: mpsc::Receiver<(CommsPublicKey, TxId, Transaction)>, cancellation_receiver: oneshot::Receiver<()>, + prev_header: Option, + height: Option, ) -> Self { Self { id, @@ -88,6 +93,8 @@ where resources, transaction_finalize_receiver: Some(transaction_finalize_receiver), cancellation_receiver: Some(cancellation_receiver), + prev_header, + height, } } @@ -361,7 +368,13 @@ where ); finalized_transaction - .validate_internal_consistency(true, &self.resources.factories, None) + .validate_internal_consistency( + true, + &self.resources.factories, + None, + self.prev_header.clone(), + self.height, + ) .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; // Find your own output in the transaction 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 774351782e..13d2cd3d35 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 @@ -43,7 +43,10 @@ use chrono::Utc; use futures::FutureExt; use log::*; use std::sync::Arc; -use tari_common_types::transaction::{TransactionDirection, TransactionStatus}; +use tari_common_types::{ + transaction::{TransactionDirection, TransactionStatus}, + types::HashOutput, +}; use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; use tari_comms_dht::{ domain_message::OutboundDomainMessage, @@ -82,6 +85,8 @@ pub struct TransactionSendProtocol { resources: TransactionServiceResources, transaction_reply_receiver: Option>, cancellation_receiver: Option>, + prev_header: Option, + height: Option, } #[allow(clippy::too_many_arguments)] @@ -103,6 +108,8 @@ where oneshot::Sender>, >, stage: TransactionSendProtocolStage, + prev_header: Option, + height: Option, ) -> Self { Self { id, @@ -115,6 +122,8 @@ where message, service_request_reply_channel, stage, + prev_header, + height, } } @@ -452,7 +461,12 @@ where outbound_tx .sender_protocol - .finalize(KernelFeatures::empty(), &self.resources.factories) + .finalize( + KernelFeatures::empty(), + &self.resources.factories, + self.prev_header.clone(), + self.height, + ) .map_err(|e| { error!( target: LOG_TARGET, diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 076c9bc489..44d162a0fb 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -23,7 +23,7 @@ use crate::{ base_node_service::handle::{BaseNodeEvent, BaseNodeServiceHandle}, connectivity_service::WalletConnectivityInterface, - output_manager_service::handle::OutputManagerHandle, + output_manager_service::{handle::OutputManagerHandle, storage::models::SpendingPriority}, storage::database::{WalletBackend, WalletDatabase}, transaction_service::{ config::TransactionServiceConfig, @@ -50,7 +50,6 @@ use crate::{ util::watch::Watch, utxo_scanner_service::utxo_scanning::RECOVERY_KEY, }; - use chrono::{NaiveDateTime, Utc}; use digest::Digest; use futures::{pin_mut, stream::FuturesUnordered, Stream, StreamExt}; @@ -74,7 +73,7 @@ use tari_core::{ proto::base_node as base_node_proto, transactions::{ tari_amount::MicroTari, - transaction::{KernelFeatures, OutputFeatures, Transaction, TransactionOutput}, + transaction::{KernelFeatures, OutputFeatures, Transaction, TransactionOutput, UnblindedOutput}, transaction_protocol::{ proto, recipient::RecipientSignedMessage, @@ -86,6 +85,7 @@ use tari_core::{ }, }; use tari_crypto::{ + inputs, keys::{DiffieHellmanSharedSecret, PublicKey as PKtrait}, script, tari_utilities::ByteArray, @@ -791,6 +791,8 @@ where message, Some(reply_channel), TransactionSendProtocolStage::Initial, + None, + self.last_seen_tip_height, ); let join_handle = tokio::spawn(protocol.execute()); @@ -851,13 +853,13 @@ where .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; // TODO: Add a standardized Diffie-Hellman method to the tari_crypto library that will return a private key, // TODO: then come back and use it here. - let spending_key = PrivateKey::from_bytes( + let spend_key = PrivateKey::from_bytes( CommsPublicKey::shared_secret(&sender_offset_private_key.clone(), &dest_pubkey.clone()).as_bytes(), ) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; let sender_message = TransactionSenderMessage::new_single_round_message(stp.get_single_round_message()?); - let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spending_key))?; + let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spend_key))?; let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; let rewind_data = RewindData { rewind_key: rewind_key.clone(), @@ -868,7 +870,7 @@ where let rtp = ReceiverTransactionProtocol::new_with_rewindable_output( sender_message, PrivateKey::random(&mut OsRng), - spending_key, + spend_key.clone(), OutputFeatures::default(), &self.resources.factories, &rewind_data, @@ -876,6 +878,17 @@ where let recipient_reply = rtp.get_signed_data()?.clone(); let output = recipient_reply.output.clone(); + let unblinded_output = UnblindedOutput::new( + amount, + spend_key, + OutputFeatures::default(), + script, + inputs!(PublicKey::from_secret_key(self.node_identity.secret_key())), + self.node_identity.secret_key().clone(), + output.sender_offset_public_key.clone(), + output.metadata_signature.clone(), + height, + ); // Start finalizing @@ -884,14 +897,19 @@ where // Finalize - stp.finalize(KernelFeatures::empty(), &self.resources.factories) - .map_err(|e| { - error!( - target: LOG_TARGET, - "Transaction (TxId: {}) could not be finalized. Failure error: {:?}", tx_id, e, - ); - TransactionServiceProtocolError::new(tx_id, e.into()) - })?; + stp.finalize( + KernelFeatures::empty(), + &self.resources.factories, + None, + self.last_seen_tip_height, + ) + .map_err(|e| { + error!( + target: LOG_TARGET, + "Transaction (TxId: {}) could not be finalized. Failure error: {:?}", tx_id, e, + ); + TransactionServiceProtocolError::new(tx_id, e.into()) + })?; info!(target: LOG_TARGET, "Finalized one-side transaction TxId: {}", tx_id); // This event being sent is important, but not critical to the protocol being successful. Send only fails if @@ -908,7 +926,9 @@ where let fee = stp .get_fee_amount() .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; - + self.output_manager_service + .add_output_with_tx_id(tx_id, unblinded_output, Some(SpendingPriority::HtlcSpendAsap)) + .await?; self.submit_transaction( transaction_broadcast_join_handles, CompletedTransaction::new( @@ -987,13 +1007,13 @@ where .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; // TODO: Add a standardized Diffie-Hellman method to the tari_crypto library that will return a private key, // TODO: then come back and use it here. - let spending_key = PrivateKey::from_bytes( + let spend_key = PrivateKey::from_bytes( CommsPublicKey::shared_secret(&sender_offset_private_key.clone(), &dest_pubkey.clone()).as_bytes(), ) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; let sender_message = TransactionSenderMessage::new_single_round_message(stp.get_single_round_message()?); - let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spending_key))?; + let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spend_key))?; let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; let rewind_data = RewindData { rewind_key: rewind_key.clone(), @@ -1004,7 +1024,7 @@ where let rtp = ReceiverTransactionProtocol::new_with_rewindable_output( sender_message, PrivateKey::random(&mut OsRng), - spending_key, + spend_key, OutputFeatures::default(), &self.resources.factories, &rewind_data, @@ -1019,14 +1039,19 @@ where // Finalize - stp.finalize(KernelFeatures::empty(), &self.resources.factories) - .map_err(|e| { - error!( - target: LOG_TARGET, - "Transaction (TxId: {}) could not be finalized. Failure error: {:?}", tx_id, e, - ); - TransactionServiceProtocolError::new(tx_id, e.into()) - })?; + stp.finalize( + KernelFeatures::empty(), + &self.resources.factories, + None, + self.last_seen_tip_height, + ) + .map_err(|e| { + error!( + target: LOG_TARGET, + "Transaction (TxId: {}) could not be finalized. Failure error: {:?}", tx_id, e, + ); + TransactionServiceProtocolError::new(tx_id, e.into()) + })?; info!(target: LOG_TARGET, "Finalized one-side transaction TxId: {}", tx_id); // This event being sent is important, but not critical to the protocol being successful. Send only fails if @@ -1290,19 +1315,6 @@ where Ok(()) } - // async fn set_completed_transaction_validity( - // &mut self, - // tx_id: TxId, - // valid: bool, - // ) -> Result<(), TransactionServiceError> { - // self.resources - // .db - // .set_completed_transaction_validity(tx_id, valid) - // .await?; - // - // Ok(()) - // } - /// Handle a Transaction Cancelled message received from the Comms layer pub async fn handle_transaction_cancelled_message( &mut self, @@ -1356,6 +1368,8 @@ where tx.message, None, TransactionSendProtocolStage::WaitForReply, + None, + self.last_seen_tip_height, ); let join_handle = tokio::spawn(protocol.execute()); @@ -1478,6 +1492,8 @@ where self.resources.clone(), tx_finalized_receiver, cancellation_receiver, + None, + self.last_seen_tip_height, ); let join_handle = tokio::spawn(protocol.execute()); @@ -1662,6 +1678,8 @@ where self.resources.clone(), tx_finalized_receiver, cancellation_receiver, + None, + self.last_seen_tip_height, ); let join_handle = tokio::spawn(protocol.execute()); diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index 52dcf91cd4..45c6370855 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -2145,7 +2145,7 @@ mod test { ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); - let mut stp = builder.build::(&factories).unwrap(); + let mut stp = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); let outbound_tx1 = OutboundTransaction { tx_id: 1u64, diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 1ea209b069..11b044b65b 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -355,6 +355,7 @@ where metadata_signature: ComSignature, script_private_key: &PrivateKey, sender_offset_public_key: &PublicKey, + script_lock_height: u64, ) -> Result { let unblinded_output = UnblindedOutput::new( amount, @@ -365,6 +366,7 @@ where script_private_key.clone(), sender_offset_public_key.clone(), metadata_signature, + script_lock_height, ); let tx_id = self @@ -373,7 +375,7 @@ where .await?; self.output_manager_service - .add_unvalidated_output(tx_id, unblinded_output.clone()) + .add_unvalidated_output(tx_id, unblinded_output.clone(), None) .await?; info!( @@ -408,7 +410,7 @@ where .await?; self.output_manager_service - .add_output_with_tx_id(tx_id, unblinded_output.clone()) + .add_output_with_tx_id(tx_id, unblinded_output.clone(), None) .await?; info!( @@ -559,6 +561,7 @@ pub async fn persist_one_sided_payment_script_for_node_identity( private_key: node_identity.secret_key().clone(), script, input: ExecutionStack::default(), + script_lock_height: 0, }; output_manager_service.add_known_script(known_script).await?; diff --git a/base_layer/wallet/tests/output_manager_service/service.rs b/base_layer/wallet/tests/output_manager_service/service.rs index 892c99c5c4..a420a1c4ca 100644 --- a/base_layer/wallet/tests/output_manager_service/service.rs +++ b/base_layer/wallet/tests/output_manager_service/service.rs @@ -311,7 +311,7 @@ fn generate_sender_transaction_message(amount: MicroTari) -> (TxId, TransactionS script_private_key, ); - let mut stp = builder.build::(&factories).unwrap(); + let mut stp = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); let tx_id = stp.get_tx_id().unwrap(); ( tx_id, @@ -328,7 +328,7 @@ async fn fee_estimate() { let (mut oms, _, _shutdown, _, _, _, _, _) = setup_output_manager_service(backend, true).await; let (_, uo) = make_input(&mut OsRng.clone(), MicroTari::from(3000), &factories.commitment); - oms.add_output(uo).await.unwrap(); + oms.add_output(uo, None).await.unwrap(); let fee_calc = Fee::new(*create_consensus_constants(0).transaction_weight()); // minimum fpg let fee_per_gram = MicroTari::from(1); @@ -407,7 +407,7 @@ async fn test_utxo_selection_no_chain_metadata() { &factories.commitment, Some(OutputFeatures::with_maturity(i)), ); - oms.add_output(uo.clone()).await.unwrap(); + oms.add_output(uo.clone(), None).await.unwrap(); } // but we have no chain state so the lowest maturity should be used @@ -509,7 +509,7 @@ async fn test_utxo_selection_with_chain_metadata() { &factories.commitment, Some(OutputFeatures::with_maturity(i)), ); - oms.add_output(uo.clone()).await.unwrap(); + oms.add_output(uo.clone(), None).await.unwrap(); } let utxos = oms.get_unspent_outputs().await.unwrap(); @@ -605,7 +605,7 @@ async fn send_not_enough_funds() { MicroTari::from(200 + OsRng.next_u64() % 1000), &factories.commitment, ); - oms.add_output(uo).await.unwrap(); + oms.add_output(uo, None).await.unwrap(); } match oms @@ -635,21 +635,27 @@ async fn send_no_change() { let constants = create_consensus_constants(0); let fee_without_change = Fee::new(*constants.transaction_weight()).calculate(fee_per_gram, 1, 2, 1, 0); let value1 = 500; - oms.add_output(create_unblinded_output( - script!(Nop), - OutputFeatures::default(), - TestParamsHelpers::new(), - MicroTari::from(value1), - )) + oms.add_output( + create_unblinded_output( + script!(Nop), + OutputFeatures::default(), + TestParamsHelpers::new(), + MicroTari::from(value1), + ), + None, + ) .await .unwrap(); let value2 = 800; - oms.add_output(create_unblinded_output( - script!(Nop), - OutputFeatures::default(), - TestParamsHelpers::new(), - MicroTari::from(value2), - )) + oms.add_output( + create_unblinded_output( + script!(Nop), + OutputFeatures::default(), + TestParamsHelpers::new(), + MicroTari::from(value2), + ), + None, + ) .await .unwrap(); @@ -682,21 +688,27 @@ async fn send_not_enough_for_change() { let constants = create_consensus_constants(0); let fee_without_change = Fee::new(*constants.transaction_weight()).calculate(fee_per_gram, 1, 2, 1, 0); let value1 = MicroTari(500); - oms.add_output(create_unblinded_output( - TariScript::default(), - OutputFeatures::default(), - TestParamsHelpers::new(), - value1, - )) + oms.add_output( + create_unblinded_output( + TariScript::default(), + OutputFeatures::default(), + TestParamsHelpers::new(), + value1, + ), + None, + ) .await .unwrap(); let value2 = MicroTari(800); - oms.add_output(create_unblinded_output( - TariScript::default(), - OutputFeatures::default(), - TestParamsHelpers::new(), - value2, - )) + oms.add_output( + create_unblinded_output( + TariScript::default(), + OutputFeatures::default(), + TestParamsHelpers::new(), + value2, + ), + None, + ) .await .unwrap(); @@ -732,7 +744,7 @@ async fn cancel_transaction() { MicroTari::from(100 + OsRng.next_u64() % 1000), &factories.commitment, ); - oms.add_output(uo).await.unwrap(); + oms.add_output(uo, None).await.unwrap(); } let stp = oms .prepare_transaction_to_send( @@ -802,11 +814,11 @@ async fn test_get_balance() { let output_val = MicroTari::from(2000); let (_ti, uo) = make_input(&mut OsRng.clone(), output_val, &factories.commitment); total += uo.value; - oms.add_output(uo).await.unwrap(); + oms.add_output(uo, None).await.unwrap(); let (_ti, uo) = make_input(&mut OsRng.clone(), output_val, &factories.commitment); total += uo.value; - oms.add_output(uo).await.unwrap(); + oms.add_output(uo, None).await.unwrap(); let send_value = MicroTari::from(1000); let stp = oms @@ -830,7 +842,7 @@ async fn test_get_balance() { let balance = oms.get_balance().await.unwrap(); assert_eq!(output_val, balance.available_balance); - assert_eq!(output_val, balance.time_locked_balance.unwrap()); + assert_eq!(MicroTari::from(0), balance.time_locked_balance.unwrap()); assert_eq!(recv_value + change_val, balance.pending_incoming_balance); assert_eq!(output_val, balance.pending_outgoing_balance); } @@ -846,11 +858,11 @@ async fn sending_transaction_with_short_term_clear() { let available_balance = 10_000 * uT; let (_ti, uo) = make_input(&mut OsRng.clone(), available_balance, &factories.commitment); - oms.add_output(uo).await.unwrap(); + oms.add_output(uo, None).await.unwrap(); let balance = oms.get_balance().await.unwrap(); assert_eq!(balance.available_balance, available_balance); - assert_eq!(balance.time_locked_balance.unwrap(), available_balance); + assert_eq!(balance.time_locked_balance.unwrap(), MicroTari::from(0)); // Check that funds are encumbered and then unencumbered if the pending tx is not confirmed before restart let _stp = oms @@ -875,7 +887,7 @@ async fn sending_transaction_with_short_term_clear() { let balance = oms.get_balance().await.unwrap(); assert_eq!(balance.available_balance, available_balance); - assert_eq!(balance.time_locked_balance.unwrap(), available_balance); + assert_eq!(balance.time_locked_balance.unwrap(), MicroTari::from(0)); // Check that is the pending tx is confirmed that the encumberance persists after restart let stp = oms @@ -914,9 +926,9 @@ async fn coin_split_with_change() { let (_ti, uo1) = make_input(&mut OsRng, val1, &factories.commitment); let (_ti, uo2) = make_input(&mut OsRng, val2, &factories.commitment); let (_ti, uo3) = make_input(&mut OsRng, val3, &factories.commitment); - assert!(oms.add_output(uo1).await.is_ok()); - assert!(oms.add_output(uo2).await.is_ok()); - assert!(oms.add_output(uo3).await.is_ok()); + assert!(oms.add_output(uo1, None).await.is_ok()); + assert!(oms.add_output(uo2, None).await.is_ok()); + assert!(oms.add_output(uo3, None).await.is_ok()); let fee_per_gram = MicroTari::from(5); let split_count = 8; @@ -962,9 +974,9 @@ async fn coin_split_no_change() { let (_ti, uo1) = make_input(&mut OsRng, val1, &factories.commitment); let (_ti, uo2) = make_input(&mut OsRng, val2, &factories.commitment); let (_ti, uo3) = make_input(&mut OsRng, val3, &factories.commitment); - assert!(oms.add_output(uo1).await.is_ok()); - assert!(oms.add_output(uo2).await.is_ok()); - assert!(oms.add_output(uo3).await.is_ok()); + assert!(oms.add_output(uo1, None).await.is_ok()); + assert!(oms.add_output(uo2, None).await.is_ok()); + assert!(oms.add_output(uo3, None).await.is_ok()); let (_tx_id, coin_split_tx, fee, amount) = oms .create_coin_split(1000.into(), split_count, fee_per_gram, None) @@ -1053,7 +1065,7 @@ async fn test_txo_validation() { MicroTari::from(output1_value), ); let output1_tx_output = output1.as_transaction_output(&factories).unwrap(); - oms.add_output_with_tx_id(1, output1.clone()).await.unwrap(); + oms.add_output_with_tx_id(1, output1.clone(), None).await.unwrap(); let output2_value = 2_000_000; let output2 = create_unblinded_output( @@ -1064,7 +1076,7 @@ async fn test_txo_validation() { ); let output2_tx_output = output2.as_transaction_output(&factories).unwrap(); - oms.add_output_with_tx_id(2, output2.clone()).await.unwrap(); + oms.add_output_with_tx_id(2, output2.clone(), None).await.unwrap(); let output3_value = 4_000_000; let output3 = create_unblinded_output( @@ -1074,7 +1086,7 @@ async fn test_txo_validation() { MicroTari::from(output3_value), ); - oms.add_output_with_tx_id(3, output3.clone()).await.unwrap(); + oms.add_output_with_tx_id(3, output3.clone(), None).await.unwrap(); let mut block1_header = BlockHeader::new(1); block1_header.height = 1; @@ -1173,11 +1185,12 @@ async fn test_txo_validation() { let output6_tx_output = output6.unblinded_output.as_transaction_output(&factories).unwrap(); let balance = oms.get_balance().await.unwrap(); + assert_eq!( balance.available_balance, MicroTari::from(output2_value) + MicroTari::from(output3_value) ); - assert_eq!(balance.available_balance, balance.time_locked_balance.unwrap()); + assert_eq!(MicroTari::from(0), balance.time_locked_balance.unwrap()); assert_eq!(balance.pending_outgoing_balance, MicroTari::from(output1_value)); assert_eq!( balance.pending_incoming_balance, @@ -1278,7 +1291,7 @@ async fn test_txo_validation() { balance.available_balance, MicroTari::from(output2_value) + MicroTari::from(output3_value) ); - assert_eq!(balance.available_balance, balance.time_locked_balance.unwrap()); + assert_eq!(MicroTari::from(0), balance.time_locked_balance.unwrap()); assert_eq!(oms.get_unspent_outputs().await.unwrap().len(), 2); @@ -1327,7 +1340,7 @@ async fn test_txo_validation() { ); assert_eq!(balance.pending_outgoing_balance, MicroTari::from(0)); assert_eq!(balance.pending_incoming_balance, MicroTari::from(0)); - assert_eq!(balance.available_balance, balance.time_locked_balance.unwrap()); + assert_eq!(MicroTari::from(0), balance.time_locked_balance.unwrap()); // Trigger another validation and only Output3 should be checked oms.validate_txos().await.unwrap(); @@ -1450,7 +1463,7 @@ async fn test_txo_validation() { balance.pending_incoming_balance, MicroTari::from(output1_value) - MicroTari::from(901_240) ); - assert_eq!(balance.available_balance, balance.time_locked_balance.unwrap()); + assert_eq!(MicroTari::from(0), balance.time_locked_balance.unwrap()); // Now we will update the mined_height in the responses so that the outputs on the reorged chain are confirmed // Output 1: Spent in Block 5 - Confirmed @@ -1510,7 +1523,7 @@ async fn test_txo_validation() { ); assert_eq!(balance.pending_outgoing_balance, MicroTari::from(0)); assert_eq!(balance.pending_incoming_balance, MicroTari::from(0)); - assert_eq!(balance.available_balance, balance.time_locked_balance.unwrap()); + assert_eq!(MicroTari::from(0), balance.time_locked_balance.unwrap()); } #[tokio::test] @@ -1546,7 +1559,7 @@ async fn test_txo_revalidation() { MicroTari::from(output1_value), ); let output1_tx_output = output1.as_transaction_output(&factories).unwrap(); - oms.add_output_with_tx_id(1, output1.clone()).await.unwrap(); + oms.add_output_with_tx_id(1, output1.clone(), None).await.unwrap(); let output2_value = 2_000_000; let output2 = create_unblinded_output( @@ -1557,7 +1570,7 @@ async fn test_txo_revalidation() { ); let output2_tx_output = output2.as_transaction_output(&factories).unwrap(); - oms.add_output_with_tx_id(2, output2.clone()).await.unwrap(); + oms.add_output_with_tx_id(2, output2.clone(), None).await.unwrap(); let mut block1_header = BlockHeader::new(1); block1_header.height = 1; diff --git a/base_layer/wallet/tests/output_manager_service/storage.rs b/base_layer/wallet/tests/output_manager_service/storage.rs index 09ba45d115..d721fbc988 100644 --- a/base_layer/wallet/tests/output_manager_service/storage.rs +++ b/base_layer/wallet/tests/output_manager_service/storage.rs @@ -55,7 +55,7 @@ pub fn test_db_backend(backend: T) { MicroTari::from(100 + OsRng.next_u64() % 1000), &factories.commitment, ); - let mut uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let mut uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); uo.unblinded_output.features.maturity = i; runtime.block_on(db.add_unspent_output(uo.clone())).unwrap(); unspent_outputs.push(uo); @@ -102,7 +102,7 @@ pub fn test_db_backend(backend: T) { MicroTari::from(100 + OsRng.next_u64() % 1000), &factories.commitment, ); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); runtime.block_on(db.add_unspent_output(uo.clone())).unwrap(); pending_tx.outputs_to_be_spent.push(uo); } @@ -112,7 +112,7 @@ pub fn test_db_backend(backend: T) { MicroTari::from(100 + OsRng.next_u64() % 1000), &factories.commitment, ); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); pending_tx.outputs_to_be_received.push(uo); } runtime @@ -254,7 +254,7 @@ pub fn test_db_backend(backend: T) { MicroTari::from(100 + OsRng.next_u64() % 1000), &factories.commitment, ); - let output_to_be_received = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let output_to_be_received = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); runtime .block_on(db.add_output_to_be_received(11, output_to_be_received.clone(), None)) .unwrap(); @@ -381,7 +381,7 @@ pub async fn test_short_term_encumberance() { MicroTari::from(100 + OsRng.next_u64() % 1000), &factories.commitment, ); - let mut uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let mut uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); uo.unblinded_output.features.maturity = i; db.add_unspent_output(uo.clone()).await.unwrap(); unspent_outputs.push(uo); @@ -434,7 +434,7 @@ pub async fn test_no_duplicate_outputs() { // create an output let (_ti, uo) = make_input(&mut OsRng, MicroTari::from(1000), &factories.commitment); - let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories).unwrap(); + let uo = DbUnblindedOutput::from_unblinded_output(uo, &factories, None).unwrap(); // add it to the database let result = db.add_unspent_output(uo.clone()).await; diff --git a/base_layer/wallet/tests/support/comms_rpc.rs b/base_layer/wallet/tests/support/comms_rpc.rs index 3a4f306fc4..05188342f8 100644 --- a/base_layer/wallet/tests/support/comms_rpc.rs +++ b/base_layer/wallet/tests/support/comms_rpc.rs @@ -131,7 +131,7 @@ impl BaseNodeWalletRpcMockState { })), tip_info_response: Arc::new(Mutex::new(TipInfoResponse { metadata: Some(ChainMetadataProto { - height_of_longest_chain: Some(std::u64::MAX), + height_of_longest_chain: Some(std::i64::MAX as u64), best_block: Some(Vec::new()), accumulated_difficulty: Vec::new(), pruned_height: 0, diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index acebd6425a..3d25188ddc 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -20,14 +20,6 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{ - collections::HashMap, - convert::{TryFrom, TryInto}, - path::Path, - sync::Arc, - time::Duration, -}; - use chrono::{Duration as ChronoDuration, Utc}; use futures::{ channel::{mpsc, mpsc::Sender}, @@ -36,6 +28,13 @@ use futures::{ }; use prost::Message; use rand::{rngs::OsRng, RngCore}; +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, + path::Path, + sync::Arc, + time::Duration, +}; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, common::Blake256, @@ -193,7 +192,7 @@ pub fn setup_transaction_service>( )); let db = WalletDatabase::new(WalletSqliteDatabase::new(db_connection.clone(), None).unwrap()); - let metadata = ChainMetadata::new(std::u64::MAX, Vec::new(), 0, 0, 0); + let metadata = ChainMetadata::new(std::i64::MAX as u64, Vec::new(), 0, 0, 0); runtime.block_on(db.set_chain_metadata(metadata)).unwrap(); @@ -540,7 +539,7 @@ fn manage_single_transaction() { )) .is_err()); - runtime.block_on(alice_oms.add_output(uo1)).unwrap(); + runtime.block_on(alice_oms.add_output(uo1, None)).unwrap(); let message = "TAKE MAH MONEYS!".to_string(); runtime .block_on(alice_ts.send_transaction( @@ -655,7 +654,7 @@ fn single_transaction_to_self() { let initial_wallet_value = 2500.into(); let (_utxo, uo1) = make_input(&mut OsRng, initial_wallet_value, &factories.commitment); - alice_oms.add_output(uo1).await.unwrap(); + alice_oms.add_output(uo1, None).await.unwrap(); let message = "TAKE MAH _OWN_ MONEYS!".to_string(); let value = 1000.into(); let tx_id = alice_ts @@ -738,7 +737,7 @@ fn send_one_sided_transaction_to_other() { let initial_wallet_value = 2500.into(); let (_utxo, uo1) = make_input(&mut OsRng, initial_wallet_value, &factories.commitment); let mut alice_oms_clone = alice_oms.clone(); - runtime.block_on(async move { alice_oms_clone.add_output(uo1).await.unwrap() }); + runtime.block_on(async move { alice_oms_clone.add_output(uo1, None).await.unwrap() }); let message = "SEE IF YOU CAN CATCH THIS ONE..... SIDED TX!".to_string(); let value = 1000.into(); @@ -860,6 +859,7 @@ fn recover_one_sided_transaction() { private_key: bob_node_identity.secret_key().clone(), script, input: ExecutionStack::default(), + script_lock_height: 0, }; let mut cloned_bob_oms = bob_oms.clone(); runtime.block_on(async move { @@ -871,7 +871,7 @@ fn recover_one_sided_transaction() { let initial_wallet_value = 2500.into(); let (_utxo, uo1) = make_input(&mut OsRng, initial_wallet_value, &factories.commitment); let mut alice_oms_clone = alice_oms; - runtime.block_on(async move { alice_oms_clone.add_output(uo1).await.unwrap() }); + runtime.block_on(async move { alice_oms_clone.add_output(uo1, None).await.unwrap() }); let message = "".to_string(); let value = 1000.into(); @@ -944,7 +944,7 @@ fn test_htlc_send_and_claim() { let bob_connection = run_migration_and_create_sqlite_connection(&bob_db_path, 16).unwrap(); let shutdown = Shutdown::new(); - let (alice_ts, alice_oms, _alice_comms, mut alice_connectivity) = setup_transaction_service( + let (mut alice_ts, mut alice_oms, _alice_comms, mut alice_connectivity) = setup_transaction_service( &mut runtime, alice_node_identity, vec![], @@ -985,7 +985,7 @@ fn test_htlc_send_and_claim() { let initial_wallet_value = 2500.into(); let (_utxo, uo1) = make_input(&mut OsRng, initial_wallet_value, &factories.commitment); let mut alice_oms_clone = alice_oms.clone(); - runtime.block_on(async move { alice_oms_clone.add_output(uo1).await.unwrap() }); + runtime.block_on(async move { alice_oms_clone.add_output(uo1, None).await.unwrap() }); let message = "".to_string(); let value = 1000.into(); @@ -998,10 +998,8 @@ fn test_htlc_send_and_claim() { .expect("Alice sending HTLC transaction") }); - let mut alice_ts_clone2 = alice_ts; - let mut alice_oms_clone = alice_oms; runtime.block_on(async move { - let completed_tx = alice_ts_clone2 + let completed_tx = alice_ts .get_completed_transaction(tx_id) .await .expect("Could not find completed HTLC tx"); @@ -1009,7 +1007,7 @@ fn test_htlc_send_and_claim() { let fees = completed_tx.fee; assert_eq!( - alice_oms_clone.get_balance().await.unwrap().pending_incoming_balance, + alice_oms.get_balance().await.unwrap().pending_incoming_balance, initial_wallet_value - value - fees ); }); @@ -1097,7 +1095,7 @@ fn send_one_sided_transaction_to_self() { let initial_wallet_value = 2500.into(); let (_utxo, uo1) = make_input(&mut OsRng, initial_wallet_value, &factories.commitment); let mut alice_oms_clone = alice_oms; - runtime.block_on(async move { alice_oms_clone.add_output(uo1).await.unwrap() }); + runtime.block_on(async move { alice_oms_clone.add_output(uo1, None).await.unwrap() }); let message = "SEE IF YOU CAN CATCH THIS ONE..... SIDED TX!".to_string(); let value = 1000.into(); @@ -1223,17 +1221,17 @@ fn manage_multiple_transactions() { ); let (_utxo, uo2) = make_input(&mut OsRng, MicroTari(3500), &factories.commitment); - runtime.block_on(bob_oms.add_output(uo2)).unwrap(); + runtime.block_on(bob_oms.add_output(uo2, None)).unwrap(); let (_utxo, uo3) = make_input(&mut OsRng, MicroTari(4500), &factories.commitment); - runtime.block_on(carol_oms.add_output(uo3)).unwrap(); + runtime.block_on(carol_oms.add_output(uo3, None)).unwrap(); // Add some funds to Alices wallet let (_utxo, uo1a) = make_input(&mut OsRng, MicroTari(5500), &factories.commitment); - runtime.block_on(alice_oms.add_output(uo1a)).unwrap(); + runtime.block_on(alice_oms.add_output(uo1a, None)).unwrap(); let (_utxo, uo1b) = make_input(&mut OsRng, MicroTari(3000), &factories.commitment); - runtime.block_on(alice_oms.add_output(uo1b)).unwrap(); + runtime.block_on(alice_oms.add_output(uo1b, None)).unwrap(); let (_utxo, uo1c) = make_input(&mut OsRng, MicroTari(3000), &factories.commitment); - runtime.block_on(alice_oms.add_output(uo1c)).unwrap(); + runtime.block_on(alice_oms.add_output(uo1c, None)).unwrap(); // A series of interleaved transactions. First with Bob and Carol offline and then two with them online let value_a_to_b_1 = MicroTari::from(1000); @@ -1412,7 +1410,7 @@ fn test_accepting_unknown_tx_id_and_malformed_reply() { let (_utxo, uo) = make_input(&mut OsRng, MicroTari(250000), &factories.commitment); - runtime.block_on(alice_output_manager.add_output(uo)).unwrap(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); runtime .block_on(alice_ts.send_transaction( @@ -1537,7 +1535,7 @@ fn finalize_tx_with_incorrect_pubkey() { ) = setup_transaction_service_no_comms(&mut runtime, factories.clone(), connection_bob, None); let (_utxo, uo) = make_input(&mut OsRng, MicroTari(250000), &factories.commitment); - runtime.block_on(bob_output_manager.add_output(uo)).unwrap(); + runtime.block_on(bob_output_manager.add_output(uo, None)).unwrap(); let mut stp = runtime .block_on(bob_output_manager.prepare_transaction_to_send( OsRng.next_u64(), @@ -1571,7 +1569,8 @@ fn finalize_tx_with_incorrect_pubkey() { stp.add_single_recipient_info(recipient_reply.clone(), &factories.range_proof) .unwrap(); - stp.finalize(KernelFeatures::empty(), &factories).unwrap(); + stp.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) + .unwrap(); let tx = stp.get_transaction().unwrap(); let finalized_transaction_message = proto::TransactionFinalizedMessage { @@ -1666,7 +1665,7 @@ fn finalize_tx_with_missing_output() { let (_utxo, uo) = make_input(&mut OsRng, MicroTari(250000), &factories.commitment); - runtime.block_on(bob_output_manager.add_output(uo)).unwrap(); + runtime.block_on(bob_output_manager.add_output(uo, None)).unwrap(); let mut stp = runtime .block_on(bob_output_manager.prepare_transaction_to_send( @@ -1701,7 +1700,8 @@ fn finalize_tx_with_missing_output() { stp.add_single_recipient_info(recipient_reply.clone(), &factories.range_proof) .unwrap(); - stp.finalize(KernelFeatures::empty(), &factories).unwrap(); + stp.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) + .unwrap(); let finalized_transaction_message = proto::TransactionFinalizedMessage { tx_id: recipient_reply.tx_id, @@ -1817,11 +1817,11 @@ fn discovery_async_return_test() { let mut alice_event_stream = alice_ts.get_event_stream(); let (_utxo, uo1a) = make_input(&mut OsRng, MicroTari(5500), &factories.commitment); - runtime.block_on(alice_oms.add_output(uo1a)).unwrap(); + runtime.block_on(alice_oms.add_output(uo1a, None)).unwrap(); let (_utxo, uo1b) = make_input(&mut OsRng, MicroTari(3000), &factories.commitment); - runtime.block_on(alice_oms.add_output(uo1b)).unwrap(); + runtime.block_on(alice_oms.add_output(uo1b, None)).unwrap(); let (_utxo, uo1c) = make_input(&mut OsRng, MicroTari(3000), &factories.commitment); - runtime.block_on(alice_oms.add_output(uo1c)).unwrap(); + runtime.block_on(alice_oms.add_output(uo1c, None)).unwrap(); let initial_balance = runtime.block_on(alice_oms.get_balance()).unwrap(); @@ -2126,7 +2126,7 @@ fn test_transaction_cancellation() { 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(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let amount_sent = 10000 * uT; @@ -2248,7 +2248,7 @@ fn test_transaction_cancellation() { ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); - let mut stp = builder.build::(&factories).unwrap(); + let mut stp = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); let tx_sender_msg = stp.build_single_round_message().unwrap(); let tx_id2 = tx_sender_msg.tx_id; let proto_message = proto::TransactionSenderMessage::single(tx_sender_msg.into()); @@ -2320,7 +2320,7 @@ fn test_transaction_cancellation() { ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); - let mut stp = builder.build::(&factories).unwrap(); + let mut stp = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); let tx_sender_msg = stp.build_single_round_message().unwrap(); let tx_id3 = tx_sender_msg.tx_id; let proto_message = proto::TransactionSenderMessage::single(tx_sender_msg.into()); @@ -2431,7 +2431,7 @@ fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { 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(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let amount_sent = 10000 * uT; @@ -2603,7 +2603,7 @@ fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { // Now to repeat sending so we can test the SAF send of the finalize message 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(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let amount_sent = 20000 * uT; @@ -2701,13 +2701,13 @@ fn test_tx_direct_send_behaviour() { let mut alice_event_stream = alice_ts.get_event_stream(); let (_utxo, uo) = make_input(&mut OsRng, 1000000 * uT, &factories.commitment); - runtime.block_on(alice_output_manager.add_output(uo)).unwrap(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let (_utxo, uo) = make_input(&mut OsRng, 1000000 * uT, &factories.commitment); - runtime.block_on(alice_output_manager.add_output(uo)).unwrap(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let (_utxo, uo) = make_input(&mut OsRng, 1000000 * uT, &factories.commitment); - runtime.block_on(alice_output_manager.add_output(uo)).unwrap(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let (_utxo, uo) = make_input(&mut OsRng, 1000000 * uT, &factories.commitment); - runtime.block_on(alice_output_manager.add_output(uo)).unwrap(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let amount_sent = 10000 * uT; @@ -2939,7 +2939,7 @@ fn test_restarting_transaction_protocols() { inputs!(PublicKey::from_secret_key(&script_private_key)), script_private_key, ); - let mut bob_stp = builder.build::(&factories).unwrap(); + let mut bob_stp = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); let msg = bob_stp.build_single_round_message().unwrap(); let bob_pre_finalize = bob_stp.clone(); @@ -2960,7 +2960,7 @@ fn test_restarting_transaction_protocols() { .add_single_recipient_info(alice_reply.clone(), &factories.range_proof) .unwrap(); - match bob_stp.finalize(KernelFeatures::empty(), &factories) { + match bob_stp.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) { Ok(_) => (), Err(e) => panic!("Should be able to finalize tx: {}", e), }; @@ -3914,7 +3914,7 @@ fn test_transaction_resending() { // Send a transaction to Bob 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(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let amount_sent = 10000 * uT; @@ -4112,7 +4112,7 @@ fn test_resend_on_startup() { ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); - let mut stp = builder.build::(&factories).unwrap(); + let mut stp = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); let stp_msg = stp.build_single_round_message().unwrap(); let tx_sender_msg = TransactionSenderMessage::Single(Box::new(stp_msg)); @@ -4410,7 +4410,7 @@ fn test_replying_to_cancelled_tx() { // Send a transaction to Bob 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(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let amount_sent = 10000 * uT; @@ -4544,7 +4544,7 @@ fn test_transaction_timeout_cancellation() { // Send a transaction to Bob 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(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let amount_sent = 10000 * uT; @@ -4619,7 +4619,7 @@ fn test_transaction_timeout_cancellation() { ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); - let mut stp = builder.build::(&factories).unwrap(); + let mut stp = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); let stp_msg = stp.build_single_round_message().unwrap(); let tx_sender_msg = TransactionSenderMessage::Single(Box::new(stp_msg)); @@ -4828,10 +4828,10 @@ fn transaction_service_tx_broadcast() { let alice_output_value = MicroTari(250000); let (_utxo, uo) = make_input(&mut OsRng, alice_output_value, &factories.commitment); - runtime.block_on(alice_output_manager.add_output(uo)).unwrap(); + runtime.block_on(alice_output_manager.add_output(uo, None)).unwrap(); let (_utxo, uo2) = make_input(&mut OsRng, alice_output_value, &factories.commitment); - runtime.block_on(alice_output_manager.add_output(uo2)).unwrap(); + runtime.block_on(alice_output_manager.add_output(uo2, None)).unwrap(); let amount_sent1 = 10000 * uT; diff --git a/base_layer/wallet/tests/transaction_service/storage.rs b/base_layer/wallet/tests/transaction_service/storage.rs index e14e28b8d0..54d51c3c92 100644 --- a/base_layer/wallet/tests/transaction_service/storage.rs +++ b/base_layer/wallet/tests/transaction_service/storage.rs @@ -93,7 +93,7 @@ pub fn test_db_backend(backend: T) { ) .with_change_script(script!(Nop), ExecutionStack::default(), PrivateKey::random(&mut OsRng)); - let stp = builder.build::(&factories).unwrap(); + let stp = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); let messages = vec!["Hey!".to_string(), "Yo!".to_string(), "Sup!".to_string()]; let amounts = vec![MicroTari::from(10_000), MicroTari::from(23_000), MicroTari::from(5_000)]; diff --git a/base_layer/wallet/tests/wallet/mod.rs b/base_layer/wallet/tests/wallet/mod.rs index 3dcc79190b..376d7f1f5c 100644 --- a/base_layer/wallet/tests/wallet/mod.rs +++ b/base_layer/wallet/tests/wallet/mod.rs @@ -156,7 +156,7 @@ async fn create_wallet( None, None, ); - let metadata = ChainMetadata::new(std::u64::MAX, Vec::new(), 0, 0, 0); + let metadata = ChainMetadata::new(std::i64::MAX as u64, Vec::new(), 0, 0, 0); let _ = wallet_backend.write(WriteOperation::Insert(DbKeyValuePair::BaseNodeChainMetadata(metadata))); @@ -241,7 +241,7 @@ async fn test_wallet() { let value = MicroTari::from(1000); let (_utxo, uo1) = make_input(&mut OsRng, MicroTari(2500), &factories.commitment); - alice_wallet.output_manager_service.add_output(uo1).await.unwrap(); + alice_wallet.output_manager_service.add_output(uo1, None).await.unwrap(); alice_wallet .transaction_service @@ -577,7 +577,7 @@ fn test_store_and_forward_send_tx() { let (_utxo, uo1) = make_input(&mut OsRng, MicroTari(2500), &factories.commitment); alice_runtime - .block_on(alice_wallet.output_manager_service.add_output(uo1)) + .block_on(alice_wallet.output_manager_service.add_output(uo1, None)) .unwrap(); let tx_id = alice_runtime @@ -738,6 +738,7 @@ async fn test_import_utxo() { utxo.metadata_signature.clone(), &p.script_private_key, &p.sender_offset_public_key, + 0, ) .await .unwrap(); diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 43006072de..6bd2cd6136 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -4381,6 +4381,7 @@ pub unsafe extern "C" fn wallet_import_utxo( }; let public_script_key = PublicKey::from_secret_key(&(*spending_key)); + // Todo the script_lock_height can be something other than 0, for example an HTLC transaction match (*wallet).runtime.block_on((*wallet).wallet.import_utxo( MicroTari::from(amount), &(*spending_key).clone(), @@ -4392,6 +4393,7 @@ pub unsafe extern "C" fn wallet_import_utxo( ComSignature::default(), &(*spending_key).clone(), &Default::default(), + 0, )) { Ok(tx_id) => { if let Err(e) = (*wallet) diff --git a/clients/wallet_grpc_client/index.js b/clients/wallet_grpc_client/index.js index 17a5e17293..6645aa4588 100644 --- a/clients/wallet_grpc_client/index.js +++ b/clients/wallet_grpc_client/index.js @@ -44,6 +44,7 @@ function Client(address) { "revalidateAllTransactions", "SendShaAtomicSwapTransaction", "claimShaAtomicSwapTransaction", + "ClaimHtlcRefundTransaction", ]; this.waitForReady = (...args) => { diff --git a/integration_tests/features/WalletTransfer.feature b/integration_tests/features/WalletTransfer.feature index 47dd6c5914..28e68b2d5c 100644 --- a/integration_tests/features/WalletTransfer.feature +++ b/integration_tests/features/WalletTransfer.feature @@ -48,4 +48,22 @@ Feature: Wallet Transfer And mining node MINER mines 6 blocks And I claim an HTLC transaction with wallet WALLET_B at fee 20 And mining node MINER mines 6 blocks - Then I wait for wallet WALLET_B to have at least 4000000000 uT \ No newline at end of file + Then I wait for wallet WALLET_B to have at least 4000000000 uT + + Scenario: As a wallet I want to claim a HTLC refund transaction + Given I have a seed node NODE + # Add a 2nd node otherwise initial sync will not succeed + And I have 1 base nodes connected to all seed nodes + And I have wallet WALLET_A connected to all seed nodes + And I have wallet WALLET_B connected to all seed nodes + And I have wallet WALLET_C connected to all seed nodes + And I have mining node MINER connected to base node NODE and wallet WALLET_A + And I have mining node MINER_2 connected to base node NODE and wallet WALLET_C + When mining node MINER mines 10 blocks + Then I wait for wallet WALLET_A to have at least 10000000000 uT + When I broadcast HTLC transaction with 5000000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 + # atomic swaps are set at lock of 720 blocks + And mining node MINER_2 mines 720 blocks + And I claim an HTLC refund transaction with wallet WALLET_A at fee 20 + And mining node MINER_2 mines 6 blocks + Then I wait for wallet WALLET_A to have at least 9000000000 uT diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js index 51d721570c..723da3e09e 100644 --- a/integration_tests/features/support/steps.js +++ b/integration_tests/features/support/steps.js @@ -1909,7 +1909,85 @@ When( const wait_seconds = 5; console.log( " " + - lastResult.results.failure_message + + this.lastResult.results.failure_message + + ", trying again after " + + wait_seconds + + "s (" + + retries + + " of " + + retries_limit + + ")" + ); + await sleep(wait_seconds * 1000); + retries++; + } + } + + if (success) { + this.addTransaction( + sourceInfo.public_key, + this.lastResult.results.transaction_id + ); + } + expect(success).to.equal(true); + //lets now wait for this transaction to be at least broadcast before we continue. + await waitFor( + async () => + sourceClient.isTransactionAtLeastBroadcast( + this.lastResult.results.transaction_id + ), + true, + 60 * 1000, + 5 * 1000, + 5 + ); + + let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( + this.lastResult.results.transaction_id + ); + + expect(transactionPending).to.equal(true); + } +); + +When( + /I claim an HTLC refund transaction with wallet (.*) at fee (.*)/, + { timeout: 25 * 5 * 1000 }, + async function (source, feePerGram) { + const sourceClient = await this.getWallet(source).connectClient(); + + const sourceInfo = await sourceClient.identify(); + console.log("Claiming HTLC refund transaction of", source); + let success = false; + let retries = 1; + let hash = this.lastResult.output_hash; + const retries_limit = 25; + while (!success && retries <= retries_limit) { + await waitFor( + async () => { + try { + this.lastResult = await sourceClient.claimHtlcRefund({ + output_hash: hash, + fee_per_gram: feePerGram, + }); + } catch (error) { + console.log(error); + return false; + } + return true; + }, + true, + 20 * 1000, + 5 * 1000, + 5 + ); + + success = this.lastResult.results.is_success; + if (!success) { + const wait_seconds = 5; + console.log( + " " + + this.lastResult.results.failure_message + ", trying again after " + wait_seconds + "s (" + diff --git a/integration_tests/helpers/walletClient.js b/integration_tests/helpers/walletClient.js index 9bbd06c639..e2ed88bd0a 100644 --- a/integration_tests/helpers/walletClient.js +++ b/integration_tests/helpers/walletClient.js @@ -156,6 +156,10 @@ class WalletClient { return await this.client.claimShaAtomicSwapTransaction(args); } + async claimHtlcRefund(args) { + return await this.client.ClaimHtlcRefundTransaction(args); + } + async importUtxos(outputs) { return await this.client.importUtxos({ outputs: outputs, From 1fdc13d105efc591e4dbf88a7503bda0998d318e Mon Sep 17 00:00:00 2001 From: Mike the Tike Date: Fri, 19 Nov 2021 08:37:16 +0200 Subject: [PATCH 34/46] v0.21.2 --- Cargo.lock | 48 +++++++++---------- applications/tari_app_grpc/Cargo.toml | 2 +- applications/tari_app_utilities/Cargo.toml | 2 +- applications/tari_base_node/Cargo.toml | 2 +- applications/tari_console_wallet/Cargo.toml | 2 +- .../tari_merge_mining_proxy/Cargo.toml | 2 +- applications/tari_mining_node/Cargo.toml | 2 +- applications/test_faucet/Cargo.toml | 2 +- base_layer/common_types/Cargo.toml | 2 +- base_layer/core/Cargo.toml | 2 +- base_layer/key_manager/Cargo.toml | 2 +- base_layer/mmr/Cargo.toml | 2 +- base_layer/p2p/Cargo.toml | 2 +- base_layer/service_framework/Cargo.toml | 2 +- base_layer/tari_stratum_ffi/Cargo.toml | 2 +- base_layer/wallet/Cargo.toml | 2 +- base_layer/wallet_ffi/Cargo.toml | 2 +- changelog.md | 7 +++ common/Cargo.toml | 2 +- comms/Cargo.toml | 2 +- comms/dht/Cargo.toml | 2 +- comms/rpc_macros/Cargo.toml | 2 +- infrastructure/derive/Cargo.toml | 2 +- infrastructure/shutdown/Cargo.toml | 2 +- infrastructure/storage/Cargo.toml | 2 +- infrastructure/test_utils/Cargo.toml | 2 +- package-lock.json | 2 +- 27 files changed, 56 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e8078ceae..eab17e3ff3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4401,7 +4401,7 @@ dependencies = [ [[package]] name = "tari_app_grpc" -version = "0.21.1" +version = "0.21.2" dependencies = [ "chrono", "prost", @@ -4416,7 +4416,7 @@ dependencies = [ [[package]] name = "tari_app_utilities" -version = "0.21.1" +version = "0.21.2" dependencies = [ "config", "dirs-next 1.0.2", @@ -4441,7 +4441,7 @@ dependencies = [ [[package]] name = "tari_base_node" -version = "0.21.1" +version = "0.21.2" dependencies = [ "anyhow", "bincode", @@ -4503,7 +4503,7 @@ dependencies = [ [[package]] name = "tari_common" -version = "0.21.1" +version = "0.21.2" dependencies = [ "anyhow", "config", @@ -4542,7 +4542,7 @@ dependencies = [ [[package]] name = "tari_common_types" -version = "0.21.1" +version = "0.21.2" dependencies = [ "digest", "futures 0.3.17", @@ -4556,7 +4556,7 @@ dependencies = [ [[package]] name = "tari_comms" -version = "0.21.1" +version = "0.21.2" dependencies = [ "anyhow", "async-trait", @@ -4606,7 +4606,7 @@ dependencies = [ [[package]] name = "tari_comms_dht" -version = "0.21.1" +version = "0.21.2" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -4654,7 +4654,7 @@ dependencies = [ [[package]] name = "tari_comms_rpc_macros" -version = "0.21.1" +version = "0.21.2" dependencies = [ "futures 0.3.17", "proc-macro2 1.0.32", @@ -4669,7 +4669,7 @@ dependencies = [ [[package]] name = "tari_console_wallet" -version = "0.21.1" +version = "0.21.2" dependencies = [ "bitflags 1.3.2", "chrono", @@ -4712,7 +4712,7 @@ dependencies = [ [[package]] name = "tari_core" -version = "0.21.1" +version = "0.21.2" dependencies = [ "async-trait", "bincode", @@ -4794,7 +4794,7 @@ dependencies = [ [[package]] name = "tari_infra_derive" -version = "0.21.1" +version = "0.21.2" dependencies = [ "blake2", "proc-macro2 0.4.30", @@ -4804,7 +4804,7 @@ dependencies = [ [[package]] name = "tari_key_manager" -version = "0.21.1" +version = "0.21.2" dependencies = [ "argon2", "arrayvec 0.7.1", @@ -4824,7 +4824,7 @@ dependencies = [ [[package]] name = "tari_merge_mining_proxy" -version = "0.21.1" +version = "0.21.2" dependencies = [ "anyhow", "bincode", @@ -4870,7 +4870,7 @@ dependencies = [ [[package]] name = "tari_mining_node" -version = "0.21.1" +version = "0.21.2" dependencies = [ "bufstream", "chrono", @@ -4900,7 +4900,7 @@ dependencies = [ [[package]] name = "tari_mmr" -version = "0.21.1" +version = "0.21.2" dependencies = [ "bincode", "blake2", @@ -4919,7 +4919,7 @@ dependencies = [ [[package]] name = "tari_p2p" -version = "0.21.1" +version = "0.21.2" dependencies = [ "anyhow", "bytes 0.5.6", @@ -4963,7 +4963,7 @@ dependencies = [ [[package]] name = "tari_service_framework" -version = "0.21.1" +version = "0.21.2" dependencies = [ "anyhow", "async-trait", @@ -4980,7 +4980,7 @@ dependencies = [ [[package]] name = "tari_shutdown" -version = "0.21.1" +version = "0.21.2" dependencies = [ "futures 0.3.17", "tokio 1.13.0", @@ -4988,7 +4988,7 @@ dependencies = [ [[package]] name = "tari_storage" -version = "0.21.1" +version = "0.21.2" dependencies = [ "bincode", "bytes 0.5.6", @@ -5006,7 +5006,7 @@ dependencies = [ [[package]] name = "tari_stratum_ffi" -version = "0.21.1" +version = "0.21.2" dependencies = [ "hex", "libc", @@ -5060,7 +5060,7 @@ dependencies = [ [[package]] name = "tari_test_utils" -version = "0.21.1" +version = "0.21.2" dependencies = [ "futures 0.3.17", "futures-test", @@ -5091,7 +5091,7 @@ dependencies = [ [[package]] name = "tari_wallet" -version = "0.21.1" +version = "0.21.2" dependencies = [ "aes-gcm 0.8.0", "argon2", @@ -5137,7 +5137,7 @@ dependencies = [ [[package]] name = "tari_wallet_ffi" -version = "0.21.1" +version = "0.21.2" dependencies = [ "chrono", "env_logger 0.7.1", @@ -5190,7 +5190,7 @@ dependencies = [ [[package]] name = "test_faucet" -version = "0.21.1" +version = "0.21.2" dependencies = [ "rand 0.8.4", "serde 1.0.130", diff --git a/applications/tari_app_grpc/Cargo.toml b/applications/tari_app_grpc/Cargo.toml index f3e7262c06..a712d82f8f 100644 --- a/applications/tari_app_grpc/Cargo.toml +++ b/applications/tari_app_grpc/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "This crate is to provide a single source for all cross application grpc files and conversions to and from tari::core" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/applications/tari_app_utilities/Cargo.toml b/applications/tari_app_utilities/Cargo.toml index 48c8f0484f..a943e35544 100644 --- a/applications/tari_app_utilities/Cargo.toml +++ b/applications/tari_app_utilities/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_app_utilities" -version = "0.21.1" +version = "0.21.2" authors = ["The Tari Development Community"] edition = "2018" diff --git a/applications/tari_base_node/Cargo.toml b/applications/tari_base_node/Cargo.toml index 440ee59ea8..491835884c 100644 --- a/applications/tari_base_node/Cargo.toml +++ b/applications/tari_base_node/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "The tari full base node implementation" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index e7827d6e9d..e336dba15f 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_console_wallet" -version = "0.21.1" +version = "0.21.2" authors = ["The Tari Development Community"] edition = "2018" diff --git a/applications/tari_merge_mining_proxy/Cargo.toml b/applications/tari_merge_mining_proxy/Cargo.toml index fc058caee9..fffe158f96 100644 --- a/applications/tari_merge_mining_proxy/Cargo.toml +++ b/applications/tari_merge_mining_proxy/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "The tari merge miner proxy for xmrig" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [features] diff --git a/applications/tari_mining_node/Cargo.toml b/applications/tari_mining_node/Cargo.toml index 8d3dadd3db..1876371903 100644 --- a/applications/tari_mining_node/Cargo.toml +++ b/applications/tari_mining_node/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "The tari mining node implementation" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/applications/test_faucet/Cargo.toml b/applications/test_faucet/Cargo.toml index 1e5ee98504..0905df40b0 100644 --- a/applications/test_faucet/Cargo.toml +++ b/applications/test_faucet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_faucet" -version = "0.21.1" +version = "0.21.2" authors = ["The Tari Development Community"] edition = "2018" diff --git a/base_layer/common_types/Cargo.toml b/base_layer/common_types/Cargo.toml index a15d105675..d7b1739226 100644 --- a/base_layer/common_types/Cargo.toml +++ b/base_layer/common_types/Cargo.toml @@ -3,7 +3,7 @@ name = "tari_common_types" authors = ["The Tari Development Community"] description = "Tari cryptocurrency common types" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index 3f9b98c03c..c64bb16bf4 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [features] diff --git a/base_layer/key_manager/Cargo.toml b/base_layer/key_manager/Cargo.toml index 18c64eb6d5..d80eeb5038 100644 --- a/base_layer/key_manager/Cargo.toml +++ b/base_layer/key_manager/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "Tari cryptocurrency wallet key management" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/base_layer/mmr/Cargo.toml b/base_layer/mmr/Cargo.toml index 62663d291f..6c5aa8111d 100644 --- a/base_layer/mmr/Cargo.toml +++ b/base_layer/mmr/Cargo.toml @@ -4,7 +4,7 @@ authors = ["The Tari Development Community"] description = "A Merkle Mountain Range implementation" repository = "https://github.com/tari-project/tari" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [features] diff --git a/base_layer/p2p/Cargo.toml b/base_layer/p2p/Cargo.toml index e3a7b6f07d..782551c063 100644 --- a/base_layer/p2p/Cargo.toml +++ b/base_layer/p2p/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_p2p" -version = "0.21.1" +version = "0.21.2" authors = ["The Tari Development community"] description = "Tari base layer-specific peer-to-peer communication features" repository = "https://github.com/tari-project/tari" diff --git a/base_layer/service_framework/Cargo.toml b/base_layer/service_framework/Cargo.toml index bc288cf93e..82461264b1 100644 --- a/base_layer/service_framework/Cargo.toml +++ b/base_layer/service_framework/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_service_framework" -version = "0.21.1" +version = "0.21.2" authors = ["The Tari Development Community"] description = "The Tari communication stack service framework" repository = "https://github.com/tari-project/tari" diff --git a/base_layer/tari_stratum_ffi/Cargo.toml b/base_layer/tari_stratum_ffi/Cargo.toml index 805be3e4b9..87c950085f 100644 --- a/base_layer/tari_stratum_ffi/Cargo.toml +++ b/base_layer/tari_stratum_ffi/Cargo.toml @@ -3,7 +3,7 @@ name = "tari_stratum_ffi" authors = ["The Tari Development Community"] description = "Tari cryptocurrency miningcore C FFI bindings" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index 13c6e527e3..18c7c003be 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -3,7 +3,7 @@ name = "tari_wallet" authors = ["The Tari Development Community"] description = "Tari cryptocurrency wallet library" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/base_layer/wallet_ffi/Cargo.toml b/base_layer/wallet_ffi/Cargo.toml index 49ddf8016e..3980446a7a 100644 --- a/base_layer/wallet_ffi/Cargo.toml +++ b/base_layer/wallet_ffi/Cargo.toml @@ -3,7 +3,7 @@ name = "tari_wallet_ffi" authors = ["The Tari Development Community"] description = "Tari cryptocurrency wallet C FFI bindings" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/changelog.md b/changelog.md index d2f69f8cfd..c8d5710ad2 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,13 @@ # Changelog +### [0.21.2](https://github.com/tari-project/tari/compare/v0.21.1...v0.21.2) (2021-11-19) + +### Features + +* add atomic swap refund transaction handling ([#3573](https://github.com/tari-project/tari/issues/3573)) ([337bc6f](https://github.com/tari-project/tari/commit/337bc6f1b11abc0f53cdc3a82a0aa5110e1fe856)) +* improve wallet connectivity status for console wallet ([#3577](https://github.com/tari-project/tari/issues/3577)) ([e191e27](https://github.com/tari-project/tari/commit/e191e27ed79aff5cfe4d76effe77473a03eb31f6)) + ### [0.21.1](https://github.com/tari-project/tari/compare/v0.21.0...v0.21.1) (2021-11-17) diff --git a/common/Cargo.toml b/common/Cargo.toml index 4ed687e6da..404fd38de2 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [features] diff --git a/comms/Cargo.toml b/comms/Cargo.toml index 091275ca46..6092edf614 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/comms/dht/Cargo.toml b/comms/dht/Cargo.toml index a02696fdbb..b3dec63a37 100644 --- a/comms/dht/Cargo.toml +++ b/comms/dht/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tari_comms_dht" -version = "0.21.1" +version = "0.21.2" authors = ["The Tari Development Community"] description = "Tari comms DHT module" repository = "https://github.com/tari-project/tari" diff --git a/comms/rpc_macros/Cargo.toml b/comms/rpc_macros/Cargo.toml index 52a73902a4..b680d347df 100644 --- a/comms/rpc_macros/Cargo.toml +++ b/comms/rpc_macros/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [lib] diff --git a/infrastructure/derive/Cargo.toml b/infrastructure/derive/Cargo.toml index 6d7208e964..7e87f9b943 100644 --- a/infrastructure/derive/Cargo.toml +++ b/infrastructure/derive/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [lib] diff --git a/infrastructure/shutdown/Cargo.toml b/infrastructure/shutdown/Cargo.toml index 4a32a93b4b..1d8aceee97 100644 --- a/infrastructure/shutdown/Cargo.toml +++ b/infrastructure/shutdown/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/infrastructure/storage/Cargo.toml b/infrastructure/storage/Cargo.toml index 42590b5b0d..238a176433 100644 --- a/infrastructure/storage/Cargo.toml +++ b/infrastructure/storage/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "0.21.1" +version = "0.21.2" edition = "2018" [dependencies] diff --git a/infrastructure/test_utils/Cargo.toml b/infrastructure/test_utils/Cargo.toml index 0fb9b1f8b2..8e043be38a 100644 --- a/infrastructure/test_utils/Cargo.toml +++ b/infrastructure/test_utils/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tari_test_utils" description = "Utility functions used in Tari test functions" -version = "0.21.1" +version = "0.21.2" authors = ["The Tari Development Community"] edition = "2018" license = "BSD-3-Clause" diff --git a/package-lock.json b/package-lock.json index fd3ecdaa80..e7f85a0e63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,4 +1,4 @@ { "lockfileVersion": 1, - "version": "0.21.1" + "version": "0.21.2" } From 889796a45875d72c4a2bc670b96846d22e359fe1 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Fri, 19 Nov 2021 09:30:36 +0200 Subject: [PATCH 35/46] fix: allow bullet proof value only rewinding in atomic swaps (#3586) Description --- This PR allows us to do bulletproof value-only rewinding on HTLC atomic swap utxo. Motivation and Context --- Currently it is not possible to do bulletproof rewinding on the value only on an HTLC atomic swap utxo due to the way the commitment blinding factor and bulletproof rewinding keys are created. Currently, the two bulletproof rewinding keys are created as: ``` let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&commitment_blinding_factor))?; let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; ``` This means that if you share the rewind key, which is used to do value only rewinding, that a person can calculate the blinding key which is used to do full rewinding and expose the commitment blinding factor. by changing the calculation order we prevent this and only allow full rewinding by something who needs to be able to do this. ``` let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&blinding_key ))?; let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&commitment_blinding_factor))?; ``` How Has This Been Tested? --- All current test pass --- base_layer/wallet/src/output_manager_service/service.rs | 4 ++-- base_layer/wallet/src/transaction_service/service.rs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 2572402e69..fe2181bdf6 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -1230,8 +1230,8 @@ where ) .as_bytes(), )?; - let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spending_key))?; - let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; + let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&spending_key))?; + let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&blinding_key))?; let rewound = output.full_rewind_range_proof(&self.resources.factories.range_proof, &rewind_key, &blinding_key)?; diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 44d162a0fb..03d0dc5f1e 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -859,8 +859,9 @@ where .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; let sender_message = TransactionSenderMessage::new_single_round_message(stp.get_single_round_message()?); - let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spend_key))?; - let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; + let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&spend_key))?; + let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&blinding_key))?; + let rewind_data = RewindData { rewind_key: rewind_key.clone(), rewind_blinding_key: blinding_key.clone(), From 8e271d769b7fc540bd78e326f0e0e8155e9de88f Mon Sep 17 00:00:00 2001 From: Philip Robinson Date: Fri, 19 Nov 2021 10:07:14 +0200 Subject: [PATCH 36/46] fix: update daily test start times and seed phrase (#3584) Description --- - This PR updates the daily test star times to be from 1am so that the tests are done before standup. - Updated the recovery seed phrase to a wallet with 1234567890uT for each spotting when recovery misses some outputs - Updates the pruning horizon to 20. How Has This Been Tested? --- YOLO --- .../daily_tests/automatic_recovery_test.js | 14 +++++++------- applications/daily_tests/automatic_sync_test.js | 2 +- applications/daily_tests/cron_jobs.js | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/applications/daily_tests/automatic_recovery_test.js b/applications/daily_tests/automatic_recovery_test.js index e9a11a6db2..f18a8025a8 100644 --- a/applications/daily_tests/automatic_recovery_test.js +++ b/applications/daily_tests/automatic_recovery_test.js @@ -16,7 +16,7 @@ async function main() { description: "Seed words to use during recovery", type: "string", default: - "pigeon marble letter canal hard close kit cash coin still melt random require long shaft antenna tent turkey neck divert enrich iron analyst abandon", + "cactus pool fuel skull chair casino season disorder flat crash wrist whisper decorate narrow oxygen remember minor among happy cricket embark blue ship sick", }) .option("log", { alias: "l", @@ -34,19 +34,19 @@ async function main() { .alias("help", "h").argv; for (let i = 0; i < argv.numWallets; i++) { - let { identity, timeDiffMinutes, height, blockRate, recoveredAmount } = + let { identity, timeDiffMinutes, numOutputs, rate, recoveredAmount } = await run(argv); console.log( "Wallet (Pubkey:", identity.public_key, - ") recovered to a block height of", - height, - "completed in", + ") scanned", + numOutputs, + "outputs, completed in", timeDiffMinutes, "minutes (", - blockRate, - "blocks/min).", + rate, + "outputs/min).", recoveredAmount, "µT recovered for instance ", i, diff --git a/applications/daily_tests/automatic_sync_test.js b/applications/daily_tests/automatic_sync_test.js index a8dc49052a..ec5e130ca5 100644 --- a/applications/daily_tests/automatic_sync_test.js +++ b/applications/daily_tests/automatic_sync_test.js @@ -61,7 +61,7 @@ async function run(options) { "true"; // Set pruning horizon in config file if `pruned` command line arg is present if (options.syncType === SyncType.Pruned) { - process.env[`TARI_BASE_NODE__${NETWORK}__PRUNING_HORIZON`] = 1000; + process.env[`TARI_BASE_NODE__${NETWORK}__PRUNING_HORIZON`] = 20; } if (options.forceSyncPeer) { diff --git a/applications/daily_tests/cron_jobs.js b/applications/daily_tests/cron_jobs.js index daed608a6b..a8038b9123 100644 --- a/applications/daily_tests/cron_jobs.js +++ b/applications/daily_tests/cron_jobs.js @@ -53,7 +53,7 @@ async function runWalletRecoveryTest(instances) { recoveredAmount, } = await walletRecoveryTest({ seedWords: - "abandon rely pave boil case broken volume bracket own false sketch ordinary gown bitter strong unhappy shoulder salad season student public will monkey inquiry", + "cactus pool fuel skull chair casino season disorder flat crash wrist whisper decorate narrow oxygen remember minor among happy cricket embark blue ship sick", log: LOG_FILE, numWallets: instances, baseDir, @@ -126,13 +126,13 @@ async function main() { }); // ------------------------- CRON ------------------------- // - new CronJob("0 7 * * *", () => runWalletRecoveryTest(1)).start(); + new CronJob("0 2 * * *", () => runWalletRecoveryTest(1)).start(); //new CronJob("30 7 * * *", () => runWalletRecoveryTest(5)).start(); - new CronJob("0 6 * * *", () => + new CronJob("0 1 * * *", () => runBaseNodeSyncTest(SyncType.Archival) ).start(); - new CronJob("30 6 * * *", () => runBaseNodeSyncTest(SyncType.Pruned)).start(); - new CronJob("0 4 * * *", () => + new CronJob("30 1 * * *", () => runBaseNodeSyncTest(SyncType.Pruned)).start(); + new CronJob("0 0 * * *", () => git.pull(__dirname).catch((err) => { failed("Failed to update git repo"); console.error(err); From 476c512127cb9bd2ff6d3d1294d2a3d9d1628767 Mon Sep 17 00:00:00 2001 From: David Main <51991544+StriderDM@users.noreply.github.com> Date: Fri, 19 Nov 2021 11:12:32 +0200 Subject: [PATCH 37/46] refactor: clean up unwraps in wallet_ffi (#3585) Description --- Remove unwrap() in functions of wallet_ffi, code is considered best-effort to try to prevent a panic since the nature of handling invalid pointers or invalidly cast pointers passed in results in undefined behavior. The function signature of log_debug_message() was modified to take into account an error occurring. PR is a breaking change due to this. Fixed potential panic when unwrapping parse() for MultiAddr. Fixed missing documentation for init_logging(). Motivation and Context --- Removal of calls to unwrap() and bug fixes. How Has This Been Tested? --- cargo test --all --- base_layer/wallet_ffi/src/error.rs | 6 + base_layer/wallet_ffi/src/lib.rs | 574 ++++++++++++++++++++++++----- base_layer/wallet_ffi/wallet.h | 2 +- 3 files changed, 482 insertions(+), 100 deletions(-) diff --git a/base_layer/wallet_ffi/src/error.rs b/base_layer/wallet_ffi/src/error.rs index e842530d11..6b17d8f552 100644 --- a/base_layer/wallet_ffi/src/error.rs +++ b/base_layer/wallet_ffi/src/error.rs @@ -41,6 +41,8 @@ const LOG_TARGET: &str = "wallet_ffi::error"; pub enum InterfaceError { #[error("An error has occurred due to one of the parameters being null: `{0}`")] NullError(String), + #[error("An invalid pointer was passed into the function")] + PointerError(String), #[error("An error has occurred when checking the length of the allocated object")] AllocationError, #[error("An error because the supplied position was out of range")] @@ -101,6 +103,10 @@ impl From for LibWalletError { code: 8, message: "Balance Unavailable".to_string(), }, + InterfaceError::PointerError(ref p) => Self { + code: 9, + message: format!("Pointer error on {}:{:?}", p, v), + }, } } } diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 6bd2cd6136..e24dec8445 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -283,7 +283,7 @@ pub unsafe extern "C" fn transaction_kernel_get_excess_hex( error_out: *mut c_int, ) -> *mut c_char { let mut error = 0; - let mut result = CString::new("").unwrap(); + let mut result = CString::new("").expect("Blank CString will not fail."); ptr::swap(error_out, &mut error as *mut c_int); if kernel.is_null() { error = LibWalletError::from(InterfaceError::NullError("kernel".to_string())).code; @@ -291,7 +291,14 @@ pub unsafe extern "C" fn transaction_kernel_get_excess_hex( return CString::into_raw(result); } let excess = (*kernel).excess.clone().to_hex(); - result = CString::new(excess).unwrap(); + match CString::new(excess) { + Ok(v) => result = v, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("kernel".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + } + result.into_raw() } @@ -312,7 +319,7 @@ pub unsafe extern "C" fn transaction_kernel_get_excess_public_nonce_hex( error_out: *mut c_int, ) -> *mut c_char { let mut error = 0; - let mut result = CString::new("").unwrap(); + let mut result = CString::new("").expect("Blank CString will not fail."); ptr::swap(error_out, &mut error as *mut c_int); if kernel.is_null() { error = LibWalletError::from(InterfaceError::NullError("kernel".to_string())).code; @@ -320,7 +327,15 @@ pub unsafe extern "C" fn transaction_kernel_get_excess_public_nonce_hex( return CString::into_raw(result); } let nonce = (*kernel).excess_sig.get_public_nonce().to_hex(); - result = CString::new(nonce).unwrap(); + + match CString::new(nonce) { + Ok(v) => result = v, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("kernel".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + } + result.into_raw() } @@ -341,7 +356,7 @@ pub unsafe extern "C" fn transaction_kernel_get_excess_signature_hex( error_out: *mut c_int, ) -> *mut c_char { let mut error = 0; - let mut result = CString::new("").unwrap(); + let mut result = CString::new("").expect("Blank CString will not fail."); ptr::swap(error_out, &mut error as *mut c_int); if kernel.is_null() { error = LibWalletError::from(InterfaceError::NullError("kernel".to_string())).code; @@ -349,7 +364,7 @@ pub unsafe extern "C" fn transaction_kernel_get_excess_signature_hex( return CString::into_raw(result); } let signature = (*kernel).excess_sig.get_signature().to_hex(); - result = CString::new(signature).unwrap(); + result = CString::new(signature).expect("Hex string will not fail"); result.into_raw() } @@ -623,7 +638,16 @@ pub unsafe extern "C" fn public_key_from_hex(key: *const c_char, error_out: *mut ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } else { - key_str = CStr::from_ptr(key).to_str().unwrap().to_owned(); + match CStr::from_ptr(key).to_str() { + Ok(v) => { + key_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("key".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } } let public_key = TariPublicKey::from_hex(key_str.as_str()); @@ -654,7 +678,7 @@ pub unsafe extern "C" fn public_key_from_hex(key: *const c_char, error_out: *mut #[no_mangle] pub unsafe extern "C" fn public_key_to_emoji_id(pk: *mut TariPublicKey, error_out: *mut c_int) -> *mut c_char { let mut error = 0; - let mut result = CString::new("").unwrap(); + let mut result = CString::new("").expect("Blank CString will not fail."); ptr::swap(error_out, &mut error as *mut c_int); if pk.is_null() { error = LibWalletError::from(InterfaceError::NullError("key".to_string())).code; @@ -663,7 +687,7 @@ pub unsafe extern "C" fn public_key_to_emoji_id(pk: *mut TariPublicKey, error_ou } let emoji = EmojiId::from_pubkey(&(*pk)); - result = CString::new(emoji.as_str()).unwrap(); + result = CString::new(emoji.as_str()).expect("Emoji will not fail."); CString::into_raw(result) } @@ -827,7 +851,16 @@ pub unsafe extern "C" fn private_key_from_hex(key: *const c_char, error_out: *mu ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } else { - key_str = CStr::from_ptr(key).to_str().unwrap().to_owned(); + match CStr::from_ptr(key).to_str() { + Ok(v) => { + key_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("key".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; } let secret_key = TariPrivateKey::from_hex(key_str.as_str()); @@ -982,17 +1015,25 @@ pub unsafe extern "C" fn seed_words_get_at( ) -> *mut c_char { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); - let mut word = CString::new("").unwrap(); + let mut word = CString::new("").expect("Blank CString will not fail."); if seed_words.is_null() { error = LibWalletError::from(InterfaceError::NullError("seed words".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); } else { - let len = (*seed_words).0.len(); - if position >= len as u32 { + let len = (*seed_words).0.len() - 1; // clamp to length + if position > len as u32 { error = LibWalletError::from(InterfaceError::PositionInvalidError).code; ptr::swap(error_out, &mut error as *mut c_int); } else { - word = CString::new((*seed_words).0[position as usize].clone()).unwrap() + match CString::new((*seed_words).0[position as usize].clone()) { + Ok(v) => { + word = v; + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("seed_words".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + } } } CString::into_raw(word) @@ -1036,11 +1077,20 @@ pub unsafe extern "C" fn seed_words_push_word( ptr::swap(error_out, &mut error as *mut c_int); return SeedWordPushResult::InvalidSeedWord as u8; } else { - word_string = CStr::from_ptr(word).to_str().unwrap().to_owned(); + match CStr::from_ptr(word).to_str() { + Ok(v) => { + word_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("word".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return SeedWordPushResult::InvalidObject as u8; + }, + } } // Check word is from a word list - match MnemonicLanguage::from(word_string.as_str()) { + match MnemonicLanguage::from(&word_string) { Ok(language) => { if (*seed_words).0.len() >= MnemonicLanguage::word_count(&language) { let error_msg = "Invalid seed words object, i.e. the entire mnemonic word list, is being used"; @@ -1071,7 +1121,7 @@ pub unsafe extern "C" fn seed_words_push_word( return if let Err(e) = CipherSeed::from_mnemonic(&(*seed_words).0, None) { log::error!( target: LOG_TARGET, - "Problem building valid private seed from seed phrase: {}", + "Problem building valid private seed from seed phrase: {:?}", e ); error = LibWalletError::from(WalletError::KeyManagerError(e)).code; @@ -1132,7 +1182,16 @@ pub unsafe extern "C" fn contact_create( ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } else { - alias_string = CStr::from_ptr(alias).to_str().unwrap().to_owned(); + match CStr::from_ptr(alias).to_str() { + Ok(v) => { + alias_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("alias".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } } if public_key.is_null() { @@ -1165,12 +1224,18 @@ pub unsafe extern "C" fn contact_create( pub unsafe extern "C" fn contact_get_alias(contact: *mut TariContact, error_out: *mut c_int) -> *mut c_char { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); - let mut a = CString::new("").unwrap(); + let mut a = CString::new("").expect("Blank CString will not fail."); if contact.is_null() { error = LibWalletError::from(InterfaceError::NullError("contact".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); } else { - a = CString::new((*contact).alias.clone()).unwrap(); + match CString::new((*contact).alias.clone()) { + Ok(v) => a = v, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("contact".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + } } CString::into_raw(a) } @@ -1849,14 +1914,21 @@ pub unsafe extern "C" fn completed_transaction_get_message( let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); let message = (*transaction).message.clone(); - let mut result = CString::new("").unwrap(); + let mut result = CString::new("").expect("Blank CString will not fail."); if transaction.is_null() { error = LibWalletError::from(InterfaceError::NullError("transaction".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); return result.into_raw(); } - result = CString::new(message).unwrap(); + match CString::new(message) { + Ok(v) => result = v, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + } + result.into_raw() } @@ -2131,14 +2203,21 @@ pub unsafe extern "C" fn pending_outbound_transaction_get_message( let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); let message = (*transaction).message.clone(); - let mut result = CString::new("").unwrap(); + let mut result = CString::new("").expect("Blank CString will not fail."); if transaction.is_null() { error = LibWalletError::from(InterfaceError::NullError("transaction".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); return result.into_raw(); } - result = CString::new(message).unwrap(); + match CString::new(message) { + Ok(v) => result = v, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + } + result.into_raw() } @@ -2331,14 +2410,21 @@ pub unsafe extern "C" fn pending_inbound_transaction_get_message( let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); let message = (*transaction).message.clone(); - let mut result = CString::new("").unwrap(); + let mut result = CString::new("").expect("Blank CString will not fail."); if transaction.is_null() { error = LibWalletError::from(InterfaceError::NullError("transaction".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); return result.into_raw(); } - result = CString::new(message).unwrap(); + match CString::new(message) { + Ok(v) => result = v, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + } + result.into_raw() } @@ -2443,17 +2529,36 @@ pub unsafe extern "C" fn transport_tcp_create( let listener_address_str; if !listener_address.is_null() { - listener_address_str = CStr::from_ptr(listener_address).to_str().unwrap().to_owned(); + match CStr::from_ptr(listener_address).to_str() { + Ok(v) => { + listener_address_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("listener_address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("listener_address".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } - let transport = TariTransportType::Tcp { - listener_address: listener_address_str.parse::().unwrap(), - tor_socks_config: None, - }; - Box::into_raw(Box::new(transport)) + + match listener_address_str.parse::() { + Ok(v) => { + let transport = TariTransportType::Tcp { + listener_address: v, + tor_socks_config: None, + }; + Box::into_raw(Box::new(transport)) + }, + Err(_) => { + error = LibWalletError::from(InterfaceError::InvalidArgument("listener_address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } } /// Creates a tor transport type @@ -2487,7 +2592,16 @@ pub unsafe extern "C" fn transport_tor_create( let control_address_str; if !control_server_address.is_null() { - control_address_str = CStr::from_ptr(control_server_address).to_str().unwrap().to_owned(); + match CStr::from_ptr(control_server_address).to_str() { + Ok(v) => { + control_address_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("control_server_address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("control_server_address".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -2497,8 +2611,26 @@ pub unsafe extern "C" fn transport_tor_create( let username_str; let password_str; let authentication = if !socks_username.is_null() && !socks_password.is_null() { - username_str = CStr::from_ptr(socks_username).to_str().unwrap().to_owned(); - password_str = CStr::from_ptr(socks_password).to_str().unwrap().to_owned(); + match CStr::from_ptr(socks_username).to_str() { + Ok(v) => { + username_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("socks_username".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } + match CStr::from_ptr(socks_password).to_str() { + Ok(v) => { + password_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("socks_password".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; socks::Authentication::Password(username_str, password_str) } else { socks::Authentication::None @@ -2513,21 +2645,30 @@ pub unsafe extern "C" fn transport_tor_create( let identity = None; - let tor_config = TorConfig { - control_server_addr: control_address_str.parse::().unwrap(), - control_server_auth: tor_authentication, - identity, - // Proxy the onion address to an OS-assigned local port - port_mapping: tor::PortMapping::new(tor_port, "127.0.0.1:0".parse().unwrap()), - socks_address_override: None, - socks_auth: authentication, - tor_proxy_bypass_addresses: vec![], - // Prefer performance - tor_proxy_bypass_for_outbound_tcp: true, - }; - let transport = TariTransportType::Tor(tor_config); + match control_address_str.parse::() { + Ok(v) => { + let tor_config = TorConfig { + control_server_addr: v, + control_server_auth: tor_authentication, + identity, + // Proxy the onion address to an OS-assigned local port + port_mapping: tor::PortMapping::new(tor_port, "127.0.0.1:0".parse().expect("Will not fail parsing")), + socks_address_override: None, + socks_auth: authentication, + tor_proxy_bypass_addresses: vec![], + // Prefer performance + tor_proxy_bypass_for_outbound_tcp: true, + }; + let transport = TariTransportType::Tor(tor_config); - Box::into_raw(Box::new(transport)) + Box::into_raw(Box::new(transport)) + }, + Err(_) => { + error = LibWalletError::from(InterfaceError::InvalidArgument("control_address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } } /// Gets the address for a memory transport type @@ -2549,11 +2690,15 @@ pub unsafe extern "C" fn transport_memory_get_address( ) -> *mut c_char { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); - let mut address = CString::new("").unwrap(); + let mut address = CString::new("").expect("Blank CString will not fail."); if !transport.is_null() { match &*transport { - TransportType::Memory { listener_address } => { - address = CString::new(listener_address.to_string()).unwrap(); + TransportType::Memory { listener_address } => match CString::new(listener_address.to_string()) { + Ok(v) => address = v, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("transport".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, }, _ => { error = LibWalletError::from(InterfaceError::NullError("transport".to_string())).code; @@ -2626,7 +2771,16 @@ pub unsafe extern "C" fn comms_config_create( ptr::swap(error_out, &mut error as *mut c_int); let public_address_str; if !public_address.is_null() { - public_address_str = CStr::from_ptr(public_address).to_str().unwrap().to_owned(); + match CStr::from_ptr(public_address).to_str() { + Ok(v) => { + public_address_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("public_address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("public_address".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -2635,7 +2789,16 @@ pub unsafe extern "C" fn comms_config_create( let database_name_string; if !database_name.is_null() { - database_name_string = CStr::from_ptr(database_name).to_str().unwrap().to_owned(); + match CStr::from_ptr(database_name).to_str() { + Ok(v) => { + database_name_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("database_name".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("database_name".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -2644,7 +2807,16 @@ pub unsafe extern "C" fn comms_config_create( let datastore_path_string; if !datastore_path.is_null() { - datastore_path_string = CStr::from_ptr(datastore_path).to_str().unwrap().to_owned(); + match CStr::from_ptr(datastore_path).to_str() { + Ok(v) => { + datastore_path_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("datastore_path".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("datastore_path".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -2664,7 +2836,16 @@ pub unsafe extern "C" fn comms_config_create( let network_str; if !network.is_null() { - network_str = CStr::from_ptr(network).to_str().unwrap().to_owned(); + match CStr::from_ptr(network).to_str() { + Ok(v) => { + network_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("network".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("network".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -2708,7 +2889,9 @@ pub unsafe extern "C" fn comms_config_create( listener_liveness_allowlist_cidrs: Vec::new(), listener_liveness_max_sessions: 0, user_agent: format!("tari/wallet/{}", env!("CARGO_PKG_VERSION")), - dns_seeds_name_server: DEFAULT_DNS_NAME_SERVER.parse().unwrap(), + dns_seeds_name_server: DEFAULT_DNS_NAME_SERVER + .parse() + .expect("Default dns name server constant should always be correct"), peer_seeds: Default::default(), dns_seeds: Default::default(), dns_seeds_use_dnssec: true, @@ -2752,8 +2935,40 @@ pub unsafe extern "C" fn comms_config_destroy(wc: *mut TariCommsConfig) { /// ------------------------------------- Wallet -------------------------------------------------/// -unsafe fn init_logging(log_path: *const c_char, num_rolling_log_files: c_uint, size_per_log_file_bytes: c_uint) { - let path = CStr::from_ptr(log_path).to_str().unwrap().to_owned(); +/// Inits logging, this function is deliberately not exposed externally in the header +/// +/// ## Arguments +/// `log_path` - Path to where the log will be stored +/// `num_rolling_log_files` - Number of rolling files to be used. +/// `size_per_log_file_bytes` - Max byte size of log file +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +unsafe fn init_logging( + log_path: *const c_char, + num_rolling_log_files: c_uint, + size_per_log_file_bytes: c_uint, + error_out: *mut c_int, +) { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + let path; + match CStr::from_ptr(log_path).to_str() { + Ok(v) => { + path = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("log_path".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + } let encoder = PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] {l:5} {m}{n}"); let log_appender: Box = if num_rolling_log_files != 0 && size_per_log_file_bytes != 0 { let mut pattern; @@ -2771,7 +2986,7 @@ unsafe fn init_logging(log_path: *const c_char, num_rolling_log_files: c_uint, s } let roller = FixedWindowRoller::builder() .build(pattern.as_str(), num_rolling_log_files) - .unwrap(); + .expect("Should be able to create a Roller"); let size_trigger = SizeTrigger::new(size_per_log_file_bytes as u64); let policy = CompoundPolicy::new(Box::new(size_trigger), Box::new(roller)); @@ -2780,7 +2995,7 @@ unsafe fn init_logging(log_path: *const c_char, num_rolling_log_files: c_uint, s .encoder(Box::new(encoder)) .append(true) .build(path.as_str(), Box::new(policy)) - .unwrap(), + .expect("Should be able to create an appender"), ) } else { Box::new( @@ -2795,7 +3010,7 @@ unsafe fn init_logging(log_path: *const c_char, num_rolling_log_files: c_uint, s let lconfig = Config::builder() .appender(Appender::builder().build("logfile", log_appender)) .build(Root::builder().appender("logfile").build(LevelFilter::Debug)) - .unwrap(); + .expect("Should be able to create a Config"); match log4rs::init_config(lconfig) { Ok(_) => debug!(target: LOG_TARGET, "Logging started"), @@ -2889,7 +3104,11 @@ pub unsafe extern "C" fn wallet_create( } if !log_path.is_null() { - init_logging(log_path, num_rolling_log_files, size_per_log_file_bytes); + init_logging(log_path, num_rolling_log_files, size_per_log_file_bytes, error_out); + + if error > 0 { + return ptr::null_mut(); + } } let passphrase_option = if !passphrase.is_null() { @@ -3104,7 +3323,7 @@ pub unsafe extern "C" fn wallet_sign_message( error_out: *mut c_int, ) -> *mut c_char { let mut error = 0; - let mut result = CString::new("").unwrap(); + let mut result = CString::new("").expect("Blank CString will not fail."); ptr::swap(error_out, &mut error as *mut c_int); if wallet.is_null() { @@ -3121,7 +3340,11 @@ pub unsafe extern "C" fn wallet_sign_message( let nonce = TariPrivateKey::random(&mut OsRng); let secret = (*wallet).wallet.comms.node_identity().secret_key().clone(); - let message = CStr::from_ptr(msg).to_str().unwrap().to_owned(); + let message = CStr::from_ptr(msg) + .to_str() + .expect("CString should not fail here.") + .to_owned(); + let signature = (*wallet).wallet.sign_message(secret, nonce, &message); match signature { @@ -3129,7 +3352,7 @@ pub unsafe extern "C" fn wallet_sign_message( let hex_sig = s.get_signature().to_hex(); let hex_nonce = s.get_public_nonce().to_hex(); let hex_return = format!("{}|{}", hex_sig, hex_nonce); - result = CString::new(hex_return).unwrap(); + result = CString::new(hex_return).expect("CString should not fail here."); }, Err(e) => { error = LibWalletError::from(e).code; @@ -3187,34 +3410,65 @@ pub unsafe extern "C" fn wallet_verify_message_signature( return result; } - let message = CStr::from_ptr(msg).to_str().unwrap().to_owned(); - let hex = CStr::from_ptr(hex_sig_nonce).to_str().unwrap().to_owned(); + let message; + match CStr::from_ptr(msg).to_str() { + Ok(v) => { + message = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("msg".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + } + let hex; + match CStr::from_ptr(hex_sig_nonce).to_str() { + Ok(v) => { + hex = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("hex_sig_nonce".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + } let hex_keys: Vec<&str> = hex.split('|').collect(); if hex_keys.len() != 2 { error = LibWalletError::from(InterfaceError::PositionInvalidError).code; ptr::swap(error_out, &mut error as *mut c_int); return result; } - let secret = TariPrivateKey::from_hex(hex_keys.get(0).unwrap()); - match secret { - Ok(p) => { - let public_nonce = TariPublicKey::from_hex(hex_keys.get(1).unwrap()); - match public_nonce { - Ok(pn) => { - result = (*wallet) - .wallet - .verify_message_signature((*public_key).clone(), pn, p, message) + + if let Some(key1) = hex_keys.get(0) { + if let Some(key2) = hex_keys.get(1) { + let secret = TariPrivateKey::from_hex(key1); + match secret { + Ok(p) => { + let public_nonce = TariPublicKey::from_hex(key2); + match public_nonce { + Ok(pn) => { + result = (*wallet) + .wallet + .verify_message_signature((*public_key).clone(), pn, p, message) + }, + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + }, + } }, Err(e) => { error = LibWalletError::from(e).code; ptr::swap(error_out, &mut error as *mut c_int); }, } - }, - Err(e) => { - error = LibWalletError::from(e).code; + } else { + error = LibWalletError::from(InterfaceError::InvalidArgument("hex_sig_nonce".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); - }, + } + } else { + error = LibWalletError::from(InterfaceError::InvalidArgument("hex_sig_nonce".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); } result @@ -3257,7 +3511,16 @@ pub unsafe extern "C" fn wallet_add_base_node_peer( let address_string; if !address.is_null() { - address_string = CStr::from_ptr(address).to_str().unwrap().to_owned(); + match CStr::from_ptr(address).to_str() { + Ok(v) => { + address_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("address".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -3530,12 +3793,30 @@ pub unsafe extern "C" fn wallet_send_transaction( return 0; } - let message_string = if !message.is_null() { - CStr::from_ptr(message).to_str().unwrap().to_owned() + let message_string; + if !message.is_null() { + match CStr::from_ptr(message).to_str() { + Ok(v) => { + message_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::NullError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + message_string = CString::new("") + .expect("Blank CString will not fail") + .to_str() + .expect("CString.to_str() will not fail") + .to_owned(); + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("message".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); - CString::new("").unwrap().to_str().unwrap().to_owned() + message_string = CString::new("") + .expect("Blank CString will not fail") + .to_str() + .expect("CString.to_str() will not fail") + .to_owned(); }; match (*wallet) @@ -4372,12 +4653,30 @@ pub unsafe extern "C" fn wallet_import_utxo( return 0; } - let message_string = if !message.is_null() { - CStr::from_ptr(message).to_str().unwrap().to_owned() + let message_string; + if !message.is_null() { + match CStr::from_ptr(message).to_str() { + Ok(v) => { + message_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + message_string = CString::new("Imported UTXO") + .expect("CString will not fail") + .to_str() + .expect("CString.to_str() will not fail") + .to_owned(); + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("message".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); - CString::new("Imported UTXO").unwrap().to_str().unwrap().to_owned() + message_string = CString::new("Imported UTXO") + .expect("CString will not fail") + .to_str() + .expect("CString.toStr() will not fail") + .to_owned(); }; let public_script_key = PublicKey::from_secret_key(&(*spending_key)); @@ -4641,10 +4940,19 @@ pub unsafe extern "C" fn wallet_coin_split( ptr::swap(error_out, &mut error as *mut c_int); } - let message = if !msg.is_null() { - CStr::from_ptr(msg).to_str().unwrap().to_owned() + let message; + + if !msg.is_null() { + match CStr::from_ptr(msg).to_str() { + Ok(v) => { + message = v.to_owned(); + }, + _ => { + message = "Coin Split".to_string(); + }, + } } else { - "Coin Split".to_string() + message = "Coin Split".to_string() }; match (*wallet).runtime.block_on((*wallet).wallet.coin_split( @@ -4858,7 +5166,16 @@ pub unsafe extern "C" fn wallet_set_key_value( ptr::swap(error_out, &mut error as *mut c_int); return false; } else { - key_string = CStr::from_ptr(key).to_str().unwrap().to_owned(); + match CStr::from_ptr(key).to_str() { + Ok(v) => { + key_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("key".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + } } let value_string; @@ -4867,7 +5184,16 @@ pub unsafe extern "C" fn wallet_set_key_value( ptr::swap(error_out, &mut error as *mut c_int); return false; } else { - value_string = CStr::from_ptr(value).to_str().unwrap().to_owned(); + match CStr::from_ptr(value).to_str() { + Ok(v) => { + value_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("value".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + } } match (*wallet) @@ -4918,7 +5244,16 @@ pub unsafe extern "C" fn wallet_get_value( ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } else { - key_string = CStr::from_ptr(key).to_str().unwrap().to_owned(); + match CStr::from_ptr(key).to_str() { + Ok(v) => { + key_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("key".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } } match (*wallet) @@ -4979,7 +5314,16 @@ pub unsafe extern "C" fn wallet_clear_value( ptr::swap(error_out, &mut error as *mut c_int); return false; } else { - key_string = CStr::from_ptr(key).to_str().unwrap().to_owned(); + match CStr::from_ptr(key).to_str() { + Ok(v) => { + key_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("key".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + } } match (*wallet) @@ -5142,7 +5486,16 @@ pub unsafe extern "C" fn file_partial_backup( let original_path_string; if !original_file_path.is_null() { - original_path_string = CStr::from_ptr(original_file_path).to_str().unwrap().to_owned(); + match CStr::from_ptr(original_file_path).to_str() { + Ok(v) => { + original_path_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("original_file_path".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("original_file_path".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -5152,7 +5505,16 @@ pub unsafe extern "C" fn file_partial_backup( let backup_path_string; if !backup_file_path.is_null() { - backup_path_string = CStr::from_ptr(backup_file_path).to_str().unwrap().to_owned(); + match CStr::from_ptr(backup_file_path).to_str() { + Ok(v) => { + backup_path_string = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("backup_file_path".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + } } else { error = LibWalletError::from(InterfaceError::NullError("backup_file_path".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -5299,13 +5661,27 @@ pub unsafe extern "C" fn wallet_destroy(wallet: *mut TariWallet) { /// /// ## Arguments /// `msg` - A string that will be logged at the debug level. If msg is null nothing will be done. +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. /// /// # Safety /// None #[no_mangle] -pub unsafe extern "C" fn log_debug_message(msg: *const c_char) { +pub unsafe extern "C" fn log_debug_message(msg: *const c_char, error_out: *mut c_int) { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let message; if !msg.is_null() { - let message = CStr::from_ptr(msg).to_str().unwrap().to_owned(); + match CStr::from_ptr(msg).to_str() { + Ok(v) => { + message = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("msg".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + } debug!(target: LOG_TARGET, "{}", message); } } diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 7b0ee7ca16..c4be5b890c 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -716,7 +716,7 @@ void balance_destroy(struct TariBalance *balance); void file_partial_backup(const char *original_file_path, const char *backup_file_path, int *error_out); /// This function will log the provided string at debug level. To be used to have a client log messages to the LibWallet -void log_debug_message(const char *msg); +void log_debug_message(const char *msg, int *error_out); struct EmojiSet *get_emoji_set(); From f6d29951a77b9d6ed869ab5dac52d477ce8a87cb Mon Sep 17 00:00:00 2001 From: David Main <51991544+StriderDM@users.noreply.github.com> Date: Fri, 19 Nov 2021 12:18:53 +0200 Subject: [PATCH 38/46] refactor: update miningcore repository links (#3593) Description --- Updates link to miningcore repositories. Requires this PR to be merged first: https://github.com/tari-project/miningcore/pull/1 Motivation and Context --- Documentation How Has This Been Tested? --- N/A --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f8a5b0194..b34b179d2a 100644 --- a/README.md +++ b/README.md @@ -422,7 +422,7 @@ The Tari Base Node, Tari Console Wallet, Tari Stratum Transcoder and Tari Mining default installation as described in [Installing using binaries](#installing-using-binaries), all these applications will be available. -For MiningCore see [here](https://github.com/StriderDM/miningcore/tree/tari#runtime-requirements-on-linux) and [here](https://github.com/StriderDM/miningcore/tree/tari#runtime-requirements-on-windows). +For MiningCore see [here](https://github.com/tari-project/miningcore/master/tari#runtime-requirements-on-linux) and [here](https://github.com/tari-project/miningcore/tree/master#runtime-requirements-on-windows). #### Configuration prerequisites @@ -453,7 +453,7 @@ transcoder_host_address = "127.0.0.1:7879" For MiningCore: -See example configuration [here](https://github.com/StriderDM/miningcore/blob/tari/examples/tari_pool.json). +See example configuration [here](https://github.com/tari-project/miningcore/blob/master/examples/tari_pool.json). For the Tari Mining Node there are some additional settings under section **`mining_node`** that can be changed: * For SHA3 Mining: From f32a38f409bb342e0ab507af5336abe60eaca2a8 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Fri, 19 Nov 2021 13:32:28 +0200 Subject: [PATCH 39/46] fix: allow bullet proof value only rewinding off one-sided transaction (#3587) Description --- This PR allows us to do bulletproof value-only rewinding on one-sided transactions. Motivation and Context --- Currently, it is not possible to do bulletproof rewinding on the value only on a one-sided transaction utxo due to the way the commitment blinding factor and bulletproof rewinding keys are created. Currently, the two bulletproof rewinding keys are created as: ``` let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&commitment_blinding_factor))?; let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; ``` This means that if you share the rewind key, which is used to do value only rewinding, that a person can calculate the blinding key which is used to do full rewinding and expose the commitment blinding factor. by changing the calculation order we prevent this and only allow full rewinding by something who needs to be able to do this. ``` let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&blinding_key ))?; let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&commitment_blinding_factor))?; ``` How Has This Been Tested? --- All current test pass --- base_layer/wallet/src/output_manager_service/service.rs | 4 ++-- base_layer/wallet/src/transaction_service/service.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index fe2181bdf6..1949e61006 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -1422,8 +1422,8 @@ where ) .as_bytes(), )?; - let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spending_key))?; - let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; + let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&spending_key))?; + let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&blinding_key))?; let rewound = output.full_rewind_range_proof(&self.resources.factories.range_proof, &rewind_key, &blinding_key); diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 03d0dc5f1e..ef43ad849b 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -1014,8 +1014,8 @@ where .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; let sender_message = TransactionSenderMessage::new_single_round_message(stp.get_single_round_message()?); - let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&spend_key))?; - let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&rewind_key))?; + let blinding_key = PrivateKey::from_bytes(&hash_secret_key(&spend_key))?; + let rewind_key = PrivateKey::from_bytes(&hash_secret_key(&blinding_key))?; let rewind_data = RewindData { rewind_key: rewind_key.clone(), rewind_blinding_key: blinding_key.clone(), From 2ba437b4a7eed2022a7555bb72a8838eb2e608a2 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Fri, 19 Nov 2021 14:06:35 +0200 Subject: [PATCH 40/46] feat: standardize output hash for unblinded output, transaction output and transaction input (#3592) Description --- - Standardized output hash calculation for unblinded output, transaction output and transaction input. - Refactored `transaction.rs` code layout to be modular in the file system. _**Note:** The only real change in the code is the standardized use of `transactions/transaction_entities/mod.rs/pub fn hash_output(...)` called from `transactions/transaction_entities/transaction_input.rs`, `transactions/transaction_entities/transaction_output.rs` and `transactions/transaction_entities/unblinded_output.rs`._ Motivation and Context --- Different output hash calculation implementations existed for output hash calculation for unblinded output vs. transaction output and transaction input. How Has This Been Tested? --- Unit testing. --- .../src/conversions/output_features.rs | 6 +- .../src/conversions/transaction.rs | 6 +- .../src/conversions/transaction_input.rs | 2 +- .../src/conversions/transaction_kernel.rs | 9 +- .../src/conversions/transaction_output.rs | 6 +- .../src/conversions/unblinded_output.rs | 2 +- .../src/grpc/base_node_grpc_server.rs | 69 +- .../src/automation/commands.rs | 2 +- .../src/automation/error.rs | 7 +- .../src/grpc/wallet_grpc_server.rs | 2 +- .../src/common/merge_mining.rs | 6 +- .../src/common/mining.rs | 6 +- applications/test_faucet/src/main.rs | 5 +- .../comms_interface/comms_response.rs | 14 +- .../comms_interface/inbound_handlers.rs | 25 +- .../comms_interface/local_interface.rs | 28 +- base_layer/core/src/base_node/rpc/service.rs | 10 +- .../states/horizon_state_sync/error.rs | 19 +- .../horizon_state_synchronization.rs | 37 +- base_layer/core/src/blocks/block.rs | 29 +- base_layer/core/src/blocks/genesis_block.rs | 30 +- base_layer/core/src/chain_storage/async_db.rs | 26 +- .../src/chain_storage/blockchain_backend.rs | 16 +- .../src/chain_storage/blockchain_database.rs | 52 +- .../core/src/chain_storage/db_transaction.rs | 32 +- .../core/src/chain_storage/lmdb_db/lmdb_db.rs | 35 +- .../core/src/chain_storage/lmdb_db/mod.rs | 16 +- .../core/src/chain_storage/pruned_output.rs | 2 +- .../tests/blockchain_database.rs | 18 +- .../core/src/consensus/consensus_manager.rs | 12 +- base_layer/core/src/mempool/async_mempool.rs | 8 +- base_layer/core/src/mempool/error.rs | 8 +- base_layer/core/src/mempool/mempool.rs | 8 +- .../core/src/mempool/mempool_storage.rs | 13 +- base_layer/core/src/mempool/mod.rs | 2 +- .../priority/prioritized_transaction.rs | 11 +- .../core/src/mempool/reorg_pool/reorg_pool.rs | 22 +- .../mempool/reorg_pool/reorg_pool_storage.rs | 13 +- base_layer/core/src/mempool/rpc/service.rs | 11 +- base_layer/core/src/mempool/service/handle.rs | 7 +- .../src/mempool/service/inbound_handlers.rs | 15 +- .../core/src/mempool/service/initializer.rs | 41 +- .../core/src/mempool/service/local_service.rs | 18 +- .../src/mempool/service/outbound_interface.rs | 14 +- .../core/src/mempool/service/request.rs | 2 +- .../core/src/mempool/service/service.rs | 41 +- .../core/src/mempool/sync_protocol/mod.rs | 46 +- .../core/src/mempool/sync_protocol/test.rs | 35 +- .../unconfirmed_pool/unconfirmed_pool.rs | 4 +- base_layer/core/src/proto/transaction.rs | 27 +- .../core/src/test_helpers/block_spec.rs | 2 +- .../core/src/test_helpers/blockchain.rs | 42 +- base_layer/core/src/test_helpers/mod.rs | 22 +- .../core/src/transactions/aggregated_body.rs | 2 +- .../core/src/transactions/coinbase_builder.rs | 4 +- base_layer/core/src/transactions/mod.rs | 2 +- .../core/src/transactions/test_helpers.rs | 4 +- .../core/src/transactions/transaction.rs | 1870 ----------------- .../transaction_entities/error.rs | 63 + .../full_rewind_result.rs | 63 + .../transaction_entities/kernel_builder.rs | 102 + .../transaction_entities/kernel_features.rs | 39 + .../transaction_entities/kernel_sum.rs | 35 + .../transactions/transaction_entities/mod.rs | 533 +++++ .../transaction_entities/output_features.rs | 152 ++ .../transaction_entities/output_flags.rs | 63 + .../transaction_entities/rewind_result.rs | 55 + .../transaction_entities/transaction.rs | 175 ++ .../transaction_builder.rs | 124 ++ .../transaction_entities/transaction_input.rs | 224 ++ .../transaction_kernel.rs | 139 ++ .../transaction_output.rs | 368 ++++ .../transaction_entities/unblinded_output.rs | 225 ++ .../transactions/transaction_protocol/mod.rs | 20 +- .../transaction_protocol/recipient.rs | 10 +- .../transaction_protocol/sender.rs | 4 +- .../transaction_protocol/single_receiver.rs | 10 +- .../transaction_initializer.rs | 4 +- .../block_validators/async_validator.rs | 2 +- .../src/validation/block_validators/test.rs | 10 +- base_layer/core/src/validation/error.rs | 10 +- base_layer/core/src/validation/helpers.rs | 2 +- base_layer/core/src/validation/mocks.rs | 17 +- base_layer/core/src/validation/test.rs | 9 +- base_layer/core/src/validation/traits.rs | 8 +- .../src/validation/transaction_validators.rs | 2 +- base_layer/core/tests/async_db.rs | 2 +- base_layer/core/tests/base_node_rpc.rs | 6 +- base_layer/core/tests/block_validation.rs | 29 +- .../core/tests/helpers/block_builders.rs | 2 +- base_layer/core/tests/helpers/database.rs | 2 +- .../core/tests/helpers/sample_blockchains.rs | 2 +- .../core/tests/helpers/test_block_builder.rs | 2 +- .../core/tests/helpers/test_blockchain.rs | 2 +- base_layer/core/tests/mempool.rs | 2 +- base_layer/core/tests/node_comms_interface.rs | 2 +- base_layer/core/tests/node_service.rs | 20 +- base_layer/wallet/src/error.rs | 2 +- .../src/output_manager_service/error.rs | 10 +- .../src/output_manager_service/handle.rs | 2 +- .../recovery/standard_outputs_recoverer.rs | 2 +- .../src/output_manager_service/service.rs | 2 +- .../storage/database.rs | 2 +- .../output_manager_service/storage/models.rs | 2 +- .../storage/sqlite_db/mod.rs | 4 +- .../storage/sqlite_db/output_sql.rs | 2 +- .../wallet/src/transaction_service/error.rs | 26 +- .../wallet/src/transaction_service/handle.rs | 25 +- .../transaction_broadcast_protocol.rs | 31 +- .../protocols/transaction_receive_protocol.rs | 2 +- .../protocols/transaction_send_protocol.rs | 2 +- .../wallet/src/transaction_service/service.rs | 2 +- .../transaction_service/storage/database.rs | 27 +- .../src/transaction_service/storage/models.rs | 3 +- .../transaction_service/storage/sqlite_db.rs | 2 +- .../tasks/send_finalized_transaction.rs | 23 +- .../src/utxo_scanner_service/utxo_scanning.rs | 14 +- base_layer/wallet/src/wallet.rs | 2 +- .../tests/output_manager_service/service.rs | 2 +- base_layer/wallet/tests/support/comms_rpc.rs | 4 +- base_layer/wallet/tests/support/utils.rs | 14 +- .../tests/transaction_service/service.rs | 2 +- .../tests/transaction_service/storage.rs | 2 +- base_layer/wallet/tests/wallet/mod.rs | 2 +- .../wallet_ffi/src/callback_handler_tests.rs | 15 +- base_layer/wallet_ffi/src/lib.rs | 4 +- 126 files changed, 3148 insertions(+), 2446 deletions(-) delete mode 100644 base_layer/core/src/transactions/transaction.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/error.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/full_rewind_result.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/kernel_builder.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/kernel_features.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/kernel_sum.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/mod.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/output_features.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/output_flags.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/rewind_result.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/transaction.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/transaction_builder.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/transaction_input.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/transaction_kernel.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/transaction_output.rs create mode 100644 base_layer/core/src/transactions/transaction_entities/unblinded_output.rs diff --git a/applications/tari_app_grpc/src/conversions/output_features.rs b/applications/tari_app_grpc/src/conversions/output_features.rs index 553219b958..bdf5cbe367 100644 --- a/applications/tari_app_grpc/src/conversions/output_features.rs +++ b/applications/tari_app_grpc/src/conversions/output_features.rs @@ -20,9 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::tari_rpc as grpc; use std::convert::TryFrom; -use tari_core::transactions::transaction::{OutputFeatures, OutputFlags}; + +use tari_core::transactions::transaction_entities::{OutputFeatures, OutputFlags}; + +use crate::tari_rpc as grpc; impl TryFrom for OutputFeatures { type Error = String; diff --git a/applications/tari_app_grpc/src/conversions/transaction.rs b/applications/tari_app_grpc/src/conversions/transaction.rs index 6aadee2066..05ada07310 100644 --- a/applications/tari_app_grpc/src/conversions/transaction.rs +++ b/applications/tari_app_grpc/src/conversions/transaction.rs @@ -20,14 +20,16 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::tari_rpc as grpc; use std::convert::{TryFrom, TryInto}; + use tari_common_types::transaction::{self as tx, TxId}; use tari_core::{ crypto::{ristretto::RistrettoSecretKey, tari_utilities::ByteArray}, - transactions::transaction::Transaction, + transactions::transaction_entities::Transaction, }; +use crate::tari_rpc as grpc; + impl From for grpc::Transaction { fn from(source: Transaction) -> Self { Self { diff --git a/applications/tari_app_grpc/src/conversions/transaction_input.rs b/applications/tari_app_grpc/src/conversions/transaction_input.rs index 48eebe04ad..fd9becc3eb 100644 --- a/applications/tari_app_grpc/src/conversions/transaction_input.rs +++ b/applications/tari_app_grpc/src/conversions/transaction_input.rs @@ -23,7 +23,7 @@ use crate::tari_rpc as grpc; use std::convert::{TryFrom, TryInto}; use tari_common_types::types::{Commitment, PublicKey}; -use tari_core::transactions::transaction::TransactionInput; +use tari_core::transactions::transaction_entities::TransactionInput; use tari_crypto::{ script::{ExecutionStack, TariScript}, tari_utilities::{ByteArray, Hashable}, diff --git a/applications/tari_app_grpc/src/conversions/transaction_kernel.rs b/applications/tari_app_grpc/src/conversions/transaction_kernel.rs index 7bf8664487..ee8437140b 100644 --- a/applications/tari_app_grpc/src/conversions/transaction_kernel.rs +++ b/applications/tari_app_grpc/src/conversions/transaction_kernel.rs @@ -20,14 +20,17 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::tari_rpc as grpc; use std::convert::{TryFrom, TryInto}; + +use tari_crypto::tari_utilities::{ByteArray, Hashable}; + use tari_common_types::types::Commitment; use tari_core::transactions::{ tari_amount::MicroTari, - transaction::{KernelFeatures, TransactionKernel}, + transaction_entities::{KernelFeatures, TransactionKernel}, }; -use tari_crypto::tari_utilities::{ByteArray, Hashable}; + +use crate::tari_rpc as grpc; impl TryFrom for TransactionKernel { type Error = String; diff --git a/applications/tari_app_grpc/src/conversions/transaction_output.rs b/applications/tari_app_grpc/src/conversions/transaction_output.rs index 7b783e3498..33f825382a 100644 --- a/applications/tari_app_grpc/src/conversions/transaction_output.rs +++ b/applications/tari_app_grpc/src/conversions/transaction_output.rs @@ -20,17 +20,19 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::tari_rpc as grpc; use std::convert::{TryFrom, TryInto}; + use tari_common_types::types::{BulletRangeProof, Commitment, PublicKey}; use tari_core::{ crypto::{ script::TariScript, tari_utilities::{ByteArray, Hashable}, }, - transactions::transaction::TransactionOutput, + transactions::transaction_entities::TransactionOutput, }; +use crate::tari_rpc as grpc; + impl TryFrom for TransactionOutput { type Error = String; diff --git a/applications/tari_app_grpc/src/conversions/unblinded_output.rs b/applications/tari_app_grpc/src/conversions/unblinded_output.rs index f452928e12..2225267f73 100644 --- a/applications/tari_app_grpc/src/conversions/unblinded_output.rs +++ b/applications/tari_app_grpc/src/conversions/unblinded_output.rs @@ -28,7 +28,7 @@ use tari_core::{ script::{ExecutionStack, TariScript}, tari_utilities::ByteArray, }, - transactions::{tari_amount::MicroTari, transaction::UnblindedOutput}, + transactions::{tari_amount::MicroTari, transaction_entities::UnblindedOutput}, }; impl From for grpc::UnblindedOutput { diff --git a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs index ac4f6c6a53..41168f7e24 100644 --- a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs +++ b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs @@ -1,3 +1,39 @@ +use std::{ + cmp, + convert::{TryFrom, TryInto}, +}; + +use either::Either; +use futures::{channel::mpsc, SinkExt}; +use log::*; +use tari_crypto::tari_utilities::{message_format::MessageFormat, Hashable}; +use tokio::task; +use tonic::{Request, Response, Status}; + +use tari_app_grpc::{ + tari_rpc, + tari_rpc::{CalcType, Sorting}, +}; +use tari_app_utilities::consts; +use tari_common_types::types::Signature; +use tari_comms::{Bytes, CommsNode}; +use tari_core::{ + base_node::{ + comms_interface::{Broadcast, CommsInterfaceError}, + LocalNodeCommsInterface, + StateMachineHandle, + }, + blocks::{Block, BlockHeader, NewBlockTemplate}, + chain_storage::ChainStorageError, + consensus::{emission::Emission, ConsensusManager, NetworkConsensus}, + crypto::tari_utilities::{hex::Hex, ByteArray}, + iterators::NonOverlappingIntegerPairIter, + mempool::{service::LocalMempoolService, TxStorageResponse}, + proof_of_work::PowAlgorithm, + transactions::transaction_entities::Transaction, +}; +use tari_p2p::{auto_update::SoftwareUpdaterHandle, services::liveness::LivenessHandle}; + // Copyright 2019. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the @@ -26,39 +62,6 @@ use crate::{ helpers::{mean, median}, }, }; -use either::Either; -use futures::{channel::mpsc, SinkExt}; -use log::*; -use std::{ - cmp, - convert::{TryFrom, TryInto}, -}; -use tari_app_grpc::{ - tari_rpc, - tari_rpc::{CalcType, Sorting}, -}; -use tari_app_utilities::consts; -use tari_common_types::types::Signature; -use tari_comms::{Bytes, CommsNode}; -use tari_core::{ - base_node::{ - comms_interface::{Broadcast, CommsInterfaceError}, - LocalNodeCommsInterface, - StateMachineHandle, - }, - blocks::{Block, BlockHeader, NewBlockTemplate}, - chain_storage::ChainStorageError, - consensus::{emission::Emission, ConsensusManager, NetworkConsensus}, - crypto::tari_utilities::{hex::Hex, ByteArray}, - iterators::NonOverlappingIntegerPairIter, - mempool::{service::LocalMempoolService, TxStorageResponse}, - proof_of_work::PowAlgorithm, - transactions::transaction::Transaction, -}; -use tari_crypto::tari_utilities::{message_format::MessageFormat, Hashable}; -use tari_p2p::{auto_update::SoftwareUpdaterHandle, services::liveness::LivenessHandle}; -use tokio::task; -use tonic::{Request, Response, Status}; const LOG_TARGET: &str = "tari::base_node::grpc"; const GET_TOKENS_IN_CIRCULATION_MAX_HEIGHTS: usize = 1_000_000; diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 8468ec8a18..27cf5f54a8 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -54,7 +54,7 @@ use tari_core::{ tari_utilities::hex::Hex, transactions::{ tari_amount::{uT, MicroTari, Tari}, - transaction::{TransactionOutput, UnblindedOutput}, + transaction_entities::{TransactionOutput, UnblindedOutput}, }, }; use tari_wallet::{ diff --git a/applications/tari_console_wallet/src/automation/error.rs b/applications/tari_console_wallet/src/automation/error.rs index e3600782fc..368a40d7fc 100644 --- a/applications/tari_console_wallet/src/automation/error.rs +++ b/applications/tari_console_wallet/src/automation/error.rs @@ -23,12 +23,15 @@ use std::num::{ParseFloatError, ParseIntError}; use log::*; +use thiserror::Error; +use tokio::task::JoinError; + use tari_common::exit_codes::ExitCodes; use tari_core::{ tari_utilities::hex::HexError, transactions::{ tari_amount::{MicroTariError, TariConversionError}, - transaction::TransactionError, + transaction_entities::TransactionError, }, }; use tari_wallet::{ @@ -36,8 +39,6 @@ use tari_wallet::{ output_manager_service::error::OutputManagerError, transaction_service::error::TransactionServiceError, }; -use thiserror::Error; -use tokio::task::JoinError; pub const LOG_TARGET: &str = "wallet::automation::error"; diff --git a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs index 18e19d6a18..2934a4c84d 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -43,7 +43,7 @@ use tari_common_types::types::{BlockHash, Signature}; use tari_comms::{types::CommsPublicKey, CommsNode}; use tari_core::{ tari_utilities::{hex::Hex, ByteArray}, - transactions::{tari_amount::MicroTari, transaction::UnblindedOutput}, + transactions::{tari_amount::MicroTari, transaction_entities::UnblindedOutput}, }; use tari_crypto::tari_utilities::Hashable; use tari_wallet::{ diff --git a/applications/tari_merge_mining_proxy/src/common/merge_mining.rs b/applications/tari_merge_mining_proxy/src/common/merge_mining.rs index 7d232069b7..fcaf5de8c9 100644 --- a/applications/tari_merge_mining_proxy/src/common/merge_mining.rs +++ b/applications/tari_merge_mining_proxy/src/common/merge_mining.rs @@ -20,14 +20,16 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::error::MmProxyError; use std::convert::TryFrom; + use tari_app_grpc::tari_rpc as grpc; use tari_core::{ blocks::NewBlockTemplate, - transactions::transaction::{TransactionKernel, TransactionOutput}, + transactions::transaction_entities::{TransactionKernel, TransactionOutput}, }; +use crate::error::MmProxyError; + pub fn add_coinbase( coinbase: grpc::Transaction, block_template: grpc::NewBlockTemplate, diff --git a/applications/tari_stratum_transcoder/src/common/mining.rs b/applications/tari_stratum_transcoder/src/common/mining.rs index bf4a714b2b..38390bcae1 100644 --- a/applications/tari_stratum_transcoder/src/common/mining.rs +++ b/applications/tari_stratum_transcoder/src/common/mining.rs @@ -20,14 +20,16 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::error::StratumTranscoderProxyError; use std::convert::TryFrom; + use tari_app_grpc::tari_rpc as grpc; use tari_core::{ blocks::NewBlockTemplate, - transactions::transaction::{TransactionKernel, TransactionOutput}, + transactions::transaction_entities::{TransactionKernel, TransactionOutput}, }; +use crate::error::StratumTranscoderProxyError; + pub fn add_coinbase( coinbase: Option, mut block: NewBlockTemplate, diff --git a/applications/test_faucet/src/main.rs b/applications/test_faucet/src/main.rs index 3b463d7d22..a3d2946eef 100644 --- a/applications/test_faucet/src/main.rs +++ b/applications/test_faucet/src/main.rs @@ -9,17 +9,16 @@ use std::{fs::File, io::Write}; use serde::Serialize; -use tari_crypto::script; +use tari_crypto::{script, tari_utilities::hex::Hex}; use tokio::{sync::mpsc, task}; use tari_common_types::types::{Commitment, PrivateKey}; use tari_core::transactions::{ tari_amount::{MicroTari, T}, test_helpers, - transaction::{KernelFeatures, OutputFeatures, TransactionKernel, TransactionOutput}, + transaction_entities::{KernelFeatures, OutputFeatures, TransactionKernel, TransactionOutput}, CryptoFactories, }; -use tari_crypto::tari_utilities::hex::Hex; const NUM_KEYS: usize = 4000; diff --git a/base_layer/core/src/base_node/comms_interface/comms_response.rs b/base_layer/core/src/base_node/comms_interface/comms_response.rs index e5a1fbaa1c..017dadce18 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_response.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_response.rs @@ -20,14 +20,20 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::fmt::{self, Display, Formatter}; + +use serde::{Deserialize, Serialize}; + +use tari_common_types::{chain_metadata::ChainMetadata, types::HashOutput}; + use crate::{ blocks::{Block, BlockHeader, ChainHeader, HistoricalBlock, NewBlockTemplate}, proof_of_work::Difficulty, - transactions::transaction::{TransactionKernel, TransactionOutput}, + transactions::transaction_entities::{ + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, + }, }; -use serde::{Deserialize, Serialize}; -use std::fmt::{self, Display, Formatter}; -use tari_common_types::{chain_metadata::ChainMetadata, types::HashOutput}; /// API Response enum #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index 6dfec0b3b4..b3b625d132 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -1,3 +1,16 @@ +use std::{ + fmt::{Display, Error, Formatter}, + sync::Arc, +}; + +use log::*; +use strum_macros::Display; +use tari_crypto::tari_utilities::{hash::Hashable, hex::Hex}; +use tokio::sync::Semaphore; + +use tari_common_types::types::{BlockHash, HashOutput}; +use tari_comms::peer_manager::NodeId; + // Copyright 2019. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the @@ -32,18 +45,8 @@ use crate::{ consensus::{ConsensusConstants, ConsensusManager}, mempool::{async_mempool, Mempool}, proof_of_work::{Difficulty, PowAlgorithm}, - transactions::transaction::TransactionKernel, -}; -use log::*; -use std::{ - fmt::{Display, Error, Formatter}, - sync::Arc, + transactions::transaction_entities::transaction_kernel::TransactionKernel, }; -use strum_macros::Display; -use tari_common_types::types::{BlockHash, HashOutput}; -use tari_comms::peer_manager::NodeId; -use tari_crypto::tari_utilities::{hash::Hashable, hex::Hex}; -use tokio::sync::Semaphore; const LOG_TARGET: &str = "c::bn::comms_interface::inbound_handler"; const MAX_HEADERS_PER_RESPONSE: u32 = 100; diff --git a/base_layer/core/src/base_node/comms_interface/local_interface.rs b/base_layer/core/src/base_node/comms_interface/local_interface.rs index 04e72860a3..c5cc6b71b6 100644 --- a/base_layer/core/src/base_node/comms_interface/local_interface.rs +++ b/base_layer/core/src/base_node/comms_interface/local_interface.rs @@ -20,31 +20,35 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::{ops::RangeInclusive, sync::Arc}; + +use tokio::sync::broadcast; + +use tari_common_types::{ + chain_metadata::ChainMetadata, + types::{BlockHash, Commitment, HashOutput, Signature}, +}; +use tari_service_framework::{reply_channel::SenderService, Service}; + use crate::{ base_node::comms_interface::{ + comms_request::GetNewBlockTemplateRequest, error::CommsInterfaceError, BlockEvent, Broadcast, NodeCommsRequest, NodeCommsResponse, }, - blocks::{Block, HistoricalBlock, NewBlockTemplate}, + blocks::{Block, ChainHeader, HistoricalBlock, NewBlockTemplate}, proof_of_work::PowAlgorithm, - transactions::transaction::TransactionKernel, + transactions::transaction_entities::{ + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, + }, }; -use std::{ops::RangeInclusive, sync::Arc}; -use tari_common_types::{chain_metadata::ChainMetadata, types::BlockHash}; -use tari_service_framework::{reply_channel::SenderService, Service}; -use tokio::sync::broadcast; pub type BlockEventSender = broadcast::Sender>; pub type BlockEventReceiver = broadcast::Receiver>; -use crate::{ - base_node::comms_interface::comms_request::GetNewBlockTemplateRequest, - blocks::ChainHeader, - transactions::transaction::TransactionOutput, -}; -use tari_common_types::types::{Commitment, HashOutput, Signature}; /// The InboundNodeCommsInterface provides an interface to request information from the current local node by other /// internal services. diff --git a/base_layer/core/src/base_node/rpc/service.rs b/base_layer/core/src/base_node/rpc/service.rs index 7edca0efa9..44400f0e3a 100644 --- a/base_layer/core/src/base_node/rpc/service.rs +++ b/base_layer/core/src/base_node/rpc/service.rs @@ -1,3 +1,8 @@ +use std::convert::TryFrom; + +use tari_common_types::types::Signature; +use tari_comms::protocol::rpc::{Request, Response, RpcStatus}; + // Copyright 2020, The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the @@ -44,11 +49,8 @@ use crate::{ }, types::{Signature as SignatureProto, Transaction as TransactionProto}, }, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, }; -use std::convert::TryFrom; -use tari_common_types::types::Signature; -use tari_comms::protocol::rpc::{Request, Response, RpcStatus}; const LOG_TARGET: &str = "c::base_node::rpc"; diff --git a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/error.rs b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/error.rs index adb65def9f..8dd46d70f7 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/error.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/error.rs @@ -20,20 +20,23 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - base_node::{comms_interface::CommsInterfaceError, state_machine_service::states::helpers::BaseNodeRequestError}, - chain_storage::{ChainStorageError, MmrTree}, - transactions::transaction::TransactionError, - validation::ValidationError, -}; use std::num::TryFromIntError; + +use thiserror::Error; +use tokio::task; + use tari_comms::{ connectivity::ConnectivityError, protocol::rpc::{RpcError, RpcStatus}, }; use tari_mmr::error::MerkleMountainRangeError; -use thiserror::Error; -use tokio::task; + +use crate::{ + base_node::{comms_interface::CommsInterfaceError, state_machine_service::states::helpers::BaseNodeRequestError}, + chain_storage::{ChainStorageError, MmrTree}, + transactions::transaction_entities::error::TransactionError, + validation::ValidationError, +}; #[derive(Debug, Error)] pub enum HorizonSyncError { diff --git a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs index 75ff753d50..9b9f344040 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/horizon_state_sync/horizon_state_synchronization.rs @@ -20,7 +20,22 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use super::error::HorizonSyncError; +use std::{ + convert::{TryFrom, TryInto}, + sync::Arc, +}; + +use croaring::Bitmap; +use futures::StreamExt; +use log::*; +use tari_crypto::{ + commitment::HomomorphicCommitment, + tari_utilities::{hex::Hex, Hashable}, +}; + +use tari_common_types::types::{HashDigest, RangeProofService}; +use tari_mmr::{MerkleMountainRange, MutableMmr}; + use crate::{ base_node::{ state_machine_service::{ @@ -39,21 +54,13 @@ use crate::{ SyncUtxosRequest, SyncUtxosResponse, }, - transactions::transaction::{TransactionKernel, TransactionOutput}, -}; -use croaring::Bitmap; -use futures::StreamExt; -use log::*; -use std::{ - convert::{TryFrom, TryInto}, - sync::Arc, -}; -use tari_common_types::types::{HashDigest, RangeProofService}; -use tari_crypto::{ - commitment::HomomorphicCommitment, - tari_utilities::{hex::Hex, Hashable}, + transactions::transaction_entities::{ + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, + }, }; -use tari_mmr::{MerkleMountainRange, MutableMmr}; + +use super::error::HorizonSyncError; const LOG_TARGET: &str = "c::bn::state_machine_service::states::horizon_state_sync"; diff --git a/base_layer/core/src/blocks/block.rs b/base_layer/core/src/blocks/block.rs index 2c6457566e..f5a17d3380 100644 --- a/base_layer/core/src/blocks/block.rs +++ b/base_layer/core/src/blocks/block.rs @@ -23,6 +23,18 @@ // Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, // Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. +use std::{ + fmt, + fmt::{Display, Formatter}, +}; + +use log::*; +use serde::{Deserialize, Serialize}; +use tari_crypto::tari_utilities::Hashable; +use thiserror::Error; + +use tari_common_types::types::BlockHash; + use crate::{ blocks::BlockHeader, consensus::ConsensusConstants, @@ -31,19 +43,16 @@ use crate::{ transactions::{ aggregated_body::AggregateBody, tari_amount::MicroTari, - transaction::{Transaction, TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, + transaction_entities::{ + error::TransactionError, + transaction::Transaction, + transaction_input::TransactionInput, + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, + }, CryptoFactories, }, }; -use log::*; -use serde::{Deserialize, Serialize}; -use std::{ - fmt, - fmt::{Display, Formatter}, -}; -use tari_common_types::types::BlockHash; -use tari_crypto::tari_utilities::Hashable; -use thiserror::Error; #[derive(Clone, Debug, PartialEq, Error)] pub enum BlockValidationError { diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index d892ba41ff..d1c9bab91a 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -20,6 +20,17 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::Arc; + +use chrono::DateTime; +use tari_crypto::{ + script::TariScript, + tari_utilities::{hash::Hashable, hex::*}, +}; + +use tari_common::configuration::Network; +use tari_common_types::types::{BulletRangeProof, Commitment, PrivateKey, PublicKey, Signature, BLOCK_HASH_LENGTH}; + // This file is used to store the genesis block use crate::{ blocks::{block::Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock}, @@ -27,17 +38,15 @@ use crate::{ transactions::{ aggregated_body::AggregateBody, tari_amount::MicroTari, - transaction::{KernelFeatures, OutputFeatures, OutputFlags, TransactionKernel, TransactionOutput}, + transaction_entities::{ + output_features::OutputFeatures, + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, + KernelFeatures, + OutputFlags, + }, }, }; -use chrono::DateTime; -use std::sync::Arc; -use tari_common::configuration::Network; -use tari_common_types::types::{BulletRangeProof, Commitment, PrivateKey, PublicKey, Signature, BLOCK_HASH_LENGTH}; -use tari_crypto::{ - script::TariScript, - tari_utilities::{hash::Hashable, hex::*}, -}; /// Returns the genesis block for the selected network. pub fn get_genesis_block(network: Network) -> ChainBlock { @@ -465,9 +474,10 @@ pub fn get_igor_genesis_block_raw() -> Block { #[cfg(test)] mod test { - use super::*; use crate::transactions::CryptoFactories; + use super::*; + #[test] fn weatherwax_genesis_sanity_check() { let block = get_weatherwax_genesis_block(); diff --git a/base_layer/core/src/chain_storage/async_db.rs b/base_layer/core/src/chain_storage/async_db.rs index 269e096da5..99e2bcbd4d 100644 --- a/base_layer/core/src/chain_storage/async_db.rs +++ b/base_layer/core/src/chain_storage/async_db.rs @@ -20,6 +20,18 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::{mem, ops::RangeBounds, sync::Arc, time::Instant}; + +use croaring::Bitmap; +use log::*; +use rand::{rngs::OsRng, RngCore}; + +use tari_common_types::{ + chain_metadata::ChainMetadata, + types::{BlockHash, Commitment, HashOutput, Signature}, +}; +use tari_mmr::pruned_hashset::PrunedHashSet; + use crate::{ blocks::{ Block, @@ -51,17 +63,11 @@ use crate::{ common::rolling_vec::RollingVec, proof_of_work::{PowAlgorithm, TargetDifficultyWindow}, tari_utilities::epoch_time::EpochTime, - transactions::transaction::{TransactionKernel, TransactionOutput}, -}; -use croaring::Bitmap; -use log::*; -use rand::{rngs::OsRng, RngCore}; -use std::{mem, ops::RangeBounds, sync::Arc, time::Instant}; -use tari_common_types::{ - chain_metadata::ChainMetadata, - types::{BlockHash, Commitment, HashOutput, Signature}, + transactions::transaction_entities::{ + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, + }, }; -use tari_mmr::pruned_hashset::PrunedHashSet; const LOG_TARGET: &str = "c::bn::async_db"; diff --git a/base_layer/core/src/chain_storage/blockchain_backend.rs b/base_layer/core/src/chain_storage/blockchain_backend.rs index bd1a3acdef..17595aae6c 100644 --- a/base_layer/core/src/chain_storage/blockchain_backend.rs +++ b/base_layer/core/src/chain_storage/blockchain_backend.rs @@ -1,3 +1,11 @@ +use croaring::Bitmap; + +use tari_common_types::{ + chain_metadata::ChainMetadata, + types::{Commitment, HashOutput, Signature}, +}; +use tari_mmr::Hash; + use crate::{ blocks::{ Block, @@ -20,14 +28,8 @@ use crate::{ MmrTree, UtxoMinedInfo, }, - transactions::transaction::{TransactionInput, TransactionKernel}, + transactions::transaction_entities::{transaction_input::TransactionInput, transaction_kernel::TransactionKernel}, }; -use croaring::Bitmap; -use tari_common_types::{ - chain_metadata::ChainMetadata, - types::{Commitment, HashOutput, Signature}, -}; -use tari_mmr::Hash; /// Identify behaviour for Blockchain database backends. Implementations must support `Send` and `Sync` so that /// `BlockchainDatabase` can be thread-safe. The backend *must* also execute transactions atomically; i.e., every diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 5b3b07abf8..e2a189b54a 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -1,3 +1,24 @@ +use std::{ + cmp, + cmp::Ordering, + collections::VecDeque, + convert::TryFrom, + mem, + ops::{Bound, RangeBounds}, + sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, + time::Instant, +}; + +use croaring::Bitmap; +use log::*; +use tari_crypto::tari_utilities::{hex::Hex, ByteArray, Hashable}; + +use tari_common_types::{ + chain_metadata::ChainMetadata, + types::{BlockHash, Commitment, HashDigest, HashOutput, Signature}, +}; +use tari_mmr::{pruned_hashset::PrunedHashSet, MerkleMountainRange, MutableMmr}; + // Copyright 2019. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the @@ -56,7 +77,7 @@ use crate::{ consensus::{chain_strength_comparer::ChainStrengthComparer, ConsensusConstants, ConsensusManager}, proof_of_work::{monero_rx::MoneroPowData, PowAlgorithm, TargetDifficultyWindow}, tari_utilities::epoch_time::EpochTime, - transactions::transaction::TransactionKernel, + transactions::transaction_entities::transaction_kernel::TransactionKernel, validation::{ helpers::calc_median_timestamp, DifficultyCalculator, @@ -66,24 +87,6 @@ use crate::{ ValidationError, }, }; -use croaring::Bitmap; -use log::*; -use std::{ - cmp, - cmp::Ordering, - collections::VecDeque, - convert::TryFrom, - mem, - ops::{Bound, RangeBounds}, - sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, - time::Instant, -}; -use tari_common_types::{ - chain_metadata::ChainMetadata, - types::{BlockHash, Commitment, HashDigest, HashOutput, Signature}, -}; -use tari_crypto::tari_utilities::{hex::Hex, ByteArray, Hashable}; -use tari_mmr::{pruned_hashset::PrunedHashSet, MerkleMountainRange, MutableMmr}; const LOG_TARGET: &str = "c::cs::database"; @@ -2129,7 +2132,11 @@ fn convert_to_option_bounds>(bounds: T) -> (Option, Opt #[cfg(test)] mod test { - use super::*; + use std::{collections::HashMap, sync}; + + use tari_common::configuration::Network; + use tari_test_utils::unpack_enum; + use crate::{ block_specs, consensus::{ @@ -2151,9 +2158,8 @@ mod test { }, validation::{header_validator::HeaderValidator, mocks::MockValidator}, }; - use std::{collections::HashMap, sync}; - use tari_common::configuration::Network; - use tari_test_utils::unpack_enum; + + use super::*; #[test] fn lmdb_fetch_monero_seeds() { diff --git a/base_layer/core/src/chain_storage/db_transaction.rs b/base_layer/core/src/chain_storage/db_transaction.rs index 970988d655..066528f73b 100644 --- a/base_layer/core/src/chain_storage/db_transaction.rs +++ b/base_layer/core/src/chain_storage/db_transaction.rs @@ -1,3 +1,18 @@ +use std::{ + fmt, + fmt::{Display, Error, Formatter}, + sync::Arc, +}; + +use croaring::Bitmap; +use tari_crypto::tari_utilities::{ + hex::{to_hex, Hex}, + Hashable, +}; + +use tari_common_types::types::{BlockHash, Commitment, HashOutput}; +use tari_mmr::pruned_hashset::PrunedHashSet; + // Copyright 2019. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the @@ -22,20 +37,11 @@ use crate::{ blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader}, chain_storage::{error::ChainStorageError, MmrTree}, - transactions::transaction::{TransactionKernel, TransactionOutput}, -}; -use croaring::Bitmap; -use std::{ - fmt, - fmt::{Display, Error, Formatter}, - sync::Arc, -}; -use tari_common_types::types::{BlockHash, Commitment, HashOutput}; -use tari_crypto::tari_utilities::{ - hex::{to_hex, Hex}, - Hashable, + transactions::transaction_entities::{ + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, + }, }; -use tari_mmr::pruned_hashset::PrunedHashSet; #[derive(Debug)] pub struct DbTransaction { diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index 3b80e8dacf..0d3e18a6a5 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -24,6 +24,22 @@ // let's ignore this clippy error in this module #![allow(clippy::ptr_arg)] +use std::{convert::TryFrom, fmt, fs, fs::File, ops::Deref, path::Path, sync::Arc, time::Instant}; + +use croaring::Bitmap; +use fs2::FileExt; +use lmdb_zero::{ConstTransaction, Database, Environment, ReadTransaction, WriteTransaction}; +use log::*; +use serde::{Deserialize, Serialize}; +use tari_crypto::tari_utilities::{hash::Hashable, hex::Hex, ByteArray}; + +use tari_common_types::{ + chain_metadata::ChainMetadata, + types::{BlockHash, Commitment, HashDigest, HashOutput, Signature, BLOCK_HASH_LENGTH}, +}; +use tari_mmr::{pruned_hashset::PrunedHashSet, Hash, MerkleMountainRange, MutableMmr}; +use tari_storage::lmdb_store::{db, LMDBBuilder, LMDBConfig, LMDBStore}; + use crate::{ blocks::{ Block, @@ -71,22 +87,13 @@ use crate::{ crypto::tari_utilities::hex::to_hex, transactions::{ aggregated_body::AggregateBody, - transaction::{TransactionInput, TransactionKernel, TransactionOutput}, + transaction_entities::{ + transaction_input::TransactionInput, + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, + }, }, }; -use croaring::Bitmap; -use fs2::FileExt; -use lmdb_zero::{ConstTransaction, Database, Environment, ReadTransaction, WriteTransaction}; -use log::*; -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, fmt, fs, fs::File, ops::Deref, path::Path, sync::Arc, time::Instant}; -use tari_common_types::{ - chain_metadata::ChainMetadata, - types::{BlockHash, Commitment, HashDigest, HashOutput, Signature, BLOCK_HASH_LENGTH}, -}; -use tari_crypto::tari_utilities::{hash::Hashable, hex::Hex, ByteArray}; -use tari_mmr::{pruned_hashset::PrunedHashSet, Hash, MerkleMountainRange, MutableMmr}; -use tari_storage::lmdb_store::{db, LMDBBuilder, LMDBConfig, LMDBStore}; type DatabaseRef = Arc>; diff --git a/base_layer/core/src/chain_storage/lmdb_db/mod.rs b/base_layer/core/src/chain_storage/lmdb_db/mod.rs index 7947da342d..41291e9a38 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/mod.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/mod.rs @@ -20,15 +20,21 @@ // 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. -mod lmdb; -#[allow(clippy::module_inception)] -mod lmdb_db; +use serde::{Deserialize, Serialize}; -use crate::transactions::transaction::{TransactionInput, TransactionKernel, TransactionOutput}; pub use lmdb_db::{create_lmdb_database, create_recovery_lmdb_database, LMDBDatabase}; -use serde::{Deserialize, Serialize}; use tari_common_types::types::HashOutput; +use crate::transactions::transaction_entities::{ + transaction_input::TransactionInput, + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, +}; + +mod lmdb; +#[allow(clippy::module_inception)] +mod lmdb_db; + #[derive(Serialize, Deserialize, Debug)] pub(crate) struct TransactionOutputRowData { pub output: Option, diff --git a/base_layer/core/src/chain_storage/pruned_output.rs b/base_layer/core/src/chain_storage/pruned_output.rs index 20f54c37ef..c69e35565a 100644 --- a/base_layer/core/src/chain_storage/pruned_output.rs +++ b/base_layer/core/src/chain_storage/pruned_output.rs @@ -19,7 +19,7 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::transactions::transaction::TransactionOutput; +use crate::transactions::transaction_entities::transaction_output::TransactionOutput; use tari_common_types::types::HashOutput; use tari_crypto::tari_utilities::Hashable; diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index b7716373b8..4bcd6099ec 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -20,6 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::Arc; + +use tari_common::configuration::Network; +use tari_test_utils::unpack_enum; + use crate::{ blocks::{Block, BlockHeader, NewBlockTemplate}, chain_storage::{BlockchainDatabase, ChainStorageError}, @@ -33,12 +38,9 @@ use crate::{ }, transactions::{ tari_amount::T, - transaction::{Transaction, UnblindedOutput}, + transaction_entities::{transaction::Transaction, unblinded_output::UnblindedOutput}, }, }; -use std::sync::Arc; -use tari_common::configuration::Network; -use tari_test_utils::unpack_enum; fn setup() -> BlockchainDatabase { create_new_blockchain() @@ -237,9 +239,10 @@ mod fetch_headers { } mod find_headers_after_hash { - use super::*; use crate::chain_storage::ChainStorageError; + use super::*; + #[test] fn it_returns_none_given_empty_vec() { let db = setup(); @@ -350,18 +353,19 @@ mod fetch_block_hashes_from_header_tip { } mod add_block { - use super::*; use crate::{ chain_storage::ChainStorageError, crypto::tari_utilities::hex::Hex, transactions::{ tari_amount::T, test_helpers::{schema_to_transaction, TransactionSchema}, - transaction::OutputFeatures, + transaction_entities::output_features::OutputFeatures, }, txn_schema, }; + use super::*; + #[test] fn it_does_not_allow_duplicate_commitments_in_the_utxo_set() { let db = setup(); diff --git a/base_layer/core/src/consensus/consensus_manager.rs b/base_layer/core/src/consensus/consensus_manager.rs index 925bfbe0bd..df78fe2b00 100644 --- a/base_layer/core/src/consensus/consensus_manager.rs +++ b/base_layer/core/src/consensus/consensus_manager.rs @@ -20,6 +20,12 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::Arc; + +use thiserror::Error; + +use tari_common::configuration::Network; + #[cfg(feature = "base_node")] use crate::{ blocks::ChainBlock, @@ -27,7 +33,6 @@ use crate::{ proof_of_work::PowAlgorithm, proof_of_work::TargetDifficultyWindow, }; - use crate::{ consensus::{ emission::{Emission, EmissionSchedule}, @@ -35,11 +40,8 @@ use crate::{ NetworkConsensus, }, proof_of_work::DifficultyAdjustmentError, - transactions::{tari_amount::MicroTari, transaction::TransactionKernel}, + transactions::{tari_amount::MicroTari, transaction_entities::transaction_kernel::TransactionKernel}, }; -use std::sync::Arc; -use tari_common::configuration::Network; -use thiserror::Error; #[derive(Debug, Error)] #[allow(clippy::large_enum_variant)] diff --git a/base_layer/core/src/mempool/async_mempool.rs b/base_layer/core/src/mempool/async_mempool.rs index d99da619f1..0955e2e58f 100644 --- a/base_layer/core/src/mempool/async_mempool.rs +++ b/base_layer/core/src/mempool/async_mempool.rs @@ -20,13 +20,15 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::Arc; + +use tari_common_types::types::Signature; + use crate::{ blocks::Block, mempool::{error::MempoolError, Mempool, StateResponse, StatsResponse, TxStorageResponse}, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, }; -use std::sync::Arc; -use tari_common_types::types::Signature; macro_rules! make_async { ($fn:ident($($param1:ident:$ptype1:ty,$param2:ident:$ptype2:ty),+) -> $rtype:ty) => { diff --git a/base_layer/core/src/mempool/error.rs b/base_layer/core/src/mempool/error.rs index d49723daa0..0c21845f02 100644 --- a/base_layer/core/src/mempool/error.rs +++ b/base_layer/core/src/mempool/error.rs @@ -20,13 +20,15 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use thiserror::Error; + +use tari_service_framework::reply_channel::TransportChannelError; + use crate::{ chain_storage::ChainStorageError, mempool::{reorg_pool::ReorgPoolError, unconfirmed_pool::UnconfirmedPoolError}, - transactions::transaction::TransactionError, + transactions::transaction_entities::error::TransactionError, }; -use tari_service_framework::reply_channel::TransportChannelError; -use thiserror::Error; #[derive(Debug, Error)] pub enum MempoolError { diff --git a/base_layer/core/src/mempool/mempool.rs b/base_layer/core/src/mempool/mempool.rs index 444bf491b6..8b45ac0ede 100644 --- a/base_layer/core/src/mempool/mempool.rs +++ b/base_layer/core/src/mempool/mempool.rs @@ -20,6 +20,10 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::{Arc, RwLock}; + +use tari_common_types::types::Signature; + use crate::{ blocks::Block, consensus::ConsensusManager, @@ -31,11 +35,9 @@ use crate::{ StatsResponse, TxStorageResponse, }, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, validation::MempoolTransactionValidation, }; -use std::sync::{Arc, RwLock}; -use tari_common_types::types::Signature; /// The Mempool consists of an Unconfirmed Transaction Pool, Pending Pool, Orphan Pool and Reorg Pool and is responsible /// for managing and maintaining all unconfirmed transactions have not yet been included in a block, and transactions diff --git a/base_layer/core/src/mempool/mempool_storage.rs b/base_layer/core/src/mempool/mempool_storage.rs index a50e25a7ae..d5f3d90a56 100644 --- a/base_layer/core/src/mempool/mempool_storage.rs +++ b/base_layer/core/src/mempool/mempool_storage.rs @@ -20,6 +20,13 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::Arc; + +use log::*; +use tari_crypto::tari_utilities::{hex::Hex, Hashable}; + +use tari_common_types::types::Signature; + use crate::{ blocks::Block, consensus::ConsensusManager, @@ -32,13 +39,9 @@ use crate::{ StatsResponse, TxStorageResponse, }, - transactions::{transaction::Transaction, weight::TransactionWeight}, + transactions::{transaction_entities::transaction::Transaction, weight::TransactionWeight}, validation::{MempoolTransactionValidation, ValidationError}, }; -use log::*; -use std::sync::Arc; -use tari_common_types::types::Signature; -use tari_crypto::tari_utilities::{hex::Hex, Hashable}; pub const LOG_TARGET: &str = "c::mp::mempool_storage"; diff --git a/base_layer/core/src/mempool/mod.rs b/base_layer/core/src/mempool/mod.rs index 4e12849f71..a471535aec 100644 --- a/base_layer/core/src/mempool/mod.rs +++ b/base_layer/core/src/mempool/mod.rs @@ -72,7 +72,7 @@ mod sync_protocol; #[cfg(feature = "base_node")] pub use sync_protocol::MempoolSyncInitializer; -use crate::transactions::transaction::Transaction; +use crate::transactions::transaction_entities::transaction::Transaction; use core::fmt::{Display, Error, Formatter}; use serde::{Deserialize, Serialize}; use tari_common_types::types::Signature; diff --git a/base_layer/core/src/mempool/priority/prioritized_transaction.rs b/base_layer/core/src/mempool/priority/prioritized_transaction.rs index 7051db9858..51d2718924 100644 --- a/base_layer/core/src/mempool/priority/prioritized_transaction.rs +++ b/base_layer/core/src/mempool/priority/prioritized_transaction.rs @@ -20,13 +20,16 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::Arc; + +use tari_crypto::tari_utilities::message_format::MessageFormat; + +use tari_common_types::types::HashOutput; + use crate::{ mempool::priority::PriorityError, - transactions::{transaction::Transaction, weight::TransactionWeight}, + transactions::{transaction_entities::transaction::Transaction, weight::TransactionWeight}, }; -use std::sync::Arc; -use tari_common_types::types::HashOutput; -use tari_crypto::tari_utilities::message_format::MessageFormat; /// Create a unique unspent transaction priority based on the transaction fee, maturity of the oldest input UTXO and the /// excess_sig. The excess_sig is included to ensure the the priority key unique so it can be used with a BTreeMap. diff --git a/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs b/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs index f86e04d7ed..06d166eb08 100644 --- a/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs +++ b/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs @@ -20,18 +20,21 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::{sync::Arc, time::Duration}; + +use serde::{Deserialize, Serialize}; + +use tari_common::configuration::seconds; +use tari_common_types::types::Signature; + use crate::{ blocks::Block, mempool::{ consts::{MEMPOOL_REORG_POOL_CACHE_TTL, MEMPOOL_REORG_POOL_STORAGE_CAPACITY}, reorg_pool::{ReorgPoolError, ReorgPoolStorage}, }, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, }; -use serde::{Deserialize, Serialize}; -use std::{sync::Arc, time::Duration}; -use tari_common::configuration::seconds; -use tari_common_types::types::Signature; /// Configuration for the ReorgPool #[derive(Clone, Copy, Deserialize, Serialize)] @@ -113,15 +116,18 @@ impl ReorgPool { #[cfg(test)] mod test { - use super::*; + use std::{thread, time::Duration}; + + use tari_common::configuration::Network; + use crate::{ consensus::ConsensusManagerBuilder, test_helpers::create_orphan_block, transactions::tari_amount::MicroTari, tx, }; - use std::{thread, time::Duration}; - use tari_common::configuration::Network; + + use super::*; #[test] fn test_insert_rlu_and_ttl() { diff --git a/base_layer/core/src/mempool/reorg_pool/reorg_pool_storage.rs b/base_layer/core/src/mempool/reorg_pool/reorg_pool_storage.rs index 4acbe7f4ba..db358cc9f5 100644 --- a/base_layer/core/src/mempool/reorg_pool/reorg_pool_storage.rs +++ b/base_layer/core/src/mempool/reorg_pool/reorg_pool_storage.rs @@ -20,13 +20,20 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{blocks::Block, mempool::reorg_pool::reorg_pool::ReorgPoolConfig, transactions::transaction::Transaction}; -use log::*; use std::sync::Arc; -use tari_common_types::types::Signature; + +use log::*; use tari_crypto::tari_utilities::hex::Hex; use ttl_cache::TtlCache; +use tari_common_types::types::Signature; + +use crate::{ + blocks::Block, + mempool::reorg_pool::reorg_pool::ReorgPoolConfig, + transactions::transaction_entities::transaction::Transaction, +}; + pub const LOG_TARGET: &str = "c::mp::reorg_pool::reorg_pool_storage"; /// Reorg makes use of ReorgPoolStorage to provide thread save access to its TtlCache. diff --git a/base_layer/core/src/mempool/rpc/service.rs b/base_layer/core/src/mempool/rpc/service.rs index 1444107e07..cbf25353f1 100644 --- a/base_layer/core/src/mempool/rpc/service.rs +++ b/base_layer/core/src/mempool/rpc/service.rs @@ -20,14 +20,17 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::convert::{TryFrom, TryInto}; + +use log::*; + +use tari_comms::protocol::rpc::{Request, Response, RpcStatus}; + use crate::{ mempool::{rpc::MempoolService, service::MempoolHandle}, proto, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, }; -use log::*; -use std::convert::{TryFrom, TryInto}; -use tari_comms::protocol::rpc::{Request, Response, RpcStatus}; const LOG_TARGET: &str = "c::mempool::rpc"; diff --git a/base_layer/core/src/mempool/service/handle.rs b/base_layer/core/src/mempool/service/handle.rs index 6eebf2b958..0534d2b4c9 100644 --- a/base_layer/core/src/mempool/service/handle.rs +++ b/base_layer/core/src/mempool/service/handle.rs @@ -20,6 +20,9 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use tari_common_types::types::Signature; +use tari_service_framework::{reply_channel::TrySenderService, Service}; + use crate::{ mempool::{ service::{MempoolRequest, MempoolResponse}, @@ -28,10 +31,8 @@ use crate::{ StatsResponse, TxStorageResponse, }, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, }; -use tari_common_types::types::Signature; -use tari_service_framework::{reply_channel::TrySenderService, Service}; #[derive(Clone)] pub struct MempoolHandle { diff --git a/base_layer/core/src/mempool/service/inbound_handlers.rs b/base_layer/core/src/mempool/service/inbound_handlers.rs index 7f3f90c44a..5e438cc3b2 100644 --- a/base_layer/core/src/mempool/service/inbound_handlers.rs +++ b/base_layer/core/src/mempool/service/inbound_handlers.rs @@ -20,6 +20,14 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::Arc; + +use log::*; +use tari_crypto::tari_utilities::hex::Hex; +use tokio::sync::broadcast; + +use tari_comms::peer_manager::NodeId; + use crate::{ base_node::comms_interface::BlockEvent, chain_storage::BlockAddResult, @@ -30,13 +38,8 @@ use crate::{ MempoolStateEvent, TxStorageResponse, }, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, }; -use log::*; -use std::sync::Arc; -use tari_comms::peer_manager::NodeId; -use tari_crypto::tari_utilities::hex::Hex; -use tokio::sync::broadcast; pub const LOG_TARGET: &str = "c::mp::service::inbound_handlers"; diff --git a/base_layer/core/src/mempool/service/initializer.rs b/base_layer/core/src/mempool/service/initializer.rs index a295daf96a..2f237ffe28 100644 --- a/base_layer/core/src/mempool/service/initializer.rs +++ b/base_layer/core/src/mempool/service/initializer.rs @@ -20,26 +20,12 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - base_node::{comms_interface::LocalNodeCommsInterface, StateMachineHandle}, - mempool::{ - mempool::Mempool, - proto as mempool_proto, - service::{ - inbound_handlers::MempoolInboundHandlers, - local_service::LocalMempoolService, - outbound_interface::OutboundMempoolServiceInterface, - service::{MempoolService, MempoolStreams}, - MempoolHandle, - }, - MempoolServiceConfig, - }, - proto, - transactions::transaction::Transaction, -}; +use std::{convert::TryFrom, sync::Arc}; + use futures::{Stream, StreamExt}; use log::*; -use std::{convert::TryFrom, sync::Arc}; +use tokio::sync::{broadcast, mpsc}; + use tari_comms_dht::Dht; use tari_p2p::{ comms_connector::{PeerMessage, SubscriptionFactory}, @@ -54,7 +40,24 @@ use tari_service_framework::{ ServiceInitializer, ServiceInitializerContext, }; -use tokio::sync::{broadcast, mpsc}; + +use crate::{ + base_node::{comms_interface::LocalNodeCommsInterface, StateMachineHandle}, + mempool::{ + mempool::Mempool, + proto as mempool_proto, + service::{ + inbound_handlers::MempoolInboundHandlers, + local_service::LocalMempoolService, + outbound_interface::OutboundMempoolServiceInterface, + service::{MempoolService, MempoolStreams}, + MempoolHandle, + }, + MempoolServiceConfig, + }, + proto, + transactions::transaction_entities::transaction::Transaction, +}; const LOG_TARGET: &str = "c::bn::mempool_service::initializer"; const SUBSCRIPTION_LABEL: &str = "Mempool"; diff --git a/base_layer/core/src/mempool/service/local_service.rs b/base_layer/core/src/mempool/service/local_service.rs index 05f3ac6779..76979a4899 100644 --- a/base_layer/core/src/mempool/service/local_service.rs +++ b/base_layer/core/src/mempool/service/local_service.rs @@ -20,6 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use tokio::sync::broadcast; + +use tari_common_types::types::Signature; +use tari_service_framework::{reply_channel::SenderService, Service}; + use crate::{ mempool::{ service::{MempoolRequest, MempoolResponse, MempoolServiceError}, @@ -28,11 +33,8 @@ use crate::{ StatsResponse, TxStorageResponse, }, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, }; -use tari_common_types::types::Signature; -use tari_service_framework::{reply_channel::SenderService, Service}; -use tokio::sync::broadcast; pub type LocalMempoolRequester = SenderService>; @@ -116,14 +118,16 @@ impl LocalMempoolService { #[cfg(test)] mod test { + use futures::StreamExt; + use tokio::{sync::broadcast, task}; + + use tari_service_framework::reply_channel::{unbounded, Receiver}; + use crate::mempool::{ service::{local_service::LocalMempoolService, MempoolRequest, MempoolResponse}, MempoolServiceError, StatsResponse, }; - use futures::StreamExt; - use tari_service_framework::reply_channel::{unbounded, Receiver}; - use tokio::{sync::broadcast, task}; pub type LocalMempoolRequestStream = Receiver>; diff --git a/base_layer/core/src/mempool/service/outbound_interface.rs b/base_layer/core/src/mempool/service/outbound_interface.rs index 8606a8ce02..45437d35fb 100644 --- a/base_layer/core/src/mempool/service/outbound_interface.rs +++ b/base_layer/core/src/mempool/service/outbound_interface.rs @@ -20,19 +20,21 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use log::*; +use tokio::sync::mpsc::UnboundedSender; + +use tari_common_types::types::Signature; +use tari_comms::peer_manager::NodeId; +use tari_service_framework::{reply_channel::SenderService, Service}; + use crate::{ mempool::{ service::{MempoolRequest, MempoolResponse, MempoolServiceError}, StatsResponse, TxStorageResponse, }, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, }; -use log::*; -use tari_common_types::types::Signature; -use tari_comms::peer_manager::NodeId; -use tari_service_framework::{reply_channel::SenderService, Service}; -use tokio::sync::mpsc::UnboundedSender; pub const LOG_TARGET: &str = "c::mp::service::outbound_interface"; diff --git a/base_layer/core/src/mempool/service/request.rs b/base_layer/core/src/mempool/service/request.rs index a6d6910024..30296c2a01 100644 --- a/base_layer/core/src/mempool/service/request.rs +++ b/base_layer/core/src/mempool/service/request.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::transactions::transaction::Transaction; +use crate::transactions::transaction_entities::transaction::Transaction; use core::fmt::{Display, Error, Formatter}; use serde::{Deserialize, Serialize}; use tari_common_types::{types::Signature, waiting_requests::RequestKey}; diff --git a/base_layer/core/src/mempool/service/service.rs b/base_layer/core/src/mempool/service/service.rs index 137dd9d0a0..28f65fcdda 100644 --- a/base_layer/core/src/mempool/service/service.rs +++ b/base_layer/core/src/mempool/service/service.rs @@ -20,6 +20,27 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::{convert::TryInto, sync::Arc, time::Duration}; + +use futures::{pin_mut, stream::StreamExt, Stream}; +use log::*; +use rand::rngs::OsRng; +use tari_crypto::tari_utilities::hex::Hex; +use tokio::{ + sync::{mpsc, oneshot::Sender as OneshotSender}, + task, +}; + +use tari_common_types::waiting_requests::{generate_request_key, RequestKey, WaitingRequests}; +use tari_comms::peer_manager::NodeId; +use tari_comms_dht::{ + domain_message::OutboundDomainMessage, + envelope::NodeDestination, + outbound::{DhtOutboundError, OutboundEncryption, OutboundMessageRequester}, +}; +use tari_p2p::{domain_message::DomainMessage, tari_message::TariMessageType}; +use tari_service_framework::{reply_channel, reply_channel::RequestContext}; + use crate::{ base_node::{ comms_interface::{BlockEvent, BlockEventReceiver}, @@ -36,25 +57,7 @@ use crate::{ MempoolServiceConfig, }, proto, - transactions::transaction::Transaction, -}; -use futures::{pin_mut, stream::StreamExt, Stream}; -use log::*; -use rand::rngs::OsRng; -use std::{convert::TryInto, sync::Arc, time::Duration}; -use tari_common_types::waiting_requests::{generate_request_key, RequestKey, WaitingRequests}; -use tari_comms::peer_manager::NodeId; -use tari_comms_dht::{ - domain_message::OutboundDomainMessage, - envelope::NodeDestination, - outbound::{DhtOutboundError, OutboundEncryption, OutboundMessageRequester}, -}; -use tari_crypto::tari_utilities::hex::Hex; -use tari_p2p::{domain_message::DomainMessage, tari_message::TariMessageType}; -use tari_service_framework::{reply_channel, reply_channel::RequestContext}; -use tokio::{ - sync::{mpsc, oneshot::Sender as OneshotSender}, - task, + transactions::transaction_entities::transaction::Transaction, }; const LOG_TARGET: &str = "c::mempool::service::service"; diff --git a/base_layer/core/src/mempool/sync_protocol/mod.rs b/base_layer/core/src/mempool/sync_protocol/mod.rs index dc133f744d..c5d3bd4e3a 100644 --- a/base_layer/core/src/mempool/sync_protocol/mod.rs +++ b/base_layer/core/src/mempool/sync_protocol/mod.rs @@ -63,23 +63,6 @@ //! | END | //! ``` -#[cfg(test)] -mod test; - -mod error; -use error::MempoolProtocolError; - -mod initializer; -pub use initializer::MempoolSyncInitializer; - -use crate::{ - mempool::{async_mempool, proto, Mempool, MempoolServiceConfig}, - proto as shared_proto, - transactions::transaction::Transaction, -}; -use futures::{stream, SinkExt, Stream, StreamExt}; -use log::*; -use prost::Message; use std::{ convert::TryFrom, iter, @@ -88,6 +71,19 @@ use std::{ Arc, }, }; + +use futures::{stream, SinkExt, Stream, StreamExt}; +use log::*; +use prost::Message; +use tari_crypto::tari_utilities::{hex::Hex, ByteArray}; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + sync::Semaphore, + task, +}; + +use error::MempoolProtocolError; +pub use initializer::MempoolSyncInitializer; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityEventRx}, framing, @@ -98,13 +94,19 @@ use tari_comms::{ Bytes, PeerConnection, }; -use tari_crypto::tari_utilities::{hex::Hex, ByteArray}; -use tokio::{ - io::{AsyncRead, AsyncWrite}, - sync::Semaphore, - task, + +use crate::{ + mempool::{async_mempool, proto, Mempool, MempoolServiceConfig}, + proto as shared_proto, + transactions::transaction_entities::transaction::Transaction, }; +#[cfg(test)] +mod test; + +mod error; +mod initializer; + const MAX_FRAME_SIZE: usize = 3 * 1024 * 1024; // 3 MiB const LOG_TARGET: &str = "c::mempool::sync_protocol"; diff --git a/base_layer/core/src/mempool/sync_protocol/test.rs b/base_layer/core/src/mempool/sync_protocol/test.rs index 8f094e635f..031e1cd0a9 100644 --- a/base_layer/core/src/mempool/sync_protocol/test.rs +++ b/base_layer/core/src/mempool/sync_protocol/test.rs @@ -20,19 +20,15 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - consensus::ConsensusManager, - mempool::{ - async_mempool, - proto, - sync_protocol::{MempoolPeerProtocol, MempoolSyncProtocol, MAX_FRAME_SIZE, MEMPOOL_SYNC_PROTOCOL}, - Mempool, - }, - transactions::{tari_amount::uT, test_helpers::create_tx, transaction::Transaction}, - validation::mocks::MockValidator, -}; -use futures::{Sink, SinkExt, Stream, StreamExt}; use std::{fmt, io, iter::repeat_with, sync::Arc}; + +use futures::{Sink, SinkExt, Stream, StreamExt}; +use tari_crypto::tari_utilities::ByteArray; +use tokio::{ + sync::{broadcast, mpsc}, + task, +}; + use tari_common::configuration::Network; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityEventTx}, @@ -45,10 +41,17 @@ use tari_comms::{ Bytes, BytesMut, }; -use tari_crypto::tari_utilities::ByteArray; -use tokio::{ - sync::{broadcast, mpsc}, - task, + +use crate::{ + consensus::ConsensusManager, + mempool::{ + async_mempool, + proto, + sync_protocol::{MempoolPeerProtocol, MempoolSyncProtocol, MAX_FRAME_SIZE, MEMPOOL_SYNC_PROTOCOL}, + Mempool, + }, + transactions::{tari_amount::uT, test_helpers::create_tx, transaction_entities::transaction::Transaction}, + validation::mocks::MockValidator, }; pub fn create_transactions(n: usize) -> Vec { diff --git a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs index 5e979b296b..bdf79e3bb8 100644 --- a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs +++ b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs @@ -27,7 +27,7 @@ use crate::{ priority::{FeePriority, PrioritizedTransaction}, unconfirmed_pool::UnconfirmedPoolError, }, - transactions::{transaction::Transaction, weight::TransactionWeight}, + transactions::{transaction_entities::Transaction, weight::TransactionWeight}, }; use log::*; use serde::{Deserialize, Serialize}; @@ -487,7 +487,7 @@ mod test { fee::Fee, tari_amount::MicroTari, test_helpers::{TestParams, UtxoTestParams}, - transaction::KernelFeatures, + transaction_entities::KernelFeatures, weight::TransactionWeight, CryptoFactories, SenderTransactionProtocol, diff --git a/base_layer/core/src/proto/transaction.rs b/base_layer/core/src/proto/transaction.rs index 93bbfbd46c..4ee0f6e7c1 100644 --- a/base_layer/core/src/proto/transaction.rs +++ b/base_layer/core/src/proto/transaction.rs @@ -22,29 +22,32 @@ //! Impls for transaction proto +use std::convert::{TryFrom, TryInto}; + +use tari_crypto::{ + script::{ExecutionStack, TariScript}, + tari_utilities::{ByteArray, ByteArrayError}, +}; + +use tari_common_types::types::{BlindingFactor, BulletRangeProof, Commitment, PublicKey}; + use crate::{ proto, tari_utilities::convert::try_convert_all, transactions::{ aggregated_body::AggregateBody, tari_amount::MicroTari, - transaction::{ + transaction_entities::{ + output_features::OutputFeatures, + transaction::Transaction, + transaction_input::TransactionInput, + transaction_kernel::TransactionKernel, + transaction_output::TransactionOutput, KernelFeatures, - OutputFeatures, OutputFlags, - Transaction, - TransactionInput, - TransactionKernel, - TransactionOutput, }, }, }; -use std::convert::{TryFrom, TryInto}; -use tari_common_types::types::{BlindingFactor, BulletRangeProof, Commitment, PublicKey}; -use tari_crypto::{ - script::{ExecutionStack, TariScript}, - tari_utilities::{ByteArray, ByteArrayError}, -}; //---------------------------------- TransactionKernel --------------------------------------------// diff --git a/base_layer/core/src/test_helpers/block_spec.rs b/base_layer/core/src/test_helpers/block_spec.rs index c1f8fa19ae..2cd7ae721f 100644 --- a/base_layer/core/src/test_helpers/block_spec.rs +++ b/base_layer/core/src/test_helpers/block_spec.rs @@ -22,7 +22,7 @@ use crate::{ proof_of_work::Difficulty, - transactions::{tari_amount::MicroTari, transaction::Transaction}, + transactions::{tari_amount::MicroTari, transaction_entities::transaction::Transaction}, }; pub struct BlockSpecs { diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index 66afe677c9..462c972621 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -20,7 +20,24 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use super::{create_block, mine_to_difficulty}; +use std::{ + collections::HashMap, + fs, + ops::Deref, + path::{Path, PathBuf}, + sync::Arc, +}; + +use croaring::Bitmap; + +use tari_common::configuration::Network; +use tari_common_types::{ + chain_metadata::ChainMetadata, + types::{Commitment, HashOutput, Signature}, +}; +use tari_storage::lmdb_store::LMDBConfig; +use tari_test_utils::paths::create_temporary_data_path; + use crate::{ blocks::{ genesis_block::get_weatherwax_genesis_block, @@ -55,7 +72,11 @@ use crate::{ proof_of_work::{AchievedTargetDifficulty, Difficulty, PowAlgorithm}, test_helpers::{block_spec::BlockSpecs, create_consensus_rules, BlockSpec}, transactions::{ - transaction::{TransactionInput, TransactionKernel, UnblindedOutput}, + transaction_entities::{ + transaction_input::TransactionInput, + transaction_kernel::TransactionKernel, + unblinded_output::UnblindedOutput, + }, CryptoFactories, }, validation::{ @@ -64,21 +85,8 @@ use crate::{ DifficultyCalculator, }, }; -use croaring::Bitmap; -use std::{ - collections::HashMap, - fs, - ops::Deref, - path::{Path, PathBuf}, - sync::Arc, -}; -use tari_common::configuration::Network; -use tari_common_types::{ - chain_metadata::ChainMetadata, - types::{Commitment, HashOutput, Signature}, -}; -use tari_storage::lmdb_store::LMDBConfig; -use tari_test_utils::paths::create_temporary_data_path; + +use super::{create_block, mine_to_difficulty}; /// Create a new blockchain database containing no blocks. pub fn create_new_blockchain() -> BlockchainDatabase { diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index 9523fe0a3e..93653f7e6f 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -23,11 +23,14 @@ //! Common test helper functions that are small and useful enough to be included in the main crate, rather than the //! integration test folder. -#[macro_use] -mod block_spec; -pub use block_spec::{BlockSpec, BlockSpecs}; +use std::{iter, path::Path, sync::Arc}; -pub mod blockchain; +use rand::{distributions::Alphanumeric, Rng}; + +pub use block_spec::{BlockSpec, BlockSpecs}; +use tari_common::configuration::Network; +use tari_comms::PeerManager; +use tari_storage::{lmdb_store::LMDBBuilder, LMDBWrapper}; use crate::{ blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainHeader}, @@ -35,16 +38,15 @@ use crate::{ crypto::tari_utilities::Hashable, proof_of_work::{sha3_difficulty, AchievedTargetDifficulty, Difficulty}, transactions::{ - transaction::{Transaction, UnblindedOutput}, + transaction_entities::{transaction::Transaction, unblinded_output::UnblindedOutput}, CoinbaseBuilder, CryptoFactories, }, }; -use rand::{distributions::Alphanumeric, Rng}; -use std::{iter, path::Path, sync::Arc}; -use tari_common::configuration::Network; -use tari_comms::PeerManager; -use tari_storage::{lmdb_store::LMDBBuilder, LMDBWrapper}; + +#[macro_use] +mod block_spec; +pub mod blockchain; pub fn create_consensus_rules() -> ConsensusManager { ConsensusManager::builder(Network::LocalNet).build() diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index f32fa6d4b8..1592115d7b 100644 --- a/base_layer/core/src/transactions/aggregated_body.rs +++ b/base_layer/core/src/transactions/aggregated_body.rs @@ -24,7 +24,7 @@ use crate::{ transactions::{ crypto_factories::CryptoFactories, tari_amount::MicroTari, - transaction::{ + transaction_entities::{ KernelFeatures, KernelSum, OutputFlags, diff --git a/base_layer/core/src/transactions/coinbase_builder.rs b/base_layer/core/src/transactions/coinbase_builder.rs index c34153c2bd..e6e3450465 100644 --- a/base_layer/core/src/transactions/coinbase_builder.rs +++ b/base_layer/core/src/transactions/coinbase_builder.rs @@ -39,7 +39,7 @@ use crate::{ transactions::{ crypto_factories::CryptoFactories, tari_amount::{uT, MicroTari}, - transaction::{ + transaction_entities::{ KernelBuilder, KernelFeatures, OutputFeatures, @@ -251,7 +251,7 @@ mod test { crypto_factories::CryptoFactories, tari_amount::uT, test_helpers::TestParams, - transaction::{KernelFeatures, OutputFeatures, OutputFlags, TransactionError}, + transaction_entities::{KernelFeatures, OutputFeatures, OutputFlags, TransactionError}, transaction_protocol::RewindData, CoinbaseBuilder, }, diff --git a/base_layer/core/src/transactions/mod.rs b/base_layer/core/src/transactions/mod.rs index ee63b708c2..474306cbd1 100644 --- a/base_layer/core/src/transactions/mod.rs +++ b/base_layer/core/src/transactions/mod.rs @@ -8,7 +8,7 @@ pub use coinbase_builder::{CoinbaseBuildError, CoinbaseBuilder}; pub mod fee; pub mod tari_amount; -pub mod transaction; +pub mod transaction_entities; mod format_currency; pub use format_currency::format_currency; diff --git a/base_layer/core/src/transactions/test_helpers.rs b/base_layer/core/src/transactions/test_helpers.rs index 7a8c9dbf76..9037366d4d 100644 --- a/base_layer/core/src/transactions/test_helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -26,7 +26,7 @@ use crate::{ crypto_factories::CryptoFactories, fee::Fee, tari_amount::MicroTari, - transaction::{ + transaction_entities::{ KernelBuilder, KernelFeatures, OutputFeatures, @@ -305,7 +305,7 @@ macro_rules! txn_schema { to:$outputs, fee:$fee, lock:0, - features: $crate::transactions::transaction::OutputFeatures::default() + features: $crate::transactions::transaction_entities::OutputFeatures::default() ) }; diff --git a/base_layer/core/src/transactions/transaction.rs b/base_layer/core/src/transactions/transaction.rs deleted file mode 100644 index c399e6473c..0000000000 --- a/base_layer/core/src/transactions/transaction.rs +++ /dev/null @@ -1,1870 +0,0 @@ -// Copyright 2018 The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// 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 -// -// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, -// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. - -use tari_common_types::types::HashOutput; -use tari_crypto::script::ScriptContext; - -use crate::{ - consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusEncodingWrapper}, - transactions::{ - aggregated_body::AggregateBody, - crypto_factories::CryptoFactories, - tari_amount::{uT, MicroTari}, - transaction_protocol::{build_challenge, RewindData, TransactionMetadata}, - weight::TransactionWeight, - }, -}; -use blake2::Digest; -use integer_encoding::{VarInt, VarIntReader, VarIntWriter}; -use rand::rngs::OsRng; -use serde::{Deserialize, Serialize}; -use std::{ - cmp::{max, min, Ordering}, - fmt, - fmt::{Display, Formatter}, - hash::{Hash, Hasher}, - io, - io::{Read, Write}, - ops::{Add, Shl}, -}; -use tari_common_types::types::{ - BlindingFactor, - Challenge, - ComSignature, - Commitment, - CommitmentFactory, - HashDigest, - MessageHash, - PrivateKey, - PublicKey, - RangeProof, - RangeProofService, - Signature, -}; -use tari_crypto::{ - commitment::HomomorphicCommitmentFactory, - keys::{PublicKey as PublicKeyTrait, SecretKey}, - range_proof::{ - FullRewindResult as CryptoFullRewindResult, - RangeProofError, - RangeProofService as RangeProofServiceTrait, - RewindResult as CryptoRewindResult, - REWIND_USER_MESSAGE_LENGTH, - }, - ristretto::pedersen::PedersenCommitmentFactory, - script::{ExecutionStack, ScriptError, StackItem, TariScript}, - signatures::CommitmentSignatureError, - tari_utilities::{hex::Hex, message_format::MessageFormat, ByteArray, Hashable}, -}; -use thiserror::Error; - -// Tx_weight(inputs(12,500), outputs(500), kernels(1)) = 126,510 still well enough below block weight of 127,795 -pub const MAX_TRANSACTION_INPUTS: usize = 12_500; -pub const MAX_TRANSACTION_OUTPUTS: usize = 500; -pub const MAX_TRANSACTION_RECIPIENTS: usize = 15; - -//-------------------------------------- Output features --------------------------------------------------// - -bitflags! { - /// Options for a kernel's structure or use. - /// TODO: expand to accommodate Tari DAN transaction types, such as namespace and validator node registrations - #[derive(Deserialize, Serialize)] - pub struct KernelFeatures: u8 { - /// Coinbase transaction - const COINBASE_KERNEL = 1u8; - } -} - -impl KernelFeatures { - pub fn create_coinbase() -> KernelFeatures { - KernelFeatures::COINBASE_KERNEL - } -} - -/// Options for UTXO's -#[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq)] -pub struct OutputFeatures { - /// Flags are the feature flags that differentiate between outputs, eg Coinbase all of which has different rules - pub flags: OutputFlags, - /// the maturity of the specific UTXO. This is the min lock height at which an UTXO can be spent. Coinbase UTXO - /// require a min maturity of the Coinbase_lock_height, this should be checked on receiving new blocks. - pub maturity: u64, -} - -impl OutputFeatures { - /// The version number to use in consensus encoding. In future, this value could be dynamic. - const CONSENSUS_ENCODING_VERSION: u8 = 0; - - /// Encodes output features using deprecated bincode encoding - pub fn to_v1_bytes(&self) -> Vec { - // unreachable panic: serialized_size is infallible because it uses DefaultOptions - let encode_size = bincode::serialized_size(self).expect("unreachable"); - let mut buf = Vec::with_capacity(encode_size as usize); - // unreachable panic: Vec's Write impl is infallible - bincode::serialize_into(&mut buf, self).expect("unreachable"); - buf - } - - /// Encodes output features using consensus encoding - pub fn to_consensus_bytes(&self) -> Vec { - let mut buf = Vec::with_capacity(self.consensus_encode_exact_size()); - // unreachable panic: Vec's Write impl is infallible - self.consensus_encode(&mut buf).expect("unreachable"); - buf - } - - pub fn create_coinbase(maturity_height: u64) -> OutputFeatures { - OutputFeatures { - flags: OutputFlags::COINBASE_OUTPUT, - maturity: maturity_height, - } - } - - /// Create an `OutputFeatures` with the given maturity and all other values at their default setting - pub fn with_maturity(maturity: u64) -> OutputFeatures { - OutputFeatures { - maturity, - ..OutputFeatures::default() - } - } -} - -impl ConsensusEncoding for OutputFeatures { - fn consensus_encode(&self, writer: &mut W) -> Result { - let mut written = writer.write_varint(Self::CONSENSUS_ENCODING_VERSION)?; - written += writer.write_varint(self.maturity)?; - written += self.flags.consensus_encode(writer)?; - Ok(written) - } -} -impl ConsensusEncodingSized for OutputFeatures { - fn consensus_encode_exact_size(&self) -> usize { - Self::CONSENSUS_ENCODING_VERSION.required_space() + - self.flags.consensus_encode_exact_size() + - self.maturity.required_space() - } -} - -impl ConsensusDecoding for OutputFeatures { - fn consensus_decode(reader: &mut R) -> Result { - // Changing the order of these operations is consensus breaking - let version = reader.read_varint::()?; - if version != Self::CONSENSUS_ENCODING_VERSION { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!( - "Invalid version. Expected {} but got {}", - Self::CONSENSUS_ENCODING_VERSION, - version - ), - )); - } - // Decode safety: read_varint will stop reading the varint after 10 bytes - let maturity = reader.read_varint()?; - let flags = OutputFlags::consensus_decode(reader)?; - Ok(Self { flags, maturity }) - } -} - -impl Default for OutputFeatures { - fn default() -> Self { - OutputFeatures { - flags: OutputFlags::empty(), - maturity: 0, - } - } -} - -impl PartialOrd for OutputFeatures { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for OutputFeatures { - fn cmp(&self, other: &Self) -> Ordering { - self.maturity.cmp(&other.maturity) - } -} - -impl Display for OutputFeatures { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "OutputFeatures: Flags = {:?}, Maturity = {}", - self.flags, self.maturity - ) - } -} - -bitflags! { - #[derive(Deserialize, Serialize)] - pub struct OutputFlags: u8 { - /// Output is a coinbase output, must not be spent until maturity - const COINBASE_OUTPUT = 0b0000_0001; - } -} - -impl ConsensusEncoding for OutputFlags { - fn consensus_encode(&self, writer: &mut W) -> Result { - writer.write(&self.bits.to_le_bytes()) - } -} - -impl ConsensusEncodingSized for OutputFlags { - fn consensus_encode_exact_size(&self) -> usize { - 1 - } -} - -impl ConsensusDecoding for OutputFlags { - fn consensus_decode(reader: &mut R) -> Result { - let mut buf = [0u8; 1]; - reader.read_exact(&mut buf)?; - // SAFETY: we have 3 options here: - // 1. error if unsupported flags are used, meaning that every new flag will be a hard fork - // 2. truncate unsupported flags, means different hashes will be produced for the same block - // 3. ignore unsupported flags, which could be set at any time and persisted to the blockchain. - // Once those flags are defined at some point in the future, depending on the functionality of the flag, - // a consensus rule may be needed that ignores flags prior to a given block height. - // Option 3 is used here - Ok(unsafe { OutputFlags::from_bits_unchecked(u8::from_le_bytes(buf)) }) - } -} - -//---------------------------------------- TransactionError ----------------------------------------------------// - -#[derive(Clone, Debug, PartialEq, Error, Deserialize, Serialize)] -pub enum TransactionError { - #[error("Error validating the transaction: {0}")] - ValidationError(String), - #[error("Signature is invalid: {0}")] - InvalidSignatureError(String), - #[error("Transaction kernel does not contain a signature")] - NoSignatureError, - #[error("A range proof construction or verification has produced an error: {0}")] - RangeProofError(#[from] RangeProofError), - #[error("An error occurred while performing a commitment signature: {0}")] - SigningError(#[from] CommitmentSignatureError), - #[error("Invalid kernel in body")] - InvalidKernel, - #[error("Invalid coinbase in body")] - InvalidCoinbase, - #[error("Invalid coinbase maturity in body")] - InvalidCoinbaseMaturity, - #[error("More than one coinbase in body")] - MoreThanOneCoinbase, - #[error("No coinbase in body")] - NoCoinbase, - #[error("Input maturity not reached")] - InputMaturity, - #[error("Tari script error : {0}")] - ScriptError(#[from] ScriptError), - #[error("Error performing conversion: {0}")] - ConversionError(String), - #[error("The script offset in body does not balance")] - ScriptOffset, - #[error("Error executing script: {0}")] - ScriptExecutionError(String), -} - -//----------------------------------------- UnblindedOutput ----------------------------------------------------// - -/// An unblinded output is one where the value and spending key (blinding factor) are known. This can be used to -/// build both inputs and outputs (every input comes from an output) -// TODO: Try to get rid of 'Serialize' and 'Deserialize' traits here; see related comment at 'struct RawTransactionInfo' -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct UnblindedOutput { - pub value: MicroTari, - pub spending_key: BlindingFactor, - pub features: OutputFeatures, - pub script: TariScript, - pub input_data: ExecutionStack, - pub script_private_key: PrivateKey, - pub sender_offset_public_key: PublicKey, - pub metadata_signature: ComSignature, - pub script_lock_height: u64, -} - -impl UnblindedOutput { - /// Creates a new un-blinded output - #[allow(clippy::too_many_arguments)] - pub fn new( - value: MicroTari, - spending_key: BlindingFactor, - features: OutputFeatures, - script: TariScript, - input_data: ExecutionStack, - script_private_key: PrivateKey, - sender_offset_public_key: PublicKey, - metadata_signature: ComSignature, - script_lock_height: u64, - ) -> UnblindedOutput { - UnblindedOutput { - value, - spending_key, - features, - script, - input_data, - script_private_key, - sender_offset_public_key, - metadata_signature, - script_lock_height, - } - } - - /// Commits an UnblindedOutput into a Transaction input - pub fn as_transaction_input(&self, factory: &CommitmentFactory) -> Result { - let commitment = factory.commit(&self.spending_key, &self.value.into()); - let script_nonce_a = PrivateKey::random(&mut OsRng); - let script_nonce_b = PrivateKey::random(&mut OsRng); - let nonce_commitment = factory.commit(&script_nonce_b, &script_nonce_a); - - let challenge = TransactionInput::build_script_challenge( - &nonce_commitment, - &self.script, - &self.input_data, - &PublicKey::from_secret_key(&self.script_private_key), - &commitment, - ); - let script_signature = ComSignature::sign( - self.value.into(), - &self.script_private_key + &self.spending_key, - script_nonce_a, - script_nonce_b, - &challenge, - factory, - ) - .map_err(|_| TransactionError::InvalidSignatureError("Generating script signature".to_string()))?; - - Ok(TransactionInput { - features: self.features.clone(), - commitment, - script: self.script.clone(), - input_data: self.input_data.clone(), - script_signature, - sender_offset_public_key: self.sender_offset_public_key.clone(), - }) - } - - pub fn as_transaction_output(&self, factories: &CryptoFactories) -> Result { - if factories.range_proof.range() < 64 && self.value >= MicroTari::from(1u64.shl(&factories.range_proof.range())) - { - return Err(TransactionError::ValidationError( - "Value provided is outside the range allowed by the range proof".into(), - )); - } - let commitment = factories.commitment.commit(&self.spending_key, &self.value.into()); - let output = TransactionOutput { - features: self.features.clone(), - commitment, - proof: RangeProof::from_bytes( - &factories - .range_proof - .construct_proof(&self.spending_key, self.value.into())?, - ) - .map_err(|_| TransactionError::RangeProofError(RangeProofError::ProofConstructionError))?, - script: self.script.clone(), - sender_offset_public_key: self.sender_offset_public_key.clone(), - metadata_signature: self.metadata_signature.clone(), - }; - - Ok(output) - } - - pub fn as_rewindable_transaction_output( - &self, - factories: &CryptoFactories, - rewind_data: &RewindData, - ) -> Result { - if factories.range_proof.range() < 64 && self.value >= MicroTari::from(1u64.shl(&factories.range_proof.range())) - { - return Err(TransactionError::ValidationError( - "Value provided is outside the range allowed by the range proof".into(), - )); - } - let commitment = factories.commitment.commit(&self.spending_key, &self.value.into()); - - let proof_bytes = factories.range_proof.construct_proof_with_rewind_key( - &self.spending_key, - self.value.into(), - &rewind_data.rewind_key, - &rewind_data.rewind_blinding_key, - &rewind_data.proof_message, - )?; - - let proof = RangeProof::from_bytes(&proof_bytes) - .map_err(|_| TransactionError::RangeProofError(RangeProofError::ProofConstructionError))?; - - let output = TransactionOutput { - features: self.features.clone(), - commitment, - proof, - script: self.script.clone(), - sender_offset_public_key: self.sender_offset_public_key.clone(), - metadata_signature: self.metadata_signature.clone(), - }; - - Ok(output) - } - - pub fn metadata_byte_size(&self) -> usize { - self.features.consensus_encode_exact_size() + - ConsensusEncodingWrapper::wrap(&self.script).consensus_encode_exact_size() - } -} - -// These implementations are used for order these outputs for UTXO selection which will be done by comparing the values -impl Eq for UnblindedOutput {} - -impl PartialEq for UnblindedOutput { - fn eq(&self, other: &UnblindedOutput) -> bool { - self.value == other.value - } -} - -impl Hash for UnblindedOutput { - fn hash(&self, state: &mut H) { - self.value.hash(state); - } -} - -impl PartialOrd for UnblindedOutput { - fn partial_cmp(&self, other: &Self) -> Option { - self.value.partial_cmp(&other.value) - } -} - -impl Ord for UnblindedOutput { - fn cmp(&self, other: &Self) -> Ordering { - self.value.cmp(&other.value) - } -} - -//---------------------------------------- TransactionInput ----------------------------------------------------// - -/// A transaction input. -/// -/// Primarily a reference to an output being spent by the transaction. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TransactionInput { - /// The features of the output being spent. We will check maturity for all outputs. - pub features: OutputFeatures, - /// The commitment referencing the output being spent. - pub commitment: Commitment, - /// The serialised script - pub script: TariScript, - /// The script input data, if any - pub input_data: ExecutionStack, - /// A signature with k_s, signing the script, input data, and mined height - pub script_signature: ComSignature, - /// The offset public key, K_O - pub sender_offset_public_key: PublicKey, -} - -/// An input for a transaction that spends an existing output -impl TransactionInput { - /// Create a new Transaction Input - pub fn new( - features: OutputFeatures, - commitment: Commitment, - script: TariScript, - input_data: ExecutionStack, - script_signature: ComSignature, - sender_offset_public_key: PublicKey, - ) -> TransactionInput { - TransactionInput { - features, - commitment, - script, - input_data, - script_signature, - sender_offset_public_key, - } - } - - pub fn build_script_challenge( - nonce_commitment: &Commitment, - script: &TariScript, - input_data: &ExecutionStack, - script_public_key: &PublicKey, - commitment: &Commitment, - ) -> Vec { - Challenge::new() - .chain(nonce_commitment.as_bytes()) - .chain(script.as_bytes().as_slice()) - .chain(input_data.as_bytes().as_slice()) - .chain(script_public_key.as_bytes()) - .chain(commitment.as_bytes()) - .finalize() - .to_vec() - } - - /// Accessor method for the commitment contained in an input - pub fn commitment(&self) -> &Commitment { - &self.commitment - } - - /// Checks if the given un-blinded input instance corresponds to this blinded Transaction Input - pub fn opened_by(&self, input: &UnblindedOutput, factory: &CommitmentFactory) -> bool { - factory.open(&input.spending_key, &input.value.into(), &self.commitment) - } - - /// This will check if the input and the output is the same transactional output by looking at the commitment and - /// features and script. This will ignore all other output and input fields - pub fn is_equal_to(&self, output: &TransactionOutput) -> bool { - self.output_hash() == output.hash() - } - - /// This will run the script contained in the TransactionInput, returning either a script error or the resulting - /// public key. - pub fn run_script(&self, context: Option) -> Result { - let context = context.unwrap_or_default(); - match self.script.execute_with_context(&self.input_data, &context)? { - StackItem::PublicKey(pubkey) => Ok(pubkey), - _ => Err(TransactionError::ScriptExecutionError( - "The script executed successfully but it did not leave a public key on the stack".to_string(), - )), - } - } - - pub fn validate_script_signature( - &self, - public_script_key: &PublicKey, - factory: &CommitmentFactory, - ) -> Result<(), TransactionError> { - let challenge = TransactionInput::build_script_challenge( - self.script_signature.public_nonce(), - &self.script, - &self.input_data, - public_script_key, - &self.commitment, - ); - if self - .script_signature - .verify_challenge(&(&self.commitment + public_script_key), &challenge, factory) - { - Ok(()) - } else { - Err(TransactionError::InvalidSignatureError( - "Verifying script signature".to_string(), - )) - } - } - - /// This will run the script and verify the script signature. If its valid, it will return the resulting public key - /// from the script. - pub fn run_and_verify_script( - &self, - factory: &CommitmentFactory, - context: Option, - ) -> Result { - let key = self.run_script(context)?; - self.validate_script_signature(&key, factory)?; - Ok(key) - } - - /// Returns true if this input is mature at the given height, otherwise false - pub fn is_mature_at(&self, block_height: u64) -> bool { - self.features.maturity <= block_height - } - - /// Returns the hash of the output data contained in this input. - /// This hash matches the hash of a transaction output that this input spends. - pub fn output_hash(&self) -> Vec { - HashDigest::new() - .chain(self.features.to_v1_bytes()) - .chain(self.commitment.as_bytes()) - .chain(self.script.as_bytes()) - .finalize() - .to_vec() - } -} - -/// Implement the canonical hashing function for TransactionInput for use in ordering -impl Hashable for TransactionInput { - fn hash(&self) -> Vec { - HashDigest::new() - .chain(self.features.to_v1_bytes()) - .chain(self.commitment.as_bytes()) - .chain(self.script.as_bytes()) - .chain(self.sender_offset_public_key.as_bytes()) - .chain(self.script_signature.u().as_bytes()) - .chain(self.script_signature.v().as_bytes()) - .chain(self.script_signature.public_nonce().as_bytes()) - .chain(self.input_data.as_bytes()) - .finalize() - .to_vec() - } -} - -impl Display for TransactionInput { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - fmt, - "{} [{:?}], Script hash: ({}), Offset_Pubkey: ({})", - self.commitment.to_hex(), - self.features, - self.script, - self.sender_offset_public_key.to_hex() - ) - } -} - -impl PartialOrd for TransactionInput { - fn partial_cmp(&self, other: &Self) -> Option { - self.commitment.partial_cmp(&other.commitment) - } -} - -impl Ord for TransactionInput { - fn cmp(&self, other: &Self) -> Ordering { - self.commitment.cmp(&other.commitment) - } -} - -//---------------------------------------- TransactionOutput ----------------------------------------------------// - -/// Output for a transaction, defining the new ownership of coins that are being transferred. The commitment is a -/// blinded value for the output while the range proof guarantees the commitment includes a positive value without -/// overflow and the ownership of the private key. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TransactionOutput { - /// Options for an output's structure or use - pub features: OutputFeatures, - /// The homomorphic commitment representing the output amount - pub commitment: Commitment, - /// A proof that the commitment is in the right range - pub proof: RangeProof, - /// The script that will be executed when spending this output - pub script: TariScript, - /// Tari script offset pubkey, K_O - pub sender_offset_public_key: PublicKey, - /// UTXO signature with the script offset private key, k_O - pub metadata_signature: ComSignature, -} - -/// An output for a transaction, includes a range proof and Tari script metadata -impl TransactionOutput { - /// Create new Transaction Output - pub fn new( - features: OutputFeatures, - commitment: Commitment, - proof: RangeProof, - script: TariScript, - sender_offset_public_key: PublicKey, - metadata_signature: ComSignature, - ) -> TransactionOutput { - TransactionOutput { - features, - commitment, - proof, - script, - sender_offset_public_key, - metadata_signature, - } - } - - /// Accessor method for the commitment contained in an output - pub fn commitment(&self) -> &Commitment { - &self.commitment - } - - /// Accessor method for the range proof contained in an output - pub fn proof(&self) -> &RangeProof { - &self.proof - } - - /// Verify that range proof is valid - pub fn verify_range_proof(&self, prover: &RangeProofService) -> Result { - Ok(prover.verify(&self.proof.0, &self.commitment)) - } - - /// Verify that the metadata signature is valid - pub fn verify_metadata_signature(&self) -> Result<(), TransactionError> { - let challenge = TransactionOutput::build_metadata_signature_challenge( - &self.script, - &self.features, - &self.sender_offset_public_key, - self.metadata_signature.public_nonce(), - &self.commitment, - ); - if !self.metadata_signature.verify_challenge( - &(&self.commitment + &self.sender_offset_public_key), - &challenge, - &PedersenCommitmentFactory::default(), - ) { - return Err(TransactionError::InvalidSignatureError( - "Metadata signature not valid!".to_string(), - )); - } - Ok(()) - } - - /// Attempt to rewind the range proof to reveal the proof message and committed value - pub fn rewind_range_proof_value_only( - &self, - prover: &RangeProofService, - rewind_public_key: &PublicKey, - rewind_blinding_public_key: &PublicKey, - ) -> Result { - Ok(prover - .rewind_proof_value_only( - &self.proof.0, - &self.commitment, - rewind_public_key, - rewind_blinding_public_key, - )? - .into()) - } - - /// Attempt to fully rewind the range proof to reveal the proof message, committed value and blinding factor - pub fn full_rewind_range_proof( - &self, - prover: &RangeProofService, - rewind_key: &PrivateKey, - rewind_blinding_key: &PrivateKey, - ) -> Result { - Ok(prover - .rewind_proof_commitment_data(&self.proof.0, &self.commitment, rewind_key, rewind_blinding_key)? - .into()) - } - - /// This will check if the input and the output is the same commitment by looking at the commitment and features. - /// This will ignore the output range proof - #[inline] - pub fn is_equal_to(&self, output: &TransactionInput) -> bool { - self.commitment == output.commitment && self.features == output.features - } - - /// Returns true if the output is a coinbase, otherwise false - pub fn is_coinbase(&self) -> bool { - self.features.flags.contains(OutputFlags::COINBASE_OUTPUT) - } - - /// Convenience function that returns the challenge for the metadata commitment signature - pub fn get_metadata_signature_challenge(&self, partial_commitment_nonce: Option<&PublicKey>) -> MessageHash { - let nonce_commitment = match partial_commitment_nonce { - None => self.metadata_signature.public_nonce().clone(), - Some(partial_nonce) => self.metadata_signature.public_nonce() + partial_nonce, - }; - TransactionOutput::build_metadata_signature_challenge( - &self.script, - &self.features, - &self.sender_offset_public_key, - &nonce_commitment, - &self.commitment, - ) - } - - /// Convenience function that calculates the challenge for the metadata commitment signature - pub fn build_metadata_signature_challenge( - script: &TariScript, - features: &OutputFeatures, - sender_offset_public_key: &PublicKey, - public_commitment_nonce: &Commitment, - commitment: &Commitment, - ) -> MessageHash { - Challenge::new() - .chain(public_commitment_nonce.as_bytes()) - .chain(script.as_bytes()) - // TODO: Use consensus encoded bytes #testnet_reset - .chain(features.to_v1_bytes()) - .chain(sender_offset_public_key.as_bytes()) - .chain(commitment.as_bytes()) - .finalize() - .to_vec() - } - - // Create commitment signature for the metadata - fn create_metadata_signature( - value: &MicroTari, - spending_key: &BlindingFactor, - script: &TariScript, - output_features: &OutputFeatures, - sender_offset_public_key: &PublicKey, - partial_commitment_nonce: Option<&PublicKey>, - sender_offset_private_key: Option<&PrivateKey>, - ) -> Result { - let nonce_a = PrivateKey::random(&mut OsRng); - let nonce_b = PrivateKey::random(&mut OsRng); - let nonce_commitment = PedersenCommitmentFactory::default().commit(&nonce_b, &nonce_a); - let nonce_commitment = match partial_commitment_nonce { - None => nonce_commitment, - Some(partial_nonce) => &nonce_commitment + partial_nonce, - }; - let value = PrivateKey::from(value.as_u64()); - let commitment = PedersenCommitmentFactory::default().commit(spending_key, &value); - let e = TransactionOutput::build_metadata_signature_challenge( - script, - output_features, - sender_offset_public_key, - &nonce_commitment, - &commitment, - ); - let secret_x = match sender_offset_private_key { - None => spending_key.clone(), - Some(key) => &spending_key.clone() + key, - }; - Ok(ComSignature::sign( - value, - secret_x, - nonce_a, - nonce_b, - &e, - &PedersenCommitmentFactory::default(), - )?) - } - - /// Create partial commitment signature for the metadata, usually done by the receiver - pub fn create_partial_metadata_signature( - value: &MicroTari, - spending_key: &BlindingFactor, - script: &TariScript, - output_features: &OutputFeatures, - sender_offset_public_key: &PublicKey, - partial_commitment_nonce: &PublicKey, - ) -> Result { - TransactionOutput::create_metadata_signature( - value, - spending_key, - script, - output_features, - sender_offset_public_key, - Some(partial_commitment_nonce), - None, - ) - } - - /// Create final commitment signature for the metadata, signing with both keys - pub fn create_final_metadata_signature( - value: &MicroTari, - spending_key: &BlindingFactor, - script: &TariScript, - output_features: &OutputFeatures, - sender_offset_private_key: &PrivateKey, - ) -> Result { - let sender_offset_public_key = PublicKey::from_secret_key(sender_offset_private_key); - TransactionOutput::create_metadata_signature( - value, - spending_key, - script, - output_features, - &sender_offset_public_key, - None, - Some(sender_offset_private_key), - ) - } - - pub fn witness_hash(&self) -> Vec { - HashDigest::new() - .chain(self.proof.as_bytes()) - .chain(self.metadata_signature.u().as_bytes()) - .chain(self.metadata_signature.v().as_bytes()) - .chain(self.metadata_signature.public_nonce().as_bytes()) - .finalize() - .to_vec() - } -} - -/// Implement the canonical hashing function for TransactionOutput for use in ordering. -/// -/// We can exclude the range proof from this hash. The rationale for this is: -/// a) It is a significant performance boost, since the RP is the biggest part of an output -/// b) Range proofs are committed to elsewhere and so we'd be hashing them twice (and as mentioned, this is slow) -/// c) TransactionInputs will now have the same hash as UTXOs, which makes locating STXOs easier when doing reorgs -impl Hashable for TransactionOutput { - fn hash(&self) -> Vec { - HashDigest::new() - // TODO: use consensus encoding #testnet_reset - .chain(self.features.to_v1_bytes()) - .chain(self.commitment.as_bytes()) - // .chain(range proof) // See docs as to why we exclude this - .chain(self.script.as_bytes()) - .finalize() - .to_vec() - } -} - -impl Default for TransactionOutput { - fn default() -> Self { - TransactionOutput::new( - OutputFeatures::default(), - CommitmentFactory::default().zero(), - RangeProof::default(), - TariScript::default(), - PublicKey::default(), - ComSignature::default(), - ) - } -} - -impl Display for TransactionOutput { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - let proof = self.proof.to_hex(); - let proof = if proof.len() > 32 { - format!( - "{}..{}", - proof[0..16].to_string(), - proof[proof.len() - 16..proof.len()].to_string() - ) - } else { - proof - }; - write!( - fmt, - "{} [{:?}], Script: ({}), Offset Pubkey: ({}), Metadata Signature: ({}, {}, {}), Proof: {}", - self.commitment.to_hex(), - self.features, - self.script, - self.sender_offset_public_key.to_hex(), - self.metadata_signature.u().to_hex(), - self.metadata_signature.v().to_hex(), - self.metadata_signature.public_nonce().to_hex(), - proof - ) - } -} - -impl PartialOrd for TransactionOutput { - fn partial_cmp(&self, other: &Self) -> Option { - self.commitment.partial_cmp(&other.commitment) - } -} - -impl Ord for TransactionOutput { - fn cmp(&self, other: &Self) -> Ordering { - self.commitment.cmp(&other.commitment) - } -} - -/// A wrapper struct to hold the result of a successful range proof rewinding to reveal the committed value and proof -/// message -#[derive(Debug, PartialEq)] -pub struct RewindResult { - pub committed_value: MicroTari, - pub proof_message: [u8; REWIND_USER_MESSAGE_LENGTH], -} - -impl RewindResult { - pub fn new(committed_value: MicroTari, proof_message: [u8; REWIND_USER_MESSAGE_LENGTH]) -> Self { - Self { - committed_value, - proof_message, - } - } -} - -impl From for RewindResult { - fn from(crr: CryptoRewindResult) -> Self { - Self { - committed_value: crr.committed_value.into(), - proof_message: crr.proof_message, - } - } -} - -/// A wrapper struct to hold the result of a successful range proof full rewinding to reveal the committed value, proof -/// message and blinding factor -#[derive(Debug, PartialEq)] -pub struct FullRewindResult { - pub committed_value: MicroTari, - pub proof_message: [u8; REWIND_USER_MESSAGE_LENGTH], - pub blinding_factor: BlindingFactor, -} - -impl FullRewindResult { - pub fn new( - committed_value: MicroTari, - proof_message: [u8; REWIND_USER_MESSAGE_LENGTH], - blinding_factor: BlindingFactor, - ) -> Self { - Self { - committed_value, - proof_message, - blinding_factor, - } - } -} - -impl From> for FullRewindResult { - fn from(crr: CryptoFullRewindResult) -> Self { - Self { - committed_value: crr.committed_value.into(), - proof_message: crr.proof_message, - blinding_factor: crr.blinding_factor, - } - } -} - -//---------------------------------------- Transaction Kernel ----------------------------------------------------// - -/// The transaction kernel tracks the excess for a given transaction. For an explanation of what the excess is, and -/// why it is necessary, refer to the -/// [Mimblewimble TLU post](https://tlu.tarilabs.com/protocols/mimblewimble-1/sources/PITCHME.link.html?highlight=mimblewimble#mimblewimble). -/// The kernel also tracks other transaction metadata, such as the lock height for the transaction (i.e. the earliest -/// this transaction can be mined) and the transaction fee, in cleartext. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TransactionKernel { - /// Options for a kernel's structure or use - pub features: KernelFeatures, - /// Fee originally included in the transaction this proof is for. - pub fee: MicroTari, - /// This kernel is not valid earlier than lock_height blocks - /// The max lock_height of all *inputs* to this transaction - pub lock_height: u64, - /// Remainder of the sum of all transaction commitments (minus an offset). If the transaction is well-formed, - /// amounts plus fee will sum to zero, and the excess is hence a valid public key. - pub excess: Commitment, - /// An aggregated signature of the metadata in this kernel, signed by the individual excess values and the offset - /// excess of the sender. - pub excess_sig: Signature, -} - -impl TransactionKernel { - pub fn is_coinbase(&self) -> bool { - self.features.contains(KernelFeatures::COINBASE_KERNEL) - } - - pub fn verify_signature(&self) -> Result<(), TransactionError> { - let excess = self.excess.as_public_key(); - let r = self.excess_sig.get_public_nonce(); - let m = TransactionMetadata { - lock_height: self.lock_height, - fee: self.fee, - }; - let c = build_challenge(r, &m); - if self.excess_sig.verify_challenge(excess, &c) { - Ok(()) - } else { - Err(TransactionError::InvalidSignatureError( - "Verifying kernel signature".to_string(), - )) - } - } - - /// This method was used to sort kernels. It has been replaced, and will be removed in future - pub fn deprecated_cmp(&self, other: &Self) -> Ordering { - self.features - .cmp(&other.features) - .then(self.fee.cmp(&other.fee)) - .then(self.lock_height.cmp(&other.lock_height)) - .then(self.excess.cmp(&other.excess)) - .then(self.excess_sig.cmp(&other.excess_sig)) - } -} - -impl Hashable for TransactionKernel { - /// Produce a canonical hash for a transaction kernel. The hash is given by - /// $$ H(feature_bits | fee | lock_height | P_excess | R_sum | s_sum) - fn hash(&self) -> Vec { - HashDigest::new() - .chain(&[self.features.bits]) - .chain(u64::from(self.fee).to_le_bytes()) - .chain(self.lock_height.to_le_bytes()) - .chain(self.excess.as_bytes()) - .chain(self.excess_sig.get_public_nonce().as_bytes()) - .chain(self.excess_sig.get_signature().as_bytes()) - .finalize() - .to_vec() - } -} - -impl Display for TransactionKernel { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - fmt, - "Fee: {}\nLock height: {}\nFeatures: {:?}\nExcess: {}\nExcess signature: {}\n", - self.fee, - self.lock_height, - self.features, - self.excess.to_hex(), - self.excess_sig - .to_json() - .unwrap_or_else(|_| "Failed to serialize signature".into()), - ) - } -} - -impl PartialOrd for TransactionKernel { - fn partial_cmp(&self, other: &Self) -> Option { - self.excess_sig.partial_cmp(&other.excess_sig) - } -} - -impl Ord for TransactionKernel { - fn cmp(&self, other: &Self) -> Ordering { - self.excess_sig.cmp(&other.excess_sig) - } -} - -/// A version of Transaction kernel with optional fields. This struct is only used in constructing transaction kernels -pub struct KernelBuilder { - features: KernelFeatures, - fee: MicroTari, - lock_height: u64, - excess: Option, - excess_sig: Option, -} - -/// Implementation of the transaction kernel -impl KernelBuilder { - /// Creates an empty transaction kernel - pub fn new() -> KernelBuilder { - KernelBuilder::default() - } - - /// Build a transaction kernel with the provided features - pub fn with_features(mut self, features: KernelFeatures) -> KernelBuilder { - self.features = features; - self - } - - /// Build a transaction kernel with the provided fee - pub fn with_fee(mut self, fee: MicroTari) -> KernelBuilder { - self.fee = fee; - self - } - - /// Build a transaction kernel with the provided lock height - pub fn with_lock_height(mut self, lock_height: u64) -> KernelBuilder { - self.lock_height = lock_height; - self - } - - /// Add the excess (sum of public spend keys minus the offset) - pub fn with_excess(mut self, excess: &Commitment) -> KernelBuilder { - self.excess = Some(excess.clone()); - self - } - - /// Add the excess signature - pub fn with_signature(mut self, signature: &Signature) -> KernelBuilder { - self.excess_sig = Some(signature.clone()); - self - } - - pub fn build(self) -> Result { - if self.excess.is_none() || self.excess_sig.is_none() { - return Err(TransactionError::NoSignatureError); - } - Ok(TransactionKernel { - features: self.features, - fee: self.fee, - lock_height: self.lock_height, - excess: self.excess.unwrap(), - excess_sig: self.excess_sig.unwrap(), - }) - } -} - -impl Default for KernelBuilder { - fn default() -> Self { - KernelBuilder { - features: KernelFeatures::empty(), - fee: MicroTari::from(0), - lock_height: 0, - excess: None, - excess_sig: None, - } - } -} - -/// This struct holds the result of calculating the sum of the kernels in a Transaction -/// and returns the summed commitments and the total fees -#[derive(Default)] -pub struct KernelSum { - pub sum: Commitment, - pub fees: MicroTari, -} - -//---------------------------------------- Transaction ----------------------------------------------------// - -/// A transaction which consists of a kernel offset and an aggregate body made up of inputs, outputs and kernels. -/// This struct is used to describe single transactions only. The common part between transactions and Tari blocks is -/// accessible via the `body` field, but single transactions also need to carry the public offset around with them so -/// that these can be aggregated into block offsets. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct Transaction { - /// This kernel offset will be accumulated when transactions are aggregated to prevent the "subset" problem where - /// kernels can be linked to inputs and outputs by testing a series of subsets and see which produce valid - /// transactions. - pub offset: BlindingFactor, - /// The constituents of a transaction which has the same structure as the body of a block. - pub body: AggregateBody, - /// A scalar offset that links outputs and inputs to prevent cut-through, enforcing the correct application of - /// the output script. - pub script_offset: BlindingFactor, -} - -impl Transaction { - /// Create a new transaction from the provided inputs, outputs, kernels and offset - pub fn new( - inputs: Vec, - outputs: Vec, - kernels: Vec, - offset: BlindingFactor, - script_offset: BlindingFactor, - ) -> Self { - Self { - offset, - body: AggregateBody::new(inputs, outputs, kernels), - script_offset, - } - } - - /// Validate this transaction by checking the following: - /// 1. The sum of inputs, outputs and fees equal the (public excess value + offset) - /// 1. The signature signs the canonical message with the private excess - /// 1. Range proofs of the outputs are valid - /// - /// This function does NOT check that inputs come from the UTXO set - #[allow(clippy::erasing_op)] // This is for 0 * uT - pub fn validate_internal_consistency( - &self, - bypass_range_proof_verification: bool, - factories: &CryptoFactories, - reward: Option, - prev_header: Option, - height: Option, - ) -> Result<(), TransactionError> { - let reward = reward.unwrap_or_else(|| 0 * uT); - self.body.validate_internal_consistency( - &self.offset, - &self.script_offset, - bypass_range_proof_verification, - reward, - factories, - prev_header, - height, - ) - } - - pub fn body(&self) -> &AggregateBody { - &self.body - } - - /// Returns the byte size or weight of a transaction - pub fn calculate_weight(&self, transaction_weight: &TransactionWeight) -> u64 { - self.body.calculate_weight(transaction_weight) - } - - /// Returns the minimum maturity of the input UTXOs - pub fn min_input_maturity(&self) -> u64 { - self.body.inputs().iter().fold(u64::MAX, |min_maturity, input| { - min(min_maturity, input.features.maturity) - }) - } - - /// Returns the maximum maturity of the input UTXOs - pub fn max_input_maturity(&self) -> u64 { - self.body - .inputs() - .iter() - .fold(0, |max_maturity, input| max(max_maturity, input.features.maturity)) - } - - /// Returns the maximum time lock of the kernels inside of the transaction - pub fn max_kernel_timelock(&self) -> u64 { - self.body.max_kernel_timelock() - } - - /// Returns the height of the minimum height where the transaction is spendable. This is calculated from the - /// transaction kernel lock_heights and the maturity of the input UTXOs. - pub fn min_spendable_height(&self) -> u64 { - max(self.max_kernel_timelock(), self.max_input_maturity()) - } - - /// This function adds two transactions together. It does not do cut-through. Calling Tx1 + Tx2 will result in - /// vut-through being applied. - pub fn add_no_cut_through(mut self, other: Self) -> Self { - self.offset = self.offset + other.offset; - self.script_offset = self.script_offset + other.script_offset; - let (mut inputs, mut outputs, mut kernels) = other.body.dissolve(); - self.body.add_inputs(&mut inputs); - self.body.add_outputs(&mut outputs); - self.body.add_kernels(&mut kernels); - self - } - - pub fn first_kernel_excess_sig(&self) -> Option<&Signature> { - Some(&self.body.kernels().first()?.excess_sig) - } -} - -impl Add for Transaction { - type Output = Self; - - fn add(mut self, other: Self) -> Self { - self = self.add_no_cut_through(other); - self - } -} - -impl Display for Transaction { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - fmt.write_str("-------------- Transaction --------------\n")?; - fmt.write_str("--- Offset ---\n")?; - fmt.write_str(&format!("{}\n", self.offset.to_hex()))?; - fmt.write_str("--- Script Offset ---\n")?; - fmt.write_str(&format!("{}\n", self.script_offset.to_hex()))?; - fmt.write_str("--- Body ---\n")?; - fmt.write_str(&format!("{}\n", self.body)) - } -} - -//---------------------------------------- Transaction Builder ----------------------------------------------------// -pub struct TransactionBuilder { - body: AggregateBody, - offset: Option, - script_offset: Option, - reward: Option, -} - -impl TransactionBuilder { - /// Create an new empty TransactionBuilder - pub fn new() -> Self { - Self::default() - } - - /// Update the offset of an existing transaction - pub fn add_offset(&mut self, offset: BlindingFactor) -> &mut Self { - self.offset = Some(offset); - self - } - - /// Update the script offset of an existing transaction - pub fn add_script_offset(&mut self, script_offset: BlindingFactor) -> &mut Self { - self.script_offset = Some(script_offset); - self - } - - /// Add an input to an existing transaction - pub fn add_input(&mut self, input: TransactionInput) -> &mut Self { - self.body.add_input(input); - self - } - - /// Add an output to an existing transaction - pub fn add_output(&mut self, output: TransactionOutput) -> &mut Self { - self.body.add_output(output); - self - } - - /// Moves a series of inputs to an existing transaction, leaving `inputs` empty - pub fn add_inputs(&mut self, inputs: &mut Vec) -> &mut Self { - self.body.add_inputs(inputs); - self - } - - /// Moves a series of outputs to an existing transaction, leaving `outputs` empty - pub fn add_outputs(&mut self, outputs: &mut Vec) -> &mut Self { - self.body.add_outputs(outputs); - self - } - - /// Set the kernel of a transaction. Currently only one kernel is allowed per transaction - pub fn with_kernel(&mut self, kernel: TransactionKernel) -> &mut Self { - self.body.set_kernel(kernel); - self - } - - pub fn with_reward(&mut self, reward: MicroTari) -> &mut Self { - self.reward = Some(reward); - self - } - - /// Build the transaction. - pub fn build( - self, - factories: &CryptoFactories, - prev_header: Option, - height: Option, - ) -> Result { - if let (Some(script_offset), Some(offset)) = (self.script_offset, self.offset) { - let (i, o, k) = self.body.dissolve(); - let tx = Transaction::new(i, o, k, offset, script_offset); - tx.validate_internal_consistency(true, factories, self.reward, prev_header, height)?; - Ok(tx) - } else { - Err(TransactionError::ValidationError( - "Transaction validation failed".into(), - )) - } - } -} - -impl Default for TransactionBuilder { - fn default() -> Self { - Self { - offset: None, - body: AggregateBody::empty(), - reward: None, - script_offset: None, - } - } -} - -//----------------------------------------- Tests ----------------------------------------------------// - -#[cfg(test)] -mod test { - use crate::{ - transactions::{ - tari_amount::T, - test_helpers, - test_helpers::{TestParams, UtxoTestParams}, - transaction::OutputFeatures, - }, - txn_schema, - }; - use rand::{self, rngs::OsRng}; - use tari_common_types::types::{BlindingFactor, PrivateKey, PublicKey}; - use tari_crypto::{ - keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, - ristretto::pedersen::PedersenCommitmentFactory, - script, - script::ExecutionStack, - }; - - use super::*; - - #[test] - fn input_and_output_hash_match() { - let test_params = TestParams::new(); - let factory = PedersenCommitmentFactory::default(); - - let i = test_params.create_unblinded_output(Default::default()); - let output = i.as_transaction_output(&CryptoFactories::default()).unwrap(); - let input = i.as_transaction_input(&factory).unwrap(); - assert_eq!(output.hash(), input.output_hash()); - } - - #[test] - fn unblinded_input() { - let test_params = TestParams::new(); - let factory = PedersenCommitmentFactory::default(); - - let i = test_params.create_unblinded_output(Default::default()); - let input = i - .as_transaction_input(&factory) - .expect("Should be able to create transaction input"); - assert_eq!(input.features, OutputFeatures::default()); - assert!(input.opened_by(&i, &factory)); - } - - #[test] - fn with_maturity() { - let features = OutputFeatures::with_maturity(42); - assert_eq!(features.maturity, 42); - assert_eq!(features.flags, OutputFlags::empty()); - } - - #[test] - fn range_proof_verification() { - let factories = CryptoFactories::new(32); - // Directly test the tx_output verification - let test_params_1 = TestParams::new(); - let test_params_2 = TestParams::new(); - let output_features = OutputFeatures::default(); - - // For testing the max range has been limited to 2^32 so this value is too large. - let unblinded_output1 = test_params_1.create_unblinded_output(UtxoTestParams { - value: (2u64.pow(32) - 1u64).into(), - ..Default::default() - }); - let script = unblinded_output1.script.clone(); - let tx_output1 = unblinded_output1.as_transaction_output(&factories).unwrap(); - assert!(tx_output1.verify_range_proof(&factories.range_proof).unwrap()); - - let unblinded_output2 = test_params_2.create_unblinded_output(UtxoTestParams { - value: (2u64.pow(32) + 1u64).into(), - ..Default::default() - }); - let tx_output2 = unblinded_output2.as_transaction_output(&factories); - match tx_output2 { - Ok(_) => panic!("Range proof should have failed to verify"), - Err(e) => assert_eq!( - e, - TransactionError::ValidationError( - "Value provided is outside the range allowed by the range proof".to_string() - ) - ), - } - - let value = 2u64.pow(32) + 1; - let v = PrivateKey::from(value); - let c = factories.commitment.commit(&test_params_2.spend_key, &v); - let proof = factories - .range_proof - .construct_proof(&test_params_2.spend_key, 2u64.pow(32) + 1) - .unwrap(); - - let tx_output3 = TransactionOutput::new( - output_features.clone(), - c, - RangeProof::from_bytes(&proof).unwrap(), - script.clone(), - test_params_2.sender_offset_public_key, - TransactionOutput::create_final_metadata_signature( - &value.into(), - &test_params_2.spend_key, - &script, - &output_features, - &test_params_2.sender_offset_private_key, - ) - .unwrap(), - ); - assert!(!tx_output3.verify_range_proof(&factories.range_proof).unwrap()); - } - - #[test] - fn sender_signature_verification() { - let test_params = TestParams::new(); - let factories = CryptoFactories::new(32); - let unblinded_output = test_params.create_unblinded_output(Default::default()); - - let mut tx_output = unblinded_output.as_transaction_output(&factories).unwrap(); - assert!(tx_output.verify_metadata_signature().is_ok()); - tx_output.script = TariScript::default(); - assert!(tx_output.verify_metadata_signature().is_err()); - - tx_output = unblinded_output.as_transaction_output(&factories).unwrap(); - assert!(tx_output.verify_metadata_signature().is_ok()); - tx_output.features = OutputFeatures::create_coinbase(0); - assert!(tx_output.verify_metadata_signature().is_err()); - - tx_output = unblinded_output.as_transaction_output(&factories).unwrap(); - assert!(tx_output.verify_metadata_signature().is_ok()); - tx_output.sender_offset_public_key = PublicKey::default(); - assert!(tx_output.verify_metadata_signature().is_err()); - } - - #[test] - fn kernel_hash() { - let s = PrivateKey::from_hex("6c6eebc5a9c02e1f3c16a69ba4331f9f63d0718401dea10adc4f9d3b879a2c09").unwrap(); - let r = PublicKey::from_hex("28e8efe4e5576aac931d358d0f6ace43c55fa9d4186d1d259d1436caa876d43b").unwrap(); - let sig = Signature::new(r, s); - let excess = Commitment::from_hex("9017be5092b85856ce71061cadeb20c2d1fabdf664c4b3f082bf44cf5065e650").unwrap(); - let k = KernelBuilder::new() - .with_signature(&sig) - .with_fee(100.into()) - .with_excess(&excess) - .with_lock_height(500) - .build() - .unwrap(); - assert_eq!( - &k.hash().to_hex(), - "fe25e4e961d5efec889c489d43e40a1334bf9b4408be4c2e8035a523f231a732" - ); - } - - #[test] - fn kernel_metadata() { - let s = PrivateKey::from_hex("df9a004360b1cf6488d8ff7fb625bc5877f4b013f9b2b20d84932172e605b207").unwrap(); - let r = PublicKey::from_hex("5c6bfaceaa1c83fa4482a816b5f82ca3975cb9b61b6e8be4ee8f01c5f1bee561").unwrap(); - let sig = Signature::new(r, s); - let excess = Commitment::from_hex("e0bd3f743b566272277c357075b0584fc840d79efac49e9b3b6dbaa8a351bc0c").unwrap(); - let k = KernelBuilder::new() - .with_signature(&sig) - .with_fee(100.into()) - .with_excess(&excess) - .with_lock_height(500) - .build() - .unwrap(); - assert_eq!( - &k.hash().to_hex(), - "f1e7348b0952d8afbec6bfaa07a1cbc9c45e51e022242d3faeb0f190e2a9dd07" - ) - } - - #[test] - fn check_timelocks() { - let factories = CryptoFactories::new(32); - let k = BlindingFactor::random(&mut OsRng); - let v = PrivateKey::from(2u64.pow(32) + 1); - let c = factories.commitment.commit(&k, &v); - - let script = TariScript::default(); - let input_data = ExecutionStack::default(); - let script_signature = ComSignature::default(); - let offset_pub_key = PublicKey::default(); - let mut input = TransactionInput::new( - OutputFeatures::default(), - c, - script, - input_data, - script_signature, - offset_pub_key, - ); - - let mut kernel = test_helpers::create_test_kernel(0.into(), 0); - let mut tx = Transaction::new(Vec::new(), Vec::new(), Vec::new(), 0.into(), 0.into()); - - // lets add time locks - input.features.maturity = 5; - kernel.lock_height = 2; - tx.body.add_input(input.clone()); - tx.body.add_kernel(kernel.clone()); - assert_eq!(tx.body.check_stxo_rules(1), Err(TransactionError::InputMaturity)); - assert_eq!(tx.body.check_stxo_rules(5), Ok(())); - - assert_eq!(tx.max_input_maturity(), 5); - assert_eq!(tx.max_kernel_timelock(), 2); - assert_eq!(tx.min_spendable_height(), 5); - - input.features.maturity = 4; - kernel.lock_height = 3; - tx.body.add_input(input.clone()); - tx.body.add_kernel(kernel.clone()); - - assert_eq!(tx.max_input_maturity(), 5); - assert_eq!(tx.max_kernel_timelock(), 3); - assert_eq!(tx.min_spendable_height(), 5); - - input.features.maturity = 2; - kernel.lock_height = 10; - tx.body.add_input(input); - tx.body.add_kernel(kernel); - - assert_eq!(tx.max_input_maturity(), 5); - assert_eq!(tx.max_kernel_timelock(), 10); - assert_eq!(tx.min_spendable_height(), 10); - } - - #[test] - fn test_validate_internal_consistency() { - let (tx, _, _) = test_helpers::create_tx(5000.into(), 3.into(), 1, 2, 1, 4); - - let factories = CryptoFactories::default(); - assert!(tx - .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) - .is_ok()); - } - - #[test] - #[allow(clippy::identity_op)] - fn check_cut_through() { - let (tx, _, outputs) = test_helpers::create_tx(50000000.into(), 3.into(), 1, 2, 1, 2); - - assert_eq!(tx.body.inputs().len(), 2); - assert_eq!(tx.body.outputs().len(), 2); - assert_eq!(tx.body.kernels().len(), 1); - - let factories = CryptoFactories::default(); - assert!(tx - .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) - .is_ok()); - - let schema = txn_schema!(from: vec![outputs[1].clone()], to: vec![1 * T, 2 * T]); - let (tx2, _outputs, _) = test_helpers::spend_utxos(schema); - - assert_eq!(tx2.body.inputs().len(), 1); - assert_eq!(tx2.body.outputs().len(), 3); - assert_eq!(tx2.body.kernels().len(), 1); - - let tx3 = tx + tx2; - let mut tx3_cut_through = tx3.clone(); - // check that all inputs are as we expect them to be - assert_eq!(tx3.body.inputs().len(), 3); - assert_eq!(tx3.body.outputs().len(), 5); - assert_eq!(tx3.body.kernels().len(), 2); - - // Do manual cut-through on tx3 - let double_inputs: Vec = tx3_cut_through - .body - .inputs() - .clone() - .iter() - .filter(|input| tx3_cut_through.body.outputs_mut().iter().any(|o| o.is_equal_to(input))) - .cloned() - .collect(); - for input in double_inputs { - tx3_cut_through.body.outputs_mut().retain(|x| !input.is_equal_to(x)); - tx3_cut_through.body.inputs_mut().retain(|x| *x != input); - } - - // Validate basis transaction where cut-through has not been applied. - assert!(tx3 - .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) - .is_ok()); - - // tx3_cut_through has manual cut-through, it should not be possible so this should fail - assert!(tx3_cut_through - .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) - .is_err()); - } - - #[test] - fn check_duplicate_inputs_outputs() { - let (tx, _, _outputs) = test_helpers::create_tx(50000000.into(), 3.into(), 1, 2, 1, 2); - assert!(!tx.body.contains_duplicated_outputs()); - assert!(!tx.body.contains_duplicated_inputs()); - - let input = tx.body.inputs()[0].clone(); - let output = tx.body.outputs()[0].clone(); - - let mut broken_tx_1 = tx.clone(); - let mut broken_tx_2 = tx; - - broken_tx_1.body.add_input(input); - broken_tx_2.body.add_output(output); - - assert!(broken_tx_1.body.contains_duplicated_inputs()); - assert!(broken_tx_2.body.contains_duplicated_outputs()); - } - - #[test] - fn inputs_not_malleable() { - let (mut inputs, outputs) = test_helpers::create_unblinded_txos(5000.into(), 1, 1, 2, 15.into()); - let mut stack = inputs[0].input_data.clone(); - inputs[0].script = script!(Drop Nop); - inputs[0].input_data.push(StackItem::Hash([0; 32])).unwrap(); - let mut tx = test_helpers::create_transaction_with(1, 15.into(), inputs, outputs); - - stack - .push(StackItem::Hash(*b"Pls put this on tha tari network")) - .unwrap(); - - tx.body.inputs_mut()[0].input_data = stack; - - let factories = CryptoFactories::default(); - let err = tx - .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) - .unwrap_err(); - assert!(matches!(err, TransactionError::InvalidSignatureError(_))); - } - - #[test] - fn test_output_rewinding() { - let test_params = TestParams::new(); - let factories = CryptoFactories::new(32); - let v = MicroTari::from(42); - let rewind_key = PrivateKey::random(&mut OsRng); - let rewind_blinding_key = PrivateKey::random(&mut OsRng); - let random_key = PrivateKey::random(&mut OsRng); - let rewind_public_key = PublicKey::from_secret_key(&rewind_key); - let rewind_blinding_public_key = PublicKey::from_secret_key(&rewind_blinding_key); - let public_random_key = PublicKey::from_secret_key(&random_key); - let proof_message = b"testing12345678910111"; - - let rewind_data = RewindData { - rewind_key: rewind_key.clone(), - rewind_blinding_key: rewind_blinding_key.clone(), - proof_message: proof_message.to_owned(), - }; - - let unblinded_output = test_params.create_unblinded_output(UtxoTestParams { - value: v, - ..Default::default() - }); - let output = unblinded_output - .as_rewindable_transaction_output(&factories, &rewind_data) - .unwrap(); - - assert_eq!( - output.rewind_range_proof_value_only( - &factories.range_proof, - &public_random_key, - &rewind_blinding_public_key - ), - Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) - ); - assert_eq!( - output.rewind_range_proof_value_only(&factories.range_proof, &rewind_public_key, &public_random_key), - Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) - ); - - let rewind_result = output - .rewind_range_proof_value_only(&factories.range_proof, &rewind_public_key, &rewind_blinding_public_key) - .unwrap(); - - assert_eq!(rewind_result.committed_value, v); - assert_eq!(&rewind_result.proof_message, proof_message); - - assert_eq!( - output.full_rewind_range_proof(&factories.range_proof, &random_key, &rewind_blinding_key), - Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) - ); - assert_eq!( - output.full_rewind_range_proof(&factories.range_proof, &rewind_key, &random_key), - Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) - ); - - let full_rewind_result = output - .full_rewind_range_proof(&factories.range_proof, &rewind_key, &rewind_blinding_key) - .unwrap(); - assert_eq!(full_rewind_result.committed_value, v); - assert_eq!(&full_rewind_result.proof_message, proof_message); - assert_eq!(full_rewind_result.blinding_factor, test_params.spend_key); - } - mod output_features { - use super::*; - - #[test] - fn consensus_encode_minimal() { - let features = OutputFeatures::with_maturity(0); - let mut buf = Vec::new(); - let written = features.consensus_encode(&mut buf).unwrap(); - assert_eq!(buf.len(), 3); - assert_eq!(written, 3); - } - - #[test] - fn consensus_encode_decode() { - let features = OutputFeatures::create_coinbase(u64::MAX); - let known_size = features.consensus_encode_exact_size(); - let mut buf = Vec::with_capacity(known_size); - assert_eq!(known_size, 12); - let written = features.consensus_encode(&mut buf).unwrap(); - assert_eq!(buf.len(), 12); - assert_eq!(written, 12); - let decoded_features = OutputFeatures::consensus_decode(&mut &buf[..]).unwrap(); - assert_eq!(features, decoded_features); - } - - #[test] - fn consensus_decode_bad_flags() { - let data = [0x00u8, 0x00, 0x02]; - let features = OutputFeatures::consensus_decode(&mut &data[..]).unwrap(); - // Assert the flag data is preserved - assert_eq!(features.flags.bits & 0x02, 0x02); - } - - #[test] - fn consensus_decode_bad_maturity() { - let data = [0x00u8, 0xFF]; - let err = OutputFeatures::consensus_decode(&mut &data[..]).unwrap_err(); - assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); - } - - #[test] - fn consensus_decode_attempt_maturity_overflow() { - let data = [0x00u8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; - let err = OutputFeatures::consensus_decode(&mut &data[..]).unwrap_err(); - assert_eq!(err.kind(), io::ErrorKind::InvalidData); - } - } -} diff --git a/base_layer/core/src/transactions/transaction_entities/error.rs b/base_layer/core/src/transactions/transaction_entities/error.rs new file mode 100644 index 0000000000..543930df18 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/error.rs @@ -0,0 +1,63 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::crypto::{range_proof::RangeProofError, script::ScriptError, signatures::CommitmentSignatureError}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +//---------------------------------------- TransactionError ----------------------------------------------------// +#[derive(Clone, Debug, PartialEq, Error, Deserialize, Serialize)] +pub enum TransactionError { + #[error("Error validating the transaction: {0}")] + ValidationError(String), + #[error("Signature is invalid: {0}")] + InvalidSignatureError(String), + #[error("Transaction kernel does not contain a signature")] + NoSignatureError, + #[error("A range proof construction or verification has produced an error: {0}")] + RangeProofError(#[from] RangeProofError), + #[error("An error occurred while performing a commitment signature: {0}")] + SigningError(#[from] CommitmentSignatureError), + #[error("Invalid kernel in body")] + InvalidKernel, + #[error("Invalid coinbase in body")] + InvalidCoinbase, + #[error("Invalid coinbase maturity in body")] + InvalidCoinbaseMaturity, + #[error("More than one coinbase in body")] + MoreThanOneCoinbase, + #[error("No coinbase in body")] + NoCoinbase, + #[error("Input maturity not reached")] + InputMaturity, + #[error("Tari script error : {0}")] + ScriptError(#[from] ScriptError), + #[error("Error performing conversion: {0}")] + ConversionError(String), + #[error("The script offset in body does not balance")] + ScriptOffset, + #[error("Error executing script: {0}")] + ScriptExecutionError(String), +} diff --git a/base_layer/core/src/transactions/transaction_entities/full_rewind_result.rs b/base_layer/core/src/transactions/transaction_entities/full_rewind_result.rs new file mode 100644 index 0000000000..2a0a4904d0 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/full_rewind_result.rs @@ -0,0 +1,63 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::{ + crypto::range_proof::{FullRewindResult as CryptoFullRewindResult, REWIND_USER_MESSAGE_LENGTH}, + transactions::tari_amount::MicroTari, +}; +use tari_common_types::types::BlindingFactor; + +/// A wrapper struct to hold the result of a successful range proof full rewinding to reveal the committed value, proof +/// message and blinding factor +#[derive(Debug, PartialEq)] +pub struct FullRewindResult { + pub committed_value: MicroTari, + pub proof_message: [u8; REWIND_USER_MESSAGE_LENGTH], + pub blinding_factor: BlindingFactor, +} + +impl FullRewindResult { + pub fn new( + committed_value: MicroTari, + proof_message: [u8; REWIND_USER_MESSAGE_LENGTH], + blinding_factor: BlindingFactor, + ) -> Self { + Self { + committed_value, + proof_message, + blinding_factor, + } + } +} + +impl From> for FullRewindResult { + fn from(crr: CryptoFullRewindResult) -> Self { + Self { + committed_value: crr.committed_value.into(), + proof_message: crr.proof_message, + blinding_factor: crr.blinding_factor, + } + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/kernel_builder.rs b/base_layer/core/src/transactions/transaction_entities/kernel_builder.rs new file mode 100644 index 0000000000..7fd8f04769 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/kernel_builder.rs @@ -0,0 +1,102 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::transactions::{ + tari_amount::MicroTari, + transaction_entities::{KernelFeatures, TransactionError, TransactionKernel}, +}; +use tari_common_types::types::{Commitment, Signature}; + +/// A version of Transaction kernel with optional fields. This struct is only used in constructing transaction kernels +pub struct KernelBuilder { + features: KernelFeatures, + fee: MicroTari, + lock_height: u64, + excess: Option, + excess_sig: Option, +} + +/// Implementation of the transaction kernel +impl KernelBuilder { + /// Creates an empty transaction kernel + pub fn new() -> KernelBuilder { + KernelBuilder::default() + } + + /// Build a transaction kernel with the provided features + pub fn with_features(mut self, features: KernelFeatures) -> KernelBuilder { + self.features = features; + self + } + + /// Build a transaction kernel with the provided fee + pub fn with_fee(mut self, fee: MicroTari) -> KernelBuilder { + self.fee = fee; + self + } + + /// Build a transaction kernel with the provided lock height + pub fn with_lock_height(mut self, lock_height: u64) -> KernelBuilder { + self.lock_height = lock_height; + self + } + + /// Add the excess (sum of public spend keys minus the offset) + pub fn with_excess(mut self, excess: &Commitment) -> KernelBuilder { + self.excess = Some(excess.clone()); + self + } + + /// Add the excess signature + pub fn with_signature(mut self, signature: &Signature) -> KernelBuilder { + self.excess_sig = Some(signature.clone()); + self + } + + pub fn build(self) -> Result { + if self.excess.is_none() || self.excess_sig.is_none() { + return Err(TransactionError::NoSignatureError); + } + Ok(TransactionKernel { + features: self.features, + fee: self.fee, + lock_height: self.lock_height, + excess: self.excess.unwrap(), + excess_sig: self.excess_sig.unwrap(), + }) + } +} + +impl Default for KernelBuilder { + fn default() -> Self { + KernelBuilder { + features: KernelFeatures::empty(), + fee: MicroTari::from(0), + lock_height: 0, + excess: None, + excess_sig: None, + } + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/kernel_features.rs b/base_layer/core/src/transactions/transaction_entities/kernel_features.rs new file mode 100644 index 0000000000..b6df7b168f --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/kernel_features.rs @@ -0,0 +1,39 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use serde::{Deserialize, Serialize}; + +bitflags! { + /// Options for a kernel's structure or use. + /// TODO: expand to accommodate Tari DAN transaction types, such as namespace and validator node registrations + #[derive(Deserialize, Serialize)] + pub struct KernelFeatures: u8 { + /// Coinbase transaction + const COINBASE_KERNEL = 1u8; + } +} + +impl KernelFeatures { + pub fn create_coinbase() -> KernelFeatures { + KernelFeatures::COINBASE_KERNEL + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/kernel_sum.rs b/base_layer/core/src/transactions/transaction_entities/kernel_sum.rs new file mode 100644 index 0000000000..73e755872f --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/kernel_sum.rs @@ -0,0 +1,35 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::transactions::tari_amount::MicroTari; +use tari_common_types::types::Commitment; + +/// This struct holds the result of calculating the sum of the kernels in a Transaction +/// and returns the summed commitments and the total fees +#[derive(Default)] +pub struct KernelSum { + pub sum: Commitment, + pub fees: MicroTari, +} diff --git a/base_layer/core/src/transactions/transaction_entities/mod.rs b/base_layer/core/src/transactions/transaction_entities/mod.rs new file mode 100644 index 0000000000..34a1d76771 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/mod.rs @@ -0,0 +1,533 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use blake2::Digest; +pub use error::TransactionError; +pub use full_rewind_result::FullRewindResult; +pub use kernel_builder::KernelBuilder; +pub use kernel_features::KernelFeatures; +pub use kernel_sum::KernelSum; +pub use output_features::OutputFeatures; +pub use output_flags::OutputFlags; +pub use rewind_result::RewindResult; +use tari_common_types::types::{Commitment, HashDigest}; +use tari_crypto::{script::TariScript, tari_utilities::ByteArray}; +pub use transaction::Transaction; +pub use transaction_builder::TransactionBuilder; +pub use transaction_input::TransactionInput; +pub use transaction_kernel::TransactionKernel; +pub use transaction_output::TransactionOutput; +pub use unblinded_output::UnblindedOutput; + +pub(crate) mod error; +pub(crate) mod full_rewind_result; +pub(crate) mod kernel_builder; +pub(crate) mod kernel_features; +pub(crate) mod kernel_sum; +pub(crate) mod output_features; +pub(crate) mod output_flags; +pub(crate) mod rewind_result; +pub(crate) mod transaction; +pub(crate) mod transaction_builder; +pub(crate) mod transaction_input; +pub(crate) mod transaction_kernel; +pub(crate) mod transaction_output; +pub(crate) mod unblinded_output; + +// Tx_weight(inputs(12,500), outputs(500), kernels(1)) = 126,510 still well enough below block weight of 127,795 +pub const MAX_TRANSACTION_INPUTS: usize = 12_500; +pub const MAX_TRANSACTION_OUTPUTS: usize = 500; +pub const MAX_TRANSACTION_RECIPIENTS: usize = 15; + +//---------------------------------------- Crate functions ----------------------------------------------------// + +/// Implement the canonical hashing function for TransactionOutput and UnblindedOutput for use in +/// ordering as well as for the output hash calculation for TransactionInput. +/// +/// We can exclude the range proof from this hash. The rationale for this is: +/// a) It is a significant performance boost, since the RP is the biggest part of an output +/// b) Range proofs are committed to elsewhere and so we'd be hashing them twice (and as mentioned, this is slow) +/// c) TransactionInputs will now have the same hash as UTXOs, which makes locating STXOs easier when doing reorgs +pub fn hash_output(features: &OutputFeatures, commitment: &Commitment, script: &TariScript) -> Vec { + HashDigest::new() + // TODO: use consensus encoding #testnet_reset + .chain(features.to_v1_bytes()) + .chain(commitment.as_bytes()) + // .chain(range proof) // See docs as to why we exclude this + .chain(script.as_bytes()) + .finalize() + .to_vec() +} + +//----------------------------------------- Tests ----------------------------------------------------// + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + transactions::{ + tari_amount::{MicroTari, T}, + test_helpers, + test_helpers::{TestParams, UtxoTestParams}, + transaction_entities::OutputFeatures, + transaction_protocol::RewindData, + CryptoFactories, + }, + txn_schema, + }; + use rand::{self, rngs::OsRng}; + use tari_common_types::types::{BlindingFactor, ComSignature, PrivateKey, PublicKey, RangeProof, Signature}; + use tari_crypto::{ + commitment::HomomorphicCommitmentFactory, + keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, + range_proof::{RangeProofError, RangeProofService}, + ristretto::pedersen::PedersenCommitmentFactory, + script, + script::{ExecutionStack, StackItem}, + tari_utilities::{hex::Hex, Hashable}, + }; + + #[test] + fn input_and_output_and_unblinded_output_hash_match() { + let test_params = TestParams::new(); + let factory = PedersenCommitmentFactory::default(); + + let i = test_params.create_unblinded_output(Default::default()); + let output = i.as_transaction_output(&CryptoFactories::default()).unwrap(); + let input = i.as_transaction_input(&factory).unwrap(); + assert_eq!(output.hash(), input.output_hash()); + assert_eq!(output.hash(), i.hash(&CryptoFactories::default())); + } + + #[test] + fn unblinded_input() { + let test_params = TestParams::new(); + let factory = PedersenCommitmentFactory::default(); + + let i = test_params.create_unblinded_output(Default::default()); + let input = i + .as_transaction_input(&factory) + .expect("Should be able to create transaction input"); + assert_eq!(input.features, OutputFeatures::default()); + assert!(input.opened_by(&i, &factory)); + } + + #[test] + fn with_maturity() { + let features = OutputFeatures::with_maturity(42); + assert_eq!(features.maturity, 42); + assert_eq!(features.flags, OutputFlags::empty()); + } + + #[test] + fn range_proof_verification() { + let factories = CryptoFactories::new(32); + // Directly test the tx_output verification + let test_params_1 = TestParams::new(); + let test_params_2 = TestParams::new(); + let output_features = OutputFeatures::default(); + + // For testing the max range has been limited to 2^32 so this value is too large. + let unblinded_output1 = test_params_1.create_unblinded_output(UtxoTestParams { + value: (2u64.pow(32) - 1u64).into(), + ..Default::default() + }); + let script = unblinded_output1.script.clone(); + let tx_output1 = unblinded_output1.as_transaction_output(&factories).unwrap(); + assert!(tx_output1.verify_range_proof(&factories.range_proof).unwrap()); + + let unblinded_output2 = test_params_2.create_unblinded_output(UtxoTestParams { + value: (2u64.pow(32) + 1u64).into(), + ..Default::default() + }); + let tx_output2 = unblinded_output2.as_transaction_output(&factories); + match tx_output2 { + Ok(_) => panic!("Range proof should have failed to verify"), + Err(e) => assert_eq!( + e, + TransactionError::ValidationError( + "Value provided is outside the range allowed by the range proof".to_string() + ) + ), + } + + let value = 2u64.pow(32) + 1; + let v = PrivateKey::from(value); + let c = factories.commitment.commit(&test_params_2.spend_key, &v); + let proof = factories + .range_proof + .construct_proof(&test_params_2.spend_key, 2u64.pow(32) + 1) + .unwrap(); + + let tx_output3 = TransactionOutput::new( + output_features.clone(), + c, + RangeProof::from_bytes(&proof).unwrap(), + script.clone(), + test_params_2.sender_offset_public_key, + TransactionOutput::create_final_metadata_signature( + &value.into(), + &test_params_2.spend_key, + &script, + &output_features, + &test_params_2.sender_offset_private_key, + ) + .unwrap(), + ); + assert!(!tx_output3.verify_range_proof(&factories.range_proof).unwrap()); + } + + #[test] + fn sender_signature_verification() { + let test_params = TestParams::new(); + let factories = CryptoFactories::new(32); + let unblinded_output = test_params.create_unblinded_output(Default::default()); + + let mut tx_output = unblinded_output.as_transaction_output(&factories).unwrap(); + assert!(tx_output.verify_metadata_signature().is_ok()); + tx_output.script = TariScript::default(); + assert!(tx_output.verify_metadata_signature().is_err()); + + tx_output = unblinded_output.as_transaction_output(&factories).unwrap(); + assert!(tx_output.verify_metadata_signature().is_ok()); + tx_output.features = OutputFeatures::create_coinbase(0); + assert!(tx_output.verify_metadata_signature().is_err()); + + tx_output = unblinded_output.as_transaction_output(&factories).unwrap(); + assert!(tx_output.verify_metadata_signature().is_ok()); + tx_output.sender_offset_public_key = PublicKey::default(); + assert!(tx_output.verify_metadata_signature().is_err()); + } + + #[test] + fn kernel_hash() { + let s = PrivateKey::from_hex("6c6eebc5a9c02e1f3c16a69ba4331f9f63d0718401dea10adc4f9d3b879a2c09").unwrap(); + let r = PublicKey::from_hex("28e8efe4e5576aac931d358d0f6ace43c55fa9d4186d1d259d1436caa876d43b").unwrap(); + let sig = Signature::new(r, s); + let excess = Commitment::from_hex("9017be5092b85856ce71061cadeb20c2d1fabdf664c4b3f082bf44cf5065e650").unwrap(); + let k = KernelBuilder::new() + .with_signature(&sig) + .with_fee(100.into()) + .with_excess(&excess) + .with_lock_height(500) + .build() + .unwrap(); + assert_eq!( + &k.hash().to_hex(), + "fe25e4e961d5efec889c489d43e40a1334bf9b4408be4c2e8035a523f231a732" + ); + } + + #[test] + fn kernel_metadata() { + let s = PrivateKey::from_hex("df9a004360b1cf6488d8ff7fb625bc5877f4b013f9b2b20d84932172e605b207").unwrap(); + let r = PublicKey::from_hex("5c6bfaceaa1c83fa4482a816b5f82ca3975cb9b61b6e8be4ee8f01c5f1bee561").unwrap(); + let sig = Signature::new(r, s); + let excess = Commitment::from_hex("e0bd3f743b566272277c357075b0584fc840d79efac49e9b3b6dbaa8a351bc0c").unwrap(); + let k = KernelBuilder::new() + .with_signature(&sig) + .with_fee(100.into()) + .with_excess(&excess) + .with_lock_height(500) + .build() + .unwrap(); + assert_eq!( + &k.hash().to_hex(), + "f1e7348b0952d8afbec6bfaa07a1cbc9c45e51e022242d3faeb0f190e2a9dd07" + ) + } + + #[test] + fn check_timelocks() { + let factories = CryptoFactories::new(32); + let k = BlindingFactor::random(&mut OsRng); + let v = PrivateKey::from(2u64.pow(32) + 1); + let c = factories.commitment.commit(&k, &v); + + let script = TariScript::default(); + let input_data = ExecutionStack::default(); + let script_signature = ComSignature::default(); + let offset_pub_key = PublicKey::default(); + let mut input = TransactionInput::new( + OutputFeatures::default(), + c, + script, + input_data, + script_signature, + offset_pub_key, + ); + + let mut kernel = test_helpers::create_test_kernel(0.into(), 0); + let mut tx = Transaction::new(Vec::new(), Vec::new(), Vec::new(), 0.into(), 0.into()); + + // lets add time locks + input.features.maturity = 5; + kernel.lock_height = 2; + tx.body.add_input(input.clone()); + tx.body.add_kernel(kernel.clone()); + assert_eq!(tx.body.check_stxo_rules(1), Err(TransactionError::InputMaturity)); + assert_eq!(tx.body.check_stxo_rules(5), Ok(())); + + assert_eq!(tx.max_input_maturity(), 5); + assert_eq!(tx.max_kernel_timelock(), 2); + assert_eq!(tx.min_spendable_height(), 5); + + input.features.maturity = 4; + kernel.lock_height = 3; + tx.body.add_input(input.clone()); + tx.body.add_kernel(kernel.clone()); + + assert_eq!(tx.max_input_maturity(), 5); + assert_eq!(tx.max_kernel_timelock(), 3); + assert_eq!(tx.min_spendable_height(), 5); + + input.features.maturity = 2; + kernel.lock_height = 10; + tx.body.add_input(input); + tx.body.add_kernel(kernel); + + assert_eq!(tx.max_input_maturity(), 5); + assert_eq!(tx.max_kernel_timelock(), 10); + assert_eq!(tx.min_spendable_height(), 10); + } + + #[test] + fn test_validate_internal_consistency() { + let (tx, _, _) = test_helpers::create_tx(5000.into(), 3.into(), 1, 2, 1, 4); + + let factories = CryptoFactories::default(); + assert!(tx + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .is_ok()); + } + + #[test] + #[allow(clippy::identity_op)] + fn check_cut_through() { + let (tx, _, outputs) = test_helpers::create_tx(50000000.into(), 3.into(), 1, 2, 1, 2); + + assert_eq!(tx.body.inputs().len(), 2); + assert_eq!(tx.body.outputs().len(), 2); + assert_eq!(tx.body.kernels().len(), 1); + + let factories = CryptoFactories::default(); + assert!(tx + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .is_ok()); + + let schema = txn_schema!(from: vec![outputs[1].clone()], to: vec![1 * T, 2 * T]); + let (tx2, _outputs, _) = test_helpers::spend_utxos(schema); + + assert_eq!(tx2.body.inputs().len(), 1); + assert_eq!(tx2.body.outputs().len(), 3); + assert_eq!(tx2.body.kernels().len(), 1); + + let tx3 = tx + tx2; + let mut tx3_cut_through = tx3.clone(); + // check that all inputs are as we expect them to be + assert_eq!(tx3.body.inputs().len(), 3); + assert_eq!(tx3.body.outputs().len(), 5); + assert_eq!(tx3.body.kernels().len(), 2); + + // Do manual cut-through on tx3 + let double_inputs: Vec = tx3_cut_through + .body + .inputs() + .clone() + .iter() + .filter(|input| tx3_cut_through.body.outputs_mut().iter().any(|o| o.is_equal_to(input))) + .cloned() + .collect(); + for input in double_inputs { + tx3_cut_through.body.outputs_mut().retain(|x| !input.is_equal_to(x)); + tx3_cut_through.body.inputs_mut().retain(|x| *x != input); + } + + // Validate basis transaction where cut-through has not been applied. + assert!(tx3 + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .is_ok()); + + // tx3_cut_through has manual cut-through, it should not be possible so this should fail + assert!(tx3_cut_through + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .is_err()); + } + + #[test] + fn check_duplicate_inputs_outputs() { + let (tx, _, _outputs) = test_helpers::create_tx(50000000.into(), 3.into(), 1, 2, 1, 2); + assert!(!tx.body.contains_duplicated_outputs()); + assert!(!tx.body.contains_duplicated_inputs()); + + let input = tx.body.inputs()[0].clone(); + let output = tx.body.outputs()[0].clone(); + + let mut broken_tx_1 = tx.clone(); + let mut broken_tx_2 = tx; + + broken_tx_1.body.add_input(input); + broken_tx_2.body.add_output(output); + + assert!(broken_tx_1.body.contains_duplicated_inputs()); + assert!(broken_tx_2.body.contains_duplicated_outputs()); + } + + #[test] + fn inputs_not_malleable() { + let (mut inputs, outputs) = test_helpers::create_unblinded_txos(5000.into(), 1, 1, 2, 15.into()); + let mut stack = inputs[0].input_data.clone(); + inputs[0].script = script!(Drop Nop); + inputs[0].input_data.push(StackItem::Hash([0; 32])).unwrap(); + let mut tx = test_helpers::create_transaction_with(1, 15.into(), inputs, outputs); + + stack + .push(StackItem::Hash(*b"Pls put this on tha tari network")) + .unwrap(); + + tx.body.inputs_mut()[0].input_data = stack; + + let factories = CryptoFactories::default(); + let err = tx + .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .unwrap_err(); + assert!(matches!(err, TransactionError::InvalidSignatureError(_))); + } + + #[test] + fn test_output_rewinding() { + let test_params = TestParams::new(); + let factories = CryptoFactories::new(32); + let v = MicroTari::from(42); + let rewind_key = PrivateKey::random(&mut OsRng); + let rewind_blinding_key = PrivateKey::random(&mut OsRng); + let random_key = PrivateKey::random(&mut OsRng); + let rewind_public_key = PublicKey::from_secret_key(&rewind_key); + let rewind_blinding_public_key = PublicKey::from_secret_key(&rewind_blinding_key); + let public_random_key = PublicKey::from_secret_key(&random_key); + let proof_message = b"testing12345678910111"; + + let rewind_data = RewindData { + rewind_key: rewind_key.clone(), + rewind_blinding_key: rewind_blinding_key.clone(), + proof_message: proof_message.to_owned(), + }; + + let unblinded_output = test_params.create_unblinded_output(UtxoTestParams { + value: v, + ..Default::default() + }); + let output = unblinded_output + .as_rewindable_transaction_output(&factories, &rewind_data) + .unwrap(); + + assert_eq!( + output.rewind_range_proof_value_only( + &factories.range_proof, + &public_random_key, + &rewind_blinding_public_key + ), + Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) + ); + assert_eq!( + output.rewind_range_proof_value_only(&factories.range_proof, &rewind_public_key, &public_random_key), + Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) + ); + + let rewind_result = output + .rewind_range_proof_value_only(&factories.range_proof, &rewind_public_key, &rewind_blinding_public_key) + .unwrap(); + + assert_eq!(rewind_result.committed_value, v); + assert_eq!(&rewind_result.proof_message, proof_message); + + assert_eq!( + output.full_rewind_range_proof(&factories.range_proof, &random_key, &rewind_blinding_key), + Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) + ); + assert_eq!( + output.full_rewind_range_proof(&factories.range_proof, &rewind_key, &random_key), + Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) + ); + + let full_rewind_result = output + .full_rewind_range_proof(&factories.range_proof, &rewind_key, &rewind_blinding_key) + .unwrap(); + assert_eq!(full_rewind_result.committed_value, v); + assert_eq!(&full_rewind_result.proof_message, proof_message); + assert_eq!(full_rewind_result.blinding_factor, test_params.spend_key); + } + mod output_features { + use std::io; + + use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}; + + use super::*; + + #[test] + fn consensus_encode_minimal() { + let features = OutputFeatures::with_maturity(0); + let mut buf = Vec::new(); + let written = features.consensus_encode(&mut buf).unwrap(); + assert_eq!(buf.len(), 3); + assert_eq!(written, 3); + } + + #[test] + fn consensus_encode_decode() { + let features = OutputFeatures::create_coinbase(u64::MAX); + let known_size = features.consensus_encode_exact_size(); + let mut buf = Vec::with_capacity(known_size); + assert_eq!(known_size, 12); + let written = features.consensus_encode(&mut buf).unwrap(); + assert_eq!(buf.len(), 12); + assert_eq!(written, 12); + let decoded_features = OutputFeatures::consensus_decode(&mut &buf[..]).unwrap(); + assert_eq!(features, decoded_features); + } + + #[test] + fn consensus_decode_bad_flags() { + let data = [0x00u8, 0x00, 0x02]; + let features = OutputFeatures::consensus_decode(&mut &data[..]).unwrap(); + // Assert the flag data is preserved + assert_eq!(features.flags.bits() & 0x02, 0x02); + } + + #[test] + fn consensus_decode_bad_maturity() { + let data = [0x00u8, 0xFF]; + let err = OutputFeatures::consensus_decode(&mut &data[..]).unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof); + } + + #[test] + fn consensus_decode_attempt_maturity_overflow() { + let data = [0x00u8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; + let err = OutputFeatures::consensus_decode(&mut &data[..]).unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::InvalidData); + } + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/output_features.rs b/base_layer/core/src/transactions/transaction_entities/output_features.rs new file mode 100644 index 0000000000..c1c00a652d --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/output_features.rs @@ -0,0 +1,152 @@ +// Copyright 2019. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}, + transactions::transaction_entities::OutputFlags, +}; +use integer_encoding::{VarInt, VarIntReader, VarIntWriter}; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + fmt, + fmt::{Display, Formatter}, + io, + io::{Read, Write}, +}; + +/// Options for UTXO's +#[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq)] +pub struct OutputFeatures { + /// Flags are the feature flags that differentiate between outputs, eg Coinbase all of which has different rules + pub flags: OutputFlags, + /// the maturity of the specific UTXO. This is the min lock height at which an UTXO can be spent. Coinbase UTXO + /// require a min maturity of the Coinbase_lock_height, this should be checked on receiving new blocks. + pub maturity: u64, +} + +impl OutputFeatures { + /// The version number to use in consensus encoding. In future, this value could be dynamic. + const CONSENSUS_ENCODING_VERSION: u8 = 0; + + /// Encodes output features using deprecated bincode encoding + pub fn to_v1_bytes(&self) -> Vec { + // unreachable panic: serialized_size is infallible because it uses DefaultOptions + let encode_size = bincode::serialized_size(self).expect("unreachable"); + let mut buf = Vec::with_capacity(encode_size as usize); + // unreachable panic: Vec's Write impl is infallible + bincode::serialize_into(&mut buf, self).expect("unreachable"); + buf + } + + /// Encodes output features using consensus encoding + pub fn to_consensus_bytes(&self) -> Vec { + let mut buf = Vec::with_capacity(self.consensus_encode_exact_size()); + // unreachable panic: Vec's Write impl is infallible + self.consensus_encode(&mut buf).expect("unreachable"); + buf + } + + pub fn create_coinbase(maturity_height: u64) -> OutputFeatures { + OutputFeatures { + flags: OutputFlags::COINBASE_OUTPUT, + maturity: maturity_height, + } + } + + /// Create an `OutputFeatures` with the given maturity and all other values at their default setting + pub fn with_maturity(maturity: u64) -> OutputFeatures { + OutputFeatures { + maturity, + ..OutputFeatures::default() + } + } +} + +impl ConsensusEncoding for OutputFeatures { + fn consensus_encode(&self, writer: &mut W) -> Result { + let mut written = writer.write_varint(Self::CONSENSUS_ENCODING_VERSION)?; + written += writer.write_varint(self.maturity)?; + written += self.flags.consensus_encode(writer)?; + Ok(written) + } +} + +impl ConsensusEncodingSized for OutputFeatures { + fn consensus_encode_exact_size(&self) -> usize { + Self::CONSENSUS_ENCODING_VERSION.required_space() + + self.flags.consensus_encode_exact_size() + + self.maturity.required_space() + } +} + +impl ConsensusDecoding for OutputFeatures { + fn consensus_decode(reader: &mut R) -> Result { + // Changing the order of these operations is consensus breaking + let version = reader.read_varint::()?; + if version != Self::CONSENSUS_ENCODING_VERSION { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!( + "Invalid version. Expected {} but got {}", + Self::CONSENSUS_ENCODING_VERSION, + version + ), + )); + } + // Decode safety: read_varint will stop reading the varint after 10 bytes + let maturity = reader.read_varint()?; + let flags = OutputFlags::consensus_decode(reader)?; + Ok(Self { flags, maturity }) + } +} + +impl Default for OutputFeatures { + fn default() -> Self { + OutputFeatures { + flags: OutputFlags::empty(), + maturity: 0, + } + } +} + +impl PartialOrd for OutputFeatures { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for OutputFeatures { + fn cmp(&self, other: &Self) -> Ordering { + self.maturity.cmp(&other.maturity) + } +} + +impl Display for OutputFeatures { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "OutputFeatures: Flags = {:?}, Maturity = {}", + self.flags, self.maturity + ) + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/output_flags.rs b/base_layer/core/src/transactions/transaction_entities/output_flags.rs new file mode 100644 index 0000000000..965995c0e9 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/output_flags.rs @@ -0,0 +1,63 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}; +use serde::{Deserialize, Serialize}; +use std::{io, io::Read}; + +bitflags! { + #[derive(Deserialize, Serialize)] + pub struct OutputFlags: u8 { + /// Output is a coinbase output, must not be spent until maturity + const COINBASE_OUTPUT = 0b0000_0001; + } +} + +impl ConsensusEncoding for OutputFlags { + fn consensus_encode(&self, writer: &mut W) -> Result { + writer.write(&self.bits.to_le_bytes()) + } +} + +impl ConsensusEncodingSized for OutputFlags { + fn consensus_encode_exact_size(&self) -> usize { + 1 + } +} + +impl ConsensusDecoding for OutputFlags { + fn consensus_decode(reader: &mut R) -> Result { + let mut buf = [0u8; 1]; + reader.read_exact(&mut buf)?; + // SAFETY: we have 3 options here: + // 1. error if unsupported flags are used, meaning that every new flag will be a hard fork + // 2. truncate unsupported flags, means different hashes will be produced for the same block + // 3. ignore unsupported flags, which could be set at any time and persisted to the blockchain. + // Once those flags are defined at some point in the future, depending on the functionality of the flag, + // a consensus rule may be needed that ignores flags prior to a given block height. + // Option 3 is used here + Ok(unsafe { OutputFlags::from_bits_unchecked(u8::from_le_bytes(buf)) }) + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/rewind_result.rs b/base_layer/core/src/transactions/transaction_entities/rewind_result.rs new file mode 100644 index 0000000000..a1b68d84f4 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/rewind_result.rs @@ -0,0 +1,55 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::{ + crypto::range_proof::{RewindResult as CryptoRewindResult, REWIND_USER_MESSAGE_LENGTH}, + transactions::tari_amount::MicroTari, +}; + +/// A wrapper struct to hold the result of a successful range proof rewinding to reveal the committed value and proof +/// message +#[derive(Debug, PartialEq)] +pub struct RewindResult { + pub committed_value: MicroTari, + pub proof_message: [u8; REWIND_USER_MESSAGE_LENGTH], +} + +impl RewindResult { + pub fn new(committed_value: MicroTari, proof_message: [u8; REWIND_USER_MESSAGE_LENGTH]) -> Self { + Self { + committed_value, + proof_message, + } + } +} + +impl From for RewindResult { + fn from(crr: CryptoRewindResult) -> Self { + Self { + committed_value: crr.committed_value.into(), + proof_message: crr.proof_message, + } + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/transaction.rs b/base_layer/core/src/transactions/transaction_entities/transaction.rs new file mode 100644 index 0000000000..27e25a6b48 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/transaction.rs @@ -0,0 +1,175 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::{ + crypto::tari_utilities::hex::Hex, + transactions::{ + aggregated_body::AggregateBody, + tari_amount::{uT, MicroTari}, + transaction_entities::{TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, + weight::TransactionWeight, + CryptoFactories, + }, +}; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::{max, min}, + fmt::{Display, Formatter}, + ops::Add, +}; +use tari_common_types::types::{BlindingFactor, HashOutput, Signature}; + +/// A transaction which consists of a kernel offset and an aggregate body made up of inputs, outputs and kernels. +/// This struct is used to describe single transactions only. The common part between transactions and Tari blocks is +/// accessible via the `body` field, but single transactions also need to carry the public offset around with them so +/// that these can be aggregated into block offsets. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Transaction { + /// This kernel offset will be accumulated when transactions are aggregated to prevent the "subset" problem where + /// kernels can be linked to inputs and outputs by testing a series of subsets and see which produce valid + /// transactions. + pub offset: BlindingFactor, + /// The constituents of a transaction which has the same structure as the body of a block. + pub body: AggregateBody, + /// A scalar offset that links outputs and inputs to prevent cut-through, enforcing the correct application of + /// the output script. + pub script_offset: BlindingFactor, +} + +impl Transaction { + /// Create a new transaction from the provided inputs, outputs, kernels and offset + pub fn new( + inputs: Vec, + outputs: Vec, + kernels: Vec, + offset: BlindingFactor, + script_offset: BlindingFactor, + ) -> Self { + Self { + offset, + body: AggregateBody::new(inputs, outputs, kernels), + script_offset, + } + } + + /// Validate this transaction by checking the following: + /// 1. The sum of inputs, outputs and fees equal the (public excess value + offset) + /// 1. The signature signs the canonical message with the private excess + /// 1. Range proofs of the outputs are valid + /// + /// This function does NOT check that inputs come from the UTXO set + #[allow(clippy::erasing_op)] // This is for 0 * uT + pub fn validate_internal_consistency( + &self, + bypass_range_proof_verification: bool, + factories: &CryptoFactories, + reward: Option, + prev_header: Option, + height: Option, + ) -> Result<(), TransactionError> { + let reward = reward.unwrap_or_else(|| 0 * uT); + self.body.validate_internal_consistency( + &self.offset, + &self.script_offset, + bypass_range_proof_verification, + reward, + factories, + prev_header, + height, + ) + } + + pub fn body(&self) -> &AggregateBody { + &self.body + } + + /// Returns the byte size or weight of a transaction + pub fn calculate_weight(&self, transaction_weight: &TransactionWeight) -> u64 { + self.body.calculate_weight(transaction_weight) + } + + /// Returns the minimum maturity of the input UTXOs + pub fn min_input_maturity(&self) -> u64 { + self.body.inputs().iter().fold(u64::MAX, |min_maturity, input| { + min(min_maturity, input.features.maturity) + }) + } + + /// Returns the maximum maturity of the input UTXOs + pub fn max_input_maturity(&self) -> u64 { + self.body + .inputs() + .iter() + .fold(0, |max_maturity, input| max(max_maturity, input.features.maturity)) + } + + /// Returns the maximum time lock of the kernels inside of the transaction + pub fn max_kernel_timelock(&self) -> u64 { + self.body.max_kernel_timelock() + } + + /// Returns the height of the minimum height where the transaction is spendable. This is calculated from the + /// transaction kernel lock_heights and the maturity of the input UTXOs. + pub fn min_spendable_height(&self) -> u64 { + max(self.max_kernel_timelock(), self.max_input_maturity()) + } + + /// This function adds two transactions together. It does not do cut-through. Calling Tx1 + Tx2 will result in + /// vut-through being applied. + pub fn add_no_cut_through(mut self, other: Self) -> Self { + self.offset = self.offset + other.offset; + self.script_offset = self.script_offset + other.script_offset; + let (mut inputs, mut outputs, mut kernels) = other.body.dissolve(); + self.body.add_inputs(&mut inputs); + self.body.add_outputs(&mut outputs); + self.body.add_kernels(&mut kernels); + self + } + + pub fn first_kernel_excess_sig(&self) -> Option<&Signature> { + Some(&self.body.kernels().first()?.excess_sig) + } +} + +impl Add for Transaction { + type Output = Self; + + fn add(mut self, other: Self) -> Self { + self = self.add_no_cut_through(other); + self + } +} + +impl Display for Transaction { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + fmt.write_str("-------------- Transaction --------------\n")?; + fmt.write_str("--- Offset ---\n")?; + fmt.write_str(&format!("{}\n", self.offset.to_hex()))?; + fmt.write_str("--- Script Offset ---\n")?; + fmt.write_str(&format!("{}\n", self.script_offset.to_hex()))?; + fmt.write_str("--- Body ---\n")?; + fmt.write_str(&format!("{}\n", self.body)) + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/transaction_builder.rs b/base_layer/core/src/transactions/transaction_entities/transaction_builder.rs new file mode 100644 index 0000000000..f58a3d6162 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/transaction_builder.rs @@ -0,0 +1,124 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::transactions::{ + aggregated_body::AggregateBody, + tari_amount::MicroTari, + transaction_entities::{Transaction, TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, + CryptoFactories, +}; +use tari_common_types::types::{BlindingFactor, HashOutput}; + +//---------------------------------------- Transaction Builder ----------------------------------------------------// +pub struct TransactionBuilder { + body: AggregateBody, + offset: Option, + script_offset: Option, + reward: Option, +} + +impl TransactionBuilder { + /// Create an new empty TransactionBuilder + pub fn new() -> Self { + Self::default() + } + + /// Update the offset of an existing transaction + pub fn add_offset(&mut self, offset: BlindingFactor) -> &mut Self { + self.offset = Some(offset); + self + } + + /// Update the script offset of an existing transaction + pub fn add_script_offset(&mut self, script_offset: BlindingFactor) -> &mut Self { + self.script_offset = Some(script_offset); + self + } + + /// Add an input to an existing transaction + pub fn add_input(&mut self, input: TransactionInput) -> &mut Self { + self.body.add_input(input); + self + } + + /// Add an output to an existing transaction + pub fn add_output(&mut self, output: TransactionOutput) -> &mut Self { + self.body.add_output(output); + self + } + + /// Moves a series of inputs to an existing transaction, leaving `inputs` empty + pub fn add_inputs(&mut self, inputs: &mut Vec) -> &mut Self { + self.body.add_inputs(inputs); + self + } + + /// Moves a series of outputs to an existing transaction, leaving `outputs` empty + pub fn add_outputs(&mut self, outputs: &mut Vec) -> &mut Self { + self.body.add_outputs(outputs); + self + } + + /// Set the kernel of a transaction. Currently only one kernel is allowed per transaction + pub fn with_kernel(&mut self, kernel: TransactionKernel) -> &mut Self { + self.body.set_kernel(kernel); + self + } + + pub fn with_reward(&mut self, reward: MicroTari) -> &mut Self { + self.reward = Some(reward); + self + } + + /// Build the transaction. + pub fn build( + self, + factories: &CryptoFactories, + prev_header: Option, + height: Option, + ) -> Result { + if let (Some(script_offset), Some(offset)) = (self.script_offset, self.offset) { + let (i, o, k) = self.body.dissolve(); + let tx = Transaction::new(i, o, k, offset, script_offset); + tx.validate_internal_consistency(true, factories, self.reward, prev_header, height)?; + Ok(tx) + } else { + Err(TransactionError::ValidationError( + "Transaction validation failed".into(), + )) + } + } +} + +impl Default for TransactionBuilder { + fn default() -> Self { + Self { + offset: None, + body: AggregateBody::empty(), + reward: None, + script_offset: None, + } + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/transaction_input.rs b/base_layer/core/src/transactions/transaction_entities/transaction_input.rs new file mode 100644 index 0000000000..1012402022 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/transaction_input.rs @@ -0,0 +1,224 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::{ + crypto::{ + commitment::HomomorphicCommitmentFactory, + script::{ExecutionStack, StackItem, TariScript}, + tari_utilities::{hex::Hex, ByteArray, Hashable}, + }, + transactions::{ + transaction_entities, + transaction_entities::{ + transaction_output::TransactionOutput, + OutputFeatures, + TransactionError, + UnblindedOutput, + }, + }, +}; +use blake2::Digest; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + fmt::{Display, Formatter}, +}; +use tari_common_types::types::{Challenge, ComSignature, Commitment, CommitmentFactory, HashDigest, PublicKey}; +use tari_crypto::script::ScriptContext; + +/// A transaction input. +/// +/// Primarily a reference to an output being spent by the transaction. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TransactionInput { + /// The features of the output being spent. We will check maturity for all outputs. + pub features: OutputFeatures, + /// The commitment referencing the output being spent. + pub commitment: Commitment, + /// The serialised script + pub script: TariScript, + /// The script input data, if any + pub input_data: ExecutionStack, + /// A signature with k_s, signing the script, input data, and mined height + pub script_signature: ComSignature, + /// The offset public key, K_O + pub sender_offset_public_key: PublicKey, +} + +/// An input for a transaction that spends an existing output +impl TransactionInput { + /// Create a new Transaction Input + pub fn new( + features: OutputFeatures, + commitment: Commitment, + script: TariScript, + input_data: ExecutionStack, + script_signature: ComSignature, + sender_offset_public_key: PublicKey, + ) -> TransactionInput { + TransactionInput { + features, + commitment, + script, + input_data, + script_signature, + sender_offset_public_key, + } + } + + pub fn build_script_challenge( + nonce_commitment: &Commitment, + script: &TariScript, + input_data: &ExecutionStack, + script_public_key: &PublicKey, + commitment: &Commitment, + ) -> Vec { + Challenge::new() + .chain(nonce_commitment.as_bytes()) + .chain(script.as_bytes().as_slice()) + .chain(input_data.as_bytes().as_slice()) + .chain(script_public_key.as_bytes()) + .chain(commitment.as_bytes()) + .finalize() + .to_vec() + } + + /// Accessor method for the commitment contained in an input + pub fn commitment(&self) -> &Commitment { + &self.commitment + } + + /// Checks if the given un-blinded input instance corresponds to this blinded Transaction Input + pub fn opened_by(&self, input: &UnblindedOutput, factory: &CommitmentFactory) -> bool { + factory.open(&input.spending_key, &input.value.into(), &self.commitment) + } + + /// This will check if the input and the output is the same transactional output by looking at the commitment and + /// features and script. This will ignore all other output and input fields + pub fn is_equal_to(&self, output: &TransactionOutput) -> bool { + self.output_hash() == output.hash() + } + + /// This will run the script contained in the TransactionInput, returning either a script error or the resulting + /// public key. + pub fn run_script(&self, context: Option) -> Result { + let context = context.unwrap_or_default(); + match self.script.execute_with_context(&self.input_data, &context)? { + StackItem::PublicKey(pubkey) => Ok(pubkey), + _ => Err(TransactionError::ScriptExecutionError( + "The script executed successfully but it did not leave a public key on the stack".to_string(), + )), + } + } + + pub fn validate_script_signature( + &self, + public_script_key: &PublicKey, + factory: &CommitmentFactory, + ) -> Result<(), TransactionError> { + let challenge = TransactionInput::build_script_challenge( + self.script_signature.public_nonce(), + &self.script, + &self.input_data, + public_script_key, + &self.commitment, + ); + if self + .script_signature + .verify_challenge(&(&self.commitment + public_script_key), &challenge, factory) + { + Ok(()) + } else { + Err(TransactionError::InvalidSignatureError( + "Verifying script signature".to_string(), + )) + } + } + + /// This will run the script and verify the script signature. If its valid, it will return the resulting public key + /// from the script. + pub fn run_and_verify_script( + &self, + factory: &CommitmentFactory, + context: Option, + ) -> Result { + let key = self.run_script(context)?; + self.validate_script_signature(&key, factory)?; + Ok(key) + } + + /// Returns true if this input is mature at the given height, otherwise false + pub fn is_mature_at(&self, block_height: u64) -> bool { + self.features.maturity <= block_height + } + + /// Returns the hash of the output data contained in this input. + /// This hash matches the hash of a transaction output that this input spends. + pub fn output_hash(&self) -> Vec { + transaction_entities::hash_output(&self.features, &self.commitment, &self.script) + } +} + +/// Implement the canonical hashing function for TransactionInput for use in ordering +impl Hashable for TransactionInput { + fn hash(&self) -> Vec { + HashDigest::new() + .chain(self.features.to_v1_bytes()) + .chain(self.commitment.as_bytes()) + .chain(self.script.as_bytes()) + .chain(self.sender_offset_public_key.as_bytes()) + .chain(self.script_signature.u().as_bytes()) + .chain(self.script_signature.v().as_bytes()) + .chain(self.script_signature.public_nonce().as_bytes()) + .chain(self.input_data.as_bytes()) + .finalize() + .to_vec() + } +} + +impl Display for TransactionInput { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + fmt, + "{} [{:?}], Script hash: ({}), Offset_Pubkey: ({})", + self.commitment.to_hex(), + self.features, + self.script, + self.sender_offset_public_key.to_hex() + ) + } +} + +impl PartialOrd for TransactionInput { + fn partial_cmp(&self, other: &Self) -> Option { + self.commitment.partial_cmp(&other.commitment) + } +} + +impl Ord for TransactionInput { + fn cmp(&self, other: &Self) -> Ordering { + self.commitment.cmp(&other.commitment) + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/transaction_kernel.rs b/base_layer/core/src/transactions/transaction_entities/transaction_kernel.rs new file mode 100644 index 0000000000..939bd9851a --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/transaction_kernel.rs @@ -0,0 +1,139 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::{ + crypto::tari_utilities::{hex::Hex, message_format::MessageFormat, ByteArray, Hashable}, + transactions::{ + tari_amount::MicroTari, + transaction_entities::{KernelFeatures, TransactionError}, + transaction_protocol::{build_challenge, TransactionMetadata}, + }, +}; +use blake2::Digest; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + fmt::{Display, Formatter}, +}; +use tari_common_types::types::{Commitment, HashDigest, Signature}; + +/// The transaction kernel tracks the excess for a given transaction. For an explanation of what the excess is, and +/// why it is necessary, refer to the +/// [Mimblewimble TLU post](https://tlu.tarilabs.com/protocols/mimblewimble-1/sources/PITCHME.link.html?highlight=mimblewimble#mimblewimble). +/// The kernel also tracks other transaction metadata, such as the lock height for the transaction (i.e. the earliest +/// this transaction can be mined) and the transaction fee, in cleartext. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TransactionKernel { + /// Options for a kernel's structure or use + pub features: KernelFeatures, + /// Fee originally included in the transaction this proof is for. + pub fee: MicroTari, + /// This kernel is not valid earlier than lock_height blocks + /// The max lock_height of all *inputs* to this transaction + pub lock_height: u64, + /// Remainder of the sum of all transaction commitments (minus an offset). If the transaction is well-formed, + /// amounts plus fee will sum to zero, and the excess is hence a valid public key. + pub excess: Commitment, + /// An aggregated signature of the metadata in this kernel, signed by the individual excess values and the offset + /// excess of the sender. + pub excess_sig: Signature, +} + +impl TransactionKernel { + pub fn is_coinbase(&self) -> bool { + self.features.contains(KernelFeatures::COINBASE_KERNEL) + } + + pub fn verify_signature(&self) -> Result<(), TransactionError> { + let excess = self.excess.as_public_key(); + let r = self.excess_sig.get_public_nonce(); + let m = TransactionMetadata { + lock_height: self.lock_height, + fee: self.fee, + }; + let c = build_challenge(r, &m); + if self.excess_sig.verify_challenge(excess, &c) { + Ok(()) + } else { + Err(TransactionError::InvalidSignatureError( + "Verifying kernel signature".to_string(), + )) + } + } + + /// This method was used to sort kernels. It has been replaced, and will be removed in future + pub fn deprecated_cmp(&self, other: &Self) -> Ordering { + self.features + .cmp(&other.features) + .then(self.fee.cmp(&other.fee)) + .then(self.lock_height.cmp(&other.lock_height)) + .then(self.excess.cmp(&other.excess)) + .then(self.excess_sig.cmp(&other.excess_sig)) + } +} + +impl Hashable for TransactionKernel { + /// Produce a canonical hash for a transaction kernel. The hash is given by + /// $$ H(feature_bits | fee | lock_height | P_excess | R_sum | s_sum) + fn hash(&self) -> Vec { + HashDigest::new() + .chain(&[self.features.bits()]) + .chain(u64::from(self.fee).to_le_bytes()) + .chain(self.lock_height.to_le_bytes()) + .chain(self.excess.as_bytes()) + .chain(self.excess_sig.get_public_nonce().as_bytes()) + .chain(self.excess_sig.get_signature().as_bytes()) + .finalize() + .to_vec() + } +} + +impl Display for TransactionKernel { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + fmt, + "Fee: {}\nLock height: {}\nFeatures: {:?}\nExcess: {}\nExcess signature: {}\n", + self.fee, + self.lock_height, + self.features, + self.excess.to_hex(), + self.excess_sig + .to_json() + .unwrap_or_else(|_| "Failed to serialize signature".into()), + ) + } +} + +impl PartialOrd for TransactionKernel { + fn partial_cmp(&self, other: &Self) -> Option { + self.excess_sig.partial_cmp(&other.excess_sig) + } +} + +impl Ord for TransactionKernel { + fn cmp(&self, other: &Self) -> Ordering { + self.excess_sig.cmp(&other.excess_sig) + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/transaction_output.rs b/base_layer/core/src/transactions/transaction_entities/transaction_output.rs new file mode 100644 index 0000000000..204278b27a --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/transaction_output.rs @@ -0,0 +1,368 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::{ + crypto::{ + commitment::HomomorphicCommitmentFactory, + range_proof::RangeProofService as RangeProofServiceTrait, + ristretto::pedersen::PedersenCommitmentFactory, + script::TariScript, + tari_utilities::{hex::Hex, ByteArray, Hashable}, + }, + transactions::{ + tari_amount::MicroTari, + transaction_entities, + transaction_entities::{ + full_rewind_result::FullRewindResult, + rewind_result::RewindResult, + OutputFeatures, + OutputFlags, + TransactionError, + TransactionInput, + }, + }, +}; +use blake2::Digest; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + fmt::{Display, Formatter}, +}; +use tari_common_types::types::{ + BlindingFactor, + Challenge, + ComSignature, + Commitment, + CommitmentFactory, + HashDigest, + MessageHash, + PrivateKey, + PublicKey, + RangeProof, + RangeProofService, +}; +use tari_crypto::keys::{PublicKey as PublicKeyTrait, SecretKey}; + +/// Output for a transaction, defining the new ownership of coins that are being transferred. The commitment is a +/// blinded value for the output while the range proof guarantees the commitment includes a positive value without +/// overflow and the ownership of the private key. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TransactionOutput { + /// Options for an output's structure or use + pub features: OutputFeatures, + /// The homomorphic commitment representing the output amount + pub commitment: Commitment, + /// A proof that the commitment is in the right range + pub proof: RangeProof, + /// The script that will be executed when spending this output + pub script: TariScript, + /// Tari script offset pubkey, K_O + pub sender_offset_public_key: PublicKey, + /// UTXO signature with the script offset private key, k_O + pub metadata_signature: ComSignature, +} + +/// An output for a transaction, includes a range proof and Tari script metadata +impl TransactionOutput { + /// Create new Transaction Output + pub fn new( + features: OutputFeatures, + commitment: Commitment, + proof: RangeProof, + script: TariScript, + sender_offset_public_key: PublicKey, + metadata_signature: ComSignature, + ) -> TransactionOutput { + TransactionOutput { + features, + commitment, + proof, + script, + sender_offset_public_key, + metadata_signature, + } + } + + /// Accessor method for the commitment contained in an output + pub fn commitment(&self) -> &Commitment { + &self.commitment + } + + /// Accessor method for the range proof contained in an output + pub fn proof(&self) -> &RangeProof { + &self.proof + } + + /// Verify that range proof is valid + pub fn verify_range_proof(&self, prover: &RangeProofService) -> Result { + Ok(prover.verify(&self.proof.0, &self.commitment)) + } + + /// Verify that the metadata signature is valid + pub fn verify_metadata_signature(&self) -> Result<(), TransactionError> { + let challenge = TransactionOutput::build_metadata_signature_challenge( + &self.script, + &self.features, + &self.sender_offset_public_key, + self.metadata_signature.public_nonce(), + &self.commitment, + ); + if !self.metadata_signature.verify_challenge( + &(&self.commitment + &self.sender_offset_public_key), + &challenge, + &PedersenCommitmentFactory::default(), + ) { + return Err(TransactionError::InvalidSignatureError( + "Metadata signature not valid!".to_string(), + )); + } + Ok(()) + } + + /// Attempt to rewind the range proof to reveal the proof message and committed value + pub fn rewind_range_proof_value_only( + &self, + prover: &RangeProofService, + rewind_public_key: &PublicKey, + rewind_blinding_public_key: &PublicKey, + ) -> Result { + Ok(prover + .rewind_proof_value_only( + &self.proof.0, + &self.commitment, + rewind_public_key, + rewind_blinding_public_key, + )? + .into()) + } + + /// Attempt to fully rewind the range proof to reveal the proof message, committed value and blinding factor + pub fn full_rewind_range_proof( + &self, + prover: &RangeProofService, + rewind_key: &PrivateKey, + rewind_blinding_key: &PrivateKey, + ) -> Result { + Ok(prover + .rewind_proof_commitment_data(&self.proof.0, &self.commitment, rewind_key, rewind_blinding_key)? + .into()) + } + + /// This will check if the input and the output is the same commitment by looking at the commitment and features. + /// This will ignore the output range proof + #[inline] + pub fn is_equal_to(&self, output: &TransactionInput) -> bool { + self.commitment == output.commitment && self.features == output.features + } + + /// Returns true if the output is a coinbase, otherwise false + pub fn is_coinbase(&self) -> bool { + self.features.flags.contains(OutputFlags::COINBASE_OUTPUT) + } + + /// Convenience function that returns the challenge for the metadata commitment signature + pub fn get_metadata_signature_challenge(&self, partial_commitment_nonce: Option<&PublicKey>) -> MessageHash { + let nonce_commitment = match partial_commitment_nonce { + None => self.metadata_signature.public_nonce().clone(), + Some(partial_nonce) => self.metadata_signature.public_nonce() + partial_nonce, + }; + TransactionOutput::build_metadata_signature_challenge( + &self.script, + &self.features, + &self.sender_offset_public_key, + &nonce_commitment, + &self.commitment, + ) + } + + /// Convenience function that calculates the challenge for the metadata commitment signature + pub fn build_metadata_signature_challenge( + script: &TariScript, + features: &OutputFeatures, + sender_offset_public_key: &PublicKey, + public_commitment_nonce: &Commitment, + commitment: &Commitment, + ) -> MessageHash { + Challenge::new() + .chain(public_commitment_nonce.as_bytes()) + .chain(script.as_bytes()) + // TODO: Use consensus encoded bytes #testnet_reset + .chain(features.to_v1_bytes()) + .chain(sender_offset_public_key.as_bytes()) + .chain(commitment.as_bytes()) + .finalize() + .to_vec() + } + + // Create commitment signature for the metadata + fn create_metadata_signature( + value: &MicroTari, + spending_key: &BlindingFactor, + script: &TariScript, + output_features: &OutputFeatures, + sender_offset_public_key: &PublicKey, + partial_commitment_nonce: Option<&PublicKey>, + sender_offset_private_key: Option<&PrivateKey>, + ) -> Result { + let nonce_a = PrivateKey::random(&mut OsRng); + let nonce_b = PrivateKey::random(&mut OsRng); + let nonce_commitment = PedersenCommitmentFactory::default().commit(&nonce_b, &nonce_a); + let nonce_commitment = match partial_commitment_nonce { + None => nonce_commitment, + Some(partial_nonce) => &nonce_commitment + partial_nonce, + }; + let value = PrivateKey::from(value.as_u64()); + let commitment = PedersenCommitmentFactory::default().commit(spending_key, &value); + let e = TransactionOutput::build_metadata_signature_challenge( + script, + output_features, + sender_offset_public_key, + &nonce_commitment, + &commitment, + ); + let secret_x = match sender_offset_private_key { + None => spending_key.clone(), + Some(key) => &spending_key.clone() + key, + }; + Ok(ComSignature::sign( + value, + secret_x, + nonce_a, + nonce_b, + &e, + &PedersenCommitmentFactory::default(), + )?) + } + + /// Create partial commitment signature for the metadata, usually done by the receiver + pub fn create_partial_metadata_signature( + value: &MicroTari, + spending_key: &BlindingFactor, + script: &TariScript, + output_features: &OutputFeatures, + sender_offset_public_key: &PublicKey, + partial_commitment_nonce: &PublicKey, + ) -> Result { + TransactionOutput::create_metadata_signature( + value, + spending_key, + script, + output_features, + sender_offset_public_key, + Some(partial_commitment_nonce), + None, + ) + } + + /// Create final commitment signature for the metadata, signing with both keys + pub fn create_final_metadata_signature( + value: &MicroTari, + spending_key: &BlindingFactor, + script: &TariScript, + output_features: &OutputFeatures, + sender_offset_private_key: &PrivateKey, + ) -> Result { + let sender_offset_public_key = PublicKey::from_secret_key(sender_offset_private_key); + TransactionOutput::create_metadata_signature( + value, + spending_key, + script, + output_features, + &sender_offset_public_key, + None, + Some(sender_offset_private_key), + ) + } + + pub fn witness_hash(&self) -> Vec { + HashDigest::new() + .chain(self.proof.as_bytes()) + .chain(self.metadata_signature.u().as_bytes()) + .chain(self.metadata_signature.v().as_bytes()) + .chain(self.metadata_signature.public_nonce().as_bytes()) + .finalize() + .to_vec() + } +} + +/// Implement the canonical hashing function for TransactionOutput for use in ordering. +impl Hashable for TransactionOutput { + fn hash(&self) -> Vec { + transaction_entities::hash_output(&self.features, &self.commitment, &self.script) + } +} + +impl Default for TransactionOutput { + fn default() -> Self { + TransactionOutput::new( + OutputFeatures::default(), + CommitmentFactory::default().zero(), + RangeProof::default(), + TariScript::default(), + PublicKey::default(), + ComSignature::default(), + ) + } +} + +impl Display for TransactionOutput { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + let proof = self.proof.to_hex(); + let proof = if proof.len() > 32 { + format!( + "{}..{}", + proof[0..16].to_string(), + proof[proof.len() - 16..proof.len()].to_string() + ) + } else { + proof + }; + write!( + fmt, + "{} [{:?}], Script: ({}), Offset Pubkey: ({}), Metadata Signature: ({}, {}, {}), Proof: {}", + self.commitment.to_hex(), + self.features, + self.script, + self.sender_offset_public_key.to_hex(), + self.metadata_signature.u().to_hex(), + self.metadata_signature.v().to_hex(), + self.metadata_signature.public_nonce().to_hex(), + proof + ) + } +} + +impl PartialOrd for TransactionOutput { + fn partial_cmp(&self, other: &Self) -> Option { + self.commitment.partial_cmp(&other.commitment) + } +} + +impl Ord for TransactionOutput { + fn cmp(&self, other: &Self) -> Ordering { + self.commitment.cmp(&other.commitment) + } +} diff --git a/base_layer/core/src/transactions/transaction_entities/unblinded_output.rs b/base_layer/core/src/transactions/transaction_entities/unblinded_output.rs new file mode 100644 index 0000000000..7e4208db3b --- /dev/null +++ b/base_layer/core/src/transactions/transaction_entities/unblinded_output.rs @@ -0,0 +1,225 @@ +// Copyright 2018 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// 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 +// +// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, +// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. + +use crate::{ + consensus::{ConsensusEncodingSized, ConsensusEncodingWrapper}, + crypto::{ + commitment::HomomorphicCommitmentFactory, + range_proof::{RangeProofError, RangeProofService}, + script::{ExecutionStack, TariScript}, + }, + transactions::{ + tari_amount::MicroTari, + transaction_entities, + transaction_entities::{ + transaction_input::TransactionInput, + transaction_output::TransactionOutput, + OutputFeatures, + TransactionError, + }, + transaction_protocol::RewindData, + CryptoFactories, + }, +}; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; +use std::{cmp::Ordering, ops::Shl}; +use tari_common_types::types::{BlindingFactor, ComSignature, CommitmentFactory, PrivateKey, PublicKey, RangeProof}; +use tari_crypto::{ + keys::{PublicKey as PublicKeyTrait, SecretKey}, + tari_utilities::ByteArray, +}; + +/// An unblinded output is one where the value and spending key (blinding factor) are known. This can be used to +/// build both inputs and outputs (every input comes from an output) +// TODO: Try to get rid of 'Serialize' and 'Deserialize' traits here; see related comment at 'struct RawTransactionInfo' +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UnblindedOutput { + pub value: MicroTari, + pub spending_key: BlindingFactor, + pub features: OutputFeatures, + pub script: TariScript, + pub input_data: ExecutionStack, + pub script_private_key: PrivateKey, + pub sender_offset_public_key: PublicKey, + pub metadata_signature: ComSignature, + pub script_lock_height: u64, +} + +impl UnblindedOutput { + /// Creates a new un-blinded output + #[allow(clippy::too_many_arguments)] + pub fn new( + value: MicroTari, + spending_key: BlindingFactor, + features: OutputFeatures, + script: TariScript, + input_data: ExecutionStack, + script_private_key: PrivateKey, + sender_offset_public_key: PublicKey, + metadata_signature: ComSignature, + script_lock_height: u64, + ) -> UnblindedOutput { + UnblindedOutput { + value, + spending_key, + features, + script, + input_data, + script_private_key, + sender_offset_public_key, + metadata_signature, + script_lock_height, + } + } + + /// Commits an UnblindedOutput into a Transaction input + pub fn as_transaction_input(&self, factory: &CommitmentFactory) -> Result { + let commitment = factory.commit(&self.spending_key, &self.value.into()); + let script_nonce_a = PrivateKey::random(&mut OsRng); + let script_nonce_b = PrivateKey::random(&mut OsRng); + let nonce_commitment = factory.commit(&script_nonce_b, &script_nonce_a); + + let challenge = TransactionInput::build_script_challenge( + &nonce_commitment, + &self.script, + &self.input_data, + &PublicKey::from_secret_key(&self.script_private_key), + &commitment, + ); + let script_signature = ComSignature::sign( + self.value.into(), + &self.script_private_key + &self.spending_key, + script_nonce_a, + script_nonce_b, + &challenge, + factory, + ) + .map_err(|_| TransactionError::InvalidSignatureError("Generating script signature".to_string()))?; + + Ok(TransactionInput { + features: self.features.clone(), + commitment, + script: self.script.clone(), + input_data: self.input_data.clone(), + script_signature, + sender_offset_public_key: self.sender_offset_public_key.clone(), + }) + } + + pub fn as_transaction_output(&self, factories: &CryptoFactories) -> Result { + if factories.range_proof.range() < 64 && self.value >= MicroTari::from(1u64.shl(&factories.range_proof.range())) + { + return Err(TransactionError::ValidationError( + "Value provided is outside the range allowed by the range proof".into(), + )); + } + let commitment = factories.commitment.commit(&self.spending_key, &self.value.into()); + let output = TransactionOutput { + features: self.features.clone(), + commitment, + proof: RangeProof::from_bytes( + &factories + .range_proof + .construct_proof(&self.spending_key, self.value.into())?, + ) + .map_err(|_| TransactionError::RangeProofError(RangeProofError::ProofConstructionError))?, + script: self.script.clone(), + sender_offset_public_key: self.sender_offset_public_key.clone(), + metadata_signature: self.metadata_signature.clone(), + }; + + Ok(output) + } + + pub fn as_rewindable_transaction_output( + &self, + factories: &CryptoFactories, + rewind_data: &RewindData, + ) -> Result { + if factories.range_proof.range() < 64 && self.value >= MicroTari::from(1u64.shl(&factories.range_proof.range())) + { + return Err(TransactionError::ValidationError( + "Value provided is outside the range allowed by the range proof".into(), + )); + } + let commitment = factories.commitment.commit(&self.spending_key, &self.value.into()); + + let proof_bytes = factories.range_proof.construct_proof_with_rewind_key( + &self.spending_key, + self.value.into(), + &rewind_data.rewind_key, + &rewind_data.rewind_blinding_key, + &rewind_data.proof_message, + )?; + + let proof = RangeProof::from_bytes(&proof_bytes) + .map_err(|_| TransactionError::RangeProofError(RangeProofError::ProofConstructionError))?; + + let output = TransactionOutput { + features: self.features.clone(), + commitment, + proof, + script: self.script.clone(), + sender_offset_public_key: self.sender_offset_public_key.clone(), + metadata_signature: self.metadata_signature.clone(), + }; + + Ok(output) + } + + pub fn metadata_byte_size(&self) -> usize { + self.features.consensus_encode_exact_size() + + ConsensusEncodingWrapper::wrap(&self.script).consensus_encode_exact_size() + } + + // Note: The Hashable trait is not used here due to the dependency on `CryptoFactories`, and `commitment` us not + // Note: added to the struct to ensure the atomic nature between `commitment`, `spending_key` and `value`. + pub fn hash(&self, factories: &CryptoFactories) -> Vec { + let commitment = factories.commitment.commit_value(&self.spending_key, self.value.into()); + transaction_entities::hash_output(&self.features, &commitment, &self.script) + } +} + +// These implementations are used for order these outputs for UTXO selection which will be done by comparing the values +impl Eq for UnblindedOutput {} + +impl PartialEq for UnblindedOutput { + fn eq(&self, other: &UnblindedOutput) -> bool { + self.value == other.value + } +} + +impl PartialOrd for UnblindedOutput { + fn partial_cmp(&self, other: &Self) -> Option { + self.value.partial_cmp(&other.value) + } +} + +impl Ord for UnblindedOutput { + fn cmp(&self, other: &Self) -> Ordering { + self.value.cmp(&other.value) + } +} diff --git a/base_layer/core/src/transactions/transaction_protocol/mod.rs b/base_layer/core/src/transactions/transaction_protocol/mod.rs index 62907deb92..2ed49efa68 100644 --- a/base_layer/core/src/transactions/transaction_protocol/mod.rs +++ b/base_layer/core/src/transactions/transaction_protocol/mod.rs @@ -82,17 +82,8 @@ // #![allow(clippy::op_ref)] -pub mod proto; -pub mod recipient; -pub mod sender; -pub mod single_receiver; -pub mod transaction_initializer; - -use crate::transactions::{tari_amount::*, transaction::TransactionError}; use digest::Digest; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{MessageHash, PrivateKey, PublicKey}; -use tari_comms::types::Challenge; use tari_crypto::{ range_proof::{RangeProofError, REWIND_USER_MESSAGE_LENGTH}, signatures::SchnorrSignatureError, @@ -100,6 +91,17 @@ use tari_crypto::{ }; use thiserror::Error; +use tari_common_types::types::{MessageHash, PrivateKey, PublicKey}; +use tari_comms::types::Challenge; + +use crate::transactions::{tari_amount::*, transaction_entities::error::TransactionError}; + +pub mod proto; +pub mod recipient; +pub mod sender; +pub mod single_receiver; +pub mod transaction_initializer; + #[derive(Clone, Debug, PartialEq, Error, Deserialize, Serialize)] pub enum TransactionProtocolError { #[error("The current state is not yet completed, cannot transition to next state: `{0}`")] diff --git a/base_layer/core/src/transactions/transaction_protocol/recipient.rs b/base_layer/core/src/transactions/transaction_protocol/recipient.rs index 2136b48a0d..2ddf1c5e02 100644 --- a/base_layer/core/src/transactions/transaction_protocol/recipient.rs +++ b/base_layer/core/src/transactions/transaction_protocol/recipient.rs @@ -24,9 +24,11 @@ use std::{collections::HashMap, fmt}; use serde::{Deserialize, Serialize}; +use tari_common_types::types::{MessageHash, PrivateKey, PublicKey, Signature}; + use crate::transactions::{ crypto_factories::CryptoFactories, - transaction::{OutputFeatures, TransactionOutput}, + transaction_entities::{output_features::OutputFeatures, transaction_output::TransactionOutput}, transaction_protocol::{ sender::{SingleRoundSenderData as SD, TransactionSenderMessage}, single_receiver::SingleReceiverTransactionProtocol, @@ -34,7 +36,6 @@ use crate::transactions::{ TransactionProtocolError, }, }; -use tari_common_types::types::{MessageHash, PrivateKey, PublicKey, Signature}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[allow(clippy::large_enum_variant)] @@ -212,13 +213,15 @@ mod test { keys::{PublicKey as PK, SecretKey as SecretKeyTrait}, }; + use tari_common_types::types::{PrivateKey, PublicKey, Signature}; + use crate::{ crypto::script::TariScript, transactions::{ crypto_factories::CryptoFactories, tari_amount::*, test_helpers::TestParams, - transaction::OutputFeatures, + transaction_entities::output_features::OutputFeatures, transaction_protocol::{ build_challenge, sender::{SingleRoundSenderData, TransactionSenderMessage}, @@ -228,7 +231,6 @@ mod test { ReceiverTransactionProtocol, }, }; - use tari_common_types::types::{PrivateKey, PublicKey, Signature}; #[test] fn single_round_recipient() { diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index 3d626270ac..dc90972098 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -26,7 +26,7 @@ use crate::{ crypto_factories::CryptoFactories, fee::Fee, tari_amount::*, - transaction::{ + transaction_entities::{ KernelBuilder, KernelFeatures, OutputFeatures, @@ -756,7 +756,7 @@ mod test { crypto_factories::CryptoFactories, tari_amount::*, test_helpers::{create_test_input, create_unblinded_output, TestParams}, - transaction::{KernelFeatures, OutputFeatures, TransactionOutput}, + transaction_entities::{KernelFeatures, OutputFeatures, TransactionOutput}, transaction_protocol::{ sender::SenderTransactionProtocol, single_receiver::SingleReceiverTransactionProtocol, diff --git a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs index ab26e3e80c..93356a4a5f 100644 --- a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs +++ b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs @@ -27,9 +27,11 @@ use tari_crypto::{ tari_utilities::byte_array::ByteArray, }; +use tari_common_types::types::{PrivateKey as SK, PublicKey, RangeProof, Signature}; + use crate::transactions::{ crypto_factories::CryptoFactories, - transaction::{OutputFeatures, TransactionOutput}, + transaction_entities::{output_features::OutputFeatures, transaction_output::TransactionOutput}, transaction_protocol::{ build_challenge, recipient::RecipientSignedMessage as RD, @@ -38,7 +40,6 @@ use crate::transactions::{ TransactionProtocolError as TPE, }, }; -use tari_common_types::types::{PrivateKey as SK, PublicKey, RangeProof, Signature}; /// SingleReceiverTransactionProtocol represents the actions taken by the single receiver in the one-round Tari /// transaction protocol. The procedure is straightforward. Upon receiving the sender's information, the receiver: @@ -142,10 +143,12 @@ mod test { script::TariScript, }; + use tari_common_types::types::{PrivateKey, PublicKey}; + use crate::transactions::{ crypto_factories::CryptoFactories, tari_amount::*, - transaction::OutputFeatures, + transaction_entities::output_features::OutputFeatures, transaction_protocol::{ build_challenge, sender::SingleRoundSenderData, @@ -154,7 +157,6 @@ mod test { TransactionProtocolError, }, }; - use tari_common_types::types::{PrivateKey, PublicKey}; fn generate_output_parms() -> (PrivateKey, PrivateKey, OutputFeatures) { let r = PrivateKey::random(&mut OsRng); diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index 7657275782..4f831f1922 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -26,7 +26,7 @@ use crate::{ crypto_factories::CryptoFactories, fee::Fee, tari_amount::*, - transaction::{ + transaction_entities::{ OutputFeatures, TransactionInput, TransactionOutput, @@ -652,7 +652,7 @@ mod test { fee::Fee, tari_amount::*, test_helpers::{create_test_input, create_unblinded_output, TestParams, UtxoTestParams}, - transaction::{OutputFeatures, MAX_TRANSACTION_INPUTS}, + transaction_entities::{OutputFeatures, MAX_TRANSACTION_INPUTS}, transaction_protocol::{ sender::SenderState, transaction_initializer::SenderTransactionInitializer, diff --git a/base_layer/core/src/validation/block_validators/async_validator.rs b/base_layer/core/src/validation/block_validators/async_validator.rs index d8f5cb9dfe..7fd1617f64 100644 --- a/base_layer/core/src/validation/block_validators/async_validator.rs +++ b/base_layer/core/src/validation/block_validators/async_validator.rs @@ -28,7 +28,7 @@ use crate::{ iterators::NonOverlappingIntegerPairIter, transactions::{ aggregated_body::AggregateBody, - transaction::{KernelSum, TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, + transaction_entities::{KernelSum, TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, CryptoFactories, }, validation::{ diff --git a/base_layer/core/src/validation/block_validators/test.rs b/base_layer/core/src/validation/block_validators/test.rs index 2f59a67c11..2b5fcbbd75 100644 --- a/base_layer/core/src/validation/block_validators/test.rs +++ b/base_layer/core/src/validation/block_validators/test.rs @@ -20,6 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::Arc; + +use tari_common::configuration::Network; +use tari_test_utils::unpack_enum; + use crate::{ consensus::{ConsensusConstantsBuilder, ConsensusManager}, test_helpers::{ @@ -30,16 +35,13 @@ use crate::{ aggregated_body::AggregateBody, tari_amount::T, test_helpers::schema_to_transaction, - transaction::TransactionError, + transaction_entities::error::TransactionError, CoinbaseBuilder, CryptoFactories, }, txn_schema, validation::{block_validators::BlockValidator, ValidationError}, }; -use std::sync::Arc; -use tari_common::configuration::Network; -use tari_test_utils::unpack_enum; fn setup_with_rules(rules: ConsensusManager) -> (TestBlockchain, BlockValidator) { let blockchain = TestBlockchain::create(rules.clone()); diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index ff31cded40..369cf1831a 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -20,15 +20,17 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use thiserror::Error; +use tokio::task; + +use tari_common_types::types::HashOutput; + use crate::{ blocks::{BlockHeaderValidationError, BlockValidationError}, chain_storage::ChainStorageError, proof_of_work::{monero_rx::MergeMineError, PowError}, - transactions::transaction::TransactionError, + transactions::transaction_entities::error::TransactionError, }; -use tari_common_types::types::HashOutput; -use thiserror::Error; -use tokio::task; #[derive(Debug, Error)] pub enum ValidationError { diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index f8d54f88a6..3a8f4b0152 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -41,7 +41,7 @@ use crate::{ }, transactions::{ tari_amount::MicroTari, - transaction::{KernelSum, TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, + transaction_entities::{KernelSum, TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, CryptoFactories, }, validation::ValidationError, diff --git a/base_layer/core/src/validation/mocks.rs b/base_layer/core/src/validation/mocks.rs index 7e9543dc11..c8cd2d2e9f 100644 --- a/base_layer/core/src/validation/mocks.rs +++ b/base_layer/core/src/validation/mocks.rs @@ -20,11 +20,20 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +use async_trait::async_trait; + +use tari_common_types::{chain_metadata::ChainMetadata, types::Commitment}; + use crate::{ blocks::{Block, BlockHeader, ChainBlock}, chain_storage::BlockchainBackend, proof_of_work::{sha3_difficulty, AchievedTargetDifficulty, Difficulty, PowAlgorithm}, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, validation::{ error::ValidationError, BlockSyncBodyValidation, @@ -36,12 +45,6 @@ use crate::{ PostOrphanBodyValidation, }, }; -use async_trait::async_trait; -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, -}; -use tari_common_types::{chain_metadata::ChainMetadata, types::Commitment}; #[derive(Clone)] pub struct MockValidator { diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index 65d66da23c..b3e3c7f103 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -25,6 +25,7 @@ use std::sync::Arc; use tari_crypto::{commitment::HomomorphicCommitment, script}; use tari_common::configuration::Network; +use tari_common_types::types::Commitment; use crate::{ blocks::{BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader}, @@ -36,12 +37,16 @@ use crate::{ transactions::{ tari_amount::{uT, MicroTari}, test_helpers::{create_random_signature_from_s_key, create_utxo}, - transaction::{KernelBuilder, KernelFeatures, OutputFeatures, TransactionKernel}, + transaction_entities::{ + kernel_builder::KernelBuilder, + output_features::OutputFeatures, + transaction_kernel::TransactionKernel, + KernelFeatures, + }, CryptoFactories, }, validation::{header_iter::HeaderIter, ChainBalanceValidator, FinalHorizonStateValidation}, }; -use tari_common_types::types::Commitment; #[test] fn header_iter_empty_and_invalid_height() { diff --git a/base_layer/core/src/validation/traits.rs b/base_layer/core/src/validation/traits.rs index dde06df434..25839f8f61 100644 --- a/base_layer/core/src/validation/traits.rs +++ b/base_layer/core/src/validation/traits.rs @@ -20,15 +20,17 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use async_trait::async_trait; + +use tari_common_types::{chain_metadata::ChainMetadata, types::Commitment}; + use crate::{ blocks::{Block, BlockHeader, ChainBlock}, chain_storage::BlockchainBackend, proof_of_work::AchievedTargetDifficulty, - transactions::transaction::Transaction, + transactions::transaction_entities::transaction::Transaction, validation::{error::ValidationError, DifficultyCalculator}, }; -use async_trait::async_trait; -use tari_common_types::{chain_metadata::ChainMetadata, types::Commitment}; /// A validator that determines if a block body is valid, assuming that the header has already been /// validated diff --git a/base_layer/core/src/validation/transaction_validators.rs b/base_layer/core/src/validation/transaction_validators.rs index 0d5b23d80f..c0d384f98d 100644 --- a/base_layer/core/src/validation/transaction_validators.rs +++ b/base_layer/core/src/validation/transaction_validators.rs @@ -24,7 +24,7 @@ use log::*; use crate::{ chain_storage::{BlockchainBackend, BlockchainDatabase}, - transactions::{transaction::Transaction, CryptoFactories}, + transactions::{transaction_entities::Transaction, CryptoFactories}, validation::{ helpers::{check_inputs_are_utxos, check_not_duplicate_txos}, MempoolTransactionValidation, diff --git a/base_layer/core/tests/async_db.rs b/base_layer/core/tests/async_db.rs index 380db7f73a..180d3993ec 100644 --- a/base_layer/core/tests/async_db.rs +++ b/base_layer/core/tests/async_db.rs @@ -38,7 +38,7 @@ use tari_core::{ transactions::{ tari_amount::T, test_helpers::schema_to_transaction, - transaction::{TransactionOutput, UnblindedOutput}, + transaction_entities::{TransactionOutput, UnblindedOutput}, CryptoFactories, }, txn_schema, diff --git a/base_layer/core/tests/base_node_rpc.rs b/base_layer/core/tests/base_node_rpc.rs index d89e5d2b30..23e2f267fb 100644 --- a/base_layer/core/tests/base_node_rpc.rs +++ b/base_layer/core/tests/base_node_rpc.rs @@ -44,9 +44,9 @@ use std::convert::TryFrom; +use randomx_rs::RandomXFlag; use tempfile::{tempdir, TempDir}; -use randomx_rs::RandomXFlag; use tari_common::configuration::Network; use tari_comms::protocol::rpc::mock::RpcRequestMock; use tari_core::{ @@ -62,6 +62,7 @@ use tari_core::{ rpc::{BaseNodeWalletRpcService, BaseNodeWalletService}, state_machine_service::states::{ListeningInfo, StateInfo, StatusInfo}, }, + blocks::ChainBlock, consensus::{ConsensusManager, ConsensusManagerBuilder, NetworkConsensus}, crypto::tari_utilities::Hashable, proto::{ @@ -72,7 +73,7 @@ use tari_core::{ transactions::{ tari_amount::{uT, T}, test_helpers::schema_to_transaction, - transaction::{TransactionOutput, UnblindedOutput}, + transaction_entities::{TransactionOutput, UnblindedOutput}, CryptoFactories, }, txn_schema, @@ -82,7 +83,6 @@ use crate::helpers::{ block_builders::{chain_block, create_genesis_block_with_coinbase_value}, nodes::{BaseNodeBuilder, NodeInterfaces}, }; -use tari_core::blocks::ChainBlock; mod helpers; diff --git a/base_layer/core/tests/block_validation.rs b/base_layer/core/tests/block_validation.rs index c838e88aa4..a689152002 100644 --- a/base_layer/core/tests/block_validation.rs +++ b/base_layer/core/tests/block_validation.rs @@ -20,19 +20,12 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::helpers::{ - block_builders::{ - chain_block_with_coinbase, - chain_block_with_new_coinbase, - create_coinbase, - create_genesis_block_with_utxos, - find_header_with_achieved_difficulty, - }, - test_blockchain::TestBlockchain, -}; +use std::sync::Arc; + use monero::blockdata::block::Block as MoneroBlock; use rand::{rngs::OsRng, RngCore}; -use std::sync::Arc; +use tari_crypto::{inputs, script}; + use tari_common::configuration::Network; use tari_core::{ blocks::{Block, BlockHeaderAccumulatedData, BlockHeaderValidationError, BlockValidationError, ChainBlock}, @@ -51,7 +44,7 @@ use tari_core::{ aggregated_body::AggregateBody, tari_amount::{uT, T}, test_helpers::{create_unblinded_output, schema_to_transaction, spend_utxos, TestParams, UtxoTestParams}, - transaction::OutputFeatures, + transaction_entities::OutputFeatures, CryptoFactories, }, txn_schema, @@ -67,7 +60,17 @@ use tari_core::{ ValidationError, }, }; -use tari_crypto::{inputs, script}; + +use crate::helpers::{ + block_builders::{ + chain_block_with_coinbase, + chain_block_with_new_coinbase, + create_coinbase, + create_genesis_block_with_utxos, + find_header_with_achieved_difficulty, + }, + test_blockchain::TestBlockchain, +}; mod helpers; diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index 66347d7918..455c3063ed 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -48,7 +48,7 @@ use tari_core::{ TestParams, TransactionSchema, }, - transaction::{ + transaction_entities::{ KernelBuilder, KernelFeatures, OutputFeatures, diff --git a/base_layer/core/tests/helpers/database.rs b/base_layer/core/tests/helpers/database.rs index e1132445a3..e4af43b58f 100644 --- a/base_layer/core/tests/helpers/database.rs +++ b/base_layer/core/tests/helpers/database.rs @@ -23,7 +23,7 @@ use tari_core::{ blocks::{Block, BlockHeader, NewBlockTemplate}, consensus::{emission::Emission, ConsensusManager}, - transactions::{tari_amount::MicroTari, transaction::Transaction, CryptoFactories}, + transactions::{tari_amount::MicroTari, transaction_entities::Transaction, CryptoFactories}, }; use crate::helpers::block_builders::create_coinbase; diff --git a/base_layer/core/tests/helpers/sample_blockchains.rs b/base_layer/core/tests/helpers/sample_blockchains.rs index 10f0d7baae..6f2844cbe3 100644 --- a/base_layer/core/tests/helpers/sample_blockchains.rs +++ b/base_layer/core/tests/helpers/sample_blockchains.rs @@ -29,7 +29,7 @@ use tari_core::{ test_helpers::blockchain::{create_store_with_consensus, TempDatabase}, transactions::{ tari_amount::{uT, T}, - transaction::UnblindedOutput, + transaction_entities::UnblindedOutput, CryptoFactories, }, txn_schema, diff --git a/base_layer/core/tests/helpers/test_block_builder.rs b/base_layer/core/tests/helpers/test_block_builder.rs index 2d036d212e..2e2aeccdfc 100644 --- a/base_layer/core/tests/helpers/test_block_builder.rs +++ b/base_layer/core/tests/helpers/test_block_builder.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -use tari_core::transactions::transaction::Transaction; +use tari_core::transactions::transaction_entities::Transaction; #[derive(Default)] pub struct TestBlockBuilder {} diff --git a/base_layer/core/tests/helpers/test_blockchain.rs b/base_layer/core/tests/helpers/test_blockchain.rs index ed6ab1234e..1c2ebf81eb 100644 --- a/base_layer/core/tests/helpers/test_blockchain.rs +++ b/base_layer/core/tests/helpers/test_blockchain.rs @@ -33,7 +33,7 @@ use tari_core::{ chain_storage::{BlockAddResult, BlockchainDatabase, ChainStorageError}, consensus::ConsensusManager, test_helpers::blockchain::TempDatabase, - transactions::{transaction::UnblindedOutput, CryptoFactories}, + transactions::{transaction_entities::UnblindedOutput, CryptoFactories}, }; use crate::helpers::{ diff --git a/base_layer/core/tests/mempool.rs b/base_layer/core/tests/mempool.rs index 444ac1f815..2e25653a67 100644 --- a/base_layer/core/tests/mempool.rs +++ b/base_layer/core/tests/mempool.rs @@ -51,7 +51,7 @@ use tari_core::{ fee::Fee, tari_amount::{uT, MicroTari, T}, test_helpers::{create_unblinded_output, schema_to_transaction, spend_utxos, TestParams}, - transaction::{KernelBuilder, OutputFeatures, Transaction, TransactionOutput}, + transaction_entities::{KernelBuilder, OutputFeatures, Transaction, TransactionOutput}, transaction_protocol::{build_challenge, TransactionMetadata}, CryptoFactories, }, diff --git a/base_layer/core/tests/node_comms_interface.rs b/base_layer/core/tests/node_comms_interface.rs index 9e1bce86f1..e8975ffc9c 100644 --- a/base_layer/core/tests/node_comms_interface.rs +++ b/base_layer/core/tests/node_comms_interface.rs @@ -38,7 +38,7 @@ use tari_core::{ transactions::{ tari_amount::MicroTari, test_helpers::{create_utxo, spend_utxos}, - transaction::{OutputFeatures, TransactionOutput, UnblindedOutput}, + transaction_entities::{OutputFeatures, TransactionOutput, UnblindedOutput}, CryptoFactories, }, txn_schema, diff --git a/base_layer/core/tests/node_service.rs b/base_layer/core/tests/node_service.rs index 3b0ca36393..15da1b7ac1 100644 --- a/base_layer/core/tests/node_service.rs +++ b/base_layer/core/tests/node_service.rs @@ -20,16 +20,17 @@ // 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. -#[allow(dead_code)] -mod helpers; -use crate::helpers::block_builders::{construct_chained_blocks, create_coinbase}; +use std::{sync::Arc, time::Duration}; + +use randomx_rs::RandomXFlag; +use tari_crypto::tari_utilities::Hashable; +use tempfile::tempdir; + use helpers::{ block_builders::{append_block, chain_block, create_genesis_block, create_genesis_block_with_utxos}, event_stream::event_stream_next, nodes::{create_network_with_2_base_nodes_with_config, random_node_identity, wait_until_online, BaseNodeBuilder}, }; -use randomx_rs::RandomXFlag; -use std::{sync::Arc, time::Duration}; use tari_common::configuration::Network; use tari_comms::protocol::messaging::MessagingEvent; use tari_core::{ @@ -45,7 +46,7 @@ use tari_core::{ transactions::{ tari_amount::{uT, T}, test_helpers::{schema_to_transaction, spend_utxos}, - transaction::OutputFeatures, + transaction_entities::OutputFeatures, CryptoFactories, }, txn_schema, @@ -55,10 +56,13 @@ use tari_core::{ mocks::MockValidator, }, }; -use tari_crypto::tari_utilities::Hashable; use tari_p2p::services::liveness::LivenessConfig; use tari_test_utils::unpack_enum; -use tempfile::tempdir; + +use crate::helpers::block_builders::{construct_chained_blocks, create_coinbase}; + +#[allow(dead_code)] +mod helpers; #[tokio::test] async fn propagate_and_forward_many_valid_blocks() { diff --git a/base_layer/wallet/src/error.rs b/base_layer/wallet/src/error.rs index 30321105c2..61c879570f 100644 --- a/base_layer/wallet/src/error.rs +++ b/base_layer/wallet/src/error.rs @@ -39,7 +39,7 @@ use tari_comms::{ peer_manager::{node_id::NodeIdError, PeerManagerError}, }; use tari_comms_dht::store_forward::StoreAndForwardError; -use tari_core::transactions::transaction::TransactionError; +use tari_core::transactions::transaction_entities::TransactionError; use tari_crypto::tari_utilities::{hex::HexError, ByteArrayError}; use tari_key_manager::error::KeyManagerError; use tari_p2p::{initialization::CommsInitializationError, services::liveness::error::LivenessError}; diff --git a/base_layer/wallet/src/output_manager_service/error.rs b/base_layer/wallet/src/output_manager_service/error.rs index 2d287bda0c..1e7eeb84e8 100644 --- a/base_layer/wallet/src/output_manager_service/error.rs +++ b/base_layer/wallet/src/output_manager_service/error.rs @@ -20,20 +20,22 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{base_node_service::error::BaseNodeServiceError, error::WalletStorageError}; use diesel::result::Error as DieselError; +use tari_crypto::{script::ScriptError, tari_utilities::ByteArrayError}; +use thiserror::Error; + use tari_common::exit_codes::ExitCodes; use tari_comms::{connectivity::ConnectivityError, peer_manager::node_id::NodeIdError, protocol::rpc::RpcError}; use tari_comms_dht::outbound::DhtOutboundError; use tari_core::transactions::{ - transaction::TransactionError, + transaction_entities::TransactionError, transaction_protocol::TransactionProtocolError, CoinbaseBuildError, }; -use tari_crypto::{script::ScriptError, tari_utilities::ByteArrayError}; use tari_key_manager::error::{KeyManagerError, MnemonicError}; use tari_service_framework::reply_channel::TransportChannelError; -use thiserror::Error; + +use crate::{base_node_service::error::BaseNodeServiceError, error::WalletStorageError}; #[derive(Debug, Error)] pub enum OutputManagerError { diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 37afdbd641..51aa1e2853 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -33,7 +33,7 @@ use tari_common_types::{ }; use tari_core::transactions::{ tari_amount::MicroTari, - transaction::{Transaction, TransactionOutput, UnblindedOutput}, + transaction_entities::{Transaction, TransactionOutput, UnblindedOutput}, transaction_protocol::sender::TransactionSenderMessage, ReceiverTransactionProtocol, SenderTransactionProtocol, diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index b7b35ae8f7..13877f7434 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -26,7 +26,7 @@ use log::*; use rand::rngs::OsRng; use tari_common_types::types::{PrivateKey, PublicKey}; use tari_core::transactions::{ - transaction::{TransactionOutput, UnblindedOutput}, + transaction_entities::{TransactionOutput, UnblindedOutput}, CryptoFactories, }; use tari_crypto::{ diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 1949e61006..49ae3bf44b 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -55,7 +55,7 @@ use tari_core::{ transactions::{ fee::Fee, tari_amount::MicroTari, - transaction::{KernelFeatures, OutputFeatures, Transaction, TransactionOutput, UnblindedOutput}, + transaction_entities::{KernelFeatures, OutputFeatures, Transaction, TransactionOutput, UnblindedOutput}, transaction_protocol::sender::TransactionSenderMessage, CoinbaseBuilder, CryptoFactories, diff --git a/base_layer/wallet/src/output_manager_service/storage/database.rs b/base_layer/wallet/src/output_manager_service/storage/database.rs index 78c40bfb40..6b2768e387 100644 --- a/base_layer/wallet/src/output_manager_service/storage/database.rs +++ b/base_layer/wallet/src/output_manager_service/storage/database.rs @@ -38,7 +38,7 @@ use tari_common_types::{ transaction::TxId, types::{BlindingFactor, Commitment, HashOutput}, }; -use tari_core::transactions::{tari_amount::MicroTari, transaction::TransactionOutput}; +use tari_core::transactions::{tari_amount::MicroTari, transaction_entities::TransactionOutput}; use tari_key_manager::cipher_seed::CipherSeed; const LOG_TARGET: &str = "wallet::output_manager_service::database"; diff --git a/base_layer/wallet/src/output_manager_service/storage/models.rs b/base_layer/wallet/src/output_manager_service/storage/models.rs index 7f0a8805c9..0062610363 100644 --- a/base_layer/wallet/src/output_manager_service/storage/models.rs +++ b/base_layer/wallet/src/output_manager_service/storage/models.rs @@ -25,7 +25,7 @@ use std::cmp::Ordering; use tari_common_types::types::{BlockHash, Commitment, HashOutput, PrivateKey}; use tari_core::{ tari_utilities::hash::Hashable, - transactions::{transaction::UnblindedOutput, transaction_protocol::RewindData, CryptoFactories}, + transactions::{transaction_entities::UnblindedOutput, transaction_protocol::RewindData, CryptoFactories}, }; use tari_crypto::script::{ExecutionStack, TariScript}; diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs index 78a7fcddaf..605391970f 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs @@ -45,7 +45,7 @@ use tari_common_types::{ transaction::TxId, types::{Commitment, PrivateKey}, }; -use tari_core::transactions::transaction::TransactionOutput; +use tari_core::transactions::transaction_entities::TransactionOutput; use tari_key_manager::cipher_seed::CipherSeed; use crate::{ @@ -1654,7 +1654,7 @@ mod test { use tari_core::transactions::{ tari_amount::MicroTari, test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, - transaction::{OutputFeatures, TransactionInput, UnblindedOutput}, + transaction_entities::{OutputFeatures, TransactionInput, UnblindedOutput}, CryptoFactories, }; use tari_crypto::script; diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs index f02317119a..dedf3c246e 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs @@ -49,7 +49,7 @@ use tari_core::{ tari_utilities::hash::Hashable, transactions::{ tari_amount::MicroTari, - transaction::{OutputFeatures, OutputFlags, UnblindedOutput}, + transaction_entities::{OutputFeatures, OutputFlags, UnblindedOutput}, CryptoFactories, }, }; diff --git a/base_layer/wallet/src/transaction_service/error.rs b/base_layer/wallet/src/transaction_service/error.rs index 94541e42aa..dd13ba6ea1 100644 --- a/base_layer/wallet/src/transaction_service/error.rs +++ b/base_layer/wallet/src/transaction_service/error.rs @@ -20,26 +20,28 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - error::WalletStorageError, - output_manager_service::error::OutputManagerError, - transaction_service::{ - storage::{database::DbKey, sqlite_db::CompletedTransactionConversionError}, - utc::NegativeDurationError, - }, -}; use diesel::result::Error as DieselError; use futures::channel::oneshot::Canceled; use serde_json::Error as SerdeJsonError; +use tari_crypto::tari_utilities::ByteArrayError; +use thiserror::Error; +use tokio::sync::broadcast::error::RecvError; + use tari_common_types::transaction::{TransactionConversionError, TransactionDirectionError, TxId}; use tari_comms::{connectivity::ConnectivityError, peer_manager::node_id::NodeIdError, protocol::rpc::RpcError}; use tari_comms_dht::outbound::DhtOutboundError; -use tari_core::transactions::{transaction::TransactionError, transaction_protocol::TransactionProtocolError}; -use tari_crypto::tari_utilities::ByteArrayError; +use tari_core::transactions::{transaction_entities::TransactionError, transaction_protocol::TransactionProtocolError}; use tari_p2p::services::liveness::error::LivenessError; use tari_service_framework::reply_channel::TransportChannelError; -use thiserror::Error; -use tokio::sync::broadcast::error::RecvError; + +use crate::{ + error::WalletStorageError, + output_manager_service::error::OutputManagerError, + transaction_service::{ + storage::{database::DbKey, sqlite_db::CompletedTransactionConversionError}, + utc::NegativeDurationError, + }, +}; #[derive(Debug, Error)] pub enum TransactionServiceError { diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 7a34b0bd7d..0f91f8fee8 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -20,21 +20,24 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::transaction_service::{ - error::TransactionServiceError, - storage::models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, -}; -use aes_gcm::Aes256Gcm; use std::{collections::HashMap, fmt, sync::Arc}; -use tari_common_types::transaction::TxId; -use tari_comms::types::CommsPublicKey; -use tari_core::transactions::{tari_amount::MicroTari, transaction::Transaction}; -use tari_service_framework::reply_channel::SenderService; + +use aes_gcm::Aes256Gcm; use tokio::sync::broadcast; use tower::Service; -use tari_common_types::types::PublicKey; -use tari_core::transactions::transaction::TransactionOutput; +use tari_common_types::{transaction::TxId, types::PublicKey}; +use tari_comms::types::CommsPublicKey; +use tari_core::transactions::{ + tari_amount::MicroTari, + transaction_entities::{Transaction, TransactionOutput}, +}; +use tari_service_framework::reply_channel::SenderService; + +use crate::transaction_service::{ + error::TransactionServiceError, + storage::models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, +}; /// API Request enum #[allow(clippy::large_enum_variant)] 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 46d37f3ead..7bcf3ccb3e 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 @@ -20,22 +20,17 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - connectivity_service::WalletConnectivityInterface, - transaction_service::{ - error::{TransactionServiceError, TransactionServiceProtocolError}, - handle::TransactionEvent, - service::TransactionServiceResources, - storage::{database::TransactionBackend, models::CompletedTransaction}, - }, -}; -use futures::FutureExt; -use log::*; use std::{ convert::TryFrom, sync::Arc, time::{Duration, Instant}, }; + +use futures::FutureExt; +use log::*; +use tari_crypto::tari_utilities::hex::Hex; +use tokio::{sync::watch, time::sleep}; + use tari_common_types::{ transaction::{TransactionStatus, TxId}, types::Signature, @@ -45,10 +40,18 @@ use tari_core::{ proto::wallet_rpc::{TxLocation, TxQueryResponse, TxSubmissionRejectionReason, TxSubmissionResponse}, rpc::BaseNodeWalletRpcClient, }, - transactions::transaction::Transaction, + transactions::transaction_entities::Transaction, +}; + +use crate::{ + connectivity_service::WalletConnectivityInterface, + transaction_service::{ + error::{TransactionServiceError, TransactionServiceProtocolError}, + handle::TransactionEvent, + service::TransactionServiceResources, + storage::{database::TransactionBackend, models::CompletedTransaction}, + }, }; -use tari_crypto::tari_utilities::hex::Hex; -use tokio::{sync::watch, time::sleep}; const LOG_TARGET: &str = "wallet::transaction_service::protocols::broadcast_protocol"; 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 c02f61e2a2..2be90c9b48 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 @@ -42,7 +42,7 @@ use tokio::sync::{mpsc, oneshot}; use crate::connectivity_service::WalletConnectivityInterface; use tari_common_types::types::HashOutput; use tari_core::transactions::{ - transaction::Transaction, + transaction_entities::Transaction, transaction_protocol::{recipient::RecipientState, sender::TransactionSenderMessage}, }; use tari_crypto::tari_utilities::Hashable; 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 13d2cd3d35..34d68644df 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 @@ -54,7 +54,7 @@ use tari_comms_dht::{ }; use tari_core::transactions::{ tari_amount::MicroTari, - transaction::KernelFeatures, + transaction_entities::KernelFeatures, transaction_protocol::{proto, recipient::RecipientSignedMessage, sender::SingleRoundSenderData}, SenderTransactionProtocol, }; diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index ef43ad849b..0ecbab6d50 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -73,7 +73,7 @@ use tari_core::{ proto::base_node as base_node_proto, transactions::{ tari_amount::MicroTari, - transaction::{KernelFeatures, OutputFeatures, Transaction, TransactionOutput, UnblindedOutput}, + transaction_entities::{KernelFeatures, OutputFeatures, Transaction, TransactionOutput, UnblindedOutput}, transaction_protocol::{ proto, recipient::RecipientSignedMessage, diff --git a/base_layer/wallet/src/transaction_service/storage/database.rs b/base_layer/wallet/src/transaction_service/storage/database.rs index 8d97ebe971..1b58703013 100644 --- a/base_layer/wallet/src/transaction_service/storage/database.rs +++ b/base_layer/wallet/src/transaction_service/storage/database.rs @@ -20,30 +20,31 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::transaction_service::{ - error::TransactionStorageError, - storage::models::{CompletedTransaction, InboundTransaction, OutboundTransaction}, -}; -use aes_gcm::Aes256Gcm; -use chrono::Utc; -use log::*; - -use crate::transaction_service::storage::{ - models::WalletTransaction, - sqlite_db::{InboundTransactionSenderInfo, UnconfirmedTransactionInfo}, -}; use std::{ collections::HashMap, fmt, fmt::{Display, Error, Formatter}, sync::Arc, }; + +use aes_gcm::Aes256Gcm; +use chrono::Utc; +use log::*; + use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus, TxId}, types::{BlindingFactor, BlockHash}, }; use tari_comms::types::CommsPublicKey; -use tari_core::transactions::{tari_amount::MicroTari, transaction::Transaction}; +use tari_core::transactions::{tari_amount::MicroTari, transaction_entities::Transaction}; + +use crate::transaction_service::{ + error::TransactionStorageError, + storage::{ + models::{CompletedTransaction, InboundTransaction, OutboundTransaction, WalletTransaction}, + sqlite_db::{InboundTransactionSenderInfo, UnconfirmedTransactionInfo}, + }, +}; const LOG_TARGET: &str = "wallet::transaction_service::database"; diff --git a/base_layer/wallet/src/transaction_service/storage/models.rs b/base_layer/wallet/src/transaction_service/storage/models.rs index a0925364a8..3b9189205a 100644 --- a/base_layer/wallet/src/transaction_service/storage/models.rs +++ b/base_layer/wallet/src/transaction_service/storage/models.rs @@ -22,6 +22,7 @@ use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; + use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus, TxId}, types::{BlockHash, PrivateKey, Signature}, @@ -29,7 +30,7 @@ use tari_common_types::{ use tari_comms::types::CommsPublicKey; use tari_core::transactions::{ tari_amount::MicroTari, - transaction::Transaction, + transaction_entities::Transaction, ReceiverTransactionProtocol, SenderTransactionProtocol, }; diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index 45c6370855..a276ce2737 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -2083,7 +2083,7 @@ mod test { use tari_core::transactions::{ tari_amount::MicroTari, test_helpers::{create_unblinded_output, TestParams}, - transaction::{OutputFeatures, Transaction}, + transaction_entities::{OutputFeatures, Transaction}, transaction_protocol::sender::TransactionSenderMessage, CryptoFactories, ReceiverTransactionProtocol, diff --git a/base_layer/wallet/src/transaction_service/tasks/send_finalized_transaction.rs b/base_layer/wallet/src/transaction_service/tasks/send_finalized_transaction.rs index 6603457751..8c504d460b 100644 --- a/base_layer/wallet/src/transaction_service/tasks/send_finalized_transaction.rs +++ b/base_layer/wallet/src/transaction_service/tasks/send_finalized_transaction.rs @@ -1,3 +1,16 @@ +use std::time::Duration; + +use log::*; + +use tari_common_types::transaction::TxId; +use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; +use tari_comms_dht::{ + domain_message::OutboundDomainMessage, + outbound::{OutboundEncryption, OutboundMessageRequester, SendMessageResponse}, +}; +use tari_core::transactions::{transaction_entities::Transaction, transaction_protocol::proto}; +use tari_p2p::tari_message::TariMessageType; + // Copyright 2020. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the @@ -24,16 +37,6 @@ use crate::transaction_service::{ error::TransactionServiceError, tasks::wait_on_dial::wait_on_dial, }; -use log::*; -use std::time::Duration; -use tari_common_types::transaction::TxId; -use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; -use tari_comms_dht::{ - domain_message::OutboundDomainMessage, - outbound::{OutboundEncryption, OutboundMessageRequester, SendMessageResponse}, -}; -use tari_core::transactions::{transaction::Transaction, transaction_protocol::proto}; -use tari_p2p::tari_message::TariMessageType; const LOG_TARGET: &str = "wallet::transaction_service::tasks::send_finalized_transaction"; const LOG_TARGET_STRESS: &str = "stress_test::send_finalized_transaction"; diff --git a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs index ed21d9d029..bd3796479d 100644 --- a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs +++ b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs @@ -33,11 +33,17 @@ use chrono::Utc; use futures::StreamExt; use log::*; use serde::{Deserialize, Serialize}; -use tokio::{sync::broadcast, task, time}; +use tokio::{ + sync::{broadcast, watch}, + task, + time, + time::MissedTickBehavior, +}; use tari_common_types::{transaction::TxId, types::HashOutput}; use tari_comms::{ - peer_manager::NodeId, + connectivity::ConnectivityRequester, + peer_manager::{NodeId, Peer}, protocol::rpc::{RpcError, RpcStatus}, types::CommsPublicKey, NodeIdentity, @@ -52,7 +58,7 @@ use tari_core::{ tari_utilities::Hashable, transactions::{ tari_amount::MicroTari, - transaction::{TransactionOutput, UnblindedOutput}, + transaction_entities::{TransactionOutput, UnblindedOutput}, CryptoFactories, }, }; @@ -70,8 +76,6 @@ use crate::{ utxo_scanner_service::{error::UtxoScannerError, handle::UtxoScannerEvent}, WalletSqlite, }; -use tari_comms::{connectivity::ConnectivityRequester, peer_manager::Peer}; -use tokio::{sync::watch, time::MissedTickBehavior}; pub const LOG_TARGET: &str = "wallet::utxo_scanning"; diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 11b044b65b..169dfc2703 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -47,7 +47,7 @@ use tari_core::{ consensus::NetworkConsensus, transactions::{ tari_amount::MicroTari, - transaction::{OutputFeatures, UnblindedOutput}, + transaction_entities::{OutputFeatures, UnblindedOutput}, CryptoFactories, }, }; diff --git a/base_layer/wallet/tests/output_manager_service/service.rs b/base_layer/wallet/tests/output_manager_service/service.rs index a420a1c4ca..f88dd9e25b 100644 --- a/base_layer/wallet/tests/output_manager_service/service.rs +++ b/base_layer/wallet/tests/output_manager_service/service.rs @@ -45,7 +45,7 @@ use tari_core::{ fee::Fee, tari_amount::{uT, MicroTari}, test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, - transaction::OutputFeatures, + transaction_entities::OutputFeatures, transaction_protocol::sender::TransactionSenderMessage, CryptoFactories, SenderTransactionProtocol, diff --git a/base_layer/wallet/tests/support/comms_rpc.rs b/base_layer/wallet/tests/support/comms_rpc.rs index 05188342f8..5163e6180f 100644 --- a/base_layer/wallet/tests/support/comms_rpc.rs +++ b/base_layer/wallet/tests/support/comms_rpc.rs @@ -60,7 +60,7 @@ use tari_core::{ }, }, tari_utilities::Hashable, - transactions::transaction::{Transaction, TransactionOutput}, + transactions::transaction_entities::{Transaction, TransactionOutput}, }; use tokio::time::sleep; @@ -662,7 +662,7 @@ mod test { rpc::{BaseNodeWalletRpcClient, BaseNodeWalletRpcServer}, }, proto::base_node::{ChainMetadata, TipInfoResponse}, - transactions::transaction::Transaction, + transactions::transaction_entities::Transaction, }; use tokio::time::Duration; diff --git a/base_layer/wallet/tests/support/utils.rs b/base_layer/wallet/tests/support/utils.rs index e0eb6e7312..16c62e2e1f 100644 --- a/base_layer/wallet/tests/support/utils.rs +++ b/base_layer/wallet/tests/support/utils.rs @@ -20,17 +20,19 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use rand::{CryptoRng, Rng}; use std::{fmt::Debug, thread, time::Duration}; + +use rand::{CryptoRng, Rng}; +use tari_crypto::{ + keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, + script, +}; + use tari_common_types::types::{CommitmentFactory, PrivateKey, PublicKey}; use tari_core::transactions::{ tari_amount::MicroTari, test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, - transaction::{OutputFeatures, TransactionInput, UnblindedOutput}, -}; -use tari_crypto::{ - keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, - script, + transaction_entities::{OutputFeatures, TransactionInput, UnblindedOutput}, }; pub fn assert_change(mut func: F, to: T, poll_count: usize) diff --git a/base_layer/wallet/tests/transaction_service/service.rs b/base_layer/wallet/tests/transaction_service/service.rs index 3d25188ddc..741e022469 100644 --- a/base_layer/wallet/tests/transaction_service/service.rs +++ b/base_layer/wallet/tests/transaction_service/service.rs @@ -91,7 +91,7 @@ use tari_core::{ fee::Fee, tari_amount::*, test_helpers::{create_unblinded_output, TestParams as TestParamsHelpers}, - transaction::{KernelBuilder, KernelFeatures, OutputFeatures, Transaction}, + transaction_entities::{KernelBuilder, KernelFeatures, OutputFeatures, Transaction}, transaction_protocol::{proto, recipient::RecipientSignedMessage, sender::TransactionSenderMessage}, CryptoFactories, ReceiverTransactionProtocol, diff --git a/base_layer/wallet/tests/transaction_service/storage.rs b/base_layer/wallet/tests/transaction_service/storage.rs index 54d51c3c92..64c157997a 100644 --- a/base_layer/wallet/tests/transaction_service/storage.rs +++ b/base_layer/wallet/tests/transaction_service/storage.rs @@ -33,7 +33,7 @@ use tari_common_types::{ use tari_core::transactions::{ tari_amount::{uT, MicroTari}, test_helpers::{create_unblinded_output, TestParams}, - transaction::{OutputFeatures, Transaction}, + transaction_entities::{OutputFeatures, Transaction}, transaction_protocol::sender::TransactionSenderMessage, CryptoFactories, ReceiverTransactionProtocol, diff --git a/base_layer/wallet/tests/wallet/mod.rs b/base_layer/wallet/tests/wallet/mod.rs index 376d7f1f5c..bd6e6f8d79 100644 --- a/base_layer/wallet/tests/wallet/mod.rs +++ b/base_layer/wallet/tests/wallet/mod.rs @@ -43,7 +43,7 @@ use tari_comms_dht::{store_forward::SafConfig, DhtConfig}; use tari_core::transactions::{ tari_amount::{uT, MicroTari}, test_helpers::{create_unblinded_output, TestParams}, - transaction::OutputFeatures, + transaction_entities::OutputFeatures, CryptoFactories, }; use tari_key_manager::cipher_seed::CipherSeed; diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index 2659eeb1a3..e544fab345 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -22,14 +22,17 @@ #[cfg(test)] mod test { - use crate::{callback_handler::CallbackHandler, output_manager_service_mock::MockOutputManagerService}; - use chrono::Utc; - use rand::rngs::OsRng; use std::{ sync::{Arc, Mutex}, thread, time::Duration, }; + + use chrono::Utc; + use rand::rngs::OsRng; + use tari_crypto::keys::{PublicKey as PublicKeyTrait, SecretKey}; + use tokio::{runtime::Runtime, sync::broadcast, time::Instant}; + use tari_common_types::{ transaction::{TransactionDirection, TransactionStatus}, types::{BlindingFactor, PrivateKey, PublicKey}, @@ -37,11 +40,10 @@ mod test { use tari_comms_dht::event::DhtEvent; use tari_core::transactions::{ tari_amount::{uT, MicroTari}, - transaction::Transaction, + transaction_entities::Transaction, ReceiverTransactionProtocol, SenderTransactionProtocol, }; - use tari_crypto::keys::{PublicKey as PublicKeyTrait, SecretKey}; use tari_service_framework::reply_channel; use tari_shutdown::Shutdown; use tari_wallet::{ @@ -59,7 +61,8 @@ mod test { }, }, }; - use tokio::{runtime::Runtime, sync::broadcast, time::Instant}; + + use crate::{callback_handler::CallbackHandler, output_manager_service_mock::MockOutputManagerService}; struct CallbackState { pub received_tx_callback_called: bool, diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index e24dec8445..d5ec154748 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -158,7 +158,7 @@ use tari_comms::{ types::CommsSecretKey, }; use tari_comms_dht::{store_forward::SafConfig, DbConnectionUrl, DhtConfig}; -use tari_core::transactions::{tari_amount::MicroTari, transaction::OutputFeatures, CryptoFactories}; +use tari_core::transactions::{tari_amount::MicroTari, transaction_entities::OutputFeatures, CryptoFactories}; use tari_key_manager::cipher_seed::CipherSeed; use tari_p2p::{ transport::{TorConfig, TransportType, TransportType::Tor}, @@ -210,7 +210,7 @@ pub type TariTransportType = tari_p2p::transport::TransportType; pub type TariPublicKey = tari_comms::types::CommsPublicKey; pub type TariPrivateKey = tari_comms::types::CommsSecretKey; pub type TariCommsConfig = tari_p2p::initialization::P2pConfig; -pub type TariTransactionKernel = tari_core::transactions::transaction::TransactionKernel; +pub type TariTransactionKernel = tari_core::transactions::transaction_entities::TransactionKernel; pub struct TariContacts(Vec); From 519290c89a3a757f561bc320e3cb19097796cd46 Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Fri, 19 Nov 2021 15:13:49 +0200 Subject: [PATCH 41/46] chore: upgrade tokio deps #3581 (#3595) Description --- Upgrades dependencies from cargo audit alerts Motivation and Context --- #3581 How Has This Been Tested? --- cargo audit, cargo test --- Cargo.lock | 282 ++++-------------- base_layer/p2p/Cargo.toml | 4 +- base_layer/service_framework/Cargo.toml | 8 +- .../service_framework/src/reply_channel.rs | 2 +- base_layer/wallet/Cargo.toml | 2 +- comms/Cargo.toml | 4 +- comms/dht/Cargo.toml | 6 +- comms/rpc_macros/Cargo.toml | 2 +- comms/src/protocol/rpc/client/mod.rs | 2 +- 9 files changed, 81 insertions(+), 231 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1e6c55f05..ec15c0129c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1664,7 +1664,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.7", + "pin-project-lite", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -1801,7 +1801,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.13.0", + "tokio", "tokio-util", "tracing", ] @@ -1900,7 +1900,7 @@ checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ "bytes 1.1.0", "http", - "pin-project-lite 0.2.7", + "pin-project-lite", ] [[package]] @@ -1946,9 +1946,9 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.7", + "pin-project-lite", "socket2", - "tokio 1.13.0", + "tokio", "tower-service", "tracing", "want", @@ -1961,8 +1961,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper", - "pin-project-lite 0.2.7", - "tokio 1.13.0", + "pin-project-lite", + "tokio", "tokio-io-timeout", ] @@ -1975,7 +1975,7 @@ dependencies = [ "bytes 1.1.0", "hyper", "native-tls", - "tokio 1.13.0", + "tokio", "tokio-native-tls", ] @@ -2851,7 +2851,7 @@ dependencies = [ "pin-project 1.0.8", "rand 0.8.4", "thiserror", - "tokio 1.13.0", + "tokio", "tokio-stream", ] @@ -2885,7 +2885,7 @@ dependencies = [ "reqwest", "thiserror", "thrift", - "tokio 1.13.0", + "tokio", ] [[package]] @@ -3129,12 +3129,6 @@ dependencies = [ "syn 1.0.81", ] -[[package]] -name = "pin-project-lite" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" - [[package]] name = "pin-project-lite" version = "0.2.7" @@ -3637,11 +3631,11 @@ dependencies = [ "mime", "native-tls", "percent-encoding 2.1.0", - "pin-project-lite 0.2.7", + "pin-project-lite", "serde 1.0.130", "serde_json", "serde_urlencoded", - "tokio 1.13.0", + "tokio", "tokio-native-tls", "url 2.2.2", "wasm-bindgen", @@ -4435,7 +4429,7 @@ dependencies = [ "tari_crypto", "tari_p2p", "thiserror", - "tokio 1.13.0", + "tokio", "tonic", ] @@ -4474,7 +4468,7 @@ dependencies = [ "tari_service_framework", "tari_shutdown", "thiserror", - "tokio 1.13.0", + "tokio", "tonic", "tracing", "tracing-opentelemetry", @@ -4547,7 +4541,7 @@ dependencies = [ "serde 1.0.130", "tari_crypto", "thiserror", - "tokio 1.13.0", + "tokio", ] [[package]] @@ -4590,10 +4584,10 @@ dependencies = [ "tari_test_utils", "tempfile", "thiserror", - "tokio 1.13.0", + "tokio", "tokio-stream", "tokio-util", - "tower 0.3.1", + "tower", "tower-make", "tracing", "tracing-futures", @@ -4640,10 +4634,10 @@ dependencies = [ "tari_utilities", "tempfile", "thiserror", - "tokio 1.13.0", + "tokio", "tokio-stream", - "tokio-test 0.4.2", - "tower 0.4.10", + "tokio-test", + "tower", "tower-test", "ttl_cache", ] @@ -4659,7 +4653,7 @@ dependencies = [ "syn 1.0.81", "tari_comms", "tari_test_utils", - "tokio 1.13.0", + "tokio", "tower-service", ] @@ -4696,7 +4690,7 @@ dependencies = [ "tari_shutdown", "tari_wallet", "thiserror", - "tokio 1.13.0", + "tokio", "tonic", "tracing", "tracing-opentelemetry", @@ -4756,7 +4750,7 @@ dependencies = [ "tari_test_utils", "tempfile", "thiserror", - "tokio 1.13.0", + "tokio", "tracing", "tracing-attributes", "tracing-futures", @@ -4848,7 +4842,7 @@ dependencies = [ "tari_crypto", "tari_utilities", "thiserror", - "tokio 1.13.0", + "tokio", "tonic", "tracing", "tracing-futures", @@ -4890,7 +4884,7 @@ dependencies = [ "tari_core", "tari_crypto", "thiserror", - "tokio 1.13.0", + "tokio", "tonic", ] @@ -4949,9 +4943,9 @@ dependencies = [ "tari_utilities", "tempfile", "thiserror", - "tokio 1.13.0", + "tokio", "tokio-stream", - "tower 0.3.1", + "tower", "tower-service", "trust-dns-client", "webpki", @@ -4969,8 +4963,8 @@ dependencies = [ "tari_shutdown", "tari_test_utils", "thiserror", - "tokio 1.13.0", - "tower 0.3.1", + "tokio", + "tower", "tower-service", ] @@ -4979,7 +4973,7 @@ name = "tari_shutdown" version = "0.21.2" dependencies = [ "futures 0.3.17", - "tokio 1.13.0", + "tokio", ] [[package]] @@ -5045,7 +5039,7 @@ dependencies = [ "tari_crypto", "tari_utilities", "thiserror", - "tokio 1.13.0", + "tokio", "tonic", "tonic-build", "tracing", @@ -5064,7 +5058,7 @@ dependencies = [ "rand 0.8.4", "tari_shutdown", "tempfile", - "tokio 1.13.0", + "tokio", ] [[package]] @@ -5127,8 +5121,8 @@ dependencies = [ "tari_test_utils", "tempfile", "thiserror", - "tokio 1.13.0", - "tower 0.3.1", + "tokio", + "tower", ] [[package]] @@ -5158,7 +5152,7 @@ dependencies = [ "tari_wallet", "tempfile", "thiserror", - "tokio 1.13.0", + "tokio", ] [[package]] @@ -5195,7 +5189,7 @@ dependencies = [ "tari_core", "tari_crypto", "tari_utilities", - "tokio 1.13.0", + "tokio", ] [[package]] @@ -5316,22 +5310,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" -dependencies = [ - "bytes 0.5.6", - "fnv", - "futures-core", - "pin-project-lite 0.1.12", - "slab", -] - -[[package]] -name = "tokio" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee" +checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144" dependencies = [ "autocfg 1.0.1", "bytes 1.1.0", @@ -5340,7 +5321,7 @@ dependencies = [ "mio", "num_cpus", "once_cell", - "pin-project-lite 0.2.7", + "pin-project-lite", "signal-hook-registry", "tokio-macros", "winapi 0.3.9", @@ -5352,15 +5333,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90c49f106be240de154571dd31fbe48acb10ba6c6dd6f6517ad603abffa42de9" dependencies = [ - "pin-project-lite 0.2.7", - "tokio 1.13.0", + "pin-project-lite", + "tokio", ] [[package]] name = "tokio-macros" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095" +checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e" dependencies = [ "proc-macro2 1.0.32", "quote 1.0.10", @@ -5374,7 +5355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 1.13.0", + "tokio", ] [[package]] @@ -5384,7 +5365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ "rustls", - "tokio 1.13.0", + "tokio", "webpki", ] @@ -5395,22 +5376,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", - "pin-project-lite 0.2.7", - "tokio 1.13.0", + "pin-project-lite", + "tokio", "tokio-util", ] -[[package]] -name = "tokio-test" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0049c119b6d505c4447f5c64873636c7af6c75ab0d45fd9f618d82acb8016d" -dependencies = [ - "bytes 0.5.6", - "futures-core", - "tokio 0.2.25", -] - [[package]] name = "tokio-test" version = "0.4.2" @@ -5420,7 +5390,7 @@ dependencies = [ "async-stream", "bytes 1.1.0", "futures-core", - "tokio 1.13.0", + "tokio", "tokio-stream", ] @@ -5433,7 +5403,7 @@ dependencies = [ "futures-util", "log", "pin-project 1.0.8", - "tokio 1.13.0", + "tokio", "tungstenite", ] @@ -5448,8 +5418,8 @@ dependencies = [ "futures-io", "futures-sink", "log", - "pin-project-lite 0.2.7", - "tokio 1.13.0", + "pin-project-lite", + "tokio", ] [[package]] @@ -5491,10 +5461,10 @@ dependencies = [ "pin-project 1.0.8", "prost", "prost-derive", - "tokio 1.13.0", + "tokio", "tokio-stream", "tokio-util", - "tower 0.4.10", + "tower", "tower-layer", "tower-service", "tracing", @@ -5515,37 +5485,19 @@ dependencies = [ [[package]] name = "tower" -version = "0.3.1" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3169017c090b7a28fce80abaad0ab4f5566423677c9331bb320af7e49cfe62" -dependencies = [ - "futures-core", - "tower-buffer", - "tower-discover", - "tower-layer", - "tower-limit", - "tower-load-shed", - "tower-retry", - "tower-service", - "tower-timeout", - "tower-util", -] - -[[package]] -name = "tower" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00e500fff5fa1131c866b246041a6bf96da9c965f8fe4128cb1421f23e93c00" +checksum = "5651b5f6860a99bd1adb59dbfe1db8beb433e73709d9032b413a77e2fb7c066a" dependencies = [ "futures-core", "futures-util", "hdrhistogram", "indexmap", "pin-project 1.0.8", - "pin-project-lite 0.2.7", + "pin-project-lite", "rand 0.8.4", "slab", - "tokio 1.13.0", + "tokio", "tokio-stream", "tokio-util", "tower-layer", @@ -5553,77 +5505,12 @@ dependencies = [ "tracing", ] -[[package]] -name = "tower-buffer" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4887dc2a65d464c8b9b66e0e4d51c2fd6cf5b3373afc72805b0a60bce00446a" -dependencies = [ - "futures-core", - "pin-project 0.4.28", - "tokio 0.2.25", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-discover" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6b5000c3c54d269cc695dff28136bb33d08cbf1df2c48129e143ab65bf3c2a" -dependencies = [ - "futures-core", - "pin-project 0.4.28", - "tower-service", -] - [[package]] name = "tower-layer" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" -[[package]] -name = "tower-limit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c3040c5dbed68abffaa0d4517ac1a454cd741044f33ab0eefab6b8d1361404" -dependencies = [ - "futures-core", - "pin-project 0.4.28", - "tokio 0.2.25", - "tower-layer", - "tower-load", - "tower-service", -] - -[[package]] -name = "tower-load" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc79fc3afd07492b7966d7efa7c6c50f8ed58d768a6075dd7ae6591c5d2017b" -dependencies = [ - "futures-core", - "log", - "pin-project 0.4.28", - "tokio 0.2.25", - "tower-discover", - "tower-service", -] - -[[package]] -name = "tower-load-shed" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f021e23900173dc315feb4b6922510dae3e79c689b74c089112066c11f0ae4e" -dependencies = [ - "futures-core", - "pin-project 0.4.28", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-make" version = "0.3.0" @@ -5633,19 +5520,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "tower-retry" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6727956aaa2f8957d4d9232b308fe8e4e65d99db30f42b225646e86c9b6a952" -dependencies = [ - "futures-core", - "pin-project 0.4.28", - "tokio 0.2.25", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-service" version = "0.3.1" @@ -5654,42 +5528,18 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tower-test" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba4bbc2c1e4a8543c30d4c13a4c8314ed72d6e07581910f665aa13fde0153c8" +checksum = "a4546773ffeab9e4ea02b8872faa49bb616a80a7da66afc2f32688943f97efa7" dependencies = [ "futures-util", - "pin-project 0.4.28", - "tokio 0.2.25", - "tokio-test 0.2.1", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-timeout" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "127b8924b357be938823eaaec0608c482d40add25609481027b96198b2e4b31e" -dependencies = [ - "pin-project 0.4.28", - "tokio 0.2.25", + "pin-project 1.0.8", + "tokio", + "tokio-test", "tower-layer", "tower-service", ] -[[package]] -name = "tower-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1093c19826d33807c72511e68f73b4a0469a3f22c2bd5f7d5212178b4b89674" -dependencies = [ - "futures-core", - "futures-util", - "pin-project 0.4.28", - "tower-service", -] - [[package]] name = "tracing" version = "0.1.29" @@ -5698,7 +5548,7 @@ checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.7", + "pin-project-lite", "tracing-attributes", "tracing-core", ] @@ -5813,7 +5663,7 @@ dependencies = [ "ring", "rustls", "thiserror", - "tokio 1.13.0", + "tokio", "trust-dns-proto", "webpki", ] @@ -5841,7 +5691,7 @@ dependencies = [ "smallvec", "thiserror", "tinyvec", - "tokio 1.13.0", + "tokio", "tokio-rustls", "url 2.2.2", "webpki", @@ -6143,7 +5993,7 @@ dependencies = [ "serde 1.0.130", "serde_json", "serde_urlencoded", - "tokio 1.13.0", + "tokio", "tokio-stream", "tokio-tungstenite", "tokio-util", diff --git a/base_layer/p2p/Cargo.toml b/base_layer/p2p/Cargo.toml index 782551c063..f9ac95f29b 100644 --- a/base_layer/p2p/Cargo.toml +++ b/base_layer/p2p/Cargo.toml @@ -36,8 +36,8 @@ serde_derive = "1.0.90" thiserror = "1.0.26" tokio = { version = "1.11", features = ["macros"] } tokio-stream = { version = "0.1.7", default-features = false, features = ["time"] } -tower = "0.3.0-alpha.2" -tower-service = { version = "0.3.0-alpha.2" } +tower = "0.4.11" +tower-service = { version = "0.3.1" } trust-dns-client = { version = "0.21.0-alpha.4", features = ["dns-over-rustls"] } rustls = "0.19.1" webpki = "0.21" diff --git a/base_layer/service_framework/Cargo.toml b/base_layer/service_framework/Cargo.toml index 82461264b1..e714492ced 100644 --- a/base_layer/service_framework/Cargo.toml +++ b/base_layer/service_framework/Cargo.toml @@ -17,12 +17,12 @@ async-trait = "0.1.50" futures = { version = "^0.3.16", features = ["async-await"] } log = "0.4.8" thiserror = "1.0.26" -tokio = { version = "1.11", features = ["rt"] } -tower-service = { version = "0.3.0" } +tokio = { version = "1.14", features = ["rt"] } +tower-service = { version = "0.3" } [dev-dependencies] tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils" } -tokio = { version = "1.11", features = ["rt-multi-thread", "macros", "time"] } +tokio = { version = "1.14", features = ["rt-multi-thread", "macros", "time"] } futures-test = { version = "0.3.3" } -tower = "0.3.1" +tower = "0.4" diff --git a/base_layer/service_framework/src/reply_channel.rs b/base_layer/service_framework/src/reply_channel.rs index 4921ed9601..55189ce597 100644 --- a/base_layer/service_framework/src/reply_channel.rs +++ b/base_layer/service_framework/src/reply_channel.rs @@ -286,7 +286,7 @@ mod test { block_on(future::join( async move { - let err = requestor.ready_and().await.unwrap().call("PING").await.unwrap_err(); + let err = requestor.ready().await.unwrap().call("PING").await.unwrap_err(); assert_eq!(err, TransportChannelError::Canceled); }, async move { diff --git a/base_layer/wallet/Cargo.toml b/base_layer/wallet/Cargo.toml index 18c7c003be..9cc95ba519 100644 --- a/base_layer/wallet/Cargo.toml +++ b/base_layer/wallet/Cargo.toml @@ -43,7 +43,7 @@ serde_json = "1.0.39" tempfile = "3.1.0" thiserror = "1.0.26" tokio = { version = "1.11", features = ["sync", "macros"] } -tower = "0.3.0-alpha.2" +tower = "0.4" [dependencies.tari_core] path = "../../base_layer/core" diff --git a/comms/Cargo.toml b/comms/Cargo.toml index 6092edf614..b24e3a3e6a 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -41,10 +41,10 @@ serde = "1.0.119" serde_derive = "1.0.119" snow = { version = "=0.8.0", features = ["default-resolver"] } thiserror = "1.0.26" -tokio = { version = "1.11", features = ["rt-multi-thread", "time", "sync", "signal", "net", "macros", "io-util"] } +tokio = { version = "1.14", features = ["rt-multi-thread", "time", "sync", "signal", "net", "macros", "io-util"] } tokio-stream = { version = "0.1.7", features = ["sync"] } tokio-util = { version = "0.6.7", features = ["codec", "compat"] } -tower = "0.3.1" +tower = "0.4" tracing = "0.1.26" tracing-futures = "0.2.5" yamux = "=0.9.0" diff --git a/comms/dht/Cargo.toml b/comms/dht/Cargo.toml index b3dec63a37..5b15421607 100644 --- a/comms/dht/Cargo.toml +++ b/comms/dht/Cargo.toml @@ -36,8 +36,8 @@ serde = "1.0.90" serde_derive = "1.0.90" serde_repr = "0.1.5" thiserror = "1.0.26" -tokio = { version = "1.11", features = ["rt", "macros"] } -tower = { version= "0.4.8", features=["full"] } +tokio = { version = "1.14", features = ["rt", "macros"] } +tower = { version= "0.4", features=["full"] } ttl_cache = "0.5.1" # tower-filter dependencies @@ -55,7 +55,7 @@ petgraph = "0.5.1" clap = "2.33.0" # tower-filter dependencies -tower-test = { version = "^0.3" } +tower-test = { version = "^0.4" } tokio-test = "^0.4.2" futures-util = "^0.3.1" lazy_static = "1.4.0" diff --git a/comms/rpc_macros/Cargo.toml b/comms/rpc_macros/Cargo.toml index b680d347df..cf9ebcebbc 100644 --- a/comms/rpc_macros/Cargo.toml +++ b/comms/rpc_macros/Cargo.toml @@ -25,4 +25,4 @@ tari_test_utils = { version = "^0.21", path = "../../infrastructure/test_utils" futures = "0.3.5" prost = "0.8.0" tokio = { version = "1", features = ["macros"] } -tower-service = "0.3.0" +tower-service = "0.3" diff --git a/comms/src/protocol/rpc/client/mod.rs b/comms/src/protocol/rpc/client/mod.rs index 52abf1049f..9bec5975ab 100644 --- a/comms/src/protocol/rpc/client/mod.rs +++ b/comms/src/protocol/rpc/client/mod.rs @@ -187,7 +187,7 @@ impl RpcClient { &mut self, request: BaseRequest, ) -> Result, RpcStatus>>, RpcError> { - let svc = self.connector.ready_and().await?; + let svc = self.connector.ready().await?; let resp = svc.call(request).await?; Ok(resp) } From 91fe921092991df59f65fbe4f448fba85b42e30b Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Sun, 21 Nov 2021 12:03:57 +0200 Subject: [PATCH 42/46] feat: track ping failures and disconnect (#3597) Description --- Keep track of failed pings in liveness and disconnect peer's that do not respond. Motivation and Context --- This allows dead connections to be detected and disconnected. This is mainly advantageous in tor connections which can silently disconnect. How Has This Been Tested? --- Unit test Manually - though no occurrence of this case --- .../p2p/src/services/liveness/config.rs | 3 + base_layer/p2p/src/services/liveness/error.rs | 4 +- .../p2p/src/services/liveness/service.rs | 27 ++++- base_layer/p2p/src/services/liveness/state.rs | 111 +++++++++++++----- 4 files changed, 113 insertions(+), 32 deletions(-) diff --git a/base_layer/p2p/src/services/liveness/config.rs b/base_layer/p2p/src/services/liveness/config.rs index 5e90db6528..d70314f30f 100644 --- a/base_layer/p2p/src/services/liveness/config.rs +++ b/base_layer/p2p/src/services/liveness/config.rs @@ -36,6 +36,8 @@ pub struct LivenessConfig { pub num_peers_per_round: usize, /// Peers to include in every auto ping round (Default: ) pub monitored_peers: Vec, + /// Number of ping failures to tolerate before disconnecting the peer. A value of zero disables this feature. + pub max_allowed_ping_failures: usize, } impl Default for LivenessConfig { @@ -46,6 +48,7 @@ impl Default for LivenessConfig { refresh_random_pool_interval: Duration::from_secs(2 * 60 * 60), num_peers_per_round: 8, monitored_peers: Default::default(), + max_allowed_ping_failures: 2, } } } diff --git a/base_layer/p2p/src/services/liveness/error.rs b/base_layer/p2p/src/services/liveness/error.rs index fdea8677c7..f07f8147bf 100644 --- a/base_layer/p2p/src/services/liveness/error.rs +++ b/base_layer/p2p/src/services/liveness/error.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use tari_comms::{connectivity::ConnectivityError, message::MessageError}; +use tari_comms::{connectivity::ConnectivityError, message::MessageError, PeerConnectionError}; use tari_comms_dht::{outbound::DhtOutboundError, DhtActorError}; use tari_service_framework::reply_channel::TransportChannelError; use thiserror::Error; @@ -31,6 +31,8 @@ pub enum LivenessError { DhtOutboundError(#[from] DhtOutboundError), #[error("Connectivity error: `{0}`")] ConnectivityError(#[from] ConnectivityError), + #[error("Peer connection error: `{0}`")] + PeerConnectionError(#[from] PeerConnectionError), #[error("DHT actor error: `{0}`")] DhtActorError(#[from] DhtActorError), #[error("Failed to send a pong message")] diff --git a/base_layer/p2p/src/services/liveness/service.rs b/base_layer/p2p/src/services/liveness/service.rs index bc1cd3bbd5..167992f128 100644 --- a/base_layer/p2p/src/services/liveness/service.rs +++ b/base_layer/p2p/src/services/liveness/service.rs @@ -122,6 +122,11 @@ where if let Err(err) = self.start_ping_round().await { warn!(target: LOG_TARGET, "Error when pinging peers: {}", err); } + if self.config.max_allowed_ping_failures > 0 { + if let Err(err) = self.disconnect_failed_peers().await { + error!(target: LOG_TARGET, "Error occurred while disconnecting failed peers: {}", err); + } + } }, // Incoming messages from the Comms layer @@ -179,7 +184,7 @@ where return Ok(()); } - let maybe_latency = self.state.record_pong(ping_pong_msg.nonce); + let maybe_latency = self.state.record_pong(ping_pong_msg.nonce, &node_id); debug!( target: LOG_TARGET, "Received pong from peer '{}' with useragent '{}'. {} (Trace: {})", @@ -285,6 +290,26 @@ where Ok(()) } + async fn disconnect_failed_peers(&mut self) -> Result<(), LivenessError> { + let max_allowed_ping_failures = self.config.max_allowed_ping_failures; + for node_id in self + .state + .failed_pings_iter() + .filter(|(_, n)| **n > max_allowed_ping_failures) + .map(|(node_id, _)| node_id) + { + if let Some(mut conn) = self.connectivity.get_connection(node_id.clone()).await? { + debug!( + target: LOG_TARGET, + "Disconnecting peer {} that failed {} rounds of pings", node_id, max_allowed_ping_failures + ); + conn.disconnect().await?; + } + } + self.state.clear_failed_pings(); + Ok(()) + } + fn publish_event(&mut self, event: LivenessEvent) { let _ = self.event_publisher.send(Arc::new(event)).map_err(|_| { trace!( diff --git a/base_layer/p2p/src/services/liveness/state.rs b/base_layer/p2p/src/services/liveness/state.rs index d8b5b29d20..4e89d8e91f 100644 --- a/base_layer/p2p/src/services/liveness/state.rs +++ b/base_layer/p2p/src/services/liveness/state.rs @@ -20,17 +20,18 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use super::LOG_TARGET; use crate::proto::liveness::MetadataKey; -use chrono::{NaiveDateTime, Utc}; +use log::*; use std::{ - collections::{hash_map::RandomState, HashMap}, + collections::HashMap, convert::TryInto, - time::Duration, + time::{Duration, Instant}, }; use tari_comms::peer_manager::NodeId; const LATENCY_SAMPLE_WINDOW_SIZE: usize = 25; -const MAX_INFLIGHT_TTL: Duration = Duration::from_secs(20); +const MAX_INFLIGHT_TTL: Duration = Duration::from_secs(40); /// Represents metadata in a ping/pong message. #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -62,7 +63,7 @@ impl From>> for Metadata { } } -impl From for HashMap, RandomState> { +impl From for HashMap> { fn from(metadata: Metadata) -> Self { metadata.inner } @@ -71,8 +72,9 @@ impl From for HashMap, RandomState> { /// State for the LivenessService. #[derive(Default, Debug)] pub struct LivenessState { - inflight_pings: HashMap, + inflight_pings: HashMap, peer_latency: HashMap, + failed_pings: HashMap, pings_received: usize, pongs_received: usize, @@ -133,18 +135,27 @@ impl LivenessState { /// Adds a ping to the inflight ping list, while noting the current time that a ping was sent. pub fn add_inflight_ping(&mut self, nonce: u64, node_id: NodeId) { - let now = Utc::now().naive_utc(); - self.inflight_pings.insert(nonce, (node_id, now)); + self.inflight_pings.insert(nonce, (node_id, Instant::now())); self.clear_stale_inflight_pings(); } - /// Clears inflight ping requests which have not responded + /// Clears inflight ping requests which have not responded and adds them to failed_ping counter fn clear_stale_inflight_pings(&mut self) { - self.inflight_pings = self + let (inflight, expired) = self .inflight_pings .drain() - .filter(|(_, (_, time))| convert_to_std_duration(Utc::now().naive_utc() - *time) <= MAX_INFLIGHT_TTL) - .collect(); + .partition(|(_, (_, time))| time.elapsed() <= MAX_INFLIGHT_TTL); + + self.inflight_pings = inflight; + + for (_, (node_id, _)) in expired { + self.failed_pings + .entry(node_id) + .and_modify(|v| { + *v = *v + 1; + }) + .or_insert(1); + } } /// Returns true if the nonce is inflight, otherwise false @@ -153,19 +164,25 @@ impl LivenessState { } /// Records a pong. Specifically, the pong counter is incremented and - /// a latency sample is added and calculated. - pub fn record_pong(&mut self, nonce: u64) -> Option { + /// a latency sample is added and calculated. The given `peer` must match the recorded peer + pub fn record_pong(&mut self, nonce: u64, sent_by: &NodeId) -> Option { self.inc_pongs_received(); - - match self.inflight_pings.remove_entry(&nonce) { - Some((_, (node_id, sent_time))) => { - let now = Utc::now().naive_utc(); - let latency = self - .add_latency_sample(node_id, convert_to_std_duration(now - sent_time)) - .calc_average(); - Some(latency) - }, - None => None, + self.failed_pings.remove_entry(&sent_by); + + let (node_id, _) = self.inflight_pings.get(&nonce)?; + if node_id == sent_by { + self.inflight_pings + .remove(&nonce) + .map(|(node_id, sent_time)| self.add_latency_sample(node_id, sent_time.elapsed()).calc_average()) + } else { + warn!( + target: LOG_TARGET, + "Peer {} sent an nonce for another peer {}. This could indicate malicious behaviour or a bug. \ + Ignoring.", + sent_by, + node_id + ); + None } } @@ -195,11 +212,14 @@ impl LivenessState { // num_peers in map will always be > 0 .map(|latency| latency / num_peers as u32) } -} -/// Convert `chrono::Duration` to `std::time::Duration` -pub(super) fn convert_to_std_duration(old_duration: chrono::Duration) -> Duration { - Duration::from_millis(old_duration.num_milliseconds() as u64) + pub fn failed_pings_iter(&self) -> impl Iterator { + self.failed_pings.iter() + } + + pub fn clear_failed_pings(&mut self) { + self.failed_pings.clear(); + } } /// A very simple implementation for calculating average latency. Samples are added in milliseconds and the mean average @@ -299,9 +319,9 @@ mod test { let mut state = LivenessState::new(); let node_id = NodeId::default(); - state.add_inflight_ping(123, node_id); + state.add_inflight_ping(123, node_id.clone()); - let latency = state.record_pong(123).unwrap(); + let latency = state.record_pong(123, &node_id).unwrap(); assert!(latency < 50); } @@ -311,4 +331,35 @@ mod test { state.set_metadata_entry(MetadataKey::ChainMetadata, b"dummy-data".to_vec()); assert_eq!(state.metadata().get(MetadataKey::ChainMetadata).unwrap(), b"dummy-data"); } + + #[test] + fn clear_stale_inflight_pings() { + let mut state = LivenessState::new(); + + let peer1 = NodeId::default(); + state.add_inflight_ping(1, peer1.clone()); + let peer2 = NodeId::from_public_key(&Default::default()); + state.add_inflight_ping(2, peer2.clone()); + state.add_inflight_ping(3, peer2.clone()); + + assert!(state.failed_pings.get(&peer1).is_none()); + assert!(state.failed_pings.get(&peer2).is_none()); + + // MAX_INFLIGHT_TTL passes + for n in [1, 2, 3] { + let (_, time) = state.inflight_pings.get_mut(&n).unwrap(); + *time = Instant::now() - (MAX_INFLIGHT_TTL + Duration::from_secs(1)); + } + + state.clear_stale_inflight_pings(); + let n = state.failed_pings.get(&peer1).unwrap(); + assert_eq!(*n, 1); + let n = state.failed_pings.get(&peer2).unwrap(); + assert_eq!(*n, 2); + + assert!(state.record_pong(2, &peer2).is_none()); + let n = state.failed_pings.get(&peer1).unwrap(); + assert_eq!(*n, 1); + assert!(state.failed_pings.get(&peer2).is_none()); + } } From c0d625c1630da1b1b25414ecdc99bd78eccf8bba Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Sun, 21 Nov 2021 12:42:21 +0200 Subject: [PATCH 43/46] fix: be more permissive of responses for the incorrect request_id (#3588) Description --- - add more intrumentation to tari_comms - improve behaviour and logs around stream interruptions - be more permissive of responses for the incorrect request_id - reduce transaction_resend_period to 10 minutes from an hour @philipr-za Motivation and Context --- Track more metrics to diagnose issues. Formally recognise a stream interruption and continue the session vs a protocol violation which terminates the session. How Has This Been Tested? --- Existing test, stress test involving 2 wallets and a base node --- RFC/src/RFC-0250_Covenants.md | 44 +++------ applications/daily_tests/washing_machine.js | 6 +- .../src/automation/command_parser.rs | 14 ++- .../sync/header_sync/synchronizer.rs | 2 +- .../core/src/base_node/sync/rpc/tests.rs | 2 +- base_layer/core/src/mempool/rpc/test.rs | 4 +- .../tasks/txo_validation_task.rs | 4 +- .../wallet/src/transaction_service/config.rs | 2 +- .../transaction_validation_protocol.rs | 2 +- .../src/utxo_scanner_service/utxo_scanning.rs | 2 +- comms/dht/examples/memory_net/utilities.rs | 2 +- comms/dht/src/rpc/test.rs | 2 +- comms/rpc_macros/tests/macro.rs | 2 +- comms/src/connection_manager/dialer.rs | 4 + comms/src/connection_manager/listener.rs | 4 + comms/src/connection_manager/manager.rs | 20 +++- comms/src/connection_manager/metrics.rs | 82 ++++++++++++++++ comms/src/connection_manager/mod.rs | 1 + comms/src/connectivity/manager.rs | 5 +- comms/src/connectivity/metrics.rs | 21 ++-- comms/src/multiplexing/metrics.rs | 32 ++++++ comms/src/multiplexing/mod.rs | 3 + comms/src/multiplexing/yamux.rs | 11 ++- comms/src/protocol/messaging/inbound.rs | 14 +-- comms/src/protocol/messaging/metrics.rs | 74 ++++++++++++++ comms/src/protocol/messaging/mod.rs | 1 + comms/src/protocol/messaging/outbound.rs | 7 +- comms/src/protocol/rpc/client/metrics.rs | 81 +++++++++++---- comms/src/protocol/rpc/client/mod.rs | 43 +++++--- comms/src/protocol/rpc/server/error.rs | 30 +++++- comms/src/protocol/rpc/server/metrics.rs | 61 ++++++++---- comms/src/protocol/rpc/server/mod.rs | 98 +++++++++++++------ comms/src/protocol/rpc/status.rs | 12 ++- .../protocol/rpc/test/comms_integration.rs | 2 +- comms/src/protocol/rpc/test/smoke.rs | 10 +- 35 files changed, 529 insertions(+), 175 deletions(-) create mode 100644 comms/src/connection_manager/metrics.rs create mode 100644 comms/src/multiplexing/metrics.rs create mode 100644 comms/src/protocol/messaging/metrics.rs diff --git a/RFC/src/RFC-0250_Covenants.md b/RFC/src/RFC-0250_Covenants.md index d2e90367db..e3eb65cc15 100644 --- a/RFC/src/RFC-0250_Covenants.md +++ b/RFC/src/RFC-0250_Covenants.md @@ -206,58 +206,46 @@ little-endian 64-byte unsigned integer. The output set is returned unaltered. This rule is implicit for an empty (0 byte) covenant. -```yaml -op_byte: 0x20 +op_byte: 0x20
args: [] -``` ##### and(A, B) The intersection (\\(A \cap B\\)) of the resulting output set for covenant rules \\(A\\) and \\(B\\). -```yaml -op_byte: 0x21 +op_byte: 0x21
args: [Covenant, Covenant] -``` ##### or(A, B) The union (\\(A \cup B\\)) of the resulting output set for covenant rules \\(A\\) and \\(B\\). -```yaml -op_byte: 0x22 +op_byte: 0x22
args: [Covenant, Covenant] -``` ##### xor(A, B) The symmetric difference (\\(A \triangle B\\)) of the resulting output set for covenant rules \\(A\\) and \\(B\\). This is, outputs that match either \\(A\\) or \\(B\\) but not both. -```yaml -op_byte: 0x23 +op_byte: 0x23
args: [Covenant, Covenant] -``` ##### not(A) Returns the compliment of `A`. That is, all the elements of `A` are removed from the resultant output set. -```yaml -op_byte: 0x24 +op_byte: 0x24
args: [Covenant] -``` ##### empty() Returns an empty set. This will always fail and, if used alone, prevents the UTXO from ever being spent. A more useful reason to use `empty` is in conjunction a conditional e.g. `if_else(Condition(older_rel(10)), A, empty)` -```yaml -op_byte: 0x25 +op_byte: 0x25
args: [] -``` #### Filters @@ -265,46 +253,36 @@ args: [] Filters for a single output that matches the hash. This filter only returns zero or one outputs. -```yaml -op_byte: 0x30 +op_byte: 0x30
args: [Hash] -``` ##### filter_fields_preserved(fields) Filter for outputs where all given fields in the input are preserved in the output. -```yaml -op_byte: 0x31 +op_byte: 0x31
args: [Fields] -``` ##### filter_field_int_eq(field, int) Filters for outputs whose field value matches the given integer value. If the given field cannot be cast to an unsigned 64-bit integer, the transaction/block is rejected. -```yaml -op_byte: 0x32 +op_byte: 0x32
args: [Field, VarInt] -``` ##### filter_fields_hashed_eq(fields, hash) -```yaml -op_byte: 0x33 +op_byte: 0x33
args: [Fields, VarInt] -``` ##### filter_relative_height(height) Checks the block height that current [UTXO] (i.e. the current input) was mined plus `height` is greater than or equal to the current block height. If so, the `identity()` is returned, otherwise `empty()`. -```yaml -op_byte: 0x34 +op_byte: 0x34
args: [VarInt] -``` #### Encoding / Decoding diff --git a/applications/daily_tests/washing_machine.js b/applications/daily_tests/washing_machine.js index fc66b8137b..c32144aa57 100644 --- a/applications/daily_tests/washing_machine.js +++ b/applications/daily_tests/washing_machine.js @@ -129,15 +129,18 @@ function WashingMachine(options) { `🚀 Launching washing machine (numTransactions = ${numTransactions}, numRounds = ${numRounds}, sleep = ${sleepAfterRound}s)` ); + debug(`Connecting to wallet1 at ${wallet1Grpc}...`); await this.wallet1.connect(wallet1Grpc); + debug(`Connected.`); - debug("Compiling and starting applications..."); let wallet2Process = null; // Start wallet2 if (wallet2Grpc) { this.wallet2 = new WalletClient(); + debug(`Connecting to wallet2 at ${wallet2Grpc}...`); await this.wallet2.connect(wallet2Grpc); } else { + debug("Compiling wallet2..."); const port = await getFreePort(20000, 25000); wallet2Process = createGrpcWallet( baseNodeSeed, @@ -148,6 +151,7 @@ function WashingMachine(options) { true ); wallet2Process.baseDir = "./wallet"; + debug("Starting wallet2..."); await wallet2Process.startNew(); this.wallet2 = await wallet2Process.connectClient(); } diff --git a/applications/tari_console_wallet/src/automation/command_parser.rs b/applications/tari_console_wallet/src/automation/command_parser.rs index 0ed8acf4a6..275c9720b4 100644 --- a/applications/tari_console_wallet/src/automation/command_parser.rs +++ b/applications/tari_console_wallet/src/automation/command_parser.rs @@ -288,16 +288,14 @@ fn parse_make_it_rain(mut args: SplitWhitespace) -> Result, parsed_args.push(ParsedArgument::PublicKey(pubkey)); // transaction type - let txn_type = args - .next() - .ok_or_else(|| ParseError::Empty("transaction type".to_string()))?; + let txn_type = args.next(); let negotiated = match txn_type { - "negotiated" => true, - "one_sided" => false, + Some("negotiated") | Some("interactive") => true, + Some("one_sided") | Some("one-sided") | Some("onesided") => false, _ => { - println!("Invalid data provided for , must be 'negotiated' or 'one_sided'\n"); + println!("Invalid data provided for , must be 'interactive' or 'one-sided'\n"); return Err(ParseError::Invalid( - "Invalid data provided for , must be 'negotiated' or 'one_sided'".to_string(), + "Invalid data provided for , must be 'interactive' or 'one-sided'".to_string(), )); }, }; @@ -531,7 +529,7 @@ mod test { Err(e) => match e { ParseError::Invalid(e) => assert_eq!( e, - "Invalid data provided for , must be 'negotiated' or 'one_sided'".to_string() + "Invalid data provided for , must be 'interactive' or 'one-sided'".to_string() ), _ => panic!("Expected parsing to return an error here"), }, diff --git a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs index ca594087b6..3748a557f5 100644 --- a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs @@ -315,7 +315,7 @@ impl<'a, B: BlockchainBackend + 'static> HeaderSynchronizer<'a, B> { let resp = match client.find_chain_split(request).await { Ok(r) => r, - Err(RpcError::RequestFailed(err)) if err.status_code().is_not_found() => { + Err(RpcError::RequestFailed(err)) if err.as_status_code().is_not_found() => { // This round we sent less hashes than the max, so the next round will not have any more hashes to // send. Exit early in this case. if block_hashes.len() < NUM_CHAIN_SPLIT_HEADERS { diff --git a/base_layer/core/src/base_node/sync/rpc/tests.rs b/base_layer/core/src/base_node/sync/rpc/tests.rs index 52fdfd0285..055dcf2203 100644 --- a/base_layer/core/src/base_node/sync/rpc/tests.rs +++ b/base_layer/core/src/base_node/sync/rpc/tests.rs @@ -62,7 +62,7 @@ mod sync_blocks { }; let req = rpc_request_mock.request_with_context(Default::default(), msg); let err = service.sync_blocks(req).await.unwrap_err(); - unpack_enum!(RpcStatusCode::NotFound = err.status_code()); + unpack_enum!(RpcStatusCode::NotFound = err.as_status_code()); } #[tokio::test] diff --git a/base_layer/core/src/mempool/rpc/test.rs b/base_layer/core/src/mempool/rpc/test.rs index a9cbb2ee49..3f11646463 100644 --- a/base_layer/core/src/mempool/rpc/test.rs +++ b/base_layer/core/src/mempool/rpc/test.rs @@ -124,7 +124,7 @@ mod get_tx_state_by_excess_sig { .await .unwrap_err(); - unpack_enum!(RpcStatusCode::BadRequest = status.status_code()); + unpack_enum!(RpcStatusCode::BadRequest = status.as_status_code()); } } @@ -174,6 +174,6 @@ mod submit_transaction { .await .unwrap_err(); - unpack_enum!(RpcStatusCode::BadRequest = status.status_code()); + unpack_enum!(RpcStatusCode::BadRequest = status.as_status_code()); } } diff --git a/base_layer/wallet/src/output_manager_service/tasks/txo_validation_task.rs b/base_layer/wallet/src/output_manager_service/tasks/txo_validation_task.rs index c4680cfe45..363530f6fd 100644 --- a/base_layer/wallet/src/output_manager_service/tasks/txo_validation_task.rs +++ b/base_layer/wallet/src/output_manager_service/tasks/txo_validation_task.rs @@ -34,7 +34,7 @@ use crate::{ use log::*; use std::{collections::HashMap, convert::TryInto, sync::Arc}; use tari_common_types::types::BlockHash; -use tari_comms::protocol::rpc::{RpcError::RequestFailed, RpcStatusCode::NotFound}; +use tari_comms::protocol::rpc::RpcError::RequestFailed; use tari_core::{ base_node::rpc::BaseNodeWalletRpcClient, blocks::BlockHeader, @@ -353,7 +353,7 @@ where info!(target: LOG_TARGET, "Error asking base node for header:{}", rpc_error); match &rpc_error { RequestFailed(status) => { - if status.status_code() == NotFound { + if status.as_status_code().is_not_found() { return Ok(None); } else { return Err(rpc_error.into()); diff --git a/base_layer/wallet/src/transaction_service/config.rs b/base_layer/wallet/src/transaction_service/config.rs index 42e6f6478b..85ebedb71f 100644 --- a/base_layer/wallet/src/transaction_service/config.rs +++ b/base_layer/wallet/src/transaction_service/config.rs @@ -50,7 +50,7 @@ impl Default for TransactionServiceConfig { direct_send_timeout: Duration::from_secs(20), broadcast_send_timeout: Duration::from_secs(60), low_power_polling_timeout: Duration::from_secs(300), - transaction_resend_period: Duration::from_secs(3600), + transaction_resend_period: Duration::from_secs(600), resend_response_cooldown: Duration::from_secs(300), pending_transaction_cancellation_timeout: Duration::from_secs(259200), // 3 Days num_confirmations_required: 3, diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs index eff2c9f0cc..639a246e7a 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs @@ -327,7 +327,7 @@ where warn!(target: LOG_TARGET, "Error asking base node for header:{}", rpc_error); match &rpc_error { RequestFailed(status) => { - if status.status_code() == NotFound { + if status.as_status_code() == NotFound { return Ok(None); } else { return Err(rpc_error.into()); diff --git a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs index bd3796479d..e6fb3f1011 100644 --- a/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs +++ b/base_layer/wallet/src/utxo_scanner_service/utxo_scanning.rs @@ -354,7 +354,7 @@ where TBackend: WalletBackend + 'static // this returns the index of the vec of hashes we sent it, that is the last hash it knows of. match client.find_chain_split(request).await { Ok(_) => Ok(metadata.utxo_index + 1), - Err(RpcError::RequestFailed(err)) if err.status_code().is_not_found() => { + Err(RpcError::RequestFailed(err)) if err.as_status_code().is_not_found() => { warn!(target: LOG_TARGET, "Reorg detected: {}", err); // The node does not know of the last hash we scanned, thus we had a chain split. // We now start at 0 again. diff --git a/comms/dht/examples/memory_net/utilities.rs b/comms/dht/examples/memory_net/utilities.rs index 9cf6340ebc..c42e433272 100644 --- a/comms/dht/examples/memory_net/utilities.rs +++ b/comms/dht/examples/memory_net/utilities.rs @@ -245,7 +245,7 @@ pub async fn network_connectivity_stats(nodes: &[TestNode], wallets: &[TestNode] total += t; avg += a; println!( - "{} total connections on the network. ({} per node on average)", + "{} total connections on the network. ({} per peer on average)", total, avg / (wallets.len() + nodes.len()) ); diff --git a/comms/dht/src/rpc/test.rs b/comms/dht/src/rpc/test.rs index cd70d65a0f..ef250217a2 100644 --- a/comms/dht/src/rpc/test.rs +++ b/comms/dht/src/rpc/test.rs @@ -159,7 +159,7 @@ mod get_closer_peers { let node_id = NodeId::default(); let req = mock.request_with_context(node_id, req); let err = service.get_closer_peers(req).await.unwrap_err(); - assert_eq!(err.status_code(), RpcStatusCode::BadRequest); + assert_eq!(err.as_status_code(), RpcStatusCode::BadRequest); } } diff --git a/comms/rpc_macros/tests/macro.rs b/comms/rpc_macros/tests/macro.rs index 71f0b7053f..15d11f5876 100644 --- a/comms/rpc_macros/tests/macro.rs +++ b/comms/rpc_macros/tests/macro.rs @@ -147,7 +147,7 @@ async fn it_returns_an_error_for_invalid_method_nums() { .await .unwrap_err(); - unpack_enum!(RpcStatusCode::UnsupportedMethod = err.status_code()); + unpack_enum!(RpcStatusCode::UnsupportedMethod = err.as_status_code()); } #[tokio::test] diff --git a/comms/src/connection_manager/dialer.rs b/comms/src/connection_manager/dialer.rs index f3e04a9eeb..147ffb9299 100644 --- a/comms/src/connection_manager/dialer.rs +++ b/comms/src/connection_manager/dialer.rs @@ -27,6 +27,7 @@ use crate::{ common, dial_state::DialState, manager::{ConnectionManagerConfig, ConnectionManagerEvent}, + metrics, peer_connection, }, multiaddr::Multiaddr, @@ -193,6 +194,7 @@ where dial_result: Result, ) { let node_id = dial_state.peer().node_id.clone(); + metrics::pending_connections(Some(&node_id), ConnectionDirection::Outbound).inc(); let removed = self.cancel_signals.remove(&node_id); drop(removed); @@ -213,6 +215,8 @@ where }, } + metrics::pending_connections(Some(&node_id), ConnectionDirection::Outbound).dec(); + if self.pending_dial_requests.contains_key(&node_id) { self.reply_to_pending_requests(&node_id, dial_result.clone()); } diff --git a/comms/src/connection_manager/listener.rs b/comms/src/connection_manager/listener.rs index c79e192795..2e96471e75 100644 --- a/comms/src/connection_manager/listener.rs +++ b/comms/src/connection_manager/listener.rs @@ -32,6 +32,7 @@ use crate::{ bounded_executor::BoundedExecutor, connection_manager::{ liveness::LivenessSession, + metrics, wire_mode::{WireMode, LIVENESS_WIRE_MODE}, }, multiaddr::Multiaddr, @@ -239,6 +240,7 @@ where let span = span!(Level::TRACE, "connection_mann::listener::inbound_task",); let inbound_fut = async move { + metrics::pending_connections(None, ConnectionDirection::Inbound).inc(); match Self::read_wire_format(&mut socket, config.time_to_first_byte).await { Ok(WireMode::Comms(byte)) if byte == config.network_info.network_byte => { let this_node_id_str = node_identity.node_id().short_str(); @@ -325,6 +327,8 @@ where ); }, } + + metrics::pending_connections(None, ConnectionDirection::Inbound).dec(); } .instrument(span); diff --git a/comms/src/connection_manager/manager.rs b/comms/src/connection_manager/manager.rs index d218a883a3..b1ad6d4c96 100644 --- a/comms/src/connection_manager/manager.rs +++ b/comms/src/connection_manager/manager.rs @@ -29,6 +29,7 @@ use super::{ }; use crate::{ backoff::Backoff, + connection_manager::{metrics, ConnectionDirection}, multiplexing::Substream, noise::NoiseConfig, peer_manager::{NodeId, NodeIdentity}, @@ -397,10 +398,14 @@ where node_id.short_str(), proto_str ); + metrics::inbound_substream_counter(&node_id, &protocol).inc(); let notify_fut = self .protocols .notify(&protocol, ProtocolEvent::NewInboundSubstream(node_id, stream)); match time::timeout(Duration::from_secs(10), notify_fut).await { + Ok(Ok(_)) => { + debug!(target: LOG_TARGET, "Protocol notification for '{}' sent", proto_str); + }, Ok(Err(err)) => { error!( target: LOG_TARGET, @@ -413,12 +418,21 @@ where "Error sending NewSubstream notification for protocol '{}' because {}", proto_str, err ); }, - _ => { - debug!(target: LOG_TARGET, "Protocol notification for '{}' sent", proto_str); - }, } }, + PeerConnected(conn) => { + metrics::successful_connections(conn.peer_node_id(), conn.direction()).inc(); + self.publish_event(PeerConnected(conn)); + }, + PeerConnectFailed(peer, err) => { + metrics::failed_connections(&peer, ConnectionDirection::Outbound).inc(); + self.publish_event(PeerConnectFailed(peer, err)); + }, + PeerInboundConnectFailed(err) => { + metrics::failed_connections(&Default::default(), ConnectionDirection::Inbound).inc(); + self.publish_event(PeerInboundConnectFailed(err)); + }, event => { self.publish_event(event); }, diff --git a/comms/src/connection_manager/metrics.rs b/comms/src/connection_manager/metrics.rs new file mode 100644 index 0000000000..b7ac2b857b --- /dev/null +++ b/comms/src/connection_manager/metrics.rs @@ -0,0 +1,82 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{connection_manager::ConnectionDirection, peer_manager::NodeId, protocol::ProtocolId}; +use once_cell::sync::Lazy; +use tari_metrics::{IntCounter, IntCounterVec, IntGauge, IntGaugeVec}; + +pub fn pending_connections(peer: Option<&NodeId>, direction: ConnectionDirection) -> IntGauge { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_gauge_vec( + "comms::connections::pending", + "Number of active connections by direction", + &["peer_id", "direction"], + ) + .unwrap() + }); + + METER.with_label_values(&[ + peer.map(ToString::to_string) + .unwrap_or_else(|| "unknown".to_string()) + .as_str(), + direction.as_str(), + ]) +} + +pub fn successful_connections(peer: &NodeId, direction: ConnectionDirection) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::connections::success", + "Number of active connections by direction", + &["peer_id", "direction"], + ) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str(), direction.as_str()]) +} + +pub fn failed_connections(peer: &NodeId, direction: ConnectionDirection) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::connections::failed", + "Number of active connections by direction", + &["peer_id", "direction"], + ) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str(), direction.as_str()]) +} + +pub fn inbound_substream_counter(peer: &NodeId, protocol: &ProtocolId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::connections::inbound_substream_request_count", + "Number of substream requests", + &["peer_id", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} diff --git a/comms/src/connection_manager/mod.rs b/comms/src/connection_manager/mod.rs index 92ef4e18f1..fd0f7a3312 100644 --- a/comms/src/connection_manager/mod.rs +++ b/comms/src/connection_manager/mod.rs @@ -23,6 +23,7 @@ mod dial_state; mod dialer; mod listener; +mod metrics; mod common; pub use common::validate_peer_addresses; diff --git a/comms/src/connectivity/manager.rs b/comms/src/connectivity/manager.rs index d8043050e7..ad75db44b0 100644 --- a/comms/src/connectivity/manager.rs +++ b/comms/src/connectivity/manager.rs @@ -510,10 +510,7 @@ impl ConnectivityManagerActor { _ => {}, } }, - #[cfg(feature = "metrics")] - NewInboundSubstream(node_id, protocol, _) => { - super::metrics::substream_request_count(node_id, protocol).inc(); - }, + _ => {}, } diff --git a/comms/src/connectivity/metrics.rs b/comms/src/connectivity/metrics.rs index 56bd8f781d..1470b4c9c7 100644 --- a/comms/src/connectivity/metrics.rs +++ b/comms/src/connectivity/metrics.rs @@ -20,32 +20,23 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{connection_manager::ConnectionDirection, peer_manager::NodeId, protocol::ProtocolId}; +use crate::connection_manager::ConnectionDirection; use once_cell::sync::Lazy; use tari_metrics::{IntGauge, IntGaugeVec}; pub fn connections(direction: ConnectionDirection) -> IntGauge { static METER: Lazy = Lazy::new(|| { - tari_metrics::register_int_gauge_vec("comms::connections", "Number of active connections by direction", &[ - "direction", - ]) + tari_metrics::register_int_gauge_vec( + "comms::connectivity::num_connections", + "Number of active connections by direction", + &["direction"], + ) .unwrap() }); METER.with_label_values(&[direction.as_str()]) } -pub fn substream_request_count(peer: &NodeId, protocol: &ProtocolId) -> IntGauge { - static METER: Lazy = Lazy::new(|| { - tari_metrics::register_int_gauge_vec("comms::substream_request_count", "Number of substream requests", &[ - "peer", "protocol", - ]) - .unwrap() - }); - - METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) -} - pub fn uptime() -> IntGauge { static METER: Lazy = Lazy::new(|| tari_metrics::register_int_gauge("comms::uptime", "Comms uptime").unwrap()); diff --git a/comms/src/multiplexing/metrics.rs b/comms/src/multiplexing/metrics.rs new file mode 100644 index 0000000000..823501aa57 --- /dev/null +++ b/comms/src/multiplexing/metrics.rs @@ -0,0 +1,32 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use once_cell::sync::Lazy; +use tari_metrics::IntCounter; + +pub static TOTAL_BYTES_READ: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter("comms::substream::total_bytes_read", "The total inbound bytes").unwrap() +}); + +pub static TOTAL_BYTES_WRITTEN: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter("comms::substream::total_bytes_written", "The total outbound bytes").unwrap() +}); diff --git a/comms/src/multiplexing/mod.rs b/comms/src/multiplexing/mod.rs index 8ac082d5c4..1732ea142b 100644 --- a/comms/src/multiplexing/mod.rs +++ b/comms/src/multiplexing/mod.rs @@ -20,5 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#[cfg(feature = "metrics")] +mod metrics; + mod yamux; pub use self::yamux::{ConnectionError, Control, IncomingSubstreams, Substream, Yamux}; diff --git a/comms/src/multiplexing/yamux.rs b/comms/src/multiplexing/yamux.rs index 811f485390..a9cf697c31 100644 --- a/comms/src/multiplexing/yamux.rs +++ b/comms/src/multiplexing/yamux.rs @@ -220,12 +220,21 @@ impl StreamId for Substream { impl tokio::io::AsyncRead for Substream { fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - Pin::new(&mut self.stream).poll_read(cx, buf) + match Pin::new(&mut self.stream).poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + #[cfg(feature = "metrics")] + super::metrics::TOTAL_BYTES_READ.inc_by(buf.filled().len() as u64); + Poll::Ready(Ok(())) + }, + res => res, + } } } impl tokio::io::AsyncWrite for Substream { fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + #[cfg(feature = "metrics")] + super::metrics::TOTAL_BYTES_WRITTEN.inc_by(buf.len() as u64); Pin::new(&mut self.stream).poll_write(cx, buf) } diff --git a/comms/src/protocol/messaging/inbound.rs b/comms/src/protocol/messaging/inbound.rs index e65f81f8ec..9445445629 100644 --- a/comms/src/protocol/messaging/inbound.rs +++ b/comms/src/protocol/messaging/inbound.rs @@ -20,12 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{ - message::InboundMessage, - peer_manager::NodeId, - protocol::messaging::{MessagingEvent, MessagingProtocol}, - rate_limit::RateLimit, -}; +use super::{metrics, MessagingEvent, MessagingProtocol}; +use crate::{message::InboundMessage, peer_manager::NodeId, rate_limit::RateLimit}; use futures::{future::Either, StreamExt}; use log::*; use std::{sync::Arc, time::Duration}; @@ -67,6 +63,7 @@ impl InboundMessaging { pub async fn run(self, socket: S) where S: AsyncRead + AsyncWrite + Unpin { let peer = &self.peer; + metrics::num_sessions().inc(); debug!( target: LOG_TARGET, "Starting inbound messaging protocol for peer '{}'", @@ -82,9 +79,11 @@ impl InboundMessaging { }; tokio::pin!(stream); + let inbound_count = metrics::inbound_message_count(&self.peer); while let Some(result) = stream.next().await { match result { Ok(Ok(raw_msg)) => { + inbound_count.inc(); let msg_len = raw_msg.len(); let inbound_msg = InboundMessage::new(peer.clone(), raw_msg.freeze()); debug!( @@ -112,6 +111,7 @@ impl InboundMessaging { let _ = self.messaging_events_tx.send(Arc::new(event)); }, Ok(Err(err)) => { + metrics::error_count(peer).inc(); error!( target: LOG_TARGET, "Failed to receive from peer '{}' because '{}'", @@ -122,6 +122,7 @@ impl InboundMessaging { }, Err(_) => { + metrics::error_count(peer).inc(); debug!( target: LOG_TARGET, "Inbound messaging for peer '{}' has stopped because it was inactive for {:.0?}", @@ -134,6 +135,7 @@ impl InboundMessaging { } } + metrics::num_sessions().dec(); debug!( target: LOG_TARGET, "Inbound messaging handler exited for peer `{}`", diff --git a/comms/src/protocol/messaging/metrics.rs b/comms/src/protocol/messaging/metrics.rs new file mode 100644 index 0000000000..e43fb4a7d6 --- /dev/null +++ b/comms/src/protocol/messaging/metrics.rs @@ -0,0 +1,74 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::peer_manager::NodeId; +use once_cell::sync::Lazy; +use tari_metrics::{IntCounter, IntCounterVec, IntGauge}; + +pub fn num_sessions() -> IntGauge { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_gauge( + "comms::messaging::num_sessions", + "The number of active messaging sessions", + ) + .unwrap() + }); + + METER.clone() +} + +pub fn outbound_message_count(peer: &NodeId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::messaging::outbound_message_count", + "The number of handshakes per peer", + &["peer_id"], + ) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str()]) +} + +pub fn inbound_message_count(peer: &NodeId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::messaging::inbound_message_count", + "The number of handshakes per peer", + &["peer_id"], + ) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str()]) +} + +pub fn error_count(peer: &NodeId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec("comms::messaging::errors", "The number of errors per peer", &[ + "peer_id", + ]) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str()]) +} diff --git a/comms/src/protocol/messaging/mod.rs b/comms/src/protocol/messaging/mod.rs index 1fa347b3a2..7fd5703e50 100644 --- a/comms/src/protocol/messaging/mod.rs +++ b/comms/src/protocol/messaging/mod.rs @@ -29,6 +29,7 @@ pub use extension::MessagingProtocolExtension; mod error; mod forward; mod inbound; +mod metrics; mod outbound; mod protocol; pub use protocol::{ diff --git a/comms/src/protocol/messaging/outbound.rs b/comms/src/protocol/messaging/outbound.rs index a2c4288b4e..98fc30a9e2 100644 --- a/comms/src/protocol/messaging/outbound.rs +++ b/comms/src/protocol/messaging/outbound.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use super::{error::MessagingProtocolError, MessagingEvent, MessagingProtocol, SendFailReason}; +use super::{error::MessagingProtocolError, metrics, MessagingEvent, MessagingProtocol, SendFailReason}; use crate::{ connection_manager::{NegotiatedSubstream, PeerConnection}, connectivity::{ConnectivityError, ConnectivityRequester}, @@ -71,6 +71,7 @@ impl OutboundMessaging { "comms::messaging::outbound", node_id = self.peer_node_id.to_string().as_str() ); + metrics::num_sessions().inc(); async move { debug!( target: LOG_TARGET, @@ -112,6 +113,7 @@ impl OutboundMessaging { ); }, Err(err) => { + metrics::error_count(&peer_node_id).inc(); error!( target: LOG_TARGET, "Outbound messaging protocol failed for peer {}: {}", peer_node_id, err @@ -119,6 +121,7 @@ impl OutboundMessaging { }, } + metrics::num_sessions().dec(); let _ = messaging_events_tx .send(MessagingEvent::OutboundProtocolExited(peer_node_id)) .await; @@ -291,7 +294,9 @@ impl OutboundMessaging { None => Either::Right(stream.map(Ok)), }; + let outbound_count = metrics::outbound_message_count(&self.peer_node_id); let stream = stream.map(|msg| { + outbound_count.inc(); msg.map(|mut out_msg| { event!(Level::DEBUG, "Message buffered for sending {}", out_msg); out_msg.reply_success(); diff --git a/comms/src/protocol/rpc/client/metrics.rs b/comms/src/protocol/rpc/client/metrics.rs index ee77a6b91f..98f99e4492 100644 --- a/comms/src/protocol/rpc/client/metrics.rs +++ b/comms/src/protocol/rpc/client/metrics.rs @@ -22,69 +22,108 @@ use crate::{peer_manager::NodeId, protocol::ProtocolId}; use once_cell::sync::Lazy; -use tari_metrics::{Histogram, HistogramVec, IntGauge, IntGaugeVec}; +use tari_metrics::{Histogram, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec}; -pub fn sessions_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { +pub fn num_sessions(peer: &NodeId, protocol: &ProtocolId) -> IntGauge { static METER: Lazy = Lazy::new(|| { tari_metrics::register_int_gauge_vec( "comms::rpc::client::num_sessions", - "The number of active clients per node per protocol", - &["peer", "protocol"], + "The number of active clients per peer per protocol", + &["peer_id", "protocol"], ) .unwrap() }); - METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) } -pub fn handshake_errors(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { - static METER: Lazy = Lazy::new(|| { - tari_metrics::register_int_gauge_vec( +pub fn handshake_counter(peer: &NodeId, protocol: &ProtocolId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::rpc::client::handshake_count", + "The number of handshakes per peer per protocol", + &["peer_id", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn handshake_errors(peer: &NodeId, protocol: &ProtocolId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( "comms::rpc::client::handshake_errors", - "The number of handshake errors per node per protocol", - &["peer", "protocol"], + "The number of handshake errors per peer per protocol", + &["peer_id", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn client_errors(peer: &NodeId, protocol: &ProtocolId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::rpc::client::error_count", + "The number of client errors per peer per protocol", + &["peer_id", "protocol"], + ) + .unwrap() + }); + + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) +} + +pub fn client_timeouts(peer: &NodeId, protocol: &ProtocolId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::rpc::client::error_timeouts", + "The number of client timeouts per peer per protocol", + &["peer_id", "protocol"], ) .unwrap() }); - METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) } -pub fn request_response_latency(node_id: &NodeId, protocol: &ProtocolId) -> Histogram { +pub fn request_response_latency(peer: &NodeId, protocol: &ProtocolId) -> Histogram { static METER: Lazy = Lazy::new(|| { tari_metrics::register_histogram_vec( "comms::rpc::client::request_response_latency", "A histogram of request to first response latency", - &["peer", "protocol"], + &["peer_id", "protocol"], ) .unwrap() }); - METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) } -pub fn outbound_request_bytes(node_id: &NodeId, protocol: &ProtocolId) -> Histogram { +pub fn outbound_request_bytes(peer: &NodeId, protocol: &ProtocolId) -> Histogram { static METER: Lazy = Lazy::new(|| { tari_metrics::register_histogram_vec( "comms::rpc::client::outbound_request_bytes", - "Avg. request bytes per node per protocol", - &["peer", "protocol"], + "Avg. request bytes per peer per protocol", + &["peer_id", "protocol"], ) .unwrap() }); - METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) } -pub fn inbound_response_bytes(node_id: &NodeId, protocol: &ProtocolId) -> Histogram { +pub fn inbound_response_bytes(peer: &NodeId, protocol: &ProtocolId) -> Histogram { static METER: Lazy = Lazy::new(|| { tari_metrics::register_histogram_vec( "comms::rpc::client::inbound_response_bytes", "Avg. response bytes per peer per protocol", - &["peer", "protocol"], + &["peer_id", "protocol"], ) .unwrap() }); - METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) + METER.with_label_values(&[peer.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) } diff --git a/comms/src/protocol/rpc/client/mod.rs b/comms/src/protocol/rpc/client/mod.rs index 9bec5975ab..b69caaa1db 100644 --- a/comms/src/protocol/rpc/client/mod.rs +++ b/comms/src/protocol/rpc/client/mod.rs @@ -462,8 +462,10 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId if let Some(r) = self.ready_tx.take() { let _ = r.send(Ok(())); } + metrics::handshake_counter(&self.node_id, &self.protocol_id).inc(); }, Err(err) => { + metrics::handshake_errors(&self.node_id, &self.protocol_id).inc(); if let Some(r) = self.ready_tx.take() { let _ = r.send(Err(err.into())); } @@ -472,8 +474,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId }, } - let session_counter = metrics::sessions_counter(&self.node_id, &self.protocol_id); - session_counter.inc(); + metrics::num_sessions(&self.node_id, &self.protocol_id).inc(); loop { tokio::select! { biased; @@ -484,6 +485,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId match req { Some(req) => { if let Err(err) = self.handle_request(req).await { + metrics::client_errors(&self.node_id, &self.protocol_id).inc(); error!(target: LOG_TARGET, "(stream={}) Unexpected error: {}. Worker is terminating.", self.stream_id(), err); break; } @@ -493,21 +495,23 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId } } } - session_counter.dec(); + metrics::num_sessions(&self.node_id, &self.protocol_id).dec(); if let Err(err) = self.framed.close().await { debug!( target: LOG_TARGET, - "(stream={}) IO Error when closing substream: {}", + "(stream: {}, peer: {}) IO Error when closing substream: {}", self.stream_id(), + self.node_id, err ); } debug!( target: LOG_TARGET, - "(stream={}) RpcClientWorker ({}) terminated.", + "(stream: {}, peer: {}) RpcClientWorker ({}) terminated.", self.stream_id(), + self.node_id, self.protocol_name() ); } @@ -552,6 +556,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId self.stream_id(), start.elapsed() ); + metrics::client_timeouts(&self.node_id, &self.protocol_id).inc(); let _ = reply.send(Err(RpcStatus::timed_out("Response timed out"))); return Ok(()); }, @@ -583,7 +588,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId Ok(()) } - #[tracing::instrument(name = "rpc_do_request_response", skip(self, reply, request), fields(request_method = ?request.method, request_size = request.message.len()))] + #[tracing::instrument(name = "rpc_do_request_response", skip(self, reply, request), fields(request_method = ?request.method, request_body_size = request.message.len()))] async fn do_request_response( &mut self, request: BaseRequest, @@ -629,6 +634,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId let mut metrics_timer = Some(latency.start_timer()); if let Err(err) = self.send_request(req).await { warn!(target: LOG_TARGET, "{}", err); + metrics::client_errors(&self.node_id, &self.protocol_id).inc(); let _ = response_tx.send(Err(err.into())); return Ok(()); } @@ -637,7 +643,9 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId if self.shutdown_signal.is_triggered() { debug!( target: LOG_TARGET, - "[{}, stream_id: {}, req_id: {}] Client connector closed. Quitting stream early", + "[peer: {}, protocol: {}, stream_id: {}, req_id: {}] Client connector closed. Quitting stream \ + early", + self.node_id, self.protocol_name(), self.stream_id(), request_id @@ -671,7 +679,17 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId "Request {} (method={}) timed out", request_id, method, ); event!(Level::ERROR, "Response timed out"); - if !response_tx.is_closed() { + metrics::client_timeouts(&self.node_id, &self.protocol_id).inc(); + if response_tx.is_closed() { + let req = proto::rpc::RpcRequest { + request_id: request_id as u32, + method, + flags: RpcMessageFlags::FIN.bits().into(), + ..Default::default() + }; + + self.send_request(req).await?; + } else { let _ = response_tx.send(Err(RpcStatus::timed_out("Response timed out"))).await; } break; @@ -713,9 +731,8 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId let req = proto::rpc::RpcRequest { request_id: request_id as u32, method, - deadline: self.config.deadline.map(|t| t.as_secs()).unwrap_or(0), flags: RpcMessageFlags::FIN.bits().into(), - payload: vec![], + ..Default::default() }; self.send_request(req).await?; @@ -773,7 +790,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId break resp; }, Err(RpcError::ResponseIdDidNotMatchRequest { actual, expected }) - if actual.saturating_add(1) == request_id => + if actual.wrapping_add(1) == request_id => { warn!( target: LOG_TARGET, @@ -783,7 +800,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId // Be lenient for a number of messages that may have been buffered to come through for the previous // request. - const MAX_ALLOWED_IGNORED: usize = 5; + const MAX_ALLOWED_IGNORED: usize = 20; if num_ignored > MAX_ALLOWED_IGNORED { return Err(RpcError::ResponseIdDidNotMatchRequest { actual, expected }); } @@ -798,7 +815,7 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin + Send + StreamId fn next_request_id(&mut self) -> u16 { let next_id = self.next_request_id; // request_id is allowed to wrap around back to 0 - self.next_request_id = self.next_request_id.checked_add(1).unwrap_or(0); + self.next_request_id = self.next_request_id.wrapping_add(1); next_id } diff --git a/comms/src/protocol/rpc/server/error.rs b/comms/src/protocol/rpc/server/error.rs index f5cc145de2..7259e987fb 100644 --- a/comms/src/protocol/rpc/server/error.rs +++ b/comms/src/protocol/rpc/server/error.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::protocol::rpc::handshake::RpcHandshakeError; +use crate::{proto, protocol::rpc::handshake::RpcHandshakeError}; use prost::DecodeError; use std::io; use tokio::sync::oneshot; @@ -42,7 +42,15 @@ pub enum RpcServerError { #[error("Service not found for protocol `{0}`")] ProtocolServiceNotFound(String), #[error("Unexpected incoming message")] - UnexpectedIncomingMessage, + UnexpectedIncomingMessage(proto::rpc::RpcRequest), + #[error("Unexpected incoming MALFORMED message")] + UnexpectedIncomingMessageMalformed, + #[error("Client interrupted stream")] + ClientInterruptedStream, + #[error("Service call exceeded deadline")] + ServiceCallExceededDeadline, + #[error("Stream read exceeded deadline")] + ReadStreamExceededDeadline, } impl From for RpcServerError { @@ -50,3 +58,21 @@ impl From for RpcServerError { RpcServerError::RequestCanceled } } + +impl RpcServerError { + pub fn to_debug_string(&self) -> String { + use RpcServerError::*; + match self { + DecodeError(_) => "DecodeError".to_string(), + Io(err) => { + format!("Io({:?})", err.kind()) + }, + HandshakeError(_) => "HandshakeError".to_string(), + ProtocolServiceNotFound(_) => "ProtocolServiceNotFound".to_string(), + UnexpectedIncomingMessage(_) => "UnexpectedIncomingMessage".to_string(), + err => { + format!("{:?}", err) + }, + } + } +} diff --git a/comms/src/protocol/rpc/server/metrics.rs b/comms/src/protocol/rpc/server/metrics.rs index cb0c1d7d2a..ed11db7e9e 100644 --- a/comms/src/protocol/rpc/server/metrics.rs +++ b/comms/src/protocol/rpc/server/metrics.rs @@ -20,16 +20,22 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{peer_manager::NodeId, protocol::ProtocolId}; +use crate::{ + peer_manager::NodeId, + protocol::{ + rpc::{RpcServerError, RpcStatusCode}, + ProtocolId, + }, +}; use once_cell::sync::Lazy; use tari_metrics::{Histogram, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec}; -pub fn sessions_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { +pub fn num_sessions(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { static METER: Lazy = Lazy::new(|| { tari_metrics::register_int_gauge_vec( "comms::rpc::server::num_sessions", - "The number of active server sessions per node per protocol", - &["peer", "protocol"], + "The number of active server sessions per peer per protocol", + &["peer_id", "protocol"], ) .unwrap() }); @@ -37,12 +43,12 @@ pub fn sessions_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) } -pub fn handshake_error_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntGauge { - static METER: Lazy = Lazy::new(|| { - tari_metrics::register_int_gauge_vec( - "comms::rpc::server::handshake_errors", - "The number of handshake errors per node per protocol", - &["peer", "protocol"], +pub fn handshake_error_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::rpc::server::handshake_error_count", + "The number of handshake errors per peer per protocol", + &["peer_id", "protocol"], ) .unwrap() }); @@ -50,25 +56,46 @@ pub fn handshake_error_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntGa METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) } -pub fn error_counter(node_id: &NodeId, protocol: &ProtocolId) -> IntCounter { +pub fn error_counter(node_id: &NodeId, protocol: &ProtocolId, err: &RpcServerError) -> IntCounter { static METER: Lazy = Lazy::new(|| { tari_metrics::register_int_counter_vec( "comms::rpc::server::error_count", - "The number of RPC errors per node per protocol", - &["peer", "protocol"], + "The number of RPC errors per peer per protocol", + &["peer_id", "protocol", "error"], ) .unwrap() }); - METER.with_label_values(&[node_id.to_string().as_str(), String::from_utf8_lossy(protocol).as_ref()]) + METER.with_label_values(&[ + node_id.to_string().as_str(), + String::from_utf8_lossy(protocol).as_ref(), + err.to_debug_string().as_str(), + ]) +} + +pub fn status_error_counter(node_id: &NodeId, protocol: &ProtocolId, status_code: RpcStatusCode) -> IntCounter { + static METER: Lazy = Lazy::new(|| { + tari_metrics::register_int_counter_vec( + "comms::rpc::server::status_error_count", + "The number of RPC errors by status code per peer per protocol", + &["peer_id", "protocol", "status"], + ) + .unwrap() + }); + + METER.with_label_values(&[ + node_id.to_string().as_str(), + String::from_utf8_lossy(protocol).as_ref(), + status_code.to_debug_string().as_str(), + ]) } pub fn inbound_requests_bytes(node_id: &NodeId, protocol: &ProtocolId) -> Histogram { static METER: Lazy = Lazy::new(|| { tari_metrics::register_histogram_vec( "comms::rpc::server::inbound_request_bytes", - "Avg. request bytes per node per protocol", - &["peer", "protocol"], + "Avg. request bytes per peer per protocol", + &["peer_id", "protocol"], ) .unwrap() }); @@ -81,7 +108,7 @@ pub fn outbound_response_bytes(node_id: &NodeId, protocol: &ProtocolId) -> Histo tari_metrics::register_histogram_vec( "comms::rpc::server::outbound_response_bytes", "Avg. response bytes per peer per protocol", - &["peer", "protocol"], + &["peer_id", "protocol"], ) .unwrap() }); diff --git a/comms/src/protocol/rpc/server/mod.rs b/comms/src/protocol/rpc/server/mod.rs index 31fa62ee5a..ed02b82149 100644 --- a/comms/src/protocol/rpc/server/mod.rs +++ b/comms/src/protocol/rpc/server/mod.rs @@ -70,12 +70,12 @@ use prost::Message; use std::{ borrow::Cow, future::Future, + io, pin::Pin, sync::Arc, task::Poll, time::{Duration, Instant}, }; -use tari_metrics::IntCounter; use tokio::{sync::mpsc, time}; use tokio_stream::Stream; use tower::Service; @@ -386,10 +386,10 @@ where let node_id = node_id.clone(); self.executor .try_spawn(async move { - let sessions_counter = metrics::sessions_counter(&node_id, &service.protocol); - sessions_counter.inc(); + let num_sessions = metrics::num_sessions(&node_id, &service.protocol); + num_sessions.inc(); service.start().await; - sessions_counter.dec(); + num_sessions.dec(); }) .map_err(|_| RpcServerError::MaximumSessionsReached)?; @@ -405,7 +405,6 @@ struct ActivePeerRpcService { framed: CanonicalFraming, comms_provider: TCommsProvider, logging_context_string: Arc, - error_counter: IntCounter, } impl ActivePeerRpcService @@ -421,7 +420,6 @@ where framed: CanonicalFraming, comms_provider: TCommsProvider, ) -> Self { - let error_counter = metrics::error_counter(&node_id, &protocol); Self { logging_context_string: Arc::new(format!( "stream_id: {}, peer: {}, protocol: {}", @@ -436,7 +434,6 @@ where service, framed, comms_provider, - error_counter, } } @@ -446,7 +443,7 @@ where "({}) Rpc server started.", self.logging_context_string, ); if let Err(err) = self.run().await { - self.error_counter.inc(); + metrics::error_counter(&self.node_id, &self.protocol, &err).inc(); error!( target: LOG_TARGET, "({}) Rpc server exited with an error: {}", self.logging_context_string, err @@ -470,7 +467,13 @@ where err ); } - self.error_counter.inc(); + error!( + target: LOG_TARGET, + "(peer: {}, protocol: {}) Failed to handle request: {}", + self.node_id, + self.protocol_name(), + err + ); return Err(err); } let elapsed = start.elapsed(); @@ -489,7 +492,6 @@ where "({}) Failed to close substream after socket error: {}", self.logging_context_string, err ); } - self.error_counter.inc(); return Err(err.into()); }, } @@ -524,8 +526,8 @@ where flags: RpcMessageFlags::FIN.bits().into(), payload: status.to_details_bytes(), }; + metrics::status_error_counter(&self.node_id, &self.protocol, status.as_status_code()).inc(); self.framed.send(bad_request.to_encoded_bytes().into()).await?; - self.error_counter.inc(); return Ok(()); } @@ -573,13 +575,17 @@ where Err(_) => { warn!( target: LOG_TARGET, - "RPC service was not able to complete within the deadline ({:.0?}). Request aborted for peer '{}' \ - ({}).", + "{} RPC service was not able to complete within the deadline ({:.0?}). Request aborted", + self.logging_context_string, deadline, - self.node_id, - self.protocol_name() ); - self.error_counter.inc(); + + metrics::error_counter( + &self.node_id, + &self.protocol, + &RpcServerError::ServiceCallExceededDeadline, + ) + .inc(); return Ok(()); }, }; @@ -589,12 +595,9 @@ where self.process_body(request_id, deadline, body).await?; }, Err(err) => { - debug!( + error!( target: LOG_TARGET, - "(peer: {}, protocol: {}) Service returned an error: {}", - self.node_id, - self.protocol_name(), - err + "{} Service returned an error: {}", self.logging_context_string, err ); let resp = proto::rpc::RpcResponse { request_id, @@ -603,7 +606,7 @@ where payload: err.to_details_bytes(), }; - self.error_counter.inc(); + metrics::status_error_counter(&self.node_id, &self.protocol, err.as_status_code()).inc(); self.framed.send(resp.to_encoded_bytes().into()).await?; }, } @@ -623,18 +626,33 @@ where ) -> Result<(), RpcServerError> { let response_bytes = metrics::outbound_response_bytes(&self.node_id, &self.protocol); trace!(target: LOG_TARGET, "Service call succeeded"); + + let node_id = self.node_id.clone(); + let protocol = self.protocol.clone(); let mut stream = body .into_message() .map(|result| into_response(request_id, result)) - .flat_map(|message| stream::iter(ChunkedResponseIter::new(message))) + .flat_map(move |message| { + if !message.status.is_ok() { + metrics::status_error_counter(&node_id, &protocol, message.status).inc(); + } + stream::iter(ChunkedResponseIter::new(message)) + }) .map(|resp| Bytes::from(resp.to_encoded_bytes())); loop { // Check if the client interrupted the outgoing stream if let Err(err) = self.check_interruptions().await { - // Debug level because there are many valid reasons to interrupt a stream - debug!(target: LOG_TARGET, "Stream interrupted: {}", err); - break; + match err { + err @ RpcServerError::ClientInterruptedStream => { + debug!(target: LOG_TARGET, "Stream was interrupted: {}", err); + break; + }, + err => { + error!(target: LOG_TARGET, "Stream was interrupted: {}", err); + return Err(err); + }, + } } let next_item = log_timing( @@ -667,7 +685,12 @@ where deadline ); - self.error_counter.inc(); + metrics::error_counter( + &self.node_id, + &self.protocol, + &RpcServerError::ReadStreamExceededDeadline, + ) + .inc(); break; }, } @@ -677,7 +700,22 @@ where async fn check_interruptions(&mut self) -> Result<(), RpcServerError> { let check = future::poll_fn(|cx| match Pin::new(&mut self.framed).poll_next(cx) { - Poll::Ready(Some(Ok(_))) => Poll::Ready(Some(RpcServerError::UnexpectedIncomingMessage)), + Poll::Ready(Some(Ok(mut msg))) => { + let decoded_msg = match proto::rpc::RpcRequest::decode(&mut msg) { + Ok(msg) => msg, + Err(err) => { + error!(target: LOG_TARGET, "Client send MALFORMED response: {}", err); + return Poll::Ready(Some(RpcServerError::UnexpectedIncomingMessageMalformed)); + }, + }; + let msg_flags = RpcMessageFlags::from_bits_truncate(decoded_msg.flags as u8); + if msg_flags.is_fin() { + Poll::Ready(Some(RpcServerError::ClientInterruptedStream)) + } else { + Poll::Ready(Some(RpcServerError::UnexpectedIncomingMessage(decoded_msg))) + } + }, + Poll::Ready(Some(Err(err))) if err.kind() == io::ErrorKind::WouldBlock => Poll::Ready(None), Poll::Ready(Some(Err(err))) => Poll::Ready(Some(RpcServerError::from(err))), Poll::Ready(None) => Poll::Ready(Some(RpcServerError::StreamClosedByRemote)), Poll::Pending => Poll::Ready(None), @@ -722,7 +760,7 @@ fn into_response(request_id: u32, result: Result) -> RpcRe } RpcResponse { request_id, - status: RpcStatus::ok().status_code(), + status: RpcStatus::ok().as_status_code(), flags, payload: msg.into_bytes().unwrap_or_else(Bytes::new), } @@ -731,7 +769,7 @@ fn into_response(request_id: u32, result: Result) -> RpcRe debug!(target: LOG_TARGET, "Body contained an error: {}", err); RpcResponse { request_id, - status: err.status_code(), + status: err.as_status_code(), flags: RpcMessageFlags::FIN, payload: Bytes::from(err.to_details_bytes()), } diff --git a/comms/src/protocol/rpc/status.rs b/comms/src/protocol/rpc/status.rs index 9194d36479..d2a8e20e6d 100644 --- a/comms/src/protocol/rpc/status.rs +++ b/comms/src/protocol/rpc/status.rs @@ -114,10 +114,10 @@ impl RpcStatus { } pub fn as_code(&self) -> u32 { - self.code as u32 + self.code.as_u32() } - pub fn status_code(&self) -> RpcStatusCode { + pub fn as_status_code(&self) -> RpcStatusCode { self.code } @@ -212,6 +212,14 @@ impl RpcStatusCode { pub fn is_timeout(self) -> bool { self == Self::Timeout } + + pub fn as_u32(&self) -> u32 { + *self as u32 + } + + pub fn to_debug_string(&self) -> String { + format!("{:?}", self) + } } impl From for RpcStatusCode { diff --git a/comms/src/protocol/rpc/test/comms_integration.rs b/comms/src/protocol/rpc/test/comms_integration.rs index f43f921081..025bfcc2b9 100644 --- a/comms/src/protocol/rpc/test/comms_integration.rs +++ b/comms/src/protocol/rpc/test/comms_integration.rs @@ -87,6 +87,6 @@ async fn run_service() { mock_state.set_response_err(RpcStatus::bad_request("Insert 💾")); let err = client.request_response::<_, ()>((), 0.into()).await.unwrap_err(); unpack_enum!(RpcError::RequestFailed(status) = err); - unpack_enum!(RpcStatusCode::BadRequest = status.status_code()); + unpack_enum!(RpcStatusCode::BadRequest = status.as_status_code()); assert_eq!(mock_state.call_count(), 2); } diff --git a/comms/src/protocol/rpc/test/smoke.rs b/comms/src/protocol/rpc/test/smoke.rs index 0fc1f05256..44eb8820ec 100644 --- a/comms/src/protocol/rpc/test/smoke.rs +++ b/comms/src/protocol/rpc/test/smoke.rs @@ -152,7 +152,7 @@ async fn request_response_errors_and_streaming() { let err = client.return_error().await.unwrap_err(); unpack_enum!(RpcError::RequestFailed(status) = err); - assert_eq!(status.status_code(), RpcStatusCode::NotImplemented); + assert_eq!(status.as_status_code(), RpcStatusCode::NotImplemented); assert_eq!(status.details(), "I haven't gotten to this yet :("); let stream = client.streaming_error("Gurglesplurb".to_string()).await.unwrap(); @@ -164,7 +164,7 @@ async fn request_response_errors_and_streaming() { .into_iter() .collect::>() .unwrap_err(); - assert_eq!(status.status_code(), RpcStatusCode::BadRequest); + assert_eq!(status.as_status_code(), RpcStatusCode::BadRequest); assert_eq!(status.details(), "What does 'Gurglesplurb' mean?"); let stream = client.streaming_error2().await.unwrap(); @@ -174,7 +174,7 @@ async fn request_response_errors_and_streaming() { assert_eq!(first_reply, "This is ok"); let second_reply = results.get(1).unwrap().as_ref().unwrap_err(); - assert_eq!(second_reply.status_code(), RpcStatusCode::BadRequest); + assert_eq!(second_reply.as_status_code(), RpcStatusCode::BadRequest); assert_eq!(second_reply.details(), "This is a problem"); let pk_hex = client.get_public_key_hex().await.unwrap(); @@ -262,7 +262,7 @@ async fn response_too_big() { .await .unwrap_err(); unpack_enum!(RpcError::RequestFailed(status) = err); - unpack_enum!(RpcStatusCode::MalformedResponse = status.status_code()); + unpack_enum!(RpcStatusCode::MalformedResponse = status.as_status_code()); // Check that the exact frame size boundary works and that the session is still going let _ = client @@ -314,7 +314,7 @@ async fn timeout() { let err = client.say_hello(Default::default()).await.unwrap_err(); unpack_enum!(RpcError::RequestFailed(status) = err); - assert_eq!(status.status_code(), RpcStatusCode::Timeout); + assert_eq!(status.as_status_code(), RpcStatusCode::Timeout); *delay.write().await = Duration::from_secs(0); From 038b9a6b14538ad13b227ed0da750fcdf42be48a Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Sun, 21 Nov 2021 13:17:27 +0200 Subject: [PATCH 44/46] chore: minor clippy fixes (#3576) Description --- - remove unnecessary vec copy in protobuf conversion - clippy fixes - remove duplicate utility function Motivation and Context --- Fix some minor clippy warnings How Has This Been Tested? --- Existing tests pass --- .../src/conversions/aggregate_body.rs | 2 +- base_layer/core/src/proto/mod.rs | 2 +- base_layer/core/src/proto/transaction.rs | 2 +- base_layer/core/src/proto/utils.rs | 15 --------------- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/applications/tari_app_grpc/src/conversions/aggregate_body.rs b/applications/tari_app_grpc/src/conversions/aggregate_body.rs index bd8a288800..2b296b96ae 100644 --- a/applications/tari_app_grpc/src/conversions/aggregate_body.rs +++ b/applications/tari_app_grpc/src/conversions/aggregate_body.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::convert::TryFrom; -use tari_core::{proto::utils::try_convert_all, transactions::aggregated_body::AggregateBody}; +use tari_core::{tari_utilities::convert::try_convert_all, transactions::aggregated_body::AggregateBody}; use crate::tari_rpc as grpc; diff --git a/base_layer/core/src/proto/mod.rs b/base_layer/core/src/proto/mod.rs index e555a36de7..f097724dae 100644 --- a/base_layer/core/src/proto/mod.rs +++ b/base_layer/core/src/proto/mod.rs @@ -51,4 +51,4 @@ mod block; #[cfg(any(feature = "base_node", feature = "base_node_proto"))] mod block_header; #[cfg(any(feature = "base_node", feature = "base_node_proto"))] -pub mod utils; +mod utils; diff --git a/base_layer/core/src/proto/transaction.rs b/base_layer/core/src/proto/transaction.rs index 4ee0f6e7c1..fc96ee43ac 100644 --- a/base_layer/core/src/proto/transaction.rs +++ b/base_layer/core/src/proto/transaction.rs @@ -162,7 +162,7 @@ impl TryFrom for TransactionOutput { let sender_offset_public_key = PublicKey::from_bytes(output.sender_offset_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; - let script = TariScript::from_bytes(&output.script.to_vec()).map_err(|err| err.to_string())?; + let script = TariScript::from_bytes(&output.script).map_err(|err| err.to_string())?; let metadata_signature = output .metadata_signature diff --git a/base_layer/core/src/proto/utils.rs b/base_layer/core/src/proto/utils.rs index 0fb2635014..6e8738995a 100644 --- a/base_layer/core/src/proto/utils.rs +++ b/base_layer/core/src/proto/utils.rs @@ -21,23 +21,8 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use prost_types::Timestamp; -use std::convert::TryInto; use tari_crypto::tari_utilities::epoch_time::EpochTime; -/// Tries to convert a series of `T`s to `U`s, returning an error at the first failure -pub fn try_convert_all(into_iter: I) -> Result, T::Error> -where - I: IntoIterator, - T: TryInto, -{ - let iter = into_iter.into_iter(); - let mut result = Vec::with_capacity(iter.size_hint().0); - for item in iter { - result.push(item.try_into()?); - } - Ok(result) -} - /// Utility function that converts a `prost::Timestamp` to a `chrono::DateTime` pub(crate) fn timestamp_to_datetime(timestamp: Timestamp) -> EpochTime { (timestamp.seconds as u64).into() From 57f51bc8fd23e8380307d210bdc247cf570bc083 Mon Sep 17 00:00:00 2001 From: David Main <51991544+StriderDM@users.noreply.github.com> Date: Sun, 21 Nov 2021 13:47:04 +0200 Subject: [PATCH 45/46] feat: language detection for mnemonic seed words (#3590) Description --- Implementation of a basic language detection algorithm for mnemonic words Motivation and Context --- Improve language detection for mnemonic words How Has This Been Tested? --- cargo test --all --- base_layer/key_manager/src/mnemonic.rs | 99 +++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 11 deletions(-) diff --git a/base_layer/key_manager/src/mnemonic.rs b/base_layer/key_manager/src/mnemonic.rs index 4226c8c250..6e7e6ef8f3 100644 --- a/base_layer/key_manager/src/mnemonic.rs +++ b/base_layer/key_manager/src/mnemonic.rs @@ -25,7 +25,7 @@ use crate::{ error::{KeyManagerError, MnemonicError}, mnemonic_wordlists::*, }; -use std::slice::Iter; +use std::{cmp::Ordering, slice::Iter}; use strum_macros::{Display, EnumString}; use tari_crypto::tari_utilities::bit::*; @@ -33,7 +33,7 @@ use tari_crypto::tari_utilities::bit::*; /// It can autodetect the language of the Mnemonic word sequence // TODO: Develop a language autodetection mechanism to distinguish between ChineseTraditional and ChineseSimplified -#[derive(Clone, Debug, PartialEq, EnumString, Display)] +#[derive(Clone, Debug, PartialEq, EnumString, Display, Copy)] pub enum MnemonicLanguage { ChineseSimplified, English, @@ -47,12 +47,8 @@ pub enum MnemonicLanguage { impl MnemonicLanguage { /// Detects the mnemonic language of a specific word by searching all defined mnemonic word lists pub fn from(mnemonic_word: &str) -> Result { - for language in MnemonicLanguage::iterator() { - if find_mnemonic_index_from_word(mnemonic_word, language).is_ok() { - return Ok((*language).clone()); - } - } - Err(MnemonicError::UnknownLanguage) + let words = vec![mnemonic_word.to_string()]; + detect_language(&words) } /// Returns an iterator for the MnemonicLanguage enum group to allow iteration over all defined languages @@ -158,11 +154,54 @@ pub fn from_bytes(bytes: Vec, language: &MnemonicLanguage) -> Result Result { + let count = words.iter().len(); + match count.cmp(&1) { + Ordering::Less => { + return Err(MnemonicError::UnknownLanguage); + }, + Ordering::Equal => { + let word = words.get(0).ok_or(MnemonicError::EncodeInvalidLength)?; + for language in MnemonicLanguage::iterator() { + if find_mnemonic_index_from_word(word, language).is_ok() { + return Ok(*language); + } + } + return Err(MnemonicError::UnknownLanguage); + }, + Ordering::Greater => { + for word in words { + let mut languages = Vec::with_capacity(MnemonicLanguage::iterator().len()); + // detect all languages in which a word falls into + for language in MnemonicLanguage::iterator() { + if find_mnemonic_index_from_word(word, language).is_ok() { + languages.push(*language); + } + } + // check if at least one of the languages is consistent for all other words against languages yielded + // from the initial word for this iteration + for language in languages { + let mut consistent = true; + for compare in words { + if compare != word && find_mnemonic_index_from_word(compare, &language).is_err() { + consistent = false; + } + } + if consistent { + return Ok(language); + } + } + } + }, + } + + Err(MnemonicError::UnknownLanguage) +} + /// Generates a vector of bytes that represent the provided mnemonic sequence of words, the language of the mnemonic -/// sequence is autodetected +/// sequence is detected pub fn to_bytes(mnemonic_seq: &[String]) -> Result, MnemonicError> { - let first_word = mnemonic_seq.get(0).ok_or(MnemonicError::EncodeInvalidLength)?; - let language = MnemonicLanguage::from(first_word)?; // Autodetect language + let language = self::detect_language(mnemonic_seq)?; to_bytes_with_language(mnemonic_seq, &language) } @@ -289,6 +328,44 @@ mod test { assert!(MnemonicLanguage::from(&"おかあさん".to_string()).is_err()); // Invalid Mnemonic Japanese word assert!(MnemonicLanguage::from(&"답정너".to_string()).is_err()); // Invalid Mnemonic Korean word assert!(MnemonicLanguage::from(&"desvelado".to_string()).is_err()); // Invalid Mnemonic Spanish word + + // English/Spanish + English/French -> English + let words1 = vec![ + "album".to_string(), + "area".to_string(), + "opera".to_string(), + "abandon".to_string(), + ]; + assert_eq!(detect_language(&words1), Ok(MnemonicLanguage::English)); + + // English/Spanish + English/French + Italian/Spanish + let words2 = vec![ + "album".to_string(), + "area".to_string(), + "opera".to_string(), + "abandon".to_string(), + "tipico".to_string(), + ]; + assert_eq!(detect_language(&words2).is_err(), true); + + // bounds check (last word is invalid) + let words3 = vec![ + "album".to_string(), + "area".to_string(), + "opera".to_string(), + "abandon".to_string(), + "topazio".to_string(), + ]; + assert_eq!(detect_language(&words3).is_err(), true); + + // building up a word list: English/French + French -> French + let mut words = Vec::with_capacity(3); + words.push("concert".to_string()); + assert_eq!(detect_language(&words), Ok(MnemonicLanguage::English)); + words.push("abandon".to_string()); + assert_eq!(detect_language(&words), Ok(MnemonicLanguage::English)); + words.push("barbier".to_string()); + assert_eq!(detect_language(&words), Ok(MnemonicLanguage::French)); } #[test] From b5a88e36871f47a657845a73663bb0468eaaa455 Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Sun, 21 Nov 2021 14:25:42 +0200 Subject: [PATCH 46/46] refactor: remove tari_common dependency from tari_comms (#3580) Description --- - Remove tari_common as a dependency on tari_comms. - Remove unused dependencies from tari_common Motivation and Context --- Tari common was added as a release dependency to tari_comms for a single error conversion. Tari comms should only rely on infrastructure crates. How Has This Been Tested? --- Code compiles --- applications/tari_console_wallet/src/recovery.rs | 5 ++++- comms/Cargo.toml | 1 - comms/src/peer_manager/error.rs | 7 ------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/applications/tari_console_wallet/src/recovery.rs b/applications/tari_console_wallet/src/recovery.rs index d3c247977a..852a9b817f 100644 --- a/applications/tari_console_wallet/src/recovery.rs +++ b/applications/tari_console_wallet/src/recovery.rs @@ -97,7 +97,10 @@ pub async fn wallet_recovery(wallet: &WalletSqlite, base_node_config: &PeerConfi peer.node_id.to_hex() ); peer_public_keys.push(peer.public_key.clone()); - peer_manager.add_peer(peer).await?; + peer_manager + .add_peer(peer) + .await + .map_err(|err| ExitCodes::NetworkError(err.to_string()))?; } let mut recovery_task = UtxoScannerService::::builder() diff --git a/comms/Cargo.toml b/comms/Cargo.toml index b24e3a3e6a..861ab55179 100644 --- a/comms/Cargo.toml +++ b/comms/Cargo.toml @@ -10,7 +10,6 @@ version = "0.21.2" edition = "2018" [dependencies] -tari_common = { path = "../common" } tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", branch = "main" } tari_storage = { version = "^0.21", path = "../infrastructure/storage" } tari_shutdown = { version = "^0.21", path = "../infrastructure/shutdown" } diff --git a/comms/src/peer_manager/error.rs b/comms/src/peer_manager/error.rs index 5f5a493e31..07db078b4f 100644 --- a/comms/src/peer_manager/error.rs +++ b/comms/src/peer_manager/error.rs @@ -21,7 +21,6 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE use std::sync::PoisonError; -use tari_common::exit_codes::ExitCodes; use tari_storage::KeyValStoreError; use thiserror::Error; @@ -37,12 +36,6 @@ pub enum PeerManagerError { MigrationError(String), } -impl From for ExitCodes { - fn from(err: PeerManagerError) -> Self { - ExitCodes::NetworkError(err.to_string()) - } -} - impl PeerManagerError { /// Returns true if this error indicates that the peer is not found, otherwise false pub fn is_peer_not_found(&self) -> bool {