mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
all: add stateless verifications
This commit is contained in:
parent
c10ac4f48f
commit
d86b650e54
34 changed files with 876 additions and 70 deletions
86
cmd/utils/stateless/stateless.go
Normal file
86
cmd/utils/stateless/stateless.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package stateless
|
||||
|
||||
import (
|
||||
"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/params"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
||||
// StatelessExecute executes the block contained in the Witness returning the post state root or an error
|
||||
func StatelessExecute(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.NewBlockValidator(chainCfg, nil, engine)
|
||||
processor := core.NewStateProcessor(chainCfg, nil, engine)
|
||||
|
||||
receipts, _, usedGas, err := processor.Process(witness.Block, db, vm.Config{}, witness)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// compute the state root.
|
||||
if root, err = validator.ValidateState(witness.Block, db, receipts, usedGas, false); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// BuildStatelessProof executes a block, collecting the accessed pre-state into
|
||||
// a Witness. The RLP-encoded witness is returned.
|
||||
func BuildStatelessProof(blockHash common.Hash, bc *core.BlockChain) ([]byte, error) {
|
||||
block := bc.GetBlockByHash(blockHash)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("non-existent block %x", blockHash)
|
||||
} else if block.NumberU64() == 0 {
|
||||
return nil, fmt.Errorf("cannot build a stateless proof of the genesis block")
|
||||
}
|
||||
parentHash := block.ParentHash()
|
||||
parent := bc.GetBlockByHash(parentHash)
|
||||
if parent == nil {
|
||||
return nil, fmt.Errorf("block %x parent not present", parentHash)
|
||||
}
|
||||
|
||||
db, err := bc.StateAt(parent.Header().Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.EnableWitnessBuilding()
|
||||
if bc.Snapshots() != nil {
|
||||
db.StartPrefetcher("BuildStatelessProof", false)
|
||||
defer db.StopPrefetcher()
|
||||
}
|
||||
stateProcessor := core.NewStateProcessor(bc.Config(), bc, bc.Engine())
|
||||
_, _, _, err = stateProcessor.Process(block, db, vm.Config{}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = db.Commit(block.NumberU64(), true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proof := db.Witness()
|
||||
proof.Block = block
|
||||
enc, err := proof.EncodeRLP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enc, nil
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -121,28 +122,31 @@ 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 {
|
||||
// If validateRemoteRoot is false, the provided block header's root is not asserted to be equal to the one computed from
|
||||
// execution.
|
||||
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, checkRemoteRoot 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 checkRemoteRoot && 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
|
||||
|
|
|
|||
|
|
@ -1916,7 +1916,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
|
|||
|
||||
// Process block using the parent state as reference point
|
||||
pstart := time.Now()
|
||||
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
|
||||
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig, nil)
|
||||
if err != nil {
|
||||
bc.reportBlock(block, receipts, err)
|
||||
return nil, err
|
||||
|
|
@ -1924,7 +1924,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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,12 +163,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{})
|
||||
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}, nil)
|
||||
if err != nil {
|
||||
blockchain.reportBlock(block, receipts, err)
|
||||
return err
|
||||
}
|
||||
err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas)
|
||||
_, err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, true)
|
||||
if err != nil {
|
||||
blockchain.reportBlock(block, receipts, err)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ func (b *BlockGen) Difficulty() *big.Int {
|
|||
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
|
||||
b.header.ParentBeaconRoot = &root
|
||||
var (
|
||||
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase, nil)
|
||||
vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
|
||||
)
|
||||
ProcessBeaconBlockRoot(root, vmenv, b.statedb)
|
||||
|
|
|
|||
17
core/evm.go
17
core/evm.go
|
|
@ -19,6 +19,8 @@ package core
|
|||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||
|
|
@ -38,8 +40,9 @@ type ChainContext interface {
|
|||
GetHeader(common.Hash, uint64) *types.Header
|
||||
}
|
||||
|
||||
// NewEVMBlockContext creates a new context for use in the EVM.
|
||||
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
|
||||
// NewEVMBlockContext creates a new context for use in the EVM. If witness is non-nil, the context sources block hashes
|
||||
// for the BLOCKHASH opcode from the witness.
|
||||
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, witness *state.Witness) vm.BlockContext {
|
||||
var (
|
||||
beneficiary common.Address
|
||||
baseFee *big.Int
|
||||
|
|
@ -62,10 +65,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.BlockHash(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,
|
||||
|
|
|
|||
|
|
@ -125,6 +125,10 @@ type Trie interface {
|
|||
// be created with new root and updated trie database for following usage
|
||||
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
|
||||
|
||||
// AccessList returns a map of path->blob containing all trie nodes that have
|
||||
// been accessed.
|
||||
AccessList() map[string][]byte
|
||||
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -323,10 +323,6 @@ func (s *stateObject) finalise() {
|
|||
//
|
||||
// It assumes all the dirty storage slots have been finalized before.
|
||||
func (s *stateObject) updateTrie() (Trie, error) {
|
||||
// Short circuit if nothing changed, don't bother with hashing anything
|
||||
if len(s.uncommittedStorage) == 0 {
|
||||
return s.trie, nil
|
||||
}
|
||||
// Retrieve a pretecher populated trie, or fall back to the database
|
||||
tr := s.getPrefetchedTrie()
|
||||
if tr != nil {
|
||||
|
|
@ -341,6 +337,14 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
// Short circuit if nothing changed, don't bother with hashing anything.
|
||||
//
|
||||
// We only quit after the prefetched trie is potentially resolved above
|
||||
// because, when building a stateless witness we will need to collect
|
||||
//storage access witnesses from the object's trie when we commit it.
|
||||
if len(s.uncommittedStorage) == 0 {
|
||||
return s.trie, nil
|
||||
}
|
||||
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
|
||||
// in circumstances similar to the following:
|
||||
//
|
||||
|
|
@ -446,7 +450,9 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
|
|||
//
|
||||
// Note, commit may run concurrently across all the state objects. Do not assume
|
||||
// thread-safe access to the statedb.
|
||||
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
||||
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, map[string][]byte, error) {
|
||||
var al map[string][]byte
|
||||
|
||||
// commit the account metadata changes
|
||||
op := &accountUpdate{
|
||||
address: s.address,
|
||||
|
|
@ -468,12 +474,18 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
|||
if len(op.storages) == 0 {
|
||||
// nothing changed, don't bother to commit the trie
|
||||
s.origin = s.data.Copy()
|
||||
return op, nil, nil
|
||||
if s.trie != nil && !s.trie.IsVerkle() {
|
||||
al = s.trie.AccessList()
|
||||
}
|
||||
return op, nil, al, nil
|
||||
}
|
||||
root, nodes := s.trie.Commit(false)
|
||||
s.data.Root = root
|
||||
s.origin = s.data.Copy()
|
||||
return op, nodes, nil
|
||||
if !s.trie.IsVerkle() {
|
||||
al = s.trie.AccessList()
|
||||
}
|
||||
return op, nodes, al, nil
|
||||
}
|
||||
|
||||
// AddBalance adds amount to s's balance.
|
||||
|
|
|
|||
374
core/state/state_witness.go
Normal file
374
core/state/state_witness.go
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// A Witness encompasses a block and all state necessary to compute the
|
||||
// post-state root.
|
||||
type Witness struct {
|
||||
Block *types.Block
|
||||
blockHashes map[uint64]common.Hash
|
||||
codes map[common.Hash][]byte
|
||||
root common.Hash
|
||||
tries map[common.Hash]map[string][]byte
|
||||
triesLock sync.Mutex
|
||||
}
|
||||
|
||||
// BlockHash returns the block hash corresponding to an ancestor block between 1-256 blocks old.
|
||||
func (w *Witness) BlockHash(num uint64) common.Hash {
|
||||
return w.blockHashes[num]
|
||||
}
|
||||
|
||||
// Root returns the post-state root of the witness if it has been computed or 0x00..0 if not.
|
||||
func (w *Witness) Root() common.Hash {
|
||||
return w.root
|
||||
}
|
||||
|
||||
// rlpWitness is the encoding structure for a Witness
|
||||
type rlpWitness struct {
|
||||
EncBlock []byte
|
||||
Root common.Hash
|
||||
Owners []common.Hash
|
||||
TriesPaths [][]string
|
||||
TriesNodes [][][]byte
|
||||
BlockNums []uint64
|
||||
BlockHashes []common.Hash
|
||||
Codes [][]byte
|
||||
}
|
||||
|
||||
func (e *rlpWitness) toWitness() (*Witness, error) {
|
||||
res := NewWitness(e.Root)
|
||||
if err := rlp.DecodeBytes(e.EncBlock, &res.Block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, code := range e.Codes {
|
||||
codeHash := crypto.Keccak256Hash(code)
|
||||
if _, ok := res.codes[codeHash]; ok {
|
||||
return nil, errors.New("duplicate code in witness")
|
||||
}
|
||||
res.codes[codeHash] = code
|
||||
}
|
||||
for i, owner := range e.Owners {
|
||||
trieNodes := make(map[string][]byte)
|
||||
for j := 0; j < len(e.TriesPaths[i]); j++ {
|
||||
trieNodes[e.TriesPaths[i][j]] = e.TriesNodes[i][j]
|
||||
}
|
||||
res.tries[owner] = trieNodes
|
||||
}
|
||||
for i, blockNum := range e.BlockNums {
|
||||
res.blockHashes[blockNum] = e.BlockHashes[i]
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// DecodeWitnessRLP decodes a byte slice into a witness object.
|
||||
func DecodeWitnessRLP(b []byte) (*Witness, error) {
|
||||
var res rlpWitness
|
||||
if err := rlp.DecodeBytes(b, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.toWitness()
|
||||
}
|
||||
|
||||
// EncodeRLP encodes a witness object into bytes. The Witness' state root is
|
||||
// zeroed before the encoding. The encoding is not deterministic (the result
|
||||
// can differ for the same Witness)
|
||||
func (w *Witness) EncodeRLP() ([]byte, error) {
|
||||
var encWit rlpWitness
|
||||
var encBlock bytes.Buffer
|
||||
if err := w.Block.EncodeRLPWithZeroRoot(&encBlock); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encWit.EncBlock = encBlock.Bytes()
|
||||
|
||||
for owner, trie := range w.tries {
|
||||
encWit.Owners = append(encWit.Owners, owner)
|
||||
var ownerPaths []string
|
||||
var ownerNodes [][]byte
|
||||
|
||||
for path, node := range trie {
|
||||
ownerPaths = append(ownerPaths, path)
|
||||
ownerNodes = append(ownerNodes, node)
|
||||
}
|
||||
encWit.TriesPaths = append(encWit.TriesPaths, ownerPaths)
|
||||
encWit.TriesNodes = append(encWit.TriesNodes, ownerNodes)
|
||||
}
|
||||
|
||||
for _, code := range w.codes {
|
||||
encWit.Codes = append(encWit.Codes, code)
|
||||
}
|
||||
|
||||
for blockNum, blockHash := range w.blockHashes {
|
||||
encWit.BlockNums = append(encWit.BlockNums, blockNum)
|
||||
encWit.BlockHashes = append(encWit.BlockHashes, blockHash)
|
||||
}
|
||||
res, err := rlp.EncodeToBytes(&encWit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// addAccessList associates a map of RLP-encoded trie nodes keyed by path to
|
||||
// an owner in the witness. the witness takes ownership of the passed map. It
|
||||
// is safe to call this method concurrently.
|
||||
func (w *Witness) addAccessList(owner common.Hash, newTrieNodes map[string][]byte) {
|
||||
var trie map[string][]byte
|
||||
|
||||
if len(newTrieNodes) == 0 {
|
||||
return
|
||||
}
|
||||
w.triesLock.Lock()
|
||||
defer w.triesLock.Unlock()
|
||||
|
||||
trie, ok := w.tries[owner]
|
||||
if !ok {
|
||||
trie = make(map[string][]byte)
|
||||
w.tries[owner] = trie
|
||||
}
|
||||
for path, node := range newTrieNodes {
|
||||
trie[path] = node
|
||||
}
|
||||
}
|
||||
|
||||
// AddBlockHash adds a block hash/number to the witness
|
||||
func (w *Witness) AddBlockHash(hash common.Hash, num uint64) {
|
||||
w.blockHashes[num] = hash
|
||||
}
|
||||
|
||||
// AddCode associates a hash with code in the Witness.
|
||||
// The Witness takes ownership over the passed code slice.
|
||||
func (w *Witness) AddCode(hash common.Hash, code []byte) {
|
||||
if hash == types.EmptyCodeHash || hash == (common.Hash{}) || len(code) == 0 {
|
||||
return
|
||||
}
|
||||
w.codes[hash] = code
|
||||
}
|
||||
|
||||
// Summary prints a human-readable summary containing the total size of the
|
||||
// witness and the sizes of the underlying components
|
||||
func (w *Witness) Summary() string {
|
||||
b := new(bytes.Buffer)
|
||||
xx, err := rlp.EncodeToBytes(w.Block)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
totBlock := len(xx)
|
||||
|
||||
yy, _ := w.EncodeRLP()
|
||||
|
||||
totWit := len(yy)
|
||||
totCode := 0
|
||||
for _, c := range w.codes {
|
||||
totCode += len(c)
|
||||
}
|
||||
totNodes := 0
|
||||
totPaths := 0
|
||||
nodePathCount := 0
|
||||
for _, ownerPaths := range w.tries {
|
||||
for path, node := range ownerPaths {
|
||||
nodePathCount++
|
||||
totNodes += len(node)
|
||||
totPaths += len(path)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(b, "%4d hashes: %v\n", len(w.blockHashes), common.StorageSize(len(w.blockHashes)*32))
|
||||
fmt.Fprintf(b, "%4d owners: %v\n", len(w.tries), common.StorageSize(len(w.tries)*32))
|
||||
fmt.Fprintf(b, "%4d nodes: %v\n", nodePathCount, common.StorageSize(totNodes))
|
||||
fmt.Fprintf(b, "%4d paths: %v\n", nodePathCount, common.StorageSize(totPaths))
|
||||
fmt.Fprintf(b, "%4d codes: %v\n", len(w.codes), common.StorageSize(totCode))
|
||||
fmt.Fprintf(b, "%4d codeHashes: %v\n", len(w.codes), common.StorageSize(len(w.codes)*32))
|
||||
fmt.Fprintf(b, "block (%4d txs): %v\n", len(w.Block.Transactions()), common.StorageSize(totBlock))
|
||||
fmt.Fprintf(b, "Total size: %v\n ", common.StorageSize(totWit))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
|
||||
// is never mutated by Witness
|
||||
func (w *Witness) Copy() *Witness {
|
||||
var res Witness
|
||||
res.Block = w.Block
|
||||
|
||||
for blockNr, blockHash := range w.blockHashes {
|
||||
res.blockHashes[blockNr] = blockHash
|
||||
}
|
||||
for codeHash, code := range w.codes {
|
||||
cpy := make([]byte, len(code))
|
||||
copy(cpy, code)
|
||||
res.codes[codeHash] = cpy
|
||||
}
|
||||
res.root = w.root
|
||||
for owner, owned := range w.tries {
|
||||
res.tries[owner] = make(map[string][]byte)
|
||||
for path, node := range owned {
|
||||
cpy := make([]byte, len(node))
|
||||
copy(cpy, node)
|
||||
res.tries[owner][path] = cpy
|
||||
}
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
// sortedWitness encodes returns an rlpWitness where hash-map items are sorted lexicographically by key
|
||||
// in the encoder object to ensure that the encoded bytes are always the same for a given witness.
|
||||
func (w *Witness) sortedWitness() *rlpWitness {
|
||||
var (
|
||||
sortedCodeHashes []common.Hash
|
||||
owners []common.Hash
|
||||
ownersPaths [][]string
|
||||
ownersNodes [][][]byte
|
||||
blockNrs []uint64
|
||||
blockHashes []common.Hash
|
||||
codeHashes []common.Hash
|
||||
codes [][]byte
|
||||
)
|
||||
for key := range w.codes {
|
||||
sortedCodeHashes = append(sortedCodeHashes, key)
|
||||
}
|
||||
sort.Slice(sortedCodeHashes, func(i, j int) bool {
|
||||
return bytes.Compare(sortedCodeHashes[i][:], sortedCodeHashes[j][:]) > 0
|
||||
})
|
||||
|
||||
// sort the list of owners
|
||||
for owner := range w.tries {
|
||||
owners = append(owners, owner)
|
||||
}
|
||||
sort.Slice(owners, func(i, j int) bool {
|
||||
return bytes.Compare(owners[i][:], owners[j][:]) > 0
|
||||
})
|
||||
|
||||
// sort the trie nodes of each trie by path
|
||||
for _, owner := range owners {
|
||||
nodes := w.tries[owner]
|
||||
var ownerPaths []string
|
||||
for path := range nodes {
|
||||
ownerPaths = append(ownerPaths, path)
|
||||
}
|
||||
sort.Strings(ownerPaths)
|
||||
|
||||
var ownerNodes [][]byte
|
||||
for _, path := range ownerPaths {
|
||||
ownerNodes = append(ownerNodes, nodes[path])
|
||||
}
|
||||
ownersPaths = append(ownersPaths, ownerPaths)
|
||||
ownersNodes = append(ownersNodes, ownerNodes)
|
||||
}
|
||||
|
||||
for blockNr, blockHash := range w.blockHashes {
|
||||
blockNrs = append(blockNrs, blockNr)
|
||||
blockHashes = append(blockHashes, blockHash)
|
||||
}
|
||||
|
||||
for codeHash := range w.codes {
|
||||
codeHashes = append(codeHashes, codeHash)
|
||||
}
|
||||
sort.Slice(codeHashes, func(i, j int) bool {
|
||||
return bytes.Compare(codeHashes[i][:], codeHashes[j][:]) > 0
|
||||
})
|
||||
|
||||
for _, codeHash := range codeHashes {
|
||||
codes = append(codes, w.codes[codeHash])
|
||||
}
|
||||
|
||||
encBlock, _ := rlp.EncodeToBytes(w.Block)
|
||||
return &rlpWitness{
|
||||
EncBlock: encBlock,
|
||||
Root: common.Hash{},
|
||||
Owners: owners,
|
||||
TriesPaths: ownersPaths,
|
||||
TriesNodes: ownersNodes,
|
||||
BlockNums: blockNrs,
|
||||
BlockHashes: blockHashes,
|
||||
Codes: codes,
|
||||
}
|
||||
}
|
||||
|
||||
// PrettyPrint displays the contents of a witness object in a human-readable format to standard output.
|
||||
func (w *Witness) PrettyPrint() string {
|
||||
sorted := w.sortedWitness()
|
||||
b := new(bytes.Buffer)
|
||||
fmt.Fprintf(b, "block: %+v\n", w.Block)
|
||||
fmt.Fprintf(b, "root: %x\n", sorted.Root)
|
||||
fmt.Fprint(b, "owners:\n")
|
||||
for i, owner := range sorted.Owners {
|
||||
if owner == (common.Hash{}) {
|
||||
fmt.Fprintf(b, "\troot:\n")
|
||||
} else {
|
||||
fmt.Fprintf(b, "\t%x:\n", owner)
|
||||
}
|
||||
ownerPaths := sorted.TriesPaths[i]
|
||||
ownerNodes := sorted.TriesNodes[i]
|
||||
for j, path := range ownerPaths {
|
||||
fmt.Fprintf(b, "\t\t%x:%x\n", []byte(path), ownerNodes[j])
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(b, "block hashes:\n")
|
||||
for i, blockNum := range sorted.BlockNums {
|
||||
blockHash := sorted.BlockHashes[i]
|
||||
fmt.Fprintf(b, "\t%d:%x\n", blockNum, blockHash)
|
||||
}
|
||||
fmt.Fprintf(b, "codes:\n")
|
||||
for _, code := range sorted.Codes {
|
||||
hash := crypto.Keccak256Hash(code)
|
||||
fmt.Fprintf(b, "\t%x:%x\n", hash, code)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Hash returns the sha256 hash of a Witness
|
||||
func (w *Witness) Hash() common.Hash {
|
||||
res, err := rlp.EncodeToBytes(w.sortedWitness())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sha256.Sum256(res[:])
|
||||
}
|
||||
|
||||
// NewWitness returns a new Witness object.
|
||||
func NewWitness(root common.Hash) *Witness {
|
||||
return &Witness{
|
||||
Block: nil,
|
||||
blockHashes: make(map[uint64]common.Hash),
|
||||
codes: make(map[common.Hash][]byte),
|
||||
root: root,
|
||||
tries: make(map[common.Hash]map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
// PopulateDB imports tries,codes and block hashes from the witness
|
||||
// into the specified path-based backing db.
|
||||
func (w *Witness) PopulateDB(db ethdb.Database) error {
|
||||
batch := db.NewBatch()
|
||||
for owner, nodes := range w.tries {
|
||||
for path, node := range nodes {
|
||||
if owner == (common.Hash{}) {
|
||||
rawdb.WriteAccountTrieNode(batch, []byte(path), node)
|
||||
} else {
|
||||
rawdb.WriteStorageTrieNode(batch, owner, []byte(path), node)
|
||||
}
|
||||
}
|
||||
}
|
||||
for codeHash, code := range w.codes {
|
||||
rawdb.WriteCode(batch, codeHash, code)
|
||||
}
|
||||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ type StateDB struct {
|
|||
// resurrection. The account value is tracked as the original value
|
||||
// before the transition. This map is populated at the transaction
|
||||
// boundaries.
|
||||
stateObjectsDestruct map[common.Address]*types.StateAccount
|
||||
stateObjectsDestruct map[common.Address]*stateObject
|
||||
|
||||
// This map tracks the account mutations that occurred during the
|
||||
// transition. Uncommitted mutations belonging to the same account
|
||||
|
|
@ -163,6 +163,7 @@ type StateDB struct {
|
|||
StorageUpdated atomic.Int64
|
||||
AccountDeleted int
|
||||
StorageDeleted atomic.Int64
|
||||
witness *Witness
|
||||
}
|
||||
|
||||
// New creates a new state from a given trie.
|
||||
|
|
@ -177,7 +178,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
|||
originalRoot: root,
|
||||
snaps: snaps,
|
||||
stateObjects: make(map[common.Address]*stateObject),
|
||||
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
|
||||
stateObjectsDestruct: make(map[common.Address]*stateObject),
|
||||
mutations: make(map[common.Address]*mutation),
|
||||
logs: make(map[common.Hash][]*types.Log),
|
||||
preimages: make(map[common.Hash][]byte),
|
||||
|
|
@ -582,7 +583,6 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
|||
start := time.Now()
|
||||
acc, err := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes()))
|
||||
s.SnapshotAccountReads += time.Since(start)
|
||||
|
||||
if err == nil {
|
||||
if acc == nil {
|
||||
return nil
|
||||
|
|
@ -703,6 +703,9 @@ func (s *StateDB) Copy() *StateDB {
|
|||
snaps: s.snaps,
|
||||
snap: s.snap,
|
||||
}
|
||||
if s.witness != nil {
|
||||
state.witness = s.witness.Copy()
|
||||
}
|
||||
// Deep copy cached state objects.
|
||||
for addr, obj := range s.stateObjects {
|
||||
state.stateObjects[addr] = obj.deepCopy(state)
|
||||
|
|
@ -755,6 +758,12 @@ func (s *StateDB) RevertToSnapshot(revid int) {
|
|||
s.validRevisions = s.validRevisions[:idx]
|
||||
}
|
||||
|
||||
// EnableWitnessRecording configures the StateDB to build a stateless block
|
||||
// witness this must becalled before starting prefetchers or applying state changes
|
||||
func (s *StateDB) EnableWitnessBuilding() {
|
||||
s.witness = NewWitness(s.originalRoot)
|
||||
}
|
||||
|
||||
// GetRefund returns the current value of the refund counter.
|
||||
func (s *StateDB) GetRefund() uint64 {
|
||||
return s.refund
|
||||
|
|
@ -788,7 +797,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
// set indefinitely). Note only the first occurred self-destruct
|
||||
// event is tracked.
|
||||
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
||||
s.stateObjectsDestruct[obj.address] = obj.origin
|
||||
s.stateObjectsDestruct[obj.address] = obj
|
||||
}
|
||||
} else {
|
||||
obj.finalise()
|
||||
|
|
@ -808,6 +817,46 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
s.clearJournalAndRefund()
|
||||
}
|
||||
|
||||
// collectNonCommittedTrieAccessLists is called if witness building is enabled.
|
||||
// It collects witness access lists for accounts that read from storage, and
|
||||
// whose tries are not going to be hashed/committed (accounts with non-mutated
|
||||
// storage and self-destructed accounts).
|
||||
func (s *StateDB) collectNonCommittedTrieAccessLists() {
|
||||
collectAccount := func(obj *stateObject) {
|
||||
if obj.Root() == types.EmptyRootHash {
|
||||
// TODO: unsure if this explicit check is needed
|
||||
return
|
||||
}
|
||||
tr := obj.getPrefetchedTrie()
|
||||
if tr == nil {
|
||||
if obj.trie == nil {
|
||||
// object storage was never read from
|
||||
return
|
||||
}
|
||||
// either the snapshot is not enabled or the object was not present
|
||||
tr = obj.trie
|
||||
}
|
||||
|
||||
al := tr.AccessList()
|
||||
if al == nil {
|
||||
panic("impossible case: storage trie is non-empty and known to have been read from but a nil access list was returned")
|
||||
}
|
||||
if len(al) == 0 {
|
||||
panic("impossible case: storage trie is non-empty and known to have been read from but a zero-length access list was returned")
|
||||
}
|
||||
s.witness.addAccessList(obj.addrHash, al)
|
||||
}
|
||||
for _, obj := range s.stateObjectsDestruct {
|
||||
collectAccount(obj)
|
||||
}
|
||||
for _, obj := range s.stateObjects {
|
||||
if _, ok := s.mutations[obj.address]; ok {
|
||||
continue
|
||||
}
|
||||
collectAccount(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// IntermediateRoot computes the current root hash of the state trie.
|
||||
// It is called in between transactions to get the root hash that
|
||||
// goes into transaction receipts.
|
||||
|
|
@ -824,6 +873,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher
|
||||
}()
|
||||
}
|
||||
|
||||
// Process all storage updates concurrently. The state object update root
|
||||
// method will internally call a blocking trie fetch from the prefetcher,
|
||||
// so there's no need to explicitly wait for the prefetchers to finish.
|
||||
|
|
@ -849,6 +899,12 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
return nil
|
||||
})
|
||||
}
|
||||
if s.witness != nil {
|
||||
// if witness building is enabled, collect storage access lists from
|
||||
// tries that will not be committed: accounts with non-mutated storage
|
||||
// and self-destructed accounts.
|
||||
s.collectNonCommittedTrieAccessLists()
|
||||
}
|
||||
workers.Wait()
|
||||
s.StorageUpdates += time.Since(start)
|
||||
|
||||
|
|
@ -1060,7 +1116,8 @@ func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trieno
|
|||
buf = crypto.NewKeccakState()
|
||||
deletes = make(map[common.Hash]*accountDelete)
|
||||
)
|
||||
for addr, prev := range s.stateObjectsDestruct {
|
||||
for addr, obj := range s.stateObjectsDestruct {
|
||||
prev := obj.origin
|
||||
// The account was non-existent, and it's marked as destructed in the scope
|
||||
// of block. It can be either case (a) or (b) and will be interpreted as
|
||||
// null->null state transition.
|
||||
|
|
@ -1168,6 +1225,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
|||
root common.Hash
|
||||
workers errgroup.Group
|
||||
)
|
||||
|
||||
// Schedule the account trie first since that will be the biggest, so give
|
||||
// it the most time to crunch.
|
||||
//
|
||||
|
|
@ -1178,8 +1236,25 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
|||
// Obviously it's not an end of the world issue, just something the original
|
||||
// code didn't anticipate for.
|
||||
workers.Go(func() error {
|
||||
var (
|
||||
newroot common.Hash
|
||||
set *trienode.NodeSet
|
||||
al map[string][]byte
|
||||
)
|
||||
// Write the account trie changes, measuring the amount of wasted time
|
||||
newroot, set := s.trie.Commit(true)
|
||||
if s.witness != nil {
|
||||
newroot, set = s.trie.Commit(true)
|
||||
al = s.trie.AccessList()
|
||||
if al == nil {
|
||||
panic("this should only happen if starting with completely empty state")
|
||||
}
|
||||
if len(al) == 0 {
|
||||
panic("blocks without state changes not possible")
|
||||
}
|
||||
s.witness.addAccessList(common.Hash{}, al)
|
||||
} else {
|
||||
newroot, set = s.trie.Commit(true)
|
||||
}
|
||||
root = newroot
|
||||
|
||||
if err := merge(set); err != nil {
|
||||
|
|
@ -1207,7 +1282,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
|||
// Run the storage updates concurrently to one another
|
||||
workers.Go(func() error {
|
||||
// Write any storage changes in the state object to its storage trie
|
||||
update, set, err := obj.commit()
|
||||
update, set, al, err := obj.commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1215,9 +1290,12 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
|||
return err
|
||||
}
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
updates[obj.addrHash] = update
|
||||
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
|
||||
lock.Unlock()
|
||||
if s.witness != nil && len(al) > 0 {
|
||||
s.witness.addAccessList(obj.addrHash, al)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
@ -1239,7 +1317,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
|
|||
|
||||
// Clear all internal flags and update state root at the end.
|
||||
s.mutations = make(map[common.Address]*mutation)
|
||||
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
|
||||
s.stateObjectsDestruct = make(map[common.Address]*stateObject)
|
||||
|
||||
origin := s.originalRoot
|
||||
s.originalRoot = root
|
||||
|
|
@ -1294,6 +1372,12 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
|
|||
return ret, err
|
||||
}
|
||||
|
||||
// Witness returns a block witness object being constructed or nil if the
|
||||
// StateDB instance is not configured to record stateless witnesses.
|
||||
func (s *StateDB) Witness() *Witness {
|
||||
return s.witness
|
||||
}
|
||||
|
||||
// Commit writes the state mutations into the configured data stores.
|
||||
//
|
||||
// Once the state is committed, tries cached in stateDB (including account
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
|
|||
var (
|
||||
header = block.Header()
|
||||
gaspool = new(GasPool).AddGas(block.GasLimit())
|
||||
blockContext = NewEVMBlockContext(header, p.bc, nil)
|
||||
blockContext = NewEVMBlockContext(header, p.bc, nil, nil)
|
||||
evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
|
||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,15 +39,40 @@ type StateProcessor struct {
|
|||
config *params.ChainConfig // Chain configuration options
|
||||
bc *BlockChain // Canonical block chain
|
||||
engine consensus.Engine // Consensus engine used for block rewards
|
||||
statelessChainCtx ChainContext
|
||||
}
|
||||
|
||||
// NewStateProcessor initialises a new StateProcessor.
|
||||
// NewStateProcessor initialises a new StateProcessor. If the provided
|
||||
// Blockchain is nil, stateless execution mode is enabled.
|
||||
func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *StateProcessor {
|
||||
if bc != nil {
|
||||
return &StateProcessor{
|
||||
config: config,
|
||||
bc: bc,
|
||||
engine: engine,
|
||||
}
|
||||
} else {
|
||||
return &StateProcessor{
|
||||
config: config,
|
||||
engine: engine,
|
||||
statelessChainCtx: &statelessChainContext{engine},
|
||||
bc: &BlockChain{
|
||||
chainConfig: config,
|
||||
engine: engine,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type statelessChainContext struct {
|
||||
engine consensus.Engine
|
||||
}
|
||||
|
||||
func (s *statelessChainContext) Engine() consensus.Engine {
|
||||
return s.engine
|
||||
}
|
||||
func (s *statelessChainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Process processes the state changes according to the Ethereum rules by running
|
||||
|
|
@ -57,7 +82,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
|
|||
// Process returns the receipts and logs accumulated during the process and
|
||||
// returns the amount of gas that was used in the process. If any of the
|
||||
// transactions failed to execute due to insufficient gas it will return an error.
|
||||
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
|
||||
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, witness *state.Witness) (types.Receipts, []*types.Log, uint64, error) {
|
||||
var (
|
||||
receipts types.Receipts
|
||||
usedGas = new(uint64)
|
||||
|
|
@ -73,10 +98,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
var (
|
||||
context = NewEVMBlockContext(header, p.bc, nil)
|
||||
vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
|
||||
context vm.BlockContext
|
||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||
)
|
||||
context = NewEVMBlockContext(header, p.bc, nil, witness)
|
||||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||
}
|
||||
|
|
@ -177,7 +203,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
|||
return nil, err
|
||||
}
|
||||
// Create a new context to be used in the EVM environment
|
||||
blockContext := NewEVMBlockContext(header, bc, author)
|
||||
blockContext := NewEVMBlockContext(header, bc, author, statedb.Witness())
|
||||
txContext := NewEVMTxContext(msg)
|
||||
vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
|
||||
return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package core
|
|||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
|
|
@ -33,7 +35,7 @@ type Validator interface {
|
|||
|
||||
// ValidateState validates the given statedb and optionally the receipts and
|
||||
// gas used.
|
||||
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64) error
|
||||
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, rootCheck bool) (common.Hash, error)
|
||||
}
|
||||
|
||||
// Prefetcher is an interface for pre-caching transaction signatures and state.
|
||||
|
|
@ -49,5 +51,5 @@ type Processor interface {
|
|||
// Process processes the state changes according to the Ethereum rules by running
|
||||
// the transaction messages using the statedb and applying any rewards to both
|
||||
// the processor (coinbase) and any included uncles.
|
||||
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error)
|
||||
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, witness *state.Witness) (types.Receipts, []*types.Log, uint64, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,18 @@ type Body struct {
|
|||
Withdrawals []*Withdrawal `rlp:"optional"`
|
||||
}
|
||||
|
||||
// EncodeRLPWithZeroRoot encodes a block (with header state root set to 0x00...0) to RLP
|
||||
func (b *Block) EncodeRLPWithZeroRoot(w io.Writer) error {
|
||||
old := b.header.Root
|
||||
b.header.Root = common.Hash{}
|
||||
err := b.EncodeRLP(w)
|
||||
b.header.Root = old
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Block represents an Ethereum block.
|
||||
//
|
||||
// Note the Block type tries to be 'immutable', and contains certain caches that rely
|
||||
|
|
|
|||
|
|
@ -231,6 +231,11 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
code := evm.StateDB.GetCode(addr)
|
||||
codeCopy := make([]byte, len(code))
|
||||
copy(codeCopy[:], code[:])
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCodeHash(addr), codeCopy)
|
||||
}
|
||||
if len(code) == 0 {
|
||||
ret, err = nil, nil // gas is unchanged
|
||||
} else {
|
||||
|
|
@ -298,6 +303,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
|||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
}
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
ret, err = evm.interpreter.Run(contract, input, false)
|
||||
gas = contract.Gas
|
||||
|
|
@ -345,6 +353,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
|||
addrCopy := addr
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
}
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
ret, err = evm.interpreter.Run(contract, input, false)
|
||||
gas = contract.Gas
|
||||
|
|
@ -400,6 +411,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
|||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
}
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||
|
|
|
|||
|
|
@ -340,6 +340,12 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
|
|||
|
||||
func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
slot := scope.Stack.peek()
|
||||
address := slot.Bytes20()
|
||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||
code := interpreter.evm.StateDB.GetCode(address)
|
||||
codeHash := interpreter.evm.StateDB.GetCodeHash(address)
|
||||
witness.AddCode(codeHash, code)
|
||||
}
|
||||
slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -378,6 +384,11 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
|||
uint64CodeOffset = math.MaxUint64
|
||||
}
|
||||
addr := common.Address(a.Bytes20())
|
||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||
hash := interpreter.evm.StateDB.GetCodeHash(addr)
|
||||
code := interpreter.evm.StateDB.GetCode(addr)
|
||||
witness.AddCode(hash, code)
|
||||
}
|
||||
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
|
||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
||||
|
||||
|
|
@ -416,6 +427,11 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
|||
if interpreter.evm.StateDB.Empty(address) {
|
||||
slot.Clear()
|
||||
} else {
|
||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||
hash := interpreter.evm.StateDB.GetCodeHash(address)
|
||||
code := interpreter.evm.StateDB.GetCode(address)
|
||||
witness.AddCode(hash, code)
|
||||
}
|
||||
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
|
||||
}
|
||||
return nil, nil
|
||||
|
|
@ -443,7 +459,11 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
|
|||
lower = upper - 256
|
||||
}
|
||||
if num64 >= lower && num64 < upper {
|
||||
num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes())
|
||||
res := interpreter.evm.Context.GetHash(num64)
|
||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddBlockHash(res, num64)
|
||||
}
|
||||
num.SetBytes(res[:])
|
||||
} else {
|
||||
num.Clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package vm
|
|||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -87,6 +89,8 @@ type StateDB interface {
|
|||
|
||||
AddLog(*types.Log)
|
||||
AddPreimage(common.Hash, []byte)
|
||||
|
||||
Witness() *state.Witness
|
||||
}
|
||||
|
||||
// CallContext provides a basic interface for the EVM calling conventions. The EVM
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st
|
|||
if blockCtx != nil {
|
||||
context = *blockCtx
|
||||
} else {
|
||||
context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
|
||||
context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil, nil)
|
||||
}
|
||||
return vm.NewEVM(context, txContext, state, b.ChainConfig(), *vmConfig)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils/stateless"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
|
|
@ -443,3 +445,17 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) {
|
|||
}
|
||||
return api.eth.blockchain.GetTrieFlushInterval().String(), nil
|
||||
}
|
||||
|
||||
// BuildStatelessProof executes a block, collecting the accessed pre-state into
|
||||
// a Witness. The RLP-encoded witness is returned.
|
||||
func (api *DebugAPI) BuildStatelessProof(numOrHash rpc.BlockNumberOrHash) ([]byte, error) {
|
||||
var blockHash common.Hash
|
||||
if numOrHash.BlockNumber != nil {
|
||||
number := numOrHash.BlockNumber.Int64()
|
||||
block := api.eth.blockchain.GetBlockByNumber(uint64(number))
|
||||
blockHash = block.Hash()
|
||||
} else {
|
||||
blockHash = *numOrHash.BlockHash
|
||||
}
|
||||
return stateless.BuildStatelessProof(blockHash, api.eth.blockchain)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
|
|||
// Assemble the call and the call context
|
||||
var (
|
||||
msgContext = core.NewEVMTxContext(call)
|
||||
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
|
||||
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil, nil)
|
||||
|
||||
dirtyState = opts.State.Copy()
|
||||
evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
|
|||
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
|
||||
return nil, nil, fmt.Errorf("block #%d not found", next)
|
||||
}
|
||||
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
|
||||
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
|
||||
}
|
||||
|
|
@ -235,7 +235,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
|
|||
}
|
||||
// Insert parent beacon block root in the state as per EIP-4788.
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
|
||||
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, nil)
|
||||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{})
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||
}
|
||||
|
|
@ -248,7 +248,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
|
|||
// Assemble the transaction call message and return if the requested offset
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
|
||||
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, nil)
|
||||
if idx == txIndex {
|
||||
return tx, context, statedb, release, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
for task := range taskCh {
|
||||
var (
|
||||
signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time())
|
||||
blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil)
|
||||
blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil, nil)
|
||||
)
|
||||
// Trace all the transactions contained within
|
||||
for i, tx := range task.block.Transactions() {
|
||||
|
|
@ -378,7 +378,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
// Insert block's parent beacon block root in the state
|
||||
// as per EIP-4788.
|
||||
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
|
||||
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
|
||||
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil, nil)
|
||||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||
}
|
||||
|
|
@ -527,7 +527,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
|||
roots []common.Hash
|
||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||
chainConfig = api.backend.ChainConfig()
|
||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
||||
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
|
||||
)
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
|
|
@ -605,7 +605,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||
var (
|
||||
txs = block.Transactions()
|
||||
blockHash = block.Hash()
|
||||
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||
results = make([]*txTraceResult, len(txs))
|
||||
)
|
||||
|
|
@ -665,7 +665,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
|
|||
// as the GetHash function of BlockContext is not safe for
|
||||
// concurrent use.
|
||||
// See: https://github.com/ethereum/go-ethereum/issues/29114
|
||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
||||
res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config)
|
||||
if err != nil {
|
||||
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
|
||||
|
|
@ -678,7 +678,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
|
|||
|
||||
// Feed the transactions into the tracers and return
|
||||
var failed error
|
||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
||||
txloop:
|
||||
for i, tx := range txs {
|
||||
// Send the trace task over for execution
|
||||
|
|
@ -755,7 +755,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
dumps []string
|
||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||
chainConfig = api.backend.ChainConfig()
|
||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
||||
canon = true
|
||||
)
|
||||
// Check if there are any overrides: the caller may wish to enable a future
|
||||
|
|
@ -935,7 +935,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
|||
}
|
||||
defer release()
|
||||
|
||||
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil)
|
||||
// Apply the customization rules if required.
|
||||
if config != nil {
|
||||
if err := config.StateOverrides.Apply(statedb); err != nil {
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
|
|||
for idx, tx := range block.Transactions() {
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
context := core.NewEVMBlockContext(block.Header(), b.chain, nil)
|
||||
context := core.NewEVMBlockContext(block.Header(), b.chain, nil, nil)
|
||||
if idx == txIndex {
|
||||
return tx, context, statedb, release, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1110,7 +1110,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
|
|||
defer cancel()
|
||||
|
||||
// Get a new instance of the EVM.
|
||||
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
|
||||
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil, nil)
|
||||
if blockOverrides != nil {
|
||||
blockOverrides.Apply(&blockCtx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -569,7 +569,7 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state
|
|||
vmConfig = b.chain.GetVMConfig()
|
||||
}
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
context := core.NewEVMBlockContext(header, b.chain, nil)
|
||||
context := core.NewEVMBlockContext(header, b.chain, nil, nil)
|
||||
if blockContext != nil {
|
||||
context = *blockContext
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error)
|
|||
return nil, err
|
||||
}
|
||||
if header.ParentBeaconRoot != nil {
|
||||
context := core.NewEVMBlockContext(header, miner.chain, nil)
|
||||
context := core.NewEVMBlockContext(header, miner.chain, nil, nil)
|
||||
vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{})
|
||||
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,94 @@ package tests
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
)
|
||||
|
||||
func TestStatelessBlockchain(t *testing.T) {
|
||||
bt := new(testMatcher)
|
||||
|
||||
// These tests fail as of https://github.com/ethereum/go-ethereum/pull/28666, since we
|
||||
// no longer delete "leftover storage" when deploying a contract.
|
||||
bt.skipLoad(`^GeneralStateTests/stSStoreTest/InitCollision\.json`)
|
||||
bt.skipLoad(`^GeneralStateTests/stRevertTest/RevertInCreateInInit\.json`)
|
||||
bt.skipLoad(`^GeneralStateTests/stExtCodeHash/dynamicAccountOverwriteEmpty\.json`)
|
||||
bt.skipLoad(`^GeneralStateTests/stCreate2/create2collisionStorage\.json`)
|
||||
bt.skipLoad(`^GeneralStateTests/stCreate2/RevertInCreateInInitCreate2\.json`)
|
||||
|
||||
// this test imports a forked chain. The witness builder API receives a block by number
|
||||
// loading it from the chain. So it fails to properly source the forked chain block,
|
||||
// erroneously using the one from the main chain (hence the state root mismatch).
|
||||
bt.skipLoad(`^InvalidBlocks/bcMultiChainTest/UncleFromSideChain\.json`)
|
||||
// Skip random failures due to selfish mining test
|
||||
bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`)
|
||||
// Skip random failures due to selfish mining test
|
||||
bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`)
|
||||
|
||||
// Slow tests
|
||||
bt.slow(`.*bcExploitTest/DelegateCallSpam.json`)
|
||||
bt.slow(`.*bcExploitTest/ShanghaiLove.json`)
|
||||
bt.slow(`.*bcExploitTest/SuicideIssue.json`)
|
||||
bt.slow(`.*/bcForkStressTest/`)
|
||||
bt.slow(`.*/bcGasPricerTest/RPC_API_Test.json`)
|
||||
bt.slow(`.*/bcWalletTest/`)
|
||||
|
||||
// Very slow test
|
||||
bt.skipLoad(`.*/stTimeConsuming/.*`)
|
||||
// test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range,
|
||||
// using 4.6 TGas
|
||||
bt.skipLoad(`.*randomStatetest94.json.*`)
|
||||
|
||||
// skip uncle tests for stateless
|
||||
bt.skipLoad(`.*/UnclePopulation.json`)
|
||||
// skip this test in stateless because it uses 5000 blocks and the
|
||||
// historical state of older blocks is unavailable for stateless
|
||||
// test verification after importing the test set.
|
||||
bt.skipLoad(`.*/bcWalletTest/walletReorganizeOwners.json`)
|
||||
|
||||
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||
if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 {
|
||||
t.Skip("test (randomly) skipped on 32-bit windows")
|
||||
}
|
||||
|
||||
config, ok := Forks[test.json.Network]
|
||||
if !ok {
|
||||
t.Fatalf("test malformed: doesn't have chain config embedded")
|
||||
}
|
||||
isMerged := config.TerminalTotalDifficulty != nil && config.TerminalTotalDifficulty.BitLen() == 0
|
||||
if isMerged {
|
||||
execBlockTestStateless(t, bt, test)
|
||||
} else {
|
||||
t.Skip("skipping pre-merge test")
|
||||
}
|
||||
})
|
||||
// There is also a LegacyTests folder, containing blockchain tests generated
|
||||
// prior to Istanbul. However, they are all derived from GeneralStateTests,
|
||||
// which run natively, so there's no reason to run them here.
|
||||
}
|
||||
|
||||
func execBlockTestStateless(t *testing.T, bt *testMatcher, test *BlockTest) {
|
||||
if err := bt.checkFailure(t, test.RunStateless(false, rawdb.HashScheme, nil, nil)); err != nil {
|
||||
t.Errorf("test in hash mode without snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
if err := bt.checkFailure(t, test.RunStateless(true, rawdb.HashScheme, nil, nil)); err != nil {
|
||||
t.Errorf("test in hash mode with snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
if err := bt.checkFailure(t, test.RunStateless(false, rawdb.PathScheme, nil, nil)); err != nil {
|
||||
t.Errorf("test in path mode without snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
if err := bt.checkFailure(t, test.RunStateless(true, rawdb.PathScheme, nil, nil)); err != nil {
|
||||
t.Errorf("test in path mode with snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockchain(t *testing.T) {
|
||||
bt := new(testMatcher)
|
||||
// General state tests are 'exported' as blockchain tests, but we can run them natively.
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ import (
|
|||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
|
|
@ -34,7 +37,6 @@ import (
|
|||
"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/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -111,6 +113,14 @@ type btHeaderMarshaling struct {
|
|||
}
|
||||
|
||||
func (t *BlockTest) Run(snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
|
||||
return t.run(false, snapshotter, scheme, tracer, postCheck)
|
||||
}
|
||||
|
||||
func (t *BlockTest) RunStateless(snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
|
||||
return t.run(true, snapshotter, scheme, tracer, postCheck)
|
||||
}
|
||||
|
||||
func (t *BlockTest) run(isStateless bool, snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
|
||||
config, ok := Forks[t.json.Network]
|
||||
if !ok {
|
||||
return UnsupportedForkError{t.json.Network}
|
||||
|
|
@ -184,7 +194,29 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer *tracing.Hooks,
|
|||
return err
|
||||
}
|
||||
}
|
||||
return t.validateImportedHeaders(chain, validBlocks)
|
||||
if err := t.validateImportedHeaders(chain, validBlocks); err != nil {
|
||||
return err
|
||||
}
|
||||
if isStateless {
|
||||
for _, blk := range validBlocks {
|
||||
proof, err := stateless.BuildStatelessProof(blk.BlockHeader.Hash, chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build proof: %v", err)
|
||||
}
|
||||
witness, err := state.DecodeWitnessRLP(proof)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode witness RLP: %v", err)
|
||||
}
|
||||
root, err := stateless.StatelessExecute(config, witness)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verification execution error: %v", err)
|
||||
}
|
||||
if root != blk.BlockHeader.StateRoot {
|
||||
return fmt.Errorf("state root mismatch (wanted: %x, got: %x)", blk.BlockHeader.StateRoot, root)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
|||
|
||||
// Prepare the EVM.
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
|
||||
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, nil)
|
||||
context.GetHash = vmTestBlockHash
|
||||
context.BaseFee = baseFee
|
||||
evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig)
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
|
|||
|
||||
// Prepare the EVM.
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
|
||||
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, nil)
|
||||
context.GetHash = vmTestBlockHash
|
||||
context.BaseFee = baseFee
|
||||
context.Random = nil
|
||||
|
|
|
|||
|
|
@ -214,6 +214,10 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte {
|
|||
return t.db.Preimage(common.BytesToHash(shaKey))
|
||||
}
|
||||
|
||||
func (t *StateTrie) AccessList() map[string][]byte {
|
||||
return t.trie.AccessList()
|
||||
}
|
||||
|
||||
// Commit collects all dirty nodes in the trie and replaces them with the
|
||||
// corresponding node hash. All collected nodes (including dirty leaves if
|
||||
// collectLeaf is true) will be encapsulated into a nodeset for return.
|
||||
|
|
|
|||
|
|
@ -601,6 +601,12 @@ func (t *Trie) Hash() common.Hash {
|
|||
return common.BytesToHash(hash.(hashNode))
|
||||
}
|
||||
|
||||
// AccessList returns a map of path->blob containing all trie nodes that have
|
||||
// been accessed.
|
||||
func (t *Trie) AccessList() map[string][]byte {
|
||||
return t.tracer.accessList
|
||||
}
|
||||
|
||||
// Commit collects all dirty nodes in the trie and replaces them with the
|
||||
// corresponding node hash. All collected nodes (including dirty leaves if
|
||||
// collectLeaf is true) will be encapsulated into a nodeset for return.
|
||||
|
|
|
|||
|
|
@ -369,3 +369,9 @@ func (t *VerkleTrie) ToDot() string {
|
|||
func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
|
||||
return t.reader.node(path, common.Hash{})
|
||||
}
|
||||
|
||||
// AccessList returns a map of path->blob containing all trie nodes that have
|
||||
// been accessed.
|
||||
func (t *VerkleTrie) AccessList() map[string][]byte {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,13 @@ var HashDefaults = &Config{
|
|||
HashDB: hashdb.Defaults,
|
||||
}
|
||||
|
||||
// PathDefaults represents a config for using path-based scheme with
|
||||
// default settings.
|
||||
var PathDefaults = &Config{
|
||||
Preimages: false,
|
||||
PathDB: pathdb.Defaults,
|
||||
}
|
||||
|
||||
// backend defines the methods needed to access/update trie nodes in different
|
||||
// state scheme.
|
||||
type backend interface {
|
||||
|
|
|
|||
Loading…
Reference in a new issue