feat: add support of eth_estimateGas for L1 transactions (#694)

* feat: add support of eth_estimateGas for L1 transactions

* feat: revise log message for calling eth estimate gas

* chore: update function signatures

* fix: unit test for merge conflicts

* fix: merge conflicts

* feat: use web3J async helper to encapsulate useEthEstimateGas option

* feat: remove calling eth_estimateGas in eth_call for aggregation tx and disable eth estimate gas on data submission

* feat: seperate useEthEstimateGas option for data submission and aggregation and remove calling eth_estimateGas on eth_call for data submission

* feat: add new function in LineaRollupSmartContractClient and remove unnecessary parameter

* feat: correct coordinator config and update sequencer config
This commit is contained in:
jonesho
2025-02-28 02:40:53 +08:00
committed by GitHub
parent 171b0ce431
commit 5c8ea31d2a
15 changed files with 397 additions and 237 deletions

View File

@@ -152,7 +152,8 @@ max-receipt-retries=120
blocks-to-finalization=0
[blob-submission]
disabled=true
disabled=false
use-eth-estimate-gas=false
db-polling-interval="PT1S"
max-blobs-to-return=100
proof-submission-delay="PT1S"
@@ -165,6 +166,7 @@ priority-fee-per-gas-lower-bound=200000000 # 0.2 GWEI
[aggregation-finalization]
disabled=false
use-eth-estimate-gas=true
db-polling-interval="PT1S"
max-aggregations-to-finalize-per-tick=1
proof-submission-delay="PT1S"

View File

@@ -91,14 +91,16 @@ fun createLineaRollupContractClient(
transactionManager: AsyncFriendlyTransactionManager,
contractGasProvider: ContractGasProvider,
web3jClient: Web3j,
smartContractErrors: SmartContractErrors
smartContractErrors: SmartContractErrors,
useEthEstimateGas: Boolean
): LineaRollupSmartContractClient {
return Web3JLineaRollupSmartContractClient.load(
contractAddress = l1Config.zkEvmContractAddress,
web3j = web3jClient,
transactionManager = transactionManager,
contractGasProvider = contractGasProvider,
smartContractErrors = smartContractErrors
smartContractErrors = smartContractErrors,
useEthEstimateGas = useEthEstimateGas
)
}

View File

@@ -439,7 +439,8 @@ class L1DependentApp(
),
contractGasProvider = primaryOrFallbackGasProvider,
web3jClient = l1Web3jClient,
smartContractErrors = smartContractErrors
smartContractErrors = smartContractErrors,
useEthEstimateGas = configs.blobSubmission.useEthEstimateGas
)
}
@@ -526,44 +527,48 @@ class L1DependentApp(
private val latestBlobSubmittedBlockNumberTracker = LatestBlobSubmittedBlockNumberTracker(0UL)
private val blobSubmissionCoordinator = run {
metricsFacade.createGauge(
category = LineaMetricsCategory.BLOB,
name = "highest.submitted.on.l1",
description = "Highest submitted blob end block number on l1",
measurementSupplier = { latestBlobSubmittedBlockNumberTracker.get() }
)
if (!configs.blobSubmission.enabled) {
DisabledLongRunningService
} else {
metricsFacade.createGauge(
category = LineaMetricsCategory.BLOB,
name = "highest.submitted.on.l1",
description = "Highest submitted blob end block number on l1",
measurementSupplier = { latestBlobSubmittedBlockNumberTracker.get() }
)
val blobSubmissionDelayHistogram = metricsFacade.createHistogram(
category = LineaMetricsCategory.BLOB,
name = "submission.delay",
description = "Delay between blob submission and end block timestamps",
baseUnit = "seconds"
)
val blobSubmissionDelayHistogram = metricsFacade.createHistogram(
category = LineaMetricsCategory.BLOB,
name = "submission.delay",
description = "Delay between blob submission and end block timestamps",
baseUnit = "seconds"
)
val blobSubmittedEventConsumers: Map<Consumer<BlobSubmittedEvent>, String> = mapOf(
Consumer<BlobSubmittedEvent> { blobSubmission ->
latestBlobSubmittedBlockNumberTracker(blobSubmission)
} to "Submitted Blob Tracker Consumer",
Consumer<BlobSubmittedEvent> { blobSubmission ->
blobSubmissionDelayHistogram.record(blobSubmission.getSubmissionDelay().toDouble())
} to "Blob Submission Delay Consumer"
)
val blobSubmittedEventConsumers: Map<Consumer<BlobSubmittedEvent>, String> = mapOf(
Consumer<BlobSubmittedEvent> { blobSubmission ->
latestBlobSubmittedBlockNumberTracker(blobSubmission)
} to "Submitted Blob Tracker Consumer",
Consumer<BlobSubmittedEvent> { blobSubmission ->
blobSubmissionDelayHistogram.record(blobSubmission.getSubmissionDelay().toDouble())
} to "Blob Submission Delay Consumer"
)
BlobSubmissionCoordinator.create(
config = BlobSubmissionCoordinator.Config(
configs.blobSubmission.dbPollingInterval.toKotlinDuration(),
configs.blobSubmission.proofSubmissionDelay.toKotlinDuration(),
configs.blobSubmission.maxBlobsToSubmitPerTick.toUInt()
),
blobsRepository = blobsRepository,
aggregationsRepository = aggregationsRepository,
lineaSmartContractClient = lineaSmartContractClientForDataSubmission,
gasPriceCapProvider = gasPriceCapProviderForDataSubmission,
alreadySubmittedBlobsFilter = alreadySubmittedBlobsFilter,
blobSubmittedEventDispatcher = EventDispatcher(blobSubmittedEventConsumers),
vertx = vertx,
clock = Clock.System
)
BlobSubmissionCoordinator.create(
config = BlobSubmissionCoordinator.Config(
configs.blobSubmission.dbPollingInterval.toKotlinDuration(),
configs.blobSubmission.proofSubmissionDelay.toKotlinDuration(),
configs.blobSubmission.maxBlobsToSubmitPerTick.toUInt()
),
blobsRepository = blobsRepository,
aggregationsRepository = aggregationsRepository,
lineaSmartContractClient = lineaSmartContractClientForDataSubmission,
gasPriceCapProvider = gasPriceCapProviderForDataSubmission,
alreadySubmittedBlobsFilter = alreadySubmittedBlobsFilter,
blobSubmittedEventDispatcher = EventDispatcher(blobSubmittedEventConsumers),
vertx = vertx,
clock = Clock.System
)
}
}
private val proofAggregationCoordinatorService: LongRunningService = run {
@@ -656,7 +661,8 @@ class L1DependentApp(
transactionManager = finalizationTransactionManager,
contractGasProvider = primaryOrFallbackGasProvider,
web3jClient = l1Web3jClient,
smartContractErrors = smartContractErrors
smartContractErrors = smartContractErrors,
useEthEstimateGas = configs.aggregationFinalization.useEthEstimateGas
)
val latestFinalizationSubmittedBlockNumberTracker = LatestFinalizationSubmittedBlockNumberTracker(0UL)

View File

@@ -221,6 +221,7 @@ data class BlobSubmissionConfig(
val maxBlobsToSubmitPerTick: Int = maxBlobsToReturn,
// defaults to 6, not supported atm, preparatory work
val targetBlobsToSendPerTransaction: Int = 6,
val useEthEstimateGas: Boolean = false,
override var disabled: Boolean = false
) : FeatureToggleable {
init {
@@ -236,6 +237,7 @@ data class AggregationFinalizationConfig(
val dbPollingInterval: Duration,
val maxAggregationsToFinalizePerTick: Int,
val proofSubmissionDelay: Duration,
val useEthEstimateGas: Boolean = false,
override var disabled: Boolean = false
) : FeatureToggleable {
init {

View File

@@ -161,13 +161,15 @@ class CoordinatorConfigTest {
priorityFeePerGasLowerBound = 200000000UL,
proofSubmissionDelay = Duration.parse("PT1S"),
targetBlobsToSendPerTransaction = 6,
disabled = true
useEthEstimateGas = false,
disabled = false
)
private val aggregationFinalizationConfig = AggregationFinalizationConfig(
dbPollingInterval = Duration.parse("PT1S"),
maxAggregationsToFinalizePerTick = 1,
proofSubmissionDelay = Duration.parse("PT1S"),
useEthEstimateGas = true,
disabled = false
)

View File

@@ -133,7 +133,8 @@ max-receipt-retries=120
blocks-to-finalization=0
[blob-submission]
disabled=true
disabled=false
use-eth-estimate-gas=false
db-polling-interval="PT1S"
max-blobs-to-return=100
proof-submission-delay="PT1S"
@@ -146,6 +147,7 @@ priority-fee-per-gas-lower-bound=200000000 # 0.2 GWEI
[aggregation-finalization]
disabled=false
use-eth-estimate-gas=true
db-polling-interval="PT1S"
max-aggregations-to-finalize-per-tick=1
proof-submission-delay="PT1S"

View File

@@ -10,10 +10,13 @@ import net.consensys.linea.web3j.EIP4844GasFees
import net.consensys.linea.web3j.EIP4844GasProvider
import net.consensys.linea.web3j.Eip4844Transaction
import net.consensys.linea.web3j.SmartContractErrors
import net.consensys.linea.web3j.getRevertReason
import net.consensys.linea.web3j.informativeEthCall
import net.consensys.zkevm.domain.BlobRecord
import net.consensys.zkevm.ethereum.gaspricing.GasPriceCaps
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.apache.tuweni.bytes.Bytes
import org.web3j.abi.FunctionEncoder
import org.web3j.abi.datatypes.Function
import org.web3j.crypto.Blob
@@ -33,8 +36,9 @@ class Web3JContractAsyncHelper(
val contractAddress: String,
val web3j: Web3j,
val transactionManager: AsyncFriendlyTransactionManager,
private val gasProvider: ContractGasProvider,
private val smartContractErrors: SmartContractErrors
private val contractGasProvider: ContractGasProvider,
private val smartContractErrors: SmartContractErrors,
private val useEthEstimateGas: Boolean
) {
private val log: Logger = LogManager.getLogger(this::class.java)
@@ -44,40 +48,127 @@ class Web3JContractAsyncHelper(
.toSafeFuture()
}
fun createEip4844Transaction(
private fun getGasLimit(
function: Function,
blobs: List<Blob>,
gasPriceCaps: GasPriceCaps? = null
): Eip4844Transaction {
require(blobs.size in 1..6) { "Blobs size=${blobs.size} must be between 1 and 6." }
blobs: List<Blob>? = null,
blobVersionedHashes: List<Bytes>? = null
): SafeFuture<BigInteger> {
return if (useEthEstimateGas) {
getEthEstimatedGas(
FunctionEncoder.encode(function),
blobs,
blobVersionedHashes
).thenApply {
it ?: contractGasProvider.getGasLimit(function.name)
}
} else {
SafeFuture.completedFuture(contractGasProvider.getGasLimit(function.name))
}
}
val gasLimit = gasProvider.getGasLimit(function.name)
val (_, maxFeePerBlobGas) = getEip4844GasFees()
return Eip4844Transaction.createFunctionCallTransaction(
from = transactionManager.fromAddress,
to = contractAddress,
data = FunctionEncoder.encode(function),
blobs = blobs,
maxFeePerBlobGas = gasPriceCaps?.maxFeePerBlobGasCap?.toBigInteger() ?: maxFeePerBlobGas.toBigInteger(),
maxPriorityFeePerGas = gasPriceCaps?.maxPriorityFeePerGasCap?.toBigInteger(),
maxFeePerGas = gasPriceCaps?.maxFeePerGasCap?.toBigInteger(),
gasLimit = gasLimit,
blobVersionedHashes = blobs.map { BlobUtils.kzgToVersionedHash(BlobUtils.getCommitment(it)) }
private fun getEthEstimatedGas(
encodedFunction: String,
blobs: List<Blob>? = null,
blobVersionedHashes: List<Bytes>? = null
): SafeFuture<BigInteger?> {
return if (blobs != null && blobVersionedHashes != null) {
createEip4844FunctionCallTransaction(encodedFunction, blobs, blobVersionedHashes)
} else {
createFunctionCallTransaction(encodedFunction)
}.run(::callEthEstimateGas)
}
private fun callEthEstimateGas(tx: Transaction): SafeFuture<BigInteger?> {
return web3j.ethEstimateGas(tx).sendAsync()
.thenApply {
val withBlobs = tx is Eip4844Transaction
if (it.hasError()) {
log.info(
"eth_estimateGas failed for tx with blobCarrying={} error={} revertReason={}",
withBlobs,
it.error.message,
getRevertReason(it.error, smartContractErrors)
)
null
} else {
log.debug(
"eth_estimateGas for tx with blobCarrying={} estimatedGas={}",
withBlobs,
it.amountUsed
)
it.amountUsed
}
}
.toSafeFuture()
}
private fun createFunctionCallTransaction(
encodedFunction: String
): Transaction {
return Transaction.createFunctionCallTransaction(
transactionManager.fromAddress,
null,
null,
null,
contractAddress,
encodedFunction
)
}
private fun createEip4844FunctionCallTransaction(
encodedFunction: String,
blobs: List<Blob>,
blobVersionedHashes: List<Bytes>
): Eip4844Transaction {
return Eip4844Transaction.createFunctionCallTransaction(
from = transactionManager.fromAddress,
to = contractAddress,
data = encodedFunction,
blobs = blobs,
maxFeePerBlobGas = null,
maxPriorityFeePerGas = null,
maxFeePerGas = null,
gasLimit = null,
blobVersionedHashes = blobVersionedHashes
)
}
private fun createEip4844Transaction(
function: Function,
blobs: List<Blob>,
gasPriceCaps: GasPriceCaps? = null
): SafeFuture<Eip4844Transaction> {
require(blobs.size in 1..6) { "Blobs size=${blobs.size} must be between 1 and 6." }
val blobVersionedHashes = blobs.map { BlobUtils.kzgToVersionedHash(BlobUtils.getCommitment(it)) }
return getGasLimit(function, blobs, blobVersionedHashes)
.thenApply { gasLimit ->
val (_, maxFeePerBlobGas) = getEip4844GasFees()
Eip4844Transaction.createFunctionCallTransaction(
from = transactionManager.fromAddress,
to = contractAddress,
data = FunctionEncoder.encode(function),
blobs = blobs,
maxFeePerBlobGas = gasPriceCaps?.maxFeePerBlobGasCap?.toBigInteger() ?: maxFeePerBlobGas.toBigInteger(),
maxPriorityFeePerGas = gasPriceCaps?.maxPriorityFeePerGasCap?.toBigInteger(),
maxFeePerGas = gasPriceCaps?.maxFeePerGasCap?.toBigInteger(),
gasLimit = gasLimit,
blobVersionedHashes = blobVersionedHashes
)
}
}
private fun isGasProviderSupportedEIP1559(): Boolean {
return gasProvider is ContractEIP1559GasProvider && gasProvider.isEIP1559Enabled
return contractGasProvider is ContractEIP1559GasProvider && contractGasProvider.isEIP1559Enabled
}
private fun getEip1559GasFees(
functionName: String
): EIP1559GasFees {
return when (gasProvider) {
is AtomicContractEIP1559GasProvider -> gasProvider.getEIP1559GasFees()
return when (contractGasProvider) {
is AtomicContractEIP1559GasProvider -> contractGasProvider.getEIP1559GasFees()
is ContractEIP1559GasProvider -> EIP1559GasFees(
maxPriorityFeePerGas = gasProvider.getMaxPriorityFeePerGas(functionName).toULong(),
maxFeePerGas = gasProvider.getMaxFeePerGas(functionName).toULong()
maxPriorityFeePerGas = contractGasProvider.getMaxPriorityFeePerGas(functionName).toULong(),
maxFeePerGas = contractGasProvider.getMaxFeePerGas(functionName).toULong()
)
else -> throw UnsupportedOperationException("GasProvider does not support EIP1559!")
@@ -85,10 +176,10 @@ class Web3JContractAsyncHelper(
}
private fun getEip4844GasFees(): EIP4844GasFees {
if (gasProvider !is EIP4844GasProvider) {
if (contractGasProvider !is EIP4844GasProvider) {
throw UnsupportedOperationException("GasProvider does not support EIP4844!")
}
return gasProvider.getEIP4844GasFees()
return contractGasProvider.getEIP4844GasFees()
}
@Synchronized
@@ -101,10 +192,10 @@ class Web3JContractAsyncHelper(
if (isGasProviderSupportedEIP1559()) {
val (maxPriorityFeePerGas, maxFeePerGas) = getEip1559GasFees(function.name)
transactionManager.sendEIP1559Transaction(
(gasProvider as ContractEIP1559GasProvider).chainId,
(contractGasProvider as ContractEIP1559GasProvider).chainId,
maxPriorityFeePerGas.toBigInteger(),
maxFeePerGas.toBigInteger(),
gasProvider.getGasLimit(function.name),
contractGasProvider.getGasLimit(function.name),
contractAddress,
encodedData,
weiValue,
@@ -112,8 +203,8 @@ class Web3JContractAsyncHelper(
)
} else {
transactionManager.sendTransaction(
gasProvider.getGasPrice(function.name),
gasProvider.getGasLimit(function.name),
contractGasProvider.getGasPrice(function.name),
contractGasProvider.getGasLimit(function.name),
contractAddress,
encodedData,
weiValue,
@@ -124,11 +215,11 @@ class Web3JContractAsyncHelper(
return sendRawTransactionResult
}
@Synchronized
fun sendTransactionAsync(
private fun createAndSendRawTransaction(
function: Function,
weiValue: BigInteger,
gasPriceCaps: GasPriceCaps? = null
gasPriceCaps: GasPriceCaps? = null,
gasLimit: BigInteger
): CompletableFuture<EthSendTransaction> {
val transaction = if (isGasProviderSupportedEIP1559()) {
val (maxPriorityFeePerGas, maxFeePerGas) = getEip1559GasFees(function.name)
@@ -142,19 +233,19 @@ class Web3JContractAsyncHelper(
)
transactionManager.createRawTransaction(
chainId = (gasProvider as ContractEIP1559GasProvider).chainId,
chainId = (contractGasProvider as ContractEIP1559GasProvider).chainId,
maxPriorityFeePerGas = gasPriceCaps?.maxPriorityFeePerGasCap?.toBigInteger()
?: maxPriorityFeePerGas.toBigInteger(),
maxFeePerGas = gasPriceCaps?.maxFeePerGasCap?.toBigInteger() ?: maxFeePerGas.toBigInteger(),
gasLimit = gasProvider.getGasLimit(function.name),
gasLimit = gasLimit,
to = contractAddress,
value = weiValue,
data = FunctionEncoder.encode(function)
)
} else {
transactionManager.createRawTransaction(
gasPrice = gasProvider.getGasPrice(function.name),
gasLimit = gasProvider.getGasLimit(function.name),
gasPrice = contractGasProvider.getGasPrice(function.name),
gasLimit = gasLimit,
to = contractAddress,
value = weiValue,
data = FunctionEncoder.encode(function)
@@ -164,6 +255,33 @@ class Web3JContractAsyncHelper(
return web3j.ethSendRawTransaction(signedMessage).sendAsync()
}
@Synchronized
fun sendTransactionAsync(
function: Function,
weiValue: BigInteger,
gasPriceCaps: GasPriceCaps? = null
): CompletableFuture<EthSendTransaction> {
return getGasLimit(function)
.thenCompose { gasLimit ->
createAndSendRawTransaction(function, weiValue, gasPriceCaps, gasLimit)
}
}
@Synchronized
fun sendTransactionAfterEthCallAsync(
function: Function,
weiValue: BigInteger,
gasPriceCaps: GasPriceCaps? = null
): CompletableFuture<EthSendTransaction> {
return getGasLimit(function)
.thenCompose { gasLimit ->
executeEthCall(function, gasLimit)
.thenCompose {
createAndSendRawTransaction(function, weiValue, gasPriceCaps, gasLimit)
}
}
}
fun sendBlobCarryingTransactionAndGetTxHash(
function: Function,
blobs: List<ByteArray>,
@@ -185,33 +303,37 @@ class Web3JContractAsyncHelper(
blobs: List<Blob>,
gasPriceCaps: GasPriceCaps? = null
): CompletableFuture<EthSendTransaction> {
val (eip1559fees, maxFeePerBlobGas) = getEip4844GasFees()
val eip4844GasProvider = gasProvider as EIP4844GasProvider
val blobVersionedHashes = blobs.map { BlobUtils.kzgToVersionedHash(BlobUtils.getCommitment(it)) }
return getGasLimit(function, blobs, blobVersionedHashes)
.thenCompose { gasLimit ->
val eip4844GasProvider = contractGasProvider as EIP4844GasProvider
val (eip1559fees, maxFeePerBlobGas) = getEip4844GasFees()
logGasPriceCapsInfo(
logMessagePrefix = function.name,
maxPriorityFeePerGas = eip1559fees.maxPriorityFeePerGas,
maxFeePerGas = eip1559fees.maxFeePerGas,
maxFeePerBlobGas = maxFeePerBlobGas,
dynamicMaxPriorityFeePerGas = gasPriceCaps?.maxPriorityFeePerGasCap,
dynamicMaxFeePerGas = gasPriceCaps?.maxFeePerGasCap,
dynamicMaxFeePerBlobGas = gasPriceCaps?.maxFeePerBlobGasCap
)
logGasPriceCapsInfo(
logMessagePrefix = function.name,
maxPriorityFeePerGas = eip1559fees.maxPriorityFeePerGas,
maxFeePerGas = eip1559fees.maxFeePerGas,
maxFeePerBlobGas = maxFeePerBlobGas,
dynamicMaxPriorityFeePerGas = gasPriceCaps?.maxPriorityFeePerGasCap,
dynamicMaxFeePerGas = gasPriceCaps?.maxFeePerGasCap,
dynamicMaxFeePerBlobGas = gasPriceCaps?.maxFeePerBlobGasCap
)
val transaction = transactionManager.createRawTransaction(
blobs = blobs,
chainId = eip4844GasProvider.chainId,
maxPriorityFeePerGas = gasPriceCaps?.maxPriorityFeePerGasCap?.toBigInteger()
?: eip1559fees.maxPriorityFeePerGas.toBigInteger(),
maxFeePerGas = gasPriceCaps?.maxFeePerGasCap?.toBigInteger() ?: eip1559fees.maxFeePerGas.toBigInteger(),
gasLimit = eip4844GasProvider.getGasLimit(function.name),
to = contractAddress,
data = FunctionEncoder.encode(function),
value = weiValue,
maxFeePerBlobGas = gasPriceCaps?.maxFeePerBlobGasCap?.toBigInteger() ?: maxFeePerBlobGas.toBigInteger()
)
val signedMessage = transactionManager.sign(transaction)
return web3j.ethSendRawTransaction(signedMessage).sendAsync()
val transaction = transactionManager.createRawTransaction(
blobs = blobs,
chainId = eip4844GasProvider.chainId,
maxPriorityFeePerGas = gasPriceCaps?.maxPriorityFeePerGasCap?.toBigInteger()
?: eip1559fees.maxPriorityFeePerGas.toBigInteger(),
maxFeePerGas = gasPriceCaps?.maxFeePerGasCap?.toBigInteger() ?: eip1559fees.maxFeePerGas.toBigInteger(),
gasLimit = gasLimit,
to = contractAddress,
data = FunctionEncoder.encode(function),
value = weiValue,
maxFeePerBlobGas = gasPriceCaps?.maxFeePerBlobGasCap?.toBigInteger() ?: maxFeePerBlobGas.toBigInteger()
)
val signedMessage = transactionManager.sign(transaction)
web3j.ethSendRawTransaction(signedMessage).sendAsync()
}
}
@Synchronized
@@ -230,19 +352,34 @@ class Web3JContractAsyncHelper(
): RemoteFunctionCall<TransactionReceipt> {
return executeRemoteCallTransaction(function, BigInteger.ZERO)
}
fun executeEthCall(function: Function, overrideGasLimit: BigInteger? = null): SafeFuture<String?> {
return (overrideGasLimit?.let { SafeFuture.completedFuture(overrideGasLimit) } ?: getGasLimit(function))
.thenCompose { gasLimit ->
Transaction.createFunctionCallTransaction(
transactionManager.fromAddress,
null,
null,
gasLimit,
contractAddress,
FunctionEncoder.encode(function)
).let { tx ->
web3j.informativeEthCall(tx, smartContractErrors)
}
}
}
fun executeEthCall(function: Function): SafeFuture<String?> {
val gasLimit = gasProvider.getGasLimit(function.name)
val tx = Transaction.createFunctionCallTransaction(
transactionManager.fromAddress,
null,
null,
gasLimit,
contractAddress,
FunctionEncoder.encode(function)
)
return web3j.informativeEthCall(tx, smartContractErrors)
fun executeBlobEthCall(
function: Function,
blobs: List<BlobRecord>,
gasPriceCaps: GasPriceCaps?
): SafeFuture<String?> {
return createEip4844Transaction(
function,
blobs.map { it.blobCompressionProof!!.compressedData }.toWeb3JTxBlob(),
gasPriceCaps
).thenCompose { tx ->
web3j.informativeEthCall(tx, smartContractErrors)
}
}
private fun logGasPriceCapsInfo(

View File

@@ -13,23 +13,27 @@ import java.math.BigInteger
internal class LineaRollupEnhancedWrapper(
contractAddress: String,
web3j: Web3j,
asyncTransactionManager: AsyncFriendlyTransactionManager,
transactionManager: AsyncFriendlyTransactionManager,
contractGasProvider: ContractGasProvider,
val helper: Web3JContractAsyncHelper
private val web3jContractHelper: Web3JContractAsyncHelper
) : LineaRollupV6(
contractAddress,
web3j,
asyncTransactionManager,
transactionManager,
contractGasProvider
) {
@Synchronized
override fun executeRemoteCallTransaction(
function: Function,
weiValue: BigInteger
): RemoteFunctionCall<TransactionReceipt> = helper.executeRemoteCallTransaction(function, weiValue)
): RemoteFunctionCall<TransactionReceipt> = web3jContractHelper.executeRemoteCallTransaction(function, weiValue)
@Synchronized
override fun executeRemoteCallTransaction(
function: Function
): RemoteFunctionCall<TransactionReceipt> = helper.executeRemoteCallTransaction(function, BigInteger.ZERO)
): RemoteFunctionCall<TransactionReceipt> = web3jContractHelper.executeRemoteCallTransaction(
function,
BigInteger.ZERO
)
}

View File

@@ -6,9 +6,7 @@ import linea.kotlin.toULong
import net.consensys.linea.contract.AsyncFriendlyTransactionManager
import net.consensys.linea.contract.Web3JContractAsyncHelper
import net.consensys.linea.contract.throwExceptionIfJsonRpcErrorReturned
import net.consensys.linea.contract.toWeb3JTxBlob
import net.consensys.linea.web3j.SmartContractErrors
import net.consensys.linea.web3j.informativeEthCall
import net.consensys.zkevm.coordinator.clients.smartcontract.BlockAndNonce
import net.consensys.zkevm.coordinator.clients.smartcontract.LineaRollupSmartContractClient
import net.consensys.zkevm.domain.BlobRecord
@@ -26,24 +24,9 @@ import java.math.BigInteger
class Web3JLineaRollupSmartContractClient internal constructor(
contractAddress: String,
web3j: Web3j,
private val asyncTransactionManager: AsyncFriendlyTransactionManager,
contractGasProvider: ContractGasProvider,
private val smartContractErrors: SmartContractErrors,
private val helper: Web3JContractAsyncHelper =
Web3JContractAsyncHelper(
contractAddress,
web3j,
asyncTransactionManager,
contractGasProvider,
smartContractErrors
),
private val web3jLineaClient: LineaRollupV6 = LineaRollupEnhancedWrapper(
contractAddress,
web3j,
asyncTransactionManager,
contractGasProvider,
helper
),
private val transactionManager: AsyncFriendlyTransactionManager,
private val web3jContractHelper: Web3JContractAsyncHelper,
private val web3jLineaClient: LineaRollupV6,
private val log: Logger = LogManager.getLogger(Web3JLineaRollupSmartContractClient::class.java)
) : Web3JLineaRollupSmartContractClientReadOnly(
contractAddress = contractAddress,
@@ -58,14 +41,30 @@ class Web3JLineaRollupSmartContractClient internal constructor(
web3j: Web3j,
transactionManager: AsyncFriendlyTransactionManager,
contractGasProvider: ContractGasProvider,
smartContractErrors: SmartContractErrors
smartContractErrors: SmartContractErrors,
useEthEstimateGas: Boolean = false
): Web3JLineaRollupSmartContractClient {
val web3JContractAsyncHelper = Web3JContractAsyncHelper(
contractAddress = contractAddress,
web3j = web3j,
transactionManager = transactionManager,
contractGasProvider = contractGasProvider,
smartContractErrors = smartContractErrors,
useEthEstimateGas = useEthEstimateGas
)
val lineaRollupEnhancedWrapper = LineaRollupEnhancedWrapper(
contractAddress = contractAddress,
web3j = web3j,
transactionManager = transactionManager,
contractGasProvider = contractGasProvider,
web3jContractHelper = web3JContractAsyncHelper
)
return Web3JLineaRollupSmartContractClient(
contractAddress,
web3j,
transactionManager,
contractGasProvider,
smartContractErrors
contractAddress = contractAddress,
web3j = web3j,
transactionManager = transactionManager,
web3jContractHelper = web3JContractAsyncHelper,
web3jLineaClient = lineaRollupEnhancedWrapper
)
}
@@ -74,7 +73,8 @@ class Web3JLineaRollupSmartContractClient internal constructor(
web3j: Web3j,
credentials: Credentials,
contractGasProvider: ContractGasProvider,
smartContractErrors: SmartContractErrors
smartContractErrors: SmartContractErrors,
useEthEstimateGas: Boolean
): Web3JLineaRollupSmartContractClient {
return load(
contractAddress,
@@ -82,23 +82,24 @@ class Web3JLineaRollupSmartContractClient internal constructor(
// chainId will default -1, which will create legacy transactions
AsyncFriendlyTransactionManager(web3j, credentials),
contractGasProvider,
smartContractErrors
smartContractErrors,
useEthEstimateGas
)
}
}
override fun currentNonce(): ULong {
return asyncTransactionManager.currentNonce().toULong()
return transactionManager.currentNonce().toULong()
}
private fun resetNonce(blockNumber: BigInteger?): SafeFuture<ULong> {
return asyncTransactionManager
return transactionManager
.resetNonce(blockNumber)
.thenApply { currentNonce() }
}
override fun updateNonceAndReferenceBlockToLastL1Block(): SafeFuture<BlockAndNonce> {
return helper.getCurrentBlock()
return web3jContractHelper.getCurrentBlock()
.thenCompose { blockNumber ->
web3jLineaClient.setDefaultBlockParameter(DefaultBlockParameter.valueOf(blockNumber))
resetNonce(blockNumber)
@@ -117,7 +118,7 @@ class Web3JLineaRollupSmartContractClient internal constructor(
return getVersion()
.thenCompose { version ->
val function = buildSubmitBlobsFunction(version, blobs)
helper.sendBlobCarryingTransactionAndGetTxHash(
web3jContractHelper.sendBlobCarryingTransactionAndGetTxHash(
function = function,
blobs = blobs.map { it.blobCompressionProof!!.compressedData },
gasPriceCaps = gasPriceCaps
@@ -132,19 +133,13 @@ class Web3JLineaRollupSmartContractClient internal constructor(
return getVersion()
.thenCompose { version ->
val function = buildSubmitBlobsFunction(version, blobs)
val transaction = helper.createEip4844Transaction(
function,
blobs.map { it.blobCompressionProof!!.compressedData }.toWeb3JTxBlob(),
gasPriceCaps
)
web3j.informativeEthCall(transaction, smartContractErrors)
web3jContractHelper.executeBlobEthCall(function, blobs, gasPriceCaps)
}
}
override fun finalizeBlocks(
aggregation: ProofToFinalize,
aggregationLastBlob: BlobRecord,
parentShnarf: ByteArray,
parentL1RollingHash: ByteArray,
parentL1RollingHashMessageNumber: Long,
gasPriceCaps: GasPriceCaps?
@@ -158,7 +153,7 @@ class Web3JLineaRollupSmartContractClient internal constructor(
parentL1RollingHash,
parentL1RollingHashMessageNumber
)
helper.sendTransactionAsync(function, BigInteger.ZERO, gasPriceCaps)
web3jContractHelper.sendTransactionAsync(function, BigInteger.ZERO, gasPriceCaps)
.thenApply { result ->
throwExceptionIfJsonRpcErrorReturned("eth_sendRawTransaction", result)
result.transactionHash
@@ -169,7 +164,6 @@ class Web3JLineaRollupSmartContractClient internal constructor(
override fun finalizeBlocksEthCall(
aggregation: ProofToFinalize,
aggregationLastBlob: BlobRecord,
parentShnarf: ByteArray,
parentL1RollingHash: ByteArray,
parentL1RollingHashMessageNumber: Long
): SafeFuture<String?> {
@@ -182,7 +176,31 @@ class Web3JLineaRollupSmartContractClient internal constructor(
parentL1RollingHash,
parentL1RollingHashMessageNumber
)
helper.executeEthCall(function)
web3jContractHelper.executeEthCall(function)
}
}
override fun finalizeBlocksAfterEthCall(
aggregation: ProofToFinalize,
aggregationLastBlob: BlobRecord,
parentL1RollingHash: ByteArray,
parentL1RollingHashMessageNumber: Long,
gasPriceCaps: GasPriceCaps?
): SafeFuture<String> {
return getVersion()
.thenCompose { version ->
val function = buildFinalizeBlocksFunction(
version,
aggregation,
aggregationLastBlob,
parentL1RollingHash,
parentL1RollingHashMessageNumber
)
web3jContractHelper.sendTransactionAfterEthCallAsync(function, BigInteger.ZERO, gasPriceCaps)
.thenApply { result ->
throwExceptionIfJsonRpcErrorReturned("eth_sendRawTransaction", result)
result.transactionHash
}
}
}
}

View File

@@ -32,7 +32,6 @@ interface LineaRollupSmartContractClient : LineaRollupSmartContractClientReadOnl
fun finalizeBlocksEthCall(
aggregation: ProofToFinalize,
aggregationLastBlob: BlobRecord,
parentShnarf: ByteArray,
parentL1RollingHash: ByteArray,
parentL1RollingHashMessageNumber: Long
): SafeFuture<String?>
@@ -48,7 +47,14 @@ interface LineaRollupSmartContractClient : LineaRollupSmartContractClientReadOnl
fun finalizeBlocks(
aggregation: ProofToFinalize,
aggregationLastBlob: BlobRecord,
parentShnarf: ByteArray,
parentL1RollingHash: ByteArray,
parentL1RollingHashMessageNumber: Long,
gasPriceCaps: GasPriceCaps?
): SafeFuture<String>
fun finalizeBlocksAfterEthCall(
aggregation: ProofToFinalize,
aggregationLastBlob: BlobRecord,
parentL1RollingHash: ByteArray,
parentL1RollingHashMessageNumber: Long,
gasPriceCaps: GasPriceCaps?

View File

@@ -8,6 +8,7 @@ import net.consensys.zkevm.domain.ProofToFinalize
import net.consensys.zkevm.ethereum.gaspricing.GasPriceCapProvider
import net.consensys.zkevm.ethereum.submission.logSubmissionError
import org.apache.logging.log4j.LogManager
import org.web3j.tx.exceptions.ContractCallException
import tech.pegasys.teku.infrastructure.async.SafeFuture
import java.util.function.Consumer
@@ -42,74 +43,45 @@ class AggregationSubmitterImpl(
parentL1RollingHash: ByteArray,
parentL1RollingHashMessageNumber: Long
): SafeFuture<String?> {
log.debug(
"eth_call submitting aggregation={}",
aggregationProof.intervalString()
)
return lineaRollup.finalizeBlocksEthCall(
aggregation = aggregationProof,
aggregationLastBlob = aggregationEndBlob,
parentShnarf = parentShnarf,
parentL1RollingHash = parentL1RollingHash,
parentL1RollingHashMessageNumber = parentL1RollingHashMessageNumber
)
.whenException { th -> logAggregationSubmissionError(aggregationProof.intervalString(), th, isEthCall = true) }
.thenPeek { result ->
log.debug("eth_call valid aggregation={} result={}", aggregationProof.intervalString(), result)
}
.thenApply { true }
.exceptionally { false }
.thenCompose { isEthCallSuccessful ->
if (!isEthCallSuccessful) {
SafeFuture.completedFuture(null)
} else {
val nonce = lineaRollup.currentNonce()
(
gasPriceCapProvider?.getGasPriceCaps(aggregationProof.firstBlockNumber)
?: SafeFuture.completedFuture(null)
).thenCompose { gasPriceCaps ->
log.debug(
"submitting aggregation={} nonce={} gasPriceCaps={}",
aggregationProof.intervalString(),
nonce,
gasPriceCaps
)
lineaRollup.finalizeBlocks(
aggregation = aggregationProof,
aggregationLastBlob = aggregationEndBlob,
parentShnarf = parentShnarf,
parentL1RollingHash = parentL1RollingHash,
parentL1RollingHashMessageNumber = parentL1RollingHashMessageNumber,
gasPriceCaps = gasPriceCaps
)
.whenException { th -> logAggregationSubmissionError(aggregationProof.intervalString(), th) }
.thenPeek { transactionHash ->
log.info(
"submitted aggregation={} transactionHash={} nonce={} gasPriceCaps={}",
aggregationProof.intervalString(),
transactionHash,
nonce,
gasPriceCaps
)
val aggregationSubmittedEvent = FinalizationSubmittedEvent(
aggregationProof = aggregationProof,
parentShnarf = parentShnarf,
parentL1RollingHash = parentL1RollingHash,
parentL1RollingHashMessageNumber = parentL1RollingHashMessageNumber,
submissionTimestamp = clock.now(),
transactionHash = transactionHash.toByteArray()
)
aggregationSubmittedEventConsumer.accept(aggregationSubmittedEvent)
}
}
}
log.debug("submitting aggregation={}", aggregationProof.intervalString())
return (
gasPriceCapProvider?.getGasPriceCaps(aggregationProof.firstBlockNumber)
?: SafeFuture.completedFuture(null)
).thenCompose { gasPriceCaps ->
val nonce = lineaRollup.currentNonce()
lineaRollup.finalizeBlocksAfterEthCall(
aggregation = aggregationProof,
aggregationLastBlob = aggregationEndBlob,
parentL1RollingHash = parentL1RollingHash,
parentL1RollingHashMessageNumber = parentL1RollingHashMessageNumber,
gasPriceCaps = gasPriceCaps
).thenPeek { transactionHash ->
log.info(
"submitted aggregation={} transactionHash={} nonce={} gasPriceCaps={}",
aggregationProof.intervalString(),
transactionHash,
nonce,
gasPriceCaps
)
val aggregationSubmittedEvent = FinalizationSubmittedEvent(
aggregationProof = aggregationProof,
parentShnarf = parentShnarf,
parentL1RollingHash = parentL1RollingHash,
parentL1RollingHashMessageNumber = parentL1RollingHashMessageNumber,
submissionTimestamp = clock.now(),
transactionHash = transactionHash.toByteArray()
)
aggregationSubmittedEventConsumer.accept(aggregationSubmittedEvent)
}
}.whenException { th ->
logAggregationSubmissionError(aggregationProof.intervalString(), th)
}
}
private fun logAggregationSubmissionError(
intervalString: String,
error: Throwable,
isEthCall: Boolean = false
isEthCall: Boolean = error.cause is ContractCallException
) {
logSubmissionError(
log,

View File

@@ -60,7 +60,7 @@ plugin-linea-tx-pool-simulation-check-api-enabled=false
plugin-linea-tx-pool-simulation-check-p2p-enabled=false
plugin-linea-max-block-calldata-size=109000
plugin-linea-max-tx-calldata-size=60000
plugin-linea-max-block-gas=50000000
plugin-linea-max-block-gas=55000000
plugin-linea-tx-pool-min-margin="0.8"
plugin-linea-min-margin="1.0"
plugin-linea-fixed-gas-cost-wei=30000000

View File

@@ -130,7 +130,6 @@ fun submitBlobsAndAggregationsAndWaitExecution(
val txHash = contractClientForAggregationSubmission.finalizeBlocks(
aggregation = aggregation.aggregationProof!!,
aggregationLastBlob = aggBlobs.last(),
parentShnarf = aggBlobs.first().blobCompressionProof!!.prevShnarf,
parentL1RollingHash = parentAgg?.aggregationProof?.l1RollingHash ?: ByteArray(32),
parentL1RollingHashMessageNumber = parentAgg?.aggregationProof?.l1RollingHashMessageNumber ?: 0L,
gasPriceCaps = null

View File

@@ -25,7 +25,7 @@ class Eip4844Transaction(
chainId: Long?,
maxPriorityFeePerGas: BigInteger?,
maxFeePerGas: BigInteger?,
_maxFeePerBlobGas: BigInteger,
_maxFeePerBlobGas: BigInteger?,
@JsonProperty("blobs")
@JsonSerialize(contentUsing = BlobSerializer::class)
val blobs: List<Blob>,
@@ -35,7 +35,7 @@ class Eip4844Transaction(
val blobVersionedHashes: List<Bytes> = computeVersionedHashesFromBlobs(blobs)
) : Transaction(from, nonce, gasPrice, gasLimit, to, value, data, chainId, maxPriorityFeePerGas, maxFeePerGas) {
@Suppress("Unused")
val maxFeePerBlobGas: String = Numeric.encodeQuantity(_maxFeePerBlobGas)
val maxFeePerBlobGas: String? = _maxFeePerBlobGas?.let { Numeric.encodeQuantity(it) }
companion object {
fun computeVersionedHashesFromBlobs(blobs: List<Blob>): List<Bytes> {
return blobs.map(BlobUtils::getCommitment).map(BlobUtils::kzgToVersionedHash)
@@ -46,7 +46,7 @@ class Eip4844Transaction(
to: String,
data: String,
blobs: List<Blob>,
maxFeePerBlobGas: BigInteger,
maxFeePerBlobGas: BigInteger? = null,
gasLimit: BigInteger?,
blobVersionedHashes: List<Bytes> = computeVersionedHashesFromBlobs(blobs),
maxPriorityFeePerGas: BigInteger? = null,

View File

@@ -2,6 +2,7 @@ package net.consensys.linea.web3j
import org.web3j.protocol.Web3j
import org.web3j.protocol.core.DefaultBlockParameterName
import org.web3j.protocol.core.Response
import org.web3j.protocol.core.methods.request.Transaction
import org.web3j.protocol.core.methods.response.EthCall
import org.web3j.tx.TransactionManager
@@ -10,18 +11,25 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture
typealias SmartContractErrors = Map<String, String>
private fun getErrorMessage(
ethCall: EthCall,
fun getRevertReason(
error: Response.Error?,
smartContractErrors: SmartContractErrors
): String {
val errorDataString = ethCall.error?.data ?: ""
val revertReason = if (errorDataString.length > 11) {
): String? {
val errorDataString = error?.data ?: ""
return if (errorDataString.length > 11) {
// execution client can return empty data: "0x", so we need to check the length
val revertId = errorDataString.substring(3, 11).lowercase()
smartContractErrors[revertId]
} else {
"UNKNOWN"
}
}
private fun getErrorMessage(
ethCall: EthCall,
smartContractErrors: SmartContractErrors
): String {
val revertReason = getRevertReason(ethCall.error, smartContractErrors)
return String.format(TransactionManager.REVERT_ERR_STR, ethCall.revertReason) +
" revertReason=$revertReason errorData=${ethCall.error?.data}"