ICRC icon indicating copy to clipboard operation
ICRC copied to clipboard

ICRC-72 Extension - ICRC-77 Event Message Replay Interface

Open skilesare opened this issue 1 year ago • 1 comments

Reserved by the Event WG.

skilesare avatar Apr 29 '24 18:04 skilesare

ICRC Title Author Discussions Status Type Category Created
77 Event Replay for ICRC‑72 Austin Fatheree (@skilesare), IC Events WG https://github.com/dfinity/ICRC/issues/77 Draft Standards Track Protocols 2025-09-08

ICRC‑77: Event Replay for ICRC‑72 (Full Draft)

ICRC‑77 defines a minimal, interoperable mechanism for replaying ICRC‑72 events for subscribers that missed notifications. It standardizes how a requester specifies a namespace and range, how systems procure historical events (potentially from external archives), and how events are resent through existing ICRC‑72 delivery paths. Payment for replay is supported.

The design keeps complexity low by:

  • Reusing ICRC‑72 notifications as the data plane (no new delivery endpoint).
  • Allowing a control plane via ICRC‑72 system namespaces or optional simple APIs.
  • Assigning archival responsibility to Publishers (with hooks to external storage such as ICRC‑3 archives), while Orchestrators coordinate and Broadcasters deliver.

Data Representations

Type definitions

// common
type Namespace = text;
type Timestamp = nat;            // UTC nanos
type EventIdentifier = nat;      // ICRC‑72 eventId

// replay ids
type MessageFulfillmentId = nat; // ICRC‑77 replay fulfillment identifier

// ICRC16 shapes
type ICRC16Property = record { name : text; immutable : bool; };
type ICRC16MapItem = record { text; ICRC16 };
type ICRC16ValueMapItem = record { ICRC16; ICRC16 };
type ICRC16Map = vec ICRC16MapItem;
type ICRC16 = variant { /* see ICRC‑16 */ };

// Request to replay a range of events in a namespace
// Range is closed‑open [from, opt to]
// If to=null, implementer returns the terminal event id in the first notification header icrc77:replay:end
// so the client can detect completion deterministically.
 type ReplayRegistration = record {
  namespace : text;
  range : record { nat; opt nat };
  config : ICRC16Map; // filters, skip, payment hints
  memo : blob;        // optional 32 bytes
};

// Result for registering one replay request
 type ReplayRegisterError = variant {
  Unauthorized; // caller not authorized
  UnauthorizedSubscriber : record { namespace : Namespace };
  ImproperConfig : text;        // invalid range/filter/etc.
  PaymentRequired : text;       // pricing policy requires payment
  RangeTooLarge : text;         // exceeds capability/limits
  GenericError : record { error_code : nat; message : text };
  GenericBatchError : text;
  PublicationNotFound;
  SubscriptionNotFound;
};

 type ReplayRegisterResult = opt variant {
  Ok : ReplayId;
  Err : ReplayRegisterError;
};

// Optional status query type
 type ReplayState = variant { pending; running; completed : nat; canceled : nat; errored : text };

 type ReplayStatus = record {
  replayId : ReplayId;
  namespace : text;
  config : ICRC16Map;
  stats : ICRC16Map;
  range : (nat; opt nat;)
  status: ReplayState
 };

 type ReplayStatusUpdate = record{
  replayId: ReplayId;
  status: ReplayState;
 }

 type CancelReplayError = variant {
    Unauthorized; // caller not authorized
    NotFound ;
    CannotCancel : text;
    GenericError : record { error_code : nat; message : text };
    GenericBatchError : text;
 }

Replay Headers (on resent notifications)

Replay delivery uses ICRC‑72 EventNotifications. Implementers MUST append (not overwrite) headers:

Publication Registration Configuration

  • icrc77:replay = #Bool(true)

Published Event Headers

  • icrc77:replay:id = #Nat(replayId)
  • icrc77:replay:end_id = #Nat(eventId) - Last expected EventID(optional - only required when a request did not include the max ID)

Methods

Implementations MUST expose a thin API at the Orchestrator and MUST route control over ICRC‑72 system namespaces (see Control Plane). Minimal APIs:

// Register new replay requests; returns IDs or per‑item errors
icrc77_register_replay : (vec ReplayRegistration) -> (vec ReplayRegisterResult);

// Cancel fulfillments; returns the IDs that were accepted for cancel
icrc77_cancel_replay : (vec ReplayId) -> (vec CancelReplayResult);

// Alert orchestrator of replay fulfillment or error
icrc77_replay_status : (vec ReplayStatusUpdate) -> (vec ReplayStatusUpdateResult);

// Optional: inquire status of fulfillments
icrc77_get_replays : ({
  prev: opt principal;
  take: opt nat;
  filter: opt OrchestrationFilter;
}) -> (vec opt ReplayStatus) query;

Control Plane via ICRC‑72 System Namespaces

All orchestration MAY be conducted using ICRC‑72 system messages to keep the surface minimal and auditable. Recommended deterministic namespaces:

  • Orchestrator → Broadcaster:

    • icrc72:broadcaster:sys:<broadcaster_principal> - Broadcasters and Broadcaster relays MUST be alerted to their responsibilities before the Publisher is notified of the request.
      • Message Data to Assigned Broadcaster: icrc77:broadcaster:replay:add: #Array[#Array[#Nat, #Text, #Blob, #Blob, (#Option(null) or #Text(filter)), (#Option(null) or #Array(#Nat(skipStart), #Nat(skipEnd))), #Blob]] - ReplayID, Namespace, Relay Broadcaster Principal as blob, Subscriber Principal as blob, Filter (optional), Skip (optional), Publisher Principal as blob(optional).
      • Message Data to Assigned Relayer: icrc77:broadcaster:replay_relay:add: #Array[#Array[#Nat, #Text, #Blob, #Blob, (#Option(null) or #Text(filter)), (#Option(null) or #Array(#Nat(skipStart), #Nat(skipEnd)))]] - ReplayID, Namespace, Target Broadcaster Principal as blob, Publisher Principal as blob, Filter (optional), Skip (optional).
      • Message Data to Assigned Relayer: icrc77:broadcaster:replay_relayer:add: #Array[#Nat, #Text, #Blob] - ReplayID, Namespace, relay Principal as blob.
      • Message Data to Assigned Broadcaster: icrc77:broadcaster:replay:remove: #Array[#Array[#Nat]] - ReplayID, Namespace, Subscriber Principal as blob, Publisher principle as blob.
      • Message Data to Assigned Relayer: icrc77:broadcaster:replay_relay:remove: #Array[#Nat] - ReplayID, Namespace, relay Principal as blob, publisher Principal as blob.
      • Message Data to Assigned Relayer: icrc77:broadcaster:replay_relayer:remove: #Array[#Nat] - ReplayID, Namespace, relay Principal as blob, publisher Principal as blob.
  • Orchestrator/Broadcaster → Publisher:

    • icrc72:publisher:sys:<publisher_principal>
      • Message Data: icrc77:publisher:replay:add - #Array[#Array[#Nat, #Text, #Nat, #Opt(#Nat), #Blob]] - ReplayId, Namespace, Start, Optional End, Broadcaster.
      • Message Data: icrc77:publisher:replay:remove - #Array[#Nat] - ReplayId
  • Broadcaster → Subscriber

    • icrc72:publisher:sys:<publisher_principal>
      • Message Data: icrc77:subscriber:replay:add - #Array[#Array[#Nat, #Blob]] - ReplayId, Broadcaster.
      • Message Data: icrc77:subscriber:replay:remove - #Array[#Array[#Nat]] - ReplayId.

Replay Life Cycle

  1. Subscriber registers a replay via icrc77_register_replay (or via a higher‑level call that emits the control message). The Subscriber MUST be authorized for the namespace (e.g., have a valid subscription).
  2. Orchestrator validates the request (authorization, publication existence, range bounds, optional payment). On success, it assigns a capable Broadcaster, Publisher, and a MessageFulfillmentId.
  3. Orchestrator emits an ICRC‑72 system message to the assigned Broadcaster/Broadcaster Replay.
  4. Orchestrator emits an ICRC‑72 system message to the assigned Publisher Replay.
  5. Publisher procures historical events (via async archival hooks such as ICRC‑3 archives) and re‑publishes them via ICRC‑72 publish into the existing data plane. Replay headers are appended.
  6. Broadcasters fan‑out to the Subscriber per normal ICRC‑72 routing. Optional deterministic replay streams MAY be used if isolation from live traffic is desired.
  7. Publisher reports progress and completion via icrc77_replay_status endpoint.
  8. A cancel request stops enqueuing further events; sent notifications are not recalled.

Authorization and Policy

  • Requesters MUST be authorized to receive the target namespace (i.e., must be Subscribers under ICRC‑72 or otherwise permitted by publication policy).
  • Orchestrators MUST select a Publisher that advertises ICRC‑77 capability for the namespace and respect publisher‑declared limits.

Filters and Skip

  • Implementations SHOULD accept filters and skip policies via ICRC‑16 config entries. Minimal profile:
    • filter: #Opt(#Text("…")) – grammar implementation‑defined; SHOULD reference ICRC‑16 path notation if available.
    • skip: #Opt(#Array([#Nat(seed), #Nat(offset?)])).
  • Unsupported filter/skip MUST result in ImproperConfig.

Payment

  • Implementations MAY require payment for replay.
  • Publication metadata SHOULD advertise pricing policy and accepted payment schemes.
  • Requests without required payment MUST return PaymentRequired.

Publisher Capabilities Metadata

Publishers SHOULD advertise capabilities either via a metadata endpoint or via Orchestrator aggregation:

Suggested ICRC‑16 keys:

  • icrc77:capable = #Bool(true)
  • icrc77:pricing = #Class([{name="perEvent"; value=#Nat}, {name="currency"; value=#Text("icrcX:…")}])

Error Handling

Errors SHOULD follow the patterns in ICRC‑7 and use itemized results for batch requests. Core errors include:

  • Unauthorized, UnauthorizedSubscriber, ImproperConfig, PaymentRequired, RangeTooLarge, GenericError, GenericBatchError.

Security Considerations

  • Idempotency: Subscribers SHOULD deduplicate by (namespace, eventId) since replay generates new notificationIds.
  • Abuse prevention: Orchestrators SHOULD enforce quotas/rate limits and size caps; Publishers SHOULD implement back‑pressure.
  • Data integrity: Implementers MAY include icrc72:eventData:hash headers and verify during archival fetch.

Compatibility with ICRC‑72

  • Delivery uses the existing notification path; no change to icrc72_publish wire format.
  • Headers are append‑only, preserving ICRC‑72 rules.
  • Confirmation flow is unchanged; replayed notifications require normal confirmation.

skilesare avatar Sep 26 '25 16:09 skilesare