1use iop_morpheus_proto::data::BeforeProofHistory;
2
3use super::*;
4
5pub(super) enum Mutation<'a> {
6 SetBlockHeight { height: BlockHeight },
7 RegisterAttempt { txid: &'a str, op: &'a OperationAttempt },
8 DoAttempt { txid: &'a str, op: &'a OperationAttempt },
9 ConfirmTxn { txid: &'a str },
10 RejectTxn { txid: &'a str },
11}
12
13#[derive(Debug, Default, Clone)]
14pub struct BeforeProofState {
15 height: BlockHeight,
16 txid: String,
17}
18
19#[derive(Debug, Default, Clone)]
20pub struct State {
21 last_seen_height: BlockHeight,
22 txn_status: HashMap<String, bool>,
23 before_proofs: HashMap<String, BeforeProofState>,
24 did_states: HashMap<String, DidDocumentState>,
25 did_txns: DidTransactionsState,
26}
27
28impl State {
29 pub fn last_seen_height(&self) -> BlockHeight {
30 self.last_seen_height
31 }
32
33 pub fn is_confirmed(&self, txid: &str) -> Option<bool> {
34 self.txn_status.get(txid).cloned()
35 }
36
37 pub fn before_proof_exists_at(&self, content_id: &str, height: Option<BlockHeight>) -> bool {
38 self.before_proofs
39 .get(content_id)
40 .map(|state| height.map(|h| h >= state.height).unwrap_or(true))
41 .unwrap_or(false)
42 }
43
44 pub fn before_proof_history(&self, content_id: &str) -> BeforeProofHistory {
45 let state = self.before_proofs.get(content_id);
46 BeforeProofHistory {
47 content_id: content_id.to_owned(),
48 exists_from_height: state.map(|s| s.height),
49 txid: state.map(|s| s.txid.clone()),
50 queried_at_height: self.last_seen_height,
51 }
52 }
53
54 pub fn get_tx_ids(
55 &self, did: &str, include_attempts: bool, from_height_inc: BlockHeight,
56 until_height_inc: Option<BlockHeight>,
57 ) -> Option<impl Iterator<Item = &TransactionIdWithHeight>> {
58 self.did_txns.get_between(did, from_height_inc, until_height_inc).map(move |i| {
59 i.filter(move |t| {
60 include_attempts || self.is_confirmed(&t.transaction_id).unwrap_or(false)
61 })
62 })
63 }
64
65 pub fn last_tx_id(&self, did: &str) -> Option<&TransactionIdWithHeight> {
66 self.get_tx_ids(did, false, 0, None).and_then(|mut i| i.next())
67 }
68
69 pub fn get_doc_at(
70 &self, did_data: &str, height_opt: Option<BlockHeight>,
71 ) -> Result<DidDocument> {
72 let height = height_opt.unwrap_or(self.last_seen_height);
73 let did: Did = did_data.parse()?;
74 let default_state = DidDocumentState::new(&did);
75 let state = self.did_states.get(did_data).unwrap_or(&default_state);
76 let doc = state.at_height(&did, height)?;
77 Ok(doc)
78 }
79
80 fn did_state_mut(
81 &mut self, did: &Did, last_tx_id: &Option<String>,
82 ) -> Result<&mut DidDocumentState> {
83 let height = self.last_seen_height;
84 let did_data = did.to_string();
85
86 let chain_last_txn = self.last_tx_id(&did_data);
87 if last_tx_id.as_ref() != chain_last_txn.map(|t| &t.transaction_id) {
88 let op_state = if let Some(txid) = last_tx_id {
89 format!("after txn {}", txid)
90 } else {
91 "on an implicit document".to_owned()
92 };
93 let chain_state = if let Some(txn) = chain_last_txn {
94 format!(
95 "but it last changed at height {} by txn {}",
96 txn.height, txn.transaction_id
97 )
98 } else {
99 "but it never changed yet".to_owned()
100 };
101 bail!(
102 "Operation on {} at height {} was attempted {}, {}",
103 &did_data,
104 height,
105 op_state,
106 chain_state
107 )
108 }
109
110 let state =
111 self.did_states.entry(did_data.clone()).or_insert_with(|| DidDocumentState::new(did));
112
113 Ok(state)
114 }
115
116 pub(super) fn apply(&mut self, mutation: Mutation) -> Result<()> {
117 fn insert_txn_status(this: &mut State, txid: &str, status: bool) -> Result<()> {
118 ensure!(
120 this.txn_status.insert(txid.to_owned(), status).is_none(),
121 "Transaction {} was already confirmed",
122 txid
123 );
124 Ok(())
125 }
126
127 fn insert_did_txns(this: &mut State, txid: &str, signed_op: &SignedOperation) {
128 signed_op.attempts_unsafe_without_signature_checking().for_each(|op| {
129 let item = DidTransactionItem {
130 did: op.did.to_string(),
131 txid,
132 height: this.last_seen_height,
133 };
134 this.did_txns.apply(item);
135 })
136 }
137
138 fn insert_before_proof(
139 this: &mut State, txid: &str, content_id: &str, height: BlockHeight,
140 ) -> Result<()> {
141 let state = BeforeProofState { height, txid: txid.to_owned() };
142 if let Some(old_state) = this.before_proofs.insert(content_id.to_owned(), state) {
144 bail!(
145 "Before proof {} is already registered at {} by txn {}",
146 content_id,
147 old_state.height,
148 old_state.txid
149 )
150 }
151 Ok(())
152 }
153
154 fn check_state(
155 state: &mut DidDocumentState, did: &Did, height: u32, signer: &Authentication,
156 ) -> Result<()> {
157 let did_data = did.to_string();
158
159 let doc = state.at_height(did, height)?;
160 let tombstoned = doc.is_tombstoned_at(height)?;
161 let can_update = doc.has_right_at(signer, Right::Update, height)?;
162
163 ensure!(
164 !tombstoned,
165 "{} cannot update {} at height {}. The DID is tombstoned",
166 signer,
167 &did_data,
168 height
169 );
170
171 ensure!(
172 can_update,
173 "{} has no right to update {} at height {}",
174 signer,
175 &did_data,
176 height
177 );
178
179 Ok(())
180 }
181
182 fn apply_signed_op(this: &mut State, op: &SignedOperation) -> Result<()> {
183 op.attempts()?.try_for_each(|a| -> Result<()> {
184 let signer = Authentication::PublicKey(op.signer_public_key.parse()?);
185 let height = this.last_seen_height;
186 let state = this.did_state_mut(&a.did, &a.last_tx_id)?;
187 check_state(state, &a.did, height, &signer)?;
188 state.apply(&a.did, height, &signer, &a.operation)
189 })
190 }
191
192 use Mutation::*;
193 match mutation {
194 SetBlockHeight { height } => {
195 ensure!(
196 self.last_seen_height <= height,
197 "The applied height ({}) is < last seen height ({})",
198 height,
199 self.last_seen_height
200 );
201 self.last_seen_height = height;
202 }
203 RegisterAttempt { txid, op } => {
204 if let OperationAttempt::Signed(signed_op) = op {
205 insert_did_txns(self, txid, signed_op);
206 }
207 }
208 DoAttempt { txid, op } => match op {
209 OperationAttempt::RegisterBeforeProof { content_id } => {
210 insert_before_proof(self, txid, content_id, self.last_seen_height)?
211 }
212 OperationAttempt::Signed(op) => apply_signed_op(self, op)?,
213 },
214 ConfirmTxn { txid } => insert_txn_status(self, txid, true)?,
215 RejectTxn { txid } => insert_txn_status(self, txid, false)?,
216 }
217 Ok(())
218 }
219
220 pub(super) fn revert(&mut self, mutation: Mutation) -> Result<()> {
221 fn remove_txn_status(this: &mut State, txid: &str, status: bool) -> Result<()> {
222 let confirmed_opt = this.txn_status.remove(txid);
223 ensure!(confirmed_opt.is_some(), "Transaction {} was not seen", txid);
224 if confirmed_opt.unwrap() {
225 ensure!(
226 status,
227 "Transaction {} was confirmed, hence its rejection cannot be reverted",
228 txid
229 );
230 } else {
231 ensure!(
232 !status,
233 "Transaction {} was rejected, hence its confirmation cannot be reverted",
234 txid
235 );
236 }
237 Ok(())
238 }
239
240 fn remove_did_txns(this: &mut State, txid: &str, signed_op: &SignedOperation) {
241 signed_op.attempts_unsafe_without_signature_checking().rev().for_each(|op| {
242 let item = DidTransactionItem {
243 did: op.did.to_string(),
244 txid,
245 height: this.last_seen_height,
246 };
247 this.did_txns.revert(item);
248 })
249 }
250
251 fn remove_before_proof(this: &mut State, txid: &str, content_id: &str) -> Result<()> {
252 let height = this.last_seen_height;
253 if let Some(old_state) = this.before_proofs.remove(content_id) {
254 let old_height = old_state.height;
255 let old_txid = old_state.txid;
256 ensure!(
257 height == old_height,
258 "Before proof {} was registered at {}, cannot be reverted at {}",
259 content_id,
260 old_height,
261 height
262 );
263 ensure!(
264 txid == old_txid,
265 "Before proof {} was registered by txn {}, cannot be reverted by txn {}",
266 content_id,
267 old_txid,
268 txid
269 );
270 Ok(())
271 } else {
272 bail!(
273 "Before proof {} was not registered, therefore cannot be reverted",
274 content_id
275 );
276 }
277 }
278
279 fn revert_signed_op(this: &mut State, op: &SignedOperation) -> Result<()> {
280 op.attempts()?.rev().try_for_each(|a| -> Result<()> {
281 let signer = Authentication::PublicKey(op.signer_public_key.parse()?);
282 let height = this.last_seen_height;
283 let state = this.did_state_mut(&a.did, &a.last_tx_id)?;
284 state.revert(&a.did, height, &signer, &a.operation)
285 })
286 }
287
288 use Mutation::*;
289 match mutation {
290 SetBlockHeight { height } => {
291 ensure!(
292 self.last_seen_height >= height,
293 "The reverted height ({}) is > last seen height ({})",
294 height,
295 self.last_seen_height
296 );
297 self.last_seen_height = height;
298 }
299 RegisterAttempt { txid, op } => {
300 if let OperationAttempt::Signed(signed_op) = op {
301 remove_did_txns(self, txid, signed_op);
302 }
303 }
304 DoAttempt { txid, op } => match op {
305 OperationAttempt::RegisterBeforeProof { content_id } => {
306 remove_before_proof(self, txid, content_id)?
307 }
308 OperationAttempt::Signed(op) => revert_signed_op(self, op)?,
309 },
310 ConfirmTxn { txid } => remove_txn_status(self, txid, true)?,
311 RejectTxn { txid } => remove_txn_status(self, txid, false)?,
312 }
313 Ok(())
314 }
315}