Skip to content
s1ns3nz0 | Known Unknowns
Go back

Lineth L2 Transaction Lifecycle (3) - Inside the Prover: From a Coordinator Request to a Proof Response

6 min read

Part of the Lineth L2 Transaction Lifecycle series. The component map is in part 0.

The previous post watched a proof from the Coordinator’s side: a request written, a result picked up 3 minutes 24 seconds later. This one looks at what happens in between, inside the Prover.

The range is L2 blocks 26,605 through 26,684, separate from the earlier ranges. I connect the Coordinator’s request, the Prover’s trace processing, the response files, and the Coordinator’s completion records.

All timestamps are UTC.

The test environment

The lab runs the PARTIAL execution prover, and dev proof paths for compression and aggregation. The logs say so:

Running the PARTIAL prover
[Aggregation] generating proof (mode: dev)
generated dummy-circuit proof

The run still reads traces, builds circuits, computes, and checks consistency. The timings below are for this local setup, not production proving.

Coordinator and Prover talk through files

The Coordinator and Prover exchange requests and responses through shared storage. Both pods mount the same l2-shared PVC at /data:

Coordinator Pod                         Prover Pod
       │                                     │
       └──────── Shared storage: /data ───────┘
                        l2-shared

A job goes like this:

Coordinator writes a request JSON
    ↓
Prover controller picks it up
    ↓
Controller runs `prover prove`
    ↓
Prover writes a temporary response
    ↓
Controller moves it to the final response name
    ↓
Coordinator finds and reads the response

The Coordinator writes the request into its request directory and polls the expected response path until a file shows up. There’s no RPC call from Coordinator to Prover. The execution trace is read from the same storage.

What files buy you

The cost is a dependency on shared storage being up, on naming conventions, on polling, and on handling failures. Files alone don’t give automatic recovery or exactly-once processing.

Only proving jobs go through files. L1 transactions go over RPC, and the Web3Signer connection is HTTPS.

The Coordinator’s request

k -n lineth-l2 logs deploy/coordinator -c coordinator --since=2h \
  | grep -E '26605|26684' \
  | grep -E 'new batch:|new blob:|execution proof request generated:|execution proof generated:|blob compression proof generated:|aggregation proof generated:|submitted aggregation='

Trimmed to the relevant fields:

2026-10-14 07:52:09.451+0000
ProofGeneratingConflationHandlerImpl
new batch: batch=[26605..26684]80 trigger=TRACES_LIMIT

2026-10-14 07:52:09.451+0000
GlobalBlockConflationCalculator
new blob: blob=[26605..26684]80 trigger=BATCHES_LIMIT
blobSizeBytes=5472 blobBatchesCount=1 blobBatchesLimit=1
blobBatchesList=[[26605..26684]80]1

2026-10-14 07:52:09.868+0000
ProofGeneratingConflationHandlerImpl
execution proof request generated:
proofIndex=ExecutionProofIndex(
  startBlockNumber=26605,
  endBlockNumber=26684,
  startBlockTimestamp=2026-10-14T07:45:29Z
)

80 blocks again (26684 - 26605 + 1), closed on TRACES_LIMIT, one batch per blob. Same pattern as the previous post. startBlockTimestamp is the first block’s time, not when the Prover started.

The request file

The Prover logs a lot of circuit output, so I filtered it down to job lifecycle lines:

k -n lineth-l2 logs pod/prover-6dcb464df7-m8fvm -c prover \
  --since=1h --timestamps \
  | grep -viE 'constraint system solver done|compiling circuit|parsed circuit inputs|using small native field|building constraint builder|module utilization|\[prover runtime\]|\[bootstrapper\]' \
  | tail -100

At 07:52:10.701Z, 0.8 seconds after the request, the controller logged the command it was about to run:

prover prove \
  --config /opt/linea/prover/config.toml \
  --in /data/prover/v3/execution/requests/26605-26684-getZkProof.json.inprogress.prover-i1 \
  --out /data/prover/v3/execution/responses/tmp-response-file.prover-i1.json

The input file is named for the range, 26605-26684-getZkProof.json. The controller renamed it with .inprogress.prover-i1 to claim it, and the output goes to a temporary file.

ComponentWhat it does
Prover controllerPicks a request, runs the command, handles the result, manages files
prover proveReads the input and does the proving

The command runs inside the Prover pod. The Coordinator never runs it.

Reading the conflated trace

2026-10-14T07:52:10.790070425Z
Conflation stats - totalTxs: 103, totalGasUsed: 2933304

2026-10-14T07:52:11.074802633Z
Running the PARTIAL prover

At 07:52:24.979Z it started reading the compressed trace:

Streaming decompression of traceFile:
"/data/traces/v2/conflated/26605-26684.conflated.linea-0f74f55.26.7.0-6580da8.lt.gz"

The request JSON and the trace are separate files, both named for the range. The trace holds the execution data; the Prover reads it and fills in the tables and variables its checks run over.

Skip constraints compatibility check between zkevm.bin and trace file

The check between the constraint file and the trace was skipped in this setup, not passed.

Module utilization

During the execution job, the Prover logged:

module=blockdata height=1134 limit=4096 ratio=0.27685546875
module=blockhash height=2022 limit=2048 ratio=0.9873046875
module=hub height=18992 limit=131072 ratio=0.1448974609375
FieldMeaning
moduleThe trace module
heightRows it used
limitRows it’s allowed
ratioheight / limit

blockhash is at 2022 / 2048, 98.7% full. That’s trace capacity, not CPU. It’s the module closest to its limit of the three, which fits a batch that closed on TRACES_LIMIT. The batch record still doesn’t name the module, and the utilization line carries no request ID, only a timestamp inside the execution job.

The Coordinator’s batch record for this range had BLOCK_HASH=1440. That’s the Coordinator’s counter, and 2022 is the Prover’s module height. They’re different measurements and don’t convert into each other.

Inside the computation

Assignment stages:

[bootstrapper] column assignment: 209.650792ms
[bootstrapper] total Arithmetization.Assign: 4.425581752s
[bootstrapper] module assigns total: 2.752168043s

Arithmetization turns the execution data into the mathematical form the proof system works with. Assignment fills those structures with values from this workload.

[prover runtime] done running prover step.
name=high-level-prover, time=7.20169442s

Circuit lines:

compiling circuit
parsed circuit inputs nbPublic=2688 nbSecret=0
building constraint builder nbConstraints=191256
constraint system solver done nbConstraints=6998907
TermMeaning
CircuitA set of mathematical checks
ConstraintOne condition in that set
WitnessThe values assigned to the circuit’s variables
Public inputAn input the verifier sees
SolverComputes assignments that satisfy the circuit

nbConstraints isn’t a block or transaction count. nbSecret=0 describes the inputs of that one circuit, not the whole proving process.

These are individual steps. One finishing doesn’t mean the job is done, and their timing windows can overlap, so they don’t add up to the job time.

The execution response

Near the end:

2026-10-14T07:55:17.463065428Z
Public input consistency check passed

Then:

Creating the witness
Generating the proof
Sanity-checking the proof
generated dummy-circuit proof

At 07:55:18.177Z the controller reported:

The processing of file `26605-26684-getZkProof.json`
took 187.475051086 seconds and returned exit code 0

And published the response:

Moving tmp response file
/data/prover/v3/execution/responses/tmp-response-file.prover-i1.json
to final response file:
/data/prover/v3/execution/responses/26605-26684-getZkProof.json

Writing to a temporary file and renaming it means the Coordinator never sees a half-written response. The final name is what it’s polling for.

The controller then deleted the trace file and moved the request into execution/requests-done with a success prefix:

Process exits 0
    ↓
Response published under its final name
    ↓
Request moved to requests-done, trace deleted

96ms after the response was published, the Coordinator logged:

2026-10-14 07:55:18.273+0000
ProofGeneratingConflationHandlerImpl
execution proof generated:
batch=Batch(startBlockNumber=26605, endBlockNumber=26684)

Compression and aggregation

The same range went through two more job types:

JobRequest file ends in
ExecutiongetZkProof.json
CompressiongetZkBlobCompressionProof.json
AggregationgetZkAggregatedProof.json

Each has its own request and response directories under /data/prover/v3.

Compression

The command was logged at 07:55:18.229Z, with this input:

compression/requests/
26605-26684-bcv0.0-ccv0.0-2d8184e1e6ca4ccc781a67201b58885a7390ca6a3433a1cb95791f7c36ed4b92-getZkBlobCompressionProof.json.inprogress.prover-i1
took 0.083499042 seconds and returned exit code 0

Response published at 07:55:18.312Z. The Coordinator:

2026-10-14 07:55:18.331+0000
BlobCompressionProofCoordinator
blob compression proof generated: blob=[26605..26684]80

Aggregation

The command was logged at 07:55:25.330Z:

[Aggregation] generating proof (mode: dev)
took 0.0422045 seconds and returned exit code 0

Response published at 07:55:25.372Z. The Coordinator:

2026-10-14 07:55:25.382+0000
ProofAggregationCoordinatorService
aggregation proof generated: aggregation=[26605..26684]80

In this run, execution finished first, then compression, then aggregation. That’s the order here, not a scheduling rule.

How aggregations chain

The aggregation input included:

LastFinalizedBlockNumber: 26604
FinalBlockNumber: 26684

ParentAggregationFinalShnarf:
0x09d3f6b940b3aa3ca02a025fdb00cab2d872a44e6bbba405499505b65306da58

FinalShnarf:
0x2d8184e1e6ca4ccc781a67201b58885a7390ca6a3433a1cb95791f7c36ed4b92

The block numbers mark the edge before this range and its last block. The previous aggregation, 26525..26604, had 0x09d3…6da58 as its FinalShnarf. The same value is this one’s ParentAggregationFinalShnarf, so each aggregation points back at the one before it.

The FinalShnarf here also appears in the compression request filename above, …2d8184e1e6ca…-getZkBlobCompressionProof.json.

These are inputs to the aggregation job, not the L1 contract’s finalized state.

Prover time against Coordinator time

What the Prover reported:

JobDuration
Execution187.475 s
Compression0.0835 s
Aggregation0.0422 s

Compression and aggregation run in dev mode, which is why they’re near zero.

From the Coordinator’s side, execution took:

07:55:18.273 - 07:52:09.868 = 188.405 s

About 0.9 seconds more than the Prover’s number. The Coordinator’s window also covers the wait before the controller picked the request up (0.8 s from the request to the command line) and the wait before the Coordinator noticed the response (96 ms). The two measurements start and stop at different points.

UTCComponentEvent
07:52:09.451CoordinatorBatch and blob created
07:52:09.868CoordinatorExecution request written
07:52:10.701ProverExecution command logged
07:55:18.177ProverExecution done, response published
07:55:18.273CoordinatorExecution completion recorded
07:55:18.312ProverCompression done, response published
07:55:18.331CoordinatorCompression completion recorded
07:55:25.372ProverAggregation done, response published
07:55:25.382CoordinatorAggregation completion recorded
07:55:27.743CoordinatorAggregation submitted to L1

Back to L1

2026-10-14 07:55:27.743+0000
AggregationSubmitterImpl
submitted aggregation=[26605..26684]80
transactionHash=0x6168dde06f3ab8a8bd4737ec32cb7c5e45e6f4f613c35ac6c82fd1128c75cba2
nonce=314 gasPriceCaps=null

The Coordinator reads the proof response and builds the contract call from it. The response file itself never goes to L1.

The next post follows a range from here to L1: blob publication, then the aggregation transaction landing on the rollup contract, which emits DataFinalizedV3 for the range.


Share this post:

Previous Post
Lineth L2 Transaction Lifecycle (4) - Following an L2 Blob from Creation to Finalization on L1
Next Post
Lineth L2 Transaction Lifecycle (2) - From L2 Blocks to Execution Proofs: Trace and Conflation in the Coordinator