eip-4844: NewMessage now expectes maxFeePerDataGas & GetPayloadV3 impl (#7365)

types.NewMessage now expects maxFeePerDataGas param, which will be used
in transaction verification (preCheck). GetPayloadV3 method added to
EngineAPI. Some cosmetic changes applied.
This commit is contained in:
racytech 2023-04-23 23:27:05 +06:00 committed by GitHub
parent ae1891491e
commit 6588bca40b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 169 additions and 27 deletions

View File

@ -51,6 +51,13 @@ type GetPayloadV2Response struct {
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
}
// GetPayloadV3Response represents the response of the getPayloadV3 method
type GetPayloadV3Response struct {
ExecutionPayload *ExecutionPayload `json:"executionPayload" gencodec:"required"`
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle" gencodec:"required"`
}
// PayloadAttributes represent the attributes required to start assembling a payload
type ForkChoiceState struct {
HeadHash common.Hash `json:"headBlockHash" gencodec:"required"`
@ -73,6 +80,12 @@ type TransitionConfiguration struct {
TerminalBlockNumber *hexutil.Big `json:"terminalBlockNumber" gencodec:"required"`
}
// BlobsBundleV1 holds the blobs of an execution payload
type BlobsBundleV1 struct {
KZGs []types.KZGCommitment `json:"kzgs" gencodec:"required"`
Blobs []types.Blob `json:"blobs" gencodec:"required"`
}
type ExecutionPayloadBodyV1 struct {
Transactions []hexutility.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals" gencodec:"required"`
@ -86,6 +99,7 @@ type EngineAPI interface {
ForkchoiceUpdatedV2(ctx context.Context, forkChoiceState *ForkChoiceState, payloadAttributes *PayloadAttributes) (map[string]interface{}, error)
GetPayloadV1(ctx context.Context, payloadID hexutility.Bytes) (*ExecutionPayload, error)
GetPayloadV2(ctx context.Context, payloadID hexutility.Bytes) (*GetPayloadV2Response, error)
GetPayloadV3(ctx context.Context, payloadID hexutility.Bytes) (*GetPayloadV3Response, error)
ExchangeTransitionConfigurationV1(ctx context.Context, transitionConfiguration *TransitionConfiguration) (*TransitionConfiguration, error)
GetPayloadBodiesByHashV1(ctx context.Context, hashes []common.Hash) ([]*ExecutionPayloadBodyV1, error)
GetPayloadBodiesByRangeV1(ctx context.Context, start, count hexutil.Uint64) ([]*ExecutionPayloadBodyV1, error)
@ -363,6 +377,50 @@ func (e *EngineImpl) GetPayloadV2(ctx context.Context, payloadID hexutility.Byte
}, nil
}
func (e *EngineImpl) GetPayloadV3(ctx context.Context, payloadID hexutility.Bytes) (*GetPayloadV3Response, error) {
if e.internalCL { // TODO: find out what is the way around it
log.Error("EXTERNAL CONSENSUS LAYER IS NOT ENABLED, PLEASE RESTART WITH FLAG --externalcl")
return nil, fmt.Errorf("engine api should not be used, restart with --externalcl")
}
decodedPayloadId := binary.BigEndian.Uint64(payloadID)
log.Info("Received GetPayloadV3", "payloadId", decodedPayloadId)
response, err := e.api.EngineGetPayload(ctx, decodedPayloadId)
if err != nil {
return nil, err
}
epl := convertPayloadFromRpc(response.ExecutionPayload)
blockValue := gointerfaces.ConvertH256ToUint256Int(response.BlockValue).ToBig()
ep, err := e.api.EngineGetBlobsBundleV1(ctx, decodedPayloadId)
if err != nil {
return nil, err
}
kzgs := ep.GetKzgs()
blobs := ep.GetBlobs()
if len(kzgs) != len(blobs) {
return nil, fmt.Errorf("should have same number of kzgs and blobs, got %v vs %v", len(kzgs), len(blobs))
}
replyKzgs := make([]types.KZGCommitment, len(kzgs))
replyBlobs := make([]types.Blob, len(blobs))
for i := range kzgs {
copy(replyKzgs[i][:], kzgs[i])
copy(replyBlobs[i][:], blobs[i])
}
bb := &BlobsBundleV1{
KZGs: replyKzgs,
Blobs: replyBlobs,
}
return &GetPayloadV3Response{
epl,
(*hexutil.Big)(blockValue),
bb,
}, nil
}
// Receives consensus layer's transition configuration and checks if the execution layer has the correct configuration.
// Can also be used to ping the execution layer (heartbeats).
// See https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.1/src/engine/specification.md#engine_exchangetransitionconfigurationv1

View File

@ -52,6 +52,7 @@ type TraceCallParam struct {
GasPrice *hexutil.Big `json:"gasPrice"`
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
MaxFeePerDataGas *hexutil.Big `json:"maxFeePerDataGas"`
Value *hexutil.Big `json:"value"`
Data hexutility.Bytes `json:"data"`
AccessList *types2.AccessList `json:"accessList"`
@ -150,9 +151,10 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int)
gas = globalGasCap
}
var (
gasPrice *uint256.Int
gasFeeCap *uint256.Int
gasTipCap *uint256.Int
gasPrice *uint256.Int
gasFeeCap *uint256.Int
gasTipCap *uint256.Int
maxFeePerDataGas *uint256.Int
)
if baseFee == nil {
// If there's no basefee, then it must be a non-1559 execution
@ -200,6 +202,9 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int)
gasFeeCap, gasTipCap = gasPrice, gasPrice
}
}
if args.MaxFeePerDataGas != nil {
maxFeePerDataGas.SetFromBig(args.MaxFeePerDataGas.ToInt())
}
}
value := new(uint256.Int)
if args.Value != nil {
@ -216,7 +221,7 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int)
if args.AccessList != nil {
accessList = *args.AccessList
}
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, false /* checkNonce */, false /* isFree */)
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, false /* checkNonce */, false /* isFree */, maxFeePerDataGas)
return msg, nil
}

View File

@ -220,6 +220,12 @@ func (back *RemoteBackend) EngineGetPayloadBodiesByRangeV1(ctx context.Context,
return back.remoteEthBackend.EngineGetPayloadBodiesByRangeV1(ctx, request)
}
func (back *RemoteBackend) EngineGetBlobsBundleV1(ctx context.Context, payloadId uint64) (*types2.BlobsBundleV1, error) {
return back.remoteEthBackend.EngineGetBlobsBundleV1(ctx, &remote.EngineGetBlobsBundleRequest{
PayloadId: payloadId,
})
}
func (back *RemoteBackend) NodeInfo(ctx context.Context, limit uint32) ([]p2p.NodeInfo, error) {
nodes, err := back.remoteEthBackend.NodeInfo(ctx, &remote.NodesInfoRequest{Limit: limit})
if err != nil {

View File

@ -78,3 +78,7 @@ func VerifyEip4844Header(config *chain.Config, parent, header *types.Header) err
func GetDataGasPrice(excessDataGas *big.Int) *big.Int {
return FakeExponential(big.NewInt(params.MinDataGasPrice), excessDataGas, big.NewInt(params.DataGasPriceUpdateFraction))
}
func GetDataGasUsed(numBlobs int) uint64 {
return uint64(numBlobs) * params.DataGasPerBlob
}

View File

@ -334,6 +334,7 @@ func SysCallContract(contract libcommon.Address, data []byte, chainConfig chain.
nil, nil,
data, nil, false,
true, // isFree
nil, // maxFeePerDataGas
)
vmConfig := vm.Config{NoReceipts: true, RestoreState: constCall}
// Create a new context to be used in the EVM environment
@ -377,6 +378,7 @@ func SysCreate(contract libcommon.Address, data []byte, chainConfig chain.Config
nil, nil,
data, nil, false,
true, // isFree
nil, // maxFeePerDataGas
)
vmConfig := vm.Config{NoReceipts: true}
// Create a new context to be used in the EVM environment

View File

@ -28,6 +28,7 @@ import (
"github.com/ledgerwatch/erigon/common"
cmath "github.com/ledgerwatch/erigon/common/math"
"github.com/ledgerwatch/erigon/common/u256"
"github.com/ledgerwatch/erigon/consensus/misc"
"github.com/ledgerwatch/erigon/core/vm"
"github.com/ledgerwatch/erigon/core/vm/evmtypes"
"github.com/ledgerwatch/erigon/crypto"
@ -454,5 +455,5 @@ func (st *StateTransition) gasUsed() uint64 {
}
func (st *StateTransition) dataGasUsed() uint64 {
return uint64(len(st.msg.DataHashes())) * params.DataGasPerBlob
return misc.GetDataGasUsed(len(st.msg.DataHashes()))
}

View File

@ -316,22 +316,22 @@ func toProofs(_proofs KZGProofs) []gokzg4844.KZGProof {
// BlobTxWrapper is the "network representation" of a Blob transaction, that is it includes not
// only the SignedBlobTx but also all the associated blob data.
type BlobTxWrapper struct {
Tx SignedBlobTx
BlobKzgs BlobKzgs
Blobs Blobs
KzgAggregatedProof KZGProofs
Tx SignedBlobTx
BlobKzgs BlobKzgs
Blobs Blobs
Proofs KZGProofs
}
func (txw *BlobTxWrapper) Deserialize(dr *codec.DecodingReader) error {
return dr.Container(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.KzgAggregatedProof)
return dr.Container(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.Proofs)
}
func (txw *BlobTxWrapper) Serialize(w *codec.EncodingWriter) error {
return w.Container(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.KzgAggregatedProof)
return w.Container(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.Proofs)
}
func (txw *BlobTxWrapper) ByteLength() uint64 {
return codec.ContainerLength(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.KzgAggregatedProof)
return codec.ContainerLength(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.Proofs)
}
func (txw *BlobTxWrapper) FixedLength() uint64 {
@ -341,11 +341,15 @@ func (txw *BlobTxWrapper) FixedLength() uint64 {
// validateBlobTransactionWrapper implements validate_blob_transaction_wrapper from EIP-4844
func (txw *BlobTxWrapper) ValidateBlobTransactionWrapper() error {
blobTx := txw.Tx.Message
l1 := len(txw.BlobKzgs)
l2 := len(blobTx.BlobVersionedHashes)
l1 := len(blobTx.BlobVersionedHashes)
if l1 == 0 {
return fmt.Errorf("a blob tx must contain at least one blob")
}
l2 := len(txw.BlobKzgs)
l3 := len(txw.Blobs)
if l1 != l2 || l2 != l3 {
return fmt.Errorf("lengths don't match %v %v %v", l1, l2, l3)
l4 := len(txw.Proofs)
if l1 != l2 || l2 != l3 || l1 != l4 {
return fmt.Errorf("lengths don't match %v %v %v %v", l1, l2, l3, l4)
}
// the following check isn't strictly necessary as it would be caught by data gas processing
// (and hence it is not explicitly in the spec for this function), but it doesn't hurt to fail
@ -354,7 +358,7 @@ func (txw *BlobTxWrapper) ValidateBlobTransactionWrapper() error {
return fmt.Errorf("number of blobs exceeds max: %v", l1)
}
cryptoCtx := kzg.CrpytoCtx()
err := cryptoCtx.VerifyBlobKZGProofBatch(toBlobs(txw.Blobs), toComms(txw.BlobKzgs), toProofs(txw.KzgAggregatedProof))
err := cryptoCtx.VerifyBlobKZGProofBatch(toBlobs(txw.Blobs), toComms(txw.BlobKzgs), toProofs(txw.Proofs))
if err != nil {
return fmt.Errorf("error during proof verification: %v", err)
}
@ -409,7 +413,7 @@ func (txw *BlobTxWrapper) IsContractDeploy() bool { return t
func (txw *BlobTxWrapper) Unwrap() Transaction { return &txw.Tx }
func (txw BlobTxWrapper) EncodingSize() int {
envelopeSize := int(codec.ContainerLength(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.KzgAggregatedProof))
envelopeSize := int(codec.ContainerLength(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.Proofs))
// Add type byte
envelopeSize++
return envelopeSize

View File

@ -52,7 +52,7 @@ const (
LegacyTxType = iota
AccessListTxType
DynamicFeeTxType
BlobTxType = 5
BlobTxType
)
// Transaction is an Ethereum transaction.
@ -473,7 +473,7 @@ type Message struct {
dataHashes []libcommon.Hash
}
func NewMessage(from libcommon.Address, to *libcommon.Address, nonce uint64, amount *uint256.Int, gasLimit uint64, gasPrice *uint256.Int, feeCap, tip *uint256.Int, data []byte, accessList types2.AccessList, checkNonce bool, isFree bool) Message {
func NewMessage(from libcommon.Address, to *libcommon.Address, nonce uint64, amount *uint256.Int, gasLimit uint64, gasPrice *uint256.Int, feeCap, tip *uint256.Int, data []byte, accessList types2.AccessList, checkNonce bool, isFree bool, maxFeePerDataGas *uint256.Int) Message {
m := Message{
from: from,
to: to,
@ -494,6 +494,9 @@ func NewMessage(from libcommon.Address, to *libcommon.Address, nonce uint64, amo
if feeCap != nil {
m.feeCap.Set(feeCap)
}
if maxFeePerDataGas != nil {
m.maxFeePerDataGas.Set(maxFeePerDataGas)
}
return m
}

View File

@ -296,8 +296,57 @@ func (s *EthBackendServer) checkWithdrawalsPresence(time uint64, withdrawals []*
return nil
}
func (s *EthBackendServer) EngineGetBlobsBundleV1(ctx context.Context, in *remote.EngineGetBlobsBundleRequest) (*types2.BlobsBundleV1, error) {
return nil, fmt.Errorf("EngineGetBlobsBundleV1: not implemented yet")
func (s *EthBackendServer) EngineGetBlobsBundleV1(ctx context.Context, req *remote.EngineGetBlobsBundleRequest) (*types2.BlobsBundleV1, error) {
if !s.proposing {
return nil, fmt.Errorf("execution layer not running as a proposer. enable proposer by taking out the --proposer.disable flag on startup")
}
if s.config.TerminalTotalDifficulty == nil {
return nil, fmt.Errorf("not a proof-of-stake chain")
}
log.Debug("[GetBlobsBundleV1] acquiring lock")
s.lock.Lock()
defer s.lock.Unlock()
log.Debug("[GetBlobsBundleV1] lock acquired")
builder, ok := s.builders[req.PayloadId]
if !ok {
log.Warn("Payload not stored", "payloadId", req.PayloadId)
return nil, &UnknownPayloadErr
}
block, err := builder.Stop()
if err != nil {
log.Error("Failed to build PoS block", "err", err)
return nil, err
}
blobsBundle := &types2.BlobsBundleV1{
BlockHash: gointerfaces.ConvertHashToH256(block.Block.Header().Hash()),
}
for i, tx := range block.Block.Transactions() {
if tx.Type() != types.BlobTxType {
continue
}
blobtx, ok := tx.(*types.BlobTxWrapper)
if !ok {
return nil, fmt.Errorf("expected blob transaction to be type BlobTxWrapper, got: %T", blobtx)
}
versionedHashes, kzgs, blobs, proofs := blobtx.GetDataHashes(), blobtx.BlobKzgs, blobtx.Blobs, blobtx.Proofs
lenCheck := len(versionedHashes)
if lenCheck != len(kzgs) || lenCheck != len(blobs) || lenCheck != len(blobtx.Proofs) {
return nil, fmt.Errorf("tx %d in block %s has inconsistent blobs (%d) / kzgs (%d) / proofs (%d)"+
" / versioned hashes (%d)", i, block.Block.Hash(), len(blobs), len(kzgs), len(proofs), lenCheck)
}
for _, blob := range blobs {
blobsBundle.Blobs = append(blobsBundle.Blobs, blob[:])
}
for _, kzg := range kzgs {
blobsBundle.Kzgs = append(blobsBundle.Kzgs, kzg[:])
}
}
return blobsBundle, nil
}
// EngineNewPayload validates and possibly executes payload

View File

@ -455,7 +455,9 @@ func toMessage(tx stTransactionMarshaling, ps stPostState, baseFee *big.Int) (co
data,
accessList,
false, /* checkNonce */
false /* isFree */)
false, /* isFree */
uint256.NewInt(tipCap.Uint64()),
)
return msg, nil
}

View File

@ -43,6 +43,7 @@ type CallArgs struct {
GasPrice *hexutil.Big `json:"gasPrice"`
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
MaxFeePerDataGas *hexutil.Big `json:"maxFeePerDataGas"`
Value *hexutil.Big `json:"value"`
Nonce *hexutil.Uint64 `json:"nonce"`
Data *hexutility.Bytes `json:"data"`
@ -81,9 +82,10 @@ func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (type
}
var (
gasPrice *uint256.Int
gasFeeCap *uint256.Int
gasTipCap *uint256.Int
gasPrice *uint256.Int
gasFeeCap *uint256.Int
gasTipCap *uint256.Int
maxFeePerDataGas *uint256.Int
)
if baseFee == nil {
// If there's no basefee, then it must be a non-1559 execution
@ -127,6 +129,9 @@ func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (type
gasPrice = math.U256Min(new(uint256.Int).Add(gasTipCap, baseFee), gasFeeCap)
}
}
if args.MaxFeePerDataGas != nil {
maxFeePerDataGas.SetFromBig(args.MaxFeePerDataGas.ToInt())
}
}
value := new(uint256.Int)
@ -145,7 +150,7 @@ func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (type
accessList = *args.AccessList
}
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, false /* checkNonce */, false /* isFree */)
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, false /* checkNonce */, false /* isFree */, maxFeePerDataGas)
return msg, nil
}
@ -377,6 +382,7 @@ type RPCTransaction struct {
GasPrice *hexutil.Big `json:"gasPrice,omitempty"`
Tip *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
FeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
MaxFeePerDataGas *hexutil.Big `json:"maxFeePerDataGas,omitempty"`
Hash libcommon.Hash `json:"hash"`
Input hexutility.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"`
@ -444,6 +450,7 @@ func newRPCTransaction(tx types.Transaction, blockHash libcommon.Hash, blockNumb
} else {
result.GasPrice = nil
}
// case *types.SignedBlobTx: // TODO
}
signer := types.LatestSignerForChainID(chainId.ToBig())
var err error

View File

@ -28,6 +28,7 @@ type ApiBackend interface {
EngineNewPayload(ctx context.Context, payload *types2.ExecutionPayload) (*remote.EnginePayloadStatus, error)
EngineForkchoiceUpdated(ctx context.Context, request *remote.EngineForkChoiceUpdatedRequest) (*remote.EngineForkChoiceUpdatedResponse, error)
EngineGetPayload(ctx context.Context, payloadId uint64) (*remote.EngineGetPayloadResponse, error)
EngineGetBlobsBundleV1(ctx context.Context, payloadId uint64) (*types2.BlobsBundleV1, error)
NodeInfo(ctx context.Context, limit uint32) ([]p2p.NodeInfo, error)
Peers(ctx context.Context) ([]*p2p.PeerInfo, error)
PendingBlock(ctx context.Context) (*types.Block, error)