Skip to content

Commit ab1794d

Browse files
fix: incorrectly counting links during partial batched transmission, implemented test to verify this going forward
1 parent 65f9690 commit ab1794d

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed

dag/transmission.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,8 +402,9 @@ func (d *Dag) finalizeBatch(batch *BatchedTransmissionPacket, parentsInBatch map
402402
continue
403403
}
404404

405-
// Count total children of this parent
406-
totalChildren := len(originalParent.Links)
405+
// Count total children of this parent using CurrentLinkCount
406+
// (Links may be pruned in partial DAGs, but CurrentLinkCount preserves original count)
407+
totalChildren := originalParent.CurrentLinkCount
407408

408409
// Skip if parent has no children or only one child (no proofs needed)
409410
if totalChildren <= 1 {

tests/transmission_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,3 +769,155 @@ func TestDirectMerkleProofValidation(t *testing.T) {
769769

770770
t.Log("Direct Merkle proof validation test completed")
771771
}
772+
773+
// TestPrunedLinksOptimization tests that partial DAGs with pruned links still work correctly.
774+
func TestPrunedLinksOptimization(t *testing.T) {
775+
testDir, err := os.MkdirTemp("", "dag_pruned_links_test_*")
776+
if err != nil {
777+
t.Fatalf("Failed to create temp directory: %v", err)
778+
}
779+
defer os.RemoveAll(testDir)
780+
781+
// Create a directory with 5 files - we'll request only 2
782+
inputDir := filepath.Join(testDir, "input")
783+
if err := os.MkdirAll(inputDir, 0755); err != nil {
784+
t.Fatalf("Failed to create input directory: %v", err)
785+
}
786+
787+
fileNames := []string{"file_a.txt", "file_b.txt", "file_c.txt", "file_d.txt", "file_e.txt"}
788+
for i, name := range fileNames {
789+
filePath := filepath.Join(inputDir, name)
790+
content := make([]byte, 512)
791+
for j := range content {
792+
content[j] = byte((i*512 + j) % 256)
793+
}
794+
if err := os.WriteFile(filePath, content, 0644); err != nil {
795+
t.Fatalf("Failed to write file: %v", err)
796+
}
797+
}
798+
799+
// Create original DAG
800+
originalDag, err := dag.CreateDag(inputDir, true)
801+
if err != nil {
802+
t.Fatalf("Failed to create DAG: %v", err)
803+
}
804+
805+
if err := originalDag.Verify(); err != nil {
806+
t.Fatalf("Original DAG verification failed: %v", err)
807+
}
808+
809+
// Find file leaf hashes (children of root)
810+
rootLeaf := originalDag.Leafs[originalDag.Root]
811+
if rootLeaf == nil {
812+
t.Fatal("Root leaf not found")
813+
}
814+
815+
if len(rootLeaf.Links) < 5 {
816+
t.Fatalf("Expected at least 5 children in root, got %d", len(rootLeaf.Links))
817+
}
818+
819+
// Get partial DAG for only first 2 files
820+
requestedHashes := rootLeaf.Links[:2]
821+
t.Logf("Requesting partial DAG for %d of %d children", len(requestedHashes), len(rootLeaf.Links))
822+
823+
partialDag, err := originalDag.GetPartial(requestedHashes, false)
824+
if err != nil {
825+
t.Fatalf("Failed to get partial DAG: %v", err)
826+
}
827+
828+
// Verify partial DAG state before pruning
829+
partialRoot := partialDag.Leafs[partialDag.Root]
830+
if partialRoot == nil {
831+
t.Fatal("Partial DAG root not found")
832+
}
833+
834+
t.Logf("Before pruning: CurrentLinkCount=%d, len(Links)=%d, len(Proofs)=%d",
835+
partialRoot.CurrentLinkCount, len(partialRoot.Links), len(partialRoot.Proofs))
836+
837+
// CurrentLinkCount should reflect original count (5)
838+
if partialRoot.CurrentLinkCount != 5 {
839+
t.Errorf("Expected CurrentLinkCount=5, got %d", partialRoot.CurrentLinkCount)
840+
}
841+
842+
// Proofs should exist for requested children
843+
if len(partialRoot.Proofs) < 2 {
844+
t.Errorf("Expected at least 2 proofs in partial root, got %d", len(partialRoot.Proofs))
845+
}
846+
847+
// Now simulate what pruneLinks=true does: remove links not in partial DAG
848+
prunedLinks := make([]string, 0)
849+
for _, linkHash := range partialRoot.Links {
850+
if _, exists := partialDag.Leafs[linkHash]; exists {
851+
prunedLinks = append(prunedLinks, linkHash)
852+
}
853+
}
854+
partialRoot.Links = prunedLinks
855+
856+
t.Logf("After pruning: CurrentLinkCount=%d, len(Links)=%d, len(Proofs)=%d",
857+
partialRoot.CurrentLinkCount, len(partialRoot.Links), len(partialRoot.Proofs))
858+
859+
// Links should now be pruned to 2
860+
if len(partialRoot.Links) != 2 {
861+
t.Errorf("Expected len(Links)=2 after pruning, got %d", len(partialRoot.Links))
862+
}
863+
864+
// CurrentLinkCount should still be 5 (original count preserved)
865+
if partialRoot.CurrentLinkCount != 5 {
866+
t.Errorf("CurrentLinkCount should remain 5 after pruning, got %d", partialRoot.CurrentLinkCount)
867+
}
868+
869+
// Set a batch size that puts all leaves in one batch
870+
dag.SetBatchSize(100 * 1024)
871+
defer dag.SetDefaultBatchSize()
872+
873+
// Get batched sequence - this is where the bug manifested
874+
sequence := partialDag.GetBatchedLeafSequence()
875+
876+
if len(sequence) == 0 {
877+
t.Fatal("No batched transmission packets generated")
878+
}
879+
880+
t.Logf("Generated %d batched transmission packets", len(sequence))
881+
882+
// Find the root in the first batch and check its proofs
883+
var rootInBatch *dag.DagLeaf
884+
for _, leaf := range sequence[0].Leaves {
885+
if leaf.Hash == partialDag.Root {
886+
rootInBatch = leaf
887+
break
888+
}
889+
}
890+
891+
if rootInBatch == nil {
892+
t.Fatal("Root not found in first batch")
893+
}
894+
895+
t.Logf("Root in batch: CurrentLinkCount=%d, len(Links)=%d, len(Proofs)=%d",
896+
rootInBatch.CurrentLinkCount, len(rootInBatch.Links), len(rootInBatch.Proofs))
897+
898+
if rootInBatch.Proofs == nil || len(rootInBatch.Proofs) == 0 {
899+
t.Fatalf("Expected proofs to be preserved for partial DAG with pruned links")
900+
}
901+
902+
t.Logf("SUCCESS: Root has %d proofs preserved after batching", len(rootInBatch.Proofs))
903+
904+
// Verify transmission works
905+
receiverDag := &dag.Dag{
906+
Root: partialDag.Root,
907+
Leafs: make(map[string]*dag.DagLeaf),
908+
}
909+
910+
for i, batch := range sequence {
911+
if err := receiverDag.ApplyAndVerifyBatchedTransmissionPacket(batch); err != nil {
912+
t.Fatalf("Failed to apply batch %d: %v", i, err)
913+
}
914+
}
915+
916+
// Verify receiver DAG
917+
if err := receiverDag.Verify(); err != nil {
918+
t.Fatalf("Receiver DAG partial verification failed: %v", err)
919+
}
920+
921+
t.Log("Pruned links optimization test completed successfully")
922+
t.Log("This confirms that partial DAGs with pruned links preserve merkle proofs correctly")
923+
}

0 commit comments

Comments
 (0)