Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions agent/ACTOR_AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ exercise so production responsibility size remains visible.
- Ephemeral effect state: correlation IDs, collector addresses, timer handles, early-arrival
buffers, and in-flight submission guards. These are grouped and named rather than mixed into
protocol state.
- External authority: EVM contract state. Writer preflights provide cross-restart idempotency where
no durable local outbox exists.
- External authority: EVM contract state. Writer preflights provide cross-restart idempotency, and
startup pairs durable effect intents with their completion events before re-driving open loops.

`Persistable::try_mutate` now accepts the snapshot write into the bounded store mailbox before it
exposes the new value in memory. This does not turn snapshots into an external-effect outbox; the
Expand All @@ -59,7 +59,8 @@ construction, and cohesive FHE/math algorithms may exceed 300 lines. Large non-a
infrastructure coordinators—notably `CiphernodeBuilder`, `NetInterface`, and the multithread task
pool—need their own behavior-preserving projects if their responsibilities are changed.

The remaining architectural gap is durable effect intent. Transaction submission and some
cryptographic work still rely on replay plus external preflight instead of a versioned local
intent/result outbox. That is a schema and recovery change, not a safe file-movement refactor, and
must be implemented with migration and crash-matrix tests.
The append-only event log now supplies durable effect intent: startup scans it in bounded pages,
pairs supported intents with completion/terminal events, and emits internal `EffectRetry`
envelopes only after `EffectsEnabled`. This closes the snapshot-advanced/open-loop loss mode.
There is still no atomic transaction/receipt outbox or full EVM reorg rollback; contract simulation
and canonical backfill remain the authority when a crash lands between submission and observation.
47 changes: 29 additions & 18 deletions agent/CRATES_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ flowchart TD
Restart[restart] --> Index[reconcile timestamp index in 1024-record pages]
Index --> Schema[schema-version preflight before runtime actor writes]
Schema --> SnapshotMeta[load aggregate cursors and initial HLC floor]
SnapshotMeta --> OpenEffects[scan complete logs for unmatched effect intents]
SnapshotMeta --> Query[query every post-snapshot aggregate]
Query --> Runs[sort 1024-event pages into secure temporary runs]
Runs --> GlobalOrder[bounded-fan-in merge by HLC timestamp]
Expand All @@ -349,7 +350,9 @@ flowchart TD
EvmBackfill --> NetBackfill[bounded historical network sync]
NetBackfill --> Merge[merge and sort EVM plus network history by HLC]
Merge --> Enable[EffectsEnabled]
Enable -->|durable pipeline and fanout fence| PersistHistory[persist and dispatch reconciled history]
OpenEffects --> Retry[bounded set of internal EffectRetry envelopes]
Enable -->|durable pipeline and fanout fence| Retry
Retry -->|durable pipeline and fanout fence| PersistHistory[persist and dispatch reconciled history]
PersistHistory -->|durable pipeline and fanout fence| End[SyncEnded]
End -->|durable pipeline and fanout fence| Live[live operation]
end
Expand All @@ -375,22 +378,29 @@ newer log records. Replay then waits for concurrent acceptance by all current Ev
An unavailable subscriber or a subscriber blocked beyond the bounded acceptance timeout aborts
recovery. An `EventBusBarrier` therefore completes only after the last replay fanout has completed.
A persisted `Shutdown` event from the previous process is classified as infrastructure and is not
replayed into newly constructed actors.
replayed into newly constructed actors. Startup separately scans every aggregate from sequence one
in the same bounded pages, retaining only supported effect intents that have no matching completion
or terminal lifecycle event. This full-log scan covers intents older than the latest snapshot cursor
while bounding retained work by count and encoded size.

The EventBus mailbox remains bounded at `MAILBOX_LIMIT_LARGE` (2,560 messages). The replay producer
no longer attempts to enqueue the entire backlog into that mailbox in one burst, and EventBus
subscriber fanout no longer bypasses downstream mailbox limits. EventStore query responses also
await recipient capacity, preventing a full aggregation mailbox from dropping one aggregate response
and hanging startup. Recovery publishes `EffectsEnabled`, canonical history, and `SyncEnded` as
three separately fenced phases. Runtime log-read failures are returned in the correlated query
response and flow through the existing error paths; a remote sync query therefore cannot panic the
EventStore actor. The fail-stop behavior below applies to durable append/index-write failures. An
event-log or timestamp-index write error panics the affected EventStore before live dispatch. This
preserves durable-before-dispatch safety, but under the default unwind profile an Actix actor panic
is contained at its spawned task boundary: it can kill the store actor and stall the sequencer
without terminating the process. Process-level health supervision would need to detect the stalled
pipeline, but the current runtime does not provide that guarantee. A restart, when it occurs, treats
the event log as authoritative and reconciles missing derived index rows.
and hanging startup. Recovery publishes `EffectsEnabled`, persisted `EffectRetry` envelopes,
canonical history, and `SyncEnded` as separately fenced phases. Retry envelopes contain a closed
set of effect payloads and are consumed only by effect executors, so state-building subscribers do
not observe the original domain event a second time. Persisted retry envelopes are classified as
infrastructure on the next replay; the original intent/completion pair remains the durable recovery
authority. Runtime log-read failures are returned in the correlated query response and flow through
the existing error paths; a remote sync query therefore cannot panic the EventStore actor. The
fail-stop behavior below applies to durable append/index-write failures. An event-log or
timestamp-index write error panics the affected EventStore before live dispatch. This preserves
durable-before-dispatch safety, but under the default unwind profile an Actix actor panic is contained
at its spawned task boundary: it can kill the store actor and stall the sequencer without terminating
the process. Process-level health supervision would need to detect the stalled pipeline, but the
current runtime does not provide that guarantee. A restart, when it occurs, treats the event log as
authoritative and reconciles missing derived index rows.

Those replay guarantees bound local replay memory and file-descriptor use, but they do not make the
whole persistence path synchronously acknowledged. Live publication and the sequencer/store response
Expand Down Expand Up @@ -567,11 +577,11 @@ coalesced by the contract's semantic replay domain across deferred, in-flight, a
submissions. Retryable submission failures release their key; successful or known-benign terminal
results retain it.

The gate is deliberately described as in-memory: there is no durable external-effect outbox or
persisted transaction intent. A crash after snapshot advancement but before receipt classification
can therefore lose the local redrive state, and a crash after submission can require on-chain
reconciliation to distinguish landed from missing work. Closing that gap requires a durable
intent/result state machine and contract preflight, not another process-local set.
The submission gate itself is in-memory, but startup reconstructs its missing work from the full
durable event log. A `SlashProposed` observation closes the matching semantic replay key during
replay; otherwise an `EffectRetry` is delivered after effects are enabled. A crash after
submission is reconciled through the contract's `evidenceConsumed` view before retry, while
`DuplicateEvidence` remains the final race-safe idempotency boundary.

## Program-server trust boundary

Expand Down Expand Up @@ -664,7 +674,8 @@ flowchart LR
| C0/share proof-verification context | Finalized-committee and ciphernode-selector repositories plus global verifier memory | Canonical slots and E3 preset/threshold metadata load before ZK actor startup, then lifecycle events maintain or clear them |
| HLC, EventBus dedup, and admission state | Event pipeline actors in memory | Maximum snapshot/replay HLC; a fresh bounded dedup window is populated by replay and live events |
| Network peer/buffer/interest state | libp2p and network actors in memory | Fresh peer dialing; document interest returns only when selection observations are replayed or redriven |
| Slash-submission replay gate | `SlashingManagerSolWriter` process memory | Rebuilt from replay; not a durable outbox |
| Open effect intents | Per-aggregate append-only event logs | Full bounded intent/completion scan followed by `EffectRetry` after `EffectsEnabled` |
| Slash-submission replay gate | `SlashingManagerSolWriter` process memory | Rebuilt from replay plus the durable open-effect scan |
| Pending transaction nonce allocation | Per-chain writer mutex in memory | Provider pending nonce on restart |
| In-flight accusation votes and timers | Per-E3 accusation actor memory | No complete durable reconstruction; only events inside the replay window may be observed again |
| libp2p identity | Encrypted keypair repository | Decrypt at startup |
Expand Down
10 changes: 9 additions & 1 deletion agent/flow-trace/03_E3_REQUEST_AND_COMMITTEE.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,10 @@ CiphernodeSelector receives WithSortitionTicket<E3Requested>
```
CiphernodeRegistrySolWriter receives TicketGenerated event
└─ Calls contract.submitTicket(e3Id, ticketNumber).send()
├─ Simulates the exact submitTicket(e3Id, ticketNumber) call
│ └─ Treats already-submitted/finalized/deadline/ineligible states as terminal no-ops
└─ If simulation succeeds, calls contract.submitTicket(e3Id, ticketNumber).send()
│ ┌─── ON-CHAIN (CiphernodeRegistryOwnable) ──────────────┐
│ │ │
Expand Down Expand Up @@ -238,6 +241,11 @@ CiphernodeRegistrySolWriter receives TicketGenerated event
│ └─────────────────────────────────────────────────────────┘
```

On restart, the sync service pairs each durable local `TicketGenerated` intent with
`TicketSubmitted`, `CommitteeFinalized`, or `CommitteeFormationFailed`. An unmatched intent is
wrapped in an internal `EffectRetry` after `EffectsEnabled`; the simulation above is the final
idempotency check when a pre-crash transaction landed but its completion log was not persisted.

---

## Step 3: Committee Finalization
Expand Down
5 changes: 3 additions & 2 deletions agent/flow-trace/04_DKG_AND_COMPUTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -1102,5 +1102,6 @@ present in the running ABI catalog is exposed as `UnknownEvmLog` with raw topics

During restart, `ComputeEffectGate` observes replay before compute workers are effects-enabled. It
buffers and deduplicates `ComputeRequest`s, prefers the newest regenerated request, cancels terminal
E3 work, and releases pending jobs only after `EffectsEnabled`. The gate changes effect timing, not
durable event order or audit state.
E3 work, and consumes matching `ComputeResponse` / `ComputeRequestError` events so completed jobs
are not released. The full-log open-effect scan wraps only unmatched requests in `EffectRetry`
after `EffectsEnabled`. The gate changes effect timing, not durable event order or audit state.
8 changes: 7 additions & 1 deletion agent/flow-trace/05_FAILURE_REFUND_SLASHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ Specific triggers:
Runtime note: `processE3Failure()` is a permissionless cleanup path. The Rust `InterfoldSolWriter`
may auto-submit it from any effects-enabled node on the same chain, and it must not depend on
active-aggregator designation because failures can happen before committee finalization or while the
current aggregator is offline.
current aggregator is offline. Before a recovered submission it simulates the exact contract call;
`NoPaymentToRefund` proves the refund was already processed and suppresses the transaction, while
other failures remain visible and retryable on later recovery.

```
Anyone calls: Interfold.processE3Failure(e3Id)
Expand Down Expand Up @@ -442,7 +444,11 @@ AccusationQuorumReached event arrives at SlashingManagerSolWriter
├─ 1. EFFECT AND REPLAY GATE:
│ Before EffectsEnabled (startup replay), retain the intent without sending a transaction
│ Coalesce by the contract replay tuple (chainId, e3Id, accused, proofType)
│ A replayed SlashProposed observation closes the matching deferred tuple
│ After EffectsEnabled, release each retained intent once and track it in flight
│ A full-log scan emits EffectRetry for an unmatched pre-snapshot intent
│ Before sending, read evidenceConsumed(keccak256(chainId,e3Id,accused,proofType))
│ and skip when a pre-crash or primary-submitter transaction already landed
├─ 2. STAGGERED SUBMISSION (fallback submitters):
│ Rank all agreeing voters by address (sorted ascending)
Expand Down
25 changes: 18 additions & 7 deletions agent/flow-trace/06_DEACTIVATION_AND_COMPLETION.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,25 +237,33 @@ On restart:
│ → ThresholdPlaintextAggregatorExtension records this role in the E3 context
│ so a plaintext buffer created later by CiphertextOutputPublished starts
│ with the correct active-aggregator flag
│ 3. Replay EventStore events since last snapshot (effects still disabled)
│ 3. Scan every aggregate from sequence one in bounded pages
│ → Pair local effect intents with their completion/terminal events
│ → Retain only open intents, bounded by count and encoded bytes
│ → Covers an intent that is older than the latest snapshot cursor
│ 4. Replay EventStore events since last snapshot (effects still disabled)
│ → Read each aggregate in 1,024-event pages, sort bounded temporary runs,
│ and perform a bounded-fan-in global merge by HLC timestamp
│ → Each concurrent EventBus fanout is acknowledged before the next event;
│ an unavailable or blocked listener aborts recovery after a bounded wait
│ → Structured progress is emitted every 10,000 EventBus-handled events
│ 4. Fetch historical EVM events from last known block
│ 5. Historical libp2p sync retries failed aggregate fetches after reconnects
│ → ComputeEffectGate buffers requests and consumes matching results/errors
│ 5. Fetch historical EVM events from last known block
│ 6. Historical libp2p sync retries failed aggregate fetches after reconnects
│ and also on bounded retry intervals even without a new connection event
6. Sort & publish merged events by HLC timestamp
7. Sort merged historical events by HLC timestamp
│ → A logical event returned by a peer with its source changed from Local to Net is
│ idempotent when timestamp, stable event ID, and payload match the stored record;
│ a different payload at the same timestamp still fails closed as a collision
│ → ComputeEffectGate has already subscribed and buffers ComputeRequest
│ effects, deduplicating semantic retries while replay is in progress
7. Enable effects (writers may submit only after this point)
8. Enable effects (writers may submit only after this point)
│ → Gate cancels work for terminal E3s and releases only the newest
│ pending request for each in-flight semantic compute operation
│ 8. SyncEnded → live operations begin
│ 9. Persist and fan out EffectRetry for each unmatched intent
│ → The closed retry envelope is consumed only by effect executors
│ → Old retry envelopes are skipped on replay; original intent/result pairs stay authoritative
│ 10. Publish merged historical events, then SyncEnded → live operations begin
└─ Node resumes from where it left off
```

Expand Down Expand Up @@ -325,12 +333,15 @@ flowchart TD

Actors --> Replay["sync(): replay EventStore<br/>effects disabled"]
EventStore --> Replay
EventStore --> OpenEffects["bounded full-log scan<br/>pair effect intents/results"]
Replay --> CommitteeReplay["CommitteePublished replay<br/>restores full committee"]

Replay --> Effects["EffectsEnabled"]
Effects --> Gate["ComputeEffectGate releases replay-safe compute work"]
OpenEffects --> Retry["unmatched EffectRetry envelopes"]
Effects --> Retry

Effects --> Live["Live/historical chain events"]
Retry --> Live["Live/historical chain events"]
Live --> Ciphertext["CiphertextOutputPublished"]
Ciphertext --> CanStart{"full + honest committee<br/>and keyshare actor ready?"}
CanStart -- yes --> NewPlaintext["Create ThresholdPlaintextAggregator<br/>seed buffer with active aggregator role"]
Expand Down
Loading
Loading