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,
|
configFileFlag,
|
||||||
utils.LogDebugFlag,
|
utils.LogDebugFlag,
|
||||||
utils.LogBacktraceAtFlag,
|
utils.LogBacktraceAtFlag,
|
||||||
|
utils.CrossValidationEndpointFlag,
|
||||||
|
utils.WitnessRecordingPathFlag,
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags)
|
}, utils.NetworkFlags, utils.DatabaseFlags)
|
||||||
|
|
||||||
rpcFlags = []cli.Flag{
|
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 (
|
var (
|
||||||
// TestnetFlags is the flag group of all built-in supported testnets.
|
// TestnetFlags is the flag group of all built-in supported testnets.
|
||||||
TestnetFlags = []cli.Flag{
|
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 {
|
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)
|
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
|
// 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)
|
beacon.ethone.Finalize(chain, header, state, txs, uncles, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Withdrawals processing.
|
state.ApplyWithdrawals(withdrawals)
|
||||||
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)
|
|
||||||
}
|
|
||||||
// No block reward which is issued by consensus layer instead.
|
// No block reward which is issued by consensus layer instead.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package core
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
@ -47,6 +48,20 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
|
||||||
return validator
|
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
|
// ValidateBody validates the given block's uncles and verifies the block
|
||||||
// header's transaction and uncle roots. The headers are assumed to be already
|
// header's transaction and uncle roots. The headers are assumed to be already
|
||||||
// validated at this point.
|
// 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,
|
// 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.
|
// 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()
|
header := block.Header()
|
||||||
if block.GasUsed() != usedGas {
|
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.
|
// Validate the received block's bloom with the one derived from the generated receipts.
|
||||||
// For valid blocks this should always validate to true.
|
// For valid blocks this should always validate to true.
|
||||||
rbloom := types.CreateBloom(receipts)
|
rbloom := types.CreateBloom(receipts)
|
||||||
if rbloom != header.Bloom {
|
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]]))
|
// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
|
||||||
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
|
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
|
||||||
if receiptSha != header.ReceiptHash {
|
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
|
// Compute the state root and if enabled, check it against the
|
||||||
// an error if they don't match.
|
// received state root and throw an error if they don't match.
|
||||||
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
|
root = statedb.IntermediateRoot(v.config.IsEIP158(header.Number))
|
||||||
return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
|
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
|
// 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
|
processor Processor // Block transaction processor interface
|
||||||
forker *ForkChoice
|
forker *ForkChoice
|
||||||
vmConfig vm.Config
|
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
|
// 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
|
// writeBlockWithState writes block, metadata and corresponding state data to the
|
||||||
// database.
|
// 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
|
// Calculate the total difficulty of the block
|
||||||
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
|
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
|
||||||
if ptd == nil {
|
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
|
// Make sure no inconsistent state is leaked during insertion
|
||||||
externTd := new(big.Int).Add(block.Difficulty(), ptd)
|
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.
|
// 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)
|
// 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.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd)
|
||||||
rawdb.WriteBlock(blockBatch, block)
|
rawdb.WriteBlock(blockBatch, block)
|
||||||
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
||||||
rawdb.WritePreimages(blockBatch, state.Preimages())
|
rawdb.WritePreimages(blockBatch, statedb.Preimages())
|
||||||
if err := blockBatch.Write(); err != nil {
|
if err := blockBatch.Write(); err != nil {
|
||||||
log.Crit("Failed to write block into disk", "err", err)
|
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
|
// If node is running in path mode, skip explicit gc operation
|
||||||
// which is unnecessary in this mode.
|
// which is unnecessary in this mode.
|
||||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
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.
|
// writeBlockAndSetHead is the internal implementation of WriteBlockAndSetHead.
|
||||||
// This function expects the chain mutex to be held.
|
// 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) {
|
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, state); err != nil {
|
if err := bc.writeBlockWithState(block, receipts, db); err != nil {
|
||||||
return NonStatTy, err
|
return NonStatTy, err
|
||||||
}
|
}
|
||||||
currentBlock := bc.CurrentBlock()
|
currentBlock := bc.CurrentBlock()
|
||||||
|
|
@ -1561,6 +1589,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
var (
|
var (
|
||||||
stats = insertStats{startTime: mclock.Now()}
|
stats = insertStats{startTime: mclock.Now()}
|
||||||
lastCanon *types.Block
|
lastCanon *types.Block
|
||||||
|
statedb *state.StateDB
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
// Fire a single chain head event if we've progressed the chain
|
// Fire a single chain head event if we've progressed the chain
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
@ -1736,7 +1766,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
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 {
|
if err != nil {
|
||||||
return it.index, err
|
return it.index, err
|
||||||
}
|
}
|
||||||
|
|
@ -1774,7 +1809,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
ptime := time.Since(pstart)
|
ptime := time.Since(pstart)
|
||||||
|
|
||||||
vstart := time.Now()
|
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)
|
bc.reportBlock(block, receipts, err)
|
||||||
followupInterrupt.Store(true)
|
followupInterrupt.Store(true)
|
||||||
return it.index, err
|
return it.index, err
|
||||||
|
|
@ -1809,6 +1844,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
} else {
|
} else {
|
||||||
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
|
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
followupInterrupt.Store(true)
|
followupInterrupt.Store(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it.index, err
|
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 (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"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.
|
// NewEVMBlockContext creates a new context for use in the EVM.
|
||||||
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
|
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 (
|
var (
|
||||||
beneficiary common.Address
|
beneficiary common.Address
|
||||||
baseFee *big.Int
|
baseFee *big.Int
|
||||||
|
|
@ -61,10 +73,19 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
||||||
if header.Difficulty.Cmp(common.Big0) == 0 {
|
if header.Difficulty.Cmp(common.Big0) == 0 {
|
||||||
random = &header.MixDigest
|
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{
|
return vm.BlockContext{
|
||||||
CanTransfer: CanTransfer,
|
CanTransfer: CanTransfer,
|
||||||
Transfer: Transfer,
|
Transfer: Transfer,
|
||||||
GetHash: GetHashFn(header, chain),
|
GetHash: getHash,
|
||||||
Coinbase: beneficiary,
|
Coinbase: beneficiary,
|
||||||
BlockNumber: new(big.Int).Set(header.Number),
|
BlockNumber: new(big.Int).Set(header.Number),
|
||||||
Time: header.Time,
|
Time: header.Time,
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,13 @@ type Trie interface {
|
||||||
// be created with new root and updated trie database for following usage
|
// be created with new root and updated trie database for following usage
|
||||||
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)
|
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
|
// 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
|
// starts at the key after the given start key. And error will be returned
|
||||||
// if fails to create node iterator.
|
// if fails to create node iterator.
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,13 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
||||||
err error
|
err error
|
||||||
value common.Hash
|
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 {
|
if s.db.snap != nil {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes()))
|
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{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
val, err := tr.GetStorage(s.address, key.Bytes())
|
val, err := tr.GetStorage(s.address, key.Bytes())
|
||||||
|
//fmt.Printf("trie access list is %v\n", tr.AccessList())
|
||||||
if metrics.EnabledExpensive {
|
if metrics.EnabledExpensive {
|
||||||
s.db.StorageReads += time.Since(start)
|
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.
|
// 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
|
// The returned set can be nil if nothing to commit. This function assumes all
|
||||||
// storage mutations have already been flushed into trie by updateRoot.
|
// 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
|
// Short circuit if trie is not even loaded, don't bother with committing anything
|
||||||
if s.trie == nil {
|
if s.trie == nil {
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
return nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
// Track the amount of time wasted on committing the storage trie
|
// Track the amount of time wasted on committing the storage trie
|
||||||
if metrics.EnabledExpensive {
|
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
|
// 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
|
// cached mutations. Call commit to acquire a set of nodes that have been
|
||||||
// modified, the set can be nil if nothing to commit.
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
s.data.Root = root
|
s.data.Root = root
|
||||||
|
|
||||||
// Update original account data after commit
|
// Update original account data after commit
|
||||||
s.origin = s.data.Copy()
|
s.origin = s.data.Copy()
|
||||||
return nodes, nil
|
return nodes, accessList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBalance adds amount to s's balance.
|
// 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
|
// Testing hooks
|
||||||
onCommit func(states *triestate.Set) // Hook invoked when commit is performed
|
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.
|
// 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.
|
// commit phase, most of the needed data is already hot.
|
||||||
func (s *StateDB) StartPrefetcher(namespace string) {
|
func (s *StateDB) StartPrefetcher(namespace string) {
|
||||||
if s.prefetcher != nil {
|
if s.prefetcher != nil {
|
||||||
|
s.prefetcher.wait()
|
||||||
s.prefetcher.close()
|
s.prefetcher.close()
|
||||||
s.prefetcher = nil
|
s.prefetcher = nil
|
||||||
}
|
}
|
||||||
|
if s.readPrefetcher != nil {
|
||||||
|
s.readPrefetcher.wait()
|
||||||
|
s.readPrefetcher.close()
|
||||||
|
s.readPrefetcher = nil
|
||||||
|
}
|
||||||
if s.snap != nil {
|
if s.snap != nil {
|
||||||
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
|
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.
|
// from the gathered metrics.
|
||||||
func (s *StateDB) StopPrefetcher() {
|
func (s *StateDB) StopPrefetcher() {
|
||||||
if s.prefetcher != nil {
|
if s.prefetcher != nil {
|
||||||
|
s.prefetcher.wait()
|
||||||
s.prefetcher.close()
|
s.prefetcher.close()
|
||||||
s.prefetcher = nil
|
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.
|
// 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 {
|
func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
return stateObject.GetCommittedState(hash)
|
res := stateObject.GetCommittedState(hash)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
@ -604,9 +634,15 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert into the live set
|
// Insert into the live set
|
||||||
obj := newObject(s, addr, data)
|
obj := newObject(s, addr, data)
|
||||||
s.setStateObject(obj)
|
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
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -712,6 +748,9 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
snaps: s.snaps,
|
snaps: s.snaps,
|
||||||
snap: s.snap,
|
snap: s.snap,
|
||||||
}
|
}
|
||||||
|
if s.witness != nil {
|
||||||
|
state.witness = s.witness.Copy()
|
||||||
|
}
|
||||||
// Copy the dirty states, logs, and preimages
|
// Copy the dirty states, logs, and preimages
|
||||||
for addr := range s.journal.dirties {
|
for addr := range s.journal.dirties {
|
||||||
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527),
|
// 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()
|
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.
|
// IntermediateRoot computes the current root hash of the state trie.
|
||||||
// It is called in between transactions to get the root hash that
|
// It is called in between transactions to get the root hash that
|
||||||
// goes into transaction receipts.
|
// 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
|
// Finalise all the dirty storage states and write them into the tries
|
||||||
s.Finalise(deleteEmptyObjects)
|
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
|
prefetcher := s.prefetcher
|
||||||
if s.prefetcher != nil {
|
if s.prefetcher != nil {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
// TODO: need to wait for read accounts to be resolved in prefetcher main trie?
|
||||||
|
s.prefetcher.wait()
|
||||||
s.prefetcher.close()
|
s.prefetcher.close()
|
||||||
s.prefetcher = nil
|
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
|
// Although naively it makes sense to retrieve the account trie and then do
|
||||||
// the contract storage and account updates sequentially, that short circuits
|
// the contract storage and account updates sequentially, that short circuits
|
||||||
// the account prefetcher. Instead, let's process all the storage updates
|
// 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()
|
obj.updateRoot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we're about to start to write changes to the trie. The trie is so far
|
// 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
|
// _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.
|
// 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))
|
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 {
|
for addr := range s.stateObjectsPending {
|
||||||
if obj := s.stateObjects[addr]; obj.deleted {
|
if obj := s.stateObjects[addr]; !obj.deleted {
|
||||||
s.deleteStateObject(obj)
|
|
||||||
s.AccountDeleted += 1
|
|
||||||
} else {
|
|
||||||
s.updateStateObject(obj)
|
s.updateStateObject(obj)
|
||||||
s.AccountUpdated += 1
|
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 {
|
if prefetcher != nil {
|
||||||
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
|
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
|
||||||
}
|
}
|
||||||
|
|
@ -929,6 +1013,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
return s.trie.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
|
// SetTxContext sets the current transaction hash and index which are
|
||||||
// used when the EVM emits new state logs. It should be invoked before
|
// used when the EVM emits new state logs. It should be invoked before
|
||||||
// transaction execution.
|
// transaction execution.
|
||||||
|
|
@ -1153,6 +1244,28 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A
|
||||||
return incomplete, nil
|
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.
|
// Commit writes the state to the underlying in-memory trie database.
|
||||||
// Once the state is committed, tries cached in stateDB (including account
|
// Once the state is committed, tries cached in stateDB (including account
|
||||||
// trie, storage tries) will no longer be functional. A new state instance
|
// 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
|
storageTrieNodesDeleted int
|
||||||
nodes = trienode.NewMergedNodeSet()
|
nodes = trienode.NewMergedNodeSet()
|
||||||
codeWriter = s.db.DiskDB().NewBatch()
|
codeWriter = s.db.DiskDB().NewBatch()
|
||||||
|
root common.Hash
|
||||||
|
set *trienode.NodeSet
|
||||||
)
|
)
|
||||||
// Handle all state deletions first
|
// Handle all state deletions first
|
||||||
incomplete, err := s.handleDestruction(nodes)
|
incomplete, err := s.handleDestruction(nodes)
|
||||||
|
|
@ -1184,6 +1299,31 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
// Handle all state updates afterwards
|
// 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 {
|
for addr := range s.stateObjectsDirty {
|
||||||
obj := s.stateObjects[addr]
|
obj := s.stateObjects[addr]
|
||||||
if obj.deleted {
|
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)
|
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
|
||||||
obj.dirtyCode = false
|
obj.dirtyCode = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write any storage changes in the state object to its storage trie
|
// Write any storage changes in the state object to its storage trie
|
||||||
set, err := obj.commit()
|
set, accessList, err := obj.commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
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
|
// 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.
|
// that the account was destructed and then resurrected in the same block.
|
||||||
// In this case, the node set is shared by both accounts.
|
// 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 {
|
if metrics.EnabledExpensive {
|
||||||
start = time.Now()
|
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 {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
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.storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
||||||
s.stateObjectsDirty = make(map[common.Address]struct{})
|
s.stateObjectsDirty = make(map[common.Address]struct{})
|
||||||
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
|
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
|
||||||
|
|
||||||
return root, nil
|
return root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -70,13 +71,16 @@ func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePre
|
||||||
}
|
}
|
||||||
return p
|
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
|
// close iterates over all the subfetchers, aborts any that were left spinning
|
||||||
// and reports the stats to the metrics subsystem.
|
// and reports the stats to the metrics subsystem.
|
||||||
func (p *triePrefetcher) close() {
|
func (p *triePrefetcher) close() {
|
||||||
for _, fetcher := range p.fetchers {
|
for _, fetcher := range p.fetchers {
|
||||||
fetcher.abort() // safe to do multiple times
|
|
||||||
|
|
||||||
if metrics.Enabled {
|
if metrics.Enabled {
|
||||||
if fetcher.root == p.root {
|
if fetcher.root == p.root {
|
||||||
p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
|
p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
|
||||||
|
|
@ -123,29 +127,22 @@ func (p *triePrefetcher) copy() *triePrefetcher {
|
||||||
storageSkipMeter: p.storageSkipMeter,
|
storageSkipMeter: p.storageSkipMeter,
|
||||||
storageWasteMeter: p.storageWasteMeter,
|
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
|
return copy
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefetch schedules a batch of trie items to prefetch.
|
// 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) {
|
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 the prefetcher is an inactive one, bail out
|
||||||
if p.fetches != nil {
|
if p.fetches != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Active fetcher, schedule the retrievals
|
// Active fetcher, schedule the retrievals
|
||||||
id := p.trieID(owner, root)
|
id := p.trieID(owner, root)
|
||||||
fetcher := p.fetchers[id]
|
fetcher := p.fetchers[id]
|
||||||
|
|
@ -175,16 +172,13 @@ func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
|
||||||
p.deliveryMissMeter.Mark(1)
|
p.deliveryMissMeter.Mark(1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Interrupt the prefetcher if it's by any chance still running and return
|
// Wait for the fetcher to finish
|
||||||
// a copy of any pre-loaded trie.
|
fetcher.wait() // safe to do multiple times
|
||||||
fetcher.abort() // safe to do multiple times
|
if fetcher.trie == nil {
|
||||||
|
|
||||||
trie := fetcher.peek()
|
|
||||||
if trie == nil {
|
|
||||||
p.deliveryMissMeter.Mark(1)
|
p.deliveryMissMeter.Mark(1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return trie
|
return fetcher.db.CopyTrie(fetcher.trie)
|
||||||
}
|
}
|
||||||
|
|
||||||
// used marks a batch of state items used to allow creating statistics as to
|
// 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
|
addr common.Address // Address of the account that the trie belongs to
|
||||||
trie Trie // Trie being populated with nodes
|
trie Trie // Trie being populated with nodes
|
||||||
|
|
||||||
tasks [][]byte // Items queued up for retrieval
|
tasks [][]byte // Items queued up for retrieval
|
||||||
lock sync.Mutex // Lock protecting the task queue
|
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
|
wake chan bool // Wake channel if a new task is scheduled, true if the subfetcher should continue running when there are no pending tasks
|
||||||
stop chan struct{} // Channel to interrupt processing
|
term chan struct{} // Channel to signal interruption
|
||||||
term chan struct{} // Channel to signal interruption
|
|
||||||
copy chan chan Trie // Channel to request a copy of the current trie
|
|
||||||
|
|
||||||
seen map[string]struct{} // Tracks the entries already loaded
|
seen map[string]struct{} // Tracks the entries already loaded
|
||||||
dups int // Number of duplicate preload tasks
|
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,
|
owner: owner,
|
||||||
root: root,
|
root: root,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
wake: make(chan struct{}, 1),
|
wake: make(chan bool, 1),
|
||||||
stop: make(chan struct{}),
|
|
||||||
term: make(chan struct{}),
|
term: make(chan struct{}),
|
||||||
copy: make(chan chan Trie),
|
|
||||||
seen: make(map[string]struct{}),
|
seen: make(map[string]struct{}),
|
||||||
}
|
}
|
||||||
go sf.loop()
|
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) {
|
func (sf *subfetcher) schedule(keys [][]byte) {
|
||||||
// Append the tasks to the current queue
|
// Append the tasks to the current queue
|
||||||
sf.lock.Lock()
|
sf.lock.Lock()
|
||||||
|
|
||||||
sf.tasks = append(sf.tasks, keys...)
|
sf.tasks = append(sf.tasks, keys...)
|
||||||
sf.lock.Unlock()
|
sf.lock.Unlock()
|
||||||
|
// Notify the prefetcher. The wake-chan is buffered, so this is async.
|
||||||
// Notify the prefetcher, it's fine if it's already terminated
|
|
||||||
select {
|
select {
|
||||||
case sf.wake <- struct{}{}:
|
case sf.wake <- true:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// peek tries to retrieve a deep copy of the fetcher's trie in whatever form it
|
// wait waits for the subfetcher to finish it's task. It is safe to call wait multiple
|
||||||
// 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
|
|
||||||
// times but it is not thread safe.
|
// times but it is not thread safe.
|
||||||
func (sf *subfetcher) abort() {
|
func (sf *subfetcher) wait() {
|
||||||
select {
|
// Signal termination by nil tasks
|
||||||
case <-sf.stop:
|
sf.lock.Lock()
|
||||||
default:
|
if sf.closing {
|
||||||
close(sf.stop)
|
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
|
<-sf.term
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,50 +295,32 @@ func (sf *subfetcher) loop() {
|
||||||
}
|
}
|
||||||
// Trie opened successfully, keep prefetching items
|
// Trie opened successfully, keep prefetching items
|
||||||
for {
|
for {
|
||||||
select {
|
keepRunning := <-sf.wake
|
||||||
case <-sf.wake:
|
if !keepRunning {
|
||||||
// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
|
return
|
||||||
sf.lock.Lock()
|
}
|
||||||
tasks := sf.tasks
|
// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
|
||||||
sf.tasks = nil
|
sf.lock.Lock()
|
||||||
sf.lock.Unlock()
|
tasks := sf.tasks
|
||||||
|
sf.tasks = nil
|
||||||
|
sf.lock.Unlock()
|
||||||
|
|
||||||
// Prefetch any tasks until the loop is interrupted
|
// Prefetch all tasks
|
||||||
for i, task := range tasks {
|
for _, task := range tasks {
|
||||||
select {
|
if _, ok := sf.seen[string(task)]; ok {
|
||||||
case <-sf.stop:
|
sf.dups++
|
||||||
// If termination is requested, add any leftover back and return
|
continue
|
||||||
sf.lock.Lock()
|
}
|
||||||
sf.tasks = append(sf.tasks, tasks[i:]...)
|
if len(task) == common.AddressLength {
|
||||||
sf.lock.Unlock()
|
sf.trie.GetAccount(common.BytesToAddress(task))
|
||||||
return
|
} else {
|
||||||
|
_, err := sf.trie.GetStorage(sf.addr, task)
|
||||||
case ch := <-sf.copy:
|
if err != nil {
|
||||||
// Somebody wants a copy of the current trie, grant them
|
// TODO: see what needs to be done in this case
|
||||||
ch <- sf.db.CopyTrie(sf.trie)
|
fmt.Printf("prefetch storage failed: %+v\n", err)
|
||||||
|
|
||||||
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{}{}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sf.seen[string(task)] = struct{}{}
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
|
|
@ -36,9 +38,10 @@ import (
|
||||||
//
|
//
|
||||||
// StateProcessor implements Processor.
|
// StateProcessor implements Processor.
|
||||||
type StateProcessor struct {
|
type StateProcessor struct {
|
||||||
config *params.ChainConfig // Chain configuration options
|
config *params.ChainConfig // Chain configuration options
|
||||||
bc *BlockChain // Canonical block chain
|
bc *BlockChain // Canonical block chain
|
||||||
engine consensus.Engine // Consensus engine used for block rewards
|
engine consensus.Engine // Consensus engine used for block rewards
|
||||||
|
chainCtx *StatelessChainContext // chain context shim for stateless verification mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStateProcessor initialises a new StateProcessor.
|
// 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
|
// 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 transaction messages using the statedb and applying any rewards to both
|
||||||
// the processor (coinbase) and any included uncles.
|
// 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
|
// 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
|
// 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.
|
// 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 (
|
var (
|
||||||
receipts types.Receipts
|
receipts types.Receipts
|
||||||
usedGas = new(uint64)
|
usedGas = new(uint64)
|
||||||
|
|
@ -72,10 +94,16 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||||
misc.ApplyDAOHardFork(statedb)
|
misc.ApplyDAOHardFork(statedb)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
context = NewEVMBlockContext(header, p.bc, nil)
|
context vm.BlockContext
|
||||||
vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
|
|
||||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
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 {
|
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||||
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
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()) {
|
if len(withdrawals) > 0 && !p.config.IsShanghai(block.Number(), block.Time()) {
|
||||||
return nil, nil, 0, errors.New("withdrawals before shanghai")
|
return nil, nil, 0, errors.New("withdrawals before shanghai")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
// 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)
|
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)
|
_, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||||
statedb.Finalise(true)
|
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
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
@ -33,7 +34,7 @@ type Validator interface {
|
||||||
|
|
||||||
// ValidateState validates the given statedb and optionally the receipts and
|
// ValidateState validates the given statedb and optionally the receipts and
|
||||||
// gas used.
|
// 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.
|
// 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
|
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.
|
// EncodeRLP serializes a block as RLP.
|
||||||
func (b *Block) EncodeRLP(w io.Writer) error {
|
func (b *Block) EncodeRLP(w io.Writer) error {
|
||||||
return rlp.Encode(w, &extblock{
|
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) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail if we're trying to transfer more than the available balance
|
// Fail if we're trying to transfer more than the available balance
|
||||||
if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||||
return nil, gas, ErrInsufficientBalance
|
return nil, gas, ErrInsufficientBalance
|
||||||
|
|
@ -188,7 +189,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
snapshot := evm.StateDB.Snapshot()
|
snapshot := evm.StateDB.Snapshot()
|
||||||
p, isPrecompile := evm.precompile(addr)
|
p, isPrecompile := evm.precompile(addr)
|
||||||
debug := evm.Config.Tracer != nil
|
debug := evm.Config.Tracer != nil
|
||||||
|
|
||||||
if !evm.StateDB.Exist(addr) {
|
if !evm.StateDB.Exist(addr) {
|
||||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
||||||
// Calling a non existing account, don't do anything, but ping the tracer
|
// 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.
|
// 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.
|
// The contract is a scoped environment for this execution context only.
|
||||||
code := evm.StateDB.GetCode(addr)
|
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 {
|
if len(code) == 0 {
|
||||||
ret, err = nil, nil // gas is unchanged
|
ret, err = nil, nil // gas is unchanged
|
||||||
} else {
|
} 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.
|
// 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.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
|
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))
|
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
gas = contract.Gas
|
||||||
|
|
@ -337,6 +345,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
addrCopy := addr
|
addrCopy := addr
|
||||||
// Initialise a new contract and make initialise the delegate values
|
// Initialise a new contract and make initialise the delegate values
|
||||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
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))
|
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
|
||||||
ret, err = evm.interpreter.Run(contract, input, false)
|
ret, err = evm.interpreter.Run(contract, input, false)
|
||||||
gas = contract.Gas
|
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.
|
// 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.
|
// The contract is a scoped environment for this execution context only.
|
||||||
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
|
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))
|
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
|
// 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
|
// 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) {
|
func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||||
slot := scope.Stack.peek()
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,6 +386,10 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
||||||
uint64CodeOffset = 0xffffffffffffffff
|
uint64CodeOffset = 0xffffffffffffffff
|
||||||
}
|
}
|
||||||
addr := common.Address(a.Bytes20())
|
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())
|
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
|
||||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
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) {
|
if interpreter.evm.StateDB.Empty(address) {
|
||||||
slot.Clear()
|
slot.Clear()
|
||||||
} else {
|
} 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())
|
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
@ -444,7 +458,13 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
|
||||||
lower = upper - 256
|
lower = upper - 256
|
||||||
}
|
}
|
||||||
if num64 >= lower && num64 < upper {
|
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 {
|
} else {
|
||||||
num.Clear()
|
num.Clear()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ package vm
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -79,6 +81,8 @@ type StateDB interface {
|
||||||
|
|
||||||
AddLog(*types.Log)
|
AddLog(*types.Log)
|
||||||
AddPreimage(common.Hash, []byte)
|
AddPreimage(common.Hash, []byte)
|
||||||
|
|
||||||
|
Witness() *state.Witness
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallContext provides a basic interface for the EVM calling conventions. The EVM
|
// CallContext provides a basic interface for the EVM calling conventions. The EVM
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||||
|
"os"
|
||||||
"time"
|
"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"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
|
@ -443,3 +448,47 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) {
|
||||||
}
|
}
|
||||||
return api.eth.blockchain.GetTrieFlushInterval().String(), nil
|
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 {
|
if config.OverrideVerkle != nil {
|
||||||
overrides.OverrideVerkle = config.OverrideVerkle
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,9 @@ type Config struct {
|
||||||
|
|
||||||
// OverrideVerkle (TODO: remove after the fork)
|
// OverrideVerkle (TODO: remove after the fork)
|
||||||
OverrideVerkle *uint64 `toml:",omitempty"`
|
OverrideVerkle *uint64 `toml:",omitempty"`
|
||||||
|
|
||||||
|
CrossValidationEndpoint string `toml:",omitempty"`
|
||||||
|
WitnessRecordingPath string `toml:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
// CreateConsensusEngine creates a consensus engine for the given chain config.
|
||||||
|
|
|
||||||
|
|
@ -501,6 +501,11 @@ web3._extend({
|
||||||
call: 'debug_getTrieFlushInterval',
|
call: 'debug_getTrieFlushInterval',
|
||||||
params: 0
|
params: 0
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'buildProof',
|
||||||
|
call: 'debug_buildProof',
|
||||||
|
params: 1
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
properties: []
|
properties: []
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,8 @@ func (env *environment) discard() {
|
||||||
if env.state == nil {
|
if env.state == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
env.state.StopPrefetcher()
|
// TODO: re-enable prefetcher
|
||||||
|
//env.state.StopPrefetcher()
|
||||||
}
|
}
|
||||||
|
|
||||||
// task contains all information for consensus engine sealing and result submitting.
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
state.StartPrefetcher("miner")
|
// TODO: re-enable prefetcher
|
||||||
|
//state.StartPrefetcher("miner")
|
||||||
|
|
||||||
// Note the passed coinbase may be different with header.Coinbase.
|
// Note the passed coinbase may be different with header.Coinbase.
|
||||||
env := &environment{
|
env := &environment{
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,14 @@ package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"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/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"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.
|
// 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.
|
// TestExecutionSpec runs the test fixtures from execution-spec-tests.
|
||||||
func TestExecutionSpec(t *testing.T) {
|
func TestExecutionSpec(t *testing.T) {
|
||||||
if !common.FileExist(executionSpecDir) {
|
if !common.FileExist(executionSpecDir) {
|
||||||
|
|
@ -74,19 +163,52 @@ func TestExecutionSpec(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
|
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)
|
t.Errorf("test in hash mode without snapshotter failed: %v", err)
|
||||||
return
|
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)
|
t.Errorf("test in hash mode with snapshotter failed: %v", err)
|
||||||
return
|
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)
|
t.Errorf("test in path mode without snapshotter failed: %v", err)
|
||||||
return
|
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)
|
t.Errorf("test in path mode with snapshotter failed: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,13 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
|
@ -109,7 +112,15 @@ type btHeaderMarshaling struct {
|
||||||
ExcessBlobGas *math.HexOrDecimal64
|
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]
|
config, ok := Forks[t.json.Network]
|
||||||
if !ok {
|
if !ok {
|
||||||
return UnsupportedForkError{t.json.Network}
|
return UnsupportedForkError{t.json.Network}
|
||||||
|
|
@ -126,6 +137,14 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po
|
||||||
} else {
|
} else {
|
||||||
tconf.HashDB = hashdb.Defaults
|
tconf.HashDB = hashdb.Defaults
|
||||||
}
|
}
|
||||||
|
closeCh, port, err := utils.RunLocalServer(config, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeCh <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
// Commit genesis state
|
// Commit genesis state
|
||||||
gspec := t.genesis(config)
|
gspec := t.genesis(config)
|
||||||
triedb := triedb.NewDatabase(db, tconf)
|
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
|
// Wrap the original engine within the beacon-engine
|
||||||
engine := beacon.New(ethash.NewFaker())
|
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 {
|
if snapshotter {
|
||||||
cache.SnapshotLimit = 1
|
cache.SnapshotLimit = 1
|
||||||
cache.SnapshotWait = true
|
cache.SnapshotWait = true
|
||||||
}
|
}
|
||||||
chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{
|
// TODO: create normal chain if not stateless mode
|
||||||
Tracer: tracer,
|
chain, err := core.NewBlockchainWithCrossValidator(
|
||||||
}, nil, nil)
|
fmt.Sprintf("http://localhost:%d", port),
|
||||||
|
"",
|
||||||
|
db,
|
||||||
|
cache,
|
||||||
|
gspec,
|
||||||
|
nil,
|
||||||
|
engine,
|
||||||
|
vm.Config{
|
||||||
|
//Tracer: tracer,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -161,11 +191,6 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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()
|
cmlast := chain.CurrentBlock().Hash()
|
||||||
if common.Hash(t.json.BestBlock) != cmlast {
|
if common.Hash(t.json.BestBlock) != cmlast {
|
||||||
return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", 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
|
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)
|
return t.validateImportedHeaders(chain, validBlocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,25 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte {
|
||||||
}
|
}
|
||||||
return t.db.Preimage(common.BytesToHash(shaKey))
|
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
|
// Commit collects all dirty nodes in the trie and replaces them with the
|
||||||
// corresponding node hash. All collected nodes (including dirty leaves if
|
// 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
|
// 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 {
|
func (t *Trie) MustNodeIterator(start []byte) NodeIterator {
|
||||||
it, err := t.NodeIterator(start)
|
it, err := t.NodeIterator(start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -581,6 +581,10 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) {
|
||||||
return n, nil
|
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
|
// 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
|
// 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
|
// 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))
|
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
|
// Commit collects all dirty nodes in the trie and replaces them with the
|
||||||
// corresponding node hash. All collected nodes (including dirty leaves if
|
// corresponding node hash. All collected nodes (including dirty leaves if
|
||||||
// collectLeaf is true) will be encapsulated into a nodeset for return.
|
// 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
|
// 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
|
// be created with new root and updated trie database for following usage
|
||||||
func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
|
func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
|
||||||
defer t.tracer.reset()
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
t.tracer.reset()
|
||||||
t.committed = true
|
t.committed = true
|
||||||
}()
|
}()
|
||||||
// Trie is empty and can be classified into two types of situations:
|
// 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()
|
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.
|
// Commit writes all nodes to the tree's memory database.
|
||||||
func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
|
func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
|
||||||
root, ok := t.root.(*verkle.InternalNode)
|
root, ok := t.root.(*verkle.InternalNode)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,11 @@ var HashDefaults = &Config{
|
||||||
HashDB: hashdb.Defaults,
|
HashDB: hashdb.Defaults,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var PathDefaults = &Config{
|
||||||
|
Preimages: false,
|
||||||
|
PathDB: pathdb.Defaults,
|
||||||
|
}
|
||||||
|
|
||||||
// backend defines the methods needed to access/update trie nodes in different
|
// backend defines the methods needed to access/update trie nodes in different
|
||||||
// state scheme.
|
// state scheme.
|
||||||
type backend interface {
|
type backend interface {
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,9 @@ import (
|
||||||
// thread-safe to use. However, callers need to ensure the thread-safety
|
// thread-safe to use. However, callers need to ensure the thread-safety
|
||||||
// of the referenced layer by themselves.
|
// of the referenced layer by themselves.
|
||||||
type layerTree struct {
|
type layerTree struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
layers map[common.Hash]layer
|
layers map[common.Hash]layer
|
||||||
|
stateless bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// newLayerTree constructs the layerTree with the given head layer.
|
// newLayerTree constructs the layerTree with the given head layer.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue