···11531153 /// - All leaf record blocks (read from storage)
11541154 ///
11551155 /// This is suitable for CAR export and avoids loading all blocks into memory.
11561156+ ///
11571157+ /// TODO: get rid of tokio dependency here if possible
11561158 pub async fn write_blocks_to_car<W: tokio::io::AsyncWrite + Send + Unpin>(
11571159 &self,
11581160 writer: &mut iroh_car::CarWriter<W>,
···11771179 }
1178118011791181 /// Recursively write MST nodes to CAR and collect leaf CIDs
11821182+ ///
11831183+ /// TODO: get rid of tokio dependency here if possible
11801184 fn write_mst_nodes_to_car<'a, W: tokio::io::AsyncWrite + Send + Unpin>(
11811185 &'a self,
11821186 writer: &'a mut iroh_car::CarWriter<W>,
-325
crates/jacquard-repo/tests/interop.rs
···204204}
205205206206#[tokio::test]
207207-async fn test_minimal_determinism() {
208208- // Minimal test with just a few keys
209209- let keys = vec!["A0/501344", "A1/700567", "B0/436099"];
210210-211211- fn test_cid(n: u8) -> cid::Cid {
212212- let data = vec![n; 32];
213213- let mh = multihash::Multihash::wrap(SHA2_256, &data).unwrap();
214214- cid::Cid::new_v1(DAG_CBOR_CID_CODEC, mh)
215215- }
216216-217217- // Build tree in forward order
218218- let storage1 = Arc::new(MemoryBlockStore::new());
219219- let mut mst1 = Mst::new(storage1);
220220- for (i, &key) in keys.iter().enumerate() {
221221- println!("MST1: Adding {}", key);
222222- mst1 = mst1.add(key, test_cid(i as u8)).await.unwrap();
223223- }
224224-225225- // Build tree in reverse order
226226- let storage2 = Arc::new(MemoryBlockStore::new());
227227- let mut mst2 = Mst::new(storage2);
228228- for (i, &key) in keys.iter().rev().enumerate() {
229229- let idx = keys.len() - 1 - i;
230230- println!("MST2: Adding {}", key);
231231- mst2 = mst2.add(key, test_cid(idx as u8)).await.unwrap();
232232- }
233233-234234- // Check if all keys exist in both trees
235235- for key in keys.iter() {
236236- let v1 = mst1.get(key).await.unwrap();
237237- let v2 = mst2.get(key).await.unwrap();
238238- println!(
239239- "Key {}: mst1={:?}, mst2={:?}",
240240- key,
241241- v1.is_some(),
242242- v2.is_some()
243243- );
244244- assert_eq!(v1.is_some(), v2.is_some(), "Key {} mismatch", key);
245245- }
246246-247247- // Root CIDs should match
248248- println!("mst1 root: {:?}", mst1.root().await.unwrap());
249249- println!("mst2 root: {:?}", mst2.root().await.unwrap());
250250-251251- // Trees should be identical
252252- assert_eq!(
253253- mst1.root().await.unwrap(),
254254- mst2.root().await.unwrap(),
255255- "Tree structure should be deterministic"
256256- );
257257-}
258258-259259-#[tokio::test]
260260-async fn test_first_10_keys_determinism() {
261261- // Test first 10 keys from example_keys.txt
262262- let keys_txt = include_str!("fixtures/example_keys.txt");
263263- let keys: Vec<&str> = keys_txt
264264- .lines()
265265- .filter(|s| !s.is_empty())
266266- .take(10)
267267- .collect();
268268-269269- fn test_cid(n: u8) -> cid::Cid {
270270- let data = vec![n; 32];
271271- let mh = multihash::Multihash::wrap(SHA2_256, &data).unwrap();
272272- cid::Cid::new_v1(DAG_CBOR_CID_CODEC, mh)
273273- }
274274-275275- let storage1 = Arc::new(MemoryBlockStore::new());
276276- let mut mst1 = Mst::new(storage1);
277277- for (i, &key) in keys.iter().enumerate() {
278278- mst1 = mst1.add(key, test_cid(i as u8)).await.unwrap();
279279- }
280280-281281- let storage2 = Arc::new(MemoryBlockStore::new());
282282- let mut mst2 = Mst::new(storage2);
283283- for (i, &key) in keys.iter().rev().enumerate() {
284284- let idx = keys.len() - 1 - i;
285285- mst2 = mst2.add(key, test_cid(idx as u8)).await.unwrap();
286286- }
287287-288288- // Check all keys present
289289- for &key in &keys {
290290- assert!(mst1.get(key).await.unwrap().is_some());
291291- assert!(mst2.get(key).await.unwrap().is_some());
292292- }
293293-294294- eprintln!("mst1 root: {:?}", mst1.root().await.unwrap());
295295- eprintln!("mst2 root: {:?}", mst2.root().await.unwrap());
296296-297297- assert_eq!(
298298- mst1.root().await.unwrap(),
299299- mst2.root().await.unwrap(),
300300- "Tree structure should be deterministic"
301301- );
302302-}
303303-304304-#[tokio::test]
305305-async fn test_minimal_corruption_case() {
306306- // Minimal reproduction of the corruption bug
307307- let storage = Arc::new(MemoryBlockStore::new());
308308- let mut mst = Mst::new(storage);
309309-310310- fn test_cid(n: u8) -> cid::Cid {
311311- let data = vec![n; 32];
312312- let mh = multihash::Multihash::wrap(SHA2_256, &data).unwrap();
313313- cid::Cid::new_v1(DAG_CBOR_CID_CODEC, mh)
314314- }
315315-316316- // Add N0 (layer 0) first
317317- println!("Adding N0/719700 (layer {})", layer_for_key("N0/719700"));
318318- mst = mst.add("N0/719700", test_cid(0)).await.unwrap();
319319-320320- // Verify N0 is retrievable
321321- assert!(
322322- mst.get("N0/719700").await.unwrap().is_some(),
323323- "N0 should exist after adding it"
324324- );
325325-326326- // Add M5 (layer 5)
327327- println!("Adding M5/340624 (layer {})", layer_for_key("M5/340624"));
328328- mst = mst.add("M5/340624", test_cid(1)).await.unwrap();
329329-330330- // Verify both are retrievable
331331- assert!(
332332- mst.get("N0/719700").await.unwrap().is_some(),
333333- "N0 should still exist after adding M5"
334334- );
335335- assert!(
336336- mst.get("M5/340624").await.unwrap().is_some(),
337337- "M5 should exist after adding it"
338338- );
339339-}
340340-341341-#[tokio::test]
342207async fn test_generated_keys_at_specific_layers() {
343208 // Generate keys at different layers and verify they work correctly
344209 let storage = Arc::new(MemoryBlockStore::new());
···371236 }
372237}
373238374374-#[tokio::test]
375375-async fn test_first_n_keys_determinism() {
376376- // Test varying numbers of keys to find breaking point
377377- let all_keys = vec![
378378- "A0/501344",
379379- "A1/700567",
380380- "A2/239654",
381381- "A3/570745",
382382- "A4/231700",
383383- "A5/343219",
384384- "B0/436099",
385385- "B1/293486",
386386- "B2/303249",
387387- "B3/690557",
388388- ];
389389-390390- fn test_cid(n: u8) -> cid::Cid {
391391- let data = vec![n; 32];
392392- let mh = multihash::Multihash::wrap(SHA2_256, &data).unwrap();
393393- cid::Cid::new_v1(DAG_CBOR_CID_CODEC, mh)
394394- }
395395-396396- for n in 3..=10 {
397397- let keys: Vec<&str> = all_keys.iter().take(n).copied().collect();
398398-399399- let storage1 = Arc::new(MemoryBlockStore::new());
400400- let mut mst1 = Mst::new(storage1);
401401- for (i, &key) in keys.iter().enumerate() {
402402- mst1 = mst1.add(key, test_cid(i as u8)).await.unwrap();
403403- }
404404-405405- let storage2 = Arc::new(MemoryBlockStore::new());
406406- let mut mst2 = Mst::new(storage2);
407407- for (i, &key) in keys.iter().rev().enumerate() {
408408- let idx = keys.len() - 1 - i;
409409- mst2 = mst2.add(key, test_cid(idx as u8)).await.unwrap();
410410- }
411411-412412- let match_result = mst1.root().await.unwrap() == mst2.root().await.unwrap();
413413- eprintln!(
414414- "{} keys - Match: {} (mst1: {:?}, mst2: {:?})",
415415- n,
416416- match_result,
417417- mst1.root().await.unwrap(),
418418- mst2.root().await.unwrap()
419419- );
420420-421421- if !match_result {
422422- panic!("Determinism breaks at {} keys!", n);
423423- }
424424- }
425425-}
426426-427427-// ============================================================================
428428-// Commit Proof Fixture Tests (Phase 2.5)
429429-// ============================================================================
430430-431239#[derive(Debug, Deserialize)]
432240struct CommitProofFixture {
433241 comment: String,
···725533}
726534727535#[tokio::test]
728728-async fn test_inspect_single_key_serialization() {
729729- // Inspect what we're actually serializing for a single key
730730- use jacquard_repo::mst::util::layer_for_key;
731731-732732- let key = "com.example.record/3jqfcqzm3ft2j";
733733- let cid1: cid::Cid = "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454"
734734- .parse()
735735- .unwrap();
736736-737737- println!("Key: {}", key);
738738- println!("Layer: {}", layer_for_key(key));
739739- println!("Value CID: {}", cid1);
740740-741741- let storage = Arc::new(MemoryBlockStore::new());
742742- let mut mst = Mst::new(storage.clone());
743743-744744- mst = mst.add(key, cid1).await.unwrap();
745745-746746- // Persist to storage so we can inspect serialized bytes
747747- let root_cid = mst.persist().await.unwrap();
748748-749749- println!("\nRoot CID: {}", root_cid);
750750- println!(
751751- "Expected: bafyreicphm6sin567zmcmw2yrbguhsdqwzkxs62rcayyk6ylivxfguazgi (from test output)"
752752- );
753753-754754- // Fetch the actual serialized bytes from storage
755755- let node_bytes = storage.get(&root_cid).await.unwrap().unwrap();
756756- println!("\nSerialized node ({} bytes):", node_bytes.len());
757757- println!("Hex: {}", hex::encode(&node_bytes));
758758-759759- // Deserialize to see structure
760760- use jacquard_repo::mst::node::NodeData;
761761- let node: NodeData = serde_ipld_dagcbor::from_slice(&node_bytes).unwrap();
762762- println!("\nNodeData:");
763763- println!(" left: {:?}", node.left);
764764- println!(" entries: {} entries", node.entries.len());
765765- for (i, entry) in node.entries.iter().enumerate() {
766766- println!(
767767- " [{}] prefix_len={}, key_suffix={:?}, value={}, tree={:?}",
768768- i,
769769- entry.prefix_len,
770770- String::from_utf8_lossy(&entry.key_suffix),
771771- entry.value,
772772- entry.tree
773773- );
774774- }
775775-}
776776-777777-#[tokio::test]
778778-async fn test_inspect_two_key_serialization() {
779779- // Inspect 2-key tree structure
780780- use jacquard_repo::mst::util::layer_for_key;
781781-782782- let key1 = "com.example.record/3jqfcqzm3ft2j"; // A
783783- let key2 = "com.example.record/3jqfcqzm3fz2j"; // C
784784- let cid1: cid::Cid = "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454"
785785- .parse()
786786- .unwrap();
787787-788788- println!("Key 1 (A): {} (layer {})", key1, layer_for_key(key1));
789789- println!("Key 2 (C): {} (layer {})", key2, layer_for_key(key2));
790790-791791- let storage = Arc::new(MemoryBlockStore::new());
792792- let mut mst = Mst::new(storage.clone());
793793-794794- mst = mst.add(key1, cid1).await.unwrap();
795795- mst = mst.add(key2, cid1).await.unwrap();
796796-797797- // Persist to storage so we can inspect serialized bytes
798798- let root_cid = mst.persist().await.unwrap();
799799-800800- println!("\nRoot CID: {}", root_cid);
801801- println!("Expected: bafyreidfcktqnfmykz2ps3dbul35pepleq7kvv526g47xahuz3rqtptmky");
802802-803803- // Fetch and inspect
804804- let node_bytes = storage.get(&root_cid).await.unwrap().unwrap();
805805- println!("\nSerialized node ({} bytes):", node_bytes.len());
806806- println!("Hex: {}", hex::encode(&node_bytes));
807807-808808- use jacquard_repo::mst::node::NodeData;
809809- let node: NodeData = serde_ipld_dagcbor::from_slice(&node_bytes).unwrap();
810810- println!("\nNodeData:");
811811- println!(" left: {:?}", node.left);
812812- println!(" entries: {} entries", node.entries.len());
813813- for (i, entry) in node.entries.iter().enumerate() {
814814- println!(
815815- " [{}] prefix_len={}, key_suffix={:?}, value={}, tree={:?}",
816816- i,
817817- entry.prefix_len,
818818- String::from_utf8_lossy(&entry.key_suffix),
819819- entry.value,
820820- entry.tree
821821- );
822822- }
823823-824824- // Calculate what prefix compression SHOULD be
825825- let prefix_len = jacquard_repo::mst::util::common_prefix_len(key1, key2);
826826- println!("\nCommon prefix length between keys: {}", prefix_len);
827827- println!("Common prefix: {:?}", &key1[..prefix_len]);
828828- println!("Key1 suffix: {:?}", &key1[prefix_len..]);
829829- println!("Key2 suffix: {:?}", &key2[prefix_len..]);
830830-}
831831-832832-#[tokio::test]
833536async fn test_real_repo_car_roundtrip() {
834537 use jacquard_repo::car::{read_car, write_car};
835538 use std::path::Path;
···893596 }
894597895598 println!("✓ All {} blocks match after roundtrip", blocks.len());
896896-}
897897-898898-#[tokio::test]
899899-async fn test_real_repo_car_header() {
900900- use jacquard_repo::car::read_car_header;
901901- use std::path::Path;
902902-903903- let fixture_path = Path::new(concat!(
904904- env!("CARGO_MANIFEST_DIR"),
905905- "/tests/fixtures/repo-nonbinary.computer-2025-10-21T13_05_55.090Z.car"
906906- ));
907907-908908- if !fixture_path.exists() {
909909- eprintln!("⚠️ Skipping test_real_repo_car_header - fixture not present");
910910- return;
911911- }
912912-913913- let roots = read_car_header(fixture_path)
914914- .await
915915- .expect("Failed to read CAR header");
916916-917917- println!("✓ CAR file has {} root(s)", roots.len());
918918-919919- assert!(!roots.is_empty(), "CAR should have at least one root");
920920-921921- for (i, root) in roots.iter().enumerate() {
922922- println!(" Root {}: {}", i, root);
923923- }
924599}
925600926601#[tokio::test]