Skip to main content

daml_grpc/data/
reassignment.rs

1use std::convert::TryFrom;
2
3use chrono::{DateTime, Utc};
4
5use crate::data::event::DamlCreatedEvent;
6use crate::data::identifier::DamlIdentifier;
7use crate::data::offset::DamlLedgerOffset;
8use crate::data::{DamlError, DamlResult};
9use crate::grpc_protobuf::com::daml::ledger::api::v2::reassignment_command::Command as ReassignmentCommandKind;
10use crate::grpc_protobuf::com::daml::ledger::api::v2::reassignment_event::Event as ReassignmentEventKind;
11use crate::grpc_protobuf::com::daml::ledger::api::v2::{
12    AssignCommand, AssignedEvent, Reassignment, ReassignmentCommand, ReassignmentCommands, ReassignmentEvent,
13    UnassignCommand, UnassignedEvent,
14};
15use crate::util;
16use crate::util::Required;
17
18// ---------------------------------------------------------------------------
19// Response-side: Reassignment + ReassignmentEvent + Unassigned/Assigned
20// ---------------------------------------------------------------------------
21
22/// An on-ledger reassignment, which moves a contract across
23/// synchronizers in two steps (unassign on the source, assign on the
24/// target). The protocol emits a separate `DamlReassignment` for each
25/// of the two halves: the source-synchronizer reassignment contains an
26/// `Unassigned` event, the target-synchronizer one an `Assigned` event.
27///
28/// `command_id` is empty for everyone except the submitting party on
29/// the submitting participant; `workflow_id` is empty when the
30/// originating command didn't set one.
31#[derive(Debug, Eq, PartialEq, Clone)]
32pub struct DamlReassignment {
33    pub update_id: String,
34    pub command_id: String,
35    pub workflow_id: String,
36    pub offset: DamlLedgerOffset,
37    pub events: Vec<DamlReassignmentEvent>,
38    /// Record time on the synchronizer this `Reassignment` came from
39    /// — the source for unassign events, the target for assign events.
40    pub record_time: DateTime<Utc>,
41    pub synchronizer_id: String,
42    pub paid_traffic_cost: Option<i64>,
43}
44
45impl TryFrom<Reassignment> for DamlReassignment {
46    type Error = DamlError;
47
48    fn try_from(r: Reassignment) -> DamlResult<Self> {
49        Ok(Self {
50            update_id: r.update_id,
51            command_id: r.command_id,
52            workflow_id: r.workflow_id,
53            offset: DamlLedgerOffset::new(r.offset),
54            events: r.events.into_iter().map(DamlReassignmentEvent::try_from).collect::<DamlResult<_>>()?,
55            record_time: util::from_grpc_timestamp(&r.record_time.req()?)?,
56            synchronizer_id: r.synchronizer_id,
57            paid_traffic_cost: r.paid_traffic_cost,
58        })
59    }
60}
61
62/// One event in a [`DamlReassignment`]: either the source-side
63/// `Unassigned` half or the target-side `Assigned` half. The same
64/// `reassignment_counter` ties matching halves together.
65#[derive(Debug, Eq, PartialEq, Clone)]
66pub enum DamlReassignmentEvent {
67    Unassigned(Box<DamlUnassignedEvent>),
68    Assigned(Box<DamlAssignedEvent>),
69}
70
71impl TryFrom<ReassignmentEvent> for DamlReassignmentEvent {
72    type Error = DamlError;
73
74    fn try_from(e: ReassignmentEvent) -> DamlResult<Self> {
75        Ok(match e.event.req()? {
76            ReassignmentEventKind::Unassigned(e) => Self::Unassigned(Box::new(DamlUnassignedEvent::try_from(e)?)),
77            ReassignmentEventKind::Assigned(e) => Self::Assigned(Box::new(DamlAssignedEvent::try_from(e)?)),
78        })
79    }
80}
81
82/// Records that a contract was unassigned on its source synchronizer
83/// and made unusable there pending a matching `Assigned` on the target.
84#[derive(Debug, Eq, PartialEq, Clone)]
85pub struct DamlUnassignedEvent {
86    /// Use this id as the `reassignment_id` in a follow-up
87    /// `DamlAssignCommand` to complete the move.
88    pub reassignment_id: String,
89    pub contract_id: String,
90    pub template_id: DamlIdentifier,
91    pub source: String,
92    pub target: String,
93    /// The submitting party, or empty if the unassignment happened
94    /// via the offline repair service.
95    pub submitter: String,
96    /// Same on the matching `Assigned` event; strictly increases with
97    /// each unassign for the same contract; `0` for the original
98    /// creation.
99    pub reassignment_counter: u64,
100    /// Until this time on the target synchronizer, only the submitter
101    /// can issue the matching `Assign`. After that, any participant
102    /// can. `None` when not applicable.
103    pub assignment_exclusivity: Option<DateTime<Utc>>,
104    pub witness_parties: Vec<String>,
105    pub package_name: String,
106    pub offset: DamlLedgerOffset,
107    pub node_id: i32,
108}
109
110impl TryFrom<UnassignedEvent> for DamlUnassignedEvent {
111    type Error = DamlError;
112
113    fn try_from(e: UnassignedEvent) -> DamlResult<Self> {
114        Ok(Self {
115            reassignment_id: e.reassignment_id,
116            contract_id: e.contract_id,
117            template_id: DamlIdentifier::from(e.template_id.req()?),
118            source: e.source,
119            target: e.target,
120            submitter: e.submitter,
121            reassignment_counter: e.reassignment_counter,
122            assignment_exclusivity: e.assignment_exclusivity.as_ref().map(util::from_grpc_timestamp).transpose()?,
123            witness_parties: e.witness_parties,
124            package_name: e.package_name,
125            offset: DamlLedgerOffset::new(e.offset),
126            node_id: e.node_id,
127        })
128    }
129}
130
131/// Records that a previously-unassigned contract was assigned on its
132/// target synchronizer, making it usable there. Carries a
133/// `CreatedEvent` that materialises the contract on the target.
134#[derive(Debug, Eq, PartialEq, Clone)]
135pub struct DamlAssignedEvent {
136    pub source: String,
137    pub target: String,
138    /// Matches the `reassignment_id` of the `Unassigned` half.
139    pub reassignment_id: String,
140    pub submitter: String,
141    pub reassignment_counter: u64,
142    /// The contract as it appears on the target synchronizer. The
143    /// event's `offset` is the assignment offset; `node_id` is the
144    /// index within the assignment batch.
145    pub created_event: DamlCreatedEvent,
146}
147
148impl TryFrom<AssignedEvent> for DamlAssignedEvent {
149    type Error = DamlError;
150
151    fn try_from(e: AssignedEvent) -> DamlResult<Self> {
152        Ok(Self {
153            source: e.source,
154            target: e.target,
155            reassignment_id: e.reassignment_id,
156            submitter: e.submitter,
157            reassignment_counter: e.reassignment_counter,
158            created_event: DamlCreatedEvent::try_from(e.created_event.req()?)?,
159        })
160    }
161}
162
163// ---------------------------------------------------------------------------
164// Request-side: ReassignmentCommands + Unassign/Assign commands
165// ---------------------------------------------------------------------------
166
167/// A batch of reassignment commands processed atomically.
168///
169/// Unlike [`DamlCommands`](crate::data::DamlCommands), reassignment
170/// submissions act on behalf of a *single* `submitter` (act-as/read-as
171/// don't apply — reassignments are single-party operations).
172#[derive(Debug, Clone, Eq, PartialEq)]
173pub struct DamlReassignmentCommands {
174    pub workflow_id: String,
175    /// Same semantics as `Commands::user_id`: ignored when the request
176    /// is authenticated with a user token (the token's `user_id` wins).
177    pub user_id: String,
178    pub command_id: String,
179    pub submitter: String,
180    /// UUID per submission attempt to disambiguate retries with the
181    /// same change-id. Empty to let the participant pick.
182    pub submission_id: String,
183    /// Must be non-empty.
184    pub commands: Vec<DamlReassignmentCommand>,
185}
186
187impl From<DamlReassignmentCommands> for ReassignmentCommands {
188    fn from(c: DamlReassignmentCommands) -> Self {
189        Self {
190            workflow_id: c.workflow_id,
191            user_id: c.user_id,
192            command_id: c.command_id,
193            submitter: c.submitter,
194            submission_id: c.submission_id,
195            commands: c.commands.into_iter().map(Into::into).collect(),
196        }
197    }
198}
199
200/// One command in a [`DamlReassignmentCommands`] batch — either an
201/// `Unassign` (move out of a synchronizer) or an `Assign` (complete a
202/// prior unassignment on the target).
203#[derive(Debug, Clone, Eq, PartialEq)]
204pub enum DamlReassignmentCommand {
205    Unassign(DamlUnassignCommand),
206    Assign(DamlAssignCommand),
207}
208
209impl From<DamlReassignmentCommand> for ReassignmentCommand {
210    fn from(c: DamlReassignmentCommand) -> Self {
211        Self {
212            command: Some(match c {
213                DamlReassignmentCommand::Unassign(u) => ReassignmentCommandKind::UnassignCommand(u.into()),
214                DamlReassignmentCommand::Assign(a) => ReassignmentCommandKind::AssignCommand(a.into()),
215            }),
216        }
217    }
218}
219
220#[derive(Debug, Clone, Eq, PartialEq, Default)]
221pub struct DamlUnassignCommand {
222    pub contract_id: String,
223    pub source: String,
224    pub target: String,
225}
226
227impl From<DamlUnassignCommand> for UnassignCommand {
228    fn from(c: DamlUnassignCommand) -> Self {
229        Self {
230            contract_id: c.contract_id,
231            source: c.source,
232            target: c.target,
233        }
234    }
235}
236
237#[derive(Debug, Clone, Eq, PartialEq, Default)]
238pub struct DamlAssignCommand {
239    /// Must match the `reassignment_id` from the matching
240    /// `Unassigned` event.
241    pub reassignment_id: String,
242    pub source: String,
243    pub target: String,
244}
245
246impl From<DamlAssignCommand> for AssignCommand {
247    fn from(c: DamlAssignCommand) -> Self {
248        Self {
249            reassignment_id: c.reassignment_id,
250            source: c.source,
251            target: c.target,
252        }
253    }
254}