Skip to content

Commit 8ba24bd

Browse files
feat: parallel dag creation with test fixes and label dependency removal fixes
1 parent 97d95bf commit 8ba24bd

12 files changed

Lines changed: 1165 additions & 66 deletions

dag/dag.go

Lines changed: 517 additions & 30 deletions
Large diffs are not rendered by default.

dag/dag_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package dag
22

33
import (
4-
"io/ioutil"
54
"os"
65
"path/filepath"
76
"testing"
87
)
98

109
func TestFull(t *testing.T) {
11-
tmpDir, err := ioutil.TempDir("", "test")
10+
tmpDir, err := os.MkdirTemp("", "test")
1211
if err != nil {
1312
t.Fatalf("Could not create temp directory: %s", err)
1413
}

dag/edge_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package dag
22

33
import (
44
"bytes"
5-
"io/ioutil"
65
"os"
76
"path/filepath"
87
"strings"
@@ -11,15 +10,15 @@ import (
1110

1211
func TestOutOfRangeLeafRequests(t *testing.T) {
1312
// Create a simple DAG with known number of leaves
14-
tmpDir, err := ioutil.TempDir("", "test")
13+
tmpDir, err := os.MkdirTemp("", "test")
1514
if err != nil {
1615
t.Fatalf("Could not create temp directory: %s", err)
1716
}
1817
defer os.RemoveAll(tmpDir)
1918

2019
// Create 5 test files
2120
for i := 0; i < 5; i++ {
22-
err := ioutil.WriteFile(
21+
err := os.WriteFile(
2322
filepath.Join(tmpDir, string(rune('a'+i))),
2423
[]byte("test content"),
2524
0644,
@@ -61,7 +60,7 @@ func TestOutOfRangeLeafRequests(t *testing.T) {
6160
}
6261

6362
func TestSingleFileScenarios(t *testing.T) {
64-
tmpDir, err := ioutil.TempDir("", "test")
63+
tmpDir, err := os.MkdirTemp("", "test")
6564
if err != nil {
6665
t.Fatalf("Could not create temp directory: %s", err)
6766
}
@@ -113,7 +112,7 @@ func TestSingleFileScenarios(t *testing.T) {
113112
}
114113

115114
// Create the test file
116-
err := ioutil.WriteFile(filePath, content, 0644)
115+
err := os.WriteFile(filePath, content, 0644)
117116
if err != nil {
118117
t.Fatalf("Failed to create test file: %v", err)
119118
}

dag/label_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestStripLabel(t *testing.T) {
3030
{
3131
name: "Hash with multiple colons",
3232
input: "1:2:3",
33-
expected: "1:2:3",
33+
expected: "2:3",
3434
},
3535
}
3636

dag/leaves.go

Lines changed: 102 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,6 @@ func (b *DagBuilder) GetLatestLabel() string {
6666
return result
6767
}
6868

69-
func (b *DagBuilder) GetNextAvailableLabel() string {
70-
latestLabel := b.GetLatestLabel()
71-
72-
number, err := strconv.ParseInt(latestLabel, 10, 64)
73-
if err != nil {
74-
fmt.Println("Failed to parse label")
75-
}
76-
77-
nextLabel := strconv.FormatInt(number+1, 10)
78-
79-
return nextLabel
80-
}
81-
8269
// CalculateTotalContentSize calculates the total size of actual content (not including metadata)
8370
func (b *DagBuilder) CalculateTotalContentSize() int64 {
8471
var totalSize int64
@@ -94,8 +81,50 @@ func (b *DagBuilder) CalculateTotalContentSize() int64 {
9481
func (b *DagBuilder) CalculateTotalDagSize() (int64, error) {
9582
var totalSize int64
9683
for _, leaf := range b.Leafs {
97-
serializable := leaf.ToSerializable()
98-
serialized, err := cbor.Marshal(serializable)
84+
bareHash := GetHash(leaf.Hash)
85+
86+
var linkHashes []string
87+
if len(leaf.Links) > 0 {
88+
linkHashes = make([]string, 0, len(leaf.Links))
89+
for _, linkHash := range leaf.Links {
90+
linkHashes = append(linkHashes, GetHash(linkHash))
91+
}
92+
sort.Strings(linkHashes)
93+
}
94+
95+
data := struct {
96+
Hash string
97+
ItemName string
98+
Type LeafType
99+
ContentHash []byte
100+
Content []byte
101+
ClassicMerkleRoot []byte
102+
CurrentLinkCount int
103+
LatestLabel string
104+
LeafCount int
105+
ContentSize int64
106+
DagSize int64
107+
Links []string
108+
AdditionalData map[string]string
109+
StoredProofs map[string]*ClassicTreeBranch
110+
}{
111+
Hash: bareHash,
112+
ItemName: leaf.ItemName,
113+
Type: leaf.Type,
114+
ContentHash: leaf.ContentHash,
115+
Content: leaf.Content,
116+
ClassicMerkleRoot: leaf.ClassicMerkleRoot,
117+
CurrentLinkCount: leaf.CurrentLinkCount,
118+
LatestLabel: leaf.LatestLabel,
119+
LeafCount: leaf.LeafCount,
120+
ContentSize: leaf.ContentSize,
121+
DagSize: leaf.DagSize,
122+
Links: linkHashes,
123+
AdditionalData: sortMapByKeys(leaf.AdditionalData),
124+
StoredProofs: leaf.Proofs,
125+
}
126+
127+
serialized, err := cbor.Marshal(data)
99128
if err != nil {
100129
return 0, fmt.Errorf("failed to serialize leaf %s: %w", leaf.Hash, err)
101130
}
@@ -253,7 +282,7 @@ func (b *DagLeafBuilder) BuildRootLeaf(dag *DagBuilder, additionalData map[strin
253282
MerkleRoot: merkleRoot,
254283
CurrentLinkCount: len(b.Links),
255284
LatestLabel: latestLabel,
256-
LeafCount: len(dag.Leafs),
285+
LeafCount: len(dag.Leafs) + 1,
257286
ContentSize: contentSize,
258287
DagSize: 0,
259288
ContentHash: nil,
@@ -525,7 +554,7 @@ func (leaf *DagLeaf) VerifyLeaf() error {
525554
func (leaf *DagLeaf) VerifyRootLeaf(dag *Dag) error {
526555
additionalData := sortMapByKeys(leaf.AdditionalData)
527556

528-
if leaf.ClassicMerkleRoot == nil || len(leaf.ClassicMerkleRoot) <= 0 {
557+
if len(leaf.ClassicMerkleRoot) <= 0 {
529558
leaf.ClassicMerkleRoot = []byte{}
530559
}
531560

@@ -536,6 +565,16 @@ func (leaf *DagLeaf) VerifyRootLeaf(dag *Dag) error {
536565
len(dag.Leafs) == leaf.LeafCount &&
537566
(leaf.ContentSize != 0 || leaf.DagSize != 0 || leaf.LeafCount == 1)
538567

568+
// If any leaf has pruned links, this is a partial DAG
569+
if isFullDag && dag != nil {
570+
for _, l := range dag.Leafs {
571+
if len(l.Links) < l.CurrentLinkCount {
572+
isFullDag = false
573+
break
574+
}
575+
}
576+
}
577+
539578
if isFullDag {
540579
for _, dagLeaf := range dag.Leafs {
541580
if dagLeaf.Content != nil {
@@ -550,8 +589,50 @@ func (leaf *DagLeaf) VerifyRootLeaf(dag *Dag) error {
550589
continue
551590
}
552591

553-
serializable := dagLeaf.ToSerializable()
554-
serialized, err := cbor.Marshal(serializable)
592+
bareHash := GetHash(dagLeaf.Hash)
593+
594+
var linkHashes []string
595+
if len(dagLeaf.Links) > 0 {
596+
linkHashes = make([]string, 0, len(dagLeaf.Links))
597+
for _, linkHash := range dagLeaf.Links {
598+
linkHashes = append(linkHashes, GetHash(linkHash))
599+
}
600+
sort.Strings(linkHashes)
601+
}
602+
603+
data := struct {
604+
Hash string
605+
ItemName string
606+
Type LeafType
607+
ContentHash []byte
608+
Content []byte
609+
ClassicMerkleRoot []byte
610+
CurrentLinkCount int
611+
LatestLabel string
612+
LeafCount int
613+
ContentSize int64
614+
DagSize int64
615+
Links []string
616+
AdditionalData map[string]string
617+
StoredProofs map[string]*ClassicTreeBranch
618+
}{
619+
Hash: bareHash,
620+
ItemName: dagLeaf.ItemName,
621+
Type: dagLeaf.Type,
622+
ContentHash: dagLeaf.ContentHash,
623+
Content: dagLeaf.Content,
624+
ClassicMerkleRoot: dagLeaf.ClassicMerkleRoot,
625+
CurrentLinkCount: dagLeaf.CurrentLinkCount,
626+
LatestLabel: dagLeaf.LatestLabel,
627+
LeafCount: dagLeaf.LeafCount,
628+
ContentSize: dagLeaf.ContentSize,
629+
DagSize: dagLeaf.DagSize,
630+
Links: linkHashes,
631+
AdditionalData: sortMapByKeys(dagLeaf.AdditionalData),
632+
StoredProofs: dagLeaf.Proofs,
633+
}
634+
635+
serialized, err := cbor.Marshal(data)
555636
if err != nil {
556637
return fmt.Errorf("failed to serialize leaf %s for size verification: %w", dagLeaf.Hash, err)
557638
}
@@ -830,10 +911,10 @@ func GetLabel(hash string) string {
830911
// StripLabel removes the "label:" prefix from a hash string
831912
func StripLabel(hash string) string {
832913
parts := strings.Split(hash, ":")
833-
if len(parts) != 2 {
914+
if len(parts) < 2 {
834915
return hash
835916
}
836-
return parts[1]
917+
return strings.Join(parts[1:], ":")
837918
}
838919

839920
// ReplaceLabelInLink replaces the label in a child's link hash

dag/merkle_verification_test.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package dag
22

33
import (
4-
"io/ioutil"
54
"os"
65
"path/filepath"
76
"testing"
@@ -10,7 +9,7 @@ import (
109
// TestMerkleRootVerificationDetectsTampering verifies that our enhanced verification
1110
// detects when children are tampered with even if the parent leaf hash is valid
1211
func TestMerkleRootVerificationDetectsTampering(t *testing.T) {
13-
tmpDir, err := ioutil.TempDir("", "merkle_verification_test")
12+
tmpDir, err := os.MkdirTemp("", "merkle_verification_test")
1413
if err != nil {
1514
t.Fatalf("Could not create temp directory: %s", err)
1615
}
@@ -202,7 +201,7 @@ func TestMerkleRootVerificationDetectsTampering(t *testing.T) {
202201

203202
// TestPartialDagMerkleVerification ensures that partial DAGs still work correctly
204203
func TestPartialDagMerkleVerification(t *testing.T) {
205-
tmpDir, err := ioutil.TempDir("", "partial_dag_merkle_test")
204+
tmpDir, err := os.MkdirTemp("", "partial_dag_merkle_test")
206205
if err != nil {
207206
t.Fatalf("Could not create temp directory: %s", err)
208207
}
@@ -242,12 +241,21 @@ func TestPartialDagMerkleVerification(t *testing.T) {
242241
// Use a range that's guaranteed to be within bounds
243242
// GetPartial uses 1-indexed leaf positions, and end must be <= rootLeaf.LeafCount
244243
rootLeaf := fullDag.Leafs[fullDag.Root]
245-
maxRange := rootLeaf.LeafCount
244+
245+
// Make sure we actually create a PARTIAL dag, not a full one
246+
// We need at least 3 leaves total to create a meaningful partial (1 root + 2+ children)
247+
if rootLeaf.LeafCount < 3 {
248+
t.Skip("Need at least 3 leaves (1 root + 2 children) to create a partial DAG")
249+
}
250+
251+
// Request only SOME of the leaves, not all
252+
// Cap at min(5, LeafCount-1) to ensure we're always leaving at least 1 leaf out
253+
maxRange := rootLeaf.LeafCount - 1 // Always exclude at least one leaf
246254
if maxRange > 5 {
247255
maxRange = 5
248256
}
249257
if maxRange < 2 {
250-
maxRange = 2 // Need at least 2 leaves for a valid range
258+
maxRange = 2
251259
}
252260

253261
partialDag, err := fullDag.GetPartial(1, maxRange)

dag/parallel_debug_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package dag
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
// TestSimpleParallelDeterminism tests with a very simple directory structure
10+
func TestSimpleParallelDeterminism(t *testing.T) {
11+
// Create temporary test directory structure
12+
tmpDir, err := os.MkdirTemp("", "simple-parallel-test-*")
13+
if err != nil {
14+
t.Fatalf("Failed to create temp dir: %v", err)
15+
}
16+
defer os.RemoveAll(tmpDir)
17+
18+
// Create simple structure:
19+
// tmpDir/
20+
// ├── a.txt
21+
// └── b.txt
22+
23+
if err := os.WriteFile(filepath.Join(tmpDir, "a.txt"), []byte("a"), 0644); err != nil {
24+
t.Fatalf("Failed to write file: %v", err)
25+
}
26+
if err := os.WriteFile(filepath.Join(tmpDir, "b.txt"), []byte("b"), 0644); err != nil {
27+
t.Fatalf("Failed to write file: %v", err)
28+
}
29+
30+
// Build DAG sequentially
31+
sequentialConfig := DefaultConfig()
32+
sequentialDAG, err := CreateDagWithConfig(tmpDir, sequentialConfig)
33+
if err != nil {
34+
t.Fatalf("Sequential DAG creation failed: %v", err)
35+
}
36+
37+
t.Logf("Sequential DAG root: %s", sequentialDAG.Root)
38+
t.Logf("Sequential DAG leaves:")
39+
for label, leaf := range sequentialDAG.Leafs {
40+
t.Logf(" %s: %s (type=%s, name=%s, links=%d)", label, leaf.Hash, leaf.Type, leaf.ItemName, len(leaf.Links))
41+
for linkLabel, linkHash := range leaf.Links {
42+
t.Logf(" -> %s: %s", linkLabel, linkHash)
43+
}
44+
}
45+
46+
// Build DAG in parallel
47+
parallelConfig := ParallelConfigWithWorkers(2)
48+
parallelDAG, err := CreateDagWithConfig(tmpDir, parallelConfig)
49+
if err != nil {
50+
t.Fatalf("Parallel DAG creation failed: %v", err)
51+
}
52+
53+
t.Logf("\nParallel DAG root: %s", parallelDAG.Root)
54+
t.Logf("Parallel DAG leaves:")
55+
for label, leaf := range parallelDAG.Leafs {
56+
t.Logf(" %s: %s (type=%s, name=%s, links=%d)", label, leaf.Hash, leaf.Type, leaf.ItemName, len(leaf.Links))
57+
for linkLabel, linkHash := range leaf.Links {
58+
t.Logf(" -> %s: %s", linkLabel, linkHash)
59+
}
60+
}
61+
62+
// Compare
63+
if sequentialDAG.Root != parallelDAG.Root {
64+
t.Errorf("Root hashes don't match!\nSequential: %s\nParallel: %s",
65+
sequentialDAG.Root, parallelDAG.Root)
66+
}
67+
}

0 commit comments

Comments
 (0)