fix(wallet) send/receive for duplicate transactions

Brings consistency in case when sender and receiver are both in the
filter address list. This fixes the case of sender and receiver in
addresses and filters out duplicate entries.

Also

- refactor tests to provide support for owners
- adapt TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB
  to the use of owner instead of from
This commit is contained in:
Stefan 2023-09-06 20:15:07 +01:00 committed by Stefan Dunca
parent 740f66a8ff
commit 70341f85a5
7 changed files with 247 additions and 200 deletions

View File

@ -421,6 +421,7 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
startFilterDisabled, filter.Period.StartTimestamp, endFilterDisabled, filter.Period.EndTimestamp,
filterActivityTypeAll, sliceContains(filter.Types, SendAT), sliceContains(filter.Types, ReceiveAT),
sliceContains(filter.Types, ContractDeploymentAT), sliceContains(filter.Types, MintAT),
transfer.MultiTransactionSend,
fromTrType, toTrType,
filterAllAddresses, filterAllToAddresses,
includeAllStatuses, filterStatusCompleted, filterStatusFailed, filterStatusFinalized, filterStatusPending,

View File

@ -219,18 +219,18 @@ func TestGetActivityEntriesAll(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
id: td.tr1.MultiTransactionID,
timestamp: td.tr1.Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(td.tr1.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &td.tr1.TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("ETH"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(td.tr1.Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &td.tr1.TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &td.tr1.From,
recipient: &td.tr1.To,
chainIDOut: &td.tr1.ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &td.tr1.ChainID,
transferType: expectedTokenType(td.tr1.Token.Address),
}, entries[3])
require.Equal(t, Entry{
@ -286,8 +286,10 @@ func TestGetActivityEntriesAll(t *testing.T) {
}, entries[0])
}
// TestGetActivityEntriesWithSenderFilter covers the issue with returning the same transaction
// twice when the sender and receiver have entries in the transfers table
// TestGetActivityEntriesWithSenderFilter covers the corner-case of having both sender and receiver in the filter.
// In this specific case we expect that there will be two transactions (one probably backed by a multi-transaction)
// In case of both sender and receiver are included we validate we receive both entries otherwise only the "owned"
// transactions should be retrieved by the filter
func TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
@ -297,39 +299,28 @@ func TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB(t *testin
mockTestAccountsWithAddresses(t, deps.accountsDb, append(fromAddresses, toAddresses...))
// Add another transaction with sender and receiver reversed
receiverTr := td.tr1
prevTo := receiverTr.To
receiverTr.To = td.tr1.From
receiverTr.From = prevTo
// TODO: test also when there is a transaction in the other direction
// Ensure they are the oldest transactions (last in the list) and we have a consistent order
receiverTr.Timestamp--
transfer.InsertTestTransfer(t, deps.db, receiverTr.To, &receiverTr)
// Add another transaction with owner reversed
senderTr := td.tr1
// Ensure we have a consistent order
senderTr.Timestamp++
// add sender as owner, fillTestData adds receiver as owner
transfer.InsertTestTransfer(t, deps.db, senderTr.From, &senderTr)
var filter Filter
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{td.tr1.From, receiverTr.From}, []common.ChainID{}, filter, 0, 10)
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{td.tr1.To, senderTr.From}, []common.ChainID{}, filter, 0, 10)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
// Check that the transaction are labeled alternatively as send and receive
require.Equal(t, ReceiveAT, entries[1].activityType)
require.NotEqual(t, eth.Address{}, entries[1].transaction.Address)
require.Equal(t, receiverTr.To, entries[1].transaction.Address)
require.Equal(t, SendAT, entries[0].activityType)
require.NotEqual(t, eth.Address{}, entries[0].transaction.Address)
require.Equal(t, td.tr1.To, *entries[0].recipient)
require.Equal(t, senderTr.From, entries[0].transaction.Address)
require.Equal(t, senderTr.From, *entries[0].sender)
require.Equal(t, senderTr.To, *entries[0].recipient)
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 10)
require.NoError(t, err)
require.Equal(t, 5, len(entries))
// Check that the transaction are labeled alternatively as send and receive
require.Equal(t, ReceiveAT, entries[4].activityType)
require.Equal(t, SendAT, entries[3].activityType)
require.Equal(t, ReceiveAT, entries[1].activityType)
require.Equal(t, td.tr1.To, *entries[1].recipient)
require.Equal(t, td.tr1.From, *entries[1].sender)
require.Equal(t, td.tr1.To, *entries[1].recipient)
}
func TestGetActivityEntriesFilterByTime(t *testing.T) {
@ -360,18 +351,18 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: trs[5].ChainID, Hash: trs[5].Hash, Address: trs[5].To},
id: 0,
timestamp: trs[5].Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[5].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[5].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("USDC"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[5].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[5].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("USDC"),
sender: &trs[5].From,
recipient: &trs[5].To,
chainIDOut: &trs[5].ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &trs[5].ChainID,
transferType: expectedTokenType(trs[5].Token.Address),
}, entries[0])
require.Equal(t, Entry{
@ -406,18 +397,18 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
id: 0,
timestamp: trs[2].Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[2].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[2].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("ETH"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[2].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[2].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &trs[2].From,
recipient: &trs[2].To,
chainIDOut: &trs[2].ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &trs[2].ChainID,
transferType: expectedTokenType(trs[2].Token.Address),
}, entries[0])
require.Equal(t, Entry{
@ -451,18 +442,18 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
id: 0,
timestamp: trs[2].Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[2].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[2].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("ETH"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[2].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[2].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &trs[2].From,
recipient: &trs[2].To,
chainIDOut: &trs[2].ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &trs[2].ChainID,
transferType: expectedTokenType(trs[2].Token.Address),
}, entries[0])
require.Equal(t, Entry{
@ -470,18 +461,18 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
id: 0,
timestamp: td.tr1.Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(td.tr1.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &td.tr1.TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("ETH"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(td.tr1.Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &td.tr1.TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &td.tr1.From,
recipient: &td.tr1.To,
chainIDOut: &td.tr1.ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &td.tr1.ChainID,
transferType: expectedTokenType(td.tr1.Token.Address),
}, entries[6])
}
@ -516,18 +507,18 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: trs[8].ChainID, Hash: trs[8].Hash, Address: trs[8].To},
id: 0,
timestamp: trs[8].Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[8].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[8].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("ETH"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[8].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[8].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &trs[8].From,
recipient: &trs[8].To,
chainIDOut: &trs[8].ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &trs[8].ChainID,
transferType: expectedTokenType(trs[8].Token.Address),
}, entries[0])
require.Equal(t, Entry{
@ -535,18 +526,18 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
id: 0,
timestamp: trs[6].Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[6].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[6].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("DAI"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[6].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[6].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("DAI"),
sender: &trs[6].From,
recipient: &trs[6].To,
chainIDOut: &trs[6].ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &trs[6].ChainID,
transferType: expectedTokenType(trs[6].Token.Address),
}, entries[2])
@ -560,18 +551,18 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
id: 0,
timestamp: trs[6].Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[6].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[6].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("DAI"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[6].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[6].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("DAI"),
sender: &trs[6].From,
recipient: &trs[6].To,
chainIDOut: &trs[6].ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &trs[6].ChainID,
transferType: expectedTokenType(trs[6].Token.Address),
}, entries[0])
require.Equal(t, Entry{
@ -579,18 +570,18 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].To},
id: 0,
timestamp: trs[4].Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[4].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[4].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("USDC"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[4].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[4].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("USDC"),
sender: &trs[4].From,
recipient: &trs[4].To,
chainIDOut: &trs[4].ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &trs[4].ChainID,
transferType: expectedTokenType(trs[4].Token.Address),
}, entries[2])
@ -604,18 +595,18 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
id: 0,
timestamp: trs[2].Timestamp,
activityType: SendAT,
activityType: ReceiveAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[2].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[2].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("USDC"),
symbolIn: nil,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[2].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[2].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("USDC"),
sender: &trs[2].From,
recipient: &trs[2].To,
chainIDOut: &trs[2].ChainID,
chainIDIn: nil,
chainIDOut: nil,
chainIDIn: &trs[2].ChainID,
transferType: expectedTokenType(trs[2].Token.Address),
}, entries[0])
}
@ -753,7 +744,7 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
td, fromTds, toTds := fillTestData(t, deps.db)
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 6)
for i := range trs {
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
transfer.InsertTestTransfer(t, deps.db, trs[i].From, &trs[i])
}
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
@ -765,32 +756,33 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 10, len(entries))
addressesFilter = []eth.Address{td.multiTx2.ToAddress, trs[1].From, trs[4].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
entries, err = getActivityEntries(context.Background(), deps, addressesFilter, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
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,
activityType: SendAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[4].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[4].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("USDC"),
amountOut: (*hexutil.Big)(big.NewInt(trs[4].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[4].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("USDC"),
symbolIn: nil,
sender: &trs[4].From,
recipient: &trs[4].To,
chainIDOut: nil,
chainIDIn: &trs[4].ChainID,
chainIDOut: &trs[4].ChainID,
chainIDIn: nil,
transferType: expectedTokenType(trs[4].Token.Address),
}, entries[0])
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[1].ChainID, Hash: trs[1].Hash, Address: trs[1].To},
transaction: &transfer.TransactionIdentity{ChainID: trs[1].ChainID, Hash: trs[1].Hash, Address: trs[1].From},
id: 0,
timestamp: trs[1].Timestamp,
activityType: SendAT,
@ -937,9 +929,9 @@ func TestGetActivityEntriesFilterByTokenType(t *testing.T) {
require.NoError(t, err)
// 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, Erc20, entries[0].tokenOut.TokenType)
require.Equal(t, transfer.UsdcMainnet.Address, entries[0].tokenOut.Address)
require.Nil(t, entries[0].tokenIn)
require.Equal(t, Erc20, entries[0].tokenIn.TokenType)
require.Equal(t, transfer.UsdcMainnet.Address, entries[0].tokenIn.Address)
require.Nil(t, entries[0].tokenOut)
// MT has only symbol, the first token is lookup by symbol for both entries
require.Equal(t, Erc20, entries[1].tokenOut.TokenType)
require.Equal(t, transfer.UsdcMainnet.Address, entries[1].tokenOut.Address)
@ -963,9 +955,9 @@ func TestGetActivityEntriesFilterByTokenType(t *testing.T) {
require.NoError(t, err)
// 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, Erc20, entries[0].tokenOut.TokenType)
require.Equal(t, transfer.UsdcGoerli.Address, entries[0].tokenOut.Address)
require.Nil(t, entries[0].tokenIn)
require.Equal(t, Erc20, entries[0].tokenIn.TokenType)
require.Equal(t, transfer.UsdcGoerli.Address, entries[0].tokenIn.Address)
require.Nil(t, entries[0].tokenOut)
}
func TestGetActivityEntriesFilterByToAddresses(t *testing.T) {
@ -1138,19 +1130,19 @@ func TestGetActivityEntriesCheckToAndFrom(t *testing.T) {
transfer.InsertTestTransfer(t, deps.db, trs[0].To, &trs[0])
transfer.InsertTestPendingTransaction(t, deps.db, &trs[1])
addresses := []eth.Address{td.tr1.From, td.pendingTr.From,
td.multiTx1.FromAddress, td.multiTx2.ToAddress, trs[0].To, trs[1].To}
addresses := []eth.Address{td.tr1.To, td.pendingTr.To,
td.multiTx1.FromAddress, td.multiTx2.FromAddress, trs[0].To, trs[1].To}
var filter Filter
entries, err := getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 6, len(entries))
require.Equal(t, SendAT, entries[5].activityType) // td.tr1
require.Equal(t, ReceiveAT, entries[5].activityType) // td.tr1
require.NotEqual(t, eth.Address{}, entries[5].transaction.Address) // td.tr1
require.Equal(t, td.tr1.To, *entries[5].recipient) // td.tr1
require.Equal(t, SendAT, entries[4].activityType) // td.pendingTr
require.Equal(t, ReceiveAT, entries[4].activityType) // td.pendingTr
// Multi-transactions are always considered as SendAT
require.Equal(t, SendAT, entries[3].activityType) // td.multiTx1
@ -1161,9 +1153,6 @@ func TestGetActivityEntriesCheckToAndFrom(t *testing.T) {
require.Equal(t, trs[0].To, entries[1].transaction.Address) // trs[0]
require.Equal(t, ReceiveAT, entries[0].activityType) // trs[1] (pending)
// TODO: add accounts to DB for proper detection of sender/receiver
// TODO: Test with all addresses
}
func TestGetActivityEntriesCheckContextCancellation(t *testing.T) {

View File

@ -27,6 +27,7 @@ WITH filter_conditions AS (
? AS filterActivityTypeReceive,
? AS filterActivityTypeContractDeployment,
? AS filterActivityTypeMint,
? AS mTTypeSend,
? AS fromTrType,
? AS toTrType,
? AS filterAllAddresses,
@ -184,7 +185,7 @@ SELECT
FROM
transfers,
filter_conditions
LEFT JOIN filter_addresses from_join ON HEX(transfers.tx_from_address) = from_join.address
LEFT JOIN filter_addresses from_join ON HEX(transfers.address) = from_join.address
LEFT JOIN filter_addresses to_join ON HEX(transfers.tx_to_address) = to_join.address
WHERE
transfers.loaded == 1
@ -206,10 +207,17 @@ WHERE
filterActivityTypeSend
AND (
filterAllAddresses
OR (HEX(transfers.tx_from_address) IN filter_addresses)
OR (
HEX(transfers.tx_from_address) IN filter_addresses
)
)
AND NOT (
tr_type = fromTrType
and transfers.tx_to_address IS NULL
AND transfers.type = "eth"
AND transfers.contract_address IS NOT NULL
AND HEX(transfers.contract_address) != zeroAddress
)
AND NOT (tr_type = fromTrType and transfers.tx_to_address IS NULL AND transfers.type = "eth"
AND transfers.contract_address IS NOT NULL AND HEX(transfers.contract_address) != zeroAddress)
)
OR (
filterActivityTypeReceive
@ -217,14 +225,22 @@ WHERE
filterAllAddresses
OR (HEX(transfers.tx_to_address) IN filter_addresses)
)
AND NOT (tr_type = toTrType AND transfers.type = "erc721" AND (
transfers.tx_from_address IS NULL
OR HEX(transfers.tx_from_address) = zeroAddress))
AND NOT (
tr_type = toTrType
AND transfers.type = "erc721"
AND (
transfers.tx_from_address IS NULL
OR HEX(transfers.tx_from_address) = zeroAddress
)
)
)
OR (
filterActivityTypeContractDeployment
AND tr_type = fromTrType AND transfers.tx_to_address IS NULL AND transfers.type = "eth"
AND transfers.contract_address IS NOT NULL AND HEX(transfers.contract_address) != zeroAddress
AND tr_type = fromTrType
AND transfers.tx_to_address IS NULL
AND transfers.type = "eth"
AND transfers.contract_address IS NOT NULL
AND HEX(transfers.contract_address) != zeroAddress
AND (
filterAllAddresses
OR (HEX(transfers.address) IN filter_addresses)
@ -232,10 +248,13 @@ WHERE
)
OR (
filterActivityTypeMint
AND tr_type = toTrType AND transfers.type = "erc721" AND (
transfers.tx_from_address IS NULL
AND tr_type = toTrType
AND transfers.type = "erc721"
AND (
transfers.tx_from_address IS NULL
OR HEX(transfers.tx_from_address) = zeroAddress
) AND (
)
AND (
filterAllAddresses
OR (HEX(transfers.address) IN filter_addresses)
)
@ -243,10 +262,7 @@ WHERE
)
AND (
filterAllAddresses
OR (
HEX(transfers.tx_from_address) IN filter_addresses
)
OR (HEX(transfers.tx_to_address) IN filter_addresses)
OR (HEX(owner_address) IN filter_addresses)
)
AND (
filterAllToAddresses
@ -391,7 +407,7 @@ SELECT
NULL as tr_type,
multi_transactions.from_address AS from_address,
multi_transactions.to_address AS to_address,
NULL AS owner_address,
multi_transactions.from_address AS owner_address,
NULL AS tr_amount,
multi_transactions.from_amount AS mt_from_amount,
multi_transactions.to_amount AS mt_to_amount,
@ -434,10 +450,16 @@ WHERE
AND (
filterAllAddresses
OR (
HEX(multi_transactions.from_address) IN filter_addresses
-- Send multi-transaction types are exclusively for outbound transfers. The receiving end will have a corresponding entry as "owner_address" in the transfers table.
mt_type = mTTypeSend
AND HEX(owner_address) IN filter_addresses
)
OR (
HEX(multi_transactions.to_address) IN filter_addresses
mt_type != mTTypeSend
AND (
HEX(multi_transactions.from_address) IN filter_addresses
OR HEX(multi_transactions.to_address) IN filter_addresses
)
)
)
AND (

View File

@ -2,7 +2,7 @@ package transfer
import (
"context"
"fmt"
"database/sql"
"math/big"
"strings"
"time"
@ -452,24 +452,63 @@ func (c *transfersCommand) Run(ctx context.Context) (err error) {
return nil
}
// saveAndConfirmPending ensures only the transaction that has owner (Address) as a sender is matched to the
// corresponding multi-transaction (by multi-transaction ID). This way we ensure that if receiver is in the list
// of accounts filter will discard the proper one
func (c *transfersCommand) saveAndConfirmPending(allTransfers []Transfer, blockNum *big.Int) error {
tx, err := c.db.client.Begin()
if err != nil {
return err
tx, resErr := c.db.client.Begin()
if resErr != nil {
return resErr
}
notifyFunctions := make([]func(), 0)
// Confirm all pending transactions that are included in this block
for i, transfer := range allTransfers {
txType, MTID, err := transactions.GetTransferData(tx, w_common.ChainID(transfer.NetworkID), transfer.Receipt.TxHash)
if err != nil {
log.Error("GetTransferData error", "error", err)
defer func() {
if resErr == nil {
commitErr := tx.Commit()
if commitErr != nil {
log.Error("failed to commit", "error", commitErr)
}
for _, notify := range notifyFunctions {
notify()
}
} else {
rollbackErr := tx.Rollback()
if rollbackErr != nil {
log.Error("failed to rollback", "error", rollbackErr)
}
}
if MTID != nil {
allTransfers[i].MultiTransactionID = MultiTransactionIDType(*MTID)
}()
// Confirm all pending transactions that are included in this block
for i, tr := range allTransfers {
chainID := w_common.ChainID(tr.NetworkID)
txHash := tr.Receipt.TxHash
txType, mTID, err := transactions.GetOwnedPendingStatus(tx, chainID, txHash, tr.Address)
if err == sql.ErrNoRows {
if tr.MultiTransactionID > 0 {
continue
} else {
// Outside transaction, already confirmed by another duplicate or not yet downloaded
existingMTID, err := GetOwnedMultiTransactionID(tx, chainID, tr.ID, tr.Address)
if err == sql.ErrNoRows || existingMTID == 0 {
// Outside transaction, ignore it
continue
} else if err != nil {
log.Warn("GetOwnedMultiTransactionID", "error", err)
continue
}
mTID = w_common.NewAndSet(existingMTID)
}
} else if err != nil {
log.Warn("GetOwnedPendingStatus", "error", err)
continue
}
if mTID != nil {
allTransfers[i].MultiTransactionID = MultiTransactionIDType(*mTID)
}
if txType != nil && *txType == transactions.WalletTransfer {
notify, err := c.pendingTxManager.DeleteBySQLTx(tx, w_common.ChainID(transfer.NetworkID), transfer.Receipt.TxHash)
notify, err := c.pendingTxManager.DeleteBySQLTx(tx, chainID, txHash)
if err != nil && err != transactions.ErrStillPending {
log.Error("DeleteBySqlTx error", "error", err)
}
@ -477,27 +516,12 @@ func (c *transfersCommand) saveAndConfirmPending(allTransfers []Transfer, blockN
}
}
err = saveTransfersMarkBlocksLoaded(tx, c.chainClient.ChainID, c.address, allTransfers, []*big.Int{blockNum})
if err != nil {
log.Error("SaveTransfers error", "error", err)
return err
resErr = saveTransfersMarkBlocksLoaded(tx, c.chainClient.ChainID, c.address, allTransfers, []*big.Int{blockNum})
if resErr != nil {
log.Error("SaveTransfers error", "error", resErr)
}
if err == nil {
err = tx.Commit()
if err != nil {
return err
}
for _, notify := range notifyFunctions {
notify()
}
} else {
err = tx.Rollback()
if err != nil {
return fmt.Errorf("failed to rollback: %w", err)
}
}
return nil
return resErr
}
// Mark all subTxs of a given Tx with the same multiTxID

View File

@ -573,3 +573,13 @@ func markBlocksAsLoaded(chainID uint64, creator statementCreator, address common
}
return nil
}
// GetOwnedMultiTransactionID returns sql.ErrNoRows if no transaction is found for the given identity
func GetOwnedMultiTransactionID(tx *sql.Tx, chainID w_common.ChainID, id common.Hash, address common.Address) (mTID int64, err error) {
row := tx.QueryRow(`SELECT COALESCE(multi_transaction_id, 0) FROM transfers WHERE network_id = ? AND hash = ? AND address = ?`, chainID, id, address)
err = row.Scan(&mTID)
if err != nil {
return 0, err
}
return mTID, nil
}

View File

@ -67,7 +67,7 @@ func (d *IterativeDownloader) Next(parent context.Context) ([]*DBHeader, *big.In
headers, err := d.downloader.GetHeadersInRange(parent, from, to)
log.Info("load erc20 transfers in range", "from", from, "to", to, "batchSize", d.batchSize)
if err != nil {
log.Error("failed to get transfer in between two bloks", "from", from, "to", to, "error", err)
log.Error("failed to get transfer in between two blocks", "from", from, "to", to, "error", err)
return nil, nil, nil, err
}

View File

@ -561,15 +561,16 @@ func (tm *PendingTxTracker) DeleteBySQLTx(tx *sql.Tx, chainID common.ChainID, ha
}, err
}
func GetTransferData(tx *sql.Tx, chainID common.ChainID, hash eth.Hash) (txType *PendingTrxType, MTID *int64, err error) {
row := tx.QueryRow(`SELECT type, multi_transaction_id FROM pending_transactions WHERE network_id = ? AND hash = ?`, chainID, hash, txType)
// GetOwnedPendingStatus returns sql.ErrNoRows if no pending transaction is found for the given identity
func GetOwnedPendingStatus(tx *sql.Tx, chainID common.ChainID, hash eth.Hash, ownerAddress eth.Address) (txType *PendingTrxType, mTID *int64, err error) {
row := tx.QueryRow(`SELECT type, multi_transaction_id FROM pending_transactions WHERE network_id = ? AND hash = ? AND from_address = ?`, chainID, hash, ownerAddress)
txType = new(PendingTrxType)
MTID = new(int64)
err = row.Scan(txType, MTID)
mTID = new(int64)
err = row.Scan(txType, mTID)
if err != nil {
return nil, nil, err
}
return txType, MTID, nil
return txType, mTID, nil
}
// Watch returns sql.ErrNoRows if no pending transaction is found for the given identity