Skip to content

Commit d3c3681

Browse files
Antoine de Chevignéclaude
andcommitted
Implement OP Stack batch parsing for L2 block range extraction
- Add full batch data parsing with zlib/brotli decompression - Parse span batch and singular batch formats per OP Stack derivation spec - Extract L2 block count and calculate block range from timestamps - Add finalizePendingOpBatches job to confirm batches on safe block - Add l2BlockTime field to OpChainConfig model - Fix finalizePendingOrbitBatches to include orbitChildConfigs association - Fix hex-to-integer conversion for l1BlockNumber in batch creation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 1b26bca commit d3c3681

File tree

9 files changed

+431
-26
lines changed

9 files changed

+431
-26
lines changed

run/jobs/blockSync.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module.exports = async job => {
2222
return 'Missing parameter';
2323

2424
const workspace = await Workspace.findOne({
25+
subQuery: false,
2526
where: {
2627
name: data.workspace,
2728
'$user.firebaseUserId$': data.userId
@@ -226,11 +227,19 @@ module.exports = async job => {
226227
isBatchTransaction(tx, opConfig.batchInboxAddress)
227228
);
228229

230+
// Get L1 block timestamp for batch parsing
231+
const l1Timestamp = typeof processedBlock.timestamp === 'string' && processedBlock.timestamp.startsWith('0x')
232+
? parseInt(processedBlock.timestamp, 16)
233+
: Number(processedBlock.timestamp);
234+
229235
for (const tx of batchTxs) {
230236
try {
231237
const batchInfo = await getBatchInfo(tx, {
232238
batchInboxAddress: opConfig.batchInboxAddress,
233-
beaconUrl: workspace.beaconUrl
239+
beaconUrl: workspace.beaconUrl,
240+
workspaceId: opConfig.workspaceId,
241+
l1Timestamp: l1Timestamp,
242+
l2BlockTime: opConfig.l2BlockTime || 2
234243
});
235244

236245
if (batchInfo) {
@@ -247,16 +256,24 @@ module.exports = async job => {
247256
});
248257
const nextBatchIndex = lastBatch ? lastBatch.batchIndex + 1 : 0;
249258

259+
// Convert hex values to integers
260+
const l1BlockNumber = typeof batchInfo.l1BlockNumber === 'string' && batchInfo.l1BlockNumber.startsWith('0x')
261+
? parseInt(batchInfo.l1BlockNumber, 16)
262+
: Number(batchInfo.l1BlockNumber);
263+
const l1TransactionIndex = typeof batchInfo.l1TransactionIndex === 'string' && batchInfo.l1TransactionIndex.startsWith('0x')
264+
? parseInt(batchInfo.l1TransactionIndex, 16)
265+
: Number(batchInfo.l1TransactionIndex);
266+
250267
await OpBatch.create({
251268
workspaceId: opConfig.workspaceId,
252269
batchIndex: nextBatchIndex,
253-
l1BlockNumber: batchInfo.l1BlockNumber,
270+
l1BlockNumber: l1BlockNumber,
254271
l1TransactionHash: batchInfo.l1TransactionHash,
255272
l1TransactionId: l1Transaction ? l1Transaction.id : null,
256-
l1TransactionIndex: batchInfo.l1TransactionIndex,
257-
epochNumber: batchInfo.l1BlockNumber, // Epoch is typically the L1 block
273+
l1TransactionIndex: l1TransactionIndex,
274+
epochNumber: l1BlockNumber, // Epoch is typically the L1 block
258275
timestamp: tx.timestamp ? new Date(tx.timestamp * 1000) : new Date(),
259-
txCount: batchInfo.estimatedBlockCount || null,
276+
txCount: batchInfo.blockCount || null,
260277
l2BlockStart: batchInfo.l2BlockStart,
261278
l2BlockEnd: batchInfo.l2BlockEnd,
262279
blobHash: batchInfo.blobHash,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @fileoverview OP Stack batch finalization job.
3+
* Confirms pending batches once parent chain reaches safe block.
4+
* @module jobs/finalizePendingOpBatches
5+
*/
6+
7+
const logger = require('../lib/logger');
8+
const { OpBatch, OpChainConfig, Workspace } = require('../models');
9+
const { Op } = require('sequelize');
10+
11+
module.exports = async () => {
12+
// Find all OP child configs and their parent workspaces
13+
const opConfigs = await OpChainConfig.findAll({
14+
include: [{
15+
model: Workspace,
16+
as: 'parentWorkspace',
17+
required: true
18+
}]
19+
});
20+
21+
let allConfirmedBatches = [];
22+
23+
for (const opConfig of opConfigs) {
24+
try {
25+
const parentWorkspace = opConfig.parentWorkspace;
26+
if (!parentWorkspace || !parentWorkspace.rpcServer) continue;
27+
28+
const client = parentWorkspace.getViemPublicClient();
29+
const safeBlock = await client.getBlock({ blockTag: 'safe' });
30+
31+
logger.info(`Validating OP batches for workspace ${opConfig.workspaceId} against safe block ${safeBlock.number}`);
32+
33+
const pendingBatches = await OpBatch.findAll({
34+
where: {
35+
workspaceId: opConfig.workspaceId,
36+
status: 'pending',
37+
l1BlockNumber: {
38+
[Op.lte]: Number(safeBlock.number)
39+
}
40+
}
41+
});
42+
43+
for (const batch of pendingBatches) {
44+
await batch.update({ status: 'confirmed' });
45+
logger.info(`Confirmed OP batch ${batch.batchIndex} for workspace ${opConfig.workspaceId}`);
46+
}
47+
48+
allConfirmedBatches = allConfirmedBatches.concat(pendingBatches);
49+
} catch (error) {
50+
logger.error(`Error finalizing OP batches for config ${opConfig.id}: ${error.message}`, {
51+
location: 'jobs.finalizePendingOpBatches',
52+
error,
53+
configId: opConfig.id
54+
});
55+
}
56+
}
57+
58+
return `Confirmed ${allConfirmedBatches.length} OP batches`;
59+
};

run/jobs/finalizePendingOrbitBatches.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ const { Op } = require('sequelize');
1010

1111
module.exports = async () => {
1212

13-
const workspaces = await Workspace.findAll({ where: { isTopL1Parent: true } });
13+
const workspaces = await Workspace.findAll({
14+
where: { isTopL1Parent: true },
15+
include: ['orbitChildConfigs']
16+
});
1417

1518
let allPendingBatches = [];
1619
for (const workspace of workspaces) {

run/jobs/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module.exports = {
2020
backfillOrbitMessageDeliveredLogs: require('./backfillOrbitMessageDeliveredLogs'),
2121
storeOrbitDeposit: require('./storeOrbitDeposit'),
2222
finalizePendingOpOutputs: require('./finalizePendingOpOutputs'),
23+
finalizePendingOpBatches: require('./finalizePendingOpBatches'),
2324
linkOpDepositsToL2Txs: require('./linkOpDepositsToL2Txs'),
2425

2526
// Medium Priority

0 commit comments

Comments
 (0)