@ -46,6 +46,7 @@ import java.util.Optional;
import java.util.stream.Collectors ;
import java.util.stream.Stream ;
import com.google.common.primitives.Longs ;
import org.apache.tuweni.bytes.Bytes ;
import org.apache.tuweni.bytes.Bytes32 ;
import org.apache.tuweni.units.bigints.UInt256 ;
@ -79,6 +80,7 @@ public class Transaction
private final Optional < Wei > maxPriorityFeePerGas ;
private final Optional < Wei > maxFeePerGas ;
private final Optional < Wei > maxFeePerDataGas ;
private final long gasLimit ;
@ -134,6 +136,7 @@ public class Transaction
* @param gasPrice the gas price
* @param maxPriorityFeePerGas the max priority fee per gas
* @param maxFeePerGas the max fee per gas
* @param maxFeePerDataGas the max fee per data gas
* @param gasLimit the gas limit
* @param to the transaction recipient
* @param value the value being transferred to the recipient
@ -157,6 +160,7 @@ public class Transaction
final Optional < Wei > gasPrice ,
final Optional < Wei > maxPriorityFeePerGas ,
final Optional < Wei > maxFeePerGas ,
final Optional < Wei > maxFeePerDataGas ,
final long gasLimit ,
final Optional < Address > to ,
final Wei value ,
@ -183,26 +187,30 @@ public class Transaction
"Must not specify access list for transaction not supporting it" ) ;
}
if ( gasPrice
. or ( ( ) - > maxFeePerGas )
. orElse ( Wei . ZERO )
. getAsBigInteger ( )
. multiply ( BigInteger . valueOf ( gasLimit ) )
. bitLength ( )
> 256 ) {
throw new IllegalArgumentException ( "Upfront gas cost exceeds UInt256" ) ;
}
if ( Objects . equals ( transactionType , TransactionType . ACCESS_LIST ) ) {
checkArgument (
maybeAccessList . isPresent ( ) , "Must specify access list for access list transaction" ) ;
}
if ( versionedHashes . isPresent ( ) | | maxFeePerDataGas . isPresent ( ) ) {
checkArgument (
transactionType . supportsBlob ( ) ,
"Must not specify blob versioned hashes of max fee per data gas for transaction not supporting it" ) ;
}
if ( transactionType . supportsBlob ( ) ) {
checkArgument (
versionedHashes . isPresent ( ) , "Must specify blob versioned hashes for blob transaction" ) ;
checkArgument (
maxFeePerDataGas . isPresent ( ) , "Must specify max fee per data gas for blob transaction" ) ;
}
this . transactionType = transactionType ;
this . nonce = nonce ;
this . gasPrice = gasPrice ;
this . maxPriorityFeePerGas = maxPriorityFeePerGas ;
this . maxFeePerGas = maxFeePerGas ;
this . maxFeePerDataGas = maxFeePerDataGas ;
this . gasLimit = gasLimit ;
this . to = to ;
this . value = value ;
@ -213,6 +221,10 @@ public class Transaction
this . chainId = chainId ;
this . v = v ;
this . versionedHashes = versionedHashes ;
if ( isUpfrontGasCostTooHigh ( ) ) {
throw new IllegalArgumentException ( "Upfront gas cost exceeds UInt256" ) ;
}
}
public Transaction (
@ -220,6 +232,7 @@ public class Transaction
final Optional < Wei > gasPrice ,
final Optional < Wei > maxPriorityFeePerGas ,
final Optional < Wei > maxFeePerGas ,
final Optional < Wei > maxFeePerDataGas ,
final long gasLimit ,
final Optional < Address > to ,
final Wei value ,
@ -235,6 +248,7 @@ public class Transaction
gasPrice ,
maxPriorityFeePerGas ,
maxFeePerGas ,
maxFeePerDataGas ,
gasLimit ,
to ,
value ,
@ -264,6 +278,7 @@ public class Transaction
Optional . of ( gasPrice ) ,
Optional . empty ( ) ,
Optional . empty ( ) ,
Optional . empty ( ) ,
gasLimit ,
Optional . of ( to ) ,
value ,
@ -302,12 +317,14 @@ public class Transaction
final SECPSignature signature ,
final Bytes payload ,
final Address sender ,
final Optional < BigInteger > chainId ) {
final Optional < BigInteger > chainId ,
final Optional < List < Hash > > versionedHashes ) {
this (
nonce ,
Optional . of ( gasPrice ) ,
Optional . empty ( ) ,
Optional . empty ( ) ,
Optional . empty ( ) ,
gasLimit ,
to ,
value ,
@ -316,7 +333,7 @@ public class Transaction
sender ,
chainId ,
Optional . empty ( ) ,
Optional . empty ( ) ) ;
versionedHashes ) ;
}
/ * *
@ -354,6 +371,7 @@ public class Transaction
Optional . of ( gasPrice ) ,
Optional . empty ( ) ,
Optional . empty ( ) ,
Optional . empty ( ) ,
gasLimit ,
to ,
value ,
@ -405,6 +423,16 @@ public class Transaction
return maxFeePerGas ;
}
/ * *
* Return the transaction max fee per data gas .
*
* @return the transaction max fee per data gas
* /
@Override
public Optional < Wei > getMaxFeePerDataGas ( ) {
return maxFeePerDataGas ;
}
/ * *
* Boolean which indicates the transaction has associated cost data , whether gas price or 1559 fee
* market parameters .
@ -412,7 +440,9 @@ public class Transaction
* @return whether cost params are present
* /
public boolean hasCostParams ( ) {
return Arrays . asList ( getGasPrice ( ) , getMaxFeePerGas ( ) , getMaxPriorityFeePerGas ( ) ) . stream ( )
return Arrays . asList (
getGasPrice ( ) , getMaxFeePerGas ( ) , getMaxPriorityFeePerGas ( ) , getMaxFeePerDataGas ( ) )
. stream ( )
. flatMap ( Optional : : stream )
. map ( Quantity : : getAsBigInteger )
. anyMatch ( q - > q . longValue ( ) > 0L ) ;
@ -437,6 +467,7 @@ public class Transaction
} )
. orElseGet ( ( ) - > getGasPrice ( ) . orElse ( Wei . ZERO ) ) ;
}
/ * *
* Returns the transaction gas limit .
*
@ -566,11 +597,13 @@ public class Transaction
gasPrice . orElse ( null ) ,
maxPriorityFeePerGas . orElse ( null ) ,
maxFeePerGas . orElse ( null ) ,
maxFeePerDataGas . orElse ( null ) ,
gasLimit ,
to ,
value ,
payload ,
maybeAccessList ,
versionedHashes . orElse ( null ) ,
chainId ) ;
}
return hashNoSignature ;
@ -673,6 +706,15 @@ public class Transaction
String . format ( "Transaction requires either gasPrice or maxFeePerGas" ) ) ) ) ;
}
/ * *
* Check if the upfront gas cost is over the max allowed
*
* @return true is upfront data cost overflow uint256 max value
* /
private boolean isUpfrontGasCostTooHigh ( ) {
return calculateUpfrontGasCost ( getMaxGasPrice ( ) ) . bitLength ( ) > 256 ;
}
/ * *
* Calculates the up - front cost for the gas the transaction can use .
*
@ -683,7 +725,9 @@ public class Transaction
if ( gasPrice = = null | | gasPrice . isZero ( ) ) {
return Wei . ZERO ;
}
var cost = BigInteger . valueOf ( getGasLimit ( ) ) . multiply ( gasPrice . getAsBigInteger ( ) ) ;
final var cost = calculateUpfrontGasCost ( gasPrice ) ;
if ( cost . bitLength ( ) > 256 ) {
return Wei . MAX_WEI ;
} else {
@ -691,6 +735,10 @@ public class Transaction
}
}
private BigInteger calculateUpfrontGasCost ( final Wei gasPrice ) {
return new BigInteger ( 1 , Longs . toByteArray ( getGasLimit ( ) ) ) . multiply ( gasPrice . getAsBigInteger ( ) ) ;
}
/ * *
* Calculates the up - front cost for the transaction .
*
@ -704,6 +752,35 @@ public class Transaction
return getUpfrontGasCost ( ) . addExact ( getValue ( ) ) ;
}
/ * *
* Return the maximum fee per gas the sender is willing to pay for this transaction .
*
* @return max fee per gas in wei
* /
public Wei getMaxGasPrice ( ) {
return maxFeePerGas . orElseGet (
( ) - >
gasPrice . orElseThrow (
( ) - >
new IllegalStateException (
"Transaction requires either gasPrice or maxFeePerGas" ) ) ) ;
}
/ * *
* Calculates the effectiveGasPrice of a transaction on the basis of an { @code Optional < Long > }
* baseFee and handles unwrapping Optional fee parameters . If baseFee is present , effective gas is
* calculated as :
*
* < p > min ( ( baseFeePerGas + maxPriorityFeePerGas ) , maxFeePerGas )
*
* < p > Otherwise , return gasPrice for legacy transactions .
*
* @param baseFeePerGas optional baseFee from the block header , if we are post - london
* @return the effective gas price .
* /
public final Wei getEffectiveGasPrice ( final Optional < Wei > baseFeePerGas ) {
return getEffectivePriorityFeePerGas ( baseFeePerGas ) . addExact ( baseFeePerGas . orElse ( Wei . ZERO ) ) ;
}
@Override
public TransactionType getType ( ) {
return this . transactionType ;
@ -752,11 +829,13 @@ public class Transaction
final Wei gasPrice ,
final Wei maxPriorityFeePerGas ,
final Wei maxFeePerGas ,
final Wei maxFeePerDataGas ,
final long gasLimit ,
final Optional < Address > to ,
final Wei value ,
final Bytes payload ,
final Optional < List < AccessListEntry > > accessList ,
final List < Hash > versionedHashes ,
final Optional < BigInteger > chainId ) {
if ( transactionType . requiresChainId ( ) ) {
checkArgument ( chainId . isPresent ( ) , "Transaction type %s requires chainId" , transactionType ) ;
@ -766,7 +845,6 @@ public class Transaction
case FRONTIER :
preimage = frontierPreimage ( nonce , gasPrice , gasLimit , to , value , payload , chainId ) ;
break ;
case BLOB : // ToDo 4844: specialize for blob when more field will be added for it
case EIP1559 :
preimage =
eip1559Preimage (
@ -780,6 +858,21 @@ public class Transaction
chainId ,
accessList ) ;
break ;
case BLOB :
preimage =
blobPreimage (
nonce ,
maxPriorityFeePerGas ,
maxFeePerGas ,
maxFeePerDataGas ,
gasLimit ,
to ,
value ,
payload ,
chainId ,
accessList ,
versionedHashes ) ;
break ;
case ACCESS_LIST :
preimage =
accessListPreimage (
@ -842,20 +935,78 @@ public class Transaction
RLP . encode (
rlpOutput - > {
rlpOutput . startList ( ) ;
rlpOutput . writeBigIntegerScalar ( chainId . orElseThrow ( ) ) ;
rlpOutput . writeLongScalar ( nonce ) ;
rlpOutput . writeUInt256Scalar ( maxPriorityFeePerGas ) ;
rlpOutput . writeUInt256Scalar ( maxFeePerGas ) ;
rlpOutput . writeLongScalar ( gasLimit ) ;
rlpOutput . writeBytes ( to . map ( Bytes : : copy ) . orElse ( Bytes . EMPTY ) ) ;
rlpOutput . writeUInt256Scalar ( value ) ;
rlpOutput . writeBytes ( payload ) ;
TransactionEncoder . writeAccessList ( rlpOutput , accessList ) ;
eip1559PreimageFields (
nonce ,
maxPriorityFeePerGas ,
maxFeePerGas ,
gasLimit ,
to ,
value ,
payload ,
chainId ,
accessList ,
rlpOutput ) ;
rlpOutput . endList ( ) ;
} ) ;
return Bytes . concatenate ( Bytes . of ( TransactionType . EIP1559 . getSerializedType ( ) ) , encoded ) ;
}
private static void eip1559PreimageFields (
final long nonce ,
final Wei maxPriorityFeePerGas ,
final Wei maxFeePerGas ,
final long gasLimit ,
final Optional < Address > to ,
final Wei value ,
final Bytes payload ,
final Optional < BigInteger > chainId ,
final Optional < List < AccessListEntry > > accessList ,
final RLPOutput rlpOutput ) {
rlpOutput . writeBigIntegerScalar ( chainId . orElseThrow ( ) ) ;
rlpOutput . writeLongScalar ( nonce ) ;
rlpOutput . writeUInt256Scalar ( maxPriorityFeePerGas ) ;
rlpOutput . writeUInt256Scalar ( maxFeePerGas ) ;
rlpOutput . writeLongScalar ( gasLimit ) ;
rlpOutput . writeBytes ( to . map ( Bytes : : copy ) . orElse ( Bytes . EMPTY ) ) ;
rlpOutput . writeUInt256Scalar ( value ) ;
rlpOutput . writeBytes ( payload ) ;
TransactionEncoder . writeAccessList ( rlpOutput , accessList ) ;
}
private static Bytes blobPreimage (
final long nonce ,
final Wei maxPriorityFeePerGas ,
final Wei maxFeePerGas ,
final Wei maxFeePerDataGas ,
final long gasLimit ,
final Optional < Address > to ,
final Wei value ,
final Bytes payload ,
final Optional < BigInteger > chainId ,
final Optional < List < AccessListEntry > > accessList ,
final List < Hash > versionedHashes ) {
final Bytes encoded =
RLP . encode (
rlpOutput - > {
rlpOutput . startList ( ) ;
eip1559PreimageFields (
nonce ,
maxPriorityFeePerGas ,
maxFeePerGas ,
gasLimit ,
to ,
value ,
payload ,
chainId ,
accessList ,
rlpOutput ) ;
rlpOutput . writeUInt256Scalar ( maxFeePerDataGas ) ;
TransactionEncoder . writeBlobVersionedHashes ( rlpOutput , versionedHashes ) ;
rlpOutput . endList ( ) ;
} ) ;
return Bytes . concatenate ( Bytes . of ( TransactionType . BLOB . getSerializedType ( ) ) , encoded ) ;
}
private static Bytes accessListPreimage (
final long nonce ,
final Wei gasPrice ,
@ -887,6 +1038,7 @@ public class Transaction
& & Objects . equals ( this . gasPrice , that . gasPrice )
& & Objects . equals ( this . maxPriorityFeePerGas , that . maxPriorityFeePerGas )
& & Objects . equals ( this . maxFeePerGas , that . maxFeePerGas )
& & Objects . equals ( this . maxFeePerDataGas , that . maxFeePerDataGas )
& & this . nonce = = that . nonce
& & Objects . equals ( this . payload , that . payload )
& & Objects . equals ( this . signature , that . signature )
@ -902,6 +1054,7 @@ public class Transaction
gasPrice ,
maxPriorityFeePerGas ,
maxFeePerGas ,
maxFeePerDataGas ,
gasLimit ,
to ,
value ,
@ -927,6 +1080,9 @@ public class Transaction
sb . append ( "maxFeePerGas=" )
. append ( getMaxFeePerGas ( ) . map ( Wei : : toShortHexString ) . get ( ) )
. append ( ", " ) ;
getMaxFeePerDataGas ( )
. ifPresent (
wei - > sb . append ( "maxFeePerDataGas=" ) . append ( wei . toShortHexString ( ) ) . append ( ", " ) ) ;
}
sb . append ( "gasLimit=" ) . append ( getGasLimit ( ) ) . append ( ", " ) ;
if ( getTo ( ) . isPresent ( ) ) sb . append ( "to=" ) . append ( getTo ( ) . get ( ) ) . append ( ", " ) ;
@ -951,6 +1107,7 @@ public class Transaction
if ( getMaxPriorityFeePerGas ( ) . isPresent ( ) & & getMaxFeePerGas ( ) . isPresent ( ) ) {
sb . append ( getMaxPriorityFeePerGas ( ) . map ( Wei : : toBigInteger ) . get ( ) ) . append ( ", " ) ;
sb . append ( getMaxFeePerGas ( ) . map ( Wei : : toBigInteger ) . get ( ) ) . append ( ", " ) ;
getMaxFeePerDataGas ( ) . ifPresent ( wei - > sb . append ( wei . toShortHexString ( ) ) . append ( ", " ) ) ;
}
sb . append ( getGasLimit ( ) ) . append ( ", " ) ;
sb . append ( getValue ( ) . toBigInteger ( ) ) . append ( ", " ) ;
@ -976,6 +1133,7 @@ public class Transaction
protected Wei maxPriorityFeePerGas ;
protected Wei maxFeePerGas ;
protected Wei maxFeePerDataGas ;
protected long gasLimit = - 1L ;
@ -1026,6 +1184,11 @@ public class Transaction
return this ;
}
public Builder maxFeePerDataGas ( final Wei maxFeePerDataGas ) {
this . maxFeePerDataGas = maxFeePerDataGas ;
return this ;
}
public Builder gasLimit ( final long gasLimit ) {
this . gasLimit = gasLimit ;
return this ;
@ -1096,6 +1259,7 @@ public class Transaction
Optional . ofNullable ( gasPrice ) ,
Optional . ofNullable ( maxPriorityFeePerGas ) ,
Optional . ofNullable ( maxFeePerGas ) ,
Optional . ofNullable ( maxFeePerDataGas ) ,
gasLimit ,
to ,
value ,
@ -1125,29 +1289,15 @@ public class Transaction
gasPrice ,
maxPriorityFeePerGas ,
maxFeePerGas ,
maxFeePerDataGas ,
gasLimit ,
to ,
value ,
payload ,
accessList ,
versionedHashes ,
chainId ) ,
keys ) ;
}
}
/ * *
* Calculates the effectiveGasPrice of a transaction on the basis of an { @code Optional < Long > }
* baseFee and handles unwrapping Optional fee parameters . If baseFee is present , effective gas is
* calculated as :
*
* < p > min ( ( baseFeePerGas + maxPriorityFeePerGas ) , maxFeePerGas )
*
* < p > Otherwise , return gasPrice for legacy transactions .
*
* @param baseFeePerGas optional baseFee from the block header , if we are post - london
* @return the effective gas price .
* /
public final Wei getEffectiveGasPrice ( final Optional < Wei > baseFeePerGas ) {
return getEffectivePriorityFeePerGas ( baseFeePerGas ) . addExact ( baseFeePerGas . orElse ( Wei . ZERO ) ) ;
}
}