Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dry run in the past #2661

Merged
merged 14 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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
2 changes: 1 addition & 1 deletion crates/client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,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!]!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should consider combining the utxoValidation, gasPrice and blockHeight args into a single input struct. This would allow us to add more fields in the future without breaking the API (though currently it's still breaking if we use the new arg in the client until we resolve #2676).

If you think this sounds sensible, we could create a follow-up issue for it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra args are already optional, isn't that already good enough? But it's good to consider the forward compatibility. I'm not sure if a struct would help in this case?

"""
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
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 `--debug` 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.debug {
return Err(anyhow::anyhow!(
"The `blockHeight` parameter requires the `--debug` option"
)
.into());
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still uses --debug and .debug.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2245cc1

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
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 @@ -202,6 +202,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>>;
}
80 changes: 49 additions & 31 deletions crates/services/upgradable-executor/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ enum ExecutionStrategy {
},
}

enum ProduceBlockMode {
Produce,
DryRunLatest,
DryRunAt { height: BlockHeight },
}
impl ProduceBlockMode {
fn is_dry_run(&self) -> bool {
matches!(self, Self::DryRunLatest | Self::DryRunAt { .. })
}
}

/// The upgradable executor supports the WASM version of the state transition function.
/// If the block has a version the same as a native executor, we will use it.
/// If not, the WASM version of the state transition function will be used
Expand Down Expand Up @@ -296,7 +307,7 @@ where
};

let options = self.config.as_ref().into();
self.produce_inner(component, options, false)
self.produce_inner(component, options, ProduceBlockMode::Produce)
}

/// Executes a dry-run of the block and returns the result of the execution without committing the changes.
Expand All @@ -308,7 +319,7 @@ where
TxSource: TransactionsSource + Send + Sync + 'static,
{
let options = self.config.as_ref().into();
self.produce_inner(block, options, true)
self.produce_inner(block, options, ProduceBlockMode::DryRunLatest)
}
}

Expand All @@ -329,7 +340,7 @@ where
TxSource: TransactionsSource + Send + Sync + 'static,
{
let options = self.config.as_ref().into();
self.produce_inner(components, options, false)
self.produce_inner(components, options, ProduceBlockMode::Produce)
}

/// Executes the block and returns the result of the execution without committing
Expand All @@ -338,6 +349,7 @@ where
&self,
component: Components<Vec<Transaction>>,
utxo_validation: Option<bool>,
at_height: Option<BlockHeight>,
) -> ExecutorResult<Vec<TransactionExecutionStatus>> {
// fallback to service config value if no utxo_validation override is provided
let utxo_validation =
Expand All @@ -361,7 +373,16 @@ where
skipped_transactions,
tx_status,
..
} = self.produce_inner(component, options, true)?.into_result();
} = self
.produce_inner(
component,
options,
match at_height {
Some(height) => ProduceBlockMode::DryRunAt { height },
None => ProduceBlockMode::DryRunLatest,
},
)?
.into_result();

// If one of the transactions fails, return an error.
if let Some((_, err)) = skipped_transactions.into_iter().next() {
Expand All @@ -384,7 +405,7 @@ where
&self,
block: Components<TxSource>,
options: ExecutionOptions,
dry_run: bool,
mode: ProduceBlockMode,
) -> ExecutorResult<Uncommitted<ExecutionResult, Changes>>
where
TxSource: TransactionsSource + Send + Sync + 'static,
Expand All @@ -394,20 +415,20 @@ where
if block_version == native_executor_version {
match &self.execution_strategy {
ExecutionStrategy::Native => {
self.native_produce_inner(block, options, dry_run)
self.native_produce_inner(block, options, mode)
}
ExecutionStrategy::Wasm { module } => {
let maybe_blocks_module = self.get_module(block_version).ok();
if let Some(blocks_module) = maybe_blocks_module {
self.wasm_produce_inner(&blocks_module, block, options, dry_run)
self.wasm_produce_inner(&blocks_module, block, options, mode)
} else {
self.wasm_produce_inner(module, block, options, dry_run)
self.wasm_produce_inner(module, block, options, mode)
}
}
}
} else {
let module = self.get_module(block_version)?;
self.wasm_produce_inner(&module, block, options, dry_run)
self.wasm_produce_inner(&module, block, options, mode)
}
}

Expand All @@ -416,15 +437,15 @@ where
&self,
block: Components<TxSource>,
options: ExecutionOptions,
dry_run: bool,
mode: ProduceBlockMode,
) -> ExecutorResult<Uncommitted<ExecutionResult, Changes>>
where
TxSource: TransactionsSource + Send + Sync + 'static,
{
let block_version = block.header_to_produce.state_transition_bytecode_version;
let native_executor_version = self.native_executor_version();
if block_version == native_executor_version {
self.native_produce_inner(block, options, dry_run)
self.native_produce_inner(block, options, mode)
} else {
Err(ExecutorError::Other(format!(
"Not supported version `{block_version}`. Expected version is `{}`",
Expand Down Expand Up @@ -493,7 +514,7 @@ where
module: &wasmtime::Module,
component: Components<TxSource>,
options: ExecutionOptions,
dry_run: bool,
mode: ProduceBlockMode,
) -> ExecutorResult<Uncommitted<ExecutionResult, Changes>>
where
TxSource: TransactionsSource + Send + Sync + 'static,
Expand All @@ -517,19 +538,16 @@ where
gas_price,
};

let previous_block_height = if !dry_run {
block.header_to_produce.height().pred()
} else {
// TODO: https://github.com/FuelLabs/fuel-core/issues/2062
None
let db_height = match mode {
ProduceBlockMode::Produce => block.header_to_produce.height().pred(),
ProduceBlockMode::DryRunLatest => None,
ProduceBlockMode::DryRunAt { height } => height.pred(),
rafal-ch marked this conversation as resolved.
Show resolved Hide resolved
rafal-ch marked this conversation as resolved.
Show resolved Hide resolved
};
xgreenx marked this conversation as resolved.
Show resolved Hide resolved

let instance_without_input =
crate::instance::Instance::new(&self.engine).add_source(source)?;

let instance_without_input = if let Some(previous_block_height) =
previous_block_height
{
let instance_without_input = if let Some(previous_block_height) = db_height {
let storage = self.storage_view_provider.view_at(&previous_block_height)?;
instance_without_input.add_storage(storage)?
} else {
Expand All @@ -540,7 +558,7 @@ where
let relayer = self.relayer_view_provider.latest_view()?;
let instance_without_input = instance_without_input.add_relayer(relayer)?;

let instance = if dry_run {
let instance = if mode.is_dry_run() {
instance_without_input.add_dry_run_input_data(block, options)?
} else {
instance_without_input.add_production_input_data(block, options)?
Expand Down Expand Up @@ -609,27 +627,27 @@ where
&self,
block: Components<TxSource>,
options: ExecutionOptions,
dry_run: bool,
mode: ProduceBlockMode,
) -> ExecutorResult<Uncommitted<ExecutionResult, Changes>>
where
TxSource: TransactionsSource + Send + Sync + 'static,
{
let previous_block_height = if !dry_run {
block.header_to_produce.height().pred()
} else {
// TODO: https://github.com/FuelLabs/fuel-core/issues/2062
None
};
let relayer = self.relayer_view_provider.latest_view()?;

if let Some(previous_block_height) = previous_block_height {
let db_height = match mode {
ProduceBlockMode::Produce => block.header_to_produce.height().pred(),
ProduceBlockMode::DryRunLatest => None,
ProduceBlockMode::DryRunAt { height } => height.pred(),
};

if let Some(previous_block_height) = db_height {
let database = self.storage_view_provider.view_at(&previous_block_height)?;
ExecutionInstance::new(relayer, database, options)
.produce_without_commit(block, dry_run)
.produce_without_commit(block, mode.is_dry_run())
} else {
let database = self.storage_view_provider.latest_view()?;
ExecutionInstance::new(relayer, database, options)
.produce_without_commit(block, dry_run)
.produce_without_commit(block, mode.is_dry_run())
}
}

Expand Down
Loading
Loading