github.com/status-im/status-go@v1.1.0/services/wallet/transfer/controller_test.go (about)

     1  package transfer
     2  
     3  import (
     4  	"context"
     5  	"math/big"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/ethereum/go-ethereum/common"
    13  	"github.com/ethereum/go-ethereum/common/hexutil"
    14  	"github.com/ethereum/go-ethereum/event"
    15  	"github.com/status-im/status-go/appdatabase"
    16  	"github.com/status-im/status-go/eth-node/types"
    17  	"github.com/status-im/status-go/multiaccounts/accounts"
    18  	"github.com/status-im/status-go/services/accounts/accountsevent"
    19  	"github.com/status-im/status-go/services/wallet/blockchainstate"
    20  	wallet_common "github.com/status-im/status-go/services/wallet/common"
    21  	"github.com/status-im/status-go/t/helpers"
    22  	"github.com/status-im/status-go/walletdatabase"
    23  )
    24  
    25  func TestController_watchAccountsChanges(t *testing.T) {
    26  	appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
    27  	require.NoError(t, err)
    28  
    29  	accountsDB, err := accounts.NewDB(appDB)
    30  	require.NoError(t, err)
    31  
    32  	walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
    33  	require.NoError(t, err)
    34  
    35  	accountFeed := &event.Feed{}
    36  
    37  	bcstate := blockchainstate.NewBlockChainState()
    38  	SetMultiTransactionIDGenerator(StaticIDCounter()) // to have different multi-transaction IDs even with fast execution
    39  	transactionManager := NewTransactionManager(NewInMemMultiTransactionStorage(), nil, nil, nil, accountsDB, nil, nil)
    40  	c := NewTransferController(
    41  		walletDB,
    42  		accountsDB,
    43  		nil, // rpcClient
    44  		accountFeed,
    45  		nil,                // transferFeed
    46  		transactionManager, // transactionManager
    47  		nil,                // pendingTxManager
    48  		nil,                // tokenManager
    49  		nil,                // balanceCacher
    50  		bcstate,
    51  	)
    52  
    53  	address := common.HexToAddress("0x1234")
    54  	chainID := uint64(777)
    55  	// Insert blocks
    56  	database := NewDB(walletDB)
    57  	err = database.SaveBlocks(chainID, []*DBHeader{
    58  		{
    59  			Number:  big.NewInt(1),
    60  			Hash:    common.Hash{1},
    61  			Network: chainID,
    62  			Address: address,
    63  			Loaded:  false,
    64  		},
    65  	})
    66  	require.NoError(t, err)
    67  
    68  	// Insert transfers
    69  	err = saveTransfersMarkBlocksLoaded(walletDB, chainID, address, []Transfer{
    70  		{
    71  			ID:          common.Hash{1},
    72  			BlockHash:   common.Hash{1},
    73  			BlockNumber: big.NewInt(1),
    74  			Address:     address,
    75  			NetworkID:   chainID,
    76  		},
    77  	}, []*big.Int{big.NewInt(1)})
    78  	require.NoError(t, err)
    79  
    80  	// Insert block ranges
    81  	blockRangesDAO := &BlockRangeSequentialDAO{walletDB}
    82  	err = blockRangesDAO.upsertRange(chainID, address, newEthTokensBlockRanges())
    83  	require.NoError(t, err)
    84  
    85  	ranges, _, err := blockRangesDAO.getBlockRange(chainID, address)
    86  	require.NoError(t, err)
    87  	require.NotNil(t, ranges)
    88  
    89  	// Insert multitransactions
    90  	// Save address to accounts DB which transactions we want to preserve
    91  	counterparty := common.Address{0x1}
    92  	err = accountsDB.SaveOrUpdateAccounts([]*accounts.Account{
    93  		{Address: types.Address(counterparty), Chat: false, Wallet: true},
    94  	}, false)
    95  	require.NoError(t, err)
    96  
    97  	// Self multi transaction
    98  	midSelf, err := transactionManager.InsertMultiTransaction(NewMultiTransaction(
    99  		/* Timestamp:     */ 1,
   100  		/* FromNetworkID: */ 1,
   101  		/* ToNetworkID:	  */ 1,
   102  		/* FromTxHash:    */ common.Hash{},
   103  		/* ToTxHash:      */ common.Hash{},
   104  		/* FromAddress:   */ address,
   105  		/* ToAddress:     */ address,
   106  		/* FromAsset:     */ "ETH",
   107  		/* ToAsset:       */ "DAI",
   108  		/* FromAmount:    */ &hexutil.Big{},
   109  		/* ToAmount:      */ &hexutil.Big{},
   110  		/* Type:		  */ MultiTransactionSend,
   111  		/* CrossTxID:	  */ "",
   112  	))
   113  
   114  	require.NoError(t, err)
   115  	mtxs, err := transactionManager.GetMultiTransactions(context.Background(), []wallet_common.MultiTransactionIDType{midSelf})
   116  	require.NoError(t, err)
   117  	require.Len(t, mtxs, 1)
   118  
   119  	// Send multi transaction
   120  	mt := NewMultiTransaction(
   121  		/* Timestamp:     */ 2,
   122  		/* FromNetworkID: */ 1,
   123  		/* ToNetworkID:	  */ 1,
   124  		/* FromTxHash:    */ common.Hash{},
   125  		/* ToTxHash:      */ common.Hash{},
   126  		/* FromAddress:   */ address,
   127  		/* ToAddress:     */ counterparty,
   128  		/* FromAsset:     */ "ETH",
   129  		/* ToAsset:       */ "DAI",
   130  		/* FromAmount:    */ &hexutil.Big{},
   131  		/* ToAmount:      */ &hexutil.Big{},
   132  		/* Type:		  */ MultiTransactionSend,
   133  		/* CrossTxID:	  */ "",
   134  	)
   135  	mid, err := transactionManager.InsertMultiTransaction(mt)
   136  
   137  	require.NoError(t, err)
   138  	mtxs, err = transactionManager.GetMultiTransactions(context.Background(), []wallet_common.MultiTransactionIDType{midSelf, mid})
   139  	require.NoError(t, err)
   140  	require.Len(t, mtxs, 2)
   141  
   142  	// Another Send multi-transaction where sender and receiver are inverted (both accounts are in accounts DB)
   143  	midReverse, err := transactionManager.InsertMultiTransaction(NewMultiTransaction(
   144  		/* Timestamp:     */ mt.Timestamp+1,
   145  		/* FromNetworkID: */ 1,
   146  		/* ToNetworkID:	  */ 1,
   147  		/* FromTxHash:    */ common.Hash{},
   148  		/* ToTxHash:      */ common.Hash{},
   149  		/* FromAddress:   */ mt.ToAddress,
   150  		/* ToAddress:     */ mt.FromAddress,
   151  		/* FromAsset:     */ mt.FromAsset,
   152  		/* ToAsset:       */ mt.ToAsset,
   153  		/* FromAmount:    */ mt.FromAmount,
   154  		/* ToAmount:      */ mt.ToAmount,
   155  		/* Type:		  */ MultiTransactionSend,
   156  		/* CrossTxID:	  */ "",
   157  	))
   158  
   159  	require.NoError(t, err)
   160  	mtxs, err = transactionManager.GetMultiTransactions(context.Background(), []wallet_common.MultiTransactionIDType{midSelf, mid, midReverse})
   161  	require.NoError(t, err)
   162  	require.Len(t, mtxs, 3)
   163  
   164  	// Start watching accounts
   165  	wg := sync.WaitGroup{}
   166  	wg.Add(1)
   167  	c.accWatcher = accountsevent.NewWatcher(c.accountsDB, c.accountFeed, func(changedAddresses []common.Address, eventType accountsevent.EventType, currentAddresses []common.Address) {
   168  		c.onAccountsChanged(changedAddresses, eventType, currentAddresses, []uint64{chainID})
   169  
   170  		// Quit channel event handler before  destroying the channel
   171  		go func() {
   172  			defer wg.Done()
   173  
   174  			time.Sleep(1 * time.Millisecond)
   175  			// Wait for DB to be cleaned up
   176  			c.accWatcher.Stop()
   177  
   178  			// Check that transfers, blocks and block ranges were deleted
   179  			transfers, err := database.GetTransfersByAddress(chainID, address, big.NewInt(2), 1)
   180  			require.NoError(t, err)
   181  			require.Len(t, transfers, 0)
   182  
   183  			blocksDAO := &BlockDAO{walletDB}
   184  			block, err := blocksDAO.GetLastBlockByAddress(chainID, address, 1)
   185  			require.NoError(t, err)
   186  			require.Nil(t, block)
   187  
   188  			ranges, _, err = blockRangesDAO.getBlockRange(chainID, address)
   189  			require.NoError(t, err)
   190  			require.Nil(t, ranges.eth.FirstKnown)
   191  			require.Nil(t, ranges.eth.LastKnown)
   192  			require.Nil(t, ranges.eth.Start)
   193  			require.Nil(t, ranges.tokens.FirstKnown)
   194  			require.Nil(t, ranges.tokens.LastKnown)
   195  			require.Nil(t, ranges.tokens.Start)
   196  
   197  			mtxs, err := transactionManager.GetMultiTransactions(context.Background(), []wallet_common.MultiTransactionIDType{mid, midSelf, midReverse})
   198  			require.NoError(t, err)
   199  			require.Len(t, mtxs, 1)
   200  			require.Equal(t, midReverse, mtxs[0].ID)
   201  		}()
   202  	})
   203  	c.startAccountWatcher([]uint64{chainID})
   204  
   205  	// Watching accounts must start before sending event.
   206  	// To avoid running goroutine immediately and let the controller subscribe first,
   207  	// use any delay.
   208  	go func() {
   209  		time.Sleep(1 * time.Millisecond)
   210  
   211  		accountFeed.Send(accountsevent.Event{
   212  			Type:     accountsevent.EventTypeRemoved,
   213  			Accounts: []common.Address{address},
   214  		})
   215  	}()
   216  
   217  	wg.Wait()
   218  }
   219  
   220  func TestController_cleanupAccountLeftovers(t *testing.T) {
   221  	appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
   222  	require.NoError(t, err)
   223  
   224  	walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
   225  	require.NoError(t, err)
   226  
   227  	accountsDB, err := accounts.NewDB(appDB)
   228  	require.NoError(t, err)
   229  
   230  	removedAddr := common.HexToAddress("0x5678")
   231  	existingAddr := types.HexToAddress("0x1234")
   232  	accounts := []*accounts.Account{
   233  		{Address: existingAddr, Chat: false, Wallet: true},
   234  	}
   235  	err = accountsDB.SaveOrUpdateAccounts(accounts, false)
   236  	require.NoError(t, err)
   237  
   238  	storedAccs, err := accountsDB.GetWalletAddresses()
   239  	require.NoError(t, err)
   240  	require.Len(t, storedAccs, 1)
   241  
   242  	transactionManager := NewTransactionManager(NewMultiTransactionDB(walletDB), nil, nil, nil, accountsDB, nil, nil)
   243  	bcstate := blockchainstate.NewBlockChainState()
   244  	c := NewTransferController(
   245  		walletDB,
   246  		accountsDB,
   247  		nil,                // rpcClient
   248  		nil,                // accountFeed
   249  		nil,                // transferFeed
   250  		transactionManager, // transactionManager
   251  		nil,                // pendingTxManager
   252  		nil,                // tokenManager
   253  		nil,                // balanceCacher
   254  		bcstate,
   255  	)
   256  	chainID := uint64(777)
   257  	// Insert blocks
   258  	database := NewDB(walletDB)
   259  	err = database.SaveBlocks(chainID, []*DBHeader{
   260  		{
   261  			Number:  big.NewInt(1),
   262  			Hash:    common.Hash{1},
   263  			Network: chainID,
   264  			Address: removedAddr,
   265  			Loaded:  false,
   266  		},
   267  	})
   268  	require.NoError(t, err)
   269  	err = database.SaveBlocks(chainID, []*DBHeader{
   270  		{
   271  			Number:  big.NewInt(2),
   272  			Hash:    common.Hash{2},
   273  			Network: chainID,
   274  			Address: common.Address(existingAddr),
   275  			Loaded:  false,
   276  		},
   277  	})
   278  	require.NoError(t, err)
   279  
   280  	blocksDAO := &BlockDAO{walletDB}
   281  	block, err := blocksDAO.GetLastBlockByAddress(chainID, removedAddr, 1)
   282  	require.NoError(t, err)
   283  	require.NotNil(t, block)
   284  	block, err = blocksDAO.GetLastBlockByAddress(chainID, common.Address(existingAddr), 1)
   285  	require.NoError(t, err)
   286  	require.NotNil(t, block)
   287  
   288  	// Insert transfers
   289  	err = saveTransfersMarkBlocksLoaded(walletDB, chainID, removedAddr, []Transfer{
   290  		{
   291  			ID:          common.Hash{1},
   292  			BlockHash:   common.Hash{1},
   293  			BlockNumber: big.NewInt(1),
   294  			Address:     removedAddr,
   295  			NetworkID:   chainID,
   296  		},
   297  	}, []*big.Int{big.NewInt(1)})
   298  	require.NoError(t, err)
   299  
   300  	err = saveTransfersMarkBlocksLoaded(walletDB, chainID, common.Address(existingAddr), []Transfer{
   301  		{
   302  			ID:          common.Hash{2},
   303  			BlockHash:   common.Hash{2},
   304  			BlockNumber: big.NewInt(2),
   305  			Address:     common.Address(existingAddr),
   306  			NetworkID:   chainID,
   307  		},
   308  	}, []*big.Int{big.NewInt(2)})
   309  	require.NoError(t, err)
   310  
   311  	err = c.cleanupAccountsLeftovers()
   312  	require.NoError(t, err)
   313  
   314  	// Check that transfers and blocks of removed account were deleted
   315  	transfers, err := database.GetTransfers(chainID, big.NewInt(1), big.NewInt(2))
   316  	require.NoError(t, err)
   317  	require.Len(t, transfers, 1)
   318  	require.Equal(t, transfers[0].Address, common.Address(existingAddr))
   319  
   320  	block, err = blocksDAO.GetLastBlockByAddress(chainID, removedAddr, 1)
   321  	require.NoError(t, err)
   322  	require.Nil(t, block)
   323  
   324  	// Make sure that transfers and blocks of existing account were not deleted
   325  	existingBlock, err := blocksDAO.GetLastBlockByAddress(chainID, common.Address(existingAddr), 1)
   326  	require.NoError(t, err)
   327  	require.NotNil(t, existingBlock)
   328  }