feat(wallet) filter activity by status

Main changes:

- extend DB query include status for transactions and pending_transactions
- extend DB query to aggregate data from complex activity entries
- extend tests to include the new status values

Other changes:

- Improve tests with mocked addresses in DB to have a true to/from state

Update status-desktop #10746
This commit is contained in:
Stefan 2023-05-28 12:40:50 +02:00 committed by Stefan Dunca
parent 47711c4f15
commit 87ce6cfbcd
7 changed files with 314 additions and 171 deletions

View File

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require"
)
func AddTestAccounts(t *testing.T, db *sql.DB, accounts []*Account) {
func MockTestAccounts(t *testing.T, db *sql.DB, accounts []*Account) {
d, err := NewDB(db)
require.NoError(t, err)

View File

@ -29,14 +29,14 @@ Improve on the identified limitations
Filter requirements
- [ ] Activity operation status
- [ ] `pending`: have to aggregate for `Buy`, `Swap`, `Bridge`
- [x] Activity operation status
- [x] `pending`: have to aggregate for `Buy`, `Swap`, `Bridge`
- already there for `Send`, `Receive`
- [ ] `complete`: only extract and check for `status` in the `receipt` for `Send`, `Receive`
- [x] `complete`: only extract and check for `status` in the `receipt` for `Send`, `Receive`
- For complex operations aggregate the `complete` status `Buy`, `Swap`, `Bridge`
- [ ] `finalized`: similar to `complete` for `Send`, `Receive`
- [x] `finalized`: similar to `complete` for `Send`, `Receive`
- all sub-transactions are `complete` for `Buy`, `Swap`, `Bridge`
- [ ] `failed`: extract from `status` for all sub-transactions
- [x] `failed`: extract from `status` for all sub-transactions
- [ ] `chainID`: aggregate data for activity entries `Bridge`, `Buy`, `Swap`
- [ ] `tokenCode` for activity entries `Send`, `Receive`
- For `Bridge` its already there and `Buy`, `Swap` is coming soon
@ -44,7 +44,7 @@ Filter requirements
UX requirements
- [ ] `status`: for status icon and label
- [x] `status`: for status icon and label
- [ ] `chainIDs`: for chain icons
- Missing for `Bridge`, `Buy`, `Swap`
- [ ] `amount`s: add to the activity.Entry
@ -61,6 +61,11 @@ UX requirements
### Refactoring
Extend `entry.nim:ActivityEntry` and `activity.go:Entry` with presentation layer data
- [ ] `activityType`: instead of the current `MultiTransactionType`
- [x] `status`: for status icon and label
## Current state
### Transfers Table

View File

@ -10,6 +10,7 @@ import (
"strings"
eth "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/transfer"
@ -182,7 +183,7 @@ func activityTypesToMultiTransactionTypes(trTypes []Type) []transfer.MultiTransa
const (
fromTrType = byte(1)
//toTrType = byte(2)
toTrType = byte(2)
// TODO: Multi-transaction network information is missing in filtering
// TODO: extract token code for non transfer type eth
@ -198,6 +199,9 @@ const (
//
// 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
//
// Only status FailedAS, PendingAS and CompleteAS are returned. FinalizedAS requires correlation with blockchain
// current state. As an optimization we can approximate it by using timestamp information or last known block number
queryFormatString = `
WITH filter_conditions AS (
SELECT
@ -210,11 +214,23 @@ const (
? AS filterActivityTypeSend,
? AS filterActivityTypeReceive,
? AS fromTrType,
? AS toTrType,
? AS filterAllAddresses,
? AS filterAllToAddresses,
? AS filterAllActivityStatus,
? AS filterStatusCompleted,
? AS filterStatusFailed,
? AS filterStatusFinalized,
? AS filterStatusPending,
? AS statusFailed,
? AS statusSuccess,
? AS statusPending,
? AS includeAllTokenTypeAssets,
? AS statusIsPending,
? AS includeAllNetworks
),
@ -231,6 +247,26 @@ const (
),
filter_networks(network_id) AS (
VALUES %s
),
tr_status AS (
SELECT
multi_transaction_id,
MIN(status) AS min_status,
COUNT(*) AS count
FROM
transfers
WHERE transfers.multi_transaction_id != 0
GROUP BY
transfers.multi_transaction_id
),
pending_status AS (
SELECT
multi_transaction_id,
COUNT(*) AS count
FROM
pending_transactions
WHERE pending_transactions.multi_transaction_id != 0
GROUP BY pending_transactions.multi_transaction_id
)
SELECT
transfers.hash AS transfer_hash,
@ -241,8 +277,8 @@ const (
NULL AS mt_type,
CASE
WHEN from_join.address IS NOT NULL AND to_join.address IS NULL THEN 1
WHEN to_join.address IS NOT NULL AND from_join.address IS NULL THEN 2
WHEN from_join.address IS NOT NULL AND to_join.address IS NULL THEN fromTrType
WHEN to_join.address IS NOT NULL AND from_join.address IS NULL THEN toTrType
WHEN from_join.address IS NOT NULL AND to_join.address IS NOT NULL THEN
CASE
WHEN from_join.address < to_join.address THEN 1
@ -252,7 +288,14 @@ const (
END as tr_type,
transfers.sender AS from_address,
transfers.address AS to_address
transfers.address AS to_address,
CASE
WHEN transfers.status IS 1 THEN statusSuccess
ELSE statusFailed
END AS agg_status,
1 AS agg_count
FROM transfers, filter_conditions
LEFT JOIN
filter_addresses from_join ON HEX(transfers.sender) = from_join.address
@ -281,6 +324,9 @@ const (
)
AND (includeAllTokenTypeAssets OR (transfers.type = "eth" AND ("ETH" IN filter_assets)))
AND (includeAllNetworks OR (transfers.network_id IN filter_networks))
AND (filterAllActivityStatus OR ((filterStatusCompleted OR filterStatusFinalized) AND transfers.status = 1)
OR (filterStatusFailed AND transfers.status = 0)
)
UNION ALL
@ -304,14 +350,16 @@ const (
END as tr_type,
pending_transactions.from_address AS from_address,
pending_transactions.to_address AS to_address
pending_transactions.to_address AS to_address,
statusPending AS agg_status,
1 AS agg_count
FROM pending_transactions, filter_conditions
LEFT JOIN
filter_addresses from_join ON HEX(pending_transactions.from_address) = from_join.address
LEFT JOIN
filter_addresses to_join ON HEX(pending_transactions.to_address) = to_join.address
WHERE pending_transactions.multi_transaction_id = 0
AND (filterAllActivityStatus OR statusIsPending)
AND (filterAllActivityStatus OR filterStatusPending)
AND ((startFilterDisabled OR timestamp >= startTimestamp)
AND (endFilterDisabled OR timestamp <= endTimestamp)
)
@ -337,10 +385,22 @@ const (
multi_transactions.type AS mt_type,
NULL as tr_type,
multi_transactions.from_address AS from_address,
multi_transactions.to_address AS to_address
multi_transactions.to_address AS to_address,
CASE
WHEN tr_status.min_status = 1 AND pending_status.count IS NULL THEN statusSuccess
WHEN tr_status.min_status = 0 THEN statusFailed
ELSE statusPending
END AS agg_status,
COALESCE(tr_status.count, 0) + COALESCE(pending_status.count, 0) AS agg_count
FROM multi_transactions, filter_conditions
WHERE ((startFilterDisabled OR timestamp >= startTimestamp)
AND (endFilterDisabled OR timestamp <= endTimestamp)
JOIN tr_status ON multi_transactions.ROWID = tr_status.multi_transaction_id
LEFT JOIN pending_status ON multi_transactions.ROWID = pending_status.multi_transaction_id
WHERE
((startFilterDisabled OR multi_transactions.timestamp >= startTimestamp)
AND (endFilterDisabled OR multi_transactions.timestamp <= endTimestamp)
)
AND (filterActivityTypeAll OR (multi_transactions.type IN (%s)))
AND (filterAllAddresses
@ -350,7 +410,11 @@ const (
AND (filterAllToAddresses
OR (HEX(multi_transactions.to_address) IN filter_to_addresses)
)
AND (includeAllTokenTypeAssets OR (UPPER(multi_transactions.from_asset) IN filter_assets) OR (UPPER(multi_transactions.to_asset) IN filter_assets))
AND (includeAllTokenTypeAssets OR (UPPER(multi_transactions.from_asset) IN filter_assets)
OR (UPPER(multi_transactions.to_asset) IN filter_assets)
)
AND (filterAllActivityStatus OR ((filterStatusCompleted OR filterStatusFinalized) AND agg_status = statusSuccess)
OR (filterStatusFailed AND agg_status = statusFailed) OR (filterStatusPending AND agg_status = statusPending))
ORDER BY timestamp DESC
LIMIT ? OFFSET ?`
@ -391,9 +455,15 @@ func GetActivityEntries(db *sql.DB, addresses []eth.Address, chainIDs []common.C
filterAllToAddresses := len(filter.CounterpartyAddresses) == 0
includeAllStatuses := len(filter.Statuses) == 0
statusIsPending := false
filterStatusPending := false
filterStatusCompleted := false
filterStatusFailed := false
filterStatusFinalized := false
if !includeAllStatuses {
statusIsPending = sliceContains(filter.Statuses, PendingAS)
filterStatusPending = sliceContains(filter.Statuses, PendingAS)
filterStatusCompleted = sliceContains(filter.Statuses, CompleteAS)
filterStatusFailed = sliceContains(filter.Statuses, FailedAS)
filterStatusFinalized = sliceContains(filter.Statuses, FinalizedAS)
}
involvedAddresses := noEntriesInTmpTableSQLValues
@ -416,7 +486,11 @@ func GetActivityEntries(db *sql.DB, addresses []eth.Address, chainIDs []common.C
rows, err := db.Query(queryString,
startFilterDisabled, filter.Period.StartTimestamp, endFilterDisabled, filter.Period.EndTimestamp,
filterActivityTypeAll, sliceContains(filter.Types, SendAT), sliceContains(filter.Types, ReceiveAT),
filterAllAddresses, filterAllToAddresses, includeAllStatuses, includeAllTokenTypeAssets, statusIsPending,
fromTrType, toTrType,
filterAllAddresses, filterAllToAddresses,
includeAllStatuses, filterStatusCompleted, filterStatusFailed, filterStatusFinalized, filterStatusPending,
FailedAS, CompleteAS, PendingAS,
includeAllTokenTypeAssets,
includeAllNetworks,
limit, offset)
if err != nil {
@ -427,40 +501,43 @@ func GetActivityEntries(db *sql.DB, addresses []eth.Address, chainIDs []common.C
var entries []Entry
for rows.Next() {
var transferHash, pendingHash []byte
var chainID, multiTxID sql.NullInt64
var chainID, multiTxID, aggregatedCount sql.NullInt64
var timestamp int64
var dbMtType, dbTrType sql.NullByte
var toAddress, fromAddress eth.Address
err := rows.Scan(&transferHash, &pendingHash, &chainID, &multiTxID, &timestamp, &dbMtType, &dbTrType, &fromAddress, &toAddress)
var aggregatedStatus int
err := rows.Scan(&transferHash, &pendingHash, &chainID, &multiTxID, &timestamp, &dbMtType, &dbTrType, &fromAddress, &toAddress, &aggregatedStatus, &aggregatedCount)
if err != nil {
return nil, err
}
getActivityType := func(trType sql.NullByte) (activityType Type, filteredAddress eth.Address) {
if trType.Valid && trType.Byte == fromTrType {
return SendAT, fromAddress
if trType.Valid {
if trType.Byte == fromTrType {
return SendAT, fromAddress
} else if trType.Byte == toTrType {
return ReceiveAT, toAddress
}
}
// Don't expect this to happen due to trType = NULL outside of tests
log.Warn(fmt.Sprintf("unexpected activity type. Missing [%s, %s] in the addresses table?", fromAddress, toAddress))
return ReceiveAT, toAddress
}
// Can be mapped directly because the values are injected into the query
activityStatus := Status(aggregatedStatus)
var entry Entry
if transferHash != nil && chainID.Valid {
// TODO: extend DB with status in order to filter by status. The status has to be extracted from the receipt upon downloading
activityStatus := FinalizedAS
activityType, filteredAddress := getActivityType(dbTrType)
entry = newActivityEntryWithSimpleTransaction(
&transfer.TransactionIdentity{ChainID: common.ChainID(chainID.Int64), Hash: eth.BytesToHash(transferHash), Address: filteredAddress},
timestamp, activityType, activityStatus)
} else if pendingHash != nil && chainID.Valid {
activityStatus := PendingAS
activityType, _ := getActivityType(dbTrType)
entry = newActivityEntryWithPendingTransaction(&transfer.TransactionIdentity{ChainID: common.ChainID(chainID.Int64), Hash: eth.BytesToHash(pendingHash)},
timestamp, activityType, activityStatus)
} else if multiTxID.Valid {
activityType := multiTransactionTypeToActivityType(transfer.MultiTransactionType(dbMtType.Byte))
// TODO: aggregate status from all sub-transactions
activityStatus := FinalizedAS
entry = NewActivityEntryWithMultiTransaction(transfer.MultiTransactionIDType(multiTxID.Int64),
timestamp, activityType, activityStatus)
} else {

View File

@ -31,17 +31,30 @@ type testData struct {
pendingTr transfer.TestTransaction // index 2
singletonMTr transfer.TestTransaction // index 3
mTr transfer.TestTransaction // index 4
subTr transfer.TestTransaction // index 5
subPendingTr transfer.TestTransaction // index 6
singleTr transfer.TestTransaction // index 5
subTr transfer.TestTransaction // index 6
subPendingTr transfer.TestTransaction // index 7
singletonMTID transfer.MultiTransactionIDType
mTrID transfer.MultiTransactionIDType
nextIndex int
}
// Generates and adds to the DB 6 transactions. 2 transactions, 2 pending and 2 multi transactions
func mockTestAccountsWithAddresses(t *testing.T, db *sql.DB, addresses []eth_common.Address) {
mockedAccounts := []*accounts.Account{}
for _, address := range addresses {
mockedAccounts = append(mockedAccounts, &accounts.Account{
Address: types.Address(address),
Type: accounts.AccountTypeWatch,
})
}
accounts.MockTestAccounts(t, db, mockedAccounts)
}
// Generates and adds to the DB 7 transactions. 3 transactions, 2 pending and 2 multi transactions (1: 1 x pending and 1: with 2 x complete)
// There are only 4 extractable transactions and multi-transaction with timestamps 1-4. The other 2 are associated with a multi-transaction
func fillTestData(t *testing.T, db *sql.DB) (td testData) {
trs := transfer.GenerateTestTransactions(t, db, 1, 6)
func fillTestData(t *testing.T, db *sql.DB) (td testData, fromAddresses, toAddresses []eth_common.Address) {
trs, fromAddresses, toAddresses := transfer.GenerateTestTransactions(t, db, 1, 7)
td.tr1 = trs[0]
transfer.InsertTestTransfer(t, db, &td.tr1)
@ -57,24 +70,29 @@ func fillTestData(t *testing.T, db *sql.DB) (td testData) {
td.mTr.ToToken = testutils.SntSymbol
td.mTrID = transfer.InsertTestMultiTransaction(t, db, &td.mTr)
td.subTr = trs[4]
td.singleTr = trs[4]
td.singleTr.MultiTransactionID = td.singletonMTID
transfer.InsertTestTransfer(t, db, &td.singleTr)
td.subTr = trs[5]
td.subTr.MultiTransactionID = td.mTrID
transfer.InsertTestTransfer(t, db, &td.subTr)
td.subPendingTr = trs[5]
td.subPendingTr = trs[6]
td.subPendingTr.MultiTransactionID = td.mTrID
transfer.InsertTestPendingTransaction(t, db, &td.subPendingTr)
return
td.nextIndex = 8
return td, fromAddresses, toAddresses
}
func TestGetActivityEntriesAll(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
td := fillTestData(t, db)
td, fromAddresses, toAddresses := fillTestData(t, db)
var filter Filter
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 10)
entries, err := GetActivityEntries(db, append(toAddresses, fromAddresses...), []common.ChainID{}, filter, 0, 10)
require.NoError(t, err)
require.Equal(t, 4, len(entries))
@ -87,11 +105,11 @@ func TestGetActivityEntriesAll(t *testing.T) {
require.True(t, testutils.StructExistsInSlice(Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.From},
id: td.tr1.MultiTransactionID,
timestamp: td.tr1.Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries))
require.True(t, testutils.StructExistsInSlice(Entry{
@ -99,7 +117,7 @@ func TestGetActivityEntriesAll(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: td.pendingTr.ChainID, Hash: td.pendingTr.Hash},
id: td.pendingTr.MultiTransactionID,
timestamp: td.pendingTr.Timestamp,
activityType: ReceiveAT,
activityType: SendAT,
activityStatus: PendingAS,
tokenType: AssetTT,
}, entries))
@ -109,7 +127,7 @@ func TestGetActivityEntriesAll(t *testing.T) {
id: td.singletonMTID,
timestamp: td.singletonMTr.Timestamp,
activityType: SendAT,
activityStatus: FinalizedAS,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries))
require.True(t, testutils.StructExistsInSlice(Entry{
@ -118,7 +136,7 @@ func TestGetActivityEntriesAll(t *testing.T) {
id: td.mTrID,
timestamp: td.mTr.Timestamp,
activityType: SendAT,
activityStatus: FinalizedAS,
activityStatus: PendingAS,
tokenType: AssetTT,
}, entries))
@ -129,7 +147,7 @@ func TestGetActivityEntriesAll(t *testing.T) {
id: td.subTr.MultiTransactionID,
timestamp: td.subTr.Timestamp,
activityType: SendAT,
activityStatus: FinalizedAS,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries))
require.False(t, testutils.StructExistsInSlice(Entry{
@ -150,7 +168,10 @@ func TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB(t *testin
defer close()
// Add 4 extractable transactions with timestamps 1-4
td := fillTestData(t, db)
td, fromAddresses, toAddresses := fillTestData(t, db)
mockTestAccountsWithAddresses(t, db, append(fromAddresses, toAddresses...))
// Add another transaction with sender and receiver reversed
receiverTr := td.tr1
prevTo := receiverTr.To
@ -177,12 +198,6 @@ func TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB(t *testin
require.NotEqual(t, eth.Address{}, entries[0].transaction.Address)
require.Equal(t, td.tr1.From, entries[0].transaction.Address)
// add accounts to DB for proper detection of sender/receiver in all cases
accounts.AddTestAccounts(t, db, []*accounts.Account{
{Address: types.Address(td.tr1.From), Type: accounts.AccountTypeWatch},
{Address: types.Address(receiverTr.From), Type: accounts.AccountTypeWatch},
})
entries, err = GetActivityEntries(db, []eth.Address{}, []common.ChainID{}, filter, 0, 10)
require.NoError(t, err)
require.Equal(t, 5, len(entries))
@ -196,13 +211,16 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
td := fillTestData(t, db)
td, fromTds, toTds := fillTestData(t, db)
// Add 6 extractable transactions with timestamps 6-12
trs := transfer.GenerateTestTransactions(t, db, 6, 6)
trs, fromTrs, toTrs := transfer.GenerateTestTransactions(t, db, td.nextIndex, 6)
for i := range trs {
transfer.InsertTestTransfer(t, db, &trs[i])
}
mockTestAccountsWithAddresses(t, db, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
// Test start only
var filter Filter
filter.Period.StartTimestamp = td.singletonMTr.Timestamp
@ -213,11 +231,11 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
// Check start and end content
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[5].ChainID, Hash: trs[5].Hash, Address: trs[5].To},
transaction: &transfer.TransactionIdentity{ChainID: trs[5].ChainID, Hash: trs[5].Hash, Address: trs[5].From},
id: 0,
timestamp: trs[5].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[0])
require.Equal(t, Entry{
@ -226,7 +244,7 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
id: td.singletonMTID,
timestamp: td.singletonMTr.Timestamp,
activityType: SendAT,
activityStatus: FinalizedAS,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[7])
@ -238,11 +256,11 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
// Check start and end content
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].From},
id: 0,
timestamp: trs[2].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[0])
require.Equal(t, Entry{
@ -251,7 +269,7 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
id: td.singletonMTID,
timestamp: td.singletonMTr.Timestamp,
activityType: SendAT,
activityStatus: FinalizedAS,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[4])
@ -263,20 +281,20 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
// Check start and end content
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].From},
id: 0,
timestamp: trs[2].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[0])
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.From},
id: 0,
timestamp: td.tr1.Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[6])
}
@ -286,11 +304,13 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
defer close()
// Add 10 extractable transactions with timestamps 1-10
trs := transfer.GenerateTestTransactions(t, db, 1, 10)
trs, fromTrs, toTrs := transfer.GenerateTestTransactions(t, db, 1, 10)
for i := range trs {
transfer.InsertTestTransfer(t, db, &trs[i])
}
mockTestAccountsWithAddresses(t, db, append(fromTrs, toTrs...))
var filter Filter
// Get all
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 5)
@ -306,20 +326,20 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
// Check start and end content
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[8].ChainID, Hash: trs[8].Hash, Address: trs[8].To},
transaction: &transfer.TransactionIdentity{ChainID: trs[8].ChainID, Hash: trs[8].Hash, Address: trs[8].From},
id: 0,
timestamp: trs[8].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[0])
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].From},
id: 0,
timestamp: trs[6].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[2])
@ -330,20 +350,20 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
// Check start and end content
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].From},
id: 0,
timestamp: trs[6].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[0])
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].To},
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].From},
id: 0,
timestamp: trs[4].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[2])
@ -354,31 +374,53 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
// Check start and end content
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].From},
id: 0,
timestamp: trs[2].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[0])
}
func countTypes(entries []Entry) (sendCount, receiveCount, swapCount, buyCount, bridgeCount int) {
for _, entry := range entries {
switch entry.activityType {
case SendAT:
sendCount++
case ReceiveAT:
receiveCount++
case SwapAT:
swapCount++
case BuyAT:
buyCount++
case BridgeAT:
bridgeCount++
}
}
return
}
func TestGetActivityEntriesFilterByType(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions
fillTestData(t, db)
// Add 6 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge rest MultiTransactionSend
trs := transfer.GenerateTestTransactions(t, db, 6, 6)
trs[1].MultiTransactionType = transfer.MultiTransactionBridge
trs[3].MultiTransactionType = transfer.MultiTransactionSwap
trs[5].MultiTransactionType = transfer.MultiTransactionBridge
td, _, _ := fillTestData(t, db)
// Add 5 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge and two MultiTransactionSend
trs, _, _ := transfer.GenerateTestTransactions(t, db, td.nextIndex, 10)
trs[0].MultiTransactionType = transfer.MultiTransactionBridge
trs[2].MultiTransactionType = transfer.MultiTransactionSwap
trs[4].MultiTransactionType = transfer.MultiTransactionSend
trs[6].MultiTransactionType = transfer.MultiTransactionBridge
trs[8].MultiTransactionType = transfer.MultiTransactionSend
var lastMT transfer.MultiTransactionIDType
for i := range trs {
if trs[i].MultiTransactionType != transfer.MultiTransactionSend {
transfer.InsertTestMultiTransaction(t, db, &trs[i])
if i%2 == 0 {
lastMT = transfer.InsertTestMultiTransaction(t, db, &trs[i])
} else {
trs[i].MultiTransactionID = lastMT
transfer.InsertTestTransfer(t, db, &trs[i])
}
}
@ -387,51 +429,35 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
var filter Filter
filter.Types = allActivityTypesFilter()
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
// Set tr1 to Receive and pendingTr to Send; rest of two MT remain default Send
addresses := []eth_common.Address{td.tr1.To, td.pendingTr.From, td.singletonMTr.From, td.mTr.From, trs[0].From, trs[2].From, trs[4].From, trs[6].From, trs[8].From}
entries, err := GetActivityEntries(db, addresses, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 10, len(entries))
require.Equal(t, 9, len(entries))
filter.Types = []Type{SendAT, SwapAT}
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
entries, err = GetActivityEntries(db, addresses, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 8, len(entries))
swapCount := 0
sendCount := 0
receiveCount := 0
for _, entry := range entries {
if entry.activityType == SendAT {
sendCount++
}
if entry.activityType == ReceiveAT {
receiveCount++
}
if entry.activityType == SwapAT {
swapCount++
}
}
require.Equal(t, 2, sendCount)
require.Equal(t, 5, receiveCount)
require.Equal(t, 1, swapCount)
// 3 from td Send + 2 trs MT Send + 1 (swap)
require.Equal(t, 6, len(entries))
sendCount, receiveCount, swapCount, _, bridgeCount := countTypes(entries)
require.Equal(t, 5, sendCount)
require.Equal(t, 0, receiveCount)
require.Equal(t, 1, swapCount)
require.Equal(t, 0, bridgeCount)
// Test filtering out with address involved
filter.Types = []Type{BridgeAT, ReceiveAT}
// Include one "to" from transfers to be detected as receive
addresses := []eth_common.Address{trs[0].To, trs[1].To, trs[2].From, trs[3].From, trs[5].From}
entries, err = GetActivityEntries(db, addresses, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
bridgeCount := 0
receiveCount = 0
for _, entry := range entries {
if entry.activityType == BridgeAT {
bridgeCount++
}
if entry.activityType == ReceiveAT {
receiveCount++
}
}
require.Equal(t, 2, bridgeCount)
sendCount, receiveCount, swapCount, _, bridgeCount = countTypes(entries)
require.Equal(t, 0, sendCount)
require.Equal(t, 1, receiveCount)
require.Equal(t, 0, swapCount)
require.Equal(t, 2, bridgeCount)
}
func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
@ -439,12 +465,14 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
defer close()
// Adds 4 extractable transactions
td := fillTestData(t, db)
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
td, fromTds, toTds := fillTestData(t, db)
trs, fromTrs, toTrs := transfer.GenerateTestTransactions(t, db, td.nextIndex, 6)
for i := range trs {
transfer.InsertTestTransfer(t, db, &trs[i])
}
mockTestAccountsWithAddresses(t, db, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
addressesFilter := allAddressesFilter()
@ -462,7 +490,7 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
id: 0,
timestamp: trs[4].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[0])
require.Equal(t, Entry{
@ -471,7 +499,7 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
id: 0,
timestamp: trs[1].Timestamp,
activityType: SendAT,
activityStatus: FinalizedAS,
activityStatus: CompleteAS,
tokenType: AssetTT,
}, entries[1])
require.Equal(t, Entry{
@ -480,7 +508,7 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
id: td.mTrID,
timestamp: td.mTr.Timestamp,
activityType: SendAT,
activityStatus: FinalizedAS,
activityStatus: PendingAS,
tokenType: AssetTT,
}, entries[2])
}
@ -489,36 +517,58 @@ func TestGetActivityEntriesFilterByStatus(t *testing.T) {
db, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions
fillTestData(t, db)
// Add 6 extractable transactions
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
// Adds 4 extractable transactions: 1 T, 1 T pending, 1 MT pending, 1 MT with 2xT success
td, fromTds, toTds := fillTestData(t, db)
// Add 7 extractable transactions: 1 pending, 1 Tr failed, 1 MT failed, 4 success
trs, fromTrs, toTrs := transfer.GenerateTestTransactions(t, db, td.nextIndex, 7)
failedMTID := transfer.InsertTestMultiTransaction(t, db, &trs[6])
trs[6].MultiTransactionID = failedMTID
for i := range trs {
transfer.InsertTestTransfer(t, db, &trs[i])
if i == 1 {
transfer.InsertTestPendingTransaction(t, db, &trs[i])
} else {
trs[i].Success = i != 3 && i != 6
transfer.InsertTestTransfer(t, db, &trs[i])
}
}
mockTestAccountsWithAddresses(t, db, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
filter.Statuses = []Status{}
filter.Statuses = allActivityStatusesFilter()
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 10, len(entries))
require.Equal(t, 11, len(entries))
filter.Statuses = allActivityStatusesFilter()
filter.Statuses = []Status{PendingAS}
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 10, len(entries))
require.Equal(t, 3, len(entries))
require.Equal(t, td.pendingTr.Hash, entries[2].transaction.Hash)
require.Equal(t, td.mTrID, entries[1].id)
require.Equal(t, trs[1].Hash, entries[0].transaction.Hash)
// TODO: enabled and finish tests after extending DB with transaction status
//
// filter.Statuses = []Status{PendingAS}
// entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
// require.NoError(t, err)
// require.Equal(t, 1, len(entries))
filter.Statuses = []Status{FailedAS}
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
// filter.Statuses = []Status{FailedAS, CompleteAS}
// entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
// require.NoError(t, err)
// require.Equal(t, 9, len(entries))
filter.Statuses = []Status{CompleteAS}
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 6, len(entries))
// Finalized is treated as Complete, would need dynamic blockchain status to track the Finalized level
filter.Statuses = []Status{FinalizedAS}
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 6, len(entries))
// Combined filter
filter.Statuses = []Status{FailedAS, PendingAS}
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 5, len(entries))
}
func TestGetActivityEntriesFilterByTokenType(t *testing.T) {
@ -526,14 +576,16 @@ func TestGetActivityEntriesFilterByTokenType(t *testing.T) {
defer close()
// Adds 4 extractable transactions 2 transactions ETH, one MT SNT to DAI and another MT ETH to SNT
fillTestData(t, db)
td, fromTds, toTds := fillTestData(t, db)
// Add 6 extractable transactions with USDC (only erc20 as type in DB)
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
trs, fromTrs, toTrs := transfer.GenerateTestTransactions(t, db, td.nextIndex, 6)
for i := range trs {
trs[i].FromToken = "USDC"
transfer.InsertTestTransfer(t, db, &trs[i])
}
mockTestAccountsWithAddresses(t, db, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
filter.Tokens = noAssetsFilter()
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
@ -574,13 +626,15 @@ func TestGetActivityEntriesFilterByToAddresses(t *testing.T) {
defer close()
// Adds 4 extractable transactions
td := fillTestData(t, db)
td, fromTds, toTds := fillTestData(t, db)
// Add 6 extractable transactions
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
trs, fromTrs, toTrs := transfer.GenerateTestTransactions(t, db, td.nextIndex, 6)
for i := range trs {
transfer.InsertTestTransfer(t, db, &trs[i])
}
mockTestAccountsWithAddresses(t, db, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
filter.CounterpartyAddresses = allAddressesFilter()
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
@ -607,12 +661,13 @@ func TestGetActivityEntriesFilterByNetworks(t *testing.T) {
defer close()
// Adds 4 extractable transactions
td := fillTestData(t, db)
td, fromTds, toTds := fillTestData(t, db)
// Add 6 extractable transactions
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
trs, fromTrs, toTrs := transfer.GenerateTestTransactions(t, db, td.nextIndex, 6)
for i := range trs {
transfer.InsertTestTransfer(t, db, &trs[i])
}
mockTestAccountsWithAddresses(t, db, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
chainIDs := allNetworksFilter()
@ -638,10 +693,10 @@ func TestGetActivityEntriesCheckToAndFrom(t *testing.T) {
defer close()
// Adds 6 transactions from which 4 are filered out
td := fillTestData(t, db)
td, _, _ := fillTestData(t, db)
// Add extra transactions to test To address
trs := transfer.GenerateTestTransactions(t, db, 7, 2)
trs, _, _ := transfer.GenerateTestTransactions(t, db, td.nextIndex, 2)
transfer.InsertTestTransfer(t, db, &trs[0])
transfer.InsertTestPendingTransaction(t, db, &trs[1])
@ -672,3 +727,5 @@ func TestGetActivityEntriesCheckToAndFrom(t *testing.T) {
// TODO: add accounts to DB for proper detection of sender/receiver
// TODO: Test with all addresses
}
// TODO test sub-transaction count for multi-transactions

View File

@ -29,10 +29,10 @@ func allActivityTypesFilter() []Type {
type Status int
const (
FailedAS Status = iota
PendingAS
CompleteAS
FinalizedAS
FailedAS Status = iota // failed status or at least one failed transaction for multi-transactions
PendingAS // in pending DB or at least one transaction in pending for multi-transactions
CompleteAS // success status
FinalizedAS // all multi-transactions have success status
)
func allActivityStatusesFilter() []Status {

View File

@ -182,7 +182,7 @@ func TestGetTransfersForIdentities(t *testing.T) {
db, _, stop := setupTestDB(t)
defer stop()
trs := GenerateTestTransactions(t, db.client, 1, 4)
trs, _, _ := GenerateTestTransactions(t, db.client, 1, 4)
for i := range trs {
InsertTestTransfer(t, db.client, &trs[i])
}

View File

@ -23,11 +23,12 @@ type TestTransaction struct {
Timestamp int64
Value int64
BlkNumber int64
Success bool
MultiTransactionID MultiTransactionIDType
MultiTransactionType MultiTransactionType
}
func GenerateTestTransactions(t *testing.T, db *sql.DB, firstStartIndex int, count int) (result []TestTransaction) {
func GenerateTestTransactions(t *testing.T, db *sql.DB, firstStartIndex int, count int) (result []TestTransaction, fromAddresses, toAddresses []eth_common.Address) {
for i := firstStartIndex; i < (firstStartIndex + count); i++ {
tr := TestTransaction{
Hash: eth_common.HexToHash(fmt.Sprintf("0x1%d", i)),
@ -37,9 +38,12 @@ func GenerateTestTransactions(t *testing.T, db *sql.DB, firstStartIndex int, cou
Timestamp: int64(i),
Value: int64(i),
BlkNumber: int64(i),
Success: true,
MultiTransactionID: NoMultiTransactionID,
MultiTransactionType: MultiTransactionSend,
}
fromAddresses = append(fromAddresses, tr.From)
toAddresses = append(toAddresses, tr.To)
result = append(result, tr)
}
return
@ -58,10 +62,10 @@ func InsertTestTransfer(t *testing.T, db *sql.DB, tr *TestTransaction) {
) VALUES (?, ?, ?, ?);
INSERT INTO transfers (network_id, hash, address, blk_hash, tx,
sender, receipt, log, type, blk_number, timestamp, loaded,
multi_transaction_id, base_gas_fee
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, 0)`,
multi_transaction_id, base_gas_fee, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, 0, ?)`,
tr.ChainID, tr.To, tr.BlkNumber, blkHash,
tr.ChainID, tr.Hash, tr.To, blkHash, &JSONBlob{}, tr.From, &JSONBlob{}, &JSONBlob{}, tokenType, tr.BlkNumber, tr.Timestamp, tr.MultiTransactionID)
tr.ChainID, tr.Hash, tr.To, blkHash, &JSONBlob{}, tr.From, &JSONBlob{}, &JSONBlob{}, tokenType, tr.BlkNumber, tr.Timestamp, tr.MultiTransactionID, tr.Success)
require.NoError(t, err)
}