## Overview
This PR fixes a race condition during blockchain shutdown where snapshot
generation could continue accessing the trie database after it has been
closed, leading to iterator errors. We noticed this in one of our nodes
on https://github.com/ava-labs/avalanchego, which relies on an older
version of geth with the same issue (so this behavior does happen!).
During node shutdown, the following sequence occurs:
1. `BlockChain.Stop()` calls `snaps.Release()` to clean up snapshot
resources
2. `Release()` only resets the cache but doesn't stop the generator
goroutine
3. The trie database is then closed via `triedb.Close()`
4. The still-running generator attempts to iterate storage tries
5. Iterator fails because the database is closed (`"Generator failed to
iterate storage trie"`)
## Problem
There are three related bugs:
1. `Release()` doesn't stop generation: The `diskLayer.Release()` method
only resets the cache without stopping ongoing snapshot generation,
leaving the generator goroutine running after database closure.
2. `stopGeneration()` has an incorrect completion check: The
`stopGeneration()` method checks `genMarker != nil` to determine if
generation is running. However, `genMarker` is set to nil when
generation completes successfully, even though the generator goroutine
is still waiting for the abort signal at the end of `generate()`. See
line 705 in `generate.go`:
|
||
|---|---|---|
| .. | ||
| 1192c3_block.rlp | ||
| 1192c3_witness.rlp | ||
| chainconfig.go | ||
| getpayload_example.go | ||
| getpayload_wasm.go | ||
| getpayload_womir.go | ||
| getpayload_ziren.go | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| README.md | ||
| stubs.go | ||
Keeper - geth as a zkvm guest
Keeper command is a specialized tool for validating stateless execution of Ethereum blocks. It's designed to run as a zkvm guest.
Overview
The keeper reads an RLP-encoded payload containing:
- A block to execute
- A witness with the necessary state data
- A chainID
It then executes the block statelessly and validates that the computed state root and receipt root match the values in the block header.
Building Keeper
The keeper uses build tags to compile platform-specific input methods and chain configurations:
Example Implementation
See getpayload_example.go for a complete example with embedded Hoodi block data:
# Build example with different chain configurations
go build -tags "example" ./cmd/keeper
Ziren zkVM Implementation
Build for the Ziren zkVM platform, which is a MIPS ISA-based zkvm:
GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -tags "ziren" ./cmd/keeper
As an example runner, refer to https://gist.github.com/gballet/7b669a99eb3ab2b593324e3a76abd23d
Creating a Custom Platform Implementation
To add support for a new platform (e.g., "myplatform"), create a new file with the appropriate build tag:
1. Create getinput_myplatform.go
//go:build myplatform
package main
import (
"github.com/ethereum/go-ethereum/params"
// ... other imports as needed
)
// getInput returns the RLP-encoded payload
func getInput() []byte {
// Your platform-specific code to retrieve the RLP-encoded payload
// This might read from:
// - Memory-mapped I/O
// - Hardware registers
// - Serial port
// - Network interface
// - File system
// The payload must be RLP-encoded and contain:
// - Block with transactions
// - Witness with parent headers and state data
return encodedPayload
}