From b9be24eb370a302cf33c87f8cc208871fcfbf4fc Mon Sep 17 00:00:00 2001 From: ozankaymak Date: Thu, 22 Aug 2024 17:01:59 +0300 Subject: [PATCH] WIP Implement endpoints with db operations and tests --- core/src/database/common.rs | 63 +++++++++++++++++++++++++++++++++++++ core/src/errors.rs | 18 +++++++++++ core/src/lib.rs | 9 ++++++ core/src/operator.rs | 17 ++++++++++ core/src/traits/rpc.rs | 11 ++++--- core/src/verifier.rs | 16 +++++++++- scripts/schema.sql | 7 +++++ 7 files changed, 135 insertions(+), 6 deletions(-) diff --git a/core/src/database/common.rs b/core/src/database/common.rs index e26fc0505..c2ac707e9 100644 --- a/core/src/database/common.rs +++ b/core/src/database/common.rs @@ -442,6 +442,39 @@ impl Database { None => Ok(None), } } + + pub async fn save_kickoff_root( + &self, + deposit_outpoint: OutPoint, + kickoff_root: [u8; 32], + ) -> Result<(), BridgeError> { + sqlx::query( + "INSERT INTO kickoff_roots (deposit_outpoint, kickoff_merkle_root) VALUES ($1, $2);", + ) + .bind(OutPointDB(deposit_outpoint)) + .bind(hex::encode(kickoff_root)) + .execute(&self.connection) + .await?; + + Ok(()) + } + + pub async fn get_kickoff_root( + &self, + deposit_outpoint: OutPoint, + ) -> Result, BridgeError> { + let qr: Option = sqlx::query_scalar( + "SELECT kickoff_merkle_root FROM kickoff_roots WHERE deposit_outpoint = $1;", + ) + .bind(OutPointDB(deposit_outpoint)) + .fetch_optional(&self.connection) + .await?; + + match qr { + Some(root) => Ok(Some(hex::decode(root)?.try_into()?)), + None => Ok(None), + } + } } #[cfg(test)] @@ -868,6 +901,36 @@ mod tests { let res = db.get_deposit_kickoff_generator_tx(txid).await.unwrap(); assert!(res.is_none()); } + + #[tokio::test] + async fn test_kickoff_root_1() { + let config = create_test_config_with_thread_name!("test_config.toml"); + let db = Database::new(config).await.unwrap(); + + let outpoint = OutPoint { + txid: Txid::from_byte_array([1u8; 32]), + vout: 1, + }; + let root = [1u8; 32]; + db.save_kickoff_root(outpoint, root).await.unwrap(); + let db_root = db.get_kickoff_root(outpoint).await.unwrap().unwrap(); + + // Sanity check + assert_eq!(db_root, root); + } + + #[tokio::test] + async fn test_kickoff_root_2() { + let config = create_test_config_with_thread_name!("test_config.toml"); + let db = Database::new(config).await.unwrap(); + + let outpoint = OutPoint { + txid: Txid::from_byte_array([1u8; 32]), + vout: 1, + }; + let res = db.get_kickoff_root(outpoint).await.unwrap(); + assert!(res.is_none()); + } } #[cfg(poc)] diff --git a/core/src/errors.rs b/core/src/errors.rs index def8880a9..cdd057fad 100644 --- a/core/src/errors.rs +++ b/core/src/errors.rs @@ -158,6 +158,12 @@ pub enum BridgeError { KickoffOutpointsNotFound, #[error("DepositInfoNotFound")] DepositInfoNotFound, + + #[error("FromHexError: {0}")] + FromHexError(hex::FromHexError), + + #[error("FromSliceError: {0}")] + FromSliceError(bitcoin::hashes::FromSliceError), } impl Into> for BridgeError { @@ -261,3 +267,15 @@ impl From for BridgeError { BridgeError::MuSig2VerifyError(err) } } + +impl From for BridgeError { + fn from(err: hex::FromHexError) -> Self { + BridgeError::FromHexError(err) + } +} + +impl From for BridgeError { + fn from(err: bitcoin::hashes::FromSliceError) -> Self { + BridgeError::FromSliceError(err) + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 92c6fc0a0..711c7e9f5 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -44,6 +44,15 @@ pub struct UTXO { pub txout: bitcoin::TxOut, } +impl UTXO { + fn to_vec(&self) -> Vec { + let outpoint_hex = bitcoin::consensus::encode::serialize_hex(&self.outpoint); + let txout_hex = bitcoin::consensus::encode::serialize_hex(&self.txout); + let all = format!("{}{}", outpoint_hex, txout_hex); + hex::decode(all).unwrap() + } +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, sqlx::Type)] #[sqlx(type_name = "bytea")] pub struct ByteArray66(#[serde(with = "hex::serde")] pub [u8; 66]); diff --git a/core/src/operator.rs b/core/src/operator.rs index 6de4795dc..aecb8511f 100644 --- a/core/src/operator.rs +++ b/core/src/operator.rs @@ -272,6 +272,14 @@ where let final_txid = self.rpc.send_raw_transaction(&signed_tx)?; Ok(Some(final_txid)) } + + async fn withdrawal_proved_on_citrea( + &self, + withdrawal_idx: usize, + kickoff_merkle_root: [u8; 32], + ) -> Result<(), BridgeError> { + Ok(()) + } } #[async_trait] @@ -303,4 +311,13 @@ where self.new_withdrawal_sig(withdrawal_idx, user_sig, input_utxo, output_txout) .await } + + async fn withdrawal_proved_on_citrea_rpc( + &self, + withdrawal_idx: usize, + kickoff_merkle_root: [u8; 32], + ) -> Result<(), BridgeError> { + self.withdrawal_proved_on_citrea(withdrawal_idx, kickoff_merkle_root) + .await + } } diff --git a/core/src/traits/rpc.rs b/core/src/traits/rpc.rs index 9eebee8c2..4ea92651e 100644 --- a/core/src/traits/rpc.rs +++ b/core/src/traits/rpc.rs @@ -80,11 +80,12 @@ pub trait OperatorRpc { output_txout: TxOut, ) -> Result, BridgeError>; - // #[method(name = "withdrawal_proved_on_citrea")] - // async fn withdrawal_proved_on_citrea_rpc( - // &self, - // withdrawal_idx: usize, - // ) -> Result<(), BridgeError>; + #[method(name = "withdrawal_proved_on_citrea")] + async fn withdrawal_proved_on_citrea_rpc( + &self, + withdrawal_idx: usize, + kickoff_merkle_root: [u8; 32], + ) -> Result<(), BridgeError>; // #[method(name = "operator_take_sendable")] // async fn operator_take_sendable_rpc(&self, withdrawal_idx: usize) -> Result<(), BridgeError>; diff --git a/core/src/verifier.rs b/core/src/verifier.rs index 0daecd1f3..089d9d2d3 100644 --- a/core/src/verifier.rs +++ b/core/src/verifier.rs @@ -3,6 +3,7 @@ use crate::config::BridgeConfig; use crate::database::verifier::VerifierDB; use crate::errors::BridgeError; use crate::extended_rpc::ExtendedRpc; +use crate::merkle::MerkleTree; use crate::musig2::{self, MuSigAggNonce, MuSigPartialSignature, MuSigPubNonce}; use crate::traits::rpc::VerifierRpcServer; use crate::transaction_builder::{TransactionBuilder, TxHandler}; @@ -10,10 +11,11 @@ use crate::{utils, EVMAddress, UTXO}; use ::musig2::secp::Point; use bitcoin::address::NetworkUnchecked; use bitcoin::hashes::Hash; -use bitcoin::Address; +use bitcoin::{merkle_tree, Address}; use bitcoin::{secp256k1, OutPoint}; use bitcoin_mock_rpc::RpcApiWrapper; use clementine_circuits::constants::BRIDGE_AMOUNT_SATS; +use clementine_circuits::incremental_merkle::IncrementalMerkleTree; use clementine_circuits::sha256_hash; use jsonrpsee::core::async_trait; use secp256k1::{rand, schnorr}; @@ -153,6 +155,14 @@ where )?; } + let root: bitcoin::hashes::sha256::Hash = bitcoin::merkle_tree::calculate_root( + kickoff_utxos + .iter() + .map(|utxo| Hash::from_byte_array(sha256_hash!(utxo.to_vec().as_slice()))), + ) + .unwrap(); + let root_bytes: [u8; 32] = *root.as_byte_array(); + self.db .save_agg_nonces(deposit_outpoint, &agg_nonces) .await?; @@ -161,6 +171,10 @@ where .save_kickoff_utxos(deposit_outpoint, &kickoff_utxos) .await?; + self.db + .save_kickoff_root(deposit_outpoint, root_bytes) + .await?; + // TODO: Sign burn txs Ok(vec![]) } diff --git a/scripts/schema.sql b/scripts/schema.sql index 426d864eb..2c070609b 100644 --- a/scripts/schema.sql +++ b/scripts/schema.sql @@ -90,4 +90,11 @@ create table funding_utxos ( created_at timestamp not null default now() ); +-- Verifier table for kickoff merkle roots for deposits +create table kickoff_roots ( + deposit_outpoint text primary key not null check (deposit_outpoint ~ '^[a-fA-F0-9]{64}:(0|[1-9][0-9]{0,9})$'), + kickoff_merkle_root text not null check (kickoff_merkle_root ~ '^[a-fA-F0-9]{64}'), + created_at timestamp not null default now() +); + COMMIT;