iop_morpheus_node/
state.rs

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            // We can change the state fearlessly even if we Err, because the caller will throw away changed state on error
119            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            // We can change the state fearlessly even if we Err, because the caller will throw away changed state on error
143            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}