From 3e090b01ccde8283f7297981e287f9c7d02daba6 Mon Sep 17 00:00:00 2001 From: Aaryamann Challani <43716372+rymnc@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:56:02 +0530 Subject: [PATCH] fault_proving(compression): include commitment to transaction ids within compressed block header --- CHANGELOG.md | 1 + crates/compression/src/compress.rs | 9 ++++ .../src/compressed_block_payload/v0.rs | 10 +++- .../src/compressed_block_payload/v1.rs | 51 +++++++++++++++++-- crates/compression/src/decompress.rs | 10 +++- crates/compression/src/lib.rs | 16 +++++- .../src/graphql_api/da_compression.rs | 3 ++ .../src/graphql_api/worker_service.rs | 9 +++- tests/Cargo.toml | 1 + tests/tests/da_compression.rs | 9 +++- 10 files changed, 108 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0513c5eb1e0..fa8e6b5f7fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - [2551](https://github.com/FuelLabs/fuel-core/pull/2551): Enhanced the DA compressed block header to include block id. +- [2572](https://github.com/FuelLabs/fuel-core/pull/2572): Enhanced the DA compressed block header to include tx id commitment. ## [Version 0.41.0] diff --git a/crates/compression/src/compress.rs b/crates/compression/src/compress.rs index 0330121daa4..5cddd1f220e 100644 --- a/crates/compression/src/compress.rs +++ b/crates/compression/src/compress.rs @@ -15,6 +15,9 @@ use crate::{ VersionedCompressedBlock, }; use anyhow::Context; +#[cfg(feature = "fault-proving")] +use fuel_core_types::fuel_tx::UniqueIdentifier; + use fuel_core_types::{ blockchain::block::Block, fuel_compression::{ @@ -51,12 +54,16 @@ pub async fn compress( config: Config, mut db: D, block: &Block, + #[cfg(feature = "fault-proving")] chain_id: fuel_core_types::fuel_types::ChainId, ) -> anyhow::Result where D: CompressDb, { let target = block.transactions_vec(); + #[cfg(feature = "fault-proving")] + let tx_ids = target.iter().map(|tx| tx.id(&chain_id)).collect::>(); + let mut prepare_ctx = PrepareCtx { config, timestamp: block.header().time(), @@ -73,6 +80,8 @@ where block.header(), registrations, transactions, + #[cfg(feature = "fault-proving")] + &tx_ids, )) } diff --git a/crates/compression/src/compressed_block_payload/v0.rs b/crates/compression/src/compressed_block_payload/v0.rs index ea1555e8e24..4009f8797bf 100644 --- a/crates/compression/src/compressed_block_payload/v0.rs +++ b/crates/compression/src/compressed_block_payload/v0.rs @@ -4,6 +4,7 @@ use crate::{ }; use fuel_core_types::{ blockchain::{ + block::PartialFuelBlock, header::{ ApplicationHeader, BlockHeader, @@ -13,7 +14,10 @@ use fuel_core_types::{ primitives::Empty, }, fuel_tx::CompressedTransaction, - fuel_types::BlockHeight, + fuel_types::{ + BlockHeight, + ChainId, + }, }; /// Compressed block, without the preceding version byte. @@ -51,6 +55,10 @@ impl VersionedBlockPayload for CompressedBlockPayloadV0 { fn partial_block_header(&self) -> PartialBlockHeader { self.header } + + fn validate_with(&self, _: &PartialFuelBlock, _: &ChainId) -> anyhow::Result<()> { + Ok(()) + } } impl CompressedBlockPayloadV0 { diff --git a/crates/compression/src/compressed_block_payload/v1.rs b/crates/compression/src/compressed_block_payload/v1.rs index 51e0012a615..d400b55b3c1 100644 --- a/crates/compression/src/compressed_block_payload/v1.rs +++ b/crates/compression/src/compressed_block_payload/v1.rs @@ -4,6 +4,7 @@ use crate::{ }; use fuel_core_types::{ blockchain::{ + block::PartialFuelBlock, header::{ ApplicationHeader, BlockHeader, @@ -15,10 +16,26 @@ use fuel_core_types::{ Empty, }, }, - fuel_tx::CompressedTransaction, - fuel_types::BlockHeight, + fuel_crypto, + fuel_tx::{ + Bytes32, + CompressedTransaction, + UniqueIdentifier, + }, + fuel_types::{ + BlockHeight, + ChainId, + }, }; +pub fn generate_tx_commitment(tx_ids: &[Bytes32]) -> Bytes32 { + let mut hasher = fuel_crypto::Hasher::default(); + for tx_id in tx_ids { + hasher.input(tx_id.as_ref()); + } + hasher.digest() +} + /// A partially complete fuel block header that does not /// have any generated fields because it has not been executed yet. #[derive( @@ -31,10 +48,12 @@ pub struct CompressedBlockHeader { pub consensus: ConsensusHeader, // The block id. pub block_id: BlockId, + // A commitment to all transaction ids in the block + pub tx_commitment: Bytes32, } -impl From<&BlockHeader> for CompressedBlockHeader { - fn from(header: &BlockHeader) -> Self { +impl CompressedBlockHeader { + fn new(header: &BlockHeader, tx_ids: &[Bytes32]) -> Self { let ConsensusHeader { prev_root, height, @@ -56,6 +75,7 @@ impl From<&BlockHeader> for CompressedBlockHeader { generated: Empty {}, }, block_id: header.id(), + tx_commitment: generate_tx_commitment(tx_ids), } } } @@ -103,6 +123,26 @@ impl VersionedBlockPayload for CompressedBlockPayloadV1 { fn partial_block_header(&self) -> PartialBlockHeader { PartialBlockHeader::from(&self.header) } + + fn validate_with( + &self, + partial_block: &PartialFuelBlock, + chain_id: &ChainId, + ) -> anyhow::Result<()> { + let txs = partial_block + .transactions + .iter() + .map(|tx| tx.id(chain_id)) + .collect::>(); + let tx_commitment = generate_tx_commitment(&txs); + let expected = self.header.tx_commitment; + if tx_commitment != expected { + anyhow::bail!( + "Invalid tx commitment. got {tx_commitment}, expected {expected}" + ); + } + Ok(()) + } } impl CompressedBlockPayloadV1 { @@ -112,9 +152,10 @@ impl CompressedBlockPayloadV1 { header: &BlockHeader, registrations: RegistrationsPerTable, transactions: Vec, + tx_ids: &[Bytes32], ) -> Self { Self { - header: CompressedBlockHeader::from(header), + header: CompressedBlockHeader::new(header, tx_ids), registrations, transactions, } diff --git a/crates/compression/src/decompress.rs b/crates/compression/src/decompress.rs index 082124e9161..4481e962ba9 100644 --- a/crates/compression/src/decompress.rs +++ b/crates/compression/src/decompress.rs @@ -39,6 +39,7 @@ use fuel_core_types::{ }, fuel_types::{ Address, + ChainId, ContractId, }, tai64::Tai64, @@ -52,6 +53,7 @@ pub async fn decompress( config: Config, mut db: D, block: VersionedCompressedBlock, + chain_id: &ChainId, ) -> anyhow::Result where D: DecompressDb, @@ -74,10 +76,14 @@ where ) .await?; - Ok(PartialFuelBlock { + let partial_block = PartialFuelBlock { header: block.partial_block_header(), transactions, - }) + }; + + block.validate_with(&partial_block, chain_id)?; + + Ok(partial_block) } pub struct DecompressCtx { diff --git a/crates/compression/src/lib.rs b/crates/compression/src/lib.rs index 681a466e121..de7342d3c1c 100644 --- a/crates/compression/src/lib.rs +++ b/crates/compression/src/lib.rs @@ -20,6 +20,7 @@ use crate::compressed_block_payload::v0::CompressedBlockPayloadV0; use crate::compressed_block_payload::v1::CompressedBlockPayloadV1; use fuel_core_types::{ blockchain::{ + block::PartialFuelBlock, header::{ ApplicationHeader, BlockHeader, @@ -29,7 +30,10 @@ use fuel_core_types::{ primitives::Empty, }, fuel_tx::CompressedTransaction, - fuel_types::BlockHeight, + fuel_types::{ + BlockHeight, + ChainId, + }, }; use registry::RegistrationsPerTable; @@ -44,6 +48,11 @@ pub trait VersionedBlockPayload { fn registrations(&self) -> &RegistrationsPerTable; fn transactions(&self) -> Vec; fn partial_block_header(&self) -> PartialBlockHeader; + fn validate_with( + &self, + partial_block: &PartialFuelBlock, + chain_id: &ChainId, + ) -> anyhow::Result<()>; } /// Versioned compressed block. @@ -60,6 +69,8 @@ impl VersionedCompressedBlock { header: &BlockHeader, registrations: RegistrationsPerTable, transactions: Vec, + #[cfg(feature = "fault-proving")] + tx_ids: &[fuel_core_types::fuel_types::Bytes32], ) -> Self { #[cfg(not(feature = "fault-proving"))] return Self::V0(CompressedBlockPayloadV0::new( @@ -72,6 +83,7 @@ impl VersionedCompressedBlock { header, registrations, transactions, + tx_ids, )) } } @@ -274,6 +286,7 @@ mod tests { generated: Empty, }, block_id: BlockId::from_str("0xecea85c17070bc2e65f911310dbd01198f4436052ebba96cded9ddf30c58dd1a").unwrap(), + tx_commitment: Default::default(), }; @@ -302,6 +315,7 @@ mod tests { if let VersionedCompressedBlock::V1(block) = decompressed { assert_eq!(block.header.block_id, header.block_id); + assert_eq!(block.header.tx_commitment, header.tx_commitment); } else { panic!("Expected V1 block, got {:?}", decompressed); } diff --git a/crates/fuel-core/src/graphql_api/da_compression.rs b/crates/fuel-core/src/graphql_api/da_compression.rs index 722f63b5080..9c75ca251ef 100644 --- a/crates/fuel-core/src/graphql_api/da_compression.rs +++ b/crates/fuel-core/src/graphql_api/da_compression.rs @@ -50,6 +50,7 @@ pub fn da_compress_block( block: &Block, block_events: &[Event], db_tx: &mut T, + #[cfg(feature = "fault-proving")] chain_id: fuel_core_types::fuel_types::ChainId, ) -> anyhow::Result<()> where T: OffChainDatabaseTransaction, @@ -61,6 +62,8 @@ where block_events, }, block, + #[cfg(feature = "fault-proving")] + chain_id, ) .now_or_never() .expect("The current implementation resolved all futures instantly")?; diff --git a/crates/fuel-core/src/graphql_api/worker_service.rs b/crates/fuel-core/src/graphql_api/worker_service.rs index e7b9f666eaa..62076af244b 100644 --- a/crates/fuel-core/src/graphql_api/worker_service.rs +++ b/crates/fuel-core/src/graphql_api/worker_service.rs @@ -188,7 +188,14 @@ where match self.da_compression_config { DaCompressionConfig::Disabled => {} DaCompressionConfig::Enabled(config) => { - da_compress_block(config, block, &result.events, &mut transaction)?; + da_compress_block( + config, + block, + &result.events, + &mut transaction, + #[cfg(feature = "fault-proving")] + self.chain_id, + )?; } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index f11175776e8..6b495055415 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -89,3 +89,4 @@ tracing = { workspace = true } default = ["fuel-core/default"] only-p2p = ["fuel-core-p2p"] aws-kms = ["dep:aws-config", "dep:aws-sdk-kms", "fuel-core-bin/aws-kms"] +fault-proving = ["fuel-core-compression/fault-proving", "fuel-core/fault-proving", "fuel-core-bin/fault-proving"] diff --git a/tests/tests/da_compression.rs b/tests/tests/da_compression.rs index cd0d1430375..0c728c39343 100644 --- a/tests/tests/da_compression.rs +++ b/tests/tests/da_compression.rs @@ -61,6 +61,11 @@ async fn can_fetch_da_compressed_block_from_graphql() { temporal_registry_retention: Duration::from_secs(3600), }; config.da_compression = DaCompressionConfig::Enabled(compression_config); + let chain_id = config + .snapshot_reader + .chain_config() + .consensus_parameters + .chain_id(); let srv = FuelService::new_node(config).await.unwrap(); let client = FuelClient::from(srv.bound_address); @@ -124,7 +129,9 @@ async fn can_fetch_da_compressed_block_from_graphql() { }, onchain_db: db.on_chain().view_at(&0u32.into()).unwrap(), }; - let decompressed = decompress(compression_config, db_tx, block).await.unwrap(); + let decompressed = decompress(compression_config, db_tx, block, &chain_id) + .await + .unwrap(); assert!(decompressed.transactions.len() == 2); }