Skip to main content
Events are the core mechanism for state changes in TAPLE. Every modification to a subject is represented as an immutable, signed event that forms part of the subject’s event chain.

Event Structure

An event contains all information about a state transition:
pub struct Event {
    /// The identifier of the subject of the event.
    pub subject_id: DigestIdentifier,
    /// The signed event request.
    pub event_request: Signed<EventRequest>,
    /// The sequence number of the event.
    pub sn: u64,
    /// The version of the governance contract.
    pub gov_version: u64,
    /// The patch to apply to the state.
    pub patch: ValueWrapper,
    /// The hash of the state after applying the patch.
    pub state_hash: DigestIdentifier,
    /// Whether the evaluation was successful and the result was validated against the schema.
    pub eval_success: bool,
    /// Whether approval is required for the event to be applied to the state.
    pub appr_required: bool,
    /// Whether the event has been approved.
    pub approved: bool,
    /// The hash of the previous event.
    pub hash_prev_event: DigestIdentifier,
    /// The set of evaluators who have evaluated the event.
    pub evaluators: HashSet<Signature>,
    /// The set of approvers who have approved the event.
    pub approvers: HashSet<Signature>,
}
Source: commons/models/event.rs:26-51
Events are immutable once created and signed. They form a cryptographically-linked chain through the hash_prev_event field.

Event Request Types

TAPLE supports four types of event requests:
pub enum EventRequest {
    /// A request to create a new subject.
    Create(StartRequest),
    /// A request to add a fact to a subject.
    Fact(FactRequest),
    /// A request to transfer ownership of a subject.
    Transfer(TransferRequest),
    /// A request to mark a subject as end-of-life.
    EOL(EOLRequest),
}
Source: commons/models/request.rs:16-25

Create Request

Creates a new subject:
pub struct StartRequest {
    /// The identifier of the governance contract.
    pub governance_id: DigestIdentifier,
    /// The identifier of the schema used to validate the event.
    pub schema_id: String,
    /// The namespace of the subject.
    pub namespace: String,
    /// The name of the subject.
    pub name: String,
    /// The identifier of the public key of the subject owner.
    pub public_key: KeyIdentifier,
}
Source: commons/models/request.rs:28-40

Fact Request

Adds data/facts to modify subject state:
pub struct FactRequest {
    /// The identifier of the subject to which the fact will be added.
    pub subject_id: DigestIdentifier,
    /// The payload of the fact to be added.
    pub payload: ValueWrapper,
}
Source: commons/models/request.rs:43-49
Fact requests are the only type that require evaluation and approval according to governance policies.

Transfer Request

Transfers subject ownership:
pub struct TransferRequest {
    /// The identifier of the subject to transfer ownership of.
    pub subject_id: DigestIdentifier,
    /// The identifier of the public key of the new owner.
    pub public_key: KeyIdentifier,
}
Source: commons/models/request.rs:52-58

EOL Request

Marks a subject as end-of-life (inactive):
pub struct EOLRequest {
    /// The identifier of the subject to mark as end-of-life.
    pub subject_id: DigestIdentifier,
}
Source: commons/models/request.rs:61-65

Event Processing Workflow

1. Event Request Submission

Requests must be signed before submission:
let signed_request = Signed::<EventRequest> {
    content: create_subject_request.clone(),
    signature: Signature::new(&create_subject_request, &node_key_pair).unwrap(),
};

let _request_id = api.external_request(signed_request).await.unwrap();
Source: lib.rs:57-63

2. Evaluation Stage

For Fact requests, evaluators execute smart contracts:
pub struct EvaluationRequest {
    pub event_request: Signed<EventRequest>,
    pub context: SubjectContext,
    pub sn: u64,
    pub gov_version: u64,
}
Evaluators return:
pub struct EvaluationResponse {
    pub patch: ValueWrapper,
    pub eval_req_hash: DigestIdentifier,
    pub state_hash: DigestIdentifier,
    pub eval_success: bool,
    pub appr_required: bool,
}
Source: Referenced from evaluator integration

3. Approval Stage (if required)

If governance requires approval:
pub struct ApprovalRequest {
    pub event_request: Signed<EventRequest>,
    pub sn: u64,
    pub gov_version: u64,
    pub patch: ValueWrapper,
    pub state_hash: DigestIdentifier,
    pub hash_prev_event: DigestIdentifier,
    pub gov_id: DigestIdentifier,
}
Approvers respond:
pub struct ApprovalResponse {
    pub appr_req_hash: DigestIdentifier,
    pub approved: bool,
}
Source: commons/models/approval.rs (referenced)

4. Validation Stage

Validators verify event integrity and signatures:
pub fn verify_eval_appr(
    &self,
    subject_context: SubjectContext,
    eval_sign_info: (&HashSet<KeyIdentifier>, u32, u32),
    appr_sign_info: (&HashSet<KeyIdentifier>, u32, u32),
) -> Result<(), SubjectError>
Source: commons/models/event.rs:147-152

5. Ledger Commitment

Once validated, events are committed to the ledger:
pub enum LedgerCommand {
    OwnEvent {
        event: Signed<Event>,
        signatures: HashSet<Signature>,
        validation_proof: ValidationProof,
    },
    Genesis {
        event: Signed<Event>,
        signatures: HashSet<Signature>,
        validation_proof: ValidationProof,
    },
    ExternalEvent {
        sender: KeyIdentifier,
        event: Signed<Event>,
        signatures: HashSet<Signature>,
        validation_proof: ValidationProof,
    },
    // ...
}
Source: ledger/mod.rs:18-34

Event Commands

The event manager processes various commands:
pub enum EventCommand {
    Event {
        event_request: Signed<EventRequest>,
    },
    EvaluatorResponse {
        evaluator_response: Signed<EvaluationResponse>,
    },
    ApproverResponse {
        approval: Signed<ApprovalResponse>,
    },
    ValidatorResponse {
        event_hash: DigestIdentifier,
        signature: Signature,
        governance_version: u64,
    },
    HigherGovernanceExpected {
        governance_id: DigestIdentifier,
        who_asked: KeyIdentifier,
    },
}
Source: event/mod.rs:18-38

Genesis Events

The first event (sn=0) for a subject is special:
pub fn from_genesis_request(
    event_request: Signed<EventRequest>,
    subject_keys: &KeyPair,
    gov_version: u64,
    init_state: &ValueWrapper,
    derivator: DigestDerivator,
) -> Result<Self, SubjectError> {
    let EventRequest::Create(start_request) = event_request.content.clone() else {
        return Err(SubjectError::NotCreateEvent)
    };
    let json_patch = serde_json::to_value(diff(&json!({}), &init_state.0)).map_err(|_| {
        SubjectError::CryptoError(String::from("Error converting patch to value"))
    })?;
    let public_key = KeyIdentifier::new(
        subject_keys.get_key_derivator(),
        &subject_keys.public_key_bytes(),
    );
    let subject_id = generate_subject_id(
        &start_request.namespace,
        &start_request.schema_id,
        public_key.to_str(),
        start_request.governance_id.to_str(),
        gov_version,
        derivator
    )?;
    let state_hash =
        DigestIdentifier::from_serializable_borsh(init_state, derivator).map_err(|_| {
            SubjectError::CryptoError(String::from("Error converting state to hash"))
        })?;
    let content = Event {
        subject_id,
        event_request,
        sn: 0,
        gov_version,
        patch: ValueWrapper(json_patch),
        state_hash,
        eval_success: true,
        appr_required: false,
        approved: true,
        hash_prev_event: DigestIdentifier::default(),
        evaluators: HashSet::new(),
        approvers: HashSet::new(),
    };
    let subject_signature_event =
        Signature::new(&content, &subject_keys, derivator).map_err(|_| {
            SubjectError::CryptoError(String::from("Error signing the hash of the proposal"))
        })?;
    Ok(Self {
        content,
        signature: subject_signature_event,
    })
}
Source: commons/models/event.rs:88-139
Genesis events have special properties: sn=0, empty hash_prev_event, and skip evaluation/approval.

Event Metadata

Events carry metadata for routing and validation:
pub struct Metadata {
    /// The namespace of the event.
    pub namespace: String,
    /// The identifier of the subject of the event.
    pub subject_id: DigestIdentifier,
    /// The identifier of the governance contract.
    pub governance_id: DigestIdentifier,
    /// The version of the governance contract.
    pub governance_version: u64,
    /// The identifier of the schema used to validate the event.
    pub schema_id: String,
}
Source: commons/models/event.rs:245-257

Signature Verification

Events must verify all signatures:
pub fn verify_signatures(&self) -> Result<(), SubjectError> {
    // Verify event and event_request signatures
    self.signature.verify(&self.content)?;
    self.content.event_request.verify()
}
Source: commons/models/event.rs:141-145

Evaluator Signatures

Evaluators sign their evaluation response:
let mut evaluators = HashSet::new();
for eval_signature in self.content.evaluators.iter() {
    if !evaluators.insert(eval_signature.signer.clone()) {
        return Err(SubjectError::RepeatedSignature(
            "Repeated Signer in Evaluators".to_string(),
        ));
    }
    eval_signature.verify(&eval_response)?;
}
Source: commons/models/event.rs:176-183

Approver Signatures

Approvers sign their approval decision:
let mut approvers = HashSet::new();
for appr_signature in self.content.approvers.iter() {
    if !approvers.insert(appr_signature.signer.clone()) {
        return Err(SubjectError::RepeatedSignature(
            "Repeated Signer in Approvers".to_string(),
        ));
    }
    appr_signature.verify(&appr_response)?;
}
Source: commons/models/event.rs:217-224

Event Hashing

Events implement cryptographic hashing:
impl HashId for Event {
    fn hash_id(&self, derivator: DigestDerivator) -> Result<DigestIdentifier, SubjectError> {
        DigestIdentifier::from_serializable_borsh(&self, derivator)
            .map_err(|_| SubjectError::SignatureCreationFails("HashId for Event Fails".to_string()))
    }
}

impl HashId for Signed<Event> {
    fn hash_id(&self, derivator: DigestDerivator) -> Result<DigestIdentifier, SubjectError> {
        DigestIdentifier::from_serializable_borsh(&self, derivator).map_err(|_| {
            SubjectError::SignatureCreationFails("HashId for Signed Event Fails".to_string())
        })
    }
}
Source: commons/models/event.rs:72-85

Quorum Requirements

Events must meet quorum requirements:
let quorum_size_eval = if self.content.eval_success {
    eval_sign_info.1
} else {
    eval_sign_info.2
};
if evaluators.len() < quorum_size_eval as usize {
    return Err(SubjectError::SignersError(
        "Not enough Evaluators signed".to_string(),
    ));
}
Source: commons/models/event.rs:189-198
Different quorums can be required for success vs failure, allowing flexible governance policies.

Request State Tracking

The system tracks request processing:
pub enum RequestState {
    Finished,
    Error,
    Processing,
}

pub struct TapleRequest {
    pub id: DigestIdentifier,
    pub subject_id: Option<DigestIdentifier>,
    pub sn: Option<u64>,
    pub event_request: Signed<EventRequest>,
    pub state: RequestState,
    pub success: Option<bool>,
}
Source: commons/models/request.rs:98-120

Event Notifications

Applications receive notifications when events occur:
let subject_id = if let Notification::NewEvent { sn: _, subject_id } = node
    .recv_notification()
    .await
    .expect("NewEvent notification received")
{
    subject_id
} else {
    panic!("Unexpected notification");
};
Source: lib.rs:66-74

Best Practices

Atomicity

Design events as atomic state transitions

Idempotency

Ensure event processing is idempotent

Validation

Always verify signatures before processing

Ordering

Respect sequence numbers for event ordering

Next Steps

Subjects

Learn how events modify subjects

Governance

Understand event validation policies

Cryptography

Explore signature schemes

Build docs developers (and LLMs) love