Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 49 additions & 37 deletions crates/e2e-tests/src/bitcoin_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::path::Path;
use std::path::PathBuf;
use std::process::Child;
use std::process::Command;
use std::sync::Arc;
use std::time::Duration;
use tracing::info;
use tracing::warn;
Expand All @@ -25,7 +26,7 @@ pub const RPC_USER: &str = "test";
pub const RPC_PASSWORD: &str = "test";

pub struct BitcoinNodeHandle {
rpc_client: Client,
rpc_client: Arc<Client>,
#[allow(unused)]
data_dir: PathBuf,
process: Child,
Expand Down Expand Up @@ -98,10 +99,10 @@ impl BitcoinNodeHandle {
}
}

let rpc_client = Client::new_with_auth(
let rpc_client = Arc::new(Client::new_with_auth(
&rpc_url,
Auth::UserPass(RPC_USER.to_string(), RPC_PASSWORD.to_string()),
)?;
)?);
Ok(Self {
rpc_client,
data_dir,
Expand All @@ -124,10 +125,12 @@ impl BitcoinNodeHandle {
self.startup_diagnostics()
));
}
match self.rpc_client.get_blockchain_info() {
match crate::btc_rpc_call(&self.rpc_client, |rpc| rpc.get_blockchain_info()).await {
Ok(_) => {
info!("Bitcoin node is ready");
match self.rpc_client.create_wallet("test") {
match crate::btc_rpc_call(&self.rpc_client, |rpc| rpc.create_wallet("test"))
.await
{
Ok(_) => info!("Created test wallet"),
Err(e) => info!("Wallet creation: {}", e),
}
Expand Down Expand Up @@ -169,53 +172,62 @@ impl BitcoinNodeHandle {
diagnostics.join("; ")
}

pub fn generate_blocks(&self, count: u64) -> Result<Vec<BlockHash>> {
let blocks = self
.rpc_client
.generate_to_address(count as usize, &self.get_new_address()?)?
.into_model()
.map_err(|e| anyhow!("invalid block hash: {e}"))?
.0;
info!("Generated {} blocks", count);
Ok(blocks)
pub async fn generate_blocks(&self, count: u64) -> Result<Vec<BlockHash>> {
let address = self.get_new_address().await?;
crate::btc_rpc_call(&self.rpc_client, move |rpc| {
let blocks = rpc
.generate_to_address(count as usize, &address)?
.into_model()
.map_err(|e| anyhow!("invalid block hash: {e}"))?
.0;
info!("Generated {} blocks", count);
Ok(blocks)
})
.await
}

pub fn send_to_address(&self, address: &Address, amount: Amount) -> Result<Txid> {
let txid = self
.rpc_client
.send_to_address(address, amount)?
.into_model()
.map_err(|e| anyhow!("invalid txid: {e}"))?
.txid;
info!("Sent {} to {}: {}", amount, address, txid);
Ok(txid)
pub async fn send_to_address(&self, address: &Address, amount: Amount) -> Result<Txid> {
let address = address.clone();
crate::btc_rpc_call(&self.rpc_client, move |rpc| {
let txid = rpc
.send_to_address(&address, amount)?
.into_model()
.map_err(|e| anyhow!("invalid txid: {e}"))?
.txid;
info!("Sent {} to {}: {}", amount, address, txid);
Ok(txid)
})
.await
}

pub fn get_balance(&self) -> Result<Amount> {
Ok(self
.rpc_client
.get_balance()?
.into_model()
.map_err(|e| anyhow!("invalid balance: {e}"))?
.0)
pub async fn get_balance(&self) -> Result<Amount> {
crate::btc_rpc_call(&self.rpc_client, |rpc| {
Ok(rpc
.get_balance()?
.into_model()
.map_err(|e| anyhow!("invalid balance: {e}"))?
.0)
})
.await
}

pub fn get_new_address(&self) -> Result<Address> {
let address = self.rpc_client.new_address()?;
Ok(address)
pub async fn get_new_address(&self) -> Result<Address> {
crate::btc_rpc_call(&self.rpc_client, |rpc| Ok(rpc.new_address()?)).await
}

pub fn get_block_count(&self) -> Result<u64> {
Ok(self.rpc_client.get_block_count()?.0)
pub async fn get_block_count(&self) -> Result<u64> {
crate::btc_rpc_call(&self.rpc_client, |rpc| Ok(rpc.get_block_count()?.0)).await
}

pub async fn wait_for_transaction(&self, txid: &Txid, timeout: Duration) -> Result<()> {
let start = std::time::Instant::now();
let txid = *txid;
loop {
if start.elapsed() > timeout {
return Err(anyhow!("Transaction {} not found within timeout", txid));
}
match self.rpc_client.get_transaction(*txid) {
match crate::btc_rpc_call(&self.rpc_client, move |rpc| rpc.get_transaction(txid)).await
{
Ok(_) => {
info!("Transaction {} confirmed", txid);
return Ok(());
Expand Down Expand Up @@ -313,7 +325,7 @@ impl BitcoinNodeBuilder {
let node_handle = BitcoinNodeHandle::new(rpc_port, data_dir, bitcoin_core_path)?;
node_handle.wait_until_ready().await?;
if self.initial_blocks > 0 {
node_handle.generate_blocks(self.initial_blocks)?;
node_handle.generate_blocks(self.initial_blocks).await?;
}
info!(
"Created Bitcoin node at RPC port {} with {} initial blocks",
Expand Down
38 changes: 21 additions & 17 deletions crates/e2e-tests/src/e2e_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,16 @@ mod tests {
info!("Sending Bitcoin to deposit address...");
let txid = networks
.bitcoin_node
.send_to_address(&deposit_address, Amount::from_sat(amount_sats))?;
.send_to_address(&deposit_address, Amount::from_sat(amount_sats))
.await?;
info!("Transaction sent: {}", txid);

info!("Mining blocks for confirmation...");
let blocks_to_mine = 10;
networks.bitcoin_node.generate_blocks(blocks_to_mine)?;
networks
.bitcoin_node
.generate_blocks(blocks_to_mine)
.await?;
info!("{blocks_to_mine} blocks mined");

info!("Creating deposit request on Sui...");
Expand Down Expand Up @@ -424,7 +428,7 @@ mod tests {
}

loop {
let mined_blocks = bitcoin_node.generate_blocks(1)?;
let mined_blocks = bitcoin_node.generate_blocks(1).await?;
let block_hash = mined_blocks
.last()
.copied()
Expand Down Expand Up @@ -511,7 +515,7 @@ mod tests {
let hashi = networks.hashi_network.nodes()[0].hashi().clone();
let user_key = networks.sui_network.user_keys.first().unwrap();
let withdrawal_amount_sats = 30_000u64;
let btc_destination = networks.bitcoin_node.get_new_address()?;
let btc_destination = networks.bitcoin_node.get_new_address().await?;
let destination_bytes = extract_witness_program(&btc_destination)?;
info!(
"Requesting withdrawal of {} sats to {}",
Expand Down Expand Up @@ -580,7 +584,7 @@ mod tests {
signer: sui_crypto::ed25519::Ed25519PrivateKey,
withdrawal_amount_sats: u64,
) -> Result<()> {
let btc_destination = networks.bitcoin_node.get_new_address()?;
let btc_destination = networks.bitcoin_node.get_new_address().await?;
let destination_bytes = extract_witness_program(&btc_destination)?;
let mut executor =
SuiTxExecutor::from_config(&hashi.config, hashi.onchain_state())?.with_signer(signer);
Expand Down Expand Up @@ -891,7 +895,7 @@ mod tests {
let user_key = networks.sui_network.user_keys.first().unwrap().clone();

// Submit withdrawal 1. Do NOT mine any Bitcoin blocks yet.
let btc_destination1 = networks.bitcoin_node.get_new_address()?;
let btc_destination1 = networks.bitcoin_node.get_new_address().await?;
let destination_bytes1 = extract_witness_program(&btc_destination1)?;
let mut executor = SuiTxExecutor::from_config(&hashi.config, hashi.onchain_state())?
.with_signer(user_key.clone());
Expand Down Expand Up @@ -926,7 +930,7 @@ mod tests {

// Submit withdrawal 2 immediately — the deposit UTXO is now locked, so
// the only available UTXO is the unconfirmed change from withdrawal 1.
let btc_destination2 = networks.bitcoin_node.get_new_address()?;
let btc_destination2 = networks.bitcoin_node.get_new_address().await?;
let destination_bytes2 = extract_witness_program(&btc_destination2)?;
executor
.execute_create_withdrawal_request(withdrawal_amount_sats, destination_bytes2)
Expand Down Expand Up @@ -1095,14 +1099,14 @@ mod tests {
// Submit two withdrawal requests back-to-back without waiting for either
// to be committed. The leader should approve both and then batch them
// together into a single Bitcoin transaction.
let btc_destination1 = networks.bitcoin_node.get_new_address()?;
let btc_destination1 = networks.bitcoin_node.get_new_address().await?;
let destination_bytes1 = extract_witness_program(&btc_destination1)?;
executor
.execute_create_withdrawal_request(withdrawal_amount_sats, destination_bytes1)
.await?;
info!("Withdrawal request 1 submitted");

let btc_destination2 = networks.bitcoin_node.get_new_address()?;
let btc_destination2 = networks.bitcoin_node.get_new_address().await?;
let destination_bytes2 = extract_witness_program(&btc_destination2)?;
executor
.execute_create_withdrawal_request(withdrawal_amount_sats, destination_bytes2)
Expand Down Expand Up @@ -1193,14 +1197,14 @@ mod tests {
let mut executor = SuiTxExecutor::from_config(&hashi.config, hashi.onchain_state())?
.with_signer(user_key.clone());

let btc_destination1 = networks.bitcoin_node.get_new_address()?;
let btc_destination1 = networks.bitcoin_node.get_new_address().await?;
let destination_bytes1 = extract_witness_program(&btc_destination1)?;
executor
.execute_create_withdrawal_request(withdrawal_amount_sats, destination_bytes1)
.await?;
info!("Withdrawal request 1 submitted");

let btc_destination2 = networks.bitcoin_node.get_new_address()?;
let btc_destination2 = networks.bitcoin_node.get_new_address().await?;
let destination_bytes2 = extract_witness_program(&btc_destination2)?;
executor
.execute_create_withdrawal_request(withdrawal_amount_sats, destination_bytes2)
Expand Down Expand Up @@ -1367,7 +1371,7 @@ mod tests {
.with_signer(user_key.clone());

// --- Withdrawal 1 ---
let btc_destination1 = networks.bitcoin_node.get_new_address()?;
let btc_destination1 = networks.bitcoin_node.get_new_address().await?;
let destination_bytes1 = extract_witness_program(&btc_destination1)?;
executor
.execute_create_withdrawal_request(withdrawal_amount_sats, destination_bytes1)
Expand All @@ -1393,14 +1397,14 @@ mod tests {
// PendingWithdrawal and its change UTXO remains Pending { chain }.
// The AncestorTx for withdrawal 1 will have confirmations=2, so
// mempool_chain_depth() returns 0 — the change UTXO is eligible.
networks.bitcoin_node.generate_blocks(2)?;
networks.bitcoin_node.generate_blocks(2).await?;
info!("Mined 2 blocks; withdrawal 1 now has 2 Bitcoin confirmations (below threshold 6)");

// --- Withdrawal 2 ---
// The only available UTXO is the change from withdrawal 1. Its ancestor
// is mined (confirmations=2 ≥ 1) so mempool_chain_depth()=0, making it
// eligible even though withdrawal 1 is not yet confirmed on Sui.
let btc_destination2 = networks.bitcoin_node.get_new_address()?;
let btc_destination2 = networks.bitcoin_node.get_new_address().await?;
let destination_bytes2 = extract_witness_program(&btc_destination2)?;
executor
.execute_create_withdrawal_request(withdrawal_amount_sats, destination_bytes2)
Expand Down Expand Up @@ -1464,7 +1468,7 @@ mod tests {
.with_signer(user_key.clone());

// --- Withdrawal A ---
let btc_destination_a = networks.bitcoin_node.get_new_address()?;
let btc_destination_a = networks.bitcoin_node.get_new_address().await?;
executor
.execute_create_withdrawal_request(
withdrawal_amount_sats,
Expand All @@ -1488,7 +1492,7 @@ mod tests {

// --- Withdrawal B ---
// UTXO_A has mempool depth 1 ≤ 3 → eligible.
let btc_destination_b = networks.bitcoin_node.get_new_address()?;
let btc_destination_b = networks.bitcoin_node.get_new_address().await?;
executor
.execute_create_withdrawal_request(
withdrawal_amount_sats,
Expand All @@ -1512,7 +1516,7 @@ mod tests {

// --- Withdrawal C ---
// UTXO_B has full ancestor chain [B, A] at mempool depth 2 ≤ 3 → eligible.
let btc_destination_c = networks.bitcoin_node.get_new_address()?;
let btc_destination_c = networks.bitcoin_node.get_new_address().await?;
executor
.execute_create_withdrawal_request(
withdrawal_amount_sats,
Expand Down
Loading
Loading