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 }