Skip to content

refactor(state): split state into State and StateReader#3563

Merged
rodrodros merged 6 commits intomainfrom
maksym/state-split
Apr 17, 2026
Merged

refactor(state): split state into State and StateReader#3563
rodrodros merged 6 commits intomainfrom
maksym/state-split

Conversation

@MaksymMalicki
Copy link
Copy Markdown
Contributor

@MaksymMalicki MaksymMalicki commented Apr 17, 2026

This PR finishes the already started split of the new State. We already have the proper constructors New and NewStateReader, however both produced the same State object with and without Batch. Even though, the caller was safe from wrongly utilising the constructors by the existing common state interfaces, technically if the interfaces were not used, the caller could call state mutation methods on the StateReader.

In this PR, the State is decoupled into StateReader - which only holds the read methods, and State which extends the StateReader with the state mutation methods. The diff is quite big due to methods reordering - the State declaration, constructor and methods remain in state.go, StateReader declaration, constructor and methods were moved to the state_reader.go. stateHistory uses the StateReader as the read layer now.

The only modification worth paying attention to is the ContractStorage method, which exists both in the State and StateReader. Previously, the ContractStorage was first reading from the dirty cache on the state object, later falling back to the DB. But this scenario was effectively a dead code - the only time the dirty buffer is filled is during the sync (state.Update called from the blockchain.Store()). We don't share the state objects, whenever we wanted read, we created a brand new State object using the blockchain.HeadState() or blockchain.StateAtBlockNumber - with an empty buffer. Now, that we split the State into the State and StateReader, we can drop this logic and make the StateReader read directly from the storage trie in the DB.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 17, 2026

Codecov Report

❌ Patch coverage is 75.00000% with 53 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.76%. Comparing base (925371e) to head (4ab162c).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
core/state/state_reader.go 76.83% 27 Missing and 14 partials ⚠️
core/state/history.go 16.66% 5 Missing ⚠️
core/state/state.go 63.63% 2 Missing and 2 partials ⚠️
core/state/object.go 70.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3563      +/-   ##
==========================================
+ Coverage   75.72%   75.76%   +0.03%     
==========================================
  Files         384      386       +2     
  Lines       33757    33741      -16     
==========================================
  Hits        25563    25563              
+ Misses       6398     6392       -6     
+ Partials     1796     1786      -10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread core/state/state.go Outdated
Comment thread core/state/state.go
Comment thread core/state/state.go Outdated
Comment thread core/state/state_reader.go
Copy link
Copy Markdown
Contributor

@rodrodros rodrodros left a comment

Choose a reason for hiding this comment

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

Why are the folders inside the state called "statesmth" instead of just "smth". Example:

  1. statefactory instead of factory
  2. statetestutils instead of testutils

@MaksymMalicki MaksymMalicki marked this pull request as draft April 17, 2026 10:08
Comment thread core/state/state.go
return contract.Nonce, nil
}

func (s *State) ContractStorage(addr, key *felt.Felt) (felt.Felt, error) {
Copy link
Copy Markdown
Contributor Author

@MaksymMalicki MaksymMalicki Apr 17, 2026

Choose a reason for hiding this comment

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

This method was moved to the StateReader in a different form. Instead of reading the dirty storage and falling back to the trie, it immediately reaches the storage trie for this value.
REASON: The dirty cache is only populated during the Update - which is writing. State instances are not shared, previously for all the read operations a new State object instance was generated using blockchain.HeadState or blockchain.StateAtBlockHash. In Juno, we never read the state, that is currently being written to the DB

Comment thread core/state/state.go
if !newComm.Equal(update.OldRoot) {
return fmt.Errorf("state commitment mismatch: %v (expected) != %v (actual)", update.OldRoot, &newComm)
}
if s.batch != nil {
Copy link
Copy Markdown
Contributor Author

@MaksymMalicki MaksymMalicki Apr 17, 2026

Choose a reason for hiding this comment

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

Those non-nil guards are not needed anymore, State always enforces a non-nil batch

Comment thread core/state/object.go
s.contract.Nonce = *nonce
}

func (s *stateObject) getStorage(key *felt.Felt) (felt.Felt, error) {
Copy link
Copy Markdown
Contributor Author

@MaksymMalicki MaksymMalicki Apr 17, 2026

Choose a reason for hiding this comment

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

This method was only used in the (s *State) ContractStorage. Removal reason explained here

@MaksymMalicki MaksymMalicki marked this pull request as ready for review April 17, 2026 11:15
@rodrodros rodrodros merged commit 026d019 into main Apr 17, 2026
17 of 19 checks passed
@rodrodros rodrodros deleted the maksym/state-split branch April 17, 2026 15:49
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.

3 participants