mirror of https://github.com/ethereum/go-ethereum
beacon/blsync: support for deneb fork (#29180)
This adds support for the Deneb beacon chain fork, and fork handling in general, to the beacon chain light client implementation. Co-authored-by: Zsolt Felfoldi <zsfelfoldi@gmail.com>pull/29304/head
parent
04bf1c802f
commit
bca6c40709
@ -0,0 +1,147 @@ |
|||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package blsync |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/engine" |
||||||
|
"github.com/ethereum/go-ethereum/beacon/types" |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
ctypes "github.com/ethereum/go-ethereum/core/types" |
||||||
|
"github.com/ethereum/go-ethereum/log" |
||||||
|
"github.com/ethereum/go-ethereum/rpc" |
||||||
|
) |
||||||
|
|
||||||
|
type engineClient struct { |
||||||
|
config *lightClientConfig |
||||||
|
rpc *rpc.Client |
||||||
|
rootCtx context.Context |
||||||
|
cancelRoot context.CancelFunc |
||||||
|
wg sync.WaitGroup |
||||||
|
} |
||||||
|
|
||||||
|
func startEngineClient(config *lightClientConfig, rpc *rpc.Client, headCh <-chan types.ChainHeadEvent) *engineClient { |
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
ec := &engineClient{ |
||||||
|
config: config, |
||||||
|
rpc: rpc, |
||||||
|
rootCtx: ctx, |
||||||
|
cancelRoot: cancel, |
||||||
|
} |
||||||
|
ec.wg.Add(1) |
||||||
|
go ec.updateLoop(headCh) |
||||||
|
return ec |
||||||
|
} |
||||||
|
|
||||||
|
func (ec *engineClient) stop() { |
||||||
|
ec.cancelRoot() |
||||||
|
ec.wg.Wait() |
||||||
|
} |
||||||
|
|
||||||
|
func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) { |
||||||
|
defer ec.wg.Done() |
||||||
|
|
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-ec.rootCtx.Done(): |
||||||
|
return |
||||||
|
|
||||||
|
case event := <-headCh: |
||||||
|
if ec.rpc == nil { // dry run, no engine API specified
|
||||||
|
log.Info("New execution block retrieved", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "finalized", event.Finalized) |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch()) |
||||||
|
forkName := strings.ToLower(fork.Name) |
||||||
|
|
||||||
|
if status, err := ec.callNewPayload(forkName, event); err == nil { |
||||||
|
log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status) |
||||||
|
} else { |
||||||
|
log.Error("Failed NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "error", err) |
||||||
|
} |
||||||
|
|
||||||
|
if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil { |
||||||
|
log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status) |
||||||
|
} else { |
||||||
|
log.Error("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent) (string, error) { |
||||||
|
execData := engine.BlockToExecutableData(event.Block, nil, nil).ExecutionPayload |
||||||
|
|
||||||
|
var ( |
||||||
|
method string |
||||||
|
params = []any{execData} |
||||||
|
) |
||||||
|
switch fork { |
||||||
|
case "deneb": |
||||||
|
method = "engine_newPayloadV3" |
||||||
|
parentBeaconRoot := event.BeaconHead.ParentRoot |
||||||
|
blobHashes := collectBlobHashes(event.Block) |
||||||
|
params = append(params, blobHashes, parentBeaconRoot) |
||||||
|
case "capella": |
||||||
|
method = "engine_newPayloadV2" |
||||||
|
default: |
||||||
|
method = "engine_newPayloadV1" |
||||||
|
} |
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5) |
||||||
|
defer cancel() |
||||||
|
var resp engine.PayloadStatusV1 |
||||||
|
err := ec.rpc.CallContext(ctx, &resp, method, params...) |
||||||
|
return resp.Status, err |
||||||
|
} |
||||||
|
|
||||||
|
func collectBlobHashes(b *ctypes.Block) []common.Hash { |
||||||
|
list := make([]common.Hash, 0) |
||||||
|
for _, tx := range b.Transactions() { |
||||||
|
list = append(list, tx.BlobHashes()...) |
||||||
|
} |
||||||
|
return list |
||||||
|
} |
||||||
|
|
||||||
|
func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHeadEvent) (string, error) { |
||||||
|
update := engine.ForkchoiceStateV1{ |
||||||
|
HeadBlockHash: event.Block.Hash(), |
||||||
|
SafeBlockHash: event.Finalized, |
||||||
|
FinalizedBlockHash: event.Finalized, |
||||||
|
} |
||||||
|
|
||||||
|
var method string |
||||||
|
switch fork { |
||||||
|
case "deneb": |
||||||
|
method = "engine_forkchoiceUpdatedV3" |
||||||
|
case "capella": |
||||||
|
method = "engine_forkchoiceUpdatedV2" |
||||||
|
default: |
||||||
|
method = "engine_forkchoiceUpdatedV1" |
||||||
|
} |
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5) |
||||||
|
defer cancel() |
||||||
|
var resp engine.ForkChoiceResponse |
||||||
|
err := ec.rpc.CallContext(ctx, &resp, method, update, nil) |
||||||
|
return resp.PayloadStatus.Status, err |
||||||
|
} |
@ -0,0 +1,110 @@ |
|||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package types |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/capella" |
||||||
|
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common" |
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/deneb" |
||||||
|
"github.com/protolambda/zrnt/eth2/configs" |
||||||
|
"github.com/protolambda/ztyp/tree" |
||||||
|
) |
||||||
|
|
||||||
|
type blockObject interface { |
||||||
|
HashTreeRoot(spec *zrntcommon.Spec, hFn tree.HashFn) zrntcommon.Root |
||||||
|
Header(spec *zrntcommon.Spec) *zrntcommon.BeaconBlockHeader |
||||||
|
} |
||||||
|
|
||||||
|
// BeaconBlock represents a full block in the beacon chain.
|
||||||
|
type BeaconBlock struct { |
||||||
|
blockObj blockObject |
||||||
|
} |
||||||
|
|
||||||
|
// BlockFromJSON decodes a beacon block from JSON.
|
||||||
|
func BlockFromJSON(forkName string, data []byte) (*BeaconBlock, error) { |
||||||
|
var obj blockObject |
||||||
|
switch forkName { |
||||||
|
case "deneb": |
||||||
|
obj = new(deneb.BeaconBlock) |
||||||
|
case "capella": |
||||||
|
obj = new(capella.BeaconBlock) |
||||||
|
default: |
||||||
|
return nil, fmt.Errorf("unsupported fork: " + forkName) |
||||||
|
} |
||||||
|
if err := json.Unmarshal(data, obj); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &BeaconBlock{obj}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// NewBeaconBlock wraps a ZRNT block.
|
||||||
|
func NewBeaconBlock(obj blockObject) *BeaconBlock { |
||||||
|
switch obj := obj.(type) { |
||||||
|
case *capella.BeaconBlock: |
||||||
|
return &BeaconBlock{obj} |
||||||
|
case *deneb.BeaconBlock: |
||||||
|
return &BeaconBlock{obj} |
||||||
|
default: |
||||||
|
panic(fmt.Errorf("unsupported block type %T", obj)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Slot returns the slot number of the block.
|
||||||
|
func (b *BeaconBlock) Slot() uint64 { |
||||||
|
switch obj := b.blockObj.(type) { |
||||||
|
case *capella.BeaconBlock: |
||||||
|
return uint64(obj.Slot) |
||||||
|
case *deneb.BeaconBlock: |
||||||
|
return uint64(obj.Slot) |
||||||
|
default: |
||||||
|
panic(fmt.Errorf("unsupported block type %T", b.blockObj)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ExecutionPayload parses and returns the execution payload of the block.
|
||||||
|
func (b *BeaconBlock) ExecutionPayload() (*types.Block, error) { |
||||||
|
switch obj := b.blockObj.(type) { |
||||||
|
case *capella.BeaconBlock: |
||||||
|
return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot) |
||||||
|
case *deneb.BeaconBlock: |
||||||
|
return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot) |
||||||
|
default: |
||||||
|
panic(fmt.Errorf("unsupported block type %T", b.blockObj)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Header returns the block's header data.
|
||||||
|
func (b *BeaconBlock) Header() Header { |
||||||
|
switch obj := b.blockObj.(type) { |
||||||
|
case *capella.BeaconBlock: |
||||||
|
return headerFromZRNT(obj.Header(configs.Mainnet)) |
||||||
|
case *deneb.BeaconBlock: |
||||||
|
return headerFromZRNT(obj.Header(configs.Mainnet)) |
||||||
|
default: |
||||||
|
panic(fmt.Errorf("unsupported block type %T", b.blockObj)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Root computes the SSZ root hash of the block.
|
||||||
|
func (b *BeaconBlock) Root() common.Hash { |
||||||
|
return common.Hash(b.blockObj.HashTreeRoot(configs.Mainnet, tree.GetHashFn())) |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package types |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
) |
||||||
|
|
||||||
|
func TestBlockFromJSON(t *testing.T) { |
||||||
|
type blocktest struct { |
||||||
|
file string |
||||||
|
version string |
||||||
|
wantSlot uint64 |
||||||
|
wantBlockNumber uint64 |
||||||
|
wantBlockHash common.Hash |
||||||
|
} |
||||||
|
tests := []blocktest{ |
||||||
|
{ |
||||||
|
file: "block_deneb.json", |
||||||
|
version: "deneb", |
||||||
|
wantSlot: 8631513, |
||||||
|
wantBlockNumber: 19431837, |
||||||
|
wantBlockHash: common.HexToHash("0x4cf7d9108fc01b50023ab7cab9b372a96068fddcadec551630393b65acb1f34c"), |
||||||
|
}, |
||||||
|
{ |
||||||
|
file: "block_capella.json", |
||||||
|
version: "capella", |
||||||
|
wantSlot: 7378495, |
||||||
|
wantBlockNumber: 18189758, |
||||||
|
wantBlockHash: common.HexToHash("0x802acf5c350f4252e31d83c431fcb259470250fa0edf49e8391cfee014239820"), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
for _, test := range tests { |
||||||
|
t.Run(test.file, func(t *testing.T) { |
||||||
|
data, err := os.ReadFile(filepath.Join("testdata", test.file)) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
beaconBlock, err := BlockFromJSON(test.version, data) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
if beaconBlock.Slot() != test.wantSlot { |
||||||
|
t.Errorf("wrong slot number %d", beaconBlock.Slot()) |
||||||
|
} |
||||||
|
execBlock, err := beaconBlock.ExecutionPayload() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("payload extraction failed: %v", err) |
||||||
|
} |
||||||
|
if execBlock.NumberU64() != test.wantBlockNumber { |
||||||
|
t.Errorf("wrong block number: %v", execBlock.NumberU64()) |
||||||
|
} |
||||||
|
if execBlock.Hash() != test.wantBlockHash { |
||||||
|
t.Errorf("wrong block hash: %v", execBlock.Hash()) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package types |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/merkle" |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/capella" |
||||||
|
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common" |
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/deneb" |
||||||
|
"github.com/protolambda/ztyp/tree" |
||||||
|
) |
||||||
|
|
||||||
|
type headerObject interface { |
||||||
|
HashTreeRoot(hFn tree.HashFn) zrntcommon.Root |
||||||
|
} |
||||||
|
|
||||||
|
type ExecutionHeader struct { |
||||||
|
obj headerObject |
||||||
|
} |
||||||
|
|
||||||
|
// HeaderFromJSON decodes an execution header from JSON data provided by
|
||||||
|
// the beacon chain API.
|
||||||
|
func ExecutionHeaderFromJSON(forkName string, data []byte) (*ExecutionHeader, error) { |
||||||
|
var obj headerObject |
||||||
|
switch forkName { |
||||||
|
case "capella": |
||||||
|
obj = new(capella.ExecutionPayloadHeader) |
||||||
|
case "deneb": |
||||||
|
obj = new(deneb.ExecutionPayloadHeader) |
||||||
|
default: |
||||||
|
return nil, fmt.Errorf("unsupported fork: " + forkName) |
||||||
|
} |
||||||
|
if err := json.Unmarshal(data, obj); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &ExecutionHeader{obj: obj}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func NewExecutionHeader(obj headerObject) *ExecutionHeader { |
||||||
|
switch obj.(type) { |
||||||
|
case *capella.ExecutionPayloadHeader: |
||||||
|
case *deneb.ExecutionPayloadHeader: |
||||||
|
default: |
||||||
|
panic(fmt.Errorf("unsupported ExecutionPayloadHeader type %T", obj)) |
||||||
|
} |
||||||
|
return &ExecutionHeader{obj: obj} |
||||||
|
} |
||||||
|
|
||||||
|
func (eh *ExecutionHeader) PayloadRoot() merkle.Value { |
||||||
|
return merkle.Value(eh.obj.HashTreeRoot(tree.GetHashFn())) |
||||||
|
} |
||||||
|
|
||||||
|
func (eh *ExecutionHeader) BlockHash() common.Hash { |
||||||
|
switch obj := eh.obj.(type) { |
||||||
|
case *capella.ExecutionPayloadHeader: |
||||||
|
return common.Hash(obj.BlockHash) |
||||||
|
case *deneb.ExecutionPayloadHeader: |
||||||
|
return common.Hash(obj.BlockHash) |
||||||
|
default: |
||||||
|
panic(fmt.Errorf("unsupported ExecutionPayloadHeader type %T", obj)) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,144 @@ |
|||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package types |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"math/big" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
"github.com/ethereum/go-ethereum/trie" |
||||||
|
"github.com/holiman/uint256" |
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/capella" |
||||||
|
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common" |
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/deneb" |
||||||
|
) |
||||||
|
|
||||||
|
type payloadType interface { |
||||||
|
*capella.ExecutionPayload | *deneb.ExecutionPayload |
||||||
|
} |
||||||
|
|
||||||
|
// convertPayload converts a beacon chain execution payload to types.Block.
|
||||||
|
func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*types.Block, error) { |
||||||
|
var ( |
||||||
|
header types.Header |
||||||
|
transactions []*types.Transaction |
||||||
|
withdrawals []*types.Withdrawal |
||||||
|
expectedHash [32]byte |
||||||
|
err error |
||||||
|
) |
||||||
|
switch p := any(payload).(type) { |
||||||
|
case *capella.ExecutionPayload: |
||||||
|
convertCapellaHeader(p, &header) |
||||||
|
transactions, err = convertTransactions(p.Transactions, &header) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
withdrawals = convertWithdrawals(p.Withdrawals, &header) |
||||||
|
expectedHash = p.BlockHash |
||||||
|
case *deneb.ExecutionPayload: |
||||||
|
convertDenebHeader(p, common.Hash(*parentRoot), &header) |
||||||
|
transactions, err = convertTransactions(p.Transactions, &header) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
withdrawals = convertWithdrawals(p.Withdrawals, &header) |
||||||
|
expectedHash = p.BlockHash |
||||||
|
default: |
||||||
|
panic("unsupported block type") |
||||||
|
} |
||||||
|
|
||||||
|
block := types.NewBlockWithHeader(&header) |
||||||
|
block = block.WithBody(transactions, nil) |
||||||
|
block = block.WithWithdrawals(withdrawals) |
||||||
|
hash := block.Hash() |
||||||
|
if hash != expectedHash { |
||||||
|
return block, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash) |
||||||
|
} |
||||||
|
return block, nil |
||||||
|
} |
||||||
|
|
||||||
|
func convertCapellaHeader(payload *capella.ExecutionPayload, h *types.Header) { |
||||||
|
// note: h.TxHash is set in convertTransactions
|
||||||
|
h.ParentHash = common.Hash(payload.ParentHash) |
||||||
|
h.UncleHash = types.EmptyUncleHash |
||||||
|
h.Coinbase = common.Address(payload.FeeRecipient) |
||||||
|
h.Root = common.Hash(payload.StateRoot) |
||||||
|
h.ReceiptHash = common.Hash(payload.ReceiptsRoot) |
||||||
|
h.Bloom = types.Bloom(payload.LogsBloom) |
||||||
|
h.Difficulty = common.Big0 |
||||||
|
h.Number = new(big.Int).SetUint64(uint64(payload.BlockNumber)) |
||||||
|
h.GasLimit = uint64(payload.GasLimit) |
||||||
|
h.GasUsed = uint64(payload.GasUsed) |
||||||
|
h.Time = uint64(payload.Timestamp) |
||||||
|
h.Extra = []byte(payload.ExtraData) |
||||||
|
h.MixDigest = common.Hash(payload.PrevRandao) |
||||||
|
h.Nonce = types.BlockNonce{} |
||||||
|
h.BaseFee = (*uint256.Int)(&payload.BaseFeePerGas).ToBig() |
||||||
|
} |
||||||
|
|
||||||
|
func convertDenebHeader(payload *deneb.ExecutionPayload, parentRoot common.Hash, h *types.Header) { |
||||||
|
// note: h.TxHash is set in convertTransactions
|
||||||
|
h.ParentHash = common.Hash(payload.ParentHash) |
||||||
|
h.UncleHash = types.EmptyUncleHash |
||||||
|
h.Coinbase = common.Address(payload.FeeRecipient) |
||||||
|
h.Root = common.Hash(payload.StateRoot) |
||||||
|
h.ReceiptHash = common.Hash(payload.ReceiptsRoot) |
||||||
|
h.Bloom = types.Bloom(payload.LogsBloom) |
||||||
|
h.Difficulty = common.Big0 |
||||||
|
h.Number = new(big.Int).SetUint64(uint64(payload.BlockNumber)) |
||||||
|
h.GasLimit = uint64(payload.GasLimit) |
||||||
|
h.GasUsed = uint64(payload.GasUsed) |
||||||
|
h.Time = uint64(payload.Timestamp) |
||||||
|
h.Extra = []byte(payload.ExtraData) |
||||||
|
h.MixDigest = common.Hash(payload.PrevRandao) |
||||||
|
h.Nonce = types.BlockNonce{} |
||||||
|
h.BaseFee = (*uint256.Int)(&payload.BaseFeePerGas).ToBig() |
||||||
|
// new in deneb
|
||||||
|
h.BlobGasUsed = (*uint64)(&payload.BlobGasUsed) |
||||||
|
h.ExcessBlobGas = (*uint64)(&payload.ExcessBlobGas) |
||||||
|
h.ParentBeaconRoot = &parentRoot |
||||||
|
} |
||||||
|
|
||||||
|
func convertTransactions(list zrntcommon.PayloadTransactions, execHeader *types.Header) ([]*types.Transaction, error) { |
||||||
|
txs := make([]*types.Transaction, len(list)) |
||||||
|
for i, opaqueTx := range list { |
||||||
|
var tx types.Transaction |
||||||
|
if err := tx.UnmarshalBinary(opaqueTx); err != nil { |
||||||
|
return nil, fmt.Errorf("failed to parse tx %d: %v", i, err) |
||||||
|
} |
||||||
|
txs[i] = &tx |
||||||
|
} |
||||||
|
execHeader.TxHash = types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)) |
||||||
|
return txs, nil |
||||||
|
} |
||||||
|
|
||||||
|
func convertWithdrawals(list zrntcommon.Withdrawals, execHeader *types.Header) []*types.Withdrawal { |
||||||
|
withdrawals := make([]*types.Withdrawal, len(list)) |
||||||
|
for i, w := range list { |
||||||
|
withdrawals[i] = &types.Withdrawal{ |
||||||
|
Index: uint64(w.Index), |
||||||
|
Validator: uint64(w.ValidatorIndex), |
||||||
|
Address: common.Address(w.Address), |
||||||
|
Amount: uint64(w.Amount), |
||||||
|
} |
||||||
|
} |
||||||
|
wroot := types.DeriveSha(types.Withdrawals(withdrawals), trie.NewStackTrie(nil)) |
||||||
|
execHeader.WithdrawalsHash = &wroot |
||||||
|
return withdrawals |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,69 +0,0 @@ |
|||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/beacon/engine" |
|
||||||
"github.com/ethereum/go-ethereum/beacon/types" |
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log" |
|
||||||
"github.com/ethereum/go-ethereum/rpc" |
|
||||||
) |
|
||||||
|
|
||||||
func updateEngineApi(client *rpc.Client, headCh chan types.ChainHeadEvent) { |
|
||||||
for event := range headCh { |
|
||||||
if client == nil { // dry run, no engine API specified
|
|
||||||
log.Info("New execution block retrieved", "block number", event.HeadBlock.Number, "block hash", event.HeadBlock.BlockHash, "finalized block hash", event.Finalized) |
|
||||||
} else { |
|
||||||
if status, err := callNewPayloadV2(client, event.HeadBlock); err == nil { |
|
||||||
log.Info("Successful NewPayload", "block number", event.HeadBlock.Number, "block hash", event.HeadBlock.BlockHash, "status", status) |
|
||||||
} else { |
|
||||||
log.Error("Failed NewPayload", "block number", event.HeadBlock.Number, "block hash", event.HeadBlock.BlockHash, "error", err) |
|
||||||
} |
|
||||||
if status, err := callForkchoiceUpdatedV1(client, event.HeadBlock.BlockHash, event.Finalized); err == nil { |
|
||||||
log.Info("Successful ForkchoiceUpdated", "head", event.HeadBlock.BlockHash, "status", status) |
|
||||||
} else { |
|
||||||
log.Error("Failed ForkchoiceUpdated", "head", event.HeadBlock.BlockHash, "error", err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func callNewPayloadV2(client *rpc.Client, execData *engine.ExecutableData) (string, error) { |
|
||||||
var resp engine.PayloadStatusV1 |
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) |
|
||||||
err := client.CallContext(ctx, &resp, "engine_newPayloadV2", execData) |
|
||||||
cancel() |
|
||||||
return resp.Status, err |
|
||||||
} |
|
||||||
|
|
||||||
func callForkchoiceUpdatedV1(client *rpc.Client, headHash, finalizedHash common.Hash) (string, error) { |
|
||||||
var resp engine.ForkChoiceResponse |
|
||||||
update := engine.ForkchoiceStateV1{ |
|
||||||
HeadBlockHash: headHash, |
|
||||||
SafeBlockHash: finalizedHash, |
|
||||||
FinalizedBlockHash: finalizedHash, |
|
||||||
} |
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) |
|
||||||
err := client.CallContext(ctx, &resp, "engine_forkchoiceUpdatedV1", update, nil) |
|
||||||
cancel() |
|
||||||
return resp.PayloadStatus.Status, err |
|
||||||
} |
|
@ -1,88 +0,0 @@ |
|||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package catalyst |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/ethereum/go-ethereum/beacon/engine" |
|
||||||
"github.com/ethereum/go-ethereum/beacon/types" |
|
||||||
"github.com/ethereum/go-ethereum/eth" |
|
||||||
"github.com/ethereum/go-ethereum/event" |
|
||||||
"github.com/ethereum/go-ethereum/log" |
|
||||||
) |
|
||||||
|
|
||||||
// Blsync tracks the head of the beacon chain through the beacon light client
|
|
||||||
// and drives the local node via ConsensusAPI.
|
|
||||||
type Blsync struct { |
|
||||||
engine *ConsensusAPI |
|
||||||
client Client |
|
||||||
headCh chan types.ChainHeadEvent |
|
||||||
headSub event.Subscription |
|
||||||
|
|
||||||
quitCh chan struct{} |
|
||||||
} |
|
||||||
|
|
||||||
type Client interface { |
|
||||||
SubscribeChainHeadEvent(ch chan<- types.ChainHeadEvent) event.Subscription |
|
||||||
Start() |
|
||||||
Stop() |
|
||||||
} |
|
||||||
|
|
||||||
// NewBlsync creates a new beacon light syncer.
|
|
||||||
func NewBlsync(client Client, eth *eth.Ethereum) *Blsync { |
|
||||||
return &Blsync{ |
|
||||||
engine: newConsensusAPIWithoutHeartbeat(eth), |
|
||||||
client: client, |
|
||||||
headCh: make(chan types.ChainHeadEvent, 16), |
|
||||||
quitCh: make(chan struct{}), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Start starts underlying beacon light client and the sync logic for driving
|
|
||||||
// the local node.
|
|
||||||
func (b *Blsync) Start() error { |
|
||||||
log.Info("Beacon light sync started") |
|
||||||
b.headSub = b.client.SubscribeChainHeadEvent(b.headCh) |
|
||||||
go b.client.Start() |
|
||||||
|
|
||||||
for { |
|
||||||
select { |
|
||||||
case <-b.quitCh: |
|
||||||
return nil |
|
||||||
case head := <-b.headCh: |
|
||||||
if _, err := b.engine.NewPayloadV2(*head.HeadBlock); err != nil { |
|
||||||
log.Error("failed to send new payload", "err", err) |
|
||||||
continue |
|
||||||
} |
|
||||||
update := engine.ForkchoiceStateV1{ |
|
||||||
HeadBlockHash: head.HeadBlock.BlockHash, |
|
||||||
SafeBlockHash: head.Finalized, //TODO pass finalized or empty hash here?
|
|
||||||
FinalizedBlockHash: head.Finalized, |
|
||||||
} |
|
||||||
if _, err := b.engine.ForkchoiceUpdatedV1(update, nil); err != nil { |
|
||||||
log.Error("failed to send forkchoice updated", "err", err) |
|
||||||
continue |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Stop signals to the light client and syncer to exit.
|
|
||||||
func (b *Blsync) Stop() error { |
|
||||||
b.client.Stop() |
|
||||||
close(b.quitCh) |
|
||||||
return nil |
|
||||||
} |
|
Loading…
Reference in new issue