github.com/ethereum/go-ethereum@v1.16.1/beacon/blsync/engineclient.go (about) 1 // Copyright 2024 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package blsync 18 19 import ( 20 "context" 21 "strings" 22 "sync" 23 "time" 24 25 "github.com/ethereum/go-ethereum/beacon/engine" 26 "github.com/ethereum/go-ethereum/beacon/params" 27 "github.com/ethereum/go-ethereum/beacon/types" 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/common/hexutil" 30 ctypes "github.com/ethereum/go-ethereum/core/types" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/rpc" 33 ) 34 35 type engineClient struct { 36 config *params.ClientConfig 37 rpc *rpc.Client 38 rootCtx context.Context 39 cancelRoot context.CancelFunc 40 wg sync.WaitGroup 41 } 42 43 func startEngineClient(config *params.ClientConfig, rpc *rpc.Client, headCh <-chan types.ChainHeadEvent) *engineClient { 44 ctx, cancel := context.WithCancel(context.Background()) 45 ec := &engineClient{ 46 config: config, 47 rpc: rpc, 48 rootCtx: ctx, 49 cancelRoot: cancel, 50 } 51 ec.wg.Add(1) 52 go ec.updateLoop(headCh) 53 return ec 54 } 55 56 func (ec *engineClient) stop() { 57 ec.cancelRoot() 58 ec.wg.Wait() 59 } 60 61 func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) { 62 defer ec.wg.Done() 63 64 for { 65 select { 66 case <-ec.rootCtx.Done(): 67 log.Debug("Stopping engine API update loop") 68 return 69 70 case event := <-headCh: 71 if ec.rpc == nil { // dry run, no engine API specified 72 log.Info("New execution block retrieved", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "finalized", event.Finalized) 73 continue 74 } 75 76 fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch()) 77 forkName := strings.ToLower(fork.Name) 78 79 log.Debug("Calling NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash()) 80 if status, err := ec.callNewPayload(forkName, event); err == nil { 81 log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status) 82 } else { 83 log.Error("Failed NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "error", err) 84 } 85 86 log.Debug("Calling ForkchoiceUpdated", "head", event.Block.Hash()) 87 if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil { 88 log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status) 89 } else { 90 log.Error("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err) 91 } 92 } 93 } 94 } 95 96 func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent) (string, error) { 97 execData := engine.BlockToExecutableData(event.Block, nil, nil, nil).ExecutionPayload 98 99 var ( 100 method string 101 params = []any{execData} 102 ) 103 switch fork { 104 case "electra": 105 method = "engine_newPayloadV4" 106 parentBeaconRoot := event.BeaconHead.ParentRoot 107 blobHashes := collectBlobHashes(event.Block) 108 hexRequests := make([]hexutil.Bytes, len(event.ExecRequests)) 109 for i := range event.ExecRequests { 110 hexRequests[i] = hexutil.Bytes(event.ExecRequests[i]) 111 } 112 params = append(params, blobHashes, parentBeaconRoot, hexRequests) 113 case "deneb": 114 method = "engine_newPayloadV3" 115 parentBeaconRoot := event.BeaconHead.ParentRoot 116 blobHashes := collectBlobHashes(event.Block) 117 params = append(params, blobHashes, parentBeaconRoot) 118 case "capella": 119 method = "engine_newPayloadV2" 120 default: 121 method = "engine_newPayloadV1" 122 } 123 124 ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5) 125 defer cancel() 126 var resp engine.PayloadStatusV1 127 err := ec.rpc.CallContext(ctx, &resp, method, params...) 128 return resp.Status, err 129 } 130 131 func collectBlobHashes(b *ctypes.Block) []common.Hash { 132 list := make([]common.Hash, 0) 133 for _, tx := range b.Transactions() { 134 list = append(list, tx.BlobHashes()...) 135 } 136 return list 137 } 138 139 func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHeadEvent) (string, error) { 140 update := engine.ForkchoiceStateV1{ 141 HeadBlockHash: event.Block.Hash(), 142 SafeBlockHash: event.Finalized, 143 FinalizedBlockHash: event.Finalized, 144 } 145 146 var method string 147 switch fork { 148 case "deneb", "electra": 149 method = "engine_forkchoiceUpdatedV3" 150 case "capella": 151 method = "engine_forkchoiceUpdatedV2" 152 default: 153 method = "engine_forkchoiceUpdatedV1" 154 } 155 156 ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5) 157 defer cancel() 158 var resp engine.ForkChoiceResponse 159 err := ec.rpc.CallContext(ctx, &resp, method, update, nil) 160 return resp.PayloadStatus.Status, err 161 }