mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
stateless block proof generation w/ execution/verification.
Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com> Co-authored-by: Martin HS <martin@swende.se>
This commit is contained in:
parent
fe91d476ba
commit
1329dcf6fb
33 changed files with 1644 additions and 179 deletions
|
|
@ -146,6 +146,8 @@ var (
|
|||
configFileFlag,
|
||||
utils.LogDebugFlag,
|
||||
utils.LogBacktraceAtFlag,
|
||||
utils.CrossValidationEndpointFlag,
|
||||
utils.WitnessRecordingPathFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags)
|
||||
|
||||
rpcFlags = []cli.Flag{
|
||||
|
|
|
|||
230
cmd/stateless/main.go
Normal file
230
cmd/stateless/main.go
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/console/prompt"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
)
|
||||
|
||||
var (
|
||||
BlockWitnessFlag = &cli.StringFlag{
|
||||
Name: "block-witness",
|
||||
Usage: "foo bar",
|
||||
}
|
||||
ChainConfigFlag = &cli.StringFlag{
|
||||
Name: "chain-config",
|
||||
Usage: "path to a genesis file to source a chain configuration from",
|
||||
}
|
||||
|
||||
BlockWitness1Flag = &cli.StringFlag{
|
||||
Name: "witness1",
|
||||
Usage: "path to a file containing an rlp-encoded block witness",
|
||||
}
|
||||
BlockWitness2Flag = &cli.StringFlag{
|
||||
Name: "witness2",
|
||||
Usage: "path to a file containing an rlp-encoded block witness",
|
||||
}
|
||||
|
||||
LogFileFlag = &cli.StringFlag{
|
||||
Name: "logfile",
|
||||
Usage: "if present, generate debug trace (just evm traces in the future). store trace to the file",
|
||||
}
|
||||
|
||||
WitnessDiffCommand = &cli.Command{
|
||||
Action: witnessCmp,
|
||||
Name: "cmp",
|
||||
Usage: "outputs whether two block witnesses are equal",
|
||||
ArgsUsage: "cmp --witness1 /path/to/bw1.rlp --witness2 /path/to/bw2.rlp",
|
||||
Flags: []cli.Flag{
|
||||
BlockWitness1Flag,
|
||||
BlockWitness2Flag,
|
||||
},
|
||||
Description: ``,
|
||||
}
|
||||
PPCommand = &cli.Command{
|
||||
Action: pp,
|
||||
Name: "pp",
|
||||
Usage: "",
|
||||
ArgsUsage: "pp --block-witness /path/to/witness.rlp",
|
||||
Flags: []cli.Flag{
|
||||
BlockWitnessFlag,
|
||||
},
|
||||
Description: `pretty-print a block witness`,
|
||||
}
|
||||
ExecCommand = &cli.Command{
|
||||
Action: execCmd,
|
||||
Name: "exec",
|
||||
Usage: "",
|
||||
ArgsUsage: "exec --block-witness /path/to/bw.rlp --chain-config /path/to/chainconfig.json" +
|
||||
"--log-file /path/to/logfile.txt",
|
||||
Flags: []cli.Flag{
|
||||
BlockWitnessFlag,
|
||||
ChainConfigFlag,
|
||||
LogFileFlag,
|
||||
},
|
||||
Description: `statelessly execute and verify a block`,
|
||||
}
|
||||
ServerCommand = &cli.Command{
|
||||
Action: server,
|
||||
Name: "server",
|
||||
Usage: "",
|
||||
ArgsUsage: "server --chain-config /path/to/chain-config.json",
|
||||
Flags: []cli.Flag{ChainConfigFlag},
|
||||
Description: `Runs an HTTP server which provides an API endpoint for stateless block verification`,
|
||||
}
|
||||
)
|
||||
|
||||
var app = flags.NewApp("stateless block execution/verification utilities")
|
||||
|
||||
func init() {
|
||||
app.Copyright = "Copyright 2013-2024 The go-ethereum Authors"
|
||||
app.Commands = []*cli.Command{
|
||||
WitnessDiffCommand,
|
||||
PPCommand,
|
||||
ExecCommand,
|
||||
ServerCommand,
|
||||
}
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
BlockWitnessFlag,
|
||||
}
|
||||
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
maxprocs.Set() // Automatically set GOMAXPROCS to match Linux container CPU quota.
|
||||
if err := debug.Setup(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
app.After = func(ctx *cli.Context) error {
|
||||
debug.Exit()
|
||||
prompt.Stdin.Close() // Resets terminal mode.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func loadChainConfig(chainConfigPath string) *params.ChainConfig {
|
||||
var chainConfig *params.ChainConfig
|
||||
|
||||
if chainConfigPath != "" {
|
||||
configBytes, err := os.ReadFile(chainConfigPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewBuffer(configBytes))
|
||||
err = dec.Decode(&chainConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
panic("chain config must be specified")
|
||||
}
|
||||
return chainConfig
|
||||
}
|
||||
|
||||
func execCmd(ctx *cli.Context) error {
|
||||
var logWriter *bufio.Writer
|
||||
blockWitnessPath := ctx.String(BlockWitnessFlag.Name)
|
||||
if blockWitnessPath == "" {
|
||||
panic("block witness required")
|
||||
}
|
||||
|
||||
logFile := ctx.String(LogFileFlag.Name)
|
||||
if logFile != "" {
|
||||
f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0744)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logWriter = bufio.NewWriter(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer logWriter.Flush()
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(blockWitnessPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
witness, err := state.DecodeWitnessRLP(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
chainConfig := loadChainConfig(ctx.String(ChainConfigFlag.Name))
|
||||
|
||||
localRoot, err := utils.StatelessExecute(os.Stdout, chainConfig, witness)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if localRoot != witness.Block.Root() {
|
||||
return fmt.Errorf("state root mismatch (local: %x, remote: %x)", localRoot, witness.Block.Root())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pp(ctx *cli.Context) error {
|
||||
witnessPath := ctx.String(BlockWitnessFlag.Name)
|
||||
b, err := os.ReadFile(witnessPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w, err := state.DecodeWitnessRLP(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(w.PrettyPrint())
|
||||
return nil
|
||||
}
|
||||
|
||||
func witnessCmp(ctx *cli.Context) error {
|
||||
witness1Path := ctx.String(BlockWitness1Flag.Name)
|
||||
witness2Path := ctx.String(BlockWitness2Flag.Name)
|
||||
|
||||
b1, err := os.ReadFile(witness1Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b2, err := os.ReadFile(witness2Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w1, err := state.DecodeWitnessRLP(b1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w2, err := state.DecodeWitnessRLP(b2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w1Hash := w1.Hash()
|
||||
w2Hash := w2.Hash()
|
||||
if w1Hash != w2Hash {
|
||||
fmt.Printf("witness 1 hash (%x) != witness 2 hash (%x)\n", w1Hash, w2Hash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
29
cmd/stateless/server.go
Normal file
29
cmd/stateless/server.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/urfave/cli/v2"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func server(ctx *cli.Context) error {
|
||||
chainConfig := loadChainConfig(ctx.String(ChainConfigFlag.Name))
|
||||
closeCh, _, err := utils.RunLocalServer(chainConfig, 8080)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT)
|
||||
select {
|
||||
case <-sigc:
|
||||
closeCh <- struct{}{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -910,6 +910,18 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
|
|||
}
|
||||
)
|
||||
|
||||
var CrossValidationEndpointFlag = &cli.StringFlag{
|
||||
Name: "crossvalidation.endpoint",
|
||||
Usage: "http endpoint(s) to cross validate blocks against. Formatted as a comma-separated list of URLs",
|
||||
Value: "",
|
||||
}
|
||||
var WitnessRecordingPathFlag = &cli.StringFlag{
|
||||
Name: "crossvalidation.badblockdir",
|
||||
Usage: "location to store rlp-encoded block witnesses that do not pass cross validation",
|
||||
// TODO: default to some directory under the datadir
|
||||
Value: "",
|
||||
}
|
||||
|
||||
var (
|
||||
// TestnetFlags is the flag group of all built-in supported testnets.
|
||||
TestnetFlags = []cli.Flag{
|
||||
|
|
@ -1842,6 +1854,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil {
|
||||
Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err)
|
||||
}
|
||||
cfg.CrossValidationEndpoint = ctx.String(CrossValidationEndpointFlag.Name)
|
||||
cfg.WitnessRecordingPath = ctx.String(WitnessRecordingPathFlag.Name)
|
||||
}
|
||||
|
||||
// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if
|
||||
|
|
|
|||
129
cmd/utils/stateless.go
Normal file
129
cmd/utils/stateless.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func StatelessExecute(logOutput io.Writer, chainCfg *params.ChainConfig, witness *state.Witness) (root common.Hash, err error) {
|
||||
rawDb := rawdb.NewMemoryDatabase()
|
||||
if err := witness.PopulateDB(rawDb); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
_, prestateRoot := rawdb.ReadAccountTrieNode(rawDb, nil)
|
||||
|
||||
db, err := state.New(prestateRoot, state.NewDatabaseWithConfig(rawDb, trie.PathDefaults), nil)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
engine := beacon.New(ethash.NewFaker())
|
||||
validator := core.NewStatelessBlockValidator(chainCfg, engine)
|
||||
chainCtx := core.NewStatelessChainContext(rawDb, engine)
|
||||
processor := core.NewStatelessStateProcessor(chainCfg, chainCtx, engine)
|
||||
|
||||
receipts, _, usedGas, err := processor.ProcessStateless(witness, witness.Block, db, vm.Config{})
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// compute the state root. skip validation of computed root against
|
||||
// the one provided in the block because this value is omitted from
|
||||
// the witness.
|
||||
if root, err = validator.ValidateState(witness.Block, db, receipts, usedGas, false); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// TODO: how to differentiate between errors that are definitely not consensus-failure caused, and ones
|
||||
// that could be?
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// RunLocalServer runs an http server at the specified port (or 0 to use a random port).
|
||||
// The server provides a POST endpoint /verify_block which takes input as an RLP-encoded
|
||||
// block witness proof in the body, executes the block proof and returns the computed state root.
|
||||
func RunLocalServer(chainConfig *params.ChainConfig, port int) (closeChan chan<- struct{}, actualPort int, err error) {
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/verify_block", &verifyHandler{chainConfig})
|
||||
srv := http.Server{Handler: mux}
|
||||
listener, err := net.Listen("tcp", ":"+fmt.Sprintf("%d", port))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
actualPort = listener.Addr().(*net.TCPAddr).Port
|
||||
|
||||
go func() {
|
||||
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
closeCh := make(chan struct{})
|
||||
go func() {
|
||||
select {
|
||||
case <-closeCh:
|
||||
if err := srv.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return closeCh, actualPort, nil
|
||||
}
|
||||
|
||||
type verifyHandler struct {
|
||||
chainConfig *params.ChainConfig
|
||||
}
|
||||
|
||||
func (v *verifyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
respError := func(descr string, err error) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if _, err := w.Write([]byte(fmt.Sprintf("%s: %s", descr, err))); err != nil {
|
||||
log.Error("write failed", "error", err)
|
||||
}
|
||||
|
||||
log.Error("responded with error", "descr", descr, "error", err)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
respError("error reading body", err)
|
||||
return
|
||||
}
|
||||
if len(body) == 0 {
|
||||
respError("error", fmt.Errorf("empty body"))
|
||||
return
|
||||
}
|
||||
witness, err := state.DecodeWitnessRLP(body)
|
||||
if err != nil {
|
||||
respError("error decoding body witness rlp", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
errr, _ := err.(error)
|
||||
respError("execution error", errr)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
root, err := StatelessExecute(nil, v.chainConfig, witness)
|
||||
if err != nil {
|
||||
respError("error verifying stateless proof", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if _, err := w.Write(root[:]); err != nil {
|
||||
log.Error("error writing response", "error", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -353,13 +353,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
beacon.ethone.Finalize(chain, header, state, txs, uncles, nil)
|
||||
return
|
||||
}
|
||||
// Withdrawals processing.
|
||||
for _, w := range withdrawals {
|
||||
// Convert amount from gwei to wei.
|
||||
amount := new(uint256.Int).SetUint64(w.Amount)
|
||||
amount = amount.Mul(amount, uint256.NewInt(params.GWei))
|
||||
state.AddBalance(w.Address, amount)
|
||||
}
|
||||
state.ApplyWithdrawals(withdrawals)
|
||||
// No block reward which is issued by consensus layer instead.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package core
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
|
|
@ -47,6 +48,20 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
|
|||
return validator
|
||||
}
|
||||
|
||||
// NewBlockStatelessBlockValidator returns a BlockValidator which is configured to validate stateless block witnesses
|
||||
// without the use of a full backing BlockChain
|
||||
func NewStatelessBlockValidator(config *params.ChainConfig, engine consensus.Engine) *BlockValidator {
|
||||
validator := &BlockValidator{
|
||||
config: config,
|
||||
engine: engine,
|
||||
bc: &BlockChain{
|
||||
chainConfig: config,
|
||||
engine: engine,
|
||||
},
|
||||
}
|
||||
return validator
|
||||
}
|
||||
|
||||
// ValidateBody validates the given block's uncles and verifies the block
|
||||
// header's transaction and uncle roots. The headers are assumed to be already
|
||||
// validated at this point.
|
||||
|
|
@ -121,28 +136,30 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
|||
|
||||
// ValidateState validates the various changes that happen after a state transition,
|
||||
// such as amount of used gas, the receipt roots and the state root itself.
|
||||
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error {
|
||||
// It returns the computed state root or an error.
|
||||
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, rootCheck bool) (root common.Hash, err error) {
|
||||
header := block.Header()
|
||||
if block.GasUsed() != usedGas {
|
||||
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
|
||||
return root, fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
|
||||
}
|
||||
// Validate the received block's bloom with the one derived from the generated receipts.
|
||||
// For valid blocks this should always validate to true.
|
||||
rbloom := types.CreateBloom(receipts)
|
||||
if rbloom != header.Bloom {
|
||||
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
|
||||
return root, fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
|
||||
}
|
||||
// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
|
||||
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
|
||||
if receiptSha != header.ReceiptHash {
|
||||
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
|
||||
return root, fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
|
||||
}
|
||||
// Validate the state root against the received state root and throw
|
||||
// an error if they don't match.
|
||||
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
|
||||
return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
|
||||
// Compute the state root and if enabled, check it against the
|
||||
// received state root and throw an error if they don't match.
|
||||
root = statedb.IntermediateRoot(v.config.IsEIP158(header.Number))
|
||||
if rootCheck && header.Root != root {
|
||||
return root, fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
|
||||
}
|
||||
return nil
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// CalcGasLimit computes the gas limit of the next block after parent. It aims
|
||||
|
|
|
|||
|
|
@ -259,6 +259,17 @@ type BlockChain struct {
|
|||
processor Processor // Block transaction processor interface
|
||||
forker *ForkChoice
|
||||
vmConfig vm.Config
|
||||
|
||||
crossValidator *crossValidator
|
||||
}
|
||||
|
||||
// NewBlockchainWithCrossValidator returns a Blockchain configured to cross-validate imported blocks against a single
|
||||
// remote stateless validator. Stateless witnesses are constructed for each imported block and validated against the
|
||||
// remote endpoint. If validation fails, they are dumped to the folder specified at witnessRecordingPath
|
||||
func NewBlockchainWithCrossValidator(endpoint string, witnessRecordingPath string, db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis, overrides *ChainOverrides, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) {
|
||||
bc, err := NewBlockChain(db, cacheConfig, genesis, overrides, engine, vmConfig, shouldPreserve, txLookupLimit)
|
||||
bc.crossValidator = &crossValidator{witnessRecordingPath, endpoint}
|
||||
return bc, err
|
||||
}
|
||||
|
||||
// NewBlockChain returns a fully initialised block chain using information
|
||||
|
|
@ -1343,7 +1354,7 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
|
|||
|
||||
// writeBlockWithState writes block, metadata and corresponding state data to the
|
||||
// database.
|
||||
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error {
|
||||
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, statedb *state.StateDB) error {
|
||||
// Calculate the total difficulty of the block
|
||||
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
|
||||
if ptd == nil {
|
||||
|
|
@ -1352,6 +1363,27 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
|||
// Make sure no inconsistent state is leaked during insertion
|
||||
externTd := new(big.Int).Add(block.Difficulty(), ptd)
|
||||
|
||||
// Commit all cached state changes into underlying memory database.
|
||||
root, err := statedb.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Need to check that we want to cross-validate on all the paths this hits
|
||||
if bc.crossValidator != nil {
|
||||
witness := statedb.Witness()
|
||||
witness.Block = block
|
||||
err := bc.crossValidator.CrossValidateBlock(bc.chainConfig, witness)
|
||||
if err != nil {
|
||||
log.Error("failed to cross validate block", "error", err)
|
||||
if innerErr := state.DumpBlockWitnessToFile(bc.Config(), statedb.Witness(), "block-dump"); innerErr != nil {
|
||||
log.Error("failed to store witness to file", "error", innerErr)
|
||||
}
|
||||
// TODO: return the error and stop importing the current chain.
|
||||
// I'm leaving it like this so I can watch validation errors as
|
||||
// the client follows the chain.
|
||||
}
|
||||
}
|
||||
// Irrelevant of the canonical status, write the block itself to the database.
|
||||
//
|
||||
// Note all the components of block(td, hash->number map, header, body, receipts)
|
||||
|
|
@ -1360,15 +1392,11 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
|||
rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd)
|
||||
rawdb.WriteBlock(blockBatch, block)
|
||||
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
||||
rawdb.WritePreimages(blockBatch, state.Preimages())
|
||||
rawdb.WritePreimages(blockBatch, statedb.Preimages())
|
||||
if err := blockBatch.Write(); err != nil {
|
||||
log.Crit("Failed to write block into disk", "err", err)
|
||||
}
|
||||
// Commit all cached state changes into underlying memory database.
|
||||
root, err := state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If node is running in path mode, skip explicit gc operation
|
||||
// which is unnecessary in this mode.
|
||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||
|
|
@ -1442,8 +1470,8 @@ func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types
|
|||
|
||||
// writeBlockAndSetHead is the internal implementation of WriteBlockAndSetHead.
|
||||
// This function expects the chain mutex to be held.
|
||||
func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
|
||||
if err := bc.writeBlockWithState(block, receipts, state); err != nil {
|
||||
func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, db *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
|
||||
if err := bc.writeBlockWithState(block, receipts, db); err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
currentBlock := bc.CurrentBlock()
|
||||
|
|
@ -1561,6 +1589,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
var (
|
||||
stats = insertStats{startTime: mclock.Now()}
|
||||
lastCanon *types.Block
|
||||
statedb *state.StateDB
|
||||
err error
|
||||
)
|
||||
// Fire a single chain head event if we've progressed the chain
|
||||
defer func() {
|
||||
|
|
@ -1736,7 +1766,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
if parent == nil {
|
||||
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
||||
}
|
||||
statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
|
||||
|
||||
if bc.crossValidator != nil {
|
||||
statedb, err = state.NewWithWitnessRecording(parent.Root, bc.stateCache, bc.snaps)
|
||||
} else {
|
||||
statedb, err = state.New(parent.Root, bc.stateCache, bc.snaps)
|
||||
}
|
||||
if err != nil {
|
||||
return it.index, err
|
||||
}
|
||||
|
|
@ -1774,7 +1809,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
ptime := time.Since(pstart)
|
||||
|
||||
vstart := time.Now()
|
||||
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil {
|
||||
if _, err := bc.validator.ValidateState(block, statedb, receipts, usedGas, true); err != nil {
|
||||
bc.reportBlock(block, receipts, err)
|
||||
followupInterrupt.Store(true)
|
||||
return it.index, err
|
||||
|
|
@ -1809,6 +1844,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
} else {
|
||||
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
|
||||
}
|
||||
|
||||
followupInterrupt.Store(true)
|
||||
if err != nil {
|
||||
return it.index, err
|
||||
|
|
|
|||
66
core/cross_validator.go
Normal file
66
core/cross_validator.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
)
|
||||
|
||||
// crossValidator holds configuration for stateless cross-validation of imported blocks.
|
||||
type crossValidator struct {
|
||||
// HTTP endpoint for stateless block validation (in the future this will be a list)
|
||||
endpoint string
|
||||
// path to dump witnesses to disk when cross-validation fails
|
||||
witnessRecordingPath string
|
||||
}
|
||||
|
||||
// CrossValidateBlock verifies the given stateless witness using the configured cross validater endpoint.
|
||||
// If cross-validation fails, it dumps the witness to a file on disk.
|
||||
//
|
||||
// TODO: differentiate between errors from witness verification (maybe consensus
|
||||
// failure) and anything else.
|
||||
func (c *crossValidator) CrossValidateBlock(chainConfig *params.ChainConfig, witness *state.Witness) error {
|
||||
// encode the witness to RLP, zeroing-out the block state root before sending
|
||||
// it for cross validation to make it impossible for a cross-validator to
|
||||
// produce a correct validation result without computing it.
|
||||
enc, _ := witness.EncodeRLP()
|
||||
|
||||
// TODO: implement retry if endpoint can't be reached
|
||||
p, err := url.JoinPath(c.endpoint, "verify_block")
|
||||
if err != nil {
|
||||
return fmt.Errorf("url.JoinPath failed: %v", err)
|
||||
}
|
||||
resp, err := http.Post(p, "application/octet-stream", bytes.NewBuffer(enc))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error accessing block verification endpoint: %v", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
return fmt.Errorf("cross-validator bad response code (%d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
if bytes.Compare(body, witness.Block.Header().Root[:]) != 0 {
|
||||
if errInner := state.DumpBlockWitnessToFile(chainConfig, witness, c.witnessRecordingPath); errInner != nil {
|
||||
log.Error("failed to dump block to file", "error", errInner)
|
||||
panic("should not happen")
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CrossValidate posts the provided witness to the URL at {endpoint}/verify_block and returns whether the remote
|
||||
// verification was successful or not.
|
||||
23
core/evm.go
23
core/evm.go
|
|
@ -19,6 +19,8 @@ package core
|
|||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||
|
|
@ -39,6 +41,16 @@ type ChainContext interface {
|
|||
|
||||
// NewEVMBlockContext creates a new context for use in the EVM.
|
||||
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
|
||||
return newEVMBlockContext(nil, header, chain, author)
|
||||
}
|
||||
|
||||
// NewStatelessEVMBlockContext creates a new context for use in the EVM in stateless execution mode. The BLOCKHASH
|
||||
// opcode sources block hashes from the provided witness in stateless execution mode.
|
||||
func NewStatelessEVMBlockContext(witness *state.Witness, header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
|
||||
return newEVMBlockContext(witness, header, chain, author)
|
||||
}
|
||||
|
||||
func newEVMBlockContext(witness *state.Witness, header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
|
||||
var (
|
||||
beneficiary common.Address
|
||||
baseFee *big.Int
|
||||
|
|
@ -61,10 +73,19 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
|||
if header.Difficulty.Cmp(common.Big0) == 0 {
|
||||
random = &header.MixDigest
|
||||
}
|
||||
var getHash vm.GetHashFunc
|
||||
if witness != nil {
|
||||
getHash = func(n uint64) common.Hash {
|
||||
return witness.GetBlockHash(n)
|
||||
}
|
||||
} else {
|
||||
getHash = GetHashFn(header, chain)
|
||||
}
|
||||
|
||||
return vm.BlockContext{
|
||||
CanTransfer: CanTransfer,
|
||||
Transfer: Transfer,
|
||||
GetHash: GetHashFn(header, chain),
|
||||
GetHash: getHash,
|
||||
Coinbase: beneficiary,
|
||||
BlockNumber: new(big.Int).Set(header.Number),
|
||||
Time: header.Time,
|
||||
|
|
|
|||
|
|
@ -126,6 +126,13 @@ type Trie interface {
|
|||
// be created with new root and updated trie database for following usage
|
||||
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)
|
||||
|
||||
// CommitAndObtainAccessList does the same thing as Commit and returns an
|
||||
// access list map of trie nodes read from the database.
|
||||
CommitAndObtainAccessList(collectLeaf bool) (common.Hash, *trienode.NodeSet, map[string][]byte, error)
|
||||
|
||||
// AccessList returns a map of trie node read from the database.
|
||||
AccessList() map[string][]byte
|
||||
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -194,6 +194,13 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
|||
err error
|
||||
value common.Hash
|
||||
)
|
||||
|
||||
if s.db.witness != nil && s.db.snap != nil && s.origin != nil {
|
||||
// when building a witness with snapshot enabled, prefetch all read slots to be collected
|
||||
// and included in the witness when the block root hash is committed (intermediateroot/commit?)
|
||||
s.db.readPrefetcher.prefetch(s.addrHash, s.origin.Root, s.address, [][]byte{key[:]})
|
||||
}
|
||||
|
||||
if s.db.snap != nil {
|
||||
start := time.Now()
|
||||
enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes()))
|
||||
|
|
@ -217,6 +224,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
|||
return common.Hash{}
|
||||
}
|
||||
val, err := tr.GetStorage(s.address, key.Bytes())
|
||||
//fmt.Printf("trie access list is %v\n", tr.AccessList())
|
||||
if metrics.EnabledExpensive {
|
||||
s.db.StorageReads += time.Since(start)
|
||||
}
|
||||
|
|
@ -379,11 +387,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 {
|
||||
|
|
@ -392,15 +400,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.
|
||||
|
|
|
|||
411
core/state/state_witness.go
Normal file
411
core/state/state_witness.go
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (w *Witness) GetBlockHash(num uint64) common.Hash {
|
||||
return w.blockHashes[num]
|
||||
}
|
||||
|
||||
func (w *Witness) Root() common.Hash {
|
||||
return w.root
|
||||
}
|
||||
|
||||
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
|
||||
// TODO bug: adding a code hash before executing the same account later would result in the account's code
|
||||
// not being added to the witness. this should be covered in state tests?
|
||||
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()
|
||||
}
|
||||
|
||||
// Hash returns the sha256 hash of a witness
|
||||
func (w *Witness) Hash() common.Hash {
|
||||
res, err := rlp.EncodeToBytes(w.sortedWitness())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return common.Hash(sha256.Sum256(res[:]))
|
||||
}
|
||||
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
// DumpBlockWitnessToFile serializes a witness object and writes it and the provided chain config to files on
|
||||
// a given path.
|
||||
func DumpBlockWitnessToFile(cfg *params.ChainConfig, w *Witness, path string) error {
|
||||
enc, _ := w.EncodeRLP()
|
||||
|
||||
blockHash := w.Block.Hash()
|
||||
witnessOutputFName := fmt.Sprintf("%d-%x.rlp", w.Block.NumberU64(), blockHash[0:8])
|
||||
witnessPath := filepath.Join(path, witnessOutputFName)
|
||||
err := os.WriteFile(witnessPath, enc, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfgOutputFName := fmt.Sprintf("%d-%x-chaincfg.json", w.Block.NumberU64(), blockHash[0:8])
|
||||
cfgPath := filepath.Join(path, cfgOutputFName)
|
||||
f, err := os.OpenFile(cfgPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
cfgWriter := json.NewEncoder(f)
|
||||
cfgWriter.Encode(cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PopulateDB imports trie nodes from the witness
|
||||
// into the specified backing database.
|
||||
func (w *Witness) PopulateDB(db ethdb.Database) error {
|
||||
batch := db.NewBatch()
|
||||
for owner, nodes := range w.lists {
|
||||
for path, node := range nodes {
|
||||
if owner == (common.Hash{}) {
|
||||
rawdb.WriteAccountTrieNode(batch, []byte(path), node)
|
||||
} else {
|
||||
rawdb.WriteStorageTrieNode(batch, owner, []byte(path), node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for blockNum, blockHash := range w.blockHashes {
|
||||
fakeHeader := types.Header{}
|
||||
fakeHeader.ParentHash = blockHash
|
||||
fakeHeader.Number = new(big.Int).SetUint64(blockNum)
|
||||
rawdb.WriteHeader(batch, &fakeHeader)
|
||||
}
|
||||
|
||||
for codeHash, code := range w.codes {
|
||||
rawdb.WriteCode(batch, codeHash, code)
|
||||
}
|
||||
|
||||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -138,6 +138,20 @@ type StateDB struct {
|
|||
|
||||
// Testing hooks
|
||||
onCommit func(states *triestate.Set) // Hook invoked when commit is performed
|
||||
|
||||
witness *Witness
|
||||
readPrefetcher *triePrefetcher
|
||||
}
|
||||
|
||||
// NewWithWitnessRecording creates a new state from a given trie. The state is configured to construct a stateless
|
||||
// block witness which is completed after Commit is called.
|
||||
func NewWithWitnessRecording(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
|
||||
sdb, err := New(root, db, snaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sdb.witness = NewWitness(root)
|
||||
return sdb, nil
|
||||
}
|
||||
|
||||
// New creates a new state from a given trie.
|
||||
|
|
@ -177,11 +191,20 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
|||
// commit phase, most of the needed data is already hot.
|
||||
func (s *StateDB) StartPrefetcher(namespace string) {
|
||||
if s.prefetcher != nil {
|
||||
s.prefetcher.wait()
|
||||
s.prefetcher.close()
|
||||
s.prefetcher = nil
|
||||
}
|
||||
if s.readPrefetcher != nil {
|
||||
s.readPrefetcher.wait()
|
||||
s.readPrefetcher.close()
|
||||
s.readPrefetcher = nil
|
||||
}
|
||||
if s.snap != nil {
|
||||
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
|
||||
if s.witness != nil {
|
||||
s.readPrefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,9 +212,15 @@ func (s *StateDB) StartPrefetcher(namespace string) {
|
|||
// from the gathered metrics.
|
||||
func (s *StateDB) StopPrefetcher() {
|
||||
if s.prefetcher != nil {
|
||||
s.prefetcher.wait()
|
||||
s.prefetcher.close()
|
||||
s.prefetcher = nil
|
||||
}
|
||||
if s.readPrefetcher != nil {
|
||||
s.readPrefetcher.wait()
|
||||
s.readPrefetcher.close()
|
||||
s.readPrefetcher = nil
|
||||
}
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
|
|
@ -350,7 +379,8 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
|||
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.GetCommittedState(hash)
|
||||
res := stateObject.GetCommittedState(hash)
|
||||
return res
|
||||
}
|
||||
return common.Hash{}
|
||||
}
|
||||
|
|
@ -604,9 +634,15 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Insert into the live set
|
||||
obj := newObject(s, addr, data)
|
||||
s.setStateObject(obj)
|
||||
if s.witness != nil && s.snap != nil {
|
||||
// when building witness with snap enabled, prefetch all read accounts to later be collected and
|
||||
// included in the witness
|
||||
s.readPrefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, [][]byte{addr[:]})
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
|
|
@ -712,6 +748,9 @@ func (s *StateDB) Copy() *StateDB {
|
|||
snaps: s.snaps,
|
||||
snap: s.snap,
|
||||
}
|
||||
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),
|
||||
|
|
@ -866,6 +905,33 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
s.clearJournalAndRefund()
|
||||
}
|
||||
|
||||
func (s *StateDB) collectReadStorageAccessLists() {
|
||||
for _, obj := range s.stateObjects {
|
||||
// load read storage slots from the finished trie in the prefetcher as these continue to be prefetched
|
||||
// until commit.
|
||||
tr := s.readPrefetcher.trie(obj.addrHash, obj.data.Root)
|
||||
if tr == nil {
|
||||
continue
|
||||
}
|
||||
accessList := tr.AccessList()
|
||||
if len(accessList) > 0 {
|
||||
s.witness.addAccessList(obj.addrHash, accessList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StateDB) collectReadAccountsAccessLists() {
|
||||
tr := s.readPrefetcher.trie(common.Hash{}, s.originalRoot)
|
||||
if tr == nil {
|
||||
// TODO: ensure this case is b/c of empty block
|
||||
return
|
||||
}
|
||||
accessList := tr.AccessList()
|
||||
if len(accessList) > 0 {
|
||||
s.witness.addAccessList(common.Hash{}, accessList)
|
||||
}
|
||||
}
|
||||
|
||||
// IntermediateRoot computes the current root hash of the state trie.
|
||||
// It is called in between transactions to get the root hash that
|
||||
// goes into transaction receipts.
|
||||
|
|
@ -873,20 +939,24 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
// Finalise all the dirty storage states and write them into the tries
|
||||
s.Finalise(deleteEmptyObjects)
|
||||
|
||||
// If there was a trie prefetcher operating, it gets aborted and irrevocably
|
||||
// modified after we start retrieving tries. Remove it from the statedb after
|
||||
// this round of use.
|
||||
//
|
||||
// This is weird pre-byzantium since the first tx runs with a prefetcher and
|
||||
// the remainder without, but pre-byzantium even the initial prefetcher is
|
||||
// useless, so no sleep lost.
|
||||
prefetcher := s.prefetcher
|
||||
if s.prefetcher != nil {
|
||||
defer func() {
|
||||
// TODO: need to wait for read accounts to be resolved in prefetcher main trie?
|
||||
s.prefetcher.wait()
|
||||
s.prefetcher.close()
|
||||
s.prefetcher = nil
|
||||
}()
|
||||
}
|
||||
if s.readPrefetcher != nil {
|
||||
// TODO: move read prefetcher logic into Commit?
|
||||
s.readPrefetcher.wait()
|
||||
s.collectReadStorageAccessLists()
|
||||
s.collectReadAccountsAccessLists()
|
||||
s.readPrefetcher.close()
|
||||
s.readPrefetcher = nil
|
||||
}
|
||||
|
||||
// Although naively it makes sense to retrieve the account trie and then do
|
||||
// the contract storage and account updates sequentially, that short circuits
|
||||
// the account prefetcher. Instead, let's process all the storage updates
|
||||
|
|
@ -897,6 +967,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
obj.updateRoot()
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
@ -906,16 +977,29 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
}
|
||||
}
|
||||
usedAddrs := make([][]byte, 0, len(s.stateObjectsPending))
|
||||
|
||||
// perform updates before deletions. In the case where a full node
|
||||
// has two children, one of them selfdestructs and makes the recipient
|
||||
// another non-existing sibling, applying deletion before update would
|
||||
// result in the unecessary premature collapse of the full node into a short node
|
||||
// for the untouched third sibling.
|
||||
var deletedObjects []*stateObject
|
||||
for addr := range s.stateObjectsPending {
|
||||
if obj := s.stateObjects[addr]; obj.deleted {
|
||||
s.deleteStateObject(obj)
|
||||
s.AccountDeleted += 1
|
||||
} else {
|
||||
if obj := s.stateObjects[addr]; !obj.deleted {
|
||||
s.updateStateObject(obj)
|
||||
s.AccountUpdated += 1
|
||||
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
|
||||
} else {
|
||||
deletedObjects = append(deletedObjects, obj)
|
||||
}
|
||||
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
|
||||
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:]))
|
||||
}
|
||||
for _, deletedObj := range deletedObjects {
|
||||
s.deleteStateObject(deletedObj)
|
||||
s.AccountDeleted += 1
|
||||
usedAddrs = append(usedAddrs, common.CopyBytes(deletedObj.address[:])) // Copy needed for closure
|
||||
}
|
||||
|
||||
if prefetcher != nil {
|
||||
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
|
||||
}
|
||||
|
|
@ -929,6 +1013,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
return s.trie.Hash()
|
||||
}
|
||||
|
||||
// makes the statedb configure to build a stateless block witness
|
||||
// this must be called after initializing a statedb and before
|
||||
// starting prefetchers or applying state changes
|
||||
func (s *StateDB) EnableWitnessRecording() {
|
||||
s.witness = NewWitness(s.originalRoot)
|
||||
}
|
||||
|
||||
// SetTxContext sets the current transaction hash and index which are
|
||||
// used when the EVM emits new state logs. It should be invoked before
|
||||
// transaction execution.
|
||||
|
|
@ -1153,6 +1244,28 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A
|
|||
return incomplete, nil
|
||||
}
|
||||
|
||||
// Witness returns a block witness object being constructed or nil if the
|
||||
// StateDB instance is not configured to record stateless witnesses.
|
||||
func (s *StateDB) Witness() *Witness {
|
||||
return s.witness
|
||||
}
|
||||
|
||||
// ApplyWithdrawals credits the balance of each account that is the recipient
|
||||
// of a withdrawal.
|
||||
func (s *StateDB) ApplyWithdrawals(withdrawals types.Withdrawals) {
|
||||
for _, w := range withdrawals {
|
||||
// Convert amount from gwei to wei.
|
||||
amount := new(big.Int).SetUint64(w.Amount)
|
||||
amount = amount.Mul(amount, big.NewInt(params.GWei))
|
||||
s.AddBalance(w.Address, amount)
|
||||
}
|
||||
|
||||
if s.witness != nil {
|
||||
al := s.trie.AccessList()
|
||||
s.witness.addAccessList(common.Hash{}, al)
|
||||
}
|
||||
}
|
||||
|
||||
// Commit writes the state to the underlying in-memory trie database.
|
||||
// Once the state is committed, tries cached in stateDB (including account
|
||||
// trie, storage tries) will no longer be functional. A new state instance
|
||||
|
|
@ -1177,6 +1290,8 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
|||
storageTrieNodesDeleted int
|
||||
nodes = trienode.NewMergedNodeSet()
|
||||
codeWriter = s.db.DiskDB().NewBatch()
|
||||
root common.Hash
|
||||
set *trienode.NodeSet
|
||||
)
|
||||
// Handle all state deletions first
|
||||
incomplete, err := s.handleDestruction(nodes)
|
||||
|
|
@ -1184,6 +1299,31 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
|||
return common.Hash{}, err
|
||||
}
|
||||
// Handle all state updates afterwards
|
||||
if s.witness != nil {
|
||||
if s.snap == nil {
|
||||
// if the snapshot is not in use, all read state values and their intermediate
|
||||
// nodes are resolved in the statedb/object tries.
|
||||
//
|
||||
// we collect access lists after commit because there are circumstances where
|
||||
// computation of a new trie root hash where a value has been deleted could
|
||||
// collapse a parent branch node + sibling into a full node for the sibling.
|
||||
// In this case, the sibling would only be read during root hash computation.
|
||||
// TODO: verify the above assertion is the case.
|
||||
for addr := range s.stateObjects {
|
||||
obj := s.stateObjects[addr]
|
||||
if _, ok := s.stateObjectsDirty[addr]; ok && !obj.deleted {
|
||||
// collect dirty object access witness if/when we commit them
|
||||
continue
|
||||
}
|
||||
if obj.trie != nil {
|
||||
al := obj.trie.AccessList()
|
||||
s.witness.addAccessList(obj.addrHash, al)
|
||||
}
|
||||
}
|
||||
accessList := s.trie.AccessList()
|
||||
s.witness.addAccessList(common.Hash{}, accessList)
|
||||
}
|
||||
}
|
||||
for addr := range s.stateObjectsDirty {
|
||||
obj := s.stateObjects[addr]
|
||||
if obj.deleted {
|
||||
|
|
@ -1194,11 +1334,19 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
|||
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
|
||||
obj.dirtyCode = false
|
||||
}
|
||||
|
||||
// Write any storage changes in the state object to its storage trie
|
||||
set, err := obj.commit()
|
||||
set, accessList, err := obj.commit()
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
if s.witness != nil {
|
||||
// storage trie nodes for writes accrue in the state object's trie instance
|
||||
// storage trie nodes from read slots are accrued in the prefetcher and
|
||||
// retrieved in IntermediateRoot
|
||||
s.witness.addAccessList(obj.addrHash, accessList)
|
||||
}
|
||||
// Merge the dirty nodes of storage trie into global set. It is possible
|
||||
// that the account was destructed and then resurrected in the same block.
|
||||
// In this case, the node set is shared by both accounts.
|
||||
|
|
@ -1221,7 +1369,14 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
|||
if metrics.EnabledExpensive {
|
||||
start = time.Now()
|
||||
}
|
||||
root, set, err := s.trie.Commit(true)
|
||||
|
||||
if s.witness != nil {
|
||||
var accessList map[string][]byte
|
||||
root, set, accessList, err = s.trie.CommitAndObtainAccessList(true)
|
||||
s.witness.addAccessList(common.Hash{}, accessList)
|
||||
} else {
|
||||
root, set, err = s.trie.Commit(true)
|
||||
}
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
|
@ -1295,6 +1450,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
|||
s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
||||
s.stateObjectsDirty = make(map[common.Address]struct{})
|
||||
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -70,13 +71,16 @@ func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePre
|
|||
}
|
||||
return p
|
||||
}
|
||||
func (p *triePrefetcher) wait() {
|
||||
for _, fetcher := range p.fetchers {
|
||||
fetcher.wait()
|
||||
}
|
||||
}
|
||||
|
||||
// close iterates over all the subfetchers, aborts any that were left spinning
|
||||
// and reports the stats to the metrics subsystem.
|
||||
func (p *triePrefetcher) close() {
|
||||
for _, fetcher := range p.fetchers {
|
||||
fetcher.abort() // safe to do multiple times
|
||||
|
||||
if metrics.Enabled {
|
||||
if fetcher.root == p.root {
|
||||
p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
|
||||
|
|
@ -123,29 +127,22 @@ func (p *triePrefetcher) copy() *triePrefetcher {
|
|||
storageSkipMeter: p.storageSkipMeter,
|
||||
storageWasteMeter: p.storageWasteMeter,
|
||||
}
|
||||
// If the prefetcher is already a copy, duplicate the data
|
||||
if p.fetches != nil {
|
||||
for root, fetch := range p.fetches {
|
||||
if fetch == nil {
|
||||
continue
|
||||
}
|
||||
copy.fetches[root] = p.db.CopyTrie(fetch)
|
||||
}
|
||||
return copy
|
||||
}
|
||||
// Otherwise we're copying an active fetcher, retrieve the current states
|
||||
for id, fetcher := range p.fetchers {
|
||||
copy.fetches[id] = fetcher.peek()
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
||||
// prefetch schedules a batch of trie items to prefetch.
|
||||
// prefetch is called from two locations:
|
||||
// 1. Finalize of the state-objects storage roots. This happens at the end
|
||||
// of every transaction, meaning that if several transactions touches
|
||||
// upon the same contract, the parameters invoking this method may be
|
||||
// repeated.
|
||||
// 2. Finalize of the main account trie. This happens only once per block.
|
||||
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) {
|
||||
// If the prefetcher is an inactive one, bail out
|
||||
if p.fetches != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Active fetcher, schedule the retrievals
|
||||
id := p.trieID(owner, root)
|
||||
fetcher := p.fetchers[id]
|
||||
|
|
@ -175,16 +172,13 @@ func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
|
|||
p.deliveryMissMeter.Mark(1)
|
||||
return nil
|
||||
}
|
||||
// Interrupt the prefetcher if it's by any chance still running and return
|
||||
// a copy of any pre-loaded trie.
|
||||
fetcher.abort() // safe to do multiple times
|
||||
|
||||
trie := fetcher.peek()
|
||||
if trie == nil {
|
||||
// Wait for the fetcher to finish
|
||||
fetcher.wait() // safe to do multiple times
|
||||
if fetcher.trie == nil {
|
||||
p.deliveryMissMeter.Mark(1)
|
||||
return nil
|
||||
}
|
||||
return trie
|
||||
return fetcher.db.CopyTrie(fetcher.trie)
|
||||
}
|
||||
|
||||
// used marks a batch of state items used to allow creating statistics as to
|
||||
|
|
@ -215,13 +209,12 @@ type subfetcher struct {
|
|||
addr common.Address // Address of the account that the trie belongs to
|
||||
trie Trie // Trie being populated with nodes
|
||||
|
||||
tasks [][]byte // Items queued up for retrieval
|
||||
lock sync.Mutex // Lock protecting the task queue
|
||||
tasks [][]byte // Items queued up for retrieval
|
||||
lock sync.Mutex // Lock protecting the task queue
|
||||
closing bool // set to true if the subfetcher is closing
|
||||
|
||||
wake chan struct{} // Wake channel if a new task is scheduled
|
||||
stop chan struct{} // Channel to interrupt processing
|
||||
term chan struct{} // Channel to signal interruption
|
||||
copy chan chan Trie // Channel to request a copy of the current trie
|
||||
wake chan bool // Wake channel if a new task is scheduled, true if the subfetcher should continue running when there are no pending tasks
|
||||
term chan struct{} // Channel to signal interruption
|
||||
|
||||
seen map[string]struct{} // Tracks the entries already loaded
|
||||
dups int // Number of duplicate preload tasks
|
||||
|
|
@ -237,10 +230,8 @@ func newSubfetcher(db Database, state common.Hash, owner common.Hash, root commo
|
|||
owner: owner,
|
||||
root: root,
|
||||
addr: addr,
|
||||
wake: make(chan struct{}, 1),
|
||||
stop: make(chan struct{}),
|
||||
wake: make(chan bool, 1),
|
||||
term: make(chan struct{}),
|
||||
copy: make(chan chan Trie),
|
||||
seen: make(map[string]struct{}),
|
||||
}
|
||||
go sf.loop()
|
||||
|
|
@ -251,42 +242,30 @@ func newSubfetcher(db Database, state common.Hash, owner common.Hash, root commo
|
|||
func (sf *subfetcher) schedule(keys [][]byte) {
|
||||
// Append the tasks to the current queue
|
||||
sf.lock.Lock()
|
||||
|
||||
sf.tasks = append(sf.tasks, keys...)
|
||||
sf.lock.Unlock()
|
||||
|
||||
// Notify the prefetcher, it's fine if it's already terminated
|
||||
// Notify the prefetcher. The wake-chan is buffered, so this is async.
|
||||
select {
|
||||
case sf.wake <- struct{}{}:
|
||||
case sf.wake <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// peek tries to retrieve a deep copy of the fetcher's trie in whatever form it
|
||||
// is currently.
|
||||
func (sf *subfetcher) peek() Trie {
|
||||
ch := make(chan Trie)
|
||||
select {
|
||||
case sf.copy <- ch:
|
||||
// Subfetcher still alive, return copy from it
|
||||
return <-ch
|
||||
|
||||
case <-sf.term:
|
||||
// Subfetcher already terminated, return a copy directly
|
||||
if sf.trie == nil {
|
||||
return nil
|
||||
}
|
||||
return sf.db.CopyTrie(sf.trie)
|
||||
}
|
||||
}
|
||||
|
||||
// abort interrupts the subfetcher immediately. It is safe to call abort multiple
|
||||
// wait waits for the subfetcher to finish it's task. It is safe to call wait multiple
|
||||
// times but it is not thread safe.
|
||||
func (sf *subfetcher) abort() {
|
||||
select {
|
||||
case <-sf.stop:
|
||||
default:
|
||||
close(sf.stop)
|
||||
func (sf *subfetcher) wait() {
|
||||
// Signal termination by nil tasks
|
||||
sf.lock.Lock()
|
||||
if sf.closing {
|
||||
sf.lock.Unlock()
|
||||
return // already exiting
|
||||
}
|
||||
sf.closing = true
|
||||
sf.lock.Unlock()
|
||||
// Notify the prefetcher. The wake-chan is buffered, so this is async.
|
||||
sf.wake <- false
|
||||
// Wait for it to terminate
|
||||
<-sf.term
|
||||
}
|
||||
|
||||
|
|
@ -316,50 +295,32 @@ func (sf *subfetcher) loop() {
|
|||
}
|
||||
// Trie opened successfully, keep prefetching items
|
||||
for {
|
||||
select {
|
||||
case <-sf.wake:
|
||||
// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
|
||||
sf.lock.Lock()
|
||||
tasks := sf.tasks
|
||||
sf.tasks = nil
|
||||
sf.lock.Unlock()
|
||||
keepRunning := <-sf.wake
|
||||
if !keepRunning {
|
||||
return
|
||||
}
|
||||
// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
|
||||
sf.lock.Lock()
|
||||
tasks := sf.tasks
|
||||
sf.tasks = nil
|
||||
sf.lock.Unlock()
|
||||
|
||||
// Prefetch any tasks until the loop is interrupted
|
||||
for i, task := range tasks {
|
||||
select {
|
||||
case <-sf.stop:
|
||||
// If termination is requested, add any leftover back and return
|
||||
sf.lock.Lock()
|
||||
sf.tasks = append(sf.tasks, tasks[i:]...)
|
||||
sf.lock.Unlock()
|
||||
return
|
||||
|
||||
case ch := <-sf.copy:
|
||||
// Somebody wants a copy of the current trie, grant them
|
||||
ch <- sf.db.CopyTrie(sf.trie)
|
||||
|
||||
default:
|
||||
// No termination request yet, prefetch the next entry
|
||||
if _, ok := sf.seen[string(task)]; ok {
|
||||
sf.dups++
|
||||
} else {
|
||||
if len(task) == common.AddressLength {
|
||||
sf.trie.GetAccount(common.BytesToAddress(task))
|
||||
} else {
|
||||
sf.trie.GetStorage(sf.addr, task)
|
||||
}
|
||||
sf.seen[string(task)] = struct{}{}
|
||||
}
|
||||
// Prefetch all tasks
|
||||
for _, task := range tasks {
|
||||
if _, ok := sf.seen[string(task)]; ok {
|
||||
sf.dups++
|
||||
continue
|
||||
}
|
||||
if len(task) == common.AddressLength {
|
||||
sf.trie.GetAccount(common.BytesToAddress(task))
|
||||
} else {
|
||||
_, err := sf.trie.GetStorage(sf.addr, task)
|
||||
if err != nil {
|
||||
// TODO: see what needs to be done in this case
|
||||
fmt.Printf("prefetch storage failed: %+v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
case ch := <-sf.copy:
|
||||
// Somebody wants a copy of the current trie, grant them
|
||||
ch <- sf.db.CopyTrie(sf.trie)
|
||||
|
||||
case <-sf.stop:
|
||||
// Termination is requested, abort and leave remaining tasks
|
||||
return
|
||||
sf.seen[string(task)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
|
|
@ -36,9 +38,10 @@ import (
|
|||
//
|
||||
// StateProcessor implements Processor.
|
||||
type StateProcessor struct {
|
||||
config *params.ChainConfig // Chain configuration options
|
||||
bc *BlockChain // Canonical block chain
|
||||
engine consensus.Engine // Consensus engine used for block rewards
|
||||
config *params.ChainConfig // Chain configuration options
|
||||
bc *BlockChain // Canonical block chain
|
||||
engine consensus.Engine // Consensus engine used for block rewards
|
||||
chainCtx *StatelessChainContext // chain context shim for stateless verification mode
|
||||
}
|
||||
|
||||
// NewStateProcessor initialises a new StateProcessor.
|
||||
|
|
@ -50,6 +53,25 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
|
|||
}
|
||||
}
|
||||
|
||||
func NewStatelessStateProcessor(config *params.ChainConfig, chainCtx *StatelessChainContext, engine consensus.Engine) *StateProcessor {
|
||||
return &StateProcessor{
|
||||
config: config,
|
||||
chainCtx: chainCtx,
|
||||
engine: engine,
|
||||
bc: &BlockChain{
|
||||
chainConfig: config,
|
||||
engine: engine,
|
||||
},
|
||||
}
|
||||
}
|
||||
func (p *StateProcessor) ProcessStateless(witness *state.Witness, block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
|
||||
return p.process(witness, block, statedb, cfg)
|
||||
}
|
||||
|
||||
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
|
||||
return p.process(nil, block, statedb, cfg)
|
||||
}
|
||||
|
||||
// Process processes the state changes according to the Ethereum rules by running
|
||||
// the transaction messages using the statedb and applying any rewards to both
|
||||
// the processor (coinbase) and any included uncles.
|
||||
|
|
@ -57,7 +79,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
|
|||
// Process returns the receipts and logs accumulated during the process and
|
||||
// returns the amount of gas that was used in the process. If any of the
|
||||
// transactions failed to execute due to insufficient gas it will return an error.
|
||||
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
|
||||
func (p *StateProcessor) process(witness *state.Witness, block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
|
||||
var (
|
||||
receipts types.Receipts
|
||||
usedGas = new(uint64)
|
||||
|
|
@ -72,10 +94,16 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
var (
|
||||
context = NewEVMBlockContext(header, p.bc, nil)
|
||||
vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
|
||||
context vm.BlockContext
|
||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||
)
|
||||
if witness != nil {
|
||||
context = NewStatelessEVMBlockContext(witness, header, p.chainCtx, nil)
|
||||
} else {
|
||||
context = NewEVMBlockContext(header, p.bc, nil)
|
||||
}
|
||||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
|
||||
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||
}
|
||||
|
|
@ -98,6 +126,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
if len(withdrawals) > 0 && !p.config.IsShanghai(block.Number(), block.Time()) {
|
||||
return nil, nil, 0, errors.New("withdrawals before shanghai")
|
||||
}
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals)
|
||||
|
||||
|
|
@ -189,3 +218,26 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat
|
|||
_, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||
statedb.Finalise(true)
|
||||
}
|
||||
|
||||
// StatelessChainContext implements a stateless chain context stub
|
||||
// which is used in place of a backing blockchain instance.
|
||||
type StatelessChainContext struct {
|
||||
chaindb ethdb.Database
|
||||
engine consensus.Engine
|
||||
}
|
||||
|
||||
func (s *StatelessChainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
|
||||
//return rawdb.ReadHeader(s.chaindb, hash, number)
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (s *StatelessChainContext) Engine() consensus.Engine {
|
||||
return s.engine
|
||||
}
|
||||
|
||||
func NewStatelessChainContext(chaindb ethdb.Database, engine consensus.Engine) *StatelessChainContext {
|
||||
return &StatelessChainContext{
|
||||
chaindb,
|
||||
engine,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
|
|
@ -33,7 +34,7 @@ type Validator interface {
|
|||
|
||||
// ValidateState validates the given statedb and optionally the receipts and
|
||||
// gas used.
|
||||
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64) error
|
||||
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, rootCheck bool) (common.Hash, error)
|
||||
}
|
||||
|
||||
// Prefetcher is an interface for pre-caching transaction signatures and state.
|
||||
|
|
|
|||
|
|
@ -319,6 +319,18 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// EncodeRLPWithZeroRoot encodes a block (with header state root set to 0x00...0) to RLP
|
||||
func (b *Block) EncodeRLPWithZeroRoot(w io.Writer) error {
|
||||
old := b.header.Root
|
||||
b.header.Root = common.Hash{}
|
||||
err := b.EncodeRLP(w)
|
||||
b.header.Root = old
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeRLP serializes a block as RLP.
|
||||
func (b *Block) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, &extblock{
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
}
|
||||
|
||||
// Fail if we're trying to transfer more than the available balance
|
||||
if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
|
|
@ -188,7 +189,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
snapshot := evm.StateDB.Snapshot()
|
||||
p, isPrecompile := evm.precompile(addr)
|
||||
debug := evm.Config.Tracer != nil
|
||||
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
||||
// Calling a non existing account, don't do anything, but ping the tracer
|
||||
|
|
@ -229,6 +229,11 @@ 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)
|
||||
codeCopy := make([]byte, len(code))
|
||||
copy(codeCopy[:], code[:])
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCodeHash(addr), codeCopy)
|
||||
}
|
||||
if len(code) == 0 {
|
||||
ret, err = nil, nil // gas is unchanged
|
||||
} else {
|
||||
|
|
@ -293,6 +298,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
|||
// 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(caller.Address()), value, gas)
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
}
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
ret, err = evm.interpreter.Run(contract, input, false)
|
||||
gas = contract.Gas
|
||||
|
|
@ -337,6 +345,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
|||
addrCopy := addr
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
}
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
ret, err = evm.interpreter.Run(contract, input, false)
|
||||
gas = contract.Gas
|
||||
|
|
@ -390,6 +401,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
|||
// 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(addrCopy), new(uint256.Int), gas)
|
||||
if witness := evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
}
|
||||
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||
|
|
|
|||
|
|
@ -342,7 +342,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())))
|
||||
address := slot.Bytes20()
|
||||
slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(address)))
|
||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||
code := interpreter.evm.StateDB.GetCode(address)
|
||||
codeHash := interpreter.evm.StateDB.GetCodeHash(address)
|
||||
witness.AddCode(codeHash, code)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -380,6 +386,10 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
|||
uint64CodeOffset = 0xffffffffffffffff
|
||||
}
|
||||
addr := common.Address(a.Bytes20())
|
||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCode(interpreter.evm.StateDB.GetCodeHash(addr), interpreter.evm.StateDB.GetCode(addr))
|
||||
}
|
||||
|
||||
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
|
||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
||||
|
||||
|
|
@ -418,6 +428,10 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
|||
if interpreter.evm.StateDB.Empty(address) {
|
||||
slot.Clear()
|
||||
} else {
|
||||
_ = interpreter.evm.StateDB.GetCode(address) // ensure the account leaf is fetched and included in the witness
|
||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||
witness.AddCodeHash(interpreter.evm.StateDB.GetCodeHash(address))
|
||||
}
|
||||
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
|
||||
}
|
||||
return nil, nil
|
||||
|
|
@ -444,7 +458,13 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
|
|||
lower = upper - 256
|
||||
}
|
||||
if num64 >= lower && num64 < upper {
|
||||
num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes())
|
||||
res := interpreter.evm.Context.GetHash(num64).Bytes()
|
||||
if witness := interpreter.evm.StateDB.Witness(); witness != nil {
|
||||
var bh common.Hash
|
||||
copy(bh[:], res[:])
|
||||
witness.AddBlockHash(bh, num64)
|
||||
}
|
||||
num.SetBytes(res[:])
|
||||
} else {
|
||||
num.Clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package vm
|
|||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -79,6 +81,8 @@ type StateDB interface {
|
|||
|
||||
AddLog(*types.Log)
|
||||
AddPreimage(common.Hash, []byte)
|
||||
|
||||
Witness() *state.Witness
|
||||
}
|
||||
|
||||
// CallContext provides a basic interface for the EVM calling conventions. The EVM
|
||||
|
|
|
|||
|
|
@ -20,8 +20,13 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
|
|
@ -443,3 +448,47 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) {
|
|||
}
|
||||
return api.eth.blockchain.GetTrieFlushInterval().String(), nil
|
||||
}
|
||||
|
||||
func BuildProof(number uint64, bc *core.BlockChain) ([]byte, error) {
|
||||
if number == 0 {
|
||||
panic("cannot build genesis block proof")
|
||||
}
|
||||
parent := bc.GetBlockByNumber(number - 1)
|
||||
db, err := bc.StateAt(parent.Header().Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.EnableWitnessRecording()
|
||||
db.StartPrefetcher("apidebug")
|
||||
block := bc.GetBlockByNumber(number)
|
||||
|
||||
logconfig := &logger.Config{
|
||||
EnableMemory: false,
|
||||
DisableStack: false,
|
||||
DisableStorage: false,
|
||||
EnableReturnData: true,
|
||||
Debug: true,
|
||||
}
|
||||
tracer := logger.NewJSONLogger(logconfig, os.Stdout)
|
||||
_ = tracer
|
||||
|
||||
stateProcessor := core.NewStateProcessor(bc.Config(), bc, bc.Engine())
|
||||
_, _, _, err = stateProcessor.Process(block, db, vm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = db.Commit(block.NumberU64(), true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proof := db.Witness()
|
||||
proof.Block = block
|
||||
enc, err := proof.EncodeRLP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enc, nil
|
||||
}
|
||||
|
||||
func (api *DebugAPI) BuildProof(num rpc.BlockNumber) ([]byte, error) {
|
||||
return BuildProof(uint64(num), api.eth.blockchain)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
if config.OverrideVerkle != nil {
|
||||
overrides.OverrideVerkle = config.OverrideVerkle
|
||||
}
|
||||
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TransactionHistory)
|
||||
eth.blockchain, err = core.NewBlockchainWithCrossValidator(config.CrossValidationEndpoint, config.WitnessRecordingPath, chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TransactionHistory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,6 +159,9 @@ type Config struct {
|
|||
|
||||
// OverrideVerkle (TODO: remove after the fork)
|
||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||
|
||||
CrossValidationEndpoint string `toml:",omitempty"`
|
||||
WitnessRecordingPath string `toml:",omitempty"`
|
||||
}
|
||||
|
||||
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
||||
|
|
|
|||
|
|
@ -501,6 +501,11 @@ web3._extend({
|
|||
call: 'debug_getTrieFlushInterval',
|
||||
params: 0
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'buildProof',
|
||||
call: 'debug_buildProof',
|
||||
params: 1
|
||||
}),
|
||||
],
|
||||
properties: []
|
||||
});
|
||||
|
|
|
|||
|
|
@ -125,7 +125,8 @@ func (env *environment) discard() {
|
|||
if env.state == nil {
|
||||
return
|
||||
}
|
||||
env.state.StopPrefetcher()
|
||||
// TODO: re-enable prefetcher
|
||||
//env.state.StopPrefetcher()
|
||||
}
|
||||
|
||||
// task contains all information for consensus engine sealing and result submitting.
|
||||
|
|
@ -720,7 +721,8 @@ func (w *worker) makeEnv(parent *types.Header, header *types.Header, coinbase co
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state.StartPrefetcher("miner")
|
||||
// TODO: re-enable prefetcher
|
||||
//state.StartPrefetcher("miner")
|
||||
|
||||
// Note the passed coinbase may be different with header.Coinbase.
|
||||
env := &environment{
|
||||
|
|
|
|||
|
|
@ -18,9 +18,14 @@ package tests
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"golang.org/x/exp/slog"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
)
|
||||
|
|
@ -61,6 +66,90 @@ func TestBlockchain(t *testing.T) {
|
|||
// which run natively, so there's no reason to run them here.
|
||||
}
|
||||
|
||||
func networkPostMerge(network string) bool {
|
||||
switch network {
|
||||
case "Frontier":
|
||||
return false
|
||||
case "EIP150":
|
||||
return false
|
||||
case "EIP158":
|
||||
return false
|
||||
case "Byzantium":
|
||||
return false
|
||||
case "Constantinople":
|
||||
return false
|
||||
case "ConstantinopleFix":
|
||||
return false
|
||||
case "Istanbul":
|
||||
return false
|
||||
case "MuirGlacier":
|
||||
return false
|
||||
case "Berlin":
|
||||
return false
|
||||
case "London":
|
||||
return false
|
||||
case "ArrowGlacier":
|
||||
return false
|
||||
case "GreyGlacier":
|
||||
return false
|
||||
case "Merge":
|
||||
return true
|
||||
case "Shanghai":
|
||||
return true
|
||||
case "Cancun":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestStatelessBlockchain(t *testing.T) {
|
||||
bt := new(testMatcher)
|
||||
|
||||
// Skip random failures due to selfish mining test
|
||||
bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`)
|
||||
|
||||
// Slow tests
|
||||
bt.slow(`.*bcExploitTest/DelegateCallSpam.json`)
|
||||
bt.slow(`.*bcExploitTest/ShanghaiLove.json`)
|
||||
bt.slow(`.*bcExploitTest/SuicideIssue.json`)
|
||||
bt.slow(`.*/bcForkStressTest/`)
|
||||
bt.slow(`.*/bcGasPricerTest/RPC_API_Test.json`)
|
||||
bt.slow(`.*/bcWalletTest/`)
|
||||
|
||||
// Very slow test
|
||||
bt.skipLoad(`.*/stTimeConsuming/.*`)
|
||||
// test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range,
|
||||
// using 4.6 TGas
|
||||
bt.skipLoad(`.*randomStatetest94.json.*`)
|
||||
|
||||
// skip uncle tests for stateless
|
||||
bt.skipLoad(`.*/UnclePopulation.json`)
|
||||
// skip this test in stateless because it uses 5000 blocks and the
|
||||
// historical state of older blocks is unavailable for stateless
|
||||
// test verification after importing the test set.
|
||||
bt.skipLoad(`.*/bcWalletTest/walletReorganizeOwners.json`)
|
||||
|
||||
bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||
if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 {
|
||||
t.Skip("test (randomly) skipped on 32-bit windows")
|
||||
}
|
||||
|
||||
config, ok := Forks[test.json.Network]
|
||||
if !ok {
|
||||
t.Fatalf("test malformed: doesn't have chain config embedded")
|
||||
}
|
||||
isMerged := config.TerminalTotalDifficulty != nil && config.TerminalTotalDifficulty.BitLen() == 0
|
||||
if isMerged {
|
||||
execBlockTestStateless(t, bt, test)
|
||||
} else {
|
||||
t.Skip("skipping pre-merge test")
|
||||
}
|
||||
})
|
||||
// There is also a LegacyTests folder, containing blockchain tests generated
|
||||
// prior to Istanbul. However, they are all derived from GeneralStateTests,
|
||||
// which run natively, so there's no reason to run them here.
|
||||
}
|
||||
|
||||
// TestExecutionSpec runs the test fixtures from execution-spec-tests.
|
||||
func TestExecutionSpec(t *testing.T) {
|
||||
if !common.FileExist(executionSpecDir) {
|
||||
|
|
@ -74,19 +163,52 @@ func TestExecutionSpec(t *testing.T) {
|
|||
}
|
||||
|
||||
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
|
||||
if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil {
|
||||
if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil)); err != nil {
|
||||
t.Errorf("test in hash mode without snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil {
|
||||
if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil)); err != nil {
|
||||
t.Errorf("test in hash mode with snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil {
|
||||
if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil)); err != nil {
|
||||
t.Errorf("test in path mode without snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil {
|
||||
if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil)); err != nil {
|
||||
t.Errorf("test in path mode with snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func execBlockTestStateless(t *testing.T, bt *testMatcher, test *BlockTest) {
|
||||
handler := log.NewTerminalHandlerWithLevel(os.Stdout, slog.Level(667), false)
|
||||
log.SetDefault(log.NewLogger(handler))
|
||||
logconfig := &logger.Config{
|
||||
EnableMemory: false,
|
||||
DisableStack: false,
|
||||
DisableStorage: false,
|
||||
EnableReturnData: true,
|
||||
Debug: true,
|
||||
}
|
||||
tracer := logger.NewJSONLogger(logconfig, os.Stdout)
|
||||
_ = tracer
|
||||
|
||||
if err := bt.checkFailure(t, test.RunStateless(false, rawdb.HashScheme, nil)); err != nil {
|
||||
t.Errorf("test in hash mode without snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := bt.checkFailure(t, test.RunStateless(true, rawdb.HashScheme, nil)); err != nil {
|
||||
t.Errorf("test in hash mode with snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := bt.checkFailure(t, test.RunStateless(false, rawdb.PathScheme, nil)); err != nil {
|
||||
t.Errorf("test in path mode without snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
if err := bt.checkFailure(t, test.RunStateless(true, rawdb.PathScheme, nil)); err != nil {
|
||||
t.Errorf("test in path mode with snapshotter failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
|
|
@ -109,7 +112,15 @@ type btHeaderMarshaling struct {
|
|||
ExcessBlobGas *math.HexOrDecimal64
|
||||
}
|
||||
|
||||
func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain)) (result error) {
|
||||
func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) error {
|
||||
return t.run(false, snapshotter, scheme, tracer)
|
||||
}
|
||||
|
||||
func (t *BlockTest) RunStateless(snapshotter bool, scheme string, tracer vm.EVMLogger) error {
|
||||
return t.run(true, snapshotter, scheme, tracer)
|
||||
}
|
||||
|
||||
func (t *BlockTest) run(stateless bool, snapshotter bool, scheme string, tracer vm.EVMLogger) error {
|
||||
config, ok := Forks[t.json.Network]
|
||||
if !ok {
|
||||
return UnsupportedForkError{t.json.Network}
|
||||
|
|
@ -126,6 +137,14 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po
|
|||
} else {
|
||||
tconf.HashDB = hashdb.Defaults
|
||||
}
|
||||
closeCh, port, err := utils.RunLocalServer(config, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
closeCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Commit genesis state
|
||||
gspec := t.genesis(config)
|
||||
triedb := triedb.NewDatabase(db, tconf)
|
||||
|
|
@ -144,14 +163,25 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po
|
|||
// Wrap the original engine within the beacon-engine
|
||||
engine := beacon.New(ethash.NewFaker())
|
||||
|
||||
cache := &core.CacheConfig{TrieCleanLimit: 0, StateScheme: scheme, Preimages: true}
|
||||
cache := &core.CacheConfig{TrieCleanLimit: 0, StateScheme: scheme, Preimages: true, TrieDirtyDisabled: true}
|
||||
if snapshotter {
|
||||
cache.SnapshotLimit = 1
|
||||
cache.SnapshotWait = true
|
||||
}
|
||||
chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{
|
||||
Tracer: tracer,
|
||||
}, nil, nil)
|
||||
// TODO: create normal chain if not stateless mode
|
||||
chain, err := core.NewBlockchainWithCrossValidator(
|
||||
fmt.Sprintf("http://localhost:%d", port),
|
||||
"",
|
||||
db,
|
||||
cache,
|
||||
gspec,
|
||||
nil,
|
||||
engine,
|
||||
vm.Config{
|
||||
//Tracer: tracer,
|
||||
},
|
||||
nil,
|
||||
nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -161,11 +191,6 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Import succeeded: regardless of whether the _test_ succeeds or not, schedule
|
||||
// the post-check to run
|
||||
if postCheck != nil {
|
||||
defer postCheck(result, chain)
|
||||
}
|
||||
cmlast := chain.CurrentBlock().Hash()
|
||||
if common.Hash(t.json.BestBlock) != cmlast {
|
||||
return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, cmlast)
|
||||
|
|
@ -183,6 +208,28 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if stateless {
|
||||
for _, blk := range validBlocks {
|
||||
// TODO: set up test chassis to do verification on block import (in BlockChain) in addition to BuildProof
|
||||
// (used by standalone witness execution path)
|
||||
enc, err := eth.BuildProof(blk.BlockHeader.Number.Uint64(), chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build proof: %v", err)
|
||||
}
|
||||
witness, err := state.DecodeWitnessRLP(enc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding witness: %v", err)
|
||||
}
|
||||
root, err := utils.StatelessExecute(os.Stdout, config, witness)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verification execution error: %v", err)
|
||||
}
|
||||
if root != blk.BlockHeader.StateRoot {
|
||||
return fmt.Errorf("state root mismatch (wanted: %x, got: %x)", blk.BlockHeader.StateRoot, root)
|
||||
}
|
||||
}
|
||||
}
|
||||
return t.validateImportedHeaders(chain, validBlocks)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -213,6 +213,25 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte {
|
|||
}
|
||||
return t.db.Preimage(common.BytesToHash(shaKey))
|
||||
}
|
||||
func (t *StateTrie) AccessList() map[string][]byte {
|
||||
return t.trie.AccessList()
|
||||
}
|
||||
|
||||
func (t *StateTrie) CommitAndObtainAccessList(collectLeaf bool) (common.Hash, *trienode.NodeSet, map[string][]byte, error) {
|
||||
// Write all the pre-images to the actual disk database
|
||||
if len(t.getSecKeyCache()) > 0 {
|
||||
if t.preimages != nil {
|
||||
preimages := make(map[common.Hash][]byte)
|
||||
for hk, key := range t.secKeyCache {
|
||||
preimages[common.BytesToHash([]byte(hk))] = key
|
||||
}
|
||||
t.preimages.insertPreimage(preimages)
|
||||
}
|
||||
t.secKeyCache = make(map[string][]byte)
|
||||
}
|
||||
// Commit the trie and return its modified nodeset.
|
||||
return t.trie.CommitAndObtainAccessList(collectLeaf)
|
||||
}
|
||||
|
||||
// Commit collects all dirty nodes in the trie and replaces them with the
|
||||
// corresponding node hash. All collected nodes (including dirty leaves if
|
||||
|
|
|
|||
24
trie/trie.go
24
trie/trie.go
|
|
@ -107,7 +107,7 @@ func NewEmpty(db database.Database) *Trie {
|
|||
}
|
||||
|
||||
// MustNodeIterator is a wrapper of NodeIterator and will omit any encountered
|
||||
// error but just print out an error message.
|
||||
// error but just printg out an error message.
|
||||
func (t *Trie) MustNodeIterator(start []byte) NodeIterator {
|
||||
it, err := t.NodeIterator(start)
|
||||
if err != nil {
|
||||
|
|
@ -581,6 +581,10 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) {
|
|||
return n, nil
|
||||
}
|
||||
|
||||
// TODO: for resolveAndTrack, differentiate between hash node resolve failure in stateless
|
||||
// vs normal execution. In normal mode, it represents an error with database
|
||||
// consistency. In stateless execution, it means that the witness is incomplete.
|
||||
|
||||
// resolveAndTrack loads node from the underlying store with the given node hash
|
||||
// and path prefix and also tracks the loaded node blob in tracer treated as the
|
||||
// node's original value. The rlp-encoded blob is preferred to be loaded from
|
||||
|
|
@ -602,6 +606,22 @@ func (t *Trie) Hash() common.Hash {
|
|||
return common.BytesToHash(hash.(hashNode))
|
||||
}
|
||||
|
||||
func (t *Trie) AccessList() map[string][]byte {
|
||||
return t.tracer.accessList
|
||||
}
|
||||
|
||||
func (t *Trie) CommitAndObtainAccessList(collectLeaf bool) (common.Hash, *trienode.NodeSet, map[string][]byte, error) {
|
||||
accessList := t.tracer.accessList
|
||||
// Commit will reset the tracer accessList, so after this
|
||||
// operation, we have full ownership of the map (hence: no need to
|
||||
// deep-copy or even copy).
|
||||
rootHash, nodes, err := t.Commit(collectLeaf)
|
||||
if err != nil {
|
||||
return rootHash, nodes, nil, err
|
||||
}
|
||||
return rootHash, nodes, accessList, err
|
||||
}
|
||||
|
||||
// Commit collects all dirty nodes in the trie and replaces them with the
|
||||
// corresponding node hash. All collected nodes (including dirty leaves if
|
||||
// collectLeaf is true) will be encapsulated into a nodeset for return.
|
||||
|
|
@ -609,8 +629,8 @@ func (t *Trie) Hash() common.Hash {
|
|||
// Once the trie is committed, it's not usable anymore. A new trie must
|
||||
// be created with new root and updated trie database for following usage
|
||||
func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
|
||||
defer t.tracer.reset()
|
||||
defer func() {
|
||||
t.tracer.reset()
|
||||
t.committed = true
|
||||
}()
|
||||
// Trie is empty and can be classified into two types of situations:
|
||||
|
|
|
|||
|
|
@ -216,6 +216,14 @@ func (t *VerkleTrie) Hash() common.Hash {
|
|||
return t.root.Commit().Bytes()
|
||||
}
|
||||
|
||||
func (t *VerkleTrie) AccessList() map[string][]byte {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (t *VerkleTrie) CommitAndObtainAccessList(collectLeaf bool) (common.Hash, *trienode.NodeSet, map[string][]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Commit writes all nodes to the tree's memory database.
|
||||
func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
|
||||
root, ok := t.root.(*verkle.InternalNode)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ var HashDefaults = &Config{
|
|||
HashDB: hashdb.Defaults,
|
||||
}
|
||||
|
||||
var PathDefaults = &Config{
|
||||
Preimages: false,
|
||||
PathDB: pathdb.Defaults,
|
||||
}
|
||||
|
||||
// backend defines the methods needed to access/update trie nodes in different
|
||||
// state scheme.
|
||||
type backend interface {
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@ import (
|
|||
// thread-safe to use. However, callers need to ensure the thread-safety
|
||||
// of the referenced layer by themselves.
|
||||
type layerTree struct {
|
||||
lock sync.RWMutex
|
||||
layers map[common.Hash]layer
|
||||
lock sync.RWMutex
|
||||
layers map[common.Hash]layer
|
||||
stateless bool
|
||||
}
|
||||
|
||||
// newLayerTree constructs the layerTree with the given head layer.
|
||||
|
|
|
|||
Loading…
Reference in a new issue