Skip to content

Commit

Permalink
Implementation of verkle trees for beverly-hills
Browse files Browse the repository at this point in the history
Squash the main verkle PR ahead of rebase

don't call Bytes() in GetTreeKey (#137)

trie: avoid endianness conversion in GetTreeKey (#140)

* trie/utils: add concrete expected value in trie key generation test

Signed-off-by: Ignacio Hagopian <[email protected]>

* mod: update to latest go-verkle

Signed-off-by: Ignacio Hagopian <[email protected]>

* trie/utils: avoid endianness conversions

Signed-off-by: Ignacio Hagopian <[email protected]>

* apply review changes & update to official go-verkle version

Signed-off-by: Ignacio Hagopian <[email protected]>

Signed-off-by: Ignacio Hagopian <[email protected]>

upgrade go-verkle to CoW version and get TestProcessVerkle to build (#138)

updating ci to use self-hosted machine (#143)

fix: storage offset in non-header group + reuse of value buffer (#145)

dedup call to ChunkifyCode, same as replay branch (#156)

* dedup call to ChunkifyCode, same as replay branch

* fix some linter issues

fix code offset in tree update (#157)

fix REVERT in state processor test execution (#158)

* fix code offset in tree update

* fix REVERT in test execution

save on key hashing: lump code size update with first code chunk group (#159)

fix code chunk key calculation and storage key calculation (#161)

* fix codeKey calculation

* Remove

* fix storageOffset

* fix the fix to the fix to the offset fix

* Remove copy/pasted, unused code in test

* fix linter

---------

Co-authored-by: Guillaume Ballet <[email protected]>

fix: infinite loop when calling extcodecopy on empty code (#151)

upgrade to latest go-verkle

fix: only update code in the tree if it's dirty (#174)

fix: read-touch the code size and Keccak of the origin (#175)

List of changes for converting a sepolia database (#182)

* naive conversion rebased on top of beverly hills

* changes for the sepolia shadow fork conversion

* fixes to please the linter

* fixes to please the linter

Unified point cache (#180)

* Unified point cache

* Use cache for Try*Account

* alter Trie interface to use caching for slots (#181)

* alter Trie interface to use caching for slots

* fix: use a lock to protect the point cache (#185)

* use fastest non-master go-verkle version & pull trie/Verkle.go changes to use new api (#184)

* mod: update to fastest go-verkle version today

Signed-off-by: Ignacio Hagopian <[email protected]>

* trie/verkle: use new batch serialization api

Signed-off-by: Ignacio Hagopian <[email protected]>

---------

Signed-off-by: Ignacio Hagopian <[email protected]>

---------

Signed-off-by: Ignacio Hagopian <[email protected]>
Co-authored-by: Ignacio Hagopian <[email protected]>

* fix: TryDelete signature in unit tests

---------

Signed-off-by: Ignacio Hagopian <[email protected]>
Co-authored-by: Ignacio Hagopian <[email protected]>

trie/utils: fix potential overflow (#191)

* trie/utils: fix potential overflow

Signed-off-by: Ignacio Hagopian <[email protected]>

* trie/utils: receive storage key as a byte slice

Signed-off-by: Ignacio Hagopian <[email protected]>

* revert formatter changes

Signed-off-by: Ignacio Hagopian <[email protected]>

* trie/utils: fix mod 256

Signed-off-by: Ignacio Hagopian <[email protected]>

---------

Signed-off-by: Ignacio Hagopian <[email protected]>

trie/utils: fix incorrect bigint assignment (#193)

Signed-off-by: Ignacio Hagopian <[email protected]>

upgrade precomp link to fix CI

fix: add missing code size&keccak leaves in empty accounts (#192)

fixes to use the latest go-verkle@master (#197)

* fixes to use the latest go-verkle@master

* linter fixes

* linter fixes for tests

* fix: use jsign's go-verkle fix

refactor: remove unused (*StateDB).GetXLittleEndian methods (#204)

fix gas accounting issue in state_processor_test.go (#207)

update go-verkle not to use StatelessNode anymore (#206)

* update go-verkle not to use StatelessNode anymore

* update go-verkle to latest

refactor: move verkle gas accounting to its own block in TransitionDB (#208)

fix a panic in deserializeVerkleProof if GetProofItems returns a nil ProofElements
  • Loading branch information
gballet committed May 12, 2023
1 parent ea26fc8 commit e063292
Show file tree
Hide file tree
Showing 50 changed files with 3,281 additions and 158 deletions.
48 changes: 48 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Go lint and test

on:
push:
branches: [ master ]
pull_request:
branches: [ master, verkle-trie-proof-in-block-rebased, verkle-trie-post-merge, beverly-hills-head, 'verkle/replay-change-with-tree-group-tryupdate' ]
workflow_dispatch:

jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Build
run: go build -v ./...

lint:
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Download golangci-lint
run: wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s latest
- name: Lint
run: ./bin/golangci-lint run
- name: Vet
run: go vet

test:
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Download precomputed points
run: wget -nv https://github.com/gballet/go-verkle/releases/download/banderwagonv3/precomp -Otrie/utils/precomp
- name: Test
run: go test ./...
251 changes: 248 additions & 3 deletions cmd/geth/verkle.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,25 @@ package main

import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"os"
"runtime"
"time"

"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
tutils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/gballet/go-verkle"
"github.com/holiman/uint256"
cli "github.com/urfave/cli/v2"
)

Expand All @@ -41,6 +49,20 @@ var (
Category: "MISCELLANEOUS COMMANDS",
Description: "",
Subcommands: []*cli.Command{
{
Name: "to-verkle",
Usage: "use the snapshot to compute a translation of a MPT into a verkle tree",
ArgsUsage: "<root>",
Action: convertToVerkle,
Flags: flags.Merge([]cli.Flag{}, utils.NetworkFlags, utils.DatabasePathFlags),
Description: `
geth verkle to-verkle <state-root>
This command takes a snapshot and inserts its values in a fresh verkle tree.
The argument is interpreted as the root hash. If none is provided, the latest
block is used.
`,
},
{
Name: "verify",
Usage: "verify the conversion of a MPT into a verkle tree",
Expand Down Expand Up @@ -68,14 +90,237 @@ in which key1, key2, ... are expanded.
}
)

func convertToVerkle(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()

chaindb := utils.MakeChainDatabase(ctx, stack, false)
if chaindb == nil {
return errors.New("nil chaindb")
}
headBlock := rawdb.ReadHeadBlock(chaindb)
if headBlock == nil {
log.Error("Failed to load head block")
return errors.New("no head block")
}
if ctx.NArg() > 1 {
log.Error("Too many arguments given")
return errors.New("too many arguments")
}
var (
root common.Hash
err error
)
if ctx.NArg() == 1 {
root, err = parseRoot(ctx.Args().First())
if err != nil {
log.Error("Failed to resolve state root", "error", err)
return err
}
log.Info("Start traversing the state", "root", root)
} else {
root = headBlock.Root()
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
}

var (
accounts int
lastReport time.Time
start = time.Now()
vRoot = verkle.New().(*verkle.InternalNode)
)

saveverkle := func(node verkle.VerkleNode) {
comm := node.Commit()
s, err := node.Serialize()
if err != nil {
panic(err)
}
commB := comm.Bytes()
if err := chaindb.Put(commB[:], s); err != nil {
panic(err)
}
}

snaptree, err := snapshot.New(snapshot.Config{CacheSize: 256}, chaindb, trie.NewDatabase(chaindb), root)
if err != nil {
return err
}
accIt, err := snaptree.AccountIterator(root, common.Hash{})
if err != nil {
return err
}
defer accIt.Release()

// root.FlushAtDepth(depth, saveverkle)

// Process all accounts sequentially
for accIt.Next() {
accounts += 1
acc, err := snapshot.FullAccount(accIt.Account())
if err != nil {
log.Error("Invalid account encountered during traversal", "error", err)
return err
}

// Store the basic account data
var (
nonce, balance, version, size [32]byte
newValues = make([][]byte, 256)
)
newValues[0] = version[:]
newValues[1] = balance[:]
newValues[2] = nonce[:]
newValues[4] = version[:] // memory-saving trick: by default, an account has 0 size
binary.LittleEndian.PutUint64(nonce[:8], acc.Nonce)
for i, b := range acc.Balance.Bytes() {
balance[len(acc.Balance.Bytes())-1-i] = b
}
addr := rawdb.ReadPreimage(chaindb, accIt.Hash())
if addr == nil {
return fmt.Errorf("could not find preimage for address %x %v %v", accIt.Hash(), acc, accIt.Error())
}
addrPoint := tutils.EvaluateAddressPoint(addr)
stem := tutils.GetTreeKeyVersionWithEvaluatedAddress(addrPoint)

// Store the account code if present
if !bytes.Equal(acc.CodeHash, emptyCode) {
code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash))
chunks := trie.ChunkifyCode(code)

for i := 0; i < 128 && i < len(chunks)/32; i++ {
newValues[128+i] = chunks[32*i : 32*(i+1)]
}

for i := 128; i < len(chunks)/32; {
values := make([][]byte, 256)
chunkkey := tutils.GetTreeKeyCodeChunkWithEvaluatedAddress(addrPoint, uint256.NewInt(uint64(i)))
j := i
for ; (j-i) < 256 && j < len(chunks)/32; j++ {
values[(j-128)%256] = chunks[32*j : 32*(j+1)]
}
i = j

// Otherwise, store the previous group in the tree with a
// stem insertion.
vRoot.InsertStem(chunkkey[:31], values, chaindb.Get)
}

// Write the code size in the account header group
binary.LittleEndian.PutUint64(size[:8], uint64(len(code)))
}
newValues[3] = acc.CodeHash[:]
newValues[4] = size[:]

// Save every slot into the tree
if !bytes.Equal(acc.Root, emptyRoot[:]) {
var translatedStorage = map[string][][]byte{}

storageIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{})
if err != nil {
log.Error("Failed to open storage trie", "root", acc.Root, "error", err)
return err
}
for storageIt.Next() {
// The value is RLP-encoded, decode it
var (
value []byte // slot value after RLP decoding
safeValue [32]byte // 32-byte aligned value
)
if err := rlp.DecodeBytes(storageIt.Slot(), &value); err != nil {
return fmt.Errorf("error decoding bytes %x: %w", storageIt.Slot(), err)
}
copy(safeValue[32-len(value):], value)

slotnr := rawdb.ReadPreimage(chaindb, storageIt.Hash())
if slotnr == nil {
return fmt.Errorf("could not find preimage for slot %x", storageIt.Hash())
}

// if the slot belongs to the header group, store it there - and skip
// calculating the slot key.
slotnrbig := uint256.NewInt(0).SetBytes(slotnr)
if slotnrbig.Cmp(uint256.NewInt(64)) < 0 {
newValues[64+slotnr[31]] = safeValue[:]
continue
}

// Slot not in the header group, get its tree key
slotkey := tutils.GetTreeKeyStorageSlotWithEvaluatedAddress(addrPoint, slotnr)

// Create the group if need be
values := translatedStorage[string(slotkey[:31])]
if values == nil {
values = make([][]byte, 256)
}

// Store value in group
values[slotkey[31]] = safeValue[:]
translatedStorage[string(slotkey[:31])] = values

// Dump the stuff to disk if we ran out of space
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
if mem.Alloc > 25*1024*1024*1024 {
fmt.Println("Memory usage exceeded threshold, calling mitigation function")
for s, vs := range translatedStorage {
var k [31]byte
copy(k[:], []byte(s))
// reminder that InsertStem will merge leaves
// if they exist.
vRoot.InsertStem(k[:31], vs, chaindb.Get)
}
translatedStorage = make(map[string][][]byte)
vRoot.FlushAtDepth(2, saveverkle)
}
}
for s, vs := range translatedStorage {
var k [31]byte
copy(k[:], []byte(s))
vRoot.InsertStem(k[:31], vs, chaindb.Get)
}
storageIt.Release()
if storageIt.Error() != nil {
log.Error("Failed to traverse storage trie", "root", acc.Root, "error", storageIt.Error())
return storageIt.Error()
}
}
// Finish with storing the complete account header group inside the tree.
vRoot.InsertStem(stem[:31], newValues, chaindb.Get)

if time.Since(lastReport) > time.Second*8 {
log.Info("Traversing state", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
lastReport = time.Now()
}

var mem runtime.MemStats
runtime.ReadMemStats(&mem)
if mem.Alloc > 25*1024*1024*1024 {
fmt.Println("Memory usage exceeded threshold, calling mitigation function")
vRoot.FlushAtDepth(2, saveverkle)
}
}
if accIt.Error() != nil {
log.Error("Failed to compute commitment", "root", root, "error", accIt.Error())
return accIt.Error()
}
log.Info("Wrote all leaves", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))

vRoot.Commit()
vRoot.Flush(saveverkle)

log.Info("Conversion complete", "root commitment", fmt.Sprintf("%x", vRoot.Commit().Bytes()), "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
return nil
}

// recurse into each child to ensure they can be loaded from the db. The tree isn't rebuilt
// (only its nodes are loaded) so there is no need to flush them, the garbage collector should
// take care of that for us.
func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error {
switch node := root.(type) {
case *verkle.InternalNode:
for i, child := range node.Children() {
childC := child.ComputeCommitment().Bytes()
childC := child.Commitment().Bytes()

childS, err := resolver(childC[:])
if bytes.Equal(childC[:], zero[:]) {
Expand All @@ -87,7 +332,7 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error
// depth is set to 0, the tree isn't rebuilt so it's not a problem
childN, err := verkle.ParseNode(childS, 0, childC[:])
if err != nil {
return fmt.Errorf("decode error child %x in db: %w", child.ComputeCommitment().Bytes(), err)
return fmt.Errorf("decode error child %x in db: %w", child.Commitment().Bytes(), err)
}
if err := checkChildren(childN, resolver); err != nil {
return fmt.Errorf("%x%w", i, err) // write the path to the erroring node
Expand Down Expand Up @@ -204,7 +449,7 @@ func expandVerkle(ctx *cli.Context) error {
root.Get(key, chaindb.Get)
}

if err := os.WriteFile("dump.dot", []byte(verkle.ToDot(root)), 0600); err != nil {
if err := os.WriteFile("dump.dot", []byte(verkle.ToDot(root)), 0o600); err != nil {
log.Error("Failed to dump file", "err", err)
} else {
log.Info("Tree was dumped to file", "file", "dump.dot")
Expand Down
16 changes: 16 additions & 0 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -666,10 +667,25 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)

if config.IsCancun(header.Number) {
uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes())
state.Witness().TouchAddressOnReadAndComputeGas(uncleCoinbase)
}
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().TouchAddressOnReadAndComputeGas(coinbase)
coinbase[31] = utils.VersionLeafKey // mark version
state.Witness().TouchAddressOnReadAndComputeGas(coinbase)
coinbase[31] = utils.NonceLeafKey // mark nonce
state.Witness().TouchAddressOnReadAndComputeGas(coinbase)
coinbase[31] = utils.CodeKeccakLeafKey // mark code keccak
state.Witness().TouchAddressOnReadAndComputeGas(coinbase)
}
state.AddBalance(header.Coinbase, reward)
}
Loading

0 comments on commit e063292

Please sign in to comment.