Skip to content

Commit 3958151

Browse files
committed
fix
1 parent 84eb3b1 commit 3958151

12 files changed

Lines changed: 61 additions & 30 deletions

collector/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ AZTEC_ADMIN_URL=http://127.0.0.1:8880
1010
AZTEC_ADMIN_API_KEY=
1111
AZTEC_SENTINEL_POLL_INTERVAL_MS=60000
1212
AZTEC_SENTINEL_LOOKBACK_EPOCHS=3
13+
AZTEC_SENTINEL_EPOCH_END_BUFFER_SLOTS=2
1314
AZTEC_SENTINEL_VALIDATOR_CONCURRENCY=8
1415
AZTEC_SENTINEL_VALIDATOR_MAX_RESPONSE_BYTES=2097152
1516

collector/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Configuration is intentionally small:
2727
| Area | Variables |
2828
| --- | --- |
2929
| Identity | `SLASHMON_NETWORK`, `SLASHMON_PUBLIC_URL` |
30-
| Aztec node | `AZTEC_NODE_URL`, `AZTEC_NODE_API_KEY`, `AZTEC_ADMIN_URL`, `AZTEC_ADMIN_API_KEY`; optional `AZTEC_SENTINEL_POLL_INTERVAL_MS`, `AZTEC_SENTINEL_LOOKBACK_EPOCHS`, `AZTEC_SENTINEL_VALIDATOR_CONCURRENCY`, `AZTEC_SENTINEL_VALIDATOR_MAX_RESPONSE_BYTES` |
30+
| Aztec node | `AZTEC_NODE_URL`, `AZTEC_NODE_API_KEY`, `AZTEC_ADMIN_URL`, `AZTEC_ADMIN_API_KEY`; optional `AZTEC_SENTINEL_POLL_INTERVAL_MS`, `AZTEC_SENTINEL_LOOKBACK_EPOCHS`, `AZTEC_SENTINEL_EPOCH_END_BUFFER_SLOTS`, `AZTEC_SENTINEL_VALIDATOR_CONCURRENCY`, `AZTEC_SENTINEL_VALIDATOR_MAX_RESPONSE_BYTES` |
3131
| Ethereum | `L1_RPC_URL`, optional `L1_REGISTRY_ADDRESS`, `L1_SLASH_LOG_LOOKBACK_BLOCKS` |
3232
| Telegram | `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_USERNAME` |
3333
| Web Push | `VAPID_SUBJECT`, `VAPID_PUBLIC_KEY`, `VAPID_PRIVATE_KEY` |

collector/deploy/slashmon-backend.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ AZTEC_ADMIN_URL=http://127.0.0.1:8880
1111
AZTEC_ADMIN_API_KEY=
1212
AZTEC_SENTINEL_POLL_INTERVAL_MS=60000
1313
AZTEC_SENTINEL_LOOKBACK_EPOCHS=3
14+
AZTEC_SENTINEL_EPOCH_END_BUFFER_SLOTS=2
1415
AZTEC_SENTINEL_VALIDATOR_CONCURRENCY=8
1516
AZTEC_SENTINEL_VALIDATOR_MAX_RESPONSE_BYTES=2097152
1617

collector/src/admin-client.mjs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -296,11 +296,6 @@ export function parseInactivityConfig(value) {
296296
'Aztec admin slashInactivityConsecutiveEpochThreshold',
297297
1,
298298
),
299-
epochEndBufferSlots: parseSafeInteger(
300-
value.sentinelEpochEndBufferSlots,
301-
'Aztec admin sentinelEpochEndBufferSlots',
302-
0,
303-
),
304299
};
305300
}
306301

collector/src/config.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ export function loadConfig(env = process.env, cwd = process.cwd()) {
6868
1,
6969
24,
7070
),
71+
sentinelEpochEndBufferSlots: readInteger(
72+
env,
73+
'AZTEC_SENTINEL_EPOCH_END_BUFFER_SLOTS',
74+
2,
75+
0,
76+
10_000,
77+
),
7178
sentinelValidatorConcurrency: readInteger(
7279
env,
7380
'AZTEC_SENTINEL_VALIDATOR_CONCURRENCY',

collector/src/main.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ async function main() {
7676
pollIntervalMs: config.sentinelPollIntervalMs,
7777
maxBackoffMs: config.maxBackoffMs,
7878
lookbackEpochs: config.sentinelLookbackEpochs,
79+
epochEndBufferSlots: config.sentinelEpochEndBufferSlots,
7980
validatorConcurrency: config.sentinelValidatorConcurrency,
8081
maxStallMs: config.syncMaxL2StallMs,
8182
logger,

collector/src/sentinel-collector.mjs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class SentinelCollector {
1414
pollIntervalMs,
1515
maxBackoffMs,
1616
lookbackEpochs = 3,
17+
epochEndBufferSlots = 2,
1718
validatorConcurrency = 8,
1819
maxStallMs = 5 * 60_000,
1920
logger,
@@ -28,6 +29,10 @@ export class SentinelCollector {
2829
this.pollIntervalMs = pollIntervalMs;
2930
this.maxBackoffMs = maxBackoffMs;
3031
this.lookbackEpochs = requirePositiveSafeInteger(lookbackEpochs, 'sentinel lookback epochs');
32+
this.epochEndBufferSlots = requireUnsignedSafeInteger(
33+
epochEndBufferSlots,
34+
'sentinel epoch-end buffer',
35+
);
3136
this.validatorConcurrency = requirePositiveSafeInteger(
3237
validatorConcurrency,
3338
'sentinel validator concurrency',
@@ -63,6 +68,12 @@ export class SentinelCollector {
6368
const l1State = this.repository.getSourceState('l1');
6469
const l1Metadata = l1State?.metadata;
6570
const canonicalRollupAddress = readCanonicalRollupAddress(this.repository);
71+
if (!canonicalRollupAddress) {
72+
return this.recordPollFailure(
73+
'Canonical L1 Rollup is unavailable; validator duties are not trusted yet',
74+
attemptedAt,
75+
);
76+
}
6677
let l1Checkpoint;
6778
let epochDuration;
6879
let confirmedL1Slot;
@@ -86,12 +97,6 @@ export class SentinelCollector {
8697
} catch (error) {
8798
return this.recordPollFailure(errorMessage(error), attemptedAt);
8899
}
89-
if (!canonicalRollupAddress) {
90-
return this.recordPollFailure(
91-
'Canonical L1 Rollup is unavailable; validator duties are not trusted yet',
92-
attemptedAt,
93-
);
94-
}
95100

96101
const controller = new AbortController();
97102
this.activeRequest = controller;
@@ -116,7 +121,7 @@ export class SentinelCollector {
116121
syncedL2Slot,
117122
confirmedL1Slot,
118123
epochDuration,
119-
epochEndBufferSlots: config.epochEndBufferSlots,
124+
epochEndBufferSlots: this.epochEndBufferSlots,
120125
});
121126
if (cursor && ready <= cursor.epoch) {
122127
return this.recordSuccess({
@@ -149,7 +154,7 @@ export class SentinelCollector {
149154
syncedL2Slot,
150155
confirmedL1Slot,
151156
epochDuration,
152-
epochEndBufferSlots: config.epochEndBufferSlots,
157+
epochEndBufferSlots: this.epochEndBufferSlots,
153158
});
154159
if (ready < 0 || (cursor && ready <= cursor.epoch)) {
155160
return this.recordSuccess({
@@ -254,7 +259,7 @@ export class SentinelCollector {
254259
? input.observedAt
255260
: input.prior.lastSyncProgressAt,
256261
nodeReady: Boolean(input.syncStatus.ready),
257-
epochEndBufferSlots: input.config.epochEndBufferSlots,
262+
epochEndBufferSlots: this.epochEndBufferSlots,
258263
targetPercentage: input.config.targetPercentage,
259264
consecutiveEpochThreshold: input.config.consecutiveEpochThreshold,
260265
epochDuration: String(input.epochDuration),
@@ -388,7 +393,6 @@ async function mapWithConcurrency(values, concurrency, operation) {
388393

389394
function readStoredConfig(metadata) {
390395
if (
391-
metadata.epochEndBufferSlots === undefined ||
392396
metadata.targetPercentage === undefined ||
393397
metadata.consecutiveEpochThreshold === undefined
394398
) {
@@ -399,10 +403,6 @@ function readStoredConfig(metadata) {
399403
return undefined;
400404
}
401405
return {
402-
epochEndBufferSlots: requireUnsignedSafeInteger(
403-
metadata.epochEndBufferSlots,
404-
'stored sentinel epoch-end buffer',
405-
),
406406
targetPercentage,
407407
consecutiveEpochThreshold: requirePositiveSafeInteger(
408408
metadata.consecutiveEpochThreshold,

collector/test/admin-client.test.mjs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,9 @@ test('single-validator stats and inactivity config parsers retain the complete d
203203
assert.deepEqual(parseInactivityConfig({
204204
slashInactivityTargetPercentage: 0.7,
205205
slashInactivityConsecutiveEpochThreshold: 2,
206-
sentinelEnabled: true,
207-
sentinelEpochEndBufferSlots: 2,
208206
}), {
209207
targetPercentage: 0.7,
210208
consecutiveEpochThreshold: 2,
211-
epochEndBufferSlots: 2,
212209
});
213210
assert.throws(() => parseSingleValidatorStats({
214211
validator: {
@@ -222,8 +219,6 @@ test('single-validator stats and inactivity config parsers retain the complete d
222219
assert.throws(() => parseInactivityConfig({
223220
slashInactivityTargetPercentage: 1.1,
224221
slashInactivityConsecutiveEpochThreshold: 2,
225-
sentinelEnabled: true,
226-
sentinelEpochEndBufferSlots: 2,
227222
}), /between 0 and 1/);
228223
});
229224

@@ -253,8 +248,6 @@ test('bounded validator stats use the public node while inactivity thresholds us
253248
: {
254249
slashInactivityTargetPercentage: 0.7,
255250
slashInactivityConsecutiveEpochThreshold: 2,
256-
sentinelEnabled: true,
257-
sentinelEpochEndBufferSlots: 2,
258251
};
259252
return new Response(JSON.stringify({ jsonrpc: '2.0', id: body.id, result }), {
260253
headers: { 'content-type': 'application/json' },

collector/test/config.test.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ test('loadConfig provides a complete local configuration', () => {
2323
assert.equal(config.l1SlashLogLookbackBlocks, 50_000);
2424
assert.equal(config.sentinelPollIntervalMs, 60_000);
2525
assert.equal(config.sentinelLookbackEpochs, 3);
26+
assert.equal(config.sentinelEpochEndBufferSlots, 2);
2627
assert.equal(config.sentinelValidatorConcurrency, 8);
2728
assert.equal(config.maxSingleValidatorStatsResponseBytes, 2 * 1024 * 1024);
2829
assert.equal(config.telegram, undefined);
@@ -71,6 +72,10 @@ test('operator-facing URLs and process settings are validated', () => {
7172
() => loadConfig({ AZTEC_SENTINEL_LOOKBACK_EPOCHS: '0' }),
7273
/between 1 and 24/,
7374
);
75+
assert.throws(
76+
() => loadConfig({ AZTEC_SENTINEL_EPOCH_END_BUFFER_SLOTS: '-1' }),
77+
/between 0 and 10000/,
78+
);
7479
assert.throws(
7580
() => loadConfig({ AZTEC_SENTINEL_VALIDATOR_CONCURRENCY: '0' }),
7681
/between 1 and 128/,

collector/test/sentinel-index.test.mjs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,29 @@ const REGISTRY = '0x0000000000000000000000000000000000000001';
1919
const ROLLUP = '0x0000000000000000000000000000000000000002';
2020
const BLOCK_HASH = `0x${'10'.repeat(32)}`;
2121

22+
test('sentinel collector reports an unavailable L1 dependency before parsing its metadata', async () => {
23+
const repository = new OffenseRepository(':memory:');
24+
try {
25+
const collector = new SentinelCollector({
26+
client: {},
27+
committeeScanner: {},
28+
repository,
29+
expectedChainId: 1,
30+
expectedRegistryAddress: REGISTRY,
31+
pollIntervalMs: 60_000,
32+
maxBackoffMs: 60_000,
33+
logger: silentLogger,
34+
now: () => 1_000,
35+
});
36+
37+
const result = await collector.runOnce();
38+
assert.equal(result.ok, false);
39+
assert.match(result.error, /Canonical L1 Rollup is unavailable/);
40+
} finally {
41+
repository.close();
42+
}
43+
});
44+
2245
test('sentinel collector indexes only L1 committee members for the shared three-epoch lookback', async () => {
2346
const repository = new OffenseRepository(':memory:');
2447
let now = 1_000;
@@ -156,7 +179,6 @@ test('a cursor gap applies the same three epochs to L1 committees and node histo
156179
coverageGeneration: 0,
157180
nodeSyncedSlot: '5',
158181
lastSyncProgressAt: 100,
159-
epochEndBufferSlots: 2,
160182
targetPercentage: 0.7,
161183
consecutiveEpochThreshold: 2,
162184
}, 100);
@@ -369,7 +391,6 @@ function config() {
369391
return {
370392
targetPercentage: 0.7,
371393
consecutiveEpochThreshold: 2,
372-
epochEndBufferSlots: 2,
373394
};
374395
}
375396

0 commit comments

Comments
 (0)