reth_dns_discovery/
sync.rs1use crate::tree::{LinkEntry, TreeRootEntry};
2use enr::EnrKeyUnambiguous;
3use linked_hash_set::LinkedHashSet;
4use secp256k1::SecretKey;
5use std::{
6 collections::HashMap,
7 time::{Duration, Instant},
8};
9
10pub(crate) struct SyncTree<K: EnrKeyUnambiguous = SecretKey> {
12 root: TreeRootEntry,
14 link: LinkEntry<K>,
16 root_updated: Instant,
18 sync_state: SyncState,
20 resolved_links: HashMap<String, LinkEntry<K>>,
22 unresolved_links: LinkedHashSet<String>,
24 unresolved_nodes: LinkedHashSet<String>,
26}
27
28impl<K: EnrKeyUnambiguous> SyncTree<K> {
31 pub(crate) fn new(root: TreeRootEntry, link: LinkEntry<K>) -> Self {
32 Self {
33 root,
34 link,
35 root_updated: Instant::now(),
36 sync_state: SyncState::Pending,
37 resolved_links: Default::default(),
38 unresolved_links: Default::default(),
39 unresolved_nodes: Default::default(),
40 }
41 }
42
43 #[cfg(test)]
44 pub(crate) const fn root(&self) -> &TreeRootEntry {
45 &self.root
46 }
47
48 pub(crate) const fn link(&self) -> &LinkEntry<K> {
49 &self.link
50 }
51
52 pub(crate) const fn resolved_links_mut(&mut self) -> &mut HashMap<String, LinkEntry<K>> {
53 &mut self.resolved_links
54 }
55
56 pub(crate) fn extend_children(
57 &mut self,
58 kind: ResolveKind,
59 children: impl IntoIterator<Item = String>,
60 ) {
61 match kind {
62 ResolveKind::Enr => {
63 self.unresolved_nodes.extend(children);
64 }
65 ResolveKind::Link => {
66 self.unresolved_links.extend(children);
67 }
68 }
69 }
70
71 pub(crate) fn poll(&mut self, now: Instant, update_timeout: Duration) -> Option<SyncAction> {
73 match self.sync_state {
74 SyncState::Pending => {
75 self.sync_state = SyncState::Enr;
76 return Some(SyncAction::Link(self.root.link_root.clone()))
77 }
78 SyncState::Enr => {
79 self.sync_state = SyncState::Active;
80 return Some(SyncAction::Enr(self.root.enr_root.clone()))
81 }
82 SyncState::Link => {
83 self.sync_state = SyncState::Active;
84 return Some(SyncAction::Link(self.root.link_root.clone()))
85 }
86 SyncState::Active => {
87 if now > self.root_updated + update_timeout {
88 self.sync_state = SyncState::RootUpdate;
89 return Some(SyncAction::UpdateRoot)
90 }
91 }
92 SyncState::RootUpdate => return None,
93 }
94
95 if let Some(link) = self.unresolved_links.pop_front() {
96 return Some(SyncAction::Link(link))
97 }
98
99 let enr = self.unresolved_nodes.pop_front()?;
100 Some(SyncAction::Enr(enr))
101 }
102
103 pub(crate) fn update_root(&mut self, root: TreeRootEntry) {
105 let enr_unchanged = root.enr_root == self.root.enr_root;
106 let link_unchanged = root.link_root == self.root.link_root;
107
108 self.root = root;
109 self.root_updated = Instant::now();
110
111 let state = match (enr_unchanged, link_unchanged) {
112 (true, true) => return,
114 (false, true) => {
116 self.unresolved_nodes.clear();
117 SyncState::Enr
118 }
119 (true, false) => {
121 self.unresolved_links.clear();
122 SyncState::Link
123 }
124 (false, false) => {
126 self.unresolved_nodes.clear();
127 self.unresolved_links.clear();
128 SyncState::Pending
129 }
130 };
131 self.sync_state = state;
132 }
133}
134
135#[derive(Debug)]
137pub(crate) enum SyncAction {
138 UpdateRoot,
139 Enr(String),
140 Link(String),
141}
142
143enum SyncState {
145 RootUpdate,
146 Pending,
147 Enr,
148 Link,
149 Active,
150}
151
152pub(crate) enum ResolveKind {
154 Enr,
155 Link,
156}
157
158impl ResolveKind {
161 pub(crate) const fn is_link(&self) -> bool {
162 matches!(self, Self::Link)
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use enr::EnrKey;
170 use secp256k1::rand::thread_rng;
171
172 fn base_root() -> TreeRootEntry {
173 let s = "enrtree-root:v1 e=QFT4PBCRX4XQCV3VUYJ6BTCEPU l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=3FmXuVwpa8Y7OstZTx9PIb1mt8FrW7VpDOFv4AaGCsZ2EIHmhraWhe4NxYhQDlw5MjeFXYMbJjsPeKlHzmJREQE";
175 s.parse::<TreeRootEntry>().unwrap()
176 }
177
178 fn make_tree() -> SyncTree {
179 let secret_key = SecretKey::new(&mut thread_rng());
180 let link =
181 LinkEntry { domain: "nodes.example.org".to_string(), pubkey: secret_key.public() };
182 SyncTree::new(base_root(), link)
183 }
184
185 fn advance_to_active(tree: &mut SyncTree) {
186 let now = Instant::now();
188 let timeout = Duration::from_secs(60 * 60 * 24);
189 let _ = tree.poll(now, timeout);
190 let _ = tree.poll(now, timeout);
191 }
192
193 #[test]
194 fn update_root_unchanged_no_action_from_active() {
195 let mut tree = make_tree();
196 let now = Instant::now();
197 let timeout = Duration::from_secs(60 * 60 * 24);
198 advance_to_active(&mut tree);
199
200 let same = base_root();
202 tree.update_root(same);
203 assert!(tree.poll(now, timeout).is_none());
204 }
205
206 #[test]
207 fn update_root_only_enr_changed_triggers_enr() {
208 let mut tree = make_tree();
209 advance_to_active(&mut tree);
210 let mut new_root = base_root();
211 new_root.enr_root = "NEW_ENR_ROOT".to_string();
212 let now = Instant::now();
213 let timeout = Duration::from_secs(60 * 60 * 24);
214
215 tree.update_root(new_root.clone());
216 match tree.poll(now, timeout) {
217 Some(SyncAction::Enr(hash)) => assert_eq!(hash, new_root.enr_root),
218 other => panic!("expected Enr action, got {:?}", other),
219 }
220 }
221
222 #[test]
223 fn update_root_only_link_changed_triggers_link() {
224 let mut tree = make_tree();
225 advance_to_active(&mut tree);
226 let mut new_root = base_root();
227 new_root.link_root = "NEW_LINK_ROOT".to_string();
228 let now = Instant::now();
229 let timeout = Duration::from_secs(60 * 60 * 24);
230
231 tree.update_root(new_root.clone());
232 match tree.poll(now, timeout) {
233 Some(SyncAction::Link(hash)) => assert_eq!(hash, new_root.link_root),
234 other => panic!("expected Link action, got {:?}", other),
235 }
236 }
237
238 #[test]
239 fn update_root_both_changed_triggers_link_then_enr() {
240 let mut tree = make_tree();
241 advance_to_active(&mut tree);
242 let mut new_root = base_root();
243 new_root.enr_root = "NEW_ENR_ROOT".to_string();
244 new_root.link_root = "NEW_LINK_ROOT".to_string();
245 let now = Instant::now();
246 let timeout = Duration::from_secs(60 * 60 * 24);
247
248 tree.update_root(new_root.clone());
249 match tree.poll(now, timeout) {
250 Some(SyncAction::Link(hash)) => assert_eq!(hash, new_root.link_root),
251 other => panic!("expected first Link action, got {:?}", other),
252 }
253 match tree.poll(now, timeout) {
254 Some(SyncAction::Enr(hash)) => assert_eq!(hash, new_root.enr_root),
255 other => panic!("expected second Enr action, got {:?}", other),
256 }
257 }
258}