github.com/status-im/status-go@v1.1.0/services/wallet/transfer/commands_sequential_test.go (about) 1 package transfer 2 3 import ( 4 "context" 5 "fmt" 6 "math/big" 7 "sort" 8 "strings" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/golang/mock/gomock" 14 "github.com/pkg/errors" 15 "github.com/stretchr/testify/mock" 16 "golang.org/x/exp/slices" // since 1.21, this is in the standard library 17 18 "github.com/stretchr/testify/require" 19 20 "github.com/ethereum/go-ethereum" 21 "github.com/ethereum/go-ethereum/accounts/abi" 22 "github.com/ethereum/go-ethereum/common" 23 "github.com/ethereum/go-ethereum/core/types" 24 "github.com/ethereum/go-ethereum/event" 25 "github.com/ethereum/go-ethereum/rpc" 26 "github.com/status-im/status-go/appdatabase" 27 "github.com/status-im/status-go/contracts" 28 "github.com/status-im/status-go/contracts/balancechecker" 29 "github.com/status-im/status-go/contracts/ethscan" 30 "github.com/status-im/status-go/contracts/ierc20" 31 ethtypes "github.com/status-im/status-go/eth-node/types" 32 "github.com/status-im/status-go/rpc/chain" 33 mock_client "github.com/status-im/status-go/rpc/chain/mock/client" 34 mock_rpcclient "github.com/status-im/status-go/rpc/mock/client" 35 "github.com/status-im/status-go/server" 36 "github.com/status-im/status-go/services/wallet/async" 37 "github.com/status-im/status-go/services/wallet/balance" 38 "github.com/status-im/status-go/services/wallet/blockchainstate" 39 "github.com/status-im/status-go/services/wallet/community" 40 "github.com/status-im/status-go/t/helpers" 41 "github.com/status-im/status-go/t/utils" 42 43 "github.com/status-im/status-go/multiaccounts/accounts" 44 multicommon "github.com/status-im/status-go/multiaccounts/common" 45 "github.com/status-im/status-go/params" 46 statusRpc "github.com/status-im/status-go/rpc" 47 "github.com/status-im/status-go/rpc/network" 48 walletcommon "github.com/status-im/status-go/services/wallet/common" 49 "github.com/status-im/status-go/services/wallet/token" 50 "github.com/status-im/status-go/transactions" 51 "github.com/status-im/status-go/walletdatabase" 52 ) 53 54 type TestClient struct { 55 t *testing.T 56 // [][block, newBalance, nonceDiff] 57 balances map[common.Address][][]int 58 outgoingERC20Transfers map[common.Address][]testERC20Transfer 59 incomingERC20Transfers map[common.Address][]testERC20Transfer 60 outgoingERC1155SingleTransfers map[common.Address][]testERC20Transfer 61 incomingERC1155SingleTransfers map[common.Address][]testERC20Transfer 62 balanceHistory map[common.Address]map[uint64]*big.Int 63 tokenBalanceHistory map[common.Address]map[common.Address]map[uint64]*big.Int 64 nonceHistory map[common.Address]map[uint64]uint64 65 traceAPICalls bool 66 printPreparedData bool 67 rw sync.RWMutex 68 callsCounter map[string]int 69 currentBlock uint64 70 limiter chain.RequestLimiter 71 tag string 72 groupTag string 73 } 74 75 var countAndlog = func(tc *TestClient, method string, params ...interface{}) error { 76 tc.incCounter(method) 77 if tc.traceAPICalls { 78 if len(params) > 0 { 79 tc.t.Log(method, params) 80 } else { 81 tc.t.Log(method) 82 } 83 } 84 85 return nil 86 } 87 88 func (tc *TestClient) countAndlog(method string, params ...interface{}) error { 89 return countAndlog(tc, method, params...) 90 } 91 92 func (tc *TestClient) incCounter(method string) { 93 tc.rw.Lock() 94 defer tc.rw.Unlock() 95 tc.callsCounter[method] = tc.callsCounter[method] + 1 96 } 97 98 func (tc *TestClient) getCounter() int { 99 tc.rw.RLock() 100 defer tc.rw.RUnlock() 101 cnt := 0 102 for _, v := range tc.callsCounter { 103 cnt += v 104 } 105 return cnt 106 } 107 108 func (tc *TestClient) printCounter() { 109 total := tc.getCounter() 110 111 tc.rw.RLock() 112 defer tc.rw.RUnlock() 113 114 tc.t.Log("========================================= Total calls", total) 115 for k, v := range tc.callsCounter { 116 tc.t.Log(k, v) 117 } 118 tc.t.Log("=========================================") 119 } 120 121 func (tc *TestClient) resetCounter() { 122 tc.rw.Lock() 123 defer tc.rw.Unlock() 124 tc.callsCounter = map[string]int{} 125 } 126 127 func (tc *TestClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { 128 if tc.traceAPICalls { 129 tc.t.Log("BatchCallContext") 130 } 131 return nil 132 } 133 134 func (tc *TestClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { 135 err := tc.countAndlog("HeaderByHash") 136 if err != nil { 137 return nil, err 138 } 139 return nil, nil 140 } 141 142 func (tc *TestClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { 143 err := tc.countAndlog("BlockByHash") 144 if err != nil { 145 return nil, err 146 } 147 return nil, nil 148 } 149 150 func (tc *TestClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { 151 err := tc.countAndlog("BlockByNumber") 152 if err != nil { 153 return nil, err 154 } 155 return nil, nil 156 } 157 158 func (tc *TestClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { 159 nonce := tc.nonceHistory[account][blockNumber.Uint64()] 160 err := tc.countAndlog("NonceAt", fmt.Sprintf("result: %d", nonce)) 161 if err != nil { 162 return nonce, err 163 } 164 return nonce, nil 165 } 166 167 func (tc *TestClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { 168 err := tc.countAndlog("FilterLogs") 169 if err != nil { 170 return nil, err 171 } 172 173 // We do not verify addresses for now 174 allTransfers := []testERC20Transfer{} 175 signatures := q.Topics[0] 176 erc20TransferSignature := walletcommon.GetEventSignatureHash(walletcommon.Erc20_721TransferEventSignature) 177 erc1155TransferSingleSignature := walletcommon.GetEventSignatureHash(walletcommon.Erc1155TransferSingleEventSignature) 178 179 var address common.Hash 180 for i := 1; i < len(q.Topics); i++ { 181 if len(q.Topics[i]) > 0 { 182 address = q.Topics[i][0] 183 break 184 } 185 } 186 187 if slices.Contains(signatures, erc1155TransferSingleSignature) { 188 from := q.Topics[2] 189 var to []common.Hash 190 if len(q.Topics) > 3 { 191 to = q.Topics[3] 192 } 193 194 if len(to) > 0 { 195 for _, addressHash := range to { 196 address := &common.Address{} 197 address.SetBytes(addressHash.Bytes()) 198 allTransfers = append(allTransfers, tc.incomingERC1155SingleTransfers[*address]...) 199 } 200 } 201 if len(from) > 0 { 202 for _, addressHash := range from { 203 address := &common.Address{} 204 address.SetBytes(addressHash.Bytes()) 205 allTransfers = append(allTransfers, tc.outgoingERC1155SingleTransfers[*address]...) 206 } 207 } 208 } 209 210 if slices.Contains(signatures, erc20TransferSignature) { 211 from := q.Topics[1] 212 to := q.Topics[2] 213 214 if len(to) > 0 { 215 for _, addressHash := range to { 216 address := &common.Address{} 217 address.SetBytes(addressHash.Bytes()) 218 allTransfers = append(allTransfers, tc.incomingERC20Transfers[*address]...) 219 } 220 } 221 222 if len(from) > 0 { 223 for _, addressHash := range from { 224 address := &common.Address{} 225 address.SetBytes(addressHash.Bytes()) 226 allTransfers = append(allTransfers, tc.outgoingERC20Transfers[*address]...) 227 } 228 } 229 } 230 231 logs := []types.Log{} 232 for _, transfer := range allTransfers { 233 if transfer.block.Cmp(q.FromBlock) >= 0 && transfer.block.Cmp(q.ToBlock) <= 0 { 234 log := types.Log{ 235 BlockNumber: transfer.block.Uint64(), 236 BlockHash: common.BigToHash(transfer.block), 237 } 238 239 // Use the address at least in one any(from/to) topic to trick the implementation 240 switch transfer.eventType { 241 case walletcommon.Erc20TransferEventType, walletcommon.Erc721TransferEventType: 242 // To detect properly ERC721, we need a different number of topics. For now we use only ERC20 for testing 243 log.Topics = []common.Hash{walletcommon.GetEventSignatureHash(walletcommon.Erc20_721TransferEventSignature), address, address} 244 case walletcommon.Erc1155TransferSingleEventType: 245 log.Topics = []common.Hash{walletcommon.GetEventSignatureHash(walletcommon.Erc1155TransferSingleEventSignature), address, address, address} 246 log.Data = make([]byte, 2*common.HashLength) 247 case walletcommon.Erc1155TransferBatchEventType: 248 log.Topics = []common.Hash{walletcommon.GetEventSignatureHash(walletcommon.Erc1155TransferBatchEventSignature), address, address, address} 249 } 250 251 logs = append(logs, log) 252 } 253 } 254 255 return logs, nil 256 } 257 258 func (tc *TestClient) getBalance(address common.Address, blockNumber *big.Int) *big.Int { 259 balance := tc.balanceHistory[address][blockNumber.Uint64()] 260 if balance == nil { 261 balance = big.NewInt(0) 262 } 263 264 return balance 265 } 266 267 func (tc *TestClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { 268 balance := tc.getBalance(account, blockNumber) 269 err := tc.countAndlog("BalanceAt", fmt.Sprintf("account: %s, result: %d", account, balance)) 270 if err != nil { 271 return nil, err 272 } 273 274 return balance, nil 275 } 276 277 func (tc *TestClient) tokenBalanceAt(account common.Address, token common.Address, blockNumber *big.Int) *big.Int { 278 balance := tc.tokenBalanceHistory[account][token][blockNumber.Uint64()] 279 if balance == nil { 280 balance = big.NewInt(0) 281 } 282 283 if tc.traceAPICalls { 284 tc.t.Log("tokenBalanceAt", token, blockNumber, "account:", account, "result:", balance) 285 } 286 return balance 287 } 288 289 func (tc *TestClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { 290 if number == nil { 291 number = big.NewInt(int64(tc.currentBlock)) 292 } 293 294 err := tc.countAndlog("HeaderByNumber", fmt.Sprintf("number: %d", number)) 295 if err != nil { 296 return nil, err 297 } 298 299 header := &types.Header{ 300 Number: number, 301 Time: 0, 302 } 303 304 return header, nil 305 } 306 307 func (tc *TestClient) CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error) { 308 err := tc.countAndlog("CallBlockHashByTransaction") 309 if err != nil { 310 return common.Hash{}, err 311 } 312 return common.BigToHash(blockNumber), nil 313 } 314 315 func (tc *TestClient) GetBaseFeeFromBlock(ctx context.Context, blockNumber *big.Int) (string, error) { 316 err := tc.countAndlog("GetBaseFeeFromBlock") 317 if err != nil { 318 return "", err 319 } 320 return "", nil 321 } 322 323 func (tc *TestClient) NetworkID() uint64 { 324 return 777333 325 } 326 327 func (tc *TestClient) ToBigInt() *big.Int { 328 if tc.traceAPICalls { 329 tc.t.Log("ToBigInt") 330 } 331 return nil 332 } 333 334 var ethscanAddress = common.HexToAddress("0x0000000000000000000000000000000000777333") 335 var balanceCheckAddress = common.HexToAddress("0x0000000000000000000000000000000010777333") 336 337 func (tc *TestClient) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { 338 err := tc.countAndlog("CodeAt", fmt.Sprintf("contract: %s, blockNumber: %d", contract, blockNumber)) 339 if err != nil { 340 return nil, err 341 } 342 343 if ethscanAddress == contract || balanceCheckAddress == contract { 344 return []byte{1}, nil 345 } 346 347 return nil, nil 348 } 349 350 func (tc *TestClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { 351 err := tc.countAndlog("CallContract", fmt.Sprintf("call: %v, blockNumber: %d, to: %s", call, blockNumber, call.To)) 352 if err != nil { 353 return nil, err 354 } 355 356 if *call.To == ethscanAddress { 357 parsed, err := abi.JSON(strings.NewReader(ethscan.BalanceScannerABI)) 358 if err != nil { 359 return nil, err 360 } 361 method := parsed.Methods["tokensBalance"] 362 params := call.Data[len(method.ID):] 363 args, err := method.Inputs.Unpack(params) 364 365 if err != nil { 366 tc.t.Log("ERROR on unpacking", err) 367 return nil, err 368 } 369 370 account := args[0].(common.Address) 371 tokens := args[1].([]common.Address) 372 balances := []*big.Int{} 373 for _, token := range tokens { 374 balances = append(balances, tc.tokenBalanceAt(account, token, blockNumber)) 375 } 376 results := []ethscan.BalanceScannerResult{} 377 for _, balance := range balances { 378 results = append(results, ethscan.BalanceScannerResult{ 379 Success: true, 380 Data: balance.Bytes(), 381 }) 382 } 383 384 output, err := method.Outputs.Pack(results) 385 if err != nil { 386 tc.t.Log("ERROR on packing", err) 387 return nil, err 388 } 389 390 return output, nil 391 } 392 393 if *call.To == tokenTXXAddress || *call.To == tokenTXYAddress { 394 parsed, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI)) 395 if err != nil { 396 return nil, err 397 } 398 399 method := parsed.Methods["balanceOf"] 400 params := call.Data[len(method.ID):] 401 args, err := method.Inputs.Unpack(params) 402 403 if err != nil { 404 tc.t.Log("ERROR on unpacking", err) 405 return nil, err 406 } 407 408 account := args[0].(common.Address) 409 410 balance := tc.tokenBalanceAt(account, *call.To, blockNumber) 411 412 output, err := method.Outputs.Pack(balance) 413 if err != nil { 414 tc.t.Log("ERROR on packing ERC20 balance", err) 415 return nil, err 416 } 417 418 return output, nil 419 } 420 421 if *call.To == balanceCheckAddress { 422 parsed, err := abi.JSON(strings.NewReader(balancechecker.BalanceCheckerABI)) 423 if err != nil { 424 return nil, err 425 } 426 427 method := parsed.Methods["balancesHash"] 428 params := call.Data[len(method.ID):] 429 args, err := method.Inputs.Unpack(params) 430 431 if err != nil { 432 tc.t.Log("ERROR on unpacking", err) 433 return nil, err 434 } 435 436 addresses := args[0].([]common.Address) 437 tokens := args[1].([]common.Address) 438 bn := big.NewInt(int64(tc.currentBlock)) 439 hashes := [][32]byte{} 440 441 for _, address := range addresses { 442 balance := tc.getBalance(address, big.NewInt(int64(tc.currentBlock))) 443 balanceBytes := balance.Bytes() 444 for _, token := range tokens { 445 balance := tc.tokenBalanceAt(address, token, bn) 446 balanceBytes = append(balanceBytes, balance.Bytes()...) 447 } 448 449 hash := [32]byte{} 450 for i, b := range ethtypes.BytesToHash(balanceBytes).Bytes() { 451 hash[i] = b 452 } 453 454 hashes = append(hashes, hash) 455 } 456 457 output, err := method.Outputs.Pack(bn, hashes) 458 if err != nil { 459 tc.t.Log("ERROR on packing", err) 460 return nil, err 461 } 462 463 return output, nil 464 } 465 466 return nil, nil 467 } 468 469 func (tc *TestClient) prepareBalanceHistory(toBlock int) { 470 tc.balanceHistory = map[common.Address]map[uint64]*big.Int{} 471 tc.nonceHistory = map[common.Address]map[uint64]uint64{} 472 473 for address, balances := range tc.balances { 474 var currentBlock, currentBalance, currentNonce int 475 476 tc.balanceHistory[address] = map[uint64]*big.Int{} 477 tc.nonceHistory[address] = map[uint64]uint64{} 478 479 if len(balances) == 0 { 480 balances = append(balances, []int{toBlock + 1, 0, 0}) 481 } else { 482 lastBlock := balances[len(balances)-1] 483 balances = append(balances, []int{toBlock + 1, lastBlock[1], 0}) 484 } 485 for _, change := range balances { 486 for blockN := currentBlock; blockN < change[0]; blockN++ { 487 tc.balanceHistory[address][uint64(blockN)] = big.NewInt(int64(currentBalance)) 488 tc.nonceHistory[address][uint64(blockN)] = uint64(currentNonce) 489 } 490 currentBlock = change[0] 491 currentBalance = change[1] 492 currentNonce += change[2] 493 } 494 } 495 496 if tc.printPreparedData { 497 tc.t.Log("========================================= ETH BALANCES") 498 tc.t.Log(tc.balanceHistory) 499 tc.t.Log(tc.nonceHistory) 500 tc.t.Log(tc.tokenBalanceHistory) 501 tc.t.Log("=========================================") 502 } 503 } 504 505 func (tc *TestClient) prepareTokenBalanceHistory(toBlock int) { 506 transfersPerAddress := map[common.Address]map[common.Address][]testERC20Transfer{} 507 for account, transfers := range tc.outgoingERC20Transfers { 508 if _, ok := transfersPerAddress[account]; !ok { 509 transfersPerAddress[account] = map[common.Address][]testERC20Transfer{} 510 } 511 for _, transfer := range transfers { 512 transfer.amount = new(big.Int).Neg(transfer.amount) 513 transfer.eventType = walletcommon.Erc20TransferEventType 514 transfersPerAddress[account][transfer.address] = append(transfersPerAddress[account][transfer.address], transfer) 515 } 516 } 517 518 for account, transfers := range tc.incomingERC20Transfers { 519 if _, ok := transfersPerAddress[account]; !ok { 520 transfersPerAddress[account] = map[common.Address][]testERC20Transfer{} 521 } 522 for _, transfer := range transfers { 523 transfer.amount = new(big.Int).Neg(transfer.amount) 524 transfer.eventType = walletcommon.Erc20TransferEventType 525 transfersPerAddress[account][transfer.address] = append(transfersPerAddress[account][transfer.address], transfer) 526 } 527 } 528 529 for account, transfers := range tc.outgoingERC1155SingleTransfers { 530 if _, ok := transfersPerAddress[account]; !ok { 531 transfersPerAddress[account] = map[common.Address][]testERC20Transfer{} 532 } 533 for _, transfer := range transfers { 534 transfer.amount = new(big.Int).Neg(transfer.amount) 535 transfer.eventType = walletcommon.Erc1155TransferSingleEventType 536 transfersPerAddress[account][transfer.address] = append(transfersPerAddress[account][transfer.address], transfer) 537 } 538 } 539 540 for account, transfers := range tc.incomingERC1155SingleTransfers { 541 if _, ok := transfersPerAddress[account]; !ok { 542 transfersPerAddress[account] = map[common.Address][]testERC20Transfer{} 543 } 544 for _, transfer := range transfers { 545 transfer.amount = new(big.Int).Neg(transfer.amount) 546 transfer.eventType = walletcommon.Erc1155TransferSingleEventType 547 transfersPerAddress[account][transfer.address] = append(transfersPerAddress[account][transfer.address], transfer) 548 } 549 } 550 551 tc.tokenBalanceHistory = map[common.Address]map[common.Address]map[uint64]*big.Int{} 552 553 for account, transfersPerToken := range transfersPerAddress { 554 tc.tokenBalanceHistory[account] = map[common.Address]map[uint64]*big.Int{} 555 for token, transfers := range transfersPerToken { 556 sort.Slice(transfers, func(i, j int) bool { 557 return transfers[i].block.Cmp(transfers[j].block) < 0 558 }) 559 560 currentBlock := uint64(0) 561 currentBalance := big.NewInt(0) 562 563 tc.tokenBalanceHistory[token] = map[common.Address]map[uint64]*big.Int{} 564 transfers = append(transfers, testERC20Transfer{big.NewInt(int64(toBlock + 1)), token, big.NewInt(0), walletcommon.Erc20TransferEventType}) 565 566 tc.tokenBalanceHistory[account][token] = map[uint64]*big.Int{} 567 for _, transfer := range transfers { 568 for blockN := currentBlock; blockN < transfer.block.Uint64(); blockN++ { 569 tc.tokenBalanceHistory[account][token][blockN] = new(big.Int).Set(currentBalance) 570 } 571 currentBlock = transfer.block.Uint64() 572 currentBalance = new(big.Int).Add(currentBalance, transfer.amount) 573 } 574 } 575 } 576 if tc.printPreparedData { 577 tc.t.Log("========================================= ERC20 BALANCES") 578 tc.t.Log(tc.tokenBalanceHistory) 579 tc.t.Log("=========================================") 580 } 581 } 582 583 func (tc *TestClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { 584 err := tc.countAndlog("CallContext") 585 if err != nil { 586 return err 587 } 588 return nil 589 } 590 591 func (tc *TestClient) GetWalletNotifier() func(chainId uint64, message string) { 592 if tc.traceAPICalls { 593 tc.t.Log("GetWalletNotifier") 594 } 595 return nil 596 } 597 598 func (tc *TestClient) SetWalletNotifier(notifier func(chainId uint64, message string)) { 599 if tc.traceAPICalls { 600 tc.t.Log("SetWalletNotifier") 601 } 602 } 603 604 func (tc *TestClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { 605 err = tc.countAndlog("EstimateGas") 606 if err != nil { 607 return 0, err 608 } 609 return 0, nil 610 } 611 612 func (tc *TestClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { 613 err := tc.countAndlog("PendingCodeAt") 614 if err != nil { 615 return nil, err 616 } 617 return nil, nil 618 } 619 620 func (tc *TestClient) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { 621 err := tc.countAndlog("PendingCallContract") 622 if err != nil { 623 return nil, err 624 } 625 return nil, nil 626 } 627 628 func (tc *TestClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { 629 err := tc.countAndlog("PendingNonceAt") 630 if err != nil { 631 return 0, err 632 } 633 return 0, nil 634 } 635 636 func (tc *TestClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { 637 err := tc.countAndlog("SuggestGasPrice") 638 if err != nil { 639 return nil, err 640 } 641 return nil, nil 642 } 643 644 func (tc *TestClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { 645 err := tc.countAndlog("SendTransaction") 646 if err != nil { 647 return err 648 } 649 return nil 650 } 651 652 func (tc *TestClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { 653 err := tc.countAndlog("SuggestGasTipCap") 654 if err != nil { 655 return nil, err 656 } 657 return nil, nil 658 } 659 660 func (tc *TestClient) BatchCallContextIgnoringLocalHandlers(ctx context.Context, b []rpc.BatchElem) error { 661 err := tc.countAndlog("BatchCallContextIgnoringLocalHandlers") 662 if err != nil { 663 return err 664 } 665 return nil 666 } 667 668 func (tc *TestClient) CallContextIgnoringLocalHandlers(ctx context.Context, result interface{}, method string, args ...interface{}) error { 669 err := tc.countAndlog("CallContextIgnoringLocalHandlers") 670 if err != nil { 671 return err 672 } 673 return nil 674 } 675 676 func (tc *TestClient) CallRaw(data string) string { 677 _ = tc.countAndlog("CallRaw") 678 return "" 679 } 680 681 func (tc *TestClient) GetChainID() *big.Int { 682 return big.NewInt(1) 683 } 684 685 func (tc *TestClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { 686 err := tc.countAndlog("SubscribeFilterLogs") 687 if err != nil { 688 return nil, err 689 } 690 return nil, nil 691 } 692 693 func (tc *TestClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { 694 err := tc.countAndlog("TransactionReceipt") 695 if err != nil { 696 return nil, err 697 } 698 return nil, nil 699 } 700 701 func (tc *TestClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { 702 err := tc.countAndlog("TransactionByHash") 703 if err != nil { 704 return nil, false, err 705 } 706 return nil, false, nil 707 } 708 709 func (tc *TestClient) BlockNumber(ctx context.Context) (uint64, error) { 710 err := tc.countAndlog("BlockNumber") 711 if err != nil { 712 return 0, err 713 } 714 return 0, nil 715 } 716 func (tc *TestClient) SetIsConnected(value bool) { 717 if tc.traceAPICalls { 718 tc.t.Log("SetIsConnected") 719 } 720 } 721 722 func (tc *TestClient) IsConnected() bool { 723 if tc.traceAPICalls { 724 tc.t.Log("GetIsConnected") 725 } 726 727 return true 728 } 729 730 func (tc *TestClient) GetLimiter() chain.RequestLimiter { 731 return tc.limiter 732 } 733 734 func (tc *TestClient) SetLimiter(limiter chain.RequestLimiter) { 735 tc.limiter = limiter 736 } 737 738 type testERC20Transfer struct { 739 block *big.Int 740 address common.Address 741 amount *big.Int 742 eventType walletcommon.EventType 743 } 744 745 type findBlockCase struct { 746 balanceChanges [][]int 747 ERC20BalanceChanges [][]int 748 fromBlock int64 749 toBlock int64 750 rangeSize int 751 expectedBlocksFound int 752 outgoingERC20Transfers []testERC20Transfer 753 incomingERC20Transfers []testERC20Transfer 754 outgoingERC1155SingleTransfers []testERC20Transfer 755 incomingERC1155SingleTransfers []testERC20Transfer 756 label string 757 expectedCalls map[string]int 758 } 759 760 func transferInEachBlock() [][]int { 761 res := [][]int{} 762 763 for i := 1; i < 101; i++ { 764 res = append(res, []int{i, i, i}) 765 } 766 767 return res 768 } 769 770 func getCases() []findBlockCase { 771 cases := []findBlockCase{} 772 case1 := findBlockCase{ 773 balanceChanges: [][]int{ 774 {5, 1, 0}, 775 {20, 2, 0}, 776 {45, 1, 1}, 777 {46, 50, 0}, 778 {75, 0, 1}, 779 }, 780 outgoingERC20Transfers: []testERC20Transfer{ 781 {big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}, 782 }, 783 toBlock: 100, 784 expectedBlocksFound: 6, 785 expectedCalls: map[string]int{ 786 "FilterLogs": 15, 787 "HeaderByNumber": 5, 788 }, 789 } 790 791 case100transfers := findBlockCase{ 792 balanceChanges: transferInEachBlock(), 793 toBlock: 100, 794 expectedBlocksFound: 100, 795 expectedCalls: map[string]int{ 796 "BalanceAt": 101, 797 "NonceAt": 0, 798 "FilterLogs": 15, 799 "HeaderByNumber": 100, 800 }, 801 } 802 803 case3 := findBlockCase{ 804 balanceChanges: [][]int{ 805 {1, 1, 1}, 806 {2, 2, 2}, 807 {45, 1, 1}, 808 {46, 50, 0}, 809 {75, 0, 1}, 810 }, 811 toBlock: 100, 812 expectedBlocksFound: 5, 813 } 814 case4 := findBlockCase{ 815 balanceChanges: [][]int{ 816 {20, 1, 0}, 817 }, 818 toBlock: 100, 819 fromBlock: 10, 820 expectedBlocksFound: 1, 821 label: "single block", 822 } 823 824 case5 := findBlockCase{ 825 balanceChanges: [][]int{}, 826 toBlock: 100, 827 fromBlock: 20, 828 expectedBlocksFound: 0, 829 } 830 831 case6 := findBlockCase{ 832 balanceChanges: [][]int{ 833 {20, 1, 0}, 834 {45, 1, 1}, 835 }, 836 toBlock: 100, 837 fromBlock: 30, 838 expectedBlocksFound: 1, 839 rangeSize: 20, 840 label: "single block in range", 841 } 842 843 case7emptyHistoryWithOneERC20Transfer := findBlockCase{ 844 balanceChanges: [][]int{}, 845 toBlock: 100, 846 rangeSize: 20, 847 expectedBlocksFound: 1, 848 incomingERC20Transfers: []testERC20Transfer{ 849 {big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}, 850 }, 851 } 852 853 case8emptyHistoryWithERC20Transfers := findBlockCase{ 854 balanceChanges: [][]int{}, 855 toBlock: 100, 856 rangeSize: 20, 857 expectedBlocksFound: 2, 858 incomingERC20Transfers: []testERC20Transfer{ 859 // edge case when a regular scan will find transfer at 80, 860 // but erc20 tail scan should only find transfer at block 6 861 {big.NewInt(80), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}, 862 {big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}, 863 }, 864 expectedCalls: map[string]int{ 865 "FilterLogs": 5, 866 "CallContract": 3, 867 }, 868 } 869 870 case9emptyHistoryWithERC20Transfers := findBlockCase{ 871 balanceChanges: [][]int{}, 872 toBlock: 100, 873 rangeSize: 20, 874 // we expect only a single eth_getLogs to be executed here for both erc20 transfers, 875 // thus only 2 blocks found 876 expectedBlocksFound: 2, 877 incomingERC20Transfers: []testERC20Transfer{ 878 {big.NewInt(7), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}, 879 {big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}, 880 }, 881 expectedCalls: map[string]int{ 882 "FilterLogs": 5, 883 }, 884 } 885 886 case10 := findBlockCase{ 887 balanceChanges: [][]int{}, 888 toBlock: 100, 889 fromBlock: 99, 890 expectedBlocksFound: 0, 891 label: "single block range, no transactions", 892 expectedCalls: map[string]int{ 893 // only two requests to check the range for incoming ERC20 894 "FilterLogs": 3, 895 // no contract calls as ERC20 is not checked 896 "CallContract": 0, 897 }, 898 } 899 900 case11IncomingERC1155SingleTransfers := findBlockCase{ 901 balanceChanges: [][]int{}, 902 toBlock: 100, 903 rangeSize: 20, 904 // we expect only a single eth_getLogs to be executed here for both erc20 transfers, 905 // thus only 2 blocks found 906 expectedBlocksFound: 2, 907 incomingERC1155SingleTransfers: []testERC20Transfer{ 908 {big.NewInt(7), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType}, 909 {big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType}, 910 }, 911 expectedCalls: map[string]int{ 912 "FilterLogs": 5, 913 "CallContract": 5, 914 }, 915 } 916 917 case12OutgoingERC1155SingleTransfers := findBlockCase{ 918 balanceChanges: [][]int{ 919 {6, 1, 0}, 920 }, 921 toBlock: 100, 922 rangeSize: 20, 923 expectedBlocksFound: 3, 924 outgoingERC1155SingleTransfers: []testERC20Transfer{ 925 {big.NewInt(80), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType}, 926 {big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType}, 927 }, 928 expectedCalls: map[string]int{ 929 "FilterLogs": 15, // 3 for each range 930 }, 931 } 932 933 case13outgoingERC20ERC1155SingleTransfers := findBlockCase{ 934 balanceChanges: [][]int{ 935 {63, 1, 0}, 936 }, 937 toBlock: 100, 938 rangeSize: 20, 939 expectedBlocksFound: 3, 940 outgoingERC1155SingleTransfers: []testERC20Transfer{ 941 {big.NewInt(80), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType}, 942 }, 943 outgoingERC20Transfers: []testERC20Transfer{ 944 {big.NewInt(63), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}, 945 }, 946 expectedCalls: map[string]int{ 947 "FilterLogs": 6, // 3 for each range, 0 for tail check becauseERC20ScanByBalance returns no ranges 948 }, 949 } 950 951 case14outgoingERC20ERC1155SingleTransfersMoreFilterLogs := findBlockCase{ 952 balanceChanges: [][]int{ 953 {61, 1, 0}, 954 }, 955 toBlock: 100, 956 rangeSize: 20, 957 expectedBlocksFound: 3, 958 outgoingERC1155SingleTransfers: []testERC20Transfer{ 959 {big.NewInt(80), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType}, 960 }, 961 outgoingERC20Transfers: []testERC20Transfer{ 962 {big.NewInt(61), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}, 963 }, 964 expectedCalls: map[string]int{ 965 "FilterLogs": 9, // 3 for each range of [40-100], 0 for tail check because ERC20ScanByBalance returns no ranges 966 }, 967 label: "outgoing ERC20 and ERC1155 transfers but more FilterLogs calls because startFromBlock is not detected at range [60-80] as it is in the first subrange", 968 } 969 970 case15incomingERC20outgoingERC1155SingleTransfers := findBlockCase{ 971 balanceChanges: [][]int{ 972 {85, 1, 0}, 973 }, 974 toBlock: 100, 975 rangeSize: 20, 976 expectedBlocksFound: 2, 977 outgoingERC1155SingleTransfers: []testERC20Transfer{ 978 {big.NewInt(85), tokenTXYAddress, big.NewInt(1), walletcommon.Erc1155TransferSingleEventType}, 979 }, 980 incomingERC20Transfers: []testERC20Transfer{ 981 {big.NewInt(88), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}, 982 }, 983 expectedCalls: map[string]int{ 984 "FilterLogs": 3, // 3 for each range of [40-100], 0 for tail check because ERC20ScanByBalance returns no ranges 985 }, 986 label: "incoming ERC20 and outgoing ERC1155 transfers are fetched with same topic", 987 } 988 989 case16 := findBlockCase{ 990 balanceChanges: [][]int{ 991 {75, 0, 1}, 992 }, 993 outgoingERC20Transfers: []testERC20Transfer{ 994 {big.NewInt(80), tokenTXXAddress, big.NewInt(4), walletcommon.Erc20TransferEventType}, 995 }, 996 toBlock: 100, 997 rangeSize: 20, 998 expectedBlocksFound: 3, // ideally we should find 2 blocks, but we will find 3 and this test shows that we are ok with that 999 label: `duplicate blocks detected but we wont fix it because we want to save requests on the edges of the ranges, 1000 taking balance and nonce from cache while ETH and tokens ranges searching are tightly coupled`, 1001 } 1002 1003 cases = append(cases, case1) 1004 cases = append(cases, case100transfers) 1005 cases = append(cases, case3) 1006 cases = append(cases, case4) 1007 cases = append(cases, case5) 1008 1009 cases = append(cases, case6) 1010 cases = append(cases, case7emptyHistoryWithOneERC20Transfer) 1011 cases = append(cases, case8emptyHistoryWithERC20Transfers) 1012 cases = append(cases, case9emptyHistoryWithERC20Transfers) 1013 cases = append(cases, case10) 1014 cases = append(cases, case11IncomingERC1155SingleTransfers) 1015 cases = append(cases, case12OutgoingERC1155SingleTransfers) 1016 cases = append(cases, case13outgoingERC20ERC1155SingleTransfers) 1017 cases = append(cases, case14outgoingERC20ERC1155SingleTransfersMoreFilterLogs) 1018 cases = append(cases, case15incomingERC20outgoingERC1155SingleTransfers) 1019 cases = append(cases, case16) 1020 1021 //cases = append([]findBlockCase{}, case10) 1022 1023 return cases 1024 } 1025 1026 var tokenTXXAddress = common.HexToAddress("0x53211") 1027 var tokenTXYAddress = common.HexToAddress("0x73211") 1028 1029 func setupFindBlocksCommand(t *testing.T, accountAddress common.Address, fromBlock, toBlock *big.Int, rangeSize int, balances map[common.Address][][]int, outgoingERC20Transfers, incomingERC20Transfers, outgoingERC1155SingleTransfers, incomingERC1155SingleTransfers map[common.Address][]testERC20Transfer) (*findBlocksCommand, *TestClient, chan []*DBHeader, *BlockRangeSequentialDAO) { 1030 appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{}) 1031 require.NoError(t, err) 1032 1033 db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) 1034 require.NoError(t, err) 1035 1036 mediaServer, err := server.NewMediaServer(appdb, nil, nil, db) 1037 require.NoError(t, err) 1038 1039 wdb := NewDB(db) 1040 tc := &TestClient{ 1041 t: t, 1042 balances: balances, 1043 outgoingERC20Transfers: outgoingERC20Transfers, 1044 incomingERC20Transfers: incomingERC20Transfers, 1045 outgoingERC1155SingleTransfers: outgoingERC1155SingleTransfers, 1046 incomingERC1155SingleTransfers: incomingERC1155SingleTransfers, 1047 callsCounter: map[string]int{}, 1048 } 1049 // tc.traceAPICalls = true 1050 // tc.printPreparedData = true 1051 tc.prepareBalanceHistory(100) 1052 tc.prepareTokenBalanceHistory(100) 1053 blockChannel := make(chan []*DBHeader, 100) 1054 1055 // Reimplement the common function that is called from every method to check for the limit 1056 countAndlog = func(tc *TestClient, method string, params ...interface{}) error { 1057 if tc.GetLimiter() != nil { 1058 if allow, _ := tc.GetLimiter().Allow(tc.tag); !allow { 1059 t.Log("ERROR: requests over limit") 1060 return chain.ErrRequestsOverLimit 1061 } 1062 if allow, _ := tc.GetLimiter().Allow(tc.groupTag); !allow { 1063 t.Log("ERROR: requests over limit for group tag") 1064 return chain.ErrRequestsOverLimit 1065 } 1066 } 1067 1068 tc.incCounter(method) 1069 if tc.traceAPICalls { 1070 if len(params) > 0 { 1071 tc.t.Log(method, params) 1072 } else { 1073 tc.t.Log(method) 1074 } 1075 } 1076 1077 return nil 1078 } 1079 client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) 1080 client.SetClient(tc.NetworkID(), tc) 1081 tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) 1082 tokenManager.SetTokens([]*token.Token{ 1083 { 1084 Address: tokenTXXAddress, 1085 Symbol: "TXX", 1086 Decimals: 18, 1087 ChainID: tc.NetworkID(), 1088 Name: "Test Token 1", 1089 Verified: true, 1090 }, 1091 { 1092 Address: tokenTXYAddress, 1093 Symbol: "TXY", 1094 Decimals: 18, 1095 ChainID: tc.NetworkID(), 1096 Name: "Test Token 2", 1097 Verified: true, 1098 }, 1099 }) 1100 accDB, err := accounts.NewDB(appdb) 1101 require.NoError(t, err) 1102 blockRangeDAO := &BlockRangeSequentialDAO{wdb.client} 1103 fbc := &findBlocksCommand{ 1104 accounts: []common.Address{accountAddress}, 1105 db: wdb, 1106 blockRangeDAO: blockRangeDAO, 1107 accountsDB: accDB, 1108 chainClient: tc, 1109 balanceCacher: balance.NewCacherWithTTL(5 * time.Minute), 1110 feed: &event.Feed{}, 1111 noLimit: false, 1112 fromBlockNumber: fromBlock, 1113 toBlockNumber: toBlock, 1114 blocksLoadedCh: blockChannel, 1115 defaultNodeBlockChunkSize: rangeSize, 1116 tokenManager: tokenManager, 1117 } 1118 return fbc, tc, blockChannel, blockRangeDAO 1119 } 1120 1121 func TestFindBlocksCommand(t *testing.T) { 1122 for idx, testCase := range getCases() { 1123 t.Log("case #", idx+1) 1124 1125 accountAddress := common.HexToAddress("0x1234") 1126 rangeSize := 20 1127 if testCase.rangeSize != 0 { 1128 rangeSize = testCase.rangeSize 1129 } 1130 1131 balances := map[common.Address][][]int{accountAddress: testCase.balanceChanges} 1132 outgoingERC20Transfers := map[common.Address][]testERC20Transfer{accountAddress: testCase.outgoingERC20Transfers} 1133 incomingERC20Transfers := map[common.Address][]testERC20Transfer{accountAddress: testCase.incomingERC20Transfers} 1134 outgoingERC1155SingleTransfers := map[common.Address][]testERC20Transfer{accountAddress: testCase.outgoingERC1155SingleTransfers} 1135 incomingERC1155SingleTransfers := map[common.Address][]testERC20Transfer{accountAddress: testCase.incomingERC1155SingleTransfers} 1136 1137 fbc, tc, blockChannel, blockRangeDAO := setupFindBlocksCommand(t, accountAddress, big.NewInt(testCase.fromBlock), big.NewInt(testCase.toBlock), rangeSize, balances, outgoingERC20Transfers, incomingERC20Transfers, outgoingERC1155SingleTransfers, incomingERC1155SingleTransfers) 1138 ctx := context.Background() 1139 group := async.NewGroup(ctx) 1140 group.Add(fbc.Command()) 1141 1142 foundBlocks := []*DBHeader{} 1143 select { 1144 case <-ctx.Done(): 1145 t.Log("ERROR") 1146 case <-group.WaitAsync(): 1147 close(blockChannel) 1148 for { 1149 bloks, ok := <-blockChannel 1150 if !ok { 1151 break 1152 } 1153 foundBlocks = append(foundBlocks, bloks...) 1154 } 1155 1156 numbers := []int64{} 1157 for _, block := range foundBlocks { 1158 numbers = append(numbers, block.Number.Int64()) 1159 } 1160 1161 if tc.traceAPICalls { 1162 tc.printCounter() 1163 } 1164 1165 for name, cnt := range testCase.expectedCalls { 1166 require.Equal(t, cnt, tc.callsCounter[name], "calls to "+name) 1167 } 1168 1169 sort.Slice(numbers, func(i, j int) bool { return numbers[i] < numbers[j] }) 1170 require.Equal(t, testCase.expectedBlocksFound, len(foundBlocks), testCase.label, "found blocks", numbers) 1171 1172 blRange, _, err := blockRangeDAO.getBlockRange(tc.NetworkID(), accountAddress) 1173 require.NoError(t, err) 1174 require.NotNil(t, blRange.eth.FirstKnown) 1175 require.NotNil(t, blRange.tokens.FirstKnown) 1176 if testCase.fromBlock == 0 { 1177 require.Equal(t, 0, blRange.tokens.FirstKnown.Cmp(zero)) 1178 } 1179 } 1180 } 1181 } 1182 1183 func TestFindBlocksCommandWithLimiter(t *testing.T) { 1184 maxRequests := 1 1185 rangeSize := 20 1186 accountAddress := common.HexToAddress("0x1234") 1187 balances := map[common.Address][][]int{accountAddress: {{5, 1, 0}, {20, 2, 0}, {45, 1, 1}, {46, 50, 0}, {75, 0, 1}}} 1188 fbc, tc, blockChannel, _ := setupFindBlocksCommand(t, accountAddress, big.NewInt(0), big.NewInt(20), rangeSize, balances, nil, nil, nil, nil) 1189 1190 limiter := chain.NewRequestLimiter(chain.NewInMemRequestsMapStorage()) 1191 err := limiter.SetLimit(transferHistoryTag, maxRequests, time.Hour) 1192 require.NoError(t, err) 1193 tc.SetLimiter(limiter) 1194 tc.tag = transferHistoryTag 1195 1196 ctx := context.Background() 1197 group := async.NewAtomicGroup(ctx) 1198 group.Add(fbc.Command(1 * time.Millisecond)) 1199 1200 select { 1201 case <-ctx.Done(): 1202 t.Log("ERROR") 1203 case <-group.WaitAsync(): 1204 close(blockChannel) 1205 require.Error(t, chain.ErrRequestsOverLimit, group.Error()) 1206 require.Equal(t, maxRequests, tc.getCounter()) 1207 } 1208 } 1209 1210 func TestFindBlocksCommandWithLimiterTagDifferentThanTransfers(t *testing.T) { 1211 rangeSize := 20 1212 maxRequests := 1 1213 accountAddress := common.HexToAddress("0x1234") 1214 balances := map[common.Address][][]int{accountAddress: {{5, 1, 0}, {20, 2, 0}, {45, 1, 1}, {46, 50, 0}, {75, 0, 1}}} 1215 outgoingERC20Transfers := map[common.Address][]testERC20Transfer{accountAddress: {{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}} 1216 incomingERC20Transfers := map[common.Address][]testERC20Transfer{accountAddress: {{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}} 1217 1218 fbc, tc, blockChannel, _ := setupFindBlocksCommand(t, accountAddress, big.NewInt(0), big.NewInt(20), rangeSize, balances, outgoingERC20Transfers, incomingERC20Transfers, nil, nil) 1219 limiter := chain.NewRequestLimiter(chain.NewInMemRequestsMapStorage()) 1220 err := limiter.SetLimit("some-other-tag-than-transfer-history", maxRequests, time.Hour) 1221 require.NoError(t, err) 1222 tc.SetLimiter(limiter) 1223 1224 ctx := context.Background() 1225 group := async.NewAtomicGroup(ctx) 1226 group.Add(fbc.Command(1 * time.Millisecond)) 1227 1228 select { 1229 case <-ctx.Done(): 1230 t.Log("ERROR") 1231 case <-group.WaitAsync(): 1232 close(blockChannel) 1233 require.NoError(t, group.Error()) 1234 require.Greater(t, tc.getCounter(), maxRequests) 1235 } 1236 } 1237 1238 func TestFindBlocksCommandWithLimiterForMultipleAccountsSameGroup(t *testing.T) { 1239 rangeSize := 20 1240 maxRequestsTotal := 5 1241 limit1 := 3 1242 limit2 := 3 1243 account1 := common.HexToAddress("0x1234") 1244 account2 := common.HexToAddress("0x5678") 1245 balances := map[common.Address][][]int{account1: {{5, 1, 0}, {20, 2, 0}, {45, 1, 1}, {46, 50, 0}, {75, 0, 1}}, account2: {{5, 1, 0}, {20, 2, 0}, {45, 1, 1}, {46, 50, 0}, {75, 0, 1}}} 1246 outgoingERC20Transfers := map[common.Address][]testERC20Transfer{account1: {{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}} 1247 incomingERC20Transfers := map[common.Address][]testERC20Transfer{account2: {{big.NewInt(6), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}} 1248 1249 // Limiters share the same storage 1250 storage := chain.NewInMemRequestsMapStorage() 1251 1252 // Set up the first account 1253 fbc, tc, blockChannel, _ := setupFindBlocksCommand(t, account1, big.NewInt(0), big.NewInt(20), rangeSize, balances, outgoingERC20Transfers, nil, nil, nil) 1254 tc.tag = transferHistoryTag + account1.String() 1255 tc.groupTag = transferHistoryTag 1256 1257 limiter1 := chain.NewRequestLimiter(storage) 1258 err := limiter1.SetLimit(transferHistoryTag, maxRequestsTotal, time.Hour) 1259 require.NoError(t, err) 1260 err = limiter1.SetLimit(transferHistoryTag+account1.String(), limit1, time.Hour) 1261 require.NoError(t, err) 1262 tc.SetLimiter(limiter1) 1263 1264 // Set up the second account 1265 fbc2, tc2, _, _ := setupFindBlocksCommand(t, account2, big.NewInt(0), big.NewInt(20), rangeSize, balances, nil, incomingERC20Transfers, nil, nil) 1266 tc2.tag = transferHistoryTag + account2.String() 1267 tc2.groupTag = transferHistoryTag 1268 limiter2 := chain.NewRequestLimiter(storage) 1269 err = limiter2.SetLimit(transferHistoryTag, maxRequestsTotal, time.Hour) 1270 require.NoError(t, err) 1271 err = limiter2.SetLimit(transferHistoryTag+account2.String(), limit2, time.Hour) 1272 require.NoError(t, err) 1273 tc2.SetLimiter(limiter2) 1274 fbc2.blocksLoadedCh = blockChannel 1275 1276 ctx := context.Background() 1277 group := async.NewGroup(ctx) 1278 group.Add(fbc.Command(1 * time.Millisecond)) 1279 group.Add(fbc2.Command(1 * time.Millisecond)) 1280 1281 select { 1282 case <-ctx.Done(): 1283 t.Log("ERROR") 1284 case <-group.WaitAsync(): 1285 close(blockChannel) 1286 require.LessOrEqual(t, tc.getCounter(), maxRequestsTotal) 1287 } 1288 } 1289 1290 type MockETHClient struct { 1291 mock.Mock 1292 } 1293 1294 func (m *MockETHClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { 1295 args := m.Called(ctx, b) 1296 return args.Error(0) 1297 } 1298 1299 type MockChainClient struct { 1300 mock.Mock 1301 mock_client.MockClientInterface 1302 1303 clients map[walletcommon.ChainID]*MockETHClient 1304 } 1305 1306 func newMockChainClient() *MockChainClient { 1307 return &MockChainClient{ 1308 clients: make(map[walletcommon.ChainID]*MockETHClient), 1309 } 1310 } 1311 1312 func (m *MockChainClient) AbstractEthClient(chainID walletcommon.ChainID) (chain.BatchCallClient, error) { 1313 if _, ok := m.clients[chainID]; !ok { 1314 panic(fmt.Sprintf("no mock client for chainID %d", chainID)) 1315 } 1316 return m.clients[chainID], nil 1317 } 1318 1319 func TestFetchTransfersForLoadedBlocks(t *testing.T) { 1320 appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{}) 1321 require.NoError(t, err) 1322 1323 db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) 1324 require.NoError(t, err) 1325 tm := &TransactionManager{NewMultiTransactionDB(db), nil, nil, nil, nil, nil, nil, nil, nil, nil} 1326 1327 mediaServer, err := server.NewMediaServer(appdb, nil, nil, db) 1328 require.NoError(t, err) 1329 1330 wdb := NewDB(db) 1331 blockChannel := make(chan []*DBHeader, 100) 1332 1333 tc := &TestClient{ 1334 t: t, 1335 balances: map[common.Address][][]int{}, 1336 outgoingERC20Transfers: map[common.Address][]testERC20Transfer{}, 1337 incomingERC20Transfers: map[common.Address][]testERC20Transfer{}, 1338 callsCounter: map[string]int{}, 1339 currentBlock: 100, 1340 } 1341 1342 client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) 1343 client.SetClient(tc.NetworkID(), tc) 1344 tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) 1345 1346 tokenManager.SetTokens([]*token.Token{ 1347 { 1348 Address: tokenTXXAddress, 1349 Symbol: "TXX", 1350 Decimals: 18, 1351 ChainID: tc.NetworkID(), 1352 Name: "Test Token 1", 1353 Verified: true, 1354 }, 1355 { 1356 Address: tokenTXYAddress, 1357 Symbol: "TXY", 1358 Decimals: 18, 1359 ChainID: tc.NetworkID(), 1360 Name: "Test Token 2", 1361 Verified: true, 1362 }, 1363 }) 1364 1365 address := common.HexToAddress("0x1234") 1366 chainClient := newMockChainClient() 1367 ctrl := gomock.NewController(t) 1368 defer ctrl.Finish() 1369 rpcClient := mock_rpcclient.NewMockClientInterface(ctrl) 1370 rpcClient.EXPECT().AbstractEthClient(tc.NetworkID()).Return(chainClient, nil).AnyTimes() 1371 tracker := transactions.NewPendingTxTracker(db, rpcClient, nil, &event.Feed{}, transactions.PendingCheckInterval) 1372 accDB, err := accounts.NewDB(appdb) 1373 require.NoError(t, err) 1374 1375 cmd := &loadBlocksAndTransfersCommand{ 1376 accounts: []common.Address{address}, 1377 db: wdb, 1378 blockRangeDAO: &BlockRangeSequentialDAO{wdb.client}, 1379 blockDAO: &BlockDAO{db}, 1380 accountsDB: accDB, 1381 chainClient: tc, 1382 feed: &event.Feed{}, 1383 balanceCacher: balance.NewCacherWithTTL(5 * time.Minute), 1384 transactionManager: tm, 1385 pendingTxManager: tracker, 1386 tokenManager: tokenManager, 1387 blocksLoadedCh: blockChannel, 1388 omitHistory: true, 1389 contractMaker: tokenManager.ContractMaker, 1390 } 1391 1392 tc.prepareBalanceHistory(int(tc.currentBlock)) 1393 tc.prepareTokenBalanceHistory(int(tc.currentBlock)) 1394 // tc.traceAPICalls = true 1395 1396 ctx := context.Background() 1397 group := async.NewAtomicGroup(ctx) 1398 1399 fromNum := big.NewInt(0) 1400 toNum, err := getHeadBlockNumber(ctx, cmd.chainClient) 1401 require.NoError(t, err) 1402 err = cmd.fetchHistoryBlocksForAccount(group, address, fromNum, toNum, blockChannel) 1403 require.NoError(t, err) 1404 1405 select { 1406 case <-ctx.Done(): 1407 t.Log("ERROR") 1408 case <-group.WaitAsync(): 1409 require.Equal(t, 1, tc.getCounter()) 1410 } 1411 } 1412 1413 func getNewBlocksCases() []findBlockCase { 1414 cases := []findBlockCase{ 1415 findBlockCase{ 1416 balanceChanges: [][]int{ 1417 {20, 1, 0}, 1418 }, 1419 fromBlock: 0, 1420 toBlock: 10, 1421 expectedBlocksFound: 0, 1422 label: "single block, but not in range", 1423 }, 1424 findBlockCase{ 1425 balanceChanges: [][]int{ 1426 {20, 1, 0}, 1427 }, 1428 fromBlock: 10, 1429 toBlock: 20, 1430 expectedBlocksFound: 1, 1431 label: "single block in range", 1432 }, 1433 } 1434 1435 return cases 1436 } 1437 1438 func TestFetchNewBlocksCommand_findBlocksWithEthTransfers(t *testing.T) { 1439 appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{}) 1440 require.NoError(t, err) 1441 1442 db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) 1443 require.NoError(t, err) 1444 1445 mediaServer, err := server.NewMediaServer(appdb, nil, nil, db) 1446 require.NoError(t, err) 1447 1448 wdb := NewDB(db) 1449 blockChannel := make(chan []*DBHeader, 10) 1450 1451 address := common.HexToAddress("0x1234") 1452 accDB, err := accounts.NewDB(appdb) 1453 require.NoError(t, err) 1454 1455 for idx, testCase := range getNewBlocksCases() { 1456 t.Log("case #", idx+1) 1457 tc := &TestClient{ 1458 t: t, 1459 balances: map[common.Address][][]int{address: testCase.balanceChanges}, 1460 outgoingERC20Transfers: map[common.Address][]testERC20Transfer{}, 1461 incomingERC20Transfers: map[common.Address][]testERC20Transfer{}, 1462 callsCounter: map[string]int{}, 1463 currentBlock: 100, 1464 } 1465 1466 client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) 1467 client.SetClient(tc.NetworkID(), tc) 1468 tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) 1469 1470 tokenManager.SetTokens([]*token.Token{ 1471 { 1472 Address: tokenTXXAddress, 1473 Symbol: "TXX", 1474 Decimals: 18, 1475 ChainID: tc.NetworkID(), 1476 Name: "Test Token 1", 1477 Verified: true, 1478 }, 1479 { 1480 Address: tokenTXYAddress, 1481 Symbol: "TXY", 1482 Decimals: 18, 1483 ChainID: tc.NetworkID(), 1484 Name: "Test Token 2", 1485 Verified: true, 1486 }, 1487 }) 1488 1489 cmd := &findNewBlocksCommand{ 1490 findBlocksCommand: &findBlocksCommand{ 1491 accounts: []common.Address{address}, 1492 db: wdb, 1493 accountsDB: accDB, 1494 blockRangeDAO: &BlockRangeSequentialDAO{wdb.client}, 1495 chainClient: tc, 1496 balanceCacher: balance.NewCacherWithTTL(5 * time.Minute), 1497 feed: &event.Feed{}, 1498 noLimit: false, 1499 tokenManager: tokenManager, 1500 blocksLoadedCh: blockChannel, 1501 defaultNodeBlockChunkSize: DefaultNodeBlockChunkSize, 1502 }, 1503 nonceCheckIntervalIterations: nonceCheckIntervalIterations, 1504 logsCheckIntervalIterations: logsCheckIntervalIterations, 1505 } 1506 tc.prepareBalanceHistory(int(tc.currentBlock)) 1507 tc.prepareTokenBalanceHistory(int(tc.currentBlock)) 1508 1509 ctx := context.Background() 1510 blocks, _, err := cmd.findBlocksWithEthTransfers(ctx, address, big.NewInt(testCase.fromBlock), big.NewInt(testCase.toBlock)) 1511 require.NoError(t, err) 1512 require.Equal(t, testCase.expectedBlocksFound, len(blocks), fmt.Sprintf("case %d: %s, blocks from %d to %d", idx+1, testCase.label, testCase.fromBlock, testCase.toBlock)) 1513 } 1514 } 1515 1516 func TestFetchNewBlocksCommand_nonceDetection(t *testing.T) { 1517 balanceChanges := [][]int{ 1518 {5, 1, 0}, 1519 {6, 0, 1}, 1520 } 1521 1522 scanRange := 5 1523 address := common.HexToAddress("0x1234") 1524 1525 tc := &TestClient{ 1526 t: t, 1527 balances: map[common.Address][][]int{address: balanceChanges}, 1528 outgoingERC20Transfers: map[common.Address][]testERC20Transfer{}, 1529 incomingERC20Transfers: map[common.Address][]testERC20Transfer{}, 1530 callsCounter: map[string]int{}, 1531 currentBlock: 0, 1532 } 1533 1534 //tc.printPreparedData = true 1535 tc.prepareBalanceHistory(20) 1536 1537 appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{}) 1538 require.NoError(t, err) 1539 1540 db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) 1541 require.NoError(t, err) 1542 1543 mediaServer, err := server.NewMediaServer(appdb, nil, nil, db) 1544 require.NoError(t, err) 1545 1546 client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) 1547 client.SetClient(tc.NetworkID(), tc) 1548 tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) 1549 1550 wdb := NewDB(db) 1551 blockChannel := make(chan []*DBHeader, 10) 1552 1553 accDB, err := accounts.NewDB(appdb) 1554 require.NoError(t, err) 1555 1556 maker, _ := contracts.NewContractMaker(client) 1557 1558 cmd := &findNewBlocksCommand{ 1559 findBlocksCommand: &findBlocksCommand{ 1560 accounts: []common.Address{address}, 1561 db: wdb, 1562 accountsDB: accDB, 1563 blockRangeDAO: &BlockRangeSequentialDAO{wdb.client}, 1564 chainClient: tc, 1565 balanceCacher: balance.NewCacherWithTTL(5 * time.Minute), 1566 feed: &event.Feed{}, 1567 noLimit: false, 1568 tokenManager: tokenManager, 1569 blocksLoadedCh: blockChannel, 1570 defaultNodeBlockChunkSize: scanRange, 1571 fromBlockNumber: big.NewInt(0), 1572 }, 1573 blockChainState: blockchainstate.NewBlockChainState(), 1574 contractMaker: maker, 1575 nonceCheckIntervalIterations: 2, 1576 logsCheckIntervalIterations: 2, 1577 } 1578 1579 acc := &accounts.Account{ 1580 Address: ethtypes.BytesToAddress(address.Bytes()), 1581 Type: accounts.AccountTypeWatch, 1582 Name: address.String(), 1583 ColorID: multicommon.CustomizationColorPrimary, 1584 Emoji: "emoji", 1585 } 1586 err = accDB.SaveOrUpdateAccounts([]*accounts.Account{acc}, false) 1587 require.NoError(t, err) 1588 1589 ctx := context.Background() 1590 tc.currentBlock = 3 1591 for i := 0; i < 3; i++ { 1592 err := cmd.Run(ctx) 1593 require.NoError(t, err) 1594 close(blockChannel) 1595 1596 foundBlocks := []*DBHeader{} 1597 for { 1598 bloks, ok := <-blockChannel 1599 if !ok { 1600 break 1601 } 1602 foundBlocks = append(foundBlocks, bloks...) 1603 } 1604 1605 numbers := []int64{} 1606 for _, block := range foundBlocks { 1607 numbers = append(numbers, block.Number.Int64()) 1608 } 1609 if i == 2 { 1610 require.Equal(t, 2, len(foundBlocks), "blocks", numbers) 1611 } else { 1612 require.Equal(t, 0, len(foundBlocks), "no blocks expected to be found") 1613 } 1614 blockChannel = make(chan []*DBHeader, 10) 1615 cmd.blocksLoadedCh = blockChannel 1616 tc.currentBlock += uint64(scanRange) 1617 } 1618 } 1619 1620 func TestFetchNewBlocksCommand(t *testing.T) { 1621 appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{}) 1622 require.NoError(t, err) 1623 1624 db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) 1625 require.NoError(t, err) 1626 1627 mediaServer, err := server.NewMediaServer(appdb, nil, nil, db) 1628 require.NoError(t, err) 1629 1630 wdb := NewDB(db) 1631 blockChannel := make(chan []*DBHeader, 10) 1632 1633 address1 := common.HexToAddress("0x1234") 1634 address2 := common.HexToAddress("0x5678") 1635 accDB, err := accounts.NewDB(appdb) 1636 require.NoError(t, err) 1637 1638 for _, address := range []*common.Address{&address1, &address2} { 1639 acc := &accounts.Account{ 1640 Address: ethtypes.BytesToAddress(address.Bytes()), 1641 Type: accounts.AccountTypeWatch, 1642 Name: address.String(), 1643 ColorID: multicommon.CustomizationColorPrimary, 1644 Emoji: "emoji", 1645 } 1646 err = accDB.SaveOrUpdateAccounts([]*accounts.Account{acc}, false) 1647 require.NoError(t, err) 1648 } 1649 1650 tc := &TestClient{ 1651 t: t, 1652 balances: map[common.Address][][]int{}, 1653 outgoingERC20Transfers: map[common.Address][]testERC20Transfer{}, 1654 incomingERC20Transfers: map[common.Address][]testERC20Transfer{}, 1655 callsCounter: map[string]int{}, 1656 currentBlock: 1, 1657 } 1658 //tc.printPreparedData = true 1659 1660 client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) 1661 client.SetClient(tc.NetworkID(), tc) 1662 1663 tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb, nil, nil), network.NewManager(appdb), appdb, mediaServer, nil, nil, nil, token.NewPersistence(db)) 1664 1665 tokenManager.SetTokens([]*token.Token{ 1666 { 1667 Address: tokenTXXAddress, 1668 Symbol: "TXX", 1669 Decimals: 18, 1670 ChainID: tc.NetworkID(), 1671 Name: "Test Token 1", 1672 Verified: true, 1673 }, 1674 { 1675 Address: tokenTXYAddress, 1676 Symbol: "TXY", 1677 Decimals: 18, 1678 ChainID: tc.NetworkID(), 1679 Name: "Test Token 2", 1680 Verified: true, 1681 }, 1682 }) 1683 1684 cmd := &findNewBlocksCommand{ 1685 findBlocksCommand: &findBlocksCommand{ 1686 accounts: []common.Address{address1, address2}, 1687 db: wdb, 1688 accountsDB: accDB, 1689 blockRangeDAO: &BlockRangeSequentialDAO{wdb.client}, 1690 chainClient: tc, 1691 balanceCacher: balance.NewCacherWithTTL(5 * time.Minute), 1692 feed: &event.Feed{}, 1693 noLimit: false, 1694 fromBlockNumber: big.NewInt(int64(tc.currentBlock)), 1695 tokenManager: tokenManager, 1696 blocksLoadedCh: blockChannel, 1697 defaultNodeBlockChunkSize: DefaultNodeBlockChunkSize, 1698 }, 1699 contractMaker: tokenManager.ContractMaker, 1700 blockChainState: blockchainstate.NewBlockChainState(), 1701 nonceCheckIntervalIterations: nonceCheckIntervalIterations, 1702 logsCheckIntervalIterations: logsCheckIntervalIterations, 1703 } 1704 1705 ctx := context.Background() 1706 1707 // I don't prepare lots of data and a loop, as I just need to verify a few cases 1708 1709 // Verify that cmd.fromBlockNumber stays the same 1710 tc.prepareBalanceHistory(int(tc.currentBlock)) 1711 tc.prepareTokenBalanceHistory(int(tc.currentBlock)) 1712 err = cmd.Run(ctx) 1713 require.NoError(t, err) 1714 require.Equal(t, uint64(1), cmd.fromBlockNumber.Uint64()) 1715 1716 // Verify that cmd.fromBlockNumber is incremented, equal to the head block number 1717 tc.currentBlock = 2 // this is the head block number that will be returned by the mock client 1718 tc.prepareBalanceHistory(int(tc.currentBlock)) 1719 tc.prepareTokenBalanceHistory(int(tc.currentBlock)) 1720 err = cmd.Run(ctx) 1721 require.NoError(t, err) 1722 require.Equal(t, tc.currentBlock, cmd.fromBlockNumber.Uint64()) 1723 1724 // Verify that blocks are found and cmd.fromBlockNumber is incremented 1725 tc.resetCounter() 1726 tc.currentBlock = 3 1727 tc.balances = map[common.Address][][]int{ 1728 address1: {{3, 1, 0}}, 1729 address2: {{3, 1, 0}}, 1730 } 1731 tc.incomingERC20Transfers = map[common.Address][]testERC20Transfer{ 1732 address1: {{big.NewInt(3), tokenTXXAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}, 1733 address2: {{big.NewInt(3), tokenTXYAddress, big.NewInt(1), walletcommon.Erc20TransferEventType}}, 1734 } 1735 tc.prepareBalanceHistory(int(tc.currentBlock)) 1736 tc.prepareTokenBalanceHistory(int(tc.currentBlock)) 1737 1738 group := async.NewGroup(ctx) 1739 group.Add(cmd.Command()) // This is an infinite command, I can't use WaitAsync() here to wait for it to finish 1740 1741 expectedBlocksNumber := 3 // ETH block is found twice for each account as we don't handle addresses in MockClient. A block with ERC20 transfer is found once 1742 blocksFound := 0 1743 stop := false 1744 for stop == false { 1745 select { 1746 case <-ctx.Done(): 1747 require.Fail(t, "context done") 1748 stop = true 1749 case <-blockChannel: 1750 blocksFound++ 1751 case <-time.After(100 * time.Millisecond): 1752 stop = true 1753 } 1754 } 1755 group.Stop() 1756 group.Wait() 1757 require.Equal(t, expectedBlocksNumber, blocksFound) 1758 require.Equal(t, tc.currentBlock, cmd.fromBlockNumber.Uint64()) 1759 // We must check all the logs for all accounts with a single iteration of eth_getLogs call 1760 require.Equal(t, 3, tc.callsCounter["FilterLogs"], "calls to FilterLogs") 1761 } 1762 1763 type TestClientWithError struct { 1764 *TestClient 1765 } 1766 1767 func (tc *TestClientWithError) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { 1768 tc.incCounter("BlockByNumber") 1769 if tc.traceAPICalls { 1770 tc.t.Log("BlockByNumber", number) 1771 } 1772 1773 return nil, errors.New("Network error") 1774 } 1775 1776 type BlockRangeSequentialDAOMockError struct { 1777 *BlockRangeSequentialDAO 1778 } 1779 1780 func (b *BlockRangeSequentialDAOMockError) getBlockRange(chainID uint64, address common.Address) (blockRange *ethTokensBlockRanges, exists bool, err error) { 1781 return nil, true, errors.New("DB error") 1782 } 1783 1784 type BlockRangeSequentialDAOMockSuccess struct { 1785 *BlockRangeSequentialDAO 1786 } 1787 1788 func (b *BlockRangeSequentialDAOMockSuccess) getBlockRange(chainID uint64, address common.Address) (blockRange *ethTokensBlockRanges, exists bool, err error) { 1789 return newEthTokensBlockRanges(), true, nil 1790 } 1791 1792 func TestLoadBlocksAndTransfersCommand_FiniteFinishedInfiniteRunning(t *testing.T) { 1793 appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{}) 1794 require.NoError(t, err) 1795 1796 db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) 1797 require.NoError(t, err) 1798 1799 client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db, nil) 1800 maker, _ := contracts.NewContractMaker(client) 1801 1802 wdb := NewDB(db) 1803 tc := &TestClient{ 1804 t: t, 1805 callsCounter: map[string]int{}, 1806 } 1807 accDB, err := accounts.NewDB(appdb) 1808 require.NoError(t, err) 1809 1810 cmd := &loadBlocksAndTransfersCommand{ 1811 accounts: []common.Address{common.HexToAddress("0x1234")}, 1812 chainClient: tc, 1813 blockDAO: &BlockDAO{db}, 1814 blockRangeDAO: &BlockRangeSequentialDAOMockSuccess{ 1815 &BlockRangeSequentialDAO{ 1816 wdb.client, 1817 }, 1818 }, 1819 accountsDB: accDB, 1820 db: wdb, 1821 contractMaker: maker, 1822 } 1823 1824 ctx, cancel := context.WithCancel(context.Background()) 1825 group := async.NewGroup(ctx) 1826 1827 group.Add(cmd.Command(1 * time.Millisecond)) 1828 1829 select { 1830 case <-ctx.Done(): 1831 cancel() // linter is not happy if cancel is not called on all code paths 1832 t.Log("Done") 1833 case <-group.WaitAsync(): 1834 require.True(t, cmd.isStarted()) 1835 1836 // Test that it stops if canceled 1837 cancel() 1838 require.NoError(t, utils.Eventually(func() error { 1839 if !cmd.isStarted() { 1840 return nil 1841 } 1842 return errors.New("command is still running") 1843 }, 100*time.Millisecond, 10*time.Millisecond)) 1844 } 1845 } 1846 1847 func TestTransfersCommand_RetryAndQuitOnMaxError(t *testing.T) { 1848 tc := &TestClientWithError{ 1849 &TestClient{ 1850 t: t, 1851 callsCounter: map[string]int{}, 1852 }, 1853 } 1854 1855 address := common.HexToAddress("0x1234") 1856 cmd := &transfersCommand{ 1857 chainClient: tc, 1858 address: address, 1859 eth: ÐDownloader{ 1860 chainClient: tc, 1861 accounts: []common.Address{address}, 1862 }, 1863 blockNums: []*big.Int{big.NewInt(1)}, 1864 } 1865 1866 ctx := context.Background() 1867 group := async.NewGroup(ctx) 1868 1869 runner := cmd.Runner(1 * time.Millisecond) 1870 group.Add(runner.Run) 1871 1872 select { 1873 case <-ctx.Done(): 1874 t.Log("Done") 1875 case <-group.WaitAsync(): 1876 errorCounter := runner.(async.FiniteCommandWithErrorCounter).ErrorCounter 1877 require.Equal(t, errorCounter.MaxErrors(), tc.callsCounter["BlockByNumber"]) 1878 1879 _, expectedErr := tc.BlockByNumber(context.TODO(), nil) 1880 require.Error(t, expectedErr, errorCounter.Error()) 1881 } 1882 }