Skip to content

Commit

Permalink
feat(bridge-exec)!: sign_[deposit|withdraw]_tx
Browse files Browse the repository at this point in the history
  • Loading branch information
storopoli committed Sep 26, 2024
1 parent 764d6d5 commit e71b23f
Show file tree
Hide file tree
Showing 11 changed files with 474 additions and 37 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion crates/bridge-exec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ rustdoc.all = "warn"

[dependencies]
alpen-express-primitives = { workspace = true }
alpen-express-rpc-api = { workspace = true, features = ["client"] }
express-bridge-sig-manager = { workspace = true }
express-bridge-tx-builder = { workspace = true }

async-trait = { workspace = true }
bitcoin = { workspace = true }
bitcoin = { workspace = true, features = ["rand-std"] }
format_serde_error = { workspace = true }
jsonrpsee = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }
15 changes: 15 additions & 0 deletions crates/bridge-exec/src/deposit_handler/errors.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
//! Defines the error types associated with executing the deposit duties.

use alpen_express_btcio::rpc::error::ClientError as L1ClientError;
use express_bridge_tx_builder::errors::BridgeTxBuilderError;
use jsonrpsee::core::ClientError as L2ClientError;
use thiserror::Error;

// TODO: use concrete types instead of passing around `String`

/// Error encountered during the deposit duty execution.
#[derive(Error, Debug)]
pub enum DepositExecError {
/// Error creating the [`TxSigningData`](alpen_express_primitives::bridge::TxSigningData).
#[error("could not build deposit transaction")]
TxBuilder(#[from] BridgeTxBuilderError),

/// Error occurred while signing a transaction.
#[error("signing failed due to: {0}")]
Signing(String),
Expand All @@ -22,6 +29,14 @@ pub enum DepositExecError {
/// An unexpected error occurred during execution.
#[error("execution failed: {0}")]
Execution(String),

/// Error communicating with the Bitcoin RPC.
#[error("bitcoin RPC communication failed: {0}")]
L1Rpc(#[from] L1ClientError),

/// Error communicating with the rollup RPC.
#[error("rollup RPC communication failed: {0}")]
L2Rpc(#[from] L2ClientError),
}

/// The result of a deposit duty execution which may produce a [`DepositExecError`].
Expand Down
181 changes: 175 additions & 6 deletions crates/bridge-exec/src/deposit_handler/handler.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,183 @@
//! Defines the functions that pertain to handling a deposit.

use alpen_express_primitives::bridge::OperatorPartialSig;
use bitcoin::secp256k1::schnorr::Signature;
use express_bridge_tx_builder::deposit::DepositInfo;
use std::sync::Arc;

use alpen_express_btcio::rpc::traits::Signer;
use alpen_express_primitives::{
l1::BitcoinTxid,
relay::{types::Scope, util::MessageSigner},
};
use alpen_express_rpc_api::AlpenApiClient;
use bitcoin::{secp256k1::SECP256K1, Txid};
use express_bridge_sig_manager::manager::SignatureManager;
use express_bridge_tx_builder::{deposit::DepositInfo, prelude::*, TxKind};
use jsonrpsee::tokio::time::{sleep, Duration};
use tracing::*;

use super::errors::DepositExecResult;
use crate::deposit_handler::errors::DepositExecError;

/// Construct and sign the deposit transaction.
pub async fn sign_deposit_tx(_deposit_info: &DepositInfo) -> DepositExecResult<OperatorPartialSig> {
unimplemented!()
/// (Partially) signs the deposit transaction.
///
/// Also broadcasts to the bridge transaction database.
///
/// # Arguments
///
/// - `deposit_info`: a pending [`DepositInfo`] duty.
/// - `l1_rpc_client`: anything that is able to sign transactions and messages; i.e. holds private
/// keys.
/// - `l2_rpc_client`: anything that can communicate with the [`AlpenApiClient`].
/// - `sig_manager`: a stateful [`SignatureManager`].
/// - `tx_build_context`: stateful [`TxBuildContext`].
///
/// # Notes
///
/// Both the [`SignatureManager`] and the [`TxBuildContext`] can be reused
/// for multiple signing sessions if the operators'
/// [`PublickeyTable`](alpen_express_primitives::bridge::PublickeyTable)
/// does _not_ change.
///
/// We don't need mutexes since all functions to [`SignatureManager`] and
/// [`TxBuildContext`] takes non-mutable references.
pub async fn sign_deposit_tx(
deposit_info: &DepositInfo,
l1_rpc_client: &Arc<impl Signer>,
l2_rpc_client: &Arc<impl AlpenApiClient + Sync>,
sig_manager: &Arc<SignatureManager>,
tx_build_context: &Arc<TxBuildContext>,
) -> DepositExecResult<Txid> {
// setup logging
let span = span!(Level::INFO, "starting deposit transaction signing");
let _guard = span.enter();

let operator_pubkeys = tx_build_context.pubkey_table();
let own_index = tx_build_context.own_index();
let own_pubkey = operator_pubkeys
.0
.get(&own_index)
.expect("could not find operator's pubkey in public key table");

event!(
Level::INFO,
event = "got the basic self information",
?deposit_info,
%own_index,
%own_pubkey,
);

// sign the transaction with MuSig2 and put inside the OperatorPartialSig
let xpriv = l1_rpc_client.get_xpriv().await?;
if let Some(xpriv) = xpriv {
let keypair = xpriv.to_keypair(SECP256K1);

// construct the transaction data
let tx_signing_data = deposit_info.construct_signing_data(tx_build_context.as_ref())?;

event!(
Level::DEBUG,
event = "got the signing data",
?tx_signing_data,
);

// add the tx_state to the sig_manager in order to generate a sec_nonce and pub_nonce
let txid = sig_manager
.add_tx_state(tx_signing_data, operator_pubkeys.clone())
.await
.map_err(|e| DepositExecError::Signing(e.to_string()))?;

event!(
Level::INFO,
event = "added the public nonce to the bridge transaction database",
%txid,
);

// Then, submit_message RPC call
let bitcoin_txid: BitcoinTxid = txid.into();

let public_nonce = sig_manager
.get_own_nonce(&txid)
.await
.map_err(|e| DepositExecError::Signing(e.to_string()))?;

let scope = Scope::V0DepositPubNonce(bitcoin_txid);
event!(
Level::DEBUG,
event = "create the deposit pub nonce scope",
?scope
);
let message = MessageSigner::new(own_index, keypair.secret_key().into())
.sign_scope(&scope, &public_nonce)
.map_err(|e| DepositExecError::Signing(e.to_string()))?;
event!(
Level::DEBUG,
event = "create the deposit pub nonce message",
?message,
);
let raw_message: Vec<u8> = message
.try_into()
.expect("could not serialize bridge message into raw bytes");

l2_rpc_client.submit_bridge_msg(raw_message.into()).await?;
event!(
Level::INFO,
event = "broadcasted the deposit pub nonce message",
);

// Wait for all the pub nonces to be broadcasted by other operators.
// Collect all nonces.
// Then signing will not fail.
loop {
event!(
Level::DEBUG,
event = "trying to get all pub nonces from the bridge transaction database, waiting for other operators' nonces",
);
let got_all_nonces = sig_manager
.get_tx_state(&txid)
.await
.map_err(|e| DepositExecError::Signing(e.to_string()))?
.has_all_nonces();
if got_all_nonces {
event!(
Level::INFO,
event = "got all pub nonces from the bridge transaction database",
%got_all_nonces
);
break;
} else {
// TODO: this is hardcoded, maybe move this to a user-configurable Config
sleep(Duration::from_millis(100)).await;
continue;
}
}

// adds the operator's partial signature
// NOTE: this should be not fail now since we have all the pub nonces
let flag = sig_manager
.add_own_partial_sig(&txid)
.await
.map_err(|e| DepositExecError::Signing(e.to_string()))?;

event!(
Level::INFO,
event = "added own operator's partial signature",
%txid,
);

// if the flag is true, then the PSBT is fully signed by all required operators
if flag {
event!(
Level::INFO,
event = "deposit transaction fully signed",
%txid,
);
}

Ok(txid)
} else {
Err(DepositExecError::Signing(
"Could not get wallet's xpriv".to_string(),
))
}
}

/// Add the signature to the already accumulated set of signatures for a deposit transaction and
Expand Down
21 changes: 18 additions & 3 deletions crates/bridge-exec/src/withdrawal_handler/errors.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
//! Defines the error types associated with executing the withdrawal duties.

use alpen_express_btcio::rpc::error::ClientError as L1ClientError;
use express_bridge_tx_builder::errors::BridgeTxBuilderError;
use jsonrpsee::core::ClientError as L2ClientError;
use thiserror::Error;

/// Error during execution of the withdrawal duty.
#[derive(Error, Debug)]
pub enum WithdrawalExecError {
/// Error creating the [`TxSigningData`](alpen_express_primitives::bridge::TxSigningData).
#[error("could not build withdraw transaction")]
TxBuilder(#[from] BridgeTxBuilderError),

/// Error while signing the withdrawal transaction.
#[error("signing error: {0}")]
Signing(String),

/// Error produced if the withdrawal request is invalid.
#[error("invalid request")]
InvalidRequest,
/// The request for signature is invalid.
#[error("invalid request: {0}")]
InvalidRequest(String),

/// Error while broadcasting the signature/transaction.
#[error("transaction broadcast error: {0}")]
Expand All @@ -24,6 +31,14 @@ pub enum WithdrawalExecError {
/// Unexpected error during the handling of the withdrawal.
#[error("execution failed: {0}")]
Execution(String),

/// Error communicating with the Bitcoin RPC.
#[error("bitcoin RPC communication failed: {0}")]
L1Rpc(#[from] L1ClientError),

/// Error communicating with the rollup RPC.
#[error("rollup RPC communication failed: {0}")]
L2Rpc(#[from] L2ClientError),
}

/// Result of a withdrawal execution that may produce an [`WithdrawalExecError`].
Expand Down
Loading

0 comments on commit e71b23f

Please sign in to comment.