Skip to content

Commit

Permalink
Dry run in the past (#2661)
Browse files Browse the repository at this point in the history
Closes #2062

## Description

Support dry running in past blocks. It's behind the
`--historical-execution` flag.
  • Loading branch information
Dentosal authored Feb 10, 2025
1 parent 8224289 commit ed6aa39
Show file tree
Hide file tree
Showing 23 changed files with 544 additions and 49 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- [2630](https://github.com/FuelLabs/fuel-core/pull/2630): Removed some noisy `tracing::info!` logs
- [2643](https://github.com/FuelLabs/fuel-core/pull/2643): Before this fix when tip is zero, transactions that use 30M have the same priority as transactions with 1M gas. Now they are correctly ordered.

### Breaking
- [2661](https://github.com/FuelLabs/fuel-core/pull/2661): Dry run now supports running in past blocks. `dry_run_opt` method now takes block number as the last argument. To retain old behavior, simply pass in `None` for the last argument.

### Added
- [2617](https://github.com/FuelLabs/fuel-core/pull/2617): Add integration skeleton of parallel-executor.
- [2553](https://github.com/FuelLabs/fuel-core/pull/2553): Scaffold global merkle root storage crate.
Expand Down
2 changes: 1 addition & 1 deletion bin/e2e-test-client/src/tests/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ async fn _dry_runs(
let query = ctx
.alice
.client
.dry_run_opt(transactions, Some(false), None)
.dry_run_opt(transactions, Some(false), None, None)
.await;
println!(
"Received the response for the query number {i} for {}ms",
Expand Down
7 changes: 7 additions & 0 deletions bin/fuel-core/src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ pub struct Command {
#[arg(long = "debug", env)]
pub debug: bool,

/// Allows execution of transactions based on past block, such as:
/// - Dry run in the past
#[arg(long = "historical-execution", env)]
pub historical_execution: bool,

/// Enable logging of backtraces from vm errors
#[arg(long = "vm-backtrace", env)]
pub vm_backtrace: bool,
Expand Down Expand Up @@ -305,6 +310,7 @@ impl Command {
continue_on_error,
vm_backtrace,
debug,
historical_execution,
utxo_validation,
native_executor_version,
#[cfg(feature = "parallel-executor")]
Expand Down Expand Up @@ -623,6 +629,7 @@ impl Command {
combined_db_config,
snapshot_reader,
debug,
historical_execution,
native_executor_version,
continue_on_error,
utxo_validation,
Expand Down
2 changes: 1 addition & 1 deletion crates/client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ type Mutation {
"""
Execute a dry-run of multiple transactions using a fork of current state, no changes are committed.
"""
dryRun(txs: [HexString!]!, utxoValidation: Boolean, gasPrice: U64): [DryRunTransactionExecutionStatus!]!
dryRun(txs: [HexString!]!, utxoValidation: Boolean, gasPrice: U64, blockHeight: U32): [DryRunTransactionExecutionStatus!]!
"""
Submits transaction to the `TxPool`.

Expand Down
4 changes: 3 additions & 1 deletion crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ impl FuelClient {
&self,
txs: &[Transaction],
) -> io::Result<Vec<TransactionExecutionStatus>> {
self.dry_run_opt(txs, None, None).await
self.dry_run_opt(txs, None, None, None).await
}

/// Dry run with options to override the node behavior
Expand All @@ -490,6 +490,7 @@ impl FuelClient {
// Disable utxo input checks (exists, unspent, and valid signature)
utxo_validation: Option<bool>,
gas_price: Option<u64>,
at_height: Option<BlockHeight>,
) -> io::Result<Vec<TransactionExecutionStatus>> {
let txs = txs
.iter()
Expand All @@ -500,6 +501,7 @@ impl FuelClient {
txs,
utxo_validation,
gas_price: gas_price.map(|gp| gp.into()),
block_height: at_height.map(|bh| bh.into()),
});
let tx_statuses = self.query(query).await.map(|r| r.dry_run)?;
tx_statuses
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
source: crates/client/src/client/schema/tx.rs
expression: query.query
---
mutation DryRun($txs: [HexString!]!, $utxoValidation: Boolean, $gasPrice: U64) {
dryRun(txs: $txs, utxoValidation: $utxoValidation, gasPrice: $gasPrice) {
mutation DryRun($txs: [HexString!]!, $utxoValidation: Boolean, $gasPrice: U64, $blockHeight: U32) {
dryRun(txs: $txs, utxoValidation: $utxoValidation, gasPrice: $gasPrice, blockHeight: $blockHeight) {
id
status {
__typename
Expand Down
4 changes: 3 additions & 1 deletion crates/client/src/client/schema/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ pub struct DryRunArg {
pub txs: Vec<HexString>,
pub utxo_validation: Option<bool>,
pub gas_price: Option<U64>,
pub block_height: Option<U32>,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
Expand All @@ -442,7 +443,7 @@ pub struct DryRunArg {
variables = "DryRunArg"
)]
pub struct DryRun {
#[arguments(txs: $txs, utxoValidation: $utxo_validation, gasPrice: $gas_price)]
#[arguments(txs: $txs, utxoValidation: $utxo_validation, gasPrice: $gas_price, blockHeight: $block_height)]
pub dry_run: Vec<DryRunTransactionExecutionStatus>,
}

Expand Down Expand Up @@ -560,6 +561,7 @@ pub mod tests {
txs: vec![HexString(Bytes(tx.to_bytes()))],
utxo_validation: Some(true),
gas_price: Some(123u64.into()),
block_height: Some(456u32.into()),
});
insta::assert_snapshot!(query.query)
}
Expand Down
1 change: 1 addition & 0 deletions crates/fuel-core/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ mod tests {
backtrace: config.backtrace,
utxo_validation_default: config.utxo_validation_default,
native_executor_version: None,
allow_historical_execution: true,
};

let database = add_consensus_parameters(database, &config.consensus_parameters);
Expand Down
1 change: 1 addition & 0 deletions crates/fuel-core/src/graphql_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub struct Config {
pub config: ServiceConfig,
pub utxo_validation: bool,
pub debug: bool,
pub historical_execution: bool,
pub vm_backtrace: bool,
pub max_tx: usize,
pub max_gas: u64,
Expand Down
19 changes: 17 additions & 2 deletions crates/fuel-core/src/schema/tx.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::scalars::U64;
use super::scalars::{
U32,
U64,
};
use crate::{
fuel_core_graphql_api::{
api_service::{
Expand All @@ -7,6 +10,7 @@ use crate::{
TxPool,
},
query_costs,
Config as GraphQLConfig,
IntoApiResult,
},
graphql_api::{
Expand Down Expand Up @@ -293,13 +297,24 @@ impl TxMutation {
// for read-only calls.
utxo_validation: Option<bool>,
gas_price: Option<U64>,
// This can be used to run the dry-run on top of a past block.
// Requires `--historical-execution` flag to be enabled.
block_height: Option<U32>,
) -> async_graphql::Result<Vec<DryRunTransactionExecutionStatus>> {
let config = ctx.data_unchecked::<GraphQLConfig>().clone();
let block_producer = ctx.data_unchecked::<BlockProducer>();
let consensus_params = ctx
.data_unchecked::<ConsensusProvider>()
.latest_consensus_params();
let block_gas_limit = consensus_params.block_gas_limit();

if block_height.is_some() && !config.historical_execution {
return Err(anyhow::anyhow!(
"The `blockHeight` parameter requires the `--historical-execution` option"
)
.into());
}

let mut transactions = txs
.iter()
.map(|tx| FuelTx::from_bytes(&tx.0))
Expand All @@ -317,7 +332,7 @@ impl TxMutation {
let tx_statuses = block_producer
.dry_run_txs(
transactions,
None, // TODO(#1749): Pass parameter from API
block_height.map(|x| x.into()),
None, // TODO(#1749): Pass parameter from API
utxo_validation,
gas_price.map(|x| x.into()),
Expand Down
6 changes: 3 additions & 3 deletions crates/fuel-core/src/service/adapters/producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ use fuel_core_types::{
},
primitives::DaBlockHeight,
},
fuel_tx,
fuel_tx::{
ConsensusParameters,
Transaction,
Expand Down Expand Up @@ -117,10 +116,11 @@ impl fuel_core_producer::ports::BlockProducer<Vec<Transaction>> for ExecutorAdap
impl fuel_core_producer::ports::DryRunner for ExecutorAdapter {
fn dry_run(
&self,
block: Components<Vec<fuel_tx::Transaction>>,
block: Components<Vec<Transaction>>,
utxo_validation: Option<bool>,
at_height: Option<BlockHeight>,
) -> ExecutorResult<Vec<TransactionExecutionStatus>> {
self.executor.dry_run(block, utxo_validation)
self.executor.dry_run(block, utxo_validation, at_height)
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/fuel-core/src/service/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub struct Config {
/// - Enables debugger endpoint.
/// - Allows setting `utxo_validation` to `false`.
pub debug: bool,
/// When `true`:
/// - Enables dry run in the past.
pub historical_execution: bool,
// default to false until downstream consumers stabilize
pub utxo_validation: bool,
pub native_executor_version: Option<StateTransitionBytecodeVersion>,
Expand Down Expand Up @@ -158,6 +161,7 @@ impl Config {
combined_db_config,
continue_on_error: false,
debug: true,
historical_execution: true,
utxo_validation,
native_executor_version: Some(native_executor_version),
#[cfg(feature = "parallel-executor")]
Expand Down
11 changes: 11 additions & 0 deletions crates/fuel-core/src/service/sub_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use super::{
P2PAdapter,
},
genesis::create_genesis_block,
DbType,
};
#[cfg(feature = "relayer")]
use crate::relayer::Config as RelayerConfig;
Expand Down Expand Up @@ -102,10 +103,19 @@ pub fn init_sub_services(

let last_height = *last_block_header.height();

if config.historical_execution
&& config.combined_db_config.database_type != DbType::RocksDb
{
return Err(anyhow::anyhow!(
"Historical execution is only supported with RocksDB"
));
}

let upgradable_executor_config = fuel_core_upgradable_executor::config::Config {
backtrace: config.vm.backtrace,
utxo_validation_default: config.utxo_validation,
native_executor_version: config.native_executor_version,
allow_historical_execution: config.historical_execution,
};
let executor = ExecutorAdapter::new(
database.on_chain().clone(),
Expand Down Expand Up @@ -332,6 +342,7 @@ pub fn init_sub_services(
config: config.graphql_config.clone(),
utxo_validation: config.utxo_validation,
debug: config.debug,
historical_execution: config.historical_execution,
vm_backtrace: config.vm.backtrace,
max_tx: config.txpool.pool_limits.max_txs,
max_gas: config.txpool.pool_limits.max_gas,
Expand Down
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 @@ -363,7 +363,7 @@ where
// use the blocking threadpool for dry_run to avoid clogging up the main async runtime
let tx_statuses = tokio_rayon::spawn_fifo(
move || -> anyhow::Result<Vec<TransactionExecutionStatus>> {
Ok(executor.dry_run(component, utxo_validation)?)
Ok(executor.dry_run(component, utxo_validation, height)?)
},
)
.await?;
Expand Down
1 change: 1 addition & 0 deletions crates/services/producer/src/mocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ impl DryRunner for MockExecutorWithCapture {
&self,
block: Components<Vec<Transaction>>,
_utxo_validation: Option<bool>,
_height: Option<BlockHeight>,
) -> ExecutorResult<Vec<TransactionExecutionStatus>> {
*self.captured.lock().unwrap() = Some(block);

Expand Down
3 changes: 2 additions & 1 deletion crates/services/producer/src/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,11 @@ pub trait BlockProducer<TxSource>: Send + Sync {
pub trait DryRunner: Send + Sync {
/// Executes the block without committing it to the database. During execution collects the
/// receipts to return them. The `utxo_validation` field can be used to disable the validation
/// of utxos during execution.
/// of utxos during execution. The `at_height` field can be used to dry run on top of a past block.
fn dry_run(
&self,
block: Components<Vec<Transaction>>,
utxo_validation: Option<bool>,
at_height: Option<BlockHeight>,
) -> ExecutorResult<Vec<TransactionExecutionStatus>>;
}
3 changes: 3 additions & 0 deletions crates/services/upgradable-executor/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ pub struct Config {
/// When a block version matches the native executor version, we use
/// the native executor; otherwise, we use the WASM executor.
pub native_executor_version: Option<StateTransitionBytecodeVersion>,
/// Allow execution using blocks in the past.
/// This is rather expensive and not needed for most use cases, so it can be disabled.
pub allow_historical_execution: bool,
}

impl From<&Config> for ExecutionOptions {
Expand Down
Loading

0 comments on commit ed6aa39

Please sign in to comment.