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 }