Compare commits

...

33 Commits

Author SHA1 Message Date
Guillaume Ballet
9c67d521ac reproduce the bug 2022-01-25 16:19:58 +01:00
Guillaume Ballet
15b353d7b4
verkle proof deserialization (#61)
* use proof serialization

* remove cruft

* save current state

* fix most issues up to this point

* fix remaining build issues

* update the go.mod to use the right branch

* remove custom-defined set type

* update go-verkle to get merged PRs

* extract key, value data from proof

* only activate precomp calculations if this is a verkle chain

Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com>
2022-01-21 12:38:40 +01:00
Guillaume Ballet
5beac51808
Charge witness gas when calling/creating a contract (#60)
* Charge witness gas when calling/creating a contract

Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com>

* gofmt

* replace checks with evm.Access!=nil with IsCancun

* remove double-charging of witness access costs for contract creation initialization

Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com>
2022-01-19 08:36:57 +01:00
jwasinger
99604b0699
Use IsCancun where applicable (#56)
* replace Accesses != nil with IsCancun(...)

* fix
2022-01-14 10:53:27 +01:00
jwasinger
952be80177
Verkle EXTCODECOPY implementation (#55)
* core/vm: verkle extcodecopy naive way (do jumpdest analysis on target contract every EXTCODECOPY)

* no double-charge

* address edge-case in touchEachChunksAndChargeGas

* simplify line

Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
2022-01-13 13:06:21 +01:00
jwasinger
d761880fd2
core/vm: don't include contract deployer bytecode in AccessWitness or charge witness access costs for it (#54) 2022-01-13 10:08:22 +01:00
Guillaume Ballet
4428439fdf
fix: don't use rlp in the tree for slot values (#51)
* fix: don't use rlp in the tree for slot values

* fix timeout after rebase
2022-01-11 11:38:49 +01:00
Guillaume Ballet
99f3c92361
fix: don't create settings each time a key is calculated (#53) 2022-01-10 15:39:46 +01:00
Guillaume Ballet
c87a6d904f reactivate working lines in test 2022-01-07 17:35:19 +01:00
Guillaume Ballet
e16e9cc84b
replace sha256 with pedersen_hash in get_key (#46)
* replace sha256 with pedersen_hash

* fix: prevent an OOB

* workaround timeout in unit test

* update go-ipa and reduce the timeout

* fix for unit tests: do not call NewAccessWitness in NewEVMTxContext (#49)

* potential fix: do not call NewAccessWitness in NewEVMTxContext

* more fixes: check for the existence of Accesses

* fix absence of witness in copy

* fix another witness issue

* workaround: ensure the prefetcher is off in verkle mode

* fix the remaining issues in tests

* review feedback

* fix witness allocation in stateless test
2022-01-07 11:53:48 +01:00
Guillaume Ballet
f215cc0791
call onleaf in verkle commit (#45) 2022-01-03 16:27:18 +01:00
jwasinger
99ebf767b9
Refactor witness-accumulation in EVM (#42)
* make push dynamically-charged.  charge witness gas costs for push.  refactor evm witness gas charging to move logic for touching a range of bytecode into a helper method 'touchEachChunksAndChargeGas'

* add witness gas calculation for CodeCopy, ExtCodeCopy, SLoad back to gas_table.go

* witness gas charging for CALL

* remove explicit reference to evm.TxContext

* core/vm: make touchEachChunksAndCharge gas handle nil code value

* core/vm: call implementation, separate out witnesses into touch/set

* some fixes

* remove witness touching from opCall: this will go in evm.go

* remove witness touching for call from gas_table.go

* (hopefully) fix tests

* add SSTORE witness charging that was removed mistakenly

* charge witness gas for call

* clean up and comment touchEachChunksAndChargeGas

* make suggested changes

* address remaining points

* fix build issues

* remove double-charging for contract creation witness gas charging
2021-12-16 11:21:59 +01:00
jwasinger
6af78cba9e
miner: embed verkle proof in sealing block (#39)
* miner: embed verkle proof in sealing block

* add test to ensure that verkle proof is present in mined blocks
2021-12-07 17:06:27 +01:00
Guillaume Ballet
fe75603d0b remove outdated comment 2021-12-06 11:09:25 +01:00
jwasinger
5bac5b3262
consensus/ethash: move accumulation of coinbase witness before coinbase account is credited (#41) 2021-12-02 09:41:11 +01:00
jwasinger
fa753db9e8
consensus/ethash: ensure uncle accounts are included in block witness (#40) 2021-12-02 09:39:51 +01:00
Guillaume Ballet
86bdc3fb39 Remove access witness from the signature of Process (#38) 2021-11-26 16:38:20 +01:00
Guillaume Ballet
909049c5fe use the witness in statedb, revert applyTx signature (#36)
* use the witness in statedb, revert applyTx signature

* fix miner tests

* fix catalyst build
2021-11-26 16:38:20 +01:00
Guillaume Ballet
7360d168c8 fix calculation in get_tree_key_for_storage_slot (#35) 2021-11-26 16:38:20 +01:00
Guillaume Ballet
361a328cb7 upgrade go version (#34) 2021-11-26 16:38:20 +01:00
Guillaume Ballet
41c2f754cc remove unnecessary cancun block declaration in tests (#33) 2021-11-26 16:38:20 +01:00
Guillaume Ballet
7cb1add36a add circleci support (#32)
* add circleci support

* disable linter, which is broken again

* actually run tests
2021-11-26 16:38:20 +01:00
Guillaume Ballet
03dbc0a210 fix boundary condition check in PUSH32 2021-11-26 16:38:20 +01:00
Guillaume Ballet
6d40e11fe3 fix bound check in code chunking 2021-11-26 16:38:20 +01:00
Guillaume Ballet
5ca990184f fix boundary check in PUSH 2021-11-26 16:38:20 +01:00
Guillaume Ballet
15d98607f3 initialize the new access witness if not already present 2021-11-26 16:38:20 +01:00
Guillaume Ballet
ef08e51e40 merge undefined instead of panicking (#30) 2021-11-26 16:38:20 +01:00
Guillaume Ballet
e1144745a7 fix linter issue 2021-11-26 16:38:20 +01:00
Guillaume Ballet
bc06d2c740 fix rebase issues 2021-11-26 16:38:20 +01:00
jwasinger
97a79f50e8 enable verkle on cancun block: take 2 (#28)
* enable verkle on cancun block: take 2

* fix typo.  make unreachable line panic message more clear
2021-11-26 16:38:17 +01:00
Guillaume Ballet
9f9c03a94c fixes for the IPA testnet
upgrade to latest go-verkle

update go-verkle to get more fixes

simplify code by removing all stateless references (#25)

fix verkle proof test by enforcing values alignment to 32 bytes

remove unneeded KZG tag

fix the stateless test

Move AccessWitness into StateDB (#27)

* move AccessWitness into StateDB

* set Accesses in TxContext constructor

* Ensures that a statedb is initialized with a witness

* copy AccessWitness in StateDB.Copy.  use copied state in miner worker.commit.

* remove redundant line

Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>

Fix contract creation issue
2021-11-26 16:30:06 +01:00
Guillaume Ballet
719bf47354 Upgrade go-verkle to its IPA version (#24) 2021-11-26 16:30:06 +01:00
Guillaume Ballet
162780515a all: implement EIP-compliant verkle trees
verkle: Implement Trie, NodeIterator and Database ifs

Fix crash in TestDump

Fix TestDump

Fix TrieCopy

remove unnecessary traces

fix: Error() returned errIteratorEnd in verkle node iterator

rewrite the iterator and change the signature of OpenStorageTrie

add the adapter to reuse the account trie for storage

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

Fix statedb unit tests (#14)

* debug code

* Fix more unit tests

* remove traces

* Go back to the full range

One tree to rule them all

remove updateRoot, there is no root to update

store code inside the account leaf

fix build

save current state for Sina

Update go-verkle to latest

Charge WITNESS_*_COST gas on storage loads

Add witness costs for SSTORE as well

Charge witness gas in the case of code execution

corresponding code deletion

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

use the snapshot to get data

stateless execution from block witness

AccessWitness functions

Add block generation test + genesis snapshot generation

test stateless block execution (#18)

* test stateless block execution

* Force tree resolution before generating the proof

increased coverage in stateless test execution (#19)

* test stateless block execution

* Force tree resolution before generating the proof

* increase coverage in stateless test execution

ensure geth compiles

fix issues in tests with verkle trees deactivated

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

* Ensure stateless data is available when executing statelessly

* Actual execution of a statless block

* bugfixes in stateless block execution

* code cleanup

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

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

fix leaf proofs in stateless execution (#22)

* Fixes in witness pre-state

* Add the recipient's nonce to the witness

* reduce PR footprint and investigate issue in root state calculation

* quick build fix

cleanup: Remove extra parameter in ToBlock

revert ToBlock to its older signature

fix import cycle in vm tests

fix linter issue

fix appveyor build

fix nil pointers in tests

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

upgrade geth dependency to drop geth's common dep

fix cmd/devp2p tests

fix rebase issues

quell an appveyor warning

fix address touching in SLOAD and SSTORE

fix access witness for code size

touch target account data before calling

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

touch all code pages in execution

add pushdata to witness

remove useless code in genesis snapshot generation

testnet: fix some of the rebase/drift issues

Fix verkle proof generation in block

fix an issue occuring when chunking past the code size

fix: ensure the code copy doesn't extend past the code size
2021-11-26 16:30:03 +01:00
43 changed files with 2126 additions and 87 deletions

45
.circleci/config.yml Normal file
View 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

View File

@ -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
}

View File

@ -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)
}

View File

@ -230,11 +230,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
cacheConfig: cacheConfig,
db: db,
triegc: prque.New(nil),
stateCache: state.NewDatabaseWithConfig(db, &trie.Config{
Cache: cacheConfig.TrieCleanLimit,
Journal: cacheConfig.TrieCleanJournal,
Preimages: cacheConfig.Preimages,
}),
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)

View File

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
)
// BlockGen creates blocks for testing.
@ -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 {

View File

@ -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 {
var trieCfg *trie.Config
if genesis == nil {
genesis = DefaultGenesisBlock()
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{

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,8 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
var emptyCodeHash = crypto.Keccak256(nil)
@ -239,10 +241,14 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
if metrics.EnabledExpensive {
meter = &s.db.StorageReads
}
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
if len(enc) > 0 {
@ -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{}) {
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 {
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

View File

@ -18,6 +18,7 @@
package state
import (
"encoding/binary"
"errors"
"fmt"
"math/big"
@ -33,6 +34,8 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
type revision struct {
@ -99,6 +102,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,11 +544,20 @@ 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
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))
}
}
}
}
// getStateObject retrieves a state object given by the address, returning nil if
@ -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,9 +930,13 @@ 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 {
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
// _untouched_. We can check with the prefetcher, if it can give us a trie
// which has the same root, but also has some content loaded into it.
@ -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 {

View File

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

View File

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

View File

@ -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})

View File

@ -340,3 +340,55 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
// Assemble and return the final block for sealing
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
}
func TestProcessStateless(t *testing.T) {
var (
config = &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
Ethash: new(params.EthashConfig),
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)
}
}

View File

@ -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

View 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
}

View File

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

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -17,13 +17,16 @@
package vm
import (
"encoding/binary"
"math/big"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
@ -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)

View File

@ -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
@ -88,12 +90,88 @@ func memoryCopierGas(stackpos int) gasFunc {
var (
gasCallDataCopy = memoryCopierGas(2)
gasCodeCopy = memoryCopierGas(2)
gasExtCodeCopy = memoryCopierGas(3)
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)
}

View File

@ -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())
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)))

View File

@ -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

View File

@ -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}

View File

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

View File

@ -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)
}

View File

@ -197,7 +197,7 @@ func TestNoStepExec(t *testing.T) {
}
func TestIsPrecompile(t *testing.T) {
chaincfg := &params.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 := &params.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
View File

@ -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
View File

@ -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=

View File

@ -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 {

View File

@ -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()

View File

@ -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) {
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 {

View File

@ -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),
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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
View 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")
}