88 "bytes"
99 "context"
1010 "crypto/rand"
11- "errors"
1211 "sync"
1312 "sync/atomic"
1413 "testing"
@@ -17,6 +16,7 @@ import (
1716 "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder"
1817 "github.com/ethersphere/bee/v2/pkg/file/redundancy"
1918 postagetesting "github.com/ethersphere/bee/v2/pkg/postage/mock"
19+ "github.com/ethersphere/bee/v2/pkg/soc"
2020 "github.com/ethersphere/bee/v2/pkg/steward"
2121 "github.com/ethersphere/bee/v2/pkg/storage"
2222 "github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore"
@@ -63,25 +63,36 @@ func TestSteward(t *testing.T) {
6363 }
6464
6565 chunkCount := int (inmem .count .Load ())
66+ replicaCount := redundancy .PARANOID .GetReplicaCount ()
67+ wantPushed := chunkCount + replicaCount
6668 done := make (chan struct {})
6769 errc := make (chan error , 1 )
70+ replicaAddrs := make (map [string ]struct {})
71+ var replicaMu sync.Mutex
6872 go func () {
6973 defer close (done )
7074 count := 0
7175 for op := range store .PusherFeed () {
72- has , err := chunkStore . Has ( ctx , op . Chunk . Address ())
73- if err != nil || ! has {
74- if ! has {
75- err = errors . New ( "chunk not found" )
76- }
76+ // DirectUpload only forwards pushed chunks over the feed; it does not
77+ // persist them. Persist here so the post-reupload assertions (Has,
78+ // IsRetrievable) observe pushed-but-not-yet-locally-known chunks the
79+ // same way a real pushsync round-trip eventually would.
80+ if err := chunkStore . Put ( ctx , op . Chunk ); err != nil {
7781 select {
7882 case errc <- err :
7983 default :
8084 }
8185 return
8286 }
87+
88+ if sch , err := soc .FromChunk (op .Chunk ); err == nil && bytes .Equal (sch .OwnerAddress (), swarm .ReplicasOwner ) {
89+ replicaMu .Lock ()
90+ replicaAddrs [op .Chunk .Address ().String ()] = struct {}{}
91+ replicaMu .Unlock ()
92+ }
93+
8394 count ++
84- if count == chunkCount {
95+ if count == wantPushed {
8596 return
8697 }
8798 }
@@ -113,8 +124,22 @@ func TestSteward(t *testing.T) {
113124 }
114125
115126 count := len (localRetrieval .retrievedChunks )
116- if count != chunkCount {
117- t .Fatalf ("unexpected no of unique chunks retrieved: want %d have %d" , chunkCount , count )
127+ // IsRetrievable's root-chunk fetch goes through joiner -> replicas.NewGetter, which
128+ // races the original root address against an initial batch of 2 replica candidate
129+ // addresses before the first success cancels the rest (see replicas/getter.go). With
130+ // real dispersed replicas now present (this is what this fix creates), up to 2 of
131+ // those speculative replica fetches can also succeed and get recorded before
132+ // cancellation lands, on top of the trie chunks retrieved by traversal.
133+ const maxSpeculativeRootFetches = 2
134+ if count < chunkCount || count > chunkCount + maxSpeculativeRootFetches {
135+ t .Fatalf ("unexpected no of unique chunks retrieved: want between %d and %d, have %d" , chunkCount , chunkCount + maxSpeculativeRootFetches , count )
136+ }
137+
138+ replicaMu .Lock ()
139+ gotReplicas := len (replicaAddrs )
140+ replicaMu .Unlock ()
141+ if gotReplicas != replicaCount {
142+ t .Fatalf ("unexpected no of dispersed replicas re-uploaded: want %d have %d" , replicaCount , gotReplicas )
118143 }
119144}
120145
0 commit comments