2023-11-28 14:23:03 +00:00
|
|
|
package transfer
|
|
|
|
|
|
|
|
import (
|
2024-03-26 12:05:49 +00:00
|
|
|
"context"
|
2023-11-28 14:23:03 +00:00
|
|
|
"math/big"
|
2024-03-26 12:05:49 +00:00
|
|
|
"sync"
|
2023-11-28 14:23:03 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2024-03-26 12:05:49 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
2023-11-28 14:23:03 +00:00
|
|
|
"github.com/ethereum/go-ethereum/event"
|
|
|
|
"github.com/status-im/status-go/appdatabase"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
|
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
|
|
|
"github.com/status-im/status-go/services/accounts/accountsevent"
|
2024-01-25 12:05:59 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/blockchainstate"
|
2024-03-26 12:05:49 +00:00
|
|
|
wallet_common "github.com/status-im/status-go/services/wallet/common"
|
2023-11-28 14:23:03 +00:00
|
|
|
"github.com/status-im/status-go/t/helpers"
|
|
|
|
"github.com/status-im/status-go/walletdatabase"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestController_watchAccountsChanges(t *testing.T) {
|
|
|
|
appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
accountsDB, err := accounts.NewDB(appDB)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
accountFeed := &event.Feed{}
|
|
|
|
|
2024-01-25 12:05:59 +00:00
|
|
|
bcstate := blockchainstate.NewBlockChainState()
|
2024-05-03 10:46:46 +00:00
|
|
|
SetMultiTransactionIDGenerator(StaticIDCounter()) // to have different multi-transaction IDs even with fast execution
|
2024-05-23 16:22:57 +00:00
|
|
|
transactionManager := NewTransactionManager(NewInMemMultiTransactionStorage(), nil, nil, nil, accountsDB, nil, nil)
|
2023-11-28 14:23:03 +00:00
|
|
|
c := NewTransferController(
|
|
|
|
walletDB,
|
|
|
|
accountsDB,
|
|
|
|
nil, // rpcClient
|
|
|
|
accountFeed,
|
2024-03-26 12:05:49 +00:00
|
|
|
nil, // transferFeed
|
|
|
|
transactionManager, // transactionManager
|
|
|
|
nil, // pendingTxManager
|
|
|
|
nil, // tokenManager
|
|
|
|
nil, // balanceCacher
|
2024-01-25 12:05:59 +00:00
|
|
|
bcstate,
|
2023-11-28 14:23:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
address := common.HexToAddress("0x1234")
|
|
|
|
chainID := uint64(777)
|
|
|
|
// Insert blocks
|
|
|
|
database := NewDB(walletDB)
|
2023-11-27 10:08:17 +00:00
|
|
|
err = database.SaveBlocks(chainID, []*DBHeader{
|
2023-11-28 14:23:03 +00:00
|
|
|
{
|
|
|
|
Number: big.NewInt(1),
|
|
|
|
Hash: common.Hash{1},
|
|
|
|
Network: chainID,
|
|
|
|
Address: address,
|
|
|
|
Loaded: false,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Insert transfers
|
|
|
|
err = saveTransfersMarkBlocksLoaded(walletDB, chainID, address, []Transfer{
|
|
|
|
{
|
|
|
|
ID: common.Hash{1},
|
|
|
|
BlockHash: common.Hash{1},
|
|
|
|
BlockNumber: big.NewInt(1),
|
|
|
|
Address: address,
|
|
|
|
NetworkID: chainID,
|
|
|
|
},
|
|
|
|
}, []*big.Int{big.NewInt(1)})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Insert block ranges
|
|
|
|
blockRangesDAO := &BlockRangeSequentialDAO{walletDB}
|
2023-11-27 10:08:17 +00:00
|
|
|
err = blockRangesDAO.upsertRange(chainID, address, newEthTokensBlockRanges())
|
2023-11-28 14:23:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2024-02-19 15:50:07 +00:00
|
|
|
ranges, _, err := blockRangesDAO.getBlockRange(chainID, address)
|
2023-11-28 14:23:03 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, ranges)
|
|
|
|
|
2024-03-26 12:05:49 +00:00
|
|
|
// Insert multitransactions
|
|
|
|
// Save address to accounts DB which transactions we want to preserve
|
|
|
|
counterparty := common.Address{0x1}
|
|
|
|
err = accountsDB.SaveOrUpdateAccounts([]*accounts.Account{
|
|
|
|
{Address: types.Address(counterparty), Chat: false, Wallet: true},
|
|
|
|
}, false)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Self multi transaction
|
2024-05-02 15:36:42 +00:00
|
|
|
midSelf, err := transactionManager.InsertMultiTransaction(NewMultiTransaction(
|
|
|
|
/* Timestamp: */ 1,
|
|
|
|
/* FromNetworkID: */ 1,
|
|
|
|
/* ToNetworkID: */ 1,
|
|
|
|
/* FromTxHash: */ common.Hash{},
|
|
|
|
/* ToTxHash: */ common.Hash{},
|
|
|
|
/* FromAddress: */ address,
|
|
|
|
/* ToAddress: */ address,
|
|
|
|
/* FromAsset: */ "ETH",
|
|
|
|
/* ToAsset: */ "DAI",
|
|
|
|
/* FromAmount: */ &hexutil.Big{},
|
|
|
|
/* ToAmount: */ &hexutil.Big{},
|
|
|
|
/* Type: */ MultiTransactionSend,
|
|
|
|
/* CrossTxID: */ "",
|
|
|
|
))
|
2024-03-26 12:05:49 +00:00
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
mtxs, err := transactionManager.GetMultiTransactions(context.Background(), []wallet_common.MultiTransactionIDType{midSelf})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, mtxs, 1)
|
|
|
|
|
|
|
|
// Send multi transaction
|
2024-05-02 15:36:42 +00:00
|
|
|
mt := NewMultiTransaction(
|
|
|
|
/* Timestamp: */ 2,
|
|
|
|
/* FromNetworkID: */ 1,
|
|
|
|
/* ToNetworkID: */ 1,
|
|
|
|
/* FromTxHash: */ common.Hash{},
|
|
|
|
/* ToTxHash: */ common.Hash{},
|
|
|
|
/* FromAddress: */ address,
|
|
|
|
/* ToAddress: */ counterparty,
|
|
|
|
/* FromAsset: */ "ETH",
|
|
|
|
/* ToAsset: */ "DAI",
|
|
|
|
/* FromAmount: */ &hexutil.Big{},
|
|
|
|
/* ToAmount: */ &hexutil.Big{},
|
|
|
|
/* Type: */ MultiTransactionSend,
|
|
|
|
/* CrossTxID: */ "",
|
|
|
|
)
|
2024-03-26 12:05:49 +00:00
|
|
|
mid, err := transactionManager.InsertMultiTransaction(mt)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
mtxs, err = transactionManager.GetMultiTransactions(context.Background(), []wallet_common.MultiTransactionIDType{midSelf, mid})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, mtxs, 2)
|
|
|
|
|
|
|
|
// Another Send multi-transaction where sender and receiver are inverted (both accounts are in accounts DB)
|
2024-05-02 15:36:42 +00:00
|
|
|
midReverse, err := transactionManager.InsertMultiTransaction(NewMultiTransaction(
|
|
|
|
/* Timestamp: */ mt.Timestamp+1,
|
|
|
|
/* FromNetworkID: */ 1,
|
|
|
|
/* ToNetworkID: */ 1,
|
|
|
|
/* FromTxHash: */ common.Hash{},
|
|
|
|
/* ToTxHash: */ common.Hash{},
|
|
|
|
/* FromAddress: */ mt.ToAddress,
|
|
|
|
/* ToAddress: */ mt.FromAddress,
|
|
|
|
/* FromAsset: */ mt.FromAsset,
|
|
|
|
/* ToAsset: */ mt.ToAsset,
|
|
|
|
/* FromAmount: */ mt.FromAmount,
|
|
|
|
/* ToAmount: */ mt.ToAmount,
|
|
|
|
/* Type: */ MultiTransactionSend,
|
|
|
|
/* CrossTxID: */ "",
|
|
|
|
))
|
2024-03-26 12:05:49 +00:00
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
mtxs, err = transactionManager.GetMultiTransactions(context.Background(), []wallet_common.MultiTransactionIDType{midSelf, mid, midReverse})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, mtxs, 3)
|
|
|
|
|
|
|
|
// Start watching accounts
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
wg.Add(1)
|
2024-03-03 07:59:21 +00:00
|
|
|
c.accWatcher = accountsevent.NewWatcher(c.accountsDB, c.accountFeed, func(changedAddresses []common.Address, eventType accountsevent.EventType, currentAddresses []common.Address) {
|
|
|
|
c.onAccountsChanged(changedAddresses, eventType, currentAddresses, []uint64{chainID})
|
|
|
|
|
|
|
|
// Quit channel event handler before destroying the channel
|
|
|
|
go func() {
|
2024-03-26 12:05:49 +00:00
|
|
|
defer wg.Done()
|
|
|
|
|
2024-03-03 07:59:21 +00:00
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
// Wait for DB to be cleaned up
|
|
|
|
c.accWatcher.Stop()
|
|
|
|
|
|
|
|
// Check that transfers, blocks and block ranges were deleted
|
|
|
|
transfers, err := database.GetTransfersByAddress(chainID, address, big.NewInt(2), 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, transfers, 0)
|
|
|
|
|
|
|
|
blocksDAO := &BlockDAO{walletDB}
|
|
|
|
block, err := blocksDAO.GetLastBlockByAddress(chainID, address, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, block)
|
|
|
|
|
|
|
|
ranges, _, err = blockRangesDAO.getBlockRange(chainID, address)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, ranges.eth.FirstKnown)
|
|
|
|
require.Nil(t, ranges.eth.LastKnown)
|
|
|
|
require.Nil(t, ranges.eth.Start)
|
|
|
|
require.Nil(t, ranges.tokens.FirstKnown)
|
|
|
|
require.Nil(t, ranges.tokens.LastKnown)
|
|
|
|
require.Nil(t, ranges.tokens.Start)
|
2024-03-26 12:05:49 +00:00
|
|
|
|
|
|
|
mtxs, err := transactionManager.GetMultiTransactions(context.Background(), []wallet_common.MultiTransactionIDType{mid, midSelf, midReverse})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, mtxs, 1)
|
|
|
|
require.Equal(t, midReverse, mtxs[0].ID)
|
2024-03-03 07:59:21 +00:00
|
|
|
}()
|
|
|
|
})
|
|
|
|
c.startAccountWatcher([]uint64{chainID})
|
2023-11-28 14:23:03 +00:00
|
|
|
|
|
|
|
// Watching accounts must start before sending event.
|
2024-03-03 07:59:21 +00:00
|
|
|
// To avoid running goroutine immediately and let the controller subscribe first,
|
|
|
|
// use any delay.
|
2023-11-28 14:23:03 +00:00
|
|
|
go func() {
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
|
|
|
|
accountFeed.Send(accountsevent.Event{
|
|
|
|
Type: accountsevent.EventTypeRemoved,
|
|
|
|
Accounts: []common.Address{address},
|
|
|
|
})
|
|
|
|
}()
|
2024-03-26 12:05:49 +00:00
|
|
|
|
|
|
|
wg.Wait()
|
2023-11-28 14:23:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestController_cleanupAccountLeftovers(t *testing.T) {
|
|
|
|
appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
accountsDB, err := accounts.NewDB(appDB)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
removedAddr := common.HexToAddress("0x5678")
|
|
|
|
existingAddr := types.HexToAddress("0x1234")
|
|
|
|
accounts := []*accounts.Account{
|
|
|
|
{Address: existingAddr, Chat: false, Wallet: true},
|
|
|
|
}
|
|
|
|
err = accountsDB.SaveOrUpdateAccounts(accounts, false)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
storedAccs, err := accountsDB.GetWalletAddresses()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, storedAccs, 1)
|
|
|
|
|
2024-05-23 16:22:57 +00:00
|
|
|
transactionManager := NewTransactionManager(NewMultiTransactionDB(walletDB), nil, nil, nil, accountsDB, nil, nil)
|
2024-01-25 12:05:59 +00:00
|
|
|
bcstate := blockchainstate.NewBlockChainState()
|
2023-11-28 14:23:03 +00:00
|
|
|
c := NewTransferController(
|
|
|
|
walletDB,
|
|
|
|
accountsDB,
|
2024-03-26 12:05:49 +00:00
|
|
|
nil, // rpcClient
|
|
|
|
nil, // accountFeed
|
|
|
|
nil, // transferFeed
|
|
|
|
transactionManager, // transactionManager
|
|
|
|
nil, // pendingTxManager
|
|
|
|
nil, // tokenManager
|
|
|
|
nil, // balanceCacher
|
2024-01-25 12:05:59 +00:00
|
|
|
bcstate,
|
2023-11-28 14:23:03 +00:00
|
|
|
)
|
|
|
|
chainID := uint64(777)
|
|
|
|
// Insert blocks
|
|
|
|
database := NewDB(walletDB)
|
2023-11-27 10:08:17 +00:00
|
|
|
err = database.SaveBlocks(chainID, []*DBHeader{
|
2023-11-28 14:23:03 +00:00
|
|
|
{
|
|
|
|
Number: big.NewInt(1),
|
|
|
|
Hash: common.Hash{1},
|
|
|
|
Network: chainID,
|
|
|
|
Address: removedAddr,
|
|
|
|
Loaded: false,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
2023-11-27 10:08:17 +00:00
|
|
|
err = database.SaveBlocks(chainID, []*DBHeader{
|
2023-11-28 14:23:03 +00:00
|
|
|
{
|
|
|
|
Number: big.NewInt(2),
|
|
|
|
Hash: common.Hash{2},
|
|
|
|
Network: chainID,
|
|
|
|
Address: common.Address(existingAddr),
|
|
|
|
Loaded: false,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
blocksDAO := &BlockDAO{walletDB}
|
|
|
|
block, err := blocksDAO.GetLastBlockByAddress(chainID, removedAddr, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, block)
|
|
|
|
block, err = blocksDAO.GetLastBlockByAddress(chainID, common.Address(existingAddr), 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, block)
|
|
|
|
|
|
|
|
// Insert transfers
|
|
|
|
err = saveTransfersMarkBlocksLoaded(walletDB, chainID, removedAddr, []Transfer{
|
|
|
|
{
|
|
|
|
ID: common.Hash{1},
|
|
|
|
BlockHash: common.Hash{1},
|
|
|
|
BlockNumber: big.NewInt(1),
|
|
|
|
Address: removedAddr,
|
|
|
|
NetworkID: chainID,
|
|
|
|
},
|
|
|
|
}, []*big.Int{big.NewInt(1)})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
err = saveTransfersMarkBlocksLoaded(walletDB, chainID, common.Address(existingAddr), []Transfer{
|
|
|
|
{
|
|
|
|
ID: common.Hash{2},
|
|
|
|
BlockHash: common.Hash{2},
|
|
|
|
BlockNumber: big.NewInt(2),
|
|
|
|
Address: common.Address(existingAddr),
|
|
|
|
NetworkID: chainID,
|
|
|
|
},
|
|
|
|
}, []*big.Int{big.NewInt(2)})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
err = c.cleanupAccountsLeftovers()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Check that transfers and blocks of removed account were deleted
|
|
|
|
transfers, err := database.GetTransfers(chainID, big.NewInt(1), big.NewInt(2))
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, transfers, 1)
|
|
|
|
require.Equal(t, transfers[0].Address, common.Address(existingAddr))
|
|
|
|
|
|
|
|
block, err = blocksDAO.GetLastBlockByAddress(chainID, removedAddr, 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, block)
|
|
|
|
|
|
|
|
// Make sure that transfers and blocks of existing account were not deleted
|
|
|
|
existingBlock, err := blocksDAO.GetLastBlockByAddress(chainID, common.Address(existingAddr), 1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, existingBlock)
|
|
|
|
}
|