iop_coeus_node/
domain.rs

1use super::*;
2
3#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
4#[serde(rename_all = "camelCase")]
5pub struct Domain {
6    #[serde(with = "serde_str")]
7    name: DomainName,
8    owner: Principal,
9    children: HashMap<Edge, Domain>,
10    subtree_policies: SubtreePolicies,
11    registration_policy: RegistrationPolicy,
12    data: DynamicContent,
13    expires_at_height: BlockHeight,
14}
15
16impl Domain {
17    pub fn name(&self) -> &DomainName {
18        &self.name
19    }
20
21    pub fn owner(&self) -> &Principal {
22        &self.owner
23    }
24
25    pub fn set_owner(&mut self, owner: Principal) {
26        self.owner = owner
27    }
28
29    pub fn child(&self, edge: &Edge) -> Option<&Domain> {
30        self.children.get(edge)
31    }
32
33    pub fn child_mut(&mut self, edge: &Edge) -> Option<&mut Domain> {
34        self.children.get_mut(edge)
35    }
36
37    pub fn child_names(&self) -> Vec<&Edge> {
38        self.children.keys().collect()
39    }
40
41    pub fn insert_or_replace_child(&mut self, domain: Domain) -> Result<Option<Domain>> {
42        ensure!(!domain.name.is_root(), "Attempt to insert root node as child entry");
43        let edge = domain
44            .name
45            .last_edge()
46            .with_context(|| "Implementation error: already checked that domain is not root")?;
47        let old_domain = self.children.insert(edge.to_owned(), domain);
48        Ok(old_domain)
49    }
50
51    pub fn remove_child(&mut self, edge: &Edge) -> Result<Domain> {
52        let domain = self
53            .children
54            .remove(edge)
55            .with_context(|| format!("Attempt to delete nonexisting child: {}", edge))?;
56        Ok(domain)
57    }
58
59    pub fn data(&self) -> &DynamicContent {
60        &self.data
61    }
62
63    pub fn data_mut(&mut self) -> &mut DynamicContent {
64        &mut self.data
65    }
66
67    pub fn set_data(&mut self, data: DynamicContent) {
68        self.data = data;
69    }
70
71    pub fn is_expired_at(&self, height: BlockHeight) -> bool {
72        self.expires_at_height <= height
73    }
74
75    pub fn expires_at_height(&self) -> BlockHeight {
76        self.expires_at_height
77    }
78
79    pub fn set_expires_at_height(&mut self, height: BlockHeight) {
80        self.expires_at_height = height
81    }
82
83    pub fn is_grace_period_over(&self, at_height: BlockHeight) -> bool {
84        // TODO this should come from some DomainPolicy instead
85        const GRACE_PERIOD_BLOCKS: BlockHeight = 5 * 60 * 24 * 30; // about a month by default
86        self.expires_at_height + GRACE_PERIOD_BLOCKS <= at_height
87    }
88
89    pub fn subtree_policies(&self) -> &SubtreePolicies {
90        &self.subtree_policies
91    }
92
93    pub fn registration_policy(&self) -> &RegistrationPolicy {
94        &self.registration_policy
95    }
96
97    pub(crate) fn validate_subtree_policies(
98        &self, state: &State, domain_after_op: &Domain,
99    ) -> Result<()> {
100        self.subtree_policies.validate(state, self, domain_after_op)?;
101        Ok(())
102    }
103    pub(crate) fn new_root() -> Self {
104        let to_edge = |e: &&str| {
105            Edge::new(e).unwrap_or_else(|_| {
106                panic!("Implementation error creating root: {} is not a valid edge name", e)
107            })
108        };
109        let name = |edges: &[&str]| {
110            let edges = edges.iter().map(to_edge).collect();
111            DomainName::new(edges)
112        };
113        let schema = Self {
114            name: name(&["schema"]),
115            owner: Principal::system(),
116            children: Default::default(),
117            subtree_policies: SubtreePolicies::new().with_schema(Self::json_schema_draft6()),
118            registration_policy: RegistrationPolicy::any(),
119            data: json!({}),
120            expires_at_height: BlockHeight::MAX,
121        };
122        let mut root = Self {
123            name: name(&[]),
124            owner: Principal::system(),
125            children: Default::default(),
126            // TODO fill in schema and root data
127            subtree_policies: SubtreePolicies::new().with_expiration(2 * ExpirationPolicy::YEAR),
128            registration_policy: Default::default(),
129            data: json!({}),
130            expires_at_height: BlockHeight::MAX,
131        };
132        root.insert_or_replace_child(schema).unwrap();
133        root
134    }
135
136    pub(crate) fn new(
137        name: DomainName, owner: Principal, subtree_policies: SubtreePolicies,
138        registration_policy: RegistrationPolicy, data: DynamicContent,
139        expires_at_height: BlockHeight,
140    ) -> Self {
141        Self {
142            name,
143            owner,
144            children: Default::default(),
145            subtree_policies,
146            registration_policy,
147            data,
148            expires_at_height,
149        }
150    }
151
152    fn json_schema_draft6() -> Schema {
153        // Valico supports Json Schema Draft 6, contents were extracted from
154        // https://json-schema.org/draft-06/schema
155        let schema = include_str!("../json-schema-draft6.json");
156        serde_json::from_str(schema).unwrap()
157    }
158}
159
160#[cfg(test)]
161mod test {
162    use super::*;
163
164    fn name(name: &str) -> DomainName {
165        name.parse().unwrap()
166    }
167
168    fn data(content: &str) -> DynamicContent {
169        serde_json::Value::String(content.to_owned())
170    }
171
172    #[test]
173    fn serde() {
174        let wallet_schema = json![{"type": "string"}];
175        let wallet_expiration = 2_628_000;
176        let mut wallet = Domain::new(
177            name(".wallet"),
178            Principal::system(),
179            SubtreePolicies::new().with_schema(wallet_schema).with_expiration(wallet_expiration),
180            RegistrationPolicy::any(),
181            data("hello"),
182            42,
183        );
184        wallet
185            .insert_or_replace_child(Domain::new(
186                name(".wallet.joe"),
187                "pez2CLkBUjHB8w8G87D3YkREjpRuiqPu6BrRsgHMQy2Pzt6".parse().unwrap(),
188                SubtreePolicies::new(),
189                Default::default(),
190                data("world!"),
191                69,
192            ))
193            .unwrap();
194        let serialized = serde_json::to_value(&wallet).unwrap();
195
196        println!("{}", serde_json::to_string_pretty(&wallet).unwrap());
197
198        // TODO: Consider removing redundancy from the `name` field of `Domain`,
199        //       e.g. ".wallet" child name ".wallet.joe" could be just "joe" or ".joe"
200        let expected = json!( {
201          "name": ".wallet",
202          "owner": "system",
203          "children": {
204            "joe": {
205              "name": ".wallet.joe",
206              "owner": "pez2CLkBUjHB8w8G87D3YkREjpRuiqPu6BrRsgHMQy2Pzt6",
207              "children": {},
208              "subtreePolicies": {},
209              "registrationPolicy": "owner",
210              "data": "world!",
211              "expiresAtHeight": 69
212            }
213          },
214          "subtreePolicies": {
215            "schema": { "type": "string" },
216            "expiration": 2_628_000,
217          },
218          "registrationPolicy": "any",
219          "data": "hello",
220          "expiresAtHeight": 42
221        });
222
223        assert_eq!(serialized, expected);
224
225        let deserialized: Domain = serde_json::from_value(expected).unwrap();
226
227        assert_eq!(deserialized, wallet);
228    }
229}