diff --git a/packages/assertion-monitor/__test__/monitoring.test.ts b/packages/assertion-monitor/__test__/monitoring.test.ts index 91f4549..c53d5fe 100644 --- a/packages/assertion-monitor/__test__/monitoring.test.ts +++ b/packages/assertion-monitor/__test__/monitoring.test.ts @@ -8,7 +8,6 @@ import { NO_CONFIRMATION_EVENTS_ALERT, CONFIRMATION_DELAY_ALERT, CREATION_EVENT_STUCK_ALERT, - NON_BOLD_NO_RECENT_CREATION_ALERT, VALIDATOR_WHITELIST_DISABLED_ALERT, NO_CONFIRMATION_BLOCKS_WITH_CONFIRMATION_EVENTS_ALERT, BOLD_LOW_BASE_STAKE_ALERT, @@ -92,7 +91,8 @@ describe('Assertion Health Monitoring', () => { recentCreationEvent: null, recentConfirmationEvent: null, isValidatorWhitelistDisabled: false, - isBaseStakeBelowThreshold: false + isBaseStakeBelowThreshold: false, + lastBlockIncludedInBatch: 800n, } } @@ -141,13 +141,15 @@ describe('Assertion Health Monitoring', () => { expect(alerts[0]).toBe(NO_CREATION_EVENTS_ALERT) }) - test('should alert when chain has activity but no recent creation events', async () => { + test('should alert when batches posted but no recent creation events', async () => { const chainState = createBaseChainState() - // Set creation event to be older than the recent activity threshold (4 hours) + // Set creation event to be older than the recent activity threshold chainState.childLatestCreatedBlock = { ...chainState.childLatestCreatedBlock!, timestamp: NOW - BigInt(5 * 60 * 60), // 5 hours ago + number: 900n, } as Block + chainState.lastBlockIncludedInBatch = 1000n const alerts = await analyzeAssertionEvents( chainState, @@ -162,6 +164,24 @@ describe('Assertion Health Monitoring', () => { expect(alerts).toContain(CHAIN_ACTIVITY_WITHOUT_ASSERTIONS_ALERT) }) + test('should NOT alert when no batches posted (low activity)', async () => { + const chainState = createBaseChainState() + chainState.childLatestCreatedBlock = { + ...chainState.childLatestCreatedBlock!, + timestamp: NOW - BigInt(5 * 60 * 60), // 5 hours ago + number: 900n, + } as Block + chainState.lastBlockIncludedInBatch = 800n + + const alerts = await analyzeAssertionEvents( + chainState, + mockChainInfo, + true + ) + + expect(alerts).not.toContain(CHAIN_ACTIVITY_WITHOUT_ASSERTIONS_ALERT) + }) + test('should alert when no confirmation events exist', async () => { const chainState = createBaseChainState() chainState.childLatestConfirmedBlock = undefined @@ -295,6 +315,8 @@ describe('Assertion Health Monitoring', () => { number: 1800n, } as Block + chainState.lastBlockIncludedInBatch = 1900n + // Set values to trigger confirmation delay chainState.childCurrentBlock = { ...chainState.childCurrentBlock!, @@ -531,13 +553,16 @@ describe('Assertion Health Monitoring', () => { expect(alerts[0]).toBe(NO_CREATION_EVENTS_ALERT) }) - test('should alert when no recent creation events for non-BOLD chain', async () => { + test('should alert when batches posted but no recent creation events for non-BOLD chain', async () => { const chainState = createBaseChainState() - // Set creation event to be older than the recent activity threshold (4 hours) + // Set creation event to be older than the recent activity threshold chainState.childLatestCreatedBlock = { ...chainState.childLatestCreatedBlock!, timestamp: NOW - BigInt(5 * 60 * 60), // 5 hours ago + number: 900n, // arbitrary block number for last assertion } as Block + // any value > 900 triggers alert (batches exist beyond last assertion) + chainState.lastBlockIncludedInBatch = 1000n const alerts = await analyzeAssertionEvents( chainState, @@ -548,8 +573,8 @@ describe('Assertion Health Monitoring', () => { // Check if alerts array exists and has at least one element expect(alerts.length).toBeGreaterThan(0) - // Check for expected alert - expect(alerts).toContain(NON_BOLD_NO_RECENT_CREATION_ALERT) + // Check for expected alert (same alert for both BOLD and non-BOLD) + expect(alerts).toContain(CHAIN_ACTIVITY_WITHOUT_ASSERTIONS_ALERT) }) test('should alert when no confirmation events exist for non-BOLD chain', async () => { @@ -610,7 +635,9 @@ describe('Assertion Health Monitoring', () => { chainState.childLatestCreatedBlock = { ...chainState.childLatestCreatedBlock!, timestamp: NOW - BigInt(7 * 24 * 60 * 60), // 7 days ago + number: 900n, } as Block + chainState.lastBlockIncludedInBatch = 1000n const alerts = await analyzeAssertionEvents( chainState, @@ -618,11 +645,8 @@ describe('Assertion Health Monitoring', () => { false ) - // The implementation will generate other alerts, but not CREATION_EVENT_STUCK_ALERT expect(alerts).not.toContain(CREATION_EVENT_STUCK_ALERT) - - // But it should contain the NON_BOLD_NO_RECENT_CREATION_ALERT - expect(alerts).toContain(NON_BOLD_NO_RECENT_CREATION_ALERT) + expect(alerts).toContain(CHAIN_ACTIVITY_WITHOUT_ASSERTIONS_ALERT) }) test('should generate alerts when extreme conditions are met for non-BOLD chain', async () => { @@ -634,6 +658,8 @@ describe('Assertion Health Monitoring', () => { number: 900n, } as Block + chainState.lastBlockIncludedInBatch = 1000n + // Set parent chain blocks to indicate a delay chainState.parentCurrentBlock = { ...chainState.parentCurrentBlock!, @@ -664,7 +690,6 @@ describe('Assertion Health Monitoring', () => { // Check for required alerts expect(alerts).toContain(CHAIN_ACTIVITY_WITHOUT_ASSERTIONS_ALERT) - expect(alerts).toContain(NON_BOLD_NO_RECENT_CREATION_ALERT) expect(alerts).toContain(CONFIRMATION_DELAY_ALERT) }) diff --git a/packages/assertion-monitor/alerts.ts b/packages/assertion-monitor/alerts.ts index 21906cc..e809b89 100644 --- a/packages/assertion-monitor/alerts.ts +++ b/packages/assertion-monitor/alerts.ts @@ -1,6 +1,6 @@ export const NO_CREATION_EVENTS_ALERT = `No assertion creation events found` -export const CHAIN_ACTIVITY_WITHOUT_ASSERTIONS_ALERT = `Chain activity detected, but no assertions created in the last 4 hours` +export const CHAIN_ACTIVITY_WITHOUT_ASSERTIONS_ALERT = `Batches have been posted but no assertions created in the last 4 hours` export const NO_CONFIRMATION_EVENTS_ALERT = `No assertion confirmation events found` @@ -8,8 +8,6 @@ export const CONFIRMATION_DELAY_ALERT = `Confirmation period exceeded` export const CREATION_EVENT_STUCK_ALERT = `Assertion event stuck in challenge period` -export const NON_BOLD_NO_RECENT_CREATION_ALERT = `No recent node creation events detected for non-BOLD chain` - export const VALIDATOR_WHITELIST_DISABLED_ALERT = `Validator whitelist disabled - this may indicate security concerns for Classic chains` export const BOLD_LOW_BASE_STAKE_ALERT = `BoLD chain has low base stake (below 1 ETH) which may indicate restricted validation` diff --git a/packages/assertion-monitor/blockchain.ts b/packages/assertion-monitor/blockchain.ts index fab5511..0498d94 100644 --- a/packages/assertion-monitor/blockchain.ts +++ b/packages/assertion-monitor/blockchain.ts @@ -5,6 +5,7 @@ import { defineChain, getContract, http, + parseAbi, type Block, type Log, } from 'viem' @@ -386,6 +387,22 @@ export const fetchChainState = async ({ isBold ) + let lastBlockIncludedInBatch: bigint | undefined + try { + lastBlockIncludedInBatch = await parentClient.readContract({ + address: childChainInfo.ethBridge.bridge as `0x${string}`, + abi: parseAbi([ + 'function sequencerReportedSubMessageCount() view returns (uint256)', + ]), + functionName: 'sequencerReportedSubMessageCount', + }) + } catch (error) { + console.error( + `Failed to query sequencerReportedSubMessageCount for ${childChainInfo.name}:`, + error + ) + } + const chainState: ChainState = { childCurrentBlock, childLatestCreatedBlock, @@ -399,6 +416,7 @@ export const fetchChainState = async ({ isBaseStakeBelowThreshold, searchFromBlock: fromBlock, searchToBlock: toBlock, + lastBlockIncludedInBatch, } console.log('Built chain state blocks:', { @@ -408,6 +426,7 @@ export const fetchChainState = async ({ parentCurrentBlock: parentCurrentBlock.number, parentBlockAtCreation: parentBlockAtCreation?.number, parentBlockAtConfirmation: parentBlockAtConfirmation?.number, + lastBlockIncludedInBatch, }) return chainState diff --git a/packages/assertion-monitor/monitoring.ts b/packages/assertion-monitor/monitoring.ts index 6fe51e0..12da0e1 100644 --- a/packages/assertion-monitor/monitoring.ts +++ b/packages/assertion-monitor/monitoring.ts @@ -7,7 +7,6 @@ import { NO_CONFIRMATION_BLOCKS_WITH_CONFIRMATION_EVENTS_ALERT, NO_CONFIRMATION_EVENTS_ALERT, NO_CREATION_EVENTS_ALERT, - NON_BOLD_NO_RECENT_CREATION_ALERT, VALIDATOR_WHITELIST_DISABLED_ALERT, } from './alerts' import { @@ -103,12 +102,11 @@ export const analyzeAssertionEvents = async ( const { doesLatestChildCreatedBlockExist, doesLatestChildConfirmedBlockExist, - hasActivityWithoutRecentAssertions, + hasBatchesWithoutRecentAssertions, noConfirmationsWithCreationEvents, noConfirmedBlocksWithConfirmationEvents, confirmationDelayExceedsPeriod, creationEventStuckInChallengePeriod, - nonBoldMissingRecentCreation, isValidatorWhitelistDisabledOnClassic, isBaseStakeBelowThresholdOnBold, } = generateConditionsForAlerts(chainInfo, chainState, isBold) @@ -133,7 +131,7 @@ export const analyzeAssertionEvents = async ( alerts.push(NO_CONFIRMATION_BLOCKS_WITH_CONFIRMATION_EVENTS_ALERT) } - if (hasActivityWithoutRecentAssertions) { + if (hasBatchesWithoutRecentAssertions) { alerts.push(CHAIN_ACTIVITY_WITHOUT_ASSERTIONS_ALERT) } @@ -149,10 +147,6 @@ export const analyzeAssertionEvents = async ( alerts.push(CREATION_EVENT_STUCK_ALERT) } - if (nonBoldMissingRecentCreation) { - alerts.push(NON_BOLD_NO_RECENT_CREATION_ALERT) - } - return alerts } @@ -170,13 +164,12 @@ export const generateConditionsForAlerts = ( const currentTimeSeconds = Number(currentTimestamp / 1000n) const { - childCurrentBlock, childLatestCreatedBlock, childLatestConfirmedBlock, parentCurrentBlock, parentBlockAtConfirmation, - recentCreationEvent, recentConfirmationEvent, + lastBlockIncludedInBatch, } = chainState /** @@ -206,20 +199,23 @@ export const generateConditionsForAlerts = ( const doesLatestChildConfirmedBlockExist = !!childLatestConfirmedBlock /** - * Detects transaction processing in child chain not yet asserted in parent chain - * Normal in small amounts due to batching, concerning in large amounts + * Detects batches posted to parent chain but not yet asserted + * Only alerts when batches exist but no recent assertions cover them */ - const hasActivityWithoutAssertions = - childCurrentBlock?.number && - childLatestCreatedBlock?.number && - childCurrentBlock.number > childLatestCreatedBlock.number + const childLatestCreatedBlockNumber = childLatestCreatedBlock?.number + const hasBatchesWithoutAssertions = + lastBlockIncludedInBatch !== undefined && + childLatestCreatedBlockNumber !== undefined && + childLatestCreatedBlockNumber !== null && + lastBlockIncludedInBatch > childLatestCreatedBlockNumber /** * Critical for BOLD due to finality implications * Indicates validator issues for both chain types + * Only alerts when batches have been posted but not asserted recently */ - const hasActivityWithoutRecentAssertions = - hasActivityWithoutAssertions && !hasRecentCreationEvents + const hasBatchesWithoutRecentAssertions = + hasBatchesWithoutAssertions && !hasRecentCreationEvents /** * May indicate active challenges or technical issues with confirmation @@ -272,15 +268,6 @@ export const generateConditionsForAlerts = ( childLatestCreatedBlock.timestamp < BigInt(currentTimeSeconds - CHALLENGE_PERIOD_SECONDS) - /** - * Only alerts when activity exists without assertions - * May be normal for low-activity chains, hence contextual consideration required - */ - const nonBoldMissingRecentCreation = - !isBold && - (!childLatestCreatedBlock || - (!hasRecentCreationEvents && hasActivityWithoutAssertions)) - /** * Whether a Classic chain's validator whitelist is disabled, allowing * unauthorized validators to post assertions. @@ -301,12 +288,11 @@ export const generateConditionsForAlerts = ( doesLatestChildCreatedBlockExist, doesLatestChildConfirmedBlockExist, hasRecentCreationEvents, - hasActivityWithoutRecentAssertions, + hasBatchesWithoutRecentAssertions, noConfirmationsWithCreationEvents, noConfirmedBlocksWithConfirmationEvents, confirmationDelayExceedsPeriod, creationEventStuckInChallengePeriod, - nonBoldMissingRecentCreation, isValidatorWhitelistDisabledOnClassic, isBaseStakeBelowThresholdOnBold, } diff --git a/packages/assertion-monitor/types.ts b/packages/assertion-monitor/types.ts index c1a39e7..14c8dca 100644 --- a/packages/assertion-monitor/types.ts +++ b/packages/assertion-monitor/types.ts @@ -55,7 +55,7 @@ export interface ChainState { childLatestCreatedBlock?: Block childLatestConfirmedBlock?: Block parentCurrentBlock?: Block - parentBlockAtCreation?: Block + parentBlockAtCreation?: Block parentBlockAtConfirmation?: Block recentCreationEvent: CreationEvent | null recentConfirmationEvent: ConfirmationEvent | null @@ -63,4 +63,6 @@ export interface ChainState { isBaseStakeBelowThreshold: boolean searchFromBlock?: bigint searchToBlock?: bigint + /** Last child block included in a batch */ + lastBlockIncludedInBatch?: bigint }