fix(wallet): cleanup multi_transactions table on account removed
Updates #4937
This commit is contained in:
parent
c21e6430a2
commit
e9a2f19c17
|
@ -418,13 +418,14 @@ func Test_removeBalanceHistoryOnEventAccountRemoved(t *testing.T) {
|
||||||
Accounts: []common.Address{address},
|
Accounts: []common.Address{address},
|
||||||
})
|
})
|
||||||
|
|
||||||
require.NoError(t, utils.Eventually(func() error {
|
err := utils.Eventually(func() error {
|
||||||
entries, err := database.getNewerThan(&assetIdentity{1, []common.Address{address}, "ETH"}, 0)
|
entries, err := database.getNewerThan(&assetIdentity{1, []common.Address{address}, "ETH"}, 0)
|
||||||
if err == nil && len(entries) == 0 {
|
if err == nil && len(entries) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("data is not removed")
|
return errors.New("data is not removed")
|
||||||
}, 100*time.Millisecond, 10*time.Millisecond))
|
}, 100*time.Millisecond, 10*time.Millisecond)
|
||||||
|
require.NoError(t, err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
group.Wait()
|
group.Wait()
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
|
||||||
"github.com/status-im/status-go/appdatabase"
|
"github.com/status-im/status-go/appdatabase"
|
||||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
|
@ -25,7 +27,9 @@ func TestKeycardPairingsFile(t *testing.T) {
|
||||||
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
|
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
service := NewService(db, accountsDb, appDB, &rpc.Client{NetworkManager: network.NewManager(db)}, nil, nil, nil, nil, ¶ms.NodeConfig{}, nil, nil, nil, nil, nil)
|
accountFeed := &event.Feed{}
|
||||||
|
|
||||||
|
service := NewService(db, accountsDb, appDB, &rpc.Client{NetworkManager: network.NewManager(db)}, accountFeed, nil, nil, nil, ¶ms.NodeConfig{}, nil, nil, nil, nil, nil)
|
||||||
|
|
||||||
data, err := service.KeycardPairings().GetPairingsJSONFileContent()
|
data, err := service.KeycardPairings().GetPairingsJSONFileContent()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -266,6 +266,11 @@ func (c *Controller) cleanUpRemovedAccount(address common.Address) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to delete blocks ranges sequential", "error", err)
|
log.Error("Failed to delete blocks ranges sequential", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = c.transactionManager.removeMultiTransactionByAddress(address)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to delete multitransactions", "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) cleanupAccountsLeftovers() error {
|
func (c *Controller) cleanupAccountsLeftovers() error {
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
package transfer
|
package transfer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/status-im/status-go/appdatabase"
|
"github.com/status-im/status-go/appdatabase"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||||
"github.com/status-im/status-go/services/accounts/accountsevent"
|
"github.com/status-im/status-go/services/accounts/accountsevent"
|
||||||
"github.com/status-im/status-go/services/wallet/blockchainstate"
|
"github.com/status-im/status-go/services/wallet/blockchainstate"
|
||||||
|
wallet_common "github.com/status-im/status-go/services/wallet/common"
|
||||||
"github.com/status-im/status-go/t/helpers"
|
"github.com/status-im/status-go/t/helpers"
|
||||||
"github.com/status-im/status-go/walletdatabase"
|
"github.com/status-im/status-go/walletdatabase"
|
||||||
)
|
)
|
||||||
|
@ -31,13 +35,14 @@ func TestController_watchAccountsChanges(t *testing.T) {
|
||||||
accountFeed := &event.Feed{}
|
accountFeed := &event.Feed{}
|
||||||
|
|
||||||
bcstate := blockchainstate.NewBlockChainState()
|
bcstate := blockchainstate.NewBlockChainState()
|
||||||
|
transactionManager := NewTransactionManager(walletDB, nil, nil, nil, accountsDB, nil, nil)
|
||||||
c := NewTransferController(
|
c := NewTransferController(
|
||||||
walletDB,
|
walletDB,
|
||||||
accountsDB,
|
accountsDB,
|
||||||
nil, // rpcClient
|
nil, // rpcClient
|
||||||
accountFeed,
|
accountFeed,
|
||||||
nil, // transferFeed
|
nil, // transferFeed
|
||||||
nil, // transactionManager
|
transactionManager, // transactionManager
|
||||||
nil, // pendingTxManager
|
nil, // pendingTxManager
|
||||||
nil, // tokenManager
|
nil, // tokenManager
|
||||||
nil, // balanceCacher
|
nil, // balanceCacher
|
||||||
|
@ -80,11 +85,73 @@ func TestController_watchAccountsChanges(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, ranges)
|
require.NotNil(t, ranges)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
midSelf, err := transactionManager.InsertMultiTransaction(&MultiTransaction{
|
||||||
|
FromAddress: address,
|
||||||
|
ToAddress: address,
|
||||||
|
FromAsset: "ETH",
|
||||||
|
ToAsset: "DAI",
|
||||||
|
FromAmount: &hexutil.Big{},
|
||||||
|
ToAmount: &hexutil.Big{},
|
||||||
|
Timestamp: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
mt := &MultiTransaction{
|
||||||
|
FromAddress: address,
|
||||||
|
ToAddress: counterparty,
|
||||||
|
FromAsset: "ETH",
|
||||||
|
ToAsset: "DAI",
|
||||||
|
FromAmount: &hexutil.Big{},
|
||||||
|
ToAmount: &hexutil.Big{},
|
||||||
|
Timestamp: 2,
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
midReverse, err := transactionManager.InsertMultiTransaction(&MultiTransaction{
|
||||||
|
FromAddress: mt.ToAddress,
|
||||||
|
ToAddress: mt.FromAddress,
|
||||||
|
FromAsset: mt.FromAsset,
|
||||||
|
ToAsset: mt.ToAsset,
|
||||||
|
FromAmount: mt.FromAmount,
|
||||||
|
ToAmount: mt.ToAmount,
|
||||||
|
Timestamp: mt.Timestamp + 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
c.accWatcher = accountsevent.NewWatcher(c.accountsDB, c.accountFeed, func(changedAddresses []common.Address, eventType accountsevent.EventType, currentAddresses []common.Address) {
|
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})
|
c.onAccountsChanged(changedAddresses, eventType, currentAddresses, []uint64{chainID})
|
||||||
|
|
||||||
// Quit channel event handler before destroying the channel
|
// Quit channel event handler before destroying the channel
|
||||||
go func() {
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
time.Sleep(1 * time.Millisecond)
|
||||||
// Wait for DB to be cleaned up
|
// Wait for DB to be cleaned up
|
||||||
c.accWatcher.Stop()
|
c.accWatcher.Stop()
|
||||||
|
@ -107,6 +174,11 @@ func TestController_watchAccountsChanges(t *testing.T) {
|
||||||
require.Nil(t, ranges.tokens.FirstKnown)
|
require.Nil(t, ranges.tokens.FirstKnown)
|
||||||
require.Nil(t, ranges.tokens.LastKnown)
|
require.Nil(t, ranges.tokens.LastKnown)
|
||||||
require.Nil(t, ranges.tokens.Start)
|
require.Nil(t, ranges.tokens.Start)
|
||||||
|
|
||||||
|
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)
|
||||||
}()
|
}()
|
||||||
})
|
})
|
||||||
c.startAccountWatcher([]uint64{chainID})
|
c.startAccountWatcher([]uint64{chainID})
|
||||||
|
@ -122,6 +194,8 @@ func TestController_watchAccountsChanges(t *testing.T) {
|
||||||
Accounts: []common.Address{address},
|
Accounts: []common.Address{address},
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestController_cleanupAccountLeftovers(t *testing.T) {
|
func TestController_cleanupAccountLeftovers(t *testing.T) {
|
||||||
|
@ -146,6 +220,7 @@ func TestController_cleanupAccountLeftovers(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, storedAccs, 1)
|
require.Len(t, storedAccs, 1)
|
||||||
|
|
||||||
|
transactionManager := NewTransactionManager(walletDB, nil, nil, nil, accountsDB, nil, nil)
|
||||||
bcstate := blockchainstate.NewBlockChainState()
|
bcstate := blockchainstate.NewBlockChainState()
|
||||||
c := NewTransferController(
|
c := NewTransferController(
|
||||||
walletDB,
|
walletDB,
|
||||||
|
@ -153,7 +228,7 @@ func TestController_cleanupAccountLeftovers(t *testing.T) {
|
||||||
nil, // rpcClient
|
nil, // rpcClient
|
||||||
nil, // accountFeed
|
nil, // accountFeed
|
||||||
nil, // transferFeed
|
nil, // transferFeed
|
||||||
nil, // transactionManager
|
transactionManager, // transactionManager
|
||||||
nil, // pendingTxManager
|
nil, // pendingTxManager
|
||||||
nil, // tokenManager
|
nil, // tokenManager
|
||||||
nil, // balanceCacher
|
nil, // balanceCacher
|
||||||
|
|
|
@ -458,3 +458,67 @@ func (tm *TransactionManager) getVerifiedWalletAccount(address, password string)
|
||||||
AccountKey: key,
|
AccountKey: key,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tm *TransactionManager) removeMultiTransactionByAddress(address common.Address) error {
|
||||||
|
// We must not remove those transactions, where from_address and to_address are different and both are stored in accounts DB
|
||||||
|
// and one of them is equal to the address, as we want to keep the records for the other address
|
||||||
|
// That is why we don't use cascade delete here with references to transfers table, as we might have 2 records in multi_transactions
|
||||||
|
// for the same transaction, one for each address
|
||||||
|
|
||||||
|
stmt, err := tm.db.Prepare(`SELECT rowid, from_address, to_address
|
||||||
|
FROM multi_transactions
|
||||||
|
WHERE from_address=? OR to_address=?`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := stmt.Query(address, address)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
rowIDs := make([]int, 0)
|
||||||
|
rowID, fromAddress, toAddress := 0, common.Address{}, common.Address{}
|
||||||
|
for rows.Next() {
|
||||||
|
err = rows.Scan(&rowID, &fromAddress, &toAddress)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to scan row", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove self transactions as well, leave only those where we have the counterparty in accounts DB
|
||||||
|
if fromAddress != toAddress {
|
||||||
|
// If both addresses are stored in accounts DB, we don't remove the record
|
||||||
|
var addressToCheck common.Address
|
||||||
|
if fromAddress == address {
|
||||||
|
addressToCheck = toAddress
|
||||||
|
} else {
|
||||||
|
addressToCheck = fromAddress
|
||||||
|
}
|
||||||
|
counterpartyExists, err := tm.accountsDB.AddressExists(types.Address(addressToCheck))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to query accounts db for a given address", "address", address, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip removal if counterparty is in accounts DB and removed address is not sender
|
||||||
|
if counterpartyExists && address != fromAddress {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rowIDs = append(rowIDs, rowID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rowIDs) > 0 {
|
||||||
|
for _, rowID := range rowIDs {
|
||||||
|
_, err = tm.db.Exec(`DELETE FROM multi_transactions WHERE rowid=?`, rowID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to remove multitransaction", "rowid", rowID, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue