mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-08 15:13:50 -05:00
feat: add calldata based pricing for variable cost (#1189)
* feat: add calldata based pricing for variable cost * feat: revised log and removed comment * feat: revised tests and checks beased on PR reviews
This commit is contained in:
@@ -116,8 +116,6 @@ failures-warning-threshold = 2
|
|||||||
l1-polling-interval = "PT1S"
|
l1-polling-interval = "PT1S"
|
||||||
l1-query-block-tag="LATEST"
|
l1-query-block-tag="LATEST"
|
||||||
|
|
||||||
[l1-submission]
|
|
||||||
disabled = true
|
|
||||||
[l1-submission.dynamic-gas-price-cap]
|
[l1-submission.dynamic-gas-price-cap]
|
||||||
disabled = false
|
disabled = false
|
||||||
[l1-submission.dynamic-gas-price-cap.gas-price-cap-calculation]
|
[l1-submission.dynamic-gas-price-cap.gas-price-cap-calculation]
|
||||||
@@ -265,6 +263,11 @@ blob-submission-expected-execution-gas = 213000
|
|||||||
variable-cost-upper-bound = 10000000001 # ~10 GWEI
|
variable-cost-upper-bound = 10000000001 # ~10 GWEI
|
||||||
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
|
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
|
||||||
margin = 4.0
|
margin = 4.0
|
||||||
|
[l2-network-gas-pricing.dynamic-gas-pricing.calldata-based-pricing]
|
||||||
|
calldata-sum-size-block-count = 5 # disabled if zero
|
||||||
|
fee-change-denominator = 32
|
||||||
|
calldata-sum-size-target = 109000
|
||||||
|
block-size-non-calldata-overhead = 540
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
hostname = "postgres"
|
hostname = "postgres"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ data class L2NetworkGasPricingConfig(
|
|||||||
val extraDataUpdateEndpoint: URL,
|
val extraDataUpdateEndpoint: URL,
|
||||||
val extraDataUpdateRequestRetries: RetryConfig,
|
val extraDataUpdateRequestRetries: RetryConfig,
|
||||||
val l1Endpoint: URL,
|
val l1Endpoint: URL,
|
||||||
|
val l2Endpoint: URL,
|
||||||
) : FeatureToggle {
|
) : FeatureToggle {
|
||||||
data class DynamicGasPricing(
|
data class DynamicGasPricing(
|
||||||
val l1BlobGas: ULong,
|
val l1BlobGas: ULong,
|
||||||
@@ -22,6 +23,14 @@ data class L2NetworkGasPricingConfig(
|
|||||||
val variableCostUpperBound: ULong,
|
val variableCostUpperBound: ULong,
|
||||||
val variableCostLowerBound: ULong,
|
val variableCostLowerBound: ULong,
|
||||||
val margin: Double,
|
val margin: Double,
|
||||||
|
val calldataBasedPricing: CalldataBasedPricing?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CalldataBasedPricing(
|
||||||
|
val calldataSumSizeBlockCount: UInt = 5u,
|
||||||
|
val feeChangeDenominator: UInt = 32u,
|
||||||
|
val calldataSumSizeTarget: ULong = 109000uL,
|
||||||
|
val blockSizeNonCalldataOverhead: UInt = 540u,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class FlatRateGasPricing(
|
data class FlatRateGasPricing(
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ data class CoordinatorConfigToml(
|
|||||||
),
|
),
|
||||||
l2NetworkGasPricing = this.configs.l2NetworkGasPricing.reified(
|
l2NetworkGasPricing = this.configs.l2NetworkGasPricing.reified(
|
||||||
l1DefaultEndpoint = this.configs.defaults.l1Endpoint,
|
l1DefaultEndpoint = this.configs.defaults.l1Endpoint,
|
||||||
|
l2DefaultEndpoint = this.configs.defaults.l2Endpoint,
|
||||||
),
|
),
|
||||||
database = this.configs.database.reified(),
|
database = this.configs.database.reified(),
|
||||||
api = this.configs.api.reified(),
|
api = this.configs.api.reified(),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
data class L2NetworkGasPricingConfigToml(
|
data class L2NetworkGasPricingConfigToml(
|
||||||
val disabled: Boolean = false,
|
val disabled: Boolean = false,
|
||||||
val l1Endpoint: URL? = null,
|
val l1Endpoint: URL? = null,
|
||||||
|
val l2Endpoint: URL? = null,
|
||||||
val priceUpdateInterval: Duration = 12.seconds,
|
val priceUpdateInterval: Duration = 12.seconds,
|
||||||
val feeHistoryBlockCount: UInt = 1000u,
|
val feeHistoryBlockCount: UInt = 1000u,
|
||||||
val feeHistoryRewardPercentile: UInt = 15u,
|
val feeHistoryRewardPercentile: UInt = 15u,
|
||||||
@@ -34,6 +35,7 @@ data class L2NetworkGasPricingConfigToml(
|
|||||||
val variableCostUpperBound: ULong,
|
val variableCostUpperBound: ULong,
|
||||||
val variableCostLowerBound: ULong,
|
val variableCostLowerBound: ULong,
|
||||||
val margin: Double,
|
val margin: Double,
|
||||||
|
val calldataBasedPricing: CalldataBasedPricingToml? = null,
|
||||||
) {
|
) {
|
||||||
fun reified(): L2NetworkGasPricingConfig.DynamicGasPricing {
|
fun reified(): L2NetworkGasPricingConfig.DynamicGasPricing {
|
||||||
return L2NetworkGasPricingConfig.DynamicGasPricing(
|
return L2NetworkGasPricingConfig.DynamicGasPricing(
|
||||||
@@ -42,6 +44,23 @@ data class L2NetworkGasPricingConfigToml(
|
|||||||
variableCostUpperBound = this.variableCostUpperBound,
|
variableCostUpperBound = this.variableCostUpperBound,
|
||||||
variableCostLowerBound = this.variableCostLowerBound,
|
variableCostLowerBound = this.variableCostLowerBound,
|
||||||
margin = this.margin,
|
margin = this.margin,
|
||||||
|
calldataBasedPricing = this.calldataBasedPricing?.reified(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CalldataBasedPricingToml(
|
||||||
|
val calldataSumSizeBlockCount: UInt = 5U,
|
||||||
|
val feeChangeDenominator: UInt = 32U,
|
||||||
|
val calldataSumSizeTarget: ULong = 109000UL,
|
||||||
|
val blockSizeNonCalldataOverhead: UInt = 540U,
|
||||||
|
) {
|
||||||
|
fun reified(): L2NetworkGasPricingConfig.CalldataBasedPricing {
|
||||||
|
return L2NetworkGasPricingConfig.CalldataBasedPricing(
|
||||||
|
calldataSumSizeBlockCount = this.calldataSumSizeBlockCount,
|
||||||
|
feeChangeDenominator = this.feeChangeDenominator,
|
||||||
|
calldataSumSizeTarget = this.calldataSumSizeTarget,
|
||||||
|
blockSizeNonCalldataOverhead = this.blockSizeNonCalldataOverhead,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +85,7 @@ data class L2NetworkGasPricingConfigToml(
|
|||||||
|
|
||||||
fun reified(
|
fun reified(
|
||||||
l1DefaultEndpoint: URL?,
|
l1DefaultEndpoint: URL?,
|
||||||
|
l2DefaultEndpoint: URL?,
|
||||||
): L2NetworkGasPricingConfig {
|
): L2NetworkGasPricingConfig {
|
||||||
return L2NetworkGasPricingConfig(
|
return L2NetworkGasPricingConfig(
|
||||||
disabled = disabled,
|
disabled = disabled,
|
||||||
@@ -78,6 +98,7 @@ data class L2NetworkGasPricingConfigToml(
|
|||||||
extraDataUpdateEndpoint = this.extraDataUpdateEndpoint,
|
extraDataUpdateEndpoint = this.extraDataUpdateEndpoint,
|
||||||
extraDataUpdateRequestRetries = this.extraDataUpdateRequestRetries.asDomain,
|
extraDataUpdateRequestRetries = this.extraDataUpdateRequestRetries.asDomain,
|
||||||
l1Endpoint = this.l1Endpoint ?: l1DefaultEndpoint ?: throw AssertionError("l1Endpoint must be set"),
|
l1Endpoint = this.l1Endpoint ?: l1DefaultEndpoint ?: throw AssertionError("l1Endpoint must be set"),
|
||||||
|
l2Endpoint = this.l2Endpoint ?: l2DefaultEndpoint ?: throw AssertionError("l2Endpoint must be set"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import linea.contract.l1.LineaRollupSmartContractClientReadOnly
|
|||||||
import linea.contract.l1.Web3JLineaRollupSmartContractClientReadOnly
|
import linea.contract.l1.Web3JLineaRollupSmartContractClientReadOnly
|
||||||
import linea.coordinator.config.toJsonRpcRetry
|
import linea.coordinator.config.toJsonRpcRetry
|
||||||
import linea.coordinator.config.v2.CoordinatorConfig
|
import linea.coordinator.config.v2.CoordinatorConfig
|
||||||
|
import linea.coordinator.config.v2.Type2StateProofManagerConfig
|
||||||
import linea.coordinator.config.v2.isDisabled
|
import linea.coordinator.config.v2.isDisabled
|
||||||
import linea.coordinator.config.v2.isEnabled
|
import linea.coordinator.config.v2.isEnabled
|
||||||
import linea.domain.BlockNumberAndHash
|
import linea.domain.BlockNumberAndHash
|
||||||
@@ -32,6 +33,8 @@ import net.consensys.linea.ethereum.gaspricing.dynamiccap.GasPriceCapProviderFor
|
|||||||
import net.consensys.linea.ethereum.gaspricing.dynamiccap.GasPriceCapProviderImpl
|
import net.consensys.linea.ethereum.gaspricing.dynamiccap.GasPriceCapProviderImpl
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.ExtraDataV1UpdaterImpl
|
import net.consensys.linea.ethereum.gaspricing.staticcap.ExtraDataV1UpdaterImpl
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.FeeHistoryFetcherImpl
|
import net.consensys.linea.ethereum.gaspricing.staticcap.FeeHistoryFetcherImpl
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.staticcap.L2CalldataBasedVariableFeesCalculator
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.staticcap.L2CalldataSizeAccumulatorImpl
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.MinerExtraDataV1CalculatorImpl
|
import net.consensys.linea.ethereum.gaspricing.staticcap.MinerExtraDataV1CalculatorImpl
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.TransactionCostCalculator
|
import net.consensys.linea.ethereum.gaspricing.staticcap.TransactionCostCalculator
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.VariableFeesCalculator
|
import net.consensys.linea.ethereum.gaspricing.staticcap.VariableFeesCalculator
|
||||||
@@ -543,11 +546,29 @@ class L1DependentApp(
|
|||||||
sequencerEndpoint = configs.l2NetworkGasPricing.extraDataUpdateEndpoint,
|
sequencerEndpoint = configs.l2NetworkGasPricing.extraDataUpdateEndpoint,
|
||||||
retryConfig = configs.l2NetworkGasPricing.extraDataUpdateRequestRetries.toJsonRpcRetry(),
|
retryConfig = configs.l2NetworkGasPricing.extraDataUpdateRequestRetries.toJsonRpcRetry(),
|
||||||
),
|
),
|
||||||
|
l2CalldataSizeAccumulatorConfig = configs.l2NetworkGasPricing.dynamicGasPricing.calldataBasedPricing?.let {
|
||||||
|
L2CalldataSizeAccumulatorImpl.Config(
|
||||||
|
blockSizeNonCalldataOverhead = it.blockSizeNonCalldataOverhead,
|
||||||
|
calldataSizeBlockCount = it.calldataSumSizeBlockCount,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
l2CalldataBasedVariableFeesCalculatorConfig =
|
||||||
|
configs.l2NetworkGasPricing.dynamicGasPricing.calldataBasedPricing?.let {
|
||||||
|
L2CalldataBasedVariableFeesCalculator.Config(
|
||||||
|
feeChangeDenominator = it.feeChangeDenominator,
|
||||||
|
calldataSizeBlockCount = it.calldataSumSizeBlockCount,
|
||||||
|
maxBlockCalldataSize = it.calldataSumSizeTarget.toUInt(),
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val l1Web3jClient = createWeb3jHttpClient(
|
val l1Web3jClient = createWeb3jHttpClient(
|
||||||
rpcUrl = configs.l2NetworkGasPricing.l1Endpoint.toString(),
|
rpcUrl = configs.l2NetworkGasPricing.l1Endpoint.toString(),
|
||||||
log = LogManager.getLogger("clients.l1.eth.l2pricing"),
|
log = LogManager.getLogger("clients.l1.eth.l2pricing"),
|
||||||
)
|
)
|
||||||
|
val l2Web3jClient = createWeb3jHttpClient(
|
||||||
|
rpcUrl = configs.l2NetworkGasPricing.l2Endpoint.toString(),
|
||||||
|
log = LogManager.getLogger("clients.l2.eth.l2pricing"),
|
||||||
|
)
|
||||||
L2NetworkGasPricingService(
|
L2NetworkGasPricingService(
|
||||||
vertx = vertx,
|
vertx = vertx,
|
||||||
httpJsonRpcClientFactory = httpJsonRpcClientFactory,
|
httpJsonRpcClientFactory = httpJsonRpcClientFactory,
|
||||||
@@ -558,6 +579,7 @@ class L1DependentApp(
|
|||||||
log = LogManager.getLogger("clients.l1.eth.l2pricing"),
|
log = LogManager.getLogger("clients.l1.eth.l2pricing"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
l2Web3jClient = ExtendedWeb3JImpl(l2Web3jClient),
|
||||||
config = config,
|
config = config,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -674,7 +696,7 @@ class L1DependentApp(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun setupL1FinalizationMonitorForShomeiFrontend(
|
fun setupL1FinalizationMonitorForShomeiFrontend(
|
||||||
type2StateProofProviderConfig: linea.coordinator.config.v2.Type2StateProofManagerConfig,
|
type2StateProofProviderConfig: Type2StateProofManagerConfig,
|
||||||
httpJsonRpcClientFactory: VertxHttpJsonRpcClientFactory,
|
httpJsonRpcClientFactory: VertxHttpJsonRpcClientFactory,
|
||||||
lineaRollupClient: LineaRollupSmartContractClientReadOnly,
|
lineaRollupClient: LineaRollupSmartContractClientReadOnly,
|
||||||
l2Web3jClient: Web3j,
|
l2Web3jClient: Web3j,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package net.consensys.zkevm.coordinator.app
|
package net.consensys.zkevm.coordinator.app
|
||||||
|
|
||||||
import io.vertx.core.Vertx
|
import io.vertx.core.Vertx
|
||||||
|
import linea.web3j.ExtendedWeb3J
|
||||||
import linea.web3j.Web3jBlobExtended
|
import linea.web3j.Web3jBlobExtended
|
||||||
import net.consensys.linea.ethereum.gaspricing.BoundableFeeCalculator
|
import net.consensys.linea.ethereum.gaspricing.BoundableFeeCalculator
|
||||||
import net.consensys.linea.ethereum.gaspricing.FeesCalculator
|
import net.consensys.linea.ethereum.gaspricing.FeesCalculator
|
||||||
@@ -11,6 +12,8 @@ import net.consensys.linea.ethereum.gaspricing.staticcap.ExtraDataV1UpdaterImpl
|
|||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.FeeHistoryFetcherImpl
|
import net.consensys.linea.ethereum.gaspricing.staticcap.FeeHistoryFetcherImpl
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.GasPriceUpdaterImpl
|
import net.consensys.linea.ethereum.gaspricing.staticcap.GasPriceUpdaterImpl
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.GasUsageRatioWeightedAverageFeesCalculator
|
import net.consensys.linea.ethereum.gaspricing.staticcap.GasUsageRatioWeightedAverageFeesCalculator
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.staticcap.L2CalldataBasedVariableFeesCalculator
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.staticcap.L2CalldataSizeAccumulatorImpl
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.MinMineableFeesPricerService
|
import net.consensys.linea.ethereum.gaspricing.staticcap.MinMineableFeesPricerService
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.MinerExtraDataV1CalculatorImpl
|
import net.consensys.linea.ethereum.gaspricing.staticcap.MinerExtraDataV1CalculatorImpl
|
||||||
import net.consensys.linea.ethereum.gaspricing.staticcap.TransactionCostCalculator
|
import net.consensys.linea.ethereum.gaspricing.staticcap.TransactionCostCalculator
|
||||||
@@ -28,6 +31,7 @@ class L2NetworkGasPricingService(
|
|||||||
httpJsonRpcClientFactory: VertxHttpJsonRpcClientFactory,
|
httpJsonRpcClientFactory: VertxHttpJsonRpcClientFactory,
|
||||||
l1Web3jClient: Web3j,
|
l1Web3jClient: Web3j,
|
||||||
l1Web3jService: Web3jBlobExtended,
|
l1Web3jService: Web3jBlobExtended,
|
||||||
|
l2Web3jClient: ExtendedWeb3J,
|
||||||
config: Config,
|
config: Config,
|
||||||
) : LongRunningService {
|
) : LongRunningService {
|
||||||
data class LegacyGasPricingCalculatorConfig(
|
data class LegacyGasPricingCalculatorConfig(
|
||||||
@@ -47,6 +51,8 @@ class L2NetworkGasPricingService(
|
|||||||
val variableFeesCalculatorBounds: BoundableFeeCalculator.Config,
|
val variableFeesCalculatorBounds: BoundableFeeCalculator.Config,
|
||||||
val extraDataCalculatorConfig: MinerExtraDataV1CalculatorImpl.Config,
|
val extraDataCalculatorConfig: MinerExtraDataV1CalculatorImpl.Config,
|
||||||
val extraDataUpdaterConfig: ExtraDataV1UpdaterImpl.Config,
|
val extraDataUpdaterConfig: ExtraDataV1UpdaterImpl.Config,
|
||||||
|
val l2CalldataSizeAccumulatorConfig: L2CalldataSizeAccumulatorImpl.Config?,
|
||||||
|
val l2CalldataBasedVariableFeesCalculatorConfig: L2CalldataBasedVariableFeesCalculator.Config?,
|
||||||
)
|
)
|
||||||
private val log = LogManager.getLogger(this::class.java)
|
private val log = LogManager.getLogger(this::class.java)
|
||||||
|
|
||||||
@@ -98,14 +104,31 @@ class L2NetworkGasPricingService(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isL2CalldataBasedVariableFeesEnabled(config: Config): Boolean {
|
||||||
|
return config.l2CalldataBasedVariableFeesCalculatorConfig != null &&
|
||||||
|
config.l2CalldataSizeAccumulatorConfig != null &&
|
||||||
|
config.l2CalldataBasedVariableFeesCalculatorConfig.calldataSizeBlockCount > 0u
|
||||||
|
}
|
||||||
|
|
||||||
private val extraDataPricerService: ExtraDataV1PricerService? = if (config.extraDataPricingPropagationEnabled) {
|
private val extraDataPricerService: ExtraDataV1PricerService? = if (config.extraDataPricingPropagationEnabled) {
|
||||||
ExtraDataV1PricerService(
|
ExtraDataV1PricerService(
|
||||||
pollingInterval = config.extraDataUpdateInterval,
|
pollingInterval = config.extraDataUpdateInterval,
|
||||||
vertx = vertx,
|
vertx = vertx,
|
||||||
feesFetcher = gasPricingFeesFetcher,
|
feesFetcher = gasPricingFeesFetcher,
|
||||||
minerExtraDataCalculatorImpl = MinerExtraDataV1CalculatorImpl(
|
minerExtraDataCalculator = MinerExtraDataV1CalculatorImpl(
|
||||||
config = config.extraDataCalculatorConfig,
|
config = config.extraDataCalculatorConfig,
|
||||||
variableFeesCalculator = boundedVariableCostCalculator,
|
variableFeesCalculator = if (isL2CalldataBasedVariableFeesEnabled(config)) {
|
||||||
|
L2CalldataBasedVariableFeesCalculator(
|
||||||
|
variableFeesCalculator = boundedVariableCostCalculator,
|
||||||
|
l2CalldataSizeAccumulator = L2CalldataSizeAccumulatorImpl(
|
||||||
|
web3jClient = l2Web3jClient,
|
||||||
|
config = config.l2CalldataSizeAccumulatorConfig!!,
|
||||||
|
),
|
||||||
|
config = config.l2CalldataBasedVariableFeesCalculatorConfig!!,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
boundedVariableCostCalculator
|
||||||
|
},
|
||||||
legacyFeesCalculator = legacyGasPricingCalculator,
|
legacyFeesCalculator = legacyGasPricingCalculator,
|
||||||
),
|
),
|
||||||
extraDataUpdater = ExtraDataV1UpdaterImpl(
|
extraDataUpdater = ExtraDataV1UpdaterImpl(
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ class BlockCreationMonitor(
|
|||||||
val blockNumber = _nexBlockNumberToFetch.get()
|
val blockNumber = _nexBlockNumberToFetch.get()
|
||||||
web3j.ethGetBlock(blockNumber.toBlockParameter())
|
web3j.ethGetBlock(blockNumber.toBlockParameter())
|
||||||
.thenPeek { block ->
|
.thenPeek { block ->
|
||||||
log.trace("requestedBock={} responselock={}", blockNumber, block?.number)
|
log.trace("requestedBlock={} responseBlock={}", blockNumber, block?.number)
|
||||||
}
|
}
|
||||||
.whenException {
|
.whenException {
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ class L2NetWorkingGasPricingConfigParsingTest {
|
|||||||
variable-cost-upper-bound = 10000000001 # ~10 GWEI
|
variable-cost-upper-bound = 10000000001 # ~10 GWEI
|
||||||
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
|
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
|
||||||
margin = 4.0
|
margin = 4.0
|
||||||
|
[l2-network-gas-pricing.dynamic-gas-pricing.calldata-based-pricing]
|
||||||
|
calldata-sum-size-block-count = 5
|
||||||
|
fee-change-denominator = 32
|
||||||
|
calldata-sum-size-target = 109000
|
||||||
|
block-size-non-calldata-overhead = 540
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val config = L2NetworkGasPricingConfigToml(
|
val config = L2NetworkGasPricingConfigToml(
|
||||||
@@ -59,6 +64,12 @@ class L2NetWorkingGasPricingConfigParsingTest {
|
|||||||
variableCostUpperBound = 10_000_000_001UL, // ~10 GWEI
|
variableCostUpperBound = 10_000_000_001UL, // ~10 GWEI
|
||||||
variableCostLowerBound = 90_000_001UL, // ~0.09 GWEI
|
variableCostLowerBound = 90_000_001UL, // ~0.09 GWEI
|
||||||
margin = 4.0,
|
margin = 4.0,
|
||||||
|
calldataBasedPricing = L2NetworkGasPricingConfigToml.CalldataBasedPricingToml(
|
||||||
|
calldataSumSizeBlockCount = 5u,
|
||||||
|
feeChangeDenominator = 32u,
|
||||||
|
calldataSumSizeTarget = 109000uL,
|
||||||
|
blockSizeNonCalldataOverhead = 540u,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
flatRateGasPricing = L2NetworkGasPricingConfigToml.FlatRateGasPricingToml(
|
flatRateGasPricing = L2NetworkGasPricingConfigToml.FlatRateGasPricingToml(
|
||||||
gasPriceUpperBound = 10_000_000_000UL, // 10 GWEI
|
gasPriceUpperBound = 10_000_000_000UL, // 10 GWEI
|
||||||
|
|||||||
@@ -4,12 +4,17 @@ import linea.domain.FeeHistory
|
|||||||
import linea.kotlin.decodeHex
|
import linea.kotlin.decodeHex
|
||||||
import linea.kotlin.encodeHex
|
import linea.kotlin.encodeHex
|
||||||
import tech.pegasys.teku.infrastructure.async.SafeFuture
|
import tech.pegasys.teku.infrastructure.async.SafeFuture
|
||||||
|
import java.math.BigInteger
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
interface FeesFetcher {
|
interface FeesFetcher {
|
||||||
fun getL1EthGasPriceData(): SafeFuture<FeeHistory>
|
fun getL1EthGasPriceData(): SafeFuture<FeeHistory>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface L2CalldataSizeAccumulator {
|
||||||
|
fun getSumOfL2CalldataSize(): SafeFuture<BigInteger>
|
||||||
|
}
|
||||||
|
|
||||||
fun interface FeesCalculator {
|
fun interface FeesCalculator {
|
||||||
fun calculateFees(feeHistory: FeeHistory): Double
|
fun calculateFees(feeHistory: FeeHistory): Double
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import io.vertx.core.Vertx
|
|||||||
import linea.kotlin.toIntervalString
|
import linea.kotlin.toIntervalString
|
||||||
import net.consensys.linea.ethereum.gaspricing.ExtraDataUpdater
|
import net.consensys.linea.ethereum.gaspricing.ExtraDataUpdater
|
||||||
import net.consensys.linea.ethereum.gaspricing.FeesFetcher
|
import net.consensys.linea.ethereum.gaspricing.FeesFetcher
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.MinerExtraDataCalculator
|
||||||
import net.consensys.linea.ethereum.gaspricing.MinerExtraDataV1
|
import net.consensys.linea.ethereum.gaspricing.MinerExtraDataV1
|
||||||
import net.consensys.zkevm.PeriodicPollingService
|
import net.consensys.zkevm.PeriodicPollingService
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
@@ -15,7 +16,7 @@ class ExtraDataV1PricerService(
|
|||||||
pollingInterval: Duration,
|
pollingInterval: Duration,
|
||||||
vertx: Vertx,
|
vertx: Vertx,
|
||||||
private val feesFetcher: FeesFetcher,
|
private val feesFetcher: FeesFetcher,
|
||||||
private val minerExtraDataCalculatorImpl: MinerExtraDataV1CalculatorImpl,
|
private val minerExtraDataCalculator: MinerExtraDataCalculator,
|
||||||
private val extraDataUpdater: ExtraDataUpdater,
|
private val extraDataUpdater: ExtraDataUpdater,
|
||||||
private val log: Logger = LogManager.getLogger(ExtraDataV1PricerService::class.java),
|
private val log: Logger = LogManager.getLogger(ExtraDataV1PricerService::class.java),
|
||||||
) : PeriodicPollingService(
|
) : PeriodicPollingService(
|
||||||
@@ -30,7 +31,7 @@ class ExtraDataV1PricerService(
|
|||||||
.getL1EthGasPriceData()
|
.getL1EthGasPriceData()
|
||||||
.thenCompose { feeHistory ->
|
.thenCompose { feeHistory ->
|
||||||
val blockRange = feeHistory.blocksRange()
|
val blockRange = feeHistory.blocksRange()
|
||||||
val newExtraData = minerExtraDataCalculatorImpl.calculateMinerExtraData(feeHistory)
|
val newExtraData = minerExtraDataCalculator.calculateMinerExtraData(feeHistory)
|
||||||
if (lastExtraData != newExtraData) {
|
if (lastExtraData != newExtraData) {
|
||||||
// this is just to avoid log noise.
|
// this is just to avoid log noise.
|
||||||
lastExtraData = newExtraData
|
lastExtraData = newExtraData
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package net.consensys.linea.ethereum.gaspricing.staticcap
|
||||||
|
|
||||||
|
import linea.domain.FeeHistory
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.FeesCalculator
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.L2CalldataSizeAccumulator
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
/*
|
||||||
|
CALLDATA_BASED_FEE_CHANGE_DENOMINATOR = 32
|
||||||
|
CALLDATA_BASED_FEE_BLOCK_COUNT = 5
|
||||||
|
MAX_BLOCK_CALLDATA_SIZE = 109000
|
||||||
|
|
||||||
|
variable_cost = as documented in VARIABLE_COST (https://docs.linea.build/get-started/how-to/gas-fees#gas-pricing)
|
||||||
|
calldata_target = CALLDATA_BASED_FEE_BLOCK_COUNT * MAX_BLOCK_CALLDATA_SIZE / 2
|
||||||
|
# delta fluctuates between [-1 and 1]
|
||||||
|
delta = (sum(block_size over CALLDATA_BASED_FEE_BLOCK_COUNT) - calldata_target) / calldata_target
|
||||||
|
variable_cost = max(variable_cost, previous_variable_cost * ( 1 + delta / CALLDATA_BASED_FEE_CHANGE_DENOMINATOR )
|
||||||
|
*/
|
||||||
|
class L2CalldataBasedVariableFeesCalculator(
|
||||||
|
val config: Config,
|
||||||
|
val variableFeesCalculator: FeesCalculator,
|
||||||
|
val l2CalldataSizeAccumulator: L2CalldataSizeAccumulator,
|
||||||
|
) : FeesCalculator {
|
||||||
|
data class Config(
|
||||||
|
val feeChangeDenominator: UInt,
|
||||||
|
val calldataSizeBlockCount: UInt,
|
||||||
|
val maxBlockCalldataSize: UInt,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(feeChangeDenominator > 0u) { "feeChangeDenominator=$feeChangeDenominator must be greater than 0" }
|
||||||
|
require(maxBlockCalldataSize > 0u) { "maxBlockCalldataSize=$maxBlockCalldataSize must be greater than 0" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val log = LogManager.getLogger(this::class.java)
|
||||||
|
private var lastVariableCost: AtomicReference<Double> = AtomicReference(0.0)
|
||||||
|
|
||||||
|
override fun calculateFees(feeHistory: FeeHistory): Double {
|
||||||
|
val variableFee = variableFeesCalculator.calculateFees(feeHistory)
|
||||||
|
|
||||||
|
if (config.calldataSizeBlockCount == 0u) {
|
||||||
|
log.debug(
|
||||||
|
"Calldata-based variable fee is disabled as calldataSizeBlockCount is set as 0: variableFee={} wei",
|
||||||
|
variableFee,
|
||||||
|
)
|
||||||
|
return variableFee
|
||||||
|
}
|
||||||
|
|
||||||
|
val callDataTargetSize = config.maxBlockCalldataSize
|
||||||
|
.times(config.calldataSizeBlockCount)
|
||||||
|
.toDouble().div(2.0)
|
||||||
|
|
||||||
|
val delta = (
|
||||||
|
l2CalldataSizeAccumulator
|
||||||
|
.getSumOfL2CalldataSize().get().toDouble()
|
||||||
|
.minus(callDataTargetSize)
|
||||||
|
)
|
||||||
|
.div(callDataTargetSize)
|
||||||
|
.coerceAtLeast(-1.0)
|
||||||
|
.coerceAtMost(1.0)
|
||||||
|
|
||||||
|
val calldataBasedVariableFee =
|
||||||
|
lastVariableCost.get().times(1.0 + (delta.div(config.feeChangeDenominator.toDouble())))
|
||||||
|
|
||||||
|
val finalVariableFee = variableFee.coerceAtLeast(calldataBasedVariableFee)
|
||||||
|
|
||||||
|
lastVariableCost.set(finalVariableFee)
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Calculated calldataBasedVariableFee={} wei variableFee={} wei finalVariableFee={} wei " +
|
||||||
|
"delta={} maxBlockCalldataSize={} calldataSizeBlockCount={} feeChangeDenominator={}",
|
||||||
|
calldataBasedVariableFee,
|
||||||
|
variableFee,
|
||||||
|
finalVariableFee,
|
||||||
|
delta,
|
||||||
|
config.maxBlockCalldataSize,
|
||||||
|
config.calldataSizeBlockCount,
|
||||||
|
config.feeChangeDenominator,
|
||||||
|
)
|
||||||
|
|
||||||
|
return finalVariableFee
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package net.consensys.linea.ethereum.gaspricing.staticcap
|
||||||
|
|
||||||
|
import linea.kotlin.toBigInteger
|
||||||
|
import linea.kotlin.toUInt
|
||||||
|
import linea.web3j.ExtendedWeb3J
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.L2CalldataSizeAccumulator
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import org.apache.logging.log4j.Logger
|
||||||
|
import tech.pegasys.teku.infrastructure.async.SafeFuture
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
|
class L2CalldataSizeAccumulatorImpl(
|
||||||
|
private val web3jClient: ExtendedWeb3J,
|
||||||
|
private val config: Config,
|
||||||
|
) : L2CalldataSizeAccumulator {
|
||||||
|
private val log: Logger = LogManager.getLogger(this::class.java)
|
||||||
|
|
||||||
|
data class Config(
|
||||||
|
val blockSizeNonCalldataOverhead: UInt,
|
||||||
|
val calldataSizeBlockCount: UInt,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(calldataSizeBlockCount <= 60u) {
|
||||||
|
"calldataSizeBlockCount must be less than 60 to avoid excessive " +
|
||||||
|
"eth_getBlockByNumber calls to the web3j client. Value=$calldataSizeBlockCount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun getRecentL2CalldataSize(): SafeFuture<BigInteger> {
|
||||||
|
return web3jClient.ethBlockNumber()
|
||||||
|
.thenCompose { currentBlockNumber ->
|
||||||
|
if (config.calldataSizeBlockCount > 0u && currentBlockNumber.toUInt() >= config.calldataSizeBlockCount) {
|
||||||
|
val futures =
|
||||||
|
((currentBlockNumber.toUInt() - config.calldataSizeBlockCount + 1U)..currentBlockNumber.toUInt())
|
||||||
|
.map { blockNumber ->
|
||||||
|
web3jClient.ethGetBlockSizeByNumber(blockNumber.toLong())
|
||||||
|
}
|
||||||
|
SafeFuture.collectAll(futures.stream())
|
||||||
|
.thenApply { blockSizes ->
|
||||||
|
blockSizes.sumOf {
|
||||||
|
it.minus(config.blockSizeNonCalldataOverhead.toULong().toBigInteger())
|
||||||
|
.coerceAtLeast(BigInteger.ZERO)
|
||||||
|
}.also {
|
||||||
|
log.debug(
|
||||||
|
"sumOfBlockSizes={} blockSizes={} blockSizeNonCalldataOverhead={}",
|
||||||
|
it,
|
||||||
|
blockSizes,
|
||||||
|
config.blockSizeNonCalldataOverhead,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SafeFuture.completedFuture(BigInteger.ZERO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSumOfL2CalldataSize(): SafeFuture<BigInteger> {
|
||||||
|
return getRecentL2CalldataSize()
|
||||||
|
.whenException { th ->
|
||||||
|
log.error(
|
||||||
|
"Get the sum of L2 calldata size from the last {} blocks failure: {}",
|
||||||
|
config.calldataSizeBlockCount,
|
||||||
|
th.message,
|
||||||
|
th,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,7 +82,7 @@ class ExtraDataV1PricerServiceTest {
|
|||||||
pollingInterval = pollingInterval,
|
pollingInterval = pollingInterval,
|
||||||
vertx = vertx,
|
vertx = vertx,
|
||||||
feesFetcher = mockFeesFetcher,
|
feesFetcher = mockFeesFetcher,
|
||||||
minerExtraDataCalculatorImpl = boundableFeeCalculator,
|
minerExtraDataCalculator = boundableFeeCalculator,
|
||||||
extraDataUpdater = mockExtraDataUpdater,
|
extraDataUpdater = mockExtraDataUpdater,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
package net.consensys.linea.ethereum.gaspricing.staticcap
|
||||||
|
|
||||||
|
import linea.domain.FeeHistory
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.FeesCalculator
|
||||||
|
import net.consensys.linea.ethereum.gaspricing.L2CalldataSizeAccumulator
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.eq
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.whenever
|
||||||
|
import tech.pegasys.teku.infrastructure.async.SafeFuture
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
|
class L2CalldataBasedVariableFeesCalculatorTest {
|
||||||
|
private val config = L2CalldataBasedVariableFeesCalculator.Config(
|
||||||
|
feeChangeDenominator = 32u,
|
||||||
|
calldataSizeBlockCount = 5u,
|
||||||
|
maxBlockCalldataSize = 109000u,
|
||||||
|
)
|
||||||
|
private val feeHistory = FeeHistory(
|
||||||
|
oldestBlock = 100uL,
|
||||||
|
baseFeePerGas = listOf(100UL),
|
||||||
|
reward = listOf(listOf(1000UL)),
|
||||||
|
gasUsedRatio = listOf(0.25),
|
||||||
|
baseFeePerBlobGas = listOf(100UL),
|
||||||
|
blobGasUsedRatio = listOf(0.25),
|
||||||
|
)
|
||||||
|
private val variableFee = 15000.0
|
||||||
|
val mockVariableFeesCalculator = mock<FeesCalculator> {
|
||||||
|
on { calculateFees(eq(feeHistory)) } doReturn variableFee
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_calculateFees_past_blocks_calldata_at_max_target() {
|
||||||
|
val sumOfCalldataSize = (109000 * 5).toBigInteger() // maxBlockCalldataSize * calldataSizeBlockCount
|
||||||
|
val mockl2CalldataSizeAccumulator = mock<L2CalldataSizeAccumulator> {
|
||||||
|
on { getSumOfL2CalldataSize() } doReturn SafeFuture.completedFuture(sumOfCalldataSize)
|
||||||
|
}
|
||||||
|
// delta would be 1.0
|
||||||
|
val delta = 1.0
|
||||||
|
val expectedVariableFees = 15000.0 * (1.0 + delta / 32.0)
|
||||||
|
|
||||||
|
val feesCalculator = L2CalldataBasedVariableFeesCalculator(
|
||||||
|
config = config,
|
||||||
|
variableFeesCalculator = mockVariableFeesCalculator,
|
||||||
|
l2CalldataSizeAccumulator = mockl2CalldataSizeAccumulator,
|
||||||
|
)
|
||||||
|
|
||||||
|
// call calculateFees first to instantiate the lastVariableCost
|
||||||
|
feesCalculator.calculateFees(feeHistory)
|
||||||
|
|
||||||
|
assertThat(feesCalculator.calculateFees(feeHistory))
|
||||||
|
.isEqualTo(expectedVariableFees)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_calculateFees_past_blocks_calldata_exceed_max_target() {
|
||||||
|
// This could happen as the calldata from L2CalldataSizeAccumulator is just approximation
|
||||||
|
val sumOfCalldataSize = (200000 * 5).toBigInteger()
|
||||||
|
val mockl2CalldataSizeAccumulator = mock<L2CalldataSizeAccumulator> {
|
||||||
|
on { getSumOfL2CalldataSize() } doReturn SafeFuture.completedFuture(sumOfCalldataSize)
|
||||||
|
}
|
||||||
|
// delta would be 1.0
|
||||||
|
val delta = 1.0
|
||||||
|
val expectedVariableFees = 15000.0 * (1.0 + delta / 32.0)
|
||||||
|
|
||||||
|
val feesCalculator = L2CalldataBasedVariableFeesCalculator(
|
||||||
|
config = config,
|
||||||
|
variableFeesCalculator = mockVariableFeesCalculator,
|
||||||
|
l2CalldataSizeAccumulator = mockl2CalldataSizeAccumulator,
|
||||||
|
)
|
||||||
|
|
||||||
|
// call calculateFees first to instantiate the lastVariableCost
|
||||||
|
feesCalculator.calculateFees(feeHistory)
|
||||||
|
|
||||||
|
assertThat(feesCalculator.calculateFees(feeHistory))
|
||||||
|
.isEqualTo(expectedVariableFees)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_calculateFees_past_blocks_calldata_size_at_zero() {
|
||||||
|
val mockl2CalldataSizeAccumulator = mock<L2CalldataSizeAccumulator> {
|
||||||
|
on { getSumOfL2CalldataSize() } doReturn SafeFuture.completedFuture(BigInteger.ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
val feesCalculator = L2CalldataBasedVariableFeesCalculator(
|
||||||
|
config = config,
|
||||||
|
variableFeesCalculator = mockVariableFeesCalculator,
|
||||||
|
l2CalldataSizeAccumulator = mockl2CalldataSizeAccumulator,
|
||||||
|
)
|
||||||
|
|
||||||
|
// call calculateFees first to instantiate the lastVariableCost
|
||||||
|
feesCalculator.calculateFees(feeHistory)
|
||||||
|
|
||||||
|
assertThat(feesCalculator.calculateFees(feeHistory))
|
||||||
|
.isEqualTo(15000.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_calculateFees_past_blocks_calldata_above_half_max() {
|
||||||
|
val sumOfCalldataSize = (81750 * 5).toBigInteger()
|
||||||
|
val mockl2CalldataSizeAccumulator = mock<L2CalldataSizeAccumulator> {
|
||||||
|
on { getSumOfL2CalldataSize() } doReturn SafeFuture.completedFuture(sumOfCalldataSize)
|
||||||
|
}
|
||||||
|
// delta would be 0.5
|
||||||
|
val delta = 0.5
|
||||||
|
val expectedVariableFees = 15000.0 * (1.0 + delta / 32.0)
|
||||||
|
|
||||||
|
val feesCalculator = L2CalldataBasedVariableFeesCalculator(
|
||||||
|
config = config,
|
||||||
|
variableFeesCalculator = mockVariableFeesCalculator,
|
||||||
|
l2CalldataSizeAccumulator = mockl2CalldataSizeAccumulator,
|
||||||
|
)
|
||||||
|
|
||||||
|
// call calculateFees first to instantiate the lastVariableCost
|
||||||
|
feesCalculator.calculateFees(feeHistory)
|
||||||
|
|
||||||
|
assertThat(feesCalculator.calculateFees(feeHistory))
|
||||||
|
.isEqualTo(expectedVariableFees)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_calculateFees_past_blocks_calldata_below_half_max() {
|
||||||
|
val sumOfCalldataSize = (27250 * 5).toBigInteger()
|
||||||
|
val mockl2CalldataSizeAccumulator = mock<L2CalldataSizeAccumulator> {
|
||||||
|
on { getSumOfL2CalldataSize() } doReturn SafeFuture.completedFuture(sumOfCalldataSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val feesCalculator = L2CalldataBasedVariableFeesCalculator(
|
||||||
|
config = config,
|
||||||
|
variableFeesCalculator = mockVariableFeesCalculator,
|
||||||
|
l2CalldataSizeAccumulator = mockl2CalldataSizeAccumulator,
|
||||||
|
)
|
||||||
|
|
||||||
|
// call calculateFees first to instantiate the lastVariableCost
|
||||||
|
feesCalculator.calculateFees(feeHistory)
|
||||||
|
|
||||||
|
assertThat(feesCalculator.calculateFees(feeHistory))
|
||||||
|
.isEqualTo(15000.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_calculateFees_increase_to_more_than_double_when_past_blocks_calldata_at_max_target() {
|
||||||
|
val sumOfCalldataSize = (109000 * 5).toBigInteger() // maxBlockCalldataSize * calldataSizeBlockCount
|
||||||
|
val mockl2CalldataSizeAccumulator = mock<L2CalldataSizeAccumulator> {
|
||||||
|
on { getSumOfL2CalldataSize() } doReturn SafeFuture.completedFuture(sumOfCalldataSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val feesCalculator = L2CalldataBasedVariableFeesCalculator(
|
||||||
|
config = config,
|
||||||
|
variableFeesCalculator = mockVariableFeesCalculator,
|
||||||
|
l2CalldataSizeAccumulator = mockl2CalldataSizeAccumulator,
|
||||||
|
)
|
||||||
|
|
||||||
|
// call calculateFees first to instantiate the lastVariableCost
|
||||||
|
feesCalculator.calculateFees(feeHistory)
|
||||||
|
|
||||||
|
// With (1 + 1/32) as the rate, after 23 calls
|
||||||
|
// the expectedVariableFees should be increased to more than double
|
||||||
|
var calculatedFee = 0.0
|
||||||
|
(0..22).forEach { _ ->
|
||||||
|
calculatedFee = feesCalculator.calculateFees(feeHistory)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(calculatedFee).isGreaterThan(15000.0 * 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_calculateFees_decrease_to_less_than_half_when_past_blocks_calldata_at_zero() {
|
||||||
|
val mockVariableFeesCalculator = mock<FeesCalculator>()
|
||||||
|
whenever(mockVariableFeesCalculator.calculateFees(eq(feeHistory)))
|
||||||
|
.thenReturn(variableFee, 0.0)
|
||||||
|
|
||||||
|
val mockl2CalldataSizeAccumulator = mock<L2CalldataSizeAccumulator> {
|
||||||
|
on { getSumOfL2CalldataSize() } doReturn SafeFuture.completedFuture(BigInteger.ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
val feesCalculator = L2CalldataBasedVariableFeesCalculator(
|
||||||
|
config = config,
|
||||||
|
variableFeesCalculator = mockVariableFeesCalculator,
|
||||||
|
l2CalldataSizeAccumulator = mockl2CalldataSizeAccumulator,
|
||||||
|
)
|
||||||
|
|
||||||
|
// call calculateFees first to instantiate the lastVariableCost
|
||||||
|
feesCalculator.calculateFees(feeHistory)
|
||||||
|
|
||||||
|
// With (1 - 1/32) as the rate, after 22 calls
|
||||||
|
// the expectedVariableFees should be decreased to less than half
|
||||||
|
var calculatedFee = 0.0
|
||||||
|
(0..21).forEach { _ ->
|
||||||
|
calculatedFee = feesCalculator.calculateFees(feeHistory)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(calculatedFee).isLessThan(15000.0 / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_calculateFees_when_block_count_is_zero() {
|
||||||
|
val sumOfCalldataSize = (109000 * 5).toBigInteger() // maxBlockCalldataSize * calldataSizeBlockCount
|
||||||
|
val mockl2CalldataSizeAccumulator = mock<L2CalldataSizeAccumulator> {
|
||||||
|
on { getSumOfL2CalldataSize() } doReturn SafeFuture.completedFuture(sumOfCalldataSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val feesCalculator = L2CalldataBasedVariableFeesCalculator(
|
||||||
|
config = L2CalldataBasedVariableFeesCalculator.Config(
|
||||||
|
feeChangeDenominator = 32u,
|
||||||
|
calldataSizeBlockCount = 0u, // set zero to disable calldata-based variable fees
|
||||||
|
maxBlockCalldataSize = 109000u,
|
||||||
|
),
|
||||||
|
variableFeesCalculator = mockVariableFeesCalculator,
|
||||||
|
l2CalldataSizeAccumulator = mockl2CalldataSizeAccumulator,
|
||||||
|
)
|
||||||
|
|
||||||
|
// call calculateFees first to instantiate the lastVariableCost
|
||||||
|
feesCalculator.calculateFees(feeHistory)
|
||||||
|
|
||||||
|
// The returned variable fees should always be 15000.0
|
||||||
|
// as calldata-based variable fees is disabled
|
||||||
|
assertThat(feesCalculator.calculateFees(feeHistory))
|
||||||
|
.isEqualTo(15000.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package net.consensys.linea.ethereum.gaspricing.staticcap
|
||||||
|
|
||||||
|
import linea.web3j.ExtendedWeb3J
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.mockito.kotlin.any
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.eq
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.times
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import tech.pegasys.teku.infrastructure.async.SafeFuture
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
|
class L2CalldataSizeAccumulatorImplTest {
|
||||||
|
private val config = L2CalldataSizeAccumulatorImpl.Config(
|
||||||
|
blockSizeNonCalldataOverhead = 540u,
|
||||||
|
calldataSizeBlockCount = 5u,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getSumOfL2CalldataSize() {
|
||||||
|
val mockWeb3jClient = mock<ExtendedWeb3J> {
|
||||||
|
on { ethBlockNumber() } doReturn SafeFuture.completedFuture(100.toBigInteger())
|
||||||
|
on { ethGetBlockSizeByNumber(any()) } doReturn SafeFuture.completedFuture(10540.toBigInteger())
|
||||||
|
}
|
||||||
|
val l2CalldataSizeAccumulator = L2CalldataSizeAccumulatorImpl(
|
||||||
|
config = config,
|
||||||
|
web3jClient = mockWeb3jClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
val sumOfL2CalldataSize = l2CalldataSizeAccumulator.getSumOfL2CalldataSize().get()
|
||||||
|
|
||||||
|
(0..4).forEach {
|
||||||
|
verify(mockWeb3jClient, times(1))
|
||||||
|
.ethGetBlockSizeByNumber(eq(100L - it))
|
||||||
|
}
|
||||||
|
|
||||||
|
val expectedCalldataSize = (10540 - 540) * 5
|
||||||
|
assertThat(sumOfL2CalldataSize).isEqualTo(expectedCalldataSize.toBigInteger())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getSumOfL2CalldataSize_for_each_calldata_size_at_zero() {
|
||||||
|
val mockWeb3jClient = mock<ExtendedWeb3J> {
|
||||||
|
on { ethBlockNumber() } doReturn SafeFuture.completedFuture(100.toBigInteger())
|
||||||
|
on { ethGetBlockSizeByNumber(any()) } doReturn SafeFuture.completedFuture(BigInteger.ZERO)
|
||||||
|
}
|
||||||
|
val l2CalldataSizeAccumulator = L2CalldataSizeAccumulatorImpl(
|
||||||
|
config = config,
|
||||||
|
web3jClient = mockWeb3jClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
val sumOfL2CalldataSize = l2CalldataSizeAccumulator.getSumOfL2CalldataSize().get()
|
||||||
|
|
||||||
|
(0..4).forEach {
|
||||||
|
verify(mockWeb3jClient, times(1))
|
||||||
|
.ethGetBlockSizeByNumber(eq(100L - it))
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(sumOfL2CalldataSize).isEqualTo(BigInteger.ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getSumOfL2CalldataSize_for_exception() {
|
||||||
|
val mockWeb3jClient = mock<ExtendedWeb3J> {
|
||||||
|
on { ethBlockNumber() } doReturn SafeFuture.failedFuture(RuntimeException("Failed for testing"))
|
||||||
|
}
|
||||||
|
val l2CalldataSizeAccumulator = L2CalldataSizeAccumulatorImpl(
|
||||||
|
config = config,
|
||||||
|
web3jClient = mockWeb3jClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<Exception> {
|
||||||
|
l2CalldataSizeAccumulator.getSumOfL2CalldataSize().get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getSumOfL2CalldataSize_when_ethBlockNumber_is_less_than_calldataSizeBlockCount() {
|
||||||
|
val mockWeb3jClient = mock<ExtendedWeb3J> {
|
||||||
|
on { ethBlockNumber() } doReturn SafeFuture.completedFuture(BigInteger.ONE)
|
||||||
|
on { ethGetBlockSizeByNumber(any()) } doReturn SafeFuture.completedFuture(10540.toBigInteger())
|
||||||
|
}
|
||||||
|
val l2CalldataSizeAccumulator = L2CalldataSizeAccumulatorImpl(
|
||||||
|
config = config,
|
||||||
|
web3jClient = mockWeb3jClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
val sumOfL2CalldataSize = l2CalldataSizeAccumulator.getSumOfL2CalldataSize().get()
|
||||||
|
|
||||||
|
verify(mockWeb3jClient, times(0)).ethGetBlockSizeByNumber(any())
|
||||||
|
|
||||||
|
assertThat(sumOfL2CalldataSize).isEqualTo(BigInteger.ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getSumOfL2CalldataSize_if_calldataSizeBlockCount_is_zero() {
|
||||||
|
val mockWeb3jClient = mock<ExtendedWeb3J> {
|
||||||
|
on { ethBlockNumber() } doReturn SafeFuture.completedFuture(BigInteger.ONE)
|
||||||
|
on { ethGetBlockSizeByNumber(any()) } doReturn SafeFuture.completedFuture(10540.toBigInteger())
|
||||||
|
}
|
||||||
|
val l2CalldataSizeAccumulator = L2CalldataSizeAccumulatorImpl(
|
||||||
|
config = L2CalldataSizeAccumulatorImpl.Config(
|
||||||
|
blockSizeNonCalldataOverhead = 540u,
|
||||||
|
calldataSizeBlockCount = 0u,
|
||||||
|
),
|
||||||
|
web3jClient = mockWeb3jClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
val sumOfL2CalldataSize = l2CalldataSizeAccumulator.getSumOfL2CalldataSize().get()
|
||||||
|
|
||||||
|
verify(mockWeb3jClient, times(0)).ethGetBlockSizeByNumber(any())
|
||||||
|
|
||||||
|
assertThat(sumOfL2CalldataSize).isEqualTo(BigInteger.ZERO)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ interface ExtendedWeb3J {
|
|||||||
fun ethBlockNumber(): SafeFuture<BigInteger>
|
fun ethBlockNumber(): SafeFuture<BigInteger>
|
||||||
fun ethGetBlock(blockParameter: BlockParameter): SafeFuture<Block?>
|
fun ethGetBlock(blockParameter: BlockParameter): SafeFuture<Block?>
|
||||||
fun ethGetBlockTimestampByNumber(blockNumber: Long): SafeFuture<BigInteger>
|
fun ethGetBlockTimestampByNumber(blockNumber: Long): SafeFuture<BigInteger>
|
||||||
|
fun ethGetBlockSizeByNumber(blockNumber: Long): SafeFuture<BigInteger>
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExtendedWeb3JImpl(override val web3jClient: Web3j) : ExtendedWeb3J {
|
class ExtendedWeb3JImpl(override val web3jClient: Web3j) : ExtendedWeb3J {
|
||||||
@@ -76,4 +77,26 @@ class ExtendedWeb3JImpl(override val web3jClient: Web3j) : ExtendedWeb3J {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun ethGetBlockSizeByNumber(
|
||||||
|
blockNumber: Long,
|
||||||
|
): SafeFuture<BigInteger> {
|
||||||
|
return SafeFuture.of(
|
||||||
|
web3jClient
|
||||||
|
.ethGetBlockByNumber(
|
||||||
|
DefaultBlockParameter.valueOf(BigInteger.valueOf(blockNumber)),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.sendAsync(),
|
||||||
|
)
|
||||||
|
.thenCompose { response ->
|
||||||
|
if (response.hasError()) {
|
||||||
|
SafeFuture.failedFuture(buildException(response.error))
|
||||||
|
} else {
|
||||||
|
response.block?.let {
|
||||||
|
SafeFuture.completedFuture(response.block.size)
|
||||||
|
} ?: SafeFuture.failedFuture(Exception("Block $blockNumber not found!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user