Fast and robust atproto CAR file processing in rust
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

hey it streams in order now

and the code is so much worse

but, later problems.

phil 349f69b2 ed8fe080

+377 -266
+1
Cargo.lock
··· 646 646 "ipld-core", 647 647 "iroh-car", 648 648 "log", 649 + "multibase", 649 650 "serde", 650 651 "serde_bytes", 651 652 "serde_ipld_dagcbor",
+2
Cargo.toml
··· 9 9 ipld-core = { version = "0.4.2", features = ["serde"] } 10 10 iroh-car = "0.5.1" 11 11 log = "0.4.28" 12 + multibase = "0.9.2" 12 13 serde = { version = "1.0.228", features = ["derive"] } 13 14 serde_bytes = "0.11.19" 14 15 serde_ipld_dagcbor = "0.6.4" ··· 18 19 [dev-dependencies] 19 20 clap = { version = "4.5.48", features = ["derive"] } 20 21 env_logger = "0.11.8" 22 + multibase = "0.9.2" 21 23 tokio = { version = "1.47.1", features = ["full"] }
+2 -2
src/mst.rs
··· 37 37 } 38 38 39 39 /// MST node data schema 40 - #[derive(Debug, Deserialize)] 40 + #[derive(Debug, Deserialize, PartialEq)] 41 41 #[serde(deny_unknown_fields)] 42 42 pub struct Node { 43 43 /// link to sub-tree Node on a lower level and with all keys sorting before ··· 68 68 } 69 69 70 70 /// TreeEntry object 71 - #[derive(Debug, Deserialize)] 71 + #[derive(Debug, Deserialize, PartialEq)] 72 72 #[serde(deny_unknown_fields)] 73 73 pub struct Entry { 74 74 /// count of bytes shared with previous TreeEntry in this Node (if any)
+372 -264
src/walk.rs
··· 3 3 use crate::mst::Node; 4 4 use ipld_core::cid::Cid; 5 5 use std::collections::HashMap; 6 + use std::fmt; 6 7 7 8 #[derive(Debug, thiserror::Error)] 8 9 pub enum Trip { ··· 23 24 Step { rkey: String, data: Vec<u8> }, 24 25 } 25 26 27 + /// some block we need (or have found) 28 + #[derive(Clone, PartialEq)] 29 + struct FindableLink { 30 + cid: Cid, 31 + found: bool, 32 + } 33 + 34 + impl From<&Cid> for FindableLink { 35 + fn from(cid: &Cid) -> Self { 36 + let cid = *cid; 37 + let found = false; 38 + FindableLink { cid, found } 39 + } 40 + } 41 + 42 + impl fmt::Debug for FindableLink { 43 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 + let status = if self.found { "??" } else { "ok" }; 45 + let cid_s = self.cid.to_string_of_base(multibase::Base::Base64).unwrap(); 46 + let split_at = cid_s.char_indices().nth_back(4).unwrap().0; 47 + let cid_s = &cid_s[split_at..]; 48 + write!(f, "({status} [{cid_s}])") 49 + } 50 + } 51 + 52 + /// a transformed mst::Entry with decompressed rkeys and which we can mutate 53 + /// 54 + /// contains exactly one record link, and one optional subtree whose contents 55 + /// are all to the right of this record's rkey 56 + #[derive(Debug, PartialEq)] 57 + struct ActionEntry { 58 + rkey: String, 59 + record: FindableLink, 60 + tree: Option<FindableLink>, 61 + } 62 + 63 + /// a transformed mst::Node which we can mutate and track progress on 64 + /// 65 + /// contains an optional left subtree, whose contents are all to the left of 66 + /// every entry in the entries array. 67 + #[derive(Debug, PartialEq)] 68 + struct ActionNode { 69 + left_tree: Option<FindableLink>, 70 + entries: Vec<ActionEntry>, 71 + } 72 + 73 + impl ActionNode { 74 + fn from_root(tree_root_cid: Cid) -> Self { 75 + ActionNode { 76 + left_tree: Some((&tree_root_cid).into()), 77 + entries: vec![], 78 + } 79 + } 80 + fn next(&self) -> Option<Need> { 81 + if let Some(findable) = &self.left_tree 82 + && !findable.found 83 + { 84 + return Some(Need::Node(findable.cid)); 85 + } 86 + for ActionEntry { rkey, record, tree } in &self.entries { 87 + if !record.found { 88 + return Some(Need::Record { 89 + rkey: rkey.to_string(), 90 + cid: record.cid, 91 + }); 92 + } 93 + if let Some(findable) = tree 94 + && !findable.found 95 + { 96 + return Some(Need::Node(findable.cid)); 97 + } 98 + } 99 + None 100 + } 101 + fn found(&mut self, cid: &Cid) { 102 + // just be horrible for now, whatever 103 + if let Some(findable) = &mut self.left_tree 104 + && !findable.found 105 + { 106 + assert_eq!(*cid, findable.cid, "wrong found for next"); 107 + findable.found = true; 108 + return; 109 + } 110 + for ActionEntry { record, tree, .. } in &mut self.entries { 111 + if !record.found { 112 + assert_eq!(*cid, record.cid, "wrong found for next, expected record"); 113 + record.found = true; 114 + return; 115 + } 116 + if let Some(findable) = tree 117 + && !findable.found 118 + { 119 + assert_eq!( 120 + *cid, findable.cid, 121 + "wrong found for next, expected entry tree" 122 + ); 123 + findable.found = true; 124 + return; 125 + } 126 + } 127 + } 128 + } 129 + 26 130 #[derive(Debug, PartialEq)] 27 131 enum Need { 28 132 Node(Cid), 29 133 Record { rkey: String, cid: Cid }, 30 134 } 31 135 32 - fn needs_from_node(node: Node) -> Result<Vec<Need>, Trip> { 33 - let mut out = vec![]; 34 - if let Some(left_cid) = node.left { 35 - out.push(Need::Node(left_cid)); 36 - } 136 + // impl Need { 137 + // fn cid(&self) -> &Cid { 138 + // match self { 139 + // Need::Node(cid) => cid, 140 + // Need::Record { cid, .. } => cid, 141 + // } 142 + // } 143 + // } 37 144 38 - let mut prefix = vec![]; 39 - for entry in &node.entries { 40 - let mut rkey = vec![]; 41 - // TODO: *definitely* need to index in a non-panicky way 42 - let pre_checked = prefix 43 - .get(..entry.prefix_len) 44 - .ok_or(Trip::EntryPrefixOutOfbounds)?; 45 - rkey.extend_from_slice(pre_checked); 46 - rkey.extend_from_slice(&entry.keysuffix); 47 - prefix = rkey.clone(); 145 + impl From<&Node> for ActionNode { 146 + fn from(node: &Node) -> Self { 147 + let left_tree = node.left.as_ref().map(Into::into); 48 148 49 - let rkey = String::from_utf8(rkey)?; 149 + let mut entries = vec![]; 150 + let mut prefix = vec![]; 151 + for entry in &node.entries { 152 + let mut rkey = vec![]; 153 + let pre_checked = prefix 154 + .get(..entry.prefix_len) 155 + .ok_or(Trip::EntryPrefixOutOfbounds) 156 + .unwrap(); // TODO has to be try_from 157 + rkey.extend_from_slice(pre_checked); 158 + rkey.extend_from_slice(&entry.keysuffix); 159 + prefix = rkey.clone(); 50 160 51 - out.push(Need::Record { 52 - rkey, 53 - cid: entry.value, 54 - }); 161 + entries.push(ActionEntry { 162 + rkey: String::from_utf8(rkey).unwrap(), // TODO this has to be try_from 163 + record: (&entry.value).into(), 164 + tree: entry.tree.as_ref().map(Into::into), 165 + }); 166 + } 55 167 56 - if let Some(child_cid) = entry.tree { 57 - out.push(Need::Node(child_cid)); 58 - } 168 + ActionNode { left_tree, entries } 59 169 } 60 - Ok(out) 61 170 } 62 171 63 172 #[derive(Debug)] 64 173 pub struct Walker { 65 - stack: Vec<Need>, 174 + stack: Vec<ActionNode>, 66 175 } 67 176 68 177 impl Walker { 69 178 pub fn new(tree_root_cid: Cid) -> Self { 70 179 Self { 71 - stack: vec![Need::Node(tree_root_cid)], 180 + stack: vec![ActionNode::from_root(tree_root_cid)], 72 181 } 73 182 } 74 183 75 184 pub fn walk(&mut self, blocks: &mut HashMap<Cid, Vec<u8>>) -> Result<Step, Trip> { 185 + // //// debug 76 186 // for (i, need) in self.stack.iter().enumerate() { 77 187 // let k = if let Need::Record { rkey, .. } = need { 78 - // String::from_utf8(rkey.to_vec()).unwrap() 188 + // rkey 79 189 // } else { 80 - // "#".to_string() 190 + // "[??]" 81 191 // }; 82 192 // println!("{: <1$} {k}", "", i) 83 193 // } 84 - // let current = if let Need::Record { rkey, .. } = &self.current { 85 - // String::from_utf8(rkey.to_vec()).unwrap() 86 - // } else { 87 - // "#".to_string() 88 - // }; 89 - // println!("{: <1$} @{current}", "", self.stack.len() - 1); 194 + 90 195 loop { 91 - let Some(mut current) = self.stack.last() else { 196 + let Some(current_node) = self.stack.last_mut() else { 92 197 log::trace!("tried to walk but we're actually done."); 93 198 return Ok(Step::Finish); 94 199 }; 95 - match &mut current { 200 + let Some(mut need) = current_node.next() else { 201 + self.stack.pop(); 202 + continue; 203 + }; 204 + 205 + match &mut need { 96 206 Need::Node(cid) => { 97 207 log::trace!("need node {cid:?}"); 98 208 let Some(block) = blocks.remove(cid) else { ··· 102 212 let node = serde_ipld_dagcbor::from_slice::<Node>(&block) 103 213 .map_err(|e| Trip::BadCommit(e.into()))?; 104 214 105 - // found node, make sure we pop it from the stack 106 - self.stack.pop(); 215 + // found node, make sure we remember 216 + current_node.found(cid); 107 217 108 - // stash future work so that the right-most work is leftest in the stack 109 - for need in needs_from_node(node)?.into_iter().rev() { 110 - self.stack.push(need); 111 - } 218 + // queue up work on the found node next 219 + self.stack.push((&node).into()); 112 220 } 113 221 Need::Record { rkey, cid } => { 114 222 log::trace!("need record {cid:?}"); ··· 119 227 let rkey = rkey.clone(); 120 228 let data = data.to_vec(); 121 229 122 - // found node, make sure we pop it from the stack 123 - self.stack.pop(); 230 + // found node, make sure we remember 231 + current_node.found(cid); 124 232 125 233 log::trace!("emitting a block as a step. depth={}", self.stack.len()); 126 234 return Ok(Step::Step { rkey, data }); ··· 130 238 } 131 239 } 132 240 133 - #[cfg(test)] 134 - mod test { 135 - use super::*; 136 - use crate::mst::Entry; 241 + // #[cfg(test)] 242 + // mod test { 243 + // use super::*; 244 + // use crate::mst::Entry; 137 245 138 - fn cid1() -> Cid { 139 - "bafyreihixenvk3ahqbytas4hk4a26w43bh6eo3w6usjqtxkpzsvi655a3m" 140 - .parse() 141 - .unwrap() 142 - } 143 - fn cid2() -> Cid { 144 - "QmY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU" 145 - .parse() 146 - .unwrap() 147 - } 148 - fn cid3() -> Cid { 149 - "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" 150 - .parse() 151 - .unwrap() 152 - } 153 - fn cid4() -> Cid { 154 - "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR" 155 - .parse() 156 - .unwrap() 157 - } 158 - fn cid5() -> Cid { 159 - "QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D" 160 - .parse() 161 - .unwrap() 162 - } 163 - fn cid6() -> Cid { 164 - "QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm" 165 - .parse() 166 - .unwrap() 167 - } 168 - fn cid7() -> Cid { 169 - "bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze" 170 - .parse() 171 - .unwrap() 172 - } 173 - fn cid8() -> Cid { 174 - "bafyreif3tfdpr5n4jdrbielmcapwvbpcthepfkwq2vwonmlhirbjmotedi" 175 - .parse() 176 - .unwrap() 177 - } 178 - fn cid9() -> Cid { 179 - "bafyreicnokmhmrnlp2wjhyk2haep4tqxiptwfrp2rrs7rzq7uk766chqvq" 180 - .parse() 181 - .unwrap() 182 - } 246 + // fn cid1() -> Cid { 247 + // "bafyreihixenvk3ahqbytas4hk4a26w43bh6eo3w6usjqtxkpzsvi655a3m" 248 + // .parse() 249 + // .unwrap() 250 + // } 251 + // fn cid2() -> Cid { 252 + // "QmY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU" 253 + // .parse() 254 + // .unwrap() 255 + // } 256 + // fn cid3() -> Cid { 257 + // "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" 258 + // .parse() 259 + // .unwrap() 260 + // } 261 + // fn cid4() -> Cid { 262 + // "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR" 263 + // .parse() 264 + // .unwrap() 265 + // } 266 + // fn cid5() -> Cid { 267 + // "QmSnuWmxptJZdLJpKRarxBMS2Ju2oANVrgbr2xWbie9b2D" 268 + // .parse() 269 + // .unwrap() 270 + // } 271 + // fn cid6() -> Cid { 272 + // "QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm" 273 + // .parse() 274 + // .unwrap() 275 + // } 276 + // fn cid7() -> Cid { 277 + // "bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze" 278 + // .parse() 279 + // .unwrap() 280 + // } 281 + // fn cid8() -> Cid { 282 + // "bafyreif3tfdpr5n4jdrbielmcapwvbpcthepfkwq2vwonmlhirbjmotedi" 283 + // .parse() 284 + // .unwrap() 285 + // } 286 + // fn cid9() -> Cid { 287 + // "bafyreicnokmhmrnlp2wjhyk2haep4tqxiptwfrp2rrs7rzq7uk766chqvq" 288 + // .parse() 289 + // .unwrap() 290 + // } 183 291 184 - #[test] 185 - fn test_needs_from_node_empty() { 186 - let node = Node { 187 - left: None, 188 - entries: vec![], 189 - }; 190 - assert_eq!(needs_from_node(node).unwrap(), vec![]); 191 - } 292 + // #[test] 293 + // fn test_needs_from_node_empty() { 294 + // let node = Node { 295 + // left: None, 296 + // entries: vec![], 297 + // }; 298 + // assert_eq!(needs_from_node(node).unwrap(), vec![]); 299 + // } 192 300 193 - #[test] 194 - fn test_needs_from_node_just_left() { 195 - let node = Node { 196 - left: Some(cid1()), 197 - entries: vec![], 198 - }; 199 - assert_eq!(needs_from_node(node).unwrap(), vec![Need::Node(cid1()),]); 200 - } 301 + // #[test] 302 + // fn test_needs_from_node_just_left() { 303 + // let node = Node { 304 + // left: Some(cid1()), 305 + // entries: vec![], 306 + // }; 307 + // assert_eq!(needs_from_node(node).unwrap(), vec![Need::Node(cid1()),]); 308 + // } 201 309 202 - #[test] 203 - fn test_needs_from_node_just_one_record() { 204 - let node = Node { 205 - left: None, 206 - entries: vec![Entry { 207 - keysuffix: "asdf".into(), 208 - prefix_len: 0, 209 - value: cid1(), 210 - tree: None, 211 - }], 212 - }; 213 - assert_eq!( 214 - needs_from_node(node).unwrap(), 215 - vec![Need::Record { 216 - rkey: "asdf".into(), 217 - cid: cid1(), 218 - },] 219 - ); 220 - } 310 + // #[test] 311 + // fn test_needs_from_node_just_one_record() { 312 + // let node = Node { 313 + // left: None, 314 + // entries: vec![Entry { 315 + // keysuffix: "asdf".into(), 316 + // prefix_len: 0, 317 + // value: cid1(), 318 + // tree: None, 319 + // }], 320 + // }; 321 + // assert_eq!( 322 + // needs_from_node(node).unwrap(), 323 + // vec![Need::Record { 324 + // rkey: "asdf".into(), 325 + // cid: cid1(), 326 + // },] 327 + // ); 328 + // } 221 329 222 - #[test] 223 - fn test_needs_from_node_two_records() { 224 - let node = Node { 225 - left: None, 226 - entries: vec![ 227 - Entry { 228 - keysuffix: "asdf".into(), 229 - prefix_len: 0, 230 - value: cid1(), 231 - tree: None, 232 - }, 233 - Entry { 234 - keysuffix: "gh".into(), 235 - prefix_len: 2, 236 - value: cid2(), 237 - tree: None, 238 - }, 239 - ], 240 - }; 241 - assert_eq!( 242 - needs_from_node(node).unwrap(), 243 - vec![ 244 - Need::Record { 245 - rkey: "asdf".into(), 246 - cid: cid1(), 247 - }, 248 - Need::Record { 249 - rkey: "asgh".into(), 250 - cid: cid2(), 251 - }, 252 - ] 253 - ); 254 - } 330 + // #[test] 331 + // fn test_needs_from_node_two_records() { 332 + // let node = Node { 333 + // left: None, 334 + // entries: vec![ 335 + // Entry { 336 + // keysuffix: "asdf".into(), 337 + // prefix_len: 0, 338 + // value: cid1(), 339 + // tree: None, 340 + // }, 341 + // Entry { 342 + // keysuffix: "gh".into(), 343 + // prefix_len: 2, 344 + // value: cid2(), 345 + // tree: None, 346 + // }, 347 + // ], 348 + // }; 349 + // assert_eq!( 350 + // needs_from_node(node).unwrap(), 351 + // vec![ 352 + // Need::Record { 353 + // rkey: "asdf".into(), 354 + // cid: cid1(), 355 + // }, 356 + // Need::Record { 357 + // rkey: "asgh".into(), 358 + // cid: cid2(), 359 + // }, 360 + // ] 361 + // ); 362 + // } 255 363 256 - #[test] 257 - fn test_needs_from_node_with_both() { 258 - let node = Node { 259 - left: None, 260 - entries: vec![Entry { 261 - keysuffix: "asdf".into(), 262 - prefix_len: 0, 263 - value: cid1(), 264 - tree: Some(cid2()), 265 - }], 266 - }; 267 - assert_eq!( 268 - needs_from_node(node).unwrap(), 269 - vec![ 270 - Need::Record { 271 - rkey: "asdf".into(), 272 - cid: cid1(), 273 - }, 274 - Need::Node(cid2()), 275 - ] 276 - ); 277 - } 364 + // #[test] 365 + // fn test_needs_from_node_with_both() { 366 + // let node = Node { 367 + // left: None, 368 + // entries: vec![Entry { 369 + // keysuffix: "asdf".into(), 370 + // prefix_len: 0, 371 + // value: cid1(), 372 + // tree: Some(cid2()), 373 + // }], 374 + // }; 375 + // assert_eq!( 376 + // needs_from_node(node).unwrap(), 377 + // vec![ 378 + // Need::Record { 379 + // rkey: "asdf".into(), 380 + // cid: cid1(), 381 + // }, 382 + // Need::Node(cid2()), 383 + // ] 384 + // ); 385 + // } 278 386 279 - #[test] 280 - fn test_needs_from_node_left_and_record() { 281 - let node = Node { 282 - left: Some(cid1()), 283 - entries: vec![Entry { 284 - keysuffix: "asdf".into(), 285 - prefix_len: 0, 286 - value: cid2(), 287 - tree: None, 288 - }], 289 - }; 290 - assert_eq!( 291 - needs_from_node(node).unwrap(), 292 - vec![ 293 - Need::Node(cid1()), 294 - Need::Record { 295 - rkey: "asdf".into(), 296 - cid: cid2(), 297 - }, 298 - ] 299 - ); 300 - } 387 + // #[test] 388 + // fn test_needs_from_node_left_and_record() { 389 + // let node = Node { 390 + // left: Some(cid1()), 391 + // entries: vec![Entry { 392 + // keysuffix: "asdf".into(), 393 + // prefix_len: 0, 394 + // value: cid2(), 395 + // tree: None, 396 + // }], 397 + // }; 398 + // assert_eq!( 399 + // needs_from_node(node).unwrap(), 400 + // vec![ 401 + // Need::Node(cid1()), 402 + // Need::Record { 403 + // rkey: "asdf".into(), 404 + // cid: cid2(), 405 + // }, 406 + // ] 407 + // ); 408 + // } 301 409 302 - #[test] 303 - fn test_needs_from_full_node() { 304 - let node = Node { 305 - left: Some(cid1()), 306 - entries: vec![ 307 - Entry { 308 - keysuffix: "asdf".into(), 309 - prefix_len: 0, 310 - value: cid2(), 311 - tree: Some(cid3()), 312 - }, 313 - Entry { 314 - keysuffix: "ghi".into(), 315 - prefix_len: 1, 316 - value: cid4(), 317 - tree: Some(cid5()), 318 - }, 319 - Entry { 320 - keysuffix: "jkl".into(), 321 - prefix_len: 2, 322 - value: cid6(), 323 - tree: Some(cid7()), 324 - }, 325 - Entry { 326 - keysuffix: "mno".into(), 327 - prefix_len: 4, 328 - value: cid8(), 329 - tree: Some(cid9()), 330 - }, 331 - ], 332 - }; 333 - assert_eq!( 334 - needs_from_node(node).unwrap(), 335 - vec![ 336 - Need::Node(cid1()), 337 - Need::Record { 338 - rkey: "asdf".into(), 339 - cid: cid2(), 340 - }, 341 - Need::Node(cid3()), 342 - Need::Record { 343 - rkey: "aghi".into(), 344 - cid: cid4(), 345 - }, 346 - Need::Node(cid5()), 347 - Need::Record { 348 - rkey: "agjkl".into(), 349 - cid: cid6(), 350 - }, 351 - Need::Node(cid7()), 352 - Need::Record { 353 - rkey: "agjkmno".into(), 354 - cid: cid8(), 355 - }, 356 - Need::Node(cid9()), 357 - ] 358 - ); 359 - } 360 - } 410 + // #[test] 411 + // fn test_needs_from_full_node() { 412 + // let node = Node { 413 + // left: Some(cid1()), 414 + // entries: vec![ 415 + // Entry { 416 + // keysuffix: "asdf".into(), 417 + // prefix_len: 0, 418 + // value: cid2(), 419 + // tree: Some(cid3()), 420 + // }, 421 + // Entry { 422 + // keysuffix: "ghi".into(), 423 + // prefix_len: 1, 424 + // value: cid4(), 425 + // tree: Some(cid5()), 426 + // }, 427 + // Entry { 428 + // keysuffix: "jkl".into(), 429 + // prefix_len: 2, 430 + // value: cid6(), 431 + // tree: Some(cid7()), 432 + // }, 433 + // Entry { 434 + // keysuffix: "mno".into(), 435 + // prefix_len: 4, 436 + // value: cid8(), 437 + // tree: Some(cid9()), 438 + // }, 439 + // ], 440 + // }; 441 + // assert_eq!( 442 + // needs_from_node(node).unwrap(), 443 + // vec![ 444 + // Need::Node(cid1()), 445 + // Need::Record { 446 + // rkey: "asdf".into(), 447 + // cid: cid2(), 448 + // }, 449 + // Need::Node(cid3()), 450 + // Need::Record { 451 + // rkey: "aghi".into(), 452 + // cid: cid4(), 453 + // }, 454 + // Need::Node(cid5()), 455 + // Need::Record { 456 + // rkey: "agjkl".into(), 457 + // cid: cid6(), 458 + // }, 459 + // Need::Node(cid7()), 460 + // Need::Record { 461 + // rkey: "agjkmno".into(), 462 + // cid: cid8(), 463 + // }, 464 + // Need::Node(cid9()), 465 + // ] 466 + // ); 467 + // } 468 + // }