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}