chore(wallet) remove activity filter dependency on accounts

Require that the activity filter is passed at least one account address
to extract type sent/received

Updates status-desktop #11980
This commit is contained in:
Stefan 2023-09-12 12:19:15 +02:00 committed by Stefan Dunca
parent 494c6707ba
commit ad971278d9
9 changed files with 149 additions and 305 deletions

View File

@ -1,15 +0,0 @@
package accounts
import (
"testing"
"github.com/stretchr/testify/require"
)
func MockTestAccounts(tb testing.TB, d *Database, accounts []*Account) {
err := d.SaveOrUpdateAccounts(accounts, false)
require.NoError(tb, err)
res, err := d.GetActiveAccounts()
require.NoError(tb, err)
require.Equal(tb, accounts[0].Address, res[0].Address)
}

View File

@ -18,7 +18,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/thirdparty"
@ -37,8 +36,6 @@ const (
PendingTransactionPT PendingTransactionPT
) )
const keypairAccountsTable = "keypairs_accounts"
var ( var (
ZeroAddress = eth.Address{} ZeroAddress = eth.Address{}
) )
@ -322,8 +319,7 @@ const (
var queryFormatString string var queryFormatString string
type FilterDependencies struct { type FilterDependencies struct {
db *sql.DB db *sql.DB
accountsDb *accounts.Database
// use token.TokenType, token.ChainID and token.Address to find the available symbol // use token.TokenType, token.ChainID and token.Address to find the available symbol
tokenSymbol func(token Token) string tokenSymbol func(token Token) string
// use the chainID and symbol to look up token.TokenType and token.Address. Return nil if not found // use the chainID and symbol to look up token.TokenType and token.Address. Return nil if not found
@ -333,10 +329,18 @@ type FilterDependencies struct {
// getActivityEntries queries the transfers, pending_transactions, and multi_transactions tables based on filter parameters and arguments // getActivityEntries queries the transfers, pending_transactions, and multi_transactions tables based on filter parameters and arguments
// it returns metadata for all entries ordered by timestamp column // it returns metadata for all entries ordered by timestamp column
// //
// addresses are mandatory and used to detect activity types SendAT and ReceiveAT for transfers entries
//
// allAddresses optimization indicates if the passed addresses include all the owners in the wallet DB
//
// Adding a no-limit option was never considered or required. // Adding a no-limit option was never considered or required.
// //
// TODO: optimization: consider implementing nullable []byte instead of using strings for addresses or insert binary (X'...' syntax) directly into the query // TODO: optimization: consider implementing nullable []byte instead of using strings for addresses or insert binary (X'...' syntax) directly into the query
func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses []eth.Address, chainIDs []common.ChainID, filter Filter, offset int, limit int) ([]Entry, error) { func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses []eth.Address, allAddresses bool, chainIDs []common.ChainID, filter Filter, offset int, limit int) ([]Entry, error) {
if len(addresses) == 0 {
return nil, errors.New("no addresses provided")
}
includeAllTokenTypeAssets := len(filter.Assets) == 0 && !filter.FilterOutAssets includeAllTokenTypeAssets := len(filter.Assets) == 0 && !filter.FilterOutAssets
// Used for symbol bearing tables multi_transactions and pending_transactions // Used for symbol bearing tables multi_transactions and pending_transactions
@ -378,7 +382,6 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
startFilterDisabled := !(filter.Period.StartTimestamp > 0) startFilterDisabled := !(filter.Period.StartTimestamp > 0)
endFilterDisabled := !(filter.Period.EndTimestamp > 0) endFilterDisabled := !(filter.Period.EndTimestamp > 0)
filterActivityTypeAll := len(filter.Types) == 0 filterActivityTypeAll := len(filter.Types) == 0
filterAllAddresses := len(addresses) == 0
filterAllToAddresses := len(filter.CounterpartyAddresses) == 0 filterAllToAddresses := len(filter.CounterpartyAddresses) == 0
includeAllStatuses := len(filter.Statuses) == 0 includeAllStatuses := len(filter.Statuses) == 0
@ -393,10 +396,7 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
filterStatusFinalized = sliceContains(filter.Statuses, FinalizedAS) filterStatusFinalized = sliceContains(filter.Statuses, FinalizedAS)
} }
involvedAddresses := noEntriesInTmpTableSQLValues involvedAddresses := joinAddresses(addresses)
if !filterAllAddresses {
involvedAddresses = joinAddresses(addresses)
}
toAddresses := noEntriesInTmpTableSQLValues toAddresses := noEntriesInTmpTableSQLValues
if !filterAllToAddresses { if !filterAllToAddresses {
toAddresses = joinAddresses(filter.CounterpartyAddresses) toAddresses = joinAddresses(filter.CounterpartyAddresses)
@ -407,23 +407,24 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
return strconv.Itoa(int(t)) return strconv.Itoa(int(t))
}) })
// Since the filter query needs addresses which are in a different database, we need to update the queryString := fmt.Sprintf(queryFormatString, involvedAddresses, toAddresses, assetsTokenCodes, assetsERC20, networks,
// keypairs_accounts table in the current database with the latest addresses from the accounts database joinedMTTypes)
err := updateKeypairsAccountsTable(deps.accountsDb, deps.db)
// The duplicated temporary table UNION with CTE acts as an optimization
// As soon as we use filter_addresses CTE or filter_addresses_table temp table
// or switch them alternatively for JOIN or IN clauses the performance drops significantly
_, err := deps.db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS filter_addresses_table; CREATE TEMP TABLE filter_addresses_table (address VARCHAR PRIMARY KEY); INSERT OR IGNORE INTO filter_addresses_table (address) VALUES %s;\n", involvedAddresses))
if err != nil { if err != nil {
return nil, err return nil, err
} }
queryString := fmt.Sprintf(queryFormatString, keypairAccountsTable, involvedAddresses, toAddresses, assetsTokenCodes, assetsERC20, networks,
joinedMTTypes)
rows, err := deps.db.QueryContext(ctx, queryString, rows, err := deps.db.QueryContext(ctx, queryString,
startFilterDisabled, filter.Period.StartTimestamp, endFilterDisabled, filter.Period.EndTimestamp, startFilterDisabled, filter.Period.StartTimestamp, endFilterDisabled, filter.Period.EndTimestamp,
filterActivityTypeAll, sliceContains(filter.Types, SendAT), sliceContains(filter.Types, ReceiveAT), filterActivityTypeAll, sliceContains(filter.Types, SendAT), sliceContains(filter.Types, ReceiveAT),
sliceContains(filter.Types, ContractDeploymentAT), sliceContains(filter.Types, MintAT), sliceContains(filter.Types, ContractDeploymentAT), sliceContains(filter.Types, MintAT),
transfer.MultiTransactionSend, transfer.MultiTransactionSend,
fromTrType, toTrType, fromTrType, toTrType,
filterAllAddresses, filterAllToAddresses, allAddresses, filterAllToAddresses,
includeAllStatuses, filterStatusCompleted, filterStatusFailed, filterStatusFinalized, filterStatusPending, includeAllStatuses, filterStatusCompleted, filterStatusFailed, filterStatusFinalized, filterStatusPending,
FailedAS, CompleteAS, PendingAS, FailedAS, CompleteAS, PendingAS,
includeAllTokenTypeAssets, includeAllTokenTypeAssets,
@ -689,48 +690,6 @@ func contractTypeFromDBType(dbType string) (transferType *TransferType) {
return transferType return transferType
} }
func updateKeypairsAccountsTable(accountsDb *accounts.Database, db *sql.DB) error {
_, err := db.Exec(fmt.Sprintf("CREATE TEMP TABLE IF NOT EXISTS %s (address VARCHAR PRIMARY KEY)",
keypairAccountsTable))
if err != nil {
log.Error("failed to create 'keypairs_accounts' table", "err", err)
return err
}
// TODO: remove dependency on accounts table by removing"all accounts filter" optimization; see #11980
if accountsDb == nil {
return nil
}
addresses, err := accountsDb.GetWalletAddresses()
if err != nil {
log.Error("failed to get wallet addresses", "err", err)
return err
}
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
_ = tx.Rollback()
}()
for _, address := range addresses {
_, err = tx.Exec(fmt.Sprintf("INSERT OR IGNORE INTO %s (address) VALUES (?)", keypairAccountsTable), address)
if err != nil {
log.Error("failed to insert wallet addresses", "err", err)
return err
}
}
return nil
}
// lookupAndFillInTokens ignores NFTs // lookupAndFillInTokens ignores NFTs
func lookupAndFillInTokens(deps FilterDependencies, tokenOut *Token, tokenIn *Token) (symbolOut *string, symbolIn *string) { func lookupAndFillInTokens(deps FilterDependencies, tokenOut *Token, tokenIn *Token) (symbolOut *string, symbolIn *string) {
if tokenOut != nil && tokenOut.TokenID == nil { if tokenOut != nil && tokenOut.TokenID == nil {

View File

@ -3,13 +3,9 @@ package activity
import ( import (
"context" "context"
"database/sql" "database/sql"
"fmt"
"math/big" "math/big"
"testing" "testing"
"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/wallet/common" "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/testutils" "github.com/status-im/status-go/services/wallet/testutils"
"github.com/status-im/status-go/services/wallet/transfer" "github.com/status-im/status-go/services/wallet/transfer"
@ -40,28 +36,20 @@ func tokenFromSymbol(chainID *common.ChainID, symbol string) *Token {
} }
func setupTestActivityDBStorageChoice(tb testing.TB, inMemory bool) (deps FilterDependencies, close func()) { func setupTestActivityDBStorageChoice(tb testing.TB, inMemory bool) (deps FilterDependencies, close func()) {
var db, appDb *sql.DB var db *sql.DB
var err error var err error
cleanupDB := func() error { return nil } cleanupDB := func() error { return nil }
cleanupWalletDB := func() error { return nil } cleanupWalletDB := func() error { return nil }
if inMemory { if inMemory {
db, err = helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) db, err = helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(tb, err) require.NoError(tb, err)
appDb, err = helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(tb, err)
} else { } else {
db, cleanupWalletDB, err = helpers.SetupTestSQLDB(walletdatabase.DbInitializer{}, "wallet-activity-tests") db, cleanupWalletDB, err = helpers.SetupTestSQLDB(walletdatabase.DbInitializer{}, "wallet-activity-tests")
require.NoError(tb, err) require.NoError(tb, err)
appDb, cleanupDB, err = helpers.SetupTestSQLDB(appdatabase.DbInitializer{}, "wallet-activity-tests")
require.NoError(tb, err)
} }
accountsDb, err := accounts.NewDB(appDb)
require.NoError(tb, err)
deps = FilterDependencies{ deps = FilterDependencies{
db: db, db: db,
accountsDb: accountsDb,
tokenSymbol: func(token Token) string { tokenSymbol: func(token Token) string {
switch token.TokenType { switch token.TokenType {
case Native: case Native:
@ -112,17 +100,6 @@ type testData struct {
nextIndex int nextIndex int
} }
func mockTestAccountsWithAddresses(tb testing.TB, db *accounts.Database, addresses []eth.Address) {
mockedAccounts := []*accounts.Account{}
for _, address := range addresses {
mockedAccounts = append(mockedAccounts, &accounts.Account{
Address: types.Address(address),
Type: accounts.AccountTypeWatch,
})
}
accounts.MockTestAccounts(tb, db, mockedAccounts)
}
// Generates and adds to the DB 7 transfers and 2 multitransactions. // Generates and adds to the DB 7 transfers and 2 multitransactions.
// There are only 4 extractable activity entries (transactions + multi-transactions) with timestamps 1-4. The others are associated with a multi-transaction // There are only 4 extractable activity entries (transactions + multi-transactions) with timestamps 1-4. The others are associated with a multi-transaction
func fillTestData(t *testing.T, db *sql.DB) (td testData, fromAddresses, toAddresses []eth.Address) { func fillTestData(t *testing.T, db *sql.DB) (td testData, fromAddresses, toAddresses []eth.Address) {
@ -203,7 +180,7 @@ func TestGetActivityEntriesAll(t *testing.T) {
td, fromAddresses, toAddresses := fillTestData(t, deps.db) td, fromAddresses, toAddresses := fillTestData(t, deps.db)
var filter Filter var filter Filter
entries, err := getActivityEntries(context.Background(), deps, append(toAddresses, fromAddresses...), []common.ChainID{}, filter, 0, 10) entries, err := getActivityEntries(context.Background(), deps, append(toAddresses, fromAddresses...), true, []common.ChainID{}, filter, 0, 10)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 4, len(entries)) require.Equal(t, 4, len(entries))
@ -295,9 +272,7 @@ func TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB(t *testin
defer close() defer close()
// Add 4 extractable transactions with timestamps 1-4 // Add 4 extractable transactions with timestamps 1-4
td, fromAddresses, toAddresses := fillTestData(t, deps.db) td, _, _ := fillTestData(t, deps.db)
mockTestAccountsWithAddresses(t, deps.accountsDb, append(fromAddresses, toAddresses...))
// Add another transaction with owner reversed // Add another transaction with owner reversed
senderTr := td.tr1 senderTr := td.tr1
@ -307,7 +282,7 @@ func TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB(t *testin
transfer.InsertTestTransfer(t, deps.db, senderTr.From, &senderTr) transfer.InsertTestTransfer(t, deps.db, senderTr.From, &senderTr)
var filter Filter var filter Filter
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{td.tr1.To, senderTr.From}, []common.ChainID{}, filter, 0, 10) entries, err := getActivityEntries(context.Background(), deps, []eth.Address{td.tr1.To, senderTr.From}, false, []common.ChainID{}, filter, 0, 10)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 2, len(entries)) require.Equal(t, 2, len(entries))
@ -335,13 +310,13 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i]) transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
} }
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...)) allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
// Test start only // Test start only
var filter Filter var filter Filter
filter.Period.StartTimestamp = td.multiTx1.Timestamp filter.Period.StartTimestamp = td.multiTx1.Timestamp
filter.Period.EndTimestamp = NoLimitTimestampForPeriod filter.Period.EndTimestamp = NoLimitTimestampForPeriod
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 8, len(entries)) require.Equal(t, 8, len(entries))
@ -387,7 +362,7 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
// Test complete interval // Test complete interval
filter.Period.EndTimestamp = trs[2].Timestamp filter.Period.EndTimestamp = trs[2].Timestamp
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 5, len(entries)) require.Equal(t, 5, len(entries))
@ -433,7 +408,7 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
// Test end only // Test end only
filter.Period.StartTimestamp = NoLimitTimestampForPeriod filter.Period.StartTimestamp = NoLimitTimestampForPeriod
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 7, len(entries)) require.Equal(t, 7, len(entries))
// Check start and end content // Check start and end content
@ -487,18 +462,18 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i]) transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
} }
mockTestAccountsWithAddresses(t, deps.accountsDb, append(fromTrs, toTrs...)) allAddresses := append(fromTrs, toTrs...)
var filter Filter var filter Filter
// Get all // Get all
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 5) entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 5)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 5, len(entries)) require.Equal(t, 5, len(entries))
// Get time based interval // Get time based interval
filter.Period.StartTimestamp = trs[2].Timestamp filter.Period.StartTimestamp = trs[2].Timestamp
filter.Period.EndTimestamp = trs[8].Timestamp filter.Period.EndTimestamp = trs[8].Timestamp
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 3) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 3)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(entries)) require.Equal(t, 3, len(entries))
// Check start and end content // Check start and end content
@ -542,7 +517,7 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
}, entries[2]) }, entries[2])
// Move window 2 entries forward // Move window 2 entries forward
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 2, 3) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 2, 3)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(entries)) require.Equal(t, 3, len(entries))
// Check start and end content // Check start and end content
@ -586,7 +561,7 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
}, entries[2]) }, entries[2])
// Move window 4 more entries to test filter cap // Move window 4 more entries to test filter cap
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 6, 3) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 6, 3)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(entries)) require.Equal(t, 1, len(entries))
// Check start and end content // Check start and end content
@ -638,16 +613,18 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
defer close() defer close()
// Adds 4 extractable transactions // Adds 4 extractable transactions
td, _, _ := fillTestData(t, deps.db) td, tdFromAdds, tdToAddrs := fillTestData(t, deps.db)
// Add 5 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge and two MultiTransactionSend // Add 5 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge and two MultiTransactionSend
multiTxs := make([]transfer.TestMultiTransaction, 5) multiTxs := make([]transfer.TestMultiTransaction, 5)
trs, _, _ := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, len(multiTxs)*2) trs, fromAddrs, toAddrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, len(multiTxs)*2)
multiTxs[0] = transfer.GenerateTestBridgeMultiTransaction(trs[0], trs[1]) multiTxs[0] = transfer.GenerateTestBridgeMultiTransaction(trs[0], trs[1])
multiTxs[1] = transfer.GenerateTestSwapMultiTransaction(trs[2], testutils.SntSymbol, 100) // trs[3] multiTxs[1] = transfer.GenerateTestSwapMultiTransaction(trs[2], testutils.SntSymbol, 100) // trs[3]
multiTxs[2] = transfer.GenerateTestSendMultiTransaction(trs[4]) // trs[5] multiTxs[2] = transfer.GenerateTestSendMultiTransaction(trs[4]) // trs[5]
multiTxs[3] = transfer.GenerateTestBridgeMultiTransaction(trs[6], trs[7]) multiTxs[3] = transfer.GenerateTestBridgeMultiTransaction(trs[6], trs[7])
multiTxs[4] = transfer.GenerateTestSendMultiTransaction(trs[8]) // trs[9] multiTxs[4] = transfer.GenerateTestSendMultiTransaction(trs[8]) // trs[9]
allAddresses := append(append(append(tdFromAdds, tdToAddrs...), fromAddrs...), toAddrs...)
var lastMT transfer.MultiTransactionIDType var lastMT transfer.MultiTransactionIDType
for i := range trs { for i := range trs {
if i%2 == 0 { if i%2 == 0 {
@ -677,12 +654,12 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
filter.Types = allActivityTypesFilter() filter.Types = allActivityTypesFilter()
// Set tr1 to Receive and pendingTr to Send; rest of two MT remain default Send // Set tr1 to Receive and pendingTr to Send; rest of two MT remain default Send
addresses := []eth.Address{td.tr1.To, td.pendingTr.From, td.multiTx1.FromAddress, td.multiTx2.FromAddress, trs[0].From, trs[2].From, trs[4].From, trs[6].From, trs[8].From, trsSpecial[0].To, trsSpecial[1].From} addresses := []eth.Address{td.tr1.To, td.pendingTr.From, td.multiTx1.FromAddress, td.multiTx2.FromAddress, trs[0].From, trs[2].From, trs[4].From, trs[6].From, trs[8].From, trsSpecial[0].To, trsSpecial[1].From}
entries, err := getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) entries, err := getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 11, len(entries)) require.Equal(t, 11, len(entries))
filter.Types = []Type{SendAT, SwapAT} filter.Types = []Type{SendAT, SwapAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
// 3 from td Send + 2 trs MT Send + 1 (swap) // 3 from td Send + 2 trs MT Send + 1 (swap)
require.Equal(t, 6, len(entries)) require.Equal(t, 6, len(entries))
@ -697,7 +674,7 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
require.Equal(t, 0, bridgeCount) require.Equal(t, 0, bridgeCount)
filter.Types = []Type{BridgeAT, ReceiveAT} filter.Types = []Type{BridgeAT, ReceiveAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(entries)) require.Equal(t, 3, len(entries))
@ -710,7 +687,7 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
require.Equal(t, 2, bridgeCount) require.Equal(t, 2, bridgeCount)
filter.Types = []Type{MintAT} filter.Types = []Type{MintAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(entries)) require.Equal(t, 1, len(entries))
@ -723,7 +700,7 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
require.Equal(t, 0, bridgeCount) require.Equal(t, 0, bridgeCount)
filter.Types = []Type{ContractDeploymentAT} filter.Types = []Type{ContractDeploymentAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(entries)) require.Equal(t, 1, len(entries))
@ -737,7 +714,7 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
// Filter with all addresses regression // Filter with all addresses regression
filter.Types = []Type{SendAT} filter.Types = []Type{SendAT}
entries, err = getActivityEntries(context.Background(), deps, allAddressesFilter(), []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 5, len(entries)) require.Equal(t, 5, len(entries))
} }
@ -753,18 +730,17 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
transfer.InsertTestTransfer(t, deps.db, trs[i].From, &trs[i]) transfer.InsertTestTransfer(t, deps.db, trs[i].From, &trs[i])
} }
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...)) allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter var filter Filter
addressesFilter := allAddressesFilter() entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
entries, err := getActivityEntries(context.Background(), deps, addressesFilter, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 10, len(entries)) require.Equal(t, 10, len(entries))
addressesFilter = []eth.Address{td.multiTx1.ToAddress, td.multiTx2.FromAddress, trs[1].From, trs[4].From, trs[3].To} addressesFilter := []eth.Address{td.multiTx1.ToAddress, td.multiTx2.FromAddress, trs[1].From, trs[4].From, trs[3].To}
// The td.multiTx1.ToAddress and trs[3].To are missing not having them as owner address // The td.multiTx1.ToAddress and trs[3].To are missing not having them as owner address
entries, err = getActivityEntries(context.Background(), deps, addressesFilter, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, addressesFilter, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(entries)) require.Equal(t, 3, len(entries))
require.Equal(t, Entry{ require.Equal(t, Entry{
@ -845,16 +821,16 @@ func TestGetActivityEntriesFilterByStatus(t *testing.T) {
} }
} }
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...)) allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter var filter Filter
filter.Statuses = allActivityStatusesFilter() filter.Statuses = allActivityStatusesFilter()
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 11, len(entries)) require.Equal(t, 11, len(entries))
filter.Statuses = []Status{PendingAS} filter.Statuses = []Status{PendingAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(entries)) require.Equal(t, 3, len(entries))
require.Equal(t, td.pendingTr.Hash, entries[2].transaction.Hash) require.Equal(t, td.pendingTr.Hash, entries[2].transaction.Hash)
@ -862,24 +838,24 @@ func TestGetActivityEntriesFilterByStatus(t *testing.T) {
require.Equal(t, trs[1].Hash, entries[0].transaction.Hash) require.Equal(t, trs[1].Hash, entries[0].transaction.Hash)
filter.Statuses = []Status{FailedAS} filter.Statuses = []Status{FailedAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 2, len(entries)) require.Equal(t, 2, len(entries))
filter.Statuses = []Status{CompleteAS} filter.Statuses = []Status{CompleteAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 6, len(entries)) require.Equal(t, 6, len(entries))
// Finalized is treated as Complete, would need dynamic blockchain status to track the Finalized level // Finalized is treated as Complete, would need dynamic blockchain status to track the Finalized level
filter.Statuses = []Status{FinalizedAS} filter.Statuses = []Status{FinalizedAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 6, len(entries)) require.Equal(t, 6, len(entries))
// Combined filter // Combined filter
filter.Statuses = []Status{FailedAS, PendingAS} filter.Statuses = []Status{FailedAS, PendingAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 5, len(entries)) require.Equal(t, 5, len(entries))
} }
@ -900,29 +876,29 @@ func TestGetActivityEntriesFilterByTokenType(t *testing.T) {
}) })
} }
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...)) allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter var filter Filter
filter.FilterOutAssets = true filter.FilterOutAssets = true
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 0, len(entries)) require.Equal(t, 0, len(entries))
filter.FilterOutAssets = false filter.FilterOutAssets = false
filter.Assets = allTokensFilter() filter.Assets = allTokensFilter()
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 13, len(entries)) require.Equal(t, 13, len(entries))
// Native tokens are network agnostic, hence all are returned // Native tokens are network agnostic, hence all are returned
filter.Assets = []Token{{TokenType: Native, ChainID: common.ChainID(transfer.EthMainnet.ChainID)}} filter.Assets = []Token{{TokenType: Native, ChainID: common.ChainID(transfer.EthMainnet.ChainID)}}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 5, len(entries)) require.Equal(t, 5, len(entries))
// Test that it doesn't break the filter // Test that it doesn't break the filter
filter.Assets = []Token{{TokenType: Erc1155}} filter.Assets = []Token{{TokenType: Erc1155}}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 0, len(entries)) require.Equal(t, 0, len(entries))
@ -931,7 +907,7 @@ func TestGetActivityEntriesFilterByTokenType(t *testing.T) {
ChainID: common.ChainID(transfer.UsdcMainnet.ChainID), ChainID: common.ChainID(transfer.UsdcMainnet.ChainID),
Address: transfer.UsdcMainnet.Address, Address: transfer.UsdcMainnet.Address,
}} }}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
// Two MT for which ChainID is ignored and one transfer on the main net and the Goerli is ignored // Two MT for which ChainID is ignored and one transfer on the main net and the Goerli is ignored
require.Equal(t, 3, len(entries)) require.Equal(t, 3, len(entries))
@ -957,7 +933,7 @@ func TestGetActivityEntriesFilterByTokenType(t *testing.T) {
ChainID: common.ChainID(transfer.UsdcGoerli.ChainID), ChainID: common.ChainID(transfer.UsdcGoerli.ChainID),
Address: transfer.UsdcGoerli.Address, Address: transfer.UsdcGoerli.Address,
}} }}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
// Two MT for which ChainID is ignored and two transfers on the main net and Goerli // Two MT for which ChainID is ignored and two transfers on the main net and Goerli
require.Equal(t, 4, len(entries)) require.Equal(t, 4, len(entries))
@ -978,26 +954,26 @@ func TestGetActivityEntriesFilterByToAddresses(t *testing.T) {
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i]) transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
} }
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...)) allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter var filter Filter
filter.CounterpartyAddresses = allAddressesFilter() filter.CounterpartyAddresses = allAddresses
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 10, len(entries)) require.Equal(t, 10, len(entries))
filter.CounterpartyAddresses = []eth.Address{eth.HexToAddress("0x567890")} filter.CounterpartyAddresses = []eth.Address{eth.HexToAddress("0x567890")}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 0, len(entries)) require.Equal(t, 0, len(entries))
filter.CounterpartyAddresses = []eth.Address{td.pendingTr.To, td.multiTx2.ToAddress, trs[3].To} filter.CounterpartyAddresses = []eth.Address{td.pendingTr.To, td.multiTx2.ToAddress, trs[3].To}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(entries)) require.Equal(t, 3, len(entries))
filter.CounterpartyAddresses = []eth.Address{td.tr1.To, td.pendingTr.From, trs[3].From, trs[5].To} filter.CounterpartyAddresses = []eth.Address{td.tr1.To, td.pendingTr.From, trs[3].From, trs[5].To}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 2, len(entries)) require.Equal(t, 2, len(entries))
} }
@ -1042,21 +1018,21 @@ func TestGetActivityEntriesFilterByNetworks(t *testing.T) {
recordPresence(trs[i].ChainID, 4+i) recordPresence(trs[i].ChainID, 4+i)
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i]) transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
} }
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...)) allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter var filter Filter
chainIDs := allNetworksFilter() chainIDs := allNetworksFilter()
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, chainIDs, filter, 0, 15) entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, chainIDs, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 10, len(entries)) require.Equal(t, 10, len(entries))
chainIDs = []common.ChainID{5674839210} chainIDs = []common.ChainID{5674839210}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, chainIDs, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, chainIDs, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 0, len(entries)) require.Equal(t, 0, len(entries))
chainIDs = []common.ChainID{td.pendingTr.ChainID, td.multiTx2Tr1.ChainID, trs[3].ChainID} chainIDs = []common.ChainID{td.pendingTr.ChainID, td.multiTx2Tr1.ChainID, trs[3].ChainID}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, chainIDs, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, chainIDs, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
expectedResults := make(map[int]int) expectedResults := make(map[int]int)
for _, chainID := range chainIDs { for _, chainID := range chainIDs {
@ -1100,25 +1076,25 @@ func TestGetActivityEntriesFilterByNetworksOfSubTransactions(t *testing.T) {
var filter Filter var filter Filter
chainIDs := allNetworksFilter() chainIDs := allNetworksFilter()
entries, err := getActivityEntries(context.Background(), deps, toTrs, chainIDs, filter, 0, 15) entries, err := getActivityEntries(context.Background(), deps, toTrs, false, chainIDs, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(entries)) require.Equal(t, 3, len(entries))
chainIDs = []common.ChainID{trs[0].ChainID, trs[1].ChainID} chainIDs = []common.ChainID{trs[0].ChainID, trs[1].ChainID}
entries, err = getActivityEntries(context.Background(), deps, toTrs, chainIDs, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, toTrs, false, chainIDs, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(entries)) require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].id, mt1.MultiTransactionID) require.Equal(t, entries[0].id, mt1.MultiTransactionID)
// Filter by pending_transactions sub-transacitons // Filter by pending_transactions sub-transacitons
chainIDs = []common.ChainID{trs[2].ChainID} chainIDs = []common.ChainID{trs[2].ChainID}
entries, err = getActivityEntries(context.Background(), deps, toTrs, chainIDs, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, toTrs, false, chainIDs, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(entries)) require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].id, mt1.MultiTransactionID) require.Equal(t, entries[0].id, mt1.MultiTransactionID)
chainIDs = []common.ChainID{trs[3].ChainID} chainIDs = []common.ChainID{trs[3].ChainID}
entries, err = getActivityEntries(context.Background(), deps, toTrs, chainIDs, filter, 0, 15) entries, err = getActivityEntries(context.Background(), deps, toTrs, false, chainIDs, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(entries)) require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].id, mt2.MultiTransactionID) require.Equal(t, entries[0].id, mt2.MultiTransactionID)
@ -1140,7 +1116,7 @@ func TestGetActivityEntriesCheckToAndFrom(t *testing.T) {
td.multiTx1.FromAddress, td.multiTx2.FromAddress, trs[0].To, trs[1].To} td.multiTx1.FromAddress, td.multiTx2.FromAddress, trs[0].To, trs[1].To}
var filter Filter var filter Filter
entries, err := getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) entries, err := getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 6, len(entries)) require.Equal(t, 6, len(entries))
@ -1165,12 +1141,12 @@ func TestGetActivityEntriesCheckContextCancellation(t *testing.T) {
deps, close := setupTestActivityDB(t) deps, close := setupTestActivityDB(t)
defer close() defer close()
_, _, _ = fillTestData(t, deps.db) _, fromAddresses, toAddresses := fillTestData(t, deps.db)
cancellableCtx, cancelFn := context.WithCancel(context.Background()) cancellableCtx, cancelFn := context.WithCancel(context.Background())
cancelFn() cancelFn()
activities, err := getActivityEntries(cancellableCtx, deps, []eth.Address{}, []common.ChainID{}, Filter{}, 0, 10) activities, err := getActivityEntries(cancellableCtx, deps, append(fromAddresses, toAddresses...), true, []common.ChainID{}, Filter{}, 0, 10)
require.ErrorIs(t, err, context.Canceled) require.ErrorIs(t, err, context.Canceled)
require.Equal(t, 0, len(activities)) require.Equal(t, 0, len(activities))
} }
@ -1187,7 +1163,7 @@ func TestGetActivityEntriesNullAddresses(t *testing.T) {
trs[1].MultiTransactionID = trs[0].MultiTransactionID trs[1].MultiTransactionID = trs[0].MultiTransactionID
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
transfer.InsertTestTransferWithOptions(t, deps.db, trs[i].To, &trs[i], &transfer.TestTransferOptions{ transfer.InsertTestTransferWithOptions(t, deps.db, trs[i].From, &trs[i], &transfer.TestTransferOptions{
NullifyAddresses: []eth.Address{trs[i].To}, NullifyAddresses: []eth.Address{trs[i].To},
}) })
} }
@ -1195,13 +1171,18 @@ func TestGetActivityEntriesNullAddresses(t *testing.T) {
trs[3].To = eth.Address{} trs[3].To = eth.Address{}
transfer.InsertTestPendingTransaction(t, deps.db, &trs[3]) transfer.InsertTestPendingTransaction(t, deps.db, &trs[3])
mockTestAccountsWithAddresses(t, deps.accountsDb, []eth.Address{trs[0].From, trs[1].From, trs[2].From, trs[3].From}) addresses := []eth.Address{trs[0].From, trs[1].From, trs[2].From, trs[3].From}
activities, err := getActivityEntries(context.Background(), deps, allAddressesFilter(), allNetworksFilter(), Filter{}, 0, 10) activities, err := getActivityEntries(context.Background(), deps, addresses, false, allNetworksFilter(), Filter{}, 0, 10)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 3, len(activities)) require.Equal(t, 3, len(activities))
} }
func TestGetActivityEntries_ErrorIfNoAddress(t *testing.T) {
_, err := getActivityEntries(context.Background(), FilterDependencies{}, []eth.Address{}, true, []common.ChainID{}, Filter{}, 0, 10)
require.EqualError(t, err, "no addresses provided")
}
func TestGetTxDetails(t *testing.T) { func TestGetTxDetails(t *testing.T) {
deps, close := setupTestActivityDB(t) deps, close := setupTestActivityDB(t)
defer close() defer close()
@ -1296,7 +1277,7 @@ func setupBenchmark(b *testing.B, inMemory bool, resultCount int) (deps FilterDe
for i = 0; i < transactionCount-pendingCount; i++ { for i = 0; i < transactionCount-pendingCount; i++ {
trs[i].From = accounts[i%len(accounts)] trs[i].From = accounts[i%len(accounts)]
transfer.InsertTestTransfer(b, deps.db, trs[i].To, &trs[i]) transfer.InsertTestTransfer(b, deps.db, trs[i].From, &trs[i])
} }
for ; i < transactionCount; i++ { for ; i < transactionCount; i++ {
@ -1304,7 +1285,6 @@ func setupBenchmark(b *testing.B, inMemory bool, resultCount int) (deps FilterDe
transfer.InsertTestPendingTransaction(b, deps.db, &trs[i]) transfer.InsertTestPendingTransaction(b, deps.db, &trs[i])
} }
mockTestAccountsWithAddresses(b, deps.accountsDb, accounts)
return return
} }
@ -1312,7 +1292,7 @@ func BenchmarkGetActivityEntries(bArg *testing.B) {
type params struct { type params struct {
inMemory bool inMemory bool
resultCount int resultCount int
generateTestParameters func([]eth.Address) (addresses []eth.Address, filter *Filter, startIndex int) generateTestParameters func([]eth.Address) (addresses []eth.Address, allAddresses bool, filter *Filter, startIndex int)
} }
testCases := []struct { testCases := []struct {
name string name string
@ -1323,8 +1303,8 @@ func BenchmarkGetActivityEntries(bArg *testing.B) {
params{ params{
true, true,
10, 10,
func([]eth.Address) ([]eth.Address, *Filter, int) { func(addresses []eth.Address) ([]eth.Address, bool, *Filter, int) {
return allAddressesFilter(), &Filter{}, 0 return addresses, true, &Filter{}, 0
}, },
}, },
}, },
@ -1333,8 +1313,8 @@ func BenchmarkGetActivityEntries(bArg *testing.B) {
params{ params{
false, false,
10, 10,
func([]eth.Address) ([]eth.Address, *Filter, int) { func(addresses []eth.Address) ([]eth.Address, bool, *Filter, int) {
return allAddressesFilter(), &Filter{}, 0 return addresses, true, &Filter{}, 0
}, },
}, },
}, },
@ -1343,18 +1323,8 @@ func BenchmarkGetActivityEntries(bArg *testing.B) {
params{ params{
false, false,
10, 10,
func(addresses []eth.Address) ([]eth.Address, *Filter, int) { func(addresses []eth.Address) ([]eth.Address, bool, *Filter, int) {
return allAddressesFilter(), &Filter{}, 200 return addresses, true, &Filter{}, 200
},
},
},
{
"SSD_AllAddresses",
params{
false,
10,
func(addresses []eth.Address) ([]eth.Address, *Filter, int) {
return addresses, &Filter{}, 0
}, },
}, },
}, },
@ -1363,8 +1333,8 @@ func BenchmarkGetActivityEntries(bArg *testing.B) {
params{ params{
false, false,
10, 10,
func(addresses []eth.Address) ([]eth.Address, *Filter, int) { func(addresses []eth.Address) ([]eth.Address, bool, *Filter, int) {
return addresses, &Filter{CounterpartyAddresses: addresses[3:]}, 0 return addresses, true, &Filter{CounterpartyAddresses: addresses[3:]}, 0
}, },
}, },
}, },
@ -1373,8 +1343,8 @@ func BenchmarkGetActivityEntries(bArg *testing.B) {
params{ params{
false, false,
10, 10,
func(addresses []eth.Address) ([]eth.Address, *Filter, int) { func(addresses []eth.Address) ([]eth.Address, bool, *Filter, int) {
return addresses[0:1], &Filter{}, 0 return addresses[0:1], false, &Filter{}, 0
}, },
}, },
}, },
@ -1385,14 +1355,14 @@ func BenchmarkGetActivityEntries(bArg *testing.B) {
const resultCount = 10 const resultCount = 10
for _, tc := range testCases { for _, tc := range testCases {
addresses, filter, startIndex := tc.params.generateTestParameters(accounts) addresses, allAddresses, filter, startIndex := tc.params.generateTestParameters(accounts)
bArg.Run(tc.name, func(b *testing.B) { bArg.Run(tc.name, func(b *testing.B) {
// Reset timer after setup // Reset timer after setup
b.ResetTimer() b.ResetTimer()
// Run benchmark // Run benchmark
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
res, err := getActivityEntries(context.Background(), deps, addresses, allNetworksFilter(), *filter, startIndex, resultCount) res, err := getActivityEntries(context.Background(), deps, addresses, allAddresses, allNetworksFilter(), *filter, startIndex, resultCount)
if err != nil || len(res) != resultCount { if err != nil || len(res) != resultCount {
b.Error(err) b.Error(err)
} }
@ -1400,59 +1370,3 @@ func BenchmarkGetActivityEntries(bArg *testing.B) {
}) })
} }
} }
func TestUpdateWalletKeypairsAccountsTable(t *testing.T) {
// initialize
appDb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(t, err)
walletDb, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
accountsAppDb, err := accounts.NewDB(appDb)
require.NoError(t, err)
accountsWalletDb, err := accounts.NewDB(walletDb)
require.NoError(t, err)
// Check initially empty
addressesApp, err := accountsAppDb.GetWalletAddresses()
require.NoError(t, err)
require.Empty(t, addressesApp)
addressesWallet, err := accountsWalletDb.GetWalletAddresses()
require.Error(t, err) // no such table error
require.Empty(t, addressesWallet)
// Insert 2 addresses in app db, but only 1 is a wallet
addresses := []types.Address{{0x01}, {0x02}, {0x03}}
accounts := []*accounts.Account{
{Address: addresses[0], Chat: true, Wallet: true},
{Address: addresses[1], Wallet: true},
{Address: addresses[2]},
}
err = accountsAppDb.SaveOrUpdateAccounts(accounts, false)
require.NoError(t, err)
// Check only 2 wallet accs is returned in app db
addressesApp, err = accountsAppDb.GetWalletAddresses()
require.NoError(t, err)
require.Len(t, addressesApp, 2)
// update wallet DB
err = updateKeypairsAccountsTable(accountsAppDb, walletDb)
require.NoError(t, err)
// Check only 2 wallet acc is returned in wallet db
var count int
err = walletDb.QueryRow(fmt.Sprintf("SELECT count(address) FROM %s", keypairAccountsTable)).Scan(&count)
require.NoError(t, err)
require.Equal(t, count, 2)
// Compare addresses between app and wallet db
rows, err := walletDb.Query(fmt.Sprintf("SELECT address FROM %s", keypairAccountsTable))
require.NoError(t, err)
for rows.Next() {
var address types.Address
err = rows.Scan(&address)
require.NoError(t, err)
require.Contains(t, addresses, address)
}
defer rows.Close()
}

View File

@ -68,10 +68,6 @@ func allTokensFilter() []Token {
return []Token{} return []Token{}
} }
func allAddressesFilter() []eth.Address {
return []eth.Address{}
}
func allNetworksFilter() []common.ChainID { func allNetworksFilter() []common.ChainID {
return []common.ChainID{} return []common.ChainID{}
} }

View File

@ -1,9 +1,5 @@
-- Query includes duplicates, will return multiple rows for the same transaction if both to and from addresses are in the address list. -- Query includes duplicates, will return multiple rows for the same transaction if both to and from addresses are in the address list.
-- --
-- The addresses list will have priority in deciding the source of the duplicate transaction; see filter_addresses temp table
-- TODO: #11980
-- However, if the addresses list is empty, and all addresses should be included, the accounts table will be used
--
-- The switch for tr_type is used to de-conflict the source for the two entries for the same transaction -- The switch for tr_type is used to de-conflict the source for the two entries for the same transaction
-- --
-- UNION ALL is used to avoid the overhead of DISTINCT given that we don't expect to have duplicate entries outside the sender and receiver addresses being in the list which is handled separately -- UNION ALL is used to avoid the overhead of DISTINCT given that we don't expect to have duplicate entries outside the sender and receiver addresses being in the list which is handled separately
@ -43,13 +39,14 @@ WITH filter_conditions AS (
? AS includeAllTokenTypeAssets, ? AS includeAllTokenTypeAssets,
? AS includeAllNetworks, ? AS includeAllNetworks,
? AS pendingStatus, ? AS pendingStatus,
"0000000000000000000000000000000000000000" AS zeroAddress '0000000000000000000000000000000000000000' AS zeroAddress
), ),
-- This UNION between CTE and TEMP TABLE acts as an optimization. As soon as we drop one or use them interchangeably the performance drops significantly.
filter_addresses(address) AS ( filter_addresses(address) AS (
SELECT SELECT
HEX(address) address
FROM FROM
%s filter_addresses_table
WHERE WHERE
( (
SELECT SELECT
@ -209,7 +206,7 @@ WHERE
AND NOT ( AND NOT (
tr_type = fromTrType tr_type = fromTrType
and transfers.tx_to_address IS NULL and transfers.tx_to_address IS NULL
AND transfers.type = "eth" AND transfers.type = 'eth'
AND transfers.contract_address IS NOT NULL AND transfers.contract_address IS NOT NULL
AND HEX(transfers.contract_address) != zeroAddress AND HEX(transfers.contract_address) != zeroAddress
) )
@ -219,7 +216,7 @@ WHERE
AND tr_type = toTrType AND tr_type = toTrType
AND NOT ( AND NOT (
tr_type = toTrType tr_type = toTrType
AND transfers.type = "erc721" AND transfers.type = 'erc721'
AND ( AND (
transfers.tx_from_address IS NULL transfers.tx_from_address IS NULL
OR HEX(transfers.tx_from_address) = zeroAddress OR HEX(transfers.tx_from_address) = zeroAddress
@ -230,7 +227,7 @@ WHERE
filterActivityTypeContractDeployment filterActivityTypeContractDeployment
AND tr_type = fromTrType AND tr_type = fromTrType
AND transfers.tx_to_address IS NULL AND transfers.tx_to_address IS NULL
AND transfers.type = "eth" AND transfers.type = 'eth'
AND transfers.contract_address IS NOT NULL AND transfers.contract_address IS NOT NULL
AND HEX(transfers.contract_address) != zeroAddress AND HEX(transfers.contract_address) != zeroAddress
AND ( AND (
@ -241,7 +238,7 @@ WHERE
OR ( OR (
filterActivityTypeMint filterActivityTypeMint
AND tr_type = toTrType AND tr_type = toTrType
AND transfers.type = "erc721" AND transfers.type = 'erc721'
AND ( AND (
transfers.tx_from_address IS NULL transfers.tx_from_address IS NULL
OR HEX(transfers.tx_from_address) = zeroAddress OR HEX(transfers.tx_from_address) = zeroAddress
@ -265,11 +262,11 @@ WHERE
AND ( AND (
includeAllTokenTypeAssets includeAllTokenTypeAssets
OR ( OR (
transfers.type = "eth" transfers.type = 'eth'
AND ("ETH" IN assets_token_codes) AND ('ETH' IN assets_token_codes)
) )
OR ( OR (
transfers.type = "erc20" transfers.type = 'erc20'
AND ( AND (
( (
transfers.network_id, transfers.network_id,

View File

@ -11,7 +11,6 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/services/wallet/async" "github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/collectibles" "github.com/status-im/status-go/services/wallet/collectibles"
w_common "github.com/status-im/status-go/services/wallet/common" w_common "github.com/status-im/status-go/services/wallet/common"
@ -43,9 +42,9 @@ var (
} }
) )
// Service provides an async interface, ensuring only one filter request, of each type, is running at a time. It also provides lazy load of NFT info and token mapping
type Service struct { type Service struct {
db *sql.DB db *sql.DB
accountsDB *accounts.Database
tokenManager token.ManagerInterface tokenManager token.ManagerInterface
collectibles collectibles.ManagerInterface collectibles collectibles.ManagerInterface
eventFeed *event.Feed eventFeed *event.Feed
@ -53,10 +52,9 @@ type Service struct {
scheduler *async.MultiClientScheduler scheduler *async.MultiClientScheduler
} }
func NewService(db *sql.DB, tokenManager token.ManagerInterface, collectibles collectibles.ManagerInterface, eventFeed *event.Feed, accountsDb *accounts.Database) *Service { func NewService(db *sql.DB, tokenManager token.ManagerInterface, collectibles collectibles.ManagerInterface, eventFeed *event.Feed) *Service {
return &Service{ return &Service{
db: db, db: db,
accountsDB: accountsDb,
tokenManager: tokenManager, tokenManager: tokenManager,
collectibles: collectibles, collectibles: collectibles,
eventFeed: eventFeed, eventFeed: eventFeed,
@ -82,11 +80,13 @@ type FilterResponse struct {
} }
// FilterActivityAsync allows only one filter task to run at a time // FilterActivityAsync allows only one filter task to run at a time
// and it cancels the current one if a new one is started // it cancels the current one if a new one is started
// and should not expect other owners to have data in one of the queried tables
//
// All calls will trigger an EventActivityFilteringDone event with the result of the filtering // All calls will trigger an EventActivityFilteringDone event with the result of the filtering
func (s *Service) FilterActivityAsync(requestID int32, addresses []common.Address, chainIDs []w_common.ChainID, filter Filter, offset int, limit int) { func (s *Service) FilterActivityAsync(requestID int32, addresses []common.Address, allAddresses bool, chainIDs []w_common.ChainID, filter Filter, offset int, limit int) {
s.scheduler.Enqueue(requestID, filterTask, func(ctx context.Context) (interface{}, error) { s.scheduler.Enqueue(requestID, filterTask, func(ctx context.Context) (interface{}, error) {
activities, err := getActivityEntries(ctx, s.getDeps(), addresses, chainIDs, filter, offset, limit) activities, err := getActivityEntries(ctx, s.getDeps(), addresses, allAddresses, chainIDs, filter, offset, limit)
return activities, err return activities, err
}, func(result interface{}, taskType async.TaskType, err error) { }, func(result interface{}, taskType async.TaskType, err error) {
res := FilterResponse{ res := FilterResponse{
@ -241,8 +241,7 @@ func (s *Service) Stop() {
func (s *Service) getDeps() FilterDependencies { func (s *Service) getDeps() FilterDependencies {
return FilterDependencies{ return FilterDependencies{
db: s.db, db: s.db,
accountsDb: s.accountsDB,
tokenSymbol: func(t Token) string { tokenSymbol: func(t Token) string {
info := s.tokenManager.LookupTokenIdentity(uint64(t.ChainID), t.Address, t.TokenType == Native) info := s.tokenManager.LookupTokenIdentity(uint64(t.ChainID), t.Address, t.TokenType == Native)
if info == nil { if info == nil {

View File

@ -63,7 +63,7 @@ func setupTestService(tb testing.TB) (service *Service, eventFeed *event.Feed, t
eventFeed = new(event.Feed) eventFeed = new(event.Feed)
tokenMock = &mockTokenManager{} tokenMock = &mockTokenManager{}
collectiblesMock = &mockCollectiblesManager{} collectiblesMock = &mockCollectiblesManager{}
service = NewService(db, tokenMock, collectiblesMock, eventFeed, nil) service = NewService(db, tokenMock, collectiblesMock, eventFeed)
return service, eventFeed, tokenMock, collectiblesMock, func() { return service, eventFeed, tokenMock, collectiblesMock, func() {
require.NoError(tb, db.Close()) require.NoError(tb, db.Close())
@ -79,8 +79,8 @@ type arg struct {
} }
// insertStubTransfersWithCollectibles will insert nil if tokenIDStr is empty // insertStubTransfersWithCollectibles will insert nil if tokenIDStr is empty
func insertStubTransfersWithCollectibles(t *testing.T, db *sql.DB, args []arg) { func insertStubTransfersWithCollectibles(t *testing.T, db *sql.DB, args []arg) (fromAddresses, toAddresses []eth.Address) {
trs, _, _ := transfer.GenerateTestTransfers(t, db, 0, len(args)) trs, fromAddresses, toAddresses := transfer.GenerateTestTransfers(t, db, 0, len(args))
for i := range args { for i := range args {
trs[i].ChainID = args[i].chainID trs[i].ChainID = args[i].chainID
if args[i].tokenIDStr == "" { if args[i].tokenIDStr == "" {
@ -96,6 +96,7 @@ func insertStubTransfersWithCollectibles(t *testing.T, db *sql.DB, args []arg) {
TokenID: args[i].tokenID, TokenID: args[i].tokenID,
}) })
} }
return fromAddresses, toAddresses
} }
func TestService_UpdateCollectibleInfo(t *testing.T) { func TestService_UpdateCollectibleInfo(t *testing.T) {
@ -108,7 +109,7 @@ func TestService_UpdateCollectibleInfo(t *testing.T) {
{5, "0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a", "", nil, nil}, {5, "0x3d6afaa395c31fcd391fe3d562e75fe9e8ec7e6a", "", nil, nil},
{5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x0F", nil, nil}, {5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x0F", nil, nil},
} }
insertStubTransfersWithCollectibles(t, s.db, args) fromAddresses, toAddresses := insertStubTransfersWithCollectibles(t, s.db, args)
ch := make(chan walletevent.Event) ch := make(chan walletevent.Event)
sub := e.Subscribe(ch) sub := e.Subscribe(ch)
@ -147,7 +148,7 @@ func TestService_UpdateCollectibleInfo(t *testing.T) {
}, },
}, nil).Once() }, nil).Once()
s.FilterActivityAsync(0, allAddressesFilter(), allNetworksFilter(), Filter{}, 0, 3) s.FilterActivityAsync(0, append(fromAddresses, toAddresses...), true, allNetworksFilter(), Filter{}, 0, 3)
filterResponseCount := 0 filterResponseCount := 0
var updates []EntryData var updates []EntryData
@ -190,43 +191,36 @@ func TestService_UpdateCollectibleInfo_Error(t *testing.T) {
{5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x762AD3E4934E687F8701F24C7274E5209213FD6208FF952ACEB325D028866949", nil, nil}, {5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x762AD3E4934E687F8701F24C7274E5209213FD6208FF952ACEB325D028866949", nil, nil},
{5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x0D", nil, nil}, {5, "0xA2838FDA19EB6EED3F8B9EFF411D4CD7D2DE0313", "0x0D", nil, nil},
} }
insertStubTransfersWithCollectibles(t, s.db, args)
ch := make(chan walletevent.Event) ch := make(chan walletevent.Event, 4)
sub := e.Subscribe(ch) sub := e.Subscribe(ch)
fromAddresses, toAddresses := insertStubTransfersWithCollectibles(t, s.db, args)
c.On("FetchAssetsByCollectibleUniqueID", mock.Anything).Return(nil, thirdparty.ErrChainIDNotSupported).Once() c.On("FetchAssetsByCollectibleUniqueID", mock.Anything).Return(nil, thirdparty.ErrChainIDNotSupported).Once()
s.FilterActivityAsync(0, allAddressesFilter(), allNetworksFilter(), Filter{}, 0, 5) s.FilterActivityAsync(0, append(fromAddresses, toAddresses...), true, allNetworksFilter(), Filter{}, 0, 5)
filterResponseCount := 0 filterResponseCount := 0
updatesCount := 0 updatesCount := 0
select { for i := 0; i < 2; i++ {
case res := <-ch: select {
switch res.Type { case res := <-ch:
case EventActivityFilteringDone: switch res.Type {
var payload FilterResponse case EventActivityFilteringDone:
err := json.Unmarshal([]byte(res.Message), &payload) var payload FilterResponse
require.NoError(t, err) err := json.Unmarshal([]byte(res.Message), &payload)
require.Equal(t, ErrorCodeSuccess, payload.ErrorCode) require.NoError(t, err)
require.Equal(t, 2, len(payload.Activities)) require.Equal(t, ErrorCodeSuccess, payload.ErrorCode)
filterResponseCount++ require.Equal(t, 2, len(payload.Activities))
case EventActivityFilteringUpdate: filterResponseCount++
updatesCount++ case EventActivityFilteringUpdate:
updatesCount++
}
case <-time.NewTimer(20 * time.Millisecond).C:
// We wait to ensure the EventActivityFilteringUpdate is never sent
} }
case <-time.NewTimer(100 * time.Millisecond).C:
}
select {
case res := <-ch:
switch res.Type {
case EventActivityFilteringDone:
filterResponseCount++
case EventActivityFilteringUpdate:
updatesCount++
}
case <-time.NewTimer(100 * time.Microsecond).C:
} }
require.Equal(t, 1, filterResponseCount) require.Equal(t, 1, filterResponseCount)

View File

@ -570,10 +570,10 @@ func (api *API) FetchAllCurrencyFormats() (currency.FormatPerSymbol, error) {
return api.s.currency.FetchAllCurrencyFormats() return api.s.currency.FetchAllCurrencyFormats()
} }
func (api *API) FilterActivityAsync(requestID int32, addresses []common.Address, chainIDs []wcommon.ChainID, filter activity.Filter, offset int, limit int) error { func (api *API) FilterActivityAsync(requestID int32, addresses []common.Address, allAddresses bool, chainIDs []wcommon.ChainID, filter activity.Filter, offset int, limit int) error {
log.Debug("wallet.api.FilterActivityAsync", "requestID", requestID, "addr.count", len(addresses), "chainIDs.count", len(chainIDs), "offset", offset, "limit", limit) log.Debug("wallet.api.FilterActivityAsync", "requestID", requestID, "addr.count", len(addresses), "allAddresses", allAddresses, "chainIDs.count", len(chainIDs), "offset", offset, "limit", limit)
api.s.activity.FilterActivityAsync(requestID, addresses, chainIDs, filter, offset, limit) api.s.activity.FilterActivityAsync(requestID, addresses, allAddresses, chainIDs, filter, offset, limit)
return nil return nil
} }

View File

@ -143,7 +143,7 @@ func NewService(
collectiblesManager := collectibles.NewManager(db, rpcClient, contractOwnershipProviders, accountOwnershipProviders, collectibleDataProviders, collectionDataProviders, openseaClient) collectiblesManager := collectibles.NewManager(db, rpcClient, contractOwnershipProviders, accountOwnershipProviders, collectibleDataProviders, collectionDataProviders, openseaClient)
collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, rpcClient.NetworkManager, collectiblesManager) collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, rpcClient.NetworkManager, collectiblesManager)
activity := activity.NewService(db, tokenManager, collectiblesManager, feed, accountsDB) activity := activity.NewService(db, tokenManager, collectiblesManager, feed)
return &Service{ return &Service{
db: db, db: db,