From 6abc623eaf0d224f0b3f3aa13c6508c1f5a18cd7 Mon Sep 17 00:00:00 2001 From: Dzejkop Date: Wed, 13 Dec 2023 01:24:33 +0100 Subject: [PATCH] Allow preconfigured networks & relayers --- config.toml | 10 ++++ src/config.rs | 40 ++++++++++++- src/keys.rs | 125 ++--------------------------------------- src/keys/kms_keys.rs | 59 +++++++++++++++++++ src/keys/local_keys.rs | 69 +++++++++++++++++++++++ src/service.rs | 47 ++++++++++++++++ tests/common/mod.rs | 50 +++++++++++------ tests/rpc_access.rs | 9 +-- tests/send_many_txs.rs | 41 ++------------ tests/send_tx.rs | 39 +------------ 10 files changed, 269 insertions(+), 220 deletions(-) create mode 100644 src/keys/kms_keys.rs create mode 100644 src/keys/local_keys.rs diff --git a/config.toml b/config.toml index f2f5ec2..d331092 100644 --- a/config.toml +++ b/config.toml @@ -3,6 +3,16 @@ escalation_interval = "1m" datadog_enabled = false statsd_enabled = false +[[predefined_networks]] +chain_id = 31337 +http_url = "http://127.0.0.1:8545" +ws_url = "ws://127.0.0.1:8545" + +[[predefined_relayers]] +id = "1b908a34-5dc1-4d2d-a146-5eb46e975830" +chain_id = 31337 +key_id = "d10607662a85424f02a33fb1e6d095bd0ac7154396ff09762e41f82ff2233aaa" + [server] host = "127.0.0.1:3000" disable_auth = false diff --git a/src/config.rs b/src/config.rs index 53ad624..f2748ea 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,6 +23,30 @@ pub struct TxSitterConfig { #[serde(default)] pub statsd_enabled: bool, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub predefined_networks: Vec, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub predefined_relayers: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct PredefinedNetwork { + pub chain_id: u64, + pub name: String, + pub http_rpc: String, + pub ws_rpc: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct PredefinedRelayer { + pub id: String, + pub name: String, + pub key_id: String, + pub chain_id: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -101,10 +125,16 @@ pub enum KeysConfig { #[serde(rename_all = "snake_case")] pub struct KmsKeysConfig {} -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct LocalKeysConfig {} +impl KeysConfig { + pub fn is_local(&self) -> bool { + matches!(self, Self::Local(_)) + } +} + #[cfg(test)] mod tests { use indoc::indoc; @@ -156,6 +186,8 @@ mod tests { escalation_interval: Duration::from_secs(60 * 60), datadog_enabled: false, statsd_enabled: false, + predefined_networks: vec![], + predefined_relayers: vec![], }, server: ServerConfig { host: SocketAddr::from(([127, 0, 0, 1], 3000)), @@ -166,7 +198,7 @@ mod tests { "postgres://postgres:postgres@127.0.0.1:52804/database" .to_string(), ), - keys: KeysConfig::Local(LocalKeysConfig {}), + keys: KeysConfig::Local(LocalKeysConfig::default()), }; let toml = toml::to_string_pretty(&config).unwrap(); @@ -181,6 +213,8 @@ mod tests { escalation_interval: Duration::from_secs(60 * 60), datadog_enabled: false, statsd_enabled: false, + predefined_networks: vec![], + predefined_relayers: vec![], }, server: ServerConfig { host: SocketAddr::from(([127, 0, 0, 1], 3000)), @@ -194,7 +228,7 @@ mod tests { password: "pass".to_string(), database: "db".to_string(), }), - keys: KeysConfig::Local(LocalKeysConfig {}), + keys: KeysConfig::Local(LocalKeysConfig::default()), }; let toml = toml::to_string_pretty(&config).unwrap(); diff --git a/src/keys.rs b/src/keys.rs index 01c4647..3dc171c 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,14 +1,10 @@ -use aws_config::BehaviorVersion; -use aws_sdk_kms::types::{KeySpec, KeyUsageType}; -use ethers::core::k256::ecdsa::SigningKey; -use ethers::signers::Wallet; -use eyre::{Context, ContextCompat}; -pub use universal_signer::UniversalSigner; - -use crate::aws::ethers_signer::AwsSigner; -use crate::config::{KmsKeysConfig, LocalKeysConfig}; +pub mod kms_keys; +pub mod local_keys; +pub mod universal_signer; -mod universal_signer; +pub use kms_keys::KmsKeys; +pub use local_keys::LocalKeys; +pub use universal_signer::UniversalSigner; #[async_trait::async_trait] pub trait KeysSource: Send + Sync + 'static { @@ -18,112 +14,3 @@ pub trait KeysSource: Send + Sync + 'static { /// Loads the key using the provided id async fn load_signer(&self, id: String) -> eyre::Result; } - -pub struct KmsKeys { - kms_client: aws_sdk_kms::Client, -} - -impl KmsKeys { - pub async fn new(_config: &KmsKeysConfig) -> eyre::Result { - let aws_config = - aws_config::load_defaults(BehaviorVersion::latest()).await; - - let kms_client = aws_sdk_kms::Client::new(&aws_config); - - Ok(Self { kms_client }) - } -} - -#[async_trait::async_trait] -impl KeysSource for KmsKeys { - async fn new_signer(&self) -> eyre::Result<(String, UniversalSigner)> { - let kms_key = self - .kms_client - .create_key() - .key_spec(KeySpec::EccSecgP256K1) - .key_usage(KeyUsageType::SignVerify) - .send() - .await - .context("AWS Error")?; - - let key_id = - kms_key.key_metadata.context("Missing key metadata")?.key_id; - - let signer = AwsSigner::new( - self.kms_client.clone(), - key_id.clone(), - 1, // TODO: get chain id from provider - ) - .await?; - - Ok((key_id, UniversalSigner::Aws(signer))) - } - - async fn load_signer(&self, id: String) -> eyre::Result { - let signer = AwsSigner::new( - self.kms_client.clone(), - id.clone(), - 1, // TODO: get chain id from provider - ) - .await?; - - Ok(UniversalSigner::Aws(signer)) - } -} - -pub struct LocalKeys { - rng: rand::rngs::OsRng, -} - -impl LocalKeys { - pub fn new(_config: &LocalKeysConfig) -> Self { - Self { - rng: rand::rngs::OsRng, - } - } -} - -#[async_trait::async_trait] -impl KeysSource for LocalKeys { - async fn new_signer(&self) -> eyre::Result<(String, UniversalSigner)> { - let signing_key = SigningKey::random(&mut self.rng.clone()); - - let key_id = signing_key.to_bytes().to_vec(); - let key_id = hex::encode(key_id); - - let signer = Wallet::from(signing_key); - - Ok((key_id, UniversalSigner::Local(signer))) - } - - async fn load_signer(&self, id: String) -> eyre::Result { - let key_id = hex::decode(id)?; - let signing_key = SigningKey::from_slice(key_id.as_slice())?; - - let signer = Wallet::from(signing_key); - - Ok(UniversalSigner::Local(signer)) - } -} - -#[cfg(test)] -mod tests { - use ethers::signers::Signer; - - use super::*; - - #[tokio::test] - async fn local_roundtrip() -> eyre::Result<()> { - let keys_source = LocalKeys::new(&LocalKeysConfig {}); - - let (id, signer) = keys_source.new_signer().await?; - - let address = signer.address(); - - let signer = keys_source.load_signer(id).await?; - - assert_eq!(address, signer.address()); - - Ok(()) - } -} diff --git a/src/keys/kms_keys.rs b/src/keys/kms_keys.rs new file mode 100644 index 0000000..baabb29 --- /dev/null +++ b/src/keys/kms_keys.rs @@ -0,0 +1,59 @@ +use aws_config::BehaviorVersion; +use aws_sdk_kms::types::{KeySpec, KeyUsageType}; +use eyre::{Context, ContextCompat}; + +use super::{KeysSource, UniversalSigner}; +use crate::aws::ethers_signer::AwsSigner; +use crate::config::KmsKeysConfig; + +pub struct KmsKeys { + kms_client: aws_sdk_kms::Client, +} + +impl KmsKeys { + pub async fn new(_config: &KmsKeysConfig) -> eyre::Result { + let aws_config = + aws_config::load_defaults(BehaviorVersion::latest()).await; + + let kms_client = aws_sdk_kms::Client::new(&aws_config); + + Ok(Self { kms_client }) + } +} + +#[async_trait::async_trait] +impl KeysSource for KmsKeys { + async fn new_signer(&self) -> eyre::Result<(String, UniversalSigner)> { + let kms_key = self + .kms_client + .create_key() + .key_spec(KeySpec::EccSecgP256K1) + .key_usage(KeyUsageType::SignVerify) + .send() + .await + .context("AWS Error")?; + + let key_id = + kms_key.key_metadata.context("Missing key metadata")?.key_id; + + let signer = AwsSigner::new( + self.kms_client.clone(), + key_id.clone(), + 1, // TODO: get chain id from provider + ) + .await?; + + Ok((key_id, UniversalSigner::Aws(signer))) + } + + async fn load_signer(&self, id: String) -> eyre::Result { + let signer = AwsSigner::new( + self.kms_client.clone(), + id.clone(), + 1, // TODO: get chain id from provider + ) + .await?; + + Ok(UniversalSigner::Aws(signer)) + } +} diff --git a/src/keys/local_keys.rs b/src/keys/local_keys.rs new file mode 100644 index 0000000..8b6e334 --- /dev/null +++ b/src/keys/local_keys.rs @@ -0,0 +1,69 @@ +use ethers::core::k256::ecdsa::SigningKey; +use ethers::signers::Wallet; + +use super::universal_signer::UniversalSigner; +use super::KeysSource; +use crate::config::LocalKeysConfig; + +pub struct LocalKeys { + rng: rand::rngs::OsRng, +} + +impl LocalKeys { + pub fn new(_config: &LocalKeysConfig) -> Self { + Self { + rng: rand::rngs::OsRng, + } + } +} + +#[async_trait::async_trait] +impl KeysSource for LocalKeys { + async fn new_signer(&self) -> eyre::Result<(String, UniversalSigner)> { + let signing_key = SigningKey::random(&mut self.rng.clone()); + + let key_id = signing_key.to_bytes().to_vec(); + let key_id = hex::encode(key_id); + + let signer = Wallet::from(signing_key); + + Ok((key_id, UniversalSigner::Local(signer))) + } + + async fn load_signer(&self, id: String) -> eyre::Result { + let signing_key = signing_key_from_hex(&id)?; + + let signer = Wallet::from(signing_key); + + Ok(UniversalSigner::Local(signer)) + } +} + +pub fn signing_key_from_hex(s: &str) -> eyre::Result { + let key_id = hex::decode(s)?; + let signing_key = SigningKey::from_slice(key_id.as_slice())?; + + Ok(signing_key) +} + +#[cfg(test)] +mod tests { + use ethers::signers::Signer; + + use super::*; + + #[tokio::test] + async fn local_roundtrip() -> eyre::Result<()> { + let keys_source = LocalKeys::new(&LocalKeysConfig::default()); + + let (id, signer) = keys_source.new_signer().await?; + + let address = signer.address(); + + let signer = keys_source.load_signer(id).await?; + + assert_eq!(address, signer.address()); + + Ok(()) + } +} diff --git a/src/service.rs b/src/service.rs index 599b0ac..4cc35c8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,10 +1,12 @@ use std::net::SocketAddr; use std::sync::Arc; +use ethers::signers::{Signer, Wallet}; use tokio::task::JoinHandle; use crate::app::App; use crate::config::Config; +use crate::keys::local_keys::signing_key_from_hex; use crate::task_runner::TaskRunner; use crate::tasks; @@ -44,6 +46,8 @@ impl Service { Ok(()) }); + initialize_predefined_values(&app).await?; + Ok(Self { _app: app, local_addr, @@ -78,3 +82,46 @@ impl Service { Ok(()) } } + +async fn initialize_predefined_values( + app: &Arc, +) -> Result<(), eyre::Error> { + if !app.config.service.predefined_relayers.is_empty() + && !app.config.keys.is_local() + { + eyre::bail!("Predefined relayers are only supported with local keys"); + } + + for predefined_network in &app.config.service.predefined_networks { + app.db + .create_network( + predefined_network.chain_id, + &predefined_network.name, + &predefined_network.http_rpc, + &predefined_network.ws_rpc, + ) + .await?; + + let task_runner = TaskRunner::new(app.clone()); + Service::spawn_chain_tasks(&task_runner, predefined_network.chain_id)?; + } + + for predefined_relayer in &app.config.service.predefined_relayers { + let secret_key = signing_key_from_hex(&predefined_relayer.key_id)?; + + let signer = Wallet::from(secret_key); + let address = signer.address(); + + app.db + .create_relayer( + &predefined_relayer.id, + &predefined_relayer.name, + predefined_relayer.chain_id, + &predefined_relayer.key_id, + address, + ) + .await?; + } + + Ok(()) +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index c24a844..ca338e5 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -18,10 +18,9 @@ use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::EnvFilter; use tx_sitter::client::TxSitterClient; use tx_sitter::config::{ - Config, DatabaseConfig, KeysConfig, LocalKeysConfig, ServerConfig, - TxSitterConfig, + Config, DatabaseConfig, KeysConfig, LocalKeysConfig, PredefinedNetwork, + PredefinedRelayer, ServerConfig, TxSitterConfig, }; -use tx_sitter::server::routes::network::NewNetworkInfo; use tx_sitter::service::Service; pub type AppMiddleware = SignerMiddleware>, LocalWallet>; @@ -49,12 +48,18 @@ pub const DEFAULT_ANVIL_PRIVATE_KEY: &[u8] = &hex_literal::hex!( "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" ); +pub const SECONDARY_ANVIL_PRIVATE_KEY: &[u8] = &hex_literal::hex!( + "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" +); + pub const ARBITRARY_ADDRESS: Address = H160(hex_literal::hex!( "1Ed53d680B8890DAe2a63f673a85fFDE1FD5C7a2" )); pub const DEFAULT_ANVIL_CHAIN_ID: u64 = 31337; +pub const DEFAULT_RELAYER_ID: &str = "1b908a34-5dc1-4d2d-a146-5eb46e975830"; + pub struct DoubleAnvilHandle { pub double_anvil: Arc, ws_addr: String, @@ -104,7 +109,7 @@ pub async fn setup_double_anvil() -> eyre::Result { let middleware = setup_middleware( format!("http://{local_addr}"), - DEFAULT_ANVIL_PRIVATE_KEY, + SECONDARY_ANVIL_PRIVATE_KEY, ) .await?; @@ -138,11 +143,25 @@ pub async fn setup_service( ) -> eyre::Result<(Service, TxSitterClient)> { let rpc_url = anvil_handle.local_addr(); + let anvil_private_key = hex::encode(DEFAULT_ANVIL_PRIVATE_KEY); + let config = Config { service: TxSitterConfig { escalation_interval, datadog_enabled: false, statsd_enabled: false, + predefined_networks: vec![PredefinedNetwork { + chain_id: DEFAULT_ANVIL_CHAIN_ID, + name: "Anvil".to_string(), + http_rpc: format!("http://{}", rpc_url), + ws_rpc: anvil_handle.ws_addr(), + }], + predefined_relayers: vec![PredefinedRelayer { + name: "Anvil".to_string(), + id: DEFAULT_RELAYER_ID.to_string(), + key_id: anvil_private_key, + chain_id: DEFAULT_ANVIL_CHAIN_ID, + }], }, server: ServerConfig { host: SocketAddr::V4(SocketAddrV4::new( @@ -153,7 +172,7 @@ pub async fn setup_service( password: None, }, database: DatabaseConfig::connection_string(db_connection_url), - keys: KeysConfig::Local(LocalKeysConfig {}), + keys: KeysConfig::Local(LocalKeysConfig::default()), }; let service = Service::new(config).await?; @@ -161,17 +180,6 @@ pub async fn setup_service( let client = TxSitterClient::new(format!("http://{}", service.local_addr())); - client - .create_network( - DEFAULT_ANVIL_CHAIN_ID, - &NewNetworkInfo { - name: "Anvil".to_string(), - http_rpc: format!("http://{}", rpc_url), - ws_rpc: anvil_handle.ws_addr(), - }, - ) - .await?; - Ok((service, client)) } @@ -179,7 +187,7 @@ pub async fn setup_middleware( rpc_url: impl AsRef, private_key: &[u8], ) -> eyre::Result { - let provider = Provider::::new(rpc_url.as_ref().parse()?); + let provider = setup_provider(rpc_url).await?; let wallet = LocalWallet::from(SigningKey::from_slice(private_key)?) .with_chain_id(provider.get_chainid().await?.as_u64()); @@ -188,3 +196,11 @@ pub async fn setup_middleware( Ok(middleware) } + +pub async fn setup_provider( + rpc_url: impl AsRef, +) -> eyre::Result> { + let provider = Provider::::new(rpc_url.as_ref().parse()?); + + Ok(provider) +} diff --git a/tests/rpc_access.rs b/tests/rpc_access.rs index 81509cc..c4ab6fe 100644 --- a/tests/rpc_access.rs +++ b/tests/rpc_access.rs @@ -18,15 +18,8 @@ async fn rpc_access() -> eyre::Result<()> { let (service, client) = setup_service(&double_anvil, &db_url, ESCALATION_INTERVAL).await?; - let CreateRelayerResponse { relayer_id, .. } = client - .create_relayer(&CreateRelayerRequest { - name: "Test relayer".to_string(), - chain_id: DEFAULT_ANVIL_CHAIN_ID, - }) - .await?; - let CreateApiKeyResponse { api_key } = - client.create_relayer_api_key(&relayer_id).await?; + client.create_relayer_api_key(DEFAULT_RELAYER_ID).await?; let rpc_url = format!("http://{}/1/api/{api_key}/rpc", service.local_addr()); diff --git a/tests/send_many_txs.rs b/tests/send_many_txs.rs index 5476a3e..1e0226c 100644 --- a/tests/send_many_txs.rs +++ b/tests/send_many_txs.rs @@ -18,44 +18,11 @@ async fn send_many_txs() -> eyre::Result<()> { let (_service, client) = setup_service(&double_anvil, &db_url, ESCALATION_INTERVAL).await?; - let CreateRelayerResponse { - address: relayer_address, - relayer_id, - } = client - .create_relayer(&CreateRelayerRequest { - name: "Test relayer".to_string(), - chain_id: DEFAULT_ANVIL_CHAIN_ID, - }) - .await?; - let CreateApiKeyResponse { api_key } = - client.create_relayer_api_key(&relayer_id).await?; - - // Fund the relayer - let middleware = setup_middleware( - format!("http://{}", double_anvil.local_addr()), - DEFAULT_ANVIL_PRIVATE_KEY, - ) - .await?; - - let amount: U256 = parse_units("1000", "ether")?.into(); - - middleware - .send_transaction( - Eip1559TransactionRequest { - to: Some(relayer_address.into()), - value: Some(amount), - ..Default::default() - }, - None, - ) - .await? - .await?; - - let provider = middleware.provider(); - - let current_balance = provider.get_balance(relayer_address, None).await?; - assert_eq!(current_balance, amount); + client.create_relayer_api_key(DEFAULT_RELAYER_ID).await?; + + let provider = + setup_provider(format!("http://{}", double_anvil.local_addr())).await?; // Send a transaction let value: U256 = parse_units("10", "ether")?.into(); diff --git a/tests/send_tx.rs b/tests/send_tx.rs index cff3991..b4236ca 100644 --- a/tests/send_tx.rs +++ b/tests/send_tx.rs @@ -16,44 +16,11 @@ async fn send_tx() -> eyre::Result<()> { let (_service, client) = setup_service(&double_anvil, &db_url, ESCALATION_INTERVAL).await?; - let CreateRelayerResponse { - address: relayer_address, - relayer_id, - } = client - .create_relayer(&CreateRelayerRequest { - name: "Test relayer".to_string(), - chain_id: DEFAULT_ANVIL_CHAIN_ID, - }) - .await?; - let CreateApiKeyResponse { api_key } = - client.create_relayer_api_key(&relayer_id).await?; - - // Fund the relayer - let middleware = setup_middleware( - format!("http://{}", double_anvil.local_addr()), - DEFAULT_ANVIL_PRIVATE_KEY, - ) - .await?; - - let amount: U256 = parse_units("100", "ether")?.into(); - - middleware - .send_transaction( - Eip1559TransactionRequest { - to: Some(relayer_address.into()), - value: Some(amount), - ..Default::default() - }, - None, - ) - .await? - .await?; - - let provider = middleware.provider(); + client.create_relayer_api_key(DEFAULT_RELAYER_ID).await?; - let current_balance = provider.get_balance(relayer_address, None).await?; - assert_eq!(current_balance, amount); + let provider = + setup_provider(format!("http://{}", double_anvil.local_addr())).await?; // Send a transaction let value: U256 = parse_units("1", "ether")?.into();