Skip to content

fix(coordinator): make runtime invariants explicit - #3716

Merged
nadeemb53 merged 11 commits into
mainfrom
fix/3686-runtime-invariants
Aug 10, 2026
Merged

fix(coordinator): make runtime invariants explicit#3716
nadeemb53 merged 11 commits into
mainfrom
fix/3686-runtime-invariants

Conversation

@nadeemb53

@nadeemb53 nadeemb53 commented Aug 5, 2026

Copy link
Copy Markdown
Member
  • report the exact proof index when a required L2 execution or rollup proof response is missing
  • validate blob compression proofs at submission and finalization boundaries
  • replace the gas-cap non-null assertion with a named invariant
  • add regression coverage for missing prover responses

Refs #3686

Replace unsafe proof and gas-cap dereferences with contextual invariant checks, and cover missing prover responses with regression tests.

Refs #3686

Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Copilot AI lite review requested due to automatic review settings August 5, 2026 05:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes several coordinator runtime invariants explicit to avoid bare NullPointerExceptions and to improve diagnosability when prover or blob compression proof data is missing, with regression tests to lock in the new behavior.

Changes:

  • Replace non-null assertions with named requireNotNull(...) invariants and actionable error messages (gas caps, blob compression proofs, and prover responses).
  • Validate blob compression proofs at smart-contract submission/finalization boundaries.
  • Add regression tests ensuring missing prover responses surface the exact missing proofIndex.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
coordinator/ethereum/gas-pricing/dynamic-cap/src/main/kotlin/net/consensys/linea/ethereum/gaspricing/dynamiccap/GasPriceCapProviderImplV2.kt Replaces !! with requireNotNull for maxBaseFeePerGasCap before applying coefficient.
coordinator/clients/smart-contract-client/src/main/kotlin/net/consensys/linea/contract/l1/Web3JLinethRollupSmartContractClient.kt Adds a boundary check (requireCompressionProofs) to fail fast with blob index when a compression proof is missing.
coordinator/clients/smart-contract-client/src/main/kotlin/net/consensys/linea/contract/l1/Web3JLinethRollupFunctionBuilders.kt Makes finalization builder invariant explicit by extracting compressionProof via requireNotNull.
coordinator/clients/smart-contract-client/src/main/kotlin/net/consensys/linea/contract/l1/Web3JLineaValidiumFunctionBuilders.kt Same finalization invariant improvement for validium finalization builder.
coordinator/clients/smart-contract-client/src/main/kotlin/net/consensys/linea/contract/l1/FunctionBuildersV8.kt Same finalization invariant improvement for V8 finalization function builder.
coordinator/clients/prover-client/riscv-client/src/main/kotlin/linea/coordinator/clients/prover/riscv/RollupProverClient.kt Reports the exact proofIndex when an L2 execution proof response is missing (null response).
coordinator/clients/prover-client/riscv-client/src/main/kotlin/linea/coordinator/clients/prover/riscv/RollupAggregationProverClient.kt Reports the exact proofIndex when a rollup proof response is missing (null response).
coordinator/clients/prover-client/riscv-client/src/test/kotlin/linea/coordinator/clients/prover/riscv/RiscVProofRequestDtoMapperTest.kt Adds regression coverage asserting the new missing-response error messages and exception types.

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.51948% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ontract/l1/Web3JLinethRollupSmartContractClient.kt 34.78% 14 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Copilot AI review requested due to automatic review settings August 5, 2026 07:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (3)

coordinator/clients/smart-contract-client/src/test/kotlin/net/consensys/linea/contract/l1/Web3JLinethRollupSmartContractClientTest.kt:53

  • Same spy-stubbing issue as above: whenever(client.getVersion()) will call the real getVersion() during stubbing. Use doReturn(...).whenever(client).getVersion() to ensure the test doesn't depend on the real implementation.
  fun `rejects blob submission without a compression proof`() {
    whenever(client.getVersion()).thenReturn(SafeFuture.completedFuture(LinethRollupContractVersion.V6))
    val blob = createBlobRecord(startBlockNumber = 1UL, endBlockNumber = 2UL)
      .copy(blobCompressionProof = null)

coordinator/ethereum/gas-pricing/dynamic-cap/src/test/kotlin/net/consensys/linea/ethereum/gaspricing/dynamiccap/GasPriceCapProviderImplV2Test.kt:185

  • whenever(spy.getGasPriceCaps(...)) on a Mockito spy will invoke the real getGasPriceCaps during stubbing, which can make the test flaky (and can fail if the real method throws/returns null). Use doReturn(...).whenever(spy).getGasPriceCaps(...) to stub without calling the real method.
    val gasPriceCapProvider = spy(createGasPriceCapProvider())
    whenever(gasPriceCapProvider.getGasPriceCaps(targetBlockTime)).thenReturn(
      tech.pegasys.teku.infrastructure.async.SafeFuture.completedFuture(

coordinator/clients/smart-contract-client/src/test/kotlin/net/consensys/linea/contract/l1/Web3JLinethRollupSmartContractClientTest.kt:43

  • whenever(client.getVersion()) is stubbing a method on a Mockito spy, which calls the real getVersion() during stubbing. If the real implementation touches Web3j/contract wrappers, this can introduce nondeterminism. Prefer doReturn(...).whenever(client).getVersion() to avoid invoking the real method.

This issue also appears on line 50 of the same file.

  fun `submits compression-proven blobs`() {
    whenever(client.getVersion()).thenReturn(SafeFuture.completedFuture(LinethRollupContractVersion.V6))
    val blob = createBlobRecord(startBlockNumber = 1UL, endBlockNumber = 2UL).let {
      it.copy(blobCompressionProof = it.blobCompressionProof!!.copy(expectedY = ByteArray(32) { 1 }))

Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Copilot AI review requested due to automatic review settings August 5, 2026 15:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 5, 2026 15:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
Copilot AI review requested due to automatic review settings August 6, 2026 03:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Suppressed comments (2)

coordinator/clients/smart-contract-client/src/main/kotlin/net/consensys/linea/contract/l1/Web3JLinethRollupSmartContractClient.kt:144

  • Same as submitBlobs: requireCompressionProofs() currently runs only after getVersion() completes, which can perform a network fetch on cache misses. You can validate first (still asynchronously) to fail fast without doing an unnecessary RPC.
  override fun submitBlobsEthCall(blobs: List<BlobRecord>, gasPriceCaps: GasPriceCaps?): SafeFuture<String?> {
    return getVersion()
      .thenCompose { version ->
        val compressionProofs = blobs.requireCompressionProofs("submitBlobsEthCall")
        val function = Web3JLinethRollupFunctionBuilders.buildSubmitBlobsFunction(version, blobs)

coordinator/clients/smart-contract-client/src/main/kotlin/net/consensys/linea/contract/l1/Web3JLinethRollupSmartContractClient.kt:131

  • requireCompressionProofs() is evaluated only after getVersion() completes. Since getVersion() may hit the network (on cache misses), invalid input can still trigger an unnecessary RPC. Consider validating the blob compression proofs before calling getVersion() while still returning a failed SafeFuture (to avoid throwing synchronously).

This issue also appears on line 140 of the same file.

  override fun submitBlobs(blobs: List<BlobRecord>, gasPriceCaps: GasPriceCaps?): SafeFuture<String> {
    return getVersion()
      .thenCompose { version ->
        val compressionProofs = blobs.requireCompressionProofs("submitBlobs")
        val function = Web3JLinethRollupFunctionBuilders.buildSubmitBlobsFunction(version, blobs)

Copilot AI review requested due to automatic review settings August 6, 2026 03:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Suppressed comments (2)

coordinator/clients/prover-client/riscv-client/src/testFixtures/kotlin/linea/coordinator/clients/prover/riscv/FakeProverProofTransports.kt:81

  • Same issue as above: awaitResponse throws synchronously when responseProvider returns null. It should return a SafeFuture that completes exceptionally so call sites behave like the real transports.
  override fun awaitResponse(proofIndex: BlockIntervalProofIndex): SafeFuture<RollupProofResponseDto> =
    SafeFuture.completedFuture(requireNotNull(response(proofIndex)))

coordinator/clients/prover-client/riscv-client/src/testFixtures/kotlin/linea/coordinator/clients/prover/riscv/FakeProverProofTransports.kt:36

  • awaitResponse currently evaluates requireNotNull(response(proofIndex)) before creating the SafeFuture, so when the response is missing this fake transport throws synchronously instead of returning a failed SafeFuture. That breaks the ProverProofTransport.awaitResponse contract and can change failure behavior in tests that expect async failures.

This issue also appears on line 80 of the same file.

  override fun awaitResponse(proofIndex: BlockIntervalProofIndex): SafeFuture<L2ExecutionProofResponseDto> =
    SafeFuture.completedFuture(requireNotNull(response(proofIndex)))

Comment thread .github/workflows/coordinator-testing.yml
Comment thread .github/workflows/coordinator-testing.yml
Signed-off-by: nadeemb53 <nadeemb53@gmail.com>
blobs = blobs.map { it.blobCompressionProof!!.compressedData },
gasPriceCaps = gasPriceCaps,
)
return SafeFuture.completedFuture(Unit)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this makes the code unnecessarily more complex (and slightly inefficient) just to avoid a !!.

We shall be more critical of taking AI feedback.

In this context, we cannot have nulls.

  1. The submission controller shall only fetch proven records
  2. integration/e2e tests will catch this, so it won't make it to prod.
  3. This change does not add extra safety/soundness; it just adds a better error message if it happens...

@nadeemb53
nadeemb53 merged commit d21905d into main Aug 10, 2026
46 checks passed
@nadeemb53
nadeemb53 deleted the fix/3686-runtime-invariants branch August 10, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants