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  }