본문으로 건너뛰기

인증된 이벤트

Authenticated Events는 Sui smart contract의 Move event stream을 암호학적으로 검증 가능하게 제공한다. 일반 event와 달리 Authenticated Events는 intermediary를 신뢰하지 않고도 light client가 검증할 수 있으므로, trustless event consumption이 필요한 애플리케이션에 적합하다.

Why Authenticated Events?

일반 Sui event는 index되어 질의할 수 있지만, 이를 검증하려면 RPC provider를 신뢰해야 한다. Authenticated Events는 다음과 같은 방식으로 이 문제를 해결한다:

  1. Cryptographic Commitments: event는 온체인에 저장된 Merkle Mountain Range (MMR) 구조에 commit된다(MMR section 아래 참조)
  2. Checkpoint Verification: commitment는 validator committee가 서명하는 Sui checkpoint와 연결된다
  3. Light Client Verification: client는 genesis committee → checkpoint signature → event inclusion proof의 chain을 검증한다

이는 full node 없이도 event completeness와 correctness를 암호학적으로 검증할 수 있게 하며, browser나 mobile에서 실행할 수 있을 만큼 가볍다.

Quick Start

Emitting Authenticated Events (Move)

module my_package::my_module;

use sui::event;

public struct MyEvent has copy, drop {
value: u64,
data: vector<u8>,
}

public entry fun do_something(value: u64) {
// authenticated event를 발생시킨다
event::emit_authenticated(MyEvent {
value,
data: vector::empty(),
});
}

event는 event type을 정의한 package와 자동으로 연결된다. 해당 stream에 event를 발생시킬 수 있는 것은 그 package뿐이다.

Backwards Compatibility with Events

Authenticated Events는 일반 Sui event와 완전히 backwards-compatible하다. Authenticated event는 검증용 추가 metadata를 가진 일반 event일 뿐이므로, 기존 event consumer는 수정 없이 그대로 동작한다.

업그레이드하려면:

  1. Contract change: Move code에서 event::emit(...) 호출을 event::emit_authenticated(...)로 바꾼다
  2. Package upgrade: 업데이트된 package를 배포한다(contract upgrade 필요)
  3. Optional: 암호학적 검증을 위해 consumer를 authenticated events client로 업데이트한다

일반 event를 소비하는 기존 indexer, explorer, 도구는 수정 없이도 authenticated event를 계속 볼 수 있다.

Consuming Events (Rust Reference Client)

use sui_light_client::authenticated_events::AuthenticatedEventsClient;
use sui_types::base_types::SuiAddress;
use futures::StreamExt;
use std::sync::Arc;

// genesis committee로 초기화한다(trust root를 수립한다)
let client = Arc::new(
AuthenticatedEventsClient::new(rpc_url, genesis_committee)
.await?
);

// package에서 event를 stream한다
let stream_id = SuiAddress::from(package_id);
let mut stream = client.clone().stream_events(stream_id).await?;

while let Some(result) = stream.next().await {
match result {
Ok(event) => {
// event.event는 검증된 Move event 데이터를 담고 있다
// event.checkpoint는 언제 commit되었는지를 나타낸다
println!("Verified event at checkpoint {}", event.checkpoint);
}
Err(e) => {
// 일시적 error(TransportError, RpcError)는 자동으로 재시도된다.
// terminal error는 조치가 필요하다:
// - VerificationError: 데이터 무결성 문제이므로 다른 RPC endpoint를 시도한다
// - InternalError: 상태가 유효하지 않으므로 원인을 조사한다
// 자세한 내용은 "Error Handling" 섹션을 참조한다.
eprintln!("Terminal error: {:?}", e);
break;
}
}
}

Resuming from a Checkpoint

stream을 재개하려면 client에 검증된 시작 상태가 필요하다. completeness를 보장하려면, 이때 해당 checkpoint에서의 EventStreamHead에 대한 OCS inclusion proof가 필요하며, 이는 해당 checkpoint에서 그 stream에 대해 authenticated event가 발생한 경우에만 존재한다. 실제로는 이는 연결이 끊기기 전에 client가 마지막으로 받은 event의 checkpoint sequence가 된다.

지정한 checkpoint에 그 stream_id에 대한 event가 없으면 client 초기화는 실패한다.

let last_checkpoint = 12345;
let mut stream = client.clone()
.stream_events_from_checkpoint(stream_id, last_checkpoint)
.await?;

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Move Contract │
│ event::emit_authenticated(MyEvent { ... }) │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ EventStreamHead (On-chain) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ mmr: vector<u256> // Merkle Mountain Range root │ │
│ │ checkpoint_seq: u64 // Last update checkpoint │ │
│ │ num_events: u64 // Total events in stream │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│ Light Client Verification │
│ 1. Fetch EventStreamHead with OCS inclusion proof │
│ 2. Verify proof against checkpoint (signed by committee) │
│ 3. Fetch events from ListAuthenticatedEvents RPC │
│ 4. Recompute MMR from events, compare to EventStreamHead │
└─────────────────────────────────────────────────────────────────┘

Key Components

ComponentDescription
emit_authenticated<T>()검증 가능한 event를 발생시키는 Move 함수
EventStreamHeadpackage별 온체인 commitment 구조
ListAuthenticatedEventspagination과 함께 event를 가져오는 gRPC API
AuthenticatedEventsClient검증을 위한 reference Rust client
MMR (Merkle Mountain Range)append-only commitment 구조

emit_authenticated and Accumulator Settlement

사용자는 authenticated event를 발생시키기 위해 emit_authenticated()를 호출한다. 이는 stream_id를 포함한 metadata와 함께 일반 Event를 Transaction Effects에 기록한다.

event는 accumulator settlement 과정을 통해 checkpoint 경계에서 batch로 정산된다:

  1. Event Emission: transaction 실행 중 authenticated event가 발생한다
  2. Consensus Commit Batching: Authenticated Events는 consensus commit별로 그룹화되고, 각 stream_id에 대한 event 위로 merkle tree가 계산된다
  3. Checkpoint Settlement: 각 checkpoint에서 validator는 다음을 수행하는 settlement transaction을 실행한다:
    • consensus commit의 merkle root를 stream의 MMR에 append한다
    • 새 MMR 상태와 event 수로 해당 stream_idEventStreamHead object를 업데이트한다

API Reference

EventService

service EventService {
rpc ListAuthenticatedEvents(ListAuthenticatedEventsRequest)
returns (ListAuthenticatedEventsResponse);
}

Request Parameters:

  • stream_id (required): event를 발생시킨 package address
  • start_checkpoint: 시작할 checkpoint(default: 0)
  • page_size: page당 event 수(default: 1000, max: 1000)
  • page_token: 이전 응답의 pagination token

Response:

  • events: checkpoint, transaction index, event index, payload를 담은 AuthenticatedEvent 목록
  • highest_indexed_checkpoint: 가장 최근에 index된 checkpoint
  • next_page_token: 다음 page용 token(더 없으면 비어 있음)

ProofService

service ProofService {
rpc GetObjectInclusionProof(GetObjectInclusionProofRequest)
returns (GetObjectInclusionProofResponse);
}

특정 checkpoint에 EventStreamHead object가 존재함을 검증하는 데 사용한다.

Request Parameters:

  • object_id (required): EventStreamHead object ID(package address에서 파생)
  • checkpoint (required): inclusion을 증명할 checkpoint sequence number

Response:

  • object_ref: object reference(object_id, version, digest)
  • inclusion_proof: 다음을 포함하는 OCS (Object Checkpoint State) merkle proof:
    • merkle_proof: BCS로 인코딩된 proof node
    • leaf_index: merkle tree에서의 위치
    • tree_root: root digest(32 byte)
  • object_data: BCS로 인코딩된 EventStreamHead object
  • checkpoint_summary: 검증용 BCS 인코딩 checkpoint summary

이 inclusion proof는 지정한 checkpoint에서 EventStreamHead가 기록되었음을 검증한다. 이는 stream 재개에 필수적인데, checkpoint는 실제로 event가 발생한 checkpoint여야 하기 때문이다.

Client Configuration

let config = ClientConfig::new(
page_size, // RPC 호출당 event 수 (max 1000)
poll_interval, // 새 event를 polling하는 주기
max_pagination_iterations, // checkpoint 경계를 강제하기 전 최대 page 수
rpc_timeout, // RPC 호출 timeout
)?;

let client = AuthenticatedEventsClient::new_with_config(
rpc_url,
genesis_committee,
config
).await?;

Merkle Mountain Range (MMR)

MMR은 완전 이진 트리("mountain")의 forest이다. n개의 leaf가 append된 뒤 mountain 크기는 n의 이진 표현에서 set된 bit에 대응한다. 예를 들어 n = 13 (binary 1101에서 bit 3, 2, 0이 set)일 때 mountain 크기는 2^3 = 8, 2^2 = 4, 2^0 = 1이다. 아래 diagram은 확장된 MMR(내부 node와 leaf)을 보여준다.

EventStreamHead의 MMR 상태는 tree height로 index된 digest의 compact vector로 저장되며(전체 확장 트리가 아님), append된 leaf 수에 대해 온체인 상태를 O(log n)으로 유지한다.

MMR의 핵심 속성은 다음과 같다:

  • compact한 온체인 accumulator 상태: O(log n) digest.
  • append-only update: 새 leaf를 현재 accumulator 상태만 사용해 통합할 수 있다(확장된 MMR 없이).
  • inclusion proof 크기는 O(log n)이다. 현재 RPC service는 event 수준의 MMR inclusion proof를 아직 노출하지 않는다(이는 ProofService가 노출하는 OCS inclusion proof와는 별개이다). 이러한 inclusion proof는 예를 들어 Move data를 off-chain으로 저장하거나, 효율적인 cross-chain read를 수행하거나, ZK에서 inclusion을 증명하는 등 많은 흥미로운 애플리케이션으로 이어질 수 있다.

Expanded MMR with 13 leaves

                           Tree 0 (size 8)
H0
┌────────┴────────┐
H1 H2
┌─────┴─────┐ ┌─────┴─────┐
H3 H4 H5 H6
┌──┴──┐ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐
L0 L1 L2 L3 L4 L5 L6 L7


Tree 1 (size 4)
H7
┌─────┴─────┐
H8 H9
┌──┴──┐ ┌──┴──┐
L8 L9 L10 L11


Tree 2 (size 1)
L12

leaf(Lx)는 consensus commit별 merkle tree digest에 대응하고, 그 merkle tree는 다시 AuthenticatedEvent를 leaf로 가진다는 점에 유의한다.

Trust Model

  1. Genesis Committee: client는 genesis validator committee public key로 초기화된다
  2. Trust Ratcheting: epoch가 바뀌면 client는 새 committee가 이전 committee에 의해 서명되었는지 검증한다
  3. Checkpoint Verification: 각 checkpoint summary는 committee의 aggregate signature에 대해 검증된다
  4. Object Inclusion Proof: EventStreamHead object가 특정 checkpoint에서 수정되었음이 증명된다
  5. MMR Verification: event는 EventStreamHead의 MMR commitment에 대해 검증된다

client는 다음 방식으로 checkpoint 범위를 검증한다:

  1. 이전 checkpoint의 검증된 EventStreamHead로 시작한다(또는 새 stream이면 빈 상태)
  2. 범위 안의 event를 가져와 MMR에 append한다
  3. 최종 checkpoint의 EventStreamHead를 가져오고 OCS proof를 통해 inclusion을 증명한다
  4. 로컬에서 계산한 MMR을 온체인 EventStreamHead와 비교한다

event가 하나라도 누락되거나, 수정되거나, 순서가 뒤바뀌면 계산된 MMR이 온체인 상태와 일치하지 않는다.

client는 epoch 전환과 committee 변경을 자동으로 처리한다.

Security Guarantees

Completeness: stream에서 event N을 받으면, 올바른 순서의 모든 event 0..N-1을 받았음이 보장된다. MMR 구조는 event가 누락되거나 순서가 바뀌면 검증이 실패하도록 만든다.

What completeness does NOT guarantee: intermediary(예: RPC provider)는 요청한 checkpoint 높이까지의 event를 모두 반환할 의무가 없다. withholding을 감지하려면 client가 authoritative event count를 담고 있는 온체인 EventStreamHead를 기준으로 검증해야 한다. reference client는 이를 자동으로 수행하며, 받은 event가 온체인 commitment와 일치하지 않으면 검증이 실패한다.

Correctness: 각 event의 내용은 MMR에 commit된다. event 데이터를 조금이라도 수정하면 계산된 MMR이 온체인 상태와 달라진다.

Limitations

  • event는 full node에 index되어 있고 pruning되지 않은 경우에만 가져올 수 있다
  • event는 checkpoint 순서대로 소비해야 한다
  • resume checkpoint에서는 EventStreamHead가 업데이트되어 있어야 한다(authenticated event가 발생했어야 한다)
  • package당 하나의 stream만 존재한다(stream_id = package address)
  • event 수준 MMR inclusion proof는 지원되지 않는다

Error Handling

Error TypeRecoverableAction
TransportErrorYesAutomatic retry
RpcError (Unavailable, DeadlineExceeded)YesAutomatic retry
VerificationErrorNoStop - data integrity issue
InternalErrorNoStop - invalid state

stream은 일시적 error를 자동으로 재시도한다. terminal error는 검증 실패 또는 invalid state를 나타내며 조사가 필요하다.