diff --git a/Cargo.lock b/Cargo.lock index 1ffae3f1..f98b23e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -425,7 +425,7 @@ checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" [[package]] name = "bitcoin-mock-rpc" version = "0.1.0" -source = "git+https://github.com/chainwayxyz/bitcoin-mock-rpc?tag=v0.0.3#ef4360f96803fd029aa269235c805fd4be9056d7" +source = "git+https://github.com/chainwayxyz/bitcoin-mock-rpc?tag=v0.0.5#dd2520b1b592a2df42da890b3ae6700b7fffa934" dependencies = [ "anyhow", "bitcoin", diff --git a/Cargo.toml b/Cargo.toml index af73b2b9..e1adb74d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ toml = "0.8.12" sqlx = { version = "0.7.4", default-features = false } k256 = { version = "=0.13.3", default-features = false } risc0-build = "0.21.0" -bitcoin-mock-rpc = { git = "https://github.com/chainwayxyz/bitcoin-mock-rpc", tag = "v0.0.3" } +bitcoin-mock-rpc = { git = "https://github.com/chainwayxyz/bitcoin-mock-rpc", tag = "v0.0.5" } # Always optimize; building and running the guest takes much longer without optimization. [profile.dev] diff --git a/core/src/database/common.rs b/core/src/database/common.rs index edb00fc9..d8f3bfdc 100644 --- a/core/src/database/common.rs +++ b/core/src/database/common.rs @@ -231,7 +231,7 @@ impl Database { idx: usize, ) -> Result<(Txid, secp256k1::schnorr::Signature), BridgeError> { let qr: (String, String) = - sqlx::query_as("SELECT (bridge_fund_txid, sig) FROM withdrawal_sigs WHERE idx = $1;") + sqlx::query_as("SELECT bridge_fund_txid, sig FROM withdrawal_sigs WHERE idx = $1;") .bind(idx as i64) .fetch_one(&self.connection) .await?; @@ -247,10 +247,10 @@ mod tests { use super::Database; use crate::{ config::BridgeConfig, create_test_config, create_test_config_with_thread_name, - mock::common, EVMAddress, + mock::common, transaction_builder::TransactionBuilder, EVMAddress, }; - use bitcoin::{Address, OutPoint, XOnlyPublicKey}; - use secp256k1::Secp256k1; + use bitcoin::{Address, Amount, OutPoint, ScriptBuf, TxOut, XOnlyPublicKey}; + use secp256k1::{schnorr::Signature, Secp256k1}; use std::thread; #[tokio::test] @@ -314,6 +314,35 @@ mod tests { .await .unwrap(); } + + #[tokio::test] + async fn get_save_withdrawal_sig() { + let config = create_test_config!("get_save_withdrawal_sig", "test_config.toml"); + let db = Database::new(config).await.unwrap(); + + let txout = TxOut { + value: Amount::from_sat(0x45), + script_pubkey: ScriptBuf::new(), + }; + let tx = TransactionBuilder::create_btc_tx(vec![], vec![txout]); + let txid = tx.compute_txid(); + + let sig_arr = [ + 0x14, 0x5f, 0x50, 0x9d, 0xab, 0x82, 0xb0, 0xa1, 0x51, 0x1a, 0x20, 0x00, 0x93, 0x03, + 0x19, 0xb1, 0x11, 0x29, 0xa5, 0x77, 0x3e, 0xe5, 0xc8, 0x6a, 0x13, 0x42, 0x0c, 0x23, + 0x8e, 0x97, 0x26, 0x0b, 0xbe, 0x8b, 0x8e, 0xdd, 0xcd, 0x71, 0x6e, 0x76, 0xd4, 0x06, + 0xb6, 0x1d, 0x54, 0x7d, 0xac, 0xd9, 0xb9, 0x32, 0xdc, 0x93, 0xbf, 0x33, 0xf5, 0xb0, + 0x3c, 0x2f, 0x99, 0x2c, 0x04, 0xf6, 0x70, 0x73, + ]; + let signature = Signature::from_slice(&sig_arr).unwrap(); + + db.save_withdrawal_sig(0x45, txid, signature).await.unwrap(); + + let (read_txid, read_signature) = db.get_withdrawal_sig_by_idx(0x45).await.unwrap(); + + assert_eq!(txid, read_txid); + assert_eq!(signature, read_signature); + } } #[cfg(poc)] diff --git a/core/src/extended_rpc.rs b/core/src/extended_rpc.rs index 4314f2da..c682a063 100644 --- a/core/src/extended_rpc.rs +++ b/core/src/extended_rpc.rs @@ -325,7 +325,10 @@ where R: RpcApiWrapper, { fn clone(&self) -> Self { - let new_client = R::new(&self.url, self.auth.clone()) + // `new_without_cleanup` call is essentially same with `new` call. But + // it won't clean mock RPC database when called. It will only establish + // a new connection with database. + let new_client = R::new_without_cleanup(&self.url, self.auth.clone()) .unwrap_or_else(|e| panic!("Failed to clone Bitcoin RPC client: {}", e)); Self { diff --git a/core/tests/data/test_config_verifier_down_for_withdrawal_signature.toml b/core/tests/data/test_config_verifier_down_for_withdrawal_signature.toml new file mode 100644 index 00000000..047e94e2 --- /dev/null +++ b/core/tests/data/test_config_verifier_down_for_withdrawal_signature.toml @@ -0,0 +1,31 @@ +tracing_debug = "debug,bitcoincore_rpc=info,hyper=error" +host = "127.0.0.1" +port = 3000 +secret_key = "5555555555555555555555555555555555555555555555555555555555555555" +verifiers_public_keys = [ + "4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa", + "466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27", + "3c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1", + "2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", + "9ac20335eb38768d2052be1dbbc3c8f6178407458e51e6b4ad22f1d91758895b", +] +num_verifiers = 4 +min_relay_fee = 305 +user_takes_after = 5 +confirmation_treshold = 1 +network = "regtest" +bitcoin_rpc_url = "http://127.0.0.1:18443" +bitcoin_rpc_user = "admin" +bitcoin_rpc_password = "admin" +all_secret_keys = [ + "1111111111111111111111111111111111111111111111111111111111111111", + "2222222222222222222222222222222222222222222222222222222222222222", + "3333333333333333333333333333333333333333333333333333333333333333", + "4444444444444444444444444444444444444444444444444444444444444444", + "5555555555555555555555555555555555555555555555555555555555555555", +] +db_host = "127.0.0.1" +db_port = 5432 +db_user = "clementine" +db_password = "" +db_name = "clementine" diff --git a/core/tests/flow.rs b/core/tests/flow.rs index 8f663a2c..2dd04047 100644 --- a/core/tests/flow.rs +++ b/core/tests/flow.rs @@ -13,6 +13,7 @@ use clementine_core::servers::*; use clementine_core::traits::rpc::OperatorRpcClient; use clementine_core::transaction_builder::{CreateTxOutputs, TransactionBuilder}; use clementine_core::utils::handle_taproot_witness_new; +use clementine_core::utils::SECP; use clementine_core::EVMAddress; use clementine_core::{ create_extended_rpc, create_test_config, create_test_config_with_thread_name, @@ -220,3 +221,117 @@ async fn test_flow_2() { .unwrap(); tracing::debug!("User takes back tx: {:#?}", user_takes_back_tx); } + +#[tokio::test] +async fn verifier_down_for_withdrawal_signature() { + let mut config = create_test_config_with_thread_name!( + "test_config_verifier_down_for_withdrawal_signature.toml" + ); + let rpc = create_extended_rpc!(config); + let handle = thread::current() + .name() + .unwrap() + .split(':') + .last() + .unwrap() + .to_owned(); + for i in 0..4 { + create_test_config!( + handle.clone() + i.to_string().as_str(), + "test_config_verifier_down_for_withdrawal_signature.toml" + ); + } + + let (xonly_pk, _) = config.secret_key.public_key(&SECP).x_only_public_key(); + let taproot_address = Address::p2tr(&SECP, xonly_pk, None, config.network); + + let tx_builder = TransactionBuilder::new(config.verifiers_public_keys.clone(), config.network); + + let (operator_client, _operator_handler, results) = + create_operator_and_verifiers(config.clone(), rpc.clone()).await; + + let evm_addresses = [ + EVMAddress([1u8; 20]), + EVMAddress([2u8; 20]), + EVMAddress([3u8; 20]), + EVMAddress([4u8; 20]), + ]; + let deposit_addresses = evm_addresses + .iter() + .map(|evm_address| { + tx_builder + .generate_deposit_address( + taproot_address.as_unchecked(), + evm_address, + BRIDGE_AMOUNT_SATS, + config.user_takes_after, + ) + .unwrap() + .0 + }) + .collect::>(); + println!("Deposit addresses: {:#?}", deposit_addresses); + + for (idx, deposit_address) in deposit_addresses.iter().enumerate() { + let deposit_utxo = rpc + .send_to_address(deposit_address, BRIDGE_AMOUNT_SATS) + .unwrap(); + println!("Deposit UTXO #{}: {:#?}", idx, deposit_utxo); + + rpc.mine_blocks(18).unwrap(); + + let output = operator_client + .new_deposit_rpc( + deposit_utxo, + taproot_address.as_unchecked().clone(), + evm_addresses[idx], + ) + .await + .unwrap(); + println!("Output #{}: {:#?}", idx, output); + } + + // Assume one of the verifier is down. + const VERIFIER_IDX: usize = 3; + results.get(VERIFIER_IDX).unwrap().1.stop().unwrap(); + + let withdrawal_address = Address::p2tr(&SECP, xonly_pk, None, config.network); + + if let Ok(_withdraw_txid) = operator_client + .new_withdrawal_direct_rpc(0, withdrawal_address.as_unchecked().clone()) + .await + { + println!( + "Verifier {} is down, this should not be possible.", + VERIFIER_IDX + ); + assert!(false); + }; + + // Restart all servers. + results.iter().for_each(|server| { + let _ = server.1.stop(); + }); + let (operator_client, _operator_handler, _results) = + create_operator_and_verifiers(config.clone(), rpc.clone()).await; + + let withdraw_txid = operator_client + .new_withdrawal_direct_rpc(0, withdrawal_address.as_unchecked().clone()) + .await + .unwrap(); + println!("Withdrawal send to address: {:?}", withdrawal_address); + println!("Withdrawal TXID: {:#?}", withdraw_txid); + + // check whether it has an output with the withdrawal address + let tx = rpc.get_raw_transaction(&withdraw_txid, None).unwrap(); + let rpc_withdraw_script = tx.output[0].script_pubkey.clone(); + let rpc_withdraw_amount = tx.output[0].value; + let expected_withdraw_script = withdrawal_address.script_pubkey(); + assert_eq!(rpc_withdraw_script, expected_withdraw_script); + + // check if the amounts match + let anyone_can_spend_amount = script_builder::anyone_can_spend_txout().value; + let expected_withdraw_amount = Amount::from_sat(BRIDGE_AMOUNT_SATS - 2 * config.min_relay_fee) + - anyone_can_spend_amount * 2; + assert_eq!(expected_withdraw_amount, rpc_withdraw_amount); +}