github.com/ethersphere/bee/v2@v2.2.0/pkg/storageincentives/soc_mine_test.go (about)

     1  // Copyright 2023 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 storageincentives_test
     6  
     7  import (
     8  	"context"
     9  	"encoding/binary"
    10  	"encoding/hex"
    11  	"fmt"
    12  	"hash"
    13  	"math/big"
    14  	"os"
    15  	"sync"
    16  	"testing"
    17  
    18  	"github.com/ethersphere/bee/v2/pkg/bmt"
    19  	"github.com/ethersphere/bee/v2/pkg/cac"
    20  	"github.com/ethersphere/bee/v2/pkg/crypto"
    21  	"github.com/ethersphere/bee/v2/pkg/soc"
    22  	"github.com/ethersphere/bee/v2/pkg/swarm"
    23  	"golang.org/x/sync/errgroup"
    24  )
    25  
    26  // TestSocMine dumps a sample made out SOCs to upload for storage incestives
    27  
    28  // dump chunks
    29  
    30  // go test -v ./pkg/storageincentives/ -run TestSocMine -count 1 > socs.txt
    31  
    32  // to generate uploads using the input
    33  // cat socs.txt | tail 19 | head 16 | perl -pne 's/([a-f0-9]+)\t([a-f0-9]+)\t([a-f0-9]+)\t([a-f0-9]+)/echo -n $4 | xxd -r -p | curl -X POST \"http:\/\/localhost:1633\/soc\/$1\/$2?sig=$3\" -H \"accept: application\/json, text\/plain, \/\" -H \"content-type: application\/octet-stream\" -H \"swarm-postage-batch-id: 14b26beca257e763609143c6b04c2c487f01a051798c535c2f542ce75a97c05f\" --data-binary \@-/'
    34  func TestSocMine(t *testing.T) {
    35  	t.Parallel()
    36  	// the anchor used in neighbourhood selection and reserve salt for sampling
    37  	prefix, err := hex.DecodeString("3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff")
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  	// the transformed address hasher factory function
    42  	prefixhasher := func() hash.Hash { return swarm.NewPrefixHasher(prefix) }
    43  	trHasher := func() hash.Hash { return bmt.NewHasher(prefixhasher) }
    44  	// the bignum cast of the maximum sample value (upper bound on transformed addresses as a 256-bit article)
    45  	// this constant is for a minimum reserve size of 2 million chunks with sample size of 16
    46  	// = 1.284401 * 10^71 = 1284401 + 66 0-s
    47  	mstring := "1284401"
    48  	for i := 0; i < 66; i++ {
    49  		mstring = mstring + "0"
    50  	}
    51  	n, ok := new(big.Int).SetString(mstring, 10)
    52  	if !ok {
    53  		t.Fatalf("SetString: error setting to '%s'", mstring)
    54  	}
    55  	// the filter function on the SOC address
    56  	// meant to make sure we pass check for proof of retrievability for
    57  	// a node of overlay 0x65xxx with a reserve depth of 1, i.e.,
    58  	// SOC address must start with zero bit
    59  	filterSOCAddr := func(a swarm.Address) bool {
    60  		return a.Bytes()[0]&0x80 != 0x00
    61  	}
    62  	// the filter function on the transformed address using the density estimation constant
    63  	filterTrAddr := func(a swarm.Address) (bool, error) {
    64  		m := new(big.Int).SetBytes(a.Bytes())
    65  		return m.Cmp(n) < 0, nil
    66  	}
    67  	// setup the signer with a private key from a fixture
    68  	data, err := hex.DecodeString("634fb5a872396d9693e5c9f9d7233cfa93f395c093371017ff44aa9ae6564cdd")
    69  	if err != nil {
    70  		t.Fatal(err)
    71  	}
    72  	privKey, err := crypto.DecodeSecp256k1PrivateKey(data)
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  	signer := crypto.NewDefaultSigner(privKey)
    77  
    78  	sampleSize := 16
    79  	// for sanity check: given a filterSOCAddr requiring a 0 leading bit (chance of 1/2)
    80  	// we expect an overall rough 4 million chunks to be mined to create this sample
    81  	// for 8 workers that is half a million round on average per worker
    82  	err = makeChunks(t, signer, sampleSize, filterSOCAddr, filterTrAddr, trHasher)
    83  	if err != nil {
    84  		t.Fatal(err)
    85  	}
    86  }
    87  
    88  func makeChunks(t *testing.T, signer crypto.Signer, sampleSize int, filterSOCAddr func(swarm.Address) bool, filterTrAddr func(swarm.Address) (bool, error), trHasher func() hash.Hash) error {
    89  	t.Helper()
    90  
    91  	// set owner address from signer
    92  	ethAddress, err := signer.EthereumAddress()
    93  	if err != nil {
    94  		return err
    95  	}
    96  	ownerAddressBytes := ethAddress.Bytes()
    97  
    98  	// use the same wrapped chunk for all mined SOCs
    99  	payload := []byte("foo")
   100  	ch, err := cac.New(payload)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	var done bool                          // to signal sampleSize number of chunks found
   106  	sampleC := make(chan *soc.SOC, 1)      // channel to push results on
   107  	sample := make([]*soc.SOC, sampleSize) // to collect the sample
   108  	ctx, cancel := context.WithCancel(context.Background())
   109  	eg, ectx := errgroup.WithContext(ctx)
   110  	// the main loop terminating after sampleSize SOCs have been generated
   111  	eg.Go(func() error {
   112  		defer cancel()
   113  		for i := 0; i < sampleSize; i++ {
   114  			select {
   115  			case sample[i] = <-sampleC:
   116  			case <-ectx.Done():
   117  				return ectx.Err()
   118  			}
   119  		}
   120  		done = true
   121  		return nil
   122  	})
   123  
   124  	// loop to start mining workers
   125  	count := 8 // number of parallel workers
   126  	wg := sync.WaitGroup{}
   127  	for i := 0; i < count; i++ {
   128  		i := i
   129  		wg.Add(1)
   130  		eg.Go(func() (err error) {
   131  			offset := i * 4
   132  			found := 0
   133  			for seed := uint32(1); ; seed++ {
   134  				select {
   135  				case <-ectx.Done():
   136  					defer wg.Done()
   137  					t.Logf("LOG quit worker: %d, rounds: %d, found: %d\n", i, seed, found)
   138  					return ectx.Err()
   139  				default:
   140  				}
   141  				id := make([]byte, 32)
   142  				binary.BigEndian.PutUint32(id[offset:], seed)
   143  				s := soc.New(id, ch)
   144  				addr, err := soc.CreateAddress(id, ownerAddressBytes)
   145  				if err != nil {
   146  					return err
   147  				}
   148  				// continue if mined SOC addr is not good
   149  				if !filterSOCAddr(addr) {
   150  					continue
   151  				}
   152  				hasher := trHasher()
   153  				data := s.WrappedChunk().Data()
   154  				hasher.(*bmt.Hasher).SetHeader(data[:8])
   155  				_, err = hasher.Write(data[8:])
   156  				if err != nil {
   157  					return err
   158  				}
   159  				trAddr := hasher.Sum(nil)
   160  				// hashing the transformed wrapped chunk address with the SOC address
   161  				// to arrive at a unique transformed SOC address despite identical payloads
   162  				trSocAddr, err := soc.CreateAddress(addr.Bytes(), trAddr)
   163  				if err != nil {
   164  					return err
   165  				}
   166  				ok, err := filterTrAddr(trSocAddr)
   167  				if err != nil {
   168  					return err
   169  				}
   170  				if ok {
   171  					select {
   172  					case sampleC <- s:
   173  						found++
   174  						t.Logf("LOG worker: %d, rounds: %d, found: %d, id:%x\n", i, seed, found, id)
   175  					case <-ectx.Done():
   176  						defer wg.Done()
   177  						t.Logf("LOG quit worker: %d, rounds: %d, found: %d\n", i, seed, found)
   178  						return ectx.Err()
   179  					}
   180  				}
   181  			}
   182  		})
   183  	}
   184  	if err := eg.Wait(); !done && err != nil {
   185  		return err
   186  	}
   187  	wg.Wait()
   188  	for _, s := range sample {
   189  
   190  		// signs the chunk
   191  		sch, err := s.Sign(signer)
   192  		if err != nil {
   193  			return err
   194  		}
   195  		data := sch.Data()
   196  		id, sig, payload := data[:32], data[32:97], data[97:]
   197  		fmt.Fprintf(os.Stdout, "%x\t%x\t%x\t%x\n", ownerAddressBytes, id, sig, payload)
   198  
   199  	}
   200  	return nil
   201  }