From 0b16f203b445ee269f21831e28a195f4c18882fe Mon Sep 17 00:00:00 2001 From: Pedro Novais <1478752+jpnovais@users.noreply.github.com> Date: Fri, 14 Feb 2025 21:36:56 +0000 Subject: [PATCH] staterecovery: refactor manual test --- .../linea/vertx/VertxConfiguration.kt | 2 +- .../net/consensys/linea/vertx/VertxFactory.kt | 36 ++++ .../kotlin/linea/web3j/Pollers.kt | 2 +- state-recovery/test-cases/build.gradle | 1 + .../ManualTestWithFakeExecutionClient.kt | 111 +++++++++++ ...erSepoliaWithFakeExecutionClientIntTest.kt | 175 ------------------ 6 files changed, 150 insertions(+), 177 deletions(-) create mode 100644 jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/VertxFactory.kt create mode 100644 state-recovery/test-cases/src/test/kotlin/linea/staterecovery/ManualTestWithFakeExecutionClient.kt delete mode 100644 state-recovery/test-cases/src/test/kotlin/linea/staterecovery/StateRecoverSepoliaWithFakeExecutionClientIntTest.kt diff --git a/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/VertxConfiguration.kt b/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/VertxConfiguration.kt index 21e2f456d..a6563c5da 100644 --- a/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/VertxConfiguration.kt +++ b/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/VertxConfiguration.kt @@ -27,6 +27,6 @@ fun loadVertxConfig(): VertxOptions { // Close the vert.x instance, we don't need it anymore. val retriever = ConfigRetriever.create(vertx, options) val parsedOptions = VertxOptions(retriever.config.get()) - vertx.close() + vertx.close().get() return parsedOptions } diff --git a/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/VertxFactory.kt b/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/VertxFactory.kt new file mode 100644 index 000000000..22e1ada2d --- /dev/null +++ b/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/VertxFactory.kt @@ -0,0 +1,36 @@ +package net.consensys.linea.vertx + +import io.vertx.core.Vertx +import io.vertx.core.VertxOptions +import io.vertx.core.json.JsonObject + +object VertxFactory { + fun createVertx( + configsOverrides: JsonObject = JsonObject() + ): Vertx { + val defaultConfigs = JsonObject( + """ + { + "preferNativeTransport": true, + "logStacktraceThreshold": 500, + "blockedThreadCheckIntervalUnit": "MINUTES", + "maxEventLoopExecuteTime": 5000, + "maxEventLoopExecuteTimeUnit": "MILLISECONDS", + "warnEventLoopBlocked": 5000, + "maxWorkerExecuteTime": 130, + "maxWorkerExecuteTimeUnit": "SECONDS", + "metricsOptions": { + "enabled": true, + "jvmMetricsEnabled": true, + "prometheusOptions": { + "enabled": true, + "publishQuantiles": true + } + } + } + """.trimIndent() + ) + + return Vertx.vertx(VertxOptions(defaultConfigs.mergeIn(configsOverrides))) + } +} diff --git a/jvm-libs/linea/web3j-extensions/src/testFixtures/kotlin/linea/web3j/Pollers.kt b/jvm-libs/linea/web3j-extensions/src/testFixtures/kotlin/linea/web3j/Pollers.kt index 3b9caecda..57a0859f6 100644 --- a/jvm-libs/linea/web3j-extensions/src/testFixtures/kotlin/linea/web3j/Pollers.kt +++ b/jvm-libs/linea/web3j-extensions/src/testFixtures/kotlin/linea/web3j/Pollers.kt @@ -34,7 +34,7 @@ fun Web3j.waitForTxReceipt( }.getOrNull() if (receipt != null) { - log.debug("tx receipt found: txHash={} receiptStatus={}", txHash, receipt?.status) + log.debug("tx receipt found: txHash={} receiptStatus={}", txHash, receipt.status) if (expectedStatus != null && receipt.status != expectedStatus) { throw RuntimeException( "Transaction status does not match expected status: " + diff --git a/state-recovery/test-cases/build.gradle b/state-recovery/test-cases/build.gradle index eee520380..8ded55c09 100644 --- a/state-recovery/test-cases/build.gradle +++ b/state-recovery/test-cases/build.gradle @@ -21,6 +21,7 @@ dependencies { api(project(':state-recovery:appcore:logic')) api(project(':state-recovery:besu-plugin')) + implementation project(':jvm-libs:generic:vertx-helper') implementation project(':jvm-libs:linea:besu-libs') implementation project(':jvm-libs:linea:testing:file-system') implementation(testFixtures(project(':jvm-libs:generic:json-rpc'))) diff --git a/state-recovery/test-cases/src/test/kotlin/linea/staterecovery/ManualTestWithFakeExecutionClient.kt b/state-recovery/test-cases/src/test/kotlin/linea/staterecovery/ManualTestWithFakeExecutionClient.kt new file mode 100644 index 000000000..5545609a9 --- /dev/null +++ b/state-recovery/test-cases/src/test/kotlin/linea/staterecovery/ManualTestWithFakeExecutionClient.kt @@ -0,0 +1,111 @@ +package linea.staterecovery + +import build.linea.clients.StateManagerClientV1 +import io.vertx.core.Vertx +import linea.domain.RetryConfig +import linea.log4j.configureLoggers +import linea.staterecovery.plugin.createAppClients +import linea.staterecovery.test.FakeExecutionLayerClient +import linea.staterecovery.test.FakeStateManagerClientReadFromL1 +import net.consensys.linea.BlockNumberAndHash +import net.consensys.linea.BlockParameter +import net.consensys.linea.async.get +import net.consensys.linea.vertx.VertxFactory +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.LogManager +import org.assertj.core.api.Assertions.assertThat +import org.awaitility.Awaitility.await +import java.net.URI +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds +import kotlin.time.toJavaDuration + +class TestRunner( + private val vertx: Vertx = VertxFactory.createVertx(), + private val l2RecoveryStartBlockNumber: ULong, + private val debugForceSyncStopBlockNumber: ULong = ULong.MAX_VALUE +) { + private val log = LogManager.getLogger("test.case.TestRunner") + private val infuraAppKey = System.getenv("INFURA_PROJECT_ID") + .also { + require(it.isNotEmpty()) { "Please define INFURA_APP_KEY environment variable" } + } + private val l1RpcUrl = "https://sepolia.infura.io/v3/$infuraAppKey" + private val blobScanUrl = "https://api.sepolia.blobscan.com/" + val appConfig = StateRecoveryApp.Config( + // l1EarliestSearchBlock = 7236630UL.toBlockParameter(), + l1EarliestSearchBlock = BlockParameter.Tag.EARLIEST, + l1LatestSearchBlock = BlockParameter.Tag.LATEST, + l1PollingInterval = 5.seconds, + executionClientPollingInterval = 1.seconds, + smartContractAddress = StateRecoveryApp.Config.lineaSepolia.smartContractAddress, + logsBlockChunkSize = 10_000u, + debugForceSyncStopBlockNumber = debugForceSyncStopBlockNumber + ) + val appClients = createAppClients( + vertx = vertx, + l1RpcEndpoint = URI(l1RpcUrl), + l1SuccessBackoffDelay = 1.milliseconds, + l1RequestRetryConfig = RetryConfig(backoffDelay = 1.seconds, maxRetries = 1u), + blobScanEndpoint = URI(blobScanUrl), + blobScanRequestRetryConfig = RetryConfig(backoffDelay = 10.milliseconds, timeout = 5.seconds), + stateManagerClientEndpoint = URI("http://it-does-not-matter:5432"), + appConfig = appConfig + ) + private val fakeExecutionLayerClient = FakeExecutionLayerClient( + headBlock = BlockNumberAndHash(number = l2RecoveryStartBlockNumber - 1UL, hash = ByteArray(32) { 0 }), + initialStateRecoverStartBlockNumber = l2RecoveryStartBlockNumber, + loggerName = "test.fake.clients.execution-layer" + ) + var fakeStateManagerClient: StateManagerClientV1 = FakeStateManagerClientReadFromL1( + headBlockNumber = ULong.MAX_VALUE, + logsSearcher = appClients.ethLogsSearcher, + contractAddress = StateRecoveryApp.Config.lineaSepolia.smartContractAddress + ) + var stateRecoverApp: StateRecoveryApp = StateRecoveryApp( + vertx = vertx, + elClient = fakeExecutionLayerClient, + blobFetcher = appClients.blobScanClient, + ethLogsSearcher = appClients.ethLogsSearcher, + stateManagerClient = fakeStateManagerClient, + transactionDetailsClient = appClients.transactionDetailsClient, + blockHeaderStaticFields = BlockHeaderStaticFields.localDev, + lineaContractClient = appClients.lineaContractClient, + config = appConfig + ) + + init { + configureLoggers( + rootLevel = Level.INFO, + "linea.staterecovery" to Level.TRACE, + "linea.plugin.staterecovery" to Level.DEBUG, + "linea.plugin.staterecovery.clients.l1.logs-searcher" to Level.TRACE + ) + } + + fun run( + timeout: kotlin.time.Duration = 10.minutes + ) { + log.info("Running test case") + stateRecoverApp.start().get() + await() + .atMost(timeout.toJavaDuration()) + .pollInterval(10.seconds.toJavaDuration()) + .untilAsserted { + val updatedStatus = fakeExecutionLayerClient.lineaGetStateRecoveryStatus().get() + assertThat(updatedStatus.headBlockNumber).isGreaterThan(debugForceSyncStopBlockNumber) + } + log.info("Test case finished") + vertx.close().get() + } +} + +fun main() { + TestRunner( + l2RecoveryStartBlockNumber = 7313000UL, + debugForceSyncStopBlockNumber = 7313050UL + ).run( + timeout = 10.minutes + ) +} diff --git a/state-recovery/test-cases/src/test/kotlin/linea/staterecovery/StateRecoverSepoliaWithFakeExecutionClientIntTest.kt b/state-recovery/test-cases/src/test/kotlin/linea/staterecovery/StateRecoverSepoliaWithFakeExecutionClientIntTest.kt deleted file mode 100644 index 2185c65c0..000000000 --- a/state-recovery/test-cases/src/test/kotlin/linea/staterecovery/StateRecoverSepoliaWithFakeExecutionClientIntTest.kt +++ /dev/null @@ -1,175 +0,0 @@ -package linea.staterecovery - -import build.linea.clients.StateManagerClientV1 -import build.linea.contract.l1.LineaRollupSmartContractClientReadOnly -import build.linea.contract.l1.Web3JLineaRollupSmartContractClientReadOnly -import io.micrometer.core.instrument.simple.SimpleMeterRegistry -import io.vertx.core.Vertx -import io.vertx.junit5.VertxExtension -import linea.EthLogsSearcher -import linea.domain.RetryConfig -import linea.log4j.configureLoggers -import linea.staterecovery.clients.VertxTransactionDetailsClient -import linea.staterecovery.clients.blobscan.BlobScanClient -import linea.staterecovery.test.FakeExecutionLayerClient -import linea.staterecovery.test.FakeStateManagerClientReadFromL1 -import linea.web3j.Web3JLogsSearcher -import net.consensys.linea.BlockNumberAndHash -import net.consensys.linea.BlockParameter -import net.consensys.linea.jsonrpc.client.RequestRetryConfig -import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClientFactory -import net.consensys.linea.metrics.micrometer.MicrometerMetricsFacade -import net.consensys.zkevm.ethereum.Web3jClientManager.buildWeb3Client -import org.apache.logging.log4j.Level -import org.apache.logging.log4j.LogManager -import org.assertj.core.api.Assertions.assertThat -import org.awaitility.Awaitility.await -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.extension.ExtendWith -import java.net.URI -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds -import kotlin.time.toJavaDuration - -@ExtendWith(VertxExtension::class) -class StateRecoverSepoliaWithFakeExecutionClientIntTest { - private val log = LogManager.getLogger("test.case.StateRecoverSepoliaWithFakeExecutionClientIntTest") - private lateinit var stateRecoverApp: StateRecoveryApp - private lateinit var logsSearcher: EthLogsSearcher - private lateinit var executionLayerClient: FakeExecutionLayerClient - private lateinit var blobFetcher: BlobFetcher - private lateinit var fakeStateManagerClient: StateManagerClientV1 - private lateinit var transactionDetailsClient: TransactionDetailsClient - private lateinit var lineaContractClient: LineaRollupSmartContractClientReadOnly - private val infuraAppKey = System.getenv("INFURA_APP_KEY") - .also { - assertThat(it) - .withFailMessage("Please define INFURA_APP_KEY environment variable") - .isNotEmpty() - } - private val l1RpcUrl = "https://sepolia.infura.io/v3/$infuraAppKey" - private val blobScanUrl = "https://api.sepolia.blobscan.com/" - - @BeforeEach - fun beforeEach(vertx: Vertx) { - val jsonRpcFactory = VertxHttpJsonRpcClientFactory( - vertx = vertx, - metricsFacade = MicrometerMetricsFacade(SimpleMeterRegistry()) - ) - executionLayerClient = FakeExecutionLayerClient( - headBlock = BlockNumberAndHash(number = 0uL, hash = ByteArray(32) { 0 }), - initialStateRecoverStartBlockNumber = null, - loggerName = "test.fake.clients.execution-layer" - ) - blobFetcher = BlobScanClient.create( - vertx = vertx, - endpoint = URI(blobScanUrl), - requestRetryConfig = RequestRetryConfig( - backoffDelay = 10.milliseconds, - timeout = 5.seconds - ), - logger = LogManager.getLogger("test.clients.l1.blobscan"), - responseLogMaxSize = 100u - ) - logsSearcher = Web3JLogsSearcher( - vertx = vertx, - web3jClient = buildWeb3Client( - rpcUrl = l1RpcUrl, - log = LogManager.getLogger("test.clients.l1.events-fetcher"), - requestResponseLogLevel = Level.TRACE, - failuresLogLevel = Level.DEBUG - ), - Web3JLogsSearcher.Config( - backoffDelay = 400.milliseconds, - requestRetryConfig = RetryConfig( - backoffDelay = 1.seconds - ) - ), - log = LogManager.getLogger("test.clients.l1.events-fetcher") - ) - fakeStateManagerClient = FakeStateManagerClientReadFromL1( - headBlockNumber = ULong.MAX_VALUE, - logsSearcher = logsSearcher, - contractAddress = StateRecoveryApp.Config.lineaSepolia.smartContractAddress - ) - transactionDetailsClient = VertxTransactionDetailsClient.create( - jsonRpcClientFactory = jsonRpcFactory, - endpoint = URI(l1RpcUrl), - retryConfig = RequestRetryConfig( - backoffDelay = 1.seconds - ), - logger = LogManager.getLogger("test.clients.l1.transaction-details") - ) - - lineaContractClient = Web3JLineaRollupSmartContractClientReadOnly( - web3j = buildWeb3Client( - rpcUrl = l1RpcUrl, - log = LogManager.getLogger("test.clients.l1.linea-contract"), - requestResponseLogLevel = Level.INFO, - failuresLogLevel = Level.DEBUG - ), - contractAddress = StateRecoveryApp.Config.lineaSepolia.smartContractAddress - ) - - stateRecoverApp = StateRecoveryApp( - vertx = vertx, - elClient = executionLayerClient, - blobFetcher = blobFetcher, - ethLogsSearcher = logsSearcher, - stateManagerClient = fakeStateManagerClient, - transactionDetailsClient = transactionDetailsClient, - blockHeaderStaticFields = BlockHeaderStaticFields.localDev, - lineaContractClient = lineaContractClient, - config = StateRecoveryApp.Config( - l1LatestSearchBlock = BlockParameter.Tag.LATEST, - l1PollingInterval = 5.seconds, - executionClientPollingInterval = 1.seconds, - smartContractAddress = lineaContractClient.getAddress(), - logsBlockChunkSize = 5000u - ) - ) - configureLoggers( - rootLevel = Level.INFO, - "test.clients.l1.execution-layer" to Level.INFO, - "test.clients.l1.web3j-default" to Level.DEBUG, - "test.clients.l1.transaction-details" to Level.INFO, - "test.clients.l1.linea-contract" to Level.INFO, - "test.clients.l1.events-fetcher" to Level.DEBUG, - "test.clients.l1.blobscan" to Level.INFO, - "net.consensys.linea.contract.l1" to Level.DEBUG - ) - } - - // "Disabled because it is for local testing and debug purposes" -// @Test - fun `simulate recovery from given point`() { - val finalizationEvents = logsSearcher - .getLogs( - fromBlock = BlockParameter.Tag.EARLIEST, - toBlock = BlockParameter.Tag.LATEST, - address = lineaContractClient.getAddress(), - topics = listOf(DataFinalizedV3.topic) - ) - .get() - val firstFinalizationEvent = DataFinalizedV3.fromEthLog(finalizationEvents.first()) - val lastFinalizationEvent = DataFinalizedV3.fromEthLog(finalizationEvents.last()) - log.info("First finalization event: $firstFinalizationEvent") - log.info("Latest finalization event: $lastFinalizationEvent") - - executionLayerClient.headBlock = BlockNumberAndHash( - number = firstFinalizationEvent.event.startBlockNumber - 1UL, - hash = ByteArray(32) { 0 } - ) - stateRecoverApp.trySetRecoveryModeAtBlockHeight(firstFinalizationEvent.event.startBlockNumber).get() - stateRecoverApp.start().get() - - await() - .atMost(10.minutes.toJavaDuration()) - .pollInterval(10.seconds.toJavaDuration()) - .untilAsserted { - val updatedStatus = executionLayerClient.lineaGetStateRecoveryStatus().get() - assertThat(updatedStatus.headBlockNumber).isGreaterThan(lastFinalizationEvent.event.endBlockNumber) - } - } -}