Description
This issue covers the migration of existing services from the current polling-based approach to the event-driven architecture using the library implemented in the issue #731 .
Current State
Services currently run polling loops that periodically query the database:
- EvmReader: Polls L1 for inputs and epoch events, writes to DB
- Advancer: Polls for
CLOSED epochs and unprocessed inputs
- Validator: Polls for
INPUTS_PROCESSED epochs
- Claimer: Polls for
CLAIM_CALCULATED epochs
- PRT: Polls for
CLAIM_CALCULATED epochs and dispute events
Target State
Services will:
- Subscribe to relevant events on startup
- React to events as they arrive
- Perform periodic sync as fallback (hybrid pattern)
- Publish events when they cause state transitions
Tasks
EvmReader
Advancer
Validator
Claimer
PRT
General
Hybrid Pattern
Each service should implement the following pattern for resilience:
func (s *Service) Run(ctx context.Context) error {
// 1. Initial sync on startup (catch up on anything missed while offline)
if err := s.syncPendingWork(ctx); err != nil {
return err
}
// 2. Subscribe to relevant events
ch, err := s.bus.Subscribe(ctx, s.eventFilter())
if err != nil {
return err
}
// 3. Periodic sync ticker (catch any missed events)
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
// 4. Main loop
for {
select {
case event := <-ch:
s.handleEvent(ctx, event)
case <-ticker.C:
s.syncPendingWork(ctx)
case <-ctx.Done():
return ctx.Err()
}
}
}
This ensures:
- No work is missed during service restarts
- Events that arrive while processing are not lost
- System remains functional even if event delivery fails
Note on event ordering
The select statement in Go does not guarantee ordering when multiple cases are ready. However, this is acceptable for our design because:
- Events are hints, not commands — they signal that work may be available
- The authoritative state is always in the database
- Business invariants (epoch ordering, sequential processing) are enforced by database queries, not event order
- Services must be idempotent — receiving the same event twice or out of order should not cause incorrect behavior
If stricter ordering guarantees are needed in the future, the library can be extended to use a priority queue or sequence numbers.
Acceptance Criteria
Dependencies
Migration Strategy
The migration can be done incrementally per service:
- Phase 1: Add event publishing to EvmReader (no consumers yet)
- Phase 2: Migrate Advancer to consume events + publish its own
- Phase 3: Migrate Validator
- Phase 4: Migrate Claimer and PRT
Each phase can be merged independently, and the system will continue working during migration since the polling fallback remains active.
Notes
- Keep polling as fallback, don't remove it entirely
- The periodic sync interval can be tuned based on operational experience
- Monitor event queue depths to detect backpressure issues
Description
This issue covers the migration of existing services from the current polling-based approach to the event-driven architecture using the library implemented in the issue #731 .
Current State
Services currently run polling loops that periodically query the database:
CLOSEDepochs and unprocessed inputsINPUTS_PROCESSEDepochsCLAIM_CALCULATEDepochsCLAIM_CALCULATEDepochs and dispute eventsTarget State
Services will:
Tasks
EvmReader
epoch.openedwhen creating a new epochepoch.closedwhen closing an epochinput.receivedwhen storing new inputsapp.registeredwhen registering new applicationsAdvancer
epoch.closedandinput.receivedeventsepoch.inputs_processedwhen completing epoch processingValidator
epoch.inputs_processedeventsepoch.claim_calculatedwhen proof is readyClaimer
epoch.claim_calculatedeventsepoch.claim_submittedwhen submitting to L1epoch.claim_acceptedorepoch.claim_rejectedbased on L1 resultapp.inoperablewhen proof is rejected (quorum)PRT
epoch.claim_calculatedeventsepoch.claim_submitted,epoch.claim_accepted,epoch.claim_rejectedapp.inoperablewhen proof is rejectedGeneral
Hybrid Pattern
Each service should implement the following pattern for resilience:
This ensures:
Acceptance Criteria
Dependencies
Migration Strategy
The migration can be done incrementally per service:
Each phase can be merged independently, and the system will continue working during migration since the polling fallback remains active.
Notes