diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6ccd535dd8..abaf3d723b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,7 +1,42 @@
version: 2.1
defaults:
- rust_image: &rust_image quay.io/tarilabs/rust_tari-build-with-deps:nightly-2019-10-04
+ rust_image: &rust_image quay.io/tarilabs/rust_tari-build-with-deps:nightly-2020-01-08
+
+commands:
+ test:
+ description: Run the tests
+ parameters:
+ release:
+ description: Set this to true to compile in release mode.
+ type: boolean
+ default: false
+ steps:
+ - 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"}}
+ - run:
+ name: Build the project
+ command: cargo build --all --all-features --jobs=3 <<#parameters.release>>--release<>
+ - run:
+ name: Cargo fmt (ignored in release mode)
+ command: |
+ TOOLCHAIN=$(cat rust-toolchain)
+ <<#parameters.release>>#<> rustup component add --toolchain $TOOLCHAIN rustfmt
+ <<#parameters.release>>#<> cargo fmt --all -- --check
+ - run:
+ name: Run tests
+ command: cargo test --workspace --all-features --jobs=3 <<#parameters.release>>--release<>
+ - save_cache:
+ paths:
+ - /usr/local/cargo/registry
+ - target
+ key: v6-cargo-cache-{{arch}}-{{checksum "rust-version"}}-<>-{{checksum "Cargo.lock"}}
jobs:
test-docs:
@@ -9,10 +44,6 @@ jobs:
- image: *rust_image
steps:
- checkout
- - run:
- command: |
- git submodule update --init --recursive
- name: Init git submodule
- run:
name: RFC documentation
command: |
@@ -24,15 +55,29 @@ jobs:
root: .
paths: book
+ test-tari-debug:
+ docker:
+ - image: *rust_image
+ resource_class: medium
+ steps:
+ - checkout
+ - test:
+ release: false
+
+ test-tari-release:
+ docker:
+ - image: *rust_image
+ resource_class: medium
+ steps:
+ - checkout
+ - test:
+ release: true
+
deploy-docs:
docker:
- image: quay.io/tarilabs/git-ssh-client:0.2-alpine
steps:
- checkout
- - run:
- command: |
- git submodule update --init --recursive
- name: Init git submodule
- attach_workspace:
at: .
- add_ssh_keys:
@@ -70,26 +115,7 @@ jobs:
echo "Published."
- test-tari:
- docker:
- - image: *rust_image
- resource_class: medium
- steps:
- - checkout
- - run:
- command: |
- git submodule update --init --recursive
- name: Init git submodule
- - run:
- name: Tari source code
- command: |
- TOOLCHAIN=$(cat rust-toolchain)
- NUM_JOBS=2
- rustup component add --toolchain $TOOLCHAIN rustfmt
- cargo build --jobs=$NUM_JOBS --all-features
- cargo fmt --all -- --check
- cargo test --workspace --all-features --jobs=$NUM_JOBS
- cargo test --workspace --all-features --release --jobs=$NUM_JOBS
+
workflows:
version: 2
@@ -99,14 +125,17 @@ workflows:
filters:
branches:
ignore: gh-pages
- - test-tari:
- filters:
- branches:
- ignore: gh-pages
- deploy-docs:
requires:
- test-docs
filters:
branches:
only: development
-
+ - test-tari-debug:
+ filters:
+ branches:
+ ignore: gh-pages
+ - test-tari-release:
+ filters:
+ branches:
+ ignore: gh-pages
diff --git a/.gitignore b/.gitignore
index 39f4caff1e..f47808e718 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,11 +28,14 @@ report
# On development branch only. This should be removed for point releases
Cargo.lock
-
*.log
-# Ignore DataStore and Database files
+# Ignore DataStore, Database and Log files
*.mdb
/data/
*.sqlite3
base_layer/wallet_ffi/build.config
+/base_layer/wallet_ffi/logs/
+base_layer/wallet_ffi/.cargo/config
+
+keys.json
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 5738ab0e15..0000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,8 +0,0 @@
-[submodule "comms/rust-multiaddr"]
- path = comms/rust-multiaddr
- url = https://github.com/tari-project/rust-multiaddr.git
- branch = tari
-[submodule "comms/yamux"]
- path = comms/yamux
- url = https://github.com/tari-project/yamux.git
- branch = futures-alpha
diff --git a/Cargo.toml b/Cargo.toml
index 41103647da..c04ff9aa5c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,32 +2,17 @@
members = [
"base_layer/core",
- "base_layer/transactions",
"base_layer/key_manager",
"base_layer/mmr",
- "base_layer/mining",
"base_layer/p2p",
"base_layer/service_framework",
"base_layer/wallet",
"base_layer/wallet_ffi",
"comms",
"comms/dht",
- "comms/middleware",
- # TODO: Remove this once tower filter (0.3.0-alpha.3) is released
- "comms/middleware/tower-filter",
- "digital_assets_layer/core",
- "infrastructure/broadcast_channel",
- "infrastructure/crypto",
- "infrastructure/protobuf_build",
- "infrastructure/pubsub",
"infrastructure/shutdown",
"infrastructure/storage",
"infrastructure/test_utils",
- #"applications/tari_testnet_miner",
"applications/tari_base_node",
- #Needs to be updated to make its calls using an Tokio runtime block_on function.
- #"ffi"
- #The wallet gRPC is based on the old Futures and not part of the Testnet scope
- #"applications/grpc_wallet",
-# "applications/console_text_messenger",
+ "applications/test_faucet",
]
diff --git a/README.md b/README.md
index a7d5586390..fe73d15e51 100644
--- a/README.md
+++ b/README.md
@@ -35,20 +35,6 @@ to generate the documentation. The generated html sits in `target/doc/`. Alterna
See [RFC-0110/CodeStructure](./RFC/src/RFC-0010_CodeStructure.md) for details on the code structure and layout.
-### Git submodules
-
-Git submodules are use temporarily until some dependent libraries are stabilized and released as crates.
-When checking out code take the following steps to ensure submodules are up to date.
-
-```shell script
-# Initialize submodules
-git submodule init
-# Sets `git pull` to automatically pull submodules
-git config submodule.recurse true
-# Checkout/update all submodules
-git submodule update --recursive --remote
-```
-
## Conversation channels
[](https://t.me/tarilab) Non-technical discussions and gentle sparring.
diff --git a/RFC/src/RFC-0152_EmojiId.md b/RFC/src/RFC-0152_EmojiId.md
new file mode 100644
index 0000000000..397b62b135
--- /dev/null
+++ b/RFC/src/RFC-0152_EmojiId.md
@@ -0,0 +1,173 @@
+# RFC-0152/EmojiId
+
+## Emoji Id specification
+
+![Status: Draft](theme/images/status-draft.svg)
+
+**Maintainer(s)**:[Cayle Sharrock](https://github.com/CjS77)
+
+# Licence
+
+[ The 3-Clause BSD Licence](https://opensource.org/licenses/BSD-3-Clause).
+
+Copyright 2020. 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 regarding the
+technological merits of the potential system outlined herein.
+
+## Goals
+
+This document describes the specification for Emoji Ids. Emoji Ids are encoded node ids used for humans to easily verify
+peer node addresses.
+
+## Related Requests for Comment
+
+None
+
+## Description
+
+Tari [Communication Node]s are identified on the network via their [Node ID]; which in turn are derived from the node's
+public key. Both the node id and public key are simple large integer numbers.
+
+The most common practice for human beings to copy large numbers in cryptocurrency software is to scan a QR code or copy
+and paste a value from one application to another. These numbers are typically encoded using hexadecimal or Base58
+encoding. The user will then typically scan (parts) of the string by eye to ensure that the value was transferred
+correctly.
+
+For Tari, we propose encoding values, the node ID in particular, using emoji. The advantages of this approach are:
+
+* Emoji are more easily identifiable; and if selected carefully, less prone to identification errors (e.g. mistaking an
+ O for a 0).
+* The alphabet can be considerably larger than hexadecimal (16) or Base58 (58), resulting in shorter character sequences
+ in the encoding.
+
+### The specification
+
+#### The emoji character map
+An emoji alphabet of 1,024 characters is selected. Each emoji is assigned a unique index from 0 to 1023 inclusive. This
+list is the emoji map. For example,
+
+* 😀 => 0
+* 😘 => 1
+* ...
+* 🦊 => 1023
+
+The emoji SHOULD be selected such that
+
+* Similar looking emoji are excluded from the map. e.g. Neither 😁 or 😄 should be included. Similarly the Irish and
+ Côte d'Ivoirean flags look very similar, and both should be excluded.
+* Modified emoji (skin tones, gender modifiers) are excluded. Only the "base" emoji is considered.
+
+#### Encoding
+
+The essential strategy in the encoding process is to map a sequence of 8-bit values onto a 10-bit alphabet. The general
+encoding procedure is as follows:
+
+Given a large integer value, represented as a _byte array_, `S`, in little-endian format (most significant digit last).
+Assume the string is addressable, i.e. `S[i]` is the `i`th byte in the array.
+* Set `CURSOR` to 0, Set `L` to a multiple of 10 that is `<= len(S)`.
+* Set `IDX` to `[]` (an empty array)
+* While `CURSOR < L`:
+ * Set `L <= S[CURSOR/8 + 1]`, the current low byte; if the index would overflow, set `L` to zero.
+ * Set `H <= S[CURSOR/8]`, the current high byte
+ * Set `n <= CURSOR % 8`, the position of the cursor in the current high byte
+ * Set `i <= ((H as u8) << n) << 2 + (L >> (6 - n))`, where the first shift left (`H as u8 <> 2`. This can be used to set the Emoji map accordingly (and may have to be
+ done iteratively, since the version is encoded into the emoji string).
+3. Set `CURSOR = 0`.
+4. Set `B = []`, and empty byte array
+5. While `CURSOR <= 11`:
+ 4. Set `k <= CURSOR * 2`
+ 5. Do a reverse lookup of the emoji`[CURSOR]` to find its index. Store this u64 value in `L`.
+ 6. If `k > 0`, set `H` to the reverse lookup index of emoji`[CURSOR-1]` as u8 (first 2 bits are discarded), else
+ `H=0`.
+ 7. Set `v = ((H as u8) << (8-k)) + (L >> (2+k))`. Push v onto tho `B`.
+ 9. Set `CURSOR <= CURSOR + 1`
+
+If the algorithm completes, `B` holds the node ID.
+
+#### Versioning
+
+The current emoji ID version number is 1. If the emoji alphabet changes, the version number MUST be incremented. This
+will usually cause incompatible versions of the emoji ID to be detected. However, this is not fail-safe.
+
+The last 6 bits of the 11th emoji encodes the version; this means that the first 4 bits are part of the node ID. On a
+reverse mapping, there is a chance that the reverse mapping would offer a valid, but incorrect version number if the new
+mapping are not chosen carefully.
+
+##### Example.
+
+In version 1, 😘 => `0b0000_000001` = 1 in the map. Seeing 😘 as the 11th emoji in a string would result in a version
+code of 1, which is consistent and expected.
+
+However, in unlucky version 13, if 😘 moves in the map to number 13 (`0b0000_001101`), the version decoding would also
+be valid and thus we wouldn't be able to unambiguously identify the version.
+
+
+
+[Communication Node]: Glossary.md#communication-node
+[Node ID]: Glossary.md#node-id
diff --git a/RFC/src/SUMMARY.md b/RFC/src/SUMMARY.md
index 7cc06213a1..7489fc08f4 100644
--- a/RFC/src/SUMMARY.md
+++ b/RFC/src/SUMMARY.md
@@ -11,6 +11,7 @@
- [RFC-0140: Sync and Seeding](RFC-0140_Syncing_and_seeding.md)
- [RFC-0150: Wallets](RFC-0150_Wallets.md)
- [RFC-0151: Transaction protocol](RFC-0151_TransactionProtocol.md)
+ - [RFC-0152: Emoji ID](RFC-0152_EmojiId.md)
- [RFC-0170: Network Communication Protocol](RFC-0170_NetworkCommunicationProtocol.md)
- [RFC-0171: Message Serialisation](RFC-0171_MessageSerialisation.md)
- [RFC-0172: Peer to Peer Messaging Protocol](RFC-0172_PeerToPeerMessagingProtocol.md)
diff --git a/applications/cli_wallet/Cargo.toml b/applications/cli_wallet/Cargo.toml
deleted file mode 100644
index a5b74bcf0e..0000000000
--- a/applications/cli_wallet/Cargo.toml
+++ /dev/null
@@ -1,5 +0,0 @@
-[package]
-name = "cli_wallet"
-version = "0.0.1"
-
-[dependencies]
diff --git a/applications/console_text_messenger/Cargo.toml b/applications/console_text_messenger/Cargo.toml
deleted file mode 100644
index fde5fe6802..0000000000
--- a/applications/console_text_messenger/Cargo.toml
+++ /dev/null
@@ -1,26 +0,0 @@
-[package]
-name = "console_text_messenger"
-version = "0.1.0"
-authors = ["Philip Robinson "]
-edition = "2018"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-tari_wallet = {path = "../../base_layer/wallet", version="^0.0"}
-tari_common = {path = "../../common", version= "^0.0"}
-tari_utilities = { path = "../../infrastructure/tari_util", version = "^0.0"}
-tari_comms = { path = "../../comms", version = "^0.0"}
-tari_p2p = {path = "../../base_layer/p2p", version = "^0.0"}
-tari_crypto = { path = "../../infrastructure/crypto", version = "^0.0"}
-clap = "2.33.0"
-serde = "1.0.90"
-serde_derive = "1.0.90"
-chrono = { version = "0.4.6", features = ["serde"]}
-config = { version = "0.9.3" }
-simple_logger = "1.2.0"
-log = { version = "0.4.0", features = ["std"] }
-crossbeam-channel = "0.3.8"
-log4rs = {version ="0.8.3",features = ["console_appender", "file_appender", "file", "yaml_format"]}
-ctrlc = "3.1.3"
-pnet = "0.22.0"
diff --git a/applications/console_text_messenger/src/main.rs b/applications/console_text_messenger/src/main.rs
deleted file mode 100644
index 93b8c8f928..0000000000
--- a/applications/console_text_messenger/src/main.rs
+++ /dev/null
@@ -1,400 +0,0 @@
-// 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.
-#[macro_use]
-extern crate clap;
-
-use clap::{App, Arg};
-use crossbeam_channel as channel;
-use log::*;
-use log4rs::{
- append::file::FileAppender,
- config::{Appender, Config, Root},
- encode::pattern::PatternEncoder,
-};
-use pnet::datalink::{self, NetworkInterface};
-use serde::{Deserialize, Serialize};
-use std::{fs, io, sync::Arc, thread, time::Duration};
-use tari_comms::{
- connection::NetAddress,
- control_service::ControlServiceConfig,
- peer_manager::Peer,
- types::{CommsPublicKey, CommsSecretKey},
-};
-use tari_crypto::keys::PublicKey;
-use tari_p2p::{initialization::CommsConfig, sync_services::ServiceError};
-use tari_utilities::{hex::Hex, message_format::MessageFormat};
-use tari_wallet::{
- text_message_service::{Contact, ReceivedTextMessage},
- wallet::WalletConfig,
- Wallet,
-};
-
-const LOG_TARGET: &str = "applications::cli_text_messenger";
-
-#[derive(Debug, Default, Deserialize)]
-struct Settings {
- control_port: Option,
- grpc_port: Option,
- secret_key: Option,
- data_path: Option,
- screen_name: Option,
-}
-#[derive(Debug, Serialize, Deserialize)]
-struct ConfigPeer {
- screen_name: String,
- pub_key: String,
- address: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-struct Peers {
- peers: Vec,
-}
-
-/// # A Barebones console based text messege application to help with debugging the comms stack and wallet library
-/// This app uses the same command switches as the grpc_wallet server and when using the -N command to load config
-/// file/Peer list pairs will load them from the grpc_wallet/sample_config folder.
-/// ## Usage
-/// You can provide your own config files and parameters via switches (-help will explain those) or you can use the -N
-/// switch followed by an integer to load an integer label config/peer list pair
-/// `e.g. cargo run --bin console_text_messenger -- -N1` will load wallet_config_node1.toml and node1_peers.json
-pub fn main() {
- let matches = App::new("Tari Console Text Message Application")
- .version("0.1")
- .arg(
- Arg::with_name("node-num")
- .long("node_num")
- .short("N")
- .help(
- "An integer indicating which Node number config to load from the Tari repo root (Node config is a \
- pair of files consisting of config + peers for that node)",
- )
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("config")
- .value_name("FILE")
- .long("config")
- .short("c")
- .help("The relative path of a wallet config.toml file")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("grpc-port")
- .long("grpc")
- .short("g")
- .help("The port the gRPC server will listen on")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("control-port")
- .long("control-port")
- .short("p")
- .help("The port the p2p stack will listen on")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("secret-key")
- .long("secret")
- .short("s")
- .help("This nodes communication secret key")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("data-path")
- .long("data-path")
- .short("d")
- .help("Path where this node's database files will be stored")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("peers")
- .value_name("FILE")
- .long("peers")
- .takes_value(true)
- .required(false),
- )
- .get_matches();
-
- let mut settings = Settings::default();
- let mut contacts = Peers { peers: Vec::new() };
- let mut database_path = "./data/text_message_service.sqlite3".to_string();
- // The node-num switch overrides the config and peers switch for quick testing from the tari repo root
- if matches.is_present("node-num") {
- let node_num = value_t!(matches, "node-num", u32).unwrap();
- let peer_path = format!("./applications/grpc_wallet/sample_config/node{}_peers.json", node_num);
- let config_path = format!(
- "./applications/grpc_wallet/sample_config/wallet_config_node{}.toml",
- node_num
- );
- let mut settings_file = config::Config::default();
- settings_file
- .merge(config::File::with_name(config_path.as_str()))
- .expect("Could not open specified config file");
- settings = settings_file.try_into().unwrap();
- let contents = fs::read_to_string(peer_path).expect("Could not open specified Peers json file");
- contacts = Peers::from_json(contents.as_str()).expect("Could not parse JSON from specified Peers json file");
- database_path = format!("./data/text_message_service_node{}.sqlite3", node_num).to_string();
- } else {
- if matches.is_present("config") {
- let mut settings_file = config::Config::default();
- settings_file
- .merge(config::File::with_name(matches.value_of("config").unwrap()))
- .expect("Could not open specified config file");
- settings = settings_file.try_into().unwrap();
- }
- if let Some(f) = matches.value_of("peers") {
- let contents = fs::read_to_string(f).expect("Could not open specified Peers json file");
- contacts =
- Peers::from_json(contents.as_str()).expect("Could not parse JSON from specified Peers json file");
- }
- }
- if let Some(_c) = matches.values_of("control-port") {
- if let Ok(v) = value_t!(matches, "control-port", u32) {
- settings.control_port = Some(v)
- }
- }
- if let Some(_c) = matches.values_of("grpc-port") {
- if let Ok(v) = value_t!(matches, "grpc-port", u32) {
- settings.grpc_port = Some(v);
- }
- }
- if let Some(c) = matches.value_of("secret-key") {
- settings.secret_key = Some(c.to_string())
- }
- if let Some(p) = matches.value_of("data-path") {
- settings.data_path = Some(p.to_string())
- }
-
- if settings.secret_key.is_none() ||
- settings.control_port.is_none() ||
- settings.grpc_port.is_none() ||
- settings.data_path.is_none() ||
- settings.screen_name.is_none()
- {
- error!(
- target: LOG_TARGET,
- "Control port, gRPC port, Data path, Screen name or Secret Key has not been provided via command line or \
- config file"
- );
- std::process::exit(1);
- }
-
- // Setup the local comms stack
- let listener_address: NetAddress = format!("0.0.0.0:{}", settings.control_port.unwrap()).parse().unwrap();
- let secret_key = CommsSecretKey::from_hex(settings.secret_key.unwrap().as_str()).unwrap();
- let public_key = CommsPublicKey::from_secret_key(&secret_key);
-
- // get and filter interfaces
- let interfaces: Vec = datalink::interfaces()
- .into_iter()
- .filter(|interface| {
- !interface.is_loopback() && interface.is_up() && interface.ips.iter().any(|addr| addr.is_ipv4())
- })
- .collect();
-
- // select first interface
- if interfaces.first().is_none() {
- error!(
- target: LOG_TARGET,
- "No available network interface with an Ipv4 Address."
- );
- std::process::exit(1);
- }
-
- // get network interface and retrieve ipv4 address
- let interface = interfaces.first().unwrap().clone();
- let local_ip = interface
- .ips
- .iter()
- .find(|addr| addr.is_ipv4())
- .unwrap()
- .ip()
- .to_string();
-
- let local_net_address = match format!("{}:{}", local_ip, settings.control_port.unwrap()).parse() {
- Ok(na) => na,
- Err(_) => {
- error!(target: LOG_TARGET, "Could not resolve local IP address");
- std::process::exit(1);
- },
- };
-
- info!(target: LOG_TARGET, "Local Net Address: {:?}", local_net_address);
-
- let config = WalletConfig {
- comms: CommsConfig {
- control_service: ControlServiceConfig {
- listener_address: listener_address.clone(),
- socks_proxy_address: None,
- requested_connection_timeout: Duration::from_millis(5000),
- },
- socks_proxy_address: None,
- host: "0.0.0.0".parse().unwrap(),
- public_key: public_key.clone(),
- secret_key: secret_key.clone(),
- public_address: local_net_address,
- datastore_path: settings.data_path.unwrap(),
- peer_database_name: public_key.to_hex(),
- },
- public_key: public_key.clone(),
- database_path,
- };
-
- let wallet = Wallet::new(config).unwrap();
-
- // Add any provided peers to Peer Manager and Text Message Service Contacts
- if !contacts.peers.is_empty() {
- for p in contacts.peers.iter() {
- let pk = CommsPublicKey::from_hex(p.pub_key.as_str()).expect("Error parsing pub key from Hex");
- if let Ok(na) = p.address.clone().parse::() {
- let peer = Peer::from_public_key_and_address(pk.clone(), na.clone()).unwrap();
- wallet.comms_services.peer_manager().add_peer(peer).unwrap();
- // If the contacts already exist we don't mind
- if let Err(e) = wallet.text_message_service.add_contact(Contact {
- screen_name: p.screen_name.clone(),
- pub_key: pk.clone(),
- address: na.clone(),
- }) {
- println!("Error adding config file contacts: {:?}", e);
- }
- }
- }
- }
-
- // Setup the logging to a file (screen_name.log), the file will appear in the root where the binary is run from
- let logfile = FileAppender::builder()
- .encoder(Box::new(PatternEncoder::new(
- "{d(%Y-%m-%d %H:%M:%S.%f)} [{M}#{L}] [{t}] {l:5} {m} (({T}:{I})){n}",
- )))
- .build(format!("{}.log", settings.screen_name.clone().unwrap()))
- .unwrap();
-
- let config = Config::builder()
- .appender(Appender::builder().build("logfile", Box::new(logfile)))
- .build(Root::builder().appender("logfile").build(LevelFilter::Debug))
- .unwrap();
-
- let _handle = log4rs::init_config(config).unwrap();
-
- let contacts = wallet
- .text_message_service
- .get_contacts()
- .expect("Could not read contacts");
-
- // Print out some help messages
- println!(
- "┌─────────────────────────────────────┐\n│Tari Console Barebones Text \
- Messenger│\n└─────────────────────────────────────┘"
- );
- // TODO Read this from the SQL database rather than the config file
- println!("This node's screen name: {}", settings.screen_name.unwrap().clone());
- for (i, c) in contacts.iter().enumerate() {
- println!("Contact {}: {}", i, c.screen_name.clone());
- }
- println!("Active Contact is 0: {}", contacts[0].screen_name.clone());
- println!("To change active contact to send to enter an integer and input_int%(# of contacts) will be made active");
-
- // Start a text input thread which sends inputted lines to the main thread via a channel
- let (tx, rx) = channel::unbounded();
- let (tx_sigint, rx_sigint) = channel::unbounded();
- thread::spawn(move || {
- let mut input = String::new();
- loop {
- if let Ok(_l) = io::stdin().read_line(&mut input) {
- tx.send(input.clone()).unwrap();
- input = "".to_string();
- }
- }
- });
-
- // setup a handler for ctrl-c
- ctrlc::set_handler(move || {
- println!("Received SIGINT");
- tx_sigint.send("shutdown").unwrap();
- })
- .expect("Error setting Ctrl-C handler");
-
- // keeps track of which received messages have been printed to the console
- let mut msg_index = 0;
- // keeps track of which contact is currently active for sending
- let mut active_contact: usize = 0;
-
- // Main Loop
- loop {
- let mut rx_messages: Vec = wallet
- .text_message_service
- .get_text_messages()
- .expect("Error retrieving text messages from TMS")
- .received_messages;
-
- rx_messages.sort();
-
- for i in msg_index..rx_messages.len() {
- let contact = contacts
- .iter()
- .find(|c| c.pub_key == rx_messages[i].source_pub_key)
- .expect("Message from unknown peer");
- println!(
- "{:?} - {:?}: {:?}",
- rx_messages[i].timestamp, contact.screen_name, rx_messages[i].message
- );
- msg_index = i + 1;
- }
-
- if let Ok(mut input) = rx.recv_timeout(Duration::from_millis(100)) {
- input.truncate(input.len() - 1);
-
- if let Ok(i) = input.clone().parse::() {
- active_contact = i % contacts.len();
- println!("Active Contact updated to: {}", contacts[active_contact].screen_name);
- }
-
- wallet
- .text_message_service
- .send_text_message(contacts[active_contact % contacts.len()].pub_key.clone(), input)
- .unwrap()
- }
-
- // check sigint to trigger shutdown
- if rx_sigint.recv_timeout(Duration::from_millis(10)).is_ok() {
- wallet.service_executor.shutdown().unwrap();
- wallet
- .service_executor
- .join_timeout(Duration::from_millis(3000))
- .unwrap();
- let comms = Arc::try_unwrap(wallet.comms_services)
- .map_err(|_| ServiceError::CommsServiceOwnershipError)
- .unwrap();
-
- comms.shutdown().unwrap();
- println!("Exiting");
- break;
- }
- }
-}
diff --git a/applications/grpc_wallet/Cargo.toml b/applications/grpc_wallet/Cargo.toml
deleted file mode 100644
index 3d50dbf57c..0000000000
--- a/applications/grpc_wallet/Cargo.toml
+++ /dev/null
@@ -1,44 +0,0 @@
-[package]
-name = "tari_grpc_wallet"
-version = "0.1.0"
-authors = ["Philip Robinson "]
-edition = "2018"
-
-[dependencies]
-tari_wallet = {path = "../../base_layer/wallet", version="^0.0"}
-tari_common = {path = "../../common", version= "^0.0"}
-tari_utilities = { path = "../../infrastructure/tari_util", version = "^0.0"}
-tari_comms = { path = "../../comms", version = "^0.0"}
-tari_p2p = {path = "../../base_layer/p2p", version = "^0.0"}
-tari_crypto = { path = "../../infrastructure/crypto"}
-chrono = { version = "0.4.6", features = ["serde"]}
-config = { version = "0.9.3" }
-crossbeam-channel = "0.3.8"
-bytes = "0.4"
-derive-error = "0.0.4"
-futures = "0.1"
-http = "0.1"
-log = { version = "0.4.0", features = ["std"] }
-prost = "0.5"
-tokio = "0.1"
-tower-request-modifier = { git = "https://github.com/tower-rs/tower-http" }
-tower-hyper = "0.1"
-hyper = "0.12"
-tower-grpc = { git = "https://github.com/tower-rs/tower-grpc.git", features = ["tower-hyper"] }
-tower-grpc-build = { git = "https://github.com/tower-rs/tower-grpc.git", features = ["tower-hyper"]}
-simple_logger = "1.2.0"
-clap = "2.33.0"
-serde = "1.0.90"
-serde_derive = "1.0.90"
-pnet = "0.22.0"
-
-
-[dev-dependencies]
-tari_crypto = { path = "../../infrastructure/crypto"}
-tower-util = "0.1"
-tempdir = "0.3.7"
-rand = "0.5"
-
-[build-dependencies]
-tower-grpc-build = { git = "https://github.com/tower-rs/tower-grpc.git", features = ["tower-hyper"] }
-
diff --git a/applications/grpc_wallet/proto/wallet_rpc.proto b/applications/grpc_wallet/proto/wallet_rpc.proto
deleted file mode 100644
index 8f9f621678..0000000000
--- a/applications/grpc_wallet/proto/wallet_rpc.proto
+++ /dev/null
@@ -1,85 +0,0 @@
-syntax = "proto3";
-
-package wallet_rpc;
-
-// Wallet gRPC service
-service WalletRpc {
- // Send a Tari Text Message
- rpc SendTextMessage(TextMessageToSend) returns (RpcResponse) {}
- // Request all messages
- // TODO implement pagination
- rpc GetTextMessages(VoidParams) returns (TextMessagesResponse) {}
- // Return messages that are from/to the Contact's pub_key
- rpc GetTextMessagesByContact(Contact) returns (TextMessagesResponse) {}
- // Get/Set for Screen Name
- rpc SetScreenName(ScreenName) returns (RpcResponse) {}
- rpc GetScreenName(VoidParams) returns (ScreenName) {}
- // CRUD for Contacts
- rpc AddContact(Contact) returns (RpcResponse) {}
- rpc RemoveContact(Contact) returns (RpcResponse) {}
- rpc GetContacts(VoidParams) returns (Contacts) {}
- //This method will update the screen_name of the contact with Pub-key contained in the argument Contact
- rpc UpdateContact(Contact) returns (RpcResponse) {}
- rpc GetPublicKey(VoidParams) returns (PublicKey) {}
-}
-
-// A generic RPC call response message to convey the result of the call
-message RpcResponse {
- bool success = 1;
- string message = 2;
-}
-
-// A Tari Text Message to be sent
-message TextMessageToSend {
- string dest_pub_key = 1;
- string message = 2;
-}
-
-// A Received Tari Text Message
-message ReceivedTextMessage {
- string id = 1;
- string source_pub_key = 2;
- string dest_pub_key = 3;
- string message = 4;
- string timestamp = 5;
-}
-
-// A Sent Tari Text Message
-message SentTextMessage {
- string id = 1;
- string source_pub_key = 2;
- string dest_pub_key = 3;
- string message = 4;
- string timestamp = 5;
- bool acknowledged = 6;
-}
-
-// An Empty message for RPC calls with no parameters
-message VoidParams{}
-
-// A collection of all messages
-message TextMessagesResponse {
- repeated ReceivedTextMessage received_messages = 1;
- repeated SentTextMessage sent_messages = 2;
-}
-
-// Current users screen name
-message ScreenName {
- string screen_name = 1;
-}
-
-// A contact
-message Contact {
- string screen_name = 1;
- string pub_key = 2;
- string address = 3; //IP address with port i.e. "127.0.0.1:11123"
-}
-
-// A list of contacts
-message Contacts {
- repeated Contact contacts = 1;
-}
-// Returns your node's communication public key
-message PublicKey {
- string pub_key = 1;
-}
\ No newline at end of file
diff --git a/applications/grpc_wallet/sample_config/node1_peers.json b/applications/grpc_wallet/sample_config/node1_peers.json
deleted file mode 100644
index 0b7813310d..0000000000
--- a/applications/grpc_wallet/sample_config/node1_peers.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "peers": [
- {
- "screen_name": "Philip",
- "pub_key": "daa67142abc315b1149b54b8f086eb16596b15ee0e70e37f0aa15294fdcbd65b",
- "address": "127.0.0.1:20000"
- },
- {
- "screen_name": "Cayle",
- "pub_key": "a091006a3c606b6dacc4d165688ef137becdcaeb00acbba61dbb15f6c2df1900",
- "address": "127.0.0.1:30000"
- }
- ]
-}
diff --git a/applications/grpc_wallet/sample_config/node2_peers.json b/applications/grpc_wallet/sample_config/node2_peers.json
deleted file mode 100644
index 98195602eb..0000000000
--- a/applications/grpc_wallet/sample_config/node2_peers.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "peers": [
- {
- "screen_name": "Jason",
- "pub_key": "e0482c31139733b954cc3e59dcfbb4a65dbec1d53c97d47b18c91f6abfd04209",
- "address": "127.0.0.1:10000"
- },
- {
- "screen_name": "Cayle",
- "pub_key": "a091006a3c606b6dacc4d165688ef137becdcaeb00acbba61dbb15f6c2df1900",
- "address": "127.0.0.1:30000"
- }
- ]
-}
diff --git a/applications/grpc_wallet/sample_config/node3_peers.json b/applications/grpc_wallet/sample_config/node3_peers.json
deleted file mode 100644
index e26e6ba1f3..0000000000
--- a/applications/grpc_wallet/sample_config/node3_peers.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "peers": [
- {
- "screen_name": "Jason",
- "pub_key": "e0482c31139733b954cc3e59dcfbb4a65dbec1d53c97d47b18c91f6abfd04209",
- "address": "127.0.0.1:10000"
- },
- {
- "screen_name": "Philip",
- "pub_key": "daa67142abc315b1149b54b8f086eb16596b15ee0e70e37f0aa15294fdcbd65b",
- "address": "127.0.0.1:20000"
- }
- ]
-}
diff --git a/applications/grpc_wallet/sample_config/wallet_config_node1.toml b/applications/grpc_wallet/sample_config/wallet_config_node1.toml
deleted file mode 100644
index 42b29f48e3..0000000000
--- a/applications/grpc_wallet/sample_config/wallet_config_node1.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-########################################################################################################################
-# #
-# Wallet Configuration Options #
-# #
-########################################################################################################################
-
-# TODO Finalize this config file
-
-control_port = 10000
-grpc_port = 10001
-secret_key = "bafd4efafac9310b85c7afdc68d0c874597102bc2e1c824caa19d8b263afff05"
-data_path = "./data"
-screen_name = "Jason"
-
-
-
-
diff --git a/applications/grpc_wallet/sample_config/wallet_config_node2.toml b/applications/grpc_wallet/sample_config/wallet_config_node2.toml
deleted file mode 100644
index 54cf4c3cb6..0000000000
--- a/applications/grpc_wallet/sample_config/wallet_config_node2.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-########################################################################################################################
-# #
-# Wallet Configuration Options #
-# #
-########################################################################################################################
-
-# TODO Finalize this config file
-
-control_port = 20000
-grpc_port = 10001
-secret_key = "7745546269487c282076ab3f54a3867c51cf71892f7f37d92323aa6f24dc8b02"
-data_path = "./data"
-screen_name = "Philip"
\ No newline at end of file
diff --git a/applications/grpc_wallet/sample_config/wallet_config_node3.toml b/applications/grpc_wallet/sample_config/wallet_config_node3.toml
deleted file mode 100644
index 78a862952f..0000000000
--- a/applications/grpc_wallet/sample_config/wallet_config_node3.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-########################################################################################################################
-# #
-# Wallet Configuration Options #
-# #
-########################################################################################################################
-
-# TODO Finalize this config file
-
-control_port = 30000
-grpc_port = 10001
-secret_key = "a6bbc534c6d390e9e8ab3626d01677a1195dd59e0594c13f786c29b9abb77102"
-data_path = "./data"
-screen_name = "Cayle"
\ No newline at end of file
diff --git a/applications/grpc_wallet/src/grpc_interface.rs b/applications/grpc_wallet/src/grpc_interface.rs
deleted file mode 100644
index d8f3afa6f2..0000000000
--- a/applications/grpc_wallet/src/grpc_interface.rs
+++ /dev/null
@@ -1,395 +0,0 @@
-// 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::grpc_interface::wallet_rpc::{
- server,
- Contact as ContactRpc,
- Contacts as ContactsRpc,
- PublicKey as PublicKeyRpc,
- ReceivedTextMessage as ReceivedTextMessageRpc,
- RpcResponse,
- ScreenName as ScreenNameRpc,
- SentTextMessage as SentTextMessageRpc,
- TextMessageToSend as TextMessageToSendRpc,
- TextMessagesResponse as TextMessagesResponseRpc,
- VoidParams,
-};
-
-use futures::future;
-use log::*;
-use std::{convert::From, sync::Arc};
-use tari_comms::{connection::NetAddress, peer_manager::Peer, types::CommsPublicKey};
-use tari_utilities::hex::Hex;
-use tari_wallet::{
- text_message_service::{Contact, ReceivedTextMessage, SentTextMessage, UpdateContact},
- Wallet,
-};
-use tower_grpc::{Request, Response};
-
-const LOG_TARGET: &str = "applications::grpc_wallet";
-
-pub mod wallet_rpc {
- include!(concat!(env!("OUT_DIR"), "/wallet_rpc.rs"));
-}
-
-impl From for ReceivedTextMessageRpc {
- fn from(m: ReceivedTextMessage) -> Self {
- ReceivedTextMessageRpc {
- id: m.id.to_hex(),
- source_pub_key: m.source_pub_key.to_hex(),
- dest_pub_key: m.dest_pub_key.to_hex(),
- message: m.message,
- timestamp: m.timestamp.to_string(),
- }
- }
-}
-
-impl From for SentTextMessageRpc {
- fn from(m: SentTextMessage) -> Self {
- SentTextMessageRpc {
- id: m.id.to_hex(),
- source_pub_key: m.source_pub_key.to_hex(),
- dest_pub_key: m.dest_pub_key.to_hex(),
- message: m.message,
- timestamp: m.timestamp.to_string(),
- acknowledged: m.acknowledged,
- }
- }
-}
-
-#[derive(Clone)]
-pub struct WalletRPC {
- pub wallet: Arc,
-}
-
-/// Implementation of the the gRPC service methods.
-impl server::WalletRpc for WalletRPC {
- type AddContactFuture = future::FutureResult, tower_grpc::Status>;
- type GetContactsFuture = future::FutureResult, tower_grpc::Status>;
- type GetPublicKeyFuture = future::FutureResult, tower_grpc::Status>;
- type GetScreenNameFuture = future::FutureResult, tower_grpc::Status>;
- type GetTextMessagesByContactFuture = future::FutureResult, tower_grpc::Status>;
- type GetTextMessagesFuture = future::FutureResult, tower_grpc::Status>;
- type RemoveContactFuture = future::FutureResult, tower_grpc::Status>;
- type SendTextMessageFuture = future::FutureResult, tower_grpc::Status>;
- type SetScreenNameFuture = future::FutureResult, tower_grpc::Status>;
- type UpdateContactFuture = future::FutureResult, tower_grpc::Status>;
-
- fn send_text_message(&mut self, request: Request) -> Self::SendTextMessageFuture {
- trace!(
- target: LOG_TARGET,
- "SendTextMessage gRPC Request received: {:?}",
- request,
- );
-
- let msg = request.into_inner();
-
- let response = match CommsPublicKey::from_hex(msg.dest_pub_key.as_str()) {
- Ok(pk) => match self.wallet.text_message_service.send_text_message(pk, msg.message) {
- Ok(()) => Response::new(RpcResponse {
- success: true,
- message: "Text Message Sent".to_string(),
- }),
- Err(e) => Response::new(RpcResponse {
- success: false,
- message: format!("Error sending text message: {:?}", e).to_string(),
- }),
- },
-
- Err(e) => Response::new(RpcResponse {
- success: false,
- message: format!("Error sending text message: {:?}", e).to_string(),
- }),
- };
-
- future::ok(response)
- }
-
- fn get_text_messages(&mut self, request: Request) -> Self::GetTextMessagesFuture {
- trace!(
- target: LOG_TARGET,
- "GetTextMessages gRPC Request received: {:?}",
- request
- );
-
- let response_body = match self.wallet.text_message_service.get_text_messages() {
- Ok(mut msgs) => TextMessagesResponseRpc {
- sent_messages: msgs.sent_messages.drain(..).map(|m| m.into()).collect(),
- received_messages: msgs.received_messages.drain(..).map(|m| m.into()).collect(),
- },
- _ => TextMessagesResponseRpc {
- sent_messages: Vec::new(),
- received_messages: Vec::new(),
- },
- };
- let response = Response::new(response_body);
-
- future::ok(response)
- }
-
- fn get_text_messages_by_contact(&mut self, request: Request) -> Self::GetTextMessagesFuture {
- trace!(
- target: LOG_TARGET,
- "GetTextMessages gRPC Request received: {:?}",
- request
- );
-
- let msg = request.into_inner();
-
- let pub_key = match CommsPublicKey::from_hex(msg.pub_key.as_str()) {
- Ok(pk) => pk,
- _ => {
- return future::ok(Response::new(TextMessagesResponseRpc {
- sent_messages: Vec::new(),
- received_messages: Vec::new(),
- }))
- },
- };
-
- let response_body = match self.wallet.text_message_service.get_text_messages_by_pub_key(pub_key) {
- Ok(mut msgs) => TextMessagesResponseRpc {
- sent_messages: msgs.sent_messages.drain(..).map(|m| m.into()).collect(),
- received_messages: msgs.received_messages.drain(..).map(|m| m.into()).collect(),
- },
- _ => TextMessagesResponseRpc {
- sent_messages: Vec::new(),
- received_messages: Vec::new(),
- },
- };
- let response = Response::new(response_body);
-
- future::ok(response)
- }
-
- fn set_screen_name(&mut self, request: Request) -> Self::SetScreenNameFuture {
- trace!(target: LOG_TARGET, "SetScreenName gRPC Request received: {:?}", request,);
-
- let msg = request.into_inner();
-
- let response = match self.wallet.text_message_service.set_screen_name(msg.screen_name) {
- Ok(()) => Response::new(RpcResponse {
- success: true,
- message: "Screen Name Set".to_string(),
- }),
- Err(e) => Response::new(RpcResponse {
- success: false,
- message: format!("Error setting screen name: {:?}", e).to_string(),
- }),
- };
-
- future::ok(response)
- }
-
- fn get_screen_name(&mut self, request: Request) -> Self::GetScreenNameFuture {
- trace!(target: LOG_TARGET, "GetScreenName gRPC Request received: {:?}", request,);
-
- let _msg = request.into_inner();
-
- let screen_name = self
- .wallet
- .text_message_service
- .get_screen_name()
- .unwrap_or_else(|_| Some("".to_string())) // Unwrap result
- .unwrap_or_else(|| "".to_string()); // Unwrap Option
-
- future::ok(Response::new(ScreenNameRpc { screen_name }))
- }
-
- fn get_public_key(&mut self, request: Request) -> Self::GetPublicKeyFuture {
- trace!(target: LOG_TARGET, "GetPublicKey gRPC Request received: {:?}", request,);
-
- let _msg = request.into_inner();
-
- let public_key = self.wallet.public_key.clone().to_hex();
-
- future::ok(Response::new(PublicKeyRpc { pub_key: public_key }))
- }
-
- fn add_contact(&mut self, request: Request) -> Self::AddContactFuture {
- trace!(target: LOG_TARGET, "AddContact gRPC Request received: {:?}", request,);
-
- let msg = request.into_inner();
-
- let screen_name = msg.screen_name.clone();
- let pub_key = match CommsPublicKey::from_hex(msg.pub_key.as_str()) {
- Ok(pk) => pk,
- _ => {
- return future::ok(Response::new(RpcResponse {
- success: false,
- message: "Failed to add contact, cannot serialize public key".to_string(),
- }))
- },
- };
-
- let net_address = match msg.address.clone().parse::() {
- Ok(n) => n,
- Err(e) => {
- return future::ok(Response::new(RpcResponse {
- success: false,
- message: format!("Failed to add contact, cannot parse net address: {:?}", e).to_string(),
- }))
- },
- };
-
- let peer = match Peer::from_public_key_and_address(pub_key.clone(), net_address.clone()) {
- Ok(p) => p,
- Err(e) => {
- return future::ok(Response::new(RpcResponse {
- success: false,
- message: format!("Failed to add contact, cannot create peer: {:?}", e).to_string(),
- }))
- },
- };
-
- match self.wallet.comms_services.peer_manager().add_peer(peer) {
- Err(e) => {
- return future::ok(Response::new(RpcResponse {
- success: false,
- message: format!("Failed to add contact, cannot add peer to Peer Manager: {:?}", e).to_string(),
- }))
- },
- _ => (),
- };
-
- match self.wallet.text_message_service.add_contact(Contact {
- screen_name,
- pub_key,
- address: net_address,
- }) {
- Ok(()) => (),
- Err(e) => {
- return future::ok(Response::new(RpcResponse {
- success: false,
- message: format!("Error adding contact: {:?}", e).to_string(),
- }))
- },
- };
-
- future::ok(Response::new(RpcResponse {
- success: true,
- message: "Successfully added contact".to_string(),
- }))
- }
-
- fn remove_contact(&mut self, request: Request) -> Self::RemoveContactFuture {
- trace!(target: LOG_TARGET, "RemoveContact gRPC Request received: {:?}", request,);
-
- let msg = request.into_inner();
-
- let net_address = match msg.address.clone().parse::() {
- Ok(n) => n,
- Err(e) => {
- return future::ok(Response::new(RpcResponse {
- success: false,
- message: format!("Failed to remove contact, cannot parse net address: {:?}", e).to_string(),
- }))
- },
- };
-
- let screen_name = msg.screen_name.clone();
-
- if let Ok(pk) = CommsPublicKey::from_hex(msg.pub_key.as_str()) {
- let response = match self.wallet.text_message_service.remove_contact(Contact {
- screen_name,
- pub_key: pk,
- address: net_address,
- }) {
- Ok(()) => Response::new(RpcResponse {
- success: true,
- message: "Successfully removed contact".to_string(),
- }),
- Err(e) => Response::new(RpcResponse {
- success: false,
- message: format!("Error removing contact: {:?}", e).to_string(),
- }),
- };
-
- return future::ok(response);
- } else {
- return future::ok(Response::new(RpcResponse {
- success: false,
- message: "Failed to remove contact, cannot serialize public key".to_string(),
- }));
- }
- }
-
- fn update_contact(&mut self, request: Request) -> Self::RemoveContactFuture {
- trace!(target: LOG_TARGET, "UpdateContact gRPC Request received: {:?}", request,);
-
- let msg = request.into_inner();
- let net_address = match msg.address.clone().parse::() {
- Ok(n) => n,
- Err(e) => {
- return future::ok(Response::new(RpcResponse {
- success: false,
- message: format!("Failed to update contact, cannot parse net address: {:?}", e).to_string(),
- }))
- },
- };
- let screen_name = msg.screen_name.clone();
- if let Ok(pk) = CommsPublicKey::from_hex(msg.pub_key.as_str()) {
- let response = match self.wallet.text_message_service.update_contact(pk, UpdateContact {
- screen_name: Some(screen_name),
- address: Some(net_address),
- }) {
- Ok(()) => Response::new(RpcResponse {
- success: true,
- message: "Successfully updated contact".to_string(),
- }),
- Err(e) => Response::new(RpcResponse {
- success: false,
- message: format!("Error updating contact: {:?}", e).to_string(),
- }),
- };
-
- return future::ok(response);
- } else {
- return future::ok(Response::new(RpcResponse {
- success: false,
- message: "Failed to update contact, cannot serialize public key".to_string(),
- }));
- }
- }
-
- fn get_contacts(&mut self, request: Request) -> Self::GetContactsFuture {
- trace!(target: LOG_TARGET, "GetContacts gRPC Request received: {:?}", request,);
-
- let mut contacts_resp: Vec = Vec::new();
-
- if let Ok(contacts) = self.wallet.text_message_service.get_contacts() {
- for c in contacts.iter() {
- let sn = c.screen_name.clone();
- let address = format!("{}", c.address.clone());
-
- contacts_resp.push(ContactRpc {
- screen_name: sn,
- pub_key: c.pub_key.to_hex(),
- address,
- });
- }
- }
-
- future::ok(Response::new(ContactsRpc {
- contacts: contacts_resp,
- }))
- }
-}
diff --git a/applications/grpc_wallet/src/main.rs b/applications/grpc_wallet/src/main.rs
deleted file mode 100644
index 6024917a5d..0000000000
--- a/applications/grpc_wallet/src/main.rs
+++ /dev/null
@@ -1,275 +0,0 @@
-// 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.
-#[macro_use]
-extern crate clap;
-
-use pnet::datalink::{self, NetworkInterface};
-
-use clap::{App, Arg};
-use log::*;
-use serde::{Deserialize, Serialize};
-use std::{fs, sync::Arc, time::Duration};
-use tari_comms::{
- connection::NetAddress,
- control_service::ControlServiceConfig,
- peer_manager::Peer,
- types::{CommsPublicKey, CommsSecretKey},
-};
-use tari_crypto::keys::PublicKey;
-use tari_grpc_wallet::wallet_server::WalletServer;
-use tari_p2p::initialization::CommsConfig;
-use tari_utilities::{hex::Hex, message_format::MessageFormat};
-use tari_wallet::{text_message_service::Contact, wallet::WalletConfig, Wallet};
-const LOG_TARGET: &str = "applications::grpc_wallet";
-
-#[derive(Debug, Default, Deserialize)]
-struct Settings {
- control_port: Option,
- grpc_port: Option,
- secret_key: Option,
- data_path: Option,
-}
-#[derive(Debug, Serialize, Deserialize)]
-struct ConfigPeer {
- screen_name: String,
- pub_key: String,
- address: String,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-struct Peers {
- peers: Vec,
-}
-
-/// Entry point into the gRPC server binary
-pub fn main() {
- let _ = simple_logger::init_with_level(log::Level::Info);
-
- let matches = App::new("Tari Wallet gRPC server")
- .version("0.1")
- .arg(
- Arg::with_name("node-num")
- .long("node_num")
- .short("N")
- .help(
- "An integer indicating which Node number config to load from the Tari repo root (Node config is a \
- pair of files consisting of config + peers for that node)",
- )
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("config")
- .value_name("FILE")
- .long("config")
- .short("c")
- .help("The relative path of a wallet config.toml file")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("grpc-port")
- .long("grpc")
- .short("g")
- .help("The port the gRPC server will listen on")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("control-port")
- .long("control-port")
- .short("p")
- .help("The port the p2p stack will listen on")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("secret-key")
- .long("secret")
- .short("s")
- .help("This nodes communication secret key")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("data-path")
- .long("data-path")
- .short("d")
- .help("Path where this node's database files will be stored")
- .takes_value(true)
- .required(false),
- )
- .arg(
- Arg::with_name("peers")
- .value_name("FILE")
- .long("peers")
- .takes_value(true)
- .required(false),
- )
- .get_matches();
-
- let mut settings = Settings::default();
- let mut contacts = Peers { peers: Vec::new() };
- let mut database_path = "./data/text_message_service.sqlite3".to_string();
- // The node-num switch overrides the config and peers switch for quick testing from the tari repo root
- if matches.is_present("node-num") {
- let node_num = value_t!(matches, "node-num", u32).unwrap();
- let peer_path = format!("./applications/grpc_wallet/sample_config/node{}_peers.json", node_num);
- let config_path = format!(
- "./applications/grpc_wallet/sample_config/wallet_config_node{}.toml",
- node_num
- );
- let mut settings_file = config::Config::default();
- settings_file
- .merge(config::File::with_name(config_path.as_str()))
- .expect("Could not open specified config file");
- settings = settings_file.try_into().unwrap();
- let contents = fs::read_to_string(peer_path).expect("Could not open specified Peers json file");
- contacts = Peers::from_json(contents.as_str()).expect("Could not parse JSON from specified Peers json file");
- database_path = format!("./data/text_message_service_node{}.sqlite3", node_num).to_string();
- } else {
- if matches.is_present("config") {
- let mut settings_file = config::Config::default();
- settings_file
- .merge(config::File::with_name(matches.value_of("config").unwrap()))
- .expect("Could not open specified config file");
- settings = settings_file.try_into().unwrap();
- }
- if let Some(f) = matches.value_of("peers") {
- let contents = fs::read_to_string(f).expect("Could not open specified Peers json file");
- contacts =
- Peers::from_json(contents.as_str()).expect("Could not parse JSON from specified Peers json file");
- }
- }
- if let Some(_c) = matches.values_of("control-port") {
- if let Ok(v) = value_t!(matches, "control-port", u32) {
- settings.control_port = Some(v)
- }
- }
- if let Some(_c) = matches.values_of("grpc-port") {
- if let Ok(v) = value_t!(matches, "grpc-port", u32) {
- settings.grpc_port = Some(v);
- }
- }
- if let Some(c) = matches.value_of("secret-key") {
- settings.secret_key = Some(c.to_string())
- }
- if let Some(p) = matches.value_of("data-path") {
- settings.data_path = Some(p.to_string())
- }
-
- if settings.secret_key.is_none() ||
- settings.control_port.is_none() ||
- settings.grpc_port.is_none() ||
- settings.data_path.is_none()
- {
- error!(
- target: LOG_TARGET,
- "Control port, gRPC port, Data path or Secret Key has not been provided via command line or config file"
- );
- std::process::exit(1);
- }
-
- // Setup the local comms stack
- let listener_address: NetAddress = format!("0.0.0.0:{}", settings.control_port.unwrap()).parse().unwrap();
- let secret_key = CommsSecretKey::from_hex(settings.secret_key.unwrap().as_str()).unwrap();
- let public_key = CommsPublicKey::from_secret_key(&secret_key);
-
- // get and filter interfaces
- let interfaces: Vec = datalink::interfaces()
- .into_iter()
- .filter(|interface| {
- !interface.is_loopback() && interface.is_up() && interface.ips.iter().any(|addr| addr.is_ipv4())
- })
- .collect();
-
- // select first interface
- if interfaces.first().is_none() {
- error!(
- target: LOG_TARGET,
- "No available network interface with an Ipv4 Address."
- );
- std::process::exit(1);
- }
-
- // get network interface and retrieve ipv4 address
- let interface = interfaces.first().unwrap().clone();
- let local_ip = interface
- .ips
- .iter()
- .find(|addr| addr.is_ipv4())
- .unwrap()
- .ip()
- .to_string();
-
- let local_net_address = match format!("{}:{}", local_ip, settings.control_port.unwrap()).parse() {
- Ok(na) => na,
- Err(_) => {
- error!(target: LOG_TARGET, "Could not resolve local IP address");
- std::process::exit(1);
- },
- };
-
- info!(target: LOG_TARGET, "Local Net Address: {:?}", local_net_address);
-
- let config = WalletConfig {
- comms: CommsConfig {
- control_service: ControlServiceConfig {
- listener_address: listener_address.clone(),
- socks_proxy_address: None,
- requested_connection_timeout: Duration::from_millis(5000),
- },
- socks_proxy_address: None,
- host: "0.0.0.0".parse().unwrap(),
- public_key: public_key.clone(),
- secret_key: secret_key.clone(),
- public_address: local_net_address,
- datastore_path: settings.data_path.unwrap(),
- peer_database_name: public_key.to_hex(),
- },
- public_key: public_key.clone(),
- database_path,
- };
-
- let wallet = Wallet::new(config).unwrap();
-
- // Add any provided peers to Peer Manager and Text Message Service Contacts
- if !contacts.peers.is_empty() {
- for p in contacts.peers.iter() {
- let pk = CommsPublicKey::from_hex(p.pub_key.as_str()).expect("Error parsing pub key from Hex");
- if let Ok(na) = p.address.clone().parse::() {
- let peer = Peer::from_public_key_and_address(pk.clone(), na.clone()).unwrap();
- wallet.comms_services.peer_manager().add_peer(peer).unwrap();
- if let Err(e) = wallet.text_message_service.add_contact(Contact {
- screen_name: p.screen_name.clone(),
- pub_key: pk.clone(),
- address: na.clone(),
- }) {
- info!("Error adding config file contacts: {:?}", e);
- }
- }
- }
- }
-
- let wallet_server = WalletServer::new(settings.grpc_port.unwrap(), Arc::new(wallet));
- let _res = wallet_server.start();
-}
diff --git a/applications/grpc_wallet/src/wallet_server.rs b/applications/grpc_wallet/src/wallet_server.rs
deleted file mode 100644
index 9266d8a0ec..0000000000
--- a/applications/grpc_wallet/src/wallet_server.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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::grpc_interface::{wallet_rpc::server, WalletRPC};
-use derive_error::Error;
-use futures::{future::Future, stream::Stream};
-use hyper::server::conn::Http;
-use log::*;
-use std::{net::AddrParseError, sync::Arc};
-use tari_utilities::message_format::MessageFormatError;
-use tari_wallet::Wallet;
-use tokio::net::TcpListener;
-use tower_hyper::Server;
-
-const LOG_TARGET: &str = "applications::grpc_wallet";
-
-#[derive(Debug, Error)]
-pub enum WalletServerError {
- AddrParseError(AddrParseError),
- IoError(std::io::Error),
- MessageFormatError(MessageFormatError),
-}
-
-/// Instance of the Wallet RPC Server with a reference to the Wallet API and the config
-pub struct WalletServer {
- // TODO some form of authentication
- port: u32,
- wallet: Arc,
-}
-
-impl WalletServer {
- pub fn new(port: u32, wallet: Arc) -> WalletServer {
- WalletServer { port, wallet }
- }
-
- pub fn start(self) -> Result<(), WalletServerError> {
- let new_service = server::WalletRpcServer::new(WalletRPC {
- wallet: self.wallet.clone(),
- });
-
- let mut server = Server::new(new_service);
-
- let http = Http::new().http2_only(true).clone();
- let addr = format!("127.0.0.1:{}", self.port);
- let bind = TcpListener::bind(&addr.clone().as_str().parse()?)?;
- let serve = bind
- .incoming()
- .for_each(move |sock| {
- if let Err(e) = sock.set_nodelay(true) {
- return Err(e);
- }
-
- let serve = server.serve_with(sock, http.clone());
- tokio::spawn(serve.map_err(|e| error!("Error starting Hyper service: {:?}", e)));
-
- Ok(())
- })
- .map_err(|e| error!("Error accepting request: {:?}", e));
- info!(target: LOG_TARGET, "Starting Wallet gRPC Server at {}", addr);
- tokio::run(serve);
- Ok(())
- }
-}
diff --git a/applications/grpc_wallet/tests/wallet_grpc_server/mod.rs b/applications/grpc_wallet/tests/wallet_grpc_server/mod.rs
deleted file mode 100644
index 5d9444883d..0000000000
--- a/applications/grpc_wallet/tests/wallet_grpc_server/mod.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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.
-pub mod wallet_grpc_server;
diff --git a/applications/grpc_wallet/tests/wallet_grpc_server/wallet_grpc_server.rs b/applications/grpc_wallet/tests/wallet_grpc_server/wallet_grpc_server.rs
deleted file mode 100644
index 8e4f49d268..0000000000
--- a/applications/grpc_wallet/tests/wallet_grpc_server/wallet_grpc_server.rs
+++ /dev/null
@@ -1,631 +0,0 @@
-// 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 crossbeam_channel::bounded;
-use futures::future::Future;
-use hyper::client::connect::{Destination, HttpConnector};
-use log::{Level, *};
-use rand::{distributions::Alphanumeric, rngs::OsRng, Rng};
-use std::{iter, path::PathBuf, sync::Arc, thread, time::Duration};
-use tari_comms::{
- connection::{net_address::NetAddressWithStats, NetAddress, NetAddressesWithStats},
- control_service::ControlServiceConfig,
- peer_manager::{peer::PeerFlags, NodeId, Peer},
- types::{CommsPublicKey, CommsSecretKey},
-};
-use tari_crypto::keys::{PublicKey, SecretKey};
-use tari_grpc_wallet::{
- grpc_interface::wallet_rpc::{
- client::WalletRpc,
- Contact as ContactRpc,
- RpcResponse,
- ScreenName as ScreenNameRpc,
- TextMessageToSend as TextMessageToSendRpc,
- VoidParams,
- },
- wallet_server::WalletServer,
-};
-use tari_p2p::initialization::CommsConfig;
-use tari_utilities::hex::Hex;
-use tari_wallet::{text_message_service::Contact, wallet::WalletConfig, Wallet};
-use tempdir::TempDir;
-use tower_grpc::Request;
-use tower_hyper::{client, util};
-use tower_util::MakeService;
-
-const LOG_TARGET: &str = "applications::grpc_wallet";
-const WALLET_GRPC_PORT: u32 = 26778;
-
-pub fn init() {
- let _ = simple_logger::init_with_level(Level::Debug);
-}
-
-fn get_path(name: Option<&str>) -> String {
- let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
- path.push("tests/data");
- path.push(name.unwrap_or(""));
- path.to_str().unwrap().to_string()
-}
-
-fn clean_up_sql_database(name: &str) {
- if std::fs::metadata(get_path(Some(name))).is_ok() {
- std::fs::remove_file(get_path(Some(name))).unwrap();
- }
-}
-
-fn init_sql_database(name: &str) {
- clean_up_sql_database(name);
- let path = get_path(None);
- let _ = std::fs::create_dir(&path).unwrap_or_default();
-}
-
-fn send_text_message_request(msg: TextMessageToSendRpc, desired_response: RpcResponse) {
- let (tx, rx) = bounded(1);
-
- let uri: http::Uri = format!("http://127.0.0.1:{}", WALLET_GRPC_PORT).parse().unwrap();
-
- let dst = Destination::try_from_uri(uri.clone()).unwrap();
- let connector = util::Connector::new(HttpConnector::new(1));
- let settings = client::Builder::new().http2_only(true).clone();
- let mut make_client = client::Connect::with_builder(connector, settings);
-
- let send_text_message = make_client
- .make_service(dst.clone())
- .map_err(|e| panic!("connect error: {:?}", e))
- .and_then(move |conn| {
- let conn = tower_request_modifier::Builder::new()
- .set_origin(uri.clone())
- .build(conn)
- .unwrap();
-
- // Wait until the client is ready...
- WalletRpc::new(conn).ready()
- })
- .and_then(|mut client| client.send_text_message(Request::new(msg)))
- .and_then(move |response| {
- info!(target: LOG_TARGET, "SendTextMessage Response received: {:?}", response);
- let inbound = response.into_inner();
-
- let _ = tx.send(inbound);
-
- Ok(())
- })
- .map_err(|e| {
- panic!("RPC Client error = {:?}", e);
- });
-
- tokio::run(send_text_message);
- thread::sleep(Duration::from_millis(100));
-
- let inbound = rx.recv().unwrap();
-
- println!("{:?}", inbound);
- println!("{:?}", desired_response);
- assert_eq!(inbound.success, desired_response.success);
- assert_eq!(inbound.message, desired_response.message);
-}
-
-fn get_text_messages_request(sent_messages: Vec, received_messages: Vec, contact: Option) {
- let mut recv_msg: Vec = Vec::new();
- let mut send_msg: Vec = Vec::new();
-
- // Check for new text messages up to 40 times with 100ms wait in between = 4000ms Timeout before moving on
- for _ in 0..40 {
- let move_contact = contact.clone();
- let (tx, rx) = bounded(2);
- let uri: http::Uri = format!("http://127.0.0.1:{}", WALLET_GRPC_PORT).parse().unwrap();
- let dst = Destination::try_from_uri(uri.clone()).unwrap();
- let connector = util::Connector::new(HttpConnector::new(1));
- let settings = client::Builder::new().http2_only(true).clone();
-
- let mut make_client = client::Connect::with_builder(connector, settings.clone());
- let get_text_messages = make_client
- .make_service(dst.clone())
- .map_err(|e| panic!("connect error: {:?}", e))
- .and_then(move |conn| {
- let conn = tower_request_modifier::Builder::new()
- .set_origin(uri.clone())
- .build(conn)
- .unwrap();
-
- // Wait until the client is ready...
- WalletRpc::new(conn).ready()
- })
- .and_then(|mut client| {
- if move_contact.is_some() {
- client.get_text_messages_by_contact(Request::new(move_contact.unwrap()))
- } else {
- client.get_text_messages(Request::new(VoidParams {}))
- }
- })
- .and_then(move |response| {
- info!(target: LOG_TARGET, "GetTextMessages Response received: {:?}", response);
- let inbound = response.into_inner();
-
- let recv_msg = inbound.received_messages.iter().map(|m| m.message.clone()).collect();
-
- let sent_msg = inbound.sent_messages.iter().map(|m| m.message.clone()).collect();
-
- let _ = tx.send(recv_msg);
- let _ = tx.send(sent_msg);
-
- Ok(())
- })
- .map_err(|e| {
- panic!("RPC Client error = {:?}", e);
- });
-
- tokio::run(get_text_messages);
-
- recv_msg = rx.recv().unwrap();
- send_msg = rx.recv().unwrap();
-
- if recv_msg.len() > 0 && send_msg.len() > 0 {
- break;
- }
- thread::sleep(Duration::from_millis(100));
- }
-
- assert_eq!(recv_msg.len(), received_messages.len());
- assert_eq!(send_msg.len(), sent_messages.len());
-
- recv_msg
- .iter()
- .for_each(|m| assert!(received_messages.iter().any(|m2| m == m2)));
- send_msg
- .iter()
- .for_each(|m| assert!(sent_messages.iter().any(|m2| m == m2)));
-}
-
-fn set_get_screen_name(name: String) {
- let requested_name = name.clone();
- let uri: http::Uri = format!("http://127.0.0.1:{}", WALLET_GRPC_PORT).parse().unwrap();
-
- let dst = Destination::try_from_uri(uri.clone()).unwrap();
- let connector = util::Connector::new(HttpConnector::new(1));
- let settings = client::Builder::new().http2_only(true).clone();
- let mut make_client = client::Connect::with_builder(connector, settings.clone());
-
- let set_screen_name = make_client
- .make_service(dst.clone())
- .map_err(|e| panic!("connect error: {:?}", e))
- .and_then(move |conn| {
- let conn = tower_request_modifier::Builder::new()
- .set_origin(uri.clone())
- .build(conn)
- .unwrap();
-
- // Wait until the client is ready...
- WalletRpc::new(conn).ready()
- })
- .and_then(|mut client| client.set_screen_name(Request::new(ScreenNameRpc { screen_name: name })))
- .and_then(move |response| {
- info!(target: LOG_TARGET, "SetScreenName Response received: {:?}", response);
- let inbound = response.into_inner();
- assert_eq!(inbound.success, true);
- Ok(())
- })
- .map_err(|e| {
- panic!("RPC Client error = {:?}", e);
- });
-
- tokio::run(set_screen_name);
- thread::sleep(Duration::from_millis(100));
- let (tx, rx) = bounded(1);
- let uri: http::Uri = format!("http://127.0.0.1:{}", WALLET_GRPC_PORT).parse().unwrap();
- let connector = util::Connector::new(HttpConnector::new(1));
- let settings = client::Builder::new().http2_only(true).clone();
- let mut make_client = client::Connect::with_builder(connector, settings);
-
- let get_screen_name = make_client
- .make_service(dst.clone())
- .map_err(|e| panic!("connect error: {:?}", e))
- .and_then(move |conn| {
- let conn = tower_request_modifier::Builder::new()
- .set_origin(uri.clone())
- .build(conn)
- .unwrap();
-
- // Wait until the client is ready...
- WalletRpc::new(conn).ready()
- })
- .and_then(|mut client| client.get_screen_name(Request::new(VoidParams {})))
- .and_then(move |response| {
- info!(target: LOG_TARGET, "GetScreenName Response received: {:?}", response);
-
- let _ = tx.send(response.into_inner());
-
- Ok(())
- })
- .map_err(|e| {
- panic!("RPC Client error = {:?}", e);
- });
-
- tokio::run(get_screen_name);
-
- let recv_screen_name = rx.recv().unwrap();
- assert_eq!(recv_screen_name.screen_name, requested_name);
-}
-
-fn get_pub_key(pub_key: String) {
- let (tx, rx) = bounded(1);
- let uri: http::Uri = format!("http://127.0.0.1:{}", WALLET_GRPC_PORT).parse().unwrap();
- let dst = Destination::try_from_uri(uri.clone()).unwrap();
- let connector = util::Connector::new(HttpConnector::new(1));
- let settings = client::Builder::new().http2_only(true).clone();
- let mut make_client = client::Connect::with_builder(connector, settings);
-
- let get_pub_key = make_client
- .make_service(dst.clone())
- .map_err(|e| panic!("connect error: {:?}", e))
- .and_then(move |conn| {
- let conn = tower_request_modifier::Builder::new()
- .set_origin(uri.clone())
- .build(conn)
- .unwrap();
-
- // Wait until the client is ready...
- WalletRpc::new(conn).ready()
- })
- .and_then(|mut client| client.get_public_key(Request::new(VoidParams {})))
- .and_then(move |response| {
- info!(target: LOG_TARGET, "GetPubKey Response received: {:?}", response);
-
- let _ = tx.send(response.into_inner());
-
- Ok(())
- })
- .map_err(|e| {
- panic!("RPC Client error = {:?}", e);
- });
-
- tokio::run(get_pub_key);
-
- let recv_pub_key = rx.recv().unwrap();
- assert_eq!(recv_pub_key.pub_key, pub_key);
-}
-
-fn add_contact(contact: ContactRpc) {
- let uri: http::Uri = format!("http://127.0.0.1:{}", WALLET_GRPC_PORT).parse().unwrap();
- let dst = Destination::try_from_uri(uri.clone()).unwrap();
- let connector = util::Connector::new(HttpConnector::new(1));
- let settings = client::Builder::new().http2_only(true).clone();
- let mut make_client = client::Connect::with_builder(connector, settings.clone());
-
- let add_contact = make_client
- .make_service(dst.clone())
- .map_err(|e| panic!("connect error: {:?}", e))
- .and_then(move |conn| {
- let conn = tower_request_modifier::Builder::new()
- .set_origin(uri.clone())
- .build(conn)
- .unwrap();
-
- // Wait until the client is ready...
- WalletRpc::new(conn).ready()
- })
- .and_then(|mut client| client.add_contact(Request::new(contact)))
- .and_then(move |response| {
- info!(target: LOG_TARGET, "AddContact Response received: {:?}", response);
- let inbound = response.into_inner();
- assert_eq!(inbound.success, true);
- Ok(())
- })
- .map_err(|e| {
- panic!("RPC Client error = {:?}", e);
- });
-
- tokio::run(add_contact);
-}
-
-fn contacts_crud() {
- let mut rng = rand::OsRng::new().unwrap();
-
- let mut contacts: Vec = Vec::new();
- let screen_names = vec!["Andy".to_string(), "Bob".to_string(), "Carol".to_string()];
- for i in 0..3 {
- let contact_secret_key = CommsSecretKey::random(&mut rng);
- let contact_public_key = CommsPublicKey::from_secret_key(&contact_secret_key);
- contacts.push(ContactRpc {
- screen_name: screen_names[i].clone(),
- pub_key: contact_public_key.to_hex(),
- address: "127.0.0.1:37522".to_string(),
- });
- }
-
- add_contact(contacts[0].clone());
- thread::sleep(Duration::from_millis(50));
-
- add_contact(contacts[1].clone());
- thread::sleep(Duration::from_millis(50));
-
- add_contact(contacts[2].clone());
- thread::sleep(Duration::from_millis(50));
-
- // Remove a contact
- let move_contact = contacts[1].clone();
- let uri: http::Uri = format!("http://127.0.0.1:{}", WALLET_GRPC_PORT).parse().unwrap();
- let dst = Destination::try_from_uri(uri.clone()).unwrap();
- let connector = util::Connector::new(HttpConnector::new(1));
- let settings = client::Builder::new().http2_only(true).clone();
- let mut make_client = client::Connect::with_builder(connector, settings);
-
- let remove_contact = make_client
- .make_service(dst.clone())
- .map_err(|e| panic!("connect error: {:?}", e))
- .and_then(move |conn| {
- let conn = tower_request_modifier::Builder::new()
- .set_origin(uri.clone())
- .build(conn)
- .unwrap();
-
- // Wait until the client is ready...
- WalletRpc::new(conn).ready()
- })
- .and_then(|mut client| client.remove_contact(Request::new(move_contact)))
- .and_then(move |response| {
- info!(target: LOG_TARGET, "RemoveContact Response received: {:?}", response);
-
- let inbound = response.into_inner();
- assert_eq!(inbound.success, true);
-
- Ok(())
- })
- .map_err(|e| {
- panic!("RPC Client error = {:?}", e);
- });
-
- tokio::run(remove_contact);
- thread::sleep(Duration::from_millis(100));
-
- // Update a contact
- let updated_contact = ContactRpc {
- screen_name: "Updated".to_string(),
- pub_key: contacts[0].pub_key.clone(),
- address: contacts[0].address.clone(),
- };
- let uri: http::Uri = format!("http://127.0.0.1:{}", WALLET_GRPC_PORT).parse().unwrap();
- let dst = Destination::try_from_uri(uri.clone()).unwrap();
- let connector = util::Connector::new(HttpConnector::new(1));
- let settings = client::Builder::new().http2_only(true).clone();
- let mut make_client = client::Connect::with_builder(connector, settings);
-
- let update_contact = make_client
- .make_service(dst.clone())
- .map_err(|e| panic!("connect error: {:?}", e))
- .and_then(move |conn| {
- let conn = tower_request_modifier::Builder::new()
- .set_origin(uri.clone())
- .build(conn)
- .unwrap();
-
- // Wait until the client is ready...
- WalletRpc::new(conn).ready()
- })
- .and_then(|mut client| client.update_contact(Request::new(updated_contact)))
- .and_then(move |response| {
- info!(target: LOG_TARGET, "UpdateContact Response received: {:?}", response);
-
- let inbound = response.into_inner();
- assert_eq!(inbound.success, true);
-
- Ok(())
- })
- .map_err(|e| {
- panic!("RPC Client error = {:?}", e);
- });
-
- tokio::run(update_contact);
- thread::sleep(Duration::from_millis(100));
-
- // check contacts
- let (tx, rx) = bounded(1);
- let uri: http::Uri = format!("http://127.0.0.1:{}", WALLET_GRPC_PORT).parse().unwrap();
- let dst = Destination::try_from_uri(uri.clone()).unwrap();
- let connector = util::Connector::new(HttpConnector::new(1));
- let settings = client::Builder::new().http2_only(true).clone();
- let mut make_client = client::Connect::with_builder(connector, settings);
-
- let get_contacts = make_client
- .make_service(dst.clone())
- .map_err(|e| panic!("connect error: {:?}", e))
- .and_then(move |conn| {
- let conn = tower_request_modifier::Builder::new()
- .set_origin(uri.clone())
- .build(conn)
- .unwrap();
-
- // Wait until the client is ready...
- WalletRpc::new(conn).ready()
- })
- .and_then(|mut client| client.get_contacts(Request::new(VoidParams {})))
- .and_then(move |response| {
- info!(target: LOG_TARGET, "RemoveContact Response received: {:?}", response);
-
- let _ = tx.send(response.into_inner());
-
- Ok(())
- })
- .map_err(|e| {
- panic!("RPC Client error = {:?}", e);
- });
-
- tokio::run(get_contacts);
-
- let recv_contacts = rx.recv().unwrap();
- assert_eq!(recv_contacts.contacts.len(), 3);
- assert_eq!(recv_contacts.contacts[1], ContactRpc {
- screen_name: "Updated".to_string(),
- pub_key: contacts[0].pub_key.clone(),
- address: contacts[0].address.clone(),
- });
- assert_eq!(recv_contacts.contacts[2], contacts[2]);
-}
-
-fn create_peer(public_key: CommsPublicKey, net_address: NetAddress) -> Peer {
- Peer::new(
- public_key.clone(),
- NodeId::from_key(&public_key).unwrap(),
- NetAddressesWithStats::new(vec![NetAddressWithStats::new(net_address.clone())]),
- PeerFlags::empty(),
- )
-}
-
-pub fn random_string(len: usize) -> String {
- let mut rng = OsRng::new().unwrap();
- iter::repeat(()).map(|_| rng.sample(Alphanumeric)).take(len).collect()
-}
-
-#[test]
-fn test_rpc_text_message_service() {
- init();
- let mut rng = rand::OsRng::new().unwrap();
- let listener_address1: NetAddress = "127.0.0.1:32775".parse().unwrap();
- let secret_key1 = CommsSecretKey::random(&mut rng);
- let public_key1 = CommsPublicKey::from_secret_key(&secret_key1);
-
- let listener_address2: NetAddress = "127.0.0.1:32776".parse().unwrap();
- let secret_key2 = CommsSecretKey::random(&mut rng);
- let public_key2 = CommsPublicKey::from_secret_key(&secret_key2);
-
- let db_name1 = "test_rpc_text_message_service1.sqlite3";
- let db_path1 = get_path(Some(db_name1));
- init_sql_database(db_name1);
-
- let db_name2 = "test_rpc_text_message_service2.sqlite3";
- let db_path2 = get_path(Some(db_name2));
- init_sql_database(db_name2);
-
- let config1 = WalletConfig {
- comms: CommsConfig {
- control_service: ControlServiceConfig {
- listener_address: listener_address1.clone(),
- socks_proxy_address: None,
- requested_connection_timeout: Duration::from_millis(5000),
- },
- socks_proxy_address: None,
- host: "127.0.0.1".parse().unwrap(),
- public_key: public_key1.clone(),
- secret_key: secret_key1,
- public_address: listener_address1.clone(),
- datastore_path: TempDir::new(random_string(8).as_str())
- .unwrap()
- .path()
- .to_str()
- .unwrap()
- .to_string(),
- peer_database_name: random_string(8),
- },
- public_key: public_key1.clone(),
- database_path: db_path1,
- };
-
- let config2 = WalletConfig {
- comms: CommsConfig {
- control_service: ControlServiceConfig {
- listener_address: listener_address2.clone(),
- socks_proxy_address: None,
- requested_connection_timeout: Duration::from_millis(5000),
- },
- socks_proxy_address: None,
- host: "127.0.0.1".parse().unwrap(),
- public_key: public_key2.clone(),
- secret_key: secret_key2,
- public_address: listener_address2.clone(),
- datastore_path: TempDir::new(random_string(8).as_str())
- .unwrap()
- .path()
- .to_str()
- .unwrap()
- .to_string(),
- peer_database_name: random_string(8),
- },
- public_key: public_key2.clone(),
- database_path: db_path2,
- };
-
- let wallet1 = Wallet::new(config1).unwrap();
-
- thread::spawn(move || {
- let wallet_server = WalletServer::new(WALLET_GRPC_PORT, Arc::new(wallet1));
- let _ = wallet_server.start().unwrap();
- });
-
- let screen_name = "Bob".to_string();
- let bob_contact = ContactRpc {
- screen_name: screen_name.clone(),
- pub_key: public_key2.to_hex(),
- address: format!("{}", listener_address2.clone()),
- };
-
- add_contact(bob_contact.clone());
-
- thread::sleep(Duration::from_millis(100));
-
- let wallet2 = Wallet::new(config2).unwrap();
-
- wallet2
- .comms_services
- .peer_manager()
- .add_peer(create_peer(public_key1.clone(), listener_address1.clone()))
- .unwrap();
- let alice_contact = Contact {
- screen_name: "Alice".to_string(),
- pub_key: public_key1.clone(),
- address: listener_address1.clone(),
- };
- wallet2.text_message_service.add_contact(alice_contact).unwrap();
-
- let test_msg = TextMessageToSendRpc {
- dest_pub_key: public_key2.clone().to_hex(),
- message: "Hey!".to_string(),
- };
-
- let test_msg2 = TextMessageToSendRpc {
- dest_pub_key: public_key2.clone().to_hex(),
- message: "Hoh!".to_string(),
- };
-
- let resp = RpcResponse {
- success: true,
- message: "Text Message Sent".to_string(),
- };
-
- send_text_message_request(test_msg, resp.clone());
- send_text_message_request(test_msg2, resp);
-
- wallet2
- .text_message_service
- .send_text_message(public_key1.clone(), "Here we go!".to_string())
- .unwrap();
-
- let sent_messages = vec!["Hey!".to_string(), "Hoh!".to_string()];
- let received_messages = vec!["Here we go!".to_string()];
-
- get_text_messages_request(sent_messages.clone(), received_messages.clone(), None);
- get_text_messages_request(sent_messages, received_messages, Some(bob_contact));
- set_get_screen_name("Alice".to_string());
- get_pub_key(public_key1.to_hex());
- contacts_crud();
- clean_up_sql_database(db_name1);
- clean_up_sql_database(db_name2);
-}
diff --git a/applications/tari_base_node/Cargo.toml b/applications/tari_base_node/Cargo.toml
new file mode 100644
index 0000000000..9cc19181d9
--- /dev/null
+++ b/applications/tari_base_node/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "tari_base_node"
+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.0.9"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+tari_common = {path = "../../common", version= "^0.0"}
+tari_comms = { version = "^0.0", path = "../../comms"}
+tari_comms_dht = { version = "^0.0", path = "../../comms/dht"}
+tari_core = {path = "../../base_layer/core", version= "^0.0"}
+tari_p2p = {path = "../../base_layer/p2p", version= "^0.0"}
+tari_service_framework = { version = "^0.0", path = "../../base_layer/service_framework"}
+tari_shutdown = { path = "../../infrastructure/shutdown", version = "^0.0" }
+tari_mmr = { path = "../../base_layer/mmr", version = "^0.0" }
+tari_wallet = { path = "../../base_layer/wallet", version = "^0.0" }
+tari_broadcast_channel = "^0.1"
+
+clap = "2.33.0"
+config = { version = "0.9.3" }
+dirs = "2.0.2"
+futures = { version = "^0.3.1", default-features = false, features = ["alloc"]}
+log = { version = "0.4.8", features = ["std"] }
+log4rs = { version = "0.8.3", features = ["toml_format"] }
+rand = "0.7.2"
+serde_json = "1.0"
+tokio = { version="0.2.10", features = ["signal"] }
+rustyline = "6.0"
+rustyline-derive = "0.3"
+strum = "0.17.1"
+strum_macros = "0.17.1"
diff --git a/applications/tari_base_node/README.md b/applications/tari_base_node/README.md
new file mode 100644
index 0000000000..6e50c5cd62
--- /dev/null
+++ b/applications/tari_base_node/README.md
@@ -0,0 +1,11 @@
+# Tari base node
+
+## Installation
+
+### Prerequisites
+
+### From source
+
+ cargo install tari_base_node
+
+## Configuration
diff --git a/applications/tari_base_node/src/builder.rs b/applications/tari_base_node/src/builder.rs
new file mode 100644
index 0000000000..24463f99f4
--- /dev/null
+++ b/applications/tari_base_node/src/builder.rs
@@ -0,0 +1,640 @@
+// 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::miner;
+use log::*;
+use rand::rngs::OsRng;
+use std::{
+ fs,
+ path::{Path, PathBuf},
+ sync::{atomic::AtomicBool, Arc},
+ time::Duration,
+};
+use tari_common::{CommsTransport, DatabaseType, GlobalConfig, Network, SocksAuthentication, TorControlAuthentication};
+use tari_comms::{
+ multiaddr::Multiaddr,
+ peer_manager::{NodeId, NodeIdentity, Peer, PeerFeatures, PeerFlags},
+ socks,
+ tor,
+ tor::TorIdentity,
+ utils::multiaddr::multiaddr_to_socketaddr,
+ CommsNode,
+};
+use tari_comms_dht::Dht;
+use tari_core::{
+ base_node::{
+ chain_metadata_service::{ChainMetadataHandle, ChainMetadataServiceInitializer},
+ service::{BaseNodeServiceConfig, BaseNodeServiceInitializer},
+ BaseNodeStateMachine,
+ BaseNodeStateMachineConfig,
+ LocalNodeCommsInterface,
+ OutboundNodeCommsInterface,
+ },
+ chain_storage::{
+ create_lmdb_database,
+ BlockchainBackend,
+ BlockchainDatabase,
+ LMDBDatabase,
+ MemoryDatabase,
+ Validators,
+ },
+ consensus::{ConsensusManager, ConsensusManagerBuilder, Network as NetworkType},
+ mempool::{Mempool, MempoolConfig, MempoolValidators},
+ mining::Miner,
+ proof_of_work::DiffAdjManager,
+ tari_utilities::{hex::Hex, message_format::MessageFormat},
+ transactions::{
+ crypto::keys::SecretKey as SK,
+ types::{CryptoFactories, HashDigest, PrivateKey, PublicKey},
+ },
+ validation::{
+ block_validators::{FullConsensusValidator, StatelessValidator},
+ transaction_validators::{FullTxValidator, TxInputAndMaturityValidator},
+ },
+};
+use tari_mmr::MmrCacheConfig;
+use tari_p2p::{
+ comms_connector::{pubsub_connector, PubsubDomainConnector, SubscriptionFactory},
+ initialization::{initialize_comms, CommsConfig},
+ services::{
+ comms_outbound::CommsOutboundServiceInitializer,
+ liveness::{LivenessConfig, LivenessInitializer},
+ },
+ transport::{TorConfig, TransportType},
+};
+use tari_service_framework::{handles::ServiceHandles, StackBuilder};
+use tari_wallet::{
+ output_manager_service::{
+ config::OutputManagerServiceConfig,
+ handle::OutputManagerHandle,
+ storage::sqlite_db::OutputManagerSqliteDatabase,
+ OutputManagerServiceInitializer,
+ },
+ storage::connection_manager::{run_migration_and_create_connection_pool, WalletConnection},
+ transaction_service::{
+ config::TransactionServiceConfig,
+ handle::TransactionServiceHandle,
+ storage::sqlite_db::TransactionServiceSqliteDatabase,
+ TransactionServiceInitializer,
+ },
+};
+use tokio::{runtime, stream::StreamExt};
+
+const LOG_TARGET: &str = "base_node::initialization";
+
+#[macro_export]
+macro_rules! using_backend {
+ ($self:expr, $i: ident, $cmd: expr) => {
+ match $self {
+ NodeContainer::LMDB($i) => $cmd,
+ NodeContainer::Memory($i) => $cmd,
+ }
+ };
+}
+
+/// The type of DB is configured dynamically in the config file, but the state machine struct has static dispatch;
+/// and so we have to use an enum wrapper to hold the various acceptable types.
+pub enum NodeContainer {
+ LMDB(BaseNodeContext>),
+ Memory(BaseNodeContext>),
+}
+
+impl NodeContainer {
+ /// Starts the node container. This entails starting the miner and wallet (if `mining_enabled` is true) and then
+ /// starting the base node state machine. This call consumes the NodeContainer instance.
+ pub async fn run(self, rt: runtime::Handle) {
+ using_backend!(self, ctx, NodeContainer::run_impl(ctx, rt).await)
+ }
+
+ pub fn interrupt_flag(&self) -> Arc {
+ using_backend!(self, ctx, ctx.node.get_interrupt_flag())
+ }
+
+ /// Returns a handle to the wallet output manager service. This function panics if it has not been registered
+ /// with the comms service
+ pub fn output_manager(&self) -> OutputManagerHandle {
+ using_backend!(self, ctx, ctx.output_manager())
+ }
+
+ /// Returns a handle to the local node communication service. This function panics if it has not been registered
+ /// with the comms service
+ pub fn local_node(&self) -> LocalNodeCommsInterface {
+ using_backend!(self, ctx, ctx.local_node())
+ }
+
+ /// Returns a handle to the wallet transaction service. This function panics if it has not been registered
+ /// with the comms service
+ pub fn wallet_transaction_service(&self) -> TransactionServiceHandle {
+ using_backend!(self, ctx, ctx.wallet_transaction_service())
+ }
+
+ async fn run_impl(mut ctx: BaseNodeContext, rt: runtime::Handle) {
+ info!(target: LOG_TARGET, "Tari base node has STARTED");
+ let mut wallet_output_handle = ctx.output_manager();
+ // Start wallet & miner
+ if let Some(mut miner) = ctx.miner.take() {
+ let mut rx = miner.get_utxo_receiver_channel();
+ rt.spawn(async move {
+ debug!(target: LOG_TARGET, "Mining wallet ready to receive coins.");
+ while let Some(utxo) = rx.next().await {
+ match wallet_output_handle.add_output(utxo).await {
+ Ok(_) => info!(
+ target: LOG_TARGET,
+ "🤑💰🤑 Newly mined coinbase output added to wallet 🤑💰🤑"
+ ),
+ Err(e) => warn!(target: LOG_TARGET, "Error adding output: {}", e),
+ }
+ }
+ });
+ rt.spawn(async move {
+ debug!(target: LOG_TARGET, "Starting miner");
+ miner.mine().await;
+ debug!(target: LOG_TARGET, "Miner has shutdown");
+ });
+ }
+ info!(
+ target: LOG_TARGET,
+ "Starting node - It will run until a fatal error occurs or until the stop flag is activated."
+ );
+ ctx.node.run().await;
+ info!(target: LOG_TARGET, "Initiating communications stack shutdown");
+ ctx.comms.shutdown().await
+ }
+}
+
+pub struct BaseNodeContext {
+ pub comms: CommsNode,
+ pub handles: Arc,
+ pub node: BaseNodeStateMachine,
+ pub miner: Option>,
+}
+
+impl BaseNodeContext {
+ pub fn output_manager(&self) -> OutputManagerHandle {
+ self.handles
+ .get_handle::()
+ .expect("Problem getting wallet output manager handle")
+ }
+
+ pub fn local_node(&self) -> LocalNodeCommsInterface {
+ self.handles
+ .get_handle::()
+ .expect("Could not get local comms interface handle")
+ }
+
+ pub fn wallet_transaction_service(&self) -> TransactionServiceHandle {
+ self.handles
+ .get_handle::()
+ .expect("Could not get wallet transaction service handle")
+ }
+}
+
+/// Tries to construct a node identity by loading the secret key and other metadata from disk and calculating the
+/// missing fields from that information.
+pub fn load_identity(path: &Path) -> Result {
+ if !path.exists() {
+ return Err(format!("Identity file, {}, does not exist.", path.to_str().unwrap()));
+ }
+
+ let id_str = std::fs::read_to_string(path).map_err(|e| {
+ format!(
+ "The node identity file, {}, could not be read. {}",
+ path.to_str().unwrap_or("?"),
+ e.to_string()
+ )
+ })?;
+ let id = NodeIdentity::from_json(&id_str).map_err(|e| {
+ format!(
+ "The node identity file, {}, has an error. {}",
+ path.to_str().unwrap_or("?"),
+ e.to_string()
+ )
+ })?;
+ info!(
+ "Node ID loaded with public key {} and Node id {}",
+ id.public_key().to_hex(),
+ id.node_id().to_hex()
+ );
+ Ok(id)
+}
+
+/// Create a new node id and save it to disk
+pub fn create_new_base_node_identity(public_addr: Multiaddr) -> Result {
+ let private_key = PrivateKey::random(&mut OsRng);
+ let features = PeerFeatures::COMMUNICATION_NODE;
+ NodeIdentity::new(private_key, public_addr, features)
+ .map_err(|e| format!("We were unable to construct a node identity. {}", e.to_string()))
+}
+
+pub fn load_from_json, T: MessageFormat>(path: P) -> Result {
+ if !path.as_ref().exists() {
+ return Err(format!(
+ "Identity file, {}, does not exist.",
+ path.as_ref().to_str().unwrap()
+ ));
+ }
+
+ let contents = fs::read_to_string(path).map_err(|err| err.to_string())?;
+ let object = T::from_json(&contents).map_err(|err| err.to_string())?;
+ Ok(object)
+}
+
+pub fn save_as_json, T: MessageFormat>(path: P, object: &T) -> Result<(), String> {
+ let json = object.to_json().unwrap();
+ if let Some(p) = path.as_ref().parent() {
+ if !p.exists() {
+ fs::create_dir_all(p).map_err(|e| format!("Could not save json to data folder. {}", e.to_string()))?;
+ }
+ }
+ fs::write(path.as_ref(), json.as_bytes()).map_err(|e| {
+ format!(
+ "Error writing json file, {}. {}",
+ path.as_ref().to_str().unwrap_or(""),
+ e.to_string()
+ )
+ })?;
+
+ Ok(())
+}
+
+pub async fn configure_and_initialize_node(
+ config: &GlobalConfig,
+ node_identity: NodeIdentity,
+) -> Result
+{
+ let network = match &config.network {
+ Network::MainNet => NetworkType::MainNet,
+ Network::Rincewind => NetworkType::Rincewind,
+ };
+ let id = Arc::new(node_identity);
+ let result = match &config.db_type {
+ DatabaseType::Memory => {
+ let backend = MemoryDatabase::::default();
+ let ctx = build_node_context(backend, network, id, config).await?;
+ NodeContainer::Memory(ctx)
+ },
+ DatabaseType::LMDB(p) => {
+ let backend = create_lmdb_database(&p, MmrCacheConfig::default()).map_err(|e| e.to_string())?;
+ let ctx = build_node_context(backend, network, id, config).await?;
+ NodeContainer::LMDB(ctx)
+ },
+ };
+ Ok(result)
+}
+
+async fn build_node_context(
+ backend: B,
+ network: NetworkType,
+ id: Arc,
+ config: &GlobalConfig,
+) -> Result, String>
+where
+ B: BlockchainBackend + 'static,
+{
+ let rules = ConsensusManagerBuilder::new(network).build();
+ let mut db = BlockchainDatabase::new(backend, rules.clone()).map_err(|e| e.to_string())?;
+ let factories = CryptoFactories::default();
+ let validators = Validators::new(
+ FullConsensusValidator::new(rules.clone(), factories.clone(), db.clone()),
+ StatelessValidator::new(&rules.consensus_constants()),
+ );
+ db.set_validators(validators);
+ let mempool_validator = MempoolValidators::new(
+ FullTxValidator::new(factories.clone(), db.clone()),
+ TxInputAndMaturityValidator::new(db.clone()),
+ );
+ let mempool = Mempool::new(db.clone(), MempoolConfig::default(), mempool_validator);
+ let diff_adj_manager = DiffAdjManager::new(db.clone(), &rules.consensus_constants()).map_err(|e| e.to_string())?;
+ rules.set_diff_manager(diff_adj_manager).map_err(|e| e.to_string())?;
+ create_peer_db_folder(&config.peer_db_path)?;
+ let handle = runtime::Handle::current().clone();
+ let (publisher, subscription_factory) = pubsub_connector(handle, 100);
+ let comms_config = CommsConfig {
+ node_identity: id.clone(),
+ transport_type: setup_transport_type(&config),
+ datastore_path: config.peer_db_path.clone(),
+ peer_database_name: "peers".to_string(),
+ max_concurrent_inbound_tasks: 100,
+ outbound_buffer_size: 100,
+ // TODO - make this configurable
+ dht: Default::default(),
+ };
+ let (comms, dht) = setup_comms_services(comms_config, publisher).await?;
+ // Save final node identity after comms has initialized. This is required because the public_address can be changed
+ // by comms during initialization when using tor.
+ save_as_json(&config.identity_file, &*comms.node_identity())
+ .map_err(|e| format!("Failed to save node identity: {}", e))?;
+ if let Some(hs) = comms.hidden_service() {
+ save_as_json(&config.tor_identity_file, &hs.get_tor_identity())
+ .map_err(|e| format!("Failed to save tor identity: {}", e))?;
+ }
+ add_peers_to_comms(&comms, assign_peers(&config.peer_seeds))?;
+ create_wallet_folder(&config.wallet_file)?;
+ let wallet_conn = run_migration_and_create_connection_pool(&config.wallet_file)
+ .map_err(|e| format!("Could not create wallet: {}", e))?;
+ debug!(target: LOG_TARGET, "Registering base node services");
+ let handles = register_services(
+ id.clone(),
+ &comms,
+ &dht,
+ db.clone(),
+ &wallet_conn,
+ subscription_factory,
+ mempool,
+ rules.clone(),
+ factories,
+ )
+ .await;
+ debug!(target: LOG_TARGET, "Base node service registration complete.");
+ let outbound_interface = handles
+ .get_handle::()
+ .expect("Problem getting node interface handle");
+ let chain_metadata_service = handles
+ .get_handle::()
+ .expect("Problem getting chain metadata interface handle");
+ debug!(target: LOG_TARGET, "Creating base node state machine.");
+ let node = BaseNodeStateMachine::new(
+ &db,
+ &outbound_interface,
+ runtime::Handle::current().clone(),
+ chain_metadata_service.get_event_stream(),
+ BaseNodeStateMachineConfig::default(),
+ );
+ let miner = if config.enable_mining {
+ debug!(target: LOG_TARGET, "Configuring solo miner");
+ let event_stream = node.get_state_change_event_stream();
+ Some(miner::build_miner(
+ &handles,
+ node.get_interrupt_flag(),
+ event_stream,
+ rules,
+ ))
+ } else {
+ debug!(
+ target: LOG_TARGET,
+ "Mining is disabled in the config file. This node will not mine for Tari"
+ );
+ None
+ };
+ Ok(BaseNodeContext {
+ comms,
+ handles,
+ node,
+ miner,
+ })
+}
+
+fn assign_peers(seeds: &[String]) -> Vec {
+ info!("Adding {} peers to the peer database", seeds.len());
+ let mut result = Vec::with_capacity(seeds.len());
+ for s in seeds {
+ let parts: Vec<&str> = s.split("::").map(|s| s.trim()).collect();
+ if parts.len() != 2 {
+ warn!(target: LOG_TARGET, "Invalid peer seed: {}", s);
+ continue;
+ }
+ let pub_key = match PublicKey::from_hex(parts[0]) {
+ Err(e) => {
+ warn!(
+ target: LOG_TARGET,
+ "{} is not a valid peer seed. The public key is incorrect. {}",
+ s,
+ e.to_string()
+ );
+ continue;
+ },
+ Ok(p) => p,
+ };
+ let addr = match parts[1].parse::() {
+ Err(e) => {
+ warn!(
+ target: LOG_TARGET,
+ "{} is not a valid peer seed. The address is incorrect. {}",
+ s,
+ e.to_string()
+ );
+ continue;
+ },
+ Ok(a) => a,
+ };
+ let node_id = match NodeId::from_key(&pub_key) {
+ Err(e) => {
+ warn!(
+ target: LOG_TARGET,
+ "{} is not a valid peer seed. A node id couldn't be derived from the public key. {}",
+ s,
+ e.to_string()
+ );
+ continue;
+ },
+ Ok(id) => id,
+ };
+ let peer = Peer::new(
+ pub_key,
+ node_id,
+ addr.into(),
+ PeerFlags::default(),
+ PeerFeatures::COMMUNICATION_NODE,
+ );
+ result.push(peer);
+ }
+ result
+}
+
+fn setup_transport_type(config: &GlobalConfig) -> TransportType {
+ match config.comms_transport.clone() {
+ CommsTransport::Tcp { listener_address } => TransportType::Tcp { listener_address },
+ CommsTransport::TorHiddenService {
+ control_server_address,
+ forward_address,
+ auth,
+ onion_port,
+ } => {
+ let tor_identity_path = Path::new(&config.tor_identity_file);
+ let identity = if tor_identity_path.exists() {
+ // If this fails, we can just use another address
+ load_from_json::<_, TorIdentity>(&tor_identity_path).ok()
+ } else {
+ None
+ };
+ info!(
+ target: LOG_TARGET,
+ "Tor identity at path '{}' {:?}",
+ tor_identity_path.to_string_lossy(),
+ identity
+ .as_ref()
+ .map(|ident| format!("loaded for address '{}.onion'", ident.service_id))
+ .or_else(|| Some("not found".to_string()))
+ .unwrap()
+ );
+
+ let forward_addr = multiaddr_to_socketaddr(&forward_address).expect("Invalid tor forward address");
+ TransportType::Tor(TorConfig {
+ control_server_addr: control_server_address,
+ control_server_auth: {
+ match auth {
+ TorControlAuthentication::None => tor::Authentication::None,
+ TorControlAuthentication::Password(password) => {
+ tor::Authentication::HashedPassword(password.clone())
+ },
+ }
+ },
+ identity: identity.map(Box::new),
+ port_mapping: (onion_port, forward_addr).into(),
+ // TODO: make configurable
+ socks_auth: socks::Authentication::None,
+ })
+ },
+ CommsTransport::Socks5 {
+ proxy_address,
+ listener_address,
+ auth,
+ } => TransportType::Socks {
+ proxy_address,
+ listener_address,
+ authentication: {
+ match auth {
+ SocksAuthentication::None => socks::Authentication::None,
+ SocksAuthentication::UsernamePassword(username, password) => {
+ socks::Authentication::Password(username, password)
+ },
+ }
+ },
+ },
+ }
+}
+
+fn create_wallet_folder(wallet_file: &str) -> Result<(), String> {
+ // sql lite for wallet, create folders for sql lite
+ let mut wallet_db_folder = PathBuf::from(wallet_file);
+ wallet_db_folder.set_extension("dat");
+ let wallet_path = PathBuf::from(wallet_db_folder.parent().expect("unable to get wallet db path"));
+ match fs::create_dir_all(wallet_path) {
+ Ok(_) => {
+ info!(
+ target: LOG_TARGET,
+ "Wallet directory has been created in {}", wallet_file
+ );
+ Ok(())
+ },
+ Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
+ info!(target: LOG_TARGET, "Wallet directory already exists in {}", wallet_file);
+ Ok(())
+ },
+ Err(e) => Err(format!("Could not create wallet directory: {}", e)),
+ }
+}
+
+fn create_peer_db_folder(peer_db_path: &str) -> Result<(), String> {
+ match fs::create_dir_all(peer_db_path) {
+ Ok(_) => {
+ info!(
+ target: LOG_TARGET,
+ "Peer database directory has been created in {}", peer_db_path
+ );
+ Ok(())
+ },
+ Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
+ info!(target: LOG_TARGET, "Peer database already exists in {}", peer_db_path);
+ Ok(())
+ },
+ Err(e) => Err(format!("could not create peer db path: {}", e)),
+ }
+}
+
+async fn setup_comms_services(
+ config: CommsConfig,
+ publisher: PubsubDomainConnector,
+) -> Result<(CommsNode, Dht), String>
+{
+ initialize_comms(config, publisher)
+ .await
+ .map_err(|e| format!("Could not create comms layer: {}", e))
+}
+
+fn add_peers_to_comms(comms: &CommsNode, peers: Vec) -> Result<(), String> {
+ for p in peers {
+ let peer_desc = p.to_string();
+ info!(target: LOG_TARGET, "Adding seed peer [{}]", peer_desc);
+ comms
+ .peer_manager()
+ .add_peer(p)
+ .map_err(|e| format!("Could not add peer {} to comms layer: {}", peer_desc, e))?;
+ }
+ Ok(())
+}
+
+async fn register_services(
+ id: Arc,
+ comms: &CommsNode,
+ dht: &Dht,
+ db: BlockchainDatabase,
+ wallet_conn: &WalletConnection,
+ subscription_factory: SubscriptionFactory,
+ mempool: Mempool,
+ consensus_manager: ConsensusManager,
+ factories: CryptoFactories,
+) -> Arc
+where
+ B: BlockchainBackend + 'static,
+{
+ let node_config = BaseNodeServiceConfig::default(); // TODO - make this configurable
+ let subscription_factory = Arc::new(subscription_factory);
+ let handle = runtime::Handle::current().clone();
+ StackBuilder::new(handle, comms.shutdown_signal())
+ .add_initializer(CommsOutboundServiceInitializer::new(dht.outbound_requester()))
+ .add_initializer(BaseNodeServiceInitializer::new(
+ subscription_factory.clone(),
+ db,
+ mempool,
+ consensus_manager,
+ node_config,
+ ))
+ .add_initializer(OutputManagerServiceInitializer::new(
+ OutputManagerServiceConfig::default(),
+ subscription_factory.clone(),
+ OutputManagerSqliteDatabase::new(wallet_conn.clone()),
+ factories.clone(),
+ ))
+ .add_initializer(TransactionServiceInitializer::new(
+ TransactionServiceConfig::default(),
+ subscription_factory.clone(),
+ comms.subscribe_messaging_events(),
+ TransactionServiceSqliteDatabase::new(wallet_conn.clone()),
+ id,
+ factories,
+ ))
+ .add_initializer(LivenessInitializer::new(
+ LivenessConfig {
+ auto_ping_interval: Some(Duration::from_secs(5)),
+ enable_auto_join: true,
+ enable_auto_stored_message_request: true,
+ refresh_neighbours_interval: Duration::from_secs(3 * 60),
+ },
+ subscription_factory,
+ dht.dht_requester(),
+ ))
+ .add_initializer(ChainMetadataServiceInitializer)
+ .finish()
+ .await
+ .expect("Service initialization failed")
+}
diff --git a/applications/tari_base_node/src/cli.rs b/applications/tari_base_node/src/cli.rs
new file mode 100644
index 0000000000..824d10dbe9
--- /dev/null
+++ b/applications/tari_base_node/src/cli.rs
@@ -0,0 +1,60 @@
+// 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::consts;
+use clap::clap_app;
+use tari_common::{bootstrap_config_from_cli, ConfigBootstrap};
+
+/// Prints a pretty banner on the console
+pub fn print_banner() {
+ println!(
+ "\n$ Tari Base Node\n$ Copyright 2019-2020. {}\n$ Version {}\n\nPress Ctrl-C to quit..",
+ consts::AUTHOR,
+ consts::VERSION
+ );
+}
+
+/// Parsed command-line arguments
+pub struct Arguments {
+ pub bootstrap: ConfigBootstrap,
+ pub create_id: bool,
+}
+
+/// Parse the command-line args and populate the minimal bootstrap config object
+pub fn parse_cli_args() -> Arguments {
+ let matches = clap_app!(myapp =>
+ (version: consts::VERSION)
+ (author: consts::AUTHOR)
+ (about: "The reference Tari cryptocurrency base node implementation")
+ (@arg config: -c --config +takes_value "A path to the configuration file to use (config.toml)")
+ (@arg log_config: -l --log_config +takes_value "A path to the logfile configuration (log4rs.yml))")
+ (@arg init: --init "Create a default configuration file if it doesn't exist")
+ (@arg create_id: --create_id "Create and save new node identity if one doesn't exist ")
+ )
+ .get_matches();
+
+ let bootstrap = bootstrap_config_from_cli(&matches);
+ let create_id = matches.is_present("create_id");
+
+ Arguments { bootstrap, create_id }
+}
diff --git a/applications/tari_base_node/src/consts.rs b/applications/tari_base_node/src/consts.rs
new file mode 100644
index 0000000000..780e23b3ca
--- /dev/null
+++ b/applications/tari_base_node/src/consts.rs
@@ -0,0 +1,25 @@
+// 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.
+//
+
+pub const VERSION: &str = "0.0.5";
+pub const AUTHOR: &str = "The Tari Community";
diff --git a/applications/tari_base_node/src/main.rs b/applications/tari_base_node/src/main.rs
new file mode 100644
index 0000000000..d4027d60b6
--- /dev/null
+++ b/applications/tari_base_node/src/main.rs
@@ -0,0 +1,223 @@
+// 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.
+//
+
+/// Utilities and helpers for building the base node instance
+mod builder;
+/// The command line interface definition and configuration
+mod cli;
+/// Application-specific constants
+mod consts;
+/// Miner lib Todo hide behind feature flag
+mod miner;
+/// Parser module used to control user commands
+mod parser;
+
+use crate::builder::{create_new_base_node_identity, load_identity};
+use log::*;
+use parser::Parser;
+use rustyline::{config::OutputStreamType, error::ReadlineError, CompletionType, Config, EditMode, Editor};
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+};
+use tari_common::{load_configuration, GlobalConfig};
+use tokio::runtime::Runtime;
+
+pub const LOG_TARGET: &str = "base_node::app";
+
+enum ExitCodes {
+ ConfigError = 101,
+ UnknownError = 102,
+}
+
+fn main() {
+ cli::print_banner();
+ match main_inner() {
+ Ok(_) => std::process::exit(0),
+ Err(exit_code) => std::process::exit(exit_code as i32),
+ }
+}
+
+fn main_inner() -> Result<(), ExitCodes> {
+ // Create the tari data directory
+ if let Err(e) = tari_common::dir_utils::create_data_directory() {
+ println!(
+ "We couldn't create a default Tari data directory and have to quit now. This makes us sad :(\n {}",
+ e.to_string()
+ );
+ return Err(ExitCodes::ConfigError);
+ }
+
+ // Parse and validate command-line arguments
+ let arguments = cli::parse_cli_args();
+
+ // Initialise the logger
+ if !tari_common::initialize_logging(&arguments.bootstrap.log_config) {
+ return Err(ExitCodes::ConfigError);
+ }
+
+ // Load and apply configuration file
+ let cfg = match load_configuration(&arguments.bootstrap) {
+ Ok(cfg) => cfg,
+ Err(s) => {
+ error!(target: LOG_TARGET, "{}", s);
+ return Err(ExitCodes::ConfigError);
+ },
+ };
+
+ // Populate the configuration struct
+ let node_config = match GlobalConfig::convert_from(cfg) {
+ Ok(c) => c,
+ Err(e) => {
+ error!(target: LOG_TARGET, "The configuration file has an error. {}", e);
+ return Err(ExitCodes::ConfigError);
+ },
+ };
+
+ trace!(target: LOG_TARGET, "Configuration file: {:?}", node_config);
+
+ // Load or create the Node identity
+ let node_identity = match load_identity(&node_config.identity_file) {
+ Ok(id) => id,
+ Err(e) => {
+ if !arguments.create_id {
+ error!(
+ target: LOG_TARGET,
+ "Node identity information not found. {}. You can update the configuration file to point to a \
+ valid node identity file, or re-run the node with the --create_id flag to create a new identity.",
+ e
+ );
+ return Err(ExitCodes::ConfigError);
+ }
+ debug!(target: LOG_TARGET, "Node id not found. {}. Creating new ID", e);
+ match create_new_base_node_identity(node_config.public_address.clone()) {
+ Ok(id) => {
+ info!(
+ target: LOG_TARGET,
+ "New node identity [{}] with public key {} has been created.",
+ id.node_id(),
+ id.public_key()
+ );
+ id
+ },
+ Err(e) => {
+ error!(target: LOG_TARGET, "Could not create new node id. {}.", e);
+ return Err(ExitCodes::ConfigError);
+ },
+ }
+ },
+ };
+
+ // Set up the Tokio runtime
+ let mut rt = match setup_runtime(&node_config) {
+ Ok(rt) => rt,
+ Err(s) => {
+ error!(target: LOG_TARGET, "{}", s);
+ return Err(ExitCodes::UnknownError);
+ },
+ };
+
+ // Build, node, build!
+ let ctx = rt.block_on(async {
+ builder::configure_and_initialize_node(&node_config, node_identity)
+ .await
+ .map_err(|err| {
+ error!(target: LOG_TARGET, "{}", err);
+ ExitCodes::UnknownError
+ })
+ })?;
+ // Run, node, run!
+ let parser = Parser::new(rt.handle().clone(), &ctx);
+ let flag = ctx.interrupt_flag();
+ let base_node_handle = rt.spawn(ctx.run(rt.handle().clone()));
+ info!(
+ target: LOG_TARGET,
+ "Node has been successfully configured and initialized. Starting CLI loop."
+ );
+ cli_loop(parser, flag);
+ match rt.block_on(base_node_handle) {
+ Ok(_) => info!(target: LOG_TARGET, "Node shutdown successfully."),
+ Err(e) => error!(target: LOG_TARGET, "Node has crashed: {}", e),
+ }
+ println!("Goodbye!");
+ Ok(())
+}
+
+fn setup_runtime(config: &GlobalConfig) -> Result {
+ let num_core_threads = config.core_threads;
+ let num_blocking_threads = config.blocking_threads;
+
+ debug!(
+ target: LOG_TARGET,
+ "Configuring the node to run on {} core threads and {} blocking worker threads.",
+ num_core_threads,
+ num_blocking_threads
+ );
+ tokio::runtime::Builder::new()
+ .threaded_scheduler()
+ .enable_all()
+ .max_threads(num_core_threads + num_blocking_threads)
+ .core_threads(num_core_threads)
+ .build()
+ .map_err(|e| format!("There was an error while building the node runtime. {}", e.to_string()))
+}
+
+fn cli_loop(parser: Parser, shutdown_flag: Arc) {
+ let cli_config = Config::builder()
+ .history_ignore_space(true)
+ .completion_type(CompletionType::List)
+ .edit_mode(EditMode::Emacs)
+ .output_stream(OutputStreamType::Stdout)
+ .build();
+ let mut rustyline = Editor::with_config(cli_config);
+ rustyline.set_helper(Some(parser));
+ loop {
+ let readline = rustyline.readline(">> ");
+ match readline {
+ Ok(line) => {
+ rustyline.add_history_entry(line.as_str());
+ if let Some(p) = rustyline.helper_mut().as_deref_mut() {
+ p.handle_command(&line)
+ }
+ },
+ Err(ReadlineError::Interrupted) => {
+ // shutdown section. Will shutdown all interfaces when ctrl-c was pressed
+ println!("CTRL-C received");
+ println!("Shutting down");
+ info!(
+ target: LOG_TARGET,
+ "Termination signal received from user. Shutting node down."
+ );
+ shutdown_flag.store(true, Ordering::SeqCst);
+ break;
+ },
+ Err(err) => {
+ println!("Error: {:?}", err);
+ break;
+ },
+ }
+ if shutdown_flag.load(Ordering::Relaxed) {
+ break;
+ };
+ }
+}
diff --git a/applications/tari_base_node/src/miner.rs b/applications/tari_base_node/src/miner.rs
new file mode 100644
index 0000000000..a7980c174f
--- /dev/null
+++ b/applications/tari_base_node/src/miner.rs
@@ -0,0 +1,47 @@
+// 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 core::sync::atomic::AtomicBool;
+use std::sync::Arc;
+use tari_broadcast_channel::Subscriber;
+use tari_core::{
+ base_node::{states::BaseNodeState, LocalNodeCommsInterface},
+ chain_storage::BlockchainBackend,
+ consensus::ConsensusManager,
+ mining::Miner,
+};
+use tari_service_framework::handles::ServiceHandles;
+
+pub fn build_miner>(
+ handles: H,
+ stop_flag: Arc,
+ event_stream: Subscriber,
+ consensus_manager: ConsensusManager,
+) -> Miner
+{
+ let handles = handles.as_ref();
+ let node_local_interface = handles.get_handle::().unwrap();
+ let mut miner = Miner::new(stop_flag, consensus_manager, &node_local_interface);
+ miner.subscribe_to_state_change(event_stream);
+ miner
+}
diff --git a/applications/tari_base_node/src/parser.rs b/applications/tari_base_node/src/parser.rs
new file mode 100644
index 0000000000..8e9e4b31f5
--- /dev/null
+++ b/applications/tari_base_node/src/parser.rs
@@ -0,0 +1,268 @@
+// 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 super::LOG_TARGET;
+use crate::builder::NodeContainer;
+use log::*;
+use rustyline::{
+ completion::Completer,
+ error::ReadlineError,
+ hint::{Hinter, HistoryHinter},
+ line_buffer::LineBuffer,
+ Context,
+};
+use rustyline_derive::{Helper, Highlighter, Validator};
+use std::{
+ str::FromStr,
+ string::ToString,
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+ },
+};
+use strum::IntoEnumIterator;
+use strum_macros::{Display, EnumIter, EnumString};
+use tari_comms::types::CommsPublicKey;
+use tari_core::{
+ base_node::LocalNodeCommsInterface,
+ tari_utilities::hex::Hex,
+ transactions::tari_amount::{uT, MicroTari},
+};
+use tari_wallet::{
+ output_manager_service::handle::OutputManagerHandle,
+ transaction_service::handle::TransactionServiceHandle,
+};
+use tokio::runtime;
+
+/// Enum representing commands used by the basenode
+#[derive(Clone, PartialEq, Debug, Display, EnumIter, EnumString)]
+#[strum(serialize_all = "snake_case")]
+pub enum BaseNodeCommand {
+ Help,
+ GetBalance,
+ SendTari,
+ GetChainMetadata,
+ Quit,
+ Exit,
+}
+
+/// This is used to parse commands from the user and execute them
+#[derive(Helper, Validator, Highlighter)]
+pub struct Parser {
+ executor: runtime::Handle,
+ shutdown_flag: Arc,
+ commands: Vec,
+ hinter: HistoryHinter,
+ wallet_output_service: OutputManagerHandle,
+ node_service: LocalNodeCommsInterface,
+ wallet_transaction_service: TransactionServiceHandle,
+}
+
+// This will go through all instructions and look for potential matches
+impl Completer for Parser {
+ type Candidate = String;
+
+ fn complete(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Result<(usize, Vec), ReadlineError> {
+ let mut completions: Vec = Vec::new();
+ for command in &self.commands {
+ if command.starts_with(line) {
+ completions.push(command.to_string());
+ }
+ }
+
+ Ok((pos, completions))
+ }
+
+ fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
+ line.update(elected, start);
+ }
+}
+
+// This allows us to make hints based on historic inputs
+impl Hinter for Parser {
+ fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option {
+ self.hinter.hint(line, pos, ctx)
+ }
+}
+
+impl Parser {
+ /// creates a new parser struct
+ pub fn new(executor: runtime::Handle, ctx: &NodeContainer) -> Self {
+ Parser {
+ executor,
+ shutdown_flag: ctx.interrupt_flag(),
+ commands: BaseNodeCommand::iter().map(|x| x.to_string()).collect(),
+ hinter: HistoryHinter {},
+ wallet_output_service: ctx.output_manager(),
+ node_service: ctx.local_node(),
+ wallet_transaction_service: ctx.wallet_transaction_service(),
+ }
+ }
+
+ /// This will parse the provided command and execute the task
+ pub fn handle_command(&mut self, command_str: &str) {
+ let commands: Vec<&str> = command_str.split(' ').collect();
+ let command = BaseNodeCommand::from_str(commands[0]);
+ if command.is_err() {
+ println!(
+ "Received: {}, this is not a valid command, please enter a valid command",
+ command_str
+ );
+ println!("Enter help or press tab for available commands");
+ return;
+ }
+ let command = command.unwrap();
+ let help_command = if commands.len() == 2 {
+ Some(BaseNodeCommand::from_str(commands[1]).unwrap_or(BaseNodeCommand::Help))
+ } else {
+ None
+ };
+ if help_command != Some(BaseNodeCommand::Help) {
+ return self.process_command(command, commands);
+ }
+ match command {
+ BaseNodeCommand::Help => {
+ println!("Available commands are: ");
+ let joined = self.commands.join(", ");
+ println!("{}", joined);
+ },
+ BaseNodeCommand::GetBalance => {
+ println!("This command gets your balance");
+ },
+ BaseNodeCommand::SendTari => {
+ println!("This command sends an amount of Tari to a address call this command via:");
+ println!("send_tari [amount of tari to send] [public key to send to]");
+ },
+ BaseNodeCommand::GetChainMetadata => {
+ println!("This command gets your base node chain meta data");
+ },
+ BaseNodeCommand::Exit | BaseNodeCommand::Quit => {
+ println!("This command exits the base node");
+ },
+ }
+ }
+
+ // Function to process commands
+ fn process_command(&mut self, command: BaseNodeCommand, command_arg: Vec<&str>) {
+ match command {
+ BaseNodeCommand::Help => {
+ println!("Available commands are: ");
+ let joined = self.commands.join(", ");
+ println!("{}", joined);
+ },
+ BaseNodeCommand::GetBalance => {
+ self.process_get_balance();
+ },
+ BaseNodeCommand::SendTari => {
+ self.process_send_tari(command_arg);
+ },
+ BaseNodeCommand::GetChainMetadata => {
+ self.process_get_chain_meta();
+ },
+ BaseNodeCommand::Exit | BaseNodeCommand::Quit => {
+ println!("quit received");
+ println!("Shutting down");
+ info!(
+ target: LOG_TARGET,
+ "Termination signal received from user. Shutting node down."
+ );
+ self.shutdown_flag.store(true, Ordering::SeqCst);
+ },
+ }
+ }
+
+ // Function to process the get balance command
+ fn process_get_balance(&mut self) {
+ let mut handler = self.wallet_output_service.clone();
+ self.executor.spawn(async move {
+ match handler.get_balance().await {
+ Err(e) => {
+ println!("Something went wrong");
+ warn!(target: LOG_TARGET, "Error communicating with wallet: {}", e.to_string(),);
+ return;
+ },
+ Ok(data) => println!("Current balance is: {}", data),
+ };
+ });
+ }
+
+ // Function to process the get chain meta data
+ fn process_get_chain_meta(&mut self) {
+ let mut handler = self.node_service.clone();
+ self.executor.spawn(async move {
+ match handler.get_metadata().await {
+ Err(e) => {
+ println!("Something went wrong");
+ warn!(
+ target: LOG_TARGET,
+ "Error communicating with base node: {}",
+ e.to_string(),
+ );
+ return;
+ },
+ Ok(data) => println!("Current meta data is is: {}", data),
+ };
+ });
+ }
+
+ // Function to process the send transaction function
+ fn process_send_tari(&mut self, command_arg: Vec<&str>) {
+ if command_arg.len() != 3 {
+ println!("Command entered wrong, please enter in the following format: ");
+ println!("send_tari [amount of tari to send] [public key to send to]");
+ return;
+ }
+ let amount = command_arg[1].parse::();
+ if amount.is_err() {
+ println!("please enter a valid amount of tari");
+ return;
+ }
+ let amount: MicroTari = amount.unwrap().into();
+ let dest_pubkey = CommsPublicKey::from_hex(command_arg[2]);
+ if dest_pubkey.is_err() {
+ println!("please enter a valid destination pub_key");
+ return;
+ }
+ let dest_pubkey = dest_pubkey.unwrap();
+ let fee_per_gram = 25 * uT;
+ let mut handler = self.wallet_transaction_service.clone();
+ self.executor.spawn(async move {
+ match handler
+ .send_transaction(
+ dest_pubkey.clone(),
+ amount,
+ fee_per_gram,
+ "coinbase reward from mining".into(),
+ )
+ .await
+ {
+ Err(e) => {
+ println!("Something went wrong sending funds");
+ println!("{:?}", e);
+ warn!(target: LOG_TARGET, "Error communicating with wallet: {}", e.to_string(),);
+ return;
+ },
+ Ok(_) => println!("Send {} Tari to {} ", amount, dest_pubkey),
+ };
+ });
+ }
+}
diff --git a/applications/tari_basenode/Cargo.toml b/applications/tari_basenode/Cargo.toml
deleted file mode 100644
index 5072dc9b1d..0000000000
--- a/applications/tari_basenode/Cargo.toml
+++ /dev/null
@@ -1,5 +0,0 @@
-[package]
-name = "tari_basenode"
-version = "0.0.1"
-
-[dependencies]
diff --git a/applications/tari_miner/Cargo.toml b/applications/tari_miner/Cargo.toml
deleted file mode 100644
index 44ad247818..0000000000
--- a/applications/tari_miner/Cargo.toml
+++ /dev/null
@@ -1,5 +0,0 @@
-[package]
-name = "tari_miner"
-version = "0.0.1"
-
-[dependencies]
diff --git a/applications/tari_pool_miner/Cargo.toml b/applications/tari_pool_miner/Cargo.toml
deleted file mode 100644
index 5b3297fe56..0000000000
--- a/applications/tari_pool_miner/Cargo.toml
+++ /dev/null
@@ -1,5 +0,0 @@
-[package]
-name = "tari_pool_miner"
-version = "0.0.1"
-
-[dependencies]
diff --git a/applications/test_faucet/Cargo.toml b/applications/test_faucet/Cargo.toml
new file mode 100644
index 0000000000..d52b4b0222
--- /dev/null
+++ b/applications/test_faucet/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "test_faucet"
+version = "0.0.1"
+authors = ["The Tari Development Community"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+tari_utilities = "0.1.1"
+serde = { version = "1.0.97", features = ["derive"] }
+serde_json = "1.0"
+rand = "0.7.2"
+
+[dependencies.tari_core]
+version = "0.0.9"
+path = "../../base_layer/core/"
+default-features = false
+features = ["transactions", "avx2"]
+
+[dependencies.tokio]
+version = "^0.2.10"
+default-features = false
+features = ["fs", "blocking", "stream", "rt-threaded", "macros", "io-util", "sync"]
diff --git a/applications/test_faucet/src/main.rs b/applications/test_faucet/src/main.rs
new file mode 100644
index 0000000000..6de049ddf5
--- /dev/null
+++ b/applications/test_faucet/src/main.rs
@@ -0,0 +1,126 @@
+use rand::{self, Rng};
+use serde::Serialize;
+use tari_core::{
+ tari_utilities::hex::Hex,
+ transactions::{
+ helpers,
+ tari_amount::{MicroTari, T},
+ transaction::{OutputFeatures, TransactionOutput},
+ types::{CryptoFactories, PrivateKey},
+ },
+};
+
+use std::{fs::File, io::Write};
+use tokio::{sync::mpsc, task};
+
+const NUM_KEYS: usize = 10;
+
+#[derive(Serialize)]
+struct Key {
+ key: String,
+ value: u64,
+ commitment: String,
+ proof: String,
+}
+
+/// UTXO generation is pretty slow (esp range proofs), so we'll use async threads to speed things up.
+/// We'll use blocking thread tasks to do the CPU intensive utxo generation, and then push the results
+/// through a channel where a file-writer is waiting to persist the results to disk.
+#[tokio::main(core_threads = 2, max_threads = 10)]
+async fn main() -> Result<(), Box> {
+ let num_keys: usize = std::env::args()
+ .skip(1)
+ .take(1)
+ .fold(NUM_KEYS, |def, v| v.parse::().unwrap_or(def));
+
+ // Create a channel to give the file writer output as the utxos are generated
+ let (tx, rx) = mpsc::channel::<(TransactionOutput, PrivateKey, MicroTari)>(500);
+
+ println!("Setting up output");
+ let write_fut = task::spawn(write_keys(rx));
+
+ println!("Generating {} UTXOs..", num_keys);
+ let factories = CryptoFactories::default();
+ let values = Values;
+ let features = UTXOFeatures;
+ // Use Rust's awesome Iterator trait to produce a sequence of values and output features.
+ for (value, feature) in values.take(num_keys).zip(features.take(num_keys)) {
+ let fc = factories.clone();
+ let mut txc = tx.clone();
+ // Notice the `spawn(.. spawn_blocking)` nested call here. If we don't do this, we're basically queuing up
+ // blocking tasks, `await`ing them to finish, and then queueing up the next one. In effect we're running things
+ // synchronously.
+ // What this construction says is: Queue up this task, and move on. "this task" (the spawning of the blocking
+ // task and awaiting its result) is not run immediately, but pushed to the scheduler to execute when it's
+ // ready. Now, we will use all the available threads for generating the keys (and the output should print
+ // "Go!" before, or right the beginning of any key generation output.
+ task::spawn(async move {
+ let result = task::spawn_blocking(move || {
+ let (utxo, key) = helpers::create_utxo(value, &fc, Some(feature));
+ print!(".");
+ (utxo, key, value)
+ })
+ .await
+ .expect("Could not create key");
+ let _ = txc.send(result).await;
+ });
+ }
+ println!("Go!");
+ // Explicitly drop the tx side here, so that rx will end its input.
+ drop(tx);
+
+ let _res = write_fut.await;
+ Ok(())
+}
+
+async fn write_keys(mut rx: mpsc::Receiver<(TransactionOutput, PrivateKey, MicroTari)>) {
+ let mut utxo_file = File::create("utxos.json").expect("Could not create utxos.json");
+ let mut key_file = File::create("keys.json").expect("Could not create keys.json");
+ let mut written: u64 = 0;
+ // The receiver channel will patiently await results until the tx is dropped.
+ while let Some((utxo, key, value)) = rx.recv().await {
+ let key = Key {
+ key: key.to_hex(),
+ value: u64::from(value),
+ commitment: utxo.commitment.to_hex(),
+ proof: utxo.proof.to_hex(),
+ };
+ let key_str = format!("{}\n", serde_json::to_string(&key).unwrap());
+ let _ = key_file.write_all(key_str.as_bytes());
+
+ let utxo_s = serde_json::to_string(&utxo).unwrap();
+ match utxo_file.write_all(format!("{}\n", utxo_s).as_bytes()) {
+ Ok(_) => {
+ written += 1;
+ if written % 50 == 0 {
+ println!("{} outputs written", written);
+ }
+ },
+ Err(e) => println!("{}", e.to_string()),
+ }
+ }
+ println!("Done.");
+}
+
+struct Values;
+
+impl Iterator for Values {
+ type Item = MicroTari;
+
+ fn next(&mut self) -> Option {
+ let mut rng = rand::rngs::OsRng;
+ let extra = rng.gen_range(0, 25) * 10_000_000;
+ Some(5000 * T + MicroTari(extra))
+ }
+}
+
+struct UTXOFeatures;
+
+impl Iterator for UTXOFeatures {
+ type Item = OutputFeatures;
+
+ fn next(&mut self) -> Option {
+ let f = OutputFeatures::with_maturity(0);
+ Some(f)
+ }
+}
diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml
index 8a1d869f75..2efcc5706e 100644
--- a/base_layer/core/Cargo.toml
+++ b/base_layer/core/Cargo.toml
@@ -6,22 +6,38 @@ repository = "https://github.com/tari-project/tari"
homepage = "https://tari.com"
readme = "README.md"
license = "BSD-3-Clause"
-version = "0.0.5"
+version = "0.0.9"
edition = "2018"
+[features]
+default = ["croaring", "tari_mmr", "transactions", "base_node", "mempool_proto", "monero", "randomx-rs", "base_node_proto"]
+transactions = []
+mempool_proto = []
+base_node = []
+base_node_proto = []
+avx2 = ["tari_crypto/avx2"]
+
[dependencies]
-tari_utilities = { path = "../../infrastructure/tari_util", version = "^0.0", features = ["chrono_dt"]}
+tari_comms = { version = "^0.0", path = "../../comms"}
tari_infra_derive = { path = "../../infrastructure/derive", version = "^0.0" }
-tari_crypto = { path = "../../infrastructure/crypto", version = "^0.0" }
-tari_p2p = {path = "../../base_layer/p2p", version = "^0.0"}
+tari_crypto = { version = "^0.3" }
tari_storage = { path = "../../infrastructure/storage", version = "^0.0" }
-tari_comms = { version = "^0.0", path = "../../comms"}
-tari_mmr = { path = "../../base_layer/mmr", version = "^0.0" }
+tari_common = {path = "../../common", version= "^0.0"}
+tari_service_framework = { version = "^0.0", path = "../service_framework"}
+tari_p2p = {path = "../../base_layer/p2p", version = "^0.0"}
+tari_comms_dht = { version = "^0.0", path = "../../comms/dht"}
+tari_broadcast_channel = "^0.1"
+tari_pubsub = "^0.1"
+tari_shutdown = { path = "../../infrastructure/shutdown", version = "^0.0"}
+tari_mmr = { path = "../../base_layer/mmr", version = "^0.0", optional = true }
+
+randomx-rs = { version = "0.1.2", optional = true }
+monero = { version = "0.5", features= ["serde_support"], optional = true }
bitflags = "1.0.4"
chrono = { version = "0.4.6", features = ["serde"]}
digest = "0.8.0"
derive-error = "0.0.4"
-rand = "0.5.5"
+rand = "0.7.2"
serde = { version = "1.0.97", features = ["derive"] }
rmp-serde = "0.13.7"
base64 = "0.10.1"
@@ -34,4 +50,26 @@ log = "0.4"
blake2 = "^0.8.0"
bigint = "^4.4.1"
ttl_cache = "0.5.1"
-croaring = "^0.4.0"
+tokio = { version="^0.2", features = ["blocking", "time"] }
+futures = {version = "^0.3.1", features = ["async-await"] }
+lmdb-zero = "0.4.4"
+tower-service = { version="0.3.0-alpha.2" }
+crossbeam-channel = "0.3.8"
+prost = "0.6.1"
+bytes = "0.4.12"
+prost-types = "0.6.1"
+cfg-if = "0.1.10"
+croaring = { version = "=0.3.9", optional = true }
+config = { version = "0.9.3" }
+strum = "0.17.1"
+strum_macros = "0.17.1"
+
+[dev-dependencies]
+tari_p2p = {path = "../../base_layer/p2p", version = "^0.0", features=["test-mocks"]}
+tari_test_utils = { path = "../../infrastructure/test_utils", version = "^0.0" }
+env_logger = "0.7.0"
+tempdir = "0.3.7"
+tokio-macros = "0.2.4"
+
+[build-dependencies]
+tari_common = { version = "^0.0", path="../../common"}
diff --git a/base_layer/core/build.rs b/base_layer/core/build.rs
new file mode 100644
index 0000000000..7f8cc23601
--- /dev/null
+++ b/base_layer/core/build.rs
@@ -0,0 +1,33 @@
+// 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.
+
+fn main() {
+ tari_common::protobuf_build::ProtoCompiler::new()
+ .include_paths(&["src/transactions/proto", "src/proto"])
+ .proto_paths(&[
+ "src/mempool/proto",
+ "src/base_node/proto",
+ "src/transactions/transaction_protocol/proto",
+ ])
+ .compile()
+ .unwrap();
+}
diff --git a/base_layer/core/src/base_node/backoff.rs b/base_layer/core/src/base_node/backoff.rs
new file mode 100644
index 0000000000..1948633e81
--- /dev/null
+++ b/base_layer/core/src/base_node/backoff.rs
@@ -0,0 +1,137 @@
+// 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 std::time::Duration;
+use tokio::time;
+
+/// A simple back-off strategy. `BackOff` is typically used in situations where you want to retry an operation a
+/// number of times, with an increasing delay between attempts
+///
+/// # Examples
+///
+/// ```no_run
+/// use std::time::Duration;
+/// use tari_core::base_node::BackOff;
+///
+/// fn foo(n: u64) -> Result<(), u64> {
+/// if n < 3 {
+/// Err(n)
+/// } else {
+/// Ok(())
+/// }
+/// }
+/// let mut backoff = BackOff::new(5, Duration::from_millis(100), 1.5);
+/// async {
+/// let mut attempts = 1;
+/// while !backoff.is_finished() {
+/// match foo(attempts) {
+/// Ok(_) => backoff.stop(),
+/// Err(n) => {
+/// assert!(n < 3);
+/// backoff.wait().await;
+/// attempts += 1;
+/// },
+/// }
+/// }
+/// };
+/// assert_eq!(backoff.attempts(), 4);
+/// ```
+#[derive(Clone, Debug, PartialEq)]
+pub struct BackOff {
+ max_attempts: usize,
+ current_attempts: usize,
+ delay: Duration,
+ backoff: f64,
+ stopped: bool,
+}
+
+impl BackOff {
+ /// Create a new `BackOff` timer.
+ ///
+ /// # Parameters
+ /// * max_attempts: The total number of attempts to make
+ /// * delay: The initial duration to wait for after the first attempt
+ /// * factor: The factor to apply to the delay after each attempt
+ pub fn new(max_attempts: usize, delay: Duration, factor: f64) -> Self {
+ BackOff {
+ max_attempts,
+ current_attempts: 0,
+ delay,
+ backoff: factor,
+ stopped: false,
+ }
+ }
+
+ pub fn attempts(&self) -> usize {
+ self.current_attempts
+ }
+
+ pub fn max_attempts(&self) -> usize {
+ self.max_attempts
+ }
+
+ pub fn is_finished(&self) -> bool {
+ self.current_attempts >= self.max_attempts || self.stopped
+ }
+
+ pub fn is_stopped(&self) -> bool {
+ self.stopped
+ }
+
+ pub fn stop(&mut self) {
+ self.stopped = true
+ }
+
+ pub async fn wait(&mut self) {
+ if self.is_finished() {
+ return;
+ }
+ time::delay_for(self.delay).await;
+ self.current_attempts += 1;
+ self.delay = self.delay.mul_f64(self.backoff);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::base_node::BackOff;
+ use std::time::Duration;
+
+ #[tokio_macros::test]
+ async fn retry() {
+ let mut retry = BackOff::new(3, Duration::from_millis(100), 1.5);
+ assert_eq!(retry.attempts(), 0);
+ retry.wait().await;
+ assert_eq!(retry.attempts(), 1);
+ assert_eq!(retry.is_finished(), false);
+ retry.wait().await;
+ assert_eq!(retry.attempts(), 2);
+ assert_eq!(retry.is_finished(), false);
+ retry.wait().await;
+ assert_eq!(retry.attempts(), 3);
+ assert_eq!(retry.is_finished(), true);
+ retry.wait().await;
+ assert_eq!(retry.attempts(), 3);
+ assert_eq!(retry.is_finished(), true);
+ }
+}
diff --git a/base_layer/core/src/base_node/base_node.rs b/base_layer/core/src/base_node/base_node.rs
index 0e855854b7..e783de9837 100644
--- a/base_layer/core/src/base_node/base_node.rs
+++ b/base_layer/core/src/base_node/base_node.rs
@@ -20,6 +20,159 @@
// 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.
-/// `BaseNode` is the highest-level struct of the Tari full node implementation. `BaseNode` collects all the
-/// sub-pieces of the Tari blockchain together, and exposes a unified API using a futures-based request-response model
-pub struct BaseNode {}
+use crate::{
+ base_node::{
+ chain_metadata_service::ChainMetadataEvent,
+ comms_interface::OutboundNodeCommsInterface,
+ states,
+ states::{BaseNodeState, BlockSyncConfig, ListeningConfig, ListeningInfo, StateEvent},
+ },
+ chain_storage::{BlockchainBackend, BlockchainDatabase},
+};
+// use bitflags::_core::sync::atomic::AtomicBool;
+// use futures_util::sink::SinkExt;
+use futures::SinkExt;
+use log::*;
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+};
+use tari_broadcast_channel::{bounded, Publisher, Subscriber};
+use tokio::runtime;
+
+const LOG_TARGET: &str = "c::bn::base_node";
+
+/// Configuration for the BaseNodeStateMachine.
+#[derive(Clone, Copy)]
+pub struct BaseNodeStateMachineConfig {
+ pub block_sync_config: BlockSyncConfig,
+ pub listening_config: ListeningConfig,
+}
+
+impl Default for BaseNodeStateMachineConfig {
+ fn default() -> Self {
+ Self {
+ block_sync_config: BlockSyncConfig::default(),
+ listening_config: ListeningConfig::default(),
+ }
+ }
+}
+
+/// A Tari full node, aka Base Node.
+///
+/// The Base Node is essentially a finite state machine that synchronises its blockchain state with its peers and
+/// then listens for new blocks to add to the blockchain. See the [SynchronizationSate] documentation for more details.
+///
+/// This struct holds fields that will be used by all the various FSM state instances, including the local blockchain
+/// database and hooks to the p2p network
+pub struct BaseNodeStateMachine {
+ pub(super) db: BlockchainDatabase,
+ pub(super) comms: OutboundNodeCommsInterface,
+ pub(super) executor: runtime::Handle,
+ pub(super) metadata_event_stream: Subscriber,
+ pub(super) user_stopped: Arc,
+ pub(super) config: BaseNodeStateMachineConfig,
+ event_sender: Publisher,
+ event_receiver: Subscriber,
+}
+
+impl BaseNodeStateMachine {
+ /// Instantiate a new Base Node.
+ pub fn new(
+ db: &BlockchainDatabase,
+ comms: &OutboundNodeCommsInterface,
+ executor: runtime::Handle,
+ metadata_event_stream: Subscriber,
+ config: BaseNodeStateMachineConfig,
+ ) -> Self
+ {
+ let (event_sender, event_receiver): (Publisher, Subscriber) = bounded(1);
+ Self {
+ db: db.clone(),
+ comms: comms.clone(),
+ executor,
+ metadata_event_stream,
+ user_stopped: Arc::new(AtomicBool::new(false)),
+ config,
+ event_sender,
+ event_receiver,
+ }
+ }
+
+ /// Describe the Finite State Machine for the base node. This function describes _every possible_ state
+ /// transition for the node given its current state and an event that gets triggered.
+ pub fn transition(state: BaseNodeState, event: StateEvent) -> BaseNodeState {
+ use crate::base_node::states::{BaseNodeState::*, StateEvent::*, SyncStatus::*};
+ match (state, event) {
+ (Starting(s), Initialized) => InitialSync(s.into()),
+ (InitialSync(s), MetadataSynced(Lagging(_))) => BlockSync(s.into()),
+ (InitialSync(_s), MetadataSynced(UpToDate)) => Listening(ListeningInfo),
+ (BlockSync(_s), BlocksSynchronized) => Listening(ListeningInfo),
+ (BlockSync(s), MaxRequestAttemptsReached) => InitialSync(s.into()),
+ (Listening(s), FallenBehind(Lagging(_))) => BlockSync(s.into()),
+ (Listening(s), NetworkSilence) => InitialSync(s.into()),
+ (_, FatalError(s)) => Shutdown(states::Shutdown::with_reason(s)),
+ (_, UserQuit) => Shutdown(states::Shutdown::with_reason("Shutdown initiated by user".to_string())),
+ (s, e) => {
+ warn!(
+ target: LOG_TARGET,
+ "No state transition occurs for event {:?} in state {}", e, s
+ );
+ s
+ },
+ }
+ }
+
+ /// Return a copy of the `user_stopped` flag. Setting this to `true` at any time will signal the node runtime to
+ /// shutdown.
+ pub fn get_interrupt_flag(&self) -> Arc {
+ Arc::clone(&self.user_stopped)
+ }
+
+ /// Returns `true` if the `user_stopped` flag has been set
+ pub fn is_stop_requested(&self) -> bool {
+ self.user_stopped.load(Ordering::SeqCst)
+ }
+
+ /// This clones the receiver end of the channel and gives out a copy to the caller
+ /// This allows multiple subscribers to this channel by only keeping one channel and cloning the receiver for every
+ /// caller.
+ pub fn get_state_change_event_stream(&self) -> Subscriber {
+ self.event_receiver.clone()
+ }
+
+ /// Start the base node runtime.
+ pub async fn run(self) {
+ use crate::base_node::states::BaseNodeState::*;
+ let mut state = Starting(states::Starting);
+ let mut shared_state = self;
+ loop {
+ if shared_state.is_stop_requested() {
+ break;
+ }
+ let _ = shared_state.event_sender.send(state.clone()).await;
+ let next_event = match &mut state {
+ Starting(s) => s.next_event(&mut shared_state).await,
+ InitialSync(s) => s.next_event(&mut shared_state).await,
+ BlockSync(s) => s.next_event(&mut shared_state).await,
+ Listening(s) => s.next_event(&mut shared_state).await,
+ Shutdown(_) => break,
+ };
+ debug!(
+ target: LOG_TARGET,
+ "=== Base Node event in State [{}]: {:?}", state, next_event
+ );
+ state = BaseNodeStateMachine::::transition(state, next_event);
+ }
+ }
+
+ /// Checks the value of the interrupt flag and returns a `FatalError` event if the flag is true. Otherwise it
+ /// returns the `default` event.
+ pub fn check_interrupt(flag: &AtomicBool, default: StateEvent) -> StateEvent {
+ if flag.load(Ordering::SeqCst) {
+ StateEvent::FatalError("User interrupted".into())
+ } else {
+ default
+ }
+ }
+}
diff --git a/base_layer/core/src/base_node/chain_metadata_service/error.rs b/base_layer/core/src/base_node/chain_metadata_service/error.rs
new file mode 100644
index 0000000000..355df992fb
--- /dev/null
+++ b/base_layer/core/src/base_node/chain_metadata_service/error.rs
@@ -0,0 +1,40 @@
+// 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::base_node::comms_interface::CommsInterfaceError;
+use derive_error::Error;
+use prost::DecodeError;
+use tari_comms::message::MessageError;
+use tari_p2p::services::liveness::error::LivenessError;
+
+#[derive(Debug, Error)]
+pub enum ChainMetadataSyncError {
+ /// Failed to decode chain metadata
+ DecodeError(DecodeError),
+ /// Peer did not send any chain metadata
+ NoChainMetadata,
+ LivenessError(LivenessError),
+ CommsInterfaceError(CommsInterfaceError),
+ MessageError(MessageError),
+ /// Failed to publish `ChainMetadataEvent`
+ EventPublishFailed,
+}
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
new file mode 100644
index 0000000000..3bb498b6e0
--- /dev/null
+++ b/base_layer/core/src/base_node/chain_metadata_service/handle.rs
@@ -0,0 +1,49 @@
+// 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::chain_storage::ChainMetadata;
+use futures::{stream::Fuse, StreamExt};
+use tari_broadcast_channel::Subscriber;
+
+#[derive(Debug)]
+pub enum ChainMetadataEvent {
+ PeerChainMetadataReceived(Vec),
+}
+
+#[derive(Clone)]
+pub struct ChainMetadataHandle {
+ event_stream: Subscriber,
+}
+
+impl ChainMetadataHandle {
+ pub fn new(event_stream: Subscriber) -> Self {
+ Self { event_stream }
+ }
+
+ pub fn get_event_stream(&self) -> Subscriber {
+ self.event_stream.clone()
+ }
+
+ pub fn get_event_stream_fused(&self) -> Fuse> {
+ self.get_event_stream().fuse()
+ }
+}
diff --git a/base_layer/core/src/base_node/chain_metadata_service/initializer.rs b/base_layer/core/src/base_node/chain_metadata_service/initializer.rs
new file mode 100644
index 0000000000..d77a83bfa6
--- /dev/null
+++ b/base_layer/core/src/base_node/chain_metadata_service/initializer.rs
@@ -0,0 +1,71 @@
+// 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 super::{service::ChainMetadataService, LOG_TARGET};
+use crate::base_node::{chain_metadata_service::handle::ChainMetadataHandle, comms_interface::LocalNodeCommsInterface};
+use futures::{future, future::select, pin_mut};
+use log::*;
+use std::future::Future;
+use tari_broadcast_channel as broadcast_channel;
+use tari_p2p::services::liveness::LivenessHandle;
+use tari_service_framework::{handles::ServiceHandlesFuture, ServiceInitializationError, ServiceInitializer};
+use tari_shutdown::ShutdownSignal;
+use tokio::runtime;
+
+const BROADCAST_EVENT_BUFFER_SIZE: usize = 10;
+
+pub struct ChainMetadataServiceInitializer;
+
+impl ServiceInitializer for ChainMetadataServiceInitializer {
+ type Future = impl Future