package transfer import ( "context" "errors" "math/big" "sort" "testing" "time" "github.com/status-im/status-go/rpc/chain" "github.com/status-im/status-go/services/wallet/balance" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/common" ) func TestConcurrentErrorInterrupts(t *testing.T) { concurrent := NewConcurrentDownloader(context.Background(), NoThreadLimit) var interrupted bool concurrent.Add(func(ctx context.Context) error { select { case <-ctx.Done(): interrupted = true case <-time.After(10 * time.Second): } return nil }) err := errors.New("interrupt") concurrent.Add(func(ctx context.Context) error { return err }) concurrent.Wait() require.True(t, interrupted) require.Equal(t, err, concurrent.Error()) } func TestConcurrentCollectsTransfers(t *testing.T) { concurrent := NewConcurrentDownloader(context.Background(), NoThreadLimit) concurrent.Add(func(context.Context) error { concurrent.Push(Transfer{}) return nil }) concurrent.Add(func(context.Context) error { concurrent.Push(Transfer{}) return nil }) concurrent.Wait() require.Len(t, concurrent.Get(), 2) } type balancesFixture []*big.Int func (f balancesFixture) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { index := int(blockNumber.Int64()) if index > len(f)-1 { return nil, errors.New("balance unknown") } return f[index], nil } func (f balancesFixture) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { return uint64(0), nil } func (f balancesFixture) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { return &types.Header{ Number: number, }, nil } func (f balancesFixture) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { return &types.Header{ Number: big.NewInt(0), }, nil } func (f balancesFixture) NetworkID() uint64 { return 0 } func (f balancesFixture) FullTransactionByBlockNumberAndIndex(ctx context.Context, blockNumber *big.Int, index uint) (*chain.FullTransaction, error) { blockHash := common.HexToHash("0x0") return &chain.FullTransaction{ Tx: &types.Transaction{}, TxExtraInfo: chain.TxExtraInfo{ BlockNumber: (*hexutil.Big)(big.NewInt(0)), BlockHash: &blockHash, }, }, nil } type batchesFixture [][]Transfer func (f batchesFixture) GetTransfersByNumber(ctx context.Context, number *big.Int) (rst []Transfer, err error) { index := int(number.Int64()) if index > len(f)-1 { return nil, errors.New("unknown block") } return f[index], nil } func TestConcurrentEthDownloader(t *testing.T) { type options struct { balances balancesFixture batches batchesFixture result []DBHeader last *big.Int } type testCase struct { desc string options options } for _, tc := range []testCase{ { desc: "NoBalances", options: options{ last: big.NewInt(3), balances: balancesFixture{big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)}, }, }, { desc: "LastBlock", options: options{ last: big.NewInt(3), balances: balancesFixture{big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(10)}, batches: batchesFixture{{}, {}, {}, {{BlockNumber: big.NewInt(3)}, {BlockNumber: big.NewInt(3)}}}, result: []DBHeader{{Number: big.NewInt(3)}}, }, }, { desc: "ChangesInEveryBlock", options: options{ last: big.NewInt(3), balances: balancesFixture{big.NewInt(0), big.NewInt(3), big.NewInt(7), big.NewInt(10)}, batches: batchesFixture{{}, {{BlockNumber: big.NewInt(1)}}, {{BlockNumber: big.NewInt(2)}}, {{BlockNumber: big.NewInt(3)}}}, result: []DBHeader{{Number: big.NewInt(1)}, {Number: big.NewInt(2)}, {Number: big.NewInt(3)}}, }, }, } { t.Run(tc.desc, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() concurrent := NewConcurrentDownloader(ctx, 0) _, headers, _, _ := findBlocksWithEthTransfers( ctx, tc.options.balances, balance.NewCacherWithTTL(5*time.Minute), common.Address{}, zero, tc.options.last, false, NoThreadLimit) concurrent.Wait() require.NoError(t, concurrent.Error()) rst := concurrent.Get() require.Len(t, headers, len(tc.options.result)) sort.Slice(rst, func(i, j int) bool { return rst[i].BlockNumber.Cmp(rst[j].BlockNumber) < 0 }) /*for i := range rst { require.Equal(t, tc.options.result[i].BlockNumber, rst[i].BlockNumber) }*/ }) } }