github.com/ethersphere/bee/v2@v2.2.0/pkg/pushsync/pushsync_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 pushsync_test
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"errors"
    11  	"strings"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/ethereum/go-ethereum/common"
    17  	"github.com/ethersphere/bee/v2/pkg/accounting"
    18  	accountingmock "github.com/ethersphere/bee/v2/pkg/accounting/mock"
    19  	"github.com/ethersphere/bee/v2/pkg/crypto"
    20  	cryptomock "github.com/ethersphere/bee/v2/pkg/crypto/mock"
    21  	"github.com/ethersphere/bee/v2/pkg/log"
    22  	"github.com/ethersphere/bee/v2/pkg/p2p"
    23  	"github.com/ethersphere/bee/v2/pkg/p2p/protobuf"
    24  	"github.com/ethersphere/bee/v2/pkg/p2p/streamtest"
    25  	pricermock "github.com/ethersphere/bee/v2/pkg/pricer/mock"
    26  	"github.com/ethersphere/bee/v2/pkg/pushsync"
    27  	"github.com/ethersphere/bee/v2/pkg/pushsync/pb"
    28  	storage "github.com/ethersphere/bee/v2/pkg/storage"
    29  	testingc "github.com/ethersphere/bee/v2/pkg/storage/testing"
    30  	"github.com/ethersphere/bee/v2/pkg/swarm"
    31  	"github.com/ethersphere/bee/v2/pkg/topology"
    32  	"github.com/ethersphere/bee/v2/pkg/topology/mock"
    33  )
    34  
    35  const (
    36  	fixedPrice = uint64(10)
    37  )
    38  
    39  var blockHash = common.HexToHash("0x1")
    40  
    41  type pricerParameters struct {
    42  	price     uint64
    43  	peerPrice uint64
    44  }
    45  
    46  var (
    47  	defaultPrices = pricerParameters{price: fixedPrice, peerPrice: fixedPrice}
    48  	defaultSigner = func(ch swarm.Chunk) crypto.Signer {
    49  		return cryptomock.New(cryptomock.WithSignFunc(func([]byte) ([]byte, error) {
    50  			key, _ := crypto.GenerateSecp256k1Key()
    51  			signer := crypto.NewDefaultSigner(key)
    52  			signature, _ := signer.Sign(ch.Address().Bytes())
    53  
    54  			return signature, nil
    55  		}))
    56  	}
    57  )
    58  
    59  // TestPushClosest inserts a chunk as uploaded chunk in db. This triggers sending a chunk to the closest node
    60  // and expects a receipt. The message are intercepted in the outgoing stream to check for correctness.
    61  func TestPushClosest(t *testing.T) {
    62  	t.Parallel()
    63  	// chunk data to upload
    64  	chunk := testingc.FixtureChunk("7000")
    65  
    66  	// create a pivot node and a mocked closest node
    67  	pivotNode := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000")   // base is 0000
    68  	closestPeer := swarm.MustParseHexAddress("6000000000000000000000000000000000000000000000000000000000000000") // binary 0110 -> po 1
    69  
    70  	// peer is the node responding to the chunk receipt message
    71  	// mock should return ErrWantSelf since there's no one to forward to
    72  	psPeer, _, peerAccounting := createPushSyncNode(t, closestPeer, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
    73  
    74  	recorder := streamtest.New(streamtest.WithProtocols(psPeer.Protocol()), streamtest.WithBaseAddr(pivotNode))
    75  
    76  	// pivot node needs the streamer since the chunk is intercepted by
    77  	// the chunk worker, then gets sent by opening a new stream
    78  	psPivot, _, pivotAccounting := createPushSyncNode(t, pivotNode, defaultPrices, recorder, nil, defaultSigner(chunk), mock.WithClosestPeer(closestPeer))
    79  
    80  	// Trigger the sending of chunk to the closest node
    81  	receipt, err := psPivot.PushChunkToClosest(context.Background(), chunk)
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  
    86  	if !chunk.Address().Equal(receipt.Address) {
    87  		t.Fatal("invalid receipt")
    88  	}
    89  
    90  	// this intercepts the outgoing delivery message
    91  	waitOnRecordAndTest(t, closestPeer, recorder, chunk.Address(), chunk.Data())
    92  
    93  	// this intercepts the incoming receipt message
    94  	waitOnRecordAndTest(t, closestPeer, recorder, chunk.Address(), nil)
    95  	balance, err := pivotAccounting.Balance(closestPeer)
    96  	if err != nil {
    97  		t.Fatal(err)
    98  	}
    99  
   100  	if balance.Int64() != -int64(fixedPrice) {
   101  		t.Fatalf("unexpected balance on pivot. want %d got %d", -int64(fixedPrice), balance)
   102  	}
   103  
   104  	balance, err = peerAccounting.Balance(pivotNode)
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  	if balance.Int64() != int64(fixedPrice) {
   109  		t.Fatalf("unexpected balance on peer. want %d got %d", int64(fixedPrice), balance)
   110  	}
   111  }
   112  
   113  // TestShallowReceipt forces the peer to send back a shallow receipt to a pushsync request. In return, the origin node returns the error along with the received receipt.
   114  func TestShallowReceipt(t *testing.T) {
   115  	t.Parallel()
   116  	// chunk data to upload
   117  	chunk := testingc.FixtureChunk("7000")
   118  
   119  	var highPO uint8 = 31
   120  
   121  	// create a pivot node and a mocked closest node
   122  	pivotNode := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000")   // base is 0000
   123  	closestPeer := swarm.MustParseHexAddress("6000000000000000000000000000000000000000000000000000000000000000") // binary 0110 -> po 1
   124  
   125  	// peer is the node responding to the chunk receipt message
   126  	// mock should return ErrWantSelf since there's no one to forward to
   127  	psPeer, _ := createPushSyncNodeWithRadius(t, closestPeer, defaultPrices, nil, nil, defaultSigner(chunk), highPO, mock.WithClosestPeerErr(topology.ErrWantSelf))
   128  
   129  	recorder := streamtest.New(streamtest.WithProtocols(psPeer.Protocol()), streamtest.WithBaseAddr(pivotNode))
   130  
   131  	// pivot node needs the streamer since the chunk is intercepted by
   132  	// the chunk worker, then gets sent by opening a new stream
   133  	psPivot, _ := createPushSyncNodeWithRadius(t, pivotNode, defaultPrices, recorder, nil, defaultSigner(chunk), highPO, mock.WithClosestPeer(closestPeer))
   134  
   135  	// Trigger the sending of chunk to the closest node
   136  	receipt, err := psPivot.PushChunkToClosest(context.Background(), chunk)
   137  	if !errors.Is(err, pushsync.ErrShallowReceipt) {
   138  		t.Fatalf("got %v, want %v", err, pushsync.ErrShallowReceipt)
   139  	}
   140  
   141  	if !chunk.Address().Equal(receipt.Address) {
   142  		t.Fatal("invalid receipt")
   143  	}
   144  
   145  	// this intercepts the outgoing delivery message
   146  	waitOnRecordAndTest(t, closestPeer, recorder, chunk.Address(), chunk.Data())
   147  
   148  	// this intercepts the incoming receipt message
   149  	waitOnRecordAndTest(t, closestPeer, recorder, chunk.Address(), nil)
   150  }
   151  
   152  // PushChunkToClosest tests the sending of chunk to closest peer from the origination source perspective.
   153  // it also checks whether the tags are incremented properly if they are present
   154  func TestPushChunkToClosest(t *testing.T) {
   155  	t.Parallel()
   156  	// chunk data to upload
   157  	chunk := testingc.FixtureChunk("7000")
   158  
   159  	// create a pivot node and a mocked closest node
   160  	pivotNode := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000")   // base is 0000
   161  	closestPeer := swarm.MustParseHexAddress("6000000000000000000000000000000000000000000000000000000000000000") // binary 0110 -> po 1
   162  	callbackC := make(chan struct{}, 1)
   163  	// peer is the node responding to the chunk receipt message
   164  	// mock should return ErrWantSelf since there's no one to forward to
   165  
   166  	psPeer, _, peerAccounting := createPushSyncNode(t, closestPeer, defaultPrices, nil, chanFunc(callbackC), defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   167  
   168  	recorder := streamtest.New(streamtest.WithProtocols(psPeer.Protocol()), streamtest.WithBaseAddr(pivotNode))
   169  
   170  	// pivot node needs the streamer since the chunk is intercepted by
   171  	// the chunk worker, then gets sent by opening a new stream
   172  	psPivot, pivotStorer, pivotAccounting := createPushSyncNode(t, pivotNode, defaultPrices, recorder, nil, defaultSigner(chunk), mock.WithClosestPeer(closestPeer))
   173  
   174  	// Trigger the sending of chunk to the closest node
   175  	receipt, err := psPivot.PushChunkToClosest(context.Background(), chunk)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	if !chunk.Address().Equal(receipt.Address) {
   181  		t.Fatal("invalid receipt")
   182  	}
   183  
   184  	// this intercepts the outgoing delivery message
   185  	waitOnRecordAndTest(t, closestPeer, recorder, chunk.Address(), chunk.Data())
   186  
   187  	// this intercepts the incoming receipt message
   188  	waitOnRecordAndTest(t, closestPeer, recorder, chunk.Address(), nil)
   189  
   190  	found, count := pivotStorer.hasReported(t, chunk.Address())
   191  	if !found {
   192  		t.Fatalf("chunk %s not reported", chunk.Address())
   193  	}
   194  
   195  	if count != 1 {
   196  		t.Fatalf("tags error")
   197  	}
   198  
   199  	balance, err := pivotAccounting.Balance(closestPeer)
   200  	if err != nil {
   201  		t.Fatal(err)
   202  	}
   203  
   204  	if balance.Int64() != -int64(fixedPrice) {
   205  		t.Fatalf("unexpected balance on pivot. want %d got %d", -int64(fixedPrice), balance)
   206  	}
   207  
   208  	balance, err = peerAccounting.Balance(pivotNode)
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  
   213  	if balance.Int64() != int64(fixedPrice) {
   214  		t.Fatalf("unexpected balance on peer. want %d got %d", int64(fixedPrice), balance)
   215  	}
   216  
   217  	// check if the pss delivery hook is called
   218  	select {
   219  	case <-callbackC:
   220  	case <-time.After(100 * time.Millisecond):
   221  		t.Fatalf("delivery hook was not called")
   222  	}
   223  }
   224  
   225  func TestPushChunkToNextClosest(t *testing.T) {
   226  	t.Parallel()
   227  	t.Skip("flaky test")
   228  
   229  	// chunk data to upload
   230  	chunk := testingc.FixtureChunk("7000")
   231  
   232  	// create a pivot node and a mocked closest node
   233  	pivotNode := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000") // base is 0000
   234  
   235  	peer1 := swarm.MustParseHexAddress("6000000000000000000000000000000000000000000000000000000000000000")
   236  	peer2 := swarm.MustParseHexAddress("5000000000000000000000000000000000000000000000000000000000000000")
   237  
   238  	// peer is the node responding to the chunk receipt message
   239  	// mock should return ErrWantSelf since there's no one to forward to
   240  	psPeer1, _, peerAccounting1 := createPushSyncNode(t, peer1, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   241  
   242  	psPeer2, _, peerAccounting2 := createPushSyncNode(t, peer2, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   243  
   244  	var fail = true
   245  	var lock sync.Mutex
   246  
   247  	recorder := streamtest.New(
   248  		streamtest.WithProtocols(
   249  			psPeer1.Protocol(),
   250  			psPeer2.Protocol(),
   251  		),
   252  		streamtest.WithMiddlewares(
   253  			func(h p2p.HandlerFunc) p2p.HandlerFunc {
   254  				return func(ctx context.Context, peer p2p.Peer, stream p2p.Stream) error {
   255  					// this hack is required to simulate first storer node failing
   256  					lock.Lock()
   257  					defer lock.Unlock()
   258  					if fail {
   259  						fail = false
   260  						stream.Close()
   261  						return errors.New("peer not reachable")
   262  					}
   263  
   264  					if err := h(ctx, peer, stream); err != nil {
   265  						return err
   266  					}
   267  					// close stream after all previous middlewares wrote to it
   268  					// so that the receiving peer can get all the post messages
   269  					return stream.Close()
   270  				}
   271  			},
   272  		),
   273  		streamtest.WithBaseAddr(pivotNode),
   274  	)
   275  
   276  	// pivot node needs the streamer since the chunk is intercepted by
   277  	// the chunk worker, then gets sent by opening a new stream
   278  	psPivot, pivotStorer, pivotAccounting := createPushSyncNode(t, pivotNode, defaultPrices, recorder, nil, defaultSigner(chunk), mock.WithPeers(peer1, peer2))
   279  
   280  	// Trigger the sending of chunk to the closest node
   281  	receipt, err := psPivot.PushChunkToClosest(context.Background(), chunk)
   282  	if err != nil {
   283  		t.Fatal(err)
   284  	}
   285  
   286  	if !chunk.Address().Equal(receipt.Address) {
   287  		t.Fatal("invalid receipt")
   288  	}
   289  
   290  	// this intercepts the outgoing delivery message
   291  	waitOnRecordAndTest(t, peer2, recorder, chunk.Address(), chunk.Data())
   292  
   293  	// this intercepts the incoming receipt message
   294  	waitOnRecordAndTest(t, peer2, recorder, chunk.Address(), nil)
   295  
   296  	found, count := pivotStorer.hasReported(t, chunk.Address())
   297  	if !found {
   298  		t.Fatalf("chunk %s not reported", chunk.Address())
   299  	}
   300  
   301  	// the write to the first peer might succeed or
   302  	// fail, so it is not guaranteed that two increments
   303  	// are made to Sent. expect >= 1
   304  	if count == 0 {
   305  		t.Fatalf("tags error got %d want >= 1", count)
   306  	}
   307  
   308  	balance, err := pivotAccounting.Balance(peer1)
   309  	if err != nil {
   310  		t.Fatal(err)
   311  	}
   312  
   313  	if balance.Int64() != -int64(fixedPrice) {
   314  		t.Fatalf("unexpected balance on pivot. want %d got %d", -int64(fixedPrice), balance)
   315  	}
   316  
   317  	balance2, err := peerAccounting2.Balance(pivotNode)
   318  	if err != nil {
   319  		t.Fatal(err)
   320  	}
   321  
   322  	if balance2.Int64() != int64(fixedPrice) {
   323  		t.Fatalf("unexpected balance on peer2. want %d got %d", int64(fixedPrice), balance2)
   324  	}
   325  
   326  	balance1, err := peerAccounting1.Balance(peer2)
   327  	if err != nil {
   328  		t.Fatal(err)
   329  	}
   330  
   331  	if balance1.Int64() != 0 {
   332  		t.Fatalf("unexpected balance on peer1. want %d got %d", 0, balance1)
   333  	}
   334  }
   335  
   336  func TestPushChunkToClosestErrorAttemptRetry(t *testing.T) {
   337  	t.Parallel()
   338  
   339  	// chunk data to upload
   340  	chunk := testingc.FixtureChunk("7000")
   341  
   342  	// create a pivot node and a mocked closest node
   343  	pivotNode := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000") // base is 0000
   344  
   345  	peer1 := swarm.MustParseHexAddress("6000000000000000000000000000000000000000000000000000000000000000")
   346  	peer2 := swarm.MustParseHexAddress("5000000000000000000000000000000000000000000000000000000000000000")
   347  	peer3 := swarm.MustParseHexAddress("9000000000000000000000000000000000000000000000000000000000000000")
   348  	peer4 := swarm.MustParseHexAddress("4000000000000000000000000000000000000000000000000000000000000000")
   349  
   350  	// peer is the node responding to the chunk receipt message
   351  	// mock should return ErrWantSelf since there's no one to forward to
   352  	psPeer1, _, peerAccounting1 := createPushSyncNode(t, peer1, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   353  
   354  	psPeer2, _, peerAccounting2 := createPushSyncNode(t, peer2, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   355  
   356  	psPeer3, _, peerAccounting3 := createPushSyncNode(t, peer3, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   357  
   358  	psPeer4, _, peerAccounting4 := createPushSyncNode(t, peer4, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   359  
   360  	recorder := streamtest.New(
   361  		streamtest.WithProtocols(
   362  			psPeer1.Protocol(),
   363  			psPeer2.Protocol(),
   364  			psPeer3.Protocol(),
   365  			psPeer4.Protocol(),
   366  		),
   367  		streamtest.WithBaseAddr(pivotNode),
   368  	)
   369  
   370  	var pivotAccounting *accountingmock.Service
   371  	pivotAccounting = accountingmock.NewAccounting(
   372  		accountingmock.WithPrepareCreditFunc(func(peer swarm.Address, price uint64, originated bool) (accounting.Action, error) {
   373  			if peer.String() == peer4.String() {
   374  				return pivotAccounting.MakeCreditAction(peer, price), nil
   375  			}
   376  			return nil, errors.New("unable to reserve")
   377  		}),
   378  	)
   379  
   380  	psPivot, pivotStorer := createPushSyncNodeWithAccounting(t, pivotNode, defaultPrices, recorder, nil, defaultSigner(chunk), pivotAccounting, log.Noop, mock.WithPeers(peer1, peer2, peer3, peer4))
   381  
   382  	// Trigger the sending of chunk to the closest node
   383  	receipt, err := psPivot.PushChunkToClosest(context.Background(), chunk)
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  
   388  	if !chunk.Address().Equal(receipt.Address) {
   389  		t.Fatal("invalid receipt")
   390  	}
   391  
   392  	// this intercepts the outgoing delivery message
   393  	waitOnRecordAndTest(t, peer4, recorder, chunk.Address(), chunk.Data())
   394  
   395  	// this intercepts the incoming receipt message
   396  	waitOnRecordAndTest(t, peer4, recorder, chunk.Address(), nil)
   397  
   398  	// out of 4, 3 peers should return accounting error. So we should have effectively
   399  	// sent only 1 msg
   400  	found, count := pivotStorer.hasReported(t, chunk.Address())
   401  	if !found {
   402  		t.Fatalf("chunk %s not reported", chunk.Address())
   403  	}
   404  	if count != 1 {
   405  		t.Fatalf("tags error")
   406  	}
   407  
   408  	balance, err := pivotAccounting.Balance(peer4)
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  
   413  	if balance.Int64() != -int64(fixedPrice) {
   414  		t.Fatalf("unexpected balance on pivot. want %d got %d", -int64(fixedPrice), balance)
   415  	}
   416  
   417  	balance4, err := peerAccounting4.Balance(pivotNode)
   418  	if err != nil {
   419  		t.Fatal(err)
   420  	}
   421  
   422  	if balance4.Int64() != int64(fixedPrice) {
   423  		t.Fatalf("unexpected balance on peer4. want %d got %d", int64(fixedPrice), balance4)
   424  	}
   425  
   426  	for _, p := range []struct {
   427  		addr swarm.Address
   428  		acct accounting.Interface
   429  	}{
   430  		{peer1, peerAccounting1},
   431  		{peer2, peerAccounting2},
   432  		{peer3, peerAccounting3},
   433  	} {
   434  		bal, err := p.acct.Balance(p.addr)
   435  		if err != nil {
   436  			t.Fatal(err)
   437  		}
   438  
   439  		if bal.Int64() != 0 {
   440  			t.Fatalf("unexpected balance on %s. want %d got %d", p.addr, 0, bal)
   441  		}
   442  	}
   443  }
   444  
   445  // TestHandler expect a chunk from a node on a stream. It then stores the chunk in the local store and
   446  // sends back a receipt. This is tested by intercepting the incoming stream for proper messages.
   447  // It also sends the chunk to the closest peer and receives a receipt.
   448  //
   449  // Chunk moves from   TriggerPeer -> PivotPeer -> ClosestPeer
   450  func TestHandler(t *testing.T) {
   451  	t.Parallel()
   452  	// chunk data to upload
   453  	chunk := testingc.FixtureChunk("7000")
   454  
   455  	// create a pivot node and a mocked closest node
   456  	triggerPeer := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000")
   457  	pivotPeer := swarm.MustParseHexAddress("5000000000000000000000000000000000000000000000000000000000000000")
   458  	closestPeer := swarm.MustParseHexAddress("6000000000000000000000000000000000000000000000000000000000000000")
   459  
   460  	// Create the closest peer
   461  	psClosestPeer, _, closestAccounting := createPushSyncNode(t, closestPeer, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   462  
   463  	// creating the pivot peer
   464  	psPivot, _, pivotAccounting := createPushSyncNode(t, pivotPeer, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithPeers(closestPeer))
   465  
   466  	combinedRecorder := streamtest.New(streamtest.WithProtocols(psPivot.Protocol(), psClosestPeer.Protocol()), streamtest.WithBaseAddr(triggerPeer))
   467  
   468  	// Creating the trigger peer
   469  	psTriggerPeer, _, triggerAccounting := createPushSyncNode(t, triggerPeer, defaultPrices, combinedRecorder, nil, defaultSigner(chunk), mock.WithPeers(pivotPeer, closestPeer))
   470  
   471  	receipt, err := psTriggerPeer.PushChunkToClosest(context.Background(), chunk)
   472  	if err != nil {
   473  		t.Fatal(err)
   474  	}
   475  
   476  	if !chunk.Address().Equal(receipt.Address) {
   477  		t.Fatal("invalid receipt")
   478  	}
   479  
   480  	// Pivot peer will forward the chunk to its closest peer. Intercept the incoming stream from pivot node and check
   481  	// for the correctness of the chunk
   482  	waitOnRecordAndTest(t, closestPeer, combinedRecorder, chunk.Address(), chunk.Data())
   483  
   484  	// Similarly intercept the same incoming stream to see if the closest peer is sending a proper receipt
   485  	waitOnRecordAndTest(t, closestPeer, combinedRecorder, chunk.Address(), nil)
   486  
   487  	// In the received stream, check if a receipt is sent from pivot peer and check for its correctness.
   488  	waitOnRecordAndTest(t, pivotPeer, combinedRecorder, chunk.Address(), nil)
   489  
   490  	// In pivot peer,  intercept the incoming delivery chunk from the trigger peer and check for correctness
   491  	waitOnRecordAndTest(t, pivotPeer, combinedRecorder, chunk.Address(), chunk.Data())
   492  
   493  	balance, err := triggerAccounting.Balance(pivotPeer)
   494  	if err != nil {
   495  		t.Fatal(err)
   496  	}
   497  
   498  	if balance.Int64() != -int64(fixedPrice) {
   499  		t.Fatalf("unexpected balance on trigger. want %d got %d", -int64(fixedPrice), balance)
   500  	}
   501  
   502  	balance, err = triggerAccounting.Balance(closestPeer)
   503  	if err != nil {
   504  		t.Fatal(err)
   505  	}
   506  
   507  	if balance.Int64() != -int64(fixedPrice) {
   508  		t.Fatalf("unexpected balance on trigger. want %d got %d", -int64(fixedPrice), balance)
   509  	}
   510  
   511  	balance, err = pivotAccounting.Balance(triggerPeer)
   512  	if err != nil {
   513  		t.Fatal(err)
   514  	}
   515  
   516  	if balance.Int64() != 0 {
   517  		t.Fatalf("unexpected balance on pivot. want %d got %d", int64(fixedPrice), balance)
   518  	}
   519  
   520  	balance, err = pivotAccounting.Balance(closestPeer)
   521  	if err != nil {
   522  		t.Fatal(err)
   523  	}
   524  
   525  	if balance.Int64() != 0 {
   526  		t.Fatalf("unexpected balance on pivot. want %d got %d", -int64(fixedPrice), balance)
   527  	}
   528  
   529  	balance, err = closestAccounting.Balance(pivotPeer)
   530  	if err != nil {
   531  		t.Fatal(err)
   532  	}
   533  
   534  	if balance.Int64() != 0 {
   535  		t.Fatalf("unexpected balance on closest. want %d got %d", int64(fixedPrice), balance)
   536  	}
   537  }
   538  
   539  func TestPropagateErrMsg(t *testing.T) {
   540  	t.Parallel()
   541  	// chunk data to upload
   542  	chunk := testingc.FixtureChunk("7000")
   543  
   544  	// create a pivot node and a mocked closest node
   545  	triggerPeer := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000")
   546  	pivotPeer := swarm.MustParseHexAddress("5000000000000000000000000000000000000000000000000000000000000000")
   547  	closestPeer := swarm.MustParseHexAddress("7000000000000000000000000000000000000000000000000000000000000000")
   548  
   549  	faultySigner := cryptomock.New(cryptomock.WithSignFunc(func([]byte) ([]byte, error) {
   550  		return nil, errors.New("simulated error")
   551  	}))
   552  
   553  	buf := new(bytes.Buffer)
   554  	captureLogger := log.NewLogger("test", log.WithSink(buf))
   555  
   556  	// Create the closest peer
   557  	psClosestPeer, _ := createPushSyncNodeWithAccounting(t, closestPeer, defaultPrices, nil, nil, faultySigner, accountingmock.NewAccounting(), log.Noop, mock.WithClosestPeerErr(topology.ErrWantSelf))
   558  
   559  	// creating the pivot peer
   560  	psPivot, _ := createPushSyncNodeWithAccounting(t, pivotPeer, defaultPrices, nil, nil, defaultSigner(chunk), accountingmock.NewAccounting(), log.Noop, mock.WithPeers(closestPeer))
   561  
   562  	combinedRecorder := streamtest.New(streamtest.WithProtocols(psPivot.Protocol(), psClosestPeer.Protocol()), streamtest.WithBaseAddr(triggerPeer))
   563  
   564  	// Creating the trigger peer
   565  	psTriggerPeer, _ := createPushSyncNodeWithAccounting(t, triggerPeer, defaultPrices, combinedRecorder, nil, defaultSigner(chunk), accountingmock.NewAccounting(), captureLogger, mock.WithPeers(pivotPeer))
   566  
   567  	_, err := psTriggerPeer.PushChunkToClosest(context.Background(), chunk)
   568  	if err == nil {
   569  		t.Fatal("should received error")
   570  	}
   571  
   572  	want := p2p.NewChunkDeliveryError("receipt signature: simulated error")
   573  	if got := buf.String(); !strings.Contains(got, want.Error()) {
   574  		t.Fatalf("got log %s, want %s", got, want)
   575  	}
   576  }
   577  
   578  func TestSignsReceipt(t *testing.T) {
   579  	t.Parallel()
   580  
   581  	// chunk data to upload
   582  	chunk := testingc.FixtureChunk("7000")
   583  
   584  	invalidSigner := cryptomock.New(cryptomock.WithSignFunc(func([]byte) ([]byte, error) {
   585  		return []byte{1}, nil
   586  	}))
   587  
   588  	// create a pivot node and a mocked closest node
   589  	pivotPeer := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000")
   590  	closestPeer := swarm.MustParseHexAddress("6000000000000000000000000000000000000000000000000000000000000000")
   591  
   592  	// Create the closest peer
   593  	psClosestPeer, _, _ := createPushSyncNode(t, closestPeer, defaultPrices, nil, nil, invalidSigner, mock.WithClosestPeerErr(topology.ErrWantSelf))
   594  
   595  	closestRecorder := streamtest.New(streamtest.WithProtocols(psClosestPeer.Protocol()), streamtest.WithBaseAddr(pivotPeer))
   596  
   597  	// creating the pivot peer who will act as a forwarder node with a higher price (17)
   598  	psPivot, _, _ := createPushSyncNode(t, pivotPeer, defaultPrices, closestRecorder, nil, invalidSigner, mock.WithPeers(closestPeer))
   599  
   600  	_, err := psPivot.PushChunkToClosest(context.Background(), chunk)
   601  	if !errors.Is(err, topology.ErrWantSelf) { // because the receipt are invalid, the node will eventually return it self
   602  		t.Fatal(err)
   603  	}
   604  }
   605  
   606  func TestMultiplePushesAsForwarder(t *testing.T) {
   607  	t.Parallel()
   608  
   609  	// chunk data to upload
   610  	chunk := testingc.FixtureChunk("7000")
   611  
   612  	// create a pivot node and a mocked closest node
   613  	pivotNode := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000") // base is 0000
   614  
   615  	peer1 := swarm.MustParseHexAddress("5000000000000000000000000000000000000000000000000000000000000000")
   616  	peer2 := swarm.MustParseHexAddress("4000000000000000000000000000000000000000000000000000000000000000")
   617  	peer3 := swarm.MustParseHexAddress("3000000000000000000000000000000000000000000000000000000000000000")
   618  
   619  	// peer is the node responding to the chunk receipt message
   620  	// mock should return ErrWantSelf since there's no one to forward to
   621  	psPeer1, storerPeer1, _ := createPushSyncNode(t, peer1, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   622  	psPeer2, storerPeer2, _ := createPushSyncNode(t, peer2, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   623  	psPeer3, storerPeer3, _ := createPushSyncNode(t, peer3, defaultPrices, nil, nil, defaultSigner(chunk), mock.WithClosestPeerErr(topology.ErrWantSelf))
   624  
   625  	recorder := streamtest.New(
   626  		streamtest.WithPeerProtocols(
   627  			map[string]p2p.ProtocolSpec{
   628  				peer1.String(): psPeer1.Protocol(),
   629  				peer2.String(): psPeer2.Protocol(),
   630  				peer3.String(): psPeer3.Protocol(),
   631  			},
   632  		),
   633  		streamtest.WithBaseAddr(pivotNode),
   634  	)
   635  
   636  	psPivot, _, _ := createPushSyncNode(t, pivotNode, defaultPrices, recorder, nil, defaultSigner(chunk), mock.WithPeers(peer1, peer2, peer3))
   637  
   638  	receipt, err := psPivot.PushChunkToClosest(context.Background(), chunk)
   639  	if err != nil {
   640  		t.Fatal(err)
   641  	}
   642  
   643  	if !chunk.Address().Equal(receipt.Address) {
   644  		t.Fatal("invalid receipt")
   645  	}
   646  
   647  	waitOnRecordAndTest(t, peer1, recorder, chunk.Address(), chunk.Data())
   648  	waitOnRecordAndTest(t, peer1, recorder, chunk.Address(), nil)
   649  	waitOnRecordAndTest(t, peer2, recorder, chunk.Address(), chunk.Data())
   650  	waitOnRecordAndTest(t, peer2, recorder, chunk.Address(), nil)
   651  	waitOnRecordAndTest(t, peer3, recorder, chunk.Address(), chunk.Data())
   652  	waitOnRecordAndTest(t, peer3, recorder, chunk.Address(), nil)
   653  
   654  	want := true
   655  
   656  	if got := storerPeer1.hasChunk(t, chunk.Address()); got != want {
   657  		t.Fatalf("got %v, want %v", got, want)
   658  	}
   659  
   660  	if got := storerPeer2.hasChunk(t, chunk.Address()); got != want {
   661  		t.Fatalf("got %v, want %v", got, want)
   662  	}
   663  
   664  	if got := storerPeer3.hasChunk(t, chunk.Address()); got != want {
   665  		t.Fatalf("got %v, want %v", got, want)
   666  	}
   667  }
   668  
   669  type testStorer struct {
   670  	chunksMu       sync.Mutex
   671  	chunksPut      map[string]swarm.Chunk
   672  	chunksReported map[string]int
   673  }
   674  
   675  func (ts *testStorer) ReservePutter() storage.Putter {
   676  	return storage.PutterFunc(
   677  		func(ctx context.Context, chunk swarm.Chunk) error {
   678  			ts.chunksMu.Lock()
   679  			defer ts.chunksMu.Unlock()
   680  			ts.chunksPut[chunk.Address().ByteString()] = chunk
   681  			return nil
   682  		},
   683  	)
   684  }
   685  
   686  func (ts *testStorer) Report(ctx context.Context, chunk swarm.Chunk, state storage.ChunkState) error {
   687  	if state != storage.ChunkSent {
   688  		return errors.New("incorrect state")
   689  	}
   690  
   691  	ts.chunksMu.Lock()
   692  	defer ts.chunksMu.Unlock()
   693  
   694  	count, exists := ts.chunksReported[chunk.Address().ByteString()]
   695  	if exists {
   696  		count++
   697  		ts.chunksReported[chunk.Address().ByteString()] = count
   698  		return nil
   699  	}
   700  
   701  	ts.chunksReported[chunk.Address().ByteString()] = 1
   702  
   703  	return nil
   704  }
   705  
   706  func (ts *testStorer) IsWithinStorageRadius(address swarm.Address) bool { return true }
   707  
   708  func (ts *testStorer) StorageRadius() uint8 { return 0 }
   709  
   710  func (ts *testStorer) hasChunk(t *testing.T, address swarm.Address) bool {
   711  	t.Helper()
   712  
   713  	ts.chunksMu.Lock()
   714  	defer ts.chunksMu.Unlock()
   715  
   716  	_, found := ts.chunksPut[address.ByteString()]
   717  	return found
   718  }
   719  
   720  func (ts *testStorer) hasReported(t *testing.T, address swarm.Address) (bool, int) {
   721  	t.Helper()
   722  
   723  	ts.chunksMu.Lock()
   724  	defer ts.chunksMu.Unlock()
   725  
   726  	count, found := ts.chunksReported[address.ByteString()]
   727  	return found, count
   728  }
   729  
   730  func createPushSyncNode(
   731  	t *testing.T,
   732  	addr swarm.Address,
   733  	prices pricerParameters,
   734  	recorder *streamtest.Recorder,
   735  	unwrap func(swarm.Chunk),
   736  	signer crypto.Signer,
   737  	mockOpts ...mock.Option,
   738  ) (*pushsync.PushSync, *testStorer, accounting.Interface) {
   739  	t.Helper()
   740  	mockAccounting := accountingmock.NewAccounting()
   741  	ps, mstorer := createPushSyncNodeWithAccounting(t, addr, prices, recorder, unwrap, signer, mockAccounting, log.Noop, mockOpts...)
   742  	return ps, mstorer, mockAccounting
   743  }
   744  
   745  func createPushSyncNodeWithRadius(
   746  	t *testing.T,
   747  	addr swarm.Address,
   748  	prices pricerParameters,
   749  	recorder *streamtest.Recorder,
   750  	unwrap func(swarm.Chunk),
   751  	signer crypto.Signer,
   752  	radius uint8,
   753  	mockOpts ...mock.Option,
   754  ) (*pushsync.PushSync, *testStorer) {
   755  	t.Helper()
   756  	storer := &testStorer{
   757  		chunksPut:      make(map[string]swarm.Chunk),
   758  		chunksReported: make(map[string]int),
   759  	}
   760  
   761  	mockTopology := mock.NewTopologyDriver(mockOpts...)
   762  	mockPricer := pricermock.NewMockService(prices.price, prices.peerPrice)
   763  
   764  	recorderDisconnecter := streamtest.NewRecorderDisconnecter(recorder)
   765  	if unwrap == nil {
   766  		unwrap = func(swarm.Chunk) {}
   767  	}
   768  
   769  	validStamp := func(ch swarm.Chunk) (swarm.Chunk, error) {
   770  		return ch, nil
   771  	}
   772  
   773  	radiusFunc := func() (uint8, error) { return radius, nil }
   774  
   775  	ps := pushsync.New(addr, 1, blockHash.Bytes(), recorderDisconnecter, storer, radiusFunc, mockTopology, true, unwrap, validStamp, log.Noop, accountingmock.NewAccounting(), mockPricer, signer, nil, -1)
   776  	t.Cleanup(func() { ps.Close() })
   777  
   778  	return ps, storer
   779  }
   780  
   781  func createPushSyncNodeWithAccounting(
   782  	t *testing.T,
   783  	addr swarm.Address,
   784  	prices pricerParameters,
   785  	recorder *streamtest.Recorder,
   786  	unwrap func(swarm.Chunk),
   787  	signer crypto.Signer,
   788  	acct accounting.Interface,
   789  	logger log.Logger,
   790  	mockOpts ...mock.Option,
   791  ) (*pushsync.PushSync, *testStorer) {
   792  	t.Helper()
   793  	storer := &testStorer{
   794  		chunksPut:      make(map[string]swarm.Chunk),
   795  		chunksReported: make(map[string]int),
   796  	}
   797  
   798  	mockTopology := mock.NewTopologyDriver(mockOpts...)
   799  	mockPricer := pricermock.NewMockService(prices.price, prices.peerPrice)
   800  
   801  	recorderDisconnecter := streamtest.NewRecorderDisconnecter(recorder)
   802  	if unwrap == nil {
   803  		unwrap = func(swarm.Chunk) {}
   804  	}
   805  
   806  	validStamp := func(ch swarm.Chunk) (swarm.Chunk, error) {
   807  		return ch, nil
   808  	}
   809  
   810  	radiusFunc := func() (uint8, error) { return 0, nil }
   811  
   812  	ps := pushsync.New(addr, 1, blockHash.Bytes(), recorderDisconnecter, storer, radiusFunc, mockTopology, true, unwrap, validStamp, logger, acct, mockPricer, signer, nil, -1)
   813  	t.Cleanup(func() { ps.Close() })
   814  
   815  	return ps, storer
   816  }
   817  
   818  func waitOnRecordAndTest(t *testing.T, peer swarm.Address, recorder *streamtest.Recorder, add swarm.Address, data []byte) {
   819  	t.Helper()
   820  	records := recorder.WaitRecords(t, peer, pushsync.ProtocolName, pushsync.ProtocolVersion, pushsync.StreamName, 1, 5)
   821  
   822  	if data != nil {
   823  		messages, err := protobuf.ReadMessages(
   824  			bytes.NewReader(records[0].In()),
   825  			func() protobuf.Message { return new(pb.Delivery) },
   826  		)
   827  		if err != nil {
   828  			t.Fatal(err)
   829  		}
   830  		if messages == nil {
   831  			t.Fatal("nil rcvd. for message")
   832  		}
   833  		if len(messages) > 1 {
   834  			t.Fatal("too many messages")
   835  		}
   836  		delivery := messages[0].(*pb.Delivery)
   837  
   838  		if !bytes.Equal(delivery.Address, add.Bytes()) {
   839  			t.Fatalf("chunk address mismatch")
   840  		}
   841  
   842  		if !bytes.Equal(delivery.Data, data) {
   843  			t.Fatalf("chunk data mismatch")
   844  		}
   845  	} else {
   846  		messages, err := protobuf.ReadMessages(
   847  			bytes.NewReader(records[0].In()),
   848  			func() protobuf.Message { return new(pb.Receipt) },
   849  		)
   850  		if err != nil {
   851  			t.Fatal(err)
   852  		}
   853  		if messages == nil {
   854  			t.Fatal("nil rcvd. for message")
   855  		}
   856  		if len(messages) > 1 {
   857  			t.Fatal("too many messages")
   858  		}
   859  		receipt := messages[0].(*pb.Receipt)
   860  		receiptAddress := swarm.NewAddress(receipt.Address)
   861  
   862  		if !receiptAddress.Equal(add) {
   863  			t.Fatalf("receipt address mismatch")
   864  		}
   865  	}
   866  }
   867  
   868  func chanFunc(c chan<- struct{}) func(swarm.Chunk) {
   869  	return func(_ swarm.Chunk) {
   870  		c <- struct{}{}
   871  	}
   872  }