all: implement EIP-compliant verkle trees

verkle: Implement Trie, NodeIterator and Database ifs

Fix crash in TestDump

Fix TestDump

Fix TrieCopy

remove unnecessary traces

fix: Error() returned errIteratorEnd in verkle node iterator

rewrite the iterator and change the signature of OpenStorageTrie

add the adapter to reuse the account trie for storage

don't try to deserialize a storage leaf into an account

Fix statedb unit tests (#14)

* debug code

* Fix more unit tests

* remove traces

* Go back to the full range

One tree to rule them all

remove updateRoot, there is no root to update

store code inside the account leaf

fix build

save current state for Sina

Update go-verkle to latest

Charge WITNESS_*_COST gas on storage loads

Add witness costs for SSTORE as well

Charge witness gas in the case of code execution

corresponding code deletion

add a --verkle flag to separate verkle experiments from regular geth operations

use the snapshot to get data

stateless execution from block witness

AccessWitness functions

Add block generation test + genesis snapshot generation

test stateless block execution (#18)

* test stateless block execution

* Force tree resolution before generating the proof

increased coverage in stateless test execution (#19)

* test stateless block execution

* Force tree resolution before generating the proof

* increase coverage in stateless test execution

ensure geth compiles

fix issues in tests with verkle trees deactivated

Ensure stateless data is available when executing statelessly (#20)

* Ensure stateless data is available when executing statelessly

* Actual execution of a statless block

* bugfixes in stateless block execution

* code cleanup

 - Reduce PR footprint by reverting NewEVM to its original signature
 - Move the access witness to the block context
 - prepare for a change in AW semantics
   Need to store the initial values.
 - Use the touch helper function, DRY

* revert the signature of MustCommit to its original form (#21)

fix leaf proofs in stateless execution (#22)

* Fixes in witness pre-state

* Add the recipient's nonce to the witness

* reduce PR footprint and investigate issue in root state calculation

* quick build fix

cleanup: Remove extra parameter in ToBlock

revert ToBlock to its older signature

fix import cycle in vm tests

fix linter issue

fix appveyor build

fix nil pointers in tests

Add indices, yis and Cis to the block's Verkle proof

upgrade geth dependency to drop geth's common dep

fix cmd/devp2p tests

fix rebase issues

quell an appveyor warning

fix address touching in SLOAD and SSTORE

fix access witness for code size

touch target account data before calling

make sure the proper locations get touched in (ext)codecopy

touch all code pages in execution

add pushdata to witness

remove useless code in genesis snapshot generation

testnet: fix some of the rebase/drift issues

Fix verkle proof generation in block

fix an issue occuring when chunking past the code size

fix: ensure the code copy doesn't extend past the code size
This commit is contained in:
Guillaume Ballet
2021-05-06 18:58:39 +02:00
parent c10a0a62c3
commit 162780515a
43 changed files with 1731 additions and 66 deletions

View File

@ -234,6 +234,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
Cache: cacheConfig.TrieCleanLimit,
Journal: cacheConfig.TrieCleanJournal,
Preimages: cacheConfig.Preimages,
UseVerkle: chainConfig.UseVerkle,
}),
quit: make(chan struct{}),
chainmu: syncx.NewClosableMutex(),
@ -375,7 +376,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
log.Warn("Enabling snapshot recovery", "chainhead", head.NumberU64(), "diskbase", *layer)
recover = true
}
bc.snaps, _ = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, true, recover)
bc.snaps, _ = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, true, recover, bc.Config().UseVerkle)
}
// Start future block processor.
@ -1592,7 +1593,22 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
// Process block using the parent state as reference point
substart := time.Now()
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
var (
usedGas uint64
receipts types.Receipts
logs []*types.Log
)
if len(block.Header().VerkleProof) == 0 {
receipts, logs, _, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig)
} else {
var leaves map[common.Hash]common.Hash
_, _, _, leaves, err = trie.DeserializeAndVerifyVerkleProof(block.Header().VerkleProof)
if err != nil {
return it.index, err
}
statedb.SetStateless(leaves)
receipts, logs, usedGas, err = bc.processor.ProcessStateless(block, statedb, bc.vmConfig, leaves)
}
if err != nil {
bc.reportBlock(block, receipts, err)
atomic.StoreUint32(&followupInterrupt, 1)

View File

@ -160,7 +160,7 @@ 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{})
if err != nil {
blockchain.reportBlock(block, receipts, err)
return err

View File

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
)
// BlockGen creates blocks for testing.
@ -43,6 +44,7 @@ type BlockGen struct {
txs []*types.Transaction
receipts []*types.Receipt
uncles []*types.Header
witness *types.AccessWitness
config *params.ChainConfig
engine consensus.Engine
@ -103,10 +105,17 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
b.SetCoinbase(common.Address{})
}
b.statedb.Prepare(tx.Hash(), len(b.txs))
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
receipt, accesses, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
if err != nil {
panic(err)
}
if accesses != nil {
if b.witness != nil {
b.witness.Merge(accesses)
} else {
b.witness = accesses
}
}
b.txs = append(b.txs, tx)
b.receipts = append(b.receipts, receipt)
}
@ -284,6 +293,90 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
return blocks, receipts
}
func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
if config == nil {
config = params.TestChainConfig
}
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
chainreader := &fakeChainReader{config: config}
genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine, witness: types.NewAccessWitness()}
b.header = makeHeader(chainreader, parent, statedb, b.engine)
// Mutate the state and block according to any hard-fork specs
if daoBlock := config.DAOForkBlock; daoBlock != nil {
limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
if b.header.Number.Cmp(daoBlock) >= 0 && b.header.Number.Cmp(limit) < 0 {
if config.DAOForkSupport {
b.header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
}
}
}
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
misc.ApplyDAOHardFork(statedb)
}
// Execute any user modifications to the block
if gen != nil {
gen(i, b)
}
if b.engine != nil {
// Finalize and seal the block
block, err := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)
if err != nil {
panic(err)
}
// Write state changes to db
root, err := statedb.Commit(config.IsEIP158(b.header.Number))
if err != nil {
panic(fmt.Sprintf("state write error: %v", err))
}
if err := statedb.Database().TrieDB().Commit(root, false, nil); err != nil {
panic(fmt.Sprintf("trie write error: %v", err))
}
// Generate an associated verkle proof
if tr := statedb.GetTrie(); tr.IsVerkle() {
vtr := tr.(*trie.VerkleTrie)
// Generate the proof if we are using a verkle tree
// WORKAROUND: make sure all keys are resolved
// before building the proof. Ultimately, node
// resolution can be done with a prefetcher or
// from GetCommitmentsAlongPath.
keys := b.witness.Keys()
for _, key := range keys {
out, err := vtr.TryGet(key)
if err != nil {
panic(err)
}
if len(out) == 0 {
panic(fmt.Sprintf("%x should be present in the tree", key))
}
}
vtr.Hash()
_, err := vtr.ProveAndSerialize(keys, b.witness.KeyVals())
//block.SetVerkleProof(p)
if err != nil {
panic(err)
}
}
return block, b.receipts
}
return nil, nil
}
for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), state.NewDatabaseWithConfig(db, &trie.Config{UseVerkle: true}), nil)
if err != nil {
panic(err)
}
block, receipt := genblock(i, parent, statedb)
blocks[i] = block
receipts[i] = receipt
parent = block
}
return blocks, receipts
}
func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
var time uint64
if parent.Time() == 0 {

View File

@ -69,6 +69,7 @@ func NewEVMTxContext(msg Message) vm.TxContext {
return vm.TxContext{
Origin: msg.From(),
GasPrice: new(big.Int).Set(msg.GasPrice()),
Accesses: types.NewAccessWitness(),
}
}

View File

@ -180,7 +180,11 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
// We have the genesis block in database(perhaps in ancient database)
// but the corresponding state is missing.
header := rawdb.ReadHeader(db, stored, 0)
if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, nil), nil); err != nil {
var cfg *trie.Config = nil
if genesis.Config.UseVerkle {
cfg = &trie.Config{UseVerkle: true}
}
if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, cfg), nil); err != nil {
if genesis == nil {
genesis = DefaultGenesisBlock()
}
@ -264,7 +268,11 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
if db == nil {
db = rawdb.NewMemoryDatabase()
}
statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
var trieCfg *trie.Config
if g.Config != nil {
trieCfg = &trie.Config{UseVerkle: g.Config.UseVerkle}
}
statedb, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(db, trieCfg), nil)
if err != nil {
panic(err)
}
@ -306,6 +314,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
}
statedb.Commit(false)
statedb.Database().TrieDB().Commit(root, true, nil)
if err := statedb.Cap(root); err != nil {
panic(err)
}
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
}
@ -357,6 +368,20 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big
return g.MustCommit(db)
}
func DefaultVerkleGenesisBlock() *Genesis {
return &Genesis{
Config: params.VerkleChainConfig,
Nonce: 86,
GasLimit: 0x2fefd8,
Difficulty: big.NewInt(1),
Alloc: map[common.Address]GenesisAccount{
common.BytesToAddress([]byte{97, 118, 97, 209, 72, 165, 43, 239, 81, 162, 104, 199, 40, 179, 162, 27, 88, 249, 67, 6}): {
Balance: big.NewInt(0).Lsh(big.NewInt(1), 27),
},
},
}
}
// DefaultGenesisBlock returns the Ethereum main net genesis block.
func DefaultGenesisBlock() *Genesis {
return &Genesis{

View File

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
"github.com/gballet/go-verkle"
lru "github.com/hashicorp/golang-lru"
)
@ -104,6 +105,9 @@ type Trie interface {
// nodes of the longest existing prefix of the key (at least the root), ending
// with the node that proves the absence of the key.
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error
// IsVerkle returns true if the trie is verkle-tree based
IsVerkle() bool
}
// NewDatabase creates a backing store for state. The returned database is safe for
@ -118,6 +122,13 @@ func NewDatabase(db ethdb.Database) Database {
// large memory cache.
func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database {
csc, _ := lru.New(codeSizeCacheSize)
if config != nil && config.UseVerkle {
return &VerkleDB{
db: trie.NewDatabaseWithConfig(db, config),
codeSizeCache: csc,
codeCache: fastcache.New(codeCacheSize),
}
}
return &cachingDB{
db: trie.NewDatabaseWithConfig(db, config),
codeSizeCache: csc,
@ -202,3 +213,67 @@ func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, erro
func (db *cachingDB) TrieDB() *trie.Database {
return db.db
}
// VerkleDB implements state.Database for a verkle tree
type VerkleDB struct {
db *trie.Database
codeSizeCache *lru.Cache
codeCache *fastcache.Cache
}
// OpenTrie opens the main account trie.
func (db *VerkleDB) OpenTrie(root common.Hash) (Trie, error) {
if root == (common.Hash{}) || root == emptyRoot {
return trie.NewVerkleTrie(verkle.New(), db.db), nil
}
payload, err := db.db.DiskDB().Get(root[:])
if err != nil {
return nil, err
}
r, err := verkle.ParseNode(payload, 0)
if err != nil {
panic(err)
}
return trie.NewVerkleTrie(r, db.db), err
}
// OpenStorageTrie opens the storage trie of an account.
func (db *VerkleDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
// alternatively, return accTrie
panic("should not be called")
}
// CopyTrie returns an independent copy of the given trie.
func (db *VerkleDB) CopyTrie(tr Trie) Trie {
t, ok := tr.(*trie.VerkleTrie)
if ok {
return t.Copy(db.db)
}
panic("invalid tree type != VerkleTrie")
}
// ContractCode retrieves a particular contract's code.
func (db *VerkleDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
return code, nil
}
code := rawdb.ReadCode(db.db.DiskDB(), codeHash)
if len(code) > 0 {
db.codeCache.Set(codeHash.Bytes(), code)
db.codeSizeCache.Add(codeHash, len(code))
return code, nil
}
return nil, errors.New("not found")
}
// ContractCodeSize retrieves a particular contracts code's size.
func (db *VerkleDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
panic("need to merge #31 for this to work")
}
// TrieDB retrieves the low level trie database used for data storage.
func (db *VerkleDB) TrieDB() *trie.Database {
return db.db
}

View File

@ -76,6 +76,14 @@ func (it *NodeIterator) step() error {
// Initialize the iterator if we've just started
if it.stateIt == nil {
it.stateIt = it.state.trie.NodeIterator(nil)
// If the trie is a verkle trie, then the data and state
// are the same tree, and as a result both iterators are
// the same. This is a hack meant for both tree types to
// work.
if _, ok := it.state.trie.(*trie.VerkleTrie); ok {
it.dataIt = it.stateIt
}
}
// If we had data nodes previously, we surely have at least state nodes
if it.dataIt != nil {
@ -100,10 +108,11 @@ func (it *NodeIterator) step() error {
it.state, it.stateIt = nil, nil
return nil
}
// If the state trie node is an internal entry, leave as is
// If the state trie node is an internal entry, leave as is.
if !it.stateIt.Leaf() {
return nil
}
// Otherwise we've reached an account node, initiate data iteration
var account types.StateAccount
if err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob()), &account); err != nil {

View File

@ -89,7 +89,7 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize uint6
if headBlock == nil {
return nil, errors.New("Failed to load head block")
}
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, false)
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, false, false)
if err != nil {
return nil, err // The relevant snapshot(s) might not exist
}
@ -362,7 +362,7 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err
// - The state HEAD is rewound already because of multiple incomplete `prune-state`
// In this case, even the state HEAD is not exactly matched with snapshot, it
// still feasible to recover the pruning correctly.
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, true)
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, true, false)
if err != nil {
return err // The relevant snapshot(s) might not exist
}

View File

@ -24,6 +24,7 @@ import (
"sync"
"sync/atomic"
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
@ -183,7 +184,7 @@ type Tree struct {
// This case happens when the snapshot is 'ahead' of the state trie.
// - otherwise, the entire snapshot is considered invalid and will be recreated on
// a background thread.
func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, rebuild bool, recovery bool) (*Tree, error) {
func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, rebuild bool, recovery bool, useVerkle bool) (*Tree, error) {
// Create a new, empty snapshot tree
snap := &Tree{
diskdb: diskdb,
@ -202,6 +203,17 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm
}
if err != nil {
if rebuild {
if useVerkle {
snap.layers = map[common.Hash]snapshot{
root: &diskLayer{
diskdb: diskdb,
triedb: triedb,
root: root,
cache: fastcache.New(cache * 1024 * 1024),
},
}
return snap, nil
}
log.Warn("Failed to load snapshot, regenerating", "err", err)
snap.Rebuild(root)
return snap, nil

View File

@ -28,6 +28,8 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
var emptyCodeHash = crypto.Keccak256(nil)
@ -239,9 +241,13 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
if metrics.EnabledExpensive {
meter = &s.db.StorageReads
}
if enc, err = s.getTrie(db).TryGet(key.Bytes()); err != nil {
s.setError(err)
return common.Hash{}
if !s.db.trie.IsVerkle() {
if enc, err = s.getTrie(db).TryGet(key.Bytes()); err != nil {
s.setError(err)
return common.Hash{}
}
} else {
panic("verkle trees use the snapshot")
}
}
var value common.Hash
@ -332,7 +338,12 @@ func (s *stateObject) updateTrie(db Database) Trie {
// The snapshot storage map for the object
var storage map[common.Hash][]byte
// Insert all the pending updates into the trie
tr := s.getTrie(db)
var tr Trie
if s.db.trie.IsVerkle() {
tr = s.db.trie
} else {
tr = s.getTrie(db)
}
hasher := s.db.hasher
usedStorage := make([][]byte, 0, len(s.pendingStorage))
@ -345,12 +356,25 @@ func (s *stateObject) updateTrie(db Database) Trie {
var v []byte
if (value == common.Hash{}) {
s.setError(tr.TryDelete(key[:]))
if tr.IsVerkle() {
k := trieUtils.GetTreeKeyStorageSlot(s.address[:], new(uint256.Int).SetBytes(key[:]))
s.setError(tr.TryDelete(k))
//s.db.db.TrieDB().DiskDB().Delete(append(s.address[:], key[:]...))
} else {
s.setError(tr.TryDelete(key[:]))
}
s.db.StorageDeleted += 1
} else {
// Encoding []byte cannot fail, ok to ignore the error.
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
s.setError(tr.TryUpdate(key[:], v))
if !tr.IsVerkle() {
s.setError(tr.TryUpdate(key[:], v))
} else {
k := trieUtils.GetTreeKeyStorageSlot(s.address[:], new(uint256.Int).SetBytes(key[:]))
// Update the trie, with v as a value
s.setError(tr.TryUpdate(k, v))
}
s.db.StorageUpdated += 1
}
// If state snapshotting is active, cache the data til commit

View File

@ -18,6 +18,7 @@
package state
import (
"encoding/binary"
"errors"
"fmt"
"math/big"
@ -33,6 +34,8 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
type revision struct {
@ -99,6 +102,9 @@ type StateDB struct {
// Per-transaction access list
accessList *accessList
// Stateless locations for this block
stateless map[common.Hash]common.Hash
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal *journal
@ -144,6 +150,12 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
accessList: newAccessList(),
hasher: crypto.NewKeccakState(),
}
if sdb.snaps == nil && tr.IsVerkle() {
sdb.snaps, err = snapshot.New(db.TrieDB().DiskDB(), db.TrieDB(), 1, root, false, true, false, true)
if err != nil {
return nil, err
}
}
if sdb.snaps != nil {
if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
sdb.snapDestructs = make(map[common.Hash]struct{})
@ -411,6 +423,12 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) {
}
}
// SetStateless sets the vales recovered from the execution of a stateless
// block.
func (s *StateDB) SetStateless(leaves map[common.Hash]common.Hash) {
s.stateless = leaves
}
func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
@ -452,6 +470,46 @@ func (s *StateDB) Suicide(addr common.Address) bool {
// Setting, updating & deleting state object methods.
//
func (s *StateDB) updateStatelessStateObject(obj *stateObject) {
addr := obj.Address()
var (
ok bool
n common.Hash
v common.Hash
b common.Hash
cs common.Hash
ch common.Hash
)
versionKey := common.BytesToHash(trieUtils.GetTreeKeyVersion(addr[:]))
if v, ok = s.stateless[versionKey]; ok {
nonceKey := common.BytesToHash(trieUtils.GetTreeKeyNonce(addr[:]))
if n, ok = s.stateless[nonceKey]; ok {
balanceKey := common.BytesToHash(trieUtils.GetTreeKeyBalance(addr[:]))
if b, ok = s.stateless[balanceKey]; ok {
codeHashKey := common.BytesToHash(trieUtils.GetTreeKeyCodeKeccak(addr[:]))
if _, ok = s.stateless[codeHashKey]; ok {
v[0] = byte(0)
binary.BigEndian.PutUint64(n[:], obj.data.Nonce)
copy(ch[:], obj.data.CodeHash[:])
copy(b[:], obj.data.Balance.Bytes())
binary.BigEndian.PutUint64(cs[:], uint64(len(obj.code)))
// TODO(@gballet) stateless tree update
// i.e. perform a "delta" update on all
// commitments. go-verkle currently has
// no support for these.
}
}
}
}
if !ok {
s.setError(fmt.Errorf("updateStatelessStateObject (%x) missing", addr[:]))
}
}
// updateStateObject writes the given object to the trie.
func (s *StateDB) updateStateObject(obj *stateObject) {
// Track the amount of time wasted on updating the account from the trie
@ -460,8 +518,22 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
}
// Encode the account and update the account trie
addr := obj.Address()
// bypass the snapshot and writing to tree if in stateless mode
if s.stateless != nil {
s.updateStatelessStateObject(obj)
return
}
if err := s.trie.TryUpdateAccount(addr[:], &obj.data); err != nil {
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
s.setError(fmt.Errorf("updateStateObject (%x) error: %w", addr[:], err))
}
if len(obj.code) > 0 && s.trie.IsVerkle() {
cs := make([]byte, 32)
binary.BigEndian.PutUint64(cs, uint64(len(obj.code)))
if err := s.trie.TryUpdate(trieUtils.GetTreeKeyCodeSize(addr[:]), cs); err != nil {
s.setError(fmt.Errorf("updateStateObject (%x) error: %w", addr[:], err))
}
}
// If state snapshotting is active, cache the data til commit. Note, this
@ -473,16 +545,34 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
}
}
func (s *StateDB) deleteStatelessStateObject(obj *stateObject) {
// unsupported
panic("not currently supported")
}
// deleteStateObject removes the given object from the state trie.
func (s *StateDB) deleteStateObject(obj *stateObject) {
// Track the amount of time wasted on deleting the account from the trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
}
if s.stateless != nil {
s.deleteStatelessStateObject(obj)
return
}
// Delete the account from the trie
addr := obj.Address()
if err := s.trie.TryDelete(addr[:]); err != nil {
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
if !s.trie.IsVerkle() {
addr := obj.Address()
if err := s.trie.TryDelete(addr[:]); err != nil {
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
}
} else {
for i := byte(0); i <= 255; i++ {
if err := s.trie.TryDelete(trieUtils.GetTreeKeyAccountLeaf(obj.Address().Bytes(), i)); err != nil {
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", obj.Address(), err))
}
}
}
}
@ -496,6 +586,48 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
return nil
}
func (s *StateDB) getStatelessDeletedStateObject(addr common.Address) *stateObject {
// Check that it is present in the witness, if running
// in stateless execution mode.
chunk := trieUtils.GetTreeKeyNonce(addr[:])
nb, ok := s.stateless[common.BytesToHash(chunk)]
if !ok {
log.Error("Failed to decode state object", "addr", addr)
s.setError(fmt.Errorf("could not find nonce chunk in proof: %x", chunk))
// TODO(gballet) remove after debug, and check the issue is found
panic("inivalid chunk")
return nil
}
chunk = trieUtils.GetTreeKeyBalance(addr[:])
bb, ok := s.stateless[common.BytesToHash(chunk)]
if !ok {
log.Error("Failed to decode state object", "addr", addr)
s.setError(fmt.Errorf("could not find balance chunk in proof: %x", chunk))
// TODO(gballet) remove after debug, and check the issue is found
panic("inivalid chunk")
return nil
}
chunk = trieUtils.GetTreeKeyCodeKeccak(addr[:])
cb, ok := s.stateless[common.BytesToHash(chunk)]
if !ok {
// Assume that this is an externally-owned account, and that
// the code has not been accessed.
// TODO(gballet) write this down, just like deletions, so
// that an error can be triggered if trying to access the
// account code.
copy(cb[:], emptyCodeHash)
}
data := &types.StateAccount{
Nonce: binary.BigEndian.Uint64(nb[:8]),
Balance: big.NewInt(0).SetBytes(bb[:]),
CodeHash: cb[:],
}
// Insert into the live set
obj := newObject(s, addr, *data)
s.setStateObject(obj)
return obj
}
// getDeletedStateObject is similar to getStateObject, but instead of returning
// nil for a deleted state object, it returns the actual object with the deleted
// flag set. This is needed by the state journal to revert to the correct s-
@ -510,6 +642,10 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
data *types.StateAccount
err error
)
// if executing statelessly, bypass the snapshot and the db.
if s.stateless != nil {
return s.getStatelessDeletedStateObject(addr)
}
if s.snap != nil {
if metrics.EnabledExpensive {
defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now())
@ -532,6 +668,14 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
data.Root = emptyRoot
}
}
// NOTE: Do not touch the addresses here, kick the can down the
// road. That is because I don't want to change the interface
// to getDeletedStateObject at this stage, as the PR would then
// have a huge footprint.
// The alternative is to make accesses available via the state
// db instead of the evm. This requires a significant rewrite,
// that isn't currently warranted.
}
// If snapshot unavailable or reading from it failed, load from the database
if s.snap == nil || err != nil {
@ -708,6 +852,13 @@ func (s *StateDB) Copy() *StateDB {
// to not blow up if we ever decide copy it in the middle of a transaction
state.accessList = s.accessList.Copy()
if s.stateless != nil {
state.stateless = make(map[common.Hash]common.Hash, len(s.stateless))
for addr, value := range s.stateless {
state.stateless[addr] = value
}
}
// If there's a prefetcher running, make an inactive copy of it that can
// only access data but does not actively preload (since the user will not
// know that they need to explicitly terminate an active copy).
@ -845,7 +996,11 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// to pull useful data from disk.
for addr := range s.stateObjectsPending {
if obj := s.stateObjects[addr]; !obj.deleted {
obj.updateRoot(s.db)
if s.trie.IsVerkle() {
obj.updateTrie(s.db)
} else {
obj.updateRoot(s.db)
}
}
}
// Now we're about to start to write changes to the trie. The trie is so far
@ -896,6 +1051,20 @@ func (s *StateDB) clearJournalAndRefund() {
s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entires
}
// GetTrie returns the account trie.
func (s *StateDB) GetTrie() Trie {
return s.trie
}
func (s *StateDB) Cap(root common.Hash) error {
if s.snaps != nil {
return s.snaps.Cap(root, 0)
}
// pre-verkle path: noop if s.snaps hasn't been
// initialized.
return nil
}
// Commit writes the state to the underlying in-memory trie database.
func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
if s.dbErr != nil {
@ -909,17 +1078,27 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
codeWriter := s.db.TrieDB().DiskDB().NewBatch()
for addr := range s.stateObjectsDirty {
if obj := s.stateObjects[addr]; !obj.deleted {
// Write any contract code associated with the state object
if obj.code != nil && obj.dirtyCode {
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
obj.dirtyCode = false
}
// Write any storage changes in the state object to its storage trie
committed, err := obj.CommitTrie(s.db)
if err != nil {
return common.Hash{}, err
}
storageCommitted += committed
// Write any contract code associated with the state object
if obj.code != nil && obj.dirtyCode {
if s.trie.IsVerkle() {
if chunks, err := trie.ChunkifyCode(addr, obj.code); err == nil {
for i, chunk := range chunks {
s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(uint64(i))), chunk[:])
}
} else {
s.setError(err)
}
} else {
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
}
obj.dirtyCode = false
}
}
}
if len(s.stateObjectsDirty) > 0 {

View File

@ -704,7 +704,10 @@ func TestMissingTrieNodes(t *testing.T) {
memDb := rawdb.NewMemoryDatabase()
db := NewDatabase(memDb)
var root common.Hash
state, _ := New(common.Hash{}, db, nil)
state, err := New(common.Hash{}, db, nil)
if err != nil {
panic("nil stte")
}
addr := common.BytesToAddress([]byte("so"))
{
state.SetBalance(addr, big.NewInt(1))
@ -736,7 +739,7 @@ func TestMissingTrieNodes(t *testing.T) {
}
// Modify the state
state.SetBalance(addr, big.NewInt(2))
root, err := state.Commit(false)
root, err = state.Commit(false)
if err == nil {
t.Fatalf("expected error, got root :%x", root)
}

View File

@ -70,7 +70,10 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
state.updateStateObject(obj)
accounts = append(accounts, acc)
}
root, _ := state.Commit(false)
root, err := state.Commit(false)
if err != nil {
panic(err)
}
// Return the generated state
return db, root, accounts

View File

@ -56,7 +56,45 @@ 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) (types.Receipts, []*types.Log, *types.AccessWitness, uint64, error) {
var (
receipts types.Receipts
usedGas = new(uint64)
header = block.Header()
blockHash = block.Hash()
blockNumber = block.Number()
allLogs []*types.Log
gp = new(GasPool).AddGas(block.GasLimit())
accesses = types.NewAccessWitness()
)
// Mutate the block and state according to any hard-fork specs
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(statedb)
}
blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
if err != nil {
return nil, nil, accesses, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.Prepare(tx.Hash(), i)
receipt, acc, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
if err != nil {
return nil, nil, accesses, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
accesses.Merge(acc)
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles())
return receipts, allLogs, accesses, *usedGas, nil
}
func (p *StateProcessor) ProcessStateless(block *types.Block, statedb *state.StateDB, cfg vm.Config, leaves map[common.Hash]common.Hash) (types.Receipts, []*types.Log, uint64, error) {
var (
receipts types.Receipts
usedGas = new(uint64)
@ -71,6 +109,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
misc.ApplyDAOHardFork(statedb)
}
blockContext := NewEVMBlockContext(header, p.bc, nil)
blockContext.StatelessAccesses = leaves
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
@ -79,7 +118,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.Prepare(tx.Hash(), i)
receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
receipt, _, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
@ -92,7 +131,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return receipts, allLogs, *usedGas, nil
}
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, *types.AccessWitness, error) {
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb)
@ -100,7 +139,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
// Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, msg, gp)
if err != nil {
return nil, err
return nil, txContext.Accesses, err
}
// Update the state with pending changes.
@ -134,17 +173,17 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
receipt.BlockHash = blockHash
receipt.BlockNumber = blockNumber
receipt.TransactionIndex = uint(statedb.TxIndex())
return receipt, err
return receipt, txContext.Accesses, err
}
// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, *types.AccessWitness, error) {
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee)
if err != nil {
return nil, err
return nil, nil, err
}
// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author)

View File

@ -340,3 +340,55 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
// Assemble and return the final block for sealing
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
}
func TestProcessStateless(t *testing.T) {
var (
config = &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
Ethash: new(params.EthashConfig),
UseVerkle: true,
}
signer = types.LatestSigner(config)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
db = rawdb.NewMemoryDatabase()
gspec = &Genesis{
Config: config,
Alloc: GenesisAlloc{
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
Balance: big.NewInt(1000000000000000000), // 1 ether
Nonce: 0,
},
},
}
)
// Verkle trees use the snapshot, which must be enabled before the
// data is saved into the tree+database.
genesis := gspec.MustCommit(db)
blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
defer blockchain.Stop()
chain, _ := GenerateVerkleChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(_ int, gen *BlockGen) {
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{1, 2, 3}, big.NewInt(999), params.TxGas, big.NewInt(875000000), nil), signer, testKey)
gen.AddTx(tx)
tx, _ = types.SignTx(types.NewTransaction(1, common.Address{}, big.NewInt(999), params.TxGas, big.NewInt(875000000), nil), signer, testKey)
gen.AddTx(tx)
tx, _ = types.SignTx(types.NewTransaction(2, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), signer, testKey)
gen.AddTx(tx)
})
_, err := blockchain.InsertChain(chain)
if err != nil {
t.Fatalf("block imported with error: %v", err)
}
}

View File

@ -17,6 +17,7 @@
package core
import (
"encoding/binary"
"fmt"
"math"
"math/big"
@ -27,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
)
var emptyCodeHash = crypto.Keccak256Hash(nil)
@ -115,7 +117,7 @@ func (result *ExecutionResult) Revert() []byte {
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
@ -302,6 +304,27 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st.gas < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas)
}
if st.evm.TxContext.Accesses != nil {
if msg.To() != nil {
toBalance := trieUtils.GetTreeKeyBalance(msg.To().Bytes())
pre := st.state.GetBalance(*msg.To())
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(toBalance, pre.Bytes())
// NOTE: Nonce also needs to be charged, because it is needed for execution
// on the statless side.
var preTN [8]byte
fromNonce := trieUtils.GetTreeKeyNonce(msg.To().Bytes())
binary.BigEndian.PutUint64(preTN[:], st.state.GetNonce(*msg.To()))
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce, preTN[:])
}
fromBalance := trieUtils.GetTreeKeyBalance(msg.From().Bytes())
preFB := st.state.GetBalance(msg.From()).Bytes()
fromNonce := trieUtils.GetTreeKeyNonce(msg.From().Bytes())
var preFN [8]byte
binary.BigEndian.PutUint64(preFN[:], st.state.GetNonce(msg.From()))
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce, preFN[:])
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromBalance, preFB[:])
}
st.gas -= gas
// Check clause 6

View File

@ -17,6 +17,7 @@
package core
import (
"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"
@ -47,5 +48,7 @@ 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) (types.Receipts, []*types.Log, *types.AccessWitness, uint64, error)
ProcessStateless(block *types.Block, statedb *state.StateDB, cfg vm.Config, accesses map[common.Hash]common.Hash) (types.Receipts, []*types.Log, uint64, error)
}

View File

@ -0,0 +1,131 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
// AccessWitness lists the locations of the state that are being accessed
// during the production of a block.
// TODO(@gballet) this doesn't fully support deletions
type AccessWitness struct {
// Branches flags if a given branch has been loaded
Branches map[[31]byte]struct{}
// Chunks contains the initial value of each address
Chunks map[common.Hash][]byte
// The initial value isn't always available at the time an
// address is touched, this map references addresses that
// were touched but can not yet be put in Chunks.
Undefined map[common.Hash]struct{}
}
func NewAccessWitness() *AccessWitness {
return &AccessWitness{
Branches: make(map[[31]byte]struct{}),
Chunks: make(map[common.Hash][]byte),
Undefined: make(map[common.Hash]struct{}),
}
}
// TouchAddress adds any missing addr to the witness and returns respectively
// true if the stem or the stub weren't arleady present.
func (aw *AccessWitness) TouchAddress(addr, value []byte) (bool, bool) {
var (
stem [31]byte
newStem bool
newSelector bool
)
copy(stem[:], addr[:31])
// Check for the presence of the stem
if _, newStem := aw.Branches[stem]; !newStem {
aw.Branches[stem] = struct{}{}
}
// Check for the presence of the selector
if _, newSelector := aw.Chunks[common.BytesToHash(addr)]; !newSelector {
if value == nil {
aw.Undefined[common.BytesToHash(addr)] = struct{}{}
} else {
if _, ok := aw.Undefined[common.BytesToHash(addr)]; !ok {
delete(aw.Undefined, common.BytesToHash(addr))
}
aw.Chunks[common.BytesToHash(addr)] = value
}
}
return newStem, newSelector
}
// TouchAddressAndChargeGas checks if a location has already been touched in
// the current witness, and charge extra gas if that isn't the case. This is
// meant to only be called on a tx-context access witness (i.e. before it is
// merged), not a block-context witness: witness costs are charged per tx.
func (aw *AccessWitness) TouchAddressAndChargeGas(addr, value []byte) uint64 {
var gas uint64
nstem, nsel := aw.TouchAddress(addr, value)
if nstem {
gas += params.WitnessBranchCost
}
if nsel {
gas += params.WitnessChunkCost
}
return gas
}
// Merge is used to merge the witness that got generated during the execution
// of a tx, with the accumulation of witnesses that were generated during the
// execution of all the txs preceding this one in a given block.
func (aw *AccessWitness) Merge(other *AccessWitness) {
// catch unresolved touched addresses
if len(other.Undefined) != 0 {
panic("undefined value in witness")
}
for k := range other.Branches {
if _, ok := aw.Branches[k]; !ok {
aw.Branches[k] = struct{}{}
}
}
for k, chunk := range other.Chunks {
if _, ok := aw.Chunks[k]; !ok {
aw.Chunks[k] = chunk
}
}
}
// Key returns, predictably, the list of keys that were touched during the
// buildup of the access witness.
func (aw *AccessWitness) Keys() [][]byte {
keys := make([][]byte, 0, len(aw.Chunks))
for key := range aw.Chunks {
var k [32]byte
copy(k[:], key[:])
keys = append(keys, k[:])
}
return keys
}
func (aw *AccessWitness) KeyVals() map[common.Hash][]byte {
return aw.Chunks
}

View File

@ -86,6 +86,9 @@ type Header struct {
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
// The verkle proof is ignored in legacy headers
VerkleProof []byte `json:"verkleProof" rlp:"optional"`
/*
TODO (MariusVanDerWijden) Add this field once needed
// Random was added during the merge and contains the BeaconState randomness
@ -337,6 +340,10 @@ func (b *Block) SanityCheck() error {
return b.header.SanityCheck()
}
func (b *Block) SetVerkleProof(vp []byte) {
b.header.VerkleProof = vp
}
type writeCounter common.StorageSize
func (c *writeCounter) Write(b []byte) (int, error) {

View File

@ -93,12 +93,12 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool {
if OpCode(c.Code[udest]) != JUMPDEST {
return false
}
return c.isCode(udest)
return c.IsCode(udest)
}
// isCode returns true if the provided PC location is an actual opcode, as
// IsCode returns true if the provided PC location is an actual opcode, as
// opposed to a data-segment following a PUSHN operation.
func (c *Contract) isCode(udest uint64) bool {
func (c *Contract) IsCode(udest uint64) bool {
// Do we already have an analysis laying around?
if c.analysis != nil {
return c.analysis.codeSegment(udest)

View File

@ -17,13 +17,16 @@
package vm
import (
"encoding/binary"
"math/big"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
@ -75,6 +78,8 @@ type BlockContext struct {
Time *big.Int // Provides information for TIME
Difficulty *big.Int // Provides information for DIFFICULTY
BaseFee *big.Int // Provides information for BASEFEE
StatelessAccesses map[common.Hash]common.Hash
}
// TxContext provides the EVM with information about a transaction.
@ -83,6 +88,8 @@ type TxContext struct {
// Message information
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE
Accesses *types.AccessWitness
}
// EVM is the Ethereum Virtual Machine base object and provides
@ -120,6 +127,8 @@ type EVM struct {
// available gas is calculated in gasCall* according to the 63/64 rule and later
// applied in opCall*.
callGasTemp uint64
accesses map[common.Hash]common.Hash
}
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
@ -222,6 +231,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if len(code) == 0 {
ret, err = nil, nil // gas is unchanged
} else {
// Touch the account data
var data [32]byte
evm.Accesses.TouchAddress(utils.GetTreeKeyVersion(addr.Bytes()), data[:])
binary.BigEndian.PutUint64(data[:], evm.StateDB.GetNonce(addr))
evm.Accesses.TouchAddress(utils.GetTreeKeyNonce(addr[:]), data[:])
evm.Accesses.TouchAddress(utils.GetTreeKeyBalance(addr[:]), evm.StateDB.GetBalance(addr).Bytes())
binary.BigEndian.PutUint64(data[:], uint64(len(code)))
evm.Accesses.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), data[:])
evm.Accesses.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes())
addrCopy := addr
// If the account has no code, we can abort here
// The depth-check is already done, and precompiles handled above

View File

@ -22,6 +22,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
// memoryGasCost calculates the quadratic gas for memory expansion. It does so
@ -86,14 +88,102 @@ func memoryCopierGas(stackpos int) gasFunc {
}
}
func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
usedGas := uint64(0)
slot := stack.Back(0)
if evm.accesses != nil {
index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
return usedGas, nil
}
var (
gasCallDataCopy = memoryCopierGas(2)
gasCodeCopy = memoryCopierGas(2)
gasExtCodeCopy = memoryCopierGas(3)
gasReturnDataCopy = memoryCopierGas(2)
gasCallDataCopy = memoryCopierGas(2)
gasCodeCopyStateful = memoryCopierGas(2)
gasExtCodeCopyStateful = memoryCopierGas(3)
gasReturnDataCopy = memoryCopierGas(2)
)
func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var statelessGas uint64
if evm.accesses != nil {
var (
codeOffset = stack.Back(1)
length = stack.Back(2)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = 0xffffffffffffffff
}
uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow()
if overflow {
uint64CodeEnd = 0xffffffffffffffff
}
addr := contract.Address()
chunk := uint64CodeOffset / 31
endChunk := uint64CodeEnd / 31
// XXX uint64 overflow in condition check
for ; chunk < endChunk; chunk++ {
// TODO make a version of GetTreeKeyCodeChunk without the bigint
index := trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk))
statelessGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
}
usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize)
return usedGas + statelessGas, err
}
func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var statelessGas uint64
if evm.accesses != nil {
var (
a = stack.Back(0)
codeOffset = stack.Back(2)
length = stack.Back(3)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = 0xffffffffffffffff
}
uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow()
if overflow {
uint64CodeEnd = 0xffffffffffffffff
}
addr := common.Address(a.Bytes20())
chunk := uint64CodeOffset / 31
endChunk := uint64CodeEnd / 31
// XXX uint64 overflow in condition check
for ; chunk < endChunk; chunk++ {
// TODO(@gballet) make a version of GetTreeKeyCodeChunk without the bigint
index := trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk))
statelessGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
}
usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize)
return usedGas + statelessGas, err
}
func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
usedGas := uint64(0)
if evm.accesses != nil {
where := stack.Back(0)
addr := contract.Address()
index := trieUtils.GetTreeKeyStorageSlot(addr[:], where)
usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
return usedGas, nil
}
func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
// Apply the witness access costs, err is nil
accessGas, _ := gasSLoad(evm, contract, stack, mem, memorySize)
var (
y, x = stack.Back(1), stack.Back(0)
current = evm.StateDB.GetState(contract.Address(), x.Bytes32())
@ -109,14 +199,15 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
// 3. From a non-zero to a non-zero (CHANGE)
switch {
case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0
return params.SstoreSetGas, nil
return params.SstoreSetGas + accessGas, nil
case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0
evm.StateDB.AddRefund(params.SstoreRefundGas)
return params.SstoreClearGas, nil
return params.SstoreClearGas + accessGas, nil
default: // non 0 => non 0 (or 0 => 0)
return params.SstoreResetGas, nil
return params.SstoreResetGas + accessGas, nil
}
}
// The new gas metering is based on net gas costs (EIP-1283):
//
// 1. If current value equals new value (this is a no-op), 200 gas is deducted.
@ -331,6 +422,14 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
transfersValue = !stack.Back(2).IsZero()
address = common.Address(stack.Back(1).Bytes20())
)
if evm.accesses != nil {
// Charge witness costs
for i := trieUtils.VersionLeafKey; i <= trieUtils.CodeSizeLeafKey; i++ {
index := trieUtils.GetTreeKeyAccountLeaf(address[:], byte(i))
gas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
}
if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
gas += params.CallNewAccountGas

View File

@ -20,6 +20,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
)
@ -341,7 +342,12 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
slot := scope.Stack.peek()
slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
cs := uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))
if interpreter.evm.accesses != nil {
index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
interpreter.evm.TxContext.Accesses.TouchAddress(index, uint256.NewInt(cs).Bytes())
}
slot.SetUint64(cs)
return nil, nil
}
@ -362,12 +368,64 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
if overflow {
uint64CodeOffset = 0xffffffffffffffff
}
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow()
if overflow {
uint64CodeEnd = 0xffffffffffffffff
}
if interpreter.evm.accesses != nil {
copyCodeFromAccesses(scope.Contract.Address(), uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope)
} else {
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm)
}
return nil, nil
}
// Helper function to touch every chunk in a code range
func touchEachChunks(start, end uint64, code []byte, contract *Contract, evm *EVM) {
for chunk := start / 31; chunk <= end/31 && chunk <= uint64(len(code))/31; chunk++ {
index := trieUtils.GetTreeKeyCodeChunk(contract.Address().Bytes(), uint256.NewInt(chunk))
count := uint64(0)
// Look for the first code byte (i.e. no pushdata)
for ; count < 31 && !contract.IsCode(chunk*31+count); count++ {
}
var value [32]byte
value[0] = byte(count)
end := (chunk + 1) * 31
if end > uint64(len(code)) {
end = uint64(len(code))
}
copy(value[1:], code[chunk*31:end])
evm.Accesses.TouchAddress(index, value[:])
}
}
// copyCodeFromAccesses perform codecopy from the witness, not from the db.
func copyCodeFromAccesses(addr common.Address, codeOffset, codeEnd, memOffset uint64, in *EVMInterpreter, scope *ScopeContext) {
chunk := codeOffset / 31
endChunk := codeEnd / 31
start := codeOffset % 31 // start inside the first code chunk
offset := uint64(0) // memory offset to write to
// XXX uint64 overflow in condition check
for end := uint64(31); chunk < endChunk; chunk, start = chunk+1, 0 {
// case of the last chunk: figure out how many bytes need to
// be extracted from the last chunk.
if chunk+1 == endChunk {
end = codeEnd % 31
}
// TODO make a version of GetTreeKeyCodeChunk without the bigint
index := common.BytesToHash(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk)))
h := in.evm.accesses[index]
//in.evm.Accesses.TouchAddress(index.Bytes(), h[1+start:1+end])
scope.Memory.Set(memOffset+offset, end-start, h[1+start:end])
offset += 31 - start
}
}
func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
stack = scope.Stack
@ -380,9 +438,19 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
if overflow {
uint64CodeOffset = 0xffffffffffffffff
}
uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow()
if overflow {
uint64CodeEnd = 0xffffffffffffffff
}
addr := common.Address(a.Bytes20())
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
if interpreter.evm.accesses != nil {
copyCodeFromAccesses(addr, uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope)
} else {
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm)
}
return nil, nil
}
@ -510,6 +578,10 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
hash := common.Hash(loc.Bytes32())
val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash)
loc.SetBytes(val.Bytes())
// Get the initial value as it might not be present
index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc)
interpreter.evm.TxContext.Accesses.TouchAddress(index, val.Bytes())
return nil, nil
}
@ -834,6 +906,25 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
*pc += 1
if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
// touch next chunk if PUSH1 is at the boundary. if so, *pc has
// advanced past this boundary.
if *pc%31 == 0 {
// touch push data by adding the last byte of the pushdata
var value [32]byte
chunk := *pc / 31
count := uint64(0)
// Look for the first code byte (i.e. no pushdata)
for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ {
}
value[0] = byte(count)
endMin := (chunk + 1) * 31
if endMin > uint64(len(scope.Contract.Code)) {
endMin = uint64(len(scope.Contract.Code))
}
copy(value[1:], scope.Contract.Code[chunk*31:endMin])
index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
} else {
scope.Stack.push(integer.Clear())
}
@ -859,6 +950,33 @@ func makePush(size uint64, pushByteSize int) executionFunc {
scope.Stack.push(integer.SetBytes(common.RightPadBytes(
scope.Contract.Code[startMin:endMin], pushByteSize)))
// touch push data by adding the last byte of the pushdata
var value [32]byte
chunk := uint64(endMin-1) / 31
count := uint64(0)
// Look for the first code byte (i.e. no pushdata)
for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ {
}
value[0] = byte(count)
copy(value[1:], scope.Contract.Code[chunk*31:endMin])
index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
// in the case of PUSH32, the end data might be two chunks away,
// so also get the middle chunk.
if pushByteSize == 32 {
chunk = uint64(endMin-2) / 31
count = uint64(0)
// Look for the first code byte (i.e. no pushdata)
for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ {
}
value[0] = byte(count)
copy(value[1:], scope.Contract.Code[chunk*31:(chunk+1)*31])
index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
*pc += size
return nil, nil
}

View File

@ -17,12 +17,15 @@
package vm
import (
"errors"
"hash"
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/log"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
// Config are the configuration options for the Interpreter
@ -191,9 +194,53 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
logged, pcCopy, gasCopy = false, pc, contract.Gas
}
// if the PC ends up in a new "page" of verkleized code, charge the
// associated witness costs.
inWitness := false
var codePage common.Hash
if in.evm.ChainConfig().UseVerkle {
index := trieUtils.GetTreeKeyCodeChunk(contract.Address().Bytes(), uint256.NewInt(pc/31))
var value [32]byte
if in.evm.accesses != nil {
codePage, inWitness = in.evm.accesses[common.BytesToHash(index)]
// Return an error if we're in stateless mode
// and the code isn't in the witness. It means
// that if code is read beyond the actual code
// size, pages of 0s need to be added to the
// witness.
if !inWitness {
return nil, errors.New("code chunk missing from proof")
}
copy(value[:], codePage[:])
} else {
// Calculate the chunk
chunk := pc / 31
end := (chunk + 1) * 31
if end >= uint64(len(contract.Code)) {
end = uint64(len(contract.Code))
}
count := uint64(0)
// Look for the first code byte (i.e. no pushdata)
for ; chunk*31+count < end && count < 31 && !contract.IsCode(chunk*31+count); count++ {
}
value[0] = byte(count)
copy(value[1:], contract.Code[chunk*31:end])
}
contract.Gas -= in.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, value[:])
}
if inWitness {
// Get the op from the tree, skipping the header byte
op = OpCode(codePage[1+pc%31])
} else {
// If we are in witness mode, then raise an error
op = contract.GetOp(pc)
}
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
op = contract.GetOp(pc)
operation := in.cfg.JumpTable[op]
if operation == nil {
return nil, &ErrInvalidOpCode{opcode: op}

View File

@ -433,6 +433,7 @@ func newFrontierInstructionSet() JumpTable {
EXTCODESIZE: {
execute: opExtCodeSize,
constantGas: params.ExtcodeSizeGasFrontier,
dynamicGas: gasExtCodeSize,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
},
@ -513,6 +514,7 @@ func newFrontierInstructionSet() JumpTable {
SLOAD: {
execute: opSload,
constantGas: params.SloadGasFrontier,
dynamicGas: gasSLoad,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
},