Skip to content

Commit 58d8ca6

Browse files
feat: added partial sequence helper
1 parent 19d4bfa commit 58d8ca6

File tree

3 files changed

+211
-32
lines changed

3 files changed

+211
-32
lines changed

dag/dag.go

+128
Original file line numberDiff line numberDiff line change
@@ -709,9 +709,137 @@ func (d *Dag) GetPartial(start, end int) (*Dag, error) {
709709
return partialDag, nil
710710
}
711711

712+
// getPartialLeafSequence returns an ordered sequence of leaves for transmission from a partial DAG
713+
// This is an internal method used by GetLeafSequence when dealing with partial DAGs
714+
func (d *Dag) getPartialLeafSequence() []*TransmissionPacket {
715+
var sequence []*TransmissionPacket
716+
717+
// Get the root leaf
718+
rootLeaf := d.Leafs[d.Root]
719+
if rootLeaf == nil {
720+
return sequence // Return empty sequence if root leaf is missing
721+
}
722+
723+
// First, build a map of proofs organized by parent hash and child hash
724+
// This will allow us to look up the proof for a specific child when creating its packet
725+
proofMap := make(map[string]map[string]*ClassicTreeBranch)
726+
727+
// Populate the proof map from all leaves in the partial DAG
728+
for _, leaf := range d.Leafs {
729+
if len(leaf.Proofs) > 0 {
730+
// Create an entry for this parent if it doesn't exist
731+
if _, exists := proofMap[leaf.Hash]; !exists {
732+
proofMap[leaf.Hash] = make(map[string]*ClassicTreeBranch)
733+
}
734+
735+
// Add all proofs from this leaf to the map
736+
for childHash, proof := range leaf.Proofs {
737+
proofMap[leaf.Hash][childHash] = proof
738+
}
739+
}
740+
}
741+
742+
// Now perform BFS traversal similar to the full DAG method
743+
visited := make(map[string]bool)
744+
745+
// Start with the root
746+
rootLeafClone := rootLeaf.Clone()
747+
748+
// We need to preserve the original links for the root leaf
749+
// because they're part of its identity and hash calculation
750+
originalLinks := make(map[string]string)
751+
for k, v := range rootLeaf.Links {
752+
originalLinks[k] = v
753+
}
754+
755+
// Clear links for transmission (they'll be reconstructed on the receiving end)
756+
rootLeafClone.Links = make(map[string]string)
757+
758+
// We need to preserve these fields for verification
759+
// but clear proofs for the root packet - they'll be sent with child packets
760+
originalMerkleRoot := rootLeafClone.ClassicMerkleRoot
761+
originalLatestLabel := rootLeafClone.LatestLabel
762+
originalLeafCount := rootLeafClone.LeafCount
763+
764+
rootLeafClone.Proofs = nil
765+
766+
// Restore the critical fields
767+
rootLeafClone.ClassicMerkleRoot = originalMerkleRoot
768+
rootLeafClone.LatestLabel = originalLatestLabel
769+
rootLeafClone.LeafCount = originalLeafCount
770+
771+
rootPacket := &TransmissionPacket{
772+
Leaf: rootLeafClone,
773+
ParentHash: "", // Root has no parent
774+
Proofs: make(map[string]*ClassicTreeBranch),
775+
}
776+
sequence = append(sequence, rootPacket)
777+
visited[d.Root] = true
778+
779+
// Restore the original links for the root leaf in the DAG
780+
rootLeaf.Links = originalLinks
781+
782+
// BFS traversal
783+
queue := []string{d.Root}
784+
for len(queue) > 0 {
785+
current := queue[0]
786+
queue = queue[1:]
787+
788+
currentLeaf := d.Leafs[current]
789+
790+
// Sort links for deterministic order
791+
var sortedLinks []string
792+
for _, link := range currentLeaf.Links {
793+
sortedLinks = append(sortedLinks, link)
794+
}
795+
sort.Strings(sortedLinks)
796+
797+
// Process each child
798+
for _, childHash := range sortedLinks {
799+
if !visited[childHash] {
800+
childLeaf := d.Leafs[childHash]
801+
if childLeaf == nil {
802+
continue // Skip if child leaf doesn't exist in this partial DAG
803+
}
804+
805+
// Clone the leaf and clear its links for transmission
806+
leafClone := childLeaf.Clone()
807+
leafClone.Links = make(map[string]string)
808+
leafClone.Proofs = nil // Clear proofs from the leaf
809+
810+
packet := &TransmissionPacket{
811+
Leaf: leafClone,
812+
ParentHash: current,
813+
Proofs: make(map[string]*ClassicTreeBranch),
814+
}
815+
816+
// Add the proof for this specific child from the proof map
817+
if parentProofs, exists := proofMap[current]; exists {
818+
if proof, hasProof := parentProofs[childHash]; hasProof {
819+
packet.Proofs[childHash] = proof
820+
}
821+
}
822+
823+
sequence = append(sequence, packet)
824+
visited[childHash] = true
825+
queue = append(queue, childHash)
826+
}
827+
}
828+
}
829+
830+
return sequence
831+
}
832+
712833
// GetLeafSequence returns an ordered sequence of leaves for transmission
713834
// Each packet contains a leaf, its parent hash, and any proofs needed for verification
714835
func (d *Dag) GetLeafSequence() []*TransmissionPacket {
836+
// Check if this is a partial DAG
837+
if d.IsPartial() {
838+
// Use specialized method for partial DAGs
839+
return d.getPartialLeafSequence()
840+
}
841+
842+
// Original implementation for complete DAGs
715843
var sequence []*TransmissionPacket
716844
visited := make(map[string]bool)
717845

dag/leaves.go

+3-32
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ func (leaf *DagLeaf) Clone() *DagLeaf {
516516
CurrentLinkCount: leaf.CurrentLinkCount,
517517
LatestLabel: leaf.LatestLabel,
518518
LeafCount: leaf.LeafCount,
519+
ParentHash: leaf.ParentHash,
519520
Links: make(map[string]string),
520521
AdditionalData: make(map[string]string),
521522
Proofs: make(map[string]*ClassicTreeBranch),
@@ -534,38 +535,8 @@ func (leaf *DagLeaf) Clone() *DagLeaf {
534535
}
535536
}
536537

537-
// Copy root-specific fields if this is the root leaf
538-
if leaf.Hash == leaf.ParentHash || leaf.Hash == GetHash(leaf.Hash) {
539-
cloned.LatestLabel = leaf.LatestLabel
540-
cloned.LeafCount = leaf.LeafCount
541-
cloned.ParentHash = cloned.Hash // Root is its own parent
542-
} else {
543-
cloned.ParentHash = leaf.ParentHash
544-
}
545-
546-
// If leaf has multiple children according to CurrentLinkCount,
547-
// we need to handle its merkle tree state
548-
if leaf.CurrentLinkCount > 1 {
549-
if len(leaf.Links) > 1 {
550-
// Build merkle tree with current links
551-
builder := merkle_tree.CreateTree()
552-
for l, h := range leaf.Links {
553-
builder.AddLeaf(l, h)
554-
}
555-
merkleTree, leafMap, err := builder.Build()
556-
if err == nil {
557-
cloned.MerkleTree = merkleTree
558-
cloned.LeafMap = leafMap
559-
cloned.ClassicMerkleRoot = merkleTree.Root
560-
}
561-
} else {
562-
// Clear merkle tree if we don't have enough links to rebuild it
563-
cloned.MerkleTree = nil
564-
cloned.LeafMap = nil
565-
// But keep ClassicMerkleRoot as it's part of the leaf's identity
566-
cloned.ClassicMerkleRoot = leaf.ClassicMerkleRoot
567-
}
568-
}
538+
// MerkleTree and LeafMap are not deep-copied because they're regenerated when needed
539+
// But we preserve the ClassicMerkleRoot which is part of the leaf's identity
569540

570541
return cloned
571542
}

dag/transmission_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,83 @@ func TestLeafByLeafTransmission(t *testing.T) {
7575

7676
t.Log("Successfully verified full DAG after discarding proofs")
7777
}
78+
79+
func TestPartialDagTransmission(t *testing.T) {
80+
testDir, err := os.MkdirTemp("", "dag_partial_transmission_test_*")
81+
if err != nil {
82+
t.Fatalf("Failed to create temp directory: %v", err)
83+
}
84+
defer os.RemoveAll(testDir)
85+
86+
GenerateDummyDirectory(filepath.Join(testDir, "input"), 3, 5, 2, 4)
87+
88+
originalDag, err := CreateDag(filepath.Join(testDir, "input"), true)
89+
if err != nil {
90+
t.Fatalf("Failed to create DAG: %v", err)
91+
}
92+
93+
err = originalDag.Verify()
94+
if err != nil {
95+
t.Fatalf("Original DAG verification failed: %v", err)
96+
}
97+
98+
partialDag, err := originalDag.GetPartial(0, 3)
99+
if err != nil {
100+
t.Fatalf("Failed to get partial DAG: %v", err)
101+
}
102+
103+
err = partialDag.Verify()
104+
if err != nil {
105+
t.Fatalf("Partial DAG verification failed: %v", err)
106+
}
107+
108+
if !partialDag.IsPartial() {
109+
t.Fatal("DAG not recognized as partial")
110+
}
111+
112+
sequence := partialDag.GetLeafSequence()
113+
if len(sequence) == 0 {
114+
t.Fatal("No transmission packets generated from partial DAG")
115+
}
116+
117+
t.Logf("Generated %d transmission packets from partial DAG with %d leaves",
118+
len(sequence), len(partialDag.Leafs))
119+
120+
receiverDag := &Dag{
121+
Root: partialDag.Root,
122+
Leafs: make(map[string]*DagLeaf),
123+
}
124+
125+
for i, p := range sequence {
126+
bytes, err := p.ToCBOR()
127+
if err != nil {
128+
t.Fatalf("Failed to serialize packet from partial DAG")
129+
}
130+
131+
packet, err := TransmissionPacketFromCBOR(bytes)
132+
if err != nil {
133+
t.Fatalf("Failed to deserialize packet from partial DAG")
134+
}
135+
136+
receiverDag.ApplyTransmissionPacket(packet)
137+
138+
err = receiverDag.Verify()
139+
if err != nil {
140+
t.Fatalf("Verification failed after packet %d from partial DAG: %v", i, err)
141+
}
142+
143+
t.Logf("Successfully verified after packet %d from partial DAG, DAG now has %d leaves",
144+
i, len(receiverDag.Leafs))
145+
}
146+
147+
if len(receiverDag.Leafs) != len(partialDag.Leafs) {
148+
t.Fatalf("Receiver DAG has %d leaves, expected %d (same as partial DAG)",
149+
len(receiverDag.Leafs), len(partialDag.Leafs))
150+
}
151+
152+
if !receiverDag.IsPartial() {
153+
t.Fatal("Reconstructed DAG not recognized as partial")
154+
}
155+
156+
t.Log("Successfully transmitted and verified partial DAG")
157+
}

0 commit comments

Comments
 (0)