Skip to content

Commit 519bc3e

Browse files
committed
backfill and FE scan fix
1 parent 6d20f9e commit 519bc3e

15 files changed

Lines changed: 210 additions & 24 deletions

collector/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ AZTEC_SENTINEL_VALIDATOR_MAX_RESPONSE_BYTES=2097152
1717
# One Ethereum RPC. The network selects the chain and Registry.
1818
L1_RPC_URL=http://127.0.0.1:8545
1919
# L1_REGISTRY_ADDRESS=
20+
# Exact mainnet v5 history. Requires an archive RPC.
21+
# L1_SLASH_LOG_START_BLOCK=25533241
22+
# Used only when no exact start block is configured.
2023
L1_SLASH_LOG_LOOKBACK_BLOCKS=50000
2124

2225
# Enable Telegram only when both values are set.

collector/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Node 24 is required. The main settings are:
2828
| --- | --- |
2929
| Identity | `SLASHMON_NETWORK`, `SLASHMON_PUBLIC_URL` |
3030
| Aztec node | `AZTEC_NODE_URL`, `AZTEC_NODE_API_KEY`, `AZTEC_ADMIN_URL`, `AZTEC_ADMIN_API_KEY` |
31-
| Ethereum | one `L1_RPC_URL`; optional Registry and log-lookback overrides |
31+
| Ethereum | one `L1_RPC_URL`; optional Registry and log-history overrides |
3232
| Notifications | optional complete VAPID keypair and/or Telegram bot |
3333
| Process | database path, bind host/port, exact CORS origin, proxy trust, log level |
3434

collector/deploy/slashmon-backend-testing.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ AZTEC_SENTINEL_VALIDATOR_MAX_RESPONSE_BYTES=2097152
2828

2929
L1_RPC_URL=http://127.0.0.1:8545
3030
# L1_REGISTRY_ADDRESS=
31+
# Exact mainnet v5 history. Requires an archive RPC.
32+
# L1_SLASH_LOG_START_BLOCK=25533241
33+
# Used only when no exact start block is configured.
3134
L1_SLASH_LOG_LOOKBACK_BLOCKS=50000
3235

3336
# Use testing-only notification credentials. In particular, Telegram long

collector/deploy/slashmon-backend.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ AZTEC_SENTINEL_VALIDATOR_MAX_RESPONSE_BYTES=2097152
2525
# One Ethereum RPC. The network selects the chain and Registry.
2626
L1_RPC_URL=http://127.0.0.1:8545
2727
# L1_REGISTRY_ADDRESS=
28+
# Exact mainnet v5 history. Requires an archive RPC.
29+
# L1_SLASH_LOG_START_BLOCK=25533241
30+
# Used only when no exact start block is configured.
2831
L1_SLASH_LOG_LOOKBACK_BLOCKS=50000
2932

3033
# Optional notification channels.

collector/src/case-repository.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@ export class CaseRepository {
10161016
});
10171017
this.recordSourceSuccess('l1_slash_logs', {
10181018
initialBackfill: Boolean(chunk.initialBackfill && chunk.hasMore),
1019+
backfillStartBlock: chunk.backfillStartBlock ?? null,
10191020
confirmedBlockNumber: chunk.confirmedBlockNumber,
10201021
rollupAddresses: chunk.rollupAddresses,
10211022
}, observedAt, {

collector/src/config.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ export function loadConfig(env = process.env, cwd = process.cwd()) {
100100
l1MaxHeadAgeMs: 15 * 60_000,
101101
l1MaxHeadStallMs: 2 * 60_000,
102102
l1MaxFutureSkewMs: 2 * 60_000,
103+
l1SlashLogStartBlock: readOptionalInteger(
104+
env,
105+
'L1_SLASH_LOG_START_BLOCK',
106+
0,
107+
1_000_000_000,
108+
),
103109
l1SlashLogLookbackBlocks: readInteger(
104110
env,
105111
'L1_SLASH_LOG_LOOKBACK_BLOCKS',
@@ -260,6 +266,12 @@ function readInteger(env, name, defaultValue, min, max) {
260266
return value;
261267
}
262268

269+
function readOptionalInteger(env, name, min, max) {
270+
const raw = env[name];
271+
if (raw === undefined || raw === '') return undefined;
272+
return readInteger(env, name, undefined, min, max);
273+
}
274+
263275
function readHttpUrl(raw, name) {
264276
let url;
265277
try {

collector/src/l1-scanner.mjs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class L1Scanner {
3131
maxHeadAgeMs = 15 * 60_000,
3232
maxHeadStallMs = 2 * 60_000,
3333
maxFutureSkewMs = 2 * 60_000,
34+
slashLogStartBlock,
3435
slashLogLookbackBlocks = 600,
3536
slashLogChunkSize = 1_000,
3637
slashLogOverlapBlocks = 12,
@@ -48,6 +49,9 @@ export class L1Scanner {
4849
this.maxHeadAgeMs = maxHeadAgeMs;
4950
this.maxHeadStallMs = maxHeadStallMs;
5051
this.maxFutureSkewMs = maxFutureSkewMs;
52+
this.slashLogStartBlock = slashLogStartBlock === undefined
53+
? undefined
54+
: BigInt(slashLogStartBlock);
5155
this.slashLogLookbackBlocks = BigInt(slashLogLookbackBlocks);
5256
this.slashLogChunkSize = BigInt(slashLogChunkSize);
5357
this.slashLogOverlapBlocks = BigInt(slashLogOverlapBlocks);
@@ -63,6 +67,9 @@ export class L1Scanner {
6367
fetchOptions: { signal },
6468
}),
6569
}));
70+
if (this.slashLogStartBlock !== undefined && this.slashLogStartBlock < 0n) {
71+
throw new RangeError('slash log start block must be non-negative');
72+
}
6673
if (this.slashLogLookbackBlocks < 1n) throw new RangeError('slash log lookback must be positive');
6774
if (this.slashLogChunkSize < 2n) throw new RangeError('slash log chunk size must be at least 2');
6875
if (this.slashLogOverlapBlocks < 1n || this.slashLogOverlapBlocks >= this.slashLogChunkSize) {
@@ -252,9 +259,24 @@ export class L1Scanner {
252259
if ((cursorNumber === undefined) !== !cursorHash) {
253260
throw new Error('persisted slash log checkpoint is incomplete');
254261
}
262+
if (
263+
this.slashLogStartBlock !== undefined &&
264+
this.slashLogStartBlock > confirmedBlockNumber
265+
) {
266+
throw new Error(
267+
`configured slash log start block ${this.slashLogStartBlock} is above confirmed L1 head ${confirmedBlockNumber}`,
268+
);
269+
}
270+
const persistedStartBlock = readOptionalBigInt(
271+
previous.metadata?.backfillStartBlock,
272+
);
273+
const restartFromConfiguredStart =
274+
cursorNumber !== undefined &&
275+
this.slashLogStartBlock !== undefined &&
276+
persistedStartBlock !== this.slashLogStartBlock;
255277

256278
let reorgDetected = false;
257-
if (cursorNumber !== undefined) {
279+
if (cursorNumber !== undefined && !restartFromConfiguredStart) {
258280
if (cursorNumber > confirmedBlockNumber) {
259281
reorgDetected = true;
260282
} else {
@@ -268,10 +290,12 @@ export class L1Scanner {
268290
}
269291

270292
let fromBlock;
271-
if (cursorNumber === undefined) {
272-
fromBlock = confirmedBlockNumber + 1n > this.slashLogLookbackBlocks
273-
? confirmedBlockNumber + 1n - this.slashLogLookbackBlocks
274-
: 0n;
293+
if (cursorNumber === undefined || restartFromConfiguredStart) {
294+
fromBlock = this.slashLogStartBlock ?? (
295+
confirmedBlockNumber + 1n > this.slashLogLookbackBlocks
296+
? confirmedBlockNumber + 1n - this.slashLogLookbackBlocks
297+
: 0n
298+
);
275299
} else if (reorgDetected) {
276300
fromBlock = cursorNumber + 1n > this.slashLogReorgRewindBlocks
277301
? cursorNumber + 1n - this.slashLogReorgRewindBlocks
@@ -282,6 +306,12 @@ export class L1Scanner {
282306
? cursorNumber + 1n - this.slashLogOverlapBlocks
283307
: 0n;
284308
}
309+
if (
310+
this.slashLogStartBlock !== undefined &&
311+
fromBlock < this.slashLogStartBlock
312+
) {
313+
fromBlock = this.slashLogStartBlock;
314+
}
285315
const toBlock = minBigInt(confirmedBlockNumber, fromBlock + this.slashLogChunkSize - 1n);
286316
const checkpointBlock = toBlock === confirmedBlockNumber
287317
? confirmedBlock
@@ -364,8 +394,12 @@ export class L1Scanner {
364394
toBlockHash: checkpointBlock.hash,
365395
confirmedBlockNumber: confirmedBlockNumber.toString(),
366396
reorgDetected,
367-
initial: cursorNumber === undefined,
368-
initialBackfill: cursorNumber === undefined || previous.metadata?.initialBackfill === true,
397+
initial: cursorNumber === undefined || restartFromConfiguredStart,
398+
initialBackfill:
399+
cursorNumber === undefined ||
400+
restartFromConfiguredStart ||
401+
previous.metadata?.initialBackfill === true,
402+
backfillStartBlock: this.slashLogStartBlock?.toString() ?? null,
369403
hasMore: toBlock < confirmedBlockNumber,
370404
registryAddress: this.registryAddress,
371405
rollupAddresses: [...knownRollups].sort(),

collector/src/main.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ async function main() {
6565
maxHeadAgeMs: config.l1MaxHeadAgeMs,
6666
maxHeadStallMs: config.l1MaxHeadStallMs,
6767
maxFutureSkewMs: config.l1MaxFutureSkewMs,
68+
slashLogStartBlock: config.l1SlashLogStartBlock,
6869
slashLogLookbackBlocks: config.l1SlashLogLookbackBlocks,
6970
slashLogChunkSize: config.l1SlashLogChunkSize,
7071
slashLogOverlapBlocks: config.l1SlashLogOverlapBlocks,

collector/test/config.test.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ test('loadConfig provides a complete local configuration', () => {
2020
assert.equal(config.port, 8790);
2121
assert.equal(config.l1ChainId, 1);
2222
assert.equal(config.l1RegistryAddress, '0x35b22e09Ee0390539439E24f06Da43D83f90e298');
23+
assert.equal(config.l1SlashLogStartBlock, undefined);
2324
assert.equal(config.l1SlashLogLookbackBlocks, 50_000);
25+
assert.equal(config.l1SlashLogChunkSize, 1_000);
2426
assert.equal(config.sentinelPollIntervalMs, 60_000);
2527
assert.equal(config.sentinelLookbackEpochs, 3);
2628
assert.equal(config.sentinelEpochEndBufferSlots, 2);
@@ -84,13 +86,18 @@ test('operator-facing URLs and process settings are validated', () => {
8486
() => loadConfig({ AZTEC_SENTINEL_VALIDATOR_CONCURRENCY: '0' }),
8587
/between 1 and 128/,
8688
);
89+
assert.throws(
90+
() => loadConfig({ L1_SLASH_LOG_START_BLOCK: '-1' }),
91+
/between 0 and 1000000000/,
92+
);
8793
assert.throws(() => loadConfig({ BACKEND_TRUST_PROXY: 'yes' }), /must be true or false/);
8894
assert.throws(() => loadConfig({ BACKEND_LOG_LEVEL: 'trace' }), /debug, info, warn, or error/);
8995

9096
const config = loadConfig({
9197
SLASHMON_PUBLIC_URL: 'https://slashveto.example/app',
9298
BACKEND_CORS_ORIGIN: 'https://slashveto.example',
9399
L1_RPC_URL: 'https://rpc-one.example/path',
100+
L1_SLASH_LOG_START_BLOCK: '25533241',
94101
L1_SLASH_LOG_LOOKBACK_BLOCKS: '75000',
95102
BACKEND_DATABASE_PATH: '../state/slashmon.sqlite',
96103
BACKEND_PORT: '9000',
@@ -102,6 +109,7 @@ test('operator-facing URLs and process settings are validated', () => {
102109
assert.equal(config.publicUrl, 'https://slashveto.example/app/');
103110
assert.equal(config.corsOrigin, 'https://slashveto.example');
104111
assert.deepEqual(config.l1RpcUrls, ['https://rpc-one.example/path']);
112+
assert.equal(config.l1SlashLogStartBlock, 25_533_241);
105113
assert.equal(config.l1SlashLogLookbackBlocks, 75_000);
106114
assert.equal(config.databasePath, '/srv/state/slashmon.sqlite');
107115
assert.equal(config.port, 9000);

collector/test/l1-collector-v3.test.mjs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,19 @@ test('confirmed Slashed logs attach only to an exact execution case', async () =
3232
logs: [slashLog(1)],
3333
initial: true,
3434
initialBackfill: true,
35+
backfillStartBlock: 1,
3536
});
3637
}
3738
assert.equal(previous.lastBlockNumber, '2');
38-
return slashChunk({ from: 3, to: 4, confirmed: 4, hasMore: false, logs: [] });
39+
return slashChunk({
40+
from: 3,
41+
to: 4,
42+
confirmed: 4,
43+
hasMore: false,
44+
logs: [],
45+
initialBackfill: true,
46+
backfillStartBlock: 1,
47+
});
3948
},
4049
};
4150
const collector = new L1Collector({
@@ -54,7 +63,10 @@ test('confirmed Slashed logs attach only to an exact execution case', async () =
5463
assert.equal(result.ok, true);
5564
assert.equal(result.slashLogs.inserted, 1);
5665
assert.equal(logCalls, 2);
57-
assert.equal(repository.getSourceState('l1_slash_logs').lastBlockNumber, '4');
66+
const source = repository.getSourceState('l1_slash_logs');
67+
assert.equal(source.lastBlockNumber, '4');
68+
assert.equal(source.metadata.initialBackfill, false);
69+
assert.equal(source.metadata.backfillStartBlock, '1');
5870
const [item] = repository.getSequencerRecord(SEQUENCER_A, 'mainnet').cases;
5971
assert.equal(item.targetEpoch, '24');
6072
assert.equal(item.state.stage, 'stake_removed');
@@ -108,7 +120,16 @@ function createRepository() {
108120
return repository;
109121
}
110122

111-
function slashChunk({ from, to, confirmed, logs, hasMore, initial = false, initialBackfill = false }) {
123+
function slashChunk({
124+
from,
125+
to,
126+
confirmed,
127+
logs,
128+
hasMore,
129+
initial = false,
130+
initialBackfill = false,
131+
backfillStartBlock = null,
132+
}) {
112133
return {
113134
chainId: 1,
114135
fromBlock: String(from),
@@ -121,6 +142,9 @@ function slashChunk({ from, to, confirmed, logs, hasMore, initial = false, initi
121142
hasMore,
122143
initial,
123144
initialBackfill,
145+
backfillStartBlock: backfillStartBlock === null
146+
? null
147+
: String(backfillStartBlock),
124148
reorgDetected: false,
125149
};
126150
}

0 commit comments

Comments
 (0)