Skip to content

Commit e737d63

Browse files
author
Alok Nerurkar
committed
fix: stewardship re-upload dispersed replicas
1 parent 5219ca9 commit e737d63

2 files changed

Lines changed: 51 additions & 9 deletions

File tree

pkg/steward/steward.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/ethersphere/bee/v2/pkg/file/redundancy"
1515
"github.com/ethersphere/bee/v2/pkg/postage"
16+
"github.com/ethersphere/bee/v2/pkg/replicas"
1617
"github.com/ethersphere/bee/v2/pkg/retrieval"
1718
"github.com/ethersphere/bee/v2/pkg/storage"
1819
"github.com/ethersphere/bee/v2/pkg/storer"
@@ -77,6 +78,22 @@ func (s *steward) Reupload(ctx context.Context, root swarm.Address, stamper post
7778
)
7879
}
7980

81+
if rLevel != redundancy.NONE {
82+
rootChunk, err := getter.Get(ctx, root)
83+
if err != nil {
84+
return errors.Join(fmt.Errorf("get root chunk for dispersed replicas: %w", err), uploaderSession.Cleanup())
85+
}
86+
87+
stamp, err := stamper.Stamp(rootChunk.Address(), rootChunk.Address())
88+
if err != nil {
89+
return errors.Join(fmt.Errorf("stamping root chunk for dispersed replicas: %w", err), uploaderSession.Cleanup())
90+
}
91+
92+
if err := replicas.NewPutter(uploaderSession, rLevel).Put(ctx, rootChunk.WithStamp(stamp)); err != nil {
93+
return errors.Join(fmt.Errorf("re-uploading dispersed replicas: %w", err), uploaderSession.Cleanup())
94+
}
95+
}
96+
8097
return uploaderSession.Done(root)
8198
}
8299

pkg/steward/steward_test.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
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

Comments
 (0)