github.com/ethersphere/bee/v2@v2.2.0/pkg/postage/stampissuer_test.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package postage_test 6 7 import ( 8 crand "crypto/rand" 9 "encoding/binary" 10 "errors" 11 "fmt" 12 "io" 13 "math" 14 "math/big" 15 "sync" 16 "sync/atomic" 17 "testing" 18 19 "github.com/ethersphere/bee/v2/pkg/postage" 20 storage "github.com/ethersphere/bee/v2/pkg/storage" 21 "github.com/ethersphere/bee/v2/pkg/storage/storagetest" 22 "github.com/ethersphere/bee/v2/pkg/swarm" 23 "github.com/google/go-cmp/cmp" 24 "github.com/google/go-cmp/cmp/cmpopts" 25 "golang.org/x/sync/errgroup" 26 ) 27 28 // TestStampIssuerMarshalling tests the idempotence of binary marshal/unmarshal. 29 func TestStampIssuerMarshalling(t *testing.T) { 30 want := newTestStampIssuer(t, 1000) 31 buf, err := want.MarshalBinary() 32 if err != nil { 33 t.Fatal(err) 34 } 35 36 have := &postage.StampIssuer{} 37 err = have.UnmarshalBinary(buf) 38 if err != nil { 39 t.Fatal(err) 40 } 41 42 opts := []cmp.Option{ 43 cmp.AllowUnexported(postage.StampIssuer{}, big.Int{}), 44 cmpopts.IgnoreInterfaces(struct{ storage.Store }{}), 45 cmpopts.IgnoreTypes(sync.Mutex{}, sync.RWMutex{}), 46 } 47 if !cmp.Equal(want, have, opts...) { 48 t.Errorf("Marshal/Unmarshal mismatch (-want +have):\n%s", cmp.Diff(want, have)) 49 } 50 } 51 52 func newTestStampIssuer(t *testing.T, block uint64) *postage.StampIssuer { 53 t.Helper() 54 return newTestStampIssuerMutability(t, block, true) 55 } 56 57 func newTestStampIssuerID(t *testing.T, block uint64, id []byte) *postage.StampIssuer { 58 t.Helper() 59 return postage.NewStampIssuer( 60 "label", 61 "keyID", 62 id, 63 big.NewInt(3), 64 16, 65 8, 66 block, 67 true, 68 ) 69 } 70 71 func newTestStampIssuerMutability(t *testing.T, block uint64, immutable bool) *postage.StampIssuer { 72 t.Helper() 73 id := make([]byte, 32) 74 _, err := io.ReadFull(crand.Reader, id) 75 if err != nil { 76 t.Fatal(err) 77 } 78 return postage.NewStampIssuer( 79 "label", 80 "keyID", 81 id, 82 big.NewInt(3), 83 16, 84 8, 85 block, 86 immutable, 87 ) 88 } 89 90 func TestStampItem(t *testing.T) { 91 t.Parallel() 92 93 tests := []struct { 94 name string 95 test *storagetest.ItemMarshalAndUnmarshalTest 96 }{{ 97 name: "zero batchID", 98 test: &storagetest.ItemMarshalAndUnmarshalTest{ 99 Item: postage.NewStampItem(), 100 Factory: func() storage.Item { return postage.NewStampItem() }, 101 MarshalErr: postage.ErrStampItemMarshalBatchIDInvalid, 102 CmpOpts: []cmp.Option{cmp.AllowUnexported(postage.StampItem{})}, 103 }, 104 }, { 105 name: "zero chunkAddress", 106 test: &storagetest.ItemMarshalAndUnmarshalTest{ 107 Item: postage.NewStampItem().WithBatchID([]byte{swarm.HashSize - 1: 9}), 108 Factory: func() storage.Item { return postage.NewStampItem() }, 109 MarshalErr: postage.ErrStampItemMarshalChunkAddressInvalid, 110 CmpOpts: []cmp.Option{cmp.AllowUnexported(postage.StampItem{})}, 111 }, 112 }, { 113 name: "valid values", 114 test: &storagetest.ItemMarshalAndUnmarshalTest{ 115 Item: postage.NewStampItem(). 116 WithBatchID([]byte{swarm.HashSize - 1: 9}). 117 WithChunkAddress(swarm.RandAddress(t)). 118 WithBatchIndex([]byte{swarm.StampIndexSize - 1: 9}). 119 WithBatchTimestamp([]byte{swarm.StampTimestampSize - 1: 9}), 120 Factory: func() storage.Item { return postage.NewStampItem() }, 121 CmpOpts: []cmp.Option{cmp.AllowUnexported(postage.StampItem{})}, 122 }, 123 }, { 124 name: "max values", 125 test: &storagetest.ItemMarshalAndUnmarshalTest{ 126 Item: postage.NewStampItem(). 127 WithBatchID(storagetest.MaxAddressBytes[:]). 128 WithChunkAddress(swarm.NewAddress(storagetest.MaxAddressBytes[:])). 129 WithBatchIndex(storagetest.MaxStampIndexBytes[:]). 130 WithBatchTimestamp(storagetest.MaxBatchTimestampBytes[:]), 131 Factory: func() storage.Item { return postage.NewStampItem() }, 132 CmpOpts: []cmp.Option{cmp.AllowUnexported(postage.StampItem{})}, 133 }, 134 }, { 135 name: "invalid size", 136 test: &storagetest.ItemMarshalAndUnmarshalTest{ 137 Item: &storagetest.ItemStub{ 138 MarshalBuf: []byte{0xFF}, 139 UnmarshalBuf: []byte{0xFF}, 140 }, 141 Factory: func() storage.Item { return postage.NewStampItem() }, 142 UnmarshalErr: postage.ErrStampItemUnmarshalInvalidSize, 143 CmpOpts: []cmp.Option{cmp.AllowUnexported(postage.StampItem{})}, 144 }, 145 }} 146 147 for _, tc := range tests { 148 tc := tc 149 150 t.Run(fmt.Sprintf("%s marshal/unmarshal", tc.name), func(t *testing.T) { 151 t.Parallel() 152 153 storagetest.TestItemMarshalAndUnmarshal(t, tc.test) 154 }) 155 156 t.Run(fmt.Sprintf("%s clone", tc.name), func(t *testing.T) { 157 t.Parallel() 158 159 storagetest.TestItemClone(t, &storagetest.ItemCloneTest{ 160 Item: tc.test.Item, 161 CmpOpts: tc.test.CmpOpts, 162 }) 163 }) 164 } 165 } 166 167 func Test_StampIssuer_inc(t *testing.T) { 168 t.Parallel() 169 170 addr := swarm.NewAddress([]byte{1, 2, 3, 4}) 171 172 t.Run("mutable", func(t *testing.T) { 173 t.Parallel() 174 175 sti := postage.NewStampIssuer("label", "keyID", make([]byte, 32), big.NewInt(3), 16, 8, 0, false) 176 count := sti.BucketUpperBound() 177 178 // Increment to upper bound (fill bucket to max cap) 179 for i := uint32(0); i < count; i++ { 180 _, _, err := sti.Increment(addr) 181 if err != nil { 182 t.Fatal(err) 183 } 184 } 185 186 // Incrementing stamp issuer above upper bound should return index starting from 0 187 for i := uint32(0); i < count; i++ { 188 idxb, _, err := sti.Increment(addr) 189 if err != nil { 190 t.Fatal(err) 191 } 192 193 if _, idx := bytesToIndex(idxb); idx != i { 194 t.Fatalf("bucket should be full %v", idx) 195 } 196 } 197 }) 198 199 t.Run("immutable", func(t *testing.T) { 200 t.Parallel() 201 202 sti := postage.NewStampIssuer("label", "keyID", make([]byte, 32), big.NewInt(3), 16, 8, 0, true) 203 count := sti.BucketUpperBound() 204 205 // Increment to upper bound (fill bucket to max cap) 206 for i := uint32(0); i < count; i++ { 207 _, _, err := sti.Increment(addr) 208 if err != nil { 209 t.Fatal(err) 210 } 211 } 212 213 // Incrementing stamp issuer above upper bound should return error 214 for i := uint32(0); i < count; i++ { 215 _, _, err := sti.Increment(addr) 216 if !errors.Is(err, postage.ErrBucketFull) { 217 t.Fatal("bucket should be full") 218 } 219 } 220 }) 221 } 222 223 func TestUtilization(t *testing.T) { 224 t.Skip("meant to be run for ad hoc testing") 225 226 for depth := uint8(17); depth < 25; depth++ { 227 sti := postage.NewStampIssuer("label", "keyID", make([]byte, 32), big.NewInt(3), depth, postage.BucketDepth, 0, true) 228 229 var count uint64 230 231 var eg errgroup.Group 232 233 for i := 0; i < 8; i++ { 234 eg.Go(func() error { 235 for { 236 _, _, err := sti.Increment(swarm.RandAddress(t)) 237 if err != nil { 238 return err 239 } 240 atomic.AddUint64(&count, 1) 241 } 242 }) 243 } 244 245 err := eg.Wait() 246 if !errors.Is(err, postage.ErrBucketFull) { 247 t.Fatalf("want: %v; have: %v", postage.ErrBucketFull, err) 248 } 249 250 t.Logf("depth: %d, actual utilization: %f", depth, float64(count)/math.Pow(2, float64(depth))) 251 } 252 253 } 254 255 func bytesToIndex(buf []byte) (bucket, index uint32) { 256 index64 := binary.BigEndian.Uint64(buf) 257 bucket = uint32(index64 >> 32) 258 index = uint32(index64) 259 return bucket, index 260 }