erigon-pulse/cl/phase1/execution_client/execution_client.go
Giulio rebuffo b4ecd7f524
Consensus Separation: Separated GetPayload using an execution service (#7933)
Miracoulously, hive tests pass first try. YIPPIE.

Also for the future, I added `--experimental.modular` which enables a
secondary engine API for consensus separation.

Now block building is responsibility of the execution module.
2023-07-27 14:37:49 +02:00

252 lines
8.8 KiB
Go

package execution_client
import (
"context"
"encoding/binary"
"fmt"
"math/big"
"time"
"github.com/c2h5oh/datasize"
"github.com/holiman/uint256"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/gointerfaces"
"github.com/ledgerwatch/erigon-lib/gointerfaces/execution"
types2 "github.com/ledgerwatch/erigon-lib/gointerfaces/types"
"github.com/ledgerwatch/log/v3"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
"github.com/ledgerwatch/erigon/cl/cltypes"
"github.com/ledgerwatch/erigon/cl/phase1/execution_client/rpc_helper"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/turbo/engineapi/engine_types"
)
const fcuTimeout = 12 * time.Second
// ExecutionClient interfaces with the Erigon-EL component consensus side.
type ExecutionClient struct {
client execution.ExecutionClient
ctx context.Context
}
func HeaderRpcToHeader(header *execution.Header) (*types.Header, error) {
var blockNonce types.BlockNonce
binary.BigEndian.PutUint64(blockNonce[:], header.Nonce)
h := &types.Header{
ParentHash: gointerfaces.ConvertH256ToHash(header.ParentHash),
UncleHash: gointerfaces.ConvertH256ToHash(header.OmmerHash),
Coinbase: gointerfaces.ConvertH160toAddress(header.Coinbase),
Root: gointerfaces.ConvertH256ToHash(header.StateRoot),
TxHash: gointerfaces.ConvertH256ToHash(header.TransactionHash),
ReceiptHash: gointerfaces.ConvertH256ToHash(header.ReceiptRoot),
Bloom: gointerfaces.ConvertH2048ToBloom(header.LogsBloom),
Difficulty: gointerfaces.ConvertH256ToUint256Int(header.Difficulty).ToBig(),
Number: big.NewInt(int64(header.BlockNumber)),
GasLimit: header.GasLimit,
GasUsed: header.GasUsed,
Time: header.Timestamp,
Extra: header.ExtraData,
MixDigest: gointerfaces.ConvertH256ToHash(header.PrevRandao),
Nonce: blockNonce,
}
if header.BaseFeePerGas != nil {
h.BaseFee = gointerfaces.ConvertH256ToUint256Int(header.BaseFeePerGas).ToBig()
}
if header.WithdrawalHash != nil {
h.WithdrawalsHash = new(libcommon.Hash)
*h.WithdrawalsHash = gointerfaces.ConvertH256ToHash(header.WithdrawalHash)
}
blockHash := gointerfaces.ConvertH256ToHash(header.BlockHash)
if blockHash != h.Hash() {
return nil, fmt.Errorf("block %d, %x has invalid hash. expected: %x", header.BlockNumber, h.Hash(), blockHash)
}
return h, nil
}
func HeaderToHeaderRPC(header *types.Header) *execution.Header {
difficulty := new(uint256.Int)
difficulty.SetFromBig(header.Difficulty)
var baseFeeReply *types2.H256
if header.BaseFee != nil {
var baseFee uint256.Int
baseFee.SetFromBig(header.BaseFee)
baseFeeReply = gointerfaces.ConvertUint256IntToH256(&baseFee)
}
var withdrawalHashReply *types2.H256
if header.WithdrawalsHash != nil {
withdrawalHashReply = gointerfaces.ConvertHashToH256(*header.WithdrawalsHash)
}
return &execution.Header{
ParentHash: gointerfaces.ConvertHashToH256(header.ParentHash),
Coinbase: gointerfaces.ConvertAddressToH160(header.Coinbase),
StateRoot: gointerfaces.ConvertHashToH256(header.Root),
TransactionHash: gointerfaces.ConvertHashToH256(header.TxHash),
LogsBloom: gointerfaces.ConvertBytesToH2048(header.Bloom[:]),
ReceiptRoot: gointerfaces.ConvertHashToH256(header.ReceiptHash),
PrevRandao: gointerfaces.ConvertHashToH256(header.MixDigest),
BlockNumber: header.Number.Uint64(),
Nonce: header.Nonce.Uint64(),
GasLimit: header.GasLimit,
GasUsed: header.GasUsed,
Timestamp: header.Time,
ExtraData: header.Extra,
Difficulty: gointerfaces.ConvertUint256IntToH256(difficulty),
BlockHash: gointerfaces.ConvertHashToH256(header.Hash()),
OmmerHash: gointerfaces.ConvertHashToH256(header.UncleHash),
BaseFeePerGas: baseFeeReply,
WithdrawalHash: withdrawalHashReply,
}
}
// NewExecutionClient establishes a client-side connection with Erigon-EL
func NewExecutionClient(ctx context.Context, addr string) (*ExecutionClient, error) {
// Set up dial options for the gRPC client connection
var dialOpts []grpc.DialOption
dialOpts = []grpc.DialOption{
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(16 * datasize.MB))),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 5 * time.Minute,
Timeout: 10 * time.Minute,
PermitWithoutStream: true,
}),
}
// Add transport credentials to the dial options
dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
// Create the gRPC client connection
conn, err := grpc.DialContext(ctx, addr, dialOpts...)
if err != nil {
// Return an error if the connection fails
return nil, fmt.Errorf("creating client connection to execution client: %w", err)
}
// Return a new ExecutionClient struct with the gRPC client and context set as fields
return &ExecutionClient{
client: execution.NewExecutionClient(conn),
ctx: ctx,
}, nil
}
// InsertHeaders will send block bodies to execution client
func (ec *ExecutionClient) InsertHeaders(headers []*types.Header) error {
grpcHeaders := make([]*execution.Header, 0, len(headers))
for _, header := range headers {
grpcHeaders = append(grpcHeaders, HeaderToHeaderRPC(header))
}
_, err := ec.client.InsertHeaders(ec.ctx, &execution.InsertHeadersRequest{Headers: grpcHeaders})
return err
}
// InsertBodies will send block bodies to execution client
func (ec *ExecutionClient) InsertBodies(bodies []*types.RawBody, blockHashes []libcommon.Hash, blockNumbers []uint64) error {
if len(bodies) != len(blockHashes) || len(bodies) != len(blockNumbers) {
return fmt.Errorf("unbalanced inputs")
}
grpcBodies := make([]*execution.BlockBody, 0, len(bodies))
for i, body := range bodies {
grpcBodies = append(grpcBodies, &execution.BlockBody{
BlockHash: gointerfaces.ConvertHashToH256(blockHashes[i]),
BlockNumber: blockNumbers[i],
Transactions: body.Transactions,
Withdrawals: engine_types.ConvertWithdrawalsToRpc(body.Withdrawals),
})
}
_, err := ec.client.InsertBodies(ec.ctx, &execution.InsertBodiesRequest{Bodies: grpcBodies})
return err
}
// InsertExecutionPayloads insert a segment of execution payloads
func (ec *ExecutionClient) InsertExecutionPayloads(payloads []*cltypes.Eth1Block) error {
headers := make([]*types.Header, 0, len(payloads))
bodies := make([]*types.RawBody, 0, len(payloads))
blockHashes := make([]libcommon.Hash, 0, len(payloads))
blockNumbers := make([]uint64, 0, len(payloads))
for _, payload := range payloads {
rlpHeader, err := payload.RlpHeader()
if err != nil {
return err
}
headers = append(headers, rlpHeader)
bodies = append(bodies, payload.Body())
blockHashes = append(blockHashes, payload.BlockHash)
blockNumbers = append(blockNumbers, payload.BlockNumber)
}
if err := ec.InsertHeaders(headers); err != nil {
return err
}
return ec.InsertBodies(bodies, blockHashes, blockNumbers)
}
func (ec *ExecutionClient) ForkChoiceUpdate(headHash libcommon.Hash) (*execution.ForkChoiceReceipt, error) {
log.Debug("[ExecutionClientRpc] Calling EL", "method", rpc_helper.ForkChoiceUpdatedV1)
return ec.client.UpdateForkChoice(ec.ctx, &execution.ForkChoice{
HeadBlockHash: gointerfaces.ConvertHashToH256(headHash),
Timeout: uint64(fcuTimeout.Milliseconds()),
})
}
func (ec *ExecutionClient) IsCanonical(hash libcommon.Hash) (bool, error) {
resp, err := ec.client.IsCanonicalHash(ec.ctx, gointerfaces.ConvertHashToH256(hash))
if err != nil {
return false, err
}
return resp.Canonical, nil
}
func (ec *ExecutionClient) ReadHeader(number uint64, blockHash libcommon.Hash) (*types.Header, error) {
resp, err := ec.client.GetHeader(ec.ctx, &execution.GetSegmentRequest{
BlockNumber: &number,
BlockHash: gointerfaces.ConvertHashToH256(blockHash),
})
if err != nil {
return nil, err
}
return HeaderRpcToHeader(resp.Header)
}
func (ec *ExecutionClient) ReadExecutionPayload(number uint64, blockHash libcommon.Hash) (*cltypes.Eth1Block, error) {
header, err := ec.ReadHeader(number, blockHash)
if err != nil {
return nil, err
}
body, err := ec.ReadBody(number, blockHash)
if err != nil {
return nil, err
}
return cltypes.NewEth1BlockFromHeaderAndBody(header, body), nil
}
func (ec *ExecutionClient) ReadBody(number uint64, blockHash libcommon.Hash) (*types.RawBody, error) {
resp, err := ec.client.GetBody(ec.ctx, &execution.GetSegmentRequest{
BlockNumber: &number,
BlockHash: gointerfaces.ConvertHashToH256(blockHash),
})
if err != nil {
return nil, err
}
uncles := make([]*types.Header, 0, len(resp.Body.Uncles))
for _, uncle := range resp.Body.Uncles {
h, err := HeaderRpcToHeader(uncle)
if err != nil {
return nil, err
}
uncles = append(uncles, h)
}
return &types.RawBody{
Transactions: resp.Body.Transactions,
Uncles: uncles,
Withdrawals: engine_types.ConvertWithdrawalsFromRpc(resp.Body.Withdrawals),
}, nil
}