github.com/ethereum/go-ethereum@v1.14.3/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/types"
    27  	"github.com/ethereum/go-ethereum/common"
    28  	ctypes "github.com/ethereum/go-ethereum/core/types"
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/ethereum/go-ethereum/rpc"
    31  )
    32  
    33  type engineClient struct {
    34  	config     *lightClientConfig
    35  	rpc        *rpc.Client
    36  	rootCtx    context.Context
    37  	cancelRoot context.CancelFunc
    38  	wg         sync.WaitGroup
    39  }
    40  
    41  func startEngineClient(config *lightClientConfig, rpc *rpc.Client, headCh <-chan types.ChainHeadEvent) *engineClient {
    42  	ctx, cancel := context.WithCancel(context.Background())
    43  	ec := &engineClient{
    44  		config:     config,
    45  		rpc:        rpc,
    46  		rootCtx:    ctx,
    47  		cancelRoot: cancel,
    48  	}
    49  	ec.wg.Add(1)
    50  	go ec.updateLoop(headCh)
    51  	return ec
    52  }
    53  
    54  func (ec *engineClient) stop() {
    55  	ec.cancelRoot()
    56  	ec.wg.Wait()
    57  }
    58  
    59  func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
    60  	defer ec.wg.Done()
    61  
    62  	for {
    63  		select {
    64  		case <-ec.rootCtx.Done():
    65  			log.Debug("Stopping engine API update loop")
    66  			return
    67  
    68  		case event := <-headCh:
    69  			if ec.rpc == nil { // dry run, no engine API specified
    70  				log.Info("New execution block retrieved", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "finalized", event.Finalized)
    71  				continue
    72  			}
    73  
    74  			fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch())
    75  			forkName := strings.ToLower(fork.Name)
    76  
    77  			log.Debug("Calling NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash())
    78  			if status, err := ec.callNewPayload(forkName, event); err == nil {
    79  				log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status)
    80  			} else {
    81  				log.Error("Failed NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "error", err)
    82  			}
    83  
    84  			log.Debug("Calling ForkchoiceUpdated", "head", event.Block.Hash())
    85  			if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil {
    86  				log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status)
    87  			} else {
    88  				log.Error("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err)
    89  			}
    90  		}
    91  	}
    92  }
    93  
    94  func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent) (string, error) {
    95  	execData := engine.BlockToExecutableData(event.Block, nil, nil).ExecutionPayload
    96  
    97  	var (
    98  		method string
    99  		params = []any{execData}
   100  	)
   101  	switch fork {
   102  	case "deneb":
   103  		method = "engine_newPayloadV3"
   104  		parentBeaconRoot := event.BeaconHead.ParentRoot
   105  		blobHashes := collectBlobHashes(event.Block)
   106  		params = append(params, blobHashes, parentBeaconRoot)
   107  	case "capella":
   108  		method = "engine_newPayloadV2"
   109  	default:
   110  		method = "engine_newPayloadV1"
   111  	}
   112  
   113  	ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5)
   114  	defer cancel()
   115  	var resp engine.PayloadStatusV1
   116  	err := ec.rpc.CallContext(ctx, &resp, method, params...)
   117  	return resp.Status, err
   118  }
   119  
   120  func collectBlobHashes(b *ctypes.Block) []common.Hash {
   121  	list := make([]common.Hash, 0)
   122  	for _, tx := range b.Transactions() {
   123  		list = append(list, tx.BlobHashes()...)
   124  	}
   125  	return list
   126  }
   127  
   128  func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHeadEvent) (string, error) {
   129  	update := engine.ForkchoiceStateV1{
   130  		HeadBlockHash:      event.Block.Hash(),
   131  		SafeBlockHash:      event.Finalized,
   132  		FinalizedBlockHash: event.Finalized,
   133  	}
   134  
   135  	var method string
   136  	switch fork {
   137  	case "deneb":
   138  		method = "engine_forkchoiceUpdatedV3"
   139  	case "capella":
   140  		method = "engine_forkchoiceUpdatedV2"
   141  	default:
   142  		method = "engine_forkchoiceUpdatedV1"
   143  	}
   144  
   145  	ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5)
   146  	defer cancel()
   147  	var resp engine.ForkChoiceResponse
   148  	err := ec.rpc.CallContext(ctx, &resp, method, update, nil)
   149  	return resp.PayloadStatus.Status, err
   150  }