erigon-pulse/cmd/erigon-el/eth1/execution.go
Giulio rebuffo e6ba82ca0b
Added Capella specs support to Erigon-CL (#7051)
Passing consensus-specs tests for Capella. Processing of withdrawals and
ExecutionChanges. efficient non-validation implemented. Refactored:
ExecutionPayload/ExecutionPayloadHeader.
2023-03-07 21:57:18 +00:00

384 lines
12 KiB
Go

package eth1
import (
"context"
"encoding/binary"
"fmt"
"math/big"
"sync"
"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/erigon-lib/kv"
"github.com/ledgerwatch/log/v3"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/eth/stagedsync"
"github.com/ledgerwatch/erigon/eth/stagedsync/stages"
"github.com/ledgerwatch/erigon/ethdb/privateapi"
"github.com/ledgerwatch/erigon/turbo/services"
)
type Eth1Execution struct {
execution.UnimplementedExecutionServer
db kv.RwDB
executionPipeline *stagedsync.Sync
blockReader services.FullBlockReader
mu sync.Mutex
}
func NewEth1Execution(db kv.RwDB, blockReader services.FullBlockReader, executionPipeline *stagedsync.Sync) *Eth1Execution {
return &Eth1Execution{
db: db,
executionPipeline: executionPipeline,
blockReader: blockReader,
}
}
func (e *Eth1Execution) InsertHeaders(ctx context.Context, req *execution.InsertHeadersRequest) (*execution.EmptyMessage, error) {
e.mu.Lock()
defer e.mu.Unlock()
tx, err := e.db.BeginRw(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
for _, header := range req.Headers {
h, err := HeaderRpcToHeader(header)
if err != nil {
return nil, err
}
rawdb.WriteHeader(tx, h)
}
return &execution.EmptyMessage{}, tx.Commit()
}
func (e *Eth1Execution) InsertBodies(ctx context.Context, req *execution.InsertBodiesRequest) (*execution.EmptyMessage, error) {
e.mu.Lock()
defer e.mu.Unlock()
tx, err := e.db.BeginRw(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
for _, body := range req.Bodies {
uncles := make([]*types.Header, 0, len(body.Uncles))
for _, uncle := range body.Uncles {
h, err := HeaderRpcToHeader(uncle)
if err != nil {
return nil, err
}
uncles = append(uncles, h)
}
// Withdrawals processing
if _, _, err := rawdb.WriteRawBodyIfNotExists(tx, gointerfaces.ConvertH256ToHash(body.BlockHash),
body.BlockNumber, &types.RawBody{
Transactions: body.Transactions,
Uncles: uncles,
Withdrawals: privateapi.ConvertWithdrawalsFromRpc(body.Withdrawals),
}); err != nil {
return nil, err
}
}
return &execution.EmptyMessage{}, tx.Commit()
}
type canonicalEntry struct {
hash libcommon.Hash
number uint64
}
func (e *Eth1Execution) UpdateForkChoice(ctx context.Context, hash *types2.H256) (*execution.ForkChoiceReceipt, error) {
e.mu.Lock()
defer e.mu.Unlock()
tx, err := e.db.BeginRw(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockHash := gointerfaces.ConvertH256ToHash(hash)
// Step one, find reconnection point, and mark all of those headers as canonical.
fcuHeader, err := e.blockReader.HeaderByHash(ctx, tx, blockHash)
if err != nil {
return nil, err
}
// If we dont have it, too bad
if fcuHeader == nil {
return &execution.ForkChoiceReceipt{
Success: false,
LatestValidHash: &types2.H256{},
}, nil
}
currentParentHash := fcuHeader.ParentHash
currentParentNumber := fcuHeader.Number.Uint64() - 1
isCanonicalHash, err := rawdb.IsCanonicalHash(tx, currentParentHash)
if err != nil {
return nil, err
}
// Find such point, and collect all hashes
newCanonicals := make([]*canonicalEntry, 0, 2048)
newCanonicals = append(newCanonicals, &canonicalEntry{
hash: fcuHeader.Hash(),
number: fcuHeader.Number.Uint64(),
})
for !isCanonicalHash {
newCanonicals = append(newCanonicals, &canonicalEntry{
hash: currentParentHash,
number: currentParentNumber,
})
currentHeader, err := e.blockReader.Header(ctx, tx, currentParentHash, currentParentNumber)
if err != nil {
return nil, err
}
if currentHeader == nil {
return &execution.ForkChoiceReceipt{
Success: false,
LatestValidHash: &types2.H256{},
}, nil
}
currentParentHash = currentHeader.ParentHash
currentParentNumber = currentHeader.Number.Uint64() - 1
isCanonicalHash, err = rawdb.IsCanonicalHash(tx, currentParentHash)
if err != nil {
return nil, err
}
}
if currentParentNumber != fcuHeader.Number.Uint64()-1 {
e.executionPipeline.UnwindTo(currentParentNumber, libcommon.Hash{})
}
// Run the unwind
if err := e.executionPipeline.RunUnwind(e.db, tx); err != nil {
return nil, err
}
// Mark all new canonicals as canonicals
for _, canonicalSegment := range newCanonicals {
if err := rawdb.WriteCanonicalHash(tx, canonicalSegment.hash, canonicalSegment.number); err != nil {
return nil, err
}
}
// Set Progress for headers and bodies accordingly.
if err := stages.SaveStageProgress(tx, stages.Headers, fcuHeader.Number.Uint64()); err != nil {
return nil, err
}
if err := stages.SaveStageProgress(tx, stages.Bodies, fcuHeader.Number.Uint64()); err != nil {
return nil, err
}
if err = rawdb.WriteHeadHeaderHash(tx, blockHash); err != nil {
return nil, err
}
// Run the forkchoice
if err := e.executionPipeline.Run(e.db, tx, false, false); err != nil {
return nil, err
}
// if head hash was set then success otherwise no
headHash := rawdb.ReadHeadBlockHash(tx)
headNumber := rawdb.ReadHeaderNumber(tx, headHash)
if headNumber != nil {
log.Info("Current forkchoice", "hash", headHash, "number", *headNumber)
}
return &execution.ForkChoiceReceipt{
LatestValidHash: gointerfaces.ConvertHashToH256(headHash),
Success: headHash == blockHash,
}, tx.Commit()
}
func (e *Eth1Execution) GetHeader(ctx context.Context, req *execution.GetSegmentRequest) (*execution.GetHeaderResponse, error) {
e.mu.Lock()
defer e.mu.Unlock()
tx, err := e.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
// Retrieve header
var header *types.Header
if req.BlockHash != nil && req.BlockNumber != nil {
blockHash := gointerfaces.ConvertH256ToHash(req.BlockHash)
header, err = e.blockReader.Header(ctx, tx, blockHash, *req.BlockNumber)
} else if req.BlockHash != nil {
blockHash := gointerfaces.ConvertH256ToHash(req.BlockHash)
header, err = e.blockReader.HeaderByHash(ctx, tx, blockHash)
} else if req.BlockNumber != nil {
header, err = e.blockReader.HeaderByNumber(ctx, tx, *req.BlockNumber)
}
if err != nil {
return nil, err
}
// Got nothing? return nothing :)
if header == nil {
return nil, nil
}
return &execution.GetHeaderResponse{
Header: HeaderToHeaderRPC(header),
}, nil
}
func (e *Eth1Execution) GetBody(ctx context.Context, req *execution.GetSegmentRequest) (*execution.GetBodyResponse, error) {
e.mu.Lock()
defer e.mu.Unlock()
tx, err := e.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
// Retrieve header
var body *types.Body
if req.BlockHash != nil && req.BlockNumber != nil {
blockHash := gointerfaces.ConvertH256ToHash(req.BlockHash)
body, err = e.blockReader.BodyWithTransactions(ctx, tx, blockHash, *req.BlockNumber)
} else if req.BlockHash != nil {
blockHash := gointerfaces.ConvertH256ToHash(req.BlockHash)
blockNumber := rawdb.ReadHeaderNumber(tx, blockHash)
if blockNumber == nil {
return nil, nil
}
body, err = e.blockReader.BodyWithTransactions(ctx, tx, blockHash, *blockNumber)
} else if req.BlockNumber != nil {
blockHash, err2 := e.blockReader.CanonicalHash(ctx, tx, *req.BlockNumber)
if err2 != nil {
return nil, err
}
body, err = e.blockReader.BodyWithTransactions(ctx, tx, blockHash, *req.BlockNumber)
}
if err != nil {
return nil, err
}
if body == nil {
return nil, nil
}
encodedTransactions, err := types.MarshalTransactionsBinary(body.Transactions)
if err != nil {
return nil, err
}
rpcWithdrawals := privateapi.ConvertWithdrawalsToRpc(body.Withdrawals)
unclesRpc := make([]*execution.Header, 0, len(body.Uncles))
for _, uncle := range body.Uncles {
unclesRpc = append(unclesRpc, HeaderToHeaderRPC(uncle))
}
return &execution.GetBodyResponse{
Body: &execution.BlockBody{
Transactions: encodedTransactions,
Withdrawals: rpcWithdrawals,
Uncles: unclesRpc,
},
}, nil
}
func (e *Eth1Execution) IsCanonicalHash(ctx context.Context, req *types2.H256) (*execution.IsCanonicalResponse, error) {
e.mu.Lock()
defer e.mu.Unlock()
tx, err := e.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockHash := gointerfaces.ConvertH256ToHash(req)
isCanonical, err := rawdb.IsCanonicalHash(tx, blockHash)
if err != nil {
return nil, err
}
return &execution.IsCanonicalResponse{Canonical: isCanonical}, nil
}
func (e *Eth1Execution) GetHeaderHashNumber(ctx context.Context, req *types2.H256) (*execution.GetHeaderHashNumberResponse, error) {
e.mu.Lock()
defer e.mu.Unlock()
tx, err := e.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
return &execution.GetHeaderHashNumberResponse{
BlockNumber: rawdb.ReadHeaderNumber(tx, gointerfaces.ConvertH256ToHash(req)),
}, nil
}
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.MixDigest),
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),
MixDigest: 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,
}
}