beacon, core, eth, miner: integrate witnesses into production Geth (#30069)
This PR integrates witness-enabled block production, witness-creating payload execution and stateless cross-validation into the `engine` API. The purpose of the PR is to enable the following use-cases (for API details, please see next section): - Cross validating locally created blocks: - Call `forkchoiceUpdatedWithWitness` instead of `forkchoiceUpdated` to trigger witness creation too. - Call `getPayload` as before to retrieve the new block and also the above created witness. - Call `executeStatelessPayload` against another client to cross-validate the block. - Cross validating locally processed blocks: - Call `newPayloadWithWitness` instead of `newPayload` to trigger witness creation too. - Call `executeStatelessPayload` against another client to cross-validate the block. - Block production for stateless clients (local or MEV builders): - Call `forkchoiceUpdatedWithWitness` instead of `forkchoiceUpdated` to trigger witness creation too. - Call `getPayload` as before to retrieve the new block and also the above created witness. - Propagate witnesses across the consensus libp2p network for stateless Ethereum. - Stateless validator validation: - Call `executeStatelessPayload` with the propagated witness to statelessly validate the block. *Note, the various `WithWitness` methods could also *just be* an additional boolean flag on the base methods, but this PR wanted to keep the methods separate until a final consensus is reached on how to integrate in production.* --- The following `engine` API types are introduced: ```go // StatelessPayloadStatusV1 is the result of a stateless payload execution. type StatelessPayloadStatusV1 struct { Status string `json:"status"` StateRoot common.Hash `json:"stateRoot"` ReceiptsRoot common.Hash `json:"receiptsRoot"` ValidationError *string `json:"validationError"` } ``` - Add `forkchoiceUpdatedWithWitnessV1,2,3` with same params and returns as `forkchoiceUpdatedV1,2,3`, but triggering a stateless witness building if block production is requested. - Extend `getPayloadV2,3` to return `executionPayloadEnvelope` with an additional `witness` field of type `bytes` iff created via `forkchoiceUpdatedWithWitnessV2,3`. - Add `newPayloadWithWitnessV1,2,3,4` with same params and returns as `newPayloadV1,2,3,4`, but triggering a stateless witness creation during payload execution to allow cross validating it. - Extend `payloadStatusV1` with a `witness` field of type `bytes` if returned by `newPayloadWithWitnessV1,2,3,4`. - Add `executeStatelessPayloadV1,2,3,4` with same base params as `newPayloadV1,2,3,4` and one more additional param (`witness`) of type `bytes`. The method returns `statelessPayloadStatusV1`, which mirrors `payloadStatusV1` but replaces `latestValidHash` with `stateRoot` and `receiptRoot`.
This commit is contained in:
parent
b018da9d02
commit
9326a118c7
|
@ -19,12 +19,14 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
|
||||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||||
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||||
Override bool `json:"shouldOverrideBuilder"`
|
Override bool `json:"shouldOverrideBuilder"`
|
||||||
|
Witness *hexutil.Bytes `json:"witness"`
|
||||||
}
|
}
|
||||||
var enc ExecutionPayloadEnvelope
|
var enc ExecutionPayloadEnvelope
|
||||||
enc.ExecutionPayload = e.ExecutionPayload
|
enc.ExecutionPayload = e.ExecutionPayload
|
||||||
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
|
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
|
||||||
enc.BlobsBundle = e.BlobsBundle
|
enc.BlobsBundle = e.BlobsBundle
|
||||||
enc.Override = e.Override
|
enc.Override = e.Override
|
||||||
|
enc.Witness = e.Witness
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +37,7 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
||||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||||
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||||
Override *bool `json:"shouldOverrideBuilder"`
|
Override *bool `json:"shouldOverrideBuilder"`
|
||||||
|
Witness *hexutil.Bytes `json:"witness"`
|
||||||
}
|
}
|
||||||
var dec ExecutionPayloadEnvelope
|
var dec ExecutionPayloadEnvelope
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
@ -54,5 +57,8 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
||||||
if dec.Override != nil {
|
if dec.Override != nil {
|
||||||
e.Override = *dec.Override
|
e.Override = *dec.Override
|
||||||
}
|
}
|
||||||
|
if dec.Witness != nil {
|
||||||
|
e.Witness = dec.Witness
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,14 @@ type executableDataMarshaling struct {
|
||||||
ExcessBlobGas *hexutil.Uint64
|
ExcessBlobGas *hexutil.Uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StatelessPayloadStatusV1 is the result of a stateless payload execution.
|
||||||
|
type StatelessPayloadStatusV1 struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
StateRoot common.Hash `json:"stateRoot"`
|
||||||
|
ReceiptsRoot common.Hash `json:"receiptsRoot"`
|
||||||
|
ValidationError *string `json:"validationError"`
|
||||||
|
}
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
|
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
|
||||||
|
|
||||||
type ExecutionPayloadEnvelope struct {
|
type ExecutionPayloadEnvelope struct {
|
||||||
|
@ -101,6 +109,7 @@ type ExecutionPayloadEnvelope struct {
|
||||||
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
|
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
|
||||||
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||||
Override bool `json:"shouldOverrideBuilder"`
|
Override bool `json:"shouldOverrideBuilder"`
|
||||||
|
Witness *hexutil.Bytes `json:"witness"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlobsBundleV1 struct {
|
type BlobsBundleV1 struct {
|
||||||
|
@ -116,6 +125,7 @@ type executionPayloadEnvelopeMarshaling struct {
|
||||||
|
|
||||||
type PayloadStatusV1 struct {
|
type PayloadStatusV1 struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Witness *hexutil.Bytes `json:"witness"`
|
||||||
LatestValidHash *common.Hash `json:"latestValidHash"`
|
LatestValidHash *common.Hash `json:"latestValidHash"`
|
||||||
ValidationError *string `json:"validationError"`
|
ValidationError *string `json:"validationError"`
|
||||||
}
|
}
|
||||||
|
@ -198,6 +208,20 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
|
||||||
// Withdrawals value will propagate through the returned block. Empty
|
// Withdrawals value will propagate through the returned block. Empty
|
||||||
// Withdrawals value must be passed via non-nil, length 0 value in data.
|
// Withdrawals value must be passed via non-nil, length 0 value in data.
|
||||||
func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
|
func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
|
||||||
|
block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if block.Hash() != data.BlockHash {
|
||||||
|
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", data.BlockHash, block.Hash())
|
||||||
|
}
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used
|
||||||
|
// for stateless execution, so it skips checking if the executable data hashes to
|
||||||
|
// the requested hash (stateless has to *compute* the root hash, it's not given).
|
||||||
|
func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
|
||||||
txs, err := decodeTransactions(data.Transactions)
|
txs, err := decodeTransactions(data.Transactions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -267,13 +291,10 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
|
||||||
ParentBeaconRoot: beaconRoot,
|
ParentBeaconRoot: beaconRoot,
|
||||||
RequestsHash: requestsHash,
|
RequestsHash: requestsHash,
|
||||||
}
|
}
|
||||||
block := types.NewBlockWithHeader(header)
|
return types.NewBlockWithHeader(header).
|
||||||
block = block.WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: requests})
|
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: requests}).
|
||||||
block = block.WithWitness(data.ExecutionWitness)
|
WithWitness(data.ExecutionWitness),
|
||||||
if block.Hash() != data.BlockHash {
|
nil
|
||||||
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", data.BlockHash, block.Hash())
|
|
||||||
}
|
|
||||||
return block, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
||||||
|
|
|
@ -156,7 +156,6 @@ var (
|
||||||
utils.BeaconGenesisRootFlag,
|
utils.BeaconGenesisRootFlag,
|
||||||
utils.BeaconGenesisTimeFlag,
|
utils.BeaconGenesisTimeFlag,
|
||||||
utils.BeaconCheckpointFlag,
|
utils.BeaconCheckpointFlag,
|
||||||
utils.CollectWitnessFlag,
|
|
||||||
}, utils.NetworkFlags, utils.DatabaseFlags)
|
}, utils.NetworkFlags, utils.DatabaseFlags)
|
||||||
|
|
||||||
rpcFlags = []cli.Flag{
|
rpcFlags = []cli.Flag{
|
||||||
|
|
|
@ -600,11 +600,6 @@ var (
|
||||||
Usage: "Disables db compaction after import",
|
Usage: "Disables db compaction after import",
|
||||||
Category: flags.LoggingCategory,
|
Category: flags.LoggingCategory,
|
||||||
}
|
}
|
||||||
CollectWitnessFlag = &cli.BoolFlag{
|
|
||||||
Name: "collectwitness",
|
|
||||||
Usage: "Enable state witness generation during block execution. Work in progress flag, don't use.",
|
|
||||||
Category: flags.MiscCategory,
|
|
||||||
}
|
|
||||||
|
|
||||||
// MISC settings
|
// MISC settings
|
||||||
SyncTargetFlag = &cli.StringFlag{
|
SyncTargetFlag = &cli.StringFlag{
|
||||||
|
@ -1767,9 +1762,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
// TODO(fjl): force-enable this in --dev mode
|
// TODO(fjl): force-enable this in --dev mode
|
||||||
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
|
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
|
||||||
}
|
}
|
||||||
if ctx.IsSet(CollectWitnessFlag.Name) {
|
|
||||||
cfg.EnableWitnessCollection = ctx.Bool(CollectWitnessFlag.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.IsSet(RPCGlobalGasCapFlag.Name) {
|
if ctx.IsSet(RPCGlobalGasCapFlag.Name) {
|
||||||
cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name)
|
cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name)
|
||||||
|
@ -2194,7 +2186,6 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
||||||
}
|
}
|
||||||
vmcfg := vm.Config{
|
vmcfg := vm.Config{
|
||||||
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
|
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
|
||||||
EnableWitnessCollection: ctx.Bool(CollectWitnessFlag.Name),
|
|
||||||
}
|
}
|
||||||
if ctx.IsSet(VMTraceFlag.Name) {
|
if ctx.IsSet(VMTraceFlag.Name) {
|
||||||
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||||
|
|
|
@ -20,10 +20,8 @@ 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"
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
|
||||||
"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"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
@ -160,28 +158,6 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateWitness cross validates a block execution with stateless remote clients.
|
|
||||||
//
|
|
||||||
// Normally we'd distribute the block witness to remote cross validators, wait
|
|
||||||
// for them to respond and then merge the results. For now, however, it's only
|
|
||||||
// Geth, so do an internal stateless run.
|
|
||||||
func (v *BlockValidator) ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error {
|
|
||||||
// Run the cross client stateless execution
|
|
||||||
// TODO(karalabe): Self-stateless for now, swap with other clients
|
|
||||||
crossReceiptRoot, crossStateRoot, err := ExecuteStateless(v.config, witness)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("stateless execution failed: %v", err)
|
|
||||||
}
|
|
||||||
// Stateless cross execution suceeeded, validate the withheld computed fields
|
|
||||||
if crossReceiptRoot != receiptRoot {
|
|
||||||
return fmt.Errorf("cross validator receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, receiptRoot)
|
|
||||||
}
|
|
||||||
if crossStateRoot != stateRoot {
|
|
||||||
return fmt.Errorf("cross validator state root mismatch (cross: %x local: %x)", crossStateRoot, stateRoot)
|
|
||||||
}
|
|
||||||
return 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
|
||||||
// to keep the baseline gas close to the provided target, and increase it towards
|
// to keep the baseline gas close to the provided target, and increase it towards
|
||||||
// the target if the baseline gas is lower.
|
// the target if the baseline gas is lower.
|
||||||
|
|
|
@ -201,7 +201,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||||
t.Fatalf("post-block %d: unexpected result returned: %v", i, result)
|
t.Fatalf("post-block %d: unexpected result returned: %v", i, result)
|
||||||
case <-time.After(25 * time.Millisecond):
|
case <-time.After(25 * time.Millisecond):
|
||||||
}
|
}
|
||||||
chain.InsertBlockWithoutSetHead(postBlocks[i])
|
chain.InsertBlockWithoutSetHead(postBlocks[i], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the blocks with pre-merge blocks and post-merge blocks
|
// Verify the blocks with pre-merge blocks and post-merge blocks
|
||||||
|
|
|
@ -80,6 +80,7 @@ var (
|
||||||
|
|
||||||
blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
|
blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
|
||||||
blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
|
blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
|
||||||
|
blockCrossValidationTimer = metrics.NewRegisteredResettingTimer("chain/crossvalidation", nil)
|
||||||
blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil)
|
blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil)
|
||||||
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil)
|
blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil)
|
||||||
|
|
||||||
|
@ -1598,7 +1599,9 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
||||||
return 0, errChainStopped
|
return 0, errChainStopped
|
||||||
}
|
}
|
||||||
defer bc.chainmu.Unlock()
|
defer bc.chainmu.Unlock()
|
||||||
return bc.insertChain(chain, true)
|
|
||||||
|
_, n, err := bc.insertChain(chain, true, false) // No witness collection for mass inserts (would get super large)
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// insertChain is the internal implementation of InsertChain, which assumes that
|
// insertChain is the internal implementation of InsertChain, which assumes that
|
||||||
|
@ -1609,10 +1612,10 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
||||||
// racey behaviour. If a sidechain import is in progress, and the historic state
|
// racey behaviour. If a sidechain import is in progress, and the historic state
|
||||||
// is imported, but then new canon-head is added before the actual sidechain
|
// is imported, but then new canon-head is added before the actual sidechain
|
||||||
// completes, then the historic state could be pruned again
|
// completes, then the historic state could be pruned again
|
||||||
func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) {
|
func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness bool) (*stateless.Witness, int, error) {
|
||||||
// If the chain is terminating, don't even bother starting up.
|
// If the chain is terminating, don't even bother starting up.
|
||||||
if bc.insertStopped() {
|
if bc.insertStopped() {
|
||||||
return 0, nil
|
return nil, 0, nil
|
||||||
}
|
}
|
||||||
// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)
|
// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)
|
||||||
SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain)
|
SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain)
|
||||||
|
@ -1667,7 +1670,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
for block != nil && bc.skipBlock(err, it) {
|
for block != nil && bc.skipBlock(err, it) {
|
||||||
log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash())
|
log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash())
|
||||||
if err := bc.writeKnownBlock(block); err != nil {
|
if err := bc.writeKnownBlock(block); err != nil {
|
||||||
return it.index, err
|
return nil, it.index, err
|
||||||
}
|
}
|
||||||
lastCanon = block
|
lastCanon = block
|
||||||
|
|
||||||
|
@ -1681,12 +1684,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
if setHead {
|
if setHead {
|
||||||
// First block is pruned, insert as sidechain and reorg only if TD grows enough
|
// First block is pruned, insert as sidechain and reorg only if TD grows enough
|
||||||
log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash())
|
log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash())
|
||||||
return bc.insertSideChain(block, it)
|
return bc.insertSideChain(block, it, makeWitness)
|
||||||
} else {
|
} else {
|
||||||
// We're post-merge and the parent is pruned, try to recover the parent state
|
// We're post-merge and the parent is pruned, try to recover the parent state
|
||||||
log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash())
|
log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash())
|
||||||
_, err := bc.recoverAncestors(block)
|
_, err := bc.recoverAncestors(block, makeWitness)
|
||||||
return it.index, err
|
return nil, it.index, err
|
||||||
}
|
}
|
||||||
// Some other error(except ErrKnownBlock) occurred, abort.
|
// Some other error(except ErrKnownBlock) occurred, abort.
|
||||||
// ErrKnownBlock is allowed here since some known blocks
|
// ErrKnownBlock is allowed here since some known blocks
|
||||||
|
@ -1694,7 +1697,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
case err != nil && !errors.Is(err, ErrKnownBlock):
|
case err != nil && !errors.Is(err, ErrKnownBlock):
|
||||||
stats.ignored += len(it.chain)
|
stats.ignored += len(it.chain)
|
||||||
bc.reportBlock(block, nil, err)
|
bc.reportBlock(block, nil, err)
|
||||||
return it.index, err
|
return nil, it.index, err
|
||||||
}
|
}
|
||||||
// No validation errors for the first block (or chain prefix skipped)
|
// No validation errors for the first block (or chain prefix skipped)
|
||||||
var activeState *state.StateDB
|
var activeState *state.StateDB
|
||||||
|
@ -1708,6 +1711,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Track the singleton witness from this chain insertion (if any)
|
||||||
|
var witness *stateless.Witness
|
||||||
|
|
||||||
for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() {
|
for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() {
|
||||||
// If the chain is terminating, stop processing blocks
|
// If the chain is terminating, stop processing blocks
|
||||||
if bc.insertStopped() {
|
if bc.insertStopped() {
|
||||||
|
@ -1744,7 +1750,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
"hash", block.Hash(), "number", block.NumberU64())
|
"hash", block.Hash(), "number", block.NumberU64())
|
||||||
}
|
}
|
||||||
if err := bc.writeKnownBlock(block); err != nil {
|
if err := bc.writeKnownBlock(block); err != nil {
|
||||||
return it.index, err
|
return nil, it.index, err
|
||||||
}
|
}
|
||||||
stats.processed++
|
stats.processed++
|
||||||
if bc.logger != nil && bc.logger.OnSkippedBlock != nil {
|
if bc.logger != nil && bc.logger.OnSkippedBlock != nil {
|
||||||
|
@ -1755,13 +1761,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
Safe: bc.CurrentSafeBlock(),
|
Safe: bc.CurrentSafeBlock(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can assume that logs are empty here, since the only way for consecutive
|
// We can assume that logs are empty here, since the only way for consecutive
|
||||||
// Clique blocks to have the same state is if there are no transactions.
|
// Clique blocks to have the same state is if there are no transactions.
|
||||||
lastCanon = block
|
lastCanon = block
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the parent block and it's state to execute on top
|
// Retrieve the parent block and it's state to execute on top
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
parent := it.previous()
|
parent := it.previous()
|
||||||
|
@ -1770,7 +1774,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
}
|
}
|
||||||
statedb, err := state.New(parent.Root, bc.statedb)
|
statedb, err := state.New(parent.Root, bc.statedb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it.index, err
|
return nil, it.index, err
|
||||||
}
|
}
|
||||||
statedb.SetLogger(bc.logger)
|
statedb.SetLogger(bc.logger)
|
||||||
|
|
||||||
|
@ -1778,11 +1782,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
// while processing transactions. Before Byzantium the prefetcher is mostly
|
// while processing transactions. Before Byzantium the prefetcher is mostly
|
||||||
// useless due to the intermediate root hashing after each transaction.
|
// useless due to the intermediate root hashing after each transaction.
|
||||||
if bc.chainConfig.IsByzantium(block.Number()) {
|
if bc.chainConfig.IsByzantium(block.Number()) {
|
||||||
var witness *stateless.Witness
|
// Generate witnesses either if we're self-testing, or if it's the
|
||||||
if bc.vmConfig.EnableWitnessCollection {
|
// only block being inserted. A bit crude, but witnesses are huge,
|
||||||
witness, err = stateless.NewWitness(bc, block)
|
// so we refuse to make an entire chain of them.
|
||||||
|
if bc.vmConfig.StatelessSelfValidation || (makeWitness && len(chain) == 1) {
|
||||||
|
witness, err = stateless.NewWitness(block.Header(), bc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it.index, err
|
return nil, it.index, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statedb.StartPrefetcher("chain", witness)
|
statedb.StartPrefetcher("chain", witness)
|
||||||
|
@ -1814,7 +1820,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
res, err := bc.processBlock(block, statedb, start, setHead)
|
res, err := bc.processBlock(block, statedb, start, setHead)
|
||||||
followupInterrupt.Store(true)
|
followupInterrupt.Store(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it.index, err
|
return nil, it.index, err
|
||||||
}
|
}
|
||||||
// Report the import stats before returning the various results
|
// Report the import stats before returning the various results
|
||||||
stats.processed++
|
stats.processed++
|
||||||
|
@ -1831,7 +1837,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
// After merge we expect few side chains. Simply count
|
// After merge we expect few side chains. Simply count
|
||||||
// all blocks the CL gives us for GC processing time
|
// all blocks the CL gives us for GC processing time
|
||||||
bc.gcproc += res.procTime
|
bc.gcproc += res.procTime
|
||||||
return it.index, nil // Direct block insertion of a single block
|
return witness, it.index, nil // Direct block insertion of a single block
|
||||||
}
|
}
|
||||||
switch res.status {
|
switch res.status {
|
||||||
case CanonStatTy:
|
case CanonStatTy:
|
||||||
|
@ -1861,7 +1867,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stats.ignored += it.remaining()
|
stats.ignored += it.remaining()
|
||||||
return it.index, err
|
return witness, it.index, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// blockProcessingResult is a summary of block processing
|
// blockProcessingResult is a summary of block processing
|
||||||
|
@ -1906,13 +1912,36 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
|
||||||
}
|
}
|
||||||
vtime := time.Since(vstart)
|
vtime := time.Since(vstart)
|
||||||
|
|
||||||
if witness := statedb.Witness(); witness != nil {
|
// If witnesses was generated and stateless self-validation requested, do
|
||||||
if err = bc.validator.ValidateWitness(witness, block.ReceiptHash(), block.Root()); err != nil {
|
// that now. Self validation should *never* run in production, it's more of
|
||||||
bc.reportBlock(block, res, err)
|
// a tight integration to enable running *all* consensus tests through the
|
||||||
return nil, fmt.Errorf("cross verification failed: %v", err)
|
// witness builder/runner, which would otherwise be impossible due to the
|
||||||
|
// various invalid chain states/behaviors being contained in those tests.
|
||||||
|
xvstart := time.Now()
|
||||||
|
if witness := statedb.Witness(); witness != nil && bc.vmConfig.StatelessSelfValidation {
|
||||||
|
log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash())
|
||||||
|
|
||||||
|
// Remove critical computed fields from the block to force true recalculation
|
||||||
|
context := block.Header()
|
||||||
|
context.Root = common.Hash{}
|
||||||
|
context.ReceiptHash = common.Hash{}
|
||||||
|
|
||||||
|
task := types.NewBlockWithHeader(context).WithBody(*block.Body())
|
||||||
|
|
||||||
|
// Run the stateless self-cross-validation
|
||||||
|
crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, task, witness)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("stateless self-validation failed: %v", err)
|
||||||
|
}
|
||||||
|
if crossStateRoot != block.Root() {
|
||||||
|
return nil, fmt.Errorf("stateless self-validation root mismatch (cross: %x local: %x)", crossStateRoot, block.Root())
|
||||||
|
}
|
||||||
|
if crossReceiptRoot != block.ReceiptHash() {
|
||||||
|
return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, block.ReceiptHash())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
proctime := time.Since(start) // processing + validation
|
xvtime := time.Since(xvstart)
|
||||||
|
proctime := time.Since(start) // processing + validation + cross validation
|
||||||
|
|
||||||
// Update the metrics touched during block processing and validation
|
// Update the metrics touched during block processing and validation
|
||||||
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
|
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
|
||||||
|
@ -1930,6 +1959,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
|
||||||
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
|
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
|
||||||
blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing
|
blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing
|
||||||
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
|
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
|
||||||
|
blockCrossValidationTimer.Update(xvtime) // The time spent on stateless cross validation
|
||||||
|
|
||||||
// Write the block to the chain and get the status.
|
// Write the block to the chain and get the status.
|
||||||
var (
|
var (
|
||||||
|
@ -1964,7 +1994,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
|
||||||
// The method writes all (header-and-body-valid) blocks to disk, then tries to
|
// The method writes all (header-and-body-valid) blocks to disk, then tries to
|
||||||
// switch over to the new chain if the TD exceeded the current chain.
|
// switch over to the new chain if the TD exceeded the current chain.
|
||||||
// insertSideChain is only used pre-merge.
|
// insertSideChain is only used pre-merge.
|
||||||
func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (int, error) {
|
func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, makeWitness bool) (*stateless.Witness, int, error) {
|
||||||
var (
|
var (
|
||||||
externTd *big.Int
|
externTd *big.Int
|
||||||
current = bc.CurrentBlock()
|
current = bc.CurrentBlock()
|
||||||
|
@ -2000,7 +2030,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||||
// If someone legitimately side-mines blocks, they would still be imported as usual. However,
|
// If someone legitimately side-mines blocks, they would still be imported as usual. However,
|
||||||
// we cannot risk writing unverified blocks to disk when they obviously target the pruning
|
// we cannot risk writing unverified blocks to disk when they obviously target the pruning
|
||||||
// mechanism.
|
// mechanism.
|
||||||
return it.index, errors.New("sidechain ghost-state attack")
|
return nil, it.index, errors.New("sidechain ghost-state attack")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if externTd == nil {
|
if externTd == nil {
|
||||||
|
@ -2011,7 +2041,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||||
if !bc.HasBlock(block.Hash(), block.NumberU64()) {
|
if !bc.HasBlock(block.Hash(), block.NumberU64()) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
if err := bc.writeBlockWithoutState(block, externTd); err != nil {
|
if err := bc.writeBlockWithoutState(block, externTd); err != nil {
|
||||||
return it.index, err
|
return nil, it.index, err
|
||||||
}
|
}
|
||||||
log.Debug("Injected sidechain block", "number", block.Number(), "hash", block.Hash(),
|
log.Debug("Injected sidechain block", "number", block.Number(), "hash", block.Hash(),
|
||||||
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
|
"diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)),
|
||||||
|
@ -2028,7 +2058,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||||
for parent != nil && !bc.HasState(parent.Root) {
|
for parent != nil && !bc.HasState(parent.Root) {
|
||||||
if bc.stateRecoverable(parent.Root) {
|
if bc.stateRecoverable(parent.Root) {
|
||||||
if err := bc.triedb.Recover(parent.Root); err != nil {
|
if err := bc.triedb.Recover(parent.Root); err != nil {
|
||||||
return 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -2038,7 +2068,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||||
parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1)
|
parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1)
|
||||||
}
|
}
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
return it.index, errors.New("missing parent")
|
return nil, it.index, errors.New("missing parent")
|
||||||
}
|
}
|
||||||
// Import all the pruned blocks to make the state available
|
// Import all the pruned blocks to make the state available
|
||||||
var (
|
var (
|
||||||
|
@ -2057,30 +2087,30 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||||
// memory here.
|
// memory here.
|
||||||
if len(blocks) >= 2048 || memory > 64*1024*1024 {
|
if len(blocks) >= 2048 || memory > 64*1024*1024 {
|
||||||
log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64())
|
log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64())
|
||||||
if _, err := bc.insertChain(blocks, true); err != nil {
|
if _, _, err := bc.insertChain(blocks, true, false); err != nil {
|
||||||
return 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
blocks, memory = blocks[:0], 0
|
blocks, memory = blocks[:0], 0
|
||||||
|
|
||||||
// If the chain is terminating, stop processing blocks
|
// If the chain is terminating, stop processing blocks
|
||||||
if bc.insertStopped() {
|
if bc.insertStopped() {
|
||||||
log.Debug("Abort during blocks processing")
|
log.Debug("Abort during blocks processing")
|
||||||
return 0, nil
|
return nil, 0, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(blocks) > 0 {
|
if len(blocks) > 0 {
|
||||||
log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64())
|
log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64())
|
||||||
return bc.insertChain(blocks, true)
|
return bc.insertChain(blocks, true, makeWitness)
|
||||||
}
|
}
|
||||||
return 0, nil
|
return nil, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// recoverAncestors finds the closest ancestor with available state and re-execute
|
// recoverAncestors finds the closest ancestor with available state and re-execute
|
||||||
// all the ancestor blocks since that.
|
// all the ancestor blocks since that.
|
||||||
// recoverAncestors is only used post-merge.
|
// recoverAncestors is only used post-merge.
|
||||||
// We return the hash of the latest block that we could correctly validate.
|
// We return the hash of the latest block that we could correctly validate.
|
||||||
func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) {
|
func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (common.Hash, error) {
|
||||||
// Gather all the sidechain hashes (full blocks may be memory heavy)
|
// Gather all the sidechain hashes (full blocks may be memory heavy)
|
||||||
var (
|
var (
|
||||||
hashes []common.Hash
|
hashes []common.Hash
|
||||||
|
@ -2120,7 +2150,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error)
|
||||||
} else {
|
} else {
|
||||||
b = bc.GetBlock(hashes[i], numbers[i])
|
b = bc.GetBlock(hashes[i], numbers[i])
|
||||||
}
|
}
|
||||||
if _, err := bc.insertChain(types.Blocks{b}, false); err != nil {
|
if _, _, err := bc.insertChain(types.Blocks{b}, false, makeWitness && i == 0); err != nil {
|
||||||
return b.ParentHash(), err
|
return b.ParentHash(), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2336,14 +2366,14 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
|
||||||
// The key difference between the InsertChain is it won't do the canonical chain
|
// The key difference between the InsertChain is it won't do the canonical chain
|
||||||
// updating. It relies on the additional SetCanonical call to finalize the entire
|
// updating. It relies on the additional SetCanonical call to finalize the entire
|
||||||
// procedure.
|
// procedure.
|
||||||
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error {
|
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*stateless.Witness, error) {
|
||||||
if !bc.chainmu.TryLock() {
|
if !bc.chainmu.TryLock() {
|
||||||
return errChainStopped
|
return nil, errChainStopped
|
||||||
}
|
}
|
||||||
defer bc.chainmu.Unlock()
|
defer bc.chainmu.Unlock()
|
||||||
|
|
||||||
_, err := bc.insertChain(types.Blocks{block}, false)
|
witness, _, err := bc.insertChain(types.Blocks{block}, false, makeWitness)
|
||||||
return err
|
return witness, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCanonical rewinds the chain to set the new head block as the specified
|
// SetCanonical rewinds the chain to set the new head block as the specified
|
||||||
|
@ -2357,7 +2387,7 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
|
||||||
|
|
||||||
// Re-execute the reorged chain in case the head state is missing.
|
// Re-execute the reorged chain in case the head state is missing.
|
||||||
if !bc.HasState(head.Root()) {
|
if !bc.HasState(head.Root()) {
|
||||||
if latestValidHash, err := bc.recoverAncestors(head); err != nil {
|
if latestValidHash, err := bc.recoverAncestors(head, false); err != nil {
|
||||||
return latestValidHash, err
|
return latestValidHash, err
|
||||||
}
|
}
|
||||||
log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash())
|
log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash())
|
||||||
|
|
|
@ -3674,7 +3674,7 @@ func testSetCanonical(t *testing.T, scheme string) {
|
||||||
gen.AddTx(tx)
|
gen.AddTx(tx)
|
||||||
})
|
})
|
||||||
for _, block := range side {
|
for _, block := range side {
|
||||||
err := chain.InsertBlockWithoutSetHead(block)
|
_, err := chain.InsertBlockWithoutSetHead(block, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to insert into chain: %v", err)
|
t.Fatalf("Failed to insert into chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,21 +25,30 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecuteStateless runs a stateless execution based on a witness, verifies
|
// ExecuteStateless runs a stateless execution based on a witness, verifies
|
||||||
// everything it can locally and returns the two computed fields that need the
|
// everything it can locally and returns the state root and receipt root, that
|
||||||
// other side to explicitly check.
|
// need the other side to explicitly check.
|
||||||
//
|
//
|
||||||
// This method is a bit of a sore thumb here, but:
|
// This method is a bit of a sore thumb here, but:
|
||||||
// - It cannot be placed in core/stateless, because state.New prodces a circular dep
|
// - It cannot be placed in core/stateless, because state.New prodces a circular dep
|
||||||
// - It cannot be placed outside of core, because it needs to construct a dud headerchain
|
// - It cannot be placed outside of core, because it needs to construct a dud headerchain
|
||||||
//
|
//
|
||||||
// TODO(karalabe): Would be nice to resolve both issues above somehow and move it.
|
// TODO(karalabe): Would be nice to resolve both issues above somehow and move it.
|
||||||
func ExecuteStateless(config *params.ChainConfig, witness *stateless.Witness) (common.Hash, common.Hash, error) {
|
func ExecuteStateless(config *params.ChainConfig, block *types.Block, witness *stateless.Witness) (common.Hash, common.Hash, error) {
|
||||||
|
// Sanity check if the supplied block accidentally contains a set root or
|
||||||
|
// receipt hash. If so, be very loud, but still continue.
|
||||||
|
if block.Root() != (common.Hash{}) {
|
||||||
|
log.Error("stateless runner received state root it's expected to calculate (faulty consensus client)", "block", block.Number())
|
||||||
|
}
|
||||||
|
if block.ReceiptHash() != (common.Hash{}) {
|
||||||
|
log.Error("stateless runner received receipt root it's expected to calculate (faulty consensus client)", "block", block.Number())
|
||||||
|
}
|
||||||
// Create and populate the state database to serve as the stateless backend
|
// Create and populate the state database to serve as the stateless backend
|
||||||
memdb := witness.MakeHashDB()
|
memdb := witness.MakeHashDB()
|
||||||
db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil))
|
db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil))
|
||||||
|
@ -57,16 +66,15 @@ func ExecuteStateless(config *params.ChainConfig, witness *stateless.Witness) (c
|
||||||
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
|
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
|
||||||
|
|
||||||
// Run the stateless blocks processing and self-validate certain fields
|
// Run the stateless blocks processing and self-validate certain fields
|
||||||
res, err := processor.Process(witness.Block, db, vm.Config{})
|
res, err := processor.Process(block, db, vm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, common.Hash{}, err
|
return common.Hash{}, common.Hash{}, err
|
||||||
}
|
}
|
||||||
if err = validator.ValidateState(witness.Block, db, res, true); err != nil {
|
if err = validator.ValidateState(block, db, res, true); err != nil {
|
||||||
return common.Hash{}, common.Hash{}, err
|
return common.Hash{}, common.Hash{}, err
|
||||||
}
|
}
|
||||||
// Almost everything validated, but receipt and state root needs to be returned
|
// Almost everything validated, but receipt and state root needs to be returned
|
||||||
receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
|
receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
|
||||||
stateRoot := db.IntermediateRoot(config.IsEIP158(witness.Block.Number()))
|
stateRoot := db.IntermediateRoot(config.IsEIP158(block.Number()))
|
||||||
|
return stateRoot, receiptRoot, nil
|
||||||
return receiptRoot, stateRoot, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,13 @@ import (
|
||||||
// MakeHashDB imports tries, codes and block hashes from a witness into a new
|
// MakeHashDB imports tries, codes and block hashes from a witness into a new
|
||||||
// hash-based memory db. We could eventually rewrite this into a pathdb, but
|
// hash-based memory db. We could eventually rewrite this into a pathdb, but
|
||||||
// simple is better for now.
|
// simple is better for now.
|
||||||
|
//
|
||||||
|
// Note, this hashdb approach is quite strictly self-validating:
|
||||||
|
// - Headers are persisted keyed by hash, so blockhash will error on junk
|
||||||
|
// - Codes are persisted keyed by hash, so bytecode lookup will error on junk
|
||||||
|
// - Trie nodes are persisted keyed by hash, so trie expansion will error on junk
|
||||||
|
//
|
||||||
|
// Acceleration structures built would need to explicitly validate the witness.
|
||||||
func (w *Witness) MakeHashDB() ethdb.Database {
|
func (w *Witness) MakeHashDB() ethdb.Database {
|
||||||
var (
|
var (
|
||||||
memdb = rawdb.NewMemoryDatabase()
|
memdb = rawdb.NewMemoryDatabase()
|
||||||
|
|
|
@ -17,42 +17,31 @@
|
||||||
package stateless
|
package stateless
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type extWitness -field-override extWitnessMarshalling -out gen_encoding_json.go
|
|
||||||
|
|
||||||
// toExtWitness converts our internal witness representation to the consensus one.
|
// toExtWitness converts our internal witness representation to the consensus one.
|
||||||
func (w *Witness) toExtWitness() *extWitness {
|
func (w *Witness) toExtWitness() *extWitness {
|
||||||
ext := &extWitness{
|
ext := &extWitness{
|
||||||
Block: w.Block,
|
|
||||||
Headers: w.Headers,
|
Headers: w.Headers,
|
||||||
}
|
}
|
||||||
ext.Codes = make([][]byte, 0, len(w.Codes))
|
ext.Codes = make([][]byte, 0, len(w.Codes))
|
||||||
for code := range w.Codes {
|
for code := range w.Codes {
|
||||||
ext.Codes = append(ext.Codes, []byte(code))
|
ext.Codes = append(ext.Codes, []byte(code))
|
||||||
}
|
}
|
||||||
slices.SortFunc(ext.Codes, bytes.Compare)
|
|
||||||
|
|
||||||
ext.State = make([][]byte, 0, len(w.State))
|
ext.State = make([][]byte, 0, len(w.State))
|
||||||
for node := range w.State {
|
for node := range w.State {
|
||||||
ext.State = append(ext.State, []byte(node))
|
ext.State = append(ext.State, []byte(node))
|
||||||
}
|
}
|
||||||
slices.SortFunc(ext.State, bytes.Compare)
|
|
||||||
return ext
|
return ext
|
||||||
}
|
}
|
||||||
|
|
||||||
// fromExtWitness converts the consensus witness format into our internal one.
|
// fromExtWitness converts the consensus witness format into our internal one.
|
||||||
func (w *Witness) fromExtWitness(ext *extWitness) error {
|
func (w *Witness) fromExtWitness(ext *extWitness) error {
|
||||||
w.Block, w.Headers = ext.Block, ext.Headers
|
w.Headers = ext.Headers
|
||||||
|
|
||||||
w.Codes = make(map[string]struct{}, len(ext.Codes))
|
w.Codes = make(map[string]struct{}, len(ext.Codes))
|
||||||
for _, code := range ext.Codes {
|
for _, code := range ext.Codes {
|
||||||
|
@ -62,12 +51,7 @@ func (w *Witness) fromExtWitness(ext *extWitness) error {
|
||||||
for _, node := range ext.State {
|
for _, node := range ext.State {
|
||||||
w.State[string(node)] = struct{}{}
|
w.State[string(node)] = struct{}{}
|
||||||
}
|
}
|
||||||
return w.sanitize()
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshals a witness as JSON.
|
|
||||||
func (w *Witness) MarshalJSON() ([]byte, error) {
|
|
||||||
return w.toExtWitness().MarshalJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeRLP serializes a witness as RLP.
|
// EncodeRLP serializes a witness as RLP.
|
||||||
|
@ -75,15 +59,6 @@ func (w *Witness) EncodeRLP(wr io.Writer) error {
|
||||||
return rlp.Encode(wr, w.toExtWitness())
|
return rlp.Encode(wr, w.toExtWitness())
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals from JSON.
|
|
||||||
func (w *Witness) UnmarshalJSON(input []byte) error {
|
|
||||||
var ext extWitness
|
|
||||||
if err := ext.UnmarshalJSON(input); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return w.fromExtWitness(&ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeRLP decodes a witness from RLP.
|
// DecodeRLP decodes a witness from RLP.
|
||||||
func (w *Witness) DecodeRLP(s *rlp.Stream) error {
|
func (w *Witness) DecodeRLP(s *rlp.Stream) error {
|
||||||
var ext extWitness
|
var ext extWitness
|
||||||
|
@ -93,37 +68,9 @@ func (w *Witness) DecodeRLP(s *rlp.Stream) error {
|
||||||
return w.fromExtWitness(&ext)
|
return w.fromExtWitness(&ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitize checks for some mandatory fields in the witness after decoding so
|
|
||||||
// the rest of the code can assume invariants and doesn't have to deal with
|
|
||||||
// corrupted data.
|
|
||||||
func (w *Witness) sanitize() error {
|
|
||||||
// Verify that the "parent" header (i.e. index 0) is available, and is the
|
|
||||||
// true parent of the block-to-be executed, since we use that to link the
|
|
||||||
// current block to the pre-state.
|
|
||||||
if len(w.Headers) == 0 {
|
|
||||||
return errors.New("parent header (for pre-root hash) missing")
|
|
||||||
}
|
|
||||||
for i, header := range w.Headers {
|
|
||||||
if header == nil {
|
|
||||||
return fmt.Errorf("witness header nil at position %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if w.Headers[0].Hash() != w.Block.ParentHash() {
|
|
||||||
return fmt.Errorf("parent hash different: witness %v, block parent %v", w.Headers[0].Hash(), w.Block.ParentHash())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// extWitness is a witness RLP encoding for transferring across clients.
|
// extWitness is a witness RLP encoding for transferring across clients.
|
||||||
type extWitness struct {
|
type extWitness struct {
|
||||||
Block *types.Block `json:"block" gencodec:"required"`
|
Headers []*types.Header
|
||||||
Headers []*types.Header `json:"headers" gencodec:"required"`
|
Codes [][]byte
|
||||||
Codes [][]byte `json:"codes"`
|
State [][]byte
|
||||||
State [][]byte `json:"state"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// extWitnessMarshalling defines the hex marshalling types for a witness.
|
|
||||||
type extWitnessMarshalling struct {
|
|
||||||
Codes []hexutil.Bytes
|
|
||||||
State []hexutil.Bytes
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
|
||||||
|
|
||||||
package stateless
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = (*extWitnessMarshalling)(nil)
|
|
||||||
|
|
||||||
// MarshalJSON marshals as JSON.
|
|
||||||
func (e extWitness) MarshalJSON() ([]byte, error) {
|
|
||||||
type extWitness struct {
|
|
||||||
Block *types.Block `json:"block" gencodec:"required"`
|
|
||||||
Headers []*types.Header `json:"headers" gencodec:"required"`
|
|
||||||
Codes []hexutil.Bytes `json:"codes"`
|
|
||||||
State []hexutil.Bytes `json:"state"`
|
|
||||||
}
|
|
||||||
var enc extWitness
|
|
||||||
enc.Block = e.Block
|
|
||||||
enc.Headers = e.Headers
|
|
||||||
if e.Codes != nil {
|
|
||||||
enc.Codes = make([]hexutil.Bytes, len(e.Codes))
|
|
||||||
for k, v := range e.Codes {
|
|
||||||
enc.Codes[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if e.State != nil {
|
|
||||||
enc.State = make([]hexutil.Bytes, len(e.State))
|
|
||||||
for k, v := range e.State {
|
|
||||||
enc.State[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return json.Marshal(&enc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals from JSON.
|
|
||||||
func (e *extWitness) UnmarshalJSON(input []byte) error {
|
|
||||||
type extWitness struct {
|
|
||||||
Block *types.Block `json:"block" gencodec:"required"`
|
|
||||||
Headers []*types.Header `json:"headers" gencodec:"required"`
|
|
||||||
Codes []hexutil.Bytes `json:"codes"`
|
|
||||||
State []hexutil.Bytes `json:"state"`
|
|
||||||
}
|
|
||||||
var dec extWitness
|
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if dec.Block == nil {
|
|
||||||
return errors.New("missing required field 'block' for extWitness")
|
|
||||||
}
|
|
||||||
e.Block = dec.Block
|
|
||||||
if dec.Headers == nil {
|
|
||||||
return errors.New("missing required field 'headers' for extWitness")
|
|
||||||
}
|
|
||||||
e.Headers = dec.Headers
|
|
||||||
if dec.Codes != nil {
|
|
||||||
e.Codes = make([][]byte, len(dec.Codes))
|
|
||||||
for k, v := range dec.Codes {
|
|
||||||
e.Codes[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dec.State != nil {
|
|
||||||
e.State = make([][]byte, len(dec.State))
|
|
||||||
for k, v := range dec.State {
|
|
||||||
e.State[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -17,16 +17,13 @@
|
||||||
package stateless
|
package stateless
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"maps"
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"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/rlp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeaderReader is an interface to pull in headers in place of block hashes for
|
// HeaderReader is an interface to pull in headers in place of block hashes for
|
||||||
|
@ -36,10 +33,11 @@ type HeaderReader interface {
|
||||||
GetHeader(hash common.Hash, number uint64) *types.Header
|
GetHeader(hash common.Hash, number uint64) *types.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Witness encompasses a block, state and any other chain data required to apply
|
// Witness encompasses the state required to apply a set of transactions and
|
||||||
// a set of transactions and derive a post state/receipt root.
|
// derive a post state/receipt root.
|
||||||
type Witness struct {
|
type Witness struct {
|
||||||
Block *types.Block // Current block with rootHash and receiptHash zeroed out
|
context *types.Header // Header to which this witness belongs to, with rootHash and receiptHash zeroed out
|
||||||
|
|
||||||
Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set.
|
Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set.
|
||||||
Codes map[string]struct{} // Set of bytecodes ran or accessed
|
Codes map[string]struct{} // Set of bytecodes ran or accessed
|
||||||
State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
|
State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
|
||||||
|
@ -49,24 +47,23 @@ type Witness struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWitness creates an empty witness ready for population.
|
// NewWitness creates an empty witness ready for population.
|
||||||
func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) {
|
func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
|
||||||
// Zero out the result fields to avoid accidentally sending them to the verifier
|
// When building witnesses, retrieve the parent header, which will *always*
|
||||||
header := block.Header()
|
// be included to act as a trustless pre-root hash container
|
||||||
header.Root = common.Hash{}
|
var headers []*types.Header
|
||||||
header.ReceiptHash = common.Hash{}
|
if chain != nil {
|
||||||
|
parent := chain.GetHeader(context.ParentHash, context.Number.Uint64()-1)
|
||||||
// Retrieve the parent header, which will *always* be included to act as a
|
|
||||||
// trustless pre-root hash container
|
|
||||||
parent := chain.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
return nil, errors.New("failed to retrieve parent header")
|
return nil, errors.New("failed to retrieve parent header")
|
||||||
}
|
}
|
||||||
|
headers = append(headers, parent)
|
||||||
|
}
|
||||||
// Create the wtness with a reconstructed gutted out block
|
// Create the wtness with a reconstructed gutted out block
|
||||||
return &Witness{
|
return &Witness{
|
||||||
Block: types.NewBlockWithHeader(header).WithBody(*block.Body()),
|
context: context,
|
||||||
|
Headers: headers,
|
||||||
Codes: make(map[string]struct{}),
|
Codes: make(map[string]struct{}),
|
||||||
State: make(map[string]struct{}),
|
State: make(map[string]struct{}),
|
||||||
Headers: []*types.Header{parent},
|
|
||||||
chain: chain,
|
chain: chain,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -76,11 +73,8 @@ func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) {
|
||||||
// the chain to cover the block being added.
|
// the chain to cover the block being added.
|
||||||
func (w *Witness) AddBlockHash(number uint64) {
|
func (w *Witness) AddBlockHash(number uint64) {
|
||||||
// Keep pulling in headers until this hash is populated
|
// Keep pulling in headers until this hash is populated
|
||||||
for int(w.Block.NumberU64()-number) > len(w.Headers) {
|
for int(w.context.Number.Uint64()-number) > len(w.Headers) {
|
||||||
tail := w.Block.Header()
|
tail := w.Headers[len(w.Headers)-1]
|
||||||
if len(w.Headers) > 0 {
|
|
||||||
tail = w.Headers[len(w.Headers)-1]
|
|
||||||
}
|
|
||||||
w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1))
|
w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,45 +101,16 @@ func (w *Witness) AddState(nodes map[string]struct{}) {
|
||||||
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
|
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
|
||||||
// is never mutated by Witness
|
// is never mutated by Witness
|
||||||
func (w *Witness) Copy() *Witness {
|
func (w *Witness) Copy() *Witness {
|
||||||
return &Witness{
|
cpy := &Witness{
|
||||||
Block: w.Block,
|
|
||||||
Headers: slices.Clone(w.Headers),
|
Headers: slices.Clone(w.Headers),
|
||||||
Codes: maps.Clone(w.Codes),
|
Codes: maps.Clone(w.Codes),
|
||||||
State: maps.Clone(w.State),
|
State: maps.Clone(w.State),
|
||||||
|
chain: w.chain,
|
||||||
}
|
}
|
||||||
|
if w.context != nil {
|
||||||
|
cpy.context = types.CopyHeader(w.context)
|
||||||
}
|
}
|
||||||
|
return cpy
|
||||||
// String prints a human-readable summary containing the total size of the
|
|
||||||
// witness and the sizes of the underlying components
|
|
||||||
func (w *Witness) String() string {
|
|
||||||
blob, _ := rlp.EncodeToBytes(w)
|
|
||||||
bytesTotal := len(blob)
|
|
||||||
|
|
||||||
blob, _ = rlp.EncodeToBytes(w.Block)
|
|
||||||
bytesBlock := len(blob)
|
|
||||||
|
|
||||||
bytesHeaders := 0
|
|
||||||
for _, header := range w.Headers {
|
|
||||||
blob, _ = rlp.EncodeToBytes(header)
|
|
||||||
bytesHeaders += len(blob)
|
|
||||||
}
|
|
||||||
bytesCodes := 0
|
|
||||||
for code := range w.Codes {
|
|
||||||
bytesCodes += len(code)
|
|
||||||
}
|
|
||||||
bytesState := 0
|
|
||||||
for node := range w.State {
|
|
||||||
bytesState += len(node)
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
fmt.Fprintf(buf, "Witness #%d: %v\n", w.Block.Number(), common.StorageSize(bytesTotal))
|
|
||||||
fmt.Fprintf(buf, " block (%4d txs): %10v\n", len(w.Block.Transactions()), common.StorageSize(bytesBlock))
|
|
||||||
fmt.Fprintf(buf, "%4d headers: %10v\n", len(w.Headers), common.StorageSize(bytesHeaders))
|
|
||||||
fmt.Fprintf(buf, "%4d trie nodes: %10v\n", len(w.State), common.StorageSize(bytesState))
|
|
||||||
fmt.Fprintf(buf, "%4d codes: %10v\n", len(w.Codes), common.StorageSize(bytesCodes))
|
|
||||||
|
|
||||||
return buf.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root returns the pre-state root from the first header.
|
// Root returns the pre-state root from the first header.
|
||||||
|
|
|
@ -19,9 +19,7 @@ package core
|
||||||
import (
|
import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
)
|
)
|
||||||
|
@ -35,9 +33,6 @@ type Validator interface {
|
||||||
|
|
||||||
// ValidateState validates the given statedb and optionally the process result.
|
// ValidateState validates the given statedb and optionally the process result.
|
||||||
ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error
|
ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error
|
||||||
|
|
||||||
// ValidateWitness cross validates a block execution with stateless remote clients.
|
|
||||||
ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot 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.
|
||||||
|
|
|
@ -33,7 +33,8 @@ type Config struct {
|
||||||
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
|
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
|
||||||
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
|
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
|
||||||
ExtraEips []int // Additional EIPS that are to be enabled
|
ExtraEips []int // Additional EIPS that are to be enabled
|
||||||
EnableWitnessCollection bool // true if witness collection is enabled
|
|
||||||
|
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScopeContext contains the things that are per-call, such as stack and memory,
|
// ScopeContext contains the things that are per-call, such as stack and memory,
|
||||||
|
|
|
@ -183,7 +183,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
var (
|
var (
|
||||||
vmConfig = vm.Config{
|
vmConfig = vm.Config{
|
||||||
EnablePreimageRecording: config.EnablePreimageRecording,
|
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||||
EnableWitnessCollection: config.EnableWitnessCollection,
|
|
||||||
}
|
}
|
||||||
cacheConfig = &core.CacheConfig{
|
cacheConfig = &core.CacheConfig{
|
||||||
TrieCleanLimit: config.TrieCleanCache,
|
TrieCleanLimit: config.TrieCleanCache,
|
||||||
|
|
|
@ -27,7 +27,9 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/beacon/engine"
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
"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"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
|
@ -37,6 +39,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/params/forks"
|
"github.com/ethereum/go-ethereum/params/forks"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,6 +85,9 @@ var caps = []string{
|
||||||
"engine_forkchoiceUpdatedV1",
|
"engine_forkchoiceUpdatedV1",
|
||||||
"engine_forkchoiceUpdatedV2",
|
"engine_forkchoiceUpdatedV2",
|
||||||
"engine_forkchoiceUpdatedV3",
|
"engine_forkchoiceUpdatedV3",
|
||||||
|
"engine_forkchoiceUpdatedWithWitnessV1",
|
||||||
|
"engine_forkchoiceUpdatedWithWitnessV2",
|
||||||
|
"engine_forkchoiceUpdatedWithWitnessV3",
|
||||||
"engine_exchangeTransitionConfigurationV1",
|
"engine_exchangeTransitionConfigurationV1",
|
||||||
"engine_getPayloadV1",
|
"engine_getPayloadV1",
|
||||||
"engine_getPayloadV2",
|
"engine_getPayloadV2",
|
||||||
|
@ -91,6 +97,14 @@ var caps = []string{
|
||||||
"engine_newPayloadV2",
|
"engine_newPayloadV2",
|
||||||
"engine_newPayloadV3",
|
"engine_newPayloadV3",
|
||||||
"engine_newPayloadV4",
|
"engine_newPayloadV4",
|
||||||
|
"engine_newPayloadWithWitnessV1",
|
||||||
|
"engine_newPayloadWithWitnessV2",
|
||||||
|
"engine_newPayloadWithWitnessV3",
|
||||||
|
"engine_newPayloadWithWitnessV4",
|
||||||
|
"engine_executeStatelessPayloadV1",
|
||||||
|
"engine_executeStatelessPayloadV2",
|
||||||
|
"engine_executeStatelessPayloadV3",
|
||||||
|
"engine_executeStatelessPayloadV4",
|
||||||
"engine_getPayloadBodiesByHashV1",
|
"engine_getPayloadBodiesByHashV1",
|
||||||
"engine_getPayloadBodiesByHashV2",
|
"engine_getPayloadBodiesByHashV2",
|
||||||
"engine_getPayloadBodiesByRangeV1",
|
"engine_getPayloadBodiesByRangeV1",
|
||||||
|
@ -188,7 +202,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
|
||||||
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai"))
|
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1)
|
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload
|
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload
|
||||||
|
@ -211,7 +225,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
|
||||||
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called with paris and shanghai payloads"))
|
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called with paris and shanghai payloads"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return api.forkchoiceUpdated(update, params, engine.PayloadV2)
|
return api.forkchoiceUpdated(update, params, engine.PayloadV2, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root
|
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root
|
||||||
|
@ -232,10 +246,68 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
|
||||||
// hash, even if params are wrong. To do this we need to split up
|
// hash, even if params are wrong. To do this we need to split up
|
||||||
// forkchoiceUpdate into a function that only updates the head and then a
|
// forkchoiceUpdate into a function that only updates the head and then a
|
||||||
// function that kicks off block construction.
|
// function that kicks off block construction.
|
||||||
return api.forkchoiceUpdated(update, params, engine.PayloadV3)
|
return api.forkchoiceUpdated(update, params, engine.PayloadV3, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion) (engine.ForkChoiceResponse, error) {
|
// ForkchoiceUpdatedWithWitnessV1 is analogous to ForkchoiceUpdatedV1, only it
|
||||||
|
// generates an execution witness too if block building was requested.
|
||||||
|
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
|
if payloadAttributes != nil {
|
||||||
|
if payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("withdrawals and beacon root not supported in V1"))
|
||||||
|
}
|
||||||
|
if api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, payloadAttributes.Timestamp) {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForkchoiceUpdatedWithWitnessV2 is analogous to ForkchoiceUpdatedV2, only it
|
||||||
|
// generates an execution witness too if block building was requested.
|
||||||
|
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
|
if params != nil {
|
||||||
|
if params.BeaconRoot != nil {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("unexpected beacon root"))
|
||||||
|
}
|
||||||
|
switch api.eth.BlockChain().Config().LatestFork(params.Timestamp) {
|
||||||
|
case forks.Paris:
|
||||||
|
if params.Withdrawals != nil {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("withdrawals before shanghai"))
|
||||||
|
}
|
||||||
|
case forks.Shanghai:
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called with paris and shanghai payloads"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return api.forkchoiceUpdated(update, params, engine.PayloadV2, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForkchoiceUpdatedWithWitnessV3 is analogous to ForkchoiceUpdatedV3, only it
|
||||||
|
// generates an execution witness too if block building was requested.
|
||||||
|
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
|
if params != nil {
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
|
||||||
|
}
|
||||||
|
if params.BeaconRoot == nil {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root"))
|
||||||
|
}
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
|
||||||
|
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO(matt): the spec requires that fcu is applied when called on a valid
|
||||||
|
// hash, even if params are wrong. To do this we need to split up
|
||||||
|
// forkchoiceUpdate into a function that only updates the head and then a
|
||||||
|
// function that kicks off block construction.
|
||||||
|
return api.forkchoiceUpdated(update, params, engine.PayloadV3, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) {
|
||||||
api.forkchoiceLock.Lock()
|
api.forkchoiceLock.Lock()
|
||||||
defer api.forkchoiceLock.Unlock()
|
defer api.forkchoiceLock.Unlock()
|
||||||
|
|
||||||
|
@ -378,7 +450,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
||||||
if api.localBlocks.has(id) {
|
if api.localBlocks.has(id) {
|
||||||
return valid(&id), nil
|
return valid(&id), nil
|
||||||
}
|
}
|
||||||
payload, err := api.eth.Miner().BuildPayload(args)
|
payload, err := api.eth.Miner().BuildPayload(args, payloadWitness)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to build payload", "err", err)
|
log.Error("Failed to build payload", "err", err)
|
||||||
return valid(nil), engine.InvalidPayloadAttributes.With(err)
|
return valid(nil), engine.InvalidPayloadAttributes.With(err)
|
||||||
|
@ -469,7 +541,7 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
|
||||||
if params.Withdrawals != nil {
|
if params.Withdrawals != nil {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
|
||||||
}
|
}
|
||||||
return api.newPayload(params, nil, nil)
|
return api.newPayload(params, nil, nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||||
|
@ -492,7 +564,7 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl
|
||||||
if params.BlobGasUsed != nil {
|
if params.BlobGasUsed != nil {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
|
||||||
}
|
}
|
||||||
return api.newPayload(params, nil, nil)
|
return api.newPayload(params, nil, nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||||
|
@ -517,9 +589,10 @@ func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHas
|
||||||
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 must only be called for cancun payloads"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 must only be called for cancun payloads"))
|
||||||
}
|
}
|
||||||
return api.newPayload(params, versionedHashes, beaconRoot)
|
return api.newPayload(params, versionedHashes, beaconRoot, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||||
// NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
// NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||||
func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
|
func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
|
||||||
if params.Withdrawals == nil {
|
if params.Withdrawals == nil {
|
||||||
|
@ -545,10 +618,186 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
|
||||||
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV4 must only be called for prague payloads"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV4 must only be called for prague payloads"))
|
||||||
}
|
}
|
||||||
return api.newPayload(params, versionedHashes, beaconRoot)
|
return api.newPayload(params, versionedHashes, beaconRoot, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
|
// NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates
|
||||||
|
// and returns a stateless witness after running the payload.
|
||||||
|
func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
|
||||||
|
if params.Withdrawals != nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
|
||||||
|
}
|
||||||
|
return api.newPayload(params, nil, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates
|
||||||
|
// and returns a stateless witness after running the payload.
|
||||||
|
func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
|
||||||
|
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun"))
|
||||||
|
}
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if params.Withdrawals != nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if params.ExcessBlobGas != nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun"))
|
||||||
|
}
|
||||||
|
if params.BlobGasUsed != nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
|
||||||
|
}
|
||||||
|
return api.newPayload(params, nil, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPayloadWithWitnessV3 is analogous to NewPayloadV3, only it also generates
|
||||||
|
// and returns a stateless witness after running the payload.
|
||||||
|
func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||||
|
}
|
||||||
|
if params.ExcessBlobGas == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
|
||||||
|
}
|
||||||
|
if params.BlobGasUsed == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if versionedHashes == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
|
||||||
|
}
|
||||||
|
if beaconRoot == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadWithWitnessV3 must only be called for cancun payloads"))
|
||||||
|
}
|
||||||
|
return api.newPayload(params, versionedHashes, beaconRoot, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPayloadWithWitnessV4 is analogous to NewPayloadV4, only it also generates
|
||||||
|
// and returns a stateless witness after running the payload.
|
||||||
|
func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||||
|
}
|
||||||
|
if params.ExcessBlobGas == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
|
||||||
|
}
|
||||||
|
if params.BlobGasUsed == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
|
||||||
|
}
|
||||||
|
if params.Deposits == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil deposits post-prague"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if versionedHashes == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
|
||||||
|
}
|
||||||
|
if beaconRoot == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadWithWitnessV4 must only be called for prague payloads"))
|
||||||
|
}
|
||||||
|
return api.newPayload(params, versionedHashes, beaconRoot, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteStatelessPayloadV1 is analogous to NewPayloadV1, only it operates in
|
||||||
|
// a stateless mode on top of a provided witness instead of the local database.
|
||||||
|
func (api *ConsensusAPI) ExecuteStatelessPayloadV1(params engine.ExecutableData, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
|
||||||
|
if params.Withdrawals != nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
|
||||||
|
}
|
||||||
|
return api.executeStatelessPayload(params, nil, nil, opaqueWitness)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteStatelessPayloadV2 is analogous to NewPayloadV2, only it operates in
|
||||||
|
// a stateless mode on top of a provided witness instead of the local database.
|
||||||
|
func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
|
||||||
|
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun"))
|
||||||
|
}
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if params.Withdrawals != nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if params.ExcessBlobGas != nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun"))
|
||||||
|
}
|
||||||
|
if params.BlobGasUsed != nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
|
||||||
|
}
|
||||||
|
return api.executeStatelessPayload(params, nil, nil, opaqueWitness)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteStatelessPayloadV3 is analogous to NewPayloadV3, only it operates in
|
||||||
|
// a stateless mode on top of a provided witness instead of the local database.
|
||||||
|
func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||||
|
}
|
||||||
|
if params.ExcessBlobGas == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
|
||||||
|
}
|
||||||
|
if params.BlobGasUsed == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if versionedHashes == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
|
||||||
|
}
|
||||||
|
if beaconRoot == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("executeStatelessPayloadV3 must only be called for cancun payloads"))
|
||||||
|
}
|
||||||
|
return api.executeStatelessPayload(params, versionedHashes, beaconRoot, opaqueWitness)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteStatelessPayloadV4 is analogous to NewPayloadV4, only it operates in
|
||||||
|
// a stateless mode on top of a provided witness instead of the local database.
|
||||||
|
func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||||
|
}
|
||||||
|
if params.ExcessBlobGas == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
|
||||||
|
}
|
||||||
|
if params.BlobGasUsed == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
|
||||||
|
}
|
||||||
|
if params.Deposits == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil deposits post-prague"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if versionedHashes == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
|
||||||
|
}
|
||||||
|
if beaconRoot == nil {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague {
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("executeStatelessPayloadV4 must only be called for prague payloads"))
|
||||||
|
}
|
||||||
|
return api.executeStatelessPayload(params, versionedHashes, beaconRoot, opaqueWitness)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, witness bool) (engine.PayloadStatusV1, error) {
|
||||||
// The locking here is, strictly, not required. Without these locks, this can happen:
|
// The locking here is, strictly, not required. Without these locks, this can happen:
|
||||||
//
|
//
|
||||||
// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
|
// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
|
||||||
|
@ -656,8 +905,9 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
|
||||||
return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil
|
return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil
|
||||||
}
|
}
|
||||||
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number())
|
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number())
|
||||||
if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
|
proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness)
|
||||||
log.Warn("NewPayloadV1: inserting block failed", "error", err)
|
if err != nil {
|
||||||
|
log.Warn("NewPayload: inserting block failed", "error", err)
|
||||||
|
|
||||||
api.invalidLock.Lock()
|
api.invalidLock.Lock()
|
||||||
api.invalidBlocksHits[block.Hash()] = 1
|
api.invalidBlocksHits[block.Hash()] = 1
|
||||||
|
@ -667,7 +917,71 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
|
||||||
return api.invalid(err, parent.Header()), nil
|
return api.invalid(err, parent.Header()), nil
|
||||||
}
|
}
|
||||||
hash := block.Hash()
|
hash := block.Hash()
|
||||||
return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
|
|
||||||
|
// If witness collection was requested, inject that into the result too
|
||||||
|
var ow *hexutil.Bytes
|
||||||
|
if proofs != nil {
|
||||||
|
ow = new(hexutil.Bytes)
|
||||||
|
*ow, _ = rlp.EncodeToBytes(proofs)
|
||||||
|
}
|
||||||
|
return engine.PayloadStatusV1{Status: engine.VALID, Witness: ow, LatestValidHash: &hash}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
|
||||||
|
log.Trace("Engine API request received", "method", "ExecuteStatelessPayload", "number", params.Number, "hash", params.BlockHash)
|
||||||
|
|
||||||
|
block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot)
|
||||||
|
if err != nil {
|
||||||
|
bgu := "nil"
|
||||||
|
if params.BlobGasUsed != nil {
|
||||||
|
bgu = strconv.Itoa(int(*params.BlobGasUsed))
|
||||||
|
}
|
||||||
|
ebg := "nil"
|
||||||
|
if params.ExcessBlobGas != nil {
|
||||||
|
ebg = strconv.Itoa(int(*params.ExcessBlobGas))
|
||||||
|
}
|
||||||
|
log.Warn("Invalid ExecuteStatelessPayload params",
|
||||||
|
"params.Number", params.Number,
|
||||||
|
"params.ParentHash", params.ParentHash,
|
||||||
|
"params.BlockHash", params.BlockHash,
|
||||||
|
"params.StateRoot", params.StateRoot,
|
||||||
|
"params.FeeRecipient", params.FeeRecipient,
|
||||||
|
"params.LogsBloom", common.PrettyBytes(params.LogsBloom),
|
||||||
|
"params.Random", params.Random,
|
||||||
|
"params.GasLimit", params.GasLimit,
|
||||||
|
"params.GasUsed", params.GasUsed,
|
||||||
|
"params.Timestamp", params.Timestamp,
|
||||||
|
"params.ExtraData", common.PrettyBytes(params.ExtraData),
|
||||||
|
"params.BaseFeePerGas", params.BaseFeePerGas,
|
||||||
|
"params.BlobGasUsed", bgu,
|
||||||
|
"params.ExcessBlobGas", ebg,
|
||||||
|
"len(params.Transactions)", len(params.Transactions),
|
||||||
|
"len(params.Withdrawals)", len(params.Withdrawals),
|
||||||
|
"len(params.Deposits)", len(params.Deposits),
|
||||||
|
"beaconRoot", beaconRoot,
|
||||||
|
"error", err)
|
||||||
|
errorMsg := err.Error()
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID, ValidationError: &errorMsg}, nil
|
||||||
|
}
|
||||||
|
witness := new(stateless.Witness)
|
||||||
|
if err := rlp.DecodeBytes(opaqueWitness, witness); err != nil {
|
||||||
|
log.Warn("Invalid ExecuteStatelessPayload witness", "err", err)
|
||||||
|
errorMsg := err.Error()
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID, ValidationError: &errorMsg}, nil
|
||||||
|
}
|
||||||
|
// Stash away the last update to warn the user if the beacon client goes offline
|
||||||
|
api.lastNewPayloadLock.Lock()
|
||||||
|
api.lastNewPayloadUpdate = time.Now()
|
||||||
|
api.lastNewPayloadLock.Unlock()
|
||||||
|
|
||||||
|
log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash)
|
||||||
|
stateRoot, receiptRoot, err := core.ExecuteStateless(api.eth.BlockChain().Config(), block, witness)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("ExecuteStatelessPayload: execution failed", "err", err)
|
||||||
|
errorMsg := err.Error()
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.INVALID, ValidationError: &errorMsg}, nil
|
||||||
|
}
|
||||||
|
return engine.StatelessPayloadStatusV1{Status: engine.VALID, StateRoot: stateRoot, ReceiptsRoot: receiptRoot}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// delayPayloadImport stashes the given block away for import at a later time,
|
// delayPayloadImport stashes the given block away for import at a later time,
|
||||||
|
|
|
@ -507,7 +507,7 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := getNewPayload(t, api, parent, w, h)
|
payload := getNewPayload(t, api, parent, w, h)
|
||||||
execResp, err := api.newPayload(*payload, []common.Hash{}, h)
|
execResp, err := api.newPayload(*payload, []common.Hash{}, h, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't execute payload: %v", err)
|
t.Fatalf("can't execute payload: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -684,7 +684,7 @@ func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *engine.Pay
|
||||||
Withdrawals: params.Withdrawals,
|
Withdrawals: params.Withdrawals,
|
||||||
BeaconRoot: params.BeaconRoot,
|
BeaconRoot: params.BeaconRoot,
|
||||||
}
|
}
|
||||||
payload, err := api.eth.Miner().BuildPayload(args)
|
payload, err := api.eth.Miner().BuildPayload(args, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -922,7 +922,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
|
||||||
Random: crypto.Keccak256Hash([]byte{byte(1)}),
|
Random: crypto.Keccak256Hash([]byte{byte(1)}),
|
||||||
FeeRecipient: parent.Coinbase(),
|
FeeRecipient: parent.Coinbase(),
|
||||||
}
|
}
|
||||||
payload, err := api.eth.Miner().BuildPayload(args)
|
payload, err := api.eth.Miner().BuildPayload(args, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err)
|
t.Fatalf("error preparing payload, err=%v", err)
|
||||||
}
|
}
|
||||||
|
@ -1704,6 +1704,108 @@ func TestParentBeaconBlockRoot(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWitnessCreationAndConsumption(t *testing.T) {
|
||||||
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), log.LevelTrace, true)))
|
||||||
|
|
||||||
|
genesis, blocks := generateMergeChain(10, true)
|
||||||
|
|
||||||
|
// Set cancun time to semi-last block + 5 seconds
|
||||||
|
timestamp := blocks[len(blocks)-2].Time() + 5
|
||||||
|
genesis.Config.ShanghaiTime = ×tamp
|
||||||
|
genesis.Config.CancunTime = ×tamp
|
||||||
|
|
||||||
|
n, ethservice := startEthService(t, genesis, blocks[:9])
|
||||||
|
defer n.Close()
|
||||||
|
|
||||||
|
api := NewConsensusAPI(ethservice)
|
||||||
|
|
||||||
|
// Put the 10th block's tx in the pool and produce a new block
|
||||||
|
txs := blocks[9].Transactions()
|
||||||
|
|
||||||
|
ethservice.TxPool().Add(txs, true, true)
|
||||||
|
blockParams := engine.PayloadAttributes{
|
||||||
|
Timestamp: blocks[8].Time() + 5,
|
||||||
|
Withdrawals: make([]*types.Withdrawal, 0),
|
||||||
|
BeaconRoot: &common.Hash{42},
|
||||||
|
}
|
||||||
|
fcState := engine.ForkchoiceStateV1{
|
||||||
|
HeadBlockHash: blocks[8].Hash(),
|
||||||
|
SafeBlockHash: common.Hash{},
|
||||||
|
FinalizedBlockHash: common.Hash{},
|
||||||
|
}
|
||||||
|
_, err := api.ForkchoiceUpdatedWithWitnessV3(fcState, &blockParams)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error preparing payload, err=%v", err)
|
||||||
|
}
|
||||||
|
// Give the payload some time to be built
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
payloadID := (&miner.BuildPayloadArgs{
|
||||||
|
Parent: fcState.HeadBlockHash,
|
||||||
|
Timestamp: blockParams.Timestamp,
|
||||||
|
FeeRecipient: blockParams.SuggestedFeeRecipient,
|
||||||
|
Random: blockParams.Random,
|
||||||
|
Withdrawals: blockParams.Withdrawals,
|
||||||
|
BeaconRoot: blockParams.BeaconRoot,
|
||||||
|
Version: engine.PayloadV3,
|
||||||
|
}).Id()
|
||||||
|
envelope, err := api.GetPayloadV3(payloadID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting payload, err=%v", err)
|
||||||
|
}
|
||||||
|
if len(envelope.ExecutionPayload.Transactions) != blocks[9].Transactions().Len() {
|
||||||
|
t.Fatalf("invalid number of transactions %d != %d", len(envelope.ExecutionPayload.Transactions), blocks[9].Transactions().Len())
|
||||||
|
}
|
||||||
|
if envelope.Witness == nil {
|
||||||
|
t.Fatalf("witness missing from payload")
|
||||||
|
}
|
||||||
|
// Test stateless execution of the created witness
|
||||||
|
wantStateRoot := envelope.ExecutionPayload.StateRoot
|
||||||
|
wantReceiptRoot := envelope.ExecutionPayload.ReceiptsRoot
|
||||||
|
|
||||||
|
envelope.ExecutionPayload.StateRoot = common.Hash{}
|
||||||
|
envelope.ExecutionPayload.ReceiptsRoot = common.Hash{}
|
||||||
|
|
||||||
|
res, err := api.ExecuteStatelessPayloadV3(*envelope.ExecutionPayload, []common.Hash{}, &common.Hash{42}, *envelope.Witness)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error executing stateless payload witness: %v", err)
|
||||||
|
}
|
||||||
|
if res.StateRoot != wantStateRoot {
|
||||||
|
t.Fatalf("stateless state root mismatch: have %v, want %v", res.StateRoot, wantStateRoot)
|
||||||
|
}
|
||||||
|
if res.ReceiptsRoot != wantReceiptRoot {
|
||||||
|
t.Fatalf("stateless receipt root mismatch: have %v, want %v", res.ReceiptsRoot, wantReceiptRoot)
|
||||||
|
}
|
||||||
|
// Test block insertion with witness creation
|
||||||
|
envelope.ExecutionPayload.StateRoot = wantStateRoot
|
||||||
|
envelope.ExecutionPayload.ReceiptsRoot = wantReceiptRoot
|
||||||
|
|
||||||
|
res2, err := api.NewPayloadWithWitnessV3(*envelope.ExecutionPayload, []common.Hash{}, &common.Hash{42})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error executing stateless payload witness: %v", err)
|
||||||
|
}
|
||||||
|
if res2.Witness == nil {
|
||||||
|
t.Fatalf("witness missing from payload")
|
||||||
|
}
|
||||||
|
// Test stateless execution of the created witness
|
||||||
|
wantStateRoot = envelope.ExecutionPayload.StateRoot
|
||||||
|
wantReceiptRoot = envelope.ExecutionPayload.ReceiptsRoot
|
||||||
|
|
||||||
|
envelope.ExecutionPayload.StateRoot = common.Hash{}
|
||||||
|
envelope.ExecutionPayload.ReceiptsRoot = common.Hash{}
|
||||||
|
|
||||||
|
res, err = api.ExecuteStatelessPayloadV3(*envelope.ExecutionPayload, []common.Hash{}, &common.Hash{42}, *res2.Witness)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error executing stateless payload witness: %v", err)
|
||||||
|
}
|
||||||
|
if res.StateRoot != wantStateRoot {
|
||||||
|
t.Fatalf("stateless state root mismatch: have %v, want %v", res.StateRoot, wantStateRoot)
|
||||||
|
}
|
||||||
|
if res.ReceiptsRoot != wantReceiptRoot {
|
||||||
|
t.Fatalf("stateless receipt root mismatch: have %v, want %v", res.ReceiptsRoot, wantReceiptRoot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestGetClientVersion verifies the expected version info is returned.
|
// TestGetClientVersion verifies the expected version info is returned.
|
||||||
func TestGetClientVersion(t *testing.T) {
|
func TestGetClientVersion(t *testing.T) {
|
||||||
genesis, preMergeBlocks := generateMergeChain(10, false)
|
genesis, preMergeBlocks := generateMergeChain(10, false)
|
||||||
|
|
|
@ -181,7 +181,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
|
||||||
Withdrawals: withdrawals,
|
Withdrawals: withdrawals,
|
||||||
Random: random,
|
Random: random,
|
||||||
BeaconRoot: &common.Hash{},
|
BeaconRoot: &common.Hash{},
|
||||||
}, engine.PayloadV3)
|
}, engine.PayloadV3, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,9 +134,6 @@ type Config struct {
|
||||||
// Enables tracking of SHA3 preimages in the VM
|
// Enables tracking of SHA3 preimages in the VM
|
||||||
EnablePreimageRecording bool
|
EnablePreimageRecording bool
|
||||||
|
|
||||||
// Enables prefetching trie nodes for read operations too
|
|
||||||
EnableWitnessCollection bool `toml:"-"`
|
|
||||||
|
|
||||||
// Enables VM tracing
|
// Enables VM tracing
|
||||||
VMTrace string
|
VMTrace string
|
||||||
VMTraceJsonConfig string
|
VMTraceJsonConfig string
|
||||||
|
|
|
@ -44,7 +44,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
BlobPool blobpool.Config
|
BlobPool blobpool.Config
|
||||||
GPO gasprice.Config
|
GPO gasprice.Config
|
||||||
EnablePreimageRecording bool
|
EnablePreimageRecording bool
|
||||||
EnableWitnessCollection bool `toml:"-"`
|
|
||||||
VMTrace string
|
VMTrace string
|
||||||
VMTraceJsonConfig string
|
VMTraceJsonConfig string
|
||||||
DocRoot string `toml:"-"`
|
DocRoot string `toml:"-"`
|
||||||
|
@ -82,7 +81,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
enc.BlobPool = c.BlobPool
|
enc.BlobPool = c.BlobPool
|
||||||
enc.GPO = c.GPO
|
enc.GPO = c.GPO
|
||||||
enc.EnablePreimageRecording = c.EnablePreimageRecording
|
enc.EnablePreimageRecording = c.EnablePreimageRecording
|
||||||
enc.EnableWitnessCollection = c.EnableWitnessCollection
|
|
||||||
enc.VMTrace = c.VMTrace
|
enc.VMTrace = c.VMTrace
|
||||||
enc.VMTraceJsonConfig = c.VMTraceJsonConfig
|
enc.VMTraceJsonConfig = c.VMTraceJsonConfig
|
||||||
enc.DocRoot = c.DocRoot
|
enc.DocRoot = c.DocRoot
|
||||||
|
@ -124,7 +122,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
BlobPool *blobpool.Config
|
BlobPool *blobpool.Config
|
||||||
GPO *gasprice.Config
|
GPO *gasprice.Config
|
||||||
EnablePreimageRecording *bool
|
EnablePreimageRecording *bool
|
||||||
EnableWitnessCollection *bool `toml:"-"`
|
|
||||||
VMTrace *string
|
VMTrace *string
|
||||||
VMTraceJsonConfig *string
|
VMTraceJsonConfig *string
|
||||||
DocRoot *string `toml:"-"`
|
DocRoot *string `toml:"-"`
|
||||||
|
@ -219,9 +216,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.EnablePreimageRecording != nil {
|
if dec.EnablePreimageRecording != nil {
|
||||||
c.EnablePreimageRecording = *dec.EnablePreimageRecording
|
c.EnablePreimageRecording = *dec.EnablePreimageRecording
|
||||||
}
|
}
|
||||||
if dec.EnableWitnessCollection != nil {
|
|
||||||
c.EnableWitnessCollection = *dec.EnableWitnessCollection
|
|
||||||
}
|
|
||||||
if dec.VMTrace != nil {
|
if dec.VMTrace != nil {
|
||||||
c.VMTrace = *dec.VMTrace
|
c.VMTrace = *dec.VMTrace
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,8 +126,8 @@ func (miner *Miner) SetGasTip(tip *big.Int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildPayload builds the payload according to the provided parameters.
|
// BuildPayload builds the payload according to the provided parameters.
|
||||||
func (miner *Miner) BuildPayload(args *BuildPayloadArgs) (*Payload, error) {
|
func (miner *Miner) BuildPayload(args *BuildPayloadArgs, witness bool) (*Payload, error) {
|
||||||
return miner.buildPayload(args)
|
return miner.buildPayload(args, witness)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPending retrieves the pending block based on the current head block.
|
// getPending retrieves the pending block based on the current head block.
|
||||||
|
@ -156,7 +156,7 @@ func (miner *Miner) getPending() *newPayloadResult {
|
||||||
withdrawals: withdrawal,
|
withdrawals: withdrawal,
|
||||||
beaconRoot: nil,
|
beaconRoot: nil,
|
||||||
noTxs: false,
|
noTxs: false,
|
||||||
})
|
}, false) // we will never make a witness for a pending block
|
||||||
if ret.err != nil {
|
if ret.err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/beacon/engine"
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
@ -69,7 +71,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
|
||||||
type Payload struct {
|
type Payload struct {
|
||||||
id engine.PayloadID
|
id engine.PayloadID
|
||||||
empty *types.Block
|
empty *types.Block
|
||||||
|
emptyWitness *stateless.Witness
|
||||||
full *types.Block
|
full *types.Block
|
||||||
|
fullWitness *stateless.Witness
|
||||||
sidecars []*types.BlobTxSidecar
|
sidecars []*types.BlobTxSidecar
|
||||||
fullFees *big.Int
|
fullFees *big.Int
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
|
@ -78,10 +82,11 @@ type Payload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPayload initializes the payload object.
|
// newPayload initializes the payload object.
|
||||||
func newPayload(empty *types.Block, id engine.PayloadID) *Payload {
|
func newPayload(empty *types.Block, witness *stateless.Witness, id engine.PayloadID) *Payload {
|
||||||
payload := &Payload{
|
payload := &Payload{
|
||||||
id: id,
|
id: id,
|
||||||
empty: empty,
|
empty: empty,
|
||||||
|
emptyWitness: witness,
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
}
|
}
|
||||||
log.Info("Starting work on payload", "id", payload.id)
|
log.Info("Starting work on payload", "id", payload.id)
|
||||||
|
@ -106,6 +111,7 @@ func (payload *Payload) update(r *newPayloadResult, elapsed time.Duration) {
|
||||||
payload.full = r.block
|
payload.full = r.block
|
||||||
payload.fullFees = r.fees
|
payload.fullFees = r.fees
|
||||||
payload.sidecars = r.sidecars
|
payload.sidecars = r.sidecars
|
||||||
|
payload.fullWitness = r.witness
|
||||||
|
|
||||||
feesInEther := new(big.Float).Quo(new(big.Float).SetInt(r.fees), big.NewFloat(params.Ether))
|
feesInEther := new(big.Float).Quo(new(big.Float).SetInt(r.fees), big.NewFloat(params.Ether))
|
||||||
log.Info("Updated payload",
|
log.Info("Updated payload",
|
||||||
|
@ -135,9 +141,19 @@ func (payload *Payload) Resolve() *engine.ExecutionPayloadEnvelope {
|
||||||
close(payload.stop)
|
close(payload.stop)
|
||||||
}
|
}
|
||||||
if payload.full != nil {
|
if payload.full != nil {
|
||||||
return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
|
envelope := engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
|
||||||
|
if payload.fullWitness != nil {
|
||||||
|
envelope.Witness = new(hexutil.Bytes)
|
||||||
|
*envelope.Witness, _ = rlp.EncodeToBytes(payload.fullWitness) // cannot fail
|
||||||
}
|
}
|
||||||
return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil)
|
return envelope
|
||||||
|
}
|
||||||
|
envelope := engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil)
|
||||||
|
if payload.emptyWitness != nil {
|
||||||
|
envelope.Witness = new(hexutil.Bytes)
|
||||||
|
*envelope.Witness, _ = rlp.EncodeToBytes(payload.emptyWitness) // cannot fail
|
||||||
|
}
|
||||||
|
return envelope
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveEmpty is basically identical to Resolve, but it expects empty block only.
|
// ResolveEmpty is basically identical to Resolve, but it expects empty block only.
|
||||||
|
@ -146,7 +162,12 @@ func (payload *Payload) ResolveEmpty() *engine.ExecutionPayloadEnvelope {
|
||||||
payload.lock.Lock()
|
payload.lock.Lock()
|
||||||
defer payload.lock.Unlock()
|
defer payload.lock.Unlock()
|
||||||
|
|
||||||
return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil)
|
envelope := engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil)
|
||||||
|
if payload.emptyWitness != nil {
|
||||||
|
envelope.Witness = new(hexutil.Bytes)
|
||||||
|
*envelope.Witness, _ = rlp.EncodeToBytes(payload.emptyWitness) // cannot fail
|
||||||
|
}
|
||||||
|
return envelope
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveFull is basically identical to Resolve, but it expects full block only.
|
// ResolveFull is basically identical to Resolve, but it expects full block only.
|
||||||
|
@ -172,11 +193,16 @@ func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope {
|
||||||
default:
|
default:
|
||||||
close(payload.stop)
|
close(payload.stop)
|
||||||
}
|
}
|
||||||
return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
|
envelope := engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
|
||||||
|
if payload.fullWitness != nil {
|
||||||
|
envelope.Witness = new(hexutil.Bytes)
|
||||||
|
*envelope.Witness, _ = rlp.EncodeToBytes(payload.fullWitness) // cannot fail
|
||||||
|
}
|
||||||
|
return envelope
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildPayload builds the payload according to the provided parameters.
|
// buildPayload builds the payload according to the provided parameters.
|
||||||
func (miner *Miner) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
|
func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload, error) {
|
||||||
// Build the initial version with no transaction included. It should be fast
|
// Build the initial version with no transaction included. It should be fast
|
||||||
// enough to run. The empty payload can at least make sure there is something
|
// enough to run. The empty payload can at least make sure there is something
|
||||||
// to deliver for not missing slot.
|
// to deliver for not missing slot.
|
||||||
|
@ -190,13 +216,12 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
|
||||||
beaconRoot: args.BeaconRoot,
|
beaconRoot: args.BeaconRoot,
|
||||||
noTxs: true,
|
noTxs: true,
|
||||||
}
|
}
|
||||||
empty := miner.generateWork(emptyParams)
|
empty := miner.generateWork(emptyParams, witness)
|
||||||
if empty.err != nil {
|
if empty.err != nil {
|
||||||
return nil, empty.err
|
return nil, empty.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a payload object for return.
|
// Construct a payload object for return.
|
||||||
payload := newPayload(empty.block, args.Id())
|
payload := newPayload(empty.block, empty.witness, args.Id())
|
||||||
|
|
||||||
// Spin up a routine for updating the payload in background. This strategy
|
// Spin up a routine for updating the payload in background. This strategy
|
||||||
// can maximum the revenue for including transactions with highest fee.
|
// can maximum the revenue for including transactions with highest fee.
|
||||||
|
@ -226,7 +251,7 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
r := miner.generateWork(fullParams)
|
r := miner.generateWork(fullParams, witness)
|
||||||
if r.err == nil {
|
if r.err == nil {
|
||||||
payload.update(r, time.Since(start))
|
payload.update(r, time.Since(start))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -160,7 +160,7 @@ func TestBuildPayload(t *testing.T) {
|
||||||
Random: common.Hash{},
|
Random: common.Hash{},
|
||||||
FeeRecipient: recipient,
|
FeeRecipient: recipient,
|
||||||
}
|
}
|
||||||
payload, err := w.buildPayload(args)
|
payload, err := w.buildPayload(args, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to build payload %v", err)
|
t.Fatalf("Failed to build payload %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
@ -56,6 +57,8 @@ type environment struct {
|
||||||
receipts []*types.Receipt
|
receipts []*types.Receipt
|
||||||
sidecars []*types.BlobTxSidecar
|
sidecars []*types.BlobTxSidecar
|
||||||
blobs int
|
blobs int
|
||||||
|
|
||||||
|
witness *stateless.Witness
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -73,6 +76,7 @@ type newPayloadResult struct {
|
||||||
sidecars []*types.BlobTxSidecar // collected blobs of blob transactions
|
sidecars []*types.BlobTxSidecar // collected blobs of blob transactions
|
||||||
stateDB *state.StateDB // StateDB after executing the transactions
|
stateDB *state.StateDB // StateDB after executing the transactions
|
||||||
receipts []*types.Receipt // Receipts collected during construction
|
receipts []*types.Receipt // Receipts collected during construction
|
||||||
|
witness *stateless.Witness // Witness is an optional stateless proof
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateParams wraps various settings for generating sealing task.
|
// generateParams wraps various settings for generating sealing task.
|
||||||
|
@ -88,8 +92,8 @@ type generateParams struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateWork generates a sealing block based on the given parameters.
|
// generateWork generates a sealing block based on the given parameters.
|
||||||
func (miner *Miner) generateWork(params *generateParams) *newPayloadResult {
|
func (miner *Miner) generateWork(params *generateParams, witness bool) *newPayloadResult {
|
||||||
work, err := miner.prepareWork(params)
|
work, err := miner.prepareWork(params, witness)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &newPayloadResult{err: err}
|
return &newPayloadResult{err: err}
|
||||||
}
|
}
|
||||||
|
@ -129,13 +133,14 @@ func (miner *Miner) generateWork(params *generateParams) *newPayloadResult {
|
||||||
sidecars: work.sidecars,
|
sidecars: work.sidecars,
|
||||||
stateDB: work.state,
|
stateDB: work.state,
|
||||||
receipts: work.receipts,
|
receipts: work.receipts,
|
||||||
|
witness: work.witness,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareWork constructs the sealing task according to the given parameters,
|
// prepareWork constructs the sealing task according to the given parameters,
|
||||||
// either based on the last chain head or specified parent. In this function
|
// either based on the last chain head or specified parent. In this function
|
||||||
// the pending transactions are not filled yet, only the empty task returned.
|
// the pending transactions are not filled yet, only the empty task returned.
|
||||||
func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error) {
|
func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*environment, error) {
|
||||||
miner.confMu.RLock()
|
miner.confMu.RLock()
|
||||||
defer miner.confMu.RUnlock()
|
defer miner.confMu.RUnlock()
|
||||||
|
|
||||||
|
@ -203,7 +208,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error)
|
||||||
// Could potentially happen if starting to mine in an odd state.
|
// Could potentially happen if starting to mine in an odd state.
|
||||||
// Note genParams.coinbase can be different with header.Coinbase
|
// Note genParams.coinbase can be different with header.Coinbase
|
||||||
// since clique algorithm can modify the coinbase field in header.
|
// since clique algorithm can modify the coinbase field in header.
|
||||||
env, err := miner.makeEnv(parent, header, genParams.coinbase)
|
env, err := miner.makeEnv(parent, header, genParams.coinbase, witness)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to create sealing context", "err", err)
|
log.Error("Failed to create sealing context", "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -222,18 +227,26 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeEnv creates a new environment for the sealing block.
|
// makeEnv creates a new environment for the sealing block.
|
||||||
func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase common.Address) (*environment, error) {
|
func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase common.Address, witness bool) (*environment, error) {
|
||||||
// Retrieve the parent state to execute on top.
|
// Retrieve the parent state to execute on top.
|
||||||
state, err := miner.chain.StateAt(parent.Root)
|
state, err := miner.chain.StateAt(parent.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if witness {
|
||||||
|
bundle, err := stateless.NewWitness(header, miner.chain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
state.StartPrefetcher("miner", bundle)
|
||||||
|
}
|
||||||
// Note the passed coinbase may be different with header.Coinbase.
|
// Note the passed coinbase may be different with header.Coinbase.
|
||||||
return &environment{
|
return &environment{
|
||||||
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
|
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
|
||||||
state: state,
|
state: state,
|
||||||
coinbase: coinbase,
|
coinbase: coinbase,
|
||||||
header: header,
|
header: header,
|
||||||
|
witness: state.Witness(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,7 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
|
||||||
}
|
}
|
||||||
chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{
|
chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{
|
||||||
Tracer: tracer,
|
Tracer: tracer,
|
||||||
EnableWitnessCollection: witness,
|
StatelessSelfValidation: witness,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in New Issue