diff --git a/Cargo.lock b/Cargo.lock
index 308c6e4fa..81b85fa31 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1944,6 +1944,7 @@ dependencies = [
"diesel_migrations",
"docopt",
"env_logger",
+ "exit_trust_root",
"futures 0.3.31",
"ipnetwork",
"lazy_static",
@@ -3229,6 +3230,7 @@ dependencies = [
"clarity",
"crypto_box",
"deep_space",
+ "exit_trust_root",
"handlebars",
"ipnetwork",
"lazy_static",
diff --git a/althea_types/src/exits/server_list_signatures.rs b/althea_types/src/exits/server_list_signatures.rs
index cf1a00006..9bb25c41f 100644
--- a/althea_types/src/exits/server_list_signatures.rs
+++ b/althea_types/src/exits/server_list_signatures.rs
@@ -63,10 +63,15 @@ impl ExitServerList {
if sig.is_valid() {
let hash = get_ethereum_msg_hash(&self.encode_to_eth_abi());
match sig.recover(&hash) {
- Ok(addr) => addr == key,
- Err(_) => false,
+ Ok(addr) => {
+ println!("Recovered address is {:?}", addr);
+ addr == key},
+ Err(_) => {
+ println!("Failed to recover address from signature");
+ false},
}
} else {
+ println!("Signature is invalid");
false
}
}
diff --git a/exit_trust_root/Cargo.toml b/exit_trust_root/Cargo.toml
index 483c33c02..5ffe86737 100644
--- a/exit_trust_root/Cargo.toml
+++ b/exit_trust_root/Cargo.toml
@@ -5,8 +5,12 @@ edition = "2021"
license = "Apache-2.0"
description = "Server to provide a root of exit trust for Althea routers"
-[[bin]]
+[lib]
name = "exit_trust_root"
+path = "src/lib.rs"
+
+[[bin]]
+name = "exit_trust_root_server"
path = "src/bin.rs"
[dependencies]
diff --git a/exit_trust_root/src/bin.rs b/exit_trust_root/src/bin.rs
index a3a989b47..ed4832808 100644
--- a/exit_trust_root/src/bin.rs
+++ b/exit_trust_root/src/bin.rs
@@ -1,129 +1,8 @@
-use actix_web::rt::System;
-use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
-use althea_types::{ExitServerList, SignedExitServerList};
-use clarity::Address;
-use config::{load_config, CONFIG};
use env_logger::Env;
-use log::info;
-use rita_client_registration::client_db::get_exits_list;
-use rustls::ServerConfig;
-use std::collections::HashMap;
-use std::sync::{Arc, RwLock};
-use std::thread;
-use std::time::Duration;
-use tls::{load_certs, load_rustls_private_key};
-use web30::client::Web3;
-use web30::jsonrpc::error::Web3Error;
-
-pub mod config;
-pub mod tls;
-
-pub const DEVELOPMENT: bool = cfg!(feature = "development");
-const SSL: bool = !DEVELOPMENT;
-pub const DOMAIN: &str = if cfg!(test) || DEVELOPMENT {
- "localhost"
-} else {
- "exitroot.althea.net"
-};
-/// The backend RPC port for the info server fucntions implemented in this repo
-const SERVER_PORT: u16 = 9000;
-
-/// This endpoint retrieves and signs the data from any specified exit contract,
-/// allowing this server to serve as a root of trust for several different exit contracts.
-#[get("/{exit_contract}")]
-async fn return_exit_contract_data(
- exit_contract: web::Path
,
- cache: web::Data>>>,
-) -> impl Responder {
- let contract = exit_contract.into_inner();
- let cached_list = {
- let cache_read = cache.read().unwrap();
- cache_read.get(&contract).cloned()
- };
-
- match cached_list {
- Some(list) => {
- // return a signed exit server list based on the given key
- HttpResponse::Ok().json(list)
- }
- None => match retrieve_exit_server_list(contract, cache.get_ref().clone()).await {
- Ok(list) => {
- HttpResponse::Ok().json(list)
- }
- Err(e) => {
- info!("Failed to get exit list from contract {:?}", e);
- HttpResponse::InternalServerError().json("Failed to get exit list from contract")
- }
- },
- }
-}
-
-async fn retrieve_exit_server_list(
- exit_contract: Address,
- cache: Arc>>,
-) -> Result {
- const WEB3_TIMEOUT: Duration = Duration::from_secs(10);
- let exits = get_exits_list(
- &Web3::new("https://dai.althea.net", WEB3_TIMEOUT),
- CONFIG.clarity_private_key.to_address(),
- exit_contract,
- )
- .await;
- match exits {
- Ok(exits) => {
- info!("Got exit list from contract");
- let exit_list = ExitServerList {
- contract: exit_contract,
- exit_list: exits,
- created: std::time::SystemTime::now(),
- };
- let cache_value = exit_list.sign(CONFIG.clarity_private_key);
-
- // add this new exit list to the cache
- cache
- .write()
- .unwrap()
- .insert(exit_contract, cache_value.clone());
- Ok(cache_value)
- }
- Err(e) => {
- info!("Failed to get exit list from contract {:?}", e);
- Err(e)
- }
- }
-}
-
-// five minutes
-const SIGNATURE_UPDATE_SLEEP: Duration = Duration::from_secs(300);
-
-/// In order to improve scalability this loop grabs and signs an updated list of exits from each exit contract
-/// that has previously been requested from this server every 5 minutes. This allows the server to return instantly
-/// on the next request from the client without having to perform rpc query 1-1 with requests.
-fn signature_update_loop(cache: Arc>>) {
- thread::spawn(move || loop {
- let runner = System::new();
- runner.block_on(async {
- let cache_iter = cache.read().unwrap().clone();
- for (exit_contract, _value) in cache_iter.iter() {
- // get the latest exit list from the contract
- match retrieve_exit_server_list(*exit_contract, cache.clone()).await {
- // grab the cache here so we don't lock it while awaiting for every single contract
- Ok(cache_value) => {
- // update the cache
- cache.write().unwrap().insert(*exit_contract, cache_value);
- }
- Err(e) => {
- info!("Failed to get exit list from contract {:?}", e);
- }
- }
- }
- });
- thread::sleep(SIGNATURE_UPDATE_SLEEP);
- });
-}
+use exit_trust_root::{config::load_config, start_exit_trust_root_server};
#[actix_web::main]
-async fn main() -> std::io::Result<()> {
+async fn main() {
openssl_probe::init_ssl_cert_env_vars();
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
@@ -131,31 +10,5 @@ async fn main() -> std::io::Result<()> {
// lazy static variable after this
load_config();
- let exit_contract_data_cache: Arc>> =
- Arc::new(RwLock::new(HashMap::new()));
- signature_update_loop(exit_contract_data_cache.clone());
- let web_data = web::Data::new(exit_contract_data_cache.clone());
-
- let server = HttpServer::new(move || {
- App::new()
- .service(return_exit_contract_data)
- .app_data(web_data.clone())
- });
- let server = if SSL {
- let cert_chain = load_certs(&format!("/etc/letsencrypt/live/{}/fullchain.pem", DOMAIN));
- let keys =
- load_rustls_private_key(&format!("/etc/letsencrypt/live/{}/privkey.pem", DOMAIN));
- let config = ServerConfig::builder()
- .with_safe_defaults()
- .with_no_client_auth()
- .with_single_cert(cert_chain, keys)
- .unwrap();
-
- info!("Binding to SSL");
- server.bind_rustls(format!("{}:{}", DOMAIN, SERVER_PORT), config.clone())?
- } else {
- server.bind(format!("{}:{}", DOMAIN, SERVER_PORT))?
- };
-
- server.run().await
+ start_exit_trust_root_server();
}
diff --git a/exit_trust_root/src/config.rs b/exit_trust_root/src/config.rs
index c236a4883..dc558330d 100644
--- a/exit_trust_root/src/config.rs
+++ b/exit_trust_root/src/config.rs
@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::DEVELOPMENT;
+
///Struct containing settings for Exit root server
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ConfigStruct {
@@ -39,7 +40,10 @@ impl ConfigStruct {
pub fn load_config() -> ConfigStruct {
// change the config name based on our development status
let file_name = if DEVELOPMENT || cfg!(test) {
- "config.toml"
+ return ConfigStruct {
+ clarity_private_key: PrivateKey::from_bytes([1u8; 32]).unwrap(),
+ wg_private_key: WgKey::from([2; 32]),
+ }
} else {
"/etc/exit_root_server.toml"
};
diff --git a/exit_trust_root/src/lib.rs b/exit_trust_root/src/lib.rs
new file mode 100644
index 000000000..739c386d0
--- /dev/null
+++ b/exit_trust_root/src/lib.rs
@@ -0,0 +1,186 @@
+use actix_web::rt::System;
+use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
+use althea_types::{ExitServerList, SignedExitServerList};
+use clarity::Address;
+use config::CONFIG;
+use log::info;
+use rita_client_registration::client_db::get_exits_list;
+use rustls::ServerConfig;
+use std::collections::HashMap;
+use std::net::{IpAddr, Ipv4Addr};
+use std::sync::{Arc, RwLock};
+use std::thread;
+use std::time::Duration;
+use tls::{load_certs, load_rustls_private_key};
+use web30::client::Web3;
+use web30::jsonrpc::error::Web3Error;
+
+pub mod config;
+pub mod tls;
+
+const RPC_SERVER: &str = "https://dai.althea.net";
+const WEB3_TIMEOUT: Duration = Duration::from_secs(10);
+
+// five minutes
+const SIGNATURE_UPDATE_SLEEP: Duration = Duration::from_secs(300);
+
+pub const DEVELOPMENT: bool = cfg!(feature = "development");
+const SSL: bool = !DEVELOPMENT;
+// todo this is copied over from exit root server, deduplicate
+pub const EXIT_ROOT_DOMAIN: &str = if cfg!(test) || cfg!(feature = "development") {
+ "http://10.0.0.1:4050"
+} else {
+ "https://exitroot.althea.net"
+};
+/// The backend RPC port for the info server fucntions implemented in this repo
+pub const SERVER_PORT: u16 = 4050;
+
+/// This endpoint retrieves and signs the data from any specified exit contract,
+/// allowing this server to serve as a root of trust for several different exit contracts.
+#[get("/{exit_contract}")]
+async fn return_exit_contract_data(
+ exit_contract: web::Path,
+ cache: web::Data>>>,
+) -> impl Responder {
+ let contract: Address = exit_contract.into_inner();
+ let cached_list = {
+ let cache_read = cache.read().unwrap();
+ cache_read.get(&contract).cloned()
+ };
+
+ match cached_list {
+ Some(list) => {
+ // return a signed exit server list based on the given key
+ HttpResponse::Ok().json(list)
+ }
+ None => match retrieve_exit_server_list(contract, cache.get_ref().clone()).await {
+ Ok(list) => HttpResponse::Ok().json(list),
+ Err(e) => {
+ info!("Failed to get exit list from contract {:?}", e);
+ HttpResponse::InternalServerError().json("Failed to get exit list from contract")
+ }
+ },
+ }
+}
+
+async fn retrieve_exit_server_list(
+ exit_contract: Address,
+ cache: Arc>>,
+) -> Result {
+ let exits = match DEVELOPMENT || cfg!(test) {
+ true => {
+ let node_ip = IpAddr::V4(Ipv4Addr::new(7, 7, 7, 1));
+ let web3_url = format!("http://{}:8545", node_ip);
+ info!("Our address is {:?}", CONFIG.clarity_private_key.to_address());
+ get_exits_list(
+ &Web3::new(&web3_url, WEB3_TIMEOUT),
+ CONFIG.clarity_private_key.to_address(),
+ exit_contract,
+ )
+ .await
+ }
+ false => {
+ get_exits_list(
+ &Web3::new(RPC_SERVER, WEB3_TIMEOUT),
+ CONFIG.clarity_private_key.to_address(),
+ exit_contract,
+ )
+ .await
+ }
+ };
+
+ match exits {
+ Ok(exits) => {
+ info!("Got exit list from contract");
+ let exit_list = ExitServerList {
+ contract: exit_contract,
+ exit_list: exits,
+ created: std::time::SystemTime::now(),
+ };
+ println!("Signing exit list with PUBKEY: {:?}", CONFIG.clarity_private_key.to_address());
+ let cache_value = exit_list.sign(CONFIG.clarity_private_key);
+
+ // add this new exit list to the cache
+ cache
+ .write()
+ .unwrap()
+ .insert(exit_contract, cache_value.clone());
+ Ok(cache_value)
+ }
+ Err(e) => {
+ info!("Failed to get exit list from contract {:?}", e);
+ Err(e)
+ }
+ }
+}
+
+pub fn start_exit_trust_root_server() {
+ let exit_contract_data_cache: Arc>> =
+ Arc::new(RwLock::new(HashMap::new()));
+ signature_update_loop(exit_contract_data_cache.clone());
+ let web_data = web::Data::new(exit_contract_data_cache.clone());
+ thread::spawn(move || {
+ let runner = System::new();
+ runner.block_on(async move {
+ let server = HttpServer::new(move || {
+ App::new()
+ .service(return_exit_contract_data)
+ .app_data(web_data.clone())
+ });
+ info!("Starting exit trust root server on {:?}", EXIT_ROOT_DOMAIN);
+ let server = if SSL {
+ let cert_chain =
+ load_certs(&format!("/etc/letsencrypt/live/{}/fullchain.pem", EXIT_ROOT_DOMAIN));
+ let keys = load_rustls_private_key(&format!(
+ "/etc/letsencrypt/live/{}/privkey.pem",
+ EXIT_ROOT_DOMAIN
+ ));
+ let config = ServerConfig::builder()
+ .with_safe_defaults()
+ .with_no_client_auth()
+ .with_single_cert(cert_chain, keys)
+ .unwrap();
+
+ info!("Binding to SSL");
+ server
+ .bind_rustls(format!("{}:{}", EXIT_ROOT_DOMAIN, SERVER_PORT), config.clone())
+ .unwrap()
+ } else {
+ info!("Binding to {}:{}", EXIT_ROOT_DOMAIN, SERVER_PORT);
+ server.bind(format!("{}:{}", EXIT_ROOT_DOMAIN, SERVER_PORT)).unwrap()
+ };
+
+ let _ = server.run().await;
+ });
+ });
+}
+
+/// In order to improve scalability this loop grabs and signs an updated list of exits from each exit contract
+/// that has previously been requested from this server every 5 minutes. This allows the server to return instantly
+/// on the next request from the client without having to perform rpc query 1-1 with requests.
+pub fn signature_update_loop(cache: Arc>>) {
+ thread::spawn(move || loop {
+ let runner = System::new();
+ runner.block_on(async {
+ let cache_iter = cache.read().unwrap().clone();
+ for (exit_contract, _value) in cache_iter.iter() {
+ // get the latest exit list from the contract
+ match retrieve_exit_server_list(*exit_contract, cache.clone()).await {
+ // grab the cache here so we don't lock it while awaiting for every single contract
+ Ok(cache_value) => {
+ // update the cache
+ cache.write().unwrap().insert(*exit_contract, cache_value);
+ }
+ Err(e) => {
+ info!("Failed to get exit list from contract {:?}", e);
+ }
+ }
+ }
+ });
+ if DEVELOPMENT || cfg!(test) {
+ thread::sleep(Duration::from_secs(10));
+ } else {
+ thread::sleep(SIGNATURE_UPDATE_SLEEP);
+ }
+ });
+}
diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml
index 032da4756..4e0d3caac 100644
--- a/integration_tests/Cargo.toml
+++ b/integration_tests/Cargo.toml
@@ -21,6 +21,7 @@ settings = { path = "../settings" }
rita_client = { path = "../rita_client", features = ["dev_env"] }
rita_common = { path = "../rita_common", features = ["integration_test"] }
rita_exit = { path = "../rita_exit", features = ["dev_env"] }
+exit_trust_root = { path = "../exit_trust_root", features = ["development"] }
rita_client_registration = { path = "../rita_client_registration" }
rita_db_migration = { path = "../rita_db_migration" }
ctrlc = { version = "3.4.5", features = ["termination"] }
diff --git a/integration_tests/src/debts.rs b/integration_tests/src/debts.rs
index d7c68a6c6..36ab89f5b 100644
--- a/integration_tests/src/debts.rs
+++ b/integration_tests/src/debts.rs
@@ -5,7 +5,7 @@ use std::time::Duration;
use crate::five_nodes::five_node_config;
use crate::registration_server::start_registration_server;
use crate::setup_utils::namespaces::*;
-use crate::setup_utils::rita::thread_spawner;
+use crate::setup_utils::rita::{spawn_exit_root, thread_spawner};
use crate::utils::{
add_exits_contract_exit_list, deploy_contracts, generate_traffic, get_default_settings,
get_ip_from_namespace, populate_routers_eth, query_debts, register_all_namespaces_to_exit,
@@ -49,7 +49,7 @@ pub async fn run_debts_test() {
info!("Starting registration server");
start_registration_server(db_addr).await;
- let (client_settings, exit_settings) = get_default_settings(namespaces.clone());
+ let (client_settings, exit_settings, exit_root_addr) = get_default_settings(namespaces.clone());
// The exit price is set to ns.cost during thread_spawner
let exit_price = namespaces.get_namespace(4).unwrap().cost;
@@ -59,6 +59,9 @@ pub async fn run_debts_test() {
let res = setup_ns(namespaces.clone());
info!("Namespaces setup: {res:?}");
+ info!("Starting root server!");
+ spawn_exit_root();
+
let rita_identities =
thread_spawner(namespaces.clone(), client_settings, exit_settings, db_addr)
.expect("Could not spawn Rita threads");
@@ -67,7 +70,7 @@ pub async fn run_debts_test() {
// Add exits to the contract exit list so clients get the propers exits they can migrate to
add_exits_contract_exit_list(db_addr, rita_identities.clone()).await;
- populate_routers_eth(rita_identities).await;
+ populate_routers_eth(rita_identities, exit_root_addr).await;
// Test for network convergence
test_reach_all(namespaces.clone());
diff --git a/integration_tests/src/five_nodes.rs b/integration_tests/src/five_nodes.rs
index 64694e090..206803bb7 100644
--- a/integration_tests/src/five_nodes.rs
+++ b/integration_tests/src/five_nodes.rs
@@ -1,6 +1,6 @@
use crate::registration_server::start_registration_server;
use crate::setup_utils::namespaces::*;
-use crate::setup_utils::rita::thread_spawner;
+use crate::setup_utils::rita::{spawn_exit_root, thread_spawner};
use crate::utils::{
add_exits_contract_exit_list, deploy_contracts, get_default_settings, populate_routers_eth,
register_all_namespaces_to_exit, test_all_internet_connectivity, test_reach_all, test_routes,
@@ -21,13 +21,16 @@ pub async fn run_five_node_test_scenario() {
info!("Starting registration server");
start_registration_server(db_addr).await;
- let (client_settings, exit_settings) = get_default_settings(namespaces.clone());
+ let (client_settings, exit_settings, exit_root_addr) = get_default_settings(namespaces.clone());
namespaces.validate();
let res = setup_ns(namespaces.clone());
info!("Namespaces setup: {res:?}");
+ info!("Starting root server!");
+ spawn_exit_root();
+
let rita_identities =
thread_spawner(namespaces.clone(), client_settings, exit_settings, db_addr)
.expect("Could not spawn Rita threads");
@@ -40,7 +43,7 @@ pub async fn run_five_node_test_scenario() {
//thread::sleep(SETUP_WAIT * 500);
info!("About to populate routers with eth");
- populate_routers_eth(rita_identities).await;
+ populate_routers_eth(rita_identities, exit_root_addr).await;
test_reach_all(namespaces.clone());
@@ -65,7 +68,7 @@ pub fn five_node_config() -> (NamespaceInfo, HashMap) {
| |
| |
| |
- 3 4---------7
+ 3 4(Exit)-------7
|\ |
| \ |
| \ |
diff --git a/integration_tests/src/mutli_exit.rs b/integration_tests/src/mutli_exit.rs
index afd6812b0..0d8ec958c 100644
--- a/integration_tests/src/mutli_exit.rs
+++ b/integration_tests/src/mutli_exit.rs
@@ -6,7 +6,7 @@ use crate::{
registration_server::start_registration_server,
setup_utils::{
namespaces::{setup_ns, Namespace, NamespaceInfo, NodeType, PriceId, RouteHop},
- rita::thread_spawner,
+ rita::{spawn_exit_root, thread_spawner},
},
utils::{
add_exits_contract_exit_list, deploy_contracts, get_default_settings, get_node_id_from_ip,
@@ -44,12 +44,16 @@ pub async fn run_multi_exit_test() {
info!("Starting registration server");
start_registration_server(db_addr).await;
- let (rita_client_settings, rita_exit_settings) = get_default_settings(namespaces.clone());
+ let (rita_client_settings, rita_exit_settings, exit_root_addr) =
+ get_default_settings(namespaces.clone());
namespaces.validate();
let res = setup_ns(namespaces.clone());
+ info!("Starting root server!");
+ spawn_exit_root();
+
let rita_identities = thread_spawner(
namespaces.clone(),
rita_client_settings,
@@ -62,7 +66,7 @@ pub async fn run_multi_exit_test() {
// Add exits to the contract exit list so clients get the propers exits they can migrate to
add_exits_contract_exit_list(db_addr, rita_identities.clone()).await;
- populate_routers_eth(rita_identities).await;
+ populate_routers_eth(rita_identities, exit_root_addr).await;
// Test for network convergence
test_reach_all(namespaces.clone());
diff --git a/integration_tests/src/payments_althea.rs b/integration_tests/src/payments_althea.rs
index 65627b65a..bceaa005f 100644
--- a/integration_tests/src/payments_althea.rs
+++ b/integration_tests/src/payments_althea.rs
@@ -1,7 +1,7 @@
use crate::five_nodes::five_node_config;
use crate::registration_server::start_registration_server;
use crate::setup_utils::namespaces::*;
-use crate::setup_utils::rita::thread_spawner;
+use crate::setup_utils::rita::{spawn_exit_root, thread_spawner};
use crate::utils::{
add_exits_contract_exit_list, deploy_contracts, generate_traffic, get_althea_grpc,
get_default_settings, populate_routers_eth, print_althea_balances,
@@ -51,13 +51,17 @@ pub async fn run_althea_payments_test_scenario() {
info!("Starting registration server");
start_registration_server(db_addr).await;
- let (mut client_settings, mut exit_settings) = get_default_settings(namespaces.clone());
+ let (mut client_settings, mut exit_settings, exit_root_addr) =
+ get_default_settings(namespaces.clone());
namespaces.validate();
let res = setup_ns(namespaces.clone());
info!("Namespaces setup: {res:?}");
+ info!("Starting root server!");
+ spawn_exit_root();
+
// Modify configs to use Althea chain
let (client_settings, exit_settings) =
althea_payments_map(&mut client_settings, &mut exit_settings);
@@ -70,7 +74,7 @@ pub async fn run_althea_payments_test_scenario() {
// Add exits to the contract exit list so clients get the propers exits they can migrate to
add_exits_contract_exit_list(db_addr, rita_identities.clone()).await;
- populate_routers_eth(rita_identities.clone()).await;
+ populate_routers_eth(rita_identities.clone(), exit_root_addr).await;
// Test for network convergence
test_reach_all(namespaces.clone());
diff --git a/integration_tests/src/payments_eth.rs b/integration_tests/src/payments_eth.rs
index 4201aa763..5c6790c65 100644
--- a/integration_tests/src/payments_eth.rs
+++ b/integration_tests/src/payments_eth.rs
@@ -2,6 +2,7 @@ use crate::five_nodes::five_node_config;
use crate::registration_server::start_registration_server;
use crate::setup_utils::namespaces::setup_ns;
use crate::setup_utils::namespaces::Namespace;
+use crate::setup_utils::rita::spawn_exit_root;
use crate::setup_utils::rita::thread_spawner;
use crate::utils::add_exits_contract_exit_list;
use crate::utils::deploy_contracts;
@@ -50,7 +51,8 @@ pub async fn run_eth_payments_test_scenario() {
info!("Starting registration server");
start_registration_server(db_addr).await;
- let (mut client_settings, mut exit_settings) = get_default_settings(namespaces.clone());
+ let (mut client_settings, mut exit_settings, exit_root_addr) =
+ get_default_settings(namespaces.clone());
// Set payment thresholds low enough so that they get triggered after an iperf
let (client_settings, exit_settings) =
@@ -61,6 +63,9 @@ pub async fn run_eth_payments_test_scenario() {
let res = setup_ns(namespaces.clone());
info!("Namespaces setup: {res:?}");
+ info!("Starting root server!");
+ spawn_exit_root();
+
let rita_identities =
thread_spawner(namespaces.clone(), client_settings, exit_settings, db_addr)
.expect("Could not spawn Rita threads");
@@ -69,7 +74,7 @@ pub async fn run_eth_payments_test_scenario() {
// Add exits to the contract exit list so clients get the propers exits they can migrate to
add_exits_contract_exit_list(db_addr, rita_identities.clone()).await;
- populate_routers_eth(rita_identities).await;
+ populate_routers_eth(rita_identities, exit_root_addr).await;
test_reach_all(namespaces.clone());
test_routes(namespaces.clone(), expected_routes);
diff --git a/integration_tests/src/setup_utils/namespaces.rs b/integration_tests/src/setup_utils/namespaces.rs
index 39c4106cf..d8aeb0c05 100644
--- a/integration_tests/src/setup_utils/namespaces.rs
+++ b/integration_tests/src/setup_utils/namespaces.rs
@@ -3,6 +3,7 @@ use nix::{
fcntl::{open, OFlag},
sys::stat::Mode,
};
+use settings::exit::EXIT_LIST_IP;
use std::collections::{HashMap, HashSet};
/// This struct holds the format for a namespace info
@@ -309,6 +310,21 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> {
&veth_exit_to_native,
],
)?;
+ // add ip address for the exit list endpoint
+ run_command(
+ "ip",
+ &[
+ "netns",
+ "exec",
+ &name.get_name(),
+ "ip",
+ "addr",
+ "add",
+ EXIT_LIST_IP,
+ "dev",
+ &veth_exit_to_native,
+ ],
+ )?;
// set default route
run_command(
"ip",
diff --git a/integration_tests/src/setup_utils/rita.rs b/integration_tests/src/setup_utils/rita.rs
index da6df1aae..7dfdbd0d4 100644
--- a/integration_tests/src/setup_utils/rita.rs
+++ b/integration_tests/src/setup_utils/rita.rs
@@ -4,9 +4,17 @@ use super::babel::spawn_babel;
use super::namespaces::get_nsfd;
use super::namespaces::NamespaceInfo;
use super::namespaces::NodeType;
+use actix_web::rt::System;
+use actix_web::web;
+use actix_web::App;
+use actix_web::HttpServer;
use althea_kernel_interface::KernelInterfaceError;
use althea_types::Identity;
+use althea_types::SignedExitServerList;
use clarity::Address;
+use exit_trust_root;
+use exit_trust_root::return_exit_contract_data;
+use exit_trust_root::signature_update_loop;
use ipnetwork::IpNetwork;
use ipnetwork::Ipv6Network;
use log::info;
@@ -24,6 +32,9 @@ use rita_exit::{
};
use settings::set_flag_config;
use settings::{client::RitaClientSettings, exit::RitaExitSettingsStruct};
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::Ordering::Relaxed;
+use std::time::Instant;
use std::{
collections::{HashMap, HashSet},
convert::TryInto,
@@ -275,3 +286,42 @@ pub fn spawn_rita_exit(
let val = router_identity_ref_local.read().unwrap().unwrap();
val
}
+
+/// Spawns the exit root server and waits for it to finish starting, panics if it does not finish starting
+pub fn spawn_exit_root() {
+ let successful_start: Arc = Arc::new(AtomicBool::new(false));
+ let start_move = successful_start.clone();
+ // the exit root server does not get its own namespace- instead it runs in the native namespace/host
+ let exit_contract_data_cache: Arc>> =
+ Arc::new(RwLock::new(HashMap::new()));
+ signature_update_loop(exit_contract_data_cache.clone());
+ let web_data = web::Data::new(exit_contract_data_cache.clone());
+ thread::spawn(move || {
+ let successful_start = start_move.clone();
+ let runner = System::new();
+ runner.block_on(async move {
+ let server = HttpServer::new(move || {
+ App::new()
+ .service(return_exit_contract_data)
+ .app_data(web_data.clone())
+ });
+ info!("Starting exit trust root server on 10.0.0.1:4050");
+ let server = server
+ .bind("10.0.0.1:4050")
+ .unwrap();
+ successful_start.store(true, Relaxed);
+ let _ = server.run().await;
+ });
+ });
+
+ const FAILURE_TIME: Duration = Duration::from_secs(10);
+ let start = Instant::now();
+ while start.elapsed() < FAILURE_TIME {
+ if successful_start.clone().load(Relaxed) {
+ break;
+ }
+ }
+ if !successful_start.load(Relaxed) {
+ panic!("Exit root server failed to start!");
+ }
+}
diff --git a/integration_tests/src/utils.rs b/integration_tests/src/utils.rs
index 95c30959c..7e7f37686 100644
--- a/integration_tests/src/utils.rs
+++ b/integration_tests/src/utils.rs
@@ -21,7 +21,7 @@ use althea_types::{
};
use awc::http::StatusCode;
use babel_monitor::{open_babel_stream, parse_routes, structs::Route};
-use clarity::{Address, Transaction, Uint256};
+use clarity::{Address, PrivateKey as ClarityPrivkey, Transaction, Uint256};
use deep_space::{Address as AltheaAddress, Coin, Contact, CosmosPrivateKey, PrivateKey};
use futures::future::join_all;
use ipnetwork::IpNetwork;
@@ -379,12 +379,17 @@ pub const EXIT_ROOT_IP: IpAddr =
IpAddr::V6(Ipv6Addr::new(0xfd00, 200, 199, 198, 197, 196, 195, 194));
// this masks public ipv6 ips in the test env and is being used to test assignment
pub const EXIT_SUBNET: Ipv6Addr = Ipv6Addr::new(0xfbad, 200, 0, 0, 0, 0, 0, 0);
+pub const EXIT_ROOT_SERVER_URL: &str = "http://10.0.0.1:4050";
/// Gets the default client and exit settings
pub fn get_default_settings(
namespaces: NamespaceInfo,
-) -> (RitaClientSettings, RitaExitSettingsStruct) {
+) -> (RitaClientSettings, RitaExitSettingsStruct, Address) {
let mut exit_servers = HashMap::new();
+ // generate keys for the exit root server
+ let exit_root_privkey = ClarityPrivkey::from_bytes([1u8; 32]).unwrap();
+ let exit_root_addr = exit_root_privkey.to_address();
+ info!("Exit root address is {:?}", exit_root_addr);
let exit = RitaExitSettingsStruct {
client_registration_url: "https://7.7.7.1:40400/register_router".to_string(),
workers: 2,
@@ -397,6 +402,8 @@ pub fn get_default_settings(
allowed_countries: HashSet::new(),
log: LoggingSettings::default(),
operator: ExitOperatorSettings::default(),
+ allowed_exit_list_signatures: vec![exit_root_addr],
+ exit_root_url: EXIT_ROOT_SERVER_URL.to_owned(),
};
let client = RitaClientSettings::default();
@@ -435,17 +442,20 @@ pub fn get_default_settings(
}
.into(),
);
+ // todo bootstrapping exits should be removed to test the exit root server?
client
.exit_client
.bootstrapping_exits
.clone_from(&exit_servers);
+ client.exit_client.allowed_exit_list_signatures = vec![exit_root_addr];
+
// first node is passed through to the host machine for testing second node is used
// for testnet queries
exit.payment.althea_grpc_list = vec![get_althea_grpc()];
exit.payment.eth_node_list = vec![get_eth_node()];
client.payment.althea_grpc_list = vec![get_althea_grpc()];
client.payment.eth_node_list = vec![get_eth_node()];
- (client, exit)
+ (client, exit, exit_root_addr)
}
pub fn althea_system_chain_client(settings: RitaClientSettings) -> RitaClientSettings {
@@ -1130,7 +1140,7 @@ pub async fn register_all_namespaces_to_exit(namespaces: NamespaceInfo) {
}
}
-pub async fn populate_routers_eth(rita_identities: InstanceData) {
+pub async fn populate_routers_eth(rita_identities: InstanceData, exit_root_addr: Address) {
// Exits need to have funds to request a registered client list, which is needed for proper setup
info!("Topup exits with funds");
let web3 = Web3::new(&get_eth_node(), WEB3_TIMEOUT);
@@ -1141,8 +1151,9 @@ pub async fn populate_routers_eth(rita_identities: InstanceData) {
for e in rita_identities.exit_identities {
to_top_up.push(e.eth_address)
}
+ to_top_up.push(exit_root_addr);
- info!("Sending 50 eth to all routers");
+ info!("Sending 50 eth to all routers and exit root server");
send_eth_bulk((ONE_ETH * 50).into(), &to_top_up, &web3).await;
}
diff --git a/rita_client/src/exit_manager/exit_loop.rs b/rita_client/src/exit_manager/exit_loop.rs
index a5ee89819..792f1ed96 100644
--- a/rita_client/src/exit_manager/exit_loop.rs
+++ b/rita_client/src/exit_manager/exit_loop.rs
@@ -14,12 +14,12 @@ use crate::traffic_watcher::{query_exit_debts, QueryExitDebts};
use actix_async::System as AsyncSystem;
use althea_kernel_interface::ip_addr::setup_ipv6_slaac as setup_ipv6_slaac_ki;
use althea_kernel_interface::ip_route::get_default_route;
+use althea_kernel_interface::netns::get_namespace;
use althea_kernel_interface::run_command;
use althea_types::ExitDetails;
use althea_types::ExitServerList;
use althea_types::{ExitIdentity, ExitState};
use rita_common::blockchain_oracle::low_balance;
-use std::net::IpAddr;
use std::thread;
use std::time::{Duration, Instant};
@@ -99,7 +99,7 @@ async fn exit_manager_loop(em_state: &mut ExitManager, babel_port: u16) {
info!("We have details for the selected exit!");
// TODO setup exit using old selected exit the first run, of the loop, right now we force a wait
// for this request to complete before we get things setup, we can store the ExitIdentity somewhere
- handle_exit_switching(em_state, current_exit_ip, babel_port).await;
+ handle_exit_switching(em_state, babel_port).await;
setup_exit_tunnel(
em_state.exit_switcher_state.currently_selected.clone(),
@@ -129,16 +129,13 @@ async fn exit_manager_loop(em_state: &mut ExitManager, babel_port: u16) {
}
/// This function handles deciding if we need to switch exits, the new selected exit is returned. If no exit is selected, the current exit is returned.
-async fn handle_exit_switching(
- em_state: &mut ExitManager,
- current_exit_id: IpAddr,
- babel_port: u16,
-) {
+async fn handle_exit_switching(em_state: &mut ExitManager, babel_port: u16) {
// Get cluster exit list. This is saved locally and updated every tick depending on what exit we connect to.
// When it is empty, it means an exit we connected to went down, and we use the list from memory to connect to a new instance
- let exit_list = match get_exit_list(current_exit_id).await {
+ let exit_list = match get_exit_list().await {
Ok(a) => {
- info!("Received an exit list: {:?}", a);
+ let ns: String = get_namespace().unwrap();
+ error!("ns {:?} Received an exit list: {:?}", ns, a);
a.data
}
Err(e) => {
diff --git a/rita_client/src/exit_manager/requests.rs b/rita_client/src/exit_manager/requests.rs
index 92d39b61b..7321e4041 100644
--- a/rita_client/src/exit_manager/requests.rs
+++ b/rita_client/src/exit_manager/requests.rs
@@ -10,8 +10,12 @@ use althea_types::WgKey;
use althea_types::{ExitClientIdentity, ExitRegistrationDetails, ExitState};
use settings::exit::EXIT_LIST_IP;
use settings::exit::EXIT_LIST_PORT;
+use settings::get_rita_client;
use settings::set_rita_client;
use std::net::{IpAddr, SocketAddr};
+use std::time::Duration;
+
+const EXIT_LIST_TIMEOUT: Duration = Duration::from_secs(20);
async fn send_exit_setup_request(
exit_pubkey: WgKey,
@@ -208,54 +212,15 @@ pub async fn exit_status_request(exit: IpAddr) -> Result<(), RitaClientError> {
Ok(())
}
-/// Hits the exit_list endpoint for a given exit.
-pub async fn get_exit_list(exit: IpAddr) -> Result {
- let current_exit = match settings::get_rita_client()
- .exit_client
- .bootstrapping_exits
- .get(&exit)
- {
- Some(current_exit) => current_exit.clone(),
- None => {
- return Err(RitaClientError::NoExitError(exit.to_string()));
- }
- };
-
- let exit_pubkey = current_exit.wg_key;
- let reg_details = match settings::get_rita_client().payment.contact_info {
- Some(val) => val.into(),
- None => {
- return Err(RitaClientError::MiscStringError(
- "No valid details".to_string(),
- ))
- }
- };
- let ident = ExitClientIdentity {
- global: match settings::get_rita_client().get_identity() {
- Some(id) => id,
- None => {
- return Err(RitaClientError::MiscStringError(
- "Identity has no mesh IP ready yet".to_string(),
- ));
- }
- },
- wg_port: DEFAULT_WG_LISTEN_PORT,
- reg_details,
- };
-
- // todo formatting for the exit list endpoint
+/// Hits the exit_list endpoint
+pub async fn get_exit_list() -> Result {
let endpoint = format!("http://{}:{}/exit_list", EXIT_LIST_IP, EXIT_LIST_PORT);
- let settings = settings::get_rita_client();
- let our_pubkey = settings.network.wg_public_key.unwrap();
- let our_privkey = settings.network.wg_private_key.unwrap();
-
- let ident = encrypt_exit_client_id(our_pubkey, &our_privkey.into(), &exit_pubkey.into(), ident);
let client = awc::Client::default();
let response = client
.post(&endpoint)
- .timeout(CLIENT_LOOP_TIMEOUT)
- .send_json(&ident)
+ .timeout(EXIT_LIST_TIMEOUT)
+ .send()
.await;
let response = match response {
Ok(mut response) => response.json().await,
@@ -278,13 +243,15 @@ pub async fn get_exit_list(exit: IpAddr) -> Result Ok(list),
- false => Err(RitaClientError::MiscStringError(
- "Failed to verify exit list signature!".to_owned(),
- )),
+ let config = get_rita_client();
+ let allowed_signers = config.exit_client.allowed_exit_list_signatures;
+ for key in allowed_signers {
+ error!("Verifying exit list with key {:?}", key);
+ if list.verify(key) {
+ return Ok(list);
+ }
}
+ Err(RitaClientError::MiscStringError(
+ "Failed to verify exit list signature!".to_owned(),
+ ))
}
diff --git a/rita_exit/Cargo.toml b/rita_exit/Cargo.toml
index 79704e069..a5093c393 100644
--- a/rita_exit/Cargo.toml
+++ b/rita_exit/Cargo.toml
@@ -7,6 +7,7 @@ license = "Apache-2.0"
[dependencies]
num256 = "0.5"
+exit_trust_root = { path = "../exit_trust_root" }
rita_common = { path = "../rita_common" }
rita_client_registration = { path = "../rita_client_registration" }
althea_kernel_interface = { path = "../althea_kernel_interface" }
diff --git a/rita_exit/src/network_endpoints/mod.rs b/rita_exit/src/network_endpoints/mod.rs
index 545e0ba15..d2c73daa2 100644
--- a/rita_exit/src/network_endpoints/mod.rs
+++ b/rita_exit/src/network_endpoints/mod.rs
@@ -19,7 +19,6 @@ use reqwest::ClientBuilder;
use rita_common::blockchain_oracle::potential_payment_issues_detected;
use rita_common::debt_keeper::get_debts_list;
use rita_common::rita_loop::get_web3_server;
-use settings::exit::EXIT_LIST_IP;
use settings::get_rita_exit;
use std::net::SocketAddr;
use std::time::Duration;
@@ -195,33 +194,44 @@ pub async fn get_exit_list() -> HttpResponse {
let contract_addr = rita_exit.exit_network.registered_users_contract_addr;
// we are receiving a SignedExitServerList from the root server.
- let signed_list: SignedExitServerList =
- match get_exit_list_from_root(contract_addr).await {
- Some(a) => a,
- None => {
- return HttpResponse::InternalServerError()
- .json("Failed to get exit list from root server");
- }
- };
+ let signed_list: SignedExitServerList = match get_exit_list_from_root(contract_addr).await {
+ Some(a) => a,
+ None => {
+ return HttpResponse::InternalServerError()
+ .json("Failed to get exit list from root server!");
+ }
+ };
HttpResponse::Ok().json(signed_list)
}
-async fn get_exit_list_from_root(
- contract_addr: Address,
-) -> Option {
- let request_url = format!("https://{}/{}", EXIT_LIST_IP, contract_addr);
+async fn get_exit_list_from_root(contract_addr: Address) -> Option {
+ let rita_exit = get_rita_exit();
+ let request_url = rita_exit.exit_root_url;
+ let allowed_signers = rita_exit.allowed_exit_list_signatures;
let timeout = Duration::new(15, 0);
let client = ClientBuilder::new().timeout(timeout).build().unwrap();
+ let request_url = format!("{}/{}", request_url, contract_addr);
+ info!("Requesting exit list from {}", request_url);
let response = client
- .head(request_url)
+ .get(request_url)
.send()
.await
.expect("Could not receive data from exit root server");
if response.status().is_success() {
info!("Received an exit list");
- match response.json().await {
- Ok(a) => Some(a),
+ match response.json::().await {
+ Ok(a) => {
+ // verify the signature of the exit list
+ for signer in allowed_signers {
+ if a.verify(signer) {
+ info!("Verified exit list signature");
+ return Some(a);
+ }
+ }
+ error!("Failed to verify exit list signature");
+ None
+ }
Err(e) => {
error!("Failed to parse exit list from root server {:?}", e);
None
diff --git a/settings/src/client.rs b/settings/src/client.rs
index 05d0202b9..8ba273933 100644
--- a/settings/src/client.rs
+++ b/settings/src/client.rs
@@ -5,6 +5,7 @@ use crate::payment::PaymentSettings;
use crate::{json_merge, set_rita_client, SettingsError};
use althea_types::regions::Regions;
use althea_types::{ExitIdentity, ExitState, Identity};
+use clarity::Address;
use std::collections::{HashMap, HashSet};
use std::net::IpAddr;
use std::path::{Path, PathBuf};
@@ -61,6 +62,8 @@ pub struct ExitClientSettings {
/// For example if we have a None value, we will connect to other exits with no specified region, but not ones that specify a region lock.
/// If we have some value we will connect to exits that have that region specified as well as exits with no region specified.
pub our_region: Option,
+ /// This is the Address/Pubkey of the exit root of trust server which clients use to verify signed exit lists
+ pub allowed_exit_list_signatures: Vec,
}
impl Default for ExitClientSettings {
@@ -71,6 +74,7 @@ impl Default for ExitClientSettings {
exit_db_smart_contract_on_xdai: exit_db_smart_contract_on_xdai(),
lan_nics: HashSet::new(),
our_region: None,
+ allowed_exit_list_signatures: Vec::new(),
}
}
}
diff --git a/settings/src/exit.rs b/settings/src/exit.rs
index 8bc5a2a35..72e195a2e 100644
--- a/settings/src/exit.rs
+++ b/settings/src/exit.rs
@@ -13,8 +13,8 @@ use std::path::{Path, PathBuf};
pub const APP_NAME: &str = "rita_exit";
-// IP serving exit lists from the root server
-pub const EXIT_LIST_IP: &str = "10.10.10.10";
+// IP serving exit lists from the root server back to clients
+pub const EXIT_LIST_IP: &str = "10.11.12.13";
/// This is the port which exit lists are served over
pub const EXIT_LIST_PORT: u16 = 5566;
/// This is the network settings specific to rita_exit
@@ -118,6 +118,10 @@ pub struct RitaExitSettingsStruct {
/// (ISO country code)
#[serde(skip_serializing_if = "HashSet::is_empty", default)]
pub allowed_countries: HashSet,
+ /// This is the Address/Pubkey of the exit root of trust server which clients use to verify signed exit lists
+ pub allowed_exit_list_signatures: Vec,
+ /// url to the exit root of trust server to query exit lists
+ pub exit_root_url: String,
}
impl RitaExitSettingsStruct {
@@ -141,6 +145,8 @@ impl RitaExitSettingsStruct {
exit_network: ExitNetworkSettings::test_default(),
allowed_countries: HashSet::new(),
log: LoggingSettings::default(),
+ allowed_exit_list_signatures: Vec::new(),
+ exit_root_url: "".to_string(),
}
}
diff --git a/test_runner/src/main.rs b/test_runner/src/main.rs
index 331a1891a..3ba0d49d4 100644
--- a/test_runner/src/main.rs
+++ b/test_runner/src/main.rs
@@ -28,6 +28,8 @@ async fn main() {
env_logger::Builder::default()
.filter(None, log::LevelFilter::Error)
.filter(Some("integration_tests"), log::LevelFilter::Info)
+ .filter(Some("rita_exit"), log::LevelFilter::Info)
+ .filter(Some("exit_trust_root"), log::LevelFilter::Info)
.init();
set_sigterm();