@@ -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