mirror of https://github.com/ethereum/go-ethereum
all: stateless witness builder and (self-)cross validator (#29719)
* all: add stateless verifications * all: simplify witness and integrate it into live geth --------- Co-authored-by: Péter Szilágyi <peterke@gmail.com>pull/30071/head
parent
73f7e7c087
commit
ed8fd0ac09
@ -0,0 +1,73 @@ |
|||||||
|
// Copyright 2024 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 core |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/common/lru" |
||||||
|
"github.com/ethereum/go-ethereum/consensus/beacon" |
||||||
|
"github.com/ethereum/go-ethereum/consensus/ethash" |
||||||
|
"github.com/ethereum/go-ethereum/core/state" |
||||||
|
"github.com/ethereum/go-ethereum/core/stateless" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
"github.com/ethereum/go-ethereum/core/vm" |
||||||
|
"github.com/ethereum/go-ethereum/params" |
||||||
|
"github.com/ethereum/go-ethereum/trie" |
||||||
|
"github.com/ethereum/go-ethereum/triedb" |
||||||
|
) |
||||||
|
|
||||||
|
// ExecuteStateless runs a stateless execution based on a witness, verifies
|
||||||
|
// everything it can locally and returns the two computed fields that need the
|
||||||
|
// other side to explicitly check.
|
||||||
|
//
|
||||||
|
// This method is a bit of a sore thumb here, but:
|
||||||
|
// - It cannot be placed in core/stateless, because state.New prodces a circular dep
|
||||||
|
// - It cannot be placed outside of core, because it needs to construct a dud headerchain
|
||||||
|
//
|
||||||
|
// TODO(karalabe): Would be nice to resolve both issues above somehow and move it.
|
||||||
|
func ExecuteStateless(config *params.ChainConfig, witness *stateless.Witness) (common.Hash, common.Hash, error) { |
||||||
|
// Create and populate the state database to serve as the stateless backend
|
||||||
|
memdb := witness.MakeHashDB() |
||||||
|
|
||||||
|
db, err := state.New(witness.Root(), state.NewDatabaseWithConfig(memdb, triedb.HashDefaults), nil) |
||||||
|
if err != nil { |
||||||
|
return common.Hash{}, common.Hash{}, err |
||||||
|
} |
||||||
|
// Create a blockchain that is idle, but can be used to access headers through
|
||||||
|
chain := &HeaderChain{ |
||||||
|
config: config, |
||||||
|
chainDb: memdb, |
||||||
|
headerCache: lru.NewCache[common.Hash, *types.Header](256), |
||||||
|
engine: beacon.New(ethash.NewFaker()), |
||||||
|
} |
||||||
|
processor := NewStateProcessor(config, chain) |
||||||
|
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
|
||||||
|
|
||||||
|
// Run the stateless blocks processing and self-validate certain fields
|
||||||
|
receipts, _, usedGas, err := processor.Process(witness.Block, db, vm.Config{}) |
||||||
|
if err != nil { |
||||||
|
return common.Hash{}, common.Hash{}, err |
||||||
|
} |
||||||
|
if err = validator.ValidateState(witness.Block, db, receipts, usedGas, true); err != nil { |
||||||
|
return common.Hash{}, common.Hash{}, err |
||||||
|
} |
||||||
|
// Almost everything validated, but receipt and state root needs to be returned
|
||||||
|
receiptRoot := types.DeriveSha(receipts, trie.NewStackTrie(nil)) |
||||||
|
stateRoot := db.IntermediateRoot(config.IsEIP158(witness.Block.Number())) |
||||||
|
|
||||||
|
return receiptRoot, stateRoot, nil |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
// Copyright 2024 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 stateless |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb" |
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/ethdb" |
||||||
|
) |
||||||
|
|
||||||
|
// MakeHashDB imports tries, codes and block hashes from a witness into a new
|
||||||
|
// hash-based memory db. We could eventually rewrite this into a pathdb, but
|
||||||
|
// simple is better for now.
|
||||||
|
func (w *Witness) MakeHashDB() ethdb.Database { |
||||||
|
var ( |
||||||
|
memdb = rawdb.NewMemoryDatabase() |
||||||
|
hasher = crypto.NewKeccakState() |
||||||
|
hash = make([]byte, 32) |
||||||
|
) |
||||||
|
// Inject all the "block hashes" (i.e. headers) into the ephemeral database
|
||||||
|
for _, header := range w.Headers { |
||||||
|
rawdb.WriteHeader(memdb, header) |
||||||
|
} |
||||||
|
// Inject all the bytecodes into the ephemeral database
|
||||||
|
for code := range w.Codes { |
||||||
|
blob := []byte(code) |
||||||
|
|
||||||
|
hasher.Reset() |
||||||
|
hasher.Write(blob) |
||||||
|
hasher.Read(hash) |
||||||
|
|
||||||
|
rawdb.WriteCode(memdb, common.BytesToHash(hash), blob) |
||||||
|
} |
||||||
|
// Inject all the MPT trie nodes into the ephemeral database
|
||||||
|
for node := range w.State { |
||||||
|
blob := []byte(node) |
||||||
|
|
||||||
|
hasher.Reset() |
||||||
|
hasher.Write(blob) |
||||||
|
hasher.Read(hash) |
||||||
|
|
||||||
|
rawdb.WriteLegacyTrieNode(memdb, common.BytesToHash(hash), blob) |
||||||
|
} |
||||||
|
return memdb |
||||||
|
} |
@ -0,0 +1,129 @@ |
|||||||
|
// Copyright 2024 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 stateless |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"slices" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
"github.com/ethereum/go-ethereum/rlp" |
||||||
|
) |
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type extWitness -field-override extWitnessMarshalling -out gen_encoding_json.go
|
||||||
|
|
||||||
|
// toExtWitness converts our internal witness representation to the consensus one.
|
||||||
|
func (w *Witness) toExtWitness() *extWitness { |
||||||
|
ext := &extWitness{ |
||||||
|
Block: w.Block, |
||||||
|
Headers: w.Headers, |
||||||
|
} |
||||||
|
ext.Codes = make([][]byte, 0, len(w.Codes)) |
||||||
|
for code := range w.Codes { |
||||||
|
ext.Codes = append(ext.Codes, []byte(code)) |
||||||
|
} |
||||||
|
slices.SortFunc(ext.Codes, bytes.Compare) |
||||||
|
|
||||||
|
ext.State = make([][]byte, 0, len(w.State)) |
||||||
|
for node := range w.State { |
||||||
|
ext.State = append(ext.State, []byte(node)) |
||||||
|
} |
||||||
|
slices.SortFunc(ext.State, bytes.Compare) |
||||||
|
return ext |
||||||
|
} |
||||||
|
|
||||||
|
// fromExtWitness converts the consensus witness format into our internal one.
|
||||||
|
func (w *Witness) fromExtWitness(ext *extWitness) error { |
||||||
|
w.Block, w.Headers = ext.Block, ext.Headers |
||||||
|
|
||||||
|
w.Codes = make(map[string]struct{}, len(ext.Codes)) |
||||||
|
for _, code := range ext.Codes { |
||||||
|
w.Codes[string(code)] = struct{}{} |
||||||
|
} |
||||||
|
w.State = make(map[string]struct{}, len(ext.State)) |
||||||
|
for _, node := range ext.State { |
||||||
|
w.State[string(node)] = struct{}{} |
||||||
|
} |
||||||
|
return w.sanitize() |
||||||
|
} |
||||||
|
|
||||||
|
// MarshalJSON marshals a witness as JSON.
|
||||||
|
func (w *Witness) MarshalJSON() ([]byte, error) { |
||||||
|
return w.toExtWitness().MarshalJSON() |
||||||
|
} |
||||||
|
|
||||||
|
// EncodeRLP serializes a witness as RLP.
|
||||||
|
func (w *Witness) EncodeRLP(wr io.Writer) error { |
||||||
|
return rlp.Encode(wr, w.toExtWitness()) |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (w *Witness) UnmarshalJSON(input []byte) error { |
||||||
|
var ext extWitness |
||||||
|
if err := ext.UnmarshalJSON(input); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return w.fromExtWitness(&ext) |
||||||
|
} |
||||||
|
|
||||||
|
// DecodeRLP decodes a witness from RLP.
|
||||||
|
func (w *Witness) DecodeRLP(s *rlp.Stream) error { |
||||||
|
var ext extWitness |
||||||
|
if err := s.Decode(&ext); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return w.fromExtWitness(&ext) |
||||||
|
} |
||||||
|
|
||||||
|
// sanitize checks for some mandatory fields in the witness after decoding so
|
||||||
|
// the rest of the code can assume invariants and doesn't have to deal with
|
||||||
|
// corrupted data.
|
||||||
|
func (w *Witness) sanitize() error { |
||||||
|
// Verify that the "parent" header (i.e. index 0) is available, and is the
|
||||||
|
// true parent of the block-to-be executed, since we use that to link the
|
||||||
|
// current block to the pre-state.
|
||||||
|
if len(w.Headers) == 0 { |
||||||
|
return errors.New("parent header (for pre-root hash) missing") |
||||||
|
} |
||||||
|
for i, header := range w.Headers { |
||||||
|
if header == nil { |
||||||
|
return fmt.Errorf("witness header nil at position %d", i) |
||||||
|
} |
||||||
|
} |
||||||
|
if w.Headers[0].Hash() != w.Block.ParentHash() { |
||||||
|
return fmt.Errorf("parent hash different: witness %v, block parent %v", w.Headers[0].Hash(), w.Block.ParentHash()) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// extWitness is a witness RLP encoding for transferring across clients.
|
||||||
|
type extWitness struct { |
||||||
|
Block *types.Block `json:"block" gencodec:"required"` |
||||||
|
Headers []*types.Header `json:"headers" gencodec:"required"` |
||||||
|
Codes [][]byte `json:"codes"` |
||||||
|
State [][]byte `json:"state"` |
||||||
|
} |
||||||
|
|
||||||
|
// extWitnessMarshalling defines the hex marshalling types for a witness.
|
||||||
|
type extWitnessMarshalling struct { |
||||||
|
Codes []hexutil.Bytes |
||||||
|
State []hexutil.Bytes |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package stateless |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"errors" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
) |
||||||
|
|
||||||
|
var _ = (*extWitnessMarshalling)(nil) |
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (e extWitness) MarshalJSON() ([]byte, error) { |
||||||
|
type extWitness struct { |
||||||
|
Block *types.Block `json:"block" gencodec:"required"` |
||||||
|
Headers []*types.Header `json:"headers" gencodec:"required"` |
||||||
|
Codes []hexutil.Bytes `json:"codes"` |
||||||
|
State []hexutil.Bytes `json:"state"` |
||||||
|
} |
||||||
|
var enc extWitness |
||||||
|
enc.Block = e.Block |
||||||
|
enc.Headers = e.Headers |
||||||
|
if e.Codes != nil { |
||||||
|
enc.Codes = make([]hexutil.Bytes, len(e.Codes)) |
||||||
|
for k, v := range e.Codes { |
||||||
|
enc.Codes[k] = v |
||||||
|
} |
||||||
|
} |
||||||
|
if e.State != nil { |
||||||
|
enc.State = make([]hexutil.Bytes, len(e.State)) |
||||||
|
for k, v := range e.State { |
||||||
|
enc.State[k] = v |
||||||
|
} |
||||||
|
} |
||||||
|
return json.Marshal(&enc) |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (e *extWitness) UnmarshalJSON(input []byte) error { |
||||||
|
type extWitness struct { |
||||||
|
Block *types.Block `json:"block" gencodec:"required"` |
||||||
|
Headers []*types.Header `json:"headers" gencodec:"required"` |
||||||
|
Codes []hexutil.Bytes `json:"codes"` |
||||||
|
State []hexutil.Bytes `json:"state"` |
||||||
|
} |
||||||
|
var dec extWitness |
||||||
|
if err := json.Unmarshal(input, &dec); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if dec.Block == nil { |
||||||
|
return errors.New("missing required field 'block' for extWitness") |
||||||
|
} |
||||||
|
e.Block = dec.Block |
||||||
|
if dec.Headers == nil { |
||||||
|
return errors.New("missing required field 'headers' for extWitness") |
||||||
|
} |
||||||
|
e.Headers = dec.Headers |
||||||
|
if dec.Codes != nil { |
||||||
|
e.Codes = make([][]byte, len(dec.Codes)) |
||||||
|
for k, v := range dec.Codes { |
||||||
|
e.Codes[k] = v |
||||||
|
} |
||||||
|
} |
||||||
|
if dec.State != nil { |
||||||
|
e.State = make([][]byte, len(dec.State)) |
||||||
|
for k, v := range dec.State { |
||||||
|
e.State[k] = v |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,159 @@ |
|||||||
|
// Copyright 2024 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 stateless |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"maps" |
||||||
|
"slices" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
"github.com/ethereum/go-ethereum/rlp" |
||||||
|
) |
||||||
|
|
||||||
|
// HeaderReader is an interface to pull in headers in place of block hashes for
|
||||||
|
// the witness.
|
||||||
|
type HeaderReader interface { |
||||||
|
// GetHeader retrieves a block header from the database by hash and number,
|
||||||
|
GetHeader(hash common.Hash, number uint64) *types.Header |
||||||
|
} |
||||||
|
|
||||||
|
// Witness encompasses a block, state and any other chain data required to apply
|
||||||
|
// a set of transactions and derive a post state/receipt root.
|
||||||
|
type Witness struct { |
||||||
|
Block *types.Block // Current block with rootHash and receiptHash zeroed out
|
||||||
|
Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set.
|
||||||
|
Codes map[string]struct{} // Set of bytecodes ran or accessed
|
||||||
|
State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
|
||||||
|
|
||||||
|
chain HeaderReader // Chain reader to convert block hash ops to header proofs
|
||||||
|
lock sync.Mutex // Lock to allow concurrent state insertions
|
||||||
|
} |
||||||
|
|
||||||
|
// NewWitness creates an empty witness ready for population.
|
||||||
|
func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) { |
||||||
|
// Zero out the result fields to avoid accidentally sending them to the verifier
|
||||||
|
header := block.Header() |
||||||
|
header.Root = common.Hash{} |
||||||
|
header.ReceiptHash = common.Hash{} |
||||||
|
|
||||||
|
// Retrieve the parent header, which will *always* be included to act as a
|
||||||
|
// trustless pre-root hash container
|
||||||
|
parent := chain.GetHeader(block.ParentHash(), block.NumberU64()-1) |
||||||
|
if parent == nil { |
||||||
|
return nil, errors.New("failed to retrieve parent header") |
||||||
|
} |
||||||
|
// Create the wtness with a reconstructed gutted out block
|
||||||
|
return &Witness{ |
||||||
|
Block: types.NewBlockWithHeader(header).WithBody(*block.Body()), |
||||||
|
Codes: make(map[string]struct{}), |
||||||
|
State: make(map[string]struct{}), |
||||||
|
Headers: []*types.Header{parent}, |
||||||
|
chain: chain, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// AddBlockHash adds a "blockhash" to the witness with the designated offset from
|
||||||
|
// chain head. Under the hood, this method actually pulls in enough headers from
|
||||||
|
// the chain to cover the block being added.
|
||||||
|
func (w *Witness) AddBlockHash(number uint64) { |
||||||
|
// Keep pulling in headers until this hash is populated
|
||||||
|
for int(w.Block.NumberU64()-number) > len(w.Headers) { |
||||||
|
tail := w.Block.Header() |
||||||
|
if len(w.Headers) > 0 { |
||||||
|
tail = w.Headers[len(w.Headers)-1] |
||||||
|
} |
||||||
|
w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// AddCode adds a bytecode blob to the witness.
|
||||||
|
func (w *Witness) AddCode(code []byte) { |
||||||
|
if len(code) == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
w.Codes[string(code)] = struct{}{} |
||||||
|
} |
||||||
|
|
||||||
|
// AddState inserts a batch of MPT trie nodes into the witness.
|
||||||
|
func (w *Witness) AddState(nodes map[string]struct{}) { |
||||||
|
if len(nodes) == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
w.lock.Lock() |
||||||
|
defer w.lock.Unlock() |
||||||
|
|
||||||
|
for node := range nodes { |
||||||
|
w.State[node] = struct{}{} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
|
||||||
|
// is never mutated by Witness
|
||||||
|
func (w *Witness) Copy() *Witness { |
||||||
|
return &Witness{ |
||||||
|
Block: w.Block, |
||||||
|
Headers: slices.Clone(w.Headers), |
||||||
|
Codes: maps.Clone(w.Codes), |
||||||
|
State: maps.Clone(w.State), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// String prints a human-readable summary containing the total size of the
|
||||||
|
// witness and the sizes of the underlying components
|
||||||
|
func (w *Witness) String() string { |
||||||
|
blob, _ := rlp.EncodeToBytes(w) |
||||||
|
bytesTotal := len(blob) |
||||||
|
|
||||||
|
blob, _ = rlp.EncodeToBytes(w.Block) |
||||||
|
bytesBlock := len(blob) |
||||||
|
|
||||||
|
bytesHeaders := 0 |
||||||
|
for _, header := range w.Headers { |
||||||
|
blob, _ = rlp.EncodeToBytes(header) |
||||||
|
bytesHeaders += len(blob) |
||||||
|
} |
||||||
|
bytesCodes := 0 |
||||||
|
for code := range w.Codes { |
||||||
|
bytesCodes += len(code) |
||||||
|
} |
||||||
|
bytesState := 0 |
||||||
|
for node := range w.State { |
||||||
|
bytesState += len(node) |
||||||
|
} |
||||||
|
buf := new(bytes.Buffer) |
||||||
|
|
||||||
|
fmt.Fprintf(buf, "Witness #%d: %v\n", w.Block.Number(), common.StorageSize(bytesTotal)) |
||||||
|
fmt.Fprintf(buf, " block (%4d txs): %10v\n", len(w.Block.Transactions()), common.StorageSize(bytesBlock)) |
||||||
|
fmt.Fprintf(buf, "%4d headers: %10v\n", len(w.Headers), common.StorageSize(bytesHeaders)) |
||||||
|
fmt.Fprintf(buf, "%4d trie nodes: %10v\n", len(w.State), common.StorageSize(bytesState)) |
||||||
|
fmt.Fprintf(buf, "%4d codes: %10v\n", len(w.Codes), common.StorageSize(bytesCodes)) |
||||||
|
|
||||||
|
return buf.String() |
||||||
|
} |
||||||
|
|
||||||
|
// Root returns the pre-state root from the first header.
|
||||||
|
//
|
||||||
|
// Note, this method will panic in case of a bad witness (but RLP decoding will
|
||||||
|
// sanitize it and fail before that).
|
||||||
|
func (w *Witness) Root() common.Hash { |
||||||
|
return w.Headers[0].Root |
||||||
|
} |
Loading…
Reference in new issue