eth, core/state: self-heal missing contract code on stateless import - #2402
Draft
lucca30 wants to merge 3 commits into
Draft
eth, core/state: self-heal missing contract code on stateless import#2402lucca30 wants to merge 3 commits into
lucca30 wants to merge 3 commits into
Conversation
Branch 3 groundwork. Introduce state.MissingCodeError{Addr,Hash}, set on the
"code is not found" paths in stateObject.Code/CodeSize (message unchanged, so
witness-regen fixtures still match), and wrap it into ExecuteStateless's result
with multi-%w. A stateless caller can now errors.As the missing code hash and
fetch exactly that blob, and tell a missing code (fetchable) from a missing
state trie node (an incomplete witness, not fetchable). Extends the branch-1
test to assert the hash round-trips via errors.As.
Branch 3 groundwork. Syncer.FetchByteCodes fetches specific contract bytecodes from a connected snap peer outside the bulk sync loop, verifying each blob against its hash and failing over across peers. Responses are routed by a top-bit reqid (loop reqids are uint64(rand.Int63()), top bit clear, so the id spaces never collide) intercepted at the head of OnByteCodes. Peers are usable because Register/Unregister track them by lifecycle, independent of a running Sync cycle — which is the stateless self-heal case (heal already completed). Tests cover a well-behaving peer, an unknown hash (never fabricated), and failover past a peer that returns bytes not matching the requested hash.
Branch 3. When a stateless batch import fails with a *state.MissingCodeError (a called contract's bytecode absent from local disk — WIT2 witnesses carry no code), fetch exactly that content-addressed blob from a snap peer via Syncer.FetchByteCodes, persist it to the chain db, and retry the batch. Bounded by maxStatelessCodeHeals so a blob no peer can serve degrades to the existing safe failure instead of looping; a batch referencing multiple missing codes heals them across successive attempts. Any non-code failure is left to the caller's existing path. Turns the mainnet-soak "code arrived later" self-heal from eventual into immediate.
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (66.40%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## lmartins/stateless-incomplete-state-guard #2402 +/- ##
============================================================================
Coverage ? 55.60%
============================================================================
Files ? 919
Lines ? 167291
Branches ? 0
============================================================================
Hits ? 93024
Misses ? 68784
Partials ? 5483
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
#2401 makes a stateless node detect a missing contract code and reject the
block instead of masking it. But rejection alone means the node stalls on that
block until the code happens to arrive by some other path. A WIT2 witness never
carries code, so there is no in-band way to recover — the node needs to fetch
exactly the missing blob.
Fix
Three layers, bottom-up:
core/state—MissingCodeError{Addr, Hash}types the two "code is notfound" paths in
stateObject.Code/CodeSize(message unchanged, sowitness-regen fixtures still match) and is wrapped into
ExecuteStateless'sresult. A caller can now
errors.Asthe missing hash, and tell a missingcode (fetchable, content-addressed) from a missing trie node (an
incomplete witness, not fetchable).
eth/protocols/snap—Syncer.FetchByteCodesfetches specific bytecodesfrom a connected snap peer outside the bulk sync loop, verifying each blob
against its hash and failing over across peers. Responses route by a top-bit
reqid (loop reqids are
uint64(rand.Int63()), top bit clear, so the idspaces never collide). Peers are usable because
Register/Unregistertrackthem independently of a running
Synccycle — the stateless case, where theinitial heal has already completed.
eth/downloader— on a*state.MissingCodeErrorfrom a stateless batchimport, fetch that content-addressed blob, persist it to the chain db, and
retry the batch. Bounded by
maxStatelessCodeHealsso a blob no peer canserve degrades to the existing safe failure instead of looping; a batch
referencing several missing codes heals them across successive attempts. Any
non-code failure is left to the caller's existing path untouched.
Test
core/state— the guard test (core: reject stateless blocks on incomplete state or code #2401) is extended to assert the hashround-trips via
errors.As.eth/protocols/snap—TestFetchByteCodesOnDemand(well-behaving peer;unknown hash never fabricated) and
TestFetchByteCodesFailsOverAndVerifies(fails over past a peer returning bytes that don't match the requested hash).
eth/downloader—TestRecoverMissingStatelessCode(a servable miss isfetched, verified and persisted; a non-code error and an unservable blob are
both left for the safe failure path, writing nothing).
Scope / risk
Every fetched blob is verified content-addressed (
keccak(code) == hash) beforeit is persisted, so a malicious peer cannot inject wrong code — it simply fails
over. Recovery is bounded and safe-stops on an unservable code. Together with
#2401 this recovers any missing code at execution time regardless of how the
gap arose (heal incompleteness, crash window, or fast-forward), which is why it
is deployed ahead of any mechanism-specific prevention.