use serde::{Deserialize, Serialize};
use std::fmt;
mod u128_str {
use serde::{self, Deserialize, Deserializer, Serializer};
pub fn serialize<S>(value: &u128, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&value.to_string())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<u128, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse::<u128>().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum VmType {
Native,
Evm,
Svm,
Daml,
}
impl fmt::Display for VmType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VmType::Native => write!(f, "native"),
VmType::Evm => write!(f, "evm"),
VmType::Svm => write!(f, "svm"),
VmType::Daml => write!(f, "daml"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventType {
NewBlock,
BlockFinalized,
BlockReorged,
NewPendingTransaction,
TransactionIncluded,
TransactionFinalized,
Log,
Transfer,
NftTransfer,
CrosschainMint,
CrosschainBurn,
IdentityRegistered,
CredentialIssued,
ComplianceViolation,
ModelRegistered,
InferenceCompleted,
InferenceStreamStarted,
InferenceStreamFirstToken,
InferenceStreamDropped,
InferenceStreamCompleted,
AgentMessage,
SettlementCompleted,
PaymentChannelUpdate,
StakeDeposited,
StakeWithdrawn,
ValidatorSlashed,
ProposalCreated,
VoteCast,
BridgeTransferInitiated,
BridgeTransferCompleted,
SyncProgress,
WorkflowCreated,
WorkflowLifecycleTransitioned,
WorkflowReceiptEmitted,
ApprovalRequested,
ApprovalFinalized,
}
impl fmt::Display for EventType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", event_type_static_name(*self))
}
}
pub fn event_type_static_name(et: EventType) -> &'static str {
match et {
EventType::NewBlock => "NewBlock",
EventType::BlockFinalized => "BlockFinalized",
EventType::BlockReorged => "BlockReorged",
EventType::NewPendingTransaction => "NewPendingTransaction",
EventType::TransactionIncluded => "TransactionIncluded",
EventType::TransactionFinalized => "TransactionFinalized",
EventType::Log => "Log",
EventType::Transfer => "Transfer",
EventType::NftTransfer => "NftTransfer",
EventType::CrosschainMint => "CrosschainMint",
EventType::CrosschainBurn => "CrosschainBurn",
EventType::IdentityRegistered => "IdentityRegistered",
EventType::CredentialIssued => "CredentialIssued",
EventType::ComplianceViolation => "ComplianceViolation",
EventType::ModelRegistered => "ModelRegistered",
EventType::InferenceCompleted => "InferenceCompleted",
EventType::InferenceStreamStarted => "InferenceStreamStarted",
EventType::InferenceStreamFirstToken => "InferenceStreamFirstToken",
EventType::InferenceStreamDropped => "InferenceStreamDropped",
EventType::InferenceStreamCompleted => "InferenceStreamCompleted",
EventType::AgentMessage => "AgentMessage",
EventType::SettlementCompleted => "SettlementCompleted",
EventType::PaymentChannelUpdate => "PaymentChannelUpdate",
EventType::StakeDeposited => "StakeDeposited",
EventType::StakeWithdrawn => "StakeWithdrawn",
EventType::ValidatorSlashed => "ValidatorSlashed",
EventType::ProposalCreated => "ProposalCreated",
EventType::VoteCast => "VoteCast",
EventType::BridgeTransferInitiated => "BridgeTransferInitiated",
EventType::BridgeTransferCompleted => "BridgeTransferCompleted",
EventType::SyncProgress => "SyncProgress",
EventType::WorkflowCreated => "WorkflowCreated",
EventType::WorkflowLifecycleTransitioned => "WorkflowLifecycleTransitioned",
EventType::WorkflowReceiptEmitted => "WorkflowReceiptEmitted",
EventType::ApprovalRequested => "ApprovalRequested",
EventType::ApprovalFinalized => "ApprovalFinalized",
}
}
pub fn event_type_name(event: &TenzroEvent) -> &'static str {
event_type_static_name(event.event_type())
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TenzroEvent {
NewBlock {
block_hash: [u8; 32],
parent_hash: [u8; 32],
height: u64,
tx_count: u32,
proposer: [u8; 20],
},
BlockFinalized {
block_hash: [u8; 32],
height: u64,
},
BlockReorged {
block_hash: [u8; 32],
height: u64,
new_block_hash: [u8; 32],
},
NewPendingTransaction {
tx_hash: [u8; 32],
from: [u8; 20],
to: Option<[u8; 20]>,
#[serde(with = "u128_str")]
value: u128,
nonce: u64,
},
TransactionIncluded {
tx_hash: [u8; 32],
block_hash: [u8; 32],
block_height: u64,
index: u32,
gas_used: u64,
success: bool,
},
TransactionFinalized {
tx_hash: [u8; 32],
block_hash: [u8; 32],
block_height: u64,
},
Log {
address: [u8; 20],
topics: Vec<[u8; 32]>,
data: Vec<u8>,
block_height: u64,
tx_hash: [u8; 32],
log_index: u32,
removed: bool,
},
Transfer {
from: [u8; 20],
to: [u8; 20],
#[serde(with = "u128_str")]
amount: u128,
token_id: String,
tx_hash: [u8; 32],
},
NftTransfer {
from: [u8; 20],
to: [u8; 20],
token_id: String,
#[serde(with = "u128_str")]
nft_id: u128,
tx_hash: [u8; 32],
},
CrosschainMint {
to: [u8; 20],
#[serde(with = "u128_str")]
amount: u128,
source_chain: String,
source_tx_hash: [u8; 32],
token_id: String,
},
CrosschainBurn {
from: [u8; 20],
#[serde(with = "u128_str")]
amount: u128,
destination_chain: String,
token_id: String,
tx_hash: [u8; 32],
},
IdentityRegistered {
did: String,
identity_type: String,
controller: Option<String>,
},
CredentialIssued {
issuer_did: String,
subject_did: String,
credential_type: String,
credential_id: String,
},
ComplianceViolation {
did: String,
violation_type: String,
details: String,
},
ModelRegistered {
model_id: String,
name: String,
provider: [u8; 20],
#[serde(default)]
modality: tenzro_types::model::ModelModality,
},
InferenceCompleted {
request_id: String,
model_id: String,
provider: [u8; 20],
latency_ms: u64,
tokens_used: u64,
#[serde(with = "u128_str")]
cost: u128,
},
InferenceStreamStarted {
request_id: String,
model_id: String,
provider_label: String,
},
InferenceStreamFirstToken {
request_id: String,
model_id: String,
provider_label: String,
ttft_ms: u64,
},
InferenceStreamDropped {
request_id: String,
model_id: String,
provider_label: String,
reason: String,
silent_for_ms: Option<u64>,
},
InferenceStreamCompleted {
request_id: String,
model_id: String,
provider_label: String,
latency_ms: u64,
success: bool,
},
AgentMessage {
from_agent: String,
to_agent: String,
message_type: String,
message_id: String,
},
SettlementCompleted {
settlement_id: String,
payer: [u8; 20],
payee: [u8; 20],
#[serde(with = "u128_str")]
amount: u128,
tx_hash: [u8; 32],
},
PaymentChannelUpdate {
channel_id: String,
sender: [u8; 20],
receiver: [u8; 20],
#[serde(with = "u128_str")]
balance: u128,
status: String,
},
StakeDeposited {
staker: [u8; 20],
#[serde(with = "u128_str")]
amount: u128,
role: String,
tx_hash: [u8; 32],
},
StakeWithdrawn {
staker: [u8; 20],
#[serde(with = "u128_str")]
amount: u128,
tx_hash: [u8; 32],
},
ValidatorSlashed {
validator: [u8; 20],
#[serde(with = "u128_str")]
amount: u128,
reason: String,
evidence_hash: [u8; 32],
},
ProposalCreated {
proposal_id: String,
proposer: [u8; 20],
title: String,
},
VoteCast {
proposal_id: String,
voter: [u8; 20],
support: bool,
#[serde(with = "u128_str")]
weight: u128,
},
BridgeTransferInitiated {
bridge_adapter: String,
source_tx_hash: [u8; 32],
destination_chain: String,
sender: [u8; 20],
#[serde(with = "u128_str")]
amount: u128,
token_id: String,
},
BridgeTransferCompleted {
bridge_adapter: String,
source_chain: String,
source_tx_hash: [u8; 32],
receiver: [u8; 20],
#[serde(with = "u128_str")]
amount: u128,
token_id: String,
},
SyncProgress {
current_block: u64,
highest_block: u64,
percent: u8,
},
WorkflowCreated {
workflow_id: [u8; 32],
creator_did: String,
title: String,
privacy_domain: Option<[u8; 32]>,
},
WorkflowLifecycleTransitioned {
workflow_id: [u8; 32],
from_status: String,
to_status: String,
trigger: String,
privacy_domain: Option<[u8; 32]>,
},
WorkflowReceiptEmitted {
workflow_id: [u8; 32],
receipt_id: [u8; 32],
event_kind: String,
privacy_domain: Option<[u8; 32]>,
payload_commitment: Option<[u8; 32]>,
},
ApprovalRequested {
workflow_id: [u8; 32],
gate_id: [u8; 32],
request_id: [u8; 32],
privacy_domain: Option<[u8; 32]>,
},
ApprovalFinalized {
workflow_id: [u8; 32],
gate_id: [u8; 32],
request_id: [u8; 32],
outcome: String,
privacy_domain: Option<[u8; 32]>,
},
}
impl TenzroEvent {
pub fn event_type(&self) -> EventType {
match self {
TenzroEvent::NewBlock { .. } => EventType::NewBlock,
TenzroEvent::BlockFinalized { .. } => EventType::BlockFinalized,
TenzroEvent::BlockReorged { .. } => EventType::BlockReorged,
TenzroEvent::NewPendingTransaction { .. } => EventType::NewPendingTransaction,
TenzroEvent::TransactionIncluded { .. } => EventType::TransactionIncluded,
TenzroEvent::TransactionFinalized { .. } => EventType::TransactionFinalized,
TenzroEvent::Log { .. } => EventType::Log,
TenzroEvent::Transfer { .. } => EventType::Transfer,
TenzroEvent::NftTransfer { .. } => EventType::NftTransfer,
TenzroEvent::CrosschainMint { .. } => EventType::CrosschainMint,
TenzroEvent::CrosschainBurn { .. } => EventType::CrosschainBurn,
TenzroEvent::IdentityRegistered { .. } => EventType::IdentityRegistered,
TenzroEvent::CredentialIssued { .. } => EventType::CredentialIssued,
TenzroEvent::ComplianceViolation { .. } => EventType::ComplianceViolation,
TenzroEvent::ModelRegistered { .. } => EventType::ModelRegistered,
TenzroEvent::InferenceCompleted { .. } => EventType::InferenceCompleted,
TenzroEvent::InferenceStreamStarted { .. } => EventType::InferenceStreamStarted,
TenzroEvent::InferenceStreamFirstToken { .. } => EventType::InferenceStreamFirstToken,
TenzroEvent::InferenceStreamDropped { .. } => EventType::InferenceStreamDropped,
TenzroEvent::InferenceStreamCompleted { .. } => EventType::InferenceStreamCompleted,
TenzroEvent::AgentMessage { .. } => EventType::AgentMessage,
TenzroEvent::SettlementCompleted { .. } => EventType::SettlementCompleted,
TenzroEvent::PaymentChannelUpdate { .. } => EventType::PaymentChannelUpdate,
TenzroEvent::StakeDeposited { .. } => EventType::StakeDeposited,
TenzroEvent::StakeWithdrawn { .. } => EventType::StakeWithdrawn,
TenzroEvent::ValidatorSlashed { .. } => EventType::ValidatorSlashed,
TenzroEvent::ProposalCreated { .. } => EventType::ProposalCreated,
TenzroEvent::VoteCast { .. } => EventType::VoteCast,
TenzroEvent::BridgeTransferInitiated { .. } => EventType::BridgeTransferInitiated,
TenzroEvent::BridgeTransferCompleted { .. } => EventType::BridgeTransferCompleted,
TenzroEvent::SyncProgress { .. } => EventType::SyncProgress,
TenzroEvent::WorkflowCreated { .. } => EventType::WorkflowCreated,
TenzroEvent::WorkflowLifecycleTransitioned { .. } => EventType::WorkflowLifecycleTransitioned,
TenzroEvent::WorkflowReceiptEmitted { .. } => EventType::WorkflowReceiptEmitted,
TenzroEvent::ApprovalRequested { .. } => EventType::ApprovalRequested,
TenzroEvent::ApprovalFinalized { .. } => EventType::ApprovalFinalized,
}
}
pub fn privacy_domain(&self) -> Option<[u8; 32]> {
match self {
TenzroEvent::WorkflowCreated { privacy_domain, .. }
| TenzroEvent::WorkflowLifecycleTransitioned { privacy_domain, .. }
| TenzroEvent::WorkflowReceiptEmitted { privacy_domain, .. }
| TenzroEvent::ApprovalRequested { privacy_domain, .. }
| TenzroEvent::ApprovalFinalized { privacy_domain, .. } => *privacy_domain,
_ => None,
}
}
}
impl fmt::Display for TenzroEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TenzroEvent::NewBlock { height, tx_count, .. } => {
write!(f, "NewBlock height={} txs={}", height, tx_count)
}
TenzroEvent::BlockFinalized { height, .. } => {
write!(f, "BlockFinalized height={}", height)
}
TenzroEvent::BlockReorged { height, .. } => {
write!(f, "BlockReorged height={}", height)
}
TenzroEvent::NewPendingTransaction { nonce, value, .. } => {
write!(f, "NewPendingTx nonce={} value={}", nonce, value)
}
TenzroEvent::TransactionIncluded { block_height, index, success, .. } => {
write!(
f,
"TxIncluded block={} idx={} ok={}",
block_height, index, success
)
}
TenzroEvent::TransactionFinalized { block_height, .. } => {
write!(f, "TxFinalized block={}", block_height)
}
TenzroEvent::Log { address, log_index, removed, .. } => {
write!(
f,
"Log addr=0x{} idx={} removed={}",
hex::encode(address),
log_index,
removed
)
}
TenzroEvent::Transfer { amount, token_id, .. } => {
write!(f, "Transfer amount={} token={}", amount, token_id)
}
TenzroEvent::NftTransfer { token_id, nft_id, .. } => {
write!(f, "NftTransfer token={} nft={}", token_id, nft_id)
}
TenzroEvent::CrosschainMint { amount, source_chain, .. } => {
write!(f, "CrosschainMint amount={} from={}", amount, source_chain)
}
TenzroEvent::CrosschainBurn { amount, destination_chain, .. } => {
write!(f, "CrosschainBurn amount={} to={}", amount, destination_chain)
}
TenzroEvent::IdentityRegistered { did, identity_type, .. } => {
write!(f, "IdentityRegistered did={} type={}", did, identity_type)
}
TenzroEvent::CredentialIssued { credential_type, subject_did, .. } => {
write!(f, "CredentialIssued type={} subject={}", credential_type, subject_did)
}
TenzroEvent::ComplianceViolation { did, violation_type, .. } => {
write!(f, "ComplianceViolation did={} type={}", did, violation_type)
}
TenzroEvent::ModelRegistered { model_id, name, .. } => {
write!(f, "ModelRegistered id={} name={}", model_id, name)
}
TenzroEvent::InferenceCompleted { model_id, latency_ms, tokens_used, .. } => {
write!(
f,
"InferenceCompleted model={} latency={}ms tokens={}",
model_id, latency_ms, tokens_used
)
}
TenzroEvent::InferenceStreamStarted { request_id, model_id, provider_label } => {
write!(
f,
"InferenceStreamStarted req={} model={} provider={}",
request_id, model_id, provider_label
)
}
TenzroEvent::InferenceStreamFirstToken { request_id, model_id, ttft_ms, .. } => {
write!(
f,
"InferenceStreamFirstToken req={} model={} ttft={}ms",
request_id, model_id, ttft_ms
)
}
TenzroEvent::InferenceStreamDropped { request_id, model_id, reason, silent_for_ms, .. } => {
match silent_for_ms {
Some(ms) => write!(
f,
"InferenceStreamDropped req={} model={} reason={} silent={}ms",
request_id, model_id, reason, ms
),
None => write!(
f,
"InferenceStreamDropped req={} model={} reason={}",
request_id, model_id, reason
),
}
}
TenzroEvent::InferenceStreamCompleted { request_id, model_id, latency_ms, success, .. } => {
write!(
f,
"InferenceStreamCompleted req={} model={} latency={}ms success={}",
request_id, model_id, latency_ms, success
)
}
TenzroEvent::AgentMessage { from_agent, to_agent, message_type, .. } => {
write!(
f,
"AgentMessage from={} to={} type={}",
from_agent, to_agent, message_type
)
}
TenzroEvent::SettlementCompleted { settlement_id, amount, .. } => {
write!(f, "SettlementCompleted id={} amount={}", settlement_id, amount)
}
TenzroEvent::PaymentChannelUpdate { channel_id, status, .. } => {
write!(f, "PaymentChannelUpdate id={} status={}", channel_id, status)
}
TenzroEvent::StakeDeposited { amount, role, .. } => {
write!(f, "StakeDeposited amount={} role={}", amount, role)
}
TenzroEvent::StakeWithdrawn { amount, .. } => {
write!(f, "StakeWithdrawn amount={}", amount)
}
TenzroEvent::ValidatorSlashed { amount, reason, .. } => {
write!(f, "ValidatorSlashed amount={} reason={}", amount, reason)
}
TenzroEvent::ProposalCreated { proposal_id, title, .. } => {
write!(f, "ProposalCreated id={} title={}", proposal_id, title)
}
TenzroEvent::VoteCast { proposal_id, support, weight, .. } => {
write!(
f,
"VoteCast proposal={} support={} weight={}",
proposal_id, support, weight
)
}
TenzroEvent::BridgeTransferInitiated { bridge_adapter, destination_chain, amount, .. } => {
write!(
f,
"BridgeTransferInitiated adapter={} dest={} amount={}",
bridge_adapter, destination_chain, amount
)
}
TenzroEvent::BridgeTransferCompleted { bridge_adapter, source_chain, amount, .. } => {
write!(
f,
"BridgeTransferCompleted adapter={} src={} amount={}",
bridge_adapter, source_chain, amount
)
}
TenzroEvent::SyncProgress { current_block, highest_block, percent } => {
write!(
f,
"SyncProgress {}/{} ({}%)",
current_block, highest_block, percent
)
}
TenzroEvent::WorkflowCreated { workflow_id, title, .. } => {
write!(f, "WorkflowCreated id={} title={}", hex::encode(workflow_id), title)
}
TenzroEvent::WorkflowLifecycleTransitioned {
workflow_id, from_status, to_status, ..
} => {
write!(
f,
"WorkflowLifecycle id={} {}→{}",
hex::encode(workflow_id),
from_status,
to_status
)
}
TenzroEvent::WorkflowReceiptEmitted { workflow_id, event_kind, .. } => {
write!(
f,
"WorkflowReceipt wf={} kind={}",
hex::encode(workflow_id),
event_kind
)
}
TenzroEvent::ApprovalRequested { workflow_id, gate_id, .. } => {
write!(
f,
"ApprovalRequested wf={} gate={}",
hex::encode(workflow_id),
hex::encode(gate_id)
)
}
TenzroEvent::ApprovalFinalized { workflow_id, outcome, .. } => {
write!(f, "ApprovalFinalized wf={} outcome={}", hex::encode(workflow_id), outcome)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EventEnvelope {
pub sequence: u64,
pub timestamp: i64,
pub block_height: Option<u64>,
pub vm_type: Option<VmType>,
pub event: TenzroEvent,
}
impl EventEnvelope {
pub fn event_type(&self) -> EventType {
self.event.event_type()
}
}
impl fmt::Display for EventEnvelope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[seq={} t={}] {}", self.sequence, self.timestamp, self.event)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SubscriptionId(pub u64);
impl fmt::Display for SubscriptionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sub:{}", self.0)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EventFilter {
pub from_block: Option<u64>,
pub to_block: Option<u64>,
pub from_sequence: Option<u64>,
pub addresses: Vec<[u8; 20]>,
pub topics: Vec<Option<Vec<[u8; 32]>>>,
pub event_types: Vec<EventType>,
pub vm_types: Vec<VmType>,
pub include_removed: bool,
}
impl EventFilter {
pub fn new() -> Self {
Self::default()
}
pub fn with_event_types(mut self, types: Vec<EventType>) -> Self {
self.event_types = types;
self
}
pub fn with_addresses(mut self, addrs: Vec<[u8; 20]>) -> Self {
self.addresses = addrs;
self
}
pub fn matches(&self, envelope: &EventEnvelope) -> bool {
if let Some(from) = self.from_block
&& let Some(bh) = envelope.block_height
&& bh < from
{
return false;
}
if let Some(to) = self.to_block
&& let Some(bh) = envelope.block_height
&& bh > to
{
return false;
}
if let Some(from_seq) = self.from_sequence
&& envelope.sequence < from_seq
{
return false;
}
if !self.event_types.is_empty()
&& !self.event_types.contains(&envelope.event_type())
{
return false;
}
if !self.vm_types.is_empty() {
match &envelope.vm_type {
Some(vm) if self.vm_types.contains(vm) => {}
Some(_) => return false,
None => return false,
}
}
if !self.addresses.is_empty() && !self.matches_address(&envelope.event) {
return false;
}
if !self.topics.is_empty() {
if let TenzroEvent::Log { topics, removed, .. } = &envelope.event {
if *removed && !self.include_removed {
return false;
}
if !self.matches_topics(topics) {
return false;
}
}
else {
return false;
}
}
if !self.include_removed
&& let TenzroEvent::Log { removed: true, .. } = &envelope.event
{
return false;
}
true
}
fn matches_address(&self, event: &TenzroEvent) -> bool {
let addrs = self.event_addresses(event);
addrs.iter().any(|a| self.addresses.contains(a))
}
fn event_addresses(&self, event: &TenzroEvent) -> Vec<[u8; 20]> {
match event {
TenzroEvent::NewBlock { proposer, .. } => vec![*proposer],
TenzroEvent::NewPendingTransaction { from, to, .. } => {
let mut v = vec![*from];
if let Some(t) = to {
v.push(*t);
}
v
}
TenzroEvent::Log { address, .. } => vec![*address],
TenzroEvent::Transfer { from, to, .. } => vec![*from, *to],
TenzroEvent::NftTransfer { from, to, .. } => vec![*from, *to],
TenzroEvent::CrosschainMint { to, .. } => vec![*to],
TenzroEvent::CrosschainBurn { from, .. } => vec![*from],
TenzroEvent::ModelRegistered { provider, .. } => vec![*provider],
TenzroEvent::InferenceCompleted { provider, .. } => vec![*provider],
TenzroEvent::SettlementCompleted { payer, payee, .. } => vec![*payer, *payee],
TenzroEvent::PaymentChannelUpdate { sender, receiver, .. } => {
vec![*sender, *receiver]
}
TenzroEvent::StakeDeposited { staker, .. } => vec![*staker],
TenzroEvent::StakeWithdrawn { staker, .. } => vec![*staker],
TenzroEvent::ValidatorSlashed { validator, .. } => vec![*validator],
TenzroEvent::ProposalCreated { proposer, .. } => vec![*proposer],
TenzroEvent::VoteCast { voter, .. } => vec![*voter],
TenzroEvent::BridgeTransferInitiated { sender, .. } => vec![*sender],
TenzroEvent::BridgeTransferCompleted { receiver, .. } => vec![*receiver],
_ => vec![],
}
}
fn matches_topics(&self, event_topics: &[[u8; 32]]) -> bool {
for (i, slot) in self.topics.iter().enumerate() {
if let Some(wanted) = slot {
match event_topics.get(i) {
Some(actual) if wanted.contains(actual) => {}
_ => return false,
}
}
}
true
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscriptionConfig {
pub id: SubscriptionId,
pub filter: EventFilter,
pub max_events_per_second: Option<u32>,
pub include_envelope: bool,
}
impl SubscriptionConfig {
pub fn new(id: SubscriptionId, filter: EventFilter) -> Self {
Self {
id,
filter,
max_events_per_second: None,
include_envelope: true,
}
}
pub fn with_max_events_per_second(mut self, rate: u32) -> Self {
self.max_events_per_second = Some(rate);
self
}
pub fn with_include_envelope(mut self, include: bool) -> Self {
self.include_envelope = include;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_addr() -> [u8; 20] {
let mut a = [0u8; 20];
a[19] = 0x42;
a
}
fn sample_hash() -> [u8; 32] {
let mut h = [0u8; 32];
h[0] = 0xAB;
h
}
fn make_transfer() -> TenzroEvent {
TenzroEvent::Transfer {
from: sample_addr(),
to: [1u8; 20],
amount: 1_000_000,
token_id: "TNZO".into(),
tx_hash: sample_hash(),
}
}
fn wrap(seq: u64, event: TenzroEvent) -> EventEnvelope {
EventEnvelope {
sequence: seq,
timestamp: 1700000000000,
block_height: Some(100),
vm_type: Some(VmType::Native),
event,
}
}
#[test]
fn test_event_creation_and_type() {
let e = make_transfer();
assert_eq!(e.event_type(), EventType::Transfer);
assert_eq!(event_type_name(&e), "Transfer");
}
#[test]
fn test_all_event_types_mapped() {
let all = vec![
EventType::NewBlock,
EventType::BlockFinalized,
EventType::BlockReorged,
EventType::NewPendingTransaction,
EventType::TransactionIncluded,
EventType::TransactionFinalized,
EventType::Log,
EventType::Transfer,
EventType::NftTransfer,
EventType::CrosschainMint,
EventType::CrosschainBurn,
EventType::IdentityRegistered,
EventType::CredentialIssued,
EventType::ComplianceViolation,
EventType::ModelRegistered,
EventType::InferenceCompleted,
EventType::InferenceStreamStarted,
EventType::InferenceStreamFirstToken,
EventType::InferenceStreamDropped,
EventType::InferenceStreamCompleted,
EventType::AgentMessage,
EventType::SettlementCompleted,
EventType::PaymentChannelUpdate,
EventType::StakeDeposited,
EventType::StakeWithdrawn,
EventType::ValidatorSlashed,
EventType::ProposalCreated,
EventType::VoteCast,
EventType::BridgeTransferInitiated,
EventType::BridgeTransferCompleted,
EventType::SyncProgress,
EventType::WorkflowCreated,
EventType::WorkflowLifecycleTransitioned,
EventType::WorkflowReceiptEmitted,
EventType::ApprovalRequested,
EventType::ApprovalFinalized,
];
for et in &all {
let name = event_type_static_name(*et);
assert!(!name.is_empty(), "Empty name for {:?}", et);
}
assert_eq!(all.len(), 36);
}
#[test]
fn workflow_event_carries_privacy_domain() {
let wf_id = [9u8; 32];
let pd = [7u8; 32];
let e = TenzroEvent::WorkflowCreated {
workflow_id: wf_id,
creator_did: "did:tenzro:human:alice".into(),
title: "Trade settlement".into(),
privacy_domain: Some(pd),
};
assert_eq!(e.event_type(), EventType::WorkflowCreated);
assert_eq!(e.privacy_domain(), Some(pd));
let public = TenzroEvent::WorkflowLifecycleTransitioned {
workflow_id: wf_id,
from_status: "Draft".into(),
to_status: "AwaitingSignatures".into(),
trigger: "Participant".into(),
privacy_domain: None,
};
assert_eq!(public.privacy_domain(), None);
}
#[test]
fn workflow_event_serde_roundtrip() {
let e = TenzroEvent::ApprovalFinalized {
workflow_id: [1u8; 32],
gate_id: [2u8; 32],
request_id: [3u8; 32],
outcome: "Approved".into(),
privacy_domain: Some([4u8; 32]),
};
let json = serde_json::to_string(&e).unwrap();
let back: TenzroEvent = serde_json::from_str(&json).unwrap();
assert_eq!(e, back);
}
#[test]
fn inference_stream_started_roundtrip() {
let e = TenzroEvent::InferenceStreamStarted {
request_id: "req-abc-123".into(),
model_id: "qwen3-0.6b".into(),
provider_label: "local".into(),
};
let json = serde_json::to_string(&e).unwrap();
let back: TenzroEvent = serde_json::from_str(&json).unwrap();
assert_eq!(e, back);
assert_eq!(e.event_type(), EventType::InferenceStreamStarted);
}
#[test]
fn inference_stream_first_token_roundtrip() {
let e = TenzroEvent::InferenceStreamFirstToken {
request_id: "req-abc-123".into(),
model_id: "qwen3-0.6b".into(),
provider_label: "0xdeadbeef".into(),
ttft_ms: 250,
};
let json = serde_json::to_string(&e).unwrap();
let back: TenzroEvent = serde_json::from_str(&json).unwrap();
assert_eq!(e, back);
assert_eq!(e.event_type(), EventType::InferenceStreamFirstToken);
if let TenzroEvent::InferenceStreamFirstToken { ttft_ms, .. } = back {
assert_eq!(ttft_ms, 250);
} else {
panic!("wrong variant");
}
}
#[test]
fn inference_stream_dropped_with_stall() {
let e = TenzroEvent::InferenceStreamDropped {
request_id: "req-abc-123".into(),
model_id: "qwen3-0.6b".into(),
provider_label: "0xdeadbeef".into(),
reason: "stall".into(),
silent_for_ms: Some(10_500),
};
let json = serde_json::to_string(&e).unwrap();
let back: TenzroEvent = serde_json::from_str(&json).unwrap();
assert_eq!(e, back);
assert_eq!(e.event_type(), EventType::InferenceStreamDropped);
if let TenzroEvent::InferenceStreamDropped {
reason,
silent_for_ms,
..
} = back
{
assert_eq!(reason, "stall");
assert_eq!(silent_for_ms, Some(10_500));
} else {
panic!("wrong variant");
}
}
#[test]
fn inference_stream_completed_success() {
let e = TenzroEvent::InferenceStreamCompleted {
request_id: "req-abc-123".into(),
model_id: "qwen3-0.6b".into(),
provider_label: "local".into(),
latency_ms: 4_200,
success: true,
};
let json = serde_json::to_string(&e).unwrap();
let back: TenzroEvent = serde_json::from_str(&json).unwrap();
assert_eq!(e, back);
assert_eq!(e.event_type(), EventType::InferenceStreamCompleted);
if let TenzroEvent::InferenceStreamCompleted {
latency_ms,
success,
..
} = back
{
assert_eq!(latency_ms, 4_200);
assert!(success);
} else {
panic!("wrong variant");
}
}
#[test]
fn test_serialization_roundtrip_transfer() {
let event = make_transfer();
let json = serde_json::to_string(&event).unwrap();
let back: TenzroEvent = serde_json::from_str(&json).unwrap();
assert_eq!(event, back);
}
#[test]
fn test_serialization_roundtrip_envelope() {
let env = wrap(42, make_transfer());
let json = serde_json::to_string(&env).unwrap();
let back: EventEnvelope = serde_json::from_str(&json).unwrap();
assert_eq!(env, back);
assert_eq!(back.event_type(), EventType::Transfer);
}
#[test]
fn test_serialization_roundtrip_log_event() {
let event = TenzroEvent::Log {
address: sample_addr(),
topics: vec![sample_hash(), [0xCDu8; 32]],
data: vec![1, 2, 3, 4],
block_height: 500,
tx_hash: sample_hash(),
log_index: 7,
removed: false,
};
let json = serde_json::to_string(&event).unwrap();
let back: TenzroEvent = serde_json::from_str(&json).unwrap();
assert_eq!(event, back);
}
#[test]
fn test_filter_matches_event_type() {
let env = wrap(1, make_transfer());
let filter = EventFilter {
event_types: vec![EventType::Transfer],
..Default::default()
};
assert!(filter.matches(&env));
let filter_miss = EventFilter {
event_types: vec![EventType::NewBlock],
..Default::default()
};
assert!(!filter_miss.matches(&env));
}
#[test]
fn test_filter_matches_block_range() {
let env = wrap(1, make_transfer());
let within = EventFilter {
from_block: Some(50),
to_block: Some(150),
..Default::default()
};
assert!(within.matches(&env));
let below = EventFilter {
from_block: Some(101),
..Default::default()
};
assert!(!below.matches(&env));
let above = EventFilter {
to_block: Some(99),
..Default::default()
};
assert!(!above.matches(&env));
}
#[test]
fn test_filter_matches_sequence_cursor() {
let env = wrap(10, make_transfer());
let before = EventFilter {
from_sequence: Some(5),
..Default::default()
};
assert!(before.matches(&env));
let exact = EventFilter {
from_sequence: Some(10),
..Default::default()
};
assert!(exact.matches(&env));
let after = EventFilter {
from_sequence: Some(11),
..Default::default()
};
assert!(!after.matches(&env));
}
#[test]
fn test_filter_matches_address() {
let env = wrap(1, make_transfer());
let hit = EventFilter {
addresses: vec![sample_addr()],
..Default::default()
};
assert!(hit.matches(&env));
let miss = EventFilter {
addresses: vec![[0xFFu8; 20]],
..Default::default()
};
assert!(!miss.matches(&env));
}
#[test]
fn test_filter_matches_topics() {
let topic0 = sample_hash();
let topic1 = [0xCDu8; 32];
let log_event = TenzroEvent::Log {
address: sample_addr(),
topics: vec![topic0, topic1],
data: vec![],
block_height: 100,
tx_hash: sample_hash(),
log_index: 0,
removed: false,
};
let env = wrap(1, log_event);
let filter = EventFilter {
topics: vec![Some(vec![topic0])],
..Default::default()
};
assert!(filter.matches(&env));
let filter2 = EventFilter {
topics: vec![None, Some(vec![topic1])],
..Default::default()
};
assert!(filter2.matches(&env));
let filter_miss = EventFilter {
topics: vec![Some(vec![[0xFFu8; 32]])],
..Default::default()
};
assert!(!filter_miss.matches(&env));
}
#[test]
fn test_filter_removed_log_excluded_by_default() {
let removed_log = TenzroEvent::Log {
address: sample_addr(),
topics: vec![],
data: vec![],
block_height: 100,
tx_hash: sample_hash(),
log_index: 0,
removed: true,
};
let env = wrap(1, removed_log);
let default_filter = EventFilter::default();
assert!(!default_filter.matches(&env));
let include_removed = EventFilter {
include_removed: true,
..Default::default()
};
assert!(include_removed.matches(&env));
}
#[test]
fn test_filter_vm_type() {
let env = wrap(1, make_transfer());
let native_filter = EventFilter {
vm_types: vec![VmType::Native],
..Default::default()
};
assert!(native_filter.matches(&env));
let evm_filter = EventFilter {
vm_types: vec![VmType::Evm],
..Default::default()
};
assert!(!evm_filter.matches(&env));
}
#[test]
fn test_display_formatting() {
let e = make_transfer();
let s = format!("{}", e);
assert!(s.contains("Transfer"));
assert!(s.contains("1000000"));
assert!(s.contains("TNZO"));
let env = wrap(42, e);
let s2 = format!("{}", env);
assert!(s2.contains("seq=42"));
}
#[test]
fn test_subscription_config_builder() {
let cfg = SubscriptionConfig::new(SubscriptionId(1), EventFilter::default())
.with_max_events_per_second(100)
.with_include_envelope(false);
assert_eq!(cfg.id, SubscriptionId(1));
assert_eq!(cfg.max_events_per_second, Some(100));
assert!(!cfg.include_envelope);
}
#[test]
fn test_envelope_event_type() {
let env = wrap(
1,
TenzroEvent::BlockFinalized {
block_hash: sample_hash(),
height: 50,
},
);
assert_eq!(env.event_type(), EventType::BlockFinalized);
}
#[test]
fn test_event_type_display() {
assert_eq!(format!("{}", EventType::NewBlock), "NewBlock");
assert_eq!(format!("{}", EventType::SyncProgress), "SyncProgress");
}
}