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

Execution tracing: GraphQL query to get storage inputs for past blocks #2491

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
676a08b
Initial support for historical execution tracing
Dentosal Dec 11, 2024
1e018f1
Update schema.sdl
Dentosal Dec 11, 2024
5f6c079
Fix typo exection => execution
Dentosal Dec 11, 2024
7c8548f
Make execution trace endpoint to work on block level
Dentosal Dec 12, 2024
6ebe050
Fix cli arguments
Dentosal Dec 12, 2024
cd0704a
Various fixes and work towards Rust client support
Dentosal Dec 12, 2024
63a0016
Merge branch 'master' into dento/execution-trace
Dentosal Dec 12, 2024
f76c96f
WIP
Dentosal Dec 18, 2024
3ba2bc7
Change from tracing to storage read replay recording
Dentosal Dec 19, 2024
88a2730
Merge branch 'master' into dento/execution-trace
Dentosal Dec 30, 2024
f558296
Merge branch 'master' into dento/execution-trace
Dentosal Jan 6, 2025
5abde1c
Add wasm executor support
Dentosal Jan 15, 2025
6827e0d
Move behind --debug flag
Dentosal Jan 23, 2025
acb9bab
Adjust query cost
Dentosal Jan 23, 2025
bb6c9dd
Add debug flag decription
Dentosal Jan 23, 2025
75ff219
Approve snapshot changes
Dentosal Jan 23, 2025
73cb7a6
Add a test case
Dentosal Jan 23, 2025
7d76dc3
Merge branch 'master' into dento/execution-trace
Dentosal Jan 23, 2025
341544f
Add changelog entry
Dentosal Jan 23, 2025
84140f4
fmt
Dentosal Jan 23, 2025
3587605
Use rng correctly to avoid repeat txids
Dentosal Jan 23, 2025
2a52ec2
restore #[deny(warnings)]
Dentosal Jan 23, 2025
2c2c788
fmt
Dentosal Jan 23, 2025
83e2919
clippy
Dentosal Jan 24, 2025
b0c40fb
Merge branch 'master' into dento/execution-trace
Dentosal Jan 24, 2025
de92215
more clippy
Dentosal Jan 24, 2025
31fd109
fix typo
Dentosal Jan 24, 2025
a90b559
Add TODO comments for refactoring
Dentosal Jan 24, 2025
017c03e
more clippy
Dentosal Jan 24, 2025
f740d28
Merge branch 'master' into dento/execution-trace
Dentosal Jan 27, 2025
fce82ea
Address PR comments
Dentosal Jan 29, 2025
a4a6bbf
Merge branch 'master' into dento/execution-trace
Dentosal Jan 29, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Added
- [2491](https://github.com/FuelLabs/fuel-core/pull/2491): Storage read replays of historical blocks for execution tracing. Only available behind `--debug` flag.
- [2635](https://github.com/FuelLabs/fuel-core/pull/2635): Add metrics to gas price service

### Changed
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ postcard = "1.0"
tracing-attributes = "0.1"
tracing-subscriber = "0.3"
serde = "1.0"
serde-big-array = { version = "0.5", default-features = false }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
serde_with = { version = "3.4", default-features = false }
strum = { version = "0.25" }
Expand Down
1 change: 1 addition & 0 deletions bin/fuel-core/src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ impl Command {
get_peers: graphql.costs.get_peers,
estimate_predicates: graphql.costs.estimate_predicates,
dry_run: graphql.costs.dry_run,
storage_read_replay: graphql.costs.storage_read_replay,
submit: graphql.costs.submit,
submit_and_await: graphql.costs.submit_and_await,
status_change: graphql.costs.status_change,
Expand Down
8 changes: 8 additions & 0 deletions bin/fuel-core/src/cli/run/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ pub struct QueryCosts {
)]
pub dry_run: usize,

/// Query costs for generating execution trace for a block.®
#[clap(
long = "query-cost-storage-read-replay",
default_value = DEFAULT_QUERY_COSTS.storage_read_replay.to_string(),
env
)]
pub storage_read_replay: usize,

/// Query costs for submitting a transaction.
#[clap(
long = "query-cost-submit",
Expand Down
10 changes: 10 additions & 0 deletions crates/client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,10 @@ type Mutation {
"""
dryRun(txs: [HexString!]!, utxoValidation: Boolean, gasPrice: U64): [DryRunTransactionExecutionStatus!]!
"""
Get execution trace for an already-executed transaction.
"""
storageReadReplay(height: U32!): [StorageReadReplayEvent!]!
"""
Submits transaction to the `TxPool`.

Returns submitted transaction if the transaction is included in the `TxPool` without problems.
Expand Down Expand Up @@ -1151,6 +1155,12 @@ type StateTransitionPurpose {
root: Bytes32!
}

type StorageReadReplayEvent {
column: String!
key: HexString!
value: HexString
}


scalar SubId

Expand Down
27 changes: 26 additions & 1 deletion crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ use fuel_core_types::{
BlockHeight,
Nonce,
},
services::executor::TransactionExecutionStatus,
services::executor::{
StorageReadReplayEvent,
TransactionExecutionStatus,
},
};
#[cfg(feature = "subscriptions")]
use futures::{
Expand Down Expand Up @@ -508,6 +511,28 @@ impl FuelClient {
.collect()
}

/// Get storage read replay for a block
pub async fn storage_read_replay(
&self,
height: &BlockHeight,
) -> io::Result<Vec<StorageReadReplayEvent>> {
let query: Operation<
schema::storage_read_replay::StorageReadReplay,
schema::storage_read_replay::StorageReadReplayArgs,
> = schema::storage_read_replay::StorageReadReplay::build(
schema::storage_read_replay::StorageReadReplayArgs {
height: (*height).into(),
},
);
Ok(self
.query(query)
.await
.map(|r| r.storage_read_replay)?
.into_iter()
.map(Into::into)
.collect())
}

/// Estimate predicates for the transaction
pub async fn estimate_predicates(&self, tx: &mut Transaction) -> io::Result<()> {
let serialized_tx = tx.to_bytes();
Expand Down
1 change: 1 addition & 0 deletions crates/client/src/client/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub mod contract;
pub mod da_compressed;
pub mod message;
pub mod node_info;
pub mod storage_read_replay;
pub mod upgrades;

pub mod gas_price;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: crates/client/src/client/schema/storage_read_replay.rs
expression: query.query
---
mutation StorageReadReplay($height: U32!) {
storageReadReplay(height: $height) {
column
key
value
}
}
58 changes: 58 additions & 0 deletions crates/client/src/client/schema/storage_read_replay.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use super::HexString;
use crate::client::schema::{
schema,
U32,
};

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct StorageReadReplayEvent {
pub column: String,
pub key: HexString,
pub value: Option<HexString>,
}
impl From<StorageReadReplayEvent>
for fuel_core_types::services::executor::StorageReadReplayEvent
{
fn from(event: StorageReadReplayEvent) -> Self {
fuel_core_types::services::executor::StorageReadReplayEvent {
column: event.column,
key: event.key.into(),
value: event.value.map(Into::into),
}
}
}

// mutations

#[derive(cynic::QueryVariables, Debug)]
pub struct StorageReadReplayArgs {
pub height: U32,
}

/// Retrieves the transaction in opaque form
#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Mutation",
variables = "StorageReadReplayArgs"
)]
pub struct StorageReadReplay {
#[arguments(height: $height)]
pub storage_read_replay: Vec<StorageReadReplayEvent>,
}

#[cfg(test)]
pub mod tests {
use super::*;
use fuel_core_types::fuel_types::BlockHeight;

#[test]
fn execution_trace_block_tx_gql_output() {
use cynic::MutationBuilder;
let query = StorageReadReplay::build(StorageReadReplayArgs {
height: BlockHeight::new(1234).into(),
});
insta::assert_snapshot!(query.query)
}
}
2 changes: 1 addition & 1 deletion crates/client/src/client/schema/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ impl TryFrom<DryRunTransactionStatus> for TransactionExecutionResult {
}
}
DryRunTransactionStatus::Unknown => {
return Err(Self::Error::UnknownVariant("DryRuynTxStatus"))
return Err(Self::Error::UnknownVariant("DryRunTxStatus"))
}
})
}
Expand Down
2 changes: 2 additions & 0 deletions crates/fuel-core/src/graphql_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub struct Costs {
pub get_peers: usize,
pub estimate_predicates: usize,
pub dry_run: usize,
pub storage_read_replay: usize,
pub submit: usize,
pub submit_and_await: usize,
pub status_change: usize,
Expand Down Expand Up @@ -90,6 +91,7 @@ pub const DEFAULT_QUERY_COSTS: Costs = Costs {
get_peers: 40001,
estimate_predicates: 40001,
dry_run: 12000,
storage_read_replay: 12000,
submit: 40001,
submit_and_await: 40001,
status_change: 40001,
Expand Down
10 changes: 9 additions & 1 deletion crates/fuel-core/src/graphql_api/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ use fuel_core_types::{
},
fuel_vm::interpreter::Memory,
services::{
executor::TransactionExecutionStatus,
executor::{
StorageReadReplayEvent,
TransactionExecutionStatus,
},
graphql_api::ContractBalance,
p2p::PeerInfo,
txpool::TransactionStatus,
Expand Down Expand Up @@ -256,6 +259,11 @@ pub trait BlockProducerPort: Send + Sync {
utxo_validation: Option<bool>,
gas_price: Option<u64>,
) -> anyhow::Result<Vec<TransactionExecutionStatus>>;

async fn storage_read_replay(
&self,
height: BlockHeight,
) -> anyhow::Result<Vec<StorageReadReplayEvent>>;
}

#[async_trait::async_trait]
Expand Down
31 changes: 30 additions & 1 deletion 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 @@ -73,6 +77,7 @@ use std::{
};
use types::{
DryRunTransactionExecutionStatus,
StorageReadReplayEvent,
Transaction,
};

Expand Down Expand Up @@ -331,6 +336,30 @@ impl TxMutation {
Ok(tx_statuses)
}

/// Get execution trace for an already-executed transaction.
#[graphql(complexity = "query_costs().storage_read_replay + child_complexity")]
async fn storage_read_replay(
&self,
ctx: &Context<'_>,
height: U32,
) -> async_graphql::Result<Vec<StorageReadReplayEvent>> {
let config = ctx.data_unchecked::<GraphQLConfig>();
if !config.debug {
return Err(
anyhow::anyhow!("`debug` must be enabled to use this endpoint").into(),
);
}

let block_height = height.into();
let block_producer = ctx.data_unchecked::<BlockProducer>();
Ok(block_producer
.storage_read_replay(block_height)
.await?
.into_iter()
.map(StorageReadReplayEvent::from)
.collect())
}

/// Submits transaction to the `TxPool`.
///
/// Returns submitted transaction if the transaction is included in the `TxPool` without problems.
Expand Down
33 changes: 33 additions & 0 deletions crates/fuel-core/src/schema/tx/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,39 @@ impl DryRunTransactionExecutionStatus {
}
}

pub struct StorageReadReplayEvent {
column: String,
key: HexString,
value: Option<HexString>,
}

impl From<fuel_core_types::services::executor::StorageReadReplayEvent>
for StorageReadReplayEvent
{
fn from(event: fuel_core_types::services::executor::StorageReadReplayEvent) -> Self {
Self {
column: event.column,
key: HexString(event.key),
value: event.value.map(HexString),
}
}
}

#[Object]
impl StorageReadReplayEvent {
async fn column(&self) -> String {
self.column.clone()
}

async fn key(&self) -> HexString {
self.key.clone()
}

async fn value(&self) -> Option<HexString> {
self.value.clone()
}
}

#[tracing::instrument(level = "debug", skip(query, txpool), ret, err)]
pub(crate) async fn get_tx_status(
id: fuel_core_types::fuel_types::Bytes32,
Expand Down
12 changes: 11 additions & 1 deletion crates/fuel-core/src/service/adapters/graphql_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ use fuel_core_types::{
fuel_types::BlockHeight,
services::{
block_importer::SharedImportResult,
executor::TransactionExecutionStatus,
executor::{
StorageReadReplayEvent,
TransactionExecutionStatus,
},
p2p::PeerInfo,
txpool::TransactionStatus,
},
Expand Down Expand Up @@ -130,6 +133,13 @@ impl BlockProducerPort for BlockProducerAdapter {
.dry_run(transactions, height, time, utxo_validation, gas_price)
.await
}

async fn storage_read_replay(
&self,
height: BlockHeight,
) -> anyhow::Result<Vec<StorageReadReplayEvent>> {
self.block_producer.storage_read_replay(height).await
}
}

#[async_trait::async_trait]
Expand Down
Loading
Loading