From 837ca2768366a6af5501877668d8b578e857750b Mon Sep 17 00:00:00 2001 From: Green Baneling Date: Tue, 19 Mar 2024 21:18:00 +0100 Subject: [PATCH] Added consensus parameters version and state transition version to the `ApplicationHeader` (#1767) It is a preparation of the public types for the next changes: https://github.com/FuelLabs/fuel-core/issues/1754 https://github.com/FuelLabs/fuel-core/issues/1753 --- CHANGELOG.md | 1 + crates/client/assets/schema.sdl | 8 + crates/client/src/client/schema/block.rs | 2 + ...sts__block_by_height_query_gql_output.snap | 4 +- ...__tests__block_by_id_query_gql_output.snap | 4 +- ...s__blocks_connection_query_gql_output.snap | 4 +- ..._chain__tests__chain_gql_query_output.snap | 2 + crates/client/src/client/types/block.rs | 6 + crates/fuel-core/src/query/message/test.rs | 4 + crates/fuel-core/src/schema/block.rs | 10 + .../src/service/adapters/producer.rs | 36 +- crates/fuel-core/src/service/genesis.rs | 26 + .../services/producer/src/block_producer.rs | 10 +- .../producer/src/block_producer/tests.rs | 503 +++++++++++------- crates/services/producer/src/mocks.rs | 18 + crates/services/producer/src/ports.rs | 14 + crates/storage/src/column.rs | 51 +- crates/storage/src/structured_storage.rs | 1 + .../src/structured_storage/upgrades.rs | 62 +++ crates/storage/src/tables.rs | 25 + crates/types/src/blockchain/block.rs | 10 +- crates/types/src/blockchain/header.rs | 17 +- 22 files changed, 589 insertions(+), 229 deletions(-) create mode 100644 crates/storage/src/structured_storage/upgrades.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index bff01b904d..816732a943 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Description of the upcoming release here. ### Added +- [#1767](https://github.com/FuelLabs/fuel-core/pull/1767): Added consensus parameters version and state transition version to the `ApplicationHeader` to describe what was used to produce this block. - [#1760](https://github.com/FuelLabs/fuel-core/pull/1760): Added tests to verify that the network operates with a custom chain id and base asset id. - [#1752](https://github.com/FuelLabs/fuel-core/pull/1752): Add `ProducerGasPrice` trait that the `Producer` depends on to get the gas price for the block. - [#1747](https://github.com/FuelLabs/fuel-core/pull/1747): The DA block height is now included in the genesis state. diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index 151b7e935f..d04b9fbda1 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -447,6 +447,14 @@ type Header { """ daHeight: U64! """ + The version of the consensus parameters used to create this block. + """ + consensusParametersVersion: U32! + """ + The version of the state transition bytecode used to create this block. + """ + stateTransitionBytecodeVersion: U32! + """ Number of transactions in this block. """ transactionsCount: U64! diff --git a/crates/client/src/client/schema/block.rs b/crates/client/src/client/schema/block.rs index 47f0cb6fc8..4314f6d885 100644 --- a/crates/client/src/client/schema/block.rs +++ b/crates/client/src/client/schema/block.rs @@ -119,6 +119,8 @@ pub struct BlockMutation { pub struct Header { pub id: BlockId, pub da_height: U64, + pub consensus_parameters_version: U32, + pub state_transition_bytecode_version: U32, pub transactions_count: U64, pub message_receipt_count: U64, pub transactions_root: Bytes32, diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap index 71c3853a77..7664a8db6f 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap @@ -8,6 +8,8 @@ query($height: U32) { header { id daHeight + consensusParametersVersion + stateTransitionBytecodeVersion transactionsCount messageReceiptCount transactionsRoot @@ -34,5 +36,3 @@ query($height: U32) { } } } - - diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap index 6a76eb9f48..b6c69071fb 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap @@ -8,6 +8,8 @@ query($id: BlockId) { header { id daHeight + consensusParametersVersion + stateTransitionBytecodeVersion transactionsCount messageReceiptCount transactionsRoot @@ -34,5 +36,3 @@ query($id: BlockId) { } } } - - diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap index 10dafa2d3c..e4f395ec34 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap @@ -11,6 +11,8 @@ query($after: String, $before: String, $first: Int, $last: Int) { header { id daHeight + consensusParametersVersion + stateTransitionBytecodeVersion transactionsCount messageReceiptCount transactionsRoot @@ -45,5 +47,3 @@ query($after: String, $before: String, $first: Int, $last: Int) { } } } - - diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap index 0cc14d286a..252aad25e3 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap @@ -11,6 +11,8 @@ query { header { id daHeight + consensusParametersVersion + stateTransitionBytecodeVersion transactionsCount messageReceiptCount transactionsRoot diff --git a/crates/client/src/client/types/block.rs b/crates/client/src/client/types/block.rs index cfd5370a8c..a3b6531f24 100644 --- a/crates/client/src/client/types/block.rs +++ b/crates/client/src/client/types/block.rs @@ -31,6 +31,8 @@ impl Block { pub struct Header { pub id: BlockId, pub da_height: u64, + pub consensus_parameters_version: u32, + pub state_transition_bytecode_version: u32, pub transactions_count: u64, pub message_receipt_count: u64, pub transactions_root: MerkleRoot, @@ -68,6 +70,10 @@ impl From for Header { Self { id: value.id.into(), da_height: value.da_height.into(), + consensus_parameters_version: value.consensus_parameters_version.into(), + state_transition_bytecode_version: value + .state_transition_bytecode_version + .into(), transactions_count: value.transactions_count.into(), message_receipt_count: value.message_receipt_count.into(), transactions_root: value.transactions_root.into(), diff --git a/crates/fuel-core/src/query/message/test.rs b/crates/fuel-core/src/query/message/test.rs index 2c40700fdf..b34b70cbc2 100644 --- a/crates/fuel-core/src/query/message/test.rs +++ b/crates/fuel-core/src/query/message/test.rs @@ -130,6 +130,8 @@ async fn can_build_message_proof() { let commit_block_header = PartialBlockHeader { application: ApplicationHeader { da_height: 0u64.into(), + consensus_parameters_version: Default::default(), + state_transition_bytecode_version: Default::default(), generated: Default::default(), }, consensus: ConsensusHeader { @@ -144,6 +146,8 @@ async fn can_build_message_proof() { let message_block_header = PartialBlockHeader { application: ApplicationHeader { da_height: 0u64.into(), + consensus_parameters_version: Default::default(), + state_transition_bytecode_version: Default::default(), generated: Default::default(), }, consensus: ConsensusHeader { diff --git a/crates/fuel-core/src/schema/block.rs b/crates/fuel-core/src/schema/block.rs index ea9e4da7c9..2233ecf35c 100644 --- a/crates/fuel-core/src/schema/block.rs +++ b/crates/fuel-core/src/schema/block.rs @@ -138,6 +138,16 @@ impl Header { self.0.da_height.0.into() } + /// The version of the consensus parameters used to create this block. + async fn consensus_parameters_version(&self) -> U32 { + self.0.consensus_parameters_version.into() + } + + /// The version of the state transition bytecode used to create this block. + async fn state_transition_bytecode_version(&self) -> U32 { + self.0.state_transition_bytecode_version.into() + } + /// Number of transactions in this block. async fn transactions_count(&self) -> U64 { self.0.transactions_count.into() diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index 3c131a96d2..c60ce4829b 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -21,8 +21,16 @@ use fuel_core_producer::{ ports::TxPool, }; use fuel_core_storage::{ + iter::{ + IterDirection, + IteratorOverTable, + }, not_found, - tables::FuelBlocks, + tables::{ + ConsensusParametersVersions, + FuelBlocks, + StateTransitionBytecodeVersions, + }, transactional::Changes, Result as StorageResult, StorageAsRef, @@ -30,6 +38,10 @@ use fuel_core_storage::{ use fuel_core_types::{ blockchain::{ block::CompressedBlock, + header::{ + ConsensusParametersVersion, + StateTransitionBytecodeVersion, + }, primitives, }, fuel_tx, @@ -146,6 +158,28 @@ impl fuel_core_producer::ports::BlockProducerDatabase for Database { fn block_header_merkle_root(&self, height: &BlockHeight) -> StorageResult { self.storage::().root(height).map(Into::into) } + + fn latest_consensus_parameters_version( + &self, + ) -> StorageResult { + let (version, _) = self + .iter_all::(Some(IterDirection::Reverse)) + .next() + .ok_or(not_found!(ConsensusParametersVersions))??; + + Ok(version) + } + + fn latest_state_transition_bytecode_version( + &self, + ) -> StorageResult { + let (version, _) = self + .iter_all::(Some(IterDirection::Reverse)) + .next() + .ok_or(not_found!(StateTransitionBytecodeVersions))??; + + Ok(version) + } } impl GasPriceProvider for StaticGasPrice { diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index f8dc1db03d..f1891a41cf 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -16,10 +16,12 @@ use fuel_core_chain_config::{ use fuel_core_storage::{ tables::{ Coins, + ConsensusParametersVersions, ContractsInfo, ContractsLatestUtxo, ContractsRawCode, Messages, + StateTransitionBytecodeVersions, }, transactional::{ Changes, @@ -38,7 +40,9 @@ use fuel_core_types::{ header::{ ApplicationHeader, ConsensusHeader, + ConsensusParametersVersion, PartialBlockHeader, + StateTransitionBytecodeVersion, }, primitives::{ DaBlockHeight, @@ -87,6 +91,8 @@ pub async fn execute_genesis_block( import_chain_state(workers).await?; let genesis = Genesis { + // TODO: We can get the serialized consensus parameters from the database. + // https://github.com/FuelLabs/fuel-core/issues/1570 chain_config_hash: config.chain_config.root()?.into(), coins_root: original_database.genesis_coins_root()?.into(), messages_root: original_database.genesis_messages_root()?.into(), @@ -101,6 +107,20 @@ pub async fn execute_genesis_block( }; let mut database_transaction = original_database.read_transaction(); + // TODO: The chain config should be part of the snapshot state. + // https://github.com/FuelLabs/fuel-core/issues/1570 + database_transaction + .storage_as_mut::() + .insert( + &ConsensusParametersVersion::MIN, + &config.chain_config.consensus_parameters, + )?; + // TODO: The bytecode of the state transition function should be part of the snapshot state. + // https://github.com/FuelLabs/fuel-core/issues/1570 + database_transaction + .storage_as_mut::() + .insert(&ConsensusParametersVersion::MIN, &[])?; + cleanup_genesis_progress(&mut database_transaction)?; let result = UncommittedImportResult::new( @@ -146,6 +166,12 @@ pub fn create_genesis_block(config: &Config) -> Block { PartialBlockHeader { application: ApplicationHeader:: { da_height: da_block_height, + // After regenesis, we don't need to support old consensus parameters, + // so we can start the versioning from the beginning. + consensus_parameters_version: ConsensusParametersVersion::MIN, + // After regenesis, we don't need to support old state transition functions, + // so we can start the versioning from the beginning. + state_transition_bytecode_version: StateTransitionBytecodeVersion::MIN, generated: Empty, }, consensus: ConsensusHeader:: { diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index a03914cdd5..b964f6824d 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -300,11 +300,17 @@ where height: BlockHeight, block_time: Tai64, ) -> anyhow::Result { - let previous_block_info = self.previous_block_info(height)?; + let view = self.view_provider.latest_view(); + let previous_block_info = self.previous_block_info(height, &view)?; + let consensus_parameters_version = view.latest_consensus_parameters_version()?; + let state_transition_bytecode_version = + view.latest_state_transition_bytecode_version()?; Ok(PartialBlockHeader { application: ApplicationHeader { da_height: previous_block_info.da_height, + consensus_parameters_version, + state_transition_bytecode_version, generated: Default::default(), }, consensus: ConsensusHeader { @@ -319,6 +325,7 @@ where fn previous_block_info( &self, height: BlockHeight, + view: &ViewProvider::View, ) -> anyhow::Result { let latest_height = self .view_provider @@ -332,7 +339,6 @@ where } .into()) } else { - let view = self.view_provider.latest_view(); // get info from previous block height let prev_height = height.pred().expect("We checked the height above"); let previous_block = view.get_block(&prev_height)?; diff --git a/crates/services/producer/src/block_producer/tests.rs b/crates/services/producer/src/block_producer/tests.rs index 41caa0af32..3d42e75328 100644 --- a/crates/services/producer/src/block_producer/tests.rs +++ b/crates/services/producer/src/block_producer/tests.rs @@ -66,216 +66,333 @@ impl GasPriceProvider for MockProducerGasPrice { } } -#[tokio::test] -async fn cant_produce_at_genesis_height() { - let ctx = TestContext::default(); - let producer = ctx.producer(); - - let err = producer - .produce_and_execute_block_txpool(0u32.into(), Tai64::now(), 1_000_000_000) - .await - .expect_err("expected failure"); - - assert!( - matches!( - err.downcast_ref::(), - Some(Error::BlockHeightShouldBeHigherThanPrevious { .. }) - ), - "unexpected err {err:?}" - ); -} +// Tests for the `produce_and_execute_block_txpool` method. +mod produce_and_execute_block_txpool { + use super::*; + + #[tokio::test] + async fn cant_produce_at_genesis_height() { + let ctx = TestContext::default(); + let producer = ctx.producer(); + + let err = producer + .produce_and_execute_block_txpool(0u32.into(), Tai64::now(), 1_000_000_000) + .await + .expect_err("expected failure"); + + assert!( + matches!( + err.downcast_ref::(), + Some(Error::BlockHeightShouldBeHigherThanPrevious { .. }) + ), + "unexpected err {err:?}" + ); + } -#[tokio::test] -async fn can_produce_initial_block() { - let ctx = TestContext::default(); - let producer = ctx.producer(); + #[tokio::test] + async fn can_produce_initial_block() { + let ctx = TestContext::default(); + let producer = ctx.producer(); - let result = producer - .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) - .await; + let result = producer + .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) + .await; - assert!(result.is_ok()); -} + assert!(result.is_ok()); + } -#[tokio::test] -async fn can_produce_next_block() { - // simple happy path for producing atop pre-existing block - let mut rng = StdRng::seed_from_u64(0u64); - // setup dummy previous block - let prev_height = 1u32.into(); - let previous_block = PartialFuelBlock { - header: PartialBlockHeader { - consensus: ConsensusHeader { - height: prev_height, - prev_root: rng.gen(), + #[tokio::test] + async fn can_produce_next_block() { + // simple happy path for producing atop pre-existing block + let mut rng = StdRng::seed_from_u64(0u64); + let consensus_parameters_version = 0; + let state_transition_bytecode_version = 0; + // setup dummy previous block + let prev_height = 1u32.into(); + let previous_block = PartialFuelBlock { + header: PartialBlockHeader { + consensus: ConsensusHeader { + height: prev_height, + prev_root: rng.gen(), + ..Default::default() + }, ..Default::default() }, - ..Default::default() - }, - transactions: vec![], - } - .generate(&[]) - .compress(&Default::default()); - - let db = MockDb { - blocks: Arc::new(Mutex::new( - vec![(prev_height, previous_block)].into_iter().collect(), - )), - }; - - let ctx = TestContext::default_from_db(db); - let producer = ctx.producer(); - let result = producer - .produce_and_execute_block_txpool( - prev_height - .succ() - .expect("The block height should be valid"), - Tai64::now(), - 1_000_000_000, - ) - .await; - - assert!(result.is_ok()); -} + transactions: vec![], + } + .generate(&[]) + .compress(&Default::default()); -#[tokio::test] -async fn cant_produce_if_no_previous_block() { - // fail if there is no block that precedes the current height. - let ctx = TestContext::default(); - let producer = ctx.producer(); + let db = MockDb { + blocks: Arc::new(Mutex::new( + vec![(prev_height, previous_block)].into_iter().collect(), + )), + consensus_parameters_version, + state_transition_bytecode_version, + }; + + let ctx = TestContext::default_from_db(db); + let producer = ctx.producer(); + let result = producer + .produce_and_execute_block_txpool( + prev_height + .succ() + .expect("The block height should be valid"), + Tai64::now(), + 1_000_000_000, + ) + .await; + + assert!(result.is_ok()); + } - let err = producer - .produce_and_execute_block_txpool(100u32.into(), Tai64::now(), 1_000_000_000) - .await - .expect_err("expected failure"); + #[tokio::test] + async fn next_block_contains_expected_consensus_parameters_version() { + let mut rng = StdRng::seed_from_u64(0u64); + // setup dummy previous block + let prev_height = 1u32.into(); + let previous_block = PartialFuelBlock { + header: PartialBlockHeader { + consensus: ConsensusHeader { + height: prev_height, + prev_root: rng.gen(), + ..Default::default() + }, + ..Default::default() + }, + transactions: vec![], + } + .generate(&[]) + .compress(&Default::default()); - assert!(err.to_string().contains("Didn't find block for test")); -} + // Given + let consensus_parameters_version = 123; + let db = MockDb { + blocks: Arc::new(Mutex::new( + vec![(prev_height, previous_block)].into_iter().collect(), + )), + consensus_parameters_version, + state_transition_bytecode_version: 0, + }; + + let ctx = TestContext::default_from_db(db); + let producer = ctx.producer(); + + // When + let result = producer + .produce_and_execute_block_txpool( + prev_height + .succ() + .expect("The block height should be valid"), + Tai64::now(), + 1_000_000_000, + ) + .await + .expect("Should produce next block successfully") + .into_result(); + + // Then + let header = result.block.header(); + assert_eq!( + header.consensus_parameters_version, + consensus_parameters_version + ); + } -#[tokio::test] -async fn cant_produce_if_previous_block_da_height_too_high() { - // setup previous block with a high da_height - let prev_da_height = 100u64.into(); - let prev_height = 1u32.into(); - let previous_block = PartialFuelBlock { - header: PartialBlockHeader { - application: ApplicationHeader { - da_height: prev_da_height, + #[tokio::test] + async fn next_block_contains_expected_state_transition_bytecode_version() { + let mut rng = StdRng::seed_from_u64(0u64); + // setup dummy previous block + let prev_height = 1u32.into(); + let previous_block = PartialFuelBlock { + header: PartialBlockHeader { + consensus: ConsensusHeader { + height: prev_height, + prev_root: rng.gen(), + ..Default::default() + }, ..Default::default() }, - consensus: ConsensusHeader { - height: prev_height, + transactions: vec![], + } + .generate(&[]) + .compress(&Default::default()); + + // Given + let state_transition_bytecode_version = 321; + let db = MockDb { + blocks: Arc::new(Mutex::new( + vec![(prev_height, previous_block)].into_iter().collect(), + )), + consensus_parameters_version: 0, + state_transition_bytecode_version, + }; + + let ctx = TestContext::default_from_db(db); + let producer = ctx.producer(); + + // When + let result = producer + .produce_and_execute_block_txpool( + prev_height + .succ() + .expect("The block height should be valid"), + Tai64::now(), + 1_000_000_000, + ) + .await + .expect("Should produce next block successfully") + .into_result(); + + // Then + let header = result.block.header(); + assert_eq!( + header.state_transition_bytecode_version, + state_transition_bytecode_version + ); + } + + #[tokio::test] + async fn cant_produce_if_no_previous_block() { + // fail if there is no block that precedes the current height. + let ctx = TestContext::default(); + let producer = ctx.producer(); + + let err = producer + .produce_and_execute_block_txpool(100u32.into(), Tai64::now(), 1_000_000_000) + .await + .expect_err("expected failure"); + + assert!(err.to_string().contains("Didn't find block for test")); + } + + #[tokio::test] + async fn cant_produce_if_previous_block_da_height_too_high() { + // setup previous block with a high da_height + let prev_da_height = 100u64.into(); + let prev_height = 1u32.into(); + let previous_block = PartialFuelBlock { + header: PartialBlockHeader { + application: ApplicationHeader { + da_height: prev_da_height, + ..Default::default() + }, + consensus: ConsensusHeader { + height: prev_height, + ..Default::default() + }, + }, + transactions: vec![], + } + .generate(&[]) + .compress(&Default::default()); + + let db = MockDb { + blocks: Arc::new(Mutex::new( + vec![(prev_height, previous_block)].into_iter().collect(), + )), + consensus_parameters_version: 0, + state_transition_bytecode_version: 0, + }; + let ctx = TestContext { + relayer: MockRelayer { + // set our relayer best finalized height to less than previous + best_finalized_height: prev_da_height - 1u64.into(), ..Default::default() }, - }, - transactions: vec![], + ..TestContext::default_from_db(db) + }; + let producer = ctx.producer(); + + let err = producer + .produce_and_execute_block_txpool( + prev_height + .succ() + .expect("The block height should be valid"), + Tai64::now(), + 1_000_000_000, + ) + .await + .expect_err("expected failure"); + + assert!( + matches!( + err.downcast_ref::(), + Some(Error::InvalidDaFinalizationState { + previous_block, + best + }) if *previous_block == prev_da_height && *best == prev_da_height - 1u64.into() + ), + "unexpected err {err:?}" + ); } - .generate(&[]) - .compress(&Default::default()); - - let db = MockDb { - blocks: Arc::new(Mutex::new( - vec![(prev_height, previous_block)].into_iter().collect(), - )), - }; - let ctx = TestContext { - relayer: MockRelayer { - // set our relayer best finalized height to less than previous - best_finalized_height: prev_da_height - 1u64.into(), - ..Default::default() - }, - ..TestContext::default_from_db(db) - }; - let producer = ctx.producer(); - - let err = producer - .produce_and_execute_block_txpool( - prev_height - .succ() - .expect("The block height should be valid"), - Tai64::now(), - 1_000_000_000, - ) - .await - .expect_err("expected failure"); - - assert!( - matches!( - err.downcast_ref::(), - Some(Error::InvalidDaFinalizationState { - previous_block, - best - }) if *previous_block == prev_da_height && *best == prev_da_height - 1u64.into() - ), - "unexpected err {err:?}" - ); -} -#[tokio::test] -async fn production_fails_on_execution_error() { - let ctx = TestContext::default_from_executor(FailingMockExecutor(Mutex::new(Some( - ExecutorError::TransactionIdCollision(Default::default()), - )))); - - let producer = ctx.producer(); - - let err = producer - .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) - .await - .expect_err("expected failure"); - - assert!( - matches!( - err.downcast_ref::(), - Some(ExecutorError::TransactionIdCollision { .. }) - ), - "unexpected err {err:?}" - ); -} + #[tokio::test] + async fn production_fails_on_execution_error() { + let ctx = TestContext::default_from_executor(FailingMockExecutor(Mutex::new( + Some(ExecutorError::TransactionIdCollision(Default::default())), + ))); + + let producer = ctx.producer(); + + let err = producer + .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) + .await + .expect_err("expected failure"); + + assert!( + matches!( + err.downcast_ref::(), + Some(ExecutorError::TransactionIdCollision { .. }) + ), + "unexpected err {err:?}" + ); + } -// TODO: Add test that checks the gas price on the mint tx after `Executor` refactor -// https://github.com/FuelLabs/fuel-core/issues/1751 -#[tokio::test] -async fn produce_and_execute_block_txpool__executor_receives_gas_price_provided() { - // given - let gas_price = 1_000; - let gas_price_provider = MockProducerGasPrice::new(gas_price); - let executor = MockExecutorWithCapture::default(); - let ctx = TestContext::default_from_executor(executor.clone()); - - let producer = ctx.producer_with_gas_price_provider(gas_price_provider); - - // when - let _ = producer - .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) - .await - .unwrap(); - - // then - let captured = executor.captured.lock().unwrap(); - let expected = gas_price; - let actual = captured - .as_ref() - .expect("expected executor to be called") - .gas_price; - assert_eq!(expected, actual); -} + // TODO: Add test that checks the gas price on the mint tx after `Executor` refactor + // https://github.com/FuelLabs/fuel-core/issues/1751 + #[tokio::test] + async fn produce_and_execute_block_txpool__executor_receives_gas_price_provided() { + // given + let gas_price = 1_000; + let gas_price_provider = MockProducerGasPrice::new(gas_price); + let executor = MockExecutorWithCapture::default(); + let ctx = TestContext::default_from_executor(executor.clone()); + + let producer = ctx.producer_with_gas_price_provider(gas_price_provider); + + // when + let _ = producer + .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) + .await + .unwrap(); + + // then + let captured = executor.captured.lock().unwrap(); + let expected = gas_price; + let actual = captured + .as_ref() + .expect("expected executor to be called") + .gas_price; + assert_eq!(expected, actual); + } -#[tokio::test] -async fn produce_and_execute_block_txpool__missing_gas_price_causes_block_production_to_fail( -) { - // given - let gas_price_provider = MockProducerGasPrice::new_none(); - let ctx = TestContext::default(); - let producer = ctx.producer_with_gas_price_provider(gas_price_provider); - - // when - let result = producer - .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) - .await; - - // then - assert!(result.is_err()); + #[tokio::test] + async fn produce_and_execute_block_txpool__missing_gas_price_causes_block_production_to_fail( + ) { + // given + let gas_price_provider = MockProducerGasPrice::new_none(); + let ctx = TestContext::default(); + let producer = ctx.producer_with_gas_price_provider(gas_price_provider); + + // when + let result = producer + .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) + .await; + + // then + assert!(result.is_err()); + } } struct TestContext { @@ -307,6 +424,8 @@ impl TestContext { blocks: Arc::new(Mutex::new( vec![(genesis_height, genesis_block)].into_iter().collect(), )), + consensus_parameters_version: 0, + state_transition_bytecode_version: 0, } } diff --git a/crates/services/producer/src/mocks.rs b/crates/services/producer/src/mocks.rs index b909ed588e..05aaceca13 100644 --- a/crates/services/producer/src/mocks.rs +++ b/crates/services/producer/src/mocks.rs @@ -18,6 +18,10 @@ use fuel_core_types::{ Block, CompressedBlock, }, + header::{ + ConsensusParametersVersion, + StateTransitionBytecodeVersion, + }, primitives::DaBlockHeight, }, fuel_types::{ @@ -205,6 +209,8 @@ impl Default for MockExecutorWithCapture { #[derive(Clone, Default, Debug)] pub struct MockDb { pub blocks: Arc>>, + pub consensus_parameters_version: ConsensusParametersVersion, + pub state_transition_bytecode_version: StateTransitionBytecodeVersion, } impl AtomicView for MockDb { @@ -242,4 +248,16 @@ impl BlockProducerDatabase for MockDb { [u8::try_from(*height.deref()).expect("Test use small values"); 32], )) } + + fn latest_consensus_parameters_version( + &self, + ) -> StorageResult { + Ok(self.consensus_parameters_version) + } + + fn latest_state_transition_bytecode_version( + &self, + ) -> StorageResult { + Ok(self.state_transition_bytecode_version) + } } diff --git a/crates/services/producer/src/ports.rs b/crates/services/producer/src/ports.rs index b1827e8c7a..6461a44144 100644 --- a/crates/services/producer/src/ports.rs +++ b/crates/services/producer/src/ports.rs @@ -6,6 +6,10 @@ use fuel_core_storage::{ use fuel_core_types::{ blockchain::{ block::CompressedBlock, + header::{ + ConsensusParametersVersion, + StateTransitionBytecodeVersion, + }, primitives::DaBlockHeight, }, fuel_tx::{ @@ -30,6 +34,16 @@ pub trait BlockProducerDatabase: Send + Sync { /// Gets the block header BMT MMR root at `height`. fn block_header_merkle_root(&self, height: &BlockHeight) -> StorageResult; + + /// Returns the latest consensus parameters version. + fn latest_consensus_parameters_version( + &self, + ) -> StorageResult; + + /// Returns the latest state transition bytecode version. + fn latest_state_transition_bytecode_version( + &self, + ) -> StorageResult; } #[async_trait] diff --git a/crates/storage/src/column.rs b/crates/storage/src/column.rs index 695ef191e6..c7cc1a65ed 100644 --- a/crates/storage/src/column.rs +++ b/crates/storage/src/column.rs @@ -18,55 +18,54 @@ use crate::kv_store::StorageColumn; Hash, )] pub enum Column { + /// The column id of metadata about the blockchain + Metadata = 0, /// See [`ContractsRawCode`](crate::tables::ContractsRawCode) - ContractsRawCode = 0, + ContractsRawCode = 1, /// See [`ContractsInfo`](crate::tables::ContractsInfo) - ContractsInfo = 1, + ContractsInfo = 2, /// See [`ContractsState`](crate::tables::ContractsState) - ContractsState = 2, + ContractsState = 3, /// See [`ContractsLatestUtxo`](crate::tables::ContractsLatestUtxo) - ContractsLatestUtxo = 3, + ContractsLatestUtxo = 4, /// See [`ContractsAssets`](crate::tables::ContractsAssets) - ContractsAssets = 4, + ContractsAssets = 5, /// See [`Coins`](crate::tables::Coins) - Coins = 5, + Coins = 6, /// See [`Transactions`](crate::tables::Transactions) - Transactions = 6, + Transactions = 7, /// See [`FuelBlocks`](crate::tables::FuelBlocks) - FuelBlocks = 7, + FuelBlocks = 8, /// See [`FuelBlockMerkleData`](crate::tables::merkle::FuelBlockMerkleData) - FuelBlockMerkleData = 8, + FuelBlockMerkleData = 9, /// See [`FuelBlockMerkleMetadata`](crate::tables::merkle::FuelBlockMerkleMetadata) - FuelBlockMerkleMetadata = 9, + FuelBlockMerkleMetadata = 10, /// Messages that have been spent. /// Existence of a key in this column means that the message has been spent. /// See [`SpentMessages`](crate::tables::SpentMessages) - SpentMessages = 10, + SpentMessages = 11, /// See [`ContractsAssetsMerkleData`](crate::tables::merkle::ContractsAssetsMerkleData) - ContractsAssetsMerkleData = 11, + ContractsAssetsMerkleData = 12, /// See [`ContractsAssetsMerkleMetadata`](crate::tables::merkle::ContractsAssetsMerkleMetadata) - ContractsAssetsMerkleMetadata = 12, + ContractsAssetsMerkleMetadata = 13, /// See [`ContractsStateMerkleData`](crate::tables::merkle::ContractsStateMerkleData) - ContractsStateMerkleData = 13, + ContractsStateMerkleData = 14, /// See [`ContractsStateMerkleMetadata`](crate::tables::merkle::ContractsStateMerkleMetadata) - ContractsStateMerkleMetadata = 14, + ContractsStateMerkleMetadata = 15, /// See [`Messages`](crate::tables::Messages) - Messages = 15, + Messages = 16, /// See [`ProcessedTransactions`](crate::tables::ProcessedTransactions) - ProcessedTransactions = 16, - - // TODO: Extract the columns below into a separate enum to not mix - // required columns and non-required columns. It will break `MemoryStore` - // and `MemoryTransactionView` because they rely on linear index incrementation. - - // Below are the tables used for p2p, block production, starting the node. - /// The column id of metadata about the blockchain - Metadata = 17, + ProcessedTransactions = 17, /// See [`SealedBlockConsensus`](crate::tables::SealedBlockConsensus) FuelBlockConsensus = 18, + /// See [`ConsensusParametersVersions`](crate::tables::ConsensusParametersVersions) + ConsensusParametersVersions = 19, + /// See [`StateTransitionBytecodeVersions`](crate::tables::StateTransitionBytecodeVersions) + StateTransitionBytecodeVersions = 20, + // TODO: Remove this column and use `Metadata` column instead. /// Table for genesis state import progress tracking. - GenesisMetadata = 19, + GenesisMetadata = 21, } impl Column { diff --git a/crates/storage/src/structured_storage.rs b/crates/storage/src/structured_storage.rs index 727c43f885..cee9484ea6 100644 --- a/crates/storage/src/structured_storage.rs +++ b/crates/storage/src/structured_storage.rs @@ -57,6 +57,7 @@ pub mod messages; pub mod sealed_block; pub mod state; pub mod transactions; +pub mod upgrades; /// The table can implement this trait to indicate that it has a blueprint. /// It inherits the default implementation of the storage traits through the [`StructuredStorage`] diff --git a/crates/storage/src/structured_storage/upgrades.rs b/crates/storage/src/structured_storage/upgrades.rs new file mode 100644 index 0000000000..8627f56d30 --- /dev/null +++ b/crates/storage/src/structured_storage/upgrades.rs @@ -0,0 +1,62 @@ +//! The module contains implementations and tests for the contracts tables. + +use crate::{ + blueprint::plain::Plain, + codec::{ + postcard::Postcard, + primitive::Primitive, + raw::Raw, + }, + column::Column, + structured_storage::TableWithBlueprint, + tables::{ + ConsensusParametersVersions, + StateTransitionBytecodeVersions, + }, +}; + +impl TableWithBlueprint for ConsensusParametersVersions { + type Blueprint = Plain, Postcard>; + type Column = Column; + + fn column() -> Column { + Column::ConsensusParametersVersions + } +} + +impl TableWithBlueprint for StateTransitionBytecodeVersions { + type Blueprint = Plain, Raw>; + type Column = Column; + + fn column() -> Column { + Column::StateTransitionBytecodeVersions + } +} + +#[cfg(test)] +mod test { + use super::*; + use fuel_core_types::fuel_tx::ConsensusParameters; + + fn generate_key(rng: &mut impl rand::Rng) -> u32 { + rng.next_u32() + } + + crate::basic_storage_tests!( + ConsensusParametersVersions, + ::Key::default(), + ConsensusParameters::default(), + ConsensusParameters::default(), + generate_key + ); + + crate::basic_storage_tests!( + StateTransitionBytecodeVersions, + ::Key::default(), + vec![32u8], + ::OwnedValue::from(vec![ + 32u8 + ]), + generate_key + ); +} diff --git a/crates/storage/src/tables.rs b/crates/storage/src/tables.rs index e72fc6243f..43bda8a927 100644 --- a/crates/storage/src/tables.rs +++ b/crates/storage/src/tables.rs @@ -6,6 +6,10 @@ use fuel_core_types::{ blockchain::{ block::CompressedBlock, consensus::Consensus, + header::{ + ConsensusParametersVersion, + StateTransitionBytecodeVersion, + }, }, entities::{ coins::coin::CompressedCoin, @@ -16,6 +20,7 @@ use fuel_core_types::{ message::Message, }, fuel_tx::{ + ConsensusParameters, Transaction, TxId, UtxoId, @@ -129,6 +134,26 @@ impl Mappable for ProcessedTransactions { type OwnedValue = (); } +/// The storage table of consensus parameters. +pub struct ConsensusParametersVersions; + +impl Mappable for ConsensusParametersVersions { + type Key = Self::OwnedKey; + type OwnedKey = ConsensusParametersVersion; + type Value = Self::OwnedValue; + type OwnedValue = ConsensusParameters; +} + +/// The storage table of state transition bytecodes. +pub struct StateTransitionBytecodeVersions; + +impl Mappable for StateTransitionBytecodeVersions { + type Key = Self::OwnedKey; + type OwnedKey = StateTransitionBytecodeVersion; + type Value = [u8]; + type OwnedValue = Vec; +} + /// The module contains definition of merkle-related tables. pub mod merkle { use crate::{ diff --git a/crates/types/src/blockchain/block.rs b/crates/types/src/blockchain/block.rs index 92a0212e6e..06ecdf8605 100644 --- a/crates/types/src/blockchain/block.rs +++ b/crates/types/src/blockchain/block.rs @@ -229,7 +229,13 @@ impl From for PartialFuelBlock { Block::V1(BlockV1 { header: BlockHeader::V1(BlockHeaderV1 { - application: ApplicationHeader { da_height, .. }, + application: + ApplicationHeader { + da_height, + consensus_parameters_version, + state_transition_bytecode_version, + .. + }, consensus: ConsensusHeader { prev_root, @@ -244,6 +250,8 @@ impl From for PartialFuelBlock { header: PartialBlockHeader { application: ApplicationHeader { da_height, + consensus_parameters_version, + state_transition_bytecode_version, generated: Empty {}, }, consensus: ConsensusHeader { diff --git a/crates/types/src/blockchain/header.rs b/crates/types/src/blockchain/header.rs index dcea305e74..6cf0b27e31 100644 --- a/crates/types/src/blockchain/header.rs +++ b/crates/types/src/blockchain/header.rs @@ -156,6 +156,11 @@ pub struct PartialBlockHeader { pub consensus: ConsensusHeader, } +/// The type representing the version of the consensus parameters. +pub type ConsensusParametersVersion = u32; +/// The type representing the version of the state transition bytecode. +pub type StateTransitionBytecodeVersion = u32; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(any(test, feature = "test-helpers"), derive(Default))] @@ -169,6 +174,10 @@ pub struct ApplicationHeader { /// layer 1 chain. They should also verify that the block number isn't too stale and is increasing. /// Some similar concerns are noted in this issue: pub da_height: DaBlockHeight, + /// The version of the consensus parameters used to execute this block. + pub consensus_parameters_version: ConsensusParametersVersion, + /// The version of the state transition bytecode used to execute this block. + pub state_transition_bytecode_version: StateTransitionBytecodeVersion, /// Generated application fields. pub generated: Generated, } @@ -370,6 +379,10 @@ impl PartialBlockHeader { let application = ApplicationHeader { da_height: self.application.da_height, + consensus_parameters_version: self.application.consensus_parameters_version, + state_transition_bytecode_version: self + .application + .state_transition_bytecode_version, generated: GeneratedApplicationFields { transactions_count: transactions.len() as u64, message_receipt_count: message_ids.len() as u64, @@ -415,11 +428,13 @@ impl ApplicationHeader { pub fn hash(&self) -> Bytes32 { // Order matters and is the same as the spec. let mut hasher = crate::fuel_crypto::Hasher::default(); - hasher.input(&self.da_height.to_bytes()[..]); + hasher.input(self.da_height.to_bytes().as_slice()); hasher.input(self.transactions_count.to_be_bytes()); hasher.input(self.message_receipt_count.to_be_bytes()); hasher.input(self.transactions_root.as_ref()); hasher.input(self.message_receipt_root.as_ref()); + hasher.input(self.consensus_parameters_version.to_bytes().as_slice()); + hasher.input(self.state_transition_bytecode_version.to_bytes().as_slice()); hasher.digest() } }