diff --git a/Cargo.lock b/Cargo.lock index 8a8b51dc1..36052b736 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1787,6 +1787,7 @@ dependencies = [ "esplora-client", "futures", "hex", + "human_bytes", "itertools", "musig2", "num-traits", @@ -2964,6 +2965,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "human_bytes" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" +dependencies = [ + "ryu", +] + [[package]] name = "hyper" version = "0.14.32" diff --git a/DEMO_INSTRUCTIONS.md b/DEMO_INSTRUCTIONS.md new file mode 100644 index 000000000..d6edaf6b1 --- /dev/null +++ b/DEMO_INSTRUCTIONS.md @@ -0,0 +1,261 @@ +Before running the demo for the first time, see [Environment Setup](#environment-setup) section below. + +# Demo Prep: Funding UTXOs +The bridge peg-in and peg-out execution consumes three funding UTXOs. For convenience, prepare them before running the demo. This process remains the same across all scenarios. You can use the recommended amounts as below: + +1. Peg-in graph - 'peg-in depost' tx input: **2097447 SAT** - will be spent by [DEPOSITOR] (use `-d` to get their address) +2. Peg-out graph - 'peg-out confirm' tx input: **3562670 SAT** - will be spent by [OPERATOR] (use `-o` to get their address) +3. Withdrawer peg-out - 'peg-out' tx input: **2097274 SAT** - will be spent by [OPERATOR] (use `-o` to get their address) + +# Demo Steps +The following is the list of command line arguments that are passed to the CLI tool in sequence by the respective actors. The arguments can be used either with `cargo run --bin bridge --` or when running the CLI binary directly. + +## Rejected Disprove Scenario (a.k.a. 'happy peg-out' execution path). +#### [DEPOSITOR] Initiate peg-in +`:` = Bridge deposit UTXO that includes the expected peg-in amount. It must be spendable by the depositor private key. Suggested test amount: `2097447 sats`. It is the UTXO #1 in [Demo Prep](#demo-prep-funding-utxos). +``` +-n -u : -d +``` +#### [OPERATOR] Create peg-out graph +`:` = UTXO funding the peg-out confirm tx. Must be spendable by the operator private key. Suggested test amount: `3562670 sats`. It is the UTXO #2 in [Demo Prep](#demo-prep-funding-utxos). +``` +-t -u : -i +``` +#### [VERIFIER_0] Push verifier_0 nonces for peg-in graph +``` +-c -i +``` +#### [VERIFIER_1] Push verifier_1 nonces for peg-in graph +``` +-c -i +``` +#### [VERIFIER_0] Push verifier_0 signatures for peg-in graph +``` +-g -i +``` +#### [VERIFIER_1] Push verifier_1 signatures for peg-in graph +``` +-g -i +``` +#### [OPERATOR] or [VERIFIER_0] or [VERIFIER_1] Broadcast peg-in confirm +``` +-b pegin -g confirm +``` +Record the peg-in confirm txid. +#### [VERIFIER_0] Push verifier_0 nonces for peg-out graph +``` +-c -i +``` +#### [VERIFIER_1] Push verifier_1 nonces for peg-out graph +``` +-c -i +``` +#### [VERIFIER_0] Push verifier_0 signatures for peg-out graph +``` +-g -i +``` +#### [VERIFIER_1] Push verifier_1 signatures for peg-out graph +``` +-g -i +``` +#### [OPERATOR] Mock L2 peg-out event (requires peg-in confirm txid mined earlier) +> [!IMPORTANT] +> Start the CLI in interactive mode here. + +`:` = The peg-in confirm txid recorded above and output index 0. +``` +-x -u : +``` +#### [OPERATOR] Broadcast peg-out +`:` = UTXO funding the payout to the withdrawer. Must be spendable by the operator private key. Suggested test amount: `2097274 sats`. It is the UTXO #3 in [Demo Prep](#demo-prep-funding-utxos). +``` +-b tx -g -u : peg_out +``` +#### [OPERATOR] Broadcast peg-out confirm +``` +-b tx -g peg_out_confirm +``` +#### [OPERATOR] Broadcast kick-off 1 +``` +-b tx -g kick_off_1 +``` +#### [OPERATOR] Broadcast kick-off 2 +``` +-b tx -g kick_off_2 +``` +#### [OPERATOR] Broadcast assert-initial +``` +-b tx -g assert_initial +``` +#### [OPERATOR] Broadcast assert-commit 1 +``` +-b tx -g assert_commit_1 +``` +#### [OPERATOR] Broadcast assert-commit 2 +``` +-b tx -g assert_commit_2 +``` +#### [OPERATOR] Broadcast assert-final +``` +-b tx -g assert_final +``` +#### [VERIFIER_1] Broadcast disprove (should fail) +`` = Receiver of the disprove reward. +``` +-b tx -g -a disprove +``` +#### [OPERATOR] Broadcast take 2 +``` +-b tx -g take_2 +``` + +## Successful Disprove Scenario (a.k.a. 'unhappy peg-out' execution path). +#### [DEPOSITOR] Initiate peg-in +`:` = Bridge deposit UTXO that includes the expected peg-in amount. It must be spendable by the depositor private key. Suggested test amount: `2097447 sats`. +``` +-n -u : -d +``` +#### [OPERATOR] Create peg-out graph +`:` = UTXO funding the peg-out confirm tx. Must be spendable by the operator private key. Suggested test amount: `3562670 sats`. +``` +-t -u : -i +``` +#### [VERIFIER_0] Push verifier_0 nonces for peg-in graph +``` +-c -i +``` +#### [VERIFIER_1] Push verifier_1 nonces for peg-in graph +``` +-c -i +``` +#### [VERIFIER_0] Push verifier_0 signatures for peg-in graph +``` +-g -i +``` +#### [VERIFIER_0] Push verifier_1 signatures for peg-in graph +``` +-g -i +``` +#### [OPERATOR] or [VERIFIER_0] or [VERIFIER_1] Broadcast peg-in confirm +``` +-b pegin -g confirm +``` +Record the peg-in confirm txid. +#### [VERIFIER_0] Push verifier_0 nonces for peg-out graph +``` +-c -i +``` +#### [VERIFIER_1] Push verifier_1 nonces for peg-out graph +``` +-c -i +``` +#### [VERIFIER_0] Push verifier_0 signatures for peg-out graph +``` +-g -i +``` +#### [VERIFIER_1] Push verifier_1 signatures for peg-out graph +``` +-g -i +``` +#### [OPERATOR] Mock L2 peg-out event (requires peg-in confirm txid mined earlier) +> [!IMPORTANT] +> Start the CLI in interactive mode here. + +`:` = The peg-in confirm txid recorded above and output index 0. +``` +-x -u : +``` +#### [OPERATOR] Broadcast peg-out +`:` = UTXO funding the payout to the withdrawer. Must be spendable by the operator private key. Suggested test amount: `2097274 sats`. +``` +-b tx -g -u : peg_out +``` +#### [OPERATOR] Broadcast peg-out confirm +``` +-b tx -g peg_out_confirm +``` +#### [OPERATOR] Broadcast kick-off 1 +``` +-b tx -g kick_off_1 +``` +#### [OPERATOR] Broadcast kick-off 2 +``` +-b tx -g kick_off_2 +``` +#### [OPERATOR] Broadcast assert-initial +``` +-b tx -g assert_initial +``` +#### [OPERATOR] Broadcast assert-commit 1 with invalid proof +``` +-b tx -g assert_commit_1_invalid +``` +#### [OPERATOR] Broadcast assert-commit 2 with invalid proof +``` +-b tx -g assert_commit_2_invalid +``` +#### [OPERATOR] Broadcast assert-final +``` +-b tx -g assert_final +``` +#### [VERIFIER_1] Broadcast disprove +`` = Receiver of the disprove reward. +``` +-b tx -g -a disprove +``` + +# Environment Setup +Clone and build this repository. The CLI executable is called `bridge`. + +## [DEPOSITOR] and [OPERATOR] and [VERIFIER_0] +All the above users can execute commands using a single setup (from the same directory). + +#### `bridge.toml` +Sample `bridge.toml` file that contains private keys of protocol participants and the ZK proof verifying key: +```toml +[keys] +depositor = "b8f17ea979be24199e7c3fec71ee88914d92fd4ca508443f765d56ce024ef1d7" +operator = "3076ca1dfc1e383be26d5dd3c0c427340f96139fa8c2520862cf551ec2d670ac" +verifier = "ee0817eac0c13aa8ee2dd3256304041f09f0499d1089b56495310ae8093583e2" +verifying_key = "9c3815c2ec66950b63e60c86dc9a2a658e0224d55ea45efe1f633be052dc7d867aff76a9e983210318f1b808aacbbba1dc04b6ac4e6845fa0cc887aeacaf5a068ab9aeaf8142740612ff2f3377ce7bfa7433936aaa23e3f3749691afaa06301fd03f043c097556e7efdf6862007edf3eb868c736d917896c014c54754f65182ae0c198157f92e667b6572ba60e6a52d58cb70dbeb3791206e928ea5e65c6199d25780cedb51796a8a43e40e192d1b23d0cfaf2ddd03e4ade7c327dbc427999244bf4b47b560cf65d672c86ef448eb5061870d3f617bd3658ad6917d0d32d9296020000000000000008f167c3f26c93dbfb91f3077b66bc0092473a15ef21c30f43d3aa96776f352a33622830e9cfcb48bdf8d3145aa0cf364bd19bbabfb3c73e44f56794ee65dc8a" +``` +Place the file in `.bitvm-bridge` directory in the user home directory. + +#### `.env` +Sample `.env` file that must be present in the same directory as the CLI tool or any of its parent directories (if you clone this repository, you can put it in the root directory). +``` +export BRIDGE_DATA_STORE_CLIENT_DATA_SUFFIX=-"bridge-client-data-demo-feb-2025.json" + +export BRIDGE_AWS_ACCESS_KEY_ID="" +export BRIDGE_AWS_SECRET_ACCESS_KEY="" +export BRIDGE_AWS_REGION="" +export BRIDGE_AWS_BUCKET="" + +# All verifier public keys +export VERIFIERS="026cc14f56ad7e8fdb323378287895c6c0bcdbb37714c74fba175a0c5f0cd0d56f,02452556ed6dbac394cbb7441fbaf06c446d1321467fa5a138895c6c9e246793dd" +``` + +## [VERIFIER_1] +Install the CLI tool in a different directory and make sure it doesn't share the `.env` file with the setup above. You can either clone this repository at another location or just copy the binary along with the .env. + +#### `bridge.toml` +Create a 'bitvm-bridge-verifier-1' directory there and put the following `bridge.toml` file there: +```toml +[keys] +verifier = "fc294c70faf210d4d0807ea7a3dba8f7e41700d90c119e1ae82a0687d89d297f" +verifying_key = "9c3815c2ec66950b63e60c86dc9a2a658e0224d55ea45efe1f633be052dc7d867aff76a9e983210318f1b808aacbbba1dc04b6ac4e6845fa0cc887aeacaf5a068ab9aeaf8142740612ff2f3377ce7bfa7433936aaa23e3f3749691afaa06301fd03f043c097556e7efdf6862007edf3eb868c736d917896c014c54754f65182ae0c198157f92e667b6572ba60e6a52d58cb70dbeb3791206e928ea5e65c6199d25780cedb51796a8a43e40e192d1b23d0cfaf2ddd03e4ade7c327dbc427999244bf4b47b560cf65d672c86ef448eb5061870d3f617bd3658ad6917d0d32d9296020000000000000008f167c3f26c93dbfb91f3077b66bc0092473a15ef21c30f43d3aa96776f352a33622830e9cfcb48bdf8d3145aa0cf364bd19bbabfb3c73e44f56794ee65dc8a" +``` +#### `.env` +``` +export BRIDGE_DATA_STORE_CLIENT_DATA_SUFFIX=-"bridge-client-data-demo-feb-2025.json" + +export BRIDGE_AWS_ACCESS_KEY_ID="" +export BRIDGE_AWS_SECRET_ACCESS_KEY="" +export BRIDGE_AWS_REGION="" +export BRIDGE_AWS_BUCKET="" + +export KEY_DIR="bitvm-bridge-verifier-1" + +# All verifier public keys +export VERIFIERS="026cc14f56ad7e8fdb323378287895c6c0bcdbb37714c74fba175a0c5f0cd0d56f,02452556ed6dbac394cbb7441fbaf06c446d1321467fa5a138895c6c9e246793dd" +``` diff --git a/README.md b/README.md index 092093e6d..3c3337fc9 100644 --- a/README.md +++ b/README.md @@ -118,9 +118,10 @@ The BitVM CLI application can be invoked with various commands. The general synt ### Global Options -- -r, --verifiers : Optional; Comma-separated list of public keys for verifiers (max: 1000). Can also be set via the VERIFIERS environment variable. -- -e, --environment : Specify the Bitcoin network environment (mainnet, testnet). Defaults to testnet. Can also be set via the ENVIRONMENT environment variable. -- --key-dir : Directory containing the private keys. Can also be set via the KEY_DIR environment variable. +- -r, --verifiers : Comma-separated list of public keys for verifiers (max: 1000). Can also be set via the VERIFIERS environment variable. +- -e, --environment : Optional; Specify the Bitcoin network environment (mainnet, testnet, regtest). Defaults to testnet. Can also be set via the ENVIRONMENT environment variable. +- --key-dir : Optional; Directory containing the private keys. Can also be set via the KEY_DIR environment variable. +- -p, --user-profile : Optional; An arbitrary name of the user running the client (e.g. 'operator_one', 'verifier_0'). Used as a namespace separator in the local file path for storing private and public client data. Can also be set by the USER_PROFILE environment variable. ### Available Commands @@ -137,6 +138,21 @@ The BitVM CLI application can be invoked with various commands. The general synt - -o, --operator : Secret key for the operator. - -v, --verifier : Secret key for the verifier. - -w, --withdrawer : Secret key for the withdrawer. +- -k, --vk : Zero-knowledge proof verifying key. + +#### Get Operator Address: +1. Description: Retrieve the address spendable by the registered operator key. +2. Usage: +```bash +./target/release/bridge get-operator-address +``` + +#### Get Operator UTXOs: +1. Description: Retrieve a list of the operator's UTXOs. +2. Usage: +```bash +./target/release/bridge get-operator-utxos +``` #### Get Depositor Address: 1. Description: Retrieve the address spendable by the registered depositor key. @@ -159,6 +175,34 @@ The BitVM CLI application can be invoked with various commands. The general synt ./target/release/bridge initiate-peg-in --utxo : --destination_address ``` +#### Create Peg-Out graph: +1. Description: Create the peg-out graph for the corresponding peg-in graph. +2. Usage: +```bash +./target/release/bridge create-peg-out --utxo : --peg_in_id +``` + +#### Push nonces (MuSig2 signing process): +1. Description: Push nonces for the corresponding peg-out or peg-in graph. +2. Usage: +```bash +./target/release/bridge push-nonces --id +``` + +#### Push signatures (MuSig2 signing process): +1. Description: Push signatures for the corresponding peg-out or peg-in graph. +2. Usage: +```bash +./target/release/bridge push-signatures --id +``` + +#### Mock L2 peg-out event: +1. Description: FOR TEST PURPOSES ONLY! Mocks L2 chain service with specified peg-in-confirm txid. +2. Usage: +```bash +./target/release/bridge mock-l2-pegout-event --utxo : +``` + #### Broadcast Transactions: 1. Description: Send various types of transactions related to peg-ins and peg-outs. 2. Usage: @@ -199,9 +243,10 @@ You can set the following environment variables to configure the CLI: - BRIDGE_AWS_REGION : The AWS region where your storage bucket is located. Required if using AWS for storage. - BRIDGE_AWS_BUCKET : The name of the S3 bucket where files will be stored. Required if using AWS for storage. -- KEY_DIR: Directory containing private keys. +- KEY_DIR: Optional; Directory containing private keys. - VERIFIERS: Comma-separated list of public keys for verifiers. -- ENVIRONMENT: Bitcoin network environment (default: mainnet). +- ENVIRONMENT: Optional; Bitcoin network environment (default: testnet). +- USER_PROFILE: Optional; An arbitrary name of the user running the client (e.g. 'operator_one', 'verifier_0'). Used as a namespace separator in the local file path for storing private and public client data. #### FTP/SFTP Environment Variables diff --git a/bridge/Cargo.toml b/bridge/Cargo.toml index de3ce3018..46f263730 100644 --- a/bridge/Cargo.toml +++ b/bridge/Cargo.toml @@ -45,6 +45,7 @@ ark-relations.workspace = true secp256k1.workspace = true zstd = "0.13.2" bitcode = "0.6.3" +human_bytes = { version = "0.4", features = ["fast"] } [profile.dev] opt-level = 3 diff --git a/bridge/src/bin/bridge/main.rs b/bridge/src/bin/bridge/main.rs index fccd794ba..e42ccfa07 100644 --- a/bridge/src/bin/bridge/main.rs +++ b/bridge/src/bin/bridge/main.rs @@ -6,6 +6,9 @@ use std::error::Error; #[tokio::main] async fn main() -> Result<(), Box> { + // Load environment variables from .env file + dotenv::dotenv().ok(); + let command = command!() // requires `cargo` feature .propagate_version(true) .subcommand_required(true) @@ -14,19 +17,25 @@ async fn main() -> Result<(), Box> { arg!(--"key-dir" "The directory containing the private keys").required(false).env("KEY_DIR"), ) .arg( - arg!(-r --verifiers [VERIFIER_PUBKEYS] "Pubkeys of the verifiers") + arg!(-f --verifiers [VERIFIER_PUBKEYS] "Comma-separated list of verifier public keys") .required(false) .num_args(0..1000) .value_delimiter(',') .value_parser(clap::value_parser!(PublicKey)) .env("VERIFIERS"), ) - .arg(arg!(-e --environment "Specify the Bitcoin network environment (mainnet, testnet). Defaults to testnet.").required(false).default_value("testnet").env("ENVIRONMENT")) - .arg(arg!(-p --prefix "Prefix for local file cache path").required(false)) + .arg(arg!(-e --environment "Specify the Bitcoin network environment (mainnet, testnet, regtest)").required(false).default_value("testnet").env("ENVIRONMENT")) + .arg(arg!(-p --"user-profile" "Name of the protocol participant (e.g. 'operator_one', 'verifier_0'). Used as a namespace separator in the local file path for storing private and public client data").required(false).default_value("default_user").env("USER_PROFILE")) .subcommand(KeysCommand::get_command()) + .subcommand(ClientCommand::get_operator_address_command()) + .subcommand(ClientCommand::get_operator_utxos_command()) .subcommand(ClientCommand::get_depositor_address_command()) .subcommand(ClientCommand::get_depositor_utxos_command()) .subcommand(ClientCommand::get_initiate_peg_in_command()) + .subcommand(ClientCommand::get_create_peg_out_graph_command()) + .subcommand(ClientCommand::get_push_nonces_command()) + .subcommand(ClientCommand::get_push_signature_command()) + .subcommand(ClientCommand::get_mock_l2_pegout_event_command()) .subcommand(ClientCommand::get_status_command()) .subcommand(ClientCommand::get_broadcast_command()) .subcommand(ClientCommand::get_automatic_command()) @@ -40,12 +49,18 @@ async fn main() -> Result<(), Box> { .get_many::("verifiers") .map(|x| x.cloned().collect::>()), environment: matches.get_one::("environment").cloned(), - path_prefix: matches.get_one::("prefix").cloned(), + path_prefix: matches.get_one::("user-profile").cloned(), }; if let Some(sub_matches) = matches.subcommand_matches("keys") { let keys_command = KeysCommand::new(global_args.key_dir); keys_command.handle_command(sub_matches)?; + } else if matches.subcommand_matches("get-operator-address").is_some() { + let mut client_command = ClientCommand::new(global_args).await; + let _ = client_command.handle_get_operator_address().await; + } else if matches.subcommand_matches("get-operator-utxos").is_some() { + let mut client_command = ClientCommand::new(global_args).await; + let _ = client_command.handle_get_operator_utxos().await; } else if matches .subcommand_matches("get-depositor-address") .is_some() @@ -60,6 +75,24 @@ async fn main() -> Result<(), Box> { let _ = client_command .handle_initiate_peg_in_command(sub_matches) .await; + } else if let Some(sub_matches) = matches.subcommand_matches("create-peg-out") { + let mut client_command = ClientCommand::new(global_args).await; + let _ = client_command + .handle_create_peg_out_graph_command(sub_matches) + .await; + } else if let Some(sub_matches) = matches.subcommand_matches("push-nonces") { + let mut client_command = ClientCommand::new(global_args).await; + let _ = client_command.handle_push_nonces_command(sub_matches).await; + } else if let Some(sub_matches) = matches.subcommand_matches("push-signatures") { + let mut client_command = ClientCommand::new(global_args).await; + let _ = client_command + .handle_push_signature_command(sub_matches) + .await; + } else if let Some(sub_matches) = matches.subcommand_matches("mock-l2-pegout-event") { + let mut client_command = ClientCommand::new(global_args).await; + let _ = client_command + .handle_mock_l2_pegout_event_command(sub_matches) + .await; } else if matches.subcommand_matches("status").is_some() { let mut client_command = ClientCommand::new(global_args).await; let _ = client_command.handle_status_command().await; diff --git a/bridge/src/client/chain/base.rs b/bridge/src/client/chain/base.rs deleted file mode 100644 index 8f3fbba98..000000000 --- a/bridge/src/client/chain/base.rs +++ /dev/null @@ -1,12 +0,0 @@ -use async_trait::async_trait; - -use super::chain::PegInEvent; -use super::chain::PegOutBurntEvent; -use super::chain::PegOutEvent; - -#[async_trait] -pub trait ChainAdaptor { - async fn get_peg_out_init_event(&self) -> Result, String>; - async fn get_peg_out_burnt_event(&self) -> Result, String>; - async fn get_peg_in_minted_event(&self) -> Result, String>; -} diff --git a/bridge/src/client/chain/chain.rs b/bridge/src/client/chain/chain.rs index 71370a9e2..207a85a24 100644 --- a/bridge/src/client/chain/chain.rs +++ b/bridge/src/client/chain/chain.rs @@ -1,12 +1,7 @@ -use std::borrow::Borrow; - use bitcoin::{Amount, OutPoint, PubkeyHash, PublicKey}; use serde::{Deserialize, Serialize}; -use super::{ - base::ChainAdaptor, - ethereum::{EthereumAdaptor, EthereumInitConfig}, -}; +use super::{chain_adaptor::ChainAdaptor, mock_adaptor::MockAdaptor}; #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] pub struct PegOutEvent { @@ -37,65 +32,32 @@ pub struct PegInEvent { pub depositor_pubkey: PublicKey, } -static CLIENT_MISSING_CHAIN_DRIVER_ERROR: &str = "Bridge client is missing chain adaptor"; - pub struct Chain { - ethereum: Option, - default: Option>, + adaptor: Box, } impl Default for Chain { - fn default() -> Self { Self::new() } + fn default() -> Self { Self::new(Box::new(MockAdaptor::new(None))) } } impl Chain { - pub fn new() -> Self { - Self { - ethereum: EthereumAdaptor::new(), - default: None, - } - } - - pub fn init_default(&mut self, adaptor: Box) { self.default = Some(adaptor); } - - pub fn init_ethereum(&mut self, conf: EthereumInitConfig) { - self.ethereum = Some(EthereumAdaptor::from_config(conf)); - } + pub fn new(adaptor: Box) -> Self { Self { adaptor } } pub async fn get_peg_out_init(&self) -> Result, String> { - match self.get_driver() { - Ok(driver) => match driver.get_peg_out_init_event().await { - Ok(events) => Ok(events), - Err(err) => Err(err.to_string()), - }, + match self.adaptor.get_peg_out_init_event().await { + Ok(events) => Ok(events), Err(err) => Err(err.to_string()), } } pub async fn get_peg_out_burnt(&self) -> Result, String> { - match self.get_driver() { - Ok(driver) => match driver.get_peg_out_burnt_event().await { - Ok(events) => Ok(events), - Err(err) => Err(err.to_string()), - }, + match self.adaptor.get_peg_out_burnt_event().await { + Ok(events) => Ok(events), Err(err) => Err(err.to_string()), } } pub async fn get_peg_in_minted(&self) -> Result, String> { - match self.get_driver() { - Ok(driver) => driver.get_peg_in_minted_event().await, - Err(err) => Err(err.to_string()), - } - } - - fn get_driver(&self) -> Result<&dyn ChainAdaptor, &str> { - if self.default.is_some() { - Ok((*self.default.as_ref().unwrap()).borrow()) - } else if self.ethereum.is_some() { - return Ok(self.ethereum.as_ref().unwrap()); - } else { - Err(CLIENT_MISSING_CHAIN_DRIVER_ERROR) - } + self.adaptor.get_peg_in_minted_event().await } } diff --git a/bridge/src/client/chain/chain_adaptor.rs b/bridge/src/client/chain/chain_adaptor.rs new file mode 100644 index 000000000..bc950a8e5 --- /dev/null +++ b/bridge/src/client/chain/chain_adaptor.rs @@ -0,0 +1,30 @@ +use async_trait::async_trait; + +use crate::constants::DestinationNetwork; + +use super::chain::PegInEvent; +use super::chain::PegOutBurntEvent; +use super::chain::PegOutEvent; +use super::ethereum_adaptor::EthereumAdaptor; +use super::ethereum_adaptor::EthereumInitConfig; +use super::mock_adaptor::MockAdaptor; +use super::mock_adaptor::MockAdaptorConfig; + +#[async_trait] +pub trait ChainAdaptor { + async fn get_peg_out_init_event(&self) -> Result, String>; + async fn get_peg_out_burnt_event(&self) -> Result, String>; + async fn get_peg_in_minted_event(&self) -> Result, String>; +} + +pub fn get_chain_adaptor( + network: DestinationNetwork, + ethereum_config: Option, + mock_adaptor_config: Option, +) -> Box { + match network { + DestinationNetwork::Ethereum => Box::new(EthereumAdaptor::new(ethereum_config)), + DestinationNetwork::EthereumSepolia => Box::new(EthereumAdaptor::new(ethereum_config)), + DestinationNetwork::Local => Box::new(MockAdaptor::new(mock_adaptor_config)), + } +} diff --git a/bridge/src/client/chain/ethereum.rs b/bridge/src/client/chain/ethereum_adaptor.rs similarity index 79% rename from bridge/src/client/chain/ethereum.rs rename to bridge/src/client/chain/ethereum_adaptor.rs index e94c229c2..e270d3a32 100644 --- a/bridge/src/client/chain/ethereum.rs +++ b/bridge/src/client/chain/ethereum_adaptor.rs @@ -2,7 +2,9 @@ use std::str::FromStr; use alloy::rpc::types::Log; -use super::{base::ChainAdaptor, chain::PegInEvent, chain::PegOutBurntEvent, chain::PegOutEvent}; +use super::{ + chain::PegInEvent, chain::PegOutBurntEvent, chain::PegOutEvent, chain_adaptor::ChainAdaptor, +}; use alloy::sol_types::SolEvent; use alloy::{ eips::BlockNumberOrTag, @@ -203,38 +205,39 @@ impl ChainAdaptor for EthereumAdaptor { } impl EthereumAdaptor { - pub fn new() -> Option { - dotenv::dotenv().ok(); - let rpc_url_str = dotenv::var("BRIDGE_CHAIN_ADAPTOR_ETHEREUM_RPC_URL"); - let bridge_address_str = dotenv::var("BRIDGE_CHAIN_ADAPTOR_ETHEREUM_BRIDGE_ADDRESS"); - let bridge_creation = dotenv::var("BRIDGE_CHAIN_ADAPTOR_ETHEREUM_BRIDGE_CREATION"); - let to_block = dotenv::var("BRIDGE_CHAIN_ADAPTOR_ETHEREUM_TO_BLOCK"); - if bridge_address_str.is_err() || bridge_creation.is_err() { - return None; - } - if rpc_url_str.is_err() { - return None; + pub fn new(config: Option) -> Self { + if let Some(_config) = config { + Self::from_config(_config) + } else { + dotenv::dotenv().ok(); + let rpc_url_str = dotenv::var("BRIDGE_CHAIN_ADAPTOR_ETHEREUM_RPC_URL") + .expect("Failed to read BRIDGE_CHAIN_ADAPTOR_ETHEREUM_RPC_URL variable"); + let bridge_address_str = dotenv::var("BRIDGE_CHAIN_ADAPTOR_ETHEREUM_BRIDGE_ADDRESS") + .expect("Failed to read BRIDGE_CHAIN_ADAPTOR_ETHEREUM_BRIDGE_ADDRESS variable"); + let bridge_creation = dotenv::var("BRIDGE_CHAIN_ADAPTOR_ETHEREUM_BRIDGE_CREATION") + .expect("Failed to read BRIDGE_CHAIN_ADAPTOR_ETHEREUM_BRIDGE_CREATION variable"); + let to_block = dotenv::var("BRIDGE_CHAIN_ADAPTOR_ETHEREUM_TO_BLOCK"); + + let rpc_url = rpc_url_str.parse::(); + let bridge_address = bridge_address_str.parse::(); + Self::from_config(EthereumInitConfig { + rpc_url: rpc_url.unwrap(), + bridge_address: bridge_address.unwrap(), + bridge_creation_block: bridge_creation.parse::().unwrap(), + to_block: match to_block { + Ok(block) => Some(BlockNumberOrTag::from_str(block.as_str()).unwrap()), + Err(_) => Some(BlockNumberOrTag::Finalized), + }, + }) } - - let rpc_url = rpc_url_str.unwrap().parse::(); - let bridge_address = bridge_address_str.unwrap().parse::(); - Some(Self::from_config(EthereumInitConfig { - rpc_url: rpc_url.unwrap(), - bridge_address: bridge_address.unwrap(), - bridge_creation_block: bridge_creation.unwrap().parse::().unwrap(), - to_block: match to_block { - Ok(block) => Some(BlockNumberOrTag::from_str(block.as_str()).unwrap()), - Err(_) => Some(BlockNumberOrTag::Finalized), - }, - })) } - pub fn from_config(conf: EthereumInitConfig) -> Self { + fn from_config(config: EthereumInitConfig) -> Self { Self { - bridge_address: conf.bridge_address, - bridge_creation_block: conf.bridge_creation_block, - provider: ProviderBuilder::new().on_http(conf.rpc_url), - to_block: conf.to_block, + bridge_address: config.bridge_address, + bridge_creation_block: config.bridge_creation_block, + provider: ProviderBuilder::new().on_http(config.rpc_url), + to_block: config.to_block, } } } diff --git a/bridge/src/client/chain/mock_adaptor.rs b/bridge/src/client/chain/mock_adaptor.rs new file mode 100644 index 000000000..f3e4b9f72 --- /dev/null +++ b/bridge/src/client/chain/mock_adaptor.rs @@ -0,0 +1,52 @@ +use async_trait::async_trait; + +use super::{ + chain::{PegInEvent, PegOutBurntEvent, PegOutEvent}, + chain_adaptor::ChainAdaptor, +}; + +pub struct MockAdaptor { + config: Option, +} + +pub struct MockAdaptorConfig { + pub peg_out_init_events: Option>, + pub peg_out_burnt_events: Option>, + pub peg_out_minted_events: Option>, +} + +impl MockAdaptor { + pub fn new(config: Option) -> Self { Self { config } } +} + +#[async_trait] +impl ChainAdaptor for MockAdaptor { + async fn get_peg_out_init_event(&self) -> Result, String> { + if let Some(_config) = &self.config { + if let Some(_init_events) = &_config.peg_out_init_events { + return Ok(_init_events.clone()); + } + } + + Ok(vec![]) + } + + async fn get_peg_out_burnt_event(&self) -> Result, String> { + if let Some(_config) = &self.config { + if let Some(_burnt_events) = &_config.peg_out_burnt_events { + return Ok(_burnt_events.clone()); + } + } + + Ok(vec![]) + } + async fn get_peg_in_minted_event(&self) -> Result, String> { + if let Some(_config) = &self.config { + if let Some(_minted_events) = &_config.peg_out_minted_events { + return Ok(_minted_events.clone()); + } + } + + Ok(vec![]) + } +} diff --git a/bridge/src/client/chain/mod.rs b/bridge/src/client/chain/mod.rs index 1ac558f40..ce0af46ea 100644 --- a/bridge/src/client/chain/mod.rs +++ b/bridge/src/client/chain/mod.rs @@ -1,3 +1,4 @@ -pub mod base; pub mod chain; -pub mod ethereum; +pub mod chain_adaptor; +pub mod ethereum_adaptor; +pub mod mock_adaptor; diff --git a/bridge/src/client/cli/client_command.rs b/bridge/src/client/cli/client_command.rs index b269759b5..0d3aca869 100644 --- a/bridge/src/client/cli/client_command.rs +++ b/bridge/src/client/cli/client_command.rs @@ -1,14 +1,17 @@ -use super::key_command::KeysCommand; +use super::key_command::{Config, KeysCommand}; +use super::utils::get_mock_chain_service; +use crate::client::chain::chain_adaptor::get_chain_adaptor; use crate::client::client::BitVMClient; +use crate::client::esplora::get_esplora_url; +use crate::commitments::CommitmentMessageId; use crate::common::ZkProofVerifyingKey; use crate::constants::DestinationNetwork; use crate::contexts::base::generate_keys_from_secret; -use crate::graphs::base::{VERIFIER_0_SECRET, VERIFIER_1_SECRET}; -use crate::proof::get_proof; +use crate::proof::{get_proof, invalidate_proof}; use crate::transactions::base::Input; use ark_serialize::CanonicalDeserialize; -use bitcoin::PublicKey; +use bitcoin::{Address, PublicKey}; use bitcoin::{Network, OutPoint}; use clap::{arg, ArgMatches, Command}; use colored::Colorize; @@ -27,6 +30,7 @@ pub struct CommonArgs { pub struct ClientCommand { client: BitVMClient, + config: Config, } impl ClientCommand { @@ -34,33 +38,31 @@ impl ClientCommand { let (source_network, destination_network) = match common_args.environment.as_deref() { Some("mainnet") => (Network::Bitcoin, DestinationNetwork::Ethereum), Some("testnet") => (Network::Testnet, DestinationNetwork::EthereumSepolia), + Some("regtest") => (Network::Regtest, DestinationNetwork::Local), _ => { - eprintln!("Invalid environment. Use mainnet, testnet."); + eprintln!("Invalid environment. Use mainnet, testnet or regtest."); std::process::exit(1); } }; let keys_command = KeysCommand::new(common_args.key_dir); - let config = keys_command.read_config().expect("Failed to read config"); + let config = keys_command + .read_config() + .expect("Failed to read config file"); - let n_of_n_public_keys = common_args.verifiers.unwrap_or_else(|| { - let (_, verifier_0_public_key) = - generate_keys_from_secret(source_network, VERIFIER_0_SECRET); - let (_, verifier_1_public_key) = - generate_keys_from_secret(source_network, VERIFIER_1_SECRET); - vec![verifier_0_public_key, verifier_1_public_key] - }); + let n_of_n_public_keys = common_args.verifiers.expect("Error: Verifier public keys must be specified either in command line or environment variable."); let mut verifying_key = None; - if let Some(vk) = config.keys.verifying_key { + if let Some(vk) = config.keys.verifying_key.clone() { let bytes = hex::decode(vk).unwrap(); verifying_key = Some(ZkProofVerifyingKey::deserialize_compressed(&*bytes).unwrap()); } let bitvm_client = BitVMClient::new( - None, + Some(get_esplora_url(source_network)), source_network, destination_network, + Some(get_chain_adaptor(DestinationNetwork::Local, None, None)), // TODO: Will be replaced with a destination network specific adaptor once Ethereum support is added. &n_of_n_public_keys, config.keys.depositor.as_deref(), config.keys.operator.as_deref(), @@ -73,19 +75,66 @@ impl ClientCommand { Self { client: bitvm_client, + config, } } + pub fn get_operator_address_command() -> Command { + Command::new("get-operator-address") + .short_flag('o') + .about("Get an address spendable by the configured operator private key") + .after_help("Get an address spendable by the configured operator private key") + } + + pub async fn handle_get_operator_address(&mut self) -> io::Result<()> { + println!( + "Operator address: {}", + self.client.get_operator_address().to_string().green() + ); + Ok(()) + } + + pub fn get_operator_utxos_command() -> Command { + Command::new("get-operator-utxos") + .short_flag('r') + .about("Get a list of the operator's utxos") + .after_help("Get a list of the operator's utxos") + } + + pub async fn handle_get_operator_utxos(&mut self) -> io::Result<()> { + let utxos = self.client.get_operator_utxos().await; + match utxos.len() { + 0 => println!("No operator UTXOs found."), + utxo_count => { + println!( + "{} operator UTXO{} found (: ):", + utxo_count, + if utxo_count == 1 { "" } else { "s" } + ); + for utxo in utxos { + println!( + "{}:{} {} {}", + utxo.txid, utxo.vout, utxo.value, utxo.status.confirmed + ); + } + } + } + + Ok(()) + } + pub fn get_depositor_address_command() -> Command { Command::new("get-depositor-address") .short_flag('d') - .about("Get an address spendable by the registered depositor key") - .after_help("Get an address spendable by the registered depositor key") + .about("Get an address spendable by the configured depositor private key") + .after_help("Get an address spendable by the configured depositor private key") } pub async fn handle_get_depositor_address(&mut self) -> io::Result<()> { - let address = self.client.get_depositor_address().to_string(); - println!("{address}"); + println!( + "Depositor address: {}", + self.client.get_depositor_address().to_string().green() + ); Ok(()) } @@ -97,9 +146,24 @@ impl ClientCommand { } pub async fn handle_get_depositor_utxos(&mut self) -> io::Result<()> { - for utxo in self.client.get_depositor_utxos().await { - println!("{}:{} {}", utxo.txid, utxo.vout, utxo.value); + let utxos = self.client.get_depositor_utxos().await; + match utxos.len() { + 0 => println!("No depositor UTXOs found."), + utxo_count => { + println!( + "{} operator UTXO{} found (: ):", + utxo_count, + if utxo_count == 1 { "" } else { "s" } + ); + for utxo in utxos { + println!( + "{}:{} {} {}", + utxo.txid, utxo.vout, utxo.value, utxo.status.confirmed + ); + } + } } + Ok(()) } @@ -108,16 +172,18 @@ impl ClientCommand { .short_flag('n') .about("Initiate a peg-in") .after_help("Initiate a peg-in by creating a peg-in graph") - .arg(arg!(-u --utxo "Specify the uxo to spend from. Format: :") + .arg(arg!(-u --utxo "Specify the utxo to spend from. Format: :") .required(true)) .arg(arg!(-d --destination_address "The evm-address to send the wrapped bitcoin to") - .required(true)) + .required(true)) } pub async fn handle_initiate_peg_in_command( &mut self, sub_matches: &ArgMatches, ) -> io::Result<()> { + self.client.sync().await; + let utxo = sub_matches.get_one::("utxo").unwrap(); let evm_address = sub_matches .get_one::("destination_address") @@ -125,7 +191,11 @@ impl ClientCommand { let outpoint = OutPoint::from_str(utxo).unwrap(); let tx = self.client.esplora.get_tx(&outpoint.txid).await.unwrap(); - let tx = tx.unwrap(); + let tx = tx.expect(&format!( + "Error: {} did not return any tx for txid {}", + self.client.esplora.url(), + outpoint.txid + )); let input = Input { outpoint, amount: tx.output[outpoint.vout as usize].value, @@ -134,12 +204,132 @@ impl ClientCommand { self.client.flush().await; - println!("Created peg-in with ID {peg_in_id}. Broadcasting deposit..."); + println!("Created peg-in graph with ID: {peg_in_id}"); + println!("Broadcasting deposit..."); - match self.client.broadcast_peg_in_deposit(&peg_in_id).await { - Ok(txid) => println!("Broadcasted peg-in deposit with txid {txid}"), - Err(e) => println!("Failed to broadcast peg-in deposit: {}", e), + if let Err(e) = self.client.broadcast_peg_in_deposit(&peg_in_id).await { + eprintln!("Failed to broadcast peg-in deposit: {e}"); } + + Ok(()) + } + + pub fn get_create_peg_out_graph_command() -> Command { + Command::new("create-peg-out") + .short_flag('t') + .about("Create peg-out graph for specified peg-in graph") + .after_help("") + .arg( + arg!(-u --utxo "Specify the utxo to spend from. Format: :") + .required(true), + ) + .arg( + arg!(-i --peg_in_id "Specify the peg-in graph ID").required(true), + ) + } + + pub async fn handle_create_peg_out_graph_command( + &mut self, + sub_matches: &ArgMatches, + ) -> io::Result<()> { + self.client.sync().await; + + let utxo = sub_matches.get_one::("utxo").unwrap(); + let peg_in_id = sub_matches.get_one::("peg_in_id").unwrap(); + let outpoint = OutPoint::from_str(utxo).unwrap(); + + let tx = self.client.esplora.get_tx(&outpoint.txid).await.unwrap(); + let tx = tx.unwrap(); + let input = Input { + outpoint, + amount: tx.output[outpoint.vout as usize].value, + }; + + let peg_out_id = self.client.create_peg_out_graph( + peg_in_id, + input, + CommitmentMessageId::generate_commitment_secrets(), + ); + + self.client.flush().await; + + println!("Created peg-out with ID: {peg_out_id}"); + Ok(()) + } + + pub fn get_push_nonces_command() -> Command { + Command::new("push-nonces") + .short_flag('c') + .about("Push nonces for peg-out or peg-in graph") + .after_help("") + .arg(arg!(-i --id "Specify the peg-in or peg-out graph ID").required(true)) + } + + pub async fn handle_push_nonces_command(&mut self, sub_matches: &ArgMatches) -> io::Result<()> { + let graph_id = sub_matches.get_one::("id").unwrap(); + + self.client.sync().await; + self.client.push_verifier_nonces(graph_id); + self.client.flush().await; + + Ok(()) + } + + pub fn get_push_signature_command() -> Command { + Command::new("push-signatures") + .short_flag('g') + .about("Push signatures for peg-out or peg-in graph") + .after_help("") + .arg(arg!(-i --id "Specify the peg-in or peg-out graph ID").required(true)) + } + + pub async fn handle_push_signature_command( + &mut self, + sub_matches: &ArgMatches, + ) -> io::Result<()> { + let graph_id = sub_matches.get_one::("id").unwrap(); + + self.client.sync().await; + self.client.push_verifier_signature(graph_id); + self.client.flush().await; + + Ok(()) + } + + pub fn get_mock_l2_pegout_event_command() -> Command { + Command::new("mock-l2-pegout-event") + .short_flag('x') + .about("FOR TEST PURPOSES ONLY! Use mock L2 chain service with specified peg-in-confirm txid") + .after_help("") + .arg( + arg!(-u --utxo "Specify the peg-in confirm utxo. Format: :") + .required(true), + ) + } + + pub async fn handle_mock_l2_pegout_event_command( + &mut self, + sub_matches: &ArgMatches, + ) -> io::Result<()> { + let utxo = sub_matches.get_one::("utxo").unwrap(); + let outpoint = OutPoint::from_str(utxo).unwrap(); + + if let Some(operator_secret) = &self.config.keys.operator { + let (_, operator_public_key) = + generate_keys_from_secret(self.client.source_network, operator_secret); + + self.client.sync().await; + let mock_chain_service = get_mock_chain_service(outpoint, operator_public_key); + self.client.set_chain_service(mock_chain_service); + self.client.sync_l2().await; + self.client.flush().await; + } else { + return Err(io::Error::new( + io::ErrorKind::Other, + "Failed to set chain service, missing operator configuration", + )); + } + Ok(()) } @@ -167,6 +357,7 @@ impl ClientCommand { } } + // TODO: there are verifier's commands missing here pub fn get_broadcast_command() -> Command { Command::new("broadcast") .short_flag('b') @@ -185,26 +376,38 @@ impl ClientCommand { Command::new("tx") .about("Broadcast transactions") .arg(arg!(-g --graph_id "Peg-out graph ID").required(true)) + .arg(arg!(-u --utxo "Specify the utxo to spend from. Format: :").required(false)) + .arg(arg!(-a --address
"Specify the reward address to receive BTC reward").required(false)) + .subcommand(Command::new("peg_out").about("Broadcast peg-out")) .subcommand(Command::new("peg_out_confirm").about("Broadcast peg-out confirm")) .subcommand(Command::new("kick_off_1").about("Broadcast kick off 1")) .subcommand(Command::new("kick_off_2").about("Broadcast kick off 2")) .subcommand(Command::new("start_time").about("Broadcast start time")) .subcommand(Command::new("assert_initial").about("Broadcast assert initial")) .subcommand( - Command::new("assert_commit_1").about("Broadcast assert commitment 1"), + Command::new("assert_commit_1").about("Broadcast assert commit 1"), + ) + .subcommand( + Command::new("assert_commit_2").about("Broadcast assert commit 2"), ) .subcommand( - Command::new("assert_commit_2").about("Broadcast assert commitment 2"), + Command::new("assert_commit_1_invalid").about("FOR TEST PURPOSES ONLY! Broadcast assert commit 1 with invalid proof"), + ) + .subcommand( + Command::new("assert_commit_2_invalid").about("FOR TEST PURPOSES ONLY! Broadcast assert commit 2 with invalid proof"), ) .subcommand(Command::new("assert_final").about("Broadcast assert final")) .subcommand(Command::new("take_1").about("Broadcast take 1")) .subcommand(Command::new("take_2").about("Broadcast take 2")) + .subcommand(Command::new("disprove").about("Broadcast disprove")) .subcommand_required(true), ) .subcommand_required(true) } pub async fn handle_broadcast_command(&mut self, sub_matches: &ArgMatches) -> io::Result<()> { + self.client.sync().await; + let subcommand = sub_matches.subcommand(); let graph_id = subcommand.unwrap().1.get_one::("graph_id").unwrap(); @@ -212,6 +415,19 @@ impl ClientCommand { Some(("deposit", _)) => self.client.broadcast_peg_in_deposit(graph_id).await, Some(("refund", _)) => self.client.broadcast_peg_in_refund(graph_id).await, Some(("confirm", _)) => self.client.broadcast_peg_in_confirm(graph_id).await, + Some(("peg_out", _)) => { + let utxo = subcommand.unwrap().1.get_one::("utxo").unwrap(); + let outpoint = OutPoint::from_str(utxo).unwrap(); + let tx = self.client.esplora.get_tx(&outpoint.txid).await.unwrap(); + let tx = tx.unwrap(); + let input = Input { + outpoint, + amount: tx.output[outpoint.vout as usize].value, + }; + let result = self.client.broadcast_peg_out(graph_id, input).await; + self.client.flush().await; + result + } Some(("peg_out_confirm", _)) => self.client.broadcast_peg_out_confirm(graph_id).await, Some(("kick_off_1", _)) => self.client.broadcast_kick_off_1(graph_id).await, Some(("kick_off_2", _)) => self.client.broadcast_kick_off_2(graph_id).await, @@ -227,15 +443,33 @@ impl ClientCommand { .broadcast_assert_commit_2(graph_id, &get_proof()) .await } + Some(("assert_commit_1_invalid", _)) => { + self.client + .broadcast_assert_commit_1(graph_id, &invalidate_proof(&get_proof())) + .await + } + Some(("assert_commit_2_invalid", _)) => { + self.client + .broadcast_assert_commit_2(graph_id, &invalidate_proof(&get_proof())) + .await + } Some(("assert_final", _)) => self.client.broadcast_assert_final(graph_id).await, Some(("take_1", _)) => self.client.broadcast_take_1(graph_id).await, Some(("take_2", _)) => self.client.broadcast_take_2(graph_id).await, + Some(("disprove", _)) => { + let address = subcommand.unwrap().1.get_one::("address").unwrap(); + let reward_address = Address::from_str(address).unwrap(); + let reward_script = reward_address.assume_checked().script_pubkey(); // TODO: verify checked/unchecked address + + self.client + .broadcast_disprove(graph_id, reward_script) + .await + } _ => unreachable!(), }; - match result { - Ok(txid) => println!("Broadcasted transaction with txid {txid}"), - Err(e) => println!("Failed to broadcast transaction: {}", e), + if let Err(e) = result { + println!("Failed to broadcast transaction: {e}"); } Ok(()) @@ -299,6 +533,10 @@ impl ClientCommand { let key_dir = matches.get_one::("key-dir").cloned(); let keys_command = KeysCommand::new(key_dir); keys_command.handle_command(sub_matches)?; + } else if matches.subcommand_matches("get-operator-address").is_some() { + self.handle_get_operator_address().await?; + } else if matches.subcommand_matches("get-operator-utxos").is_some() { + self.handle_get_operator_utxos().await?; } else if matches .subcommand_matches("get-depositor-address") .is_some() @@ -308,6 +546,16 @@ impl ClientCommand { self.handle_get_depositor_utxos().await?; } else if let Some(sub_matches) = matches.subcommand_matches("initiate-peg-in") { self.handle_initiate_peg_in_command(sub_matches).await?; + } else if let Some(sub_matches) = matches.subcommand_matches("create-peg-out") { + self.handle_create_peg_out_graph_command(sub_matches) + .await?; + } else if let Some(sub_matches) = matches.subcommand_matches("push-nonces") { + self.handle_push_nonces_command(sub_matches).await?; + } else if let Some(sub_matches) = matches.subcommand_matches("push-signatures") { + self.handle_push_signature_command(sub_matches).await?; + } else if let Some(sub_matches) = matches.subcommand_matches("mock-l2-pegout-event") { + self.handle_mock_l2_pegout_event_command(sub_matches) + .await?; } else if matches.subcommand_matches("status").is_some() { self.handle_status_command().await?; } else if let Some(sub_matches) = matches.subcommand_matches("broadcast") { diff --git a/bridge/src/client/cli/key_command.rs b/bridge/src/client/cli/key_command.rs index e3918d863..5566fdb3b 100644 --- a/bridge/src/client/cli/key_command.rs +++ b/bridge/src/client/cli/key_command.rs @@ -1,6 +1,7 @@ use bitcoin::{Network, PublicKey}; use clap::{arg, ArgGroup, ArgMatches, Command}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::env; use std::fs::{self, OpenOptions}; use std::io::{self, Read, Write}; @@ -52,74 +53,117 @@ impl KeysCommand { Command::new("keys") .short_flag('k') .about("Manage secret keys for different contexts") - .after_help("The depositor, operator, verifier, and withdrawer contexts are optional and can be specified using the -d, -o, -v, and -w flags respectively. If a context is not specified, the default key for that context will be used. The verifying key for the zero-knowledge proof is optional and must be specified when running scenarios that require it.") + .after_help("The depositor, operator, verifier, and withdrawer contexts are optional and can be specified using the -d, -o, -v, and -w flags respectively. If a context is not specified, the current key configuration will be displayed. The verifying key for the zero-knowledge proof is optional and must be specified when running scenarios that involve proof verification.") .arg(arg!(-d --depositor "Secret key for depositor").required(false)) .arg(arg!(-o --operator "Secret key for operator").required(false)) .arg(arg!(-v --verifier "Secret key for verifier").required(false)) .arg(arg!(-w --withdrawer "Secret key for withdrawer").required(false)) .arg(arg!(-k --vk "Zero-knowledge proof verifying key").required(false)) .group(ArgGroup::new("context") - .args(["depositor", "operator", "verifier", "withdrawer"]) - .required(true)) + .args(["depositor", "operator", "verifier", "withdrawer"])) } pub fn handle_command(&self, sub_matches: &ArgMatches) -> io::Result<()> { let mut config = self.read_config()?; - if let Some(secret_key) = sub_matches.get_one::("depositor") { - if self.validate_key(secret_key) { - config.keys.depositor = Some(secret_key.clone()); - println!( - "Secret key for depositor {} saved successfully!", - pubkey_of(secret_key) - ); + if !sub_matches.args_present() { + // If no arguments are specified, output the current key configuration. + let keys = HashMap::from([ + ("DEPOSITOR", &config.keys.depositor), + ("OPERATOR", &config.keys.operator), + ("VERIFIER", &config.keys.verifier), + ("WITHDRAWER", &config.keys.withdrawer), + ("VERIFYING KEY", &config.keys.verifying_key), + ]); + + if keys.values().any(|k| k.is_some()) { + println!("Key configuration:"); + println!(); + + let print_user_key = |private_key: &Option, name: &str| { + if let Some(prvkey) = private_key { + println!("[{name}]:"); + println!(" Private key: {}", prvkey); + println!(" Public key: {}", pubkey_of(prvkey)); + println!(); + } + }; + + let print_verifying_key = |verifying_key: &Option, name: &str| { + if let Some(vk) = verifying_key { + println!("[{name}]:"); + println!(" Key: {}", vk); + println!(); + } + }; + + let mut name = "DEPOSITOR"; + print_user_key(keys.get(name).unwrap(), name); + name = "OPERATOR"; + print_user_key(keys.get(name).unwrap(), name); + name = "VERIFIER"; + print_user_key(keys.get(name).unwrap(), name); + name = "WITHDRAWER"; + print_user_key(keys.get(name).unwrap(), name); + name = "VERIFYING KEY"; + print_verifying_key(keys.get(name).unwrap(), name); } else { - println!("error: Invalid depositor secret key."); - } - } else if let Some(secret_key) = sub_matches.get_one::("operator") { - if self.validate_key(secret_key) { - config.keys.operator = Some(secret_key.clone()); - println!( - "Secret key for operator {} saved successfully!", - pubkey_of(secret_key) - ); - } else { - println!("error: Invalid operator secret key."); - } - } else if let Some(secret_key) = sub_matches.get_one::("verifier") { - if self.validate_key(secret_key) { - config.keys.verifier = Some(secret_key.clone()); - println!( - "Secret key for verifier {} saved successfully!", - pubkey_of(secret_key) - ); - } else { - println!("error: Invalid verifier secret key."); - } - } else if let Some(secret_key) = sub_matches.get_one::("withdrawer") { - if self.validate_key(secret_key) { - config.keys.withdrawer = Some(secret_key.clone()); - println!( - "Secret key for withdrawer {} saved successfully!", - pubkey_of(secret_key) - ); - } else { - eprintln!("error: Invalid withdrawer secret key."); - std::process::exit(1); - } - } else if let Some(verifying_key) = sub_matches.get_one::("vk") { - if self.validate_verifying_key(verifying_key) { - config.keys.verifying_key = Some(verifying_key.clone()); - println!("ZK proof verifying key saved successfully!"); - } else { - println!("error: Invalid ZK proof verifying key."); + println!("No keys are configured."); + println!(); } + + Ok(()) } else { - eprintln!("Invalid command. Use --help to see the valid commands."); - std::process::exit(1); + if let Some(secret_key) = sub_matches.get_one::("depositor") { + if self.validate_key(secret_key) { + config.keys.depositor = Some(secret_key.clone()); + println!( + "Secret key for depositor {} saved successfully!", + pubkey_of(secret_key) + ); + } else { + eprintln!("error: Invalid depositor secret key."); + } + } else if let Some(secret_key) = sub_matches.get_one::("operator") { + if self.validate_key(secret_key) { + config.keys.operator = Some(secret_key.clone()); + println!( + "Secret key for operator {} saved successfully!", + pubkey_of(secret_key) + ); + } else { + eprintln!("error: Invalid operator secret key."); + } + } else if let Some(secret_key) = sub_matches.get_one::("verifier") { + if self.validate_key(secret_key) { + config.keys.verifier = Some(secret_key.clone()); + println!( + "Secret key for verifier {} saved successfully!", + pubkey_of(secret_key) + ); + } else { + eprintln!("error: Invalid verifier secret key."); + } + } else if let Some(secret_key) = sub_matches.get_one::("withdrawer") { + if self.validate_key(secret_key) { + config.keys.withdrawer = Some(secret_key.clone()); + println!( + "Secret key for withdrawer {} saved successfully!", + pubkey_of(secret_key) + ); + } else { + eprintln!("error: Invalid withdrawer secret key."); + } + } else if let Some(verifying_key) = sub_matches.get_one::("vk") { + if self.validate_verifying_key(verifying_key) { + config.keys.verifying_key = Some(verifying_key.clone()); + println!("ZK proof verifying key saved successfully!"); + } else { + eprintln!("error: Invalid ZK proof verifying key."); + } + } + self.write_config(&config) } - - self.write_config(&config) } pub fn read_config(&self) -> io::Result { diff --git a/bridge/src/client/cli/mod.rs b/bridge/src/client/cli/mod.rs index 311415351..4bbf4be67 100644 --- a/bridge/src/client/cli/mod.rs +++ b/bridge/src/client/cli/mod.rs @@ -2,4 +2,5 @@ pub mod client_command; pub mod key_command; pub mod query_command; pub mod query_response; +pub mod utils; pub mod validation; diff --git a/bridge/src/client/cli/query_command.rs b/bridge/src/client/cli/query_command.rs index e79c6878f..27819f214 100644 --- a/bridge/src/client/cli/query_command.rs +++ b/bridge/src/client/cli/query_command.rs @@ -9,25 +9,24 @@ use super::{ }; use crate::{ client::{ + chain::chain_adaptor::get_chain_adaptor, client::BitVMClient, + esplora::get_esplora_url, sdk::{query::ClientCliQuery, query_contexts::depositor_signatures::DepositorSignatures}, }, constants::DestinationNetwork, contexts::base::generate_keys_from_secret, - graphs::base::VERIFIER_0_SECRET, scripts::generate_pay_to_pubkey_script_address, transactions::base::Input, }; -// TODO: This is Alpen signet. Verify what we need here and update accordingly. -const ESPLORA_URL: &str = "https://esploraapi53d3659b.devnet-annapurna.stratabtc.org/"; - pub struct QueryCommand { client: BitVMClient, network: Network, } -pub const FAKE_SECRET: &str = "1000000000000000000000000000000000000000000000000000000000000000"; +const VERIFIER_0_SECRET: &str = "ee0817eac0c13aa8ee2dd3256304041f09f0499d1089b56495310ae8093583e2"; +const FAKE_SECRET: &str = "1000000000000000000000000000000000000000000000000000000000000000"; const QUERY_COMMAND_PATH_PREFIX: &str = "query_command"; @@ -43,9 +42,10 @@ impl QueryCommand { let n_of_n_public_keys: Vec = vec![verifier_0_public_key]; let bitvm_client = BitVMClient::new( - Some(ESPLORA_URL), + Some(get_esplora_url(source_network)), source_network, destination_network, + Some(get_chain_adaptor(DestinationNetwork::Local, None, None)), // TODO: Update this according to the requirements for query command. &n_of_n_public_keys, Some(FAKE_SECRET), Some(FAKE_SECRET), @@ -557,7 +557,7 @@ impl QueryCommand { "Fund {:?} with {} sats at {}", funding_utxo_address, input_value.to_sat(), - ESPLORA_URL, + client.esplora.url(), ); }); OutPoint { diff --git a/bridge/src/client/cli/utils.rs b/bridge/src/client/cli/utils.rs new file mode 100644 index 000000000..f3f9ba87b --- /dev/null +++ b/bridge/src/client/cli/utils.rs @@ -0,0 +1,30 @@ +use std::str::FromStr; + +use bitcoin::{hashes::hash160::Hash, Amount, OutPoint, PubkeyHash, PublicKey}; + +use crate::client::chain::{ + chain::{Chain, PegOutEvent}, + mock_adaptor::{MockAdaptor, MockAdaptorConfig}, +}; + +pub fn get_mock_chain_service(outpoint: OutPoint, operator_public_key: PublicKey) -> Chain { + let mock_adaptor_config = MockAdaptorConfig { + peg_out_init_events: Some(vec![PegOutEvent { + source_outpoint: outpoint, + amount: Amount::from_sat(0), + timestamp: 1722328130u32, + withdrawer_chain_address: "0x0000000000000000000000000000000000000000".to_string(), + withdrawer_destination_address: "0x0000000000000000000000000000000000000000" + .to_string(), + withdrawer_public_key_hash: PubkeyHash::from_raw_hash( + Hash::from_str("0e6719ac074b0e3cac76d057643506faa1c266b3").unwrap(), + ), + operator_public_key: operator_public_key, + tx_hash: [0u8; 32].into(), + }]), + peg_out_burnt_events: None, + peg_out_minted_events: None, + }; + let mock_adaptor = MockAdaptor::new(Some(mock_adaptor_config)); + Chain::new(Box::new(mock_adaptor)) +} diff --git a/bridge/src/client/client.rs b/bridge/src/client/client.rs index 800b5f01b..539223259 100644 --- a/bridge/src/client/client.rs +++ b/bridge/src/client/client.rs @@ -2,8 +2,10 @@ use bitcoin::{ absolute::Height, consensus::encode::serialize_hex, Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Transaction, Txid, XOnlyPublicKey, }; +use colored::Colorize; use esplora_client::{AsyncClient, Builder, TxStatus, Utxo}; use futures::future::join_all; +use human_bytes::human_bytes; use musig2::SecNonce; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -13,7 +15,10 @@ use std::{ }; use crate::{ - client::files::DEFAULT_PATH_PREFIX, + client::{ + chain::ethereum_adaptor::EthereumAdaptor, esplora::get_esplora_url, + files::DEFAULT_PATH_PREFIX, + }, commitments::CommitmentMessageId, common::ZkProofVerifyingKey, connectors::{base::TaprootConnector, connector_0::Connector0, connector_z::ConnectorZ}, @@ -30,6 +35,7 @@ use crate::{ }, proof::get_proof, scripts::generate_pay_to_pubkey_script_address, + serialization::{serialize, try_deserialize_slice}, transactions::{ peg_in_confirm::PegInConfirmTransaction, peg_in_deposit::PegInDepositTransaction, peg_in_refund::PegInRefundTransaction, pre_signed_musig2::PreSignedMusig2Transaction, @@ -51,13 +57,12 @@ use super::{ peg_in::{generate_id as peg_in_generate_id, PegInGraph}, peg_out::{generate_id as peg_out_generate_id, PegOutGraph}, }, - serialization::{serialize, try_deserialize}, transactions::{ base::{Input, InputWithScript}, pre_signed::PreSignedTransaction, }, }, - chain::chain::Chain, + chain::{chain::Chain, chain_adaptor::ChainAdaptor}, data_store::data_store::DataStore, files::{ get_private_data_file_path, get_private_data_from_file, save_local_private_file, @@ -69,8 +74,6 @@ use super::{ }, }; -// TODO: Update for production. -const ESPLORA_URL: &str = "https://esploraapi53d3659b.devnet-annapurna.stratabtc.org/"; const TEN_MINUTES: u64 = 10 * 60; pub type UtxoSet = HashMap; @@ -90,7 +93,7 @@ impl BitVMClientPublicData { if let Some(peg_out) = self.peg_out_graphs.iter_mut().find(|x| x.id() == graph_id) { return peg_out; } - panic!("graph id not found"); + panic!("Graph ID not found"); } } @@ -123,7 +126,7 @@ pub struct BitVMClient { private_data: BitVMClientPrivateData, - chain_adaptor: Chain, + chain_service: Chain, zkproof_verifying_key: Option, } @@ -134,6 +137,7 @@ impl BitVMClient { esplora_url: Option<&str>, source_network: Network, destination_network: DestinationNetwork, + chain_adaptor: Option>, n_of_n_public_keys: &[PublicKey], depositor_secret: Option<&str>, operator_secret: Option<&str>, @@ -188,7 +192,7 @@ impl BitVMClient { // The local file path, on the other hand, must use the platform specific path separator. // Additionally, it includes the provided file path prefix to create a user namespace. let local_file_path = Path::new(BRIDGE_DATA_DIRECTORY_NAME) - .join(file_path_prefix.unwrap_or(DEFAULT_PATH_PREFIX)) + .join(file_path_prefix.unwrap_or(DEFAULT_PATH_PREFIX)) // TODO: Refactor to require a prefix and remove the const, as the client already has a default and will always pass it. Also rename to 'user_profile' to match the client implementation. .join(source_network.to_string()) .join(destination_network.to_string()) .join(n_of_n_public_key.to_string()); @@ -205,10 +209,8 @@ impl BitVMClient { let private_data = get_private_data_from_file(&get_private_data_file_path(&local_file_path)); - let chain_adaptor = Chain::new(); - Self { - esplora: Builder::new(esplora_url.unwrap_or(ESPLORA_URL)) + esplora: Builder::new(esplora_url.unwrap_or(get_esplora_url(source_network))) .build_async() .expect("Could not build esplora client"), source_network, @@ -226,7 +228,9 @@ impl BitVMClient { private_data, - chain_adaptor, + chain_service: Chain::new( + chain_adaptor.unwrap_or_else(|| Box::new(EthereumAdaptor::new(None))), + ), zkproof_verifying_key, } @@ -239,6 +243,11 @@ impl BitVMClient { // TODO: This should be private. Currently used in the fees test. See if it can be refactored. pub fn private_data(&self) -> &BitVMClientPrivateData { &self.private_data } + // TODO: This fn is only used in tests. Consider refactoring, so it can be removed. + pub fn set_chain_service(&mut self, chain_service: Chain) { + self.chain_service = chain_service; + } + fn save_private_data(&self) { save_local_private_file(&self.local_file_path, &serialize(&self.private_data)); } @@ -250,14 +259,14 @@ impl BitVMClient { pub async fn flush(&mut self) { self.save_to_data_store().await; } /* - File syncing flow with data store - 1. Fetch the latest file - 2. Fetch all files within 10 minutes (use timestamp) - 3. Merge files - 4. Client modifies file and clicks save - 5. Fetch files that were created after fetching 1-2. - 6. Merge with your file - 7. Push the file to the server + Expected file syncing flow with data store: + 1. Fetch the latest file ⎫ + 2. Fetch all files within 10 minutes (use timestamp) ⎬ BitVMClient::sync() + 3. Merge files ⎭ + 4. Client modifies file and clicks save } BitVMClient:: + 5. Fetch files that were created after fetching 1-2. ⎫ + 6. Merge with your file ⎬ BitVMClient::flush() + 7. Push the file to the server ⎭ */ async fn read_from_data_store(&mut self) { @@ -303,12 +312,8 @@ impl BitVMClient { } } - pub fn set_chain_adaptor(&mut self, chain_adaptor: Chain) { - self.chain_adaptor = chain_adaptor; - } - async fn read_from_l2(&mut self) { - let peg_out_result = self.chain_adaptor.get_peg_out_init().await; + let peg_out_result = self.chain_service.get_peg_out_init().await; if peg_out_result.is_ok() { let mut events = peg_out_result.unwrap(); for peg_out_graph in self.data.peg_out_graphs.iter_mut() { @@ -317,7 +322,7 @@ impl BitVMClient { Ok(_) => { if peg_out_graph.peg_out_chain_event.is_some() { println!( - "Peg Out Graph id: {} Event Matched, Event: {:?}", + "Peg-out graph ID: {} Event Matched, Event: {:?}", peg_out_graph.id(), peg_out_graph.peg_out_chain_event ) @@ -417,11 +422,10 @@ impl BitVMClient { for file_name in file_names.iter() { let result = self .data_store - .fetch_data_by_key(file_name, Some(&self.remote_file_path)) + .fetch_compressed_data_by_key(file_name, Some(&self.remote_file_path)) .await; // TODO: use `fetch_by_key()` function - if result.is_ok() && result.as_ref().unwrap().is_some() { - let data = - try_deserialize::(&(result.unwrap()).unwrap()); + if result.is_ok() && result.as_ref().unwrap().0.is_some() { + let data = try_deserialize_slice(&(result.unwrap()).0.unwrap()); if data.is_ok() && Self::validate_data(data.as_ref().unwrap()) { // merge the file if the data is valid println!("Merging {} data...", { file_name }); @@ -452,13 +456,15 @@ impl BitVMClient { let file_name_result = file_names.pop(); if file_name_result.is_some() { let file_name = file_name_result.unwrap(); - let (latest_data, latest_data_len) = + let (latest_data, latest_data_len, encoded_size) = Self::fetch_by_key(data_store, &file_name, file_path).await; if latest_data.is_some() && Self::validate_data(latest_data.as_ref().unwrap()) { // data is valid println!( - "Fetched valid file: {} (size: {})", - file_name, latest_data_len + "Fetched valid file: {} (size: {}, compressed: {})", + file_name, + human_bytes(latest_data_len as f64), + human_bytes(encoded_size as f64) ); latest_valid_file = latest_data; latest_valid_file_name = Some(file_name); @@ -477,18 +483,22 @@ impl BitVMClient { data_store: &DataStore, key: &String, file_path: Option<&str>, - ) -> (Option, usize) { - let result = data_store.fetch_data_by_key(key, file_path).await; + ) -> (Option, usize, usize) { + let result = data_store + .fetch_compressed_data_by_key(key, file_path) + .await; if result.is_ok() { - if let Some(json) = result.unwrap() { - let data = try_deserialize::(&json); + if let (Some(content), encoded_size) = result.unwrap() { + let data = try_deserialize_slice(&content); if let Ok(data) = data { - return (Some(data), json.len()); + return (Some(data), content.len(), encoded_size); + } else { + eprintln!("{}", data.err().unwrap()); } } } - (None, 0) + (None, 0, 0) } async fn save_to_data_store(&mut self) { @@ -513,11 +523,16 @@ impl BitVMClient { let contents = serialize(&self.data); let result = self .data_store - .write_data(&contents, Some(&self.remote_file_path)) + .write_compressed_data(&contents.as_bytes().to_vec(), Some(&self.remote_file_path)) .await; match result { - Ok(file_name) => { - println!("Pushed new file: {} (size: {})", file_name, contents.len()); + Ok((file_name, size)) => { + println!( + "Pushed new file: {} (size: {}, compressed: {})", + file_name, + human_bytes(contents.len() as f64), + human_bytes(size as f64) + ); save_local_public_file(&self.local_file_path, &file_name, &contents); self.latest_processed_file_name = Some(file_name); } @@ -529,7 +544,7 @@ impl BitVMClient { for peg_in_graph in data.peg_in_graphs.iter() { if !peg_in_graph.validate() { println!( - "Encountered invalid peg in graph (Graph id: {})", + "Encountered invalid peg-in graph (graph ID: {})", peg_in_graph.id() ); return false; @@ -538,7 +553,7 @@ impl BitVMClient { for peg_out_graph in data.peg_out_graphs.iter() { if !peg_out_graph.validate() { println!( - "Encountered invalid peg out graph (Graph id: {})", + "Encountered invalid peg-out graph (graph ID: {})", peg_out_graph.id() ); return false; @@ -647,7 +662,11 @@ impl BitVMClient { for peg_in_graph in self.data.peg_in_graphs.iter() { if peg_in_graph.depositor_public_key.eq(depositor_public_key) { let status = peg_in_graph.depositor_status(&self.esplora).await; - println!("Graph id: {} status: {}\n", peg_in_graph.id(), status); + println!( + "[DEPOSITOR]: Peg-in graph ID: {} status: {}\n", + peg_in_graph.id(), + status + ); } } } @@ -667,13 +686,17 @@ impl BitVMClient { let peg_out_graph_id = peg_out_generate_id(peg_in_graph, operator_public_key); if !peg_out_graphs_by_id.contains_key(&peg_out_graph_id) { println!( - "Graph id: {} status: Missing peg out graph\n", + "[OPERATOR]: Peg-in graph ID: {} status: Missing peg out graph.\n", peg_in_graph.id() // TODO update this to ask the operator to create a new peg out graph ); } else { let peg_out_graph = peg_out_graphs_by_id.get(&peg_out_graph_id).unwrap(); let status = peg_out_graph.operator_status(&self.esplora).await; - println!("Graph id: {} status: {}\n", peg_out_graph.id(), status); + println!( + "[OPERATOR]: Peg-out graph ID: {} status: {}\n", + peg_out_graph.id(), + status + ); } } } @@ -709,7 +732,7 @@ impl BitVMClient { .filter(|peg_out| peg_in_graph.peg_out_graphs.contains(peg_out.id())) .collect::>(); let status = peg_in_graph - .verifier_status(&self.esplora, Some(context), &peg_outs_for_this_peg_in) + .verifier_status(&self.esplora, context, &peg_outs_for_this_peg_in) .await; match status { PegInVerifierStatus::PendingOurNonces(graph_ids) => { @@ -856,10 +879,31 @@ impl BitVMClient { .unwrap() }) .collect::>(); - let status = peg_in_graph - .verifier_status(&self.esplora, self.verifier_context.as_ref(), &peg_outs) + let peg_in_status = peg_in_graph + .verifier_status( + &self.esplora, + self.verifier_context.as_ref().unwrap(), + &peg_outs, + ) .await; - println!("Graph id: {} status: {}\n", peg_in_graph.id(), status); + + if peg_in_status == PegInVerifierStatus::Complete { + for peg_out_graph in peg_outs { + let peg_out_status = peg_out_graph + .verifier_status(&self.esplora, self.verifier_context.as_ref().unwrap()) + .await; + println!( + "[VERIFIER]: Peg-out graph ID: {} status: {}\n", + peg_out_graph.id(), + peg_out_status + ); + } + } + println!( + "[VERIFIER]: Peg-in graph ID: {} status: {}\n", + peg_in_graph.id(), + peg_in_status + ); } } @@ -930,7 +974,7 @@ impl BitVMClient { .peg_in_graphs .iter_mut() .find(|peg_in_graph| peg_in_graph.id().eq(peg_in_graph_id)) - .unwrap_or_else(|| panic!("Invalid graph id")); + .unwrap_or_else(|| panic!("Invalid graph ID")); let peg_out_graph_id = peg_out_generate_id(peg_in_graph, operator_public_key); let peg_out_graph = self @@ -1250,11 +1294,26 @@ impl BitVMClient { } } + pub fn get_operator_address(&self) -> Address { + if let Some(ref context) = self.operator_context { + generate_pay_to_pubkey_script_address(context.network, &context.operator_public_key) + } else { + panic!("Operator private key not provided in configuration."); + } + } + + pub async fn get_operator_utxos(&self) -> Vec { + self.esplora + .get_address_utxo(self.get_operator_address()) + .await + .unwrap() + } + pub fn get_depositor_address(&self) -> Address { if let Some(ref context) = self.depositor_context { generate_pay_to_pubkey_script_address(context.network, &context.depositor_public_key) } else { - panic!("No depositor key set"); + panic!("Depositor private key not provided in configuration."); } } @@ -1333,11 +1392,12 @@ impl BitVMClient { } async fn broadcast_tx(&self, tx: &Transaction) -> Result { - let transaction_id = tx.compute_txid(); let status_message = broadcast_and_verify(&self.esplora, tx).await?; - // TODO: expose this or have it print out here? - println!("{} ({:?})", status_message, transaction_id); - Ok(tx.compute_txid()) + + let txid = tx.compute_txid(); + println!("{} Txid: {}", status_message, txid.to_string().green()); + + Ok(txid) } fn merge_secret_nonces( diff --git a/bridge/src/client/data_store/aws_s3.rs b/bridge/src/client/data_store/aws_s3.rs index f3052010c..3fe278792 100644 --- a/bridge/src/client/data_store/aws_s3.rs +++ b/bridge/src/client/data_store/aws_s3.rs @@ -1,3 +1,8 @@ +use crate::{ + error::err_to_string, + utils::{compress, decompress, DEFAULT_COMPRESSION_LEVEL}, +}; + use super::base::DataStoreDriver; use async_trait::async_trait; use aws_sdk_s3::{ @@ -55,25 +60,21 @@ impl AwsS3 { key_with_prefix = key.to_string(); } - let object = self + let mut data = self .client .get_object() .bucket(&self.bucket) .key(key_with_prefix) .send() - .await; - - match object { - Ok(mut data) => { - let mut buffer: Vec = vec![]; - while let Some(bytes) = data.body.try_next().await.unwrap() { - buffer.append(&mut bytes.to_vec()); - } + .await + .map_err(err_to_string)?; - Ok(buffer) - } - Err(err) => Err(err.to_string()), + let mut buffer: Vec = vec![]; + while let Some(bytes) = data.body.try_next().await.map_err(err_to_string)? { + buffer.append(&mut bytes.to_vec()); } + + Ok(buffer) } async fn upload_object( @@ -166,4 +167,36 @@ impl DataStoreDriver for AwsS3 { Err(err) => Err(format!("Failed to save json file: {}", err)), } } + + async fn fetch_compressed_object( + &self, + file_name: &str, + file_path: Option<&str>, + ) -> Result<(Vec, usize), String> { + let response = self.get_object(file_name, file_path).await; + match response { + Ok(buffer) => { + let size = buffer.len(); + Ok((decompress(&buffer).map_err(err_to_string)?, size)) + } + Err(err) => Err(format!("Failed to get json file: {}", err)), + } + } + + async fn upload_compressed_object( + &self, + file_name: &str, + contents: &Vec, + file_path: Option<&str>, + ) -> Result { + let compressed_data = + compress(contents, DEFAULT_COMPRESSION_LEVEL).map_err(err_to_string)?; + let size = compressed_data.len(); + let byte_stream = ByteStream::from(compressed_data); + + match self.upload_object(file_name, byte_stream, file_path).await { + Ok(_) => Ok(size), + Err(err) => Err(format!("Failed to save json file: {}", err)), + } + } } diff --git a/bridge/src/client/data_store/base.rs b/bridge/src/client/data_store/base.rs index 139ef6c5e..82202598a 100644 --- a/bridge/src/client/data_store/base.rs +++ b/bridge/src/client/data_store/base.rs @@ -14,4 +14,15 @@ pub trait DataStoreDriver { contents: &str, file_path: Option<&str>, ) -> Result; + async fn fetch_compressed_object( + &self, + file_name: &str, + file_path: Option<&str>, + ) -> Result<(Vec, usize), String>; + async fn upload_compressed_object( + &self, + file_name: &str, + contents: &Vec, + file_path: Option<&str>, + ) -> Result; } diff --git a/bridge/src/client/data_store/data_store.rs b/bridge/src/client/data_store/data_store.rs index d1f1b5887..165745c68 100644 --- a/bridge/src/client/data_store/data_store.rs +++ b/bridge/src/client/data_store/data_store.rs @@ -126,6 +126,51 @@ impl DataStore { } } + pub async fn fetch_compressed_data_by_key( + &self, + key: &String, + file_path: Option<&str>, + ) -> Result<(Option>, usize), String> { + match self.get_driver() { + Ok(driver) => { + let json = driver.fetch_compressed_object(key, file_path).await; + if let Ok((data, size)) = json { + // println!("Fetched data file: {}", key); + return Ok((Some(data), size)); + } + + println!("No data file {} found", key); + Ok((None, 0)) + } + Err(err) => Err(err.to_string()), + } + } + + pub async fn write_compressed_data( + &self, + contents: &Vec, + file_path: Option<&str>, + ) -> Result<(String, usize), String> { + match self.get_driver() { + Ok(driver) => { + let time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(); + let file_name = self.create_file_name(time); + let response = driver + .upload_compressed_object(&file_name, contents, file_path) + .await; + + match response { + Ok(size) => Ok((file_name, size)), + Err(_) => Err(String::from("Failed to save data file")), + } + } + Err(err) => Err(err.to_string()), + } + } + pub fn get_past_max_file_name_by_timestamp( &self, latest_timestamp: u64, diff --git a/bridge/src/client/data_store/ftp/ftp.rs b/bridge/src/client/data_store/ftp/ftp.rs index d9a00343f..71897cc68 100644 --- a/bridge/src/client/data_store/ftp/ftp.rs +++ b/bridge/src/client/data_store/ftp/ftp.rs @@ -75,4 +75,21 @@ impl DataStoreDriver for Ftp { ) -> Result { lib::upload_object(&self.credentials, file_name, contents, file_path).await } + + async fn fetch_compressed_object( + &self, + file_name: &str, + file_path: Option<&str>, + ) -> Result<(Vec, usize), String> { + lib::fetch_compressed_object(&self.credentials, file_name, file_path).await + } + + async fn upload_compressed_object( + &self, + file_name: &str, + contents: &Vec, + file_path: Option<&str>, + ) -> Result { + lib::upload_compressed_object(&self.credentials, file_name, contents, file_path).await + } } diff --git a/bridge/src/client/data_store/ftp/ftps.rs b/bridge/src/client/data_store/ftp/ftps.rs index 70d1203ba..9225ea0d1 100644 --- a/bridge/src/client/data_store/ftp/ftps.rs +++ b/bridge/src/client/data_store/ftp/ftps.rs @@ -77,4 +77,21 @@ impl DataStoreDriver for Ftps { ) -> Result { lib::upload_object(&self.credentials, file_name, contents, file_path).await } + + async fn fetch_compressed_object( + &self, + file_name: &str, + file_path: Option<&str>, + ) -> Result<(Vec, usize), String> { + lib::fetch_compressed_object(&self.credentials, file_name, file_path).await + } + + async fn upload_compressed_object( + &self, + file_name: &str, + contents: &Vec, + file_path: Option<&str>, + ) -> Result { + lib::upload_compressed_object(&self.credentials, file_name, contents, file_path).await + } } diff --git a/bridge/src/client/data_store/ftp/lib.rs b/bridge/src/client/data_store/ftp/lib.rs index da68fd009..8810799b5 100644 --- a/bridge/src/client/data_store/ftp/lib.rs +++ b/bridge/src/client/data_store/ftp/lib.rs @@ -4,6 +4,11 @@ use suppaftp::{ AsyncNativeTlsFtpStream, }; +use crate::{ + error::err_to_string, + utils::{compress, decompress, DEFAULT_COMPRESSION_LEVEL}, +}; + pub struct FtpCredentials { pub is_secure: bool, pub host: String, @@ -102,6 +107,45 @@ pub async fn upload_object( } } +pub async fn fetch_compressed_object( + credentials: &FtpCredentials, + file_name: &str, + file_path: Option<&str>, +) -> Result<(Vec, usize), String> { + let response = get_object(credentials, file_name, file_path).await; + match response { + Ok(buffer) => { + let size = buffer.len(); + Ok((decompress(&buffer).map_err(err_to_string)?, size)) + } + Err(err) => Err(format!("Failed to get json file: {}", err)), + } +} + +pub async fn upload_compressed_object( + credentials: &FtpCredentials, + file_name: &str, + contents: &Vec, + file_path: Option<&str>, +) -> Result { + let compressed_data = compress(contents, DEFAULT_COMPRESSION_LEVEL).map_err(err_to_string)?; + let size = compressed_data.len(); + + println!("Writing data file to {} (size: {})", file_name, size); + + match upload_file( + credentials, + file_name, + compressed_data.as_slice(), + file_path, + ) + .await + { + Ok(_) => Ok(size), + Err(err) => Err(format!("Failed to save json file: {}", err)), + } +} + async fn get_object( credentials: &FtpCredentials, file_name: &str, diff --git a/bridge/src/client/data_store/sftp.rs b/bridge/src/client/data_store/sftp.rs index b858f6563..8e8dd091f 100644 --- a/bridge/src/client/data_store/sftp.rs +++ b/bridge/src/client/data_store/sftp.rs @@ -1,3 +1,8 @@ +use crate::{ + error::err_to_string, + utils::{compress, decompress, DEFAULT_COMPRESSION_LEVEL}, +}; + use super::base::DataStoreDriver; use async_trait::async_trait; use dotenv; @@ -223,6 +228,42 @@ impl DataStoreDriver for Sftp { Err(err) => Err(format!("Failed to save json file: {}", err)), } } + + async fn fetch_compressed_object( + &self, + file_name: &str, + file_path: Option<&str>, + ) -> Result<(Vec, usize), String> { + let response = self.get_object(file_name, file_path).await; + match response { + Ok(buffer) => { + let size = buffer.len(); + Ok((decompress(&buffer).map_err(err_to_string)?, size)) + } + Err(err) => Err(format!("Failed to get json file: {}", err)), + } + } + + async fn upload_compressed_object( + &self, + file_name: &str, + contents: &Vec, + file_path: Option<&str>, + ) -> Result { + let compressed_data = + compress(contents, DEFAULT_COMPRESSION_LEVEL).map_err(err_to_string)?; + let size = compressed_data.len(); + + println!("Writing data file to {} (size: {})", file_name, size); + + match self + .upload_object(file_name, compressed_data.as_slice(), file_path) + .await + { + Ok(_) => Ok(size), + Err(err) => Err(format!("Failed to save json file: {}", err)), + } + } } async fn test_connection(credentials: &SftpCredentials) -> Result<(), String> { diff --git a/bridge/src/client/esplora.rs b/bridge/src/client/esplora.rs new file mode 100644 index 000000000..1f0a3f2c3 --- /dev/null +++ b/bridge/src/client/esplora.rs @@ -0,0 +1,13 @@ +use bitcoin::Network; + +const REGTEST_ESPLORA_URL: &str = "http://localhost:8094/regtest/api/"; +// This endpoint accepts non-standard transactions. +const ALPEN_SIGNET_ESPLORA_URL: &str = "https://esplora-large.devnet-annapurna.stratabtc.org"; + +// TODO: Needs to be updated for production environment. +pub fn get_esplora_url(network: Network) -> &'static str { + match network { + Network::Regtest => REGTEST_ESPLORA_URL, + _ => ALPEN_SIGNET_ESPLORA_URL, + } +} diff --git a/bridge/src/client/files.rs b/bridge/src/client/files.rs index a81c43b35..cc0451bf8 100644 --- a/bridge/src/client/files.rs +++ b/bridge/src/client/files.rs @@ -43,27 +43,27 @@ pub fn create_directories_if_non_existent(data_root_path: &Path) { } pub fn get_private_data_from_file(path: &Path) -> BitVMClientPrivateData { - println!("Reading private data from local file..."); match read_file(path) { Some(data) => try_deserialize::(&data) .expect("Could not deserialize private data"), - None => { - println!("New private data will be generated if required."); - BitVMClientPrivateData { - secret_nonces: HashMap::new(), - commitment_secrets: HashMap::new(), - } - } + None => BitVMClientPrivateData { + secret_nonces: HashMap::new(), + commitment_secrets: HashMap::new(), + }, } } fn read_file(path: &Path) -> Option { match fs::read_to_string(path) { Ok(content) => Some(content), - Err(e) => { - eprintln!("Could not read file {} due to error: {}", path.display(), e); - None - } + Err(e) => match e.kind() { + std::io::ErrorKind::NotFound => return None, + _ => { + // If the file exists, but we cannot read it (e.g. due to invalid permissions, etc.), + // we want to fail and let the user fix the issue. + panic!("Could not read file {} due to error: {}", path.display(), e); + } + }, } } diff --git a/bridge/src/client/memory_cache.rs b/bridge/src/client/memory_cache.rs new file mode 100644 index 000000000..fefb1f07d --- /dev/null +++ b/bridge/src/client/memory_cache.rs @@ -0,0 +1,40 @@ +use std::{ + borrow::Borrow, + collections::HashMap, + hash::Hash, + sync::{LazyLock, RwLock}, +}; + +use crate::connectors::base::TaprootSpendInfoCache; + +const DEFAULT_CACHE_SIZE: usize = 200; +pub(crate) static TAPROOT_SPEND_INFO_CACHE: LazyLock>> = + LazyLock::new(|| RwLock::new(Cache::new(DEFAULT_CACHE_SIZE))); + +pub struct Cache(HashMap); + +impl Cache +where + K: Eq + Hash, + V: Clone, +{ + fn new(capacity: usize) -> Self { Self(HashMap::with_capacity(capacity)) } + + pub fn push(&mut self, key: K, value: V) -> Option { self.0.insert(key, value) } + + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: Hash + Eq, + { + self.0.get(key) + } + + pub fn contains(&self, key: &Q) -> bool + where + K: Borrow, + Q: Hash + Eq, + { + self.0.contains_key(key) + } +} diff --git a/bridge/src/client/mod.rs b/bridge/src/client/mod.rs index f95e06d72..25bc3e1ab 100644 --- a/bridge/src/client/mod.rs +++ b/bridge/src/client/mod.rs @@ -3,5 +3,7 @@ pub mod chain; pub mod cli; pub mod client; pub mod data_store; +pub mod esplora; pub mod files; +pub mod memory_cache; pub mod sdk; diff --git a/bridge/src/commitments.rs b/bridge/src/commitments.rs index 94d5388a5..3d4b41ceb 100644 --- a/bridge/src/commitments.rs +++ b/bridge/src/commitments.rs @@ -73,6 +73,7 @@ impl TryFrom for CommitmentMessageId { impl CommitmentMessageId { // btree map is a copy of chunker related commitments pub fn generate_commitment_secrets() -> HashMap { + println!("Generating commitment secrets ..."); let mut commitment_map = HashMap::from([ ( CommitmentMessageId::PegOutTxIdSourceNetwork, diff --git a/bridge/src/connectors/base.rs b/bridge/src/connectors/base.rs index 78c24a28b..4c14c05ce 100644 --- a/bridge/src/connectors/base.rs +++ b/bridge/src/connectors/base.rs @@ -1,4 +1,8 @@ -use bitcoin::{taproot::TaprootSpendInfo, Address, ScriptBuf, Sequence, TxIn, Witness}; +use bitcoin::{ + key::TweakedPublicKey, taproot::TaprootSpendInfo, Address, ScriptBuf, Sequence, TapNodeHash, + TxIn, Witness, +}; +use serde::{Deserialize, Serialize}; use super::super::transactions::base::Input; @@ -44,6 +48,12 @@ pub trait P2wshConnector { fn generate_tx_in(&self, input: &Input) -> TxIn; } +#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] +pub struct TaprootSpendInfoCache { + pub merkle_root: Option, + pub output_key: TweakedPublicKey, +} + pub trait TaprootConnector { fn generate_taproot_leaf_script(&self, leaf_index: u32) -> ScriptBuf; diff --git a/bridge/src/connectors/connector_c.rs b/bridge/src/connectors/connector_c.rs index 691e32be9..46a282ef9 100644 --- a/bridge/src/connectors/connector_c.rs +++ b/bridge/src/connectors/connector_c.rs @@ -5,20 +5,22 @@ use std::{ }; use crate::{ - client::files::BRIDGE_DATA_DIRECTORY_NAME, + client::{files::BRIDGE_DATA_DIRECTORY_NAME, memory_cache::TAPROOT_SPEND_INFO_CACHE}, commitments::CommitmentMessageId, common::ZkProofVerifyingKey, connectors::base::*, error::{ChunkerError, ConnectorError, Error}, transactions::base::Input, utils::{ - cleanup_cache_files, read_cache, remove_script_and_control_block_from_witness, write_cache, + cleanup_cache_files, read_disk_cache, remove_script_and_control_block_from_witness, + write_disk_cache, }, }; use bitcoin::{ hashes::{hash160, Hash}, + key::TweakedPublicKey, taproot::{TaprootBuilder, TaprootSpendInfo}, - Address, Network, ScriptBuf, Transaction, TxIn, XOnlyPublicKey, + Address, Network, ScriptBuf, TapNodeHash, Transaction, TxIn, XOnlyPublicKey, }; use num_traits::ToPrimitive; use secp256k1::SECP256K1; @@ -85,7 +87,7 @@ impl Serialize for ConnectorC { let lock_scripts_cache_path = get_lock_scripts_cache_path(&cache_id); if !lock_scripts_cache_path.exists() { - write_cache(&lock_scripts_cache_path, &self.lock_scripts_bytes) + write_disk_cache(&lock_scripts_cache_path, &self.lock_scripts_bytes) .map_err(SerError::custom)?; } @@ -173,7 +175,7 @@ impl ConnectorC { ) -> Self { let lock_scripts_cache = lock_scripts_cache_id.and_then(|cache_id| { let file_path = get_lock_scripts_cache_path(&cache_id); - read_cache(&file_path) + read_disk_cache(&file_path) .inspect_err(|e| { eprintln!( "Failed to read lock scripts cache from expected location: {}", @@ -222,6 +224,43 @@ impl ConnectorC { .ok_or(Error::Chunker(ChunkerError::ValidProof)) } + pub fn taproot_merkle_root(&self) -> Option { + self.taproot_spend_info_cache() + .map(|cache| cache.merkle_root) + .unwrap_or_else(|| self.generate_taproot_spend_info().merkle_root()) + } + + pub fn taproot_output_key(&self) -> TweakedPublicKey { + self.taproot_spend_info_cache() + .map(|cache| cache.output_key) + .unwrap_or_else(|| self.generate_taproot_spend_info().output_key()) + } + + // read from cache or generate from [`TaprootConnector`] + fn taproot_spend_info_cache(&self) -> Option { + let spend_info_cache = match Self::cache_id(&self.commitment_public_keys).map(|cache_id| { + TAPROOT_SPEND_INFO_CACHE + .read() + .unwrap() + .get(&cache_id) + .cloned() + }) { + Ok(Some(spend_info_cache)) => Some(spend_info_cache), + Ok(None) => { + let spend_info = self.generate_taproot_spend_info(); + let output_key = spend_info.output_key(); + let spend_info_cache = TaprootSpendInfoCache { + merkle_root: spend_info.merkle_root(), + output_key, + }; + Some(spend_info_cache) + } + _ => None, + }; + + spend_info_cache + } + pub fn cache_id( commitment_public_keys: &BTreeMap, ) -> Result { @@ -255,22 +294,37 @@ impl TaprootConnector for ConnectorC { } fn generate_taproot_spend_info(&self) -> TaprootSpendInfo { + println!("Generating new taproot spend info for connector C..."); let script_weights = self .lock_scripts_bytes .iter() .map(|b| (1, ScriptBuf::from_bytes(b.clone()))); - TaprootBuilder::with_huffman_tree(script_weights) + let spend_info = TaprootBuilder::with_huffman_tree(script_weights) .expect("Unable to add assert leaves") .finalize(SECP256K1, self.operator_taproot_public_key) - .expect("Unable to finalize assert transaction connector c taproot") + .expect("Unable to finalize assert transaction connector c taproot"); + + // write to cache + if let Ok(cache_id) = Self::cache_id(&self.commitment_public_keys) { + let output_key = spend_info.output_key(); + let spend_info_cache = TaprootSpendInfoCache { + merkle_root: spend_info.merkle_root(), + output_key, + }; + if !TAPROOT_SPEND_INFO_CACHE.read().unwrap().contains(&cache_id) { + TAPROOT_SPEND_INFO_CACHE + .write() + .unwrap() + .push(cache_id, spend_info_cache); + } + } + + spend_info } fn generate_taproot_address(&self) -> Address { - Address::p2tr_tweaked( - self.generate_taproot_spend_info().output_key(), - self.network, - ) + Address::p2tr_tweaked(self.taproot_output_key(), self.network) } } diff --git a/bridge/src/error.rs b/bridge/src/error.rs index 31686a917..933d507ca 100644 --- a/bridge/src/error.rs +++ b/bridge/src/error.rs @@ -2,7 +2,7 @@ use super::commitments::CommitmentMessageId; use super::graphs::base::GraphId; use super::transactions::{base::BaseTransaction, pre_signed::PreSignedTransaction}; use bitcoin::Txid; -use std::fmt; +use std::fmt::{self, Display}; use strum::Display; #[derive(Debug)] @@ -73,3 +73,5 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } + +pub fn err_to_string(err: impl Display) -> String { err.to_string() } diff --git a/bridge/src/graphs/base.rs b/bridge/src/graphs/base.rs index 9b76c9e3f..c7d02429a 100644 --- a/bridge/src/graphs/base.rs +++ b/bridge/src/graphs/base.rs @@ -23,8 +23,8 @@ pub const NUM_REQUIRED_OPERATORS: usize = 1; pub const GRAPH_VERSION: &str = "0.1"; -pub const CROWDFUNDING_AMOUNT: f64 = 1.0; //1 btc -pub const FEE_AMOUNT: u64 = 10_000; +//1 btc +pub const CROWDFUNDING_AMOUNT: f64 = 1.0; // for commonly used type in codebase - p2wsh txout // 67 = (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4) for segwit TxOut // TODO: Use lower dust amount for other txout types @@ -52,26 +52,6 @@ pub const PEG_OUT_FEE: u64 = MIN_RELAY_FEE_PEG_OUT_CONFIRM // depth 0 pub const PEG_IN_FEE: u64 = MIN_RELAY_FEE_PEG_IN_DEPOSIT + max(MIN_RELAY_FEE_PEG_IN_CONFIRM, MIN_RELAY_FEE_PEG_IN_REFUND); -// TODO delete -// DEMO SECRETS -pub const OPERATOR_SECRET: &str = - "3076ca1dfc1e383be26d5dd3c0c427340f96139fa8c2520862cf551ec2d670ac"; - -pub const VERIFIER_0_SECRET: &str = - "ee0817eac0c13aa8ee2dd3256304041f09f0499d1089b56495310ae8093583e2"; - -pub const VERIFIER_1_SECRET: &str = - "fc294c70faf210d4d0807ea7a3dba8f7e41700d90c119e1ae82a0687d89d297f"; - -pub const DEPOSITOR_SECRET: &str = - "b8f17ea979be24199e7c3fec71ee88914d92fd4ca508443f765d56ce024ef1d7"; - -pub const WITHDRAWER_SECRET: &str = - "fffd54f6d8f8ad470cb507fd4b6e9b3ea26b4221a4900cc5ad5916ce67c02f1e"; - -pub const DEPOSITOR_EVM_ADDRESS: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; // l2 local test network account 1 -pub const WITHDRAWER_EVM_ADDRESS: &str = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"; // l2 local test network account 2 - pub type GraphId = String; pub trait BaseGraph { @@ -117,13 +97,14 @@ pub async fn broadcast_and_verify( let txid = transaction.compute_txid(); if let Ok(Some(_)) = client.get_tx(&txid).await { - return Ok("Tx already submitted."); + return Ok("Tx already broadcasted."); } let tx_result = client.broadcast(transaction).await; match (tx_result, is_confirmed(client, txid).await) { - (Ok(_), _) | (Err(_), Ok(true)) => Ok("Tx mined successfully."), + (Ok(_), Ok(false)) | (Ok(_), Err(_)) => Ok("Tx broadcasted successfully."), + (Ok(_), Ok(true)) | (Err(_), Ok(true)) => Ok("Tx mined successfully."), (Err(e), _) => Err(Error::Esplora(e)), } } diff --git a/bridge/src/graphs/peg_in.rs b/bridge/src/graphs/peg_in.rs index 99480dc29..f71ab4ebe 100644 --- a/bridge/src/graphs/peg_in.rs +++ b/bridge/src/graphs/peg_in.rs @@ -352,7 +352,7 @@ impl PegInGraph { pub async fn verifier_status( &self, client: &AsyncClient, - verifier: Option<&VerifierContext>, + verifier_context: &VerifierContext, peg_outs: &[&PegOutGraph], ) -> PegInVerifierStatus { // check that the supplied peg out graphs match our expectation @@ -379,53 +379,27 @@ impl PegInGraph { return PegInVerifierStatus::Complete; } - if let Some(verifier_context) = verifier { - let mut peg_outs_without_nonces = peg_outs - .iter() - .filter(|x| !x.has_all_nonces_of(verifier_context)) - .map(|x| x.id().clone()) - .collect::>(); - if !self - .peg_in_confirm_transaction - .has_nonce_of(verifier_context) - { - peg_outs_without_nonces.push(self.id().clone()); - } - if !peg_outs_without_nonces.is_empty() { - return PegInVerifierStatus::PendingOurNonces(peg_outs_without_nonces); - } + if !self + .peg_in_confirm_transaction + .has_nonce_of(verifier_context) + { + return PegInVerifierStatus::PendingOurNonces(vec![self.id.clone()]); } let has_all_pegin_nonces = self.peg_in_confirm_transaction.has_all_nonces(); - let has_all_pegout_nonces = peg_outs - .iter() - .any(|peg_out| peg_out.has_all_nonces(&self.n_of_n_public_keys)); - if !has_all_pegin_nonces || !has_all_pegout_nonces { + if !has_all_pegin_nonces { return PegInVerifierStatus::AwaitingNonces; } - if let Some(verifier_context) = verifier { - let mut peg_outs_without_signatures = peg_outs - .iter() - .filter(|x| !x.has_all_signatures_of(verifier_context)) - .map(|x| x.id().clone()) - .collect::>(); - if !self - .peg_in_confirm_transaction - .has_signatures_for(verifier_context.verifier_public_key) - { - peg_outs_without_signatures.push(self.id().clone()); - } - if !peg_outs_without_signatures.is_empty() { - return PegInVerifierStatus::PendingOurSignature(peg_outs_without_signatures); - } + if !self + .peg_in_confirm_transaction + .has_signatures_for(verifier_context.verifier_public_key) + { + return PegInVerifierStatus::PendingOurSignature(vec![self.id.clone()]); } let has_all_pegin_signatures = self.peg_in_confirm_transaction.has_all_signatures(); - let has_all_pegout_signatures = peg_outs - .iter() - .any(|peg_out| peg_out.has_all_signatures(&self.n_of_n_public_keys)); - if !has_all_pegin_signatures || !has_all_pegout_signatures { + if !has_all_pegin_signatures { return PegInVerifierStatus::AwaitingSignatures; } diff --git a/bridge/src/graphs/peg_out.rs b/bridge/src/graphs/peg_out.rs index 012d94d75..40c02db39 100644 --- a/bridge/src/graphs/peg_out.rs +++ b/bridge/src/graphs/peg_out.rs @@ -103,7 +103,10 @@ impl Display for PegOutWithdrawerStatus { } pub enum PegOutVerifierStatus { - PegOutPresign, // should presign peg-out graph + PegOutPendingNonces, // should push nonces + PegOutAwaitingNonces, // should wait for nonces from other verifiers + PegOutPendingSignatures, // should push signatures + PegOutAwaitingSignatures, // should wait for signatures from other verifiers PegOutComplete, // peg-out complete PegOutWait, // no action required, wait PegOutChallengeAvailable, // can call challenge @@ -117,8 +120,20 @@ pub enum PegOutVerifierStatus { impl Display for PegOutVerifierStatus { fn fmt(&self, f: &mut Formatter) -> FmtResult { match self { - PegOutVerifierStatus::PegOutPresign => { - write!(f, "Signatures required. Presign peg-out transactions?") + PegOutVerifierStatus::PegOutPendingNonces => { + write!(f, "Nonces required. Push nonces for peg-out transactions?") + } + PegOutVerifierStatus::PegOutAwaitingNonces => { + write!(f, "Awaiting nonces for peg-out transactions. Wait...") + } + PegOutVerifierStatus::PegOutPendingSignatures => { + write!( + f, + "Signatures required. Push signatures for peg-out transactions?" + ) + } + PegOutVerifierStatus::PegOutAwaitingSignatures => { + write!(f, "Awaiting signatures for peg-out transactions. Wait...") } PegOutVerifierStatus::PegOutComplete => { write!(f, "Peg-out complete, reimbursement succeded. Done.") @@ -709,7 +724,6 @@ impl PegOutGraph { }, ); - let script_index = 1; // TODO replace placeholder let disprove_vout_0 = 1; let disprove_vout_1 = 2; let disprove_transaction = DisproveTransaction::new( @@ -730,7 +744,6 @@ impl PegOutGraph { }, amount: assert_final_transaction.tx().output[disprove_vout_1].value, }, - script_index, ); let disprove_chain_vout_0 = 1; @@ -1081,7 +1094,6 @@ impl PegOutGraph { }, ); - let script_index = 1; // TODO replace placeholder let disprove_vout_0 = 1; let disprove_vout_1 = 2; let disprove_transaction = DisproveTransaction::new_for_validation( @@ -1102,7 +1114,6 @@ impl PegOutGraph { }, amount: assert_final_transaction.tx().output[disprove_vout_1].value, }, - script_index, ); let disprove_chain_vout_0 = 1; @@ -1164,7 +1175,11 @@ impl PegOutGraph { } } - pub async fn verifier_status(&self, client: &AsyncClient) -> PegOutVerifierStatus { + pub async fn verifier_status( + &self, + client: &AsyncClient, + verifier_context: &VerifierContext, + ) -> PegOutVerifierStatus { if self.n_of_n_presigned { let ( _, @@ -1265,7 +1280,17 @@ impl PegOutGraph { return PegOutVerifierStatus::PegOutWait; } } else { - PegOutVerifierStatus::PegOutPresign + if !self.has_all_nonces_of(verifier_context) { + return PegOutVerifierStatus::PegOutPendingNonces; + } else if !self.has_all_nonces(&verifier_context.n_of_n_public_keys) { + return PegOutVerifierStatus::PegOutAwaitingNonces; + } else if !self.has_all_signatures_of(verifier_context) { + return PegOutVerifierStatus::PegOutPendingSignatures; + } else if !self.has_all_signatures(&verifier_context.n_of_n_public_keys) { + return PegOutVerifierStatus::PegOutAwaitingSignatures; + } else { + return PegOutVerifierStatus::PegOutWait; + } } } diff --git a/bridge/src/proof.rs b/bridge/src/proof.rs index 3f9ad8dfd..61fc12da2 100644 --- a/bridge/src/proof.rs +++ b/bridge/src/proof.rs @@ -1,4 +1,91 @@ +use ark_bn254::{g1::G1Affine, Bn254}; +use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; +use ark_ec::pairing::Pairing; +use ark_ff::PrimeField; +use ark_groth16::Groth16; +use ark_relations::lc; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; +use ark_std::{test_rng, UniformRand}; + use bitvm::chunker::disprove_execution::RawProof; +use rand::{RngCore, SeedableRng}; // TODO: replace with actual implementation -pub fn get_proof() -> RawProof { RawProof::default() } +pub fn get_proof() -> RawProof { + type E = Bn254; + let k = 6; + let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); + let circuit = DummyCircuit::<::ScalarField> { + a: Some(::ScalarField::rand(&mut rng)), + b: Some(::ScalarField::rand(&mut rng)), + num_variables: 10, + num_constraints: 1 << k, + }; + let (pk, vk) = Groth16::::setup(circuit, &mut rng).unwrap(); + + let c = circuit.a.unwrap() * circuit.b.unwrap(); + + let proof = Groth16::::prove(&pk, circuit, &mut rng).unwrap(); + + RawProof { + proof, + public: vec![c], + vk, + } +} + +// DO NOT USE IN PRODUCTION! This is a test function. +pub fn invalidate_proof(valid_proof: &RawProof) -> RawProof { + let mut invalid_proof = valid_proof.clone(); + let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); + invalid_proof.proof.a = G1Affine::rand(&mut rng); + + invalid_proof +} + +// TODO: Consider importing `gen_correct_proof` fn from bitvm/src/chunker/disprove_execution.rs +// It requires refactoring bitvm crate +// Copied from bitvm/src/chunker/disprove_execution.rs +#[derive(Copy)] +pub struct DummyCircuit { + pub a: Option, + pub b: Option, + pub num_variables: usize, + pub num_constraints: usize, +} + +impl Clone for DummyCircuit { + fn clone(&self) -> Self { + DummyCircuit { + a: self.a, + b: self.b, + num_variables: self.num_variables, + num_constraints: self.num_constraints, + } + } +} + +impl ConstraintSynthesizer for DummyCircuit { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + let a = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; + let b = cs.new_witness_variable(|| self.b.ok_or(SynthesisError::AssignmentMissing))?; + let c = cs.new_input_variable(|| { + let a = self.a.ok_or(SynthesisError::AssignmentMissing)?; + let b = self.b.ok_or(SynthesisError::AssignmentMissing)?; + + Ok(a * b) + })?; + + for _ in 0..(self.num_variables - 3) { + let _ = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; + } + + for _ in 0..self.num_constraints - 1 { + cs.enforce_constraint(lc!() + a, lc!() + b, lc!() + c)?; + } + + cs.enforce_constraint(lc!(), lc!(), lc!())?; + + Ok(()) + } +} diff --git a/bridge/src/serialization.rs b/bridge/src/serialization.rs index bed807c7c..e1ad9950d 100644 --- a/bridge/src/serialization.rs +++ b/bridge/src/serialization.rs @@ -1,6 +1,10 @@ use serde::{Deserialize, Serialize}; -pub fn serialize(object: &impl Serialize) -> String { serde_json::to_string(object).unwrap() } +pub fn serialize(object: &impl Serialize) -> String { + serde_json::to_string(object) + .map_err(|e| format!("Failed to serialize an object to JSON: {}", e)) + .unwrap() +} pub fn deserialize<'a, T>(data: &'a str) -> T where @@ -15,6 +19,16 @@ where { match serde_json::from_str::(data) { Ok(x) => Ok(x), - Err(err) => Err(format!("Failed to parse json: {}", err)), + Err(err) => Err(format!("Failed to parse JSON string to object: {}", err)), + } +} + +pub fn try_deserialize_slice<'a, T>(data: &'a [u8]) -> Result +where + T: Deserialize<'a>, +{ + match serde_json::from_slice::(data) { + Ok(x) => Ok(x), + Err(err) => Err(format!("Failed to decode JSON data to object: {}", err)), } } diff --git a/bridge/src/transactions/base.rs b/bridge/src/transactions/base.rs index 8fe6cc40f..2ad426b6e 100644 --- a/bridge/src/transactions/base.rs +++ b/bridge/src/transactions/base.rs @@ -10,9 +10,9 @@ use std::collections::HashMap; pub const RELAY_FEE_BUFFER_MULTIPLIER: f32 = 1.0; pub const MIN_RELAY_FEE_KICK_OFF_1: u64 = relay_fee(6231); pub const MIN_RELAY_FEE_START_TIME: u64 = relay_fee(407); -pub const MIN_RELAY_FEE_START_TIME_TIMEOUT: u64 = relay_fee(264); +pub const MIN_RELAY_FEE_START_TIME_TIMEOUT: u64 = relay_fee(265); pub const MIN_RELAY_FEE_KICK_OFF_2: u64 = relay_fee(5461); -pub const MIN_RELAY_FEE_KICK_OFF_TIMEOUT: u64 = relay_fee(181); +pub const MIN_RELAY_FEE_KICK_OFF_TIMEOUT: u64 = relay_fee(182); pub const MIN_RELAY_FEE_TAKE_1: u64 = relay_fee(380); pub const MIN_RELAY_FEE_TAKE_2: u64 = relay_fee(347); pub const MIN_RELAY_FEE_PEG_IN_DEPOSIT: u64 = relay_fee(122); diff --git a/bridge/src/transactions/disprove.rs b/bridge/src/transactions/disprove.rs index 105d36beb..2161d3d28 100644 --- a/bridge/src/transactions/disprove.rs +++ b/bridge/src/transactions/disprove.rs @@ -74,16 +74,8 @@ impl DisproveTransaction { connector_c: &ConnectorC, input_0: Input, input_1: Input, - script_index: u32, ) -> Self { - Self::new_for_validation( - context.network, - connector_5, - connector_c, - input_0, - input_1, - script_index, - ) + Self::new_for_validation(context.network, connector_5, connector_c, input_0, input_1) } pub fn new_for_validation( @@ -92,13 +84,11 @@ impl DisproveTransaction { connector_c: &ConnectorC, input_0: Input, input_1: Input, - script_index: u32, ) -> Self { let input_0_leaf = 1; let _input_0 = connector_5.generate_taproot_leaf_tx_in(input_0_leaf, &input_0); - let input_1_leaf = script_index; - let _input_1 = connector_c.generate_taproot_leaf_tx_in(input_1_leaf, &input_1); + let _input_1 = generate_default_tx_in(&input_1); let total_output_amount = input_0.amount + input_1.amount - Amount::from_sat(MIN_RELAY_FEE_DISPROVE); @@ -134,7 +124,7 @@ impl DisproveTransaction { ], prev_scripts: vec![ connector_5.generate_taproot_leaf_script(input_0_leaf), - connector_c.generate_taproot_leaf_script(input_1_leaf), + // `input_1` prev_script is not known at this point ], reward_output_amount, musig2_nonces: HashMap::new(), diff --git a/bridge/src/transactions/signing.rs b/bridge/src/transactions/signing.rs index fb3f72c66..65d72572a 100644 --- a/bridge/src/transactions/signing.rs +++ b/bridge/src/transactions/signing.rs @@ -3,8 +3,8 @@ use bitcoin::{ secp256k1::Message, sighash::{Prevouts, SighashCache}, taproot::{LeafVersion, TaprootSpendInfo}, - Amount, EcdsaSighashType, PublicKey, Script, ScriptBuf, TapLeafHash, TapSighashType, - Transaction, TxOut, + Amount, EcdsaSighashType, PublicKey, Script, ScriptBuf, TapLeafHash, TapNodeHash, + TapSighashType, Transaction, TxOut, }; use secp256k1::SECP256K1; @@ -307,7 +307,7 @@ fn generate_p2tr_key_spend_schnorr_signature( input_index: usize, prev_outs: &[TxOut], sighash_type: TapSighashType, - taproot_spend_info: &TaprootSpendInfo, + merkle_root: Option, keypair: &Keypair, ) -> bitcoin::taproot::Signature { let sighash = if sighash_type == TapSighashType::AllPlusAnyoneCanPay @@ -327,7 +327,7 @@ fn generate_p2tr_key_spend_schnorr_signature( .expect("Failed to construct sighash") }; - let tweak_keypair = keypair.tap_tweak(SECP256K1, taproot_spend_info.merkle_root()); + let tweak_keypair = keypair.tap_tweak(SECP256K1, merkle_root); // If secp256k1 is updated to 0.30.0, the following line can be replaced with // let signature = keypair.sign_schnorr_no_aux_rand(&Message::from(sighash)); @@ -345,7 +345,7 @@ pub fn populate_p2tr_key_spend_witness( input_index: usize, prev_outs: &[TxOut], sighash_type: TapSighashType, - taproot_spend_info: &TaprootSpendInfo, + merkle_root: Option, keypair: &Keypair, ) { let signature = generate_p2tr_key_spend_schnorr_signature( @@ -353,7 +353,7 @@ pub fn populate_p2tr_key_spend_witness( input_index, prev_outs, sighash_type, - taproot_spend_info, + merkle_root, keypair, ); tx.input[input_index].witness.push(signature.to_vec()); diff --git a/bridge/src/transactions/take_2.rs b/bridge/src/transactions/take_2.rs index 3537a1648..0a14ddfa1 100644 --- a/bridge/src/transactions/take_2.rs +++ b/bridge/src/transactions/take_2.rs @@ -120,8 +120,7 @@ impl Take2Transaction { let input_2_leaf = 0; let _input_2 = connector_5.generate_taproot_leaf_tx_in(input_2_leaf, &input_2); - let input_3_leaf = 0; - let _input_3 = connector_c.generate_taproot_leaf_tx_in(input_3_leaf, &input_3); + let _input_3 = generate_default_tx_in(&input_3); let total_output_amount = input_0.amount + input_1.amount + input_2.amount + input_3.amount - Amount::from_sat(MIN_RELAY_FEE_TAKE_2); @@ -161,7 +160,7 @@ impl Take2Transaction { connector_0.generate_taproot_leaf_script(input_0_leaf), connector_4.generate_script(), connector_5.generate_taproot_leaf_script(input_2_leaf), - connector_c.generate_taproot_leaf_script(input_3_leaf), + // No `input_3` script - key spend path is used ], musig2_nonces: HashMap::new(), musig2_nonce_signatures: HashMap::new(), @@ -246,14 +245,14 @@ impl Take2Transaction { fn sign_input_3(&mut self, context: &OperatorContext, connector_c: &ConnectorC) { let input_index = 3; let prev_outs = &self.prev_outs().clone(); - let taproot_spend_info = connector_c.generate_taproot_spend_info(); + let merkle_root = connector_c.taproot_merkle_root(); populate_p2tr_key_spend_witness( self.tx_mut(), input_index, prev_outs, TapSighashType::All, - &taproot_spend_info, + merkle_root, &context.operator_keypair, ); } diff --git a/bridge/src/utils.rs b/bridge/src/utils.rs index bf7d8b866..84a62d9ec 100644 --- a/bridge/src/utils.rs +++ b/bridge/src/utils.rs @@ -103,37 +103,26 @@ pub fn sb_hash_from_bytes() -> Script { } } -pub fn write_cache(file_path: &Path, data: &impl Encode) -> std::io::Result<()> { +pub fn write_disk_cache(file_path: &Path, data: &impl Encode) -> std::io::Result<()> { println!("Writing cache to {}...", file_path.display()); if let Some(parent) = file_path.parent() { if !parent.exists() { std::fs::create_dir_all(parent)?; } } - let now = std::time::Instant::now(); let encoded_data = bitcode::encode(data); - let compressed_data = zstd::stream::encode_all(encoded_data.as_slice(), 5)?; - let elapsed = now.elapsed(); - println!("Encoded cache to in {} ms", elapsed.as_millis()); + let compressed_data = compress(&encoded_data, DEFAULT_COMPRESSION_LEVEL)?; std::fs::write(file_path, compressed_data) } -pub fn read_cache(file_path: &Path) -> std::io::Result +pub fn read_disk_cache(file_path: &Path) -> std::io::Result where T: for<'de> Decode<'de>, { println!("Reading cache from {}...", file_path.display()); let compressed_data = std::fs::read(file_path)?; - let now = std::time::Instant::now(); - let encoded_data: Vec = zstd::stream::decode_all(compressed_data.as_slice())?; - let decoded = bitcode::decode(&encoded_data).map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!("bitcode error: {}", e), - ) - })?; - let elapsed = now.elapsed(); - println!("Decoded cache in {} ms", elapsed.as_millis()); + let encoded_data: Vec = decompress(&compressed_data)?; + let decoded = bitcode::decode(&encoded_data).map_err(std::io::Error::other)?; Ok(decoded) } @@ -159,3 +148,13 @@ pub fn cleanup_cache_files(prefix: &str, cache_location: &Path, max_cache_files: } } } + +pub const DEFAULT_COMPRESSION_LEVEL: i32 = 5; + +pub fn compress(data: &Vec, level: i32) -> std::io::Result> { + zstd::stream::encode_all(data.as_slice(), level) +} + +pub fn decompress(data: &Vec) -> std::io::Result> { + zstd::stream::decode_all(data.as_slice()) +} diff --git a/bridge/tests/bridge/client/fee.rs b/bridge/tests/bridge/client/fee.rs index d0126fdae..ab1584408 100644 --- a/bridge/tests/bridge/client/fee.rs +++ b/bridge/tests/bridge/client/fee.rs @@ -1,7 +1,10 @@ use bitcoin::{Address, Amount, OutPoint}; use bridge::{ client::{ - chain::chain::{Chain, PegOutEvent}, + chain::{ + chain::{Chain, PegOutEvent}, + mock_adaptor::{MockAdaptor, MockAdaptorConfig}, + }, client::BitVMClient, }, commitments::CommitmentMessageId, @@ -10,7 +13,6 @@ use bridge::{ peg_in::PegInGraph, peg_out::PegOutGraph, }, - proof::get_proof, scripts::{ generate_p2pkh_address, generate_pay_to_pubkey_script, generate_pay_to_pubkey_script_address, @@ -35,7 +37,6 @@ use crate::bridge::{ check_tx_output_sum, find_peg_in_graph_by_peg_out, generate_stub_outpoint, get_reward_amount, wait_for_confirmation, wait_for_timelock_expiry, }, - mock::chain::mock::MockAdaptor, setup::{setup_test, INITIAL_AMOUNT, ONE_HUNDRED}, }; @@ -209,30 +210,35 @@ async fn test_peg_out_fees() { let peg_in_confirm_tx = peg_in_graph.peg_in_confirm_transaction_ref().tx(); let peg_in_confirm_vout: usize = 0; let peg_in_confirm_amount = peg_in_confirm_tx.output[peg_in_confirm_vout].value; - let mut mock_adaptor = MockAdaptor::new(); - mock_adaptor.peg_out_init_events = vec![PegOutEvent { - source_outpoint: OutPoint { - txid: peg_in_graph.peg_in_confirm_transaction.tx().compute_txid(), - vout: peg_in_confirm_vout.to_u32().unwrap(), - }, - amount: peg_in_confirm_amount, - timestamp: 1722328130u32, - withdrawer_chain_address: config.withdrawer_evm_address, - withdrawer_destination_address: generate_p2pkh_address( - config.withdrawer_context.network, - &config.withdrawer_context.withdrawer_public_key, - ) - .to_string(), - withdrawer_public_key_hash: config - .withdrawer_context - .withdrawer_public_key - .pubkey_hash(), - operator_public_key: config.operator_context.operator_public_key, - tx_hash: [0u8; 32].into(), // 32 bytes 0 - }]; - let mut chain_adaptor = Chain::new(); - chain_adaptor.init_default(Box::new(mock_adaptor)); - config.client_0.set_chain_adaptor(chain_adaptor); + + let mock_adaptor_config = MockAdaptorConfig { + peg_out_init_events: Some(vec![PegOutEvent { + source_outpoint: OutPoint { + txid: peg_in_graph.peg_in_confirm_transaction.tx().compute_txid(), + vout: peg_in_confirm_vout.to_u32().unwrap(), + }, + amount: peg_in_confirm_amount, + timestamp: 1722328130u32, + withdrawer_chain_address: config.withdrawer_evm_address, + withdrawer_destination_address: generate_p2pkh_address( + config.withdrawer_context.network, + &config.withdrawer_context.withdrawer_public_key, + ) + .to_string(), + withdrawer_public_key_hash: config + .withdrawer_context + .withdrawer_public_key + .pubkey_hash(), + operator_public_key: config.operator_context.operator_public_key, + tx_hash: [0u8; 32].into(), // 32 bytes 0 + }]), + peg_out_burnt_events: None, + peg_out_minted_events: None, + }; + let adaptor = MockAdaptor::new(Some(mock_adaptor_config)); + let chain_service = Chain::new(Box::new(adaptor)); + + config.client_0.set_chain_service(chain_service); config.client_0.sync_l2().await; let peg_out_graph = get_peg_out_graph_mut(&mut config.client_0, peg_out_graph_id.clone()); @@ -447,7 +453,11 @@ async fn test_peg_out_fees() { wait_for_confirmation(config.network).await; let assert_commit1_tx = peg_out_graph - .assert_commit_1(&esplora_client, &config.commitment_secrets, &get_proof()) + .assert_commit_1( + &esplora_client, + &config.commitment_secrets, + &config.invalid_proof, + ) .await .unwrap(); // checked in assert_commit_1 single tx test @@ -461,7 +471,11 @@ async fn test_peg_out_fees() { wait_for_confirmation(config.network).await; let assert_commit2_tx = peg_out_graph - .assert_commit_2(&esplora_client, &config.commitment_secrets, &get_proof()) + .assert_commit_2( + &esplora_client, + &config.commitment_secrets, + &config.invalid_proof, + ) .await .unwrap(); // checked in assert_commit_2 single tx test @@ -510,8 +524,8 @@ async fn test_peg_out_fees() { ) .await .unwrap(); - // minus 2 dust from kick off 1, 1 dust from kick off 2 - check_tx_output_sum(reward_amount - DUST_AMOUNT * 3, &disprove_tx); + // minus 2 dust from kick off 1, 1 dust from kick off 2, 1 dust from assert final + check_tx_output_sum(reward_amount - DUST_AMOUNT * 4, &disprove_tx); } // TODO: consider making the graph getter in client public after refactor diff --git a/bridge/tests/bridge/client/musig2_peg_in.rs b/bridge/tests/bridge/client/musig2_peg_in.rs index c5390ed96..6b4806ad5 100644 --- a/bridge/tests/bridge/client/musig2_peg_in.rs +++ b/bridge/tests/bridge/client/musig2_peg_in.rs @@ -1,7 +1,7 @@ use bitcoin::Amount; use bridge::{ - graphs::base::FEE_AMOUNT, scripts::generate_pay_to_pubkey_script_address, + graphs::base::PEG_IN_FEE, scripts::generate_pay_to_pubkey_script_address, transactions::base::Input, }; @@ -21,7 +21,7 @@ async fn test_musig2_peg_in() { let mut verifier_1_client = config.client_1; // Depositor: generate graph - let amount = Amount::from_sat(INITIAL_AMOUNT + FEE_AMOUNT); + let amount = Amount::from_sat(INITIAL_AMOUNT + PEG_IN_FEE); let depositor_funding_utxo_address = generate_pay_to_pubkey_script_address( config.depositor_context.network, &config.depositor_context.depositor_public_key, diff --git a/bridge/tests/bridge/client/musig2_peg_out.rs b/bridge/tests/bridge/client/musig2_peg_out.rs index 6e55b0b24..be3d708ea 100644 --- a/bridge/tests/bridge/client/musig2_peg_out.rs +++ b/bridge/tests/bridge/client/musig2_peg_out.rs @@ -1,13 +1,17 @@ use bitcoin::{Address, Amount, OutPoint}; +use bitvm::chunker::disprove_execution::RawProof; use bridge::{ client::{ - chain::chain::{Chain, PegOutEvent}, + chain::{ + chain::{Chain, PegOutEvent}, + mock_adaptor::{MockAdaptor, MockAdaptorConfig}, + }, client::BitVMClient, }, contexts::{ depositor::DepositorContext, operator::OperatorContext, withdrawer::WithdrawerContext, }, - graphs::base::FEE_AMOUNT, + graphs::base::{PEG_IN_FEE, PEG_OUT_FEE}, scripts::{ generate_p2pkh_address, generate_pay_to_pubkey_script, generate_pay_to_pubkey_script_address, @@ -24,14 +28,15 @@ use crate::bridge::{ faucet::{Faucet, FaucetType}, helper::{ find_peg_in_graph_by_peg_out, generate_stub_outpoint, wait_for_confirmation_with_message, + wait_for_timelock_expiry, }, - mock::chain::mock::MockAdaptor, setup::{setup_test, INITIAL_AMOUNT}, }; #[tokio::test] #[serial] async fn test_musig2_peg_out_take_1() { + println!("Testing musig2 signing for take 1"); let ( mut depositor_operator_verifier_0_client, _, @@ -40,6 +45,7 @@ async fn test_musig2_peg_out_take_1() { withdrawer_evm_address, withdrawer_context, operator_context, + _, ) = create_peg_out_graph().await; simulate_peg_out_from_l2( &mut depositor_operator_verifier_0_client, @@ -52,7 +58,7 @@ async fn test_musig2_peg_out_take_1() { let with_kick_off_2_tx = true; let with_challenge_tx = false; - let with_assert_tx = false; + let with_assert_tx = None; broadcast_transactions_from_peg_out_graph( &mut depositor_operator_verifier_0_client, &peg_out_graph_id, @@ -73,6 +79,7 @@ async fn test_musig2_peg_out_take_1() { #[tokio::test] #[serial] async fn test_musig2_peg_out_take_2() { + println!("Testing musig2 signing for take 2"); let ( mut depositor_operator_verifier_0_client, _, @@ -81,6 +88,7 @@ async fn test_musig2_peg_out_take_2() { withdrawer_evm_address, withdrawer_context, operator_context, + invalid_proof, ) = create_peg_out_graph().await; simulate_peg_out_from_l2( &mut depositor_operator_verifier_0_client, @@ -93,7 +101,7 @@ async fn test_musig2_peg_out_take_2() { let with_kick_off_2_tx = true; let with_challenge_tx = false; - let with_assert_tx = true; + let with_assert_tx = Some(invalid_proof); broadcast_transactions_from_peg_out_graph( &mut depositor_operator_verifier_0_client, &peg_out_graph_id, @@ -115,6 +123,7 @@ async fn test_musig2_peg_out_take_2() { #[tokio::test] #[serial] async fn test_musig2_start_time_timeout() { + println!("Testing musig2 signing for start time timeout"); let ( mut depositor_operator_verifier_0_client, _, @@ -123,6 +132,7 @@ async fn test_musig2_start_time_timeout() { withdrawer_evm_address, withdrawer_context, operator_context, + _, ) = create_peg_out_graph().await; simulate_peg_out_from_l2( &mut depositor_operator_verifier_0_client, @@ -135,7 +145,7 @@ async fn test_musig2_start_time_timeout() { let with_kick_off_2_tx = false; let with_challenge_tx = false; - let with_assert_tx = false; + let with_assert_tx = None; broadcast_transactions_from_peg_out_graph( &mut depositor_operator_verifier_0_client, &peg_out_graph_id, @@ -159,6 +169,7 @@ async fn test_musig2_start_time_timeout() { #[tokio::test] #[serial] async fn test_musig2_kick_off_timeout() { + println!("Testing musig2 signing for kick off timeout"); let ( mut depositor_operator_verifier_0_client, _, @@ -167,6 +178,7 @@ async fn test_musig2_kick_off_timeout() { withdrawer_evm_address, withdrawer_context, operator_context, + _, ) = create_peg_out_graph().await; simulate_peg_out_from_l2( &mut depositor_operator_verifier_0_client, @@ -179,7 +191,7 @@ async fn test_musig2_kick_off_timeout() { let with_kick_off_2_tx = false; let with_challenge_tx = false; - let with_assert_tx = false; + let with_assert_tx = None; broadcast_transactions_from_peg_out_graph( &mut depositor_operator_verifier_0_client, &peg_out_graph_id, @@ -203,6 +215,7 @@ async fn test_musig2_kick_off_timeout() { #[tokio::test] #[serial] async fn test_musig2_peg_out_disprove_with_challenge() { + println!("Testing musig2 signing for disprove with challenge"); let ( mut depositor_operator_verifier_0_client, _, @@ -211,6 +224,7 @@ async fn test_musig2_peg_out_disprove_with_challenge() { withdrawer_evm_address, withdrawer_context, operator_context, + invalid_proof, ) = create_peg_out_graph().await; simulate_peg_out_from_l2( &mut depositor_operator_verifier_0_client, @@ -223,7 +237,7 @@ async fn test_musig2_peg_out_disprove_with_challenge() { let with_kick_off_2_tx = true; let with_challenge_tx = true; - let with_assert_tx = true; + let with_assert_tx = Some(invalid_proof); broadcast_transactions_from_peg_out_graph( &mut depositor_operator_verifier_0_client, &peg_out_graph_id, @@ -244,9 +258,11 @@ async fn test_musig2_peg_out_disprove_with_challenge() { .expect("Failed to broadcast disprove"); } +#[ignore] #[tokio::test] #[serial] async fn test_musig2_peg_out_disprove_chain_with_challenge() { + println!("Testing musig2 signing for disprove chain with challenge"); let ( mut depositor_operator_verifier_0_client, _, @@ -255,6 +271,7 @@ async fn test_musig2_peg_out_disprove_chain_with_challenge() { withdrawer_evm_address, withdrawer_context, operator_context, + _, ) = create_peg_out_graph().await; simulate_peg_out_from_l2( &mut depositor_operator_verifier_0_client, @@ -267,7 +284,7 @@ async fn test_musig2_peg_out_disprove_chain_with_challenge() { let with_kick_off_2_tx = true; let with_challenge_tx = true; - let with_assert_tx = false; + let with_assert_tx = None; broadcast_transactions_from_peg_out_graph( &mut depositor_operator_verifier_0_client, &peg_out_graph_id, @@ -291,6 +308,7 @@ async fn test_musig2_peg_out_disprove_chain_with_challenge() { #[tokio::test] #[serial] async fn test_musig2_peg_out_peg_out() { + println!("Testing musig2 signing for peg out"); let ( mut depositor_operator_verifier_0_client, _, @@ -299,6 +317,7 @@ async fn test_musig2_peg_out_peg_out() { withdrawer_evm_address, withdrawer_context, operator_context, + _, ) = create_peg_out_graph().await; simulate_peg_out_from_l2( &mut depositor_operator_verifier_0_client, @@ -316,7 +335,7 @@ async fn broadcast_transactions_from_peg_out_graph( depositor_context: &DepositorContext, with_kick_off_2_tx: bool, with_challenge_tx: bool, - _with_assert_tx: bool, + with_assert_proof: Option, ) { println!("Broadcasting kick-off 1..."); client.sync().await; @@ -325,7 +344,7 @@ async fn broadcast_transactions_from_peg_out_graph( .await .expect("Failed to broadcast kick-off 1"); - wait_for_confirmation_with_message(client.source_network, Some("peg-out kick-off 1 tx")).await; + wait_for_timelock_expiry(client.source_network, Some("kick-off 1 connector 1")).await; if with_kick_off_2_tx { println!("Broadcasting start time..."); @@ -343,12 +362,11 @@ async fn broadcast_transactions_from_peg_out_graph( .await .expect("Failed to broadcast kick-off 2"); - wait_for_confirmation_with_message(client.source_network, Some("peg-out kick-off 2 tx")) - .await; + wait_for_timelock_expiry(client.source_network, Some("kick-off 2 connector b")).await; } if with_challenge_tx { - let challenge_input_amount = Amount::from_sat(INITIAL_AMOUNT + FEE_AMOUNT); + let challenge_input_amount = Amount::from_btc(1.0).unwrap(); let challenge_funding_utxo_address = generate_pay_to_pubkey_script_address( depositor_context.network, &depositor_context.depositor_public_key, @@ -385,13 +403,47 @@ async fn broadcast_transactions_from_peg_out_graph( .await; } - // TODO: uncomment after assert txs are done - // if with_assert_tx { - // println!("Broadcasting assert..."); - // client.broadcast_assert(&peg_out_graph_id).await; + if let Some(assert_proof) = with_assert_proof { + println!("Broadcasting assert initial..."); + client + .broadcast_assert_initial(&peg_out_graph_id) + .await + .expect("Failed to broadcast assert initial"); + wait_for_confirmation_with_message( + client.source_network, + Some("peg-out assert initial tx"), + ) + .await; - // wait_for_confirmation_with_message(client.source_network, Some("peg-out assert tx")).await; - // } + println!("Broadcasting assert commit 1..."); + client + .broadcast_assert_commit_1(&peg_out_graph_id, &assert_proof) + .await + .expect("Failed to broadcast assert commit 1"); + wait_for_confirmation_with_message( + client.source_network, + Some("peg-out assert commit 1 tx"), + ) + .await; + + println!("Broadcasting assert commit 2..."); + client + .broadcast_assert_commit_2(&peg_out_graph_id, &assert_proof) + .await + .expect("Failed to broadcast assert commit 2"); + wait_for_confirmation_with_message( + client.source_network, + Some("peg-out assert commit 2 tx"), + ) + .await; + + println!("Broadcasting assert final..."); + client + .broadcast_assert_final(&peg_out_graph_id) + .await + .expect("Failed to broadcast assert final"); + wait_for_timelock_expiry(client.source_network, Some("assert final connector 4")).await; + } } async fn create_peg_out_graph() -> ( @@ -402,6 +454,7 @@ async fn create_peg_out_graph() -> ( String, WithdrawerContext, OperatorContext, + RawProof, ) { let config = setup_test().await; let mut depositor_operator_verifier_0_client = config.client_0; @@ -410,14 +463,14 @@ async fn create_peg_out_graph() -> ( // verify funding inputs let mut funding_inputs: Vec<(&Address, Amount)> = vec![]; - let deposit_input_amount = Amount::from_sat(INITIAL_AMOUNT + FEE_AMOUNT); + let deposit_input_amount = Amount::from_sat(INITIAL_AMOUNT + PEG_IN_FEE); let deposit_funding_address = generate_pay_to_pubkey_script_address( config.depositor_context.network, &config.depositor_context.depositor_public_key, ); funding_inputs.push((&deposit_funding_address, deposit_input_amount)); - let kick_off_input_amount = Amount::from_sat(INITIAL_AMOUNT + FEE_AMOUNT); + let kick_off_input_amount = Amount::from_sat(INITIAL_AMOUNT + PEG_OUT_FEE); let kick_off_funding_utxo_address = generate_pay_to_pubkey_script_address( config.operator_context.network, &config.operator_context.operator_public_key, @@ -442,7 +495,6 @@ async fn create_peg_out_graph() -> ( // create and complete peg-in graph let peg_in_graph_id = create_peg_in_graph( &mut depositor_operator_verifier_0_client, - &mut verifier_1_client, deposit_funding_address, deposit_input_amount, &config.depositor_evm_address, @@ -450,7 +502,6 @@ async fn create_peg_out_graph() -> ( .await; println!("Creating peg-out graph..."); - depositor_operator_verifier_0_client.sync().await; let peg_out_graph_id = depositor_operator_verifier_0_client.create_peg_out_graph( &peg_in_graph_id, Input { @@ -461,24 +512,38 @@ async fn create_peg_out_graph() -> ( ); println!("Verifier 0 push peg-out nonces"); - depositor_operator_verifier_0_client.push_verifier_nonces(&peg_out_graph_id); + depositor_operator_verifier_0_client + .process_peg_in_as_verifier(&peg_in_graph_id) // verifier 0 push nonces + .await; depositor_operator_verifier_0_client.flush().await; println!("Verifier 1 push peg-out nonces"); verifier_1_client.sync().await; - verifier_1_client.push_verifier_nonces(&peg_out_graph_id); + verifier_1_client + .process_peg_in_as_verifier(&peg_in_graph_id) + .await; verifier_1_client.flush().await; println!("Verifier 0 pre-sign peg-out"); depositor_operator_verifier_0_client.sync().await; - depositor_operator_verifier_0_client.push_verifier_signature(&peg_out_graph_id); + depositor_operator_verifier_0_client + .process_peg_in_as_verifier(&peg_in_graph_id) + .await; depositor_operator_verifier_0_client.flush().await; println!("Verifier 1 pre-sign peg-out"); verifier_1_client.sync().await; - verifier_1_client.push_verifier_signature(&peg_out_graph_id); + verifier_1_client + .process_peg_in_as_verifier(&peg_in_graph_id) + .await; verifier_1_client.flush().await; + println!("Verifier 0 broadcast peg-in confirm"); + depositor_operator_verifier_0_client.sync().await; + depositor_operator_verifier_0_client + .process_peg_in_as_verifier(&peg_in_graph_id) + .await; + ( depositor_operator_verifier_0_client, verifier_1_client, @@ -487,12 +552,12 @@ async fn create_peg_out_graph() -> ( config.withdrawer_evm_address, config.withdrawer_context, config.operator_context, + config.invalid_proof, ) } async fn create_peg_in_graph( client_0: &mut BitVMClient, - client_1: &mut BitVMClient, deposit_funding_address: Address, deposit_amount: Amount, depositor_evm_address: &String, @@ -508,35 +573,14 @@ async fn create_peg_in_graph( depositor_evm_address, ) .await; + println!("Peg in graph created: {}", graph_id); client_0 .broadcast_peg_in_deposit(&graph_id) .await .expect("Failed to broadcast peg-in deposit"); - client_0.push_verifier_nonces(&graph_id); - client_0.flush().await; - - client_1.sync().await; - client_1.push_verifier_nonces(&graph_id); - client_1.flush().await; - - client_0.sync().await; - client_0.push_verifier_signature(&graph_id); - client_0.flush().await; - - client_1.sync().await; - client_1.push_verifier_signature(&graph_id); - client_1.flush().await; - wait_for_confirmation_with_message(client_0.source_network, Some("peg-in deposit tx")).await; - client_0.sync().await; - client_0 - .broadcast_peg_in_confirm(&graph_id) - .await - .expect("Failed to broadcast peg-in confirm"); - client_0.flush().await; - graph_id } @@ -556,27 +600,31 @@ async fn simulate_peg_out_from_l2( ); let peg_in_confirm_amount = peg_in_confirm.tx().output[peg_in_confirm_vout].value; - let mut mock_adaptor = MockAdaptor::new(); - mock_adaptor.peg_out_init_events = vec![PegOutEvent { - source_outpoint: OutPoint { - txid: peg_in_confirm.tx().compute_txid(), - vout: peg_in_confirm_vout.to_u32().unwrap(), - }, - amount: peg_in_confirm_amount, - timestamp: 1722328130u32, - withdrawer_chain_address: withdrawer_evm_address.clone(), - withdrawer_destination_address: generate_p2pkh_address( - withdrawer_context.network, - &withdrawer_context.withdrawer_public_key, - ) - .to_string(), - withdrawer_public_key_hash: withdrawer_context.withdrawer_public_key.pubkey_hash(), - operator_public_key: operator_context.operator_public_key, - tx_hash: [0u8; 4].into(), - }]; - let mut chain_adaptor = Chain::new(); - chain_adaptor.init_default(Box::new(mock_adaptor)); - client.set_chain_adaptor(chain_adaptor); + let mock_adaptor_config = MockAdaptorConfig { + peg_out_init_events: Some(vec![PegOutEvent { + source_outpoint: OutPoint { + txid: peg_in_confirm.tx().compute_txid(), + vout: peg_in_confirm_vout.to_u32().unwrap(), + }, + amount: peg_in_confirm_amount, + timestamp: 1722328130u32, + withdrawer_chain_address: withdrawer_evm_address.clone(), + withdrawer_destination_address: generate_p2pkh_address( + withdrawer_context.network, + &withdrawer_context.withdrawer_public_key, + ) + .to_string(), + withdrawer_public_key_hash: withdrawer_context.withdrawer_public_key.pubkey_hash(), + operator_public_key: operator_context.operator_public_key, + tx_hash: [0u8; 4].into(), + }]), + peg_out_burnt_events: None, + peg_out_minted_events: None, + }; + let mock_adaptor = MockAdaptor::new(Some(mock_adaptor_config)); + let chain_service = Chain::new(Box::new(mock_adaptor)); + + client.set_chain_service(chain_service); client.sync_l2().await; let operator_funding_utxo_address = generate_pay_to_pubkey_script_address( diff --git a/bridge/tests/bridge/client/sync.rs b/bridge/tests/bridge/client/sync.rs index 5b6a5ea05..d71da8090 100644 --- a/bridge/tests/bridge/client/sync.rs +++ b/bridge/tests/bridge/client/sync.rs @@ -1,7 +1,7 @@ use bitcoin::Amount; use bridge::{ - graphs::base::FEE_AMOUNT, scripts::generate_pay_to_pubkey_script_address, + graphs::base::PEG_OUT_FEE, scripts::generate_pay_to_pubkey_script_address, transactions::base::Input, }; @@ -19,7 +19,7 @@ async fn test_sync() { config.client_0.sync().await; println!("Modify data and save"); - let amount = Amount::from_sat(INITIAL_AMOUNT + FEE_AMOUNT + 1); + let amount = Amount::from_sat(INITIAL_AMOUNT + PEG_OUT_FEE + 1); let faucet = Faucet::new(FaucetType::EsploraRegtest); let address = generate_pay_to_pubkey_script_address( config.depositor_context.network, diff --git a/bridge/tests/bridge/disprove/disprove.rs b/bridge/tests/bridge/disprove/disprove.rs index 4ad315ffa..e2f92293f 100644 --- a/bridge/tests/bridge/disprove/disprove.rs +++ b/bridge/tests/bridge/disprove/disprove.rs @@ -50,7 +50,6 @@ async fn test_disprove_tx_success() { outpoint: outpoint_1, amount: amount_1, }, - 1, ); let secret_nonces_0 = disprove_tx.push_nonces(&config.verifier_0_context); @@ -115,7 +114,6 @@ async fn test_disprove_tx_with_verifier_added_to_output_success() { outpoint: outpoint_1, amount: amount_1, }, - 1, ); let secret_nonces_0 = disprove_tx.push_nonces(&config.verifier_0_context); diff --git a/bridge/tests/bridge/disprove_chain/disprove_chain.rs b/bridge/tests/bridge/disprove_chain/disprove_chain.rs index 31bd97c9a..8769e37c4 100644 --- a/bridge/tests/bridge/disprove_chain/disprove_chain.rs +++ b/bridge/tests/bridge/disprove_chain/disprove_chain.rs @@ -19,6 +19,7 @@ use crate::bridge::{ setup::{setup_test, INITIAL_AMOUNT}, }; +#[ignore] #[tokio::test] async fn test_disprove_chain_tx_success() { let config = setup_test().await; diff --git a/bridge/tests/bridge/e2e/chain/chain.rs b/bridge/tests/bridge/e2e/chain/chain.rs index 9c013df23..056210c0a 100644 --- a/bridge/tests/bridge/e2e/chain/chain.rs +++ b/bridge/tests/bridge/e2e/chain/chain.rs @@ -3,7 +3,7 @@ use bridge::client::chain::chain::Chain; #[ignore] #[tokio::test] async fn test_rpc() { - let adaptor = Chain::new(); + let adaptor = Chain::default(); let result = adaptor.get_peg_out_init().await; assert!(result.is_ok()); diff --git a/bridge/tests/bridge/e2e/chain/ethereum.rs b/bridge/tests/bridge/e2e/chain/ethereum.rs index 0aadb4b14..331554d3d 100644 --- a/bridge/tests/bridge/e2e/chain/ethereum.rs +++ b/bridge/tests/bridge/e2e/chain/ethereum.rs @@ -2,14 +2,14 @@ use alloy::{ eips::BlockNumberOrTag, primitives::Address as EvmAddress, transports::http::reqwest::Url, }; use bridge::client::chain::{ - base::ChainAdaptor, - ethereum::{EthereumAdaptor, EthereumInitConfig}, + chain_adaptor::ChainAdaptor, + ethereum_adaptor::{EthereumAdaptor, EthereumInitConfig}, }; #[ignore] #[tokio::test] async fn test_ethereum_peg_out_init() { - let adaptor = EthereumAdaptor::new().unwrap(); + let adaptor = EthereumAdaptor::new(None); let result = adaptor.get_peg_out_init_event().await; assert!(result.is_ok()); @@ -22,14 +22,14 @@ async fn test_ethereum_peg_out_init() { #[ignore] #[tokio::test] async fn test_ethereum_peg_out_burnt() { - let adaptor = EthereumAdaptor::from_config(EthereumInitConfig { + let adaptor = EthereumAdaptor::new(Some(EthereumInitConfig { rpc_url: "http://127.0.0.1:8545".parse::().unwrap(), bridge_address: "0x76d05F58D14c0838EC630C8140eDC5aB7CD159Dc" .parse::() .unwrap(), bridge_creation_block: 20588300, to_block: Some(BlockNumberOrTag::Latest), - }); + })); let result = adaptor.get_peg_out_burnt_event().await; assert!(result.is_ok()); diff --git a/bridge/tests/bridge/e2e/chain/peg_out.rs b/bridge/tests/bridge/e2e/chain/peg_out.rs index 5445a4cb2..760b22f61 100644 --- a/bridge/tests/bridge/e2e/chain/peg_out.rs +++ b/bridge/tests/bridge/e2e/chain/peg_out.rs @@ -4,8 +4,11 @@ use alloy::{ use bitcoin::Amount; use bridge::{ - client::chain::{chain::Chain, ethereum::EthereumInitConfig}, - graphs::base::FEE_AMOUNT, + client::chain::{ + chain::Chain, + ethereum_adaptor::{EthereumAdaptor, EthereumInitConfig}, + }, + graphs::base::PEG_OUT_FEE, scripts::generate_pay_to_pubkey_script_address, transactions::{ base::{BaseTransaction, Input}, @@ -23,21 +26,21 @@ use crate::bridge::{ #[tokio::test] async fn test_peg_out_for_chain() { let config = setup_test().await; - let mut adaptors = Chain::new(); - adaptors.init_ethereum(EthereumInitConfig { + let adaptor = EthereumAdaptor::new(Some(EthereumInitConfig { rpc_url: "http://127.0.0.1:8545".parse::().unwrap(), bridge_address: "0x76d05F58D14c0838EC630C8140eDC5aB7CD159Dc" .parse::() .unwrap(), bridge_creation_block: 20588300, to_block: Some(BlockNumberOrTag::Latest), - }); - let events_result = adaptors.get_peg_out_init().await; + })); + let chain_service = Chain::new(Box::new(adaptor)); + let events_result = chain_service.get_peg_out_init().await; assert!(events_result.as_ref().is_ok_and(|x| !x.is_empty())); let mut peg_out_event = events_result.unwrap().pop().unwrap(); - let input_amount_raw = INITIAL_AMOUNT + FEE_AMOUNT; + let input_amount_raw = INITIAL_AMOUNT + PEG_OUT_FEE; let operator_input_amount = Amount::from_sat(input_amount_raw); let operator_funding_utxo_address = generate_pay_to_pubkey_script_address( diff --git a/bridge/tests/bridge/e2e/e2e_l2_ui.rs b/bridge/tests/bridge/e2e/e2e_l2_ui.rs index 25c05b3f2..e385bd9f0 100644 --- a/bridge/tests/bridge/e2e/e2e_l2_ui.rs +++ b/bridge/tests/bridge/e2e/e2e_l2_ui.rs @@ -1,20 +1,18 @@ use crate::bridge::{ faucet::{Faucet, FaucetType}, - helper::{ - find_peg_out_graph, generate_stub_outpoint, wait_for_confirmation_with_message, - REGTEST_ESPLORA_URL, - }, + helper::{find_peg_out_graph, generate_stub_outpoint, wait_for_confirmation_with_message}, setup::{setup_test, INITIAL_AMOUNT}, }; use bitcoin::{Address, Amount}; use bridge::{ - client::chain::chain::Chain, graphs::base::BaseGraph, + client::chain::chain::Chain, + graphs::base::{BaseGraph, PEG_IN_FEE, PEG_OUT_FEE}, transactions::pre_signed::PreSignedTransaction, }; use bridge::{ client::client::BitVMClient, contexts::{depositor::DepositorContext, operator::OperatorContext}, - graphs::{base::FEE_AMOUNT, peg_out::PegOutOperatorStatus}, + graphs::peg_out::PegOutOperatorStatus, scripts::generate_pay_to_pubkey_script_address, transactions::base::Input, }; @@ -22,6 +20,8 @@ use esplora_client::Builder; use futures::StreamExt; use serial_test::serial; +const REGTEST_ESPLORA_URL: &str = "http://localhost:8094/regtest/api/"; + #[ignore] #[tokio::test] #[serial] @@ -134,7 +134,7 @@ async fn test_e2e_1_simulate_peg_out() { #[tokio::test] #[serial] async fn test_e2e_2_verify_burn_event_and_simulate_peg_out_process() { - let chain_adaptor = Chain::new(); + let chain_adaptor = Chain::default(); let burnt_events = chain_adaptor.get_peg_out_burnt().await; assert!(burnt_events.is_ok()); let burnt_events = burnt_events.unwrap(); @@ -161,14 +161,14 @@ async fn create_graph() -> ( // verify funding inputs let mut funding_inputs: Vec<(&Address, Amount)> = vec![]; - let deposit_input_amount = Amount::from_sat(INITIAL_AMOUNT + FEE_AMOUNT); + let deposit_input_amount = Amount::from_sat(INITIAL_AMOUNT + PEG_IN_FEE); let deposit_funding_address = generate_pay_to_pubkey_script_address( config.depositor_context.network, &config.depositor_context.depositor_public_key, ); funding_inputs.push((&deposit_funding_address, deposit_input_amount)); - let kick_off_input_amount = Amount::from_sat(INITIAL_AMOUNT + FEE_AMOUNT); + let kick_off_input_amount = Amount::from_sat(INITIAL_AMOUNT + PEG_OUT_FEE); let kick_off_funding_utxo_address = generate_pay_to_pubkey_script_address( config.operator_context.network, &config.operator_context.operator_public_key, diff --git a/bridge/tests/bridge/e2e/peg_out/utils.rs b/bridge/tests/bridge/e2e/peg_out/utils.rs index f921d0ffb..e6432582c 100644 --- a/bridge/tests/bridge/e2e/peg_out/utils.rs +++ b/bridge/tests/bridge/e2e/peg_out/utils.rs @@ -2,7 +2,7 @@ use bitcoin::{Address, Amount, ScriptBuf}; use bitvm::chunker::disprove_execution::RawProof; use bridge::{ client::client::BitVMClient, - graphs::base::{BaseGraph, FEE_AMOUNT, PEG_OUT_FEE}, + graphs::base::{BaseGraph, PEG_IN_FEE, PEG_OUT_FEE}, scripts::generate_pay_to_pubkey_script_address, transactions::base::{Input, MIN_RELAY_FEE_PEG_OUT}, }; @@ -100,7 +100,7 @@ pub async fn create_peg_out_graph() -> ( // verify funding inputs let mut funding_inputs: Vec<(&Address, Amount)> = Vec::new(); - let deposit_input_amount = Amount::from_sat(INITIAL_AMOUNT + FEE_AMOUNT); + let deposit_input_amount = Amount::from_sat(INITIAL_AMOUNT + PEG_IN_FEE); let deposit_funding_address = generate_pay_to_pubkey_script_address( config.depositor_context.network, &config.depositor_context.depositor_public_key, diff --git a/bridge/tests/bridge/faucet.rs b/bridge/tests/bridge/faucet.rs index 01baaad10..0aef553b5 100644 --- a/bridge/tests/bridge/faucet.rs +++ b/bridge/tests/bridge/faucet.rs @@ -9,10 +9,10 @@ use serde::{Deserialize, Serialize}; use std::{collections::HashMap, process::Command, time::Duration}; use tokio::time::sleep; -use crate::bridge::helper::ALPEN_SIGNET_ESPLORA_URL; - use super::helper::wait_for_confirmation_with_message; +const ALPEN_SIGNET_ESPLORA_URL: &str = "https://esploraapi53d3659b.devnet-annapurna.stratabtc.org/"; + const ESPLORA_RETRIES: usize = 5; const ESPLORA_RETRY_WAIT_TIME: u64 = 10; @@ -23,7 +23,7 @@ struct FundResult { } pub enum FaucetType { - Mutinynet, + Signet, EsploraRegtest, } @@ -49,7 +49,7 @@ impl Faucet { fn get_network(&self) -> Network { match self.faucet_type { - FaucetType::Mutinynet => Network::Signet, + FaucetType::Signet => Network::Signet, FaucetType::EsploraRegtest => Network::Regtest, } } @@ -60,7 +60,7 @@ impl Faucet { pub async fn fund_input(&self, address: &Address, amount: Amount) -> &Self { match self.faucet_type { - FaucetType::Mutinynet => self.fund_input_with_retry(address, amount).await, + FaucetType::Signet => self.fund_input_with_retry(address, amount).await, FaucetType::EsploraRegtest => self.fund_input_by_cli(address, amount), }; self @@ -83,7 +83,7 @@ impl Faucet { let expected_count = *addr_count.get(input.0).unwrap_or(&0); if utxos.is_none() || utxos.is_some_and(|x| x.len() < expected_count) { match self.faucet_type { - FaucetType::Mutinynet => self.fund_input_with_retry(input.0, input.1).await, + FaucetType::Signet => self.fund_input_with_retry(input.0, input.1).await, FaucetType::EsploraRegtest => self.fund_input_by_cli(input.0, input.1), }; } diff --git a/bridge/tests/bridge/helper.rs b/bridge/tests/bridge/helper.rs index c51619700..2d9315f40 100644 --- a/bridge/tests/bridge/helper.rs +++ b/bridge/tests/bridge/helper.rs @@ -1,13 +1,6 @@ use std::{borrow::Cow, collections::BTreeMap, path::Path, str::FromStr, time::Duration}; use ark_bn254::g1::G1Affine; -use ark_bn254::Bn254; -use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; -use ark_ec::pairing::Pairing; -use ark_ff::PrimeField; -use ark_groth16::Groth16; -use ark_relations::lc; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use ark_std::{test_rng, UniformRand}; use bitcoin::hashes::hash160::Hash; @@ -19,6 +12,7 @@ use bitcoin::{ use bitcoin::{PubkeyHash, PublicKey, Txid}; use bridge::client::chain::chain::PegOutEvent; +use bridge::proof::get_proof; use bridge::{ client::client::BitVMClient, graphs::{ @@ -26,7 +20,7 @@ use bridge::{ peg_in::PegInGraph, peg_out::PegOutGraph, }, - utils::{num_blocks_per_network, read_cache, write_cache}, + utils::{num_blocks_per_network, read_disk_cache, write_disk_cache}, }; use bitvm::chunker::{assigner::BridgeAssigner, disprove_execution::RawProof}; @@ -34,20 +28,6 @@ use colored::Colorize; use rand::{RngCore, SeedableRng}; use tokio::time::sleep; -pub const REGTEST_ESPLORA_URL: &str = "http://localhost:8094/regtest/api/"; -pub const ALPEN_SIGNET_ESPLORA_URL: &str = - "https://esploraapi53d3659b.devnet-annapurna.stratabtc.org/"; - -pub const ESPLORA_RETRIES: usize = 3; -pub const ESPLORA_RETRY_WAIT_TIME: u64 = 5; - -pub fn get_esplora_url(network: Network) -> &'static str { - match network { - Network::Regtest => REGTEST_ESPLORA_URL, - _ => ALPEN_SIGNET_ESPLORA_URL, - } -} - // Test environment config file and its variables const TEST_ENV_FILE: &str = ".env.test"; const REGTEST_BLOCK_TIME: &str = "REGTEST_BLOCK_TIME"; @@ -143,7 +123,7 @@ pub async fn generate_stub_outpoint( "Fund {:?} with {} sats at {}", funding_utxo_address, input_value.to_sat(), - ALPEN_SIGNET_ESPLORA_URL, + client.esplora.url(), ); }); OutPoint { @@ -165,7 +145,7 @@ pub async fn generate_stub_outpoints( "Fund {:?} with {} sats at {}", funding_utxo_address, input_value.to_sat(), - ALPEN_SIGNET_ESPLORA_URL, + client.esplora.url(), ); }); funding_utxos @@ -195,7 +175,7 @@ pub async fn verify_funding_inputs(client: &BitVMClient, funding_inputs: &Vec<(& "Fund {:?} with {} sats at {}", input_to_fund.0, input_to_fund.1.to_sat(), - ALPEN_SIGNET_ESPLORA_URL, + client.esplora.url(), ); } if !inputs_to_fund.is_empty() { @@ -289,7 +269,7 @@ pub fn get_intermediate_variables_cached() -> BTreeMap { let intermediate_variables_cache_path = Path::new(TEST_CACHE_DIRECTORY_NAME).join(INTERMEDIATE_VARIABLES_FILE_NAME); let intermediate_variables = if intermediate_variables_cache_path.exists() { - read_cache(&intermediate_variables_cache_path) + read_disk_cache(&intermediate_variables_cache_path) .inspect_err(|e| { eprintln!( "Failed to read intermediate variables cache after validates its existence: {}", @@ -304,33 +284,12 @@ pub fn get_intermediate_variables_cached() -> BTreeMap { intermediate_variables.unwrap_or_else(|| { println!("Generating new intermediate variables..."); let intermediate_variables = BridgeAssigner::default().all_intermediate_variables(); - write_cache(&intermediate_variables_cache_path, &intermediate_variables).unwrap(); + write_disk_cache(&intermediate_variables_cache_path, &intermediate_variables).unwrap(); intermediate_variables }) } -pub fn get_valid_proof() -> RawProof { - type E = Bn254; - let k = 6; - let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); - let circuit = DummyCircuit::<::ScalarField> { - a: Some(::ScalarField::rand(&mut rng)), - b: Some(::ScalarField::rand(&mut rng)), - num_variables: 10, - num_constraints: 1 << k, - }; - let (pk, vk) = Groth16::::setup(circuit, &mut rng).unwrap(); - - let c = circuit.a.unwrap() * circuit.b.unwrap(); - - let proof = Groth16::::prove(&pk, circuit, &mut rng).unwrap(); - - RawProof { - proof, - public: vec![c], - vk, - } -} +pub fn get_valid_proof() -> RawProof { get_proof() } pub fn invalidate_proof(valid_proof: &RawProof) -> RawProof { let mut invalid_proof = valid_proof.clone(); @@ -343,50 +302,3 @@ pub fn invalidate_proof(valid_proof: &RawProof) -> RawProof { pub fn print_tx_broadcasted(tx_name: &str, txid: Txid) { println!("Broadcasted {} with txid: {txid}", tx_name.bold().green(),); } - -// TODO: Consider importing `gen_correct_proof` fn from bitvm/src/chunker/disprove_execution.rs -// It requires refactoring bitvm crate -// Copied from bitvm/src/chunker/disprove_execution.rs -#[derive(Copy)] -pub struct DummyCircuit { - pub a: Option, - pub b: Option, - pub num_variables: usize, - pub num_constraints: usize, -} - -impl Clone for DummyCircuit { - fn clone(&self) -> Self { - DummyCircuit { - a: self.a, - b: self.b, - num_variables: self.num_variables, - num_constraints: self.num_constraints, - } - } -} - -impl ConstraintSynthesizer for DummyCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - let a = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; - let b = cs.new_witness_variable(|| self.b.ok_or(SynthesisError::AssignmentMissing))?; - let c = cs.new_input_variable(|| { - let a = self.a.ok_or(SynthesisError::AssignmentMissing)?; - let b = self.b.ok_or(SynthesisError::AssignmentMissing)?; - - Ok(a * b) - })?; - - for _ in 0..(self.num_variables - 3) { - let _ = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; - } - - for _ in 0..self.num_constraints - 1 { - cs.enforce_constraint(lc!() + a, lc!() + b, lc!() + c)?; - } - - cs.enforce_constraint(lc!(), lc!(), lc!())?; - - Ok(()) - } -} diff --git a/bridge/tests/bridge/integration/peg_in/peg_in.rs b/bridge/tests/bridge/integration/peg_in/peg_in.rs index 50d8812eb..e68681c10 100644 --- a/bridge/tests/bridge/integration/peg_in/peg_in.rs +++ b/bridge/tests/bridge/integration/peg_in/peg_in.rs @@ -309,7 +309,6 @@ async fn test_peg_in_graph_automatic_verifier() { let client_0 = &mut config.client_0; let client_1 = &mut config.client_1; let esplora = client_0.esplora.clone(); - let context = Some(&config.verifier_0_context); // create the actual graph & check that status changes to PegInWait client_0 @@ -317,7 +316,7 @@ async fn test_peg_in_graph_automatic_verifier() { .await; assert_eq!( graph(client_0) - .verifier_status(&esplora, context, &[]) + .verifier_status(&esplora, &config.verifier_0_context, &[]) .await, PegInVerifierStatus::AwaitingDeposit ); @@ -329,7 +328,7 @@ async fn test_peg_in_graph_automatic_verifier() { loop { if !matches!( graph(client_0) - .verifier_status(&esplora, context, &[]) + .verifier_status(&esplora, &config.verifier_0_context, &[]) .await, PegInVerifierStatus::AwaitingDeposit ) { @@ -341,7 +340,7 @@ async fn test_peg_in_graph_automatic_verifier() { assert_eq!( graph(client_0) - .verifier_status(&esplora, context, &[]) + .verifier_status(&esplora, &config.verifier_0_context, &[]) .await, PegInVerifierStatus::AwaitingPegOutCreation ); @@ -379,7 +378,7 @@ async fn test_peg_in_graph_automatic_verifier() { graph(client_0) .verifier_status( &esplora, - context, + &config.verifier_0_context, &pegouts_of(client_0).iter().collect::>() ) .await, @@ -398,7 +397,7 @@ async fn test_peg_in_graph_automatic_verifier() { graph(client_0) .verifier_status( &esplora, - context, + &config.verifier_0_context, &pegouts_of(client_0).iter().collect::>() ) .await, @@ -414,7 +413,7 @@ async fn test_peg_in_graph_automatic_verifier() { graph(client_0) .verifier_status( &esplora, - context, + &config.verifier_0_context, &pegouts_of(client_0).iter().collect::>() ) .await, @@ -430,7 +429,7 @@ async fn test_peg_in_graph_automatic_verifier() { graph(client_0) .verifier_status( &esplora, - context, + &config.verifier_0_context, &pegouts_of(client_0).iter().collect::>() ) .await, @@ -446,7 +445,7 @@ async fn test_peg_in_graph_automatic_verifier() { graph(client_0) .verifier_status( &esplora, - context, + &config.verifier_0_context, &pegouts_of(client_0).iter().collect::>() ) .await, @@ -461,7 +460,7 @@ async fn test_peg_in_graph_automatic_verifier() { if graph(client_0) .verifier_status( &esplora, - context, + &config.verifier_0_context, &pegouts_of(client_0).iter().collect::>(), ) .await diff --git a/bridge/tests/bridge/integration/peg_out/disprove.rs b/bridge/tests/bridge/integration/peg_out/disprove.rs index 8d19c2987..3650f8f55 100644 --- a/bridge/tests/bridge/integration/peg_out/disprove.rs +++ b/bridge/tests/bridge/integration/peg_out/disprove.rs @@ -268,7 +268,6 @@ async fn test_disprove_success() { &config.connector_c, disprove_input_0, disprove_input_1, - script_index as u32, ); let secret_nonces_0 = disprove.push_nonces(&config.verifier_0_context); diff --git a/bridge/tests/bridge/mock/chain/mock.rs b/bridge/tests/bridge/mock/chain/mock.rs deleted file mode 100644 index 85acda0d4..000000000 --- a/bridge/tests/bridge/mock/chain/mock.rs +++ /dev/null @@ -1,48 +0,0 @@ -use async_trait::async_trait; -use bridge::client::chain::{ - base::ChainAdaptor, - chain::{PegInEvent, PegOutBurntEvent, PegOutEvent}, -}; - -pub struct MockAdaptor { - pub peg_out_init_events: Vec, - pub peg_out_burnt_events: Vec, - pub peg_in_minted_events: Vec, -} - -#[async_trait] -impl ChainAdaptor for MockAdaptor { - async fn get_peg_out_init_event(&self) -> Result, String> { - Ok(self.peg_out_init_events.clone()) - } - - async fn get_peg_out_burnt_event(&self) -> Result, String> { - Ok(self.peg_out_burnt_events.clone()) - } - - async fn get_peg_in_minted_event(&self) -> Result, String> { - Ok(self.peg_in_minted_events.clone()) - } -} - -impl Default for MockAdaptor { - fn default() -> Self { Self::new() } -} - -impl MockAdaptor { - pub fn new() -> Self { - Self { - peg_out_init_events: vec![], - peg_out_burnt_events: vec![], - peg_in_minted_events: vec![], - } - } - - pub fn from_peg_out_init(peg_out_init_events: Vec) -> Self { - Self { - peg_out_init_events, - peg_out_burnt_events: vec![], - peg_in_minted_events: vec![], - } - } -} diff --git a/bridge/tests/bridge/mock/chain/mod.rs b/bridge/tests/bridge/mock/chain/mod.rs deleted file mode 100644 index 9afc1d5e9..000000000 --- a/bridge/tests/bridge/mock/chain/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod mock; diff --git a/bridge/tests/bridge/mock/mod.rs b/bridge/tests/bridge/mock/mod.rs deleted file mode 100644 index 28ac57df5..000000000 --- a/bridge/tests/bridge/mock/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod chain; diff --git a/bridge/tests/bridge/mod.rs b/bridge/tests/bridge/mod.rs index a3d1d9da7..a1686264b 100644 --- a/bridge/tests/bridge/mod.rs +++ b/bridge/tests/bridge/mod.rs @@ -12,7 +12,6 @@ pub mod integration; pub mod kick_off_1; pub mod kick_off_2; pub mod kick_off_timeout; -pub mod mock; pub mod peg_in; pub mod serialization; pub mod setup; diff --git a/bridge/tests/bridge/setup.rs b/bridge/tests/bridge/setup.rs index 0ef7b6c13..6e91c939d 100644 --- a/bridge/tests/bridge/setup.rs +++ b/bridge/tests/bridge/setup.rs @@ -2,11 +2,11 @@ use std::collections::HashMap; use bitcoin::{Network, PublicKey}; -use super::helper::{ - get_esplora_url, get_intermediate_variables_cached, get_valid_proof, invalidate_proof, -}; +use super::helper::{get_intermediate_variables_cached, get_valid_proof, invalidate_proof}; use bridge::{ - client::client::BitVMClient, + client::{ + chain::chain_adaptor::get_chain_adaptor, client::BitVMClient, esplora::get_esplora_url, + }, commitments::CommitmentMessageId, connectors::{ connector_0::Connector0, connector_1::Connector1, connector_2::Connector2, @@ -23,10 +23,6 @@ use bridge::{ base::generate_keys_from_secret, depositor::DepositorContext, operator::OperatorContext, verifier::VerifierContext, withdrawer::WithdrawerContext, }, - graphs::base::{ - DEPOSITOR_EVM_ADDRESS, DEPOSITOR_SECRET, OPERATOR_SECRET, VERIFIER_0_SECRET, - VERIFIER_1_SECRET, WITHDRAWER_EVM_ADDRESS, WITHDRAWER_SECRET, - }, serialization::serialize, superblock::{SUPERBLOCK_HASH_MESSAGE_LENGTH, SUPERBLOCK_MESSAGE_LENGTH}, transactions::assert_transactions::utils::{ @@ -43,6 +39,15 @@ use bitvm::{ }, }; +const OPERATOR_SECRET: &str = "3076ca1dfc1e383be26d5dd3c0c427340f96139fa8c2520862cf551ec2d670ac"; +const VERIFIER_0_SECRET: &str = "ee0817eac0c13aa8ee2dd3256304041f09f0499d1089b56495310ae8093583e2"; +const VERIFIER_1_SECRET: &str = "fc294c70faf210d4d0807ea7a3dba8f7e41700d90c119e1ae82a0687d89d297f"; +const DEPOSITOR_SECRET: &str = "b8f17ea979be24199e7c3fec71ee88914d92fd4ca508443f765d56ce024ef1d7"; +const WITHDRAWER_SECRET: &str = "fffd54f6d8f8ad470cb507fd4b6e9b3ea26b4221a4900cc5ad5916ce67c02f1e"; + +const DEPOSITOR_EVM_ADDRESS: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; // l2 local test network account 1 +const WITHDRAWER_EVM_ADDRESS: &str = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"; // l2 local test network account 2 + pub const INITIAL_AMOUNT: u64 = 2 << 20; // 2097152 pub const ONE_HUNDRED: u64 = 2 << 26; // 134217728 @@ -212,6 +217,7 @@ pub async fn setup_test() -> SetupConfig { Some(get_esplora_url(source_network)), source_network, destination_network, + Some(get_chain_adaptor(destination_network, None, None)), &n_of_n_public_keys, Some(DEPOSITOR_SECRET), Some(OPERATOR_SECRET), @@ -226,6 +232,7 @@ pub async fn setup_test() -> SetupConfig { Some(get_esplora_url(source_network)), source_network, destination_network, + Some(get_chain_adaptor(destination_network, None, None)), &n_of_n_public_keys, Some(DEPOSITOR_SECRET), Some(OPERATOR_SECRET),