@@ -6,10 +6,12 @@ package storer_test
66
77import (
88 "context"
9+ "fmt"
910 "math/rand"
1011 "testing"
1112 "time"
1213
14+ "github.com/ethersphere/bee/v2/pkg/cac"
1315 "github.com/ethersphere/bee/v2/pkg/postage"
1416
1517 postagetesting "github.com/ethersphere/bee/v2/pkg/postage/testing"
@@ -112,7 +114,6 @@ func TestReserveSampler(t *testing.T) {
112114
113115 assertSampleNoErrors (t , sample )
114116 })
115-
116117 }
117118
118119 t .Run ("disk" , func (t * testing.T ) {
@@ -233,7 +234,6 @@ func TestReserveSamplerSisterNeighborhood(t *testing.T) {
233234 t .Fatalf ("sample should not have ignored chunks" )
234235 }
235236 })
236-
237237 }
238238
239239 t .Run ("disk" , func (t * testing.T ) {
@@ -309,6 +309,63 @@ func assertValidSample(t *testing.T, sample storer.Sample, minRadius uint8, anch
309309 }
310310}
311311
312+ // TestSampleVectorCAC is a deterministic test vector that verifies the chunk
313+ // address and transformed address produced by MakeSampleUsingChunks for a
314+ // single hardcoded CAC chunk and anchor. It guards against regressions in the
315+ // BMT hashing or sampling pipeline.
316+ func TestSampleVectorCAC (t * testing.T ) {
317+ t .Parallel ()
318+
319+ // Chunk content: 4096 bytes with repeating pattern i%256.
320+ chunkContent := make ([]byte , swarm .ChunkSize )
321+ for i := range chunkContent {
322+ chunkContent [i ] = byte (i % 256 )
323+ }
324+
325+ ch , err := cac .New (chunkContent )
326+ if err != nil {
327+ t .Fatal (err )
328+ }
329+
330+ // Attach a hardcoded (but otherwise irrelevant) stamp so that
331+ // MakeSampleUsingChunks can read ch.Stamp() without panicking.
332+ batchID := make ([]byte , 32 )
333+ for i := range batchID {
334+ batchID [i ] = byte (i + 1 )
335+ }
336+ sig := make ([]byte , 65 )
337+ for i := range sig {
338+ sig [i ] = byte (i + 1 )
339+ }
340+ ch = ch .WithStamp (postage .NewStamp (batchID , make ([]byte , 8 ), make ([]byte , 8 ), sig ))
341+
342+ // Anchor: exactly 32 bytes, constant across runs.
343+ anchor := []byte ("swarm-test-anchor-deterministic!" )
344+
345+ sample , err := storer .MakeSampleUsingChunks ([]swarm.Chunk {ch }, anchor )
346+ if err != nil {
347+ t .Fatal (err )
348+ }
349+
350+ if len (sample .Items ) != 1 {
351+ t .Fatalf ("expected 1 sample item, got %d" , len (sample .Items ))
352+ }
353+
354+ item := sample .Items [0 ]
355+
356+ const (
357+ wantChunkAddr = "902406053a7a2f3a17f16097e1d0b4b6a4abeae6b84968f5503ae621f9522e16"
358+ wantTransformedAddr = "9dee91d1ed794460474ffc942996bd713176731db4581a3c6470fe9862905a60"
359+ )
360+
361+ if got := item .ChunkAddress .String (); got != wantChunkAddr {
362+ t .Errorf ("chunk address mismatch:\n got: %s\n want: %s" , got , wantChunkAddr )
363+ }
364+ if got := item .TransformedAddress .String (); got != wantTransformedAddr {
365+ t .Errorf ("transformed address mismatch:\n got: %s\n want: %s" , got , wantTransformedAddr )
366+ }
367+ }
368+
312369func assertSampleNoErrors (t * testing.T , sample storer.Sample ) {
313370 t .Helper ()
314371
@@ -325,3 +382,99 @@ func assertSampleNoErrors(t *testing.T, sample storer.Sample) {
325382 t .Fatalf ("got unexpected invalid stamps" )
326383 }
327384}
385+
386+ // Benchmark results:
387+ // goos: linux
388+ // goarch: amd64
389+ // pkg: github.com/ethersphere/bee/v2/pkg/storer
390+ // cpu: Intel(R) Core(TM) Ultra 7 165U
391+ // BenchmarkCachePutter-14 473118 2149 ns/op 1184 B/op 24 allocs/op
392+ // BenchmarkReservePutter-14 48109 29760 ns/op 12379 B/op 141 allocs/op
393+ // BenchmarkReserveSample1k-14 100 12392598 ns/op 9364970 B/op 161383 allocs/op
394+ // BenchmarkSampleHashing/chunks=1000-14 9 127425952 ns/op 32.14 MB/s 69386109 B/op 814005 allocs/op
395+ // BenchmarkSampleHashing/chunks=10000-14 1 1241432669 ns/op 32.99 MB/s 693843032 B/op 8140005 allocs/op
396+ // PASS
397+ // ok github.com/ethersphere/bee/v2/pkg/storer 34.319s
398+
399+ // BenchmarkReserveSample measures the end-to-end time of the ReserveSample
400+ // method, including DB iteration, chunk loading, stamp validation, and sample
401+ // assembly.
402+ func BenchmarkReserveSample1k (b * testing.B ) {
403+ const chunkCountPerPO = 100
404+ const maxPO = 10
405+
406+ baseAddr := swarm .RandAddress (b )
407+ opts := dbTestOps (baseAddr , 5000 , nil , nil , time .Second )
408+ opts .ValidStamp = func (ch swarm.Chunk ) (swarm.Chunk , error ) { return ch , nil }
409+
410+ st , err := diskStorer (b , opts )()
411+ if err != nil {
412+ b .Fatal (err )
413+ }
414+
415+ timeVar := uint64 (time .Now ().UnixNano ())
416+
417+ putter := st .ReservePutter ()
418+ for po := range maxPO {
419+ for range chunkCountPerPO {
420+ ch := chunk .GenerateValidRandomChunkAt (b , baseAddr , po ).WithBatch (3 , 2 , false )
421+ ch = ch .WithStamp (postagetesting .MustNewStampWithTimestamp (timeVar - 1 ))
422+ if err := putter .Put (context .Background (), ch ); err != nil {
423+ b .Fatal (err )
424+ }
425+ }
426+ }
427+
428+ var (
429+ radius uint8 = 5
430+ anchor = swarm .RandAddressAt (b , baseAddr , int (radius )).Bytes ()
431+ )
432+
433+ b .ResetTimer ()
434+
435+ for range b .N {
436+ _ , err := st .ReserveSample (context .TODO (), anchor , radius , timeVar , nil )
437+ if err != nil {
438+ b .Fatal (err )
439+ }
440+ }
441+ }
442+
443+ // BenchmarkSampleHashing measures the time taken by MakeSampleUsingChunks to
444+ // hash a fixed set of CAC chunks.
445+ func BenchmarkSampleHashing (b * testing.B ) {
446+ anchor := []byte ("swarm-test-anchor-deterministic!" )
447+
448+ // Shared zero-value stamp: its contents don't affect hash computation.
449+ stamp := postage .NewStamp (make ([]byte , 32 ), make ([]byte , 8 ), make ([]byte , 8 ), make ([]byte , 65 ))
450+
451+ for _ , count := range []int {1_000 , 10_000 } {
452+ b .Run (fmt .Sprintf ("chunks=%d" , count ), func (b * testing.B ) {
453+ // Build chunks once outside the measured loop.
454+ // Content is derived deterministically from the chunk index so
455+ // that every run produces the same set of chunk addresses.
456+ chunks := make ([]swarm.Chunk , count )
457+ content := make ([]byte , swarm .ChunkSize )
458+ for i := range chunks {
459+ for j := range content {
460+ content [j ] = byte (i + j )
461+ }
462+ ch , err := cac .New (content )
463+ if err != nil {
464+ b .Fatal (err )
465+ }
466+ chunks [i ] = ch .WithStamp (stamp )
467+ }
468+
469+ // Report throughput so the output shows MB/s as well as ns/op.
470+ b .SetBytes (int64 (count ) * swarm .ChunkSize )
471+ b .ResetTimer ()
472+
473+ for range b .N {
474+ if _ , err := storer .MakeSampleUsingChunks (chunks , anchor ); err != nil {
475+ b .Fatal (err )
476+ }
477+ }
478+ })
479+ }
480+ }
0 commit comments