Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/services/producer/src/block_producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ where
height,
previous_block: latest_height,
}
.into())
.into());
}

let maybe_mint_tx = transactions_source.pop();
Expand Down
39 changes: 18 additions & 21 deletions crates/services/producer/src/block_producer/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use crate::{
},
},
mocks::{
FailingMockExecutor,
MockDb,
MockExecutor,
MockExecutorWithCapture,
Expand Down Expand Up @@ -518,9 +517,12 @@ mod produce_and_execute_block_txpool {

#[tokio::test]
async fn production_fails_on_execution_error() {
let ctx = TestContext::default_from_executor(FailingMockExecutor(Mutex::new(
Some(ExecutorError::TransactionIdCollision(Default::default())),
)));
let db = TestContext::<MockExecutor>::default_db();
let executor = crate::mocks::create_failing_mock_executor(
ExecutorError::TransactionIdCollision(Default::default()),
db.clone(),
);
Comment thread
raushan728 marked this conversation as resolved.
let ctx = TestContext::default_from_db_and_executor(db, executor);

let producer = ctx.producer();

Expand Down Expand Up @@ -1015,7 +1017,7 @@ impl TestContext<MockExecutor> {
}

pub fn default_from_db(db: MockDb) -> Self {
let executor = MockExecutor(db.clone());
let executor = crate::mocks::create_mock_executor(db.clone());
Self::default_from_db_and_executor(db, executor)
}
}
Expand All @@ -1039,8 +1041,9 @@ impl<Executor> TestContext<Executor> {
}

pub fn default_from_db_and_executor(db: MockDb, executor: Executor) -> Self {
let txpool = MockTxPool::default();
let relayer = MockRelayer::default();
let txpool = crate::mocks::create_mock_txpool(vec![]);
let relayer =
crate::mocks::create_mock_relayer(DaBlockHeight::default(), HashMap::new());
let config = Config::default();
let gas_price = Some(0);
Self {
Expand Down Expand Up @@ -1229,13 +1232,10 @@ impl TestContextBuilder {
fn build(self) -> TestContext<MockExecutor> {
let block_gas_limit = self.block_gas_limit.unwrap_or_default();

let mock_relayer = MockRelayer {
latest_block_height: self.latest_block_height,
latest_da_blocks_with_costs_and_transactions_number: self
.blocks_with_gas_costs_and_transactions_number
.clone(),
..Default::default()
};
let mock_relayer = crate::mocks::create_mock_relayer(
self.latest_block_height,
self.blocks_with_gas_costs_and_transactions_number.clone(),
);

let db = MockDb {
blocks: self.pre_existing_blocks(),
Expand All @@ -1253,13 +1253,10 @@ impl TestContextBuilder {
fn build_with_executor<Ex>(self, executor: Ex) -> TestContext<Ex> {
let block_gas_limit = self.block_gas_limit.unwrap_or_default();

let mock_relayer = MockRelayer {
latest_block_height: self.latest_block_height,
latest_da_blocks_with_costs_and_transactions_number: self
.blocks_with_gas_costs_and_transactions_number
.clone(),
..Default::default()
};
let mock_relayer = crate::mocks::create_mock_relayer(
self.latest_block_height,
self.blocks_with_gas_costs_and_transactions_number.clone(),
);

let db = MockDb {
blocks: self.pre_existing_blocks(),
Expand Down
239 changes: 145 additions & 94 deletions crates/services/producer/src/mocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ use fuel_core_types::{
},
fuel_tx::Transaction,
fuel_types::{
Address,
BlockHeight,
Bytes32,
ChainId,
Expand All @@ -53,65 +52,161 @@ use std::{
Mutex,
},
};
// TODO: Replace mocks with `mockall`.

#[derive(Default, Clone)]
pub struct MockRelayer {
pub block_production_key: Address,
pub latest_block_height: DaBlockHeight,
pub latest_da_blocks_with_costs_and_transactions_number:
HashMap<DaBlockHeight, (u64, u64)>,
}
// Mockall-generated mocks for testing
#[cfg(feature = "test-helpers")]
use mockall::mock;

#[async_trait::async_trait]
impl Relayer for MockRelayer {
async fn wait_for_at_least_height(
&self,
_height: &DaBlockHeight,
) -> anyhow::Result<DaBlockHeight> {
let highest = self.latest_block_height;
Ok(highest)
}
#[cfg(feature = "test-helpers")]
mock! {
pub Relayer {}

async fn get_cost_and_transactions_number_for_block(
&self,
height: &DaBlockHeight,
) -> anyhow::Result<RelayerBlockInfo> {
let (gas_cost, tx_count) = self
.latest_da_blocks_with_costs_and_transactions_number
.get(height)
.cloned()
.unwrap_or_default();
Ok(RelayerBlockInfo { gas_cost, tx_count })
#[async_trait::async_trait]
impl Relayer for Relayer {
async fn wait_for_at_least_height(
&self,
height: &DaBlockHeight,
) -> anyhow::Result<DaBlockHeight>;

async fn get_cost_and_transactions_number_for_block(
&self,
height: &DaBlockHeight,
) -> anyhow::Result<RelayerBlockInfo>;
}
}

#[derive(Default)]
pub struct MockTxPool(pub Vec<Transaction>);
// Re-export for backward compatibility during migration
#[cfg(feature = "test-helpers")]
pub use MockRelayer as MockRelayerLegacy;

impl TxPool for MockTxPool {
type TxSource = Vec<Transaction>;
// Helper function to create a MockRelayer with default behavior
#[cfg(feature = "test-helpers")]
pub fn create_mock_relayer(
latest_height: DaBlockHeight,
blocks_with_costs: HashMap<DaBlockHeight, (u64, u64)>,
) -> MockRelayer {
let mut mock = MockRelayer::new();

async fn get_source(&self, _: u64, _: BlockHeight) -> anyhow::Result<Self::TxSource> {
Ok(self.0.clone())
}
// Default behavior: return the latest height
mock.expect_wait_for_at_least_height()
.returning(move |_| Ok(latest_height));

// Default behavior: return gas cost and tx count from the map
mock.expect_get_cost_and_transactions_number_for_block()
.returning(move |height| {
let (gas_cost, tx_count) =
blocks_with_costs.get(height).cloned().unwrap_or_default();
Ok(RelayerBlockInfo { gas_cost, tx_count })
});

mock
}

#[derive(Default)]
pub struct MockExecutor(pub MockDb);
// MockTxPool with associated type
#[cfg(feature = "test-helpers")]
mock! {
pub TxPool {}

impl AsMut<MockDb> for MockDb {
fn as_mut(&mut self) -> &mut MockDb {
self
impl TxPool for TxPool {
type TxSource = Vec<Transaction>;

async fn get_source(&self, gas_price: u64, block_height: BlockHeight) -> anyhow::Result<Vec<Transaction>>;
}
}

impl AsRef<MockDb> for MockDb {
fn as_ref(&self) -> &MockDb {
self
// Helper function to create a MockTxPool with default behavior
#[cfg(feature = "test-helpers")]
pub fn create_mock_txpool(transactions: Vec<Transaction>) -> MockTxPool {
let mut mock = MockTxPool::new();

// Default behavior: return the transactions
mock.expect_get_source()
.returning(move |_, _| Ok(transactions.clone()));

mock
}

// MockExecutor - BlockProducer implementation
#[cfg(feature = "test-helpers")]
mock! {
pub Executor {}

impl BlockProducer<Vec<Transaction>> for Executor {
type Deadline = ();

async fn produce_without_commit(
&self,
component: Components<Vec<Transaction>>,
deadline: (),
) -> ExecutorResult<UncommittedResult<Changes>>;
}
}

// Helper function to create a MockExecutor with default successful behavior
#[cfg(feature = "test-helpers")]
pub fn create_mock_executor(db: MockDb) -> MockExecutor {
let mut mock = MockExecutor::new();

// Default behavior: create a block and insert it into the database
mock.expect_produce_without_commit()
.returning(move |component, _| {
let block = arc_pool_tx_comp_to_block(&component);
// simulate executor inserting a block
let mut block_db = db.blocks.lock().unwrap();
block_db.insert(
*block.header().height(),
block.compress(&ChainId::default()),
);
Ok(UncommittedResult::new(
ExecutionResult {
block,
skipped_transactions: vec![],
tx_status: vec![],
events: vec![],
},
Default::default(),
))
});

mock
}

// Helper function to create a failing MockExecutor
// This replaces the old FailingMockExecutor struct
// Fails once with the provided error, then succeeds on subsequent calls
#[cfg(feature = "test-helpers")]
pub fn create_failing_mock_executor(error: ExecutorError, db: MockDb) -> MockExecutor {
let mut mock = MockExecutor::new();

// First call: fail with the provided error
mock.expect_produce_without_commit()
.times(1)
.return_once(move |_, _| Err(error));

// Subsequent calls: succeed with standard executor logic
mock.expect_produce_without_commit()
.returning(move |component, _| {
let block = arc_pool_tx_comp_to_block(&component);
let mut block_db = db.blocks.lock().unwrap();
block_db.insert(
*block.header().height(),
block.compress(&ChainId::default()),
);
Ok(UncommittedResult::new(
ExecutionResult {
block,
skipped_transactions: vec![],
tx_status: vec![],
events: vec![],
},
Default::default(),
))
});

mock
}
Comment thread
raushan728 marked this conversation as resolved.

// Helper functions for block creation (used by mocks)
#[cfg(feature = "test-helpers")]
fn arc_pool_tx_comp_to_block(component: &Components<Vec<Transaction>>) -> Block {
let transactions = component.transactions_source.clone();
Block::new(
Expand All @@ -125,59 +220,15 @@ fn arc_pool_tx_comp_to_block(component: &Components<Vec<Transaction>>) -> Block
.unwrap()
}

impl BlockProducer<Vec<Transaction>> for MockExecutor {
type Deadline = ();

async fn produce_without_commit(
&self,
component: Components<Vec<Transaction>>,
_: (),
) -> ExecutorResult<UncommittedResult<Changes>> {
let block = arc_pool_tx_comp_to_block(&component);
// simulate executor inserting a block
let mut block_db = self.0.blocks.lock().unwrap();
block_db.insert(
*block.header().height(),
block.compress(&ChainId::default()),
);
Ok(UncommittedResult::new(
ExecutionResult {
block,
skipped_transactions: vec![],
tx_status: vec![],
events: vec![],
},
Default::default(),
))
impl AsMut<MockDb> for MockDb {
fn as_mut(&mut self) -> &mut MockDb {
self
}
}

pub struct FailingMockExecutor(pub Mutex<Option<ExecutorError>>);

impl BlockProducer<Vec<Transaction>> for FailingMockExecutor {
type Deadline = ();
async fn produce_without_commit(
&self,
component: Components<Vec<Transaction>>,
_: (),
) -> ExecutorResult<UncommittedResult<Changes>> {
// simulate an execution failure
let mut err = self.0.lock().unwrap();
match err.take() {
Some(err) => Err(err),
_ => {
let block = arc_pool_tx_comp_to_block(&component);
Ok(UncommittedResult::new(
ExecutionResult {
block,
skipped_transactions: vec![],
tx_status: vec![],
events: vec![],
},
Default::default(),
))
}
}
impl AsRef<MockDb> for MockDb {
fn as_ref(&self) -> &MockDb {
self
}
}

Expand Down