Skip to content

Conversation

@pamungkaski
Copy link
Collaborator

No description provided.

Ki Ageng Satria Pamungkas added 2 commits December 17, 2025 12:04
@pamungkaski pamungkaski changed the title feat(based-op-reth): handle newFragV0 RPC for reth without fork feat(based-op-reth): based-op changes for reth without fork Dec 17, 2025
Copy link
Collaborator

@mempirate mempirate left a comment

Choose a reason for hiding this comment

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

Nice, this is looking like a good base. Implementing the executor as in the flashblocks example would be a good starting point, after that we can start looking at how to serve it at the RPC level and how to integrate canonical blocks (should be fairly easy). Let's do the frag executor first since it's probably the most difficult.


/// A very small “dummy” executor so you can compile & test state machine early.
/// Replace with Reth executor.
pub struct NoopExecutor;
Copy link
Collaborator

Choose a reason for hiding this comment

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

type Resp<T> = oneshot::Sender<Reply<T>>;

#[derive(Debug)]
enum Reply<T> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not just use result directly? Or type Reply<T> = Result<T, DriverError>

async fn handle_forkchoice_updated(&mut self, new_block_number: u64) -> Result<(), DriverError> {
self.fcu_count_since_unseal_reset += 1;

let Some(ub) = self.current_unsealed_block.as_ref() else {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add references to the original based op geth code in a comment? So it's easier to cross-reference the implementations


self.exec.set_canonical(&presealed_block).await?;

self.reset_current_unsealed_block();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure how this is actionable, but I see a lot of these resets sprinkled in the handlers.
I'd expect it to be present on the caller of these handlers, so the handlers mostly need to worry about the "happy path" and errors are handled upstream...
But again, unsure how actionable this is, but it felt weird so I had to leave a comment


/// Execute all txs in `frag` on top of current overlay state.
///
/// MUST be cumulative: txs execute after all previous frags's txs.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm I'd probably either pass in the full set of txs or have it already included in the unsealed block (which I think is what we do here)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yea we are moving the unsealedbloc inside executor so we are only going to pass frag

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

just like on geth

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

applied

Copy link
Collaborator

Choose a reason for hiding this comment

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

Then in that case this doc is outdated

mempirate and others added 5 commits December 18, 2025 15:33
Co-authored-by: Ki Ageng Satria Pamungkas <ageng.satria@bukalapak.com>
Co-authored-by: Jonas Bostoen <jonasbostoen@fastmail.com>
@mempirate mempirate marked this pull request as ready for review December 19, 2025 14:40
Copy link
Collaborator

@Karrq Karrq left a comment

Choose a reason for hiding this comment

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

LGTM. There are a few differences with Geth, but overall I think it's more complete so it's good differences

#[method(name = "getBlockByNumber")]
async fn block_by_number(&self, number: BlockNumberOrTag, full: bool) -> RpcResult<Option<RpcBlock<Optimism>>>;

/// Returns the transaction receipt for a given transaction hash, with support for frags.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: technically the trait doesn't need to be "aware of frags" (as is the case, looking at the signatures) so I'd rather have this documented in the implementor of the trait

timeout_ms: Option<u64>,
) -> RpcResult<RpcReceipt<Optimism>>;

/// Executes a call with flashblock state support.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// Executes a call with flashblock state support.
/// Executes a call with unsealed block state support.

Plus others below

Copy link
Collaborator

Choose a reason for hiding this comment

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

(even this, I'd say can be omitted as it's not part of the API)

if self.use_unsealed_state(&number) &&
let Some(unsealed_block) = self.unsealed_block.load_full()
{
Ok(Some(unsealed_block.to_rpc_block(full)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think Geth supports this. I can see that they retrieve the miner block for pending, but also they just retrieve the current block if it's the "latest", which doesn't resolve to the unsealed block even with the flag

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, I think this endpoint shouldn't use unsealed block at all. Latest and pending can just return canonical block

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

changed, thanks!

address = %address
);
let block_id = block_number.unwrap_or_default();
if self.use_unsealed_state(&block_id) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here, Geth doesn't resolve block header/number from unsealed state

let mut count =
EthState::transaction_count(&self.canonical, address, block_number).await.map_err(Into::into)?;

if self.use_unsealed_state(&block_id) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

same


let ub_arc = ub_arc_opt.ok_or(ExecError::NotInitialized)?;

// Make an owned, mutable working copy from the start
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Make an owned, mutable working copy from the start
// Make a local working copy for the frag simulation

}
}

db = evm.into_db();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
db = evm.into_db();
// Update state views of the local unsealed block and
// commit frag execution to it
db = evm.into_db();


ub.accept_frag_execution(frag, logs, receipts, gas_used);

self.current_unsealed_block.store(Some(Arc::new(ub)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
self.current_unsealed_block.store(Some(Arc::new(ub)));
// Finally, replace shared unsealed block with the updated one
self.current_unsealed_block.store(Some(Arc::new(ub)));

fn seal(&mut self) -> Result<(), ExecError> {
let ub = self.current_unsealed_block.load_full().ok_or(ExecError::NotInitialized)?;
let withdrawals_hash = if ub.is_prague {
let canonical_block = ub.env.number.saturating_sub(1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This was in another place with a TODO to check if it was correct. Worth adding a method to retrieve the canonical block from the unsealed block? Just to avoid fixing only one (if it's indeed incorrect)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: correct withdrawals root when sealing Based OP Geth: Port changes to OP Reth

5 participants