Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make deposit and withdrawal at Citrea #570

Merged
merged 26 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0327ee8
test: Add common citrea code.
ceyhunsen Feb 26, 2025
1741652
tests: Don't spawn new regtest in create_actors.
ceyhunsen Feb 26, 2025
239f4fa
tests: Add regtest spawner to 2 tests.
ceyhunsen Feb 26, 2025
81ea160
tests(deposit): Add asserts to deposittocitrea test.
ceyhunsen Feb 26, 2025
6966e83
test: Add initial deposit test.
ceyhunsen Feb 26, 2025
4fe5a6c
tests(withdraw): Make 2 deposits.
ceyhunsen Feb 26, 2025
aeab4cf
test(withdraw): Create withdrawal tx for evm.
ceyhunsen Feb 27, 2025
ca2e786
test(withdraw): Refactor bits.
ceyhunsen Feb 27, 2025
143ede6
chore(test/citrea): Move citrea_url config change to update_config_wi…
ceyhunsen Feb 27, 2025
5022dd2
test(withdraw): Add/improve withdraw related calls.
ceyhunsen Feb 28, 2025
783929d
Some improvments (#574)
ekrembal Mar 1, 2025
49a8b76
test(utils): Revert changes in generate_withdrawal_transaction_and_si…
ceyhunsen Mar 1, 2025
da79cc6
tests(citrea): Define more constants.
ceyhunsen Mar 2, 2025
7bc8bed
tests(citrea): Reuse sol types defined in json.
ceyhunsen Mar 2, 2025
a6e0b62
fix(test/utils): Remove erpc creation from create_actors.
ceyhunsen Mar 3, 2025
119a90e
fix(test_utils): Use process cleanup to not kill actor servers.
ceyhunsen Mar 3, 2025
4168fb2
test(utils): Add ServerHandles struct.
ceyhunsen Mar 3, 2025
a6709bb
feat: Add initial Citrea module.
ceyhunsen Mar 3, 2025
f2b2289
feat(citrea): Add new CitreaContractClient type.
ceyhunsen Mar 3, 2025
dfd04ea
fix(citrea): Make secret_key optional for CitreaContractClient.
ceyhunsen Mar 3, 2025
60e69fe
chore: Comment fixes.
ceyhunsen Mar 3, 2025
f910f35
chore: Fix typo in bitcoin_merkle module name.
ceyhunsen Mar 3, 2025
7e6246f
chore: Add comments to Citrea tests.
ceyhunsen Mar 3, 2025
4123d5b
Merge remote-tracking branch 'origin/dev' into ceyhun/citrea_withdrawal
ceyhunsen Mar 3, 2025
12f576e
chore: Fix compilation errors caused by merge.
ceyhunsen Mar 3, 2025
6ee47fe
chore: Don't run coverage test.
ceyhunsen Mar 4, 2025
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
2,620 changes: 2,396 additions & 224 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ serde_json = "1.0.128"
thiserror = "1.0.64"
tracing = { version = "0.1.40", default-features = false }
tracing-subscriber = { version = "0.3.18", features = ["json"] }
jsonrpsee = { version = "0.22.5", default-features = false }
jsonrpsee = { version = "0.24.2", default-features = false }
async-trait = "0.1.83"
clap = "4.5.20"
toml = "0.8.19"
sqlx = { version = "0.7.4", default-features = false }
serial_test = "3.2.0"
tempfile = "3.16.0"
eyre = { version = "0.6.12" }
citrea-e2e = { git = "https://github.com/chainwayxyz/citrea-e2e", rev = "078ea25" }
alloy = { version = "0.11.1", features = ["full"] }

# bitcoin
bitcoin = { version = "0.32.5", features = ["serde"] }
Expand Down
5 changes: 2 additions & 3 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,12 @@ http = { workspace = true }
hyper = { workspace = true }
tower = { workspace = true }
hyper-util = { workspace = true }
alloy = { workspace = true }

[dev-dependencies]
serial_test = { workspace = true }
bitcoin-script = { workspace = true }

[features]
default = []
citrea-e2e = { workspace = true }

[[bin]]
name = "server"
Expand Down
1 change: 1 addition & 0 deletions core/src/Bridge.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions core/src/builder/transaction/creator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,10 +576,11 @@ mod tests {

#[tokio::test(flavor = "multi_thread")]
async fn test_deposit_and_sign_txs() {
let config = create_test_config_with_thread_name(None).await;
let mut config = create_test_config_with_thread_name(None).await;
let _regtest = create_regtest_rpc(&mut config).await;

let paramset = config.protocol_paramset();
let (mut verifiers, mut operators, mut aggregator, mut watchtowers, _regtest) =
let (mut verifiers, mut operators, mut aggregator, mut watchtowers, _cleanup) =
create_actors(&config).await;

tracing::info!("Setting up aggregator");
Expand Down
125 changes: 125 additions & 0 deletions core/src/citrea.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! # Citrea Related Utilities

use crate::errors::BridgeError;
use alloy::{
network::EthereumWallet,
primitives::U256,
providers::{
fillers::{
BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller,
WalletFiller,
},
ProviderBuilder, RootProvider,
},
signers::{local::PrivateKeySigner, Signer},
sol,
transports::http::reqwest::Url,
};
use bitcoin::{hashes::Hash, OutPoint, Txid};

pub const CITREA_CHAIN_ID: u64 = 5655;
pub const LIGHT_CLIENT_ADDRESS: &str = "0x3100000000000000000000000000000000000001";
pub const BRIDGE_CONTRACT_ADDRESS: &str = "0x3100000000000000000000000000000000000002";
pub const SATS_TO_WEI_MULTIPLIER: u64 = 10_000_000_000;

// Codegen from ABI file to interact with the contract.
sol!(
#[allow(missing_docs)]
#[sol(rpc)]
BRIDGE_CONTRACT,
"src/Bridge.json"
);

// Ugly typedefs.
type Contract = BRIDGE_CONTRACT::BRIDGE_CONTRACTInstance<
(),
FillProvider<
JoinFill<
JoinFill<
alloy::providers::Identity,
JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
>,
WalletFiller<EthereumWallet>,
>,
RootProvider,
>,
>;
type Provider = FillProvider<
JoinFill<
JoinFill<
alloy::providers::Identity,
JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
>,
WalletFiller<EthereumWallet>,
>,
RootProvider,
>;

/// Citrea contract client is responsible for creating contracts and interacting
/// with the EVM.
#[derive(Clone, Debug)]
pub struct CitreaContractClient {
pub wallet_address: alloy::primitives::Address,
pub provider: Provider,
pub contract: Contract,
}

impl CitreaContractClient {
/// # Parameters
///
/// - `citrea_rpc_url`: URL of the Citrea RPC.
/// - `secret_key`: Etherium secret key of the EVM user. If not give, dummy
/// secret key is used (wallet is not required).
pub fn new(citrea_rpc_url: Url, secret_key: Option<String>) -> Result<Self, BridgeError> {
let secret_key = secret_key.unwrap_or(["01"; 32].concat());

let key = secret_key
.parse::<PrivateKeySigner>()
.map_err(|e| BridgeError::Error(format!("Can't parse secret key: {:?}", e)))?
.with_chain_id(Some(CITREA_CHAIN_ID));
let wallet_address = key.address();

let provider = ProviderBuilder::new()
.wallet(EthereumWallet::from(key))
.on_http(citrea_rpc_url);

let contract = BRIDGE_CONTRACT::new(
BRIDGE_CONTRACT_ADDRESS.parse().map_err(|e| {
BridgeError::Error(format!("Can't create bridge contract address {:?}", e))
})?,
provider.clone(),
);

Ok(CitreaContractClient {
wallet_address,
provider,
contract,
})
}

/// Fetches an UTXO from Citrea for the given withdrawal with the index.
///
/// # Parameters
///
/// - `provider`: Provider to interact with the Ethereum network.
/// - `withdrawal_index`: Index of the withdrawal.
///
/// # Returns
///
/// - [`OutPoint`]: UTXO for the given withdrawal.
pub async fn withdrawal_utxos(&self, withdrawal_index: u64) -> Result<OutPoint, BridgeError> {
let withdrawal_utxo = self
.contract
.withdrawalUTXOs(U256::from(withdrawal_index))
.call()
.await?;

let txid = withdrawal_utxo.txId.0;
let txid = Txid::from_slice(txid.as_slice())?;

let vout = withdrawal_utxo.outputId.0;
let vout = u32::from_be_bytes(vout);

Ok(OutPoint { txid, vout })
}
}
3 changes: 3 additions & 0 deletions core/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@ pub enum BridgeError {
/// 0: Data name, 1: Error message
#[error("Error while sending {0} data: {1}")]
SendError(&'static str, String),

#[error("Error while creating contract: {0}")]
AlloyContract(#[from] alloy::contract::Error),
}

impl From<BridgeError> for ErrorObject<'static> {
Expand Down
1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod actor;
pub mod aggregator;
pub mod bitcoin_syncer;
pub mod builder;
pub mod citrea;
pub mod cli;
pub mod config;
pub mod constants;
Expand Down
35 changes: 19 additions & 16 deletions core/src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::builder::sighash::{create_operator_sighash_stream, PartialSignatureIn
use crate::builder::transaction::deposit_signature_owner::EntityType;
use crate::builder::transaction::{create_round_txhandlers, KickoffWinternitzKeys};
use crate::builder::transaction::{DepositData, OperatorData, TransactionType, TxHandler};
use crate::citrea::CitreaContractClient;
use crate::config::BridgeConfig;
use crate::database::Database;
use crate::errors::BridgeError;
Expand All @@ -11,6 +12,7 @@ use crate::musig2::AggregateFromPublicKeys;
use crate::tx_sender::TxSender;
use crate::utils::SECP;
use crate::{builder, UTXO};
use alloy::transports::http::reqwest::Url;
use bitcoin::consensus::deserialize;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::schnorr::Signature;
Expand Down Expand Up @@ -40,6 +42,7 @@ pub struct Operator {
pub(crate) reimburse_addr: Address,
pub tx_sender: TxSender,
pub citrea_client: Option<jsonrpsee::http_client::HttpClient>,
pub citrea_contract_client: Option<CitreaContractClient>,
}

impl Operator {
Expand Down Expand Up @@ -107,6 +110,16 @@ impl Operator {
}
};

let citrea_contract_client = if !config.citrea_rpc_url.is_empty() {
Some(CitreaContractClient::new(
Url::parse(&config.citrea_rpc_url).map_err(|e| {
BridgeError::Error(format!("Can't parse Citrea RPC URL: {:?}", e))
})?,
None,
)?)
} else {
None
};
let citrea_client = if !config.citrea_rpc_url.is_empty() {
Some(HttpClientBuilder::default().build(config.citrea_rpc_url.clone())?)
} else {
Expand All @@ -129,6 +142,7 @@ impl Operator {
collateral_funding_outpoint,
tx_sender,
citrea_client,
citrea_contract_client,
reimburse_addr,
})
}
Expand Down Expand Up @@ -508,23 +522,12 @@ impl Operator {
};

// Check Citrea for the withdrawal state.
if let Some(citrea_client) = &self.citrea_client {
// See: https://gist.github.com/okkothejawa/a9379b02a16dada07a2b85cbbd3c1e80
let params = rpc_params![
json!({
"to": "0x3100000000000000000000000000000000000002",
"data": format!("0x471ba1e300000000000000000000000000000000000000000000000000000000{}",
hex::encode(withdrawal_index.to_be_bytes())),
}),
"latest"
];
let response: String = citrea_client.request("eth_call", params).await?;

let txid_response = &response[2..66];
let txid = hex::decode(txid_response).map_err(|e| BridgeError::Error(e.to_string()))?;
// txid.reverse(); // TODO: we should need to reverse this, test this with declareWithdrawalFiller
if let Some(citrea_contract_client) = &self.citrea_contract_client {
let txid = citrea_contract_client
.withdrawal_utxos(withdrawal_index.into())
.await?
.txid;

let txid = Txid::from_slice(&txid)?;
if txid != input_utxo.outpoint.txid || 0 != input_utxo.outpoint.vout {
// TODO: Fix this, vout can be different from 0 as well
return Err(BridgeError::InvalidInputUTXO(
Expand Down
15 changes: 8 additions & 7 deletions core/src/rpc/aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,6 @@ impl ClementineAggregator for Aggregator {
mod tests {
use crate::actor::Actor;
use crate::musig2::AggregateFromPublicKeys;

use crate::rpc::clementine::{self};
use crate::{builder, EVMAddress};
use crate::{rpc::clementine::DepositParams, test::common::*};
Expand All @@ -870,9 +869,10 @@ mod tests {

#[tokio::test]
async fn aggregator_double_setup_fail() {
let config = create_test_config_with_thread_name(None).await;
let mut config = create_test_config_with_thread_name(None).await;
let _regtest = create_regtest_rpc(&mut config).await;

let (_, _, mut aggregator, _, _regtest) = create_actors(&config).await;
let (_, _, mut aggregator, _, _cleanup) = create_actors(&config).await;

aggregator
.setup(tonic::Request::new(clementine::Empty {}))
Expand All @@ -890,7 +890,7 @@ mod tests {
async fn aggregator_setup_and_deposit() {
let config = create_test_config_with_thread_name(None).await;

let (_, _, mut aggregator, _, _regtest) = create_actors(&config).await;
let (_, _, mut aggregator, _, _cleanup) = create_actors(&config).await;

tracing::info!("Setting up aggregator");
let start = std::time::Instant::now();
Expand Down Expand Up @@ -926,10 +926,11 @@ mod tests {

#[tokio::test(flavor = "multi_thread")]
async fn aggregator_deposit_movetx_lands_onchain() {
let config = create_test_config_with_thread_name(None).await;
let (_verifiers, _operators, mut aggregator, _watchtowers, regtest) =
create_actors(&config).await;
let mut config = create_test_config_with_thread_name(None).await;
let regtest = create_regtest_rpc(&mut config).await;
let rpc = regtest.rpc();
let (_verifiers, _operators, mut aggregator, _watchtowers, _cleanup) =
create_actors(&config).await;

let evm_address = EVMAddress([1u8; 20]);
let signer = Actor::new(
Expand Down
4 changes: 2 additions & 2 deletions core/src/rpc/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ impl ClementineVerifier for Verifier {
req: Request<Streaming<VerifierDepositFinalizeParams>>,
) -> Result<Response<PartialSig>, Status> {
let mut in_stream = req.into_inner();
tracing::debug!("In verifier {} deposit_finalize()", self.idx);
tracing::trace!("In verifier {} deposit_finalize()", self.idx);

let (sig_tx, sig_rx) = mpsc::channel(1280);
let (agg_nonce_tx, agg_nonce_rx) = mpsc::channel(1);
Expand All @@ -263,7 +263,7 @@ impl ClementineVerifier for Verifier {
}
_ => Err(Status::internal("Expected DepositOutpoint"))?,
};
tracing::debug!(
tracing::trace!(
"verifier {} got DepositSignFirstParam in deposit_finalize()",
self.idx
);
Expand Down
7 changes: 0 additions & 7 deletions core/src/servers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,6 @@ pub async fn create_verifier_grpc_server(
pub async fn create_operator_grpc_server(
config: BridgeConfig,
) -> Result<(std::net::SocketAddr, oneshot::Sender<()>), BridgeError> {
let _rpc = ExtendedRpc::connect(
config.bitcoin_rpc_url.clone(),
config.bitcoin_rpc_user.clone(),
config.bitcoin_rpc_password.clone(),
)
.await?;

tracing::info!(
"config host and port are: {} and {}",
config.host,
Expand Down
Loading
Loading