From 23ed6e5f2b80a68732fb344525cb4f381e93ca93 Mon Sep 17 00:00:00 2001 From: steph-mipt Date: Tue, 6 Aug 2024 21:49:57 +0600 Subject: [PATCH 01/12] feat: basic lib indexer structure [ci skip] --- core/lib/via_btc_client/src/indexer/mod.rs | 42 ++++++++++++++++++++++ core/lib/via_btc_client/src/traits.rs | 18 ++++++---- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/core/lib/via_btc_client/src/indexer/mod.rs b/core/lib/via_btc_client/src/indexer/mod.rs index 8b137891791f..c3e55a0c145d 100644 --- a/core/lib/via_btc_client/src/indexer/mod.rs +++ b/core/lib/via_btc_client/src/indexer/mod.rs @@ -1 +1,43 @@ +use async_trait::async_trait; +use bitcoin::{BlockHash, Network}; +use crate::{ + traits::{BitcoinIndexerOpt, BitcoinRpc}, + types::{BitcoinClientResult, BitcoinInscriptionIndexerResult}, +}; + +#[allow(unused)] +pub struct BitcoinInscriptionIndexer { + pub rpc: Box, + network: Network, +} + +#[async_trait] +impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { + async fn new() -> BitcoinClientResult + where + Self: Sized, + { + todo!() + } + + async fn process_blocks( + &self, + starting_block: u128, + ending_block: u128, + ) -> BitcoinInscriptionIndexerResult> { + todo!() + } + + async fn process_block(&self, block: u128) -> BitcoinInscriptionIndexerResult> { + todo!() + } + + async fn are_blocks_connected( + &self, + parent_hash: &BlockHash, + child_hash: &BlockHash, + ) -> BitcoinInscriptionIndexerResult { + todo!() + } +} diff --git a/core/lib/via_btc_client/src/traits.rs b/core/lib/via_btc_client/src/traits.rs index 10f4046bf874..4ae998465d9d 100644 --- a/core/lib/via_btc_client/src/traits.rs +++ b/core/lib/via_btc_client/src/traits.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use bitcoin::{Address, Block, OutPoint, Transaction, TxOut, Txid}; +use bitcoin::{Address, Block, BlockHash, OutPoint, Transaction, TxOut, Txid}; use crate::types; @@ -90,19 +90,23 @@ pub trait BitcoinInscriber: Send + Sync { #[allow(dead_code)] #[async_trait] -pub trait BitcoinInscriptionIndexer: Send + Sync { - async fn new(config: &str) -> types::BitcoinInscriptionIndexerResult +pub trait BitcoinIndexerOpt: Send + Sync { + async fn new() -> types::BitcoinInscriptionIndexerResult where Self: Sized; - async fn get_inscription_messages( + async fn process_blocks( &self, starting_block: u128, ending_block: u128, ) -> types::BitcoinInscriptionIndexerResult>; - async fn get_specific_block_inscription_messages( + async fn process_block(&self, block: u128) + -> types::BitcoinInscriptionIndexerResult>; + + async fn are_blocks_connected( &self, - block_height: u128, - ) -> types::BitcoinInscriptionIndexerResult>; + parent_hash: &BlockHash, + child_hash: &BlockHash, + ) -> types::BitcoinInscriptionIndexerResult; } #[allow(dead_code)] From a8f3c454637d91ce7c6da5707240ea08d719866b Mon Sep 17 00:00:00 2001 From: steph-mipt Date: Thu, 8 Aug 2024 20:15:56 +0600 Subject: [PATCH 02/12] feat: indexer implementation, a few more fixes --- core/lib/via_btc_client/Cargo.toml | 4 +- core/lib/via_btc_client/src/client/mod.rs | 22 ++--- .../via_btc_client/src/client/rpc_client.rs | 18 ++-- core/lib/via_btc_client/src/indexer/mod.rs | 68 +++++++++++--- core/lib/via_btc_client/src/traits.rs | 89 +++++++------------ core/lib/via_btc_client/src/types.rs | 21 +---- 6 files changed, 111 insertions(+), 111 deletions(-) diff --git a/core/lib/via_btc_client/Cargo.toml b/core/lib/via_btc_client/Cargo.toml index 7afe2784bf5e..20c2a88f3179 100644 --- a/core/lib/via_btc_client/Cargo.toml +++ b/core/lib/via_btc_client/Cargo.toml @@ -26,6 +26,7 @@ bitcoincore-rpc = "0.19.0" rand.workspace = true hex.workspace = true secp256k1 = "0.29.0" +serde.workspace = true reqwest = "0.12.5" serde_json.workspace = true @@ -33,14 +34,13 @@ serde_json.workspace = true inquire = "0.7.5" tempfile = { workspace = true, optional = true } anyhow = { workspace = true, optional = true } -serde = { workspace = true, optional = true } [dev-dependencies] anyhow.workspace = true mockall = "0.13.0" [features] -regtest = ["tempfile", "anyhow", "serde"] +regtest = ["tempfile", "anyhow"] [[example]] name = "bitcoin_client_example" diff --git a/core/lib/via_btc_client/src/client/mod.rs b/core/lib/via_btc_client/src/client/mod.rs index 044fdf3a3eb3..ac1bf70e02a2 100644 --- a/core/lib/via_btc_client/src/client/mod.rs +++ b/core/lib/via_btc_client/src/client/mod.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use bitcoin::{Address, Network, TxOut, Txid}; +use bitcoin::{Address, Block, TxOut, Txid}; use bitcoincore_rpc::json::EstimateMode; use crate::{ @@ -8,8 +8,8 @@ use crate::{ }; mod rpc_client; -#[allow(unused)] -pub use rpc_client::BitcoinRpcClient; +pub use bitcoin::Network; +pub use rpc_client::{Auth, BitcoinRpcClient}; use crate::types::BitcoinError; @@ -21,18 +21,11 @@ pub struct BitcoinClient { #[async_trait] impl BitcoinOps for BitcoinClient { - async fn new(rpc_url: &str, network: &str) -> BitcoinClientResult + async fn new(rpc_url: &str, network: Network, auth: Auth) -> BitcoinClientResult where Self: Sized, { - // TODO: change rpc_user & rpc_password here, move it to args - let rpc = Box::new(BitcoinRpcClient::new(rpc_url, "rpcuser", "rpcpassword")?); - let network = match network.to_lowercase().as_str() { - "mainnet" => Network::Bitcoin, - "testnet" => Network::Testnet, - "regtest" => Network::Regtest, - _ => return Err(BitcoinError::InvalidNetwork(network.to_string())), - }; + let rpc = Box::new(BitcoinRpcClient::new(rpc_url, auth)?); Ok(Self { rpc, network }) } @@ -80,9 +73,8 @@ impl BitcoinOps for BitcoinClient { Ok(height as u128) } - async fn fetch_and_parse_block(&self, block_height: u128) -> BitcoinClientResult { - let _block = self.rpc.get_block(block_height).await?; - todo!() + async fn fetch_block(&self, block_height: u128) -> BitcoinClientResult { + self.rpc.get_block_by_height(block_height).await } async fn get_fee_rate(&self, conf_target: u16) -> BitcoinClientResult { diff --git a/core/lib/via_btc_client/src/client/rpc_client.rs b/core/lib/via_btc_client/src/client/rpc_client.rs index 7bea94dec93e..e47c6538d508 100644 --- a/core/lib/via_btc_client/src/client/rpc_client.rs +++ b/core/lib/via_btc_client/src/client/rpc_client.rs @@ -1,9 +1,10 @@ use async_trait::async_trait; -use bitcoin::{Address, Block, OutPoint, Transaction, Txid}; +use bitcoin::{Address, Block, BlockHash, OutPoint, Transaction, Txid}; +pub use bitcoincore_rpc::Auth; use bitcoincore_rpc::{ bitcoincore_rpc_json::EstimateMode, json::{EstimateSmartFeeResult, ScanTxOutRequest}, - Auth, Client, RpcApi, + Client, RpcApi, }; use crate::{traits::BitcoinRpc, types::BitcoinRpcResult}; @@ -14,12 +15,7 @@ pub struct BitcoinRpcClient { #[allow(unused)] impl BitcoinRpcClient { - pub fn new( - url: &str, - rpc_user: &str, - rpc_password: &str, - ) -> Result { - let auth = Auth::UserPass(rpc_user.to_string(), rpc_password.to_string()); + pub fn new(url: &str, auth: Auth) -> Result { let client = Client::new(url, auth)?; Ok(Self { client }) } @@ -71,11 +67,15 @@ impl BitcoinRpc for BitcoinRpcClient { self.client.get_block_count().map_err(|e| e.into()) } - async fn get_block(&self, block_height: u128) -> BitcoinRpcResult { + async fn get_block_by_height(&self, block_height: u128) -> BitcoinRpcResult { let block_hash = self.client.get_block_hash(block_height as u64)?; self.client.get_block(&block_hash).map_err(|e| e.into()) } + async fn get_block_by_hash(&self, block_hash: &BlockHash) -> BitcoinRpcResult { + self.client.get_block(block_hash).map_err(|e| e.into()) + } + async fn get_best_block_hash(&self) -> BitcoinRpcResult { self.client.get_best_block_hash().map_err(|e| e.into()) } diff --git a/core/lib/via_btc_client/src/indexer/mod.rs b/core/lib/via_btc_client/src/indexer/mod.rs index c3e55a0c145d..3f88ac44fbbb 100644 --- a/core/lib/via_btc_client/src/indexer/mod.rs +++ b/core/lib/via_btc_client/src/indexer/mod.rs @@ -1,20 +1,19 @@ use async_trait::async_trait; -use bitcoin::{BlockHash, Network}; +use bitcoin::{script::Instruction, BlockHash, Transaction}; use crate::{ - traits::{BitcoinIndexerOpt, BitcoinRpc}, - types::{BitcoinClientResult, BitcoinInscriptionIndexerResult}, + traits::BitcoinIndexerOpt, + types::{BitcoinIndexerResult, BitcoinMessage}, + BitcoinOps, }; -#[allow(unused)] pub struct BitcoinInscriptionIndexer { - pub rpc: Box, - network: Network, + client: Box, } #[async_trait] impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { - async fn new() -> BitcoinClientResult + async fn new() -> Self where Self: Sized, { @@ -25,19 +24,62 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { &self, starting_block: u128, ending_block: u128, - ) -> BitcoinInscriptionIndexerResult> { - todo!() + ) -> BitcoinIndexerResult> { + let mut res = Vec::with_capacity((ending_block - starting_block + 1) as usize); + for block in starting_block..=ending_block { + res.extend(self.process_block(block).await?); + } + Ok(res) } - async fn process_block(&self, block: u128) -> BitcoinInscriptionIndexerResult> { - todo!() + async fn process_block(&self, block: u128) -> BitcoinIndexerResult> { + let block = self + .client + .get_rpc_client() + .get_block_by_height(block) + .await?; + let res: Vec<_> = block + .txdata + .iter() + .filter_map(|tx| self.process_tx(tx)) + .flatten() + .collect(); + Ok(res) } async fn are_blocks_connected( &self, parent_hash: &BlockHash, child_hash: &BlockHash, - ) -> BitcoinInscriptionIndexerResult { - todo!() + ) -> BitcoinIndexerResult { + let child_block = self + .client + .get_rpc_client() + .get_block_by_hash(child_hash) + .await?; + Ok(child_block.header.prev_blockhash == *parent_hash) } } + +impl BitcoinInscriptionIndexer { + fn process_tx(&self, tx: &Transaction) -> Option> { + // only first? + let witness = tx.input.get(0)?.witness.to_vec(); + let script = bitcoin::Script::from_bytes(witness.last()?); + let instructions: Vec<_> = script.instructions().filter_map(Result::ok).collect(); + + let via_index = match is_via_inscription_protocol(&instructions) { + Some(pos) => pos, + None => return None, + }; + // TODO: collect and parse + let _msg_type = instructions.get(via_index + 1); + None + } +} + +fn is_via_inscription_protocol(instructions: &[Instruction]) -> Option { + instructions.iter().position(|instr| { + matches!(instr, Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('via_inscription_protocol')") + }) +} diff --git a/core/lib/via_btc_client/src/traits.rs b/core/lib/via_btc_client/src/traits.rs index 4ae998465d9d..c3bda5f388ab 100644 --- a/core/lib/via_btc_client/src/traits.rs +++ b/core/lib/via_btc_client/src/traits.rs @@ -1,62 +1,59 @@ use async_trait::async_trait; -use bitcoin::{Address, Block, BlockHash, OutPoint, Transaction, TxOut, Txid}; +use bitcoin::{Address, Block, BlockHash, Network, OutPoint, Transaction, TxOut, Txid}; +use bitcoincore_rpc::Auth; -use crate::types; +use crate::types::{ + BitcoinClientResult, BitcoinIndexerResult, BitcoinInscriberResult, BitcoinMessage, + BitcoinRpcResult, BitcoinSignerResult, +}; #[allow(dead_code)] #[async_trait] pub trait BitcoinOps: Send + Sync { - async fn new(rpc_url: &str, network: &str) -> types::BitcoinClientResult + async fn new(rpc_url: &str, network: Network, auth: Auth) -> BitcoinClientResult where Self: Sized; - async fn get_balance(&self, address: &Address) -> types::BitcoinClientResult; + async fn get_balance(&self, address: &Address) -> BitcoinClientResult; async fn broadcast_signed_transaction( &self, // TODO: change type here signed_transaction: &str, - ) -> types::BitcoinClientResult; - async fn fetch_utxos( - &self, - address: &Address, - ) -> types::BitcoinClientResult>; - async fn check_tx_confirmation( - &self, - txid: &Txid, - conf_num: u32, - ) -> types::BitcoinClientResult; - async fn fetch_block_height(&self) -> types::BitcoinClientResult; - async fn fetch_and_parse_block(&self, block_height: u128) - -> types::BitcoinClientResult; - async fn get_fee_rate(&self, conf_target: u16) -> types::BitcoinClientResult; + ) -> BitcoinClientResult; + async fn fetch_utxos(&self, address: &Address) -> BitcoinClientResult>; + async fn check_tx_confirmation(&self, txid: &Txid, conf_num: u32) -> BitcoinClientResult; + async fn fetch_block_height(&self) -> BitcoinClientResult; + async fn fetch_block(&self, block_height: u128) -> BitcoinClientResult; + async fn get_fee_rate(&self, conf_target: u16) -> BitcoinClientResult; fn get_rpc_client(&self) -> &dyn BitcoinRpc; } #[allow(dead_code)] #[async_trait] pub trait BitcoinRpc: Send + Sync { - async fn get_balance(&self, address: &Address) -> types::BitcoinRpcResult; - async fn send_raw_transaction(&self, tx_hex: &str) -> types::BitcoinRpcResult; - async fn list_unspent(&self, address: &Address) -> types::BitcoinRpcResult>; - async fn get_transaction(&self, tx_id: &Txid) -> types::BitcoinRpcResult; - async fn get_block_count(&self) -> types::BitcoinRpcResult; - async fn get_block(&self, block_height: u128) -> types::BitcoinRpcResult; - async fn get_best_block_hash(&self) -> types::BitcoinRpcResult; + async fn get_balance(&self, address: &Address) -> BitcoinRpcResult; + async fn send_raw_transaction(&self, tx_hex: &str) -> BitcoinRpcResult; + async fn list_unspent(&self, address: &Address) -> BitcoinRpcResult>; + async fn get_transaction(&self, tx_id: &Txid) -> BitcoinRpcResult; + async fn get_block_count(&self) -> BitcoinRpcResult; + async fn get_block_by_height(&self, block_height: u128) -> BitcoinRpcResult; + + async fn get_block_by_hash(&self, block_hash: &BlockHash) -> BitcoinRpcResult; + async fn get_best_block_hash(&self) -> BitcoinRpcResult; async fn get_raw_transaction_info( &self, txid: &Txid, - // block_hash: Option<&bitcoin::BlockHash>, - ) -> types::BitcoinRpcResult; + ) -> BitcoinRpcResult; async fn estimate_smart_fee( &self, conf_target: u16, estimate_mode: Option, - ) -> types::BitcoinRpcResult; + ) -> BitcoinRpcResult; } #[allow(dead_code)] #[async_trait] pub trait BitcoinSigner<'a>: Send + Sync { - fn new(private_key: &str, rpc_client: &'a dyn BitcoinRpc) -> types::BitcoinSignerResult + fn new(private_key: &str, rpc_client: &'a dyn BitcoinRpc) -> BitcoinSignerResult where Self: Sized; @@ -64,7 +61,7 @@ pub trait BitcoinSigner<'a>: Send + Sync { &self, unsigned_tx: &Transaction, input_index: usize, - ) -> types::BitcoinSignerResult; + ) -> BitcoinSignerResult; async fn sign_reveal( &self, @@ -73,51 +70,33 @@ pub trait BitcoinSigner<'a>: Send + Sync { tapscript: &bitcoin::ScriptBuf, leaf_version: bitcoin::taproot::LeafVersion, control_block: &bitcoin::taproot::ControlBlock, - ) -> types::BitcoinSignerResult; + ) -> BitcoinSignerResult; } #[allow(dead_code)] #[async_trait] pub trait BitcoinInscriber: Send + Sync { - async fn new(config: &str) -> types::BitcoinInscriberResult + async fn new(config: &str) -> BitcoinInscriberResult where Self: Sized; - async fn inscribe( - &self, - message_type: &str, - data: &str, - ) -> types::BitcoinInscriberResult; + async fn inscribe(&self, message_type: &str, data: &str) -> BitcoinInscriberResult; } #[allow(dead_code)] #[async_trait] pub trait BitcoinIndexerOpt: Send + Sync { - async fn new() -> types::BitcoinInscriptionIndexerResult + async fn new() -> Self where Self: Sized; async fn process_blocks( &self, starting_block: u128, ending_block: u128, - ) -> types::BitcoinInscriptionIndexerResult>; - async fn process_block(&self, block: u128) - -> types::BitcoinInscriptionIndexerResult>; + ) -> BitcoinIndexerResult>; + async fn process_block(&self, block: u128) -> BitcoinIndexerResult>; async fn are_blocks_connected( &self, parent_hash: &BlockHash, child_hash: &BlockHash, - ) -> types::BitcoinInscriptionIndexerResult; -} - -#[allow(dead_code)] -#[async_trait] -pub trait BitcoinWithdrawalTransactionBuilder: Send + Sync { - async fn new(config: &str) -> types::BitcoinTransactionBuilderResult - where - Self: Sized; - async fn build_withdrawal_transaction( - &self, - address: &str, - amount: u128, - ) -> types::BitcoinTransactionBuilderResult; + ) -> BitcoinIndexerResult; } diff --git a/core/lib/via_btc_client/src/types.rs b/core/lib/via_btc_client/src/types.rs index ce5d0a2c2b24..de8eeb54dc9f 100644 --- a/core/lib/via_btc_client/src/types.rs +++ b/core/lib/via_btc_client/src/types.rs @@ -1,21 +1,8 @@ -use bitcoin::Network; +use serde::{Deserialize, Serialize}; use thiserror::Error; -pub enum BitcoinNetwork { - Mainnet, - Testnet, - Regtest, -} - -impl From for Network { - fn from(network: BitcoinNetwork) -> Self { - match network { - BitcoinNetwork::Mainnet => Network::Bitcoin, - BitcoinNetwork::Testnet => Network::Testnet, - BitcoinNetwork::Regtest => Network::Regtest, - } - } -} +#[derive(Serialize, Deserialize)] +pub enum BitcoinMessage {} #[allow(unused)] #[derive(Debug, Error)] @@ -82,5 +69,5 @@ impl From for BitcoinError { pub type BitcoinSignerResult = Result; pub type BitcoinInscriberResult = Result; -pub type BitcoinInscriptionIndexerResult = Result; +pub type BitcoinIndexerResult = Result; pub type BitcoinTransactionBuilderResult = Result; From 11f4c79e411c40545c70051a95236ef5f7607d7d Mon Sep 17 00:00:00 2001 From: steph-mipt Date: Mon, 12 Aug 2024 22:21:19 +0200 Subject: [PATCH 03/12] feat: add common types for inscriber and indexer [ci skip] --- Cargo.lock | 2 + core/lib/via_btc_client/Cargo.toml | 2 + core/lib/via_btc_client/src/types.rs | 154 ++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7794d02ab358..9011619fd039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7679,7 +7679,9 @@ dependencies = [ "tempfile", "thiserror", "tokio", + "zksync_basic_types", "zksync_config", + "zksync_da_client", "zksync_types", ] diff --git a/core/lib/via_btc_client/Cargo.toml b/core/lib/via_btc_client/Cargo.toml index 20c2a88f3179..47c018fb9e46 100644 --- a/core/lib/via_btc_client/Cargo.toml +++ b/core/lib/via_btc_client/Cargo.toml @@ -13,6 +13,8 @@ categories.workspace = true [dependencies] zksync_types.workspace = true zksync_config.workspace = true +zksync_da_client.workspace = true +zksync_basic_types.workspace = true thiserror.workspace = true async-trait.workspace = true diff --git a/core/lib/via_btc_client/src/types.rs b/core/lib/via_btc_client/src/types.rs index de8eeb54dc9f..f4ad2ac0a157 100644 --- a/core/lib/via_btc_client/src/types.rs +++ b/core/lib/via_btc_client/src/types.rs @@ -1,10 +1,160 @@ +use std::collections::VecDeque; use serde::{Deserialize, Serialize}; use thiserror::Error; +use bitcoin::script::PushBytesBuf; +use bitcoin::taproot::Signature as TaprootSignature; +use bitcoin::{Address as BitcoinAddress, Amount, TxIn, Txid}; +use zksync_basic_types::H256; +use zksync_types::{Address as EVMAddress, L1BatchNumber}; #[derive(Serialize, Deserialize)] -pub enum BitcoinMessage {} +pub enum BitcoinMessage { +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Vote { + Ok, + NotOk, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CommonFields { + pub schnorr_signature: TaprootSignature, + pub encoded_public_key: PushBytesBuf, + pub via_inscription_protocol_identifier: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct L1BatchDAReferenceInput { + pub l1_batch_hash: H256, + pub l1_batch_index: L1BatchNumber, + pub da_identifier: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct L1BatchDAReference { + pub common: CommonFields, + pub input: L1BatchDAReferenceInput, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ProofDAReferenceInput { + pub l1_batch_reveal_txid: Txid, + pub da_identifier: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ProofDAReference { + pub common: CommonFields, + pub input: ProofDAReferenceInput, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ValidatorAttestationInput { + pub reference_txid: Txid, + pub attestation: Vote, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ValidatorAttestation { + pub common: CommonFields, + pub input: ValidatorAttestationInput, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SystemBootstrappingInput { + pub start_block_height: u32, + pub verifier_addresses: Vec, + pub bridge_p2wpkh_mpc_address: BitcoinAddress, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SystemBootstrapping { + pub common: CommonFields, + pub input: SystemBootstrappingInput, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ProposeSequencerInput { + pub sequencer_p2wpkh_address: BitcoinAddress, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ProposeSequencer { + pub common: CommonFields, + pub input: ProposeSequencerInput, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct L1ToL2MessageInput { + pub receiver_l2_address: EVMAddress, + pub l2_contract_address: EVMAddress, + pub call_data: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct L1ToL2Message { + pub common: CommonFields, + pub amount: Amount, + pub input: L1ToL2MessageInput, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Message { + L1BatchDAReference(L1BatchDAReference), + ProofDAReference(ProofDAReference), + ValidatorAttestation(ValidatorAttestation), + SystemBootstrapping(SystemBootstrapping), + ProposeSequencer(ProposeSequencer), + L1ToL2Message(L1ToL2Message), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct FeePayerCtx { + pub fee_payer_utxo_txid: Txid, + pub fee_payer_utxo_vout: u32, + pub fee_payer_utxo_value: Amount, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CommitTxInput { + pub spent_utxo: Vec, +} + +#[derive(Clone, Debug)] +pub struct InscriptionRequest { + pub message: Message, + pub inscriber_output: InscriberOutput, + pub fee_payer_ctx: FeePayerCtx, + pub commit_tx_input: CommitTxInput, +} + +#[derive(Clone, Debug)] +pub struct InscriberContext { + pub fifo_queue: VecDeque, +} + +const CTX_CAPACITY: usize = 10; + +impl InscriberContext { + pub fn new() -> Self { + Self { + fifo_queue: VecDeque::with_capacity(CTX_CAPACITY), + } + } +} + +#[derive(Clone, Debug)] +pub struct InscriberOutput { + pub commit_txid: Txid, + pub commit_raw_tx: String, + pub commit_tx_fee_rate: u64, + pub reveal_txid: Txid, + pub reveal_raw_tx: String, + pub reveal_tx_fee_rate: u64, + pub is_broadcasted: bool, +} -#[allow(unused)] #[derive(Debug, Error)] pub enum BitcoinError { #[error("RPC error: {0}")] From d11b11966b25c3645f95befd5ec492a351ad31eb Mon Sep 17 00:00:00 2001 From: steph-mipt Date: Tue, 13 Aug 2024 18:30:54 +0200 Subject: [PATCH 04/12] feat: add message parsing and main indexer logic [ci skip] --- core/lib/via_btc_client/src/client/mod.rs | 4 + core/lib/via_btc_client/src/indexer/mod.rs | 415 ++++++++++++++++++++- core/lib/via_btc_client/src/traits.rs | 20 +- core/lib/via_btc_client/src/types.rs | 19 +- 4 files changed, 425 insertions(+), 33 deletions(-) diff --git a/core/lib/via_btc_client/src/client/mod.rs b/core/lib/via_btc_client/src/client/mod.rs index ac1bf70e02a2..bc3e1d1fcc74 100644 --- a/core/lib/via_btc_client/src/client/mod.rs +++ b/core/lib/via_btc_client/src/client/mod.rs @@ -98,6 +98,10 @@ impl BitcoinOps for BitcoinClient { fn get_rpc_client(&self) -> &dyn BitcoinRpc { self.rpc.as_ref() } + + fn get_network(&self) -> Network { + self.network + } } #[cfg(test)] diff --git a/core/lib/via_btc_client/src/indexer/mod.rs b/core/lib/via_btc_client/src/indexer/mod.rs index 3f88ac44fbbb..7f3806d34b6c 100644 --- a/core/lib/via_btc_client/src/indexer/mod.rs +++ b/core/lib/via_btc_client/src/indexer/mod.rs @@ -1,30 +1,80 @@ use async_trait::async_trait; -use bitcoin::{script::Instruction, BlockHash, Transaction}; +use bitcoin::{ + address::NetworkUnchecked, + hashes::Hash, + script::{Instruction, PushBytesBuf}, + secp256k1::XOnlyPublicKey, + taproot::Signature, + Address, Amount, BlockHash, KnownHrp, Network, ScriptBuf, Transaction, +}; +pub use bitcoin::{Network as BitcoinNetwork, Txid}; +use bitcoincore_rpc::Auth; +use zksync_basic_types::H256; +use zksync_types::{Address as EVMAddress, L1BatchNumber}; use crate::{ + client::BitcoinClient, traits::BitcoinIndexerOpt, - types::{BitcoinIndexerResult, BitcoinMessage}, + types::{ + BitcoinError, BitcoinIndexerResult, CommonFields, L1BatchDAReference, + L1BatchDAReferenceInput, L1ToL2Message, L1ToL2MessageInput, Message, ProofDAReference, + ProofDAReferenceInput, ProposeSequencer, ProposeSequencerInput, SystemBootstrapping, + SystemBootstrappingInput, ValidatorAttestation, ValidatorAttestationInput, Vote, + }, BitcoinOps, }; pub struct BitcoinInscriptionIndexer { client: Box, + bridge_address: Option
, + verifier_addresses: Vec
, + starting_block_number: u32, + sequencer_address: Option
, } #[async_trait] impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { - async fn new() -> Self + async fn new(rpc_url: &str, network: BitcoinNetwork, txid: &Txid) -> BitcoinIndexerResult where Self: Sized, { - todo!() + let client = Box::new(BitcoinClient::new(rpc_url, network, Auth::None).await?); + let tx = client.get_rpc_client().get_transaction(txid).await?; + + let temp_indexer = Self { + client, + bridge_address: None, + sequencer_address: None, + verifier_addresses: Vec::new(), + starting_block_number: 0, + }; + + let mut init_msgs = temp_indexer + .process_tx(&tx) + .ok_or_else(|| BitcoinError::Other("Failed to process transaction".to_string()))?; + + if let Some(Message::SystemBootstrapping(system_bootstrapping)) = init_msgs.pop() { + let client = Box::new(BitcoinClient::new(rpc_url, network, Auth::None).await?); + Ok(Self { + client, + bridge_address: Some(system_bootstrapping.input.bridge_p2wpkh_mpc_address), + verifier_addresses: system_bootstrapping.input.verifier_addresses, + starting_block_number: system_bootstrapping.input.start_block_height, + sequencer_address: None, + }) + } else { + Err(BitcoinError::Other( + "Indexer error: provided txid does not contain SystemBootstrapping message" + .to_string(), + )) + } } async fn process_blocks( &self, - starting_block: u128, - ending_block: u128, - ) -> BitcoinIndexerResult> { + starting_block: u32, + ending_block: u32, + ) -> BitcoinIndexerResult> { let mut res = Vec::with_capacity((ending_block - starting_block + 1) as usize); for block in starting_block..=ending_block { res.extend(self.process_block(block).await?); @@ -32,11 +82,17 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { Ok(res) } - async fn process_block(&self, block: u128) -> BitcoinIndexerResult> { + async fn process_block(&self, block: u32) -> BitcoinIndexerResult> { + if block < self.starting_block_number { + return Err(BitcoinError::Other( + "Indexer error: can't get block before starting block".to_string(), + )); + } + let block = self .client .get_rpc_client() - .get_block_by_height(block) + .get_block_by_height(block as u128) .await?; let res: Vec<_> = block .txdata @@ -62,19 +118,338 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { } impl BitcoinInscriptionIndexer { - fn process_tx(&self, tx: &Transaction) -> Option> { - // only first? - let witness = tx.input.get(0)?.witness.to_vec(); - let script = bitcoin::Script::from_bytes(witness.last()?); - let instructions: Vec<_> = script.instructions().filter_map(Result::ok).collect(); - - let via_index = match is_via_inscription_protocol(&instructions) { - Some(pos) => pos, + fn process_tx(&self, tx: &Transaction) -> Option> { + let mut messages = Vec::new(); + + for (input_index, input) in tx.input.iter().enumerate() { + let witness = &input.witness; + if witness.len() < 3 { + continue; + } + + let signature = Signature::from_slice(&witness[0]).ok()?; + let public_key = XOnlyPublicKey::from_slice(&witness[1]).ok()?; + let script = ScriptBuf::from_bytes(witness.last()?.to_vec()); + + let instructions: Vec<_> = script.instructions().filter_map(Result::ok).collect(); + let via_index = is_via_inscription_protocol(&instructions)?; + + let common_fields = CommonFields { + schnorr_signature: signature, + encoded_public_key: PushBytesBuf::from(public_key.serialize()), + via_inscription_protocol_identifier: "via_inscription_protocol".to_string(), + }; + + if let Some(message) = + self.parse_message(input_index, tx, &instructions[via_index..], &common_fields) + { + messages.push(message); + } + } + + if messages.is_empty() { + None + } else { + Some(messages) + } + } + + fn parse_system_bootstrapping( + &self, + instructions: &[Instruction], + common_fields: CommonFields, + ) -> Option { + if instructions.len() < 11 { + return None; + } + + let start_block_height = u32::from_be_bytes( + instructions + .get(2)? + .push_bytes()? + .as_bytes() + .try_into() + .ok()?, + ); + + let mut verifier_addresses = Vec::new(); + for i in 3..10 { + if let Some(Instruction::PushBytes(bytes)) = instructions.get(i) { + if let Ok(address_str) = std::str::from_utf8(bytes.as_bytes()) { + if let Ok(address) = address_str.parse::>() { + if let Ok(network_address) = + address.require_network(self.client.get_network()) + { + verifier_addresses.push(network_address); + continue; + } + } + } + break; + } else { + break; + } + } + + let bridge_address = match instructions.get(10)?.push_bytes() { + Some(bytes) => { + if let Ok(address_str) = std::str::from_utf8(bytes.as_bytes()) { + address_str + .parse::>() + .ok()? + .require_network(self.client.get_network()) + .ok()? + } else { + return None; + } + } None => return None, }; - // TODO: collect and parse - let _msg_type = instructions.get(via_index + 1); - None + + Some(Message::SystemBootstrapping(SystemBootstrapping { + common: common_fields, + input: SystemBootstrappingInput { + start_block_height, + verifier_addresses, + bridge_p2wpkh_mpc_address: bridge_address, + }, + })) + } + + fn parse_propose_sequencer( + &self, + instructions: &[Instruction], + common_fields: CommonFields, + ) -> Option { + if instructions.len() < 3 { + return None; + } + + let proposer_address = match instructions.get(2)?.push_bytes() { + Some(bytes) => { + if let Ok(address_str) = std::str::from_utf8(bytes.as_bytes()) { + address_str + .parse::>() + .ok()? + .require_network(self.client.get_network()) + .ok()? + } else { + return None; + } + } + None => return None, + }; + + Some(Message::ProposeSequencer(ProposeSequencer { + common: common_fields, + input: ProposeSequencerInput { + sequencer_p2wpkh_address: proposer_address, + }, + })) + } + + fn parse_validator_attestation( + &self, + instructions: &[Instruction], + common_fields: CommonFields, + ) -> Option { + if instructions.len() < 4 { + return None; + } + + let reference_txid = + Txid::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()).ok()?; + let attestation = match instructions.get(3)?.push_bytes()?.as_bytes() { + b"OP_1" => Vote::Ok, + b"OP_0" => Vote::NotOk, + _ => return None, + }; + + Some(Message::ValidatorAttestation(ValidatorAttestation { + common: common_fields, + input: ValidatorAttestationInput { + reference_txid, + attestation, + }, + })) + } + + fn parse_proof_da_reference( + &self, + instructions: &[Instruction], + common_fields: CommonFields, + ) -> Option { + if instructions.len() < 5 { + return None; + } + + let l1_batch_reveal_txid = + Txid::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()).ok()?; + let da_identifier = std::str::from_utf8(instructions.get(3)?.push_bytes()?.as_bytes()) + .ok()? + .to_string(); + let blob_id = std::str::from_utf8(instructions.get(4)?.push_bytes()?.as_bytes()) + .ok()? + .to_string(); + + Some(Message::ProofDAReference(ProofDAReference { + common: common_fields, + input: ProofDAReferenceInput { + l1_batch_reveal_txid, + da_identifier, + blob_id, + }, + })) + } + + fn parse_l1_batch_da_reference( + &self, + instructions: &[Instruction], + common_fields: CommonFields, + ) -> Option { + if instructions.len() < 6 { + return None; + } + + let l1_batch_hash = H256::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()); + let l1_batch_index = L1BatchNumber(u32::from_be_bytes( + instructions + .get(3)? + .push_bytes()? + .as_bytes() + .try_into() + .ok()?, + )); + let da_identifier = std::str::from_utf8(instructions.get(4)?.push_bytes()?.as_bytes()) + .ok()? + .to_string(); + let blob_id = std::str::from_utf8(instructions.get(5)?.push_bytes()?.as_bytes()) + .ok()? + .to_string(); + + Some(Message::L1BatchDAReference(L1BatchDAReference { + common: common_fields, + input: L1BatchDAReferenceInput { + l1_batch_hash, + l1_batch_index, + da_identifier, + blob_id, + }, + })) + } + + fn parse_l1_to_l2_message( + &self, + _input_index: usize, + tx: &Transaction, + instructions: &[Instruction], + common_fields: CommonFields, + ) -> Option { + if instructions.len() < 5 { + return None; + } + + let receiver_l2_address = + EVMAddress::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()); + let l2_contract_address = + EVMAddress::from_slice(instructions.get(3)?.push_bytes()?.as_bytes()); + let call_data = instructions.get(4)?.push_bytes()?.as_bytes().to_vec(); + + let is_bridging = l2_contract_address == EVMAddress::zero() && call_data.is_empty(); + + let amount = if is_bridging { + tx.output + .iter() + .find(|output| { + Some(&output.script_pubkey) + == self + .bridge_address + .as_ref() + .map(|addr| addr.script_pubkey()) + .as_ref() + }) + .map(|output| output.value) + .unwrap_or(Amount::ZERO) + } else { + Amount::ZERO + }; + + Some(Message::L1ToL2Message(L1ToL2Message { + common: common_fields, + amount, + input: L1ToL2MessageInput { + receiver_l2_address, + l2_contract_address, + call_data, + }, + })) + } + + fn parse_message( + &self, + input_index: usize, + tx: &Transaction, + instructions: &[Instruction], + common_fields: &CommonFields, + ) -> Option { + let message_type = instructions.get(1)?; + let hrp = match self.client.get_network() { + Network::Bitcoin => KnownHrp::Mainnet, + Network::Testnet | Network::Signet => KnownHrp::Testnets, + Network::Regtest => KnownHrp::Regtest, + _ => return None, // TODO: why do we need that here? + }; + let sender_address = Address::p2tr( + &bitcoin::secp256k1::Secp256k1::new(), + XOnlyPublicKey::from_slice(&common_fields.encoded_public_key.as_bytes()).ok()?, + None, + hrp, + ); + + match message_type { + Instruction::PushBytes(bytes) + if bytes.as_bytes() == b"Str('SystemBootstrappingMessage')" => + { + if self.starting_block_number != 0 { + return None; + } + self.parse_system_bootstrapping(instructions, common_fields.clone()) + } + Instruction::PushBytes(bytes) + if bytes.as_bytes() == b"Str('ProposeSequencerMessage')" => + { + if !self.verifier_addresses.contains(&sender_address) { + return None; + } + self.parse_propose_sequencer(instructions, common_fields.clone()) + } + Instruction::PushBytes(bytes) + if bytes.as_bytes() == b"Str('ValidatorAttestationMessage')" => + { + if !self.verifier_addresses.contains(&sender_address) { + return None; + } + self.parse_validator_attestation(instructions, common_fields.clone()) + } + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('L1BatchDAReference')" => { + if Some(&sender_address) != self.sequencer_address.as_ref() { + return None; + } + self.parse_l1_batch_da_reference(instructions, common_fields.clone()) + } + Instruction::PushBytes(bytes) + if bytes.as_bytes() == b"Str('ProofDAReferenceMessage')" => + { + if Some(&sender_address) != self.sequencer_address.as_ref() { + return None; + } + self.parse_proof_da_reference(instructions, common_fields.clone()) + } + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('L1ToL2Message')" => { + self.parse_l1_to_l2_message(input_index, tx, instructions, common_fields.clone()) + } + _ => None, + } } } diff --git a/core/lib/via_btc_client/src/traits.rs b/core/lib/via_btc_client/src/traits.rs index c3bda5f388ab..c1cda74702d2 100644 --- a/core/lib/via_btc_client/src/traits.rs +++ b/core/lib/via_btc_client/src/traits.rs @@ -2,9 +2,12 @@ use async_trait::async_trait; use bitcoin::{Address, Block, BlockHash, Network, OutPoint, Transaction, TxOut, Txid}; use bitcoincore_rpc::Auth; -use crate::types::{ - BitcoinClientResult, BitcoinIndexerResult, BitcoinInscriberResult, BitcoinMessage, - BitcoinRpcResult, BitcoinSignerResult, +use crate::{ + indexer::BitcoinNetwork, + types::{ + BitcoinClientResult, BitcoinIndexerResult, BitcoinInscriberResult, BitcoinRpcResult, + BitcoinSignerResult, Message, + }, }; #[allow(dead_code)] @@ -25,6 +28,7 @@ pub trait BitcoinOps: Send + Sync { async fn fetch_block(&self, block_height: u128) -> BitcoinClientResult; async fn get_fee_rate(&self, conf_target: u16) -> BitcoinClientResult; fn get_rpc_client(&self) -> &dyn BitcoinRpc; + fn get_network(&self) -> Network; } #[allow(dead_code)] @@ -84,15 +88,15 @@ pub trait BitcoinInscriber: Send + Sync { #[allow(dead_code)] #[async_trait] pub trait BitcoinIndexerOpt: Send + Sync { - async fn new() -> Self + async fn new(rpc_url: &str, network: BitcoinNetwork, txid: &Txid) -> BitcoinIndexerResult where Self: Sized; async fn process_blocks( &self, - starting_block: u128, - ending_block: u128, - ) -> BitcoinIndexerResult>; - async fn process_block(&self, block: u128) -> BitcoinIndexerResult>; + starting_block: u32, + ending_block: u32, + ) -> BitcoinIndexerResult>; + async fn process_block(&self, block: u32) -> BitcoinIndexerResult>; async fn are_blocks_connected( &self, diff --git a/core/lib/via_btc_client/src/types.rs b/core/lib/via_btc_client/src/types.rs index f4ad2ac0a157..b604da9b59e5 100644 --- a/core/lib/via_btc_client/src/types.rs +++ b/core/lib/via_btc_client/src/types.rs @@ -1,15 +1,16 @@ use std::collections::VecDeque; + +use bitcoin::{ + script::PushBytesBuf, taproot::Signature as TaprootSignature, Address as BitcoinAddress, + Amount, TxIn, Txid, +}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use bitcoin::script::PushBytesBuf; -use bitcoin::taproot::Signature as TaprootSignature; -use bitcoin::{Address as BitcoinAddress, Amount, TxIn, Txid}; use zksync_basic_types::H256; use zksync_types::{Address as EVMAddress, L1BatchNumber}; #[derive(Serialize, Deserialize)] -pub enum BitcoinMessage { -} +pub enum BitcoinMessage {} #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Vote { @@ -29,6 +30,7 @@ pub struct L1BatchDAReferenceInput { pub l1_batch_hash: H256, pub l1_batch_index: L1BatchNumber, pub da_identifier: String, + pub blob_id: String, } #[derive(Clone, Debug, PartialEq)] @@ -41,6 +43,7 @@ pub struct L1BatchDAReference { pub struct ProofDAReferenceInput { pub l1_batch_reveal_txid: Txid, pub da_identifier: String, + pub blob_id: String, } #[derive(Clone, Debug, PartialEq)] @@ -121,6 +124,7 @@ pub struct CommitTxInput { pub spent_utxo: Vec, } +#[allow(unused)] #[derive(Clone, Debug)] pub struct InscriptionRequest { pub message: Message, @@ -129,13 +133,16 @@ pub struct InscriptionRequest { pub commit_tx_input: CommitTxInput, } +#[allow(unused)] #[derive(Clone, Debug)] pub struct InscriberContext { pub fifo_queue: VecDeque, } +#[allow(unused)] const CTX_CAPACITY: usize = 10; +#[allow(unused)] impl InscriberContext { pub fn new() -> Self { Self { @@ -144,6 +151,7 @@ impl InscriberContext { } } +#[allow(unused)] #[derive(Clone, Debug)] pub struct InscriberOutput { pub commit_txid: Txid, @@ -220,4 +228,5 @@ impl From for BitcoinError { pub type BitcoinSignerResult = Result; pub type BitcoinInscriberResult = Result; pub type BitcoinIndexerResult = Result; +#[allow(unused)] pub type BitcoinTransactionBuilderResult = Result; From 16f4ab5155a1e20c2a2286a5f10c528462c23067 Mon Sep 17 00:00:00 2001 From: steph-mipt Date: Thu, 15 Aug 2024 21:40:09 +0200 Subject: [PATCH 05/12] feat: add `parser` module, a bit more logic [ci skip] --- core/lib/via_btc_client/src/indexer/mod.rs | 406 ++---------------- core/lib/via_btc_client/src/indexer/parser.rs | 321 ++++++++++++++ core/lib/via_btc_client/src/traits.rs | 11 +- core/lib/via_btc_client/src/types.rs | 1 - 4 files changed, 367 insertions(+), 372 deletions(-) create mode 100644 core/lib/via_btc_client/src/indexer/parser.rs diff --git a/core/lib/via_btc_client/src/indexer/mod.rs b/core/lib/via_btc_client/src/indexer/mod.rs index 7f3806d34b6c..1f12b667e3bc 100644 --- a/core/lib/via_btc_client/src/indexer/mod.rs +++ b/core/lib/via_btc_client/src/indexer/mod.rs @@ -1,62 +1,42 @@ use async_trait::async_trait; -use bitcoin::{ - address::NetworkUnchecked, - hashes::Hash, - script::{Instruction, PushBytesBuf}, - secp256k1::XOnlyPublicKey, - taproot::Signature, - Address, Amount, BlockHash, KnownHrp, Network, ScriptBuf, Transaction, -}; -pub use bitcoin::{Network as BitcoinNetwork, Txid}; +use bitcoin::{Address, BlockHash, KnownHrp, Network, Transaction, Txid}; use bitcoincore_rpc::Auth; -use zksync_basic_types::H256; -use zksync_types::{Address as EVMAddress, L1BatchNumber}; + +mod parser; +use parser::MessageParser; use crate::{ client::BitcoinClient, traits::BitcoinIndexerOpt, - types::{ - BitcoinError, BitcoinIndexerResult, CommonFields, L1BatchDAReference, - L1BatchDAReferenceInput, L1ToL2Message, L1ToL2MessageInput, Message, ProofDAReference, - ProofDAReferenceInput, ProposeSequencer, ProposeSequencerInput, SystemBootstrapping, - SystemBootstrappingInput, ValidatorAttestation, ValidatorAttestationInput, Vote, - }, + types::{BitcoinError, BitcoinIndexerResult, CommonFields, Message}, BitcoinOps, }; pub struct BitcoinInscriptionIndexer { client: Box, + parser: MessageParser, bridge_address: Option
, + sequencer_address: Option
, verifier_addresses: Vec
, starting_block_number: u32, - sequencer_address: Option
, } #[async_trait] impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { - async fn new(rpc_url: &str, network: BitcoinNetwork, txid: &Txid) -> BitcoinIndexerResult + async fn new(rpc_url: &str, network: Network, txid: &Txid) -> BitcoinIndexerResult where Self: Sized, { let client = Box::new(BitcoinClient::new(rpc_url, network, Auth::None).await?); + let parser = MessageParser::new(network); let tx = client.get_rpc_client().get_transaction(txid).await?; - let temp_indexer = Self { - client, - bridge_address: None, - sequencer_address: None, - verifier_addresses: Vec::new(), - starting_block_number: 0, - }; - - let mut init_msgs = temp_indexer - .process_tx(&tx) - .ok_or_else(|| BitcoinError::Other("Failed to process transaction".to_string()))?; + let mut init_msgs = parser.parse_transaction(&tx); if let Some(Message::SystemBootstrapping(system_bootstrapping)) = init_msgs.pop() { - let client = Box::new(BitcoinClient::new(rpc_url, network, Auth::None).await?); Ok(Self { client, + parser, bridge_address: Some(system_bootstrapping.input.bridge_p2wpkh_mpc_address), verifier_addresses: system_bootstrapping.input.verifier_addresses, starting_block_number: system_bootstrapping.input.start_block_height, @@ -94,13 +74,15 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { .get_rpc_client() .get_block_by_height(block as u128) .await?; - let res: Vec<_> = block + + let messages: Vec = block .txdata .iter() - .filter_map(|tx| self.process_tx(tx)) - .flatten() + .flat_map(|tx| self.process_tx(tx)) + .filter(|message| self.is_valid_message(message)) .collect(); - Ok(res) + + Ok(messages) } async fn are_blocks_connected( @@ -118,343 +100,39 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { } impl BitcoinInscriptionIndexer { - fn process_tx(&self, tx: &Transaction) -> Option> { - let mut messages = Vec::new(); - - for (input_index, input) in tx.input.iter().enumerate() { - let witness = &input.witness; - if witness.len() < 3 { - continue; - } - - let signature = Signature::from_slice(&witness[0]).ok()?; - let public_key = XOnlyPublicKey::from_slice(&witness[1]).ok()?; - let script = ScriptBuf::from_bytes(witness.last()?.to_vec()); - - let instructions: Vec<_> = script.instructions().filter_map(Result::ok).collect(); - let via_index = is_via_inscription_protocol(&instructions)?; - - let common_fields = CommonFields { - schnorr_signature: signature, - encoded_public_key: PushBytesBuf::from(public_key.serialize()), - via_inscription_protocol_identifier: "via_inscription_protocol".to_string(), - }; - - if let Some(message) = - self.parse_message(input_index, tx, &instructions[via_index..], &common_fields) - { - messages.push(message); - } - } - - if messages.is_empty() { - None - } else { - Some(messages) - } + fn process_tx(&self, tx: &Transaction) -> Vec { + self.parser.parse_transaction(tx) } - fn parse_system_bootstrapping( - &self, - instructions: &[Instruction], - common_fields: CommonFields, - ) -> Option { - if instructions.len() < 11 { - return None; - } - - let start_block_height = u32::from_be_bytes( - instructions - .get(2)? - .push_bytes()? - .as_bytes() - .try_into() - .ok()?, - ); - - let mut verifier_addresses = Vec::new(); - for i in 3..10 { - if let Some(Instruction::PushBytes(bytes)) = instructions.get(i) { - if let Ok(address_str) = std::str::from_utf8(bytes.as_bytes()) { - if let Ok(address) = address_str.parse::>() { - if let Ok(network_address) = - address.require_network(self.client.get_network()) - { - verifier_addresses.push(network_address); - continue; - } - } - } - break; - } else { - break; + fn is_valid_message(&self, message: &Message) -> bool { + match message { + Message::ProposeSequencer(m) => self + .verifier_addresses + .contains(&self.get_sender_address(&m.common)), + Message::ValidatorAttestation(m) => self + .verifier_addresses + .contains(&self.get_sender_address(&m.common)), + Message::L1BatchDAReference(m) => { + Some(&self.get_sender_address(&m.common)) == self.sequencer_address.as_ref() } - } - - let bridge_address = match instructions.get(10)?.push_bytes() { - Some(bytes) => { - if let Ok(address_str) = std::str::from_utf8(bytes.as_bytes()) { - address_str - .parse::>() - .ok()? - .require_network(self.client.get_network()) - .ok()? - } else { - return None; - } + Message::ProofDAReference(m) => { + Some(&self.get_sender_address(&m.common)) == self.sequencer_address.as_ref() } - None => return None, - }; - - Some(Message::SystemBootstrapping(SystemBootstrapping { - common: common_fields, - input: SystemBootstrappingInput { - start_block_height, - verifier_addresses, - bridge_p2wpkh_mpc_address: bridge_address, - }, - })) - } - - fn parse_propose_sequencer( - &self, - instructions: &[Instruction], - common_fields: CommonFields, - ) -> Option { - if instructions.len() < 3 { - return None; - } - - let proposer_address = match instructions.get(2)?.push_bytes() { - Some(bytes) => { - if let Ok(address_str) = std::str::from_utf8(bytes.as_bytes()) { - address_str - .parse::>() - .ok()? - .require_network(self.client.get_network()) - .ok()? - } else { - return None; - } - } - None => return None, - }; - - Some(Message::ProposeSequencer(ProposeSequencer { - common: common_fields, - input: ProposeSequencerInput { - sequencer_p2wpkh_address: proposer_address, - }, - })) - } - - fn parse_validator_attestation( - &self, - instructions: &[Instruction], - common_fields: CommonFields, - ) -> Option { - if instructions.len() < 4 { - return None; - } - - let reference_txid = - Txid::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()).ok()?; - let attestation = match instructions.get(3)?.push_bytes()?.as_bytes() { - b"OP_1" => Vote::Ok, - b"OP_0" => Vote::NotOk, - _ => return None, - }; - - Some(Message::ValidatorAttestation(ValidatorAttestation { - common: common_fields, - input: ValidatorAttestationInput { - reference_txid, - attestation, - }, - })) - } - - fn parse_proof_da_reference( - &self, - instructions: &[Instruction], - common_fields: CommonFields, - ) -> Option { - if instructions.len() < 5 { - return None; - } - - let l1_batch_reveal_txid = - Txid::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()).ok()?; - let da_identifier = std::str::from_utf8(instructions.get(3)?.push_bytes()?.as_bytes()) - .ok()? - .to_string(); - let blob_id = std::str::from_utf8(instructions.get(4)?.push_bytes()?.as_bytes()) - .ok()? - .to_string(); - - Some(Message::ProofDAReference(ProofDAReference { - common: common_fields, - input: ProofDAReferenceInput { - l1_batch_reveal_txid, - da_identifier, - blob_id, - }, - })) - } - - fn parse_l1_batch_da_reference( - &self, - instructions: &[Instruction], - common_fields: CommonFields, - ) -> Option { - if instructions.len() < 6 { - return None; + Message::L1ToL2Message(m) => m.amount > bitcoin::Amount::ZERO, + Message::SystemBootstrapping(_) => true, } - - let l1_batch_hash = H256::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()); - let l1_batch_index = L1BatchNumber(u32::from_be_bytes( - instructions - .get(3)? - .push_bytes()? - .as_bytes() - .try_into() - .ok()?, - )); - let da_identifier = std::str::from_utf8(instructions.get(4)?.push_bytes()?.as_bytes()) - .ok()? - .to_string(); - let blob_id = std::str::from_utf8(instructions.get(5)?.push_bytes()?.as_bytes()) - .ok()? - .to_string(); - - Some(Message::L1BatchDAReference(L1BatchDAReference { - common: common_fields, - input: L1BatchDAReferenceInput { - l1_batch_hash, - l1_batch_index, - da_identifier, - blob_id, - }, - })) } - fn parse_l1_to_l2_message( - &self, - _input_index: usize, - tx: &Transaction, - instructions: &[Instruction], - common_fields: CommonFields, - ) -> Option { - if instructions.len() < 5 { - return None; - } - - let receiver_l2_address = - EVMAddress::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()); - let l2_contract_address = - EVMAddress::from_slice(instructions.get(3)?.push_bytes()?.as_bytes()); - let call_data = instructions.get(4)?.push_bytes()?.as_bytes().to_vec(); - - let is_bridging = l2_contract_address == EVMAddress::zero() && call_data.is_empty(); - - let amount = if is_bridging { - tx.output - .iter() - .find(|output| { - Some(&output.script_pubkey) - == self - .bridge_address - .as_ref() - .map(|addr| addr.script_pubkey()) - .as_ref() - }) - .map(|output| output.value) - .unwrap_or(Amount::ZERO) - } else { - Amount::ZERO - }; - - Some(Message::L1ToL2Message(L1ToL2Message { - common: common_fields, - amount, - input: L1ToL2MessageInput { - receiver_l2_address, - l2_contract_address, - call_data, - }, - })) - } - - fn parse_message( - &self, - input_index: usize, - tx: &Transaction, - instructions: &[Instruction], - common_fields: &CommonFields, - ) -> Option { - let message_type = instructions.get(1)?; - let hrp = match self.client.get_network() { - Network::Bitcoin => KnownHrp::Mainnet, - Network::Testnet | Network::Signet => KnownHrp::Testnets, - Network::Regtest => KnownHrp::Regtest, - _ => return None, // TODO: why do we need that here? - }; - let sender_address = Address::p2tr( + fn get_sender_address(&self, common_fields: &CommonFields) -> Address { + let public_key = + secp256k1::XOnlyPublicKey::from_slice(&common_fields.encoded_public_key.as_bytes()) + .map_err(|_| BitcoinError::Other("Invalid public key".to_string())) + .unwrap(); + Address::p2tr( &bitcoin::secp256k1::Secp256k1::new(), - XOnlyPublicKey::from_slice(&common_fields.encoded_public_key.as_bytes()).ok()?, + public_key, None, - hrp, - ); - - match message_type { - Instruction::PushBytes(bytes) - if bytes.as_bytes() == b"Str('SystemBootstrappingMessage')" => - { - if self.starting_block_number != 0 { - return None; - } - self.parse_system_bootstrapping(instructions, common_fields.clone()) - } - Instruction::PushBytes(bytes) - if bytes.as_bytes() == b"Str('ProposeSequencerMessage')" => - { - if !self.verifier_addresses.contains(&sender_address) { - return None; - } - self.parse_propose_sequencer(instructions, common_fields.clone()) - } - Instruction::PushBytes(bytes) - if bytes.as_bytes() == b"Str('ValidatorAttestationMessage')" => - { - if !self.verifier_addresses.contains(&sender_address) { - return None; - } - self.parse_validator_attestation(instructions, common_fields.clone()) - } - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('L1BatchDAReference')" => { - if Some(&sender_address) != self.sequencer_address.as_ref() { - return None; - } - self.parse_l1_batch_da_reference(instructions, common_fields.clone()) - } - Instruction::PushBytes(bytes) - if bytes.as_bytes() == b"Str('ProofDAReferenceMessage')" => - { - if Some(&sender_address) != self.sequencer_address.as_ref() { - return None; - } - self.parse_proof_da_reference(instructions, common_fields.clone()) - } - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('L1ToL2Message')" => { - self.parse_l1_to_l2_message(input_index, tx, instructions, common_fields.clone()) - } - _ => None, - } + KnownHrp::from(self.client.get_network()), + ) } } - -fn is_via_inscription_protocol(instructions: &[Instruction]) -> Option { - instructions.iter().position(|instr| { - matches!(instr, Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('via_inscription_protocol')") - }) -} diff --git a/core/lib/via_btc_client/src/indexer/parser.rs b/core/lib/via_btc_client/src/indexer/parser.rs new file mode 100644 index 000000000000..6d1e27776493 --- /dev/null +++ b/core/lib/via_btc_client/src/indexer/parser.rs @@ -0,0 +1,321 @@ +use bitcoin::{ + address::NetworkUnchecked, + hashes::Hash, + script::{Instruction, PushBytesBuf}, + secp256k1::XOnlyPublicKey, + taproot::Signature as TaprootSignature, + Address, Amount, Network, ScriptBuf, Transaction, Txid, +}; +use zksync_basic_types::H256; +use zksync_types::{Address as EVMAddress, L1BatchNumber}; + +use crate::types::{ + CommonFields, L1BatchDAReference, L1BatchDAReferenceInput, L1ToL2Message, L1ToL2MessageInput, + Message, ProofDAReference, ProofDAReferenceInput, ProposeSequencer, ProposeSequencerInput, + SystemBootstrapping, SystemBootstrappingInput, ValidatorAttestation, ValidatorAttestationInput, + Vote, +}; + +// TODO: make this a trait (ViaProtocolParser_V1, ViaProtocolParser_V2, etc) +pub struct MessageParser { + network: Network, +} + +impl MessageParser { + pub fn new(network: Network) -> Self { + Self { network } + } + + pub fn parse_transaction(&self, tx: &Transaction) -> Vec { + tx.input + .iter() + .filter_map(|input| self.parse_input(input, tx)) + .collect() + } + + fn parse_input(&self, input: &bitcoin::TxIn, tx: &Transaction) -> Option { + let witness = &input.witness; + if witness.len() < 3 { + return None; + } + + let signature = TaprootSignature::from_slice(&witness[0]).ok()?; + let public_key = XOnlyPublicKey::from_slice(&witness[1]).ok()?; + let script = ScriptBuf::from_bytes(witness.last()?.to_vec()); + + let instructions: Vec<_> = script.instructions().filter_map(Result::ok).collect(); + let via_index = is_via_inscription_protocol(&instructions)?; + + // TODO: not to pass common fields around + let common_fields = CommonFields { + schnorr_signature: signature, + encoded_public_key: PushBytesBuf::from(public_key.serialize()), + }; + + self.parse_message(tx, &instructions[via_index..], &common_fields) + } + + fn parse_message( + &self, + tx: &Transaction, + instructions: &[Instruction], + common_fields: &CommonFields, + ) -> Option { + let message_type = instructions.get(1)?; + + match message_type { + Instruction::PushBytes(bytes) + if bytes.as_bytes() == b"Str('SystemBootstrappingMessage')" => + { + self.parse_system_bootstrapping(instructions, common_fields) + } + Instruction::PushBytes(bytes) + if bytes.as_bytes() == b"Str('ProposeSequencerMessage')" => + { + self.parse_propose_sequencer(instructions, common_fields) + } + Instruction::PushBytes(bytes) + if bytes.as_bytes() == b"Str('ValidatorAttestationMessage')" => + { + self.parse_validator_attestation(instructions, common_fields) + } + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('L1BatchDAReference')" => { + self.parse_l1_batch_da_reference(instructions, common_fields) + } + Instruction::PushBytes(bytes) + if bytes.as_bytes() == b"Str('ProofDAReferenceMessage')" => + { + self.parse_proof_da_reference(instructions, common_fields) + } + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('L1ToL2Message')" => { + self.parse_l1_to_l2_message(tx, instructions, common_fields) + } + _ => None, + } + } + + fn parse_system_bootstrapping( + &self, + instructions: &[Instruction], + common_fields: &CommonFields, + ) -> Option { + if instructions.len() < 5 { + return None; + } + + let start_block_height = u32::from_be_bytes( + instructions + .get(2)? + .push_bytes()? + .as_bytes() + .try_into() + .ok()?, + ); + + let verifier_addresses = instructions[3..] + .iter() + .take_while(|instr| matches!(instr, Instruction::PushBytes(_))) + .filter_map(|instr| { + if let Instruction::PushBytes(bytes) = instr { + std::str::from_utf8(bytes.as_bytes()).ok().and_then(|s| { + Some( + s.parse::>() + .ok()? + .require_network(self.network) + .ok()?, + ) + }) + } else { + None + } + }) + .collect::>(); + + let bridge_address = instructions.last().and_then(|instr| { + if let Instruction::PushBytes(bytes) = instr { + std::str::from_utf8(bytes.as_bytes()).ok().and_then(|s| { + Some( + s.parse::>() + .ok()? + .require_network(self.network) + .ok()?, + ) + }) + } else { + None + } + })?; + + Some(Message::SystemBootstrapping(SystemBootstrapping { + common: common_fields.clone(), + input: SystemBootstrappingInput { + start_block_height, + verifier_addresses, + bridge_p2wpkh_mpc_address: bridge_address, + }, + })) + } + + fn parse_propose_sequencer( + &self, + instructions: &[Instruction], + common_fields: &CommonFields, + ) -> Option { + if instructions.len() < 3 { + return None; + } + + let sequencer_address = instructions.get(2).and_then(|instr| { + if let Instruction::PushBytes(bytes) = instr { + std::str::from_utf8(bytes.as_bytes()).ok().and_then(|s| { + Some( + s.parse::>() + .ok()? + .require_network(self.network) + .ok()?, + ) + }) + } else { + None + } + })?; + + Some(Message::ProposeSequencer(ProposeSequencer { + common: common_fields.clone(), + input: ProposeSequencerInput { + sequencer_p2wpkh_address: sequencer_address, + }, + })) + } + + fn parse_validator_attestation( + &self, + instructions: &[Instruction], + common_fields: &CommonFields, + ) -> Option { + if instructions.len() < 4 { + return None; + } + + let reference_txid = + Txid::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()).ok()?; + let attestation = match instructions.get(3)?.push_bytes()?.as_bytes() { + b"OP_1" => Vote::Ok, + b"OP_0" => Vote::NotOk, + _ => return None, + }; + + Some(Message::ValidatorAttestation(ValidatorAttestation { + common: common_fields.clone(), + input: ValidatorAttestationInput { + reference_txid, + attestation, + }, + })) + } + + fn parse_l1_batch_da_reference( + &self, + instructions: &[Instruction], + common_fields: &CommonFields, + ) -> Option { + if instructions.len() < 6 { + return None; + } + + let l1_batch_hash = H256::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()); + let l1_batch_index = L1BatchNumber(u32::from_be_bytes( + instructions + .get(3)? + .push_bytes()? + .as_bytes() + .try_into() + .ok()?, + )); + let da_identifier = std::str::from_utf8(instructions.get(4)?.push_bytes()?.as_bytes()) + .ok()? + .to_string(); + let blob_id = std::str::from_utf8(instructions.get(5)?.push_bytes()?.as_bytes()) + .ok()? + .to_string(); + + Some(Message::L1BatchDAReference(L1BatchDAReference { + common: common_fields.clone(), + input: L1BatchDAReferenceInput { + l1_batch_hash, + l1_batch_index, + da_identifier, + blob_id, + }, + })) + } + + fn parse_proof_da_reference( + &self, + instructions: &[Instruction], + common_fields: &CommonFields, + ) -> Option { + if instructions.len() < 5 { + return None; + } + + let l1_batch_reveal_txid = + Txid::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()).ok()?; + let da_identifier = std::str::from_utf8(instructions.get(3)?.push_bytes()?.as_bytes()) + .ok()? + .to_string(); + let blob_id = std::str::from_utf8(instructions.get(4)?.push_bytes()?.as_bytes()) + .ok()? + .to_string(); + + Some(Message::ProofDAReference(ProofDAReference { + common: common_fields.clone(), + input: ProofDAReferenceInput { + l1_batch_reveal_txid, + da_identifier, + blob_id, + }, + })) + } + + fn parse_l1_to_l2_message( + &self, + tx: &Transaction, + instructions: &[Instruction], + common_fields: &CommonFields, + ) -> Option { + if instructions.len() < 5 { + return None; + } + + let receiver_l2_address = + EVMAddress::from_slice(instructions.get(2)?.push_bytes()?.as_bytes()); + let l2_contract_address = + EVMAddress::from_slice(instructions.get(3)?.push_bytes()?.as_bytes()); + let call_data = instructions.get(4)?.push_bytes()?.as_bytes().to_vec(); + + let amount = tx + .output + .iter() + .find(|output| output.script_pubkey.is_p2wpkh()) + .map(|output| output.value) + .unwrap_or(Amount::ZERO); + + Some(Message::L1ToL2Message(L1ToL2Message { + common: common_fields.clone(), + amount, + input: L1ToL2MessageInput { + receiver_l2_address, + l2_contract_address, + call_data, + }, + })) + } +} + +fn is_via_inscription_protocol(instructions: &[Instruction]) -> Option { + // TODO: also check first part of the script (OP_CHECKSIG and other stuff) + instructions.iter().position(|instr| { + matches!(instr, Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('via_inscription_protocol')") + }) +} diff --git a/core/lib/via_btc_client/src/traits.rs b/core/lib/via_btc_client/src/traits.rs index c1cda74702d2..2b9fae44b71c 100644 --- a/core/lib/via_btc_client/src/traits.rs +++ b/core/lib/via_btc_client/src/traits.rs @@ -2,12 +2,9 @@ use async_trait::async_trait; use bitcoin::{Address, Block, BlockHash, Network, OutPoint, Transaction, TxOut, Txid}; use bitcoincore_rpc::Auth; -use crate::{ - indexer::BitcoinNetwork, - types::{ - BitcoinClientResult, BitcoinIndexerResult, BitcoinInscriberResult, BitcoinRpcResult, - BitcoinSignerResult, Message, - }, +use crate::types::{ + BitcoinClientResult, BitcoinIndexerResult, BitcoinInscriberResult, BitcoinRpcResult, + BitcoinSignerResult, Message, }; #[allow(dead_code)] @@ -88,7 +85,7 @@ pub trait BitcoinInscriber: Send + Sync { #[allow(dead_code)] #[async_trait] pub trait BitcoinIndexerOpt: Send + Sync { - async fn new(rpc_url: &str, network: BitcoinNetwork, txid: &Txid) -> BitcoinIndexerResult + async fn new(rpc_url: &str, network: Network, txid: &Txid) -> BitcoinIndexerResult where Self: Sized; async fn process_blocks( diff --git a/core/lib/via_btc_client/src/types.rs b/core/lib/via_btc_client/src/types.rs index b604da9b59e5..fb3b91aa71c0 100644 --- a/core/lib/via_btc_client/src/types.rs +++ b/core/lib/via_btc_client/src/types.rs @@ -22,7 +22,6 @@ pub enum Vote { pub struct CommonFields { pub schnorr_signature: TaprootSignature, pub encoded_public_key: PushBytesBuf, - pub via_inscription_protocol_identifier: String, } #[derive(Clone, Debug, PartialEq)] From 29716358f253299d6cb6a3dc86d0475e729540a3 Mon Sep 17 00:00:00 2001 From: steph-mipt Date: Thu, 15 Aug 2024 21:59:06 +0200 Subject: [PATCH 06/12] feat: change bootstrap logic [ci skip] --- core/lib/via_btc_client/src/indexer/mod.rs | 180 +++++++++++++++++---- core/lib/via_btc_client/src/traits.rs | 6 +- 2 files changed, 152 insertions(+), 34 deletions(-) diff --git a/core/lib/via_btc_client/src/indexer/mod.rs b/core/lib/via_btc_client/src/indexer/mod.rs index 1f12b667e3bc..177a0052a508 100644 --- a/core/lib/via_btc_client/src/indexer/mod.rs +++ b/core/lib/via_btc_client/src/indexer/mod.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use async_trait::async_trait; use bitcoin::{Address, BlockHash, KnownHrp, Network, Transaction, Txid}; use bitcoincore_rpc::Auth; @@ -8,44 +10,106 @@ use parser::MessageParser; use crate::{ client::BitcoinClient, traits::BitcoinIndexerOpt, - types::{BitcoinError, BitcoinIndexerResult, CommonFields, Message}, + types::{BitcoinError, BitcoinIndexerResult, CommonFields, Message, Vote}, BitcoinOps, }; +struct BootstrapState { + verifier_addresses: Vec
, + proposed_sequencer: Option
, + sequencer_votes: HashMap, + bridge_address: Option
, + starting_block_number: u32, +} + +impl BootstrapState { + fn new() -> Self { + Self { + verifier_addresses: Vec::new(), + proposed_sequencer: None, + sequencer_votes: HashMap::new(), + bridge_address: None, + starting_block_number: 0, + } + } + + fn is_complete(&self) -> bool { + !self.verifier_addresses.is_empty() + && self.proposed_sequencer.is_some() + && self.bridge_address.is_some() + && self.starting_block_number > 0 + && self.has_majority_votes() + } + + fn has_majority_votes(&self) -> bool { + let total_votes = self.sequencer_votes.len(); + let positive_votes = self + .sequencer_votes + .values() + .filter(|&v| *v == Vote::Ok) + .count(); + positive_votes * 2 > total_votes + } +} + pub struct BitcoinInscriptionIndexer { client: Box, parser: MessageParser, - bridge_address: Option
, - sequencer_address: Option
, + bridge_address: Address, + sequencer_address: Address, verifier_addresses: Vec
, starting_block_number: u32, } #[async_trait] impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { - async fn new(rpc_url: &str, network: Network, txid: &Txid) -> BitcoinIndexerResult + async fn new( + rpc_url: &str, + network: Network, + bootstrap_txids: Vec, + ) -> BitcoinIndexerResult where Self: Sized, { let client = Box::new(BitcoinClient::new(rpc_url, network, Auth::None).await?); let parser = MessageParser::new(network); - let tx = client.get_rpc_client().get_transaction(txid).await?; - - let mut init_msgs = parser.parse_transaction(&tx); - - if let Some(Message::SystemBootstrapping(system_bootstrapping)) = init_msgs.pop() { - Ok(Self { - client, - parser, - bridge_address: Some(system_bootstrapping.input.bridge_p2wpkh_mpc_address), - verifier_addresses: system_bootstrapping.input.verifier_addresses, - starting_block_number: system_bootstrapping.input.start_block_height, - sequencer_address: None, - }) + let mut bootstrap_state = BootstrapState::new(); + + for txid in bootstrap_txids { + let tx = client.get_rpc_client().get_transaction(&txid).await?; + let messages = parser.parse_transaction(&tx); + + for message in messages { + Self::process_bootstrap_message(&mut bootstrap_state, message)?; + } + + if bootstrap_state.is_complete() { + break; + } + } + + if bootstrap_state.is_complete() { + if let (Some(bridge), Some(sequencer)) = ( + bootstrap_state.bridge_address, + bootstrap_state.proposed_sequencer, + ) { + Ok(Self { + client, + parser, + bridge_address: bridge, + sequencer_address: sequencer, + verifier_addresses: bootstrap_state.verifier_addresses, + starting_block_number: bootstrap_state.starting_block_number, + }) + } else { + Err(BitcoinError::Other( + "Incomplete bootstrap process despite state being marked as complete" + .to_string(), + )) + } } else { Err(BitcoinError::Other( - "Indexer error: provided txid does not contain SystemBootstrapping message" - .to_string(), + "Bootstrap process did not complete with provided transactions".to_string(), )) } } @@ -106,33 +170,83 @@ impl BitcoinInscriptionIndexer { fn is_valid_message(&self, message: &Message) -> bool { match message { - Message::ProposeSequencer(m) => self - .verifier_addresses - .contains(&self.get_sender_address(&m.common)), - Message::ValidatorAttestation(m) => self - .verifier_addresses - .contains(&self.get_sender_address(&m.common)), + Message::ProposeSequencer(m) => { + if let Ok(sender) = Self::get_sender_address(&m.common) { + self.verifier_addresses.contains(&sender) + } else { + false + } + } + Message::ValidatorAttestation(m) => { + if let Ok(sender) = Self::get_sender_address(&m.common) { + self.verifier_addresses.contains(&sender) + } else { + false + } + } Message::L1BatchDAReference(m) => { - Some(&self.get_sender_address(&m.common)) == self.sequencer_address.as_ref() + if let Ok(sender) = Self::get_sender_address(&m.common) { + sender == self.sequencer_address + } else { + false + } } Message::ProofDAReference(m) => { - Some(&self.get_sender_address(&m.common)) == self.sequencer_address.as_ref() + if let Ok(sender) = Self::get_sender_address(&m.common) { + sender == self.sequencer_address + } else { + false + } } Message::L1ToL2Message(m) => m.amount > bitcoin::Amount::ZERO, Message::SystemBootstrapping(_) => true, } } - fn get_sender_address(&self, common_fields: &CommonFields) -> Address { + fn process_bootstrap_message( + state: &mut BootstrapState, + message: Message, + ) -> BitcoinIndexerResult<()> { + match message { + Message::SystemBootstrapping(sb) => { + state.verifier_addresses = sb.input.verifier_addresses; + state.bridge_address = Some(sb.input.bridge_p2wpkh_mpc_address); + state.starting_block_number = sb.input.start_block_height; + } + Message::ProposeSequencer(ps) => { + if let Ok(sender) = Self::get_sender_address(&ps.common) { + if state.verifier_addresses.contains(&sender) { + state.proposed_sequencer = Some(ps.input.sequencer_p2wpkh_address); + } + } + } + Message::ValidatorAttestation(va) => { + if state.proposed_sequencer.is_some() { + if let Ok(sender) = Self::get_sender_address(&va.common) { + if state.verifier_addresses.contains(&sender) { + state.sequencer_votes.insert(sender, va.input.attestation); + } + } + } + } + _ => { + return Err(BitcoinError::Other( + "Unexpected message during bootstrap".to_string(), + )) + } + } + Ok(()) + } + + fn get_sender_address(common_fields: &CommonFields) -> BitcoinIndexerResult
{ let public_key = secp256k1::XOnlyPublicKey::from_slice(&common_fields.encoded_public_key.as_bytes()) - .map_err(|_| BitcoinError::Other("Invalid public key".to_string())) - .unwrap(); - Address::p2tr( + .map_err(|_| BitcoinError::Other("Invalid public key".to_string()))?; + Ok(Address::p2tr( &bitcoin::secp256k1::Secp256k1::new(), public_key, None, - KnownHrp::from(self.client.get_network()), - ) + KnownHrp::from(Network::Testnet), // TODO: make it configurable + )) } } diff --git a/core/lib/via_btc_client/src/traits.rs b/core/lib/via_btc_client/src/traits.rs index 2b9fae44b71c..d18cf1324d41 100644 --- a/core/lib/via_btc_client/src/traits.rs +++ b/core/lib/via_btc_client/src/traits.rs @@ -85,7 +85,11 @@ pub trait BitcoinInscriber: Send + Sync { #[allow(dead_code)] #[async_trait] pub trait BitcoinIndexerOpt: Send + Sync { - async fn new(rpc_url: &str, network: Network, txid: &Txid) -> BitcoinIndexerResult + async fn new( + rpc_url: &str, + network: Network, + bootstrap_txids: Vec, + ) -> BitcoinIndexerResult where Self: Sized; async fn process_blocks( From 2df3b12d28448324249fdb939cc3b29612b361a0 Mon Sep 17 00:00:00 2001 From: steph-mipt Date: Fri, 16 Aug 2024 14:50:12 +0200 Subject: [PATCH 07/12] fix: merge errors [ci skip] --- core/lib/via_btc_client/src/client/mod.rs | 18 ++++-- core/lib/via_btc_client/src/indexer/mod.rs | 50 +++++++-------- core/lib/via_btc_client/src/indexer/parser.rs | 6 +- core/lib/via_btc_client/src/inscriber/mod.rs | 12 ++-- core/lib/via_btc_client/src/traits.rs | 55 ++++++++++------ core/lib/via_btc_client/src/types.rs | 64 +++---------------- 6 files changed, 89 insertions(+), 116 deletions(-) diff --git a/core/lib/via_btc_client/src/client/mod.rs b/core/lib/via_btc_client/src/client/mod.rs index 9dd869974f5b..2b8efa2eb872 100644 --- a/core/lib/via_btc_client/src/client/mod.rs +++ b/core/lib/via_btc_client/src/client/mod.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use bitcoin::{Address, Network, OutPoint, TxOut, Txid}; +use bitcoin::{Address, OutPoint, TxOut, Txid, Block, Transaction, BlockHash}; use bitcoincore_rpc::json::EstimateMode; use crate::{ @@ -78,6 +78,10 @@ impl BitcoinOps for BitcoinClient { self.rpc.get_block_by_height(block_height).await } + async fn fetch_block_by_hash(&self, block_hash: &BlockHash) -> BitcoinClientResult { + self.rpc.get_block_by_hash(block_hash).await + } + async fn get_fee_rate(&self, conf_target: u16) -> BitcoinClientResult { let estimation = self .rpc @@ -96,6 +100,10 @@ impl BitcoinOps for BitcoinClient { } } + async fn get_transaction(&self, txid: &Txid) -> BitcoinClientResult { + self.rpc.get_transaction(txid).await + } + fn get_network(&self) -> Network { self.network } @@ -122,7 +130,7 @@ mod tests { #[tokio::test] async fn test_get_balance() { let context = BitcoinRegtest::new().expect("Failed to create BitcoinRegtest"); - let _client = BitcoinClient::new(&context.get_url(), Network::Regtest) + let _client = BitcoinClient::new(&context.get_url(), Network::Regtest, Auth::None) .await .expect("Failed to create BitcoinClient"); @@ -139,7 +147,7 @@ mod tests { #[tokio::test] async fn test_fetch_utxos() { let context = BitcoinRegtest::new().expect("Failed to create BitcoinRegtest"); - let _client = BitcoinClient::new(&context.get_url(), Network::Regtest) + let _client = BitcoinClient::new(&context.get_url(), Network::Regtest, Auth::None) .await .expect("Failed to create BitcoinClient"); @@ -155,7 +163,7 @@ mod tests { #[tokio::test] async fn test_fetch_block_height() { let context = BitcoinRegtest::new().expect("Failed to create BitcoinRegtest"); - let client = BitcoinClient::new(&context.get_url(), Network::Regtest) + let client = BitcoinClient::new(&context.get_url(), Network::Regtest, Auth::None) .await .expect("Failed to create BitcoinClient"); @@ -171,7 +179,7 @@ mod tests { #[tokio::test] async fn test_estimate_fee() { let context = BitcoinRegtest::new().expect("Failed to create BitcoinRegtest"); - let client = BitcoinClient::new(&context.get_url(), Network::Regtest) + let client = BitcoinClient::new(&context.get_url(), Network::Regtest, Auth::None) .await .expect("Failed to create BitcoinClient"); diff --git a/core/lib/via_btc_client/src/indexer/mod.rs b/core/lib/via_btc_client/src/indexer/mod.rs index 177a0052a508..7db3f6ae507c 100644 --- a/core/lib/via_btc_client/src/indexer/mod.rs +++ b/core/lib/via_btc_client/src/indexer/mod.rs @@ -9,8 +9,8 @@ use parser::MessageParser; use crate::{ client::BitcoinClient, - traits::BitcoinIndexerOpt, - types::{BitcoinError, BitcoinIndexerResult, CommonFields, Message, Vote}, + traits::BitcoinInscriptionIndexerOpt, + types::{BitcoinError, BitcoinIndexerResult, CommonFields, FullInscriptionMessage, Vote}, BitcoinOps, }; @@ -62,7 +62,7 @@ pub struct BitcoinInscriptionIndexer { } #[async_trait] -impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { +impl BitcoinInscriptionIndexerOpt for BitcoinInscriptionIndexer { async fn new( rpc_url: &str, network: Network, @@ -76,7 +76,7 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { let mut bootstrap_state = BootstrapState::new(); for txid in bootstrap_txids { - let tx = client.get_rpc_client().get_transaction(&txid).await?; + let tx = client.get_transaction(&txid).await?; let messages = parser.parse_transaction(&tx); for message in messages { @@ -118,7 +118,7 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { &self, starting_block: u32, ending_block: u32, - ) -> BitcoinIndexerResult> { + ) -> BitcoinIndexerResult> { let mut res = Vec::with_capacity((ending_block - starting_block + 1) as usize); for block in starting_block..=ending_block { res.extend(self.process_block(block).await?); @@ -126,8 +126,8 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { Ok(res) } - async fn process_block(&self, block: u32) -> BitcoinIndexerResult> { - if block < self.starting_block_number { + async fn process_block(&self, block_height: u32) -> BitcoinIndexerResult> { + if block_height < self.starting_block_number { return Err(BitcoinError::Other( "Indexer error: can't get block before starting block".to_string(), )); @@ -135,11 +135,10 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { let block = self .client - .get_rpc_client() - .get_block_by_height(block as u128) + .fetch_block(block_height as u128) .await?; - let messages: Vec = block + let messages: Vec = block .txdata .iter() .flat_map(|tx| self.process_tx(tx)) @@ -156,71 +155,70 @@ impl BitcoinIndexerOpt for BitcoinInscriptionIndexer { ) -> BitcoinIndexerResult { let child_block = self .client - .get_rpc_client() - .get_block_by_hash(child_hash) + .fetch_block_by_hash(child_hash) .await?; Ok(child_block.header.prev_blockhash == *parent_hash) } } impl BitcoinInscriptionIndexer { - fn process_tx(&self, tx: &Transaction) -> Vec { + fn process_tx(&self, tx: &Transaction) -> Vec { self.parser.parse_transaction(tx) } - fn is_valid_message(&self, message: &Message) -> bool { + fn is_valid_message(&self, message: &FullInscriptionMessage) -> bool { match message { - Message::ProposeSequencer(m) => { + FullInscriptionMessage::ProposeSequencer(m) => { if let Ok(sender) = Self::get_sender_address(&m.common) { self.verifier_addresses.contains(&sender) } else { false } } - Message::ValidatorAttestation(m) => { + FullInscriptionMessage::ValidatorAttestation(m) => { if let Ok(sender) = Self::get_sender_address(&m.common) { self.verifier_addresses.contains(&sender) } else { false } } - Message::L1BatchDAReference(m) => { + FullInscriptionMessage::L1BatchDAReference(m) => { if let Ok(sender) = Self::get_sender_address(&m.common) { sender == self.sequencer_address } else { false } } - Message::ProofDAReference(m) => { + FullInscriptionMessage::ProofDAReference(m) => { if let Ok(sender) = Self::get_sender_address(&m.common) { sender == self.sequencer_address } else { false } } - Message::L1ToL2Message(m) => m.amount > bitcoin::Amount::ZERO, - Message::SystemBootstrapping(_) => true, + FullInscriptionMessage::L1ToL2Message(m) => m.amount > bitcoin::Amount::ZERO, + FullInscriptionMessage::SystemBootstrapping(_) => true, } } fn process_bootstrap_message( state: &mut BootstrapState, - message: Message, + message: FullInscriptionMessage, ) -> BitcoinIndexerResult<()> { match message { - Message::SystemBootstrapping(sb) => { - state.verifier_addresses = sb.input.verifier_addresses; + FullInscriptionMessage::SystemBootstrapping(sb) => { + state.verifier_addresses = sb.input.verifier_p2wpkh_addresses; state.bridge_address = Some(sb.input.bridge_p2wpkh_mpc_address); state.starting_block_number = sb.input.start_block_height; } - Message::ProposeSequencer(ps) => { + FullInscriptionMessage::ProposeSequencer(ps) => { if let Ok(sender) = Self::get_sender_address(&ps.common) { if state.verifier_addresses.contains(&sender) { - state.proposed_sequencer = Some(ps.input.sequencer_p2wpkh_address); + state.proposed_sequencer = Some(ps.input.sequencer_new_p2wpkh_address); } } } - Message::ValidatorAttestation(va) => { + FullInscriptionMessage::ValidatorAttestation(va) => { if state.proposed_sequencer.is_some() { if let Ok(sender) = Self::get_sender_address(&va.common) { if state.verifier_addresses.contains(&sender) { diff --git a/core/lib/via_btc_client/src/indexer/parser.rs b/core/lib/via_btc_client/src/indexer/parser.rs index 6d1e27776493..2b4225266b63 100644 --- a/core/lib/via_btc_client/src/indexer/parser.rs +++ b/core/lib/via_btc_client/src/indexer/parser.rs @@ -11,7 +11,7 @@ use zksync_types::{Address as EVMAddress, L1BatchNumber}; use crate::types::{ CommonFields, L1BatchDAReference, L1BatchDAReferenceInput, L1ToL2Message, L1ToL2MessageInput, - Message, ProofDAReference, ProofDAReferenceInput, ProposeSequencer, ProposeSequencerInput, + FullInscriptionMessage as Message, ProofDAReference, ProofDAReferenceInput, ProposeSequencer, ProposeSequencerInput, SystemBootstrapping, SystemBootstrappingInput, ValidatorAttestation, ValidatorAttestationInput, Vote, }; @@ -150,8 +150,8 @@ impl MessageParser { common: common_fields.clone(), input: SystemBootstrappingInput { start_block_height, - verifier_addresses, bridge_p2wpkh_mpc_address: bridge_address, + verifier_p2wpkh_addresses: verifier_addresses, }, })) } @@ -183,7 +183,7 @@ impl MessageParser { Some(Message::ProposeSequencer(ProposeSequencer { common: common_fields.clone(), input: ProposeSequencerInput { - sequencer_p2wpkh_address: sequencer_address, + sequencer_new_p2wpkh_address: sequencer_address, }, })) } diff --git a/core/lib/via_btc_client/src/inscriber/mod.rs b/core/lib/via_btc_client/src/inscriber/mod.rs index 4a9e5216b493..390de15c9c67 100644 --- a/core/lib/via_btc_client/src/inscriber/mod.rs +++ b/core/lib/via_btc_client/src/inscriber/mod.rs @@ -8,9 +8,9 @@ use bitcoin::{ absolute, transaction, Address, Amount, EcdsaSighashType, OutPoint, ScriptBuf, Sequence, TapLeafHash, TapSighashType, Transaction, TxIn, TxOut, Witness, }; -use bitcoincore_rpc::RawTx; +use bitcoincore_rpc::{Auth, RawTx}; use secp256k1::Message; -use std::collections::{HashMap, VecDeque}; +use std::collections::HashMap; use crate::client::BitcoinClient; use crate::inscriber::fee::InscriberFeeCalculator; @@ -58,15 +58,13 @@ impl Inscriber { pub async fn new( rpc_url: &str, network: BitcoinNetwork, + auth: Auth, signer_private_key: &str, persisted_ctx: Option, ) -> Result { - let client = Box::new(BitcoinClient::new(rpc_url, network).await?); + let client = Box::new(BitcoinClient::new(rpc_url, network, auth).await?); let signer = Box::new(KeyManager::new(signer_private_key, network)?); - let context = match persisted_ctx { - Some(ctx) => ctx, - None => types::InscriberContext::new(), - }; + let context = persisted_ctx.unwrap_or_else(|| types::InscriberContext::new()); Ok(Self { client, diff --git a/core/lib/via_btc_client/src/traits.rs b/core/lib/via_btc_client/src/traits.rs index eb6bc6631ecb..bca6fc5c0706 100644 --- a/core/lib/via_btc_client/src/traits.rs +++ b/core/lib/via_btc_client/src/traits.rs @@ -1,14 +1,15 @@ use async_trait::async_trait; -use bitcoin::{Address, Block, BlockHash, Network, OutPoint, Transaction, TxOut, Txid}; +use bitcoin::{Address, Block, BlockHash, Network, OutPoint, Transaction, TxOut, Txid, ScriptBuf}; use bitcoincore_rpc::Auth; use crate::types; -use async_trait::async_trait; +use types::{BitcoinRpcResult}; use bitcoin::key::UntweakedPublicKey; use bitcoin::secp256k1::{All, Secp256k1}; use secp256k1::ecdsa::Signature as ECDSASignature; use secp256k1::schnorr::Signature as SchnorrSignature; use secp256k1::{Message, PublicKey}; +use crate::types::{BitcoinClientResult, BitcoinIndexerResult, FullInscriptionMessage}; #[allow(dead_code)] #[async_trait] @@ -32,10 +33,12 @@ pub trait BitcoinOps: Send + Sync { conf_num: u32, ) -> types::BitcoinClientResult; async fn fetch_block_height(&self) -> types::BitcoinClientResult; - async fn fetch_and_parse_block(&self, block_height: u128) - -> types::BitcoinClientResult; async fn get_fee_rate(&self, conf_target: u16) -> types::BitcoinClientResult; fn get_network(&self) -> Network; + async fn fetch_block(&self, block_height: u128) -> BitcoinClientResult; + + async fn get_transaction(&self, txid: &Txid) -> BitcoinClientResult; + async fn fetch_block_by_hash(&self, block_hash: &BlockHash) -> BitcoinClientResult; } #[allow(dead_code)] @@ -82,30 +85,40 @@ pub trait BitcoinSigner: Send + Sync { fn get_public_key(&self) -> PublicKey; } -#[allow(dead_code)] -#[async_trait] -pub trait BitcoinInscriber: Send + Sync { - async fn new(config: &str) -> BitcoinInscriberResult - where - Self: Sized; - async fn inscribe(&self, message_type: &str, data: &str) -> BitcoinInscriberResult; -} + +// #[allow(dead_code)] +// #[async_trait] +// pub trait BitcoinInscriber: Send + Sync { +// async fn new(config: &str) -> BitcoinInscriberResult +// where +// Self: Sized; +// async fn inscribe(&self, message_type: &str, data: &str) -> BitcoinInscriberResult; +// } #[allow(dead_code)] #[async_trait] -pub trait BitcoinInscriptionIndexer: Send + Sync { - async fn new(config: &str) -> types::BitcoinIndexerResult - where +pub trait BitcoinInscriptionIndexerOpt: Send + Sync { + async fn new( + rpc_url: &str, + network: Network, + bootstrap_txids: Vec, + ) -> BitcoinIndexerResult + where Self: Sized; async fn process_blocks( &self, - starting_block: u128, - ending_block: u128, - ) -> types::BitcoinIndexerResult>; - async fn get_specific_block_inscription_messages( + starting_block: u32, + ending_block: u32, + ) -> BitcoinIndexerResult>; + async fn process_block( + &self, + block_height: u32, + ) -> BitcoinIndexerResult>; + async fn are_blocks_connected( &self, - block_height: u128, - ) -> types::BitcoinIndexerResult>; + parent_hash: &BlockHash, + child_hash: &BlockHash, + ) -> BitcoinIndexerResult; } #[allow(dead_code)] diff --git a/core/lib/via_btc_client/src/types.rs b/core/lib/via_btc_client/src/types.rs index 7109bb059d5d..133a7ececc3f 100644 --- a/core/lib/via_btc_client/src/types.rs +++ b/core/lib/via_btc_client/src/types.rs @@ -9,22 +9,12 @@ use thiserror::Error; use zksync_basic_types::H256; use zksync_types::{Address as EVMAddress, L1BatchNumber}; -#[derive(Serialize, Deserialize)] -pub enum BitcoinMessage {} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Vote { Ok, NotOk, } -#[derive(Clone, Debug, PartialEq)] -pub struct CommonFields { - pub schnorr_signature: TaprootSignature, - pub encoded_public_key: PushBytesBuf, - pub via_inscription_protocol_identifier: String, -} - #[derive(Clone, Debug, PartialEq)] pub struct L1BatchDAReferenceInput { pub l1_batch_hash: H256, @@ -63,17 +53,6 @@ pub struct ValidatorAttestation { pub common: CommonFields, pub input: ValidatorAttestationInput, } -use zksync_basic_types::H256; -use zksync_types::{Address as EVMAddress, L1BatchNumber}; - -#[derive(Serialize, Deserialize)] -pub enum BitcoinMessage {} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum Vote { - Ok, - NotOk, -} #[derive(Clone, Debug, PartialEq)] pub struct CommonFields { @@ -81,39 +60,6 @@ pub struct CommonFields { pub encoded_public_key: PushBytesBuf, } -#[derive(Clone, Debug, PartialEq)] -pub struct L1BatchDAReferenceInput { - pub l1_batch_hash: H256, - pub l1_batch_index: L1BatchNumber, - pub da_identifier: String, - pub blob_id: String, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct L1BatchDAReference { - pub common: CommonFields, - pub input: L1BatchDAReferenceInput, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct ProofDAReferenceInput { - pub l1_batch_reveal_txid: Txid, - pub da_identifier: String, - pub blob_id: String, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct ProofDAReference { - pub common: CommonFields, - pub input: ProofDAReferenceInput, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct ValidatorAttestationInput { - pub reference_txid: Txid, - pub attestation: Vote, -} - #[derive(Clone, Debug, PartialEq)] pub struct SystemBootstrappingInput { pub start_block_height: u32, @@ -163,6 +109,16 @@ pub enum InscriptionMessage { L1ToL2Message(L1ToL2MessageInput), } +#[derive(Clone, Debug, PartialEq)] +pub enum FullInscriptionMessage { + L1BatchDAReference(L1BatchDAReference), + ProofDAReference(ProofDAReference), + ValidatorAttestation(ValidatorAttestation), + SystemBootstrapping(SystemBootstrapping), + ProposeSequencer(ProposeSequencer), + L1ToL2Message(L1ToL2Message), +} + #[derive(Clone, Debug, PartialEq)] pub struct FeePayerCtx { pub fee_payer_utxo_txid: Txid, From ed80ff6db80820ce05aaa83c9e7b5808643133d1 Mon Sep 17 00:00:00 2001 From: steph-mipt Date: Fri, 16 Aug 2024 19:10:24 +0200 Subject: [PATCH 08/12] fix: review fixes [ci skip] --- core/lib/via_btc_client/src/indexer/mod.rs | 106 +++++++++--------- core/lib/via_btc_client/src/indexer/parser.rs | 48 ++++---- core/lib/via_btc_client/src/types.rs | 8 +- 3 files changed, 78 insertions(+), 84 deletions(-) diff --git a/core/lib/via_btc_client/src/indexer/mod.rs b/core/lib/via_btc_client/src/indexer/mod.rs index 7db3f6ae507c..57b202c01ba5 100644 --- a/core/lib/via_btc_client/src/indexer/mod.rs +++ b/core/lib/via_btc_client/src/indexer/mod.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use async_trait::async_trait; -use bitcoin::{Address, BlockHash, KnownHrp, Network, Transaction, Txid}; +use bitcoin::{Address, BlockHash, KnownHrp, Network, Txid}; use bitcoincore_rpc::Auth; mod parser; @@ -10,13 +10,15 @@ use parser::MessageParser; use crate::{ client::BitcoinClient, traits::BitcoinInscriptionIndexerOpt, - types::{BitcoinError, BitcoinIndexerResult, CommonFields, FullInscriptionMessage, Vote}, + types::{BitcoinError, BitcoinIndexerResult, FullInscriptionMessage, Vote}, BitcoinOps, }; +use crate::types::{CommonFields, L1ToL2Message}; struct BootstrapState { verifier_addresses: Vec
, proposed_sequencer: Option
, + proposed_sequencer_txid: Option, sequencer_votes: HashMap, bridge_address: Option
, starting_block_number: u32, @@ -27,6 +29,7 @@ impl BootstrapState { Self { verifier_addresses: Vec::new(), proposed_sequencer: None, + proposed_sequencer_txid: None, sequencer_votes: HashMap::new(), bridge_address: None, starting_block_number: 0, @@ -68,8 +71,8 @@ impl BitcoinInscriptionIndexerOpt for BitcoinInscriptionIndexer { network: Network, bootstrap_txids: Vec, ) -> BitcoinIndexerResult - where - Self: Sized, + where + Self: Sized, { let client = Box::new(BitcoinClient::new(rpc_url, network, Auth::None).await?); let parser = MessageParser::new(network); @@ -80,7 +83,7 @@ impl BitcoinInscriptionIndexerOpt for BitcoinInscriptionIndexer { let messages = parser.parse_transaction(&tx); for message in messages { - Self::process_bootstrap_message(&mut bootstrap_state, message)?; + Self::process_bootstrap_message(&mut bootstrap_state, message, txid); } if bootstrap_state.is_complete() { @@ -141,7 +144,7 @@ impl BitcoinInscriptionIndexerOpt for BitcoinInscriptionIndexer { let messages: Vec = block .txdata .iter() - .flat_map(|tx| self.process_tx(tx)) + .flat_map(|tx| self.parser.parse_transaction(tx)) .filter(|message| self.is_valid_message(message)) .collect(); @@ -162,41 +165,27 @@ impl BitcoinInscriptionIndexerOpt for BitcoinInscriptionIndexer { } impl BitcoinInscriptionIndexer { - fn process_tx(&self, tx: &Transaction) -> Vec { - self.parser.parse_transaction(tx) - } - fn is_valid_message(&self, message: &FullInscriptionMessage) -> bool { match message { FullInscriptionMessage::ProposeSequencer(m) => { - if let Ok(sender) = Self::get_sender_address(&m.common) { - self.verifier_addresses.contains(&sender) - } else { - false - } + Self::get_sender_address(&m.common) + .map_or(false, |addr| self.verifier_addresses.contains(&addr)) } FullInscriptionMessage::ValidatorAttestation(m) => { - if let Ok(sender) = Self::get_sender_address(&m.common) { - self.verifier_addresses.contains(&sender) - } else { - false - } + Self::get_sender_address(&m.common) + .map_or(false, |addr| self.verifier_addresses.contains(&addr)) } FullInscriptionMessage::L1BatchDAReference(m) => { - if let Ok(sender) = Self::get_sender_address(&m.common) { - sender == self.sequencer_address - } else { - false - } + Self::get_sender_address(&m.common) + .map_or(false, |addr| addr == self.sequencer_address) } FullInscriptionMessage::ProofDAReference(m) => { - if let Ok(sender) = Self::get_sender_address(&m.common) { - sender == self.sequencer_address - } else { - false - } + Self::get_sender_address(&m.common) + .map_or(false, |addr| addr == self.sequencer_address) + } + FullInscriptionMessage::L1ToL2Message(m) => { + m.amount > bitcoin::Amount::ZERO && self.is_valid_l1_to_l2_transfer(m) // check the bridge address in the output } - FullInscriptionMessage::L1ToL2Message(m) => m.amount > bitcoin::Amount::ZERO, FullInscriptionMessage::SystemBootstrapping(_) => true, } } @@ -204,7 +193,8 @@ impl BitcoinInscriptionIndexer { fn process_bootstrap_message( state: &mut BootstrapState, message: FullInscriptionMessage, - ) -> BitcoinIndexerResult<()> { + txid: Txid, + ) { match message { FullInscriptionMessage::SystemBootstrapping(sb) => { state.verifier_addresses = sb.input.verifier_p2wpkh_addresses; @@ -212,39 +202,47 @@ impl BitcoinInscriptionIndexer { state.starting_block_number = sb.input.start_block_height; } FullInscriptionMessage::ProposeSequencer(ps) => { - if let Ok(sender) = Self::get_sender_address(&ps.common) { - if state.verifier_addresses.contains(&sender) { + if let Some(sender_address) = Self::get_sender_address(&ps.common) { + if state.verifier_addresses.contains(&sender_address) { state.proposed_sequencer = Some(ps.input.sequencer_new_p2wpkh_address); + state.proposed_sequencer_txid = Some(txid); } } } FullInscriptionMessage::ValidatorAttestation(va) => { if state.proposed_sequencer.is_some() { - if let Ok(sender) = Self::get_sender_address(&va.common) { - if state.verifier_addresses.contains(&sender) { - state.sequencer_votes.insert(sender, va.input.attestation); + if let Some(sender_address) = Self::get_sender_address(&va.common) { + if state.verifier_addresses.contains(&sender_address) { + // check if this is an attestation for the proposed sequencer + if let Some(proposed_txid) = state.proposed_sequencer_txid { + if va.input.reference_txid == proposed_txid { + state.sequencer_votes.insert(sender_address, va.input.attestation); + } + } } } } } - _ => { - return Err(BitcoinError::Other( - "Unexpected message during bootstrap".to_string(), - )) - } + _ => {} // ignore other messages } - Ok(()) } - fn get_sender_address(common_fields: &CommonFields) -> BitcoinIndexerResult
{ - let public_key = - secp256k1::XOnlyPublicKey::from_slice(&common_fields.encoded_public_key.as_bytes()) - .map_err(|_| BitcoinError::Other("Invalid public key".to_string()))?; - Ok(Address::p2tr( - &bitcoin::secp256k1::Secp256k1::new(), - public_key, - None, - KnownHrp::from(Network::Testnet), // TODO: make it configurable - )) + fn get_sender_address(common_fields: &CommonFields) -> Option
{ + secp256k1::XOnlyPublicKey::from_slice(&common_fields.encoded_public_key.as_bytes()) + .ok() + .map(|public_key| { + Address::p2tr( + &bitcoin::secp256k1::Secp256k1::new(), + public_key, + None, + KnownHrp::from(Network::Testnet), // TODO: make it configurable + ) + }) } -} + + fn is_valid_l1_to_l2_transfer(&self, message: &L1ToL2Message) -> bool { + message.tx_outputs.iter().any(|output| { + output.script_pubkey == self.bridge_address.script_pubkey() + }) + } +} \ No newline at end of file diff --git a/core/lib/via_btc_client/src/indexer/parser.rs b/core/lib/via_btc_client/src/indexer/parser.rs index 2b4225266b63..a406030b3ea3 100644 --- a/core/lib/via_btc_client/src/indexer/parser.rs +++ b/core/lib/via_btc_client/src/indexer/parser.rs @@ -2,8 +2,7 @@ use bitcoin::{ address::NetworkUnchecked, hashes::Hash, script::{Instruction, PushBytesBuf}, - secp256k1::XOnlyPublicKey, - taproot::Signature as TaprootSignature, + taproot::{ControlBlock, Signature as TaprootSignature}, Address, Amount, Network, ScriptBuf, Transaction, Txid, }; use zksync_basic_types::H256; @@ -16,7 +15,9 @@ use crate::types::{ Vote, }; -// TODO: make this a trait (ViaProtocolParser_V1, ViaProtocolParser_V2, etc) +const MIN_WITNESS_LENGTH: usize = 3; +const VIA_INSCRIPTION_PROTOCOL: &str = "via_inscription_protocol"; + pub struct MessageParser { network: Network, } @@ -35,18 +36,22 @@ impl MessageParser { fn parse_input(&self, input: &bitcoin::TxIn, tx: &Transaction) -> Option { let witness = &input.witness; - if witness.len() < 3 { + // A valid P2TR input for our inscriptions should have at least 3 witness elements: + // 1. Signature + // 2. Inscription script + // 3. Control block + if witness.len() < MIN_WITNESS_LENGTH { return None; } let signature = TaprootSignature::from_slice(&witness[0]).ok()?; - let public_key = XOnlyPublicKey::from_slice(&witness[1]).ok()?; - let script = ScriptBuf::from_bytes(witness.last()?.to_vec()); + let script = ScriptBuf::from_bytes(witness[1].to_vec()); + let control_block = ControlBlock::decode(&witness[2]).ok()?; let instructions: Vec<_> = script.instructions().filter_map(Result::ok).collect(); - let via_index = is_via_inscription_protocol(&instructions)?; + let via_index = find_via_inscription_protocol(&instructions)?; - // TODO: not to pass common fields around + let public_key = control_block.internal_key; let common_fields = CommonFields { schnorr_signature: signature, encoded_public_key: PushBytesBuf::from(public_key.serialize()), @@ -64,30 +69,22 @@ impl MessageParser { let message_type = instructions.get(1)?; match message_type { - Instruction::PushBytes(bytes) - if bytes.as_bytes() == b"Str('SystemBootstrappingMessage')" => - { + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"SystemBootstrappingMessage" => { self.parse_system_bootstrapping(instructions, common_fields) } - Instruction::PushBytes(bytes) - if bytes.as_bytes() == b"Str('ProposeSequencerMessage')" => - { + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"ProposeSequencerMessage" => { self.parse_propose_sequencer(instructions, common_fields) } - Instruction::PushBytes(bytes) - if bytes.as_bytes() == b"Str('ValidatorAttestationMessage')" => - { + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"ValidatorAttestationMessage" => { self.parse_validator_attestation(instructions, common_fields) } - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('L1BatchDAReference')" => { + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"L1BatchDAReference" => { self.parse_l1_batch_da_reference(instructions, common_fields) } - Instruction::PushBytes(bytes) - if bytes.as_bytes() == b"Str('ProofDAReferenceMessage')" => - { + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"ProofDAReferenceMessage" => { self.parse_proof_da_reference(instructions, common_fields) } - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('L1ToL2Message')" => { + Instruction::PushBytes(bytes) if bytes.as_bytes() == b"L1ToL2Message" => { self.parse_l1_to_l2_message(tx, instructions, common_fields) } _ => None, @@ -309,13 +306,14 @@ impl MessageParser { l2_contract_address, call_data, }, + tx_outputs: tx.output.clone(), // include all transaction outputs })) } } -fn is_via_inscription_protocol(instructions: &[Instruction]) -> Option { +fn find_via_inscription_protocol(instructions: &[Instruction]) -> Option { // TODO: also check first part of the script (OP_CHECKSIG and other stuff) instructions.iter().position(|instr| { - matches!(instr, Instruction::PushBytes(bytes) if bytes.as_bytes() == b"Str('via_inscription_protocol')") + matches!(instr, Instruction::PushBytes(bytes) if bytes.as_bytes() == VIA_INSCRIPTION_PROTOCOL.as_bytes()) }) -} +} \ No newline at end of file diff --git a/core/lib/via_btc_client/src/types.rs b/core/lib/via_btc_client/src/types.rs index 133a7ececc3f..ff75469f9c73 100644 --- a/core/lib/via_btc_client/src/types.rs +++ b/core/lib/via_btc_client/src/types.rs @@ -1,9 +1,6 @@ use std::collections::VecDeque; -use bitcoin::{ - script::PushBytesBuf, taproot::Signature as TaprootSignature, Address as BitcoinAddress, - Amount, TxIn, Txid, -}; +use bitcoin::{script::PushBytesBuf, taproot::Signature as TaprootSignature, Address as BitcoinAddress, Amount, TxIn, Txid, TxOut}; use serde::{Deserialize, Serialize}; use thiserror::Error; use zksync_basic_types::H256; @@ -96,6 +93,7 @@ pub struct L1ToL2Message { pub common: CommonFields, pub amount: Amount, pub input: L1ToL2MessageInput, + pub tx_outputs: Vec, } #[allow(unused)] @@ -239,7 +237,7 @@ impl From for BitcoinError { } pub type BitcoinSignerResult = Result; -pub type BitcoinInscriberResult = Result; +// pub type BitcoinInscriberResult = Result; pub type BitcoinIndexerResult = Result; #[allow(unused)] pub type BitcoinTransactionBuilderResult = Result; From bc5c211067c285440f1b92227bc9793adc3013b6 Mon Sep 17 00:00:00 2001 From: steph-mipt Date: Fri, 16 Aug 2024 19:13:25 +0200 Subject: [PATCH 09/12] chore: `zk fmt` & `clippy` fixes [ci skip] --- core/lib/via_btc_client/DEV.md | 3 +- core/lib/via_btc_client/src/client/mod.rs | 2 +- core/lib/via_btc_client/src/indexer/mod.rs | 67 +++++++++---------- core/lib/via_btc_client/src/indexer/parser.rs | 42 +++++------- core/lib/via_btc_client/src/inscriber/mod.rs | 46 +++++++------ .../src/inscriber/script_builder.rs | 19 +++--- core/lib/via_btc_client/src/signer/mod.rs | 6 +- core/lib/via_btc_client/src/traits.rs | 24 ++++--- core/lib/via_btc_client/src/types.rs | 5 +- 9 files changed, 108 insertions(+), 106 deletions(-) diff --git a/core/lib/via_btc_client/DEV.md b/core/lib/via_btc_client/DEV.md index 9b25125023ae..4d7d2abc8978 100644 --- a/core/lib/via_btc_client/DEV.md +++ b/core/lib/via_btc_client/DEV.md @@ -71,6 +71,7 @@ Write integration tests in the `examples` directory. - Only make methods public that are needed by external users. ## Taproot Script witness data for via inscription standard + ``` Witness Structure for each message type in our case da_identifier is b"celestia" @@ -101,7 +102,7 @@ Votable : No (2) -Propose Sequencer +Propose Sequencer verifier should sent attestation to network to validate this message Sender Validation: one of the verifiers Votable: Yes diff --git a/core/lib/via_btc_client/src/client/mod.rs b/core/lib/via_btc_client/src/client/mod.rs index 2b8efa2eb872..841c53f52a1c 100644 --- a/core/lib/via_btc_client/src/client/mod.rs +++ b/core/lib/via_btc_client/src/client/mod.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use bitcoin::{Address, OutPoint, TxOut, Txid, Block, Transaction, BlockHash}; +use bitcoin::{Address, Block, BlockHash, OutPoint, Transaction, TxOut, Txid}; use bitcoincore_rpc::json::EstimateMode; use crate::{ diff --git a/core/lib/via_btc_client/src/indexer/mod.rs b/core/lib/via_btc_client/src/indexer/mod.rs index 57b202c01ba5..3110cc7bd316 100644 --- a/core/lib/via_btc_client/src/indexer/mod.rs +++ b/core/lib/via_btc_client/src/indexer/mod.rs @@ -10,10 +10,12 @@ use parser::MessageParser; use crate::{ client::BitcoinClient, traits::BitcoinInscriptionIndexerOpt, - types::{BitcoinError, BitcoinIndexerResult, FullInscriptionMessage, Vote}, + types::{ + BitcoinError, BitcoinIndexerResult, CommonFields, FullInscriptionMessage, L1ToL2Message, + Vote, + }, BitcoinOps, }; -use crate::types::{CommonFields, L1ToL2Message}; struct BootstrapState { verifier_addresses: Vec
, @@ -71,8 +73,8 @@ impl BitcoinInscriptionIndexerOpt for BitcoinInscriptionIndexer { network: Network, bootstrap_txids: Vec, ) -> BitcoinIndexerResult - where - Self: Sized, + where + Self: Sized, { let client = Box::new(BitcoinClient::new(rpc_url, network, Auth::None).await?); let parser = MessageParser::new(network); @@ -129,17 +131,17 @@ impl BitcoinInscriptionIndexerOpt for BitcoinInscriptionIndexer { Ok(res) } - async fn process_block(&self, block_height: u32) -> BitcoinIndexerResult> { + async fn process_block( + &self, + block_height: u32, + ) -> BitcoinIndexerResult> { if block_height < self.starting_block_number { return Err(BitcoinError::Other( "Indexer error: can't get block before starting block".to_string(), )); } - let block = self - .client - .fetch_block(block_height as u128) - .await?; + let block = self.client.fetch_block(block_height as u128).await?; let messages: Vec = block .txdata @@ -156,10 +158,7 @@ impl BitcoinInscriptionIndexerOpt for BitcoinInscriptionIndexer { parent_hash: &BlockHash, child_hash: &BlockHash, ) -> BitcoinIndexerResult { - let child_block = self - .client - .fetch_block_by_hash(child_hash) - .await?; + let child_block = self.client.fetch_block_by_hash(child_hash).await?; Ok(child_block.header.prev_blockhash == *parent_hash) } } @@ -167,24 +166,17 @@ impl BitcoinInscriptionIndexerOpt for BitcoinInscriptionIndexer { impl BitcoinInscriptionIndexer { fn is_valid_message(&self, message: &FullInscriptionMessage) -> bool { match message { - FullInscriptionMessage::ProposeSequencer(m) => { - Self::get_sender_address(&m.common) - .map_or(false, |addr| self.verifier_addresses.contains(&addr)) - } - FullInscriptionMessage::ValidatorAttestation(m) => { - Self::get_sender_address(&m.common) - .map_or(false, |addr| self.verifier_addresses.contains(&addr)) - } - FullInscriptionMessage::L1BatchDAReference(m) => { - Self::get_sender_address(&m.common) - .map_or(false, |addr| addr == self.sequencer_address) - } - FullInscriptionMessage::ProofDAReference(m) => { - Self::get_sender_address(&m.common) - .map_or(false, |addr| addr == self.sequencer_address) - } + FullInscriptionMessage::ProposeSequencer(m) => Self::get_sender_address(&m.common) + .map_or(false, |addr| self.verifier_addresses.contains(&addr)), + FullInscriptionMessage::ValidatorAttestation(m) => Self::get_sender_address(&m.common) + .map_or(false, |addr| self.verifier_addresses.contains(&addr)), + FullInscriptionMessage::L1BatchDAReference(m) => Self::get_sender_address(&m.common) + .map_or(false, |addr| addr == self.sequencer_address), + FullInscriptionMessage::ProofDAReference(m) => Self::get_sender_address(&m.common) + .map_or(false, |addr| addr == self.sequencer_address), FullInscriptionMessage::L1ToL2Message(m) => { - m.amount > bitcoin::Amount::ZERO && self.is_valid_l1_to_l2_transfer(m) // check the bridge address in the output + m.amount > bitcoin::Amount::ZERO && self.is_valid_l1_to_l2_transfer(m) + // check the bridge address in the output } FullInscriptionMessage::SystemBootstrapping(_) => true, } @@ -216,7 +208,9 @@ impl BitcoinInscriptionIndexer { // check if this is an attestation for the proposed sequencer if let Some(proposed_txid) = state.proposed_sequencer_txid { if va.input.reference_txid == proposed_txid { - state.sequencer_votes.insert(sender_address, va.input.attestation); + state + .sequencer_votes + .insert(sender_address, va.input.attestation); } } } @@ -228,7 +222,7 @@ impl BitcoinInscriptionIndexer { } fn get_sender_address(common_fields: &CommonFields) -> Option
{ - secp256k1::XOnlyPublicKey::from_slice(&common_fields.encoded_public_key.as_bytes()) + secp256k1::XOnlyPublicKey::from_slice(common_fields.encoded_public_key.as_bytes()) .ok() .map(|public_key| { Address::p2tr( @@ -241,8 +235,9 @@ impl BitcoinInscriptionIndexer { } fn is_valid_l1_to_l2_transfer(&self, message: &L1ToL2Message) -> bool { - message.tx_outputs.iter().any(|output| { - output.script_pubkey == self.bridge_address.script_pubkey() - }) + message + .tx_outputs + .iter() + .any(|output| output.script_pubkey == self.bridge_address.script_pubkey()) } -} \ No newline at end of file +} diff --git a/core/lib/via_btc_client/src/indexer/parser.rs b/core/lib/via_btc_client/src/indexer/parser.rs index a406030b3ea3..b13aea7795cf 100644 --- a/core/lib/via_btc_client/src/indexer/parser.rs +++ b/core/lib/via_btc_client/src/indexer/parser.rs @@ -9,10 +9,10 @@ use zksync_basic_types::H256; use zksync_types::{Address as EVMAddress, L1BatchNumber}; use crate::types::{ - CommonFields, L1BatchDAReference, L1BatchDAReferenceInput, L1ToL2Message, L1ToL2MessageInput, - FullInscriptionMessage as Message, ProofDAReference, ProofDAReferenceInput, ProposeSequencer, ProposeSequencerInput, - SystemBootstrapping, SystemBootstrappingInput, ValidatorAttestation, ValidatorAttestationInput, - Vote, + CommonFields, FullInscriptionMessage as Message, L1BatchDAReference, L1BatchDAReferenceInput, + L1ToL2Message, L1ToL2MessageInput, ProofDAReference, ProofDAReferenceInput, ProposeSequencer, + ProposeSequencerInput, SystemBootstrapping, SystemBootstrappingInput, ValidatorAttestation, + ValidatorAttestationInput, Vote, }; const MIN_WITNESS_LENGTH: usize = 3; @@ -115,12 +115,10 @@ impl MessageParser { .filter_map(|instr| { if let Instruction::PushBytes(bytes) = instr { std::str::from_utf8(bytes.as_bytes()).ok().and_then(|s| { - Some( - s.parse::>() - .ok()? - .require_network(self.network) - .ok()?, - ) + s.parse::>() + .ok()? + .require_network(self.network) + .ok() }) } else { None @@ -131,12 +129,10 @@ impl MessageParser { let bridge_address = instructions.last().and_then(|instr| { if let Instruction::PushBytes(bytes) = instr { std::str::from_utf8(bytes.as_bytes()).ok().and_then(|s| { - Some( - s.parse::>() - .ok()? - .require_network(self.network) - .ok()?, - ) + s.parse::>() + .ok()? + .require_network(self.network) + .ok() }) } else { None @@ -165,12 +161,10 @@ impl MessageParser { let sequencer_address = instructions.get(2).and_then(|instr| { if let Instruction::PushBytes(bytes) = instr { std::str::from_utf8(bytes.as_bytes()).ok().and_then(|s| { - Some( - s.parse::>() - .ok()? - .require_network(self.network) - .ok()?, - ) + s.parse::>() + .ok()? + .require_network(self.network) + .ok() }) } else { None @@ -306,7 +300,7 @@ impl MessageParser { l2_contract_address, call_data, }, - tx_outputs: tx.output.clone(), // include all transaction outputs + tx_outputs: tx.output.clone(), // include all transaction outputs })) } } @@ -316,4 +310,4 @@ fn find_via_inscription_protocol(instructions: &[Instruction]) -> Option instructions.iter().position(|instr| { matches!(instr, Instruction::PushBytes(bytes) if bytes.as_bytes() == VIA_INSCRIPTION_PROTOCOL.as_bytes()) }) -} \ No newline at end of file +} diff --git a/core/lib/via_btc_client/src/inscriber/mod.rs b/core/lib/via_btc_client/src/inscriber/mod.rs index 390de15c9c67..095e5eeb124a 100644 --- a/core/lib/via_btc_client/src/inscriber/mod.rs +++ b/core/lib/via_btc_client/src/inscriber/mod.rs @@ -1,30 +1,36 @@ -use crate::traits::{BitcoinOps, BitcoinSigner}; +use std::collections::HashMap; + use anyhow::{Context, Result}; -use bitcoin::hashes::Hash; -use bitcoin::sighash::{Prevouts, SighashCache}; -use bitcoin::taproot::{ControlBlock, LeafVersion}; pub use bitcoin::Network as BitcoinNetwork; use bitcoin::{ - absolute, transaction, Address, Amount, EcdsaSighashType, OutPoint, ScriptBuf, Sequence, - TapLeafHash, TapSighashType, Transaction, TxIn, TxOut, Witness, + absolute, + hashes::Hash, + sighash::{Prevouts, SighashCache}, + taproot::{ControlBlock, LeafVersion}, + transaction, Address, Amount, EcdsaSighashType, OutPoint, ScriptBuf, Sequence, TapLeafHash, + TapSighashType, Transaction, TxIn, TxOut, Witness, }; use bitcoincore_rpc::{Auth, RawTx}; use secp256k1::Message; -use std::collections::HashMap; -use crate::client::BitcoinClient; -use crate::inscriber::fee::InscriberFeeCalculator; -use crate::inscriber::script_builder::InscriptionData; -use crate::inscriber::internal_type::{ - CommitTxInputRes, CommitTxOutputRes, FinalTx, RevealTxInputRes, RevealTxOutputRes, +use crate::{ + client::BitcoinClient, + inscriber::{ + fee::InscriberFeeCalculator, + internal_type::{ + CommitTxInputRes, CommitTxOutputRes, FinalTx, RevealTxInputRes, RevealTxOutputRes, + }, + script_builder::InscriptionData, + }, + signer::KeyManager, + traits::{BitcoinOps, BitcoinSigner}, + types, + types::InscriberContext, }; -use crate::signer::KeyManager; -use crate::types::InscriberContext; -use crate::types; mod fee; -mod script_builder; mod internal_type; +mod script_builder; const CTX_REQUIRED_CONFIRMATIONS: u32 = 1; const FEE_RATE_CONF_TARGET: u16 = 1; @@ -64,7 +70,7 @@ impl Inscriber { ) -> Result { let client = Box::new(BitcoinClient::new(rpc_url, network, auth).await?); let signer = Box::new(KeyManager::new(signer_private_key, network)?); - let context = persisted_ctx.unwrap_or_else(|| types::InscriberContext::new()); + let context = persisted_ctx.unwrap_or_else(types::InscriberContext::new); Ok(Self { client, @@ -87,8 +93,7 @@ impl Inscriber { let internal_key = self.signer.get_internal_key()?; let network = self.client.get_network(); - let inscription_data = - InscriptionData::new(&input, secp_ref, internal_key, network)?; + let inscription_data = InscriptionData::new(&input, secp_ref, internal_key, network)?; let commit_tx_input_info = self.prepare_commit_tx_input().await?; @@ -182,7 +187,6 @@ impl Inscriber { let mut spent_utxos: HashMap = HashMap::new(); for i in 0..context_queue_len { - let inscription_req = self.context.fifo_queue.get(i).ok_or_else(|| { anyhow::anyhow!("Failed to get inscription request from context") @@ -333,7 +337,6 @@ impl Inscriber { input: &CommitTxInputRes, output: &CommitTxOutputRes, ) -> Result { - let mut commit_outputs: [TxOut; 2] = [TxOut::NULL, TxOut::NULL]; commit_outputs[COMMIT_TX_CHANGE_OUTPUT_INDEX as usize] = @@ -452,7 +455,6 @@ impl Inscriber { let unlock_value = fee_payer_utxo_input.1.value + reveal_p2tr_utxo_input.1.value; - let mut reveal_tx_inputs: [TxIn; 2] = [TxIn::default(), TxIn::default()]; reveal_tx_inputs[REVEAL_TX_FEE_INPUT_INDEX as usize] = fee_payer_input; diff --git a/core/lib/via_btc_client/src/inscriber/script_builder.rs b/core/lib/via_btc_client/src/inscriber/script_builder.rs index d3fb1edb7665..857eda4b2221 100644 --- a/core/lib/via_btc_client/src/inscriber/script_builder.rs +++ b/core/lib/via_btc_client/src/inscriber/script_builder.rs @@ -1,15 +1,18 @@ // Please checkout ../../Dev.md file Taproot Script section for more details on the via_inscription_protocol structure and message types // use crate::inscriber::types; -use crate::types; use anyhow::{Context, Result}; -use bitcoin::hashes::Hash; -use bitcoin::opcodes::{all, OP_0, OP_FALSE}; -use bitcoin::script::{Builder as ScriptBuilder, PushBytesBuf}; -use bitcoin::secp256k1::{Secp256k1, Signing, Verification}; -use bitcoin::taproot::TaprootBuilder; -use bitcoin::{key::UntweakedPublicKey, taproot::TaprootSpendInfo, ScriptBuf}; -use bitcoin::{Address, Network}; +use bitcoin::{ + hashes::Hash, + key::UntweakedPublicKey, + opcodes::{all, OP_0, OP_FALSE}, + script::{Builder as ScriptBuilder, PushBytesBuf}, + secp256k1::{Secp256k1, Signing, Verification}, + taproot::{TaprootBuilder, TaprootSpendInfo}, + Address, Network, ScriptBuf, +}; + +use crate::types; const VIA_INSCRIPTION_PROTOCOL: &str = "via_inscription_protocol"; diff --git a/core/lib/via_btc_client/src/signer/mod.rs b/core/lib/via_btc_client/src/signer/mod.rs index 64da3b810069..4b4fa4ba52a5 100644 --- a/core/lib/via_btc_client/src/signer/mod.rs +++ b/core/lib/via_btc_client/src/signer/mod.rs @@ -4,9 +4,9 @@ use bitcoin::{ secp256k1::{All, Keypair, Message, Secp256k1, SecretKey}, Address, CompressedPublicKey, Network, PrivateKey, ScriptBuf, }; - -use secp256k1::schnorr::Signature as SchnorrSignature; -use secp256k1::{ecdsa::Signature as ECDSASignature, PublicKey}; +use secp256k1::{ + ecdsa::Signature as ECDSASignature, schnorr::Signature as SchnorrSignature, PublicKey, +}; use crate::{ traits::BitcoinSigner, diff --git a/core/lib/via_btc_client/src/traits.rs b/core/lib/via_btc_client/src/traits.rs index bca6fc5c0706..e4dbfaae2dd5 100644 --- a/core/lib/via_btc_client/src/traits.rs +++ b/core/lib/via_btc_client/src/traits.rs @@ -1,15 +1,19 @@ use async_trait::async_trait; -use bitcoin::{Address, Block, BlockHash, Network, OutPoint, Transaction, TxOut, Txid, ScriptBuf}; +use bitcoin::{ + key::UntweakedPublicKey, + secp256k1::{All, Secp256k1}, + Address, Block, BlockHash, Network, OutPoint, ScriptBuf, Transaction, TxOut, Txid, +}; use bitcoincore_rpc::Auth; +use secp256k1::{ + ecdsa::Signature as ECDSASignature, schnorr::Signature as SchnorrSignature, Message, PublicKey, +}; +use types::BitcoinRpcResult; -use crate::types; -use types::{BitcoinRpcResult}; -use bitcoin::key::UntweakedPublicKey; -use bitcoin::secp256k1::{All, Secp256k1}; -use secp256k1::ecdsa::Signature as ECDSASignature; -use secp256k1::schnorr::Signature as SchnorrSignature; -use secp256k1::{Message, PublicKey}; -use crate::types::{BitcoinClientResult, BitcoinIndexerResult, FullInscriptionMessage}; +use crate::{ + types, + types::{BitcoinClientResult, BitcoinIndexerResult, FullInscriptionMessage}, +}; #[allow(dead_code)] #[async_trait] @@ -103,7 +107,7 @@ pub trait BitcoinInscriptionIndexerOpt: Send + Sync { network: Network, bootstrap_txids: Vec, ) -> BitcoinIndexerResult - where + where Self: Sized; async fn process_blocks( &self, diff --git a/core/lib/via_btc_client/src/types.rs b/core/lib/via_btc_client/src/types.rs index ff75469f9c73..738b91d9224c 100644 --- a/core/lib/via_btc_client/src/types.rs +++ b/core/lib/via_btc_client/src/types.rs @@ -1,6 +1,9 @@ use std::collections::VecDeque; -use bitcoin::{script::PushBytesBuf, taproot::Signature as TaprootSignature, Address as BitcoinAddress, Amount, TxIn, Txid, TxOut}; +use bitcoin::{ + script::PushBytesBuf, taproot::Signature as TaprootSignature, Address as BitcoinAddress, + Amount, TxIn, TxOut, Txid, +}; use serde::{Deserialize, Serialize}; use thiserror::Error; use zksync_basic_types::H256; From 3ae4008294b31adae57c95abd66c8ad1f70a315a Mon Sep 17 00:00:00 2001 From: Steph Sinyakov Date: Mon, 19 Aug 2024 11:52:11 +0200 Subject: [PATCH 10/12] fix: add message name to inscription --- Cargo.lock | 1 + core/lib/via_btc_client/Cargo.toml | 1 + core/lib/via_btc_client/DEV.md | 2 +- core/lib/via_btc_client/src/indexer/parser.rs | 36 ++++++++++++------- .../src/inscriber/script_builder.rs | 13 +++++-- core/lib/via_btc_client/src/types.rs | 15 ++++++++ 6 files changed, 52 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c600ea7c4e59..80205513156d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7670,6 +7670,7 @@ dependencies = [ "hex", "inquire", "jsonrpsee", + "lazy_static", "mockall", "rand 0.8.5", "reqwest 0.12.5", diff --git a/core/lib/via_btc_client/Cargo.toml b/core/lib/via_btc_client/Cargo.toml index 19e32ab60d81..292de6f4ad09 100644 --- a/core/lib/via_btc_client/Cargo.toml +++ b/core/lib/via_btc_client/Cargo.toml @@ -22,6 +22,7 @@ jsonrpsee = { workspace = true, features = [ "client", "macros", ] } +lazy_static.workspace = true tokio.workspace = true bitcoin = "0.32.2" bitcoincore-rpc = "0.19.0" diff --git a/core/lib/via_btc_client/DEV.md b/core/lib/via_btc_client/DEV.md index 4d7d2abc8978..db059c2bcd94 100644 --- a/core/lib/via_btc_client/DEV.md +++ b/core/lib/via_btc_client/DEV.md @@ -151,7 +151,7 @@ Sender Validation: only valid sequencer | OP_FALSE | | OP_IF | | OP_PUSHBYTES_32 b"Str('via_inscription_protocol')" | -| OP_PUSHBYTES_32 b"Str('L1BatchDAReference')" | +| OP_PUSHBYTES_32 b"Str('L1BatchDAReferenceMessage')"| | OP_PUSHBYTES_32 b"l1_batch_hash" | | OP_PUSHBYTES_32 b"l1_batch_index" | | OP_PUSHBYTES_32 b"celestia" | diff --git a/core/lib/via_btc_client/src/indexer/parser.rs b/core/lib/via_btc_client/src/indexer/parser.rs index b13aea7795cf..a934d33d826d 100644 --- a/core/lib/via_btc_client/src/indexer/parser.rs +++ b/core/lib/via_btc_client/src/indexer/parser.rs @@ -1,3 +1,10 @@ +use crate::types; +use crate::types::{ + CommonFields, FullInscriptionMessage as Message, L1BatchDAReference, L1BatchDAReferenceInput, + L1ToL2Message, L1ToL2MessageInput, ProofDAReference, ProofDAReferenceInput, ProposeSequencer, + ProposeSequencerInput, SystemBootstrapping, SystemBootstrappingInput, ValidatorAttestation, + ValidatorAttestationInput, Vote, +}; use bitcoin::{ address::NetworkUnchecked, hashes::Hash, @@ -8,13 +15,6 @@ use bitcoin::{ use zksync_basic_types::H256; use zksync_types::{Address as EVMAddress, L1BatchNumber}; -use crate::types::{ - CommonFields, FullInscriptionMessage as Message, L1BatchDAReference, L1BatchDAReferenceInput, - L1ToL2Message, L1ToL2MessageInput, ProofDAReference, ProofDAReferenceInput, ProposeSequencer, - ProposeSequencerInput, SystemBootstrapping, SystemBootstrappingInput, ValidatorAttestation, - ValidatorAttestationInput, Vote, -}; - const MIN_WITNESS_LENGTH: usize = 3; const VIA_INSCRIPTION_PROTOCOL: &str = "via_inscription_protocol"; @@ -69,22 +69,32 @@ impl MessageParser { let message_type = instructions.get(1)?; match message_type { - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"SystemBootstrappingMessage" => { + Instruction::PushBytes(bytes) + if bytes.as_bytes() == types::SYSTEM_BOOTSTRAPPING_MSG.as_bytes() => + { self.parse_system_bootstrapping(instructions, common_fields) } - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"ProposeSequencerMessage" => { + Instruction::PushBytes(bytes) + if bytes.as_bytes() == types::PROPOSE_SEQUENCER_MSG.as_bytes() => + { self.parse_propose_sequencer(instructions, common_fields) } - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"ValidatorAttestationMessage" => { + Instruction::PushBytes(bytes) + if bytes.as_bytes() == types::VALIDATOR_ATTESTATION_MSG.as_bytes() => + { self.parse_validator_attestation(instructions, common_fields) } - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"L1BatchDAReference" => { + Instruction::PushBytes(bytes) + if bytes.as_bytes() == types::L1_BATCH_DA_REFERENCE_MSG.as_bytes() => + { self.parse_l1_batch_da_reference(instructions, common_fields) } - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"ProofDAReferenceMessage" => { + Instruction::PushBytes(bytes) + if bytes.as_bytes() == types::PROOF_DA_REFERENCE_MSG.as_bytes() => + { self.parse_proof_da_reference(instructions, common_fields) } - Instruction::PushBytes(bytes) if bytes.as_bytes() == b"L1ToL2Message" => { + Instruction::PushBytes(bytes) if bytes.as_bytes() == types::L1_TO_L2_MSG.as_bytes() => { self.parse_l1_to_l2_message(tx, instructions, common_fields) } _ => None, diff --git a/core/lib/via_btc_client/src/inscriber/script_builder.rs b/core/lib/via_btc_client/src/inscriber/script_builder.rs index 857eda4b2221..990e6fb6deb2 100644 --- a/core/lib/via_btc_client/src/inscriber/script_builder.rs +++ b/core/lib/via_btc_client/src/inscriber/script_builder.rs @@ -131,6 +131,7 @@ impl InscriptionData { .ok(); final_script_result = basic_script + .push_slice(&*types::L1_BATCH_DA_REFERENCE_MSG) .push_slice(l1_batch_hash_encoded) .push_slice(l1_batch_index_encoded) .push_slice(da_identifier_encoded) @@ -161,6 +162,7 @@ impl InscriptionData { .ok(); final_script_result = basic_script + .push_slice(&*types::PROOF_DA_REFERENCE_MSG) .push_slice(l1_batch_reveal_txid_encoded) .push_slice(da_identifier_encoded) .push_slice(da_reference_encoded); @@ -177,11 +179,13 @@ impl InscriptionData { match input.attestation { types::Vote::Ok => { final_script_result = basic_script + .push_slice(&*types::VALIDATOR_ATTESTATION_MSG) .push_slice(reference_txid_encoded) .push_opcode(all::OP_PUSHNUM_1); } types::Vote::NotOk => { final_script_result = basic_script + .push_slice(&*types::VALIDATOR_ATTESTATION_MSG) .push_slice(reference_txid_encoded) .push_opcode(OP_0); } @@ -219,7 +223,9 @@ impl InscriptionData { .extend_from_slice(bridge_p2wpkh_mpc_address_bytes) .ok(); - final_script_result = tapscript.push_slice(bridge_p2wpkh_mpc_address_encoded); + final_script_result = tapscript + .push_slice(&*types::SYSTEM_BOOTSTRAPPING_MSG) + .push_slice(bridge_p2wpkh_mpc_address_encoded); } types::InscriptionMessage::ProposeSequencer(input) => { @@ -231,7 +237,9 @@ impl InscriptionData { .extend_from_slice(sequencer_new_p2wpkh_address_bytes) .ok(); - final_script_result = basic_script.push_slice(sequencer_new_p2wpkh_address_encoded); + final_script_result = basic_script + .push_slice(&*types::PROPOSE_SEQUENCER_MSG) + .push_slice(sequencer_new_p2wpkh_address_encoded); } types::InscriptionMessage::L1ToL2Message(input) => { @@ -253,6 +261,7 @@ impl InscriptionData { call_data_encoded.extend_from_slice(&input.call_data).ok(); final_script_result = basic_script + .push_slice(&*types::L1_TO_L2_MSG) .push_slice(receiver_l2_address_encoded) .push_slice(l2_contract_address_encoded) .push_slice(call_data_encoded); diff --git a/core/lib/via_btc_client/src/types.rs b/core/lib/via_btc_client/src/types.rs index 738b91d9224c..f37028c95e67 100644 --- a/core/lib/via_btc_client/src/types.rs +++ b/core/lib/via_btc_client/src/types.rs @@ -4,6 +4,7 @@ use bitcoin::{ script::PushBytesBuf, taproot::Signature as TaprootSignature, Address as BitcoinAddress, Amount, TxIn, TxOut, Txid, }; +use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use thiserror::Error; use zksync_basic_types::H256; @@ -132,6 +133,20 @@ pub struct CommitTxInput { pub spent_utxo: Vec, } +lazy_static! { + pub static ref SYSTEM_BOOTSTRAPPING_MSG: PushBytesBuf = + PushBytesBuf::from(b"SystemBootstrappingMessage"); + pub static ref PROPOSE_SEQUENCER_MSG: PushBytesBuf = + PushBytesBuf::from(b"ProposeSequencerMessage"); + pub static ref VALIDATOR_ATTESTATION_MSG: PushBytesBuf = + PushBytesBuf::from(b"ValidatorAttestationMessage"); + pub static ref L1_BATCH_DA_REFERENCE_MSG: PushBytesBuf = + PushBytesBuf::from(b"L1BatchDAReferenceMessage"); + pub static ref PROOF_DA_REFERENCE_MSG: PushBytesBuf = + PushBytesBuf::from(b"ProofDAReferenceMessage"); + pub static ref L1_TO_L2_MSG: PushBytesBuf = PushBytesBuf::from(b"L1ToL2Message"); +} + #[allow(unused)] #[derive(Clone, Debug)] pub struct InscriptionRequest { From f7d4d944d5d83933921d87c46de0557914c37bfd Mon Sep 17 00:00:00 2001 From: Steph Sinyakov Date: Mon, 19 Aug 2024 12:11:19 +0200 Subject: [PATCH 11/12] fix: via inscription message moved to types [ci skip] --- core/lib/via_btc_client/src/indexer/parser.rs | 3 +-- core/lib/via_btc_client/src/inscriber/script_builder.rs | 7 +++---- core/lib/via_btc_client/src/types.rs | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/lib/via_btc_client/src/indexer/parser.rs b/core/lib/via_btc_client/src/indexer/parser.rs index a934d33d826d..79cf2340ca90 100644 --- a/core/lib/via_btc_client/src/indexer/parser.rs +++ b/core/lib/via_btc_client/src/indexer/parser.rs @@ -16,7 +16,6 @@ use zksync_basic_types::H256; use zksync_types::{Address as EVMAddress, L1BatchNumber}; const MIN_WITNESS_LENGTH: usize = 3; -const VIA_INSCRIPTION_PROTOCOL: &str = "via_inscription_protocol"; pub struct MessageParser { network: Network, @@ -318,6 +317,6 @@ impl MessageParser { fn find_via_inscription_protocol(instructions: &[Instruction]) -> Option { // TODO: also check first part of the script (OP_CHECKSIG and other stuff) instructions.iter().position(|instr| { - matches!(instr, Instruction::PushBytes(bytes) if bytes.as_bytes() == VIA_INSCRIPTION_PROTOCOL.as_bytes()) + matches!(instr, Instruction::PushBytes(bytes) if bytes.as_bytes() == types::VIA_INSCRIPTION_PROTOCOL.as_bytes()) }) } diff --git a/core/lib/via_btc_client/src/inscriber/script_builder.rs b/core/lib/via_btc_client/src/inscriber/script_builder.rs index 990e6fb6deb2..f4a54931e1bb 100644 --- a/core/lib/via_btc_client/src/inscriber/script_builder.rs +++ b/core/lib/via_btc_client/src/inscriber/script_builder.rs @@ -14,8 +14,6 @@ use bitcoin::{ use crate::types; -const VIA_INSCRIPTION_PROTOCOL: &str = "via_inscription_protocol"; - pub struct InscriptionData { pub inscription_script: ScriptBuf, pub script_size: usize, @@ -79,9 +77,10 @@ impl InscriptionData { } fn build_basic_inscription_script(encoded_pubkey: &PushBytesBuf) -> Result { - let mut via_prefix_encoded = PushBytesBuf::with_capacity(VIA_INSCRIPTION_PROTOCOL.len()); + let mut via_prefix_encoded = + PushBytesBuf::with_capacity(types::VIA_INSCRIPTION_PROTOCOL.len()); via_prefix_encoded - .extend_from_slice(VIA_INSCRIPTION_PROTOCOL.as_bytes()) + .extend_from_slice(types::VIA_INSCRIPTION_PROTOCOL.as_bytes()) .ok(); let script = ScriptBuilder::new() diff --git a/core/lib/via_btc_client/src/types.rs b/core/lib/via_btc_client/src/types.rs index f37028c95e67..e0b09c5d3d99 100644 --- a/core/lib/via_btc_client/src/types.rs +++ b/core/lib/via_btc_client/src/types.rs @@ -146,6 +146,7 @@ lazy_static! { PushBytesBuf::from(b"ProofDAReferenceMessage"); pub static ref L1_TO_L2_MSG: PushBytesBuf = PushBytesBuf::from(b"L1ToL2Message"); } +pub(crate) const VIA_INSCRIPTION_PROTOCOL: &str = "via_inscription_protocol"; #[allow(unused)] #[derive(Clone, Debug)] From 79962cb77de056273d71968268b39581ca5de703 Mon Sep 17 00:00:00 2001 From: Steph Sinyakov Date: Mon, 19 Aug 2024 12:24:37 +0200 Subject: [PATCH 12/12] fix: review fixes [ci skip] --- core/lib/via_btc_client/src/indexer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/via_btc_client/src/indexer/mod.rs b/core/lib/via_btc_client/src/indexer/mod.rs index 3110cc7bd316..8647846018cb 100644 --- a/core/lib/via_btc_client/src/indexer/mod.rs +++ b/core/lib/via_btc_client/src/indexer/mod.rs @@ -53,7 +53,7 @@ impl BootstrapState { .values() .filter(|&v| *v == Vote::Ok) .count(); - positive_votes * 2 > total_votes + positive_votes * 2 > total_votes && total_votes == self.verifier_addresses.len() } }