@ -19,6 +19,7 @@ package ethapi
import (
import (
"bytes"
"bytes"
"context"
"context"
"crypto/sha256"
"errors"
"errors"
"fmt"
"fmt"
"math/big"
"math/big"
@ -29,11 +30,17 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"
"github.com/holiman/uint256"
)
)
var (
maxBlobsPerTransaction = params . MaxBlobGasPerBlock / params . BlobTxBlobGasPerBlob
)
// TransactionArgs represents the arguments to construct a new transaction
// TransactionArgs represents the arguments to construct a new transaction
// or a message call.
// or a message call.
type TransactionArgs struct {
type TransactionArgs struct {
@ -56,9 +63,17 @@ type TransactionArgs struct {
AccessList * types . AccessList ` json:"accessList,omitempty" `
AccessList * types . AccessList ` json:"accessList,omitempty" `
ChainID * hexutil . Big ` json:"chainId,omitempty" `
ChainID * hexutil . Big ` json:"chainId,omitempty" `
// Introduced by EIP-4844.
// For BlobTxType
BlobFeeCap * hexutil . Big ` json:"maxFeePerBlobGas" `
BlobFeeCap * hexutil . Big ` json:"maxFeePerBlobGas" `
BlobHashes [ ] common . Hash ` json:"blobVersionedHashes,omitempty" `
BlobHashes [ ] common . Hash ` json:"blobVersionedHashes,omitempty" `
// For BlobTxType transactions with blob sidecar
Blobs [ ] kzg4844 . Blob ` json:"blobs" `
Commitments [ ] kzg4844 . Commitment ` json:"commitments" `
Proofs [ ] kzg4844 . Proof ` json:"proofs" `
// This configures whether blobs are allowed to be passed.
blobSidecarAllowed bool
}
}
// from retrieves the transaction sender address.
// from retrieves the transaction sender address.
@ -82,9 +97,13 @@ func (args *TransactionArgs) data() []byte {
// setDefaults fills in default values for unspecified tx fields.
// setDefaults fills in default values for unspecified tx fields.
func ( args * TransactionArgs ) setDefaults ( ctx context . Context , b Backend ) error {
func ( args * TransactionArgs ) setDefaults ( ctx context . Context , b Backend ) error {
if err := args . setBlobTxSidecar ( ctx , b ) ; err != nil {
return err
}
if err := args . setFeeDefaults ( ctx , b ) ; err != nil {
if err := args . setFeeDefaults ( ctx , b ) ; err != nil {
return err
return err
}
}
if args . Value == nil {
if args . Value == nil {
args . Value = new ( hexutil . Big )
args . Value = new ( hexutil . Big )
}
}
@ -98,15 +117,25 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
if args . Data != nil && args . Input != nil && ! bytes . Equal ( * args . Data , * args . Input ) {
if args . Data != nil && args . Input != nil && ! bytes . Equal ( * args . Data , * args . Input ) {
return errors . New ( ` both "data" and "input" are set and not equal. Please use "input" to pass transaction call data ` )
return errors . New ( ` both "data" and "input" are set and not equal. Please use "input" to pass transaction call data ` )
}
}
if args . BlobHashes != nil && args . To == nil {
return errors . New ( ` blob transactions cannot have the form of a create transaction ` )
// BlobTx fields
}
if args . BlobHashes != nil && len ( args . BlobHashes ) == 0 {
if args . BlobHashes != nil && len ( args . BlobHashes ) == 0 {
return errors . New ( ` need at least 1 blob for a blob transaction ` )
return errors . New ( ` need at least 1 blob for a blob transaction ` )
}
}
if args . To == nil && len ( args . data ( ) ) == 0 {
if args . BlobHashes != nil && len ( args . BlobHashes ) > maxBlobsPerTransaction {
return errors . New ( ` contract creation without any data provided ` )
return fmt . Errorf ( ` too many blobs in transaction (have=%d, max=%d) ` , len ( args . BlobHashes ) , maxBlobsPerTransaction )
}
// create check
if args . To == nil {
if args . BlobHashes != nil {
return errors . New ( ` missing "to" in blob transaction ` )
}
if len ( args . data ( ) ) == 0 {
return errors . New ( ` contract creation without any data provided ` )
}
}
}
// Estimate the gas usage if necessary.
// Estimate the gas usage if necessary.
if args . Gas == nil {
if args . Gas == nil {
// These fields are immutable during the estimation, safe to
// These fields are immutable during the estimation, safe to
@ -130,6 +159,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
args . Gas = & estimated
args . Gas = & estimated
log . Trace ( "Estimate gas usage automatically" , "gas" , args . Gas )
log . Trace ( "Estimate gas usage automatically" , "gas" , args . Gas )
}
}
// If chain id is provided, ensure it matches the local chain id. Otherwise, set the local
// If chain id is provided, ensure it matches the local chain id. Otherwise, set the local
// chain id as the default.
// chain id as the default.
want := b . ChainConfig ( ) . ChainID
want := b . ChainConfig ( ) . ChainID
@ -165,10 +195,12 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) erro
}
}
return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas
return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas
}
}
// Sanity check the EIP-4844 fee parameters.
// Sanity check the EIP-4844 fee parameters.
if args . BlobFeeCap != nil && args . BlobFeeCap . ToInt ( ) . Sign ( ) == 0 {
if args . BlobFeeCap != nil && args . BlobFeeCap . ToInt ( ) . Sign ( ) == 0 {
return errors . New ( "maxFeePerBlobGas must be non-zero" )
return errors . New ( "maxFeePerBlobGas must be non-zero" )
}
}
// Sanity check the non-EIP-1559 fee parameters.
// Sanity check the non-EIP-1559 fee parameters.
head := b . CurrentHeader ( )
head := b . CurrentHeader ( )
isLondon := b . ChainConfig ( ) . IsLondon ( head . Number )
isLondon := b . ChainConfig ( ) . IsLondon ( head . Number )
@ -250,6 +282,81 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ
return nil
return nil
}
}
// setBlobTxSidecar adds the blob tx
func ( args * TransactionArgs ) setBlobTxSidecar ( ctx context . Context , b Backend ) error {
// No blobs, we're done.
if args . Blobs == nil {
return nil
}
// Passing blobs is not allowed in all contexts, only in specific methods.
if ! args . blobSidecarAllowed {
return errors . New ( ` "blobs" is not supported for this RPC method ` )
}
n := len ( args . Blobs )
// Assume user provides either only blobs (w/o hashes), or
// blobs together with commitments and proofs.
if args . Commitments == nil && args . Proofs != nil {
return errors . New ( ` blob proofs provided while commitments were not ` )
} else if args . Commitments != nil && args . Proofs == nil {
return errors . New ( ` blob commitments provided while proofs were not ` )
}
// len(blobs) == len(commitments) == len(proofs) == len(hashes)
if args . Commitments != nil && len ( args . Commitments ) != n {
return fmt . Errorf ( "number of blobs and commitments mismatch (have=%d, want=%d)" , len ( args . Commitments ) , n )
}
if args . Proofs != nil && len ( args . Proofs ) != n {
return fmt . Errorf ( "number of blobs and proofs mismatch (have=%d, want=%d)" , len ( args . Proofs ) , n )
}
if args . BlobHashes != nil && len ( args . BlobHashes ) != n {
return fmt . Errorf ( "number of blobs and hashes mismatch (have=%d, want=%d)" , len ( args . BlobHashes ) , n )
}
if args . Commitments == nil {
// Generate commitment and proof.
commitments := make ( [ ] kzg4844 . Commitment , n )
proofs := make ( [ ] kzg4844 . Proof , n )
for i , b := range args . Blobs {
c , err := kzg4844 . BlobToCommitment ( b )
if err != nil {
return fmt . Errorf ( "blobs[%d]: error computing commitment: %v" , i , err )
}
commitments [ i ] = c
p , err := kzg4844 . ComputeBlobProof ( b , c )
if err != nil {
return fmt . Errorf ( "blobs[%d]: error computing proof: %v" , i , err )
}
proofs [ i ] = p
}
args . Commitments = commitments
args . Proofs = proofs
} else {
for i , b := range args . Blobs {
if err := kzg4844 . VerifyBlobProof ( b , args . Commitments [ i ] , args . Proofs [ i ] ) ; err != nil {
return fmt . Errorf ( "failed to verify blob proof: %v" , err )
}
}
}
hashes := make ( [ ] common . Hash , n )
hasher := sha256 . New ( )
for i , c := range args . Commitments {
hashes [ i ] = kzg4844 . CalcBlobHashV1 ( hasher , & c )
}
if args . BlobHashes != nil {
for i , h := range hashes {
if h != args . BlobHashes [ i ] {
return fmt . Errorf ( "blob hash verification failed (have=%s, want=%s)" , args . BlobHashes [ i ] , h )
}
}
} else {
args . BlobHashes = hashes
}
return nil
}
// ToMessage converts the transaction arguments to the Message type used by the
// ToMessage converts the transaction arguments to the Message type used by the
// core evm. This method is used in calls and traces that do not require a real
// core evm. This method is used in calls and traces that do not require a real
// live transaction.
// live transaction.
@ -363,6 +470,14 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
BlobHashes : args . BlobHashes ,
BlobHashes : args . BlobHashes ,
BlobFeeCap : uint256 . MustFromBig ( ( * big . Int ) ( args . BlobFeeCap ) ) ,
BlobFeeCap : uint256 . MustFromBig ( ( * big . Int ) ( args . BlobFeeCap ) ) ,
}
}
if args . Blobs != nil {
data . ( * types . BlobTx ) . Sidecar = & types . BlobTxSidecar {
Blobs : args . Blobs ,
Commitments : args . Commitments ,
Proofs : args . Proofs ,
}
}
case args . MaxFeePerGas != nil :
case args . MaxFeePerGas != nil :
al := types . AccessList { }
al := types . AccessList { }
if args . AccessList != nil {
if args . AccessList != nil {
@ -379,6 +494,7 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
Data : args . data ( ) ,
Data : args . data ( ) ,
AccessList : al ,
AccessList : al ,
}
}
case args . AccessList != nil :
case args . AccessList != nil :
data = & types . AccessListTx {
data = & types . AccessListTx {
To : args . To ,
To : args . To ,
@ -390,6 +506,7 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
Data : args . data ( ) ,
Data : args . data ( ) ,
AccessList : * args . AccessList ,
AccessList : * args . AccessList ,
}
}
default :
default :
data = & types . LegacyTx {
data = & types . LegacyTx {
To : args . To ,
To : args . To ,
@ -403,12 +520,6 @@ func (args *TransactionArgs) toTransaction() *types.Transaction {
return types . NewTx ( data )
return types . NewTx ( data )
}
}
// ToTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called.
func ( args * TransactionArgs ) ToTransaction ( ) * types . Transaction {
return args . toTransaction ( )
}
// IsEIP4844 returns an indicator if the args contains EIP4844 fields.
// IsEIP4844 returns an indicator if the args contains EIP4844 fields.
func ( args * TransactionArgs ) IsEIP4844 ( ) bool {
func ( args * TransactionArgs ) IsEIP4844 ( ) bool {
return args . BlobHashes != nil || args . BlobFeeCap != nil
return args . BlobHashes != nil || args . BlobFeeCap != nil