Skip to main content

tenzro_types/
canton.rs

1//! Canton Network / Daml 3.x types for Tenzro Network
2//!
3//! Types representing Canton/Daml concepts as specified by the Canton Network protocol
4//! and Daml 3.x Ledger API. Tenzro nodes run Canton participant/validator processes
5//! natively, making every Tenzro validator a full participant in the Canton Network.
6//!
7//! # Canton Architecture
8//!
9//! Canton operates as a "network of networks" — a permissionless network of permissioned
10//! subnets (synchronizers) that delivers enterprise-grade sub-transaction privacy without
11//! sacrificing composability. Key components:
12//!
13//! - **Participant Nodes**: Runtime environment for Daml contracts, exposing Ledger API (gRPC)
14//! - **Synchronizers** (formerly "domains"): Provide message routing, ordering, and 2PC coordination
15//! - **Global Synchronizer**: Public, permissionless sync domain for cross-domain coordination
16//!
17//! # Daml 3.x Ledger API Services
18//!
19//! The Ledger API (default port 5001) exposes these gRPC services:
20//! - `CommandService` / `CommandSubmissionService` — Submit Daml commands
21//! - `CommandCompletionService` — Track command execution status
22//! - `UpdateService` (replaces TransactionService) — Stream committed transactions
23//! - `StateService` (replaces ActiveContractsService) — Query active contracts
24//! - `EventQueryService` — Query create/archive events
25//! - `PackageService` / `PackageManagementService` — Manage DAR packages
26//! - `PartyManagementService` — Allocate and query parties
27//! - `UserManagementService` — Manage users and access rights
28//! - `InteractiveSubmissionService` — Two-step prepare→sign→execute flow
29//!
30//! The Admin API (default port 5002) exposes:
31//! - `PackageService` — DAR upload/removal via `com.digitalasset.canton.admin.participant.v30`
32//! - `TopologyManagerWriteService` / `TopologyManagerReadService` — Topology transactions
33//! - `ParticipantStatusService` — Node health and status
34
35use serde::{Deserialize, Serialize};
36
37// ---------------------------------------------------------------------------
38// Identifiers
39// ---------------------------------------------------------------------------
40
41/// Unique identifier for a Daml contract instance.
42///
43/// In the Daml Ledger API, contract IDs are opaque strings assigned by the
44/// participant when a contract is created. They are globally unique.
45#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
46pub struct DamlContractId(pub String);
47
48impl DamlContractId {
49    /// Create a new contract ID
50    pub fn new(id: impl Into<String>) -> Self {
51        Self(id.into())
52    }
53
54    /// Get the contract ID as a string
55    pub fn as_str(&self) -> &str {
56        &self.0
57    }
58}
59
60impl std::fmt::Display for DamlContractId {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(f, "{}", self.0)
63    }
64}
65
66/// Fully-qualified Daml template/interface identifier.
67///
68/// Maps to the Ledger API protobuf `Identifier` message:
69/// ```protobuf
70/// message Identifier {
71///   string package_id = 1;
72///   string module_name = 2;
73///   string entity_name = 3;
74/// }
75/// ```
76#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
77pub struct DamlTemplateId {
78    /// Package ID (content-hash of the DAR package)
79    pub package_id: String,
80    /// Module name within the package (e.g. "MyModule.Contracts")
81    pub module_name: String,
82    /// Entity (template/interface) name within the module
83    pub entity_name: String,
84}
85
86impl DamlTemplateId {
87    /// Create a new template ID
88    pub fn new(
89        package_id: impl Into<String>,
90        module_name: impl Into<String>,
91        entity_name: impl Into<String>,
92    ) -> Self {
93        Self {
94            package_id: package_id.into(),
95            module_name: module_name.into(),
96            entity_name: entity_name.into(),
97        }
98    }
99
100    /// Get the fully-qualified name in `package_id:module_name:entity_name` format
101    pub fn qualified_name(&self) -> String {
102        format!("{}:{}:{}", self.package_id, self.module_name, self.entity_name)
103    }
104}
105
106impl std::fmt::Display for DamlTemplateId {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(f, "{}:{}:{}", self.package_id, self.module_name, self.entity_name)
109    }
110}
111
112/// A Daml party identifier.
113///
114/// In Canton, parties follow the format `name::fingerprint` where the fingerprint
115/// is derived from a SHA-256 hash of the party's namespace signing key. Parties
116/// are on-ledger identities that can be hosted on multiple participant nodes
117/// simultaneously via `PartyToParticipant` topology transactions.
118#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
119pub struct DamlParty(pub String);
120
121impl DamlParty {
122    /// Create a new party
123    pub fn new(party: impl Into<String>) -> Self {
124        Self(party.into())
125    }
126
127    /// Get the party as a string
128    pub fn as_str(&self) -> &str {
129        &self.0
130    }
131}
132
133impl std::fmt::Display for DamlParty {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        write!(f, "{}", self.0)
136    }
137}
138
139/// Canton synchronizer identifier (formerly "domain ID").
140///
141/// In Canton 3.x, what was previously called a "domain" is now called a
142/// "synchronizer". A synchronizer provides message routing, transaction ordering,
143/// and two-phase commit coordination. It comprises three core services:
144///
145/// - **Sequencer**: Establishes total order of messages, assigns unique timestamps
146/// - **Mediator**: Transaction commit coordinator, collects confirmations
147/// - **Topology Manager**: Manages participant joins/leaves, key registration
148///
149/// The Global Synchronizer is a special public, permissionless synchronizer
150/// operated by Super Validators using BFT consensus.
151#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
152pub struct CantonSynchronizerId(pub String);
153
154impl CantonSynchronizerId {
155    /// Create a new synchronizer ID
156    pub fn new(id: impl Into<String>) -> Self {
157        Self(id.into())
158    }
159
160    /// Get the synchronizer ID as a string
161    pub fn as_str(&self) -> &str {
162        &self.0
163    }
164}
165
166impl std::fmt::Display for CantonSynchronizerId {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        write!(f, "{}", self.0)
169    }
170}
171
172/// Backwards-compatible alias: Canton 3.x renamed "domain" to "synchronizer"
173pub type CantonDomainId = CantonSynchronizerId;
174
175// ---------------------------------------------------------------------------
176// Values
177// ---------------------------------------------------------------------------
178
179/// Daml value — represents any Daml data value.
180///
181/// Maps to the Ledger API protobuf `Value` message. Daml's type system includes
182/// records (product types), variants (sum types), lists, optionals, maps, and
183/// primitive types (Int64, Numeric, Text, Bool, Timestamp, Date, Party, ContractId).
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
185#[serde(tag = "type", content = "value")]
186pub enum DamlValue {
187    /// Record (product type) with named fields.
188    /// Maps to `Value.record` with `record_id` (Identifier) and repeated `fields` (RecordField).
189    Record {
190        /// Record type ID (optional Identifier)
191        record_id: Option<DamlTemplateId>,
192        /// Fields as (label, value) pairs
193        fields: Vec<(String, DamlValue)>,
194    },
195    /// Variant (sum type) with a constructor and value.
196    /// Maps to `Value.variant` with `variant_id`, `constructor`, and `value`.
197    Variant {
198        /// Variant type ID (optional Identifier)
199        variant_id: Option<DamlTemplateId>,
200        /// Constructor name
201        constructor: String,
202        /// Constructor argument
203        value: Box<DamlValue>,
204    },
205    /// Ordered list of values. Maps to `Value.list`.
206    List(Vec<DamlValue>),
207    /// 64-bit signed integer. Maps to `Value.int64`.
208    Int64(i64),
209    /// Arbitrary-precision decimal (as string). Maps to `Value.numeric`.
210    Numeric(String),
211    /// Text string. Maps to `Value.text`.
212    Text(String),
213    /// Boolean. Maps to `Value.bool`.
214    Bool(bool),
215    /// Timestamp (microseconds since epoch). Maps to `Value.timestamp`.
216    Timestamp(i64),
217    /// Date (days since epoch). Maps to `Value.date`.
218    Date(i32),
219    /// Party identifier. Maps to `Value.party`.
220    Party(DamlParty),
221    /// Contract ID reference. Maps to `Value.contract_id`.
222    ContractId(DamlContractId),
223    /// Optional value. Maps to `Value.optional`.
224    Optional(Option<Box<DamlValue>>),
225    /// Map with text keys. Maps to `Value.map`.
226    Map(Vec<(String, DamlValue)>),
227    /// Enum constructor (no value). Maps to `Value.enum`.
228    Enum {
229        /// Enum type ID (optional Identifier)
230        enum_id: Option<DamlTemplateId>,
231        /// Constructor name
232        constructor: String,
233    },
234    /// Unit value. Maps to `Value.unit`.
235    Unit,
236}
237
238// ---------------------------------------------------------------------------
239// Commands
240// ---------------------------------------------------------------------------
241
242/// Daml command — an action to submit to the Canton Ledger API.
243///
244/// Commands are submitted via `CommandService.SubmitAndWait` (synchronous) or
245/// `CommandSubmissionService.Submit` (asynchronous). Multiple commands in a
246/// single submission are executed atomically.
247///
248/// In Daml 3.x, the `InteractiveSubmissionService` also supports a two-step
249/// prepare→sign→execute flow for external signing.
250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
251#[serde(tag = "command_type")]
252pub enum DamlCommand {
253    /// Create a new contract from a template.
254    /// The submitting party must be a signatory of the template.
255    Create {
256        /// Template to instantiate
257        template_id: DamlTemplateId,
258        /// Contract arguments (Record value matching template fields)
259        create_arguments: DamlValue,
260    },
261    /// Exercise a choice on an existing contract.
262    /// The submitting party must be a controller of the choice.
263    /// Consuming choices archive the contract; non-consuming choices leave it active.
264    Exercise {
265        /// Template of the contract
266        template_id: DamlTemplateId,
267        /// Contract to exercise on
268        contract_id: DamlContractId,
269        /// Choice name
270        choice: String,
271        /// Choice arguments
272        choice_argument: DamlValue,
273    },
274    /// Create a contract and immediately exercise a choice on it.
275    /// Useful when commands depend on each other within a single transaction.
276    CreateAndExercise {
277        /// Template to instantiate
278        template_id: DamlTemplateId,
279        /// Contract arguments
280        create_arguments: DamlValue,
281        /// Choice to exercise
282        choice: String,
283        /// Choice arguments
284        choice_argument: DamlValue,
285    },
286    /// Exercise a choice on a contract identified by its key rather than contract ID.
287    /// The contract key must be maintained by the submitting party.
288    ExerciseByKey {
289        /// Template of the contract
290        template_id: DamlTemplateId,
291        /// Contract key value
292        contract_key: DamlValue,
293        /// Choice name
294        choice: String,
295        /// Choice arguments
296        choice_argument: DamlValue,
297    },
298}
299
300// ---------------------------------------------------------------------------
301// Events
302// ---------------------------------------------------------------------------
303
304/// A Daml ledger event.
305///
306/// Events are produced by committed transactions and delivered through the
307/// `UpdateService` (Daml 3.x) or `TransactionService` (legacy). Parties only
308/// receive events for contracts where they are a stakeholder (signatory or observer),
309/// enforcing Canton's sub-transaction privacy model.
310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311#[serde(tag = "event_type")]
312pub enum DamlEvent {
313    /// A contract was created (maps to `CreatedEvent` in the Ledger API).
314    Created {
315        /// Event ID (unique within the transaction)
316        event_id: String,
317        /// Contract ID of the created contract
318        contract_id: DamlContractId,
319        /// Template ID
320        template_id: DamlTemplateId,
321        /// Contract arguments (the template payload)
322        create_arguments: DamlValue,
323        /// Signatories — parties who authorized the creation
324        signatories: Vec<DamlParty>,
325        /// Observers — parties who can see the contract
326        observers: Vec<DamlParty>,
327    },
328    /// A contract was archived (maps to `ArchivedEvent` in the Ledger API).
329    /// This occurs when a consuming choice is exercised on the contract.
330    Archived {
331        /// Event ID
332        event_id: String,
333        /// Contract ID of the archived contract
334        contract_id: DamlContractId,
335        /// Template ID
336        template_id: DamlTemplateId,
337    },
338}
339
340// ---------------------------------------------------------------------------
341// Transactions
342// ---------------------------------------------------------------------------
343
344/// A completed Daml transaction.
345///
346/// Transactions are the result of successfully committed command submissions.
347/// They contain the events produced and metadata about where and when the
348/// transaction was committed. In Canton, transactions have sub-transaction privacy:
349/// each party only sees the events they are entitled to.
350#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
351pub struct DamlTransaction {
352    /// Unique transaction ID
353    pub transaction_id: String,
354    /// Command ID that triggered this transaction (for correlation)
355    pub command_id: String,
356    /// Workflow ID (for cross-party workflow tracking)
357    pub workflow_id: Option<String>,
358    /// Events produced by this transaction
359    pub events: Vec<DamlEvent>,
360    /// When the transaction took effect (microseconds since epoch)
361    pub effective_at: i64,
362    /// Offset in the participant's ledger (for resuming event streams)
363    pub offset: String,
364    /// Synchronizer on which the transaction was committed
365    pub synchronizer_id: Option<CantonSynchronizerId>,
366}
367
368// ---------------------------------------------------------------------------
369// Participant configuration
370// ---------------------------------------------------------------------------
371
372/// Canton participant node configuration.
373///
374/// Each Tenzro validator runs a Canton participant process that exposes:
375/// - **Ledger API** (default port 5001): gRPC API for applications
376/// - **Admin API** (default port 5002): gRPC API for administration
377///
378/// The participant maintains a Private Contract Store (PCS) with party-specific
379/// views of the distributed ledger, runs the Daml engine for contract execution,
380/// and can connect to multiple synchronizers simultaneously.
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct CantonParticipantConfig {
383    /// Ledger API host
384    pub host: String,
385    /// Ledger API port (default: 5001 in Canton 3.x)
386    pub ledger_api_port: u16,
387    /// Admin API port (default: 5002 in Canton 3.x)
388    pub admin_api_port: u16,
389    /// Whether to use TLS for Ledger API connections
390    pub use_tls: bool,
391    /// Optional TLS certificate path
392    pub tls_cert_path: Option<String>,
393    /// Acting-as parties for command submissions
394    pub act_as: Vec<DamlParty>,
395    /// Read-as parties for queries (additional visibility)
396    pub read_as: Vec<DamlParty>,
397    /// Application ID for command deduplication
398    pub application_id: String,
399    /// Synchronizer IDs this participant is connected to
400    pub synchronizer_ids: Vec<CantonSynchronizerId>,
401}
402
403impl Default for CantonParticipantConfig {
404    fn default() -> Self {
405        Self {
406            host: "localhost".to_string(),
407            ledger_api_port: 5001,
408            admin_api_port: 5002,
409            use_tls: false,
410            tls_cert_path: None,
411            act_as: vec![],
412            read_as: vec![],
413            application_id: "tenzro-canton-participant".to_string(),
414            synchronizer_ids: vec![],
415        }
416    }
417}
418
419impl CantonParticipantConfig {
420    /// Get the Ledger API endpoint URL
421    pub fn ledger_api_endpoint(&self) -> String {
422        let scheme = if self.use_tls { "https" } else { "http" };
423        format!("{}://{}:{}", scheme, self.host, self.ledger_api_port)
424    }
425
426    /// Get the Admin API endpoint URL
427    pub fn admin_api_endpoint(&self) -> String {
428        let scheme = if self.use_tls { "https" } else { "http" };
429        format!("{}://{}:{}", scheme, self.host, self.admin_api_port)
430    }
431}
432
433// ---------------------------------------------------------------------------
434// Command status
435// ---------------------------------------------------------------------------
436
437/// Status of a Canton command submission.
438///
439/// Commands go through: Accepted → Committed (success) or Rejected (failure).
440/// The `CommandCompletionService` streams these status updates.
441#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
442pub enum CantonCommandStatus {
443    /// Command accepted by the participant and being processed
444    Accepted,
445    /// Command successfully committed to the ledger
446    Committed,
447    /// Command rejected by the ledger (validation failure, authorization error, etc.)
448    Rejected { reason: String },
449    /// Command timed out before receiving a definitive result
450    TimedOut,
451}
452
453// ---------------------------------------------------------------------------
454// Package management
455// ---------------------------------------------------------------------------
456
457/// A DAR (Daml Archive) package descriptor.
458///
459/// DARs are uploaded via the Admin API's `PackageService.UploadDar` or the
460/// Ledger API's `PackageManagementService`. Each package is identified by a
461/// content-hash and must be "vetted" by participants via `VettedPackages`
462/// topology transactions before contracts from it can be created.
463#[derive(Debug, Clone, Serialize, Deserialize)]
464pub struct DamlPackageInfo {
465    /// Package ID (content hash of the Daml-LF archive)
466    pub package_id: String,
467    /// Package name (from Daml.yaml)
468    pub package_name: Option<String>,
469    /// Package version (from Daml.yaml)
470    pub package_version: Option<String>,
471    /// Size of the DAR in bytes
472    pub size_bytes: u64,
473    /// Modules contained in the package
474    pub modules: Vec<String>,
475}
476
477// ---------------------------------------------------------------------------
478// Topology transactions
479// ---------------------------------------------------------------------------
480
481/// Canton topology transaction types.
482///
483/// Topology transactions define the network structure: which parties are hosted
484/// on which participants, which keys are authorized, and which packages are vetted.
485/// All topology transactions require signatures from authorized namespace keys.
486#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
487pub enum TopologyTransaction {
488    /// Delegates namespace authorization to a key.
489    /// Root delegations inherit the right to authorize other delegations.
490    NamespaceDelegation {
491        /// Namespace being delegated
492        namespace: String,
493        /// Target signing key fingerprint
494        target_key: String,
495        /// Whether this is a root delegation
496        is_root: bool,
497    },
498    /// Maps a party to one or more participant nodes.
499    /// A party can be hosted on multiple participants simultaneously.
500    PartyToParticipant {
501        /// Party identifier
502        party: DamlParty,
503        /// Participant ID
504        participant_id: String,
505        /// Permission level (Submission, Confirmation, Observation)
506        permission: ParticipantPermission,
507    },
508    /// Maps an owner to their public signing/encryption keys.
509    OwnerToKeyMapping {
510        /// Owner (participant or party namespace)
511        owner: String,
512        /// Public key fingerprints
513        keys: Vec<String>,
514    },
515    /// Declares packages vetted by a participant.
516    /// Contracts from unvetted packages cannot be created on that participant.
517    VettedPackages {
518        /// Participant ID
519        participant_id: String,
520        /// Package IDs that are vetted
521        package_ids: Vec<String>,
522    },
523    /// Defines participant permissions on a synchronizer.
524    ParticipantSynchronizerPermission {
525        /// Synchronizer ID
526        synchronizer_id: CantonSynchronizerId,
527        /// Participant ID
528        participant_id: String,
529        /// Permission level
530        permission: ParticipantPermission,
531    },
532}
533
534/// Permission level for a participant on a synchronizer or for a party on a participant.
535#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
536pub enum ParticipantPermission {
537    /// Can submit commands (highest level)
538    Submission,
539    /// Can confirm transactions
540    Confirmation,
541    /// Can only observe (read-only)
542    Observation,
543}
544
545// ---------------------------------------------------------------------------
546// Synchronizer configuration
547// ---------------------------------------------------------------------------
548
549/// Configuration for a Canton synchronizer (formerly "domain").
550///
551/// A synchronizer provides the coordination layer for Daml transactions. It
552/// comprises three services that can be deployed monolithically or distributed:
553///
554/// - **Sequencer**: Total-order multicast, assigns timestamps
555/// - **Mediator**: 2PC coordinator, collects confirmations
556/// - **Topology Manager**: Party/key/package topology state
557///
558/// All payloads are end-to-end encrypted between participant nodes — the
559/// synchronizer is "blind" to transaction contents.
560#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct SynchronizerConfig {
562    /// Synchronizer ID
563    pub synchronizer_id: CantonSynchronizerId,
564    /// Sequencer connection endpoints
565    pub sequencer_connections: Vec<String>,
566    /// Whether this is the Global Synchronizer (public, permissionless)
567    pub is_global: bool,
568    /// Estimated finality time in seconds
569    pub finality_seconds: u32,
570}
571
572impl Default for SynchronizerConfig {
573    fn default() -> Self {
574        Self {
575            synchronizer_id: CantonSynchronizerId::new("default"),
576            sequencer_connections: vec!["http://localhost:5008".to_string()],
577            is_global: false,
578            finality_seconds: 5,
579        }
580    }
581}