diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index 3dd7553ef55..5b1a96b7bda 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -381,10 +381,15 @@ impl OpBuilder<'_, Txs> { ctx.execute_sequencer_transactions(&mut builder)?; builder.into_executor().apply_post_execution_changes()?; - let ExecutionWitnessRecord { hashed_state, codes, keys } = + let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number: _ } = ExecutionWitnessRecord::from_executed_state(&db); let state = state_provider.witness(Default::default(), hashed_state)?; - Ok(ExecutionWitness { state: state.into_iter().collect(), codes, keys, headers: vec![] }) + Ok(ExecutionWitness { + state: state.into_iter().collect(), + codes, + keys, + ..Default::default() + }) } } diff --git a/crates/revm/src/witness.rs b/crates/revm/src/witness.rs index 2db09d08913..e2283148fa2 100644 --- a/crates/revm/src/witness.rs +++ b/crates/revm/src/witness.rs @@ -19,6 +19,14 @@ pub struct ExecutionWitnessRecord { /// /// `keccak(address|slot) => address|slot` pub keys: Vec, + /// The lowest block number referenced by any BLOCKHASH opcode call during transaction + /// execution. + /// + /// This helps determine which ancestor block headers must be included in the + /// `ExecutionWitness`. + /// + /// `None` - when the BLOCKHASH opcode was not called during execution + pub lowest_block_number: Option, } impl ExecutionWitnessRecord { @@ -62,6 +70,8 @@ impl ExecutionWitnessRecord { } } } + // BTreeMap keys are ordered, so the first key is the smallest + self.lowest_block_number = statedb.block_hashes.keys().next().copied() } /// Creates the record from the state after execution. diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index c890318e833..7521082b84b 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -631,7 +631,10 @@ where block: Arc>>, ) -> Result { let this = self.clone(); - self.eth_api() + let block_number = block.header().number(); + + let (mut exec_witness, lowest_block_number) = self + .eth_api() .spawn_with_state_at_block(block.parent_hash().into(), move |state_provider| { let db = StateProviderDatabase::new(&state_provider); let block_executor = this.inner.block_executor.executor(db); @@ -644,14 +647,43 @@ where }) .map_err(|err| EthApiError::Internal(err.into()))?; - let ExecutionWitnessRecord { hashed_state, codes, keys } = witness_record; + let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number } = + witness_record; let state = state_provider .witness(Default::default(), hashed_state) .map_err(EthApiError::from)?; - Ok(ExecutionWitness { state, codes, keys, headers: vec![] }) + Ok(( + ExecutionWitness { state, codes, keys, ..Default::default() }, + lowest_block_number, + )) }) - .await + .await?; + + let smallest = match lowest_block_number { + Some(smallest) => smallest, + None => { + // Return only the parent header, if there were no calls to the + // BLOCKHASH opcode. + block_number.saturating_sub(1) + } + }; + + let range = smallest..block_number; + // TODO: Check if headers_range errors when one of the headers in the range is missing + exec_witness.headers = self + .provider() + .headers_range(range) + .map_err(EthApiError::from)? + .into_iter() + .map(|header| { + let mut serialized_header = Vec::new(); + header.encode(&mut serialized_header); + serialized_header.into() + }) + .collect(); + + Ok(exec_witness) } /// Returns the code associated with a given hash at the specified block ID. If no code is