Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes necessary for Stateless Witness Building #29029

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ type Trie interface {
// be created with new root and updated trie database for following usage
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)

// AccessList returns a map of path->blob containing all trie nodes that have
// been accessed.
AccessList() map[string][]byte

// CommitAndObtainAccessList does the same thing as Commit, but also returns
// the access list of the trie.
CommitAndObtainAccessList(collectLeaf bool) (common.Hash, *trienode.NodeSet, map[string][]byte, error)

// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key. And error will be returned
// if fails to create node iterator.
Expand Down
15 changes: 10 additions & 5 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
}
value.SetBytes(content)
}
// If witness building is enabled, prefetch any trie paths loaded directly
// via the snapshots
if s.db.prefetcher != nil && err == nil && s.db.witness != nil && s.data.Root != types.EmptyRootHash {
s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, [][]byte{key[:]})
}
}
// If the snapshot is unavailable or reading from it fails, load from the database.
if s.db.snap == nil || err != nil {
Expand Down Expand Up @@ -379,11 +384,11 @@ func (s *stateObject) updateRoot() {
// commit obtains a set of dirty storage trie nodes and updates the account data.
// The returned set can be nil if nothing to commit. This function assumes all
// storage mutations have already been flushed into trie by updateRoot.
func (s *stateObject) commit() (*trienode.NodeSet, error) {
func (s *stateObject) commit() (*trienode.NodeSet, map[string][]byte, error) {
// Short circuit if trie is not even loaded, don't bother with committing anything
if s.trie == nil {
s.origin = s.data.Copy()
return nil, nil
return nil, nil, nil
}
// Track the amount of time wasted on committing the storage trie
if metrics.EnabledExpensive {
Expand All @@ -392,15 +397,15 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) {
// The trie is currently in an open state and could potentially contain
// cached mutations. Call commit to acquire a set of nodes that have been
// modified, the set can be nil if nothing to commit.
root, nodes, err := s.trie.Commit(false)
root, nodes, accessList, err := s.trie.CommitAndObtainAccessList(false)
if err != nil {
return nil, err
return nil, nil, err
}
s.data.Root = root

// Update original account data after commit
s.origin = s.data.Copy()
return nodes, nil
return nodes, accessList, nil
}

// AddBalance adds amount to s's balance.
Expand Down
328 changes: 328 additions & 0 deletions core/state/state_witness.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
package state

import (
"bytes"
"fmt"
"sort"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)

// A Witness contains a block and all pre-state needed to execute the block
// and compute the post-state root.
type Witness struct {
Block *types.Block
blockHashes map[uint64]common.Hash
codes map[common.Hash]Code
root common.Hash
lists map[common.Hash]map[string][]byte
}

type rlpWitness struct {
EncBlock []byte
Root common.Hash
Owners []common.Hash
AllPaths [][]string
AllNodes [][][]byte
BlockNums []uint64
BlockHashes []common.Hash
Codes []Code
CodeHashes []common.Hash
}

func (e *rlpWitness) toWitness() (*Witness, error) {
res := NewWitness(e.Root)
if err := rlp.DecodeBytes(e.EncBlock, &res.Block); err != nil {
return nil, err
}
for i := 0; i < len(e.Codes); i++ {
res.codes[e.CodeHashes[i]] = e.Codes[i]
}
for i, owner := range e.Owners {
pathMap := make(map[string][]byte)
for j := 0; j < len(e.AllPaths[i]); j++ {
pathMap[e.AllPaths[i][j]] = e.AllNodes[i][j]
}
res.lists[owner] = pathMap
}
for i, blockNum := range e.BlockNums {
res.blockHashes[blockNum] = e.BlockHashes[i]
}
return res, nil
}

func DecodeWitnessRLP(b []byte) (*Witness, error) {
var res rlpWitness
if err := rlp.DecodeBytes(b, &res); err != nil {
return nil, err
}
if wit, err := res.toWitness(); err != nil {
return nil, err
} else {
return wit, nil
}
}

func (w *Witness) EncodeRLP() ([]byte, error) {
var encWit rlpWitness
var encBlock bytes.Buffer
if err := w.Block.EncodeRLPWithZeroRoot(&encBlock); err != nil {
return nil, err
}
encWit.EncBlock = encBlock.Bytes()

for owner, nodeMap := range w.lists {
encWit.Owners = append(encWit.Owners, owner)
var ownerPaths []string
var ownerNodes [][]byte

for path, node := range nodeMap {
ownerPaths = append(ownerPaths, path)
ownerNodes = append(ownerNodes, node)
}
encWit.AllPaths = append(encWit.AllPaths, ownerPaths)
encWit.AllNodes = append(encWit.AllNodes, ownerNodes)
}

for codeHash, code := range w.codes {
encWit.CodeHashes = append(encWit.CodeHashes, codeHash)
encWit.Codes = append(encWit.Codes, code)
}

for blockNum, blockHash := range w.blockHashes {
encWit.BlockNums = append(encWit.BlockNums, blockNum)
encWit.BlockHashes = append(encWit.BlockHashes, blockHash)
}
res, err := rlp.EncodeToBytes(&encWit)
if err != nil {
return nil, err
}

return res, nil
}

// addAccessList associates a map of raw trie nodes keyed by path to an owner
// in the witness. the witness takes ownership of the passed map.
func (w *Witness) addAccessList(owner common.Hash, list map[string][]byte) {
var stateNodes map[string][]byte

if len(list) == 0 {
return
}
stateNodes, ok := w.lists[owner]
if !ok {
stateNodes = make(map[string][]byte)
w.lists[owner] = stateNodes
}

for path, node := range list {
stateNodes[path] = node
}
}

// AddBlockHash adds a block hash/number to the witness
func (w *Witness) AddBlockHash(hash common.Hash, num uint64) {
w.blockHashes[num] = hash
}

// AddCode associates a hash with EVM bytecode in the witness. It does
// nothing if there is already a code associated with the given hash.
// The witness takes ownership over the passed code slice.
func (w *Witness) AddCode(hash common.Hash, code Code) {
if code, ok := w.codes[hash]; ok && len(code) > 0 {
return
}
w.codes[hash] = code
}

// AddCodeHash adds a code hash to the witness
func (w *Witness) AddCodeHash(hash common.Hash) {
if _, ok := w.codes[hash]; ok {
return
}
w.codes[hash] = []byte{}
}

// Summary prints a human-readable summary containing the total size of the
// witness and the sizes of the underlying components
func (w *Witness) Summary() string {
b := new(bytes.Buffer)
xx, err := rlp.EncodeToBytes(w.Block)
if err != nil {
panic(err)
}
totBlock := len(xx)

yy, _ := w.EncodeRLP()

totWit := len(yy)
totCode := 0
for _, c := range w.codes {
totCode += len(c)
}
totNodes := 0
totPaths := 0
nodePathCount := 0
for _, ownerPaths := range w.lists {
for path, node := range ownerPaths {
nodePathCount++
totNodes += len(node)
totPaths += len(path)
}
}

fmt.Fprintf(b, "%4d hashes: %v\n", len(w.blockHashes), common.StorageSize(len(w.blockHashes)*32))
fmt.Fprintf(b, "%4d owners: %v\n", len(w.lists), common.StorageSize(len(w.lists)*32))
fmt.Fprintf(b, "%4d nodes: %v\n", nodePathCount, common.StorageSize(totNodes))
fmt.Fprintf(b, "%4d paths: %v\n", nodePathCount, common.StorageSize(totPaths))
fmt.Fprintf(b, "%4d codes: %v\n", len(w.codes), common.StorageSize(totCode))
fmt.Fprintf(b, "%4d codeHashes: %v\n", len(w.codes), common.StorageSize(len(w.codes)*32))
fmt.Fprintf(b, "block (%4d txs): %v\n", len(w.Block.Transactions()), common.StorageSize(totBlock))
fmt.Fprintf(b, "Total size: %v\n ", common.StorageSize(totWit))
return b.String()
}

// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
// is never mutated by Witness
func (w *Witness) Copy() *Witness {
var res Witness
res.Block = w.Block //

for blockNr, blockHash := range w.blockHashes {
res.blockHashes[blockNr] = blockHash
}
for codeHash, code := range w.codes {
cpy := make([]byte, len(code))
copy(cpy, code)
res.codes[codeHash] = cpy
}
res.root = w.root
for owner, owned := range w.lists {
res.lists[owner] = make(map[string][]byte)
for path, node := range owned {
cpy := make([]byte, len(node))
copy(cpy, node)
res.lists[owner][path] = cpy
}
}
return &res
}

// sortedWitness encodes returns an rlpWitness where hash-map items are sorted lexicographically by key
// in the encoder object to ensure that the encoded bytes are always the same for a given witness.
func (w *Witness) sortedWitness() *rlpWitness {
var sortedCodeHashes []common.Hash
for key, _ := range w.codes {
sortedCodeHashes = append(sortedCodeHashes, key)
}
sort.Slice(sortedCodeHashes, func(i, j int) bool {
return bytes.Compare(sortedCodeHashes[i][:], sortedCodeHashes[j][:]) > 0
})

// sort the list of owners
var owners []common.Hash
for owner, _ := range w.lists {
owners = append(owners, owner)
}
sort.Slice(owners, func(i, j int) bool {
return bytes.Compare(owners[i][:], owners[j][:]) > 0
})

var ownersPaths [][]string
var ownersNodes [][][]byte

// sort the nodes of each owner by path
for _, owner := range owners {
nodes := w.lists[owner]
var ownerPaths []string
for path, _ := range nodes {
ownerPaths = append(ownerPaths, path)
}
sort.Strings(ownerPaths)

var ownerNodes [][]byte
for _, path := range ownerPaths {
ownerNodes = append(ownerNodes, nodes[path])
}
ownersPaths = append(ownersPaths, ownerPaths)
ownersNodes = append(ownersNodes, ownerNodes)
}

var blockNrs []uint64
var blockHashes []common.Hash
for blockNr, blockHash := range w.blockHashes {
blockNrs = append(blockNrs, blockNr)
blockHashes = append(blockHashes, blockHash)
}

var codeHashes []common.Hash
var codes []Code
for codeHash, _ := range w.codes {
codeHashes = append(codeHashes, codeHash)
}
sort.Slice(codeHashes, func(i, j int) bool {
return bytes.Compare(codeHashes[i][:], codeHashes[j][:]) > 0
})

for _, codeHash := range codeHashes {
codes = append(codes, w.codes[codeHash])
}

encBlock, _ := rlp.EncodeToBytes(w.Block)
return &rlpWitness{
EncBlock: encBlock,
Root: common.Hash{},
Owners: owners,
AllPaths: ownersPaths,
AllNodes: ownersNodes,
BlockNums: blockNrs,
BlockHashes: blockHashes,
Codes: codes,
CodeHashes: codeHashes,
}
}

// PrettyPrint displays the contents of a witness object in a human-readable format to standard output.
func (w *Witness) PrettyPrint() string {
sorted := w.sortedWitness()
b := new(bytes.Buffer)
fmt.Fprintf(b, "block: %+v\n", w.Block)
fmt.Fprintf(b, "root: %x\n", sorted.Root)
fmt.Fprint(b, "owners:\n")
for i, owner := range sorted.Owners {
if owner == (common.Hash{}) {
fmt.Fprintf(b, "\troot:\n")
} else {
fmt.Fprintf(b, "\t%x:\n", owner)
}
ownerPaths := sorted.AllPaths[i]
ownerNodes := sorted.AllNodes[i]
for j, path := range ownerPaths {
fmt.Fprintf(b, "\t\t%x:%x\n", []byte(path), ownerNodes[j])
}
}
fmt.Fprintf(b, "block hashes:\n")
for i, blockNum := range sorted.BlockNums {
blockHash := sorted.BlockHashes[i]
fmt.Fprintf(b, "\t%d:%x\n", blockNum, blockHash)
}
fmt.Fprintf(b, "codes:\n")
for i, codeHash := range sorted.CodeHashes {
code := sorted.Codes[i]
fmt.Fprintf(b, "\t%x:%x\n", codeHash, code)
}
return b.String()
}

// NewWitness returns a new witness object.
func NewWitness(root common.Hash) *Witness {
return &Witness{
Block: nil,
blockHashes: make(map[uint64]common.Hash),
codes: make(map[common.Hash]Code),
root: root,
lists: make(map[common.Hash]map[string][]byte),
}
}