diff --git a/services/wallet/activity/activity.go b/services/wallet/activity/activity.go index f2daf9aba..a518cfd94 100644 --- a/services/wallet/activity/activity.go +++ b/services/wallet/activity/activity.go @@ -364,7 +364,7 @@ const ( ) OR (filterActivityTypeReceive AND (filterAllAddresses - OR (HEX(transfers.tx_to_address) IN filter_addresses)) + OR (HEX(transfers.tx_to_address) IN filter_addresses)) ) ) AND (filterAllAddresses diff --git a/services/wallet/activity/filter.go b/services/wallet/activity/filter.go index 66a4d8619..29973edd5 100644 --- a/services/wallet/activity/filter.go +++ b/services/wallet/activity/filter.go @@ -3,6 +3,7 @@ package activity import ( "context" "database/sql" + "fmt" eth "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -91,28 +92,28 @@ type Filter struct { // TODO: consider sorting by saved address and contacts to offload the client from doing it at runtime func GetRecipients(ctx context.Context, db *sql.DB, offset int, limit int) (addresses []eth.Address, hasMore bool, err error) { rows, err := db.QueryContext(ctx, ` - SELECT - transfers.address as to_address, - transfers.timestamp AS timestamp - FROM transfers - WHERE transfers.multi_transaction_id = 0 + SELECT + transfers.tx_to_address as to_address, + transfers.timestamp AS timestamp + FROM transfers + WHERE transfers.multi_transaction_id = 0 - UNION ALL + UNION ALL - SELECT - pending_transactions.to_address AS to_address, - pending_transactions.timestamp AS timestamp - FROM pending_transactions - WHERE pending_transactions.multi_transaction_id = 0 + SELECT + pending_transactions.to_address AS to_address, + pending_transactions.timestamp AS timestamp + FROM pending_transactions + WHERE pending_transactions.multi_transaction_id = 0 - UNION ALL + UNION ALL - SELECT - multi_transactions.to_address AS to_address, - multi_transactions.timestamp AS timestamp - FROM multi_transactions - ORDER BY timestamp DESC - LIMIT ? OFFSET ?`, limit, offset) + SELECT + multi_transactions.to_address AS to_address, + multi_transactions.timestamp AS timestamp + FROM multi_transactions + ORDER BY timestamp DESC + LIMIT ? OFFSET ?`, limit, offset) if err != nil { return nil, false, err } @@ -137,3 +138,60 @@ func GetRecipients(ctx context.Context, db *sql.DB, offset int, limit int) (addr return entries, hasMore, nil } + +func GetOldestTimestamp(ctx context.Context, db *sql.DB, addresses []eth.Address) (timestamp int64, err error) { + queryFormatString := ` + WITH filter_conditions AS (SELECT ? AS filterAllAddresses), + filter_addresses(address) AS ( + SELECT * FROM (VALUES %s) WHERE (SELECT filterAllAddresses FROM filter_conditions) = 0 + ) + + SELECT + transfers.tx_from_address AS from_address, + transfers.tx_to_address AS to_address, + transfers.timestamp AS timestamp + FROM transfers, filter_conditions + WHERE transfers.multi_transaction_id = 0 + AND (filterAllAddresses OR HEX(from_address) IN filter_addresses OR HEX(to_address) IN filter_addresses) + + UNION ALL + + SELECT + pending_transactions.from_address AS from_address, + pending_transactions.to_address AS to_address, + pending_transactions.timestamp AS timestamp + FROM pending_transactions, filter_conditions + WHERE pending_transactions.multi_transaction_id = 0 + AND (filterAllAddresses OR HEX(from_address) IN filter_addresses OR HEX(to_address) IN filter_addresses) + + UNION ALL + + SELECT + multi_transactions.from_address AS from_address, + multi_transactions.to_address AS to_address, + multi_transactions.timestamp AS timestamp + FROM multi_transactions, filter_conditions + WHERE filterAllAddresses OR HEX(from_address) IN filter_addresses OR HEX(to_address) IN filter_addresses + ORDER BY timestamp ASC + LIMIT 1` + + filterAllAddresses := len(addresses) == 0 + involvedAddresses := noEntriesInTmpTableSQLValues + if !filterAllAddresses { + involvedAddresses = joinAddresses(addresses) + } + queryString := fmt.Sprintf(queryFormatString, involvedAddresses) + + row := db.QueryRowContext(ctx, queryString, filterAllAddresses) + var fromAddress, toAddress sql.NullString + err = row.Scan(&fromAddress, &toAddress, ×tamp) + if err == sql.ErrNoRows { + return 0, nil + } + + if err != nil { + return 0, err + } + + return timestamp, nil +} diff --git a/services/wallet/activity/filter_test.go b/services/wallet/activity/filter_test.go index 8f7f31aef..0dc48ee0d 100644 --- a/services/wallet/activity/filter_test.go +++ b/services/wallet/activity/filter_test.go @@ -6,7 +6,10 @@ import ( "fmt" "testing" + eth "github.com/ethereum/go-ethereum/common" + "github.com/status-im/status-go/appdatabase" + "github.com/status-im/status-go/services/wallet/testutils" "github.com/status-im/status-go/services/wallet/transfer" "github.com/stretchr/testify/require" @@ -61,3 +64,65 @@ func TestGetRecipients(t *testing.T) { require.Equal(t, 4, len(entries)) require.True(t, hasMore) } + +func TestGetOldestTimestampEmptyDB(t *testing.T) { + db, close := setupTestFilterDB(t) + defer close() + + timestamp, err := GetOldestTimestamp(context.Background(), db, []eth.Address{eth.HexToAddress("0x1")}) + require.NoError(t, err) + require.Equal(t, int64(0), timestamp) +} + +func TestGetOldestTimestamp(t *testing.T) { + db, close := setupTestFilterDB(t) + defer close() + + // Add 6 extractable transactions + trs, _, _ := transfer.GenerateTestTransfers(t, db, 0, 7) + for i := range trs { + if i < 5 { + transfer.InsertTestTransfer(t, db, trs[i].To, &trs[i]) + } else { + transfer.InsertTestPendingTransaction(t, db, &trs[i]) + } + } + + multiTxs := []transfer.TestMultiTransaction{ + transfer.GenerateTestBridgeMultiTransaction(trs[0], trs[1]), + transfer.GenerateTestSwapMultiTransaction(trs[2], testutils.SntSymbol, 100), + } + + // Extract oldest timestamp, no filter + timestamp, err := GetOldestTimestamp(context.Background(), db, []eth.Address{}) + require.NoError(t, err) + require.Equal(t, multiTxs[0].Timestamp, timestamp) + + // Test to filter + timestamp, err = GetOldestTimestamp(context.Background(), db, []eth.Address{ + trs[3].To, + }) + require.NoError(t, err) + require.Equal(t, trs[3].Timestamp, timestamp) + + // Test from filter + timestamp, err = GetOldestTimestamp(context.Background(), db, []eth.Address{ + trs[4].From, + }) + require.NoError(t, err) + require.Equal(t, trs[4].Timestamp, timestamp) + + // Test MT + timestamp, err = GetOldestTimestamp(context.Background(), db, []eth.Address{ + multiTxs[1].FromAddress, trs[4].To, + }) + require.NoError(t, err) + require.Equal(t, multiTxs[1].Timestamp, timestamp) + + // Test Pending + timestamp, err = GetOldestTimestamp(context.Background(), db, []eth.Address{ + trs[6].To, + }) + require.NoError(t, err) + require.Equal(t, trs[6].Timestamp, timestamp) +} diff --git a/services/wallet/api.go b/services/wallet/api.go index 60ea95b99..13e9e71a2 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -551,3 +551,8 @@ func (api *API) GetAllRecipients(ctx context.Context, offset int, limit int) (re result.Addresses, result.HasMore, err = activity.GetRecipients(ctx, api.s.db, offset, limit) return result, err } + +func (api *API) GetOldestActivityTimestamp(ctx context.Context, addresses []common.Address) (timestamp int64, err error) { + log.Debug("wallet.api.GetOldestActivityTimestamp", "addresses.len", len(addresses)) + return activity.GetOldestTimestamp(ctx, api.s.db, addresses) +}