Skip to content

Commit

Permalink
rebase. (give me a proper commit message)
Browse files Browse the repository at this point in the history
Co-authored-by: Martin HS <martin@swende.se>
Co-authored-by: Péter Szilágyi <peterke@gmail.com>
  • Loading branch information
3 people committed May 13, 2024
1 parent 86a1f0c commit fce0ff4
Show file tree
Hide file tree
Showing 24 changed files with 1,356 additions and 338 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)
}
}
34 changes: 25 additions & 9 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package core
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"

"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state"
Expand Down Expand Up @@ -47,6 +48,20 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
return validator
}

// NewBlockStatelessBlockValidator returns a BlockValidator which is configured to validate stateless block witnesses
// without the use of a full backing BlockChain
func NewStatelessBlockValidator(config *params.ChainConfig, engine consensus.Engine) *BlockValidator {
validator := &BlockValidator{
config: config,
engine: engine,
bc: &BlockChain{
chainConfig: config,
engine: engine,
},
}
return validator
}

// ValidateBody validates the given block's uncles and verifies the block
// header's transaction and uncle roots. The headers are assumed to be already
// validated at this point.
Expand Down Expand Up @@ -121,28 +136,29 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {

// ValidateState validates the various changes that happen after a state transition,
// such as amount of used gas, the receipt roots and the state root itself.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error {
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, checkRoot bool) (root common.Hash, err error) {
header := block.Header()
if block.GasUsed() != usedGas {
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
return root, fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
}
// Validate the received block's bloom with the one derived from the generated receipts.
// For valid blocks this should always validate to true.
rbloom := types.CreateBloom(receipts)
if rbloom != header.Bloom {
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
return root, fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
}
// The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
return root, fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
}
// Validate the state root against the received state root and throw
// an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
// Compute the state root and if enabled, check it against the
// received state root and throw an error if they don't match.
root = statedb.IntermediateRoot(v.config.IsEIP158(header.Number))
if checkRoot && header.Root != root {
return root, fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
}
return nil
return root, nil
}

// CalcGasLimit computes the gas limit of the next block after parent. It aims
Expand Down
10 changes: 7 additions & 3 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1806,8 +1806,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
}
statedb.SetLogger(bc.logger)

// Enable prefetching to pull in trie node paths while processing transactions
statedb.StartPrefetcher("chain")
// If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction.
if bc.chainConfig.IsByzantium(block.Number()) {
statedb.StartPrefetcher("chain")
}
activeState = statedb

// If we have a followup block, run that against the current state to pre-cache
Expand Down Expand Up @@ -1921,7 +1925,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
ptime := time.Since(pstart)

vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
if _, err := bc.validator.ValidateState(block, statedb, receipts, usedGas, true); err != nil {
bc.reportBlock(block, receipts, err)
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
blockchain.reportBlock(block, receipts, err)
return err
}
err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas)
_, err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas)
if err != nil {
blockchain.reportBlock(block, receipts, err)
return err
Expand Down
21 changes: 20 additions & 1 deletion core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package core

import (
"github.com/ethereum/go-ethereum/core/state"
"math/big"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -40,6 +41,16 @@ type ChainContext interface {

// NewEVMBlockContext creates a new context for use in the EVM.
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
return newEVMBlockContext(nil, header, chain, author)
}

// NewStatelessEVMBlockContext creates a new context for use in the EVM in stateless execution mode. The BLOCKHASH
// opcode sources block hashes from the provided witness in stateless execution mode.
func NewStatelessEVMBlockContext(witness *state.Witness, header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
return newEVMBlockContext(witness, header, chain, author)
}

func newEVMBlockContext(witness *state.Witness, header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
var (
beneficiary common.Address
baseFee *big.Int
Expand All @@ -62,10 +73,18 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
if header.Difficulty.Sign() == 0 {
random = &header.MixDigest
}
var getHash vm.GetHashFunc
if witness != nil {
getHash = func(n uint64) common.Hash {
return witness.GetBlockHash(n)
}
} else {
getHash = GetHashFn(header, chain)
}
return vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
GetHash: getHash,
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,
Expand Down
8 changes: 8 additions & 0 deletions core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ type Trie interface {
// be created with new root and updated trie database for following usage
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)

// AccessList returns a map of path->blob containing all trie nodes that have
// been accessed.
AccessList() map[string][]byte

// CommitAndObtainAccessList does the same thing as Commit, but also returns
// the access list of the trie.
CommitAndObtainAccessList(collectLeaf bool) (common.Hash, *trienode.NodeSet, map[string][]byte, error)

// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key. And error will be returned
// if fails to create node iterator.
Expand Down

0 comments on commit fce0ff4

Please sign in to comment.