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 }