Compare commits
33 Commits
master
...
verkle/fix
Author | SHA1 | Date | |
---|---|---|---|
|
9c67d521ac | ||
|
15b353d7b4 | ||
|
5beac51808 | ||
|
99604b0699 | ||
|
952be80177 | ||
|
d761880fd2 | ||
|
4428439fdf | ||
|
99f3c92361 | ||
|
c87a6d904f | ||
|
e16e9cc84b | ||
|
f215cc0791 | ||
|
99ebf767b9 | ||
|
6af78cba9e | ||
|
fe75603d0b | ||
|
5bac5b3262 | ||
|
fa753db9e8 | ||
|
86bdc3fb39 | ||
|
909049c5fe | ||
|
7360d168c8 | ||
|
361a328cb7 | ||
|
41c2f754cc | ||
|
7cb1add36a | ||
|
03dbc0a210 | ||
|
6d40e11fe3 | ||
|
5ca990184f | ||
|
15d98607f3 | ||
|
ef08e51e40 | ||
|
e1144745a7 | ||
|
bc06d2c740 | ||
|
97a79f50e8 | ||
|
9f9c03a94c | ||
|
719bf47354 | ||
|
162780515a |
45
.circleci/config.yml
Normal file
45
.circleci/config.yml
Normal file
@ -0,0 +1,45 @@
|
||||
# Use the latest 2.1 version of CircleCI pipeline process engine.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference
|
||||
version: 2.1
|
||||
|
||||
# Define a job to be invoked later in a workflow.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
||||
jobs:
|
||||
build:
|
||||
working_directory: ~/repo
|
||||
# Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
|
||||
docker:
|
||||
- image: circleci/golang:1.16.10
|
||||
# Add steps to the job
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-mod-v4-{{ checksum "go.sum" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: go mod download
|
||||
- save_cache:
|
||||
key: go-mod-v4-{{ checksum "go.sum" }}
|
||||
paths:
|
||||
- "/go/pkg/mod"
|
||||
#- run:
|
||||
# name: Run linter
|
||||
# command: |
|
||||
# go run build/ci.go lint
|
||||
- run:
|
||||
name: Run tests
|
||||
command: |
|
||||
go run build/ci.go test -coverage
|
||||
- store_test_results:
|
||||
path: /tmp/test-reports
|
||||
|
||||
# Invoke jobs via workflows
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
|
||||
workflows:
|
||||
sample: # This is the name of the workflow, feel free to change it to better match your workflow.
|
||||
# Inside the workflow, you define the jobs you want to run.
|
||||
jobs:
|
||||
- build
|
@ -220,7 +220,7 @@ func verifyState(ctx *cli.Context) error {
|
||||
log.Error("Failed to load head block")
|
||||
return errors.New("no head block")
|
||||
}
|
||||
snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false)
|
||||
snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false, false)
|
||||
if err != nil {
|
||||
log.Error("Failed to open snapshot tree", "err", err)
|
||||
return err
|
||||
@ -472,7 +472,7 @@ func dumpState(ctx *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, root, false, false, false)
|
||||
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, root, false, false, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@ -660,10 +661,19 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
|
||||
r.Sub(r, header.Number)
|
||||
r.Mul(r, blockReward)
|
||||
r.Div(r, big8)
|
||||
|
||||
if state.Witness() != nil {
|
||||
uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes())
|
||||
state.Witness().TouchAddress(uncleCoinbase, state.GetBalance(uncle.Coinbase).Bytes())
|
||||
}
|
||||
state.AddBalance(uncle.Coinbase, r)
|
||||
|
||||
r.Div(blockReward, big32)
|
||||
reward.Add(reward, r)
|
||||
}
|
||||
if config.IsCancun(header.Number) {
|
||||
coinbase := utils.GetTreeKeyBalance(header.Coinbase.Bytes())
|
||||
state.Witness().TouchAddress(coinbase, state.GetBalance(header.Coinbase).Bytes())
|
||||
}
|
||||
state.AddBalance(header.Coinbase, reward)
|
||||
}
|
||||
|
@ -226,15 +226,10 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
futureBlocks, _ := lru.New(maxFutureBlocks)
|
||||
|
||||
bc := &BlockChain{
|
||||
chainConfig: chainConfig,
|
||||
cacheConfig: cacheConfig,
|
||||
db: db,
|
||||
triegc: prque.New(nil),
|
||||
stateCache: state.NewDatabaseWithConfig(db, &trie.Config{
|
||||
Cache: cacheConfig.TrieCleanLimit,
|
||||
Journal: cacheConfig.TrieCleanJournal,
|
||||
Preimages: cacheConfig.Preimages,
|
||||
}),
|
||||
chainConfig: chainConfig,
|
||||
cacheConfig: cacheConfig,
|
||||
db: db,
|
||||
triegc: prque.New(nil),
|
||||
quit: make(chan struct{}),
|
||||
chainmu: syncx.NewClosableMutex(),
|
||||
bodyCache: bodyCache,
|
||||
@ -283,6 +278,13 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
|
||||
// Make sure the state associated with the block is available
|
||||
head := bc.CurrentBlock()
|
||||
bc.stateCache = state.NewDatabaseWithConfig(db, &trie.Config{
|
||||
Cache: cacheConfig.TrieCleanLimit,
|
||||
Journal: cacheConfig.TrieCleanJournal,
|
||||
Preimages: cacheConfig.Preimages,
|
||||
UseVerkle: chainConfig.IsCancun(head.Header().Number),
|
||||
})
|
||||
|
||||
if _, err := state.New(head.Root(), bc.stateCache, bc.snaps); err != nil {
|
||||
// Head state is missing, before the state recovery, find out the
|
||||
// disk layer point of snapshot(if it's enabled). Make sure the
|
||||
@ -375,7 +377,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, chainConfig.IsCancun(head.Header().Number))
|
||||
}
|
||||
|
||||
// Start future block processor.
|
||||
@ -1592,7 +1594,12 @@ 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
|
||||
)
|
||||
receipts, logs, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig)
|
||||
if err != nil {
|
||||
bc.reportBlock(block, receipts, err)
|
||||
atomic.StoreUint32(&followupInterrupt, 1)
|
||||
|
@ -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.
|
||||
@ -284,6 +285,91 @@ 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}
|
||||
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 := statedb.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()
|
||||
p, err := vtr.ProveAndSerialize(keys, statedb.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 {
|
||||
|
@ -162,6 +162,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
|
||||
if genesis != nil && genesis.Config == nil {
|
||||
return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
|
||||
}
|
||||
|
||||
// Just commit the new block if there is no stored genesis block.
|
||||
stored := rawdb.ReadCanonicalHash(db, 0)
|
||||
if (stored == common.Hash{}) {
|
||||
@ -177,13 +178,29 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
|
||||
}
|
||||
return genesis.Config, block.Hash(), nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if genesis == nil {
|
||||
genesis = DefaultGenesisBlock()
|
||||
|
||||
var trieCfg *trie.Config
|
||||
|
||||
if genesis == nil {
|
||||
storedcfg := rawdb.ReadChainConfig(db, stored)
|
||||
if storedcfg == nil {
|
||||
panic("this should never be reached: if genesis is nil, the config is already present or 'geth init' is being called which created it (in the code above, which means genesis != nil)")
|
||||
}
|
||||
|
||||
if storedcfg.CancunBlock != nil {
|
||||
if storedcfg.CancunBlock.Cmp(big.NewInt(0)) != 0 {
|
||||
panic("cancun block must be 0")
|
||||
}
|
||||
|
||||
trieCfg = &trie.Config{UseVerkle: storedcfg.IsCancun(big.NewInt(header.Number.Int64()))}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, trieCfg), nil); err != nil {
|
||||
// Ensure the stored genesis matches with the given one.
|
||||
hash := genesis.ToBlock(nil).Hash()
|
||||
if hash != stored {
|
||||
@ -264,7 +281,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.IsCancun(big.NewInt(int64(g.Number)))}
|
||||
}
|
||||
statedb, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(db, trieCfg), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -306,6 +327,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 +381,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{
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
|
||||
|
||||
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, value[:]))
|
||||
}
|
||||
s.db.StorageUpdated += 1
|
||||
}
|
||||
// If state snapshotting is active, cache the data til commit
|
||||
|
@ -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,8 @@ type StateDB struct {
|
||||
// Per-transaction access list
|
||||
accessList *accessList
|
||||
|
||||
witness *types.AccessWitness
|
||||
|
||||
// Journal of state modifications. This is the backbone of
|
||||
// Snapshot and RevertToSnapshot.
|
||||
journal *journal
|
||||
@ -144,6 +149,15 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
||||
accessList: newAccessList(),
|
||||
hasher: crypto.NewKeccakState(),
|
||||
}
|
||||
if tr.IsVerkle() {
|
||||
sdb.witness = types.NewAccessWitness()
|
||||
if sdb.snaps == nil {
|
||||
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{})
|
||||
@ -154,6 +168,14 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
||||
return sdb, nil
|
||||
}
|
||||
|
||||
func (s *StateDB) Witness() *types.AccessWitness {
|
||||
return s.witness
|
||||
}
|
||||
|
||||
func (s *StateDB) SetWitness(aw *types.AccessWitness) {
|
||||
s.witness = aw
|
||||
}
|
||||
|
||||
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
||||
// state trie concurrently while the state is mutated so that when we reach the
|
||||
// commit phase, most of the needed data is already hot.
|
||||
@ -162,7 +184,7 @@ func (s *StateDB) StartPrefetcher(namespace string) {
|
||||
s.prefetcher.close()
|
||||
s.prefetcher = nil
|
||||
}
|
||||
if s.snap != nil {
|
||||
if s.snap != nil && !s.trie.IsVerkle() {
|
||||
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
|
||||
}
|
||||
}
|
||||
@ -266,6 +288,24 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int {
|
||||
return common.Big0
|
||||
}
|
||||
|
||||
func (s *StateDB) GetNonceLittleEndian(address common.Address) []byte {
|
||||
var nonceBytes [8]byte
|
||||
binary.LittleEndian.PutUint64(nonceBytes[:], s.GetNonce(address))
|
||||
return nonceBytes[:]
|
||||
}
|
||||
|
||||
func (s *StateDB) GetBalanceLittleEndian(address common.Address) []byte {
|
||||
var paddedBalance [32]byte
|
||||
balanceBytes := s.GetBalance(address).Bytes()
|
||||
// swap to little-endian
|
||||
for i, j := 0, len(balanceBytes)-1; i < j; i, j = i+1, j-1 {
|
||||
balanceBytes[i], balanceBytes[j] = balanceBytes[j], balanceBytes[i]
|
||||
}
|
||||
|
||||
copy(paddedBalance[:len(balanceBytes)], balanceBytes)
|
||||
return paddedBalance[:len(balanceBytes)]
|
||||
}
|
||||
|
||||
func (s *StateDB) GetNonce(addr common.Address) uint64 {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
@ -460,8 +500,33 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
|
||||
}
|
||||
// Encode the account and update the account trie
|
||||
addr := obj.Address()
|
||||
|
||||
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 s.trie.IsVerkle() {
|
||||
if len(obj.code) > 0 {
|
||||
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 obj.dirtyCode {
|
||||
if chunks, err := trie.ChunkifyCode(addr, obj.code); err == nil {
|
||||
for i := range chunks {
|
||||
s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(uint64(i))), chunks[i][:])
|
||||
}
|
||||
} else {
|
||||
s.setError(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cs := []byte{0}
|
||||
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
|
||||
@ -479,10 +544,19 @@ func (s *StateDB) deleteStateObject(obj *stateObject) {
|
||||
if metrics.EnabledExpensive {
|
||||
defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -532,6 +606,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 {
|
||||
@ -659,6 +741,9 @@ func (s *StateDB) Copy() *StateDB {
|
||||
journal: newJournal(),
|
||||
hasher: crypto.NewKeccakState(),
|
||||
}
|
||||
if s.witness != nil {
|
||||
state.witness = s.witness.Copy()
|
||||
}
|
||||
// Copy the dirty states, logs, and preimages
|
||||
for addr := range s.journal.dirties {
|
||||
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527),
|
||||
@ -845,7 +930,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 +985,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 +1012,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 := range chunks {
|
||||
s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(uint64(i))), chunks[i][:])
|
||||
}
|
||||
} else {
|
||||
s.setError(err)
|
||||
}
|
||||
} else {
|
||||
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
|
||||
}
|
||||
obj.dirtyCode = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(s.stateObjectsDirty) > 0 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -95,6 +95,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||
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) {
|
||||
// Create a new context to be used in the EVM environment.
|
||||
txContext := NewEVMTxContext(msg)
|
||||
if config.IsCancun(blockNumber) {
|
||||
txContext.Accesses = types.NewAccessWitness()
|
||||
}
|
||||
evm.Reset(txContext, statedb)
|
||||
|
||||
// Apply the transaction to the current state (included in the env).
|
||||
@ -128,6 +131,10 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
|
||||
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
|
||||
}
|
||||
|
||||
if config.IsCancun(blockNumber) {
|
||||
statedb.Witness().Merge(txContext.Accesses)
|
||||
}
|
||||
|
||||
// Set the receipt logs and create the bloom filter.
|
||||
receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash)
|
||||
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
|
||||
|
@ -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 = ¶ms.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),
|
||||
CancunBlock: big.NewInt(0),
|
||||
}
|
||||
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, 2, 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)
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
@ -115,7 +116,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 {
|
||||
@ -259,6 +260,19 @@ func (st *StateTransition) preCheck() error {
|
||||
return st.buyGas()
|
||||
}
|
||||
|
||||
// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool
|
||||
// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false
|
||||
// otherwise, do the subtraction setting the result in gasPool and return true
|
||||
func tryConsumeGas(gasPool *uint64, gas uint64) bool {
|
||||
if *gasPool < gas {
|
||||
*gasPool = 0
|
||||
return false
|
||||
}
|
||||
|
||||
*gasPool -= gas
|
||||
return true
|
||||
}
|
||||
|
||||
// TransitionDb will transition the state by applying the current message and
|
||||
// returning the evm execution result with following fields.
|
||||
//
|
||||
@ -302,6 +316,35 @@ 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.ChainConfig().IsCancun(st.evm.Context.BlockNumber) {
|
||||
var targetBalance, targetNonce, targetCodeSize, targetCodeKeccak, originBalance, originNonce []byte
|
||||
|
||||
targetAddr := msg.To()
|
||||
originAddr := msg.From()
|
||||
|
||||
statelessGasOrigin := st.evm.Accesses.TouchTxOriginAndChargeGas(originAddr.Bytes())
|
||||
if !tryConsumeGas(&st.gas, statelessGasOrigin) {
|
||||
return nil, fmt.Errorf("insufficient gas to cover witness access costs")
|
||||
}
|
||||
originBalance = st.evm.StateDB.GetBalanceLittleEndian(originAddr)
|
||||
originNonce = st.evm.StateDB.GetNonceLittleEndian(originAddr)
|
||||
st.evm.Accesses.SetTxTouchedLeaves(originAddr.Bytes(), originBalance, originNonce)
|
||||
|
||||
if msg.To() != nil {
|
||||
statelessGasDest := st.evm.Accesses.TouchTxExistingAndChargeGas(targetAddr.Bytes())
|
||||
if !tryConsumeGas(&st.gas, statelessGasDest) {
|
||||
return nil, fmt.Errorf("insufficient gas to cover witness access costs")
|
||||
}
|
||||
targetBalance = st.evm.StateDB.GetBalanceLittleEndian(*targetAddr)
|
||||
targetNonce = st.evm.StateDB.GetNonceLittleEndian(*targetAddr)
|
||||
targetCodeKeccak = st.evm.StateDB.GetCodeHash(*targetAddr).Bytes()
|
||||
|
||||
codeSize := uint64(st.evm.StateDB.GetCodeSize(*targetAddr))
|
||||
var codeSizeBytes [32]byte
|
||||
binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize)
|
||||
st.evm.Accesses.SetTxExistingTouchedLeaves(targetAddr.Bytes(), targetBalance, targetNonce, targetCodeSize, targetCodeKeccak)
|
||||
}
|
||||
}
|
||||
st.gas -= gas
|
||||
|
||||
// Check clause 6
|
||||
|
264
core/types/access_witness.go
Normal file
264
core/types/access_witness.go
Normal file
@ -0,0 +1,264 @@
|
||||
// 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"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
)
|
||||
|
||||
// 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{}),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO TouchAndCharge + SetLeafValue* does redundant calls to GetTreeKey*
|
||||
|
||||
func (aw *AccessWitness) TouchAndChargeProofOfAbsence(addr []byte) uint64 {
|
||||
var gas uint64
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr[:]), nil)
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(addr[:]), nil)
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(addr[:]), nil)
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(addr[:]), nil)
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(addr[:]), nil)
|
||||
return gas
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 {
|
||||
var gas uint64
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr[:]), nil)
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(addr[:]), nil)
|
||||
return gas
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) SetLeafValuesMessageCall(addr, codeSize []byte) {
|
||||
var data [32]byte
|
||||
aw.TouchAddress(utils.GetTreeKeyVersion(addr[:]), data[:])
|
||||
aw.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), codeSize[:])
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 {
|
||||
var gas uint64
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(callerAddr[:]), nil)
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(targetAddr[:]), nil)
|
||||
return gas
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) SetLeafValuesValueTransfer(callerAddr, targetAddr, callerBalance, targetBalance []byte) {
|
||||
aw.TouchAddress(utils.GetTreeKeyBalance(callerAddr[:]), callerBalance)
|
||||
aw.TouchAddress(utils.GetTreeKeyBalance(targetAddr[:]), targetBalance)
|
||||
}
|
||||
|
||||
// TouchAndChargeContractCreateInit charges access costs to initiate
|
||||
// a contract creation
|
||||
func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte) uint64 {
|
||||
var gas uint64
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr[:]), nil)
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(addr[:]), nil)
|
||||
return gas
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) SetLeafValuesContractCreateInit(addr, nonce []byte) {
|
||||
var version [32]byte
|
||||
aw.TouchAddress(utils.GetTreeKeyVersion(addr[:]), version[:])
|
||||
aw.TouchAddress(utils.GetTreeKeyNonce(addr[:]), nonce)
|
||||
}
|
||||
|
||||
// TouchAndChargeContractCreateCompleted charges access access costs after
|
||||
// the completion of a contract creation to populate the created account in
|
||||
// the tree
|
||||
func (aw *AccessWitness) TouchAndChargeContractCreateCompleted(addr []byte, withValue bool) uint64 {
|
||||
var gas uint64
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr[:]), nil)
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(addr[:]), nil)
|
||||
if withValue {
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(addr[:]), nil)
|
||||
}
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(addr[:]), nil)
|
||||
gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(addr[:]), nil)
|
||||
return gas
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) SetLeafValuesContractCreateCompleted(addr, codeSize, codeKeccak []byte) {
|
||||
aw.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), codeSize)
|
||||
aw.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), codeKeccak)
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) TouchTxAndChargeGas(originAddr, targetAddr []byte) uint64 {
|
||||
var gasUsed uint64
|
||||
var version [32]byte
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(originAddr[:]), version[:])
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(originAddr[:]), nil)
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(originAddr[:]), nil)
|
||||
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(targetAddr[:]), version[:])
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(targetAddr[:]), nil)
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(targetAddr[:]), nil)
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(targetAddr[:]), nil)
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(targetAddr[:]), nil)
|
||||
return gasUsed
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) TouchTxOriginAndChargeGas(originAddr []byte) uint64 {
|
||||
var gasUsed uint64
|
||||
var version [32]byte
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(originAddr[:]), version[:])
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(originAddr[:]), nil)
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(originAddr[:]), nil)
|
||||
return gasUsed
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) TouchTxExistingAndChargeGas(targetAddr []byte) uint64 {
|
||||
var gasUsed uint64
|
||||
var version [32]byte
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(targetAddr[:]), version[:])
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(targetAddr[:]), nil)
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(targetAddr[:]), nil)
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(targetAddr[:]), nil)
|
||||
gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(targetAddr[:]), nil)
|
||||
return gasUsed
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) SetTxTouchedLeaves(originAddr, originBalance, originNonce []byte) {
|
||||
aw.TouchAddress(utils.GetTreeKeyBalance(originAddr[:]), originBalance)
|
||||
aw.TouchAddress(utils.GetTreeKeyNonce(originAddr[:]), originNonce)
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) SetTxExistingTouchedLeaves(targetAddr, targetBalance, targetNonce, targetCodeSize, targetCodeHash []byte) {
|
||||
aw.TouchAddress(utils.GetTreeKeyBalance(targetAddr[:]), targetBalance)
|
||||
aw.TouchAddress(utils.GetTreeKeyNonce(targetAddr[:]), targetNonce)
|
||||
aw.TouchAddress(utils.GetTreeKeyCodeSize(targetAddr[:]), targetCodeSize)
|
||||
aw.TouchAddress(utils.GetTreeKeyCodeKeccak(targetAddr[:]), targetCodeHash)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
for k := range other.Undefined {
|
||||
if _, ok := aw.Undefined[k]; !ok {
|
||||
aw.Undefined[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (aw *AccessWitness) Copy() *AccessWitness {
|
||||
naw := &AccessWitness{
|
||||
Branches: make(map[[31]byte]struct{}),
|
||||
Chunks: make(map[common.Hash][]byte),
|
||||
Undefined: make(map[common.Hash]struct{}),
|
||||
}
|
||||
|
||||
naw.Merge(aw)
|
||||
|
||||
return naw
|
||||
}
|
@ -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) {
|
||||
|
@ -61,8 +61,8 @@ func (bits bitvec) set16(pos uint64) {
|
||||
bits[pos/8+2] = ^a
|
||||
}
|
||||
|
||||
// codeSegment checks if the position is in a code segment.
|
||||
func (bits *bitvec) codeSegment(pos uint64) bool {
|
||||
// IsCode checks if the position is in a code segment.
|
||||
func (bits *bitvec) IsCode(pos uint64) bool {
|
||||
return ((*bits)[pos/8] & (0x80 >> (pos % 8))) == 0
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte {
|
||||
return common.RightPadBytes(data[start:end], int(size))
|
||||
}
|
||||
|
||||
func getDataAndAdjustedBounds(data []byte, start uint64, size uint64) (codeCopyPadded []byte, actualStart uint64, sizeNonPadded uint64) {
|
||||
length := uint64(len(data))
|
||||
if start > length {
|
||||
start = length
|
||||
}
|
||||
end := start + size
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
return common.RightPadBytes(data[start:end], int(size)), start, end
|
||||
}
|
||||
|
||||
// toWordSize returns the ceiled word size required for memory expansion.
|
||||
func toWordSize(size uint64) uint64 {
|
||||
if size > math.MaxUint64-31 {
|
||||
|
@ -28,6 +28,13 @@ type ContractRef interface {
|
||||
Address() common.Address
|
||||
}
|
||||
|
||||
// AnalyzedContract is an interface for a piece of contract
|
||||
// code that has undegone jumpdest analysis, and whose bytecode
|
||||
// can be queried to determine if it is "contract code"
|
||||
type AnalyzedContract interface {
|
||||
IsCode(dest uint64) bool
|
||||
}
|
||||
|
||||
// AccountRef implements ContractRef.
|
||||
//
|
||||
// Account references are used during EVM initialisation and
|
||||
@ -58,6 +65,9 @@ type Contract struct {
|
||||
CodeAddr *common.Address
|
||||
Input []byte
|
||||
|
||||
// is the execution frame represented by this object a contract deployment
|
||||
IsDeployment bool
|
||||
|
||||
Gas uint64
|
||||
value *big.Int
|
||||
}
|
||||
@ -93,15 +103,15 @@ 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)
|
||||
return c.analysis.IsCode(udest)
|
||||
}
|
||||
// Do we have a contract hash already?
|
||||
// If we do have a hash, that means it's a 'regular' contract. For regular
|
||||
@ -117,7 +127,7 @@ func (c *Contract) isCode(udest uint64) bool {
|
||||
}
|
||||
// Also stash it in current contract for faster access
|
||||
c.analysis = analysis
|
||||
return analysis.codeSegment(udest)
|
||||
return analysis.IsCode(udest)
|
||||
}
|
||||
// We don't have the code hash, most likely a piece of initcode not already
|
||||
// in state trie. In that case, we do an analysis, and save it locally, so
|
||||
@ -126,7 +136,7 @@ func (c *Contract) isCode(udest uint64) bool {
|
||||
if c.analysis == nil {
|
||||
c.analysis = codeBitmap(c.Code)
|
||||
}
|
||||
return c.analysis.codeSegment(udest)
|
||||
return c.analysis.IsCode(udest)
|
||||
}
|
||||
|
||||
// AsDelegate sets the contract to be a delegate call and returns the current
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
@ -83,6 +86,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
|
||||
@ -125,6 +130,9 @@ type EVM struct {
|
||||
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
|
||||
// only ever be used *once*.
|
||||
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM {
|
||||
if txCtx.Accesses == nil && chainConfig.IsCancun(blockCtx.BlockNumber) {
|
||||
txCtx.Accesses = types.NewAccessWitness()
|
||||
}
|
||||
evm := &EVM{
|
||||
Context: blockCtx,
|
||||
TxContext: txCtx,
|
||||
@ -160,6 +168,19 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
|
||||
return evm.interpreter
|
||||
}
|
||||
|
||||
// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool
|
||||
// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false
|
||||
// otherwise, do the subtraction setting the result in gasPool and return true
|
||||
func tryConsumeGas(gasPool *uint64, gas uint64) bool {
|
||||
if *gasPool < gas {
|
||||
*gasPool = 0
|
||||
return false
|
||||
}
|
||||
|
||||
*gasPool -= gas
|
||||
return true
|
||||
}
|
||||
|
||||
// Call executes the contract associated with the addr with the given input as
|
||||
// parameters. It also handles any necessary value transfer required and takes
|
||||
// the necessary steps to create accounts and reverses the state in case of an
|
||||
@ -181,6 +202,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
// proof of absence
|
||||
tryConsumeGas(&gas, evm.Accesses.TouchAndChargeProofOfAbsence(caller.Address().Bytes()))
|
||||
}
|
||||
// Calling a non existing account, don't do anything, but ping the tracer
|
||||
if evm.Config.Debug {
|
||||
if evm.depth == 0 {
|
||||
@ -195,6 +220,11 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
}
|
||||
evm.StateDB.CreateAccount(addr)
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) && value.Sign() != 0 {
|
||||
callerBalanceBefore := evm.StateDB.GetBalanceLittleEndian(caller.Address())
|
||||
targetBalanceBefore := evm.StateDB.GetBalanceLittleEndian(addr)
|
||||
evm.Accesses.SetLeafValuesValueTransfer(caller.Address().Bytes()[:], addr[:], callerBalanceBefore, targetBalanceBefore)
|
||||
}
|
||||
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
|
||||
|
||||
// Capture the tracer start/end events in debug mode
|
||||
@ -219,6 +249,13 @@ 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)
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
codeSize := uint64(len(code))
|
||||
var codeSizeBytes [32]byte
|
||||
binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize)
|
||||
evm.Accesses.SetLeafValuesMessageCall(addr[:], codeSizeBytes[:])
|
||||
}
|
||||
|
||||
if len(code) == 0 {
|
||||
ret, err = nil, nil // gas is unchanged
|
||||
} else {
|
||||
@ -227,6 +264,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
// The depth-check is already done, and precompiles handled above
|
||||
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
|
||||
contract.IsDeployment = true
|
||||
ret, err = evm.interpreter.Run(contract, input, false)
|
||||
gas = contract.Gas
|
||||
}
|
||||
@ -282,6 +320,12 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
codeSize := uint64(evm.StateDB.GetCodeSize(addr))
|
||||
var codeSizeBytes [32]byte
|
||||
binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize)
|
||||
evm.Accesses.SetLeafValuesMessageCall(addr.Bytes()[:], codeSizeBytes[:])
|
||||
}
|
||||
addrCopy := addr
|
||||
// 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.
|
||||
@ -326,6 +370,12 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
codeSize := uint64(evm.StateDB.GetCodeSize(addr))
|
||||
var codeSizeBytes [32]byte
|
||||
binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize)
|
||||
evm.Accesses.SetLeafValuesMessageCall(addr.Bytes()[:], codeSizeBytes[:])
|
||||
}
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||
@ -378,6 +428,12 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
codeSize := uint64(evm.StateDB.GetCodeSize(addr))
|
||||
var codeSizeBytes [32]byte
|
||||
binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize)
|
||||
evm.Accesses.SetLeafValuesMessageCall(addr.Bytes()[:], codeSizeBytes[:])
|
||||
}
|
||||
// At this point, we use a copy of address. If we don't, the go compiler will
|
||||
// leak the 'contract' to the outer scope, and make allocation for 'contract'
|
||||
// even if the actual execution ends on RunPrecompiled above.
|
||||
@ -415,6 +471,13 @@ func (c *codeAndHash) Hash() common.Hash {
|
||||
|
||||
// create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
|
||||
var zeroVerkleLeaf [32]byte
|
||||
var balanceBefore []byte
|
||||
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
evm.Accesses.SetLeafValuesContractCreateInit(address.Bytes()[:], zeroVerkleLeaf[:])
|
||||
}
|
||||
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
@ -444,12 +507,21 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
if evm.chainRules.IsEIP158 {
|
||||
evm.StateDB.SetNonce(address, 1)
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
// note: assumption is that the nonce, code size, code hash
|
||||
// will be 0x0000...00 at the target account before it is created
|
||||
// otherwise would imply contract creation collision which is
|
||||
// impossible if self-destruct is removed
|
||||
balanceBefore = evm.StateDB.GetBalanceLittleEndian(address)
|
||||
}
|
||||
|
||||
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)
|
||||
|
||||
// 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(address), value, gas)
|
||||
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||
contract.IsDeployment = true
|
||||
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, address, gas, nil
|
||||
@ -500,6 +572,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateCompleted(address.Bytes()[:], value.Sign() != 0)) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
err = ErrOutOfGas
|
||||
} else {
|
||||
evm.Accesses.SetLeafValuesContractCreateCompleted(address.Bytes()[:], zeroVerkleLeaf[:], zeroVerkleLeaf[:])
|
||||
if value.Sign() != 0 {
|
||||
evm.Accesses.TouchAddress(utils.GetTreeKeyBalance(address.Bytes()[:]), balanceBefore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if evm.Config.Debug {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||
|
@ -21,7 +21,9 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
|
||||
)
|
||||
|
||||
// memoryGasCost calculates the quadratic gas for memory expansion. It does so
|
||||
@ -87,13 +89,89 @@ func memoryCopierGas(stackpos int) gasFunc {
|
||||
}
|
||||
|
||||
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 gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
usedGas := uint64(0)
|
||||
slot := stack.Back(0)
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
|
||||
usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
|
||||
}
|
||||
|
||||
return usedGas, nil
|
||||
}
|
||||
|
||||
func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var statelessGas uint64
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
var (
|
||||
codeOffset = stack.Back(1)
|
||||
length = stack.Back(2)
|
||||
)
|
||||
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||
if overflow {
|
||||
uint64CodeOffset = 0xffffffffffffffff
|
||||
}
|
||||
uint64Length, overflow := length.Uint64WithOverflow()
|
||||
if overflow {
|
||||
uint64Length = 0xffffffffffffffff
|
||||
}
|
||||
_, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length)
|
||||
statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], nil, nil, evm.Accesses)
|
||||
}
|
||||
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.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
var (
|
||||
codeOffset = stack.Back(2)
|
||||
length = stack.Back(3)
|
||||
targetAddr = stack.Back(0).Bytes20()
|
||||
)
|
||||
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||
if overflow {
|
||||
uint64CodeOffset = 0xffffffffffffffff
|
||||
}
|
||||
uint64Length, overflow := length.Uint64WithOverflow()
|
||||
if overflow {
|
||||
uint64Length = 0xffffffffffffffff
|
||||
}
|
||||
// note: we must charge witness costs for the specified range regardless of whether it
|
||||
// is in-bounds of the actual target account code. This is because we must charge the cost
|
||||
// before hitting the db to be able to now what the actual code size is. This is different
|
||||
// behavior from CODECOPY which only charges witness access costs for the part of the range
|
||||
// which overlaps in the account code. TODO: clarify this is desired behavior and amend the
|
||||
// spec.
|
||||
statelessGas = touchEachChunksAndChargeGas(uint64CodeOffset, uint64Length, targetAddr[:], nil, nil, evm.Accesses)
|
||||
}
|
||||
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.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
where := stack.Back(0)
|
||||
addr := contract.Address()
|
||||
index := trieUtils.GetTreeKeyStorageSlot(addr[:], where)
|
||||
usedGas += evm.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,12 +187,12 @@ 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):
|
||||
@ -331,6 +409,7 @@ 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.chainRules.IsEIP158 {
|
||||
if transfersValue && evm.StateDB.Empty(address) {
|
||||
gas += params.CallNewAccountGas
|
||||
@ -357,6 +436,21 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
if _, isPrecompile := evm.precompile(address); !isPrecompile {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()[:]))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
if transfersValue {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:]))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
@ -382,6 +476,15 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
address := common.Address(stack.Back(1).Bytes20())
|
||||
if _, isPrecompile := evm.precompile(address); !isPrecompile {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
@ -398,6 +501,15 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
address := common.Address(stack.Back(1).Bytes20())
|
||||
if _, isPrecompile := evm.precompile(address); !isPrecompile {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
@ -414,6 +526,15 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
address := common.Address(stack.Back(1).Bytes20())
|
||||
if _, isPrecompile := evm.precompile(address); !isPrecompile {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
@ -434,6 +555,12 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
||||
}
|
||||
}
|
||||
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
// TODO turn this into a panic (when we are sure this method
|
||||
// will never execute when verkle is enabled)
|
||||
log.Warn("verkle witness accumulation not supported for selfdestruct")
|
||||
}
|
||||
|
||||
if !evm.StateDB.HasSuicided(contract.Address()) {
|
||||
evm.StateDB.AddRefund(params.SelfdestructRefundGas)
|
||||
}
|
||||
|
@ -19,7 +19,9 @@ package vm
|
||||
import (
|
||||
"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"
|
||||
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/holiman/uint256"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
@ -341,7 +343,13 @@ 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.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
|
||||
index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
|
||||
statelessGas := interpreter.evm.Accesses.TouchAddressAndChargeGas(index, uint256.NewInt(cs).Bytes())
|
||||
scope.Contract.UseGas(statelessGas)
|
||||
}
|
||||
slot.SetUint64(cs)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -362,12 +370,90 @@ 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)
|
||||
|
||||
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64())
|
||||
if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
|
||||
touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses)
|
||||
}
|
||||
scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// touchEachChunksAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
|
||||
func touchEachChunksAndChargeGas(offset, size uint64, address []byte, code []byte, contract AnalyzedContract, accesses *types.AccessWitness) uint64 {
|
||||
// note that in the case where the copied code is outside the range of the
|
||||
// contract code but touches the last leaf with contract code in it,
|
||||
// we don't include the last leaf of code in the AccessWitness. The
|
||||
// reason that we do not need the last leaf is the account's code size
|
||||
// is already in the AccessWitness so a stateless verifier can see that
|
||||
// the code from the last leaf is not needed.
|
||||
if contract != nil && (size == 0 || offset > uint64(len(code))) {
|
||||
return 0
|
||||
}
|
||||
var (
|
||||
statelessGasCharged uint64
|
||||
startLeafOffset uint64
|
||||
endLeafOffset uint64
|
||||
startOffset uint64
|
||||
endOffset uint64
|
||||
numLeaves uint64
|
||||
index [32]byte
|
||||
)
|
||||
// startLeafOffset, endLeafOffset is the evm code offset of the first byte in the first leaf touched
|
||||
// and the evm code offset of the last byte in the last leaf touched
|
||||
startOffset = offset - (offset % 31)
|
||||
if contract != nil && startOffset+size > uint64(len(code)) {
|
||||
endOffset = uint64(len(code))
|
||||
} else {
|
||||
endOffset = startOffset + size
|
||||
}
|
||||
endLeafOffset = endOffset + (endOffset % 31)
|
||||
numLeaves = (endLeafOffset - startLeafOffset) / 31
|
||||
chunkOffset := new(uint256.Int)
|
||||
treeIndex := new(uint256.Int)
|
||||
|
||||
for i := 0; i < int(numLeaves); i++ {
|
||||
chunkOffset.Add(trieUtils.CodeOffset, uint256.NewInt(uint64(i)))
|
||||
treeIndex.Div(chunkOffset, trieUtils.VerkleNodeWidth)
|
||||
var subIndex byte
|
||||
subIndexMod := chunkOffset.Mod(chunkOffset, trieUtils.VerkleNodeWidth).Bytes()
|
||||
if len(subIndexMod) == 0 {
|
||||
subIndex = 0
|
||||
} else {
|
||||
subIndex = subIndexMod[0]
|
||||
}
|
||||
treeKey := trieUtils.GetTreeKey(address, treeIndex, subIndex)
|
||||
copy(index[0:31], treeKey)
|
||||
index[31] = subIndex
|
||||
|
||||
var value []byte
|
||||
if len(code) > 0 {
|
||||
// the offset into the leaf that the first PUSH occurs
|
||||
var firstPushOffset uint64 = 0
|
||||
// Look for the first code byte (i.e. no pushdata)
|
||||
for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ {
|
||||
}
|
||||
curEnd := (uint64(i) + 1) * 31
|
||||
if curEnd > endOffset {
|
||||
curEnd = endOffset
|
||||
}
|
||||
valueSize := curEnd - (uint64(i) * 31)
|
||||
value = make([]byte, 32, 32)
|
||||
value[0] = byte(firstPushOffset)
|
||||
|
||||
copy(value[1:valueSize+1], code[i*31:curEnd])
|
||||
if valueSize < 31 {
|
||||
padding := make([]byte, 31-valueSize, 31-valueSize)
|
||||
copy(value[valueSize+1:], padding)
|
||||
}
|
||||
}
|
||||
|
||||
statelessGasCharged += accesses.TouchAddressAndChargeGas(index[:], value)
|
||||
}
|
||||
|
||||
return statelessGasCharged
|
||||
}
|
||||
|
||||
func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
var (
|
||||
stack = scope.Stack
|
||||
@ -381,8 +467,16 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
||||
uint64CodeOffset = 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.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
|
||||
code := interpreter.evm.StateDB.GetCode(addr)
|
||||
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
|
||||
cb := codeBitmap(code)
|
||||
touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, addr[:], code, &cb, interpreter.evm.Accesses)
|
||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
|
||||
} else {
|
||||
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
|
||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@ -510,6 +604,11 @@ 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())
|
||||
|
||||
if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
|
||||
index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc)
|
||||
interpreter.evm.Accesses.TouchAddressAndChargeGas(index, val.Bytes())
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -569,6 +668,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
gas = scope.Contract.Gas
|
||||
)
|
||||
if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
|
||||
contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address()))
|
||||
statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:])
|
||||
if !tryConsumeGas(&gas, statelessGas) {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
}
|
||||
if interpreter.evm.chainRules.IsEIP150 {
|
||||
gas -= gas / 64
|
||||
}
|
||||
@ -611,6 +717,14 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
gas = scope.Contract.Gas
|
||||
)
|
||||
if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
|
||||
codeAndHash := &codeAndHash{code: input}
|
||||
contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
|
||||
statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:])
|
||||
if !tryConsumeGas(&gas, statelessGas) {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
}
|
||||
|
||||
// Apply EIP150
|
||||
gas -= gas / 64
|
||||
@ -834,6 +948,28 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
|
||||
*pc += 1
|
||||
if *pc < codeLen {
|
||||
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
|
||||
|
||||
if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) && *pc%31 == 0 {
|
||||
// touch next chunk if PUSH1 is at the boundary. if so, *pc has
|
||||
// advanced past this boundary.
|
||||
|
||||
// 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))
|
||||
statelessGas := interpreter.evm.Accesses.TouchAddressAndChargeGas(index, nil)
|
||||
scope.Contract.UseGas(statelessGas)
|
||||
}
|
||||
} else {
|
||||
scope.Stack.push(integer.Clear())
|
||||
}
|
||||
@ -855,6 +991,11 @@ func makePush(size uint64, pushByteSize int) executionFunc {
|
||||
endMin = startMin + pushByteSize
|
||||
}
|
||||
|
||||
if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
|
||||
statelessGas := touchEachChunksAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses)
|
||||
scope.Contract.UseGas(statelessGas)
|
||||
}
|
||||
|
||||
integer := new(uint256.Int)
|
||||
scope.Stack.push(integer.SetBytes(common.RightPadBytes(
|
||||
scope.Contract.Code[startMin:endMin], pushByteSize)))
|
||||
|
@ -47,6 +47,9 @@ type StateDB interface {
|
||||
GetState(common.Address, common.Hash) common.Hash
|
||||
SetState(common.Address, common.Hash, common.Hash)
|
||||
|
||||
GetBalanceLittleEndian(address common.Address) []byte
|
||||
GetNonceLittleEndian(address common.Address) []byte
|
||||
|
||||
Suicide(common.Address) bool
|
||||
HasSuicided(common.Address) bool
|
||||
|
||||
@ -74,6 +77,9 @@ type StateDB interface {
|
||||
AddPreimage(common.Hash, []byte)
|
||||
|
||||
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
|
||||
|
||||
Witness() *types.AccessWitness
|
||||
SetWitness(*types.AccessWitness)
|
||||
}
|
||||
|
||||
// CallContext provides a basic interface for the EVM calling conventions. The EVM
|
||||
|
@ -191,9 +191,18 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
||||
}
|
||||
|
||||
if in.evm.ChainConfig().IsCancun(in.evm.Context.BlockNumber) && !contract.IsDeployment {
|
||||
// if the PC ends up in a new "page" of verkleized code, charge the
|
||||
// associated witness costs.
|
||||
contract.Gas -= touchEachChunksAndChargeGas(pc, 1, contract.Address().Bytes()[:], contract.Code, contract, in.evm.TxContext.Accesses)
|
||||
}
|
||||
|
||||
// TODO how can we tell if we are in stateless mode here and need to get the op from the witness
|
||||
// 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}
|
||||
|
@ -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),
|
||||
},
|
||||
|
@ -56,6 +56,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/gballet/go-verkle"
|
||||
)
|
||||
|
||||
// Config contains the configuration options of the ETH protocol.
|
||||
@ -139,6 +140,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||
}
|
||||
log.Info("Initialised chain configuration", "config", chainConfig)
|
||||
|
||||
// Start the precomputation of Lagrange points
|
||||
// if this config supports verkle trees.
|
||||
if chainConfig.CancunBlock != nil {
|
||||
log.Info("Detected the use of verkle trees, rebuilding the cache")
|
||||
verkle.GetConfig()
|
||||
}
|
||||
|
||||
if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil {
|
||||
log.Error("Failed to recover state", "error", err)
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ func TestNoStepExec(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsPrecompile(t *testing.T) {
|
||||
chaincfg := ¶ms.ChainConfig{ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP150Hash: common.Hash{}, EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0), BerlinBlock: big.NewInt(300), LondonBlock: big.NewInt(0), TerminalTotalDifficulty: nil, Ethash: new(params.EthashConfig), Clique: nil}
|
||||
chaincfg := ¶ms.ChainConfig{ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP150Hash: common.Hash{}, EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0), BerlinBlock: big.NewInt(300), LondonBlock: big.NewInt(0), TerminalTotalDifficulty: nil, CancunBlock: nil, Ethash: new(params.EthashConfig), Clique: nil}
|
||||
chaincfg.ByzantiumBlock = big.NewInt(100)
|
||||
chaincfg.IstanbulBlock = big.NewInt(200)
|
||||
chaincfg.BerlinBlock = big.NewInt(300)
|
||||
@ -235,14 +235,17 @@ func TestEnterExit(t *testing.T) {
|
||||
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test that the enter and exit method are correctly invoked and the values passed
|
||||
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
scope := &vm.ScopeContext{
|
||||
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
|
||||
}
|
||||
|
||||
tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int))
|
||||
tracer.CaptureExit([]byte{}, 400, nil)
|
||||
|
||||
|
4
go.mod
4
go.mod
@ -16,6 +16,7 @@ require (
|
||||
github.com/cespare/cp v0.1.0
|
||||
github.com/cloudflare/cloudflare-go v0.14.0
|
||||
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20220120174240-fe21866d2ad5
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
|
||||
github.com/deepmap/oapi-codegen v1.8.2 // indirect
|
||||
@ -25,6 +26,7 @@ require (
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
|
||||
github.com/gballet/go-verkle v0.0.0-20220121105610-351986d619a8
|
||||
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||
github.com/go-stack/stack v1.8.0
|
||||
github.com/golang/protobuf v1.4.3
|
||||
@ -64,7 +66,7 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
|
||||
golang.org/x/text v0.3.6
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
|
||||
|
29
go.sum
29
go.sum
@ -104,6 +104,15 @@ github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/
|
||||
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f h1:C43yEtQ6NIf4ftFXD/V55gnGFgPbMQobd//YlnLjUJ8=
|
||||
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20211107182441-1aeb67f49de7/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20211211194659-8730b6787450 h1:FW8SSFr58vgCbvNAWHUgigf6BYG1uIcwgZA8z539RLE=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20211211194659-8730b6787450/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20211223165939-ab3f49447206 h1:nMTTM1b+4WtWrb43nmTUV/FJz7M0M2g5fioLmd4QzUQ=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20211223165939-ab3f49447206/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20220114181434-991b62f9b1da h1:2luwsOSyUPVE67DmD7s2nKM+JbxaRuOCu6Y82qBTdnI=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20220114181434-991b62f9b1da/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20220120174240-fe21866d2ad5 h1:BsLconJDsODyRO72xjBHhxZKPCuY/kBgZdPeV4GFNlU=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20220120174240-fe21866d2ad5/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
|
||||
@ -142,6 +151,18 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
|
||||
github.com/gballet/go-verkle v0.0.0-20211221174846-e7cddd97495c h1:d34wNrUBGD6s2OCWQd31nqjkPWt+YbXUm8FAJ+7+tHM=
|
||||
github.com/gballet/go-verkle v0.0.0-20211221174846-e7cddd97495c/go.mod h1:6hL9IP6wLByFGqj1VDbruVAjfa8DghWD96o8kuNMDCA=
|
||||
github.com/gballet/go-verkle v0.0.0-20220113152809-0c00dbeef550 h1:uyv9x+hIyWF6Jrlz9pg/PtIhQS/lPGwVl5Rkp04m/Ik=
|
||||
github.com/gballet/go-verkle v0.0.0-20220113152809-0c00dbeef550/go.mod h1:2ObZwG6QCVAzvnUrsezVfjEL28O+e1cSA9xV/PB7IME=
|
||||
github.com/gballet/go-verkle v0.0.0-20220119102938-379d70afb801 h1:XoZ7eOVOdDXeVz05t3hO4JtRWXYvZa8lGm9zoPhSyEc=
|
||||
github.com/gballet/go-verkle v0.0.0-20220119102938-379d70afb801/go.mod h1:e1m+0UuY3AFFTubxZZ3ePf4yO/rHMAeTb7udhUAJduk=
|
||||
github.com/gballet/go-verkle v0.0.0-20220119112812-69e1c35fb0e0 h1:OQ2MGbAcc9X/51/llMp1CozPwc3ryRyXSQ19E2mbI2k=
|
||||
github.com/gballet/go-verkle v0.0.0-20220119112812-69e1c35fb0e0/go.mod h1:e1m+0UuY3AFFTubxZZ3ePf4yO/rHMAeTb7udhUAJduk=
|
||||
github.com/gballet/go-verkle v0.0.0-20220119205306-b466d7bff5ba h1:iOhT1XFktDNN72Ly2GUI/UTj2dnA1qI7+kyYqdpSCCk=
|
||||
github.com/gballet/go-verkle v0.0.0-20220119205306-b466d7bff5ba/go.mod h1:e1m+0UuY3AFFTubxZZ3ePf4yO/rHMAeTb7udhUAJduk=
|
||||
github.com/gballet/go-verkle v0.0.0-20220121105610-351986d619a8 h1:6fW03c2tXGttQv1b3mt9zmcXw4/srV62DltEER5mp/U=
|
||||
github.com/gballet/go-verkle v0.0.0-20220121105610-351986d619a8/go.mod h1:e1m+0UuY3AFFTubxZZ3ePf4yO/rHMAeTb7udhUAJduk=
|
||||
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
|
||||
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@ -536,8 +557,12 @@ golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -163,6 +163,8 @@ func (t *odrTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter
|
||||
return errors.New("not implemented, needs client/server interface split")
|
||||
}
|
||||
|
||||
func (t *odrTrie) IsVerkle() bool { return false }
|
||||
|
||||
// do tries and retries to execute a function until it returns with no error or
|
||||
// an error type other than MissingNodeError
|
||||
func (t *odrTrie) do(key []byte, fn func() error) error {
|
||||
|
@ -1039,7 +1039,15 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tr := s.GetTrie(); tr.IsVerkle() {
|
||||
vtr := tr.(*trie.VerkleTrie)
|
||||
// Generate the proof if we are using a verkle tree
|
||||
p, err := vtr.ProveAndSerialize(s.Witness().Keys(), s.Witness().KeyVals())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block.SetVerkleProof(p)
|
||||
}
|
||||
if w.isRunning() && !w.merger.TDDReached() {
|
||||
if interval != nil {
|
||||
interval()
|
||||
|
@ -116,7 +116,7 @@ type testWorkerBackend struct {
|
||||
uncleBlock *types.Block
|
||||
}
|
||||
|
||||
func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend {
|
||||
func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int, isVerkle bool) *testWorkerBackend {
|
||||
var gspec = core.Genesis{
|
||||
Config: chainConfig,
|
||||
Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
|
||||
@ -151,9 +151,17 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine
|
||||
if n > 0 {
|
||||
parent = chain.GetBlockByHash(chain.CurrentBlock().ParentHash())
|
||||
}
|
||||
blocks, _ := core.GenerateChain(chainConfig, parent, engine, db, 1, func(i int, gen *core.BlockGen) {
|
||||
gen.SetCoinbase(testUserAddress)
|
||||
})
|
||||
var blocks []*types.Block
|
||||
|
||||
if isVerkle {
|
||||
blocks, _ = core.GenerateVerkleChain(chainConfig, parent, engine, db, 1, func(i int, gen *core.BlockGen) {
|
||||
gen.SetCoinbase(testUserAddress)
|
||||
})
|
||||
} else {
|
||||
blocks, _ = core.GenerateChain(chainConfig, parent, engine, db, 1, func(i int, gen *core.BlockGen) {
|
||||
gen.SetCoinbase(testUserAddress)
|
||||
})
|
||||
}
|
||||
|
||||
return &testWorkerBackend{
|
||||
db: db,
|
||||
@ -183,6 +191,22 @@ func (b *testWorkerBackend) newRandomUncle() *types.Block {
|
||||
return blocks[0]
|
||||
}
|
||||
|
||||
func (b *testWorkerBackend) newRandomVerkleUncle() *types.Block {
|
||||
var parent *types.Block
|
||||
cur := b.chain.CurrentBlock()
|
||||
if cur.NumberU64() == 0 {
|
||||
parent = b.chain.Genesis()
|
||||
} else {
|
||||
parent = b.chain.GetBlockByHash(b.chain.CurrentBlock().ParentHash())
|
||||
}
|
||||
blocks, _ := core.GenerateVerkleChain(b.chain.Config(), parent, b.chain.Engine(), b.db, 1, func(i int, gen *core.BlockGen) {
|
||||
var addr = make([]byte, common.AddressLength)
|
||||
rand.Read(addr)
|
||||
gen.SetCoinbase(common.BytesToAddress(addr))
|
||||
})
|
||||
return blocks[0]
|
||||
}
|
||||
|
||||
func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
|
||||
var tx *types.Transaction
|
||||
gasPrice := big.NewInt(10 * params.InitialBaseFee)
|
||||
@ -194,8 +218,8 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
|
||||
return tx
|
||||
}
|
||||
|
||||
func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) {
|
||||
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
|
||||
func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int, isVerkle bool) (*worker, *testWorkerBackend) {
|
||||
backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks, isVerkle)
|
||||
backend.txPool.AddLocals(pendingTxs)
|
||||
w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, consensus.NewMerger(rawdb.NewMemoryDatabase()))
|
||||
w.setEtherbase(testBankAddress)
|
||||
@ -226,7 +250,7 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
|
||||
}
|
||||
|
||||
chainConfig.LondonBlock = big.NewInt(0)
|
||||
w, b := newTestWorker(t, chainConfig, engine, db, 0)
|
||||
w, b := newTestWorker(t, chainConfig, engine, db, 0, false)
|
||||
defer w.close()
|
||||
|
||||
// This test chain imports the mined blocks.
|
||||
@ -265,6 +289,59 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateBlocksAndImportVerkle(t *testing.T) {
|
||||
var (
|
||||
engine consensus.Engine
|
||||
chainConfig *params.ChainConfig
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
)
|
||||
chainConfig = params.VerkleChainConfig
|
||||
engine = ethash.NewFaker()
|
||||
|
||||
w, b := newTestWorker(t, chainConfig, engine, db, 0, true)
|
||||
defer w.close()
|
||||
|
||||
// This test chain imports the mined blocks.
|
||||
db2 := rawdb.NewMemoryDatabase()
|
||||
b.genesis.MustCommit(db2)
|
||||
chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil, nil)
|
||||
defer chain.Stop()
|
||||
|
||||
// Ignore empty commit here for less noise.
|
||||
/*
|
||||
w.skipSealHook = func(task *task) bool {
|
||||
return len(task.receipts) == 0
|
||||
}
|
||||
*/
|
||||
|
||||
// Wait for mined blocks.
|
||||
sub := w.mux.Subscribe(core.NewMinedBlockEvent{})
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Start mining!
|
||||
w.start()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
b.txPool.AddLocal(b.newRandomTx(true))
|
||||
b.txPool.AddLocal(b.newRandomTx(false))
|
||||
w.postSideBlock(core.ChainSideEvent{Block: b.newRandomVerkleUncle()})
|
||||
w.postSideBlock(core.ChainSideEvent{Block: b.newRandomVerkleUncle()})
|
||||
|
||||
select {
|
||||
case ev := <-sub.Chan():
|
||||
block := ev.Data.(core.NewMinedBlockEvent).Block
|
||||
if block.Header().VerkleProof == nil {
|
||||
t.Fatalf("expected Verkle proof in mined block header")
|
||||
}
|
||||
if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
|
||||
t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err)
|
||||
}
|
||||
case <-time.After(3 * time.Second): // Worker needs 1s to include new changes.
|
||||
t.Fatalf("timeout")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyWorkEthash(t *testing.T) {
|
||||
testEmptyWork(t, ethashChainConfig, ethash.NewFaker())
|
||||
}
|
||||
@ -275,7 +352,7 @@ func TestEmptyWorkClique(t *testing.T) {
|
||||
func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
||||
defer engine.Close()
|
||||
|
||||
w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
|
||||
w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, false)
|
||||
defer w.close()
|
||||
|
||||
var (
|
||||
@ -321,7 +398,7 @@ func TestStreamUncleBlock(t *testing.T) {
|
||||
ethash := ethash.NewFaker()
|
||||
defer ethash.Close()
|
||||
|
||||
w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1)
|
||||
w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1, false)
|
||||
defer w.close()
|
||||
|
||||
var taskCh = make(chan struct{})
|
||||
@ -379,7 +456,7 @@ func TestRegenerateMiningBlockClique(t *testing.T) {
|
||||
func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
||||
defer engine.Close()
|
||||
|
||||
w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
|
||||
w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, false)
|
||||
defer w.close()
|
||||
|
||||
var taskCh = make(chan struct{})
|
||||
@ -439,7 +516,7 @@ func TestAdjustIntervalClique(t *testing.T) {
|
||||
func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
||||
defer engine.Close()
|
||||
|
||||
w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
|
||||
w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, false)
|
||||
defer w.close()
|
||||
|
||||
w.skipSealHook = func(task *task) bool {
|
||||
|
@ -75,6 +75,26 @@ var (
|
||||
Ethash: new(EthashConfig),
|
||||
}
|
||||
|
||||
VerkleChainConfig = &ChainConfig{
|
||||
ChainID: big.NewInt(86),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
DAOForkBlock: big.NewInt(0),
|
||||
DAOForkSupport: true,
|
||||
EIP150Block: big.NewInt(0),
|
||||
EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
|
||||
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),
|
||||
CancunBlock: big.NewInt(0),
|
||||
Ethash: new(EthashConfig),
|
||||
}
|
||||
|
||||
// MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network.
|
||||
MainnetTrustedCheckpoint = &TrustedCheckpoint{
|
||||
SectionIndex: 413,
|
||||
@ -257,16 +277,16 @@ var (
|
||||
//
|
||||
// This configuration is intentionally not using keyed fields to force anyone
|
||||
// adding flags to the config to also have to set these fields.
|
||||
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
|
||||
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
|
||||
|
||||
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
|
||||
// and accepted by the Ethereum core developers into the Clique consensus.
|
||||
//
|
||||
// This configuration is intentionally not using keyed fields to force anyone
|
||||
// adding flags to the config to also have to set these fields.
|
||||
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
|
||||
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
|
||||
|
||||
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil}
|
||||
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
|
||||
TestRules = TestChainConfig.Rules(new(big.Int))
|
||||
)
|
||||
|
||||
@ -346,6 +366,7 @@ type ChainConfig struct {
|
||||
BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin)
|
||||
LondonBlock *big.Int `json:"londonBlock,omitempty"` // London switch block (nil = no fork, 0 = already on london)
|
||||
ArrowGlacierBlock *big.Int `json:"arrowGlacierBlock,omitempty"` // Eip-4345 (bomb delay) switch block (nil = no fork, 0 = already activated)
|
||||
CancunBlock *big.Int `json:"cancunBlock,omitempty"`
|
||||
|
||||
// TerminalTotalDifficulty is the amount of total difficulty reached by
|
||||
// the network that triggers the consensus upgrade.
|
||||
@ -386,7 +407,7 @@ func (c *ChainConfig) String() string {
|
||||
default:
|
||||
engine = "unknown"
|
||||
}
|
||||
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, Engine: %v}",
|
||||
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, Cancun: %v, Engine: %v}",
|
||||
c.ChainID,
|
||||
c.HomesteadBlock,
|
||||
c.DAOForkBlock,
|
||||
@ -402,6 +423,7 @@ func (c *ChainConfig) String() string {
|
||||
c.BerlinBlock,
|
||||
c.LondonBlock,
|
||||
c.ArrowGlacierBlock,
|
||||
c.CancunBlock,
|
||||
engine,
|
||||
)
|
||||
}
|
||||
@ -446,6 +468,10 @@ func (c *ChainConfig) IsMuirGlacier(num *big.Int) bool {
|
||||
return isForked(c.MuirGlacierBlock, num)
|
||||
}
|
||||
|
||||
func (c *ChainConfig) IsCancun(num *big.Int) bool {
|
||||
return isForked(c.CancunBlock, num)
|
||||
}
|
||||
|
||||
// IsPetersburg returns whether num is either
|
||||
// - equal to or greater than the PetersburgBlock fork block,
|
||||
// - OR is nil, and Constantinople is active
|
||||
@ -661,7 +687,7 @@ type Rules struct {
|
||||
ChainID *big.Int
|
||||
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
|
||||
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
|
||||
IsBerlin, IsLondon bool
|
||||
IsBerlin, IsLondon, IsCancun bool
|
||||
}
|
||||
|
||||
// Rules ensures c's ChainID is not nil.
|
||||
@ -682,5 +708,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
|
||||
IsIstanbul: c.IsIstanbul(num),
|
||||
IsBerlin: c.IsBerlin(num),
|
||||
IsLondon: c.IsLondon(num),
|
||||
IsCancun: c.IsCancun(num),
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +156,10 @@ const (
|
||||
// up to half the consumed gas could be refunded. Redefined as 1/5th in EIP-3529
|
||||
RefundQuotient uint64 = 2
|
||||
RefundQuotientEIP3529 uint64 = 5
|
||||
|
||||
// Verkle tree EIP: costs associated to witness accesses
|
||||
WitnessBranchCost = uint64(1900)
|
||||
WitnessChunkCost = uint64(200)
|
||||
)
|
||||
|
||||
// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
|
||||
|
@ -261,7 +261,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo
|
||||
|
||||
var snaps *snapshot.Tree
|
||||
if snapshotter {
|
||||
snaps, _ = snapshot.New(db, sdb.TrieDB(), 1, root, false, true, false)
|
||||
snaps, _ = snapshot.New(db, sdb.TrieDB(), 1, root, false, true, false, false)
|
||||
}
|
||||
statedb, _ = state.New(root, sdb, snaps)
|
||||
return snaps, statedb
|
||||
|
@ -277,6 +277,7 @@ type Config struct {
|
||||
Cache int // Memory allowance (MB) to use for caching trie nodes in memory
|
||||
Journal string // Journal of clean cache to survive node restarts
|
||||
Preimages bool // Flag whether the preimage of trie key is recorded
|
||||
UseVerkle bool // Flag whether the data is stored in a verkle trie
|
||||
}
|
||||
|
||||
// NewDatabase creates a new trie database to store ephemeral trie content before
|
||||
|
@ -217,3 +217,7 @@ func (t *SecureTrie) getSecKeyCache() map[string][]byte {
|
||||
}
|
||||
return t.secKeyCache
|
||||
}
|
||||
|
||||
func (t *SecureTrie) IsVerkle() bool {
|
||||
return false
|
||||
}
|
||||
|
116
trie/utils/verkle.go
Normal file
116
trie/utils/verkle.go
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright 2021 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 utils
|
||||
|
||||
import (
|
||||
"github.com/crate-crypto/go-ipa/bandersnatch/fr"
|
||||
"github.com/gballet/go-verkle"
|
||||
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
const (
|
||||
VersionLeafKey = 0
|
||||
BalanceLeafKey = 1
|
||||
NonceLeafKey = 2
|
||||
CodeKeccakLeafKey = 3
|
||||
CodeSizeLeafKey = 4
|
||||
)
|
||||
|
||||
var (
|
||||
zero = uint256.NewInt(0)
|
||||
HeaderStorageOffset = uint256.NewInt(64)
|
||||
CodeOffset = uint256.NewInt(128)
|
||||
MainStorageOffset = new(uint256.Int).Lsh(uint256.NewInt(256), 31)
|
||||
VerkleNodeWidth = uint256.NewInt(8)
|
||||
codeStorageDelta = uint256.NewInt(0).Sub(CodeOffset, HeaderStorageOffset)
|
||||
)
|
||||
|
||||
func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte {
|
||||
var poly [256]fr.Element
|
||||
verkle.FromLEBytes(&poly[0], []byte{1})
|
||||
verkle.FromLEBytes(&poly[0], []byte{2, 63})
|
||||
verkle.FromLEBytes(&poly[1], address[:16])
|
||||
verkle.FromLEBytes(&poly[2], address[16:])
|
||||
var index [32]byte
|
||||
copy(index[:], treeIndex.Bytes())
|
||||
verkle.FromLEBytes(&poly[3], index[:16])
|
||||
verkle.FromLEBytes(&poly[4], index[16:])
|
||||
for i := 5; i < len(poly); i++ {
|
||||
verkle.CopyFr(&poly[i], &verkle.FrZero)
|
||||
}
|
||||
|
||||
ret := verkle.GetConfig().CommitToPoly(poly[:], 0)
|
||||
retb := ret.Bytes()
|
||||
return retb[:]
|
||||
|
||||
}
|
||||
|
||||
func GetTreeKeyAccountLeaf(address []byte, leaf byte) []byte {
|
||||
return GetTreeKey(address, zero, leaf)
|
||||
}
|
||||
|
||||
func GetTreeKeyVersion(address []byte) []byte {
|
||||
return GetTreeKey(address, zero, VersionLeafKey)
|
||||
}
|
||||
|
||||
func GetTreeKeyBalance(address []byte) []byte {
|
||||
return GetTreeKey(address, zero, BalanceLeafKey)
|
||||
}
|
||||
|
||||
func GetTreeKeyNonce(address []byte) []byte {
|
||||
return GetTreeKey(address, zero, NonceLeafKey)
|
||||
}
|
||||
|
||||
func GetTreeKeyCodeKeccak(address []byte) []byte {
|
||||
return GetTreeKey(address, zero, CodeKeccakLeafKey)
|
||||
}
|
||||
|
||||
func GetTreeKeyCodeSize(address []byte) []byte {
|
||||
return GetTreeKey(address, zero, CodeSizeLeafKey)
|
||||
}
|
||||
|
||||
func GetTreeKeyCodeChunk(address []byte, chunk *uint256.Int) []byte {
|
||||
chunkOffset := new(uint256.Int).Add(CodeOffset, chunk)
|
||||
treeIndex := new(uint256.Int).Div(chunkOffset, VerkleNodeWidth)
|
||||
subIndexMod := new(uint256.Int).Mod(chunkOffset, VerkleNodeWidth).Bytes()
|
||||
var subIndex byte
|
||||
if len(subIndexMod) != 0 {
|
||||
subIndex = subIndexMod[0]
|
||||
}
|
||||
return GetTreeKey(address, treeIndex, subIndex)
|
||||
}
|
||||
|
||||
func GetTreeKeyStorageSlot(address []byte, storageKey *uint256.Int) []byte {
|
||||
treeIndex := storageKey.Clone()
|
||||
if storageKey.Cmp(codeStorageDelta) < 0 {
|
||||
treeIndex.Add(HeaderStorageOffset, storageKey)
|
||||
} else {
|
||||
treeIndex.Add(MainStorageOffset, storageKey)
|
||||
}
|
||||
treeIndex.Div(treeIndex, VerkleNodeWidth)
|
||||
|
||||
// calculate the sub_index, i.e. the index in the stem tree.
|
||||
// Because the modulus is 256, it's the last byte of treeIndex
|
||||
subIndexMod := treeIndex.Bytes()
|
||||
var subIndex byte
|
||||
if len(subIndexMod) != 0 {
|
||||
// Get the last byte, as uint256.Int is big-endian
|
||||
subIndex = subIndexMod[len(subIndexMod)-1]
|
||||
}
|
||||
return GetTreeKey(address, treeIndex, subIndex)
|
||||
}
|
324
trie/verkle.go
Normal file
324
trie/verkle.go
Normal file
@ -0,0 +1,324 @@
|
||||
// Copyright 2021 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 trie
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/gballet/go-verkle"
|
||||
)
|
||||
|
||||
// VerkleTrie is a wrapper around VerkleNode that implements the trie.Trie
|
||||
// interface so that Verkle trees can be reused verbatim.
|
||||
type VerkleTrie struct {
|
||||
root verkle.VerkleNode
|
||||
db *Database
|
||||
}
|
||||
|
||||
//func (vt *VerkleTrie) ToDot() string {
|
||||
//return verkle.ToDot(vt.root)
|
||||
//}
|
||||
|
||||
func NewVerkleTrie(root verkle.VerkleNode, db *Database) *VerkleTrie {
|
||||
return &VerkleTrie{
|
||||
root: root,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
var errInvalidProof = errors.New("invalid proof")
|
||||
|
||||
// GetKey returns the sha3 preimage of a hashed key that was previously used
|
||||
// to store a value.
|
||||
func (trie *VerkleTrie) GetKey(key []byte) []byte {
|
||||
return key
|
||||
}
|
||||
|
||||
// TryGet returns the value for key stored in the trie. The value bytes must
|
||||
// not be modified by the caller. If a node was not found in the database, a
|
||||
// trie.MissingNodeError is returned.
|
||||
func (trie *VerkleTrie) TryGet(key []byte) ([]byte, error) {
|
||||
return trie.root.Get(key, trie.db.DiskDB().Get)
|
||||
}
|
||||
|
||||
func (t *VerkleTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error {
|
||||
|
||||
var err error
|
||||
if err = t.TryUpdate(utils.GetTreeKeyVersion(key), []byte{0}); err != nil {
|
||||
return fmt.Errorf("updateStateObject (%x) error: %v", key, err)
|
||||
}
|
||||
var nonce [32]byte
|
||||
binary.BigEndian.PutUint64(nonce[:], acc.Nonce)
|
||||
if err = t.TryUpdate(utils.GetTreeKeyNonce(key), nonce[:]); err != nil {
|
||||
return fmt.Errorf("updateStateObject (%x) error: %v", key, err)
|
||||
}
|
||||
if err = t.TryUpdate(utils.GetTreeKeyBalance(key), acc.Balance.Bytes()); err != nil {
|
||||
return fmt.Errorf("updateStateObject (%x) error: %v", key, err)
|
||||
}
|
||||
if err = t.TryUpdate(utils.GetTreeKeyCodeKeccak(key), acc.CodeHash); err != nil {
|
||||
return fmt.Errorf("updateStateObject (%x) error: %v", key, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryUpdate associates key with value in the trie. If value has length zero, any
|
||||
// existing value is deleted from the trie. The value bytes must not be modified
|
||||
// by the caller while they are stored in the trie. If a node was not found in the
|
||||
// database, a trie.MissingNodeError is returned.
|
||||
func (trie *VerkleTrie) TryUpdate(key, value []byte) error {
|
||||
return trie.root.Insert(key, value, func(h []byte) ([]byte, error) {
|
||||
return trie.db.DiskDB().Get(h)
|
||||
})
|
||||
}
|
||||
|
||||
// TryDelete removes any existing value for key from the trie. If a node was not
|
||||
// found in the database, a trie.MissingNodeError is returned.
|
||||
func (trie *VerkleTrie) TryDelete(key []byte) error {
|
||||
return trie.root.Delete(key)
|
||||
}
|
||||
|
||||
// Hash returns the root hash of the trie. It does not write to the database and
|
||||
// can be used even if the trie doesn't have one.
|
||||
func (trie *VerkleTrie) Hash() common.Hash {
|
||||
// TODO cache this value
|
||||
rootC := trie.root.ComputeCommitment()
|
||||
return rootC.Bytes()
|
||||
}
|
||||
|
||||
func nodeToDBKey(n verkle.VerkleNode) []byte {
|
||||
ret := n.ComputeCommitment().Bytes()
|
||||
return ret[:]
|
||||
}
|
||||
|
||||
// Commit writes all nodes to the trie's memory database, tracking the internal
|
||||
// and external (for account tries) references.
|
||||
func (trie *VerkleTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) {
|
||||
flush := make(chan verkle.VerkleNode)
|
||||
go func() {
|
||||
trie.root.(*verkle.InternalNode).Flush(func(n verkle.VerkleNode) {
|
||||
if onleaf != nil {
|
||||
if leaf, isLeaf := n.(*verkle.LeafNode); isLeaf {
|
||||
for i := 0; i < verkle.NodeWidth; i++ {
|
||||
if leaf.Value(i) != nil {
|
||||
comm := n.ComputeCommitment().Bytes()
|
||||
onleaf(nil, nil, leaf.Value(i), common.BytesToHash(comm[:]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
flush <- n
|
||||
})
|
||||
close(flush)
|
||||
}()
|
||||
var commitCount int
|
||||
for n := range flush {
|
||||
commitCount += 1
|
||||
value, err := n.Serialize()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := trie.db.DiskDB().Put(nodeToDBKey(n), value); err != nil {
|
||||
return common.Hash{}, commitCount, err
|
||||
}
|
||||
}
|
||||
|
||||
return trie.Hash(), commitCount, nil
|
||||
}
|
||||
|
||||
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
|
||||
// starts at the key after the given start key.
|
||||
func (trie *VerkleTrie) NodeIterator(startKey []byte) NodeIterator {
|
||||
return newVerkleNodeIterator(trie, nil)
|
||||
}
|
||||
|
||||
// Prove constructs a Merkle proof for key. The result contains all encoded nodes
|
||||
// on the path to the value at key. The value itself is also included in the last
|
||||
// node and can be retrieved by verifying the proof.
|
||||
//
|
||||
// If the trie does not contain a value for key, the returned proof contains all
|
||||
// nodes of the longest existing prefix of the key (at least the root), ending
|
||||
// with the node that proves the absence of the key.
|
||||
func (trie *VerkleTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (trie *VerkleTrie) Copy(db *Database) *VerkleTrie {
|
||||
return &VerkleTrie{
|
||||
root: trie.root.Copy(),
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
func (trie *VerkleTrie) IsVerkle() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type KeyValuePair struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
func (trie *VerkleTrie) ProveAndSerialize(keys [][]byte, kv map[common.Hash][]byte) ([]byte, error) {
|
||||
proof, _, _, _ := verkle.MakeVerkleMultiProof(trie.root, keys)
|
||||
return verkle.SerializeProof(proof)
|
||||
}
|
||||
|
||||
type set = map[string]struct{}
|
||||
|
||||
func hasKey(s set, key []byte) bool {
|
||||
_, ok := s[string(key)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func addKey(s set, key []byte) {
|
||||
s[string(key)] = struct{}{}
|
||||
}
|
||||
|
||||
func DeserializeAndVerifyVerkleProof(serialized []byte) (map[common.Hash]common.Hash, error) {
|
||||
proof, cis, indices, yis, leaves, err := deserializeVerkleProof(serialized)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deserialize proof: %w", err)
|
||||
}
|
||||
if !verkle.VerifyVerkleProof(proof, cis, indices, yis, verkle.GetConfig()) {
|
||||
return nil, errInvalidProof
|
||||
}
|
||||
|
||||
return leaves, nil
|
||||
}
|
||||
|
||||
func deserializeVerkleProof(serialized []byte) (*verkle.Proof, []*verkle.Point, []byte, []*verkle.Fr, map[common.Hash]common.Hash, error) {
|
||||
var (
|
||||
indices []byte // List of zis
|
||||
yis []*verkle.Fr // List of yis
|
||||
seenIdx, seenComm set // Mark when a zi/yi has already been seen in deserialization
|
||||
others set // Mark when an "other" stem has been seen
|
||||
)
|
||||
|
||||
proof, err := verkle.DeserializeProof(serialized)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("verkle proof deserialization error: %w", err)
|
||||
}
|
||||
|
||||
for _, stem := range proof.PoaStems {
|
||||
addKey(others, stem)
|
||||
}
|
||||
|
||||
keyvals := make(map[common.Hash]common.Hash)
|
||||
for i, key := range proof.Keys {
|
||||
keyvals[common.BytesToHash(key)] = common.BytesToHash(proof.Values[i])
|
||||
}
|
||||
|
||||
if len(proof.Keys) != len(proof.Values) {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("keys and values are of different length %d != %d", len(proof.Keys), len(proof.Values))
|
||||
}
|
||||
if len(proof.Keys) != len(proof.ExtStatus) {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("keys and values are of different length %d != %d", len(proof.Keys), len(proof.Values))
|
||||
}
|
||||
|
||||
// Rebuild the tree, creating nodes in the lexicographic order of their path
|
||||
lastcomm, lastpoa := 0, 0
|
||||
root := verkle.NewStateless()
|
||||
for i, es := range proof.ExtStatus {
|
||||
depth := es & 0x1F
|
||||
status := es >> 5
|
||||
node := root
|
||||
stem := proof.Keys[i]
|
||||
|
||||
// go over the stem's bytes, in order to rebuild the internal nodes
|
||||
for j := byte(0); j < depth; j++ {
|
||||
// Recurse into the tree that is being rebuilt
|
||||
if node.Children()[stem[j]] == nil {
|
||||
node.SetChild(int(stem[j]), verkle.NewStatelessWithCommitment(proof.Cs[lastcomm]))
|
||||
lastcomm++
|
||||
}
|
||||
|
||||
node = node.Children()[stem[j]].(*verkle.StatelessNode)
|
||||
|
||||
// if that zi hasn't been encountered yet, add it to
|
||||
// the list of zis sorted by path.
|
||||
if !hasKey(seenIdx, stem[:j]) {
|
||||
addKey(seenIdx, stem[:j])
|
||||
indices = append(indices, stem[j])
|
||||
}
|
||||
|
||||
// same thing with a yi
|
||||
if !hasKey(seenComm, stem[:j]) {
|
||||
addKey(seenComm, stem[:j])
|
||||
yis = append(yis, node.ComputeCommitment())
|
||||
}
|
||||
}
|
||||
|
||||
// Reached the end, add the extension-and-suffix tree
|
||||
switch status {
|
||||
case 0:
|
||||
// missing stem, leave it as is
|
||||
break
|
||||
case 1:
|
||||
// another stem is found, build it
|
||||
node.SetStem(proof.PoaStems[lastpoa])
|
||||
lastpoa++
|
||||
break
|
||||
case 2:
|
||||
// stem is present
|
||||
node.SetStem(stem[:31])
|
||||
break
|
||||
default:
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("verkle proof deserialization error: invalid extension status %d", status)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return proof, proof.Cs, indices, yis, keyvals, nil
|
||||
}
|
||||
|
||||
// Copy the values here so as to avoid an import cycle
|
||||
const (
|
||||
PUSH1 = 0x60
|
||||
PUSH32 = 0x71
|
||||
)
|
||||
|
||||
func ChunkifyCode(addr common.Address, code []byte) ([][32]byte, error) {
|
||||
lastOffset := byte(0)
|
||||
chunkCount := len(code) / 31
|
||||
if len(code)%31 != 0 {
|
||||
chunkCount++
|
||||
}
|
||||
chunks := make([][32]byte, chunkCount)
|
||||
for i := range chunks {
|
||||
end := 31 * (i + 1)
|
||||
if len(code) < end {
|
||||
end = len(code)
|
||||
}
|
||||
copy(chunks[i][1:], code[31*i:end])
|
||||
for j := lastOffset; int(j) < len(code[31*i:end]); j++ {
|
||||
if code[j] >= byte(PUSH1) && code[j] <= byte(PUSH32) {
|
||||
j += code[j] - byte(PUSH1) + 1
|
||||
lastOffset = (j + 1) % 31
|
||||
}
|
||||
}
|
||||
chunks[i][0] = lastOffset
|
||||
}
|
||||
|
||||
return chunks, nil
|
||||
}
|
249
trie/verkle_iterator.go
Normal file
249
trie/verkle_iterator.go
Normal file
@ -0,0 +1,249 @@
|
||||
// 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 trie
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
|
||||
"github.com/gballet/go-verkle"
|
||||
)
|
||||
|
||||
type verkleNodeIteratorState struct {
|
||||
Node verkle.VerkleNode
|
||||
Index int
|
||||
}
|
||||
|
||||
type verkleNodeIterator struct {
|
||||
trie *VerkleTrie
|
||||
current verkle.VerkleNode
|
||||
lastErr error
|
||||
|
||||
stack []verkleNodeIteratorState
|
||||
}
|
||||
|
||||
func newVerkleNodeIterator(trie *VerkleTrie, start []byte) NodeIterator {
|
||||
if trie.Hash() == emptyState {
|
||||
return new(nodeIterator)
|
||||
}
|
||||
it := &verkleNodeIterator{trie: trie, current: trie.root}
|
||||
//it.err = it.seek(start)
|
||||
return it
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next node. If the parameter is false, any child
|
||||
// nodes will be skipped.
|
||||
func (it *verkleNodeIterator) Next(descend bool) bool {
|
||||
if it.lastErr == errIteratorEnd {
|
||||
it.lastErr = errIteratorEnd
|
||||
return false
|
||||
}
|
||||
|
||||
if len(it.stack) == 0 {
|
||||
it.stack = append(it.stack, verkleNodeIteratorState{Node: it.trie.root, Index: 0})
|
||||
it.current = it.trie.root
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
switch node := it.current.(type) {
|
||||
case *verkle.InternalNode:
|
||||
context := &it.stack[len(it.stack)-1]
|
||||
|
||||
// Look for the next non-empty child
|
||||
children := node.Children()
|
||||
for ; context.Index < len(children); context.Index++ {
|
||||
if _, ok := children[context.Index].(verkle.Empty); !ok {
|
||||
it.stack = append(it.stack, verkleNodeIteratorState{Node: children[context.Index], Index: 0})
|
||||
it.current = children[context.Index]
|
||||
return it.Next(descend)
|
||||
}
|
||||
}
|
||||
|
||||
// Reached the end of this node, go back to the parent, if
|
||||
// this isn't root.
|
||||
if len(it.stack) == 1 {
|
||||
it.lastErr = errIteratorEnd
|
||||
return false
|
||||
}
|
||||
it.stack = it.stack[:len(it.stack)-1]
|
||||
it.current = it.stack[len(it.stack)-1].Node
|
||||
it.stack[len(it.stack)-1].Index++
|
||||
return it.Next(descend)
|
||||
case *verkle.LeafNode:
|
||||
// Look for the next non-empty value
|
||||
for i := it.stack[len(it.stack)-1].Index + 1; i < 256; i++ {
|
||||
if node.Value(i) != nil {
|
||||
it.stack[len(it.stack)-1].Index = i
|
||||
return true
|
||||
}
|
||||
}
|
||||
// go back to parent to get the next leaf
|
||||
it.stack = it.stack[:len(it.stack)-1]
|
||||
it.current = it.stack[len(it.stack)-1].Node
|
||||
it.stack[len(it.stack)-1].Index++
|
||||
return it.Next(descend)
|
||||
case *verkle.HashedNode:
|
||||
// resolve the node
|
||||
data, err := it.trie.db.diskdb.Get(nodeToDBKey(node))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
it.current, err = verkle.ParseNode(data, byte(len(it.stack)-1))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// update the stack and parent with the resolved node
|
||||
it.stack[len(it.stack)-1].Node = it.current
|
||||
parent := &it.stack[len(it.stack)-2]
|
||||
parent.Node.(*verkle.InternalNode).SetChild(parent.Index, it.current)
|
||||
return true
|
||||
default:
|
||||
fmt.Println(node)
|
||||
panic("invalid node type")
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the error status of the iterator.
|
||||
func (it *verkleNodeIterator) Error() error {
|
||||
if it.lastErr == errIteratorEnd {
|
||||
return nil
|
||||
}
|
||||
return it.lastErr
|
||||
}
|
||||
|
||||
// Hash returns the hash of the current node.
|
||||
func (it *verkleNodeIterator) Hash() common.Hash {
|
||||
return it.current.ComputeCommitment().Bytes()
|
||||
}
|
||||
|
||||
// Parent returns the hash of the parent of the current node. The hash may be the one
|
||||
// grandparent if the immediate parent is an internal node with no hash.
|
||||
func (it *verkleNodeIterator) Parent() common.Hash {
|
||||
return it.stack[len(it.stack)-1].Node.ComputeCommitment().Bytes()
|
||||
}
|
||||
|
||||
// Path returns the hex-encoded path to the current node.
|
||||
// Callers must not retain references to the return value after calling Next.
|
||||
// For leaf nodes, the last element of the path is the 'terminator symbol' 0x10.
|
||||
func (it *verkleNodeIterator) Path() []byte {
|
||||
|
||||
panic("not completely implemented")
|
||||
}
|
||||
|
||||
// Leaf returns true iff the current node is a leaf node.
|
||||
func (it *verkleNodeIterator) Leaf() bool {
|
||||
_, ok := it.current.(*verkle.LeafNode)
|
||||
return ok
|
||||
}
|
||||
|
||||
// LeafKey returns the key of the leaf. The method panics if the iterator is not
|
||||
// positioned at a leaf. Callers must not retain references to the value after
|
||||
// calling Next.
|
||||
func (it *verkleNodeIterator) LeafKey() []byte {
|
||||
leaf, ok := it.current.(*verkle.LeafNode)
|
||||
if !ok {
|
||||
panic("Leaf() called on an verkle node iterator not at a leaf location")
|
||||
}
|
||||
|
||||
return leaf.Key(it.stack[len(it.stack)-1].Index)
|
||||
}
|
||||
|
||||
// LeafBlob returns the content of the leaf. The method panics if the iterator
|
||||
// is not positioned at a leaf. Callers must not retain references to the value
|
||||
// after calling Next.
|
||||
func (it *verkleNodeIterator) LeafBlob() []byte {
|
||||
leaf, ok := it.current.(*verkle.LeafNode)
|
||||
if !ok {
|
||||
panic("LeafBlob() called on an verkle node iterator not at a leaf location")
|
||||
}
|
||||
|
||||
return leaf.Value(it.stack[len(it.stack)-1].Index)
|
||||
}
|
||||
|
||||
// LeafProof returns the Merkle proof of the leaf. The method panics if the
|
||||
// iterator is not positioned at a leaf. Callers must not retain references
|
||||
// to the value after calling Next.
|
||||
func (it *verkleNodeIterator) LeafProof() [][]byte {
|
||||
_, ok := it.current.(*verkle.LeafNode)
|
||||
if !ok {
|
||||
panic("LeafProof() called on an verkle node iterator not at a leaf location")
|
||||
}
|
||||
|
||||
//return it.trie.Prove(leaf.Key())
|
||||
panic("not completely implemented")
|
||||
}
|
||||
|
||||
// AddResolver sets an intermediate database to use for looking up trie nodes
|
||||
// before reaching into the real persistent layer.
|
||||
//
|
||||
// This is not required for normal operation, rather is an optimization for
|
||||
// cases where trie nodes can be recovered from some external mechanism without
|
||||
// reading from disk. In those cases, this resolver allows short circuiting
|
||||
// accesses and returning them from memory.
|
||||
//
|
||||
// Before adding a similar mechanism to any other place in Geth, consider
|
||||
// making trie.Database an interface and wrapping at that level. It's a huge
|
||||
// refactor, but it could be worth it if another occurrence arises.
|
||||
func (it *verkleNodeIterator) AddResolver(ethdb.KeyValueStore) {
|
||||
panic("not completely implemented")
|
||||
}
|
||||
|
||||
type dummy struct{}
|
||||
|
||||
func (it dummy) Next(descend bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (it dummy) Error() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it dummy) Hash() common.Hash {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (it dummy) Leaf() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (it dummy) LeafKey() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it dummy) LeafProof() [][]byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it dummy) LeafBlob() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it dummy) Parent() common.Hash {
|
||||
return common.Hash{}
|
||||
}
|
||||
|
||||
func (it dummy) Path() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it dummy) AddResolver(ethdb.KeyValueStore) {
|
||||
panic("not completely implemented")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user