Skip to content

Commit 9326a11

Browse files
authored
beacon, core, eth, miner: integrate witnesses into production Geth (ethereum#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`.
1 parent b018da9 commit 9326a11

26 files changed

+669
-353
lines changed

beacon/engine/gen_epe.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon/engine/types.go

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,22 @@ type executableDataMarshaling struct {
9494
ExcessBlobGas *hexutil.Uint64
9595
}
9696

97+
// StatelessPayloadStatusV1 is the result of a stateless payload execution.
98+
type StatelessPayloadStatusV1 struct {
99+
Status string `json:"status"`
100+
StateRoot common.Hash `json:"stateRoot"`
101+
ReceiptsRoot common.Hash `json:"receiptsRoot"`
102+
ValidationError *string `json:"validationError"`
103+
}
104+
97105
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
98106

99107
type ExecutionPayloadEnvelope struct {
100108
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
101109
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
102110
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
103111
Override bool `json:"shouldOverrideBuilder"`
112+
Witness *hexutil.Bytes `json:"witness"`
104113
}
105114

106115
type BlobsBundleV1 struct {
@@ -115,9 +124,10 @@ type executionPayloadEnvelopeMarshaling struct {
115124
}
116125

117126
type PayloadStatusV1 struct {
118-
Status string `json:"status"`
119-
LatestValidHash *common.Hash `json:"latestValidHash"`
120-
ValidationError *string `json:"validationError"`
127+
Status string `json:"status"`
128+
Witness *hexutil.Bytes `json:"witness"`
129+
LatestValidHash *common.Hash `json:"latestValidHash"`
130+
ValidationError *string `json:"validationError"`
121131
}
122132

123133
type TransitionConfigurationV1 struct {
@@ -198,6 +208,20 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
198208
// Withdrawals value will propagate through the returned block. Empty
199209
// Withdrawals value must be passed via non-nil, length 0 value in data.
200210
func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
211+
block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot)
212+
if err != nil {
213+
return nil, err
214+
}
215+
if block.Hash() != data.BlockHash {
216+
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", data.BlockHash, block.Hash())
217+
}
218+
return block, nil
219+
}
220+
221+
// ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used
222+
// for stateless execution, so it skips checking if the executable data hashes to
223+
// the requested hash (stateless has to *compute* the root hash, it's not given).
224+
func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
201225
txs, err := decodeTransactions(data.Transactions)
202226
if err != nil {
203227
return nil, err
@@ -267,13 +291,10 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
267291
ParentBeaconRoot: beaconRoot,
268292
RequestsHash: requestsHash,
269293
}
270-
block := types.NewBlockWithHeader(header)
271-
block = block.WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: requests})
272-
block = block.WithWitness(data.ExecutionWitness)
273-
if block.Hash() != data.BlockHash {
274-
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", data.BlockHash, block.Hash())
275-
}
276-
return block, nil
294+
return types.NewBlockWithHeader(header).
295+
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals, Requests: requests}).
296+
WithWitness(data.ExecutionWitness),
297+
nil
277298
}
278299

279300
// BlockToExecutableData constructs the ExecutableData structure by filling the

cmd/geth/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ var (
156156
utils.BeaconGenesisRootFlag,
157157
utils.BeaconGenesisTimeFlag,
158158
utils.BeaconCheckpointFlag,
159-
utils.CollectWitnessFlag,
160159
}, utils.NetworkFlags, utils.DatabaseFlags)
161160

162161
rpcFlags = []cli.Flag{

cmd/utils/flags.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -600,11 +600,6 @@ var (
600600
Usage: "Disables db compaction after import",
601601
Category: flags.LoggingCategory,
602602
}
603-
CollectWitnessFlag = &cli.BoolFlag{
604-
Name: "collectwitness",
605-
Usage: "Enable state witness generation during block execution. Work in progress flag, don't use.",
606-
Category: flags.MiscCategory,
607-
}
608603

609604
// MISC settings
610605
SyncTargetFlag = &cli.StringFlag{
@@ -1767,9 +1762,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
17671762
// TODO(fjl): force-enable this in --dev mode
17681763
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
17691764
}
1770-
if ctx.IsSet(CollectWitnessFlag.Name) {
1771-
cfg.EnableWitnessCollection = ctx.Bool(CollectWitnessFlag.Name)
1772-
}
17731765

17741766
if ctx.IsSet(RPCGlobalGasCapFlag.Name) {
17751767
cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name)
@@ -2194,7 +2186,6 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
21942186
}
21952187
vmcfg := vm.Config{
21962188
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
2197-
EnableWitnessCollection: ctx.Bool(CollectWitnessFlag.Name),
21982189
}
21992190
if ctx.IsSet(VMTraceFlag.Name) {
22002191
if name := ctx.String(VMTraceFlag.Name); name != "" {

core/block_validator.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ import (
2020
"errors"
2121
"fmt"
2222

23-
"github.com/ethereum/go-ethereum/common"
2423
"github.com/ethereum/go-ethereum/consensus"
2524
"github.com/ethereum/go-ethereum/core/state"
26-
"github.com/ethereum/go-ethereum/core/stateless"
2725
"github.com/ethereum/go-ethereum/core/types"
2826
"github.com/ethereum/go-ethereum/params"
2927
"github.com/ethereum/go-ethereum/trie"
@@ -160,28 +158,6 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
160158
return nil
161159
}
162160

163-
// ValidateWitness cross validates a block execution with stateless remote clients.
164-
//
165-
// Normally we'd distribute the block witness to remote cross validators, wait
166-
// for them to respond and then merge the results. For now, however, it's only
167-
// Geth, so do an internal stateless run.
168-
func (v *BlockValidator) ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error {
169-
// Run the cross client stateless execution
170-
// TODO(karalabe): Self-stateless for now, swap with other clients
171-
crossReceiptRoot, crossStateRoot, err := ExecuteStateless(v.config, witness)
172-
if err != nil {
173-
return fmt.Errorf("stateless execution failed: %v", err)
174-
}
175-
// Stateless cross execution suceeeded, validate the withheld computed fields
176-
if crossReceiptRoot != receiptRoot {
177-
return fmt.Errorf("cross validator receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, receiptRoot)
178-
}
179-
if crossStateRoot != stateRoot {
180-
return fmt.Errorf("cross validator state root mismatch (cross: %x local: %x)", crossStateRoot, stateRoot)
181-
}
182-
return nil
183-
}
184-
185161
// CalcGasLimit computes the gas limit of the next block after parent. It aims
186162
// to keep the baseline gas close to the provided target, and increase it towards
187163
// the target if the baseline gas is lower.

core/block_validator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
201201
t.Fatalf("post-block %d: unexpected result returned: %v", i, result)
202202
case <-time.After(25 * time.Millisecond):
203203
}
204-
chain.InsertBlockWithoutSetHead(postBlocks[i])
204+
chain.InsertBlockWithoutSetHead(postBlocks[i], false)
205205
}
206206

207207
// Verify the blocks with pre-merge blocks and post-merge blocks

0 commit comments

Comments
 (0)