Skip to content

Conversation

bewakes
Copy link
Contributor

@bewakes bewakes commented Sep 16, 2025

Description

This is a draft and PoC implementation of OL STF. It is a PoC but not meant to discard as it follows the spec document to most extent. Some specific details like snark verification and mmr, and possibly some validation are omitted for simplicity.

The PR consists of two aspects:

  1. Add services/stf-runner crate: This is the main change for this PR. All the new OL related types from various docs are present right here. Not the best way to do but I did not want to interfere with other crates. Later we can either swap or replace the types and traits. Also, used the name stf-runner to be different from chain-executor, although similar in functionalities, to reduce confusion.
  2. Add bin/stf-runner-demo binary(using CLAUDE): The sole purpose is to see in action how the state and roots change. This has generator for deposit logs, and ol transactions. See corresponding readme for info on how to run this. This is meant to be removed as this PR evolves.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature/Enhancement (non-breaking change which adds functionality or enhances an existing one)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactor
  • New or updated tests
  • Dependency Update

Notes to Reviewers

Checklist

  • I have performed a self-review of my code.
  • I have commented my code where necessary.
  • I have updated the documentation if needed.
  • My changes do not introduce new warnings.
  • I have added (where necessary) tests that prove my changes are effective or that my feature works.
  • New and existing tests pass with my changes.
  • I have disclosed my use of AI
    in the body of this PR.

Related Issues

Copy link
Contributor

@delbonis delbonis left a comment

Choose a reason for hiding this comment

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

There's some procedural issues with this PR too, it's bound to be an even huger PR than it appears. It also goes and implements the snark account spec, which should have been implemented separately first (probably in its own crates).

The demo runner crate should be replaced a bunch of unit tests in the stf crate(s). The kinds of operations that are in that crate should be unit tests that we can run more reliably. I realize it's a Claude invention but Claude is really bad at understanding project organization.

Comment on lines 97 to 109
account_serial: u32,
account_id: AccountId,
payload: Vec<u8>, // TODO: make this typed, serialization can be done at the edges
Copy link
Contributor

Choose a reason for hiding this comment

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

Having both the serial and ID is redundant, we should only need the serial.

Also no, the payload should not be typed, because serialization can be done at the edges.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having both the serial and ID is redundant, we should only need the serial.

Oh yes, having both is redundant.

Regarding payload, we should have some struct that gets serialized into payload: Vec<u8>, right? There's got to be something to serialize.

Comment on lines +53 to +70
pub struct BlockExecutionOutput<WB = WriteBatch, L = LogMessage> {
// TODO: Generic parameters, WB and L for transition
/// State root as computed by the STF.
computed_state_root: Buf32,

/// Log messages emitted while executing the block.
///
/// These will eventually be accumulated to be processed by ASM.
logs: Vec<LogMessage>,
logs: Vec<L>,

/// Changes to the state we store in the database.
///
/// This is NOT a state diff, that requires more precise tracking.
write_batch: WriteBatch,
write_batch: WB,
}

impl BlockExecutionOutput {
pub fn new(computed_state_root: Buf32, logs: Vec<LogMessage>, write_batch: WriteBatch) -> Self {
impl<WB, L> BlockExecutionOutput<WB, L> {
pub fn new(computed_state_root: Buf32, logs: Vec<L>, write_batch: WB) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why were these made generic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are transitional. Since the current chainstate differs significantly from the OLState in the spec, I did not want to make intrusive changes on the Chainstate and hence a new OLState which should be merged/replaced in the future. The generic is just to support both, with default being the Chainstate. I guess Log generic can be removed since the LogMessage and OLLog are pretty similar and thus only one should exist.

Comment on lines +15 to 16
// TODO/NOTE: There's already a similar trait called `L2Header`, might need to merge those.
pub trait BlockHeaderContext {
Copy link
Contributor

Choose a reason for hiding this comment

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

Trying to expose only a minimal amount of information about the block header to avoid an issue with the cyclical state root we had a while ago.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. We can probably tackle this with supertraits.

Comment on lines 75 to 104
// state accessor is expected to allow CRUD operations on state
state_accessor: &mut impl StateAccessor<OLState>,
// ledger_provider is expected to allow CRUD operations on accounts ledger
ledger_provider: &mut impl LedgerProvider,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is OLState being passed in explicitly if we have a StateAccessor<OLState> also? Why does LedgerProvider exist separately when StateAccessor was created to be there to support accessing accounts in a structured way?

Copy link
Contributor Author

@bewakes bewakes Sep 16, 2025

Choose a reason for hiding this comment

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

Why is OLState being passed in explicitly if we have a StateAccessor also?

See the comment on the first argument, I do intend to remove it.

Why does LedgerProvider exist separately when StateAccessor was created to be there to support accessing accounts in a structured way?

We should probably merge them, I had the thought as well. But StateAccessor is already huge so I wanted to separate out the logic.

}
}

impl AsyncService for StfRunnerService {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be a sync service not an async one, but this already exists in the chain executor crate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see, will keep that in mind. Can I ask what's the rationale for this being Sync and not Async?

Copy link
Contributor

Choose a reason for hiding this comment

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

all of the DB calls are blocking and IO bound, so there's literally nothing gained by being async

Copy link
Contributor

Choose a reason for hiding this comment

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

This supposed to be an account on the OL? There are some functionalities here that I'd defer to a later stage, like messaging. I think that right now the only kind of message we have is deposits, and I'm not sure if this is treated generically.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the consume_messages is not required actually. The only message that the impl deals with is the deposit messages. All other inter-EE messages are ignored.

self.root_cache = None;
}

pub fn compute_root(&mut self) -> LedgerResult<Buf32> {
Copy link
Contributor

Choose a reason for hiding this comment

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

What exactly do we compute here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an abstraction. We compute the accounts root here, or if we have a trie then we could just return the trie root from here.

@bewakes
Copy link
Contributor Author

bewakes commented Sep 17, 2025

The demo runner crate should be replaced a bunch of unit tests in the stf crate(s)

Totally agree @delbonis. I'll be gradually adding tests along the way as I start tightening the bolts - at this stage of the PR these feel maintenance burden. The binary is something claude is very good at creating, I can just quickly see what's happening and can be safely removed after a point.

Copy link
Contributor

github-actions bot commented Sep 17, 2025

Commit: bcd9aa0

SP1 Execution Results

program cycles success
Bitcoin Blockspace 70,870
EVM EE STF 1,172,715
CL STF 377,814
Checkpoint 4,229

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.

4 participants