eip-4844: RPCTransactions to support BlobTx (#7407)

Small additions to support eip-4844 transaction marshalling
This commit is contained in:
racytech 2023-04-30 13:03:38 +06:00 committed by GitHub
parent f31d2b097d
commit 726ce264ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 266 additions and 47 deletions

View File

@ -343,25 +343,27 @@ func NewEthAPI(base *BaseAPI, db kv.RoDB, eth rpchelper.ApiBackend, txPool txpoo
// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
type RPCTransaction struct {
BlockHash *common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"`
From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice,omitempty"`
Tip *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
FeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
Hash common.Hash `json:"hash"`
Input hexutility.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"`
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
Value *hexutil.Big `json:"value"`
Type hexutil.Uint64 `json:"type"`
Accesses *types2.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"`
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
BlockHash *common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"`
From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice,omitempty"`
Tip *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
FeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
Hash common.Hash `json:"hash"`
Input hexutility.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"`
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
Value *hexutil.Big `json:"value"`
Type hexutil.Uint64 `json:"type"`
Accesses *types2.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"`
MaxFeePerDataGas *hexutil.Big `json:"maxFeePerDataGas,omitempty"`
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
}
// newRPCTransaction returns a transaction that will serialize to the RPC
@ -409,14 +411,17 @@ func newRPCTransaction(tx types.Transaction, blockHash common.Hash, blockNumber
result.R = (*hexutil.Big)(t.R.ToBig())
result.S = (*hexutil.Big)(t.S.ToBig())
result.Accesses = &t.AccessList
baseFee, overflow := uint256.FromBig(baseFee)
if baseFee != nil && !overflow && blockHash != (common.Hash{}) {
// price = min(tip + baseFee, gasFeeCap)
price := math.Min256(new(uint256.Int).Add(tx.GetTip(), baseFee), tx.GetFeeCap())
result.GasPrice = (*hexutil.Big)(price.ToBig())
} else {
result.GasPrice = nil
}
result.GasPrice = computeGasPrice(tx, blockHash, baseFee)
case *types.SignedBlobTx:
result.Tip = (*hexutil.Big)(t.GetTip().ToBig())
result.FeeCap = (*hexutil.Big)(t.GetFeeCap().ToBig())
result.MaxFeePerDataGas = (*hexutil.Big)(t.GetMaxFeePerDataGas().ToBig())
result.V = (*hexutil.Big)(t.Signature.GetV().ToBig())
result.R = (*hexutil.Big)(t.Signature.GetR().ToBig())
result.S = (*hexutil.Big)(t.Signature.GetS().ToBig())
al := t.GetAccessList()
result.Accesses = &al
result.GasPrice = computeGasPrice(tx, blockHash, baseFee)
}
signer := types.LatestSignerForChainID(chainId.ToBig())
result.From, _ = tx.Sender(*signer)
@ -428,6 +433,16 @@ func newRPCTransaction(tx types.Transaction, blockHash common.Hash, blockNumber
return result
}
func computeGasPrice(tx types.Transaction, blockHash common.Hash, baseFee *big.Int) *hexutil.Big {
fee, overflow := uint256.FromBig(baseFee)
if fee != nil && !overflow && blockHash != (common.Hash{}) {
// price = min(tip + baseFee, gasFeeCap)
price := math.Min256(new(uint256.Int).Add(tx.GetTip(), fee), tx.GetFeeCap())
return (*hexutil.Big)(price.ToBig())
}
return nil
}
// newRPCBorTransaction returns a Bor transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func newRPCBorTransaction(opaqueTx types.Transaction, txHash common.Hash, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int, chainId *big.Int) *RPCTransaction {

View File

@ -743,6 +743,8 @@ func marshalReceipt(receipt *types.Receipt, txn types.Transaction, chainConfig *
chainId = t.ChainID.ToBig()
case *types.DynamicFeeTransaction:
chainId = t.ChainID.ToBig()
// case *types.SignedBlobTx: // TODO: needs eip-4844 signer
// chainId = t.GetChainID().ToBig()
}
var from common.Address

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
Proofs KZGProofs
Tx SignedBlobTx
Commitments BlobKzgs
Blobs Blobs
Proofs KZGProofs
}
func (txw *BlobTxWrapper) Deserialize(dr *codec.DecodingReader) error {
return dr.Container(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.Proofs)
return dr.Container(&txw.Tx, &txw.Commitments, &txw.Blobs, &txw.Proofs)
}
func (txw *BlobTxWrapper) Serialize(w *codec.EncodingWriter) error {
return w.Container(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.Proofs)
return w.Container(&txw.Tx, &txw.Commitments, &txw.Blobs, &txw.Proofs)
}
func (txw *BlobTxWrapper) ByteLength() uint64 {
return codec.ContainerLength(&txw.Tx, &txw.BlobKzgs, &txw.Blobs, &txw.Proofs)
return codec.ContainerLength(&txw.Tx, &txw.Commitments, &txw.Blobs, &txw.Proofs)
}
func (txw *BlobTxWrapper) FixedLength() uint64 {
@ -345,7 +345,7 @@ func (txw *BlobTxWrapper) ValidateBlobTransactionWrapper() error {
if l1 == 0 {
return fmt.Errorf("a blob tx must contain at least one blob")
}
l2 := len(txw.BlobKzgs)
l2 := len(txw.Commitments)
l3 := len(txw.Blobs)
l4 := len(txw.Proofs)
if l1 != l2 || l2 != l3 || l1 != l4 {
@ -358,12 +358,12 @@ 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.Proofs))
err := cryptoCtx.VerifyBlobKZGProofBatch(toBlobs(txw.Blobs), toComms(txw.Commitments), toProofs(txw.Proofs))
if err != nil {
return fmt.Errorf("error during proof verification: %v", err)
}
for i, h := range blobTx.BlobVersionedHashes {
if computed := txw.BlobKzgs[i].ComputeVersionedHash(); computed != h {
if computed := txw.Commitments[i].ComputeVersionedHash(); computed != h {
return fmt.Errorf("versioned hash %d supposedly %s but does not match computed %s", i, h, computed)
}
}
@ -413,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.Proofs))
envelopeSize := int(codec.ContainerLength(&txw.Tx, &txw.Commitments, &txw.Blobs, &txw.Proofs))
// Add type byte
envelopeSize++
return envelopeSize

View File

@ -6,6 +6,7 @@ import (
"fmt"
"github.com/holiman/uint256"
. "github.com/protolambda/ztyp/view"
"github.com/valyala/fastjson"
libcommon "github.com/ledgerwatch/erigon-lib/common"
@ -36,6 +37,14 @@ type txJSON struct {
ChainID *hexutil.Big `json:"chainId,omitempty"`
AccessList *types2.AccessList `json:"accessList,omitempty"`
// Blob transaction fields:
MaxFeePerDataGas *hexutil.Big `json:"maxFeePerDataGas,omitempty"`
BlobVersionedHashes []libcommon.Hash `json:"blobVersionedHashes,omitempty"`
// Blob wrapper fields:
Blobs Blobs `json:"blobs,omitempty"`
Commitments BlobKzgs `json:"commitments,omitempty"`
Proofs KZGProofs `json:"proofs,omitempty"`
// Only used for encoding:
Hash libcommon.Hash `json:"hash"`
}
@ -96,6 +105,45 @@ func (tx DynamicFeeTransaction) MarshalJSON() ([]byte, error) {
return json.Marshal(&enc)
}
func toSignedBlobTxJSON(tx *SignedBlobTx) *txJSON {
var enc txJSON
// These are set for all tx types.
enc.Hash = tx.Hash()
enc.Type = hexutil.Uint64(tx.Type())
enc.ChainID = (*hexutil.Big)(tx.GetChainID().ToBig())
accessList := tx.GetAccessList()
enc.AccessList = &accessList
nonce := tx.GetNonce()
enc.Nonce = (*hexutil.Uint64)(&nonce)
gas := tx.GetGas()
enc.Gas = (*hexutil.Uint64)(&gas)
enc.FeeCap = (*hexutil.Big)(tx.GetFeeCap().ToBig())
enc.Tip = (*hexutil.Big)(tx.GetTip().ToBig())
enc.Value = (*hexutil.Big)(tx.GetValue().ToBig())
enc.Data = (*hexutility.Bytes)(&tx.Message.Data)
enc.To = tx.GetTo()
enc.V = (*hexutil.Big)(tx.Signature.GetV().ToBig())
enc.R = (*hexutil.Big)(tx.Signature.GetR().ToBig())
enc.S = (*hexutil.Big)(tx.Signature.GetS().ToBig())
enc.MaxFeePerDataGas = (*hexutil.Big)(tx.GetMaxFeePerDataGas().ToBig())
enc.BlobVersionedHashes = tx.GetDataHashes()
return &enc
}
func (tx SignedBlobTx) MarshalJSON() ([]byte, error) {
return json.Marshal(toSignedBlobTxJSON(&tx))
}
func (tx BlobTxWrapper) MarshalJSON() ([]byte, error) {
enc := toSignedBlobTxJSON(&tx.Tx)
enc.Blobs = tx.Blobs
enc.Commitments = tx.Commitments
enc.Proofs = tx.Proofs
return json.Marshal(enc)
}
func UnmarshalTransactionFromJSON(input []byte) (Transaction, error) {
var p fastjson.Parser
v, err := p.ParseBytes(input)
@ -129,6 +177,12 @@ func UnmarshalTransactionFromJSON(input []byte) (Transaction, error) {
return nil, err
}
return tx, nil
case BlobTxType:
tx, err := UnmarshalBlobTxJSON(input)
if err != nil {
return nil, err
}
return tx, nil
default:
return nil, fmt.Errorf("unknown transaction type: %v", txType)
}
@ -360,3 +414,131 @@ func (tx *DynamicFeeTransaction) UnmarshalJSON(input []byte) error {
}
return nil
}
func UnmarshalBlobTxJSON(input []byte) (Transaction, error) {
var dec txJSON
if err := json.Unmarshal(input, &dec); err != nil {
return nil, err
}
tx := SignedBlobTx{}
if dec.AccessList != nil {
tx.Message.AccessList = AccessListView(*dec.AccessList)
} else {
tx.Message.AccessList = AccessListView([]types2.AccessTuple{})
}
if dec.ChainID == nil {
return nil, errors.New("missing required field 'chainId' in transaction")
}
chainID, overflow := uint256.FromBig(dec.ChainID.ToInt())
if overflow {
return nil, errors.New("'chainId' in transaction does not fit in 256 bits")
}
tx.Message.ChainID = Uint256View(*chainID)
if dec.To != nil {
address := AddressSSZ(*dec.To)
tx.Message.To = AddressOptionalSSZ{Address: &address}
}
if dec.Nonce == nil {
return nil, errors.New("missing required field 'nonce' in transaction")
}
tx.Message.Nonce = Uint64View(uint64(*dec.Nonce))
tip, overflow := uint256.FromBig(dec.Tip.ToInt())
if overflow {
return nil, errors.New("'tip' in transaction does not fit in 256 bits")
}
tx.Message.GasTipCap = Uint256View(*tip)
feeCap, overflow := uint256.FromBig(dec.FeeCap.ToInt())
if overflow {
return nil, errors.New("'feeCap' in transaction does not fit in 256 bits")
}
tx.Message.GasFeeCap = Uint256View(*feeCap)
if dec.Gas == nil {
return nil, errors.New("missing required field 'gas' in transaction")
}
tx.Message.Gas = Uint64View(uint64(*dec.Gas))
if dec.Value == nil {
return nil, errors.New("missing required field 'value' in transaction")
}
value, overflow := uint256.FromBig(dec.Value.ToInt())
if overflow {
return nil, errors.New("'value' in transaction does not fit in 256 bits")
}
tx.Message.Value = Uint256View(*value)
if dec.Data == nil {
return nil, errors.New("missing required field 'input' in transaction")
}
tx.Message.Data = TxDataView(*dec.Data)
if dec.MaxFeePerDataGas == nil {
return nil, errors.New("missing required field 'maxFeePerDataGas' in transaction")
}
maxFeePerDataGas, overflow := uint256.FromBig(dec.MaxFeePerDataGas.ToInt())
if overflow {
return nil, errors.New("'maxFeePerDataGas' in transaction does not fit in 256 bits")
}
tx.Message.MaxFeePerDataGas = Uint256View(*maxFeePerDataGas)
if dec.BlobVersionedHashes != nil {
tx.Message.BlobVersionedHashes = VersionedHashesView(dec.BlobVersionedHashes)
} else {
tx.Message.BlobVersionedHashes = VersionedHashesView([]libcommon.Hash{})
}
if dec.V == nil {
return nil, errors.New("missing required field 'v' in transaction")
}
var v uint256.Int
overflow = v.SetFromBig(dec.V.ToInt())
if overflow {
return nil, fmt.Errorf("dec.V higher than 2^256-1")
}
if v.Uint64() > 255 {
return nil, fmt.Errorf("dev.V higher than 2^8 - 1")
}
tx.Signature.V = Uint8View(v.Uint64())
if dec.R == nil {
return nil, errors.New("missing required field 'r' in transaction")
}
var r uint256.Int
overflow = r.SetFromBig(dec.R.ToInt())
if overflow {
return nil, fmt.Errorf("dec.R higher than 2^256-1")
}
tx.Signature.R = Uint256View(r)
if dec.S == nil {
return nil, errors.New("missing required field 's' in transaction")
}
var s uint256.Int
overflow = s.SetFromBig(dec.S.ToInt())
if overflow {
return nil, errors.New("'s' in transaction does not fit in 256 bits")
}
tx.Signature.S = Uint256View(s)
withSignature := !v.IsZero() || !r.IsZero() || !s.IsZero()
if withSignature {
if err := sanityCheckSignature(&v, &r, &s, false); err != nil {
return nil, err
}
}
if len(dec.Blobs) == 0 {
// if no blobs are specified in the json we assume it is an unwrapped blob tx
return &tx, nil
}
btx := BlobTxWrapper{
Tx: tx,
Commitments: dec.Commitments,
Blobs: dec.Blobs,
Proofs: dec.Proofs,
}
err := btx.ValidateBlobTransactionWrapper()
if err != nil {
return nil, err
}
return &btx, nil
}

View File

@ -297,6 +297,7 @@ func (s *EthBackendServer) checkWithdrawalsPresence(time uint64, withdrawals []*
}
func (s *EthBackendServer) EngineGetBlobsBundleV1(ctx context.Context, req *remote.EngineGetBlobsBundleRequest) (*types2.BlobsBundleV1, error) {
// TODO: get the latest update on this function (it was replaced)
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")
}
@ -333,7 +334,7 @@ func (s *EthBackendServer) EngineGetBlobsBundleV1(ctx context.Context, req *remo
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
versionedHashes, kzgs, blobs, proofs := blobtx.GetDataHashes(), blobtx.Commitments, 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)"+

View File

@ -395,6 +395,8 @@ type RPCTransaction struct {
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
BlobVersionedHashes []libcommon.Hash `json:"blobVersionedHashes,omitempty"`
}
// newRPCTransaction returns a transaction that will serialize to the RPC
@ -443,14 +445,22 @@ func newRPCTransaction(tx types.Transaction, blockHash libcommon.Hash, blockNumb
result.S = (*hexutil.Big)(t.S.ToBig())
result.Accesses = &t.AccessList
// if the transaction has been mined, compute the effective gas price
if baseFee != nil && blockHash != (libcommon.Hash{}) {
// price = min(tip, gasFeeCap - baseFee) + baseFee
price := math.BigMin(new(big.Int).Add(t.Tip.ToBig(), baseFee), t.FeeCap.ToBig())
result.GasPrice = (*hexutil.Big)(price)
} else {
result.GasPrice = nil
}
// case *types.SignedBlobTx: // TODO
result.GasPrice = computeGasPrice(tx, blockHash, baseFee)
case *types.SignedBlobTx:
chainId.Set(t.GetChainID())
result.ChainID = (*hexutil.Big)(chainId.ToBig())
result.Tip = (*hexutil.Big)(t.GetTip().ToBig())
result.FeeCap = (*hexutil.Big)(t.GetFeeCap().ToBig())
v, r, s := t.RawSignatureValues()
result.V = (*hexutil.Big)(v.ToBig())
result.R = (*hexutil.Big)(r.ToBig())
result.S = (*hexutil.Big)(s.ToBig())
al := t.GetAccessList()
result.Accesses = &al
// if the transaction has been mined, compute the effective gas price
result.GasPrice = computeGasPrice(tx, blockHash, baseFee)
result.MaxFeePerDataGas = (*hexutil.Big)(t.GetMaxFeePerDataGas().ToBig())
result.BlobVersionedHashes = t.GetDataHashes()
}
signer := types.LatestSignerForChainID(chainId.ToBig())
var err error
@ -466,6 +476,15 @@ func newRPCTransaction(tx types.Transaction, blockHash libcommon.Hash, blockNumb
return result
}
func computeGasPrice(tx types.Transaction, blockHash libcommon.Hash, baseFee *big.Int) *hexutil.Big {
if baseFee != nil && blockHash != (libcommon.Hash{}) {
// price = min(tip + baseFee, gasFeeCap)
price := math.BigMin(new(big.Int).Add(tx.GetTip().ToBig(), baseFee), tx.GetFeeCap().ToBig())
return (*hexutil.Big)(price)
}
return nil
}
// newRPCBorTransaction returns a Bor transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func newRPCBorTransaction(opaqueTx types.Transaction, txHash libcommon.Hash, blockHash libcommon.Hash, blockNumber uint64, index uint64, baseFee *big.Int) *RPCTransaction {