# Understanding Fuel Core Block Producer Modes of Operation: Executor, Manual, and Automatic Triggers

> Explore Fuel Core block producer modes: Executor for state control, Manual for requests, and Automatic for scheduling. Understand how each mode governs block production on the Fuel blockchain.

- Repository: [Fuel Labs/fuel-core](https://github.com/FuelLabs/fuel-core)
- Tags: deep-dive
- Published: 2026-03-06

---

**Fuel Core supports three distinct block producer operation modes—`ProduceBlockMode` for executor-level state control, `Mode` for manual production requests, and `Trigger` for automatic scheduling—that together govern how blocks are constructed, validated, and committed in the Fuel blockchain.**

The Fuel Core block producer operates through a layered architecture that decouples scheduling logic from execution strategy. According to the FuelLabs/fuel-core source code, these distinct modes allow the Proof-of-Authority (PoA) consensus module to handle everything from production testing to live network validation. Understanding these modes is essential for node operators developers building high-performance applications on the Fuel network.

## Executor-Level Production: `ProduceBlockMode`

At the lowest level, the `ProduceBlockMode` enum in [`crates/services/upgradable-executor/src/executor.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/upgradable-executor/src/executor.rs) (lines 109–115) determines whether the executor commits state changes or merely simulates execution. This mode controls **how** the block is processed at the database level.

### Normal Production vs. Dry-Run

The executor recognizes two primary variants:

- **`Produce`** – The standard mode that executes transactions and commits the resulting state to the database permanently.
- **`DryRun { height, record_storage_reads }`** – A simulation mode that executes the block without committing state changes. This variant accepts a target block height (allowing historical replays) and a boolean flag to optionally record storage reads for debugging purposes.

The `DryRun` variant is particularly valuable for estimating gas costs, testing transaction validity against historical states, and debugging contract interactions without consuming network resources. When invoked, the executor returns an `ExecutionResult` containing transaction receipts while leaving the underlying Merkle trees unchanged.

## Manual Production Requests: `Mode`

The PoA consensus module exposes a `Mode` enum in [`crates/services/consensus_module/poa/src/service.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/consensus_module/poa/src/service.rs) (lines 96–101) that enables external callers to request specific block production behaviors. This layer controls **what** content is included in the block.

### Bulk Block Production

The **`Blocks { number_of_blocks: u32 }`** variant instructs the producer to generate a fixed number of consecutive blocks using transactions from the mempool. This mode is primarily used for stress testing, network bootstrapping, or bulk-generating empty blocks during initialization. The `manually_produce_block` method accepts this variant to trigger immediate production outside normal scheduling.

### Custom Transaction Sets

Alternatively, **`BlockWithTransactions(Vec<Transaction>)`** allows the caller to specify an exact list of transactions to include in a single block. This deterministic approach bypasses the transaction pool entirely, making it ideal for testing specific execution paths, replaying historical blocks, or implementing custom sequencing logic in private networks.

## Automatic Production Triggers: `Trigger`

The `Trigger` enum defined in [`crates/services/consensus_module/poa/src/config.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/consensus_module/poa/src/config.rs) (lines 33–47) configures **when** the block producer activates. This automatic scheduling layer operates independently of manual requests.

### Instant, Interval, and Open Modes

- **`Instant`** – Produces a block immediately upon detecting any transaction in the mempool. This minimizes latency for high-throughput test environments.
- **`Interval { block_time }`** – Produces blocks at fixed time intervals (e.g., every 3 seconds), simulating traditional blockchain cadences suitable for production networks.
- **`Open { period }`** – Opens a temporary production window for a specified duration, allowing continuous block production during bootstrap phases or maintenance windows.

### Never Mode for Observer Nodes

The **`Never`** variant disables automatic production entirely, configuring the node as a pure observer or API endpoint. In this mode, the node validates and forwards transactions but never proposes new blocks, which is useful for indexers, explorers, or sentry nodes in a validator cluster.

## Architectural Flow: How Modes Interact

The three modes operate in a cascading hierarchy that separates concerns across the stack:

1. **Trigger → Scheduler** – The PoA service reads the `Trigger` configuration from [`config.rs`](https://github.com/FuelLabs/fuel-core/blob/main/config.rs) and initializes an asynchronous scheduler.
2. **Scheduler → Mode** – When the trigger fires or an admin API call arrives, the service constructs either a `Mode::Blocks` request for bulk production or `Mode::BlockWithTransactions` for custom sets.
3. **Mode → Executor** – The service forwards the request to the block producer ([`crates/services/producer/src/block_producer.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/producer/src/block_producer.rs)), which invokes the upgradable executor with `ProduceBlockMode::Produce` for normal operations or `ProduceBlockMode::DryRun` for simulations.

This layered design allows operators to mix and match configurations: for example, running with `Trigger::Never` for manual control while using `DryRun` to validate transactions before submission.

## Practical Implementation Examples

### Simulating Blocks with Dry-Run

To execute a block without committing state changes, invoke the executor's dry-run method as implemented in [`crates/services/upgradable-executor/src/executor.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/upgradable-executor/src/executor.rs):

```rust
use fuel_core_services::upgradable_executor::Executor;
use fuel_core_types::services::executor::Result as ExecutorResult;

// executor is an initialized Executor instance
let block = /* construct PartialFuelBlock */;

let dry_result: ExecutorResult<_> = executor.dry_run_without_commit(block);
match dry_result {
    Ok(uncommitted) => {
        println!("Executed {} transactions", uncommitted.result().transactions.len());
        // State remains uncommitted; inspect receipts without side effects
    }
    Err(e) => eprintln!("Dry-run failed: {e:?}"),
}

```

### Manually Producing Block Batches

For testing or bootstrap scenarios, manually trigger batch production using the PoA service:

```rust
use fuel_core_consensus_module_poa::service::{Mode, Trigger};
use std::time::Duration;

let cfg = Config {
    trigger: Trigger::Never,  // Disable automatic triggers
    ..Default::default()
};

let ctx = TestContextBuilder::new()
    .with_config(cfg)
    .build()
    .await;

// Produce exactly 5 blocks from the mempool
ctx.service
    .shared
    .manually_produce_block(None, Mode::Blocks { number_of_blocks: 5 })
    .await
    .expect("Manual production failed");

```

### Configuring Automatic Interval Production

Configure `Trigger::Interval` via the node's TOML configuration file to enable regular block production:

```toml
[consensus_module.poa]
trigger = { Interval = { block_time = "3s" } }
signer = { Key = "0xdeadbeef..." }

```

This configuration directs the scheduler in [`crates/services/consensus_module/poa/src/service.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/consensus_module/poa/src/service.rs) to call `produce_and_execute` every 3 seconds.

## Summary

- **`ProduceBlockMode`** controls executor behavior, offering `Produce` for permanent state commits and `DryRun` for simulation without side effects.
- **`Mode`** handles manual production requests, supporting both bulk block generation (`Blocks`) and custom transaction injection (`BlockWithTransactions`).
- **`Trigger`** manages automatic scheduling with variants for immediate (`Instant`), periodic (`Interval`), temporary (`Open`), or disabled (`Never`) production.
- The architecture decouples scheduling (`Trigger`), content selection (`Mode`), and execution semantics (`ProduceBlockMode`) across `crates/services/consensus_module/poa/` and `crates/services/upgradable-executor/`.

## Frequently Asked Questions

### What is the difference between DryRun and Produce modes in Fuel Core?

**`DryRun`** simulates block execution without writing to the database, returning transaction receipts and gas estimates while leaving state unchanged. **`Produce`** commits all state transitions permanently to the underlying storage. According to the implementation in [`crates/services/upgradable-executor/src/executor.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/upgradable-executor/src/executor.rs), `DryRun` accepts optional parameters for historical height targeting and storage read recording, making it ideal for debugging and gas estimation.

### How do I configure a Fuel Core node to produce blocks at regular intervals?

Set the `Trigger` variant to `Interval` in your configuration TOML: `[consensus_module.poa] trigger = { Interval = { block_time = "3s" } }`. This corresponds to `Trigger::Interval { block_time: Duration }` in [`crates/services/consensus_module/poa/src/config.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/consensus_module/poa/src/config.rs). The PoA service scheduler will then invoke the block producer at the specified cadence.

### Can Fuel Core produce blocks without a transaction pool?

Yes, by using **`Mode::BlockWithTransactions`**. This mode, defined in [`crates/services/consensus_module/poa/src/service.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/consensus_module/poa/src/service.rs), allows the caller to pass a `Vec<Transaction>` directly to `manually_produce_block`, bypassing the mempool entirely. This is commonly used in deterministic testing environments where specific transaction sequences must be executed.

### What is the purpose of the Open trigger mode?

**`Trigger::Open { period }`** creates a time-bounded window where the block producer remains active and can generate multiple blocks as transactions arrive. Unlike `Instant` (which produces one block per transaction batch) or `Interval` (which produces at fixed times), `Open` is useful for bootstrap phases or maintenance windows where continuous production is needed for a specific duration before returning to normal scheduling rules.