1use super::*;
2
3pub type Version = u64;
4
5#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
6pub struct TxnStatus {
7 pub version_before_txn: Version,
8 pub success: bool,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
12#[serde(rename_all = "camelCase")]
13pub struct State {
14 corrupted: bool,
15 root: Domain,
16 last_seen_height: BlockHeight,
17 version_of_first_undo_operation: Version,
18 undo_operations: Vec<UndoOperation>,
19 nonces: HashMap<MPublicKey, Nonce>,
20 txn_statuses: HashMap<String, TxnStatus>,
21}
22
23impl Default for State {
24 fn default() -> Self {
25 Self {
26 corrupted: false,
27 root: Domain::new_root(),
28 last_seen_height: Default::default(),
29 version_of_first_undo_operation: Default::default(),
30 undo_operations: Default::default(),
31 nonces: Default::default(),
32 txn_statuses: Default::default(),
33 }
34 }
35}
36
37impl State {
38 pub fn new() -> Self {
39 Default::default()
40 }
41
42 pub fn is_corrupted(&self) -> bool {
43 self.corrupted
44 }
45
46 pub fn ensure_not_corrupted(&self) -> Result<()> {
47 if self.corrupted {
48 bail!("Coeus state is corrupt. All incoming changes will be ignored.");
49 }
50 Ok(())
51 }
52
53 pub fn root(&self) -> &Domain {
54 &self.root
55 }
56
57 pub fn last_seen_height(&self) -> BlockHeight {
58 self.last_seen_height
59 }
60
61 pub(crate) fn set_last_seen_height(&mut self, height: BlockHeight) {
62 self.last_seen_height = height;
63 }
64
65 pub fn version(&self) -> Version {
66 self.undo_operations.len() as Version + self.version_of_first_undo_operation
67 }
68
69 pub fn block_applying(&mut self, height: BlockHeight) -> Result<()> {
70 self.ensure_not_corrupted()?;
71 self.apply_operations(vec![SystemOperation::start_block(height)]).map(|_version| ())
72 }
73
74 pub fn block_reverted(&mut self, height: BlockHeight) -> Result<()> {
75 self.ensure_not_corrupted()?;
76 let height_before_revert = self.last_seen_height;
77 self.set_corrupted_on_err(|state| {
78 ensure!(
79 height_before_revert == height,
80 "Cannot revert block at height {}, because currently the state is at height {}",
81 height,
82 height_before_revert,
83 );
84 state.undo_operation(state.version() - 1)?;
85 ensure!(height_before_revert > state.last_seen_height, "Cannot revert block at height {}, because the operation undone was not reducing the block height.", height);
86 Ok(())
87 })
88 }
89
90 pub fn apply_transaction(&mut self, txid: &str, asset: CoeusAsset) -> Result<()> {
91 self.ensure_not_corrupted()?;
92
93 let version_before_txn = self.version();
94
95 for bundle in asset.bundles {
96 if let Err(e) = self.apply_signed_bundle(bundle) {
97 self.undo_operations(version_before_txn)?;
98 self.txn_statuses
99 .insert(txid.to_owned(), TxnStatus { version_before_txn, success: false });
100 return Err(e);
101 }
102 }
103 self.txn_statuses.insert(txid.to_owned(), TxnStatus { version_before_txn, success: true });
104 Ok(())
105 }
106
107 pub fn revert_transaction(&mut self, txid: &str, asset: CoeusAsset) -> Result<()> {
108 self.ensure_not_corrupted()?;
109 self.set_corrupted_on_err(|state| match state.txn_statuses.remove(txid) {
110 None => {
111 bail!("Transaction has not been applied previously.");
112 }
113 Some(status) => {
114 let current_version = state.version();
115 let version_before_txn = status.version_before_txn;
116 let operation_count: usize =
117 asset.bundles.iter().map(|signed| signed.bundle.operations.len()).sum();
118 ensure!(
119 version_before_txn + operation_count as u64 == current_version,
120 "Number of operations in transaction do not match our previous records."
121 );
122 state.undo_operations(version_before_txn)?;
123 Ok(())
124 }
125 })
126 }
127
128 pub(crate) fn apply_signed_bundle(&mut self, ops: SignedBundle) -> Result<Version> {
129 ensure!(ops.verify(), "Invalid signature or the operations were tampered with");
130 self.authorize_operations(&ops.bundle.operations, &ops.public_key)?;
131 self.apply_nonced_bundle(ops.bundle, ops.public_key)
132 }
133
134 fn authorize_operations(&mut self, ops: &[UserOperation], pk: &MPublicKey) -> Result<()> {
135 ops.iter().try_for_each(|op| op.validate_auth(self, pk))
136 }
137
138 pub fn validate_domain_owner(&self, name: &DomainName, pk: &MPublicKey) -> Result<()> {
139 let domain = self.domain(name)?;
140 domain.owner().validate_impersonation(pk)
141 }
142
143 fn apply_nonced_bundle(&mut self, bundle: NoncedBundle, pk: MPublicKey) -> Result<Version> {
144 let old_nonce = self.nonces.get(&pk).copied().unwrap_or_default();
145 ensure!(
146 bundle.nonce == old_nonce + 1,
147 "Invalid nonce {}, expected {}",
148 bundle.nonce,
149 old_nonce + 1
150 );
151
152 let version = self.apply_operations(bundle.operations)?;
153
154 self.nonces.insert(pk, old_nonce + 1);
155
156 Ok(version)
157 }
158
159 pub(crate) fn apply_operations(&mut self, mut ops: Vec<impl Command>) -> Result<Version> {
160 let mut undos = vec![];
161 let res = ops.drain(..).try_fold(&mut undos, |undos, op| {
162 let undo = op.execute(self)?;
163 undos.push(undo);
164 Ok(undos)
165 });
166 match res {
167 Err(e) => {
168 undos.drain(..).rev().try_for_each(|op| op.execute(self))?;
170 Err(e)
171 }
172 Ok(_) => {
173 self.undo_operations.extend_from_slice(&undos);
174 Ok(self.version())
175 }
176 }
177 }
178
179 pub fn domain(&self, name: &DomainName) -> Result<&Domain> {
180 name.iter()
181 .try_fold(&self.root, |dom, e| dom.child(e))
182 .with_context(|| format!("Cannot find domain with name {}", name))
183 }
184
185 pub(crate) fn domain_mut(&mut self, name: &DomainName) -> Result<&mut Domain> {
186 name.iter()
187 .try_fold(&mut self.root, |dom, e| dom.child_mut(e))
188 .with_context(|| format!("Cannot find domain with name {}", name))
189 }
190
191 pub fn nonce(&self, pk: &MPublicKey) -> Nonce {
192 self.nonces.get(pk).copied().unwrap_or(0)
193 }
194
195 pub fn get_txn_status(&self, txid: &str) -> Result<&TxnStatus> {
196 self.txn_statuses.get(txid).with_context(|| format!("Cannot find txn with id {}", txid))
197 }
198
199 pub fn resolve_data(&self, name: &DomainName) -> Result<&DynamicContent> {
200 let domain = name.iter().try_fold(self.root(), |dom, edge| {
201 dom.child(edge)
202 .with_context(|| format!("Edge {} was not found for domain {}", edge, name))
203 .and_then(|child| {
204 if child.is_expired_at(self.last_seen_height) {
205 bail!("Edge {} in domain {} expired", edge, name)
206 } else {
207 Ok(child)
208 }
209 })
210 })?;
211 Ok(domain.data())
212 }
213
214 pub fn validate_subtree_policies(&self, domain_name: &DomainName) -> Result<()> {
215 let domain_after_op = self.domain(domain_name)?;
216 let mut policy_domain = self.root();
217 policy_domain.validate_subtree_policies(self, domain_after_op)?;
218
219 for edge in domain_name.edges() {
220 policy_domain = policy_domain
221 .child(edge)
222 .expect("Implementation error: validating nonexisting domain data");
223 policy_domain.validate_subtree_policies(self, domain_after_op)?;
224 }
225
226 Ok(())
227 }
228
229 fn set_corrupted_on_err<R>(&mut self, func: impl FnOnce(&mut Self) -> Result<R>) -> Result<R> {
230 match func(self) {
231 Err(e) => {
232 self.corrupted = true;
233 Err(e)
234 }
235 Ok(r) => Ok(r),
236 }
237 }
238
239 fn undo_operations(&mut self, to_version: Version) -> Result<()> {
240 for version in (to_version..self.version()).rev() {
241 self.undo_operation(version)?;
242 }
243 Ok(())
244 }
245
246 fn undo_operation(&mut self, to_version: Version) -> Result<()> {
247 self.set_corrupted_on_err(|state| {
248 let undo_op = state
249 .undo_operations
250 .pop()
251 .with_context(|| format!("Cannot undo to version {} anymore", to_version))?;
252 undo_op.execute(state)
253 })
254 }
255}
256
257#[cfg(test)]
258mod test {
259 use super::*;
260
261 use iop_keyvault::{multicipher::MPrivateKey, PrivateKey};
262
263 trait StateExt {
264 fn apply_operation(&mut self, op: impl Command) -> Result<Version>;
265 }
266
267 impl StateExt for State {
268 fn apply_operation(&mut self, op: impl Command) -> Result<Version> {
269 self.apply_operations(vec![op])
270 }
271 }
272
273 fn ark_sk_from(passphrase: &str) -> MPrivateKey {
274 let secp_sk =
275 iop_keyvault::secp256k1::SecpPrivateKey::from_ark_passphrase(passphrase).unwrap();
276 MPrivateKey::from(secp_sk)
277 }
278
279 fn ark_sk() -> MPrivateKey {
280 let passphrase = "scout try doll stuff cake welcome random taste load town clerk ostrich";
281 ark_sk_from(passphrase)
282 }
283
284 #[test]
285 fn empty_state() {
286 let state = State::new();
287
288 let root = state.root();
289 assert!(root.name().is_root());
290 assert_eq!(root.owner(), &Principal::system());
291
292 let child_names = root.child_names();
293 let schema_edge = Edge::new("schema").unwrap();
294 assert_eq!(child_names.len(), 1);
295 assert_eq!(child_names[0], &schema_edge);
296
297 let schema_domain = root.child(&schema_edge).unwrap();
298 assert!(schema_domain.child_names().is_empty());
299 assert!(!schema_domain.name().is_root());
300 assert_eq!(schema_domain.owner(), &Principal::system());
301
302 assert!(state.undo_operations.is_empty());
303 assert_eq!(state.last_seen_height(), 0);
304 assert_eq!(state.version(), 0);
305 }
306
307 fn name_resolves_to(
308 state: &State, name: &DomainName, expected_data: &DynamicContent,
309 ) -> Result<()> {
310 let data = state.resolve_data(name)?;
311 assert_eq!(data, expected_data);
312 Ok(())
313 }
314
315 fn name_does_not_resolve(state: &State, domain_name: &DomainName) -> Result<()> {
316 let last_edge = domain_name.last_edge().unwrap();
317 let err_msg = format!("Edge {} in domain {} expired", last_edge, domain_name);
318
319 let err = state.resolve_data(domain_name).unwrap_err();
320 assert_eq!(err.to_string(), err_msg);
321 Ok(())
322 }
323
324 #[test]
325 fn expiry() {
326 let mut state = State::new();
327
328 let domain_name = domain_name(".schema.decentralizers");
329 let domain_data = json!({"data": "We're gonna rule the world"});
330 state
331 .apply_operation(UserOperation::register(
332 domain_name.clone(),
333 domain_owner(),
334 no_policies(),
335 Default::default(),
336 domain_data.clone(),
337 42,
338 ))
339 .unwrap();
340
341 name_resolves_to(&state, &domain_name, &domain_data).unwrap();
342
343 state.block_applying(41).unwrap();
344 name_resolves_to(&state, &domain_name, &domain_data).unwrap();
345
346 state.block_applying(42).unwrap();
347 name_does_not_resolve(&state, &domain_name).unwrap();
348
349 state.apply_operation(UserOperation::renew(domain_name.clone(), 43)).unwrap();
350 name_resolves_to(&state, &domain_name, &domain_data).unwrap();
351
352 state.block_applying(1234567890).unwrap();
353 name_does_not_resolve(&state, &domain_name).unwrap();
354 assert_eq!(state.version(), 5);
355
356 state.block_reverted(1234567890).unwrap(); name_resolves_to(&state, &domain_name, &domain_data).unwrap();
358
359 state.undo_operation(3).unwrap(); name_does_not_resolve(&state, &domain_name).unwrap();
361
362 state.block_reverted(42).unwrap(); name_resolves_to(&state, &domain_name, &domain_data).unwrap();
364 }
365
366 fn no_policies() -> SubtreePolicies {
367 SubtreePolicies::new() }
369
370 fn data(data: &str) -> serde_json::Value {
371 json!({ "data": data })
372 }
373
374 fn domain_name(name: &str) -> DomainName {
375 name.parse().unwrap()
376 }
377
378 fn domain_owner_pk() -> MPublicKey {
379 let sk = ark_sk();
380 sk.public_key()
381 }
382
383 fn domain_owner() -> Principal {
384 Principal::PublicKey(domain_owner_pk())
385 }
386
387 fn sign_ops(ops: NoncedBundle) -> SignedBundle {
388 let sk =
389 ark_sk_from("scout try doll stuff cake welcome random taste load town clerk ostrich");
390 ops.sign(&sk).unwrap()
391 }
392
393 fn sign_ops_by_wrong_key(ops: NoncedBundle) -> SignedBundle {
394 let sk = ark_sk_from(
395 "not scout try doll stuff cake welcome random taste load town clerk ostrich",
396 );
397 ops.sign(&sk).unwrap()
398 }
399
400 fn check_domain_exists(
401 state: &State, name: &DomainName, expected_data: &serde_json::Value,
402 expected_owner: &Principal,
403 ) {
404 let edge = name.last_edge().unwrap();
405 let parent = state.domain(&name.parent().unwrap()).unwrap();
406 assert!(parent.child_names().contains(&edge));
407
408 let registered_domain = parent.child(edge).unwrap();
409 assert_eq!(registered_domain.name(), name);
410 assert_eq!(registered_domain.owner(), expected_owner);
411 assert_eq!(registered_domain.subtree_policies(), &no_policies());
412 assert_eq!(registered_domain.data(), expected_data);
413 assert!(registered_domain.child_names().is_empty());
414 }
415
416 fn check_domain_missing(state: &State, name: &DomainName) {
417 let edge = name.last_edge().unwrap();
418 let parent = state.domain(&name.parent().unwrap()).unwrap();
419 assert!(!parent.child_names().contains(&edge));
420 }
421
422 fn execute_tld_register_system_domain() -> State {
423 let mut state = State::new();
424
425 let register_operation = UserOperation::register(
426 domain_name(".wallet"),
427 Principal::system(),
428 no_policies(),
429 RegistrationPolicy::Owner,
430 data("a"),
431 ExpirationPolicy::YEAR,
432 );
433 let version = state.apply_operation(register_operation).unwrap();
434
435 assert_eq!(version, 1);
436
437 state
438 }
439
440 #[test]
441 fn execute_checks_registration_policy() {
442 let mut state = execute_tld_register_system_domain();
443
444 let pk = "pezDj6ea4tVfNRUTMyssVDepAAzPW67Fe3yHtuHL6ZNtcfJ".parse().unwrap();
445 let register_operation = UserOperation::register(
446 domain_name(".wallet.wigy"),
447 Principal::public_key(&pk),
448 no_policies(),
449 Default::default(),
450 data("a"),
451 ExpirationPolicy::YEAR,
452 );
453 let err = state.apply_operation(register_operation).unwrap_err();
454
455 assert_eq!(err.to_string(), "Only system can register a child of .wallet");
456 }
457
458 #[test]
459 fn signed_register_cannot_impersonate_system() {
460 let mut state = State::new();
461 let sk = ark_sk();
462
463 let register_operation = UserOperation::register(
464 domain_name(".schema.system"),
465 Principal::system(),
466 no_policies(),
467 Default::default(),
468 data("a"),
469 ExpirationPolicy::YEAR,
470 );
471 let signed_ops = NoncedBundle::new(vec![register_operation], 42).sign(&sk).unwrap();
472 let err = state.apply_signed_bundle(signed_ops).unwrap_err();
473
474 assert_eq!(err.to_string(), "System principal cannot be impersonated");
475 }
476
477 #[test]
478 fn serde_roundtrip() {
479 let mut state = State::new();
480
481 let register_operation = UserOperation::register(
482 domain_name(".schema.a"),
483 domain_owner(),
484 no_policies(),
485 Default::default(),
486 data("a"),
487 ExpirationPolicy::YEAR,
488 );
489 state.apply_operation(register_operation).unwrap();
490
491 let update_operation = UserOperation::update(domain_name(".schema.a"), data("b"));
492 state.apply_operation(update_operation).unwrap();
493
494 let register_operation = UserOperation::register(
495 domain_name(".schema.ageover"),
496 domain_owner(),
497 no_policies(),
498 Default::default(),
499 data("c"),
500 ExpirationPolicy::YEAR,
501 );
502 state.apply_operation(register_operation).unwrap();
503
504 let serialized = serde_json::to_string(&state).unwrap();
505
506 let deserialized: State = serde_json::from_str(&serialized).unwrap();
509
510 assert_eq!(deserialized, state);
511 }
512
513 #[test]
514 fn register_update_transfer_delete_domain() {
515 let name = ".schema.a";
516 let domain_name = || domain_name(name);
517
518 let mut state = State::new();
519
520 let register_operation = UserOperation::register(
521 domain_name(),
522 domain_owner(),
523 no_policies(),
524 Default::default(),
525 data("top level"),
526 ExpirationPolicy::YEAR,
527 );
528 state.apply_operation(register_operation).unwrap();
529
530 assert_eq!(state.version(), 1);
531 check_domain_exists(&state, &domain_name(), &data("top level"), &domain_owner());
532
533 let update_operation = UserOperation::update(domain_name(), data("cool, heh?"));
534 state.apply_operation(update_operation).unwrap();
535
536 assert_eq!(state.version(), 2);
537 check_domain_exists(&state, &domain_name(), &data("cool, heh?"), &domain_owner());
538
539 let pk = "pezDj6ea4tVfNRUTMyssVDepAAzPW67Fe3yHtuHL6ZNtcfJ".parse().unwrap();
540 let transfer_to = Principal::public_key(&pk);
541 let transfer_operation = UserOperation::transfer(domain_name(), transfer_to.clone());
542 state.apply_operation(transfer_operation).unwrap();
543
544 assert_eq!(state.version(), 3);
545 check_domain_exists(&state, &domain_name(), &data("cool, heh?"), &transfer_to);
546
547 state.apply_operation(UserOperation::delete(domain_name())).unwrap();
548
549 check_domain_missing(&state, &domain_name());
550 assert_eq!(state.last_seen_height(), 0);
551 assert_eq!(state.version(), 4);
552
553 state.undo_operation(3).unwrap(); check_domain_exists(&state, &domain_name(), &data("cool, heh?"), &transfer_to);
556
557 state.undo_operation(2).unwrap(); check_domain_exists(&state, &domain_name(), &data("cool, heh?"), &domain_owner());
560
561 state.undo_operation(1).unwrap(); check_domain_exists(&state, &domain_name(), &data("top level"), &domain_owner());
564 assert_eq!(state.version(), 1);
565
566 state.undo_operation(0).unwrap(); check_domain_missing(&state, &domain_name());
569 assert_eq!(state.version(), 0);
570 }
571
572 fn schema_policy(schema: Schema) -> SubtreePolicies {
573 SubtreePolicies::new().with_schema(schema)
574 }
575
576 #[test]
577 fn schema_validation() {
578 let mut state = State::new();
579
580 let reg_badschema = UserOperation::register(
581 domain_name(".schema.badschema"),
582 domain_owner(),
583 schema_policy(json!({"properties": "invalid"})),
584 Default::default(),
585 json!({}),
586 ExpirationPolicy::YEAR,
587 );
588 assert_eq!(
589 state.apply_operation(reg_badschema).unwrap_err().to_string(),
590 "Domain .schema.badschema has invalid schema"
591 );
592
593 let reg_baddata = UserOperation::register(
594 domain_name(".schema.baddata"),
595 domain_owner(),
596 schema_policy(json!( {
597 "properties": {
598 "someProperty": {
599 "type": "string",
600 },
601 },
602 "additionalProperties": false,
603 })),
604 Default::default(),
605 json!({"data": "notmatching"}),
606 ExpirationPolicy::YEAR,
607 );
608 assert_eq!(
609 state.apply_operation(reg_baddata).unwrap_err().to_string(),
610 "Domain .schema.baddata data does not match schema of .schema.baddata"
611 );
612
613 let reg_schema_empty = UserOperation::register(
614 domain_name(".schema.empty"),
615 domain_owner(),
616 schema_policy(json!({"additionalProperties": false,})),
617 Default::default(),
618 json!({}),
619 ExpirationPolicy::YEAR,
620 );
621 state.apply_operation(reg_schema_empty).unwrap();
622
623 let upd_schema_empty =
624 UserOperation::update(domain_name(".schema.empty"), json!({ "bad": "data"}));
625 assert_eq!(
626 state.apply_operation(upd_schema_empty).unwrap_err().to_string(),
627 "Domain .schema.empty data does not match schema of .schema.empty"
628 );
629 }
630
631 #[test]
632 fn authorization() {
633 let name = ".schema.a";
634 let domain_name = || domain_name(name);
635
636 let mut state = State::new();
637
638 let reg_op = UserOperation::register(
639 domain_name(),
640 domain_owner(),
641 no_policies(),
642 Default::default(),
643 data("top level"),
644 10,
645 );
646 state.apply_operation(reg_op).unwrap();
648
649 assert_eq!(state.version(), 1);
650 assert_eq!(state.nonce(&domain_owner_pk()), 0);
651 check_domain_exists(&state, &domain_name(), &data("top level"), &domain_owner());
652 name_resolves_to(&state, &domain_name(), &data("top level")).unwrap();
653
654 state.block_applying(10).unwrap();
655 assert_eq!(state.version(), 2);
656 name_does_not_resolve(&state, &domain_name()).unwrap();
657
658 let update_op = UserOperation::update(domain_name(), data("cool, heh?"));
659 let renew_op = UserOperation::renew(domain_name(), 20);
660 let nonced_ops = NoncedBundle::new(vec![renew_op.clone(), update_op.clone()], 1);
661
662 let bad_nonce_ops = NoncedBundle::new(vec![update_op.clone(), renew_op.clone()], 2);
663 let signed_ops_bad_nonce = sign_ops(bad_nonce_ops);
664 let bad_nonce_err = state.apply_signed_bundle(signed_ops_bad_nonce).unwrap_err();
665 assert_eq!(bad_nonce_err.to_string(), "Invalid nonce 2, expected 1");
666 assert_eq!(state.version(), 2);
667 name_does_not_resolve(&state, &domain_name()).unwrap();
668
669 let bad_signed_ops = sign_ops_by_wrong_key(nonced_ops.clone());
670 let bad_signer_err = state.apply_signed_bundle(bad_signed_ops).unwrap_err();
671 assert_eq!(
672 bad_signer_err.to_string(),
673 "PublicKey principal psz291QGsvwafGPkKMu6MUsXThWRcBRzRf6pcVPM1Pst6WgW cannot be impersonated by pszcYyCB1iBEWSD9xFGzFYYQnJvYyvaENgRS9TnjJPNqfkz"
674 );
675 name_does_not_resolve(&state, &domain_name()).unwrap();
676
677 let bad_order_ops = NoncedBundle::new(vec![update_op, renew_op], 1);
678 let bad_order_signed_ops = sign_ops(bad_order_ops);
679 let bad_order_err = state.apply_signed_bundle(bad_order_signed_ops).unwrap_err();
680 assert_eq!(bad_order_err.to_string(), "Domain .schema.a expired");
681
682 let signed_ops = sign_ops(nonced_ops);
683 state.apply_signed_bundle(signed_ops).unwrap();
684
685 assert_eq!(state.version(), 4);
686 assert_eq!(state.nonce(&domain_owner_pk()), 1);
687 check_domain_exists(&state, &domain_name(), &data("cool, heh?"), &domain_owner());
688 name_resolves_to(&state, &domain_name(), &data("cool, heh?")).unwrap();
689 }
690}