Skip to content

Commit

Permalink
include "core/state: don't use the prefetcher for missing snapshot it…
Browse files Browse the repository at this point in the history
…ems" from base branch. prefetch read storage slots when witness building is enabled
  • Loading branch information
jwasinger committed May 9, 2024
1 parent fcb937a commit 301389c
Show file tree
Hide file tree
Showing 10 changed files with 601 additions and 29 deletions.
132 changes: 132 additions & 0 deletions cmd/utils/stateless.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package utils

import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb"
"io"
"net"
"net/http"
)

func StatelessExecute(logOutput io.Writer, chainCfg *params.ChainConfig, witness *state.Witness) (root common.Hash, err error) {
rawDb := rawdb.NewMemoryDatabase()
if err := witness.PopulateDB(rawDb); err != nil {
return common.Hash{}, err
}
blob := rawdb.ReadAccountTrieNode(rawDb, nil)
prestateRoot := crypto.Keccak256Hash(blob)

db, err := state.New(prestateRoot, state.NewDatabaseWithConfig(rawDb, triedb.PathDefaults), nil)
if err != nil {
return common.Hash{}, err
}
engine := beacon.New(ethash.NewFaker())
validator := core.NewStatelessBlockValidator(chainCfg, engine)
chainCtx := core.NewStatelessChainContext(rawDb, engine)
processor := core.NewStatelessStateProcessor(chainCfg, chainCtx, engine)

receipts, _, usedGas, err := processor.ProcessStateless(witness, witness.Block, db, vm.Config{})
if err != nil {
return common.Hash{}, err
}
// compute the state root. skip validation of computed root against
// the one provided in the block because this value is omitted from
// the witness.
if root, err = validator.ValidateState(witness.Block, db, receipts, usedGas, false); err != nil {
return common.Hash{}, err
}
// TODO: how to differentiate between errors that are definitely not consensus-failure caused, and ones
// that could be?
return root, nil
}

// RunLocalServer runs an http server on the local address at the specified
// port (or 0 to use a random port).
// The server provides a POST endpoint /verify_block which takes input as an octet-stream RLP-encoded
// block witness proof in the body, executes the block proof and returns the computed state root.
func RunLocalServer(chainConfig *params.ChainConfig, port int) (closeChan chan<- struct{}, actualPort int, err error) {
mux := http.NewServeMux()
mux.Handle("/verify_block", &verifyHandler{chainConfig})
srv := http.Server{Handler: mux}
listener, err := net.Listen("tcp", ":"+fmt.Sprintf("%d", port))
if err != nil {
return nil, 0, err
}
actualPort = listener.Addr().(*net.TCPAddr).Port

go func() {
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
panic(err)
}
}()

closeCh := make(chan struct{})
go func() {
select {
case <-closeCh:
if err := srv.Close(); err != nil {
panic(err)
}
}
}()
return closeCh, actualPort, nil
}

type verifyHandler struct {
chainConfig *params.ChainConfig
}

func (v *verifyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
respError := func(descr string, err error) {
w.WriteHeader(http.StatusBadRequest)
if _, err := w.Write([]byte(fmt.Sprintf("%s: %s", descr, err))); err != nil {
log.Error("write failed", "error", err)
}

log.Error("responded with error", "descr", descr, "error", err)
}
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
respError("error reading body", err)
return
}
if len(body) == 0 {
respError("error", fmt.Errorf("empty body"))
return
}
witness, err := state.DecodeWitnessRLP(body)
if err != nil {
respError("error decoding body witness rlp", err)
return
}
defer func() {
if err := recover(); err != nil {
errr, _ := err.(error)
respError("execution error", errr)
return
}
}()

root, err := StatelessExecute(nil, v.chainConfig, witness)
if err != nil {
respError("error verifying stateless proof", err)
return
}

w.WriteHeader(http.StatusOK)
if _, err := w.Write(root[:]); err != nil {
log.Error("error writing response", "error", err)
}
}
2 changes: 1 addition & 1 deletion core/state/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
}
if !conf.SkipStorage {
account.Storage = make(map[common.Hash]string)
tr, err := obj.getTrie()
tr, err := obj.getTrie(true)
if err != nil {
log.Error("Failed to load storage trie", "err", err)
continue
Expand Down
21 changes: 17 additions & 4 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,15 @@ func (s *stateObject) touch() {
// getTrie returns the associated storage trie. The trie will be opened
// if it's not loaded previously. An error will be returned if trie can't
// be loaded.
func (s *stateObject) getTrie() (Trie, error) {
//
// The skipPrefetcher parameter is used to request a direct load from disk, even
// if a prefetcher is available. This path is used if snapshots are unavailable,
// since that requires reading the trie *during* execution, when the prefetchers
// cannot yet return data.
func (s *stateObject) getTrie(skipPrefetcher bool) (Trie, error) {
if s.trie == nil {
// Try fetching from prefetcher first
if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil {
if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil && !skipPrefetcher {
// When the miner is creating the pending state, there is no prefetcher
trie, err := s.db.prefetcher.trie(s.addrHash, s.data.Root)
if err != nil {
Expand Down Expand Up @@ -207,11 +212,17 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
}
value.SetBytes(content)
}
// If witness building is enabled, prefetch any trie paths loaded directly
// via the snapshots
if s.db.prefetcher != nil && err == nil && s.db.witness != nil && s.data.Root != types.EmptyRootHash {
fmt.Printf("prefetch %x: %x\n", s.address, key)
s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, [][]byte{key[:]})
}
}
// If the snapshot is unavailable or reading from it fails, load from the database.
if s.db.snap == nil || err != nil {
start := time.Now()
tr, err := s.getTrie()
tr, err := s.getTrie(true)
if err != nil {
s.db.setError(err)
return common.Hash{}
Expand Down Expand Up @@ -315,7 +326,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
storage map[common.Hash][]byte
origin map[common.Hash][]byte
)
tr, err := s.getTrie()
tr, err := s.getTrie(false)
if err != nil {
s.db.setError(err)
return nil, err
Expand Down Expand Up @@ -430,11 +441,13 @@ func (s *stateObject) updateRoot() {
// Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb.
func (s *stateObject) commit() (*trienode.NodeSet, map[string][]byte, error) {
fmt.Println("before trie check")
// Short circuit if trie is not even loaded, don't bother with committing anything
if s.trie == nil {
s.origin = s.data.Copy()
return nil, nil, nil
}
fmt.Println("before CommitAndObtainAccessList")
// The trie is currently in an open state and could potentially contain
// cached mutations. Call commit to acquire a set of nodes that have been
// modified, the set can be nil if nothing to commit.
Expand Down

0 comments on commit 301389c

Please sign in to comment.