diff --git a/crates/sui-bridge/src/e2e_tests/basic.rs b/crates/sui-bridge/src/e2e_tests/basic.rs index 587b7116b7490..5ab5215f5daa7 100644 --- a/crates/sui-bridge/src/e2e_tests/basic.rs +++ b/crates/sui-bridge/src/e2e_tests/basic.rs @@ -3,10 +3,8 @@ use crate::abi::{eth_sui_bridge, EthBridgeEvent, EthSuiBridge}; use crate::client::bridge_authority_aggregator::BridgeAuthorityAggregator; -use crate::e2e_tests::test_utils::{get_signatures, initialize_bridge_environment, TEST_PK}; -use crate::e2e_tests::test_utils::{ - publish_coins_return_add_coins_on_sui_action, start_bridge_cluster, -}; +use crate::e2e_tests::test_utils::publish_coins_return_add_coins_on_sui_action; +use crate::e2e_tests::test_utils::{get_signatures, BridgeTestClusterBuilder}; use crate::events::SuiBridgeEvent; use crate::sui_client::SuiBridgeClient; use crate::sui_transaction_builder::build_add_tokens_on_sui_transaction; @@ -40,27 +38,27 @@ async fn test_bridge_from_eth_to_sui_to_eth() { let eth_chain_id = BridgeChainId::EthCustom as u8; let sui_chain_id = BridgeChainId::SuiCustom as u8; - let (mut test_cluster, eth_environment) = initialize_bridge_environment(true).await; - - let (eth_signer, _) = eth_environment.get_signer(TEST_PK).await.unwrap(); - - let eth_address = eth_signer.address(); - - let sui_client = test_cluster.fullnode_handle.sui_client.clone(); + let mut bridge_test_cluster = BridgeTestClusterBuilder::new() + .with_eth_env(true) + .with_bridge_cluster(true) + .build() + .await; - let sui_bridge_client = SuiBridgeClient::new(&test_cluster.fullnode_handle.rpc_url) + let (eth_signer, eth_address) = bridge_test_cluster + .get_eth_signer_and_address() .await .unwrap(); - let sui_address = test_cluster.get_address_0(); + let sui_client = bridge_test_cluster.sui_client(); + let sui_bridge_client = bridge_test_cluster.sui_bridge_client().await.unwrap(); + let sui_address = bridge_test_cluster.sui_user_address(); let amount = 42; - // ETH coin has 8 decimals on Sui let sui_amount = amount * 100_000_000; initiate_bridge_eth_to_sui( &sui_bridge_client, ð_signer, - eth_environment.contracts().sui_bridge, + bridge_test_cluster.contracts().sui_bridge, sui_address, eth_address, eth_chain_id, @@ -98,7 +96,7 @@ async fn test_bridge_from_eth_to_sui_to_eth() { &sui_bridge_client, &sui_client, sui_address, - test_cluster.wallet_mut(), + bridge_test_cluster.wallet_mut(), eth_chain_id, sui_chain_id, eth_address_1, @@ -122,7 +120,7 @@ async fn test_bridge_from_eth_to_sui_to_eth() { .await; let eth_sui_bridge = EthSuiBridge::new( - eth_environment.contracts().sui_bridge, + bridge_test_cluster.contracts().sui_bridge, eth_signer.clone().into(), ); let tx = eth_sui_bridge.transfer_bridged_tokens_with_signatures(signatures, message); @@ -138,24 +136,27 @@ async fn test_bridge_from_eth_to_sui_to_eth() { #[tokio::test] async fn test_add_new_coins_on_sui() { telemetry_subscribers::init_for_testing(); + let mut bridge_test_cluster = BridgeTestClusterBuilder::new() + .with_eth_env(true) + .with_bridge_cluster(false) + .build() + .await; - let (mut test_cluster, eth_environment) = initialize_bridge_environment(false).await; - - let bridge_arg = test_cluster.get_mut_bridge_arg().await.unwrap(); + let bridge_arg = bridge_test_cluster.get_mut_bridge_arg().await.unwrap(); // Register tokens let token_id = 42; let token_price = 10000; - let sender = test_cluster.get_address_0(); - let tx = test_cluster + let sender = bridge_test_cluster.sui_user_address(); + let tx = bridge_test_cluster .test_transaction_builder_with_sender(sender) .await .publish(Path::new("../../bridge/move/tokens/mock/ka").into()) .build(); - let publish_token_response = test_cluster.sign_and_execute_transaction(&tx).await; + let publish_token_response = bridge_test_cluster.sign_and_execute_transaction(&tx).await; info!("Published new token"); let action = publish_coins_return_add_coins_on_sui_action( - test_cluster.wallet_mut(), + bridge_test_cluster.wallet_mut(), bridge_arg, vec![publish_token_response], vec![token_id], @@ -164,28 +165,22 @@ async fn test_add_new_coins_on_sui() { ) .await; - // TODO: do not block on `build_with_bridge`, return with bridge keys immediately - // to paralyze the setup. - let sui_bridge_client = SuiBridgeClient::new(&test_cluster.fullnode_handle.rpc_url) - .await - .unwrap(); + let sui_bridge_client = bridge_test_cluster.sui_bridge_client().await.unwrap(); info!("Starting bridge cluster"); - start_bridge_cluster( - &test_cluster, - ð_environment, - vec![ - vec![action.clone()], - vec![action.clone()], - vec![action.clone()], - vec![], - ], - ) - .await; - - test_cluster.wait_for_bridge_cluster_to_be_up(10).await; + bridge_test_cluster.set_approved_governance_actions_for_next_start(vec![ + vec![action.clone()], + vec![action.clone()], + vec![action.clone()], + vec![], + ]); + bridge_test_cluster.start_bridge_cluster().await; + bridge_test_cluster + .wait_for_bridge_cluster_to_be_up(10) + .await; info!("Bridge cluster is up"); + let bridge_committee = Arc::new( sui_bridge_client .get_bridge_committee() @@ -201,8 +196,8 @@ async fn test_add_new_coins_on_sui() { let tx = build_add_tokens_on_sui_transaction( sender, - &test_cluster - .wallet + &bridge_test_cluster + .wallet() .get_one_gas_object_owned_by_address(sender) .await .unwrap() @@ -212,7 +207,7 @@ async fn test_add_new_coins_on_sui() { ) .unwrap(); - let response = test_cluster.sign_and_execute_transaction(&tx).await; + let response = bridge_test_cluster.sign_and_execute_transaction(&tx).await; assert_eq!( response.effects.unwrap().status(), &SuiExecutionStatus::Success diff --git a/crates/sui-bridge/src/e2e_tests/test_utils.rs b/crates/sui-bridge/src/e2e_tests/test_utils.rs index 11776ae8248fb..a70d1cf77bc09 100644 --- a/crates/sui-bridge/src/e2e_tests/test_utils.rs +++ b/crates/sui-bridge/src/e2e_tests/test_utils.rs @@ -22,13 +22,17 @@ use std::process::Command; use std::str::FromStr; use sui_json_rpc_types::SuiTransactionBlockResponse; use sui_sdk::wallet_context::WalletContext; +use sui_test_transaction_builder::TestTransactionBuilder; use sui_types::bridge::{ BridgeChainId, BRIDGE_MODULE_NAME, BRIDGE_REGISTER_FOREIGN_TOKEN_FUNCTION_NAME, }; use sui_types::committee::TOTAL_VOTING_POWER; +use sui_types::crypto::get_key_pair; use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder; use sui_types::transaction::{ObjectArg, TransactionData}; use sui_types::BRIDGE_PACKAGE_ID; +use tokio::join; +use tokio::task::JoinHandle; use tracing::error; use tracing::info; @@ -66,6 +70,224 @@ const USDT_NAME: &str = "USDT"; pub const TEST_PK: &str = "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356"; +/// A helper struct that holds TestCluster and other Bridge related +/// structs that are needed for testing. +pub struct BridgeTestCluster { + pub test_cluster: TestCluster, + eth_environment: EthBridgeEnvironment, + bridge_node_handles: Option>>, + approved_governance_actions_for_next_start: Option>>, +} + +pub struct BridgeTestClusterBuilder { + with_eth_env: bool, + with_bridge_cluster: bool, + approved_governance_actions: Option>>, +} + +impl Default for BridgeTestClusterBuilder { + fn default() -> Self { + Self::new() + } +} + +impl BridgeTestClusterBuilder { + pub fn new() -> Self { + BridgeTestClusterBuilder { + with_eth_env: false, + with_bridge_cluster: false, + approved_governance_actions: None, + } + } + + pub fn with_eth_env(mut self, with_eth_env: bool) -> Self { + self.with_eth_env = with_eth_env; + self + } + + pub fn with_bridge_cluster(mut self, with_bridge_cluster: bool) -> Self { + self.with_bridge_cluster = with_bridge_cluster; + self + } + + pub fn with_approved_governance_actions( + mut self, + approved_governance_actions: Vec>, + ) -> Self { + self.approved_governance_actions = Some(approved_governance_actions); + self + } + + pub async fn build(self) -> BridgeTestCluster { + let mut bridge_keys = vec![]; + let mut bridge_keys_copy = vec![]; + for _ in 0..=3 { + let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); + bridge_keys.push(kp.copy()); + bridge_keys_copy.push(kp); + } + let start_cluster_task = tokio::task::spawn(Self::start_test_cluster(bridge_keys)); + let start_eth_env_task = tokio::task::spawn(Self::start_eth_env(bridge_keys_copy)); + let (start_cluster_res, start_eth_env_res) = join!(start_cluster_task, start_eth_env_task); + let test_cluster = start_cluster_res.unwrap(); + let eth_environment = start_eth_env_res.unwrap(); + + let mut bridge_node_handles = None; + if self.with_bridge_cluster { + let approved_governace_actions = self + .approved_governance_actions + .clone() + .unwrap_or(vec![vec![], vec![], vec![], vec![]]); + bridge_node_handles = Some( + start_bridge_cluster(&test_cluster, ð_environment, approved_governace_actions) + .await, + ); + } + + BridgeTestCluster { + test_cluster, + eth_environment, + bridge_node_handles, + approved_governance_actions_for_next_start: self.approved_governance_actions, + } + } + + async fn start_test_cluster(bridge_keys: Vec) -> TestCluster { + let test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() + .with_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) + .build_with_bridge(bridge_keys, true) + .await; + info!("Test cluster built"); + test_cluster + .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized() + .await; + info!("Bridge committee is finalized"); + test_cluster + } + + async fn start_eth_env(bridge_keys: Vec) -> EthBridgeEnvironment { + let anvil_port = get_available_port("127.0.0.1"); + let anvil_url = format!("http://127.0.0.1:{anvil_port}"); + let mut eth_environment = EthBridgeEnvironment::new(&anvil_url, anvil_port) + .await + .unwrap(); + // Give anvil a bit of time to start + tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; + let (eth_signer, eth_pk_hex) = eth_environment + .get_signer(TEST_PK) + .await + .unwrap_or_else(|e| panic!("Failed to get eth signer from anvil at {anvil_url}: {e}")); + let deployed_contracts = + deploy_sol_contract(&anvil_url, eth_signer, bridge_keys, eth_pk_hex).await; + info!("Deployed contracts: {:?}", deployed_contracts); + eth_environment.contracts = Some(deployed_contracts); + eth_environment + } +} + +impl BridgeTestCluster { + pub async fn get_eth_signer_and_private_key(&self) -> anyhow::Result<(EthSigner, String)> { + self.eth_environment.get_signer(TEST_PK).await + } + + pub async fn get_eth_signer_and_address(&self) -> anyhow::Result<(EthSigner, EthAddress)> { + let (eth_signer, _) = self.get_eth_signer_and_private_key().await?; + let eth_address = eth_signer.address(); + Ok((eth_signer, eth_address)) + } + + pub async fn sui_bridge_client(&self) -> anyhow::Result { + SuiBridgeClient::new(&self.test_cluster.fullnode_handle.rpc_url).await + } + + pub fn sui_client(&self) -> SuiClient { + self.test_cluster.fullnode_handle.sui_client.clone() + } + + pub fn sui_user_address(&self) -> SuiAddress { + self.test_cluster.get_address_0() + } + + pub fn contracts(&self) -> &DeployedSolContracts { + self.eth_environment.contracts() + } + + pub fn sui_bridge_address(&self) -> String { + self.eth_environment.contracts().sui_bridge_addrress_hex() + } + + pub fn wallet_mut(&mut self) -> &mut WalletContext { + self.test_cluster.wallet_mut() + } + + pub fn wallet(&mut self) -> &WalletContext { + &self.test_cluster.wallet + } + + pub fn bridge_authority_key(&self, index: usize) -> BridgeAuthorityKeyPair { + self.test_cluster.bridge_authority_keys.as_ref().unwrap()[index].copy() + } + + pub fn sui_rpc_url(&self) -> String { + self.test_cluster.fullnode_handle.rpc_url.clone() + } + + pub fn eth_rpc_url(&self) -> String { + self.eth_environment.rpc_url.clone() + } + + pub async fn get_mut_bridge_arg(&self) -> Option { + self.test_cluster.get_mut_bridge_arg().await + } + + pub async fn test_transaction_builder_with_sender( + &self, + sender: SuiAddress, + ) -> TestTransactionBuilder { + self.test_cluster + .test_transaction_builder_with_sender(sender) + .await + } + + pub async fn wait_for_bridge_cluster_to_be_up(&self, timeout_sec: u64) { + self.test_cluster + .wait_for_bridge_cluster_to_be_up(timeout_sec) + .await; + } + + pub async fn sign_and_execute_transaction( + &self, + tx_data: &TransactionData, + ) -> SuiTransactionBlockResponse { + self.test_cluster + .sign_and_execute_transaction(tx_data) + .await + } + + pub fn set_approved_governance_actions_for_next_start( + &mut self, + approved_governance_actions: Vec>, + ) { + self.approved_governance_actions_for_next_start = Some(approved_governance_actions); + } + + pub async fn start_bridge_cluster(&mut self) { + assert!(self.bridge_node_handles.is_none()); + let approved_governace_actions = self + .approved_governance_actions_for_next_start + .clone() + .unwrap_or(vec![vec![], vec![], vec![], vec![]]); + self.bridge_node_handles = Some( + start_bridge_cluster( + &self.test_cluster, + &self.eth_environment, + approved_governace_actions, + ) + .await, + ); + } +} + pub async fn publish_coins_return_add_coins_on_sui_action( wallet_context: &mut WalletContext, bridge_arg: ObjectArg, @@ -218,79 +440,12 @@ struct SolDeployConfig { token_prices: Vec, } -pub(crate) async fn initialize_bridge_environment( - should_start_bridge_cluster: bool, -) -> (TestCluster, EthBridgeEnvironment) { - let anvil_port = get_available_port("127.0.0.1"); - let anvil_url = format!("http://127.0.0.1:{anvil_port}"); - let mut eth_environment = EthBridgeEnvironment::new(&anvil_url, anvil_port) - .await - .unwrap(); - - // Deploy solidity contracts in a separate task - let anvil_url_clone = anvil_url.clone(); - let (tx_ack, rx_ack) = tokio::sync::oneshot::channel(); - - let mut server_ports = vec![]; - for _ in 0..3 { - server_ports.push(get_available_port("127.0.0.1")); - } - - let test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) - .build_with_bridge(true) - .await; - info!("Test cluster built"); - test_cluster - .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized() - .await; - info!("Bridge committee is finalized"); - // TODO: do not block on `build_with_bridge`, return with bridge keys immediately - // to parallize the setup. - let bridge_authority_keys = test_cluster - .bridge_authority_keys - .as_ref() - .unwrap() - .iter() - .map(|k| k.copy()) - .collect::>(); - - let (eth_signer, eth_pk_hex) = eth_environment.get_signer(TEST_PK).await.unwrap(); - tokio::task::spawn(async move { - deploy_sol_contract( - &anvil_url_clone, - eth_signer, - bridge_authority_keys, - tx_ack, - eth_pk_hex, - ) - .await - }); - let deployed_contracts = rx_ack.await.unwrap(); - info!("Deployed contracts: {:?}", deployed_contracts); - eth_environment.contracts = Some(deployed_contracts); - - // if start bridge cluster is enabled, start the bridge cluster - if should_start_bridge_cluster { - start_bridge_cluster( - &test_cluster, - ð_environment, - vec![vec![], vec![], vec![], vec![]], - ) - .await; - info!("Started bridge cluster"); - } - - (test_cluster, eth_environment) -} - pub(crate) async fn deploy_sol_contract( anvil_url: &str, eth_signer: EthSigner, bridge_authority_keys: Vec, - tx: tokio::sync::oneshot::Sender, eth_private_key_hex: String, -) { +) -> DeployedSolContracts { let sol_path = format!("{}/../../bridge/evm", env!("CARGO_MANIFEST_DIR")); // Write the deploy config to a temp file then provide it to the forge late @@ -445,7 +600,7 @@ pub(crate) async fn deploy_sol_contract( ); assert!(!eth_bridge_committee.blocklist(eth_address).await.unwrap()); } - tx.send(contracts).unwrap(); + contracts } #[derive(Debug)] @@ -498,8 +653,7 @@ pub(crate) async fn start_bridge_cluster( test_cluster: &TestCluster, eth_environment: &EthBridgeEnvironment, approved_governance_actions: Vec>, -) { - // TODO: move this to TestCluster +) -> Vec> { let bridge_authority_keys = test_cluster .bridge_authority_keys .as_ref() @@ -520,6 +674,7 @@ pub(crate) async fn start_bridge_cluster( .unwrap() .sui_bridge_addrress_hex(); + let mut handles = vec![]; for (i, ((kp, server_listen_port), approved_governance_actions)) in bridge_authority_keys .iter() .zip(bridge_server_ports.iter()) @@ -536,9 +691,10 @@ pub(crate) async fn start_bridge_cluster( std::fs::write(authority_key_path.clone(), base64_encoded).unwrap(); let client_sui_address = SuiAddress::from(kp.public()); + let sender_address = test_cluster.get_address_0(); // send some gas to this address test_cluster - .transfer_sui_must_exceed(client_sui_address, 1000000000) + .transfer_sui_must_exceed(sender_address, client_sui_address, 1000000000) .await; let config = BridgeNodeConfig { @@ -565,11 +721,9 @@ pub(crate) async fn start_bridge_cluster( }; // Spawn bridge node in memory let config_clone = config.clone(); - tokio::spawn(async move { - tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; - run_bridge_node(config_clone).await.unwrap(); - }); + handles.push(run_bridge_node(config_clone).await.unwrap()); } + handles } pub(crate) async fn get_signatures( diff --git a/crates/sui-bridge/src/main.rs b/crates/sui-bridge/src/main.rs index ed25477bde4d4..a2c9ecc53208b 100644 --- a/crates/sui-bridge/src/main.rs +++ b/crates/sui-bridge/src/main.rs @@ -58,5 +58,5 @@ async fn main() -> anyhow::Result<()> { .with_prom_registry(&prometheus_registry) .init(); - run_bridge_node(config).await + Ok(run_bridge_node(config).await?.await?) } diff --git a/crates/sui-bridge/src/node.rs b/crates/sui-bridge/src/node.rs index 469d9998ca46e..565ea1a4ac5c2 100644 --- a/crates/sui-bridge/src/node.rs +++ b/crates/sui-bridge/src/node.rs @@ -22,7 +22,7 @@ use sui_types::{bridge::BRIDGE_MODULE_NAME, event::EventID, Identifier}; use tokio::task::JoinHandle; use tracing::info; -pub async fn run_bridge_node(config: BridgeNodeConfig) -> anyhow::Result<()> { +pub async fn run_bridge_node(config: BridgeNodeConfig) -> anyhow::Result> { let (server_config, client_config) = config.validate().await?; // Start Client @@ -37,7 +37,7 @@ pub async fn run_bridge_node(config: BridgeNodeConfig) -> anyhow::Result<()> { IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), server_config.server_listen_port, ); - run_server( + Ok(run_server( &socket_address, BridgeRequestHandler::new( server_config.key, @@ -45,10 +45,7 @@ pub async fn run_bridge_node(config: BridgeNodeConfig) -> anyhow::Result<()> { server_config.eth_client, server_config.approved_governance_actions, ), - ) - .await; - - Ok(()) + )) } // TODO: is there a way to clean up the overrides after it's stored in DB? @@ -178,16 +175,14 @@ fn get_eth_contracts_to_watch( #[cfg(test)] mod tests { use ethers::types::Address as EthAddress; - use std::process::Child; use super::*; use crate::config::BridgeNodeConfig; use crate::config::EthConfig; use crate::config::SuiConfig; - use crate::e2e_tests::test_utils::deploy_sol_contract; - use crate::e2e_tests::test_utils::get_eth_signer_client_e2e_test_only; use crate::e2e_tests::test_utils::wait_for_server_to_be_up; - use crate::BRIDGE_ENABLE_PROTOCOL_VERSION; + use crate::e2e_tests::test_utils::BridgeTestCluster; + use crate::e2e_tests::test_utils::BridgeTestClusterBuilder; use fastcrypto::secp256k1::Secp256k1KeyPair; use sui_config::local_ip_utils::get_available_port; use sui_types::base_types::SuiAddress; @@ -199,7 +194,6 @@ mod tests { use sui_types::digests::TransactionDigest; use sui_types::event::EventID; use tempfile::tempdir; - use test_cluster::TestClusterBuilder; #[tokio::test] async fn test_get_eth_contracts_to_watch() { @@ -313,13 +307,13 @@ mod tests { #[tokio::test] async fn test_starting_bridge_node() { telemetry_subscribers::init_for_testing(); - let (test_cluster, anvil_port, mut child, bridge_contract_address) = setup().await; + let bridge_test_cluster = setup().await; + let kp = bridge_test_cluster.bridge_authority_key(0); // prepare node config (server only) let tmp_dir = tempdir().unwrap().into_path(); let authority_key_path = "test_starting_bridge_node_bridge_authority_key"; let server_listen_port = get_available_port("127.0.0.1"); - let kp = test_cluster.bridge_authority_keys.as_ref().unwrap()[0].copy(); let base64_encoded = kp.encode_base64(); std::fs::write(tmp_dir.join(authority_key_path), base64_encoded).unwrap(); @@ -328,15 +322,15 @@ mod tests { metrics_port: get_available_port("127.0.0.1"), bridge_authority_key_path_base64_raw: tmp_dir.join(authority_key_path), sui: SuiConfig { - sui_rpc_url: test_cluster.fullnode_handle.rpc_url, + sui_rpc_url: bridge_test_cluster.sui_rpc_url(), sui_bridge_chain_id: BridgeChainId::SuiCustom as u8, bridge_client_key_path_base64_sui_key: None, bridge_client_gas_object: None, sui_bridge_module_last_processed_event_id_override: None, }, eth: EthConfig { - eth_rpc_url: format!("http://127.0.0.1:{}", anvil_port), - eth_bridge_proxy_address: format!("{:#x}", bridge_contract_address), + eth_rpc_url: bridge_test_cluster.eth_rpc_url(), + eth_bridge_proxy_address: bridge_test_cluster.sui_bridge_address(), eth_bridge_chain_id: BridgeChainId::EthCustom as u8, eth_contracts_start_block_fallback: None, eth_contracts_start_block_override: None, @@ -346,21 +340,19 @@ mod tests { db_path: None, }; // Spawn bridge node in memory - tokio::spawn(async move { - run_bridge_node(config).await.unwrap(); - }); + let _handle = run_bridge_node(config).await.unwrap(); let server_url = format!("http://127.0.0.1:{}", server_listen_port); // Now we expect to see the server to be up and running. let res = wait_for_server_to_be_up(server_url, 5).await; - child.kill().unwrap(); res.unwrap(); } #[tokio::test] async fn test_starting_bridge_node_with_client() { telemetry_subscribers::init_for_testing(); - let (test_cluster, anvil_port, mut child, bridge_contract_address) = setup().await; + let bridge_test_cluster = setup().await; + let kp = bridge_test_cluster.bridge_authority_key(0); // prepare node config (server + client) let tmp_dir = tempdir().unwrap().into_path(); @@ -368,14 +360,15 @@ mod tests { let authority_key_path = "test_starting_bridge_node_with_client_bridge_authority_key"; let server_listen_port = get_available_port("127.0.0.1"); - let kp = test_cluster.bridge_authority_keys.as_ref().unwrap()[0].copy(); let base64_encoded = kp.encode_base64(); std::fs::write(tmp_dir.join(authority_key_path), base64_encoded).unwrap(); let client_sui_address = SuiAddress::from(kp.public()); + let sender_address = bridge_test_cluster.sui_user_address(); // send some gas to this address - test_cluster - .transfer_sui_must_exceed(client_sui_address, 1000000000) + bridge_test_cluster + .test_cluster + .transfer_sui_must_exceed(sender_address, client_sui_address, 1000000000) .await; let config = BridgeNodeConfig { @@ -383,7 +376,7 @@ mod tests { metrics_port: get_available_port("127.0.0.1"), bridge_authority_key_path_base64_raw: tmp_dir.join(authority_key_path), sui: SuiConfig { - sui_rpc_url: test_cluster.fullnode_handle.rpc_url, + sui_rpc_url: bridge_test_cluster.sui_rpc_url(), sui_bridge_chain_id: BridgeChainId::SuiCustom as u8, bridge_client_key_path_base64_sui_key: None, bridge_client_gas_object: None, @@ -393,8 +386,8 @@ mod tests { }), }, eth: EthConfig { - eth_rpc_url: format!("http://127.0.0.1:{}", anvil_port), - eth_bridge_proxy_address: format!("{:#x}", bridge_contract_address), + eth_rpc_url: bridge_test_cluster.eth_rpc_url(), + eth_bridge_proxy_address: bridge_test_cluster.sui_bridge_address(), eth_bridge_chain_id: BridgeChainId::EthCustom as u8, eth_contracts_start_block_fallback: Some(0), eth_contracts_start_block_override: None, @@ -404,24 +397,21 @@ mod tests { db_path: Some(db_path), }; // Spawn bridge node in memory - let config_clone = config.clone(); - tokio::spawn(async move { - run_bridge_node(config_clone).await.unwrap(); - }); + let _handle = run_bridge_node(config).await.unwrap(); let server_url = format!("http://127.0.0.1:{}", server_listen_port); // Now we expect to see the server to be up and running. // client components are spawned earlier than server, so as long as the server is up, // we know the client components are already running. let res = wait_for_server_to_be_up(server_url, 5).await; - child.kill().unwrap(); res.unwrap(); } #[tokio::test] async fn test_starting_bridge_node_with_client_and_separate_client_key() { telemetry_subscribers::init_for_testing(); - let (test_cluster, anvil_port, mut child, bridge_contract_address) = setup().await; + let bridge_test_cluster = setup().await; + let kp = bridge_test_cluster.bridge_authority_key(0); // prepare node config (server + client) let tmp_dir = tempdir().unwrap().into_path(); @@ -432,7 +422,6 @@ mod tests { let server_listen_port = get_available_port("127.0.0.1"); // prepare bridge authority key - let kp = test_cluster.bridge_authority_keys.as_ref().unwrap()[0].copy(); let base64_encoded = kp.encode_base64(); std::fs::write(tmp_dir.join(authority_key_path), base64_encoded).unwrap(); @@ -443,10 +432,11 @@ mod tests { "test_starting_bridge_node_with_client_and_separate_client_key_bridge_client_key"; std::fs::write(tmp_dir.join(client_key_path), kp.encode_base64()).unwrap(); let client_sui_address = SuiAddress::from(&kp.public()); - + let sender_address = bridge_test_cluster.sui_user_address(); // send some gas to this address - let gas_obj = test_cluster - .transfer_sui_must_exceed(client_sui_address, 1000000000) + let gas_obj = bridge_test_cluster + .test_cluster + .transfer_sui_must_exceed(sender_address, client_sui_address, 1000000000) .await; let config = BridgeNodeConfig { @@ -454,7 +444,7 @@ mod tests { metrics_port: get_available_port("127.0.0.1"), bridge_authority_key_path_base64_raw: tmp_dir.join(authority_key_path), sui: SuiConfig { - sui_rpc_url: test_cluster.fullnode_handle.rpc_url, + sui_rpc_url: bridge_test_cluster.sui_rpc_url(), sui_bridge_chain_id: BridgeChainId::SuiCustom as u8, bridge_client_key_path_base64_sui_key: Some(tmp_dir.join(client_key_path)), bridge_client_gas_object: Some(gas_obj), @@ -464,8 +454,8 @@ mod tests { }), }, eth: EthConfig { - eth_rpc_url: format!("http://127.0.0.1:{}", anvil_port), - eth_bridge_proxy_address: format!("{:#x}", bridge_contract_address), + eth_rpc_url: bridge_test_cluster.eth_rpc_url(), + eth_bridge_proxy_address: bridge_test_cluster.sui_bridge_address(), eth_bridge_chain_id: BridgeChainId::EthCustom as u8, eth_contracts_start_block_fallback: Some(0), eth_contracts_start_block_override: Some(0), @@ -475,66 +465,21 @@ mod tests { db_path: Some(db_path), }; // Spawn bridge node in memory - let config_clone = config.clone(); - tokio::spawn(async move { - run_bridge_node(config_clone).await.unwrap(); - }); + let _handle = run_bridge_node(config).await.unwrap(); let server_url = format!("http://127.0.0.1:{}", server_listen_port); // Now we expect to see the server to be up and running. // client components are spawned earlier than server, so as long as the server is up, // we know the client components are already running. let res = wait_for_server_to_be_up(server_url, 5).await; - child.kill().unwrap(); res.unwrap(); } - async fn setup() -> (test_cluster::TestCluster, u16, Child, EthAddress) { - // Start eth node with anvil - let anvil_port = get_available_port("127.0.0.1"); - let eth_node_process = std::process::Command::new("anvil") - .arg("--port") - .arg(anvil_port.to_string()) - .spawn() - .expect("Failed to start anvil"); - - let test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) - .build_with_bridge(true) - .await; - - test_cluster - .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized() - .await; - - let (tx_ack, rx_ack) = tokio::sync::oneshot::channel(); - let bridge_authority_keys = test_cluster - .bridge_authority_keys - .as_ref() - .unwrap() - .iter() - .map(|k| k.copy()) - .collect::>(); - let anvil_url = format!("http://127.0.0.1:{}", anvil_port); - let (eth_signer_0, eth_private_key_hex_0) = get_eth_signer_client_e2e_test_only(&anvil_url) - .await - .unwrap(); - tokio::task::spawn(async move { - deploy_sol_contract( - &anvil_url, - eth_signer_0, - bridge_authority_keys, - tx_ack, - eth_private_key_hex_0, - ) + async fn setup() -> BridgeTestCluster { + BridgeTestClusterBuilder::new() + .with_eth_env(true) + .with_bridge_cluster(false) + .build() .await - }); - let address = rx_ack.await.unwrap(); - ( - test_cluster, - anvil_port, - eth_node_process, - address.sui_bridge, - ) } } diff --git a/crates/sui-bridge/src/server/mod.rs b/crates/sui-bridge/src/server/mod.rs index 0806ae05cb45e..544271eb175ed 100644 --- a/crates/sui-bridge/src/server/mod.rs +++ b/crates/sui-bridge/src/server/mod.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::inconsistent_digit_grouping)] - use crate::{ crypto::BridgeAuthorityPublicKeyBytes, error::BridgeError, @@ -54,11 +53,15 @@ pub const ADD_TOKENS_ON_SUI_PATH: &str = pub const ADD_TOKENS_ON_EVM_PATH: &str = "/sign/add_tokens_on_evm/:chain_id/:nonce/:native/:token_ids/:token_addresses/:token_sui_decimals/:token_prices"; -pub async fn run_server(socket_address: &SocketAddr, handler: BridgeRequestHandler) { - axum::Server::bind(socket_address) - .serve(make_router(Arc::new(handler)).into_make_service()) - .await - .unwrap(); +pub fn run_server( + socket_address: &SocketAddr, + handler: BridgeRequestHandler, +) -> tokio::task::JoinHandle<()> { + let service = axum::Server::bind(socket_address) + .serve(make_router(Arc::new(handler)).into_make_service()); + tokio::spawn(async move { + service.await.unwrap(); + }) } pub(crate) fn make_router( diff --git a/crates/sui-bridge/src/sui_client.rs b/crates/sui-bridge/src/sui_client.rs index 6d85d43e60f4e..01fb50c07e2ce 100644 --- a/crates/sui-bridge/src/sui_client.rs +++ b/crates/sui-bridge/src/sui_client.rs @@ -604,6 +604,7 @@ impl SuiClientInner for SuiSdkClient { #[cfg(test)] mod tests { + use crate::crypto::BridgeAuthorityKeyPair; use crate::BRIDGE_ENABLE_PROTOCOL_VERSION; use crate::{ events::{EmittedSuiToEthTokenBridgeV1, MoveTokenBridgeEvent}, @@ -753,9 +754,14 @@ mod tests { #[tokio::test] async fn test_get_action_onchain_status_for_sui_to_eth_transfer() { telemetry_subscribers::init_for_testing(); + let mut bridge_keys = vec![]; + for _ in 0..=3 { + let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); + bridge_keys.push(kp); + } let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(true) + .build_with_bridge(bridge_keys, true) .await; let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url) diff --git a/crates/sui-bridge/src/sui_transaction_builder.rs b/crates/sui-bridge/src/sui_transaction_builder.rs index 2b3b2d62379f6..932e749f9fd58 100644 --- a/crates/sui-bridge/src/sui_transaction_builder.rs +++ b/crates/sui-bridge/src/sui_transaction_builder.rs @@ -584,6 +584,7 @@ pub fn build_committee_register_transaction( #[cfg(test)] mod tests { + use crate::crypto::BridgeAuthorityKeyPair; use crate::sui_client::SuiClient; use crate::types::BridgeAction; use crate::types::EmergencyAction; @@ -600,15 +601,21 @@ mod tests { use ethers::types::Address as EthAddress; use std::collections::HashMap; use sui_types::bridge::{BridgeChainId, TOKEN_ID_BTC, TOKEN_ID_USDC}; + use sui_types::crypto::get_key_pair; use sui_types::crypto::ToFromBytes; use test_cluster::TestClusterBuilder; #[tokio::test] async fn test_build_sui_transaction_for_token_transfer() { telemetry_subscribers::init_for_testing(); + let mut bridge_keys = vec![]; + for _ in 0..=3 { + let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); + bridge_keys.push(kp); + } let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) - .build_with_bridge(true) + .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) + .build_with_bridge(bridge_keys, true) .await; let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url) @@ -677,9 +684,14 @@ mod tests { #[tokio::test] async fn test_build_sui_transaction_for_emergency_op() { telemetry_subscribers::init_for_testing(); + let mut bridge_keys = vec![]; + for _ in 0..=3 { + let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); + bridge_keys.push(kp); + } let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(true) + .build_with_bridge(bridge_keys, true) .await; let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url) .await @@ -741,9 +753,14 @@ mod tests { #[tokio::test] async fn test_build_sui_transaction_for_committee_blocklist() { telemetry_subscribers::init_for_testing(); + let mut bridge_keys = vec![]; + for _ in 0..=3 { + let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); + bridge_keys.push(kp); + } let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(true) + .build_with_bridge(bridge_keys, true) .await; let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url) .await @@ -824,9 +841,14 @@ mod tests { #[tokio::test] async fn test_build_sui_transaction_for_limit_update() { telemetry_subscribers::init_for_testing(); + let mut bridge_keys = vec![]; + for _ in 0..=3 { + let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); + bridge_keys.push(kp); + } let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(true) + .build_with_bridge(bridge_keys, true) .await; let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url) .await @@ -888,9 +910,14 @@ mod tests { #[tokio::test] async fn test_build_sui_transaction_for_price_update() { telemetry_subscribers::init_for_testing(); + let mut bridge_keys = vec![]; + for _ in 0..=3 { + let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); + bridge_keys.push(kp); + } let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) - .build_with_bridge(true) + .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) + .build_with_bridge(bridge_keys, true) .await; let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url) .await diff --git a/crates/sui-e2e-tests/tests/bridge_tests.rs b/crates/sui-e2e-tests/tests/bridge_tests.rs index 109a712bfb1e9..981d5a922908c 100644 --- a/crates/sui-e2e-tests/tests/bridge_tests.rs +++ b/crates/sui-e2e-tests/tests/bridge_tests.rs @@ -1,11 +1,13 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use sui_bridge::crypto::BridgeAuthorityKeyPair; use sui_bridge::BRIDGE_ENABLE_PROTOCOL_VERSION; use sui_json_rpc_api::BridgeReadApiClient; use sui_macros::sim_test; use sui_types::bridge::get_bridge; use sui_types::bridge::BridgeTrait; +use sui_types::crypto::get_key_pair; use sui_types::SUI_BRIDGE_OBJECT_ID; use test_cluster::TestClusterBuilder; @@ -53,9 +55,14 @@ async fn test_create_bridge_state_object() { #[tokio::test] async fn test_committee_registration() { telemetry_subscribers::init_for_testing(); + let mut bridge_keys = vec![]; + for _ in 0..=3 { + let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); + bridge_keys.push(kp); + } let test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) - .build_with_bridge(false) + .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) + .build_with_bridge(bridge_keys, false) .await; let bridge = get_bridge( diff --git a/crates/test-cluster/src/lib.rs b/crates/test-cluster/src/lib.rs index 71bb95e408cf4..19cfc454bb883 100644 --- a/crates/test-cluster/src/lib.rs +++ b/crates/test-cluster/src/lib.rs @@ -37,6 +37,7 @@ use sui_json_rpc_types::{ use sui_keys::keystore::{AccountKeystore, FileBasedKeystore, Keystore}; use sui_node::SuiNodeHandle; use sui_protocol_config::{ProtocolVersion, SupportedProtocolVersions}; +use sui_sdk::apis::QuorumDriverApi; use sui_sdk::sui_client_config::{SuiClientConfig, SuiEnv}; use sui_sdk::wallet_context::WalletContext; use sui_sdk::{SuiClient, SuiClientBuilder}; @@ -56,7 +57,6 @@ use sui_types::bridge::{get_bridge, TOKEN_ID_BTC, TOKEN_ID_ETH, TOKEN_ID_USDC, T use sui_types::bridge::{get_bridge_obj_initial_shared_version, BridgeSummary, BridgeTrait}; use sui_types::committee::CommitteeTrait; use sui_types::committee::{Committee, EpochId}; -use sui_types::crypto::get_key_pair; use sui_types::crypto::KeypairTraits; use sui_types::crypto::SuiKeyPair; use sui_types::effects::{TransactionEffects, TransactionEvents}; @@ -129,6 +129,10 @@ impl TestCluster { &self.fullnode_handle.sui_client } + pub fn quorum_driver_api(&self) -> &QuorumDriverApi { + self.sui_client().quorum_driver_api() + } + pub fn rpc_url(&self) -> &str { &self.fullnode_handle.rpc_url } @@ -741,8 +745,13 @@ impl TestCluster { .unwrap() } - pub async fn transfer_sui_must_exceed(&self, receiver: SuiAddress, amount: u64) -> ObjectID { - let sender = self.get_address_0(); + pub async fn transfer_sui_must_exceed( + &self, + sender: SuiAddress, + receiver: SuiAddress, + amount: u64, + ) -> ObjectID { + // let sender = self.get_address_0(); let tx = self .test_transaction_builder_with_sender(sender) .await @@ -1093,39 +1102,99 @@ impl TestClusterBuilder { } } - pub async fn build_with_bridge(self, deploy_tokens: bool) -> TestCluster { + pub async fn build_with_bridge( + self, + bridge_authority_keys: Vec, + deploy_tokens: bool, + ) -> TestCluster { + let timer = Instant::now(); let mut test_cluster = self.build().await; + info!( + "TestCluster build took {:?} secs", + timer.elapsed().as_secs() + ); let ref_gas_price = test_cluster.get_reference_gas_price().await; let bridge_arg = test_cluster.get_mut_bridge_arg().await.unwrap(); - let mut bridge_authority_keys = vec![]; + assert_eq!( + bridge_authority_keys.len(), + test_cluster.swarm.active_validators().count() + ); + + let publish_tokens_tasks = if deploy_tokens { + let quorum_driver_api = Arc::new(test_cluster.quorum_driver_api().clone()); + // Register tokens + let token_packages_dir = [ + Path::new("../../bridge/move/tokens/btc"), + Path::new("../../bridge/move/tokens/eth"), + Path::new("../../bridge/move/tokens/usdc"), + Path::new("../../bridge/move/tokens/usdt"), + ]; + + // publish coin packages + let mut publish_tokens_tasks = vec![]; + let sender = test_cluster.get_address_0(); + let gases = test_cluster + .wallet + .get_gas_objects_owned_by_address(sender, None) + .await + .unwrap(); + assert!(gases.len() >= token_packages_dir.len()); + for (token_package_dir, gas) in token_packages_dir.iter().zip(gases) { + let tx = test_cluster + .test_transaction_builder_with_gas_object(sender, gas) + .await + .publish(token_package_dir.to_path_buf()) + .build(); + let tx = test_cluster.wallet.sign_transaction(&tx); + let api_clone = quorum_driver_api.clone(); + publish_tokens_tasks.push(tokio::spawn(async move { + api_clone.execute_transaction_block( + tx, + SuiTransactionBlockResponseOptions::new() + .with_effects() + .with_input() + .with_events() + .with_object_changes() + .with_balance_changes(), + Some(sui_types::quorum_driver_types::ExecuteTransactionRequestType::WaitForLocalExecution), + ).await + })); + } + Some(publish_tokens_tasks) + } else { + None + }; + let mut server_ports = vec![]; let mut tasks = vec![]; - - for node in test_cluster.swarm.active_validators() { + // use a different sender address than the coin publish to avoid object locks + let sender_address = test_cluster.get_address_1(); + for (node, kp) in test_cluster + .swarm + .active_validators() + .zip(bridge_authority_keys.iter()) + { let validator_address = node.config.sui_address(); // 1, send some gas to validator test_cluster - .transfer_sui_must_exceed(validator_address, 1000000000) + .transfer_sui_must_exceed(sender_address, validator_address, 1000000000) .await; // 2, create committee registration tx - let coins = test_cluster - .sui_client() - .coin_read_api() - .get_coins(validator_address, None, None, None) + let gas = test_cluster + .wallet + .get_one_gas_object_owned_by_address(validator_address) .await + .unwrap() .unwrap(); - let gas = coins.data.first().unwrap(); - let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); - bridge_authority_keys.push(kp.copy()); let server_port = get_available_port("127.0.0.1"); let server_url = format!("http://127.0.0.1:{}", server_port); server_ports.push(server_port); let data = build_committee_register_transaction( validator_address, - &gas.object_ref(), + &gas, bridge_arg, - kp, + kp.copy(), &server_url, ref_gas_price, ) @@ -1162,27 +1231,22 @@ impl TestClusterBuilder { } if deploy_tokens { - // Register tokens - let token_ids = vec![TOKEN_ID_BTC, TOKEN_ID_ETH, TOKEN_ID_USDC, TOKEN_ID_USDT]; - let token_packages_dir = [ - Path::new("../../bridge/move/tokens/btc"), - Path::new("../../bridge/move/tokens/eth"), - Path::new("../../bridge/move/tokens/usdc"), - Path::new("../../bridge/move/tokens/usdt"), - ]; - - // publish coin packages - // let mut published_tokens = vec![]; - let mut publish_tokens_responses = vec![]; - for token_package_dir in token_packages_dir { - let sender = test_cluster.get_address_0(); - let tx = test_cluster - .test_transaction_builder_with_sender(sender) - .await - .publish(token_package_dir.to_path_buf()) - .build(); - publish_tokens_responses.push(test_cluster.sign_and_execute_transaction(&tx).await); + let timer = Instant::now(); + let publish_tokens_responses = join_all(publish_tokens_tasks.unwrap()) + .await + .into_iter() + .collect::, _>>() + .unwrap() + .into_iter() + .collect::, _>>() + .unwrap(); + for resp in &publish_tokens_responses { + assert_eq!( + resp.effects.as_ref().unwrap().status(), + &SuiExecutionStatus::Success + ); } + let token_ids = vec![TOKEN_ID_BTC, TOKEN_ID_ETH, TOKEN_ID_USDC, TOKEN_ID_USDT]; let token_prices = vec![500_000_000u64, 30_000_000u64, 1_000u64, 1_000u64]; let action = publish_coins_return_add_coins_on_sui_action( @@ -1194,6 +1258,7 @@ impl TestClusterBuilder { 0, ) .await; + info!("register tokens took {:?} secs", timer.elapsed().as_secs()); let sig_map = bridge_authority_keys .iter() .map(|key| { @@ -1236,7 +1301,12 @@ impl TestClusterBuilder { response.effects.unwrap().status(), &SuiExecutionStatus::Success ); + info!("Deploy tokens took {:?} secs", timer.elapsed().as_secs()); } + info!( + "TestCluster build_with_bridge took {:?} secs", + timer.elapsed().as_secs() + ); test_cluster.bridge_authority_keys = Some(bridge_authority_keys); test_cluster.bridge_server_ports = Some(server_ports); test_cluster