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  }