diff --git a/committer/src/main.rs b/committer/src/main.rs index e4763d45..4d0f02b2 100644 --- a/committer/src/main.rs +++ b/committer/src/main.rs @@ -75,6 +75,7 @@ async fn main() -> Result<()> { storage.clone(), &metrics_registry, cancel_token.clone(), + &config, ); launch_api_server( diff --git a/committer/src/setup.rs b/committer/src/setup.rs index 43f37519..abdf4318 100644 --- a/committer/src/setup.rs +++ b/committer/src/setup.rs @@ -92,11 +92,13 @@ pub fn state_importer( storage: impl Storage + 'static, _registry: &Registry, cancel_token: CancellationToken, + config: &config::Config, ) -> tokio::task::JoinHandle<()> { - let state_importer = services::StateImporter::new(storage, fuel); + let validator = BlockValidator::new(config.fuel.block_producer_public_key); + let state_importer = services::StateImporter::new(storage, fuel, validator); schedule_polling( - Duration::from_secs(10), + config.app.block_check_interval, state_importer, "State Importer", cancel_token, diff --git a/packages/eth/src/eip_4844/types.rs b/packages/eth/src/eip_4844/types.rs index 74df0910..099e9e7f 100644 --- a/packages/eth/src/eip_4844/types.rs +++ b/packages/eth/src/eip_4844/types.rs @@ -69,6 +69,9 @@ impl BlobSidecar { self.blobs.iter().map(|blob| blob.versioned_hash).collect() } + // When preparing a blob transaction, we compute the KZG commitment and proof for the blob data. + // To be able to apply the KZG commitment scheme, the data is treated as a polynomial with the field elements as coefficients. + // We split it into 31-byte chunks (field elements) padded with a zero byte. fn partition_data(data: Vec) -> Vec<[u8; 32]> { let capacity = data.len().div_ceil(31); let mut field_elements = Vec::with_capacity(capacity); @@ -80,11 +83,13 @@ impl BlobSidecar { field_elements } + // Generate the right amount of blobs to carry all the field elements. fn field_elements_to_blobs(field_elements: Vec<[u8; 32]>) -> Vec { let mut blobs = Vec::new(); let mut current_blob = [0u8; c_kzg::BYTES_PER_BLOB]; let mut offset = 0; + // Each field element is 32 bytes long. A blob can hold 4096 field elements. for fe in field_elements { if offset + 32 > c_kzg::BYTES_PER_BLOB { blobs.push(current_blob.into()); @@ -127,7 +132,7 @@ impl BlobSidecar { fn kzg_proof(blob: &c_kzg::Blob, commitment: &c_kzg::KzgCommitment) -> c_kzg::KzgProof { c_kzg::KzgProof::compute_blob_kzg_proof(blob, &commitment.to_bytes(), &KZG_SETTINGS) - .unwrap() + .expect("KZG proof computation failed") } } @@ -164,6 +169,7 @@ impl BlobTransactionEncoder { let blobs_count = self.sidecar.num_blobs(); let mut stream = RlpStream::new(); + // 4 fields: tx type, blobs, commitments, proofs stream.begin_list(4); // skip the tx type byte @@ -215,6 +221,7 @@ impl BlobTransactionEncoder { fn rlp(&self) -> Vec { let mut stream = RlpStream::new(); + // 11 fields: common tx fields, unused fields, blob tx fields stream.begin_list(11); self.append_common_tx_fields(&mut stream); @@ -226,6 +233,7 @@ impl BlobTransactionEncoder { fn rlp_signed(&self, signature: Signature) -> Vec { let mut stream = RlpStream::new(); + // 14 fields: common tx fields, unused fields, blob tx fields, signature stream.begin_list(14); self.append_common_tx_fields(&mut stream); diff --git a/packages/eth/src/eip_4844/utils.rs b/packages/eth/src/eip_4844/utils.rs index 6d5ceca7..7538db09 100644 --- a/packages/eth/src/eip_4844/utils.rs +++ b/packages/eth/src/eip_4844/utils.rs @@ -4,6 +4,8 @@ const BLOB_BASE_FEE_UPDATE_FRACTION: u64 = 3338477; const GAS_PER_BLOB: u64 = 131_072; const MIN_BASE_FEE_PER_BLOB_GAS: u64 = 1; +// Calculate blob fee based on the EIP-4844 specs +// https://eips.ethereum.org/EIPS/eip-4844 pub fn calculate_blob_fee(excess_blob_gas: U256, num_blobs: u64) -> U256 { get_total_blob_gas(num_blobs) * get_base_fee_per_blob_gas(excess_blob_gas) } diff --git a/packages/eth/src/websocket/connection.rs b/packages/eth/src/websocket/connection.rs index 08d1d4b1..caa8a2a6 100644 --- a/packages/eth/src/websocket/connection.rs +++ b/packages/eth/src/websocket/connection.rs @@ -16,6 +16,8 @@ use crate::{ error::{Error, Result}, }; +const STANDARD_GAS_LIMIT: u64 = 21000; + abigen!( FUEL_STATE_CONTRACT, r#"[ @@ -163,16 +165,7 @@ impl WsConnection { let (max_fee_per_gas, max_priority_fee_per_gas) = self.provider.estimate_eip1559_fees(None).await?; - // Gas limit should be 21000, otherwise we'll have to estimate it - let gas_limit = U256::from(21000); - // let estimate_tx = TypedTransaction::Eip1559(Eip1559TransactionRequest { - // from: address.into(), - // to: Some(address.into()), - // max_priority_fee_per_gas: Some(max_priority_fee_per_gas), - // max_fee_per_gas: Some(max_fee_per_gas), - // ..Default::default() - // }); - // let gas_limit = self.provider.estimate_gas(&estimate_tx, None).await?; + let gas_limit = U256::from(STANDARD_GAS_LIMIT); let max_fee_per_blob_gas = self.calculate_blob_fee(blob_versioned_hashes.len()).await?; diff --git a/packages/services/src/state_importer.rs b/packages/services/src/state_importer.rs index e488230b..d559c98a 100644 --- a/packages/services/src/state_importer.rs +++ b/packages/services/src/state_importer.rs @@ -4,31 +4,38 @@ use ports::{ storage::Storage, types::{StateFragment, StateSubmission}, }; +use validator::Validator; use crate::{Result, Runner}; -pub struct StateImporter { +pub struct StateImporter { storage: Db, fuel_adapter: A, + block_validator: BlockValidator, } -impl StateImporter { - pub fn new(storage: Db, fuel_adapter: A) -> Self { +impl StateImporter { + pub fn new(storage: Db, fuel_adapter: A, block_validator: BlockValidator) -> Self { Self { storage, fuel_adapter, + block_validator, } } } -impl StateImporter +impl StateImporter where Db: Storage, A: ports::fuel::Api, + BlockValidator: Validator, { async fn fetch_latest_block(&self) -> Result { let latest_block = self.fuel_adapter.latest_block().await?; - // validate if needed + + // validate block but don't return the validated block + // so we can use the original block for state submission + self.block_validator.validate(&latest_block)?; Ok(latest_block) } @@ -90,10 +97,11 @@ where } #[async_trait] -impl Runner for StateImporter +impl Runner for StateImporter where Db: Storage, Fuel: ports::fuel::Api, + BlockValidator: Validator, { async fn run(&mut self) -> Result<()> { let block = self.fetch_latest_block().await?; @@ -114,13 +122,22 @@ where #[cfg(test)] mod tests { + use fuel_crypto::SecretKey; use ports::fuel::FuelBytes32; + use rand::{rngs::StdRng, SeedableRng}; use storage::PostgresProcess; use tai64::Tai64; + use validator::BlockValidator; use super::*; - fn given_block() -> FuelBlock { + fn given_secret_key() -> SecretKey { + let mut rng = StdRng::seed_from_u64(42); + + SecretKey::random(&mut rng) + } + + fn given_block(secret: SecretKey) -> FuelBlock { let id = FuelBytes32::from([1u8; 32]); let header = ports::fuel::FuelHeader { id, @@ -142,7 +159,7 @@ mod tests { header, transactions: vec![[2u8; 32].into()], consensus: ports::fuel::FuelConsensus::Unknown, - block_producer: Default::default(), + block_producer: Some(secret.public_key()), }; block @@ -160,13 +177,15 @@ mod tests { #[tokio::test] async fn test_import_state() -> Result<()> { - let block = given_block(); + let secret_key = given_secret_key(); + let block = given_block(secret_key); let block_id = *block.id; let fuel_mock = given_fetcher(block); + let block_validator = BlockValidator::new(secret_key.public_key()); let process = PostgresProcess::shared().await.unwrap(); let db = process.create_random_db().await?; - let mut importer = StateImporter::new(db.clone(), fuel_mock); + let mut importer = StateImporter::new(db.clone(), fuel_mock, block_validator); importer.run().await.unwrap();