2023-08-01 18:50:30 +00:00
|
|
|
package transactions
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-02-27 07:58:12 +00:00
|
|
|
"database/sql"
|
2023-08-01 18:50:30 +00:00
|
|
|
"encoding/json"
|
2024-01-08 21:24:30 +00:00
|
|
|
"math/big"
|
2024-02-27 07:58:12 +00:00
|
|
|
"sync"
|
2023-08-01 18:50:30 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/require"
|
2024-09-20 09:08:11 +00:00
|
|
|
"go.uber.org/mock/gomock"
|
2023-08-01 18:50:30 +00:00
|
|
|
|
|
|
|
eth "github.com/ethereum/go-ethereum/common"
|
2024-01-08 21:24:30 +00:00
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
2023-08-01 18:50:30 +00:00
|
|
|
"github.com/ethereum/go-ethereum/event"
|
|
|
|
"github.com/ethereum/go-ethereum/rpc"
|
2024-09-24 13:07:26 +00:00
|
|
|
"github.com/status-im/status-go/rpc/chain/ethclient"
|
2024-06-27 21:27:09 +00:00
|
|
|
mock_rpcclient "github.com/status-im/status-go/rpc/mock/client"
|
2023-08-01 18:50:30 +00:00
|
|
|
|
|
|
|
"github.com/status-im/status-go/services/wallet/common"
|
|
|
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
|
|
|
"github.com/status-im/status-go/t/helpers"
|
|
|
|
"github.com/status-im/status-go/walletdatabase"
|
|
|
|
)
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
// setupTestTransactionDB will use the default pending check interval if checkInterval is nil
|
|
|
|
func setupTestTransactionDB(t *testing.T, checkInterval *time.Duration) (*PendingTxTracker, func(), *MockChainClient, *event.Feed) {
|
2023-08-01 18:50:30 +00:00
|
|
|
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2024-01-08 18:31:33 +00:00
|
|
|
chainClient := NewMockChainClient()
|
2024-06-27 21:27:09 +00:00
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
2023-08-01 18:50:30 +00:00
|
|
|
eventFeed := &event.Feed{}
|
2023-08-30 16:14:57 +00:00
|
|
|
pendingCheckInterval := PendingCheckInterval
|
|
|
|
if checkInterval != nil {
|
|
|
|
pendingCheckInterval = *checkInterval
|
|
|
|
}
|
2024-06-27 21:27:09 +00:00
|
|
|
rpcClient := mock_rpcclient.NewMockClientInterface(ctrl)
|
|
|
|
rpcClient.EXPECT().EthClient(common.EthereumMainnet).Return(chainClient, nil).AnyTimes()
|
|
|
|
|
|
|
|
// Delegate the call to the fake implementation
|
2024-09-24 13:07:26 +00:00
|
|
|
rpcClient.EXPECT().AbstractEthClient(gomock.Any()).DoAndReturn(func(chainID common.ChainID) (ethclient.BatchCallClient, error) {
|
2024-06-27 21:27:09 +00:00
|
|
|
return chainClient.AbstractEthClient(chainID)
|
|
|
|
}).AnyTimes()
|
|
|
|
return NewPendingTxTracker(db, rpcClient, nil, eventFeed, pendingCheckInterval), func() {
|
2023-08-01 18:50:30 +00:00
|
|
|
require.NoError(t, db.Close())
|
|
|
|
}, chainClient, eventFeed
|
|
|
|
}
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
func waitForTaskToStop(pt *PendingTxTracker) {
|
|
|
|
for pt.taskRunner.IsRunning() {
|
|
|
|
time.Sleep(1 * time.Microsecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-08 21:24:30 +00:00
|
|
|
func TestPendingTxTracker_ValidateConfirmedWithSuccessStatus(t *testing.T) {
|
2023-08-30 16:14:57 +00:00
|
|
|
m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
|
2023-08-01 18:50:30 +00:00
|
|
|
defer stop()
|
|
|
|
|
2024-01-08 21:24:30 +00:00
|
|
|
txs := MockTestTransactions(t, chainClient, []TestTxSummary{{}})
|
2023-08-01 18:50:30 +00:00
|
|
|
|
2024-01-08 21:24:30 +00:00
|
|
|
eventChan := make(chan walletevent.Event, 3)
|
|
|
|
sub := eventFeed.Subscribe(eventChan)
|
|
|
|
|
|
|
|
err := m.StoreAndTrackPendingTx(&txs[0])
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
select {
|
|
|
|
case we := <-eventChan:
|
|
|
|
if i == 0 || i == 1 {
|
|
|
|
// Check add and delete
|
|
|
|
require.Equal(t, EventPendingTransactionUpdate, we.Type)
|
|
|
|
} else {
|
|
|
|
require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
|
|
|
|
var p StatusChangedPayload
|
|
|
|
err = json.Unmarshal([]byte(we.Message), &p)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, txs[0].Hash, p.Hash)
|
|
|
|
require.Equal(t, Success, p.Status)
|
|
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Fatal("timeout waiting for event")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the answer to be processed
|
|
|
|
err = m.Stop()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
waitForTaskToStop(m)
|
|
|
|
|
|
|
|
res, err := m.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 0, len(res))
|
|
|
|
|
|
|
|
sub.Unsubscribe()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPendingTxTracker_ValidateConfirmedWithFailedStatus(t *testing.T) {
|
|
|
|
m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
|
|
|
|
defer stop()
|
|
|
|
|
|
|
|
txs := MockTestTransactions(t, chainClient, []TestTxSummary{{failStatus: true}})
|
2023-08-01 18:50:30 +00:00
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
eventChan := make(chan walletevent.Event, 3)
|
2023-08-01 18:50:30 +00:00
|
|
|
sub := eventFeed.Subscribe(eventChan)
|
|
|
|
|
|
|
|
err := m.StoreAndTrackPendingTx(&txs[0])
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
select {
|
|
|
|
case we := <-eventChan:
|
|
|
|
if i == 0 || i == 1 {
|
|
|
|
// Check add and delete
|
|
|
|
require.Equal(t, EventPendingTransactionUpdate, we.Type)
|
|
|
|
} else {
|
|
|
|
require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
|
|
|
|
var p StatusChangedPayload
|
|
|
|
err = json.Unmarshal([]byte(we.Message), &p)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, txs[0].Hash, p.Hash)
|
2024-01-08 21:24:30 +00:00
|
|
|
require.Equal(t, Failed, p.Status)
|
2023-08-01 18:50:30 +00:00
|
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Fatal("timeout waiting for event")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the answer to be processed
|
|
|
|
err = m.Stop()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
waitForTaskToStop(m)
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
res, err := m.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 0, len(res))
|
|
|
|
|
|
|
|
sub.Unsubscribe()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPendingTxTracker_InterruptWatching(t *testing.T) {
|
2023-08-30 16:14:57 +00:00
|
|
|
m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
|
2023-08-01 18:50:30 +00:00
|
|
|
defer stop()
|
|
|
|
|
2024-01-26 04:31:18 +00:00
|
|
|
txs := GenerateTestPendingTransactions(0, 2)
|
2023-08-01 18:50:30 +00:00
|
|
|
|
|
|
|
// Mock the first call to getTransactionByHash
|
2024-01-08 18:31:33 +00:00
|
|
|
chainClient.SetAvailableClients([]common.ChainID{txs[0].ChainID})
|
|
|
|
cl := chainClient.Clients[txs[0].ChainID]
|
2023-08-01 18:50:30 +00:00
|
|
|
cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
|
2024-01-08 21:24:30 +00:00
|
|
|
return (len(b) == 2 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[0].Hash && b[1].Method == GetTransactionReceiptRPCName && b[1].Args[0] == txs[1].Hash)
|
2023-08-01 18:50:30 +00:00
|
|
|
})).Return(nil).Once().Run(func(args mock.Arguments) {
|
|
|
|
elems := args.Get(1).([]rpc.BatchElem)
|
2023-08-30 16:14:57 +00:00
|
|
|
|
2024-01-08 21:24:30 +00:00
|
|
|
// Simulate still pending due to "null" return from eth_getTransactionReceipt
|
|
|
|
elems[0].Result.(*nullableReceipt).Receipt = nil
|
2023-08-30 16:14:57 +00:00
|
|
|
|
2024-01-08 21:24:30 +00:00
|
|
|
// Simulate parsing of eth_getTransactionReceipt response
|
|
|
|
elems[1].Result.(*nullableReceipt).Receipt = &types.Receipt{
|
|
|
|
BlockNumber: new(big.Int).SetUint64(1),
|
|
|
|
Status: 1,
|
|
|
|
}
|
2023-08-01 18:50:30 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
eventChan := make(chan walletevent.Event, 2)
|
|
|
|
sub := eventFeed.Subscribe(eventChan)
|
|
|
|
|
|
|
|
for i := range txs {
|
2024-11-06 10:14:25 +00:00
|
|
|
err := m.addPendingAndNotify(&txs[i])
|
2023-08-01 18:50:30 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check add
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
select {
|
|
|
|
case we := <-eventChan:
|
|
|
|
require.Equal(t, EventPendingTransactionUpdate, we.Type)
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Fatal("timeout waiting for event")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.Start()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
select {
|
|
|
|
case we := <-eventChan:
|
|
|
|
if i == 0 {
|
|
|
|
require.Equal(t, EventPendingTransactionUpdate, we.Type)
|
|
|
|
} else {
|
|
|
|
require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
|
|
|
|
var p StatusChangedPayload
|
|
|
|
err := json.Unmarshal([]byte(we.Message), &p)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, txs[1].Hash, p.Hash)
|
|
|
|
require.Equal(t, txs[1].ChainID, p.ChainID)
|
2024-01-08 21:24:30 +00:00
|
|
|
require.Equal(t, Success, p.Status)
|
2023-08-01 18:50:30 +00:00
|
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Fatal("timeout waiting for event")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop the next timed call
|
|
|
|
err = m.Stop()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
waitForTaskToStop(m)
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
res, err := m.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, len(res), "should have only one pending tx")
|
|
|
|
|
|
|
|
// Restart the tracker to process leftovers
|
|
|
|
//
|
|
|
|
cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
|
2024-01-08 21:24:30 +00:00
|
|
|
return (len(b) == 1 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[0].Hash)
|
2023-08-01 18:50:30 +00:00
|
|
|
})).Return(nil).Once().Run(func(args mock.Arguments) {
|
|
|
|
elems := args.Get(1).([]rpc.BatchElem)
|
2024-01-08 21:24:30 +00:00
|
|
|
// Simulate parsing of eth_getTransactionReceipt response
|
|
|
|
elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
|
|
|
|
BlockNumber: new(big.Int).SetUint64(1),
|
|
|
|
Status: 1,
|
|
|
|
}
|
2023-08-01 18:50:30 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
err = m.Start()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
select {
|
|
|
|
case we := <-eventChan:
|
|
|
|
if i == 0 {
|
|
|
|
require.Equal(t, EventPendingTransactionUpdate, we.Type)
|
|
|
|
} else {
|
|
|
|
require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
|
|
|
|
var p StatusChangedPayload
|
|
|
|
err := json.Unmarshal([]byte(we.Message), &p)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, txs[0].ChainID, p.ChainID)
|
|
|
|
require.Equal(t, txs[0].Hash, p.Hash)
|
2024-01-08 21:24:30 +00:00
|
|
|
require.Equal(t, Success, p.Status)
|
2023-08-01 18:50:30 +00:00
|
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Fatal("timeout waiting for event")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = m.Stop()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
waitForTaskToStop(m)
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
res, err = m.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 0, len(res))
|
|
|
|
|
|
|
|
sub.Unsubscribe()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPendingTxTracker_MultipleClients(t *testing.T) {
|
2023-08-30 16:14:57 +00:00
|
|
|
m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
|
2023-08-01 18:50:30 +00:00
|
|
|
defer stop()
|
|
|
|
|
2024-01-26 04:31:18 +00:00
|
|
|
txs := GenerateTestPendingTransactions(0, 2)
|
2023-08-01 18:50:30 +00:00
|
|
|
txs[1].ChainID++
|
|
|
|
|
|
|
|
// Mock the both clients to be available
|
2024-01-08 18:31:33 +00:00
|
|
|
chainClient.SetAvailableClients([]common.ChainID{txs[0].ChainID, txs[1].ChainID})
|
|
|
|
cl := chainClient.Clients[txs[0].ChainID]
|
2023-08-01 18:50:30 +00:00
|
|
|
cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
|
2024-01-08 21:24:30 +00:00
|
|
|
return (len(b) == 1 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[0].Hash)
|
2023-08-01 18:50:30 +00:00
|
|
|
})).Return(nil).Once().Run(func(args mock.Arguments) {
|
|
|
|
elems := args.Get(1).([]rpc.BatchElem)
|
2024-01-08 21:24:30 +00:00
|
|
|
// Simulate parsing of eth_getTransactionReceipt response
|
|
|
|
elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
|
|
|
|
BlockNumber: new(big.Int).SetUint64(1),
|
|
|
|
Status: 1,
|
|
|
|
}
|
2023-08-01 18:50:30 +00:00
|
|
|
})
|
2024-01-08 18:31:33 +00:00
|
|
|
cl = chainClient.Clients[txs[1].ChainID]
|
2023-08-01 18:50:30 +00:00
|
|
|
cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
|
2024-01-08 21:24:30 +00:00
|
|
|
return (len(b) == 1 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[1].Hash)
|
2023-08-01 18:50:30 +00:00
|
|
|
})).Return(nil).Once().Run(func(args mock.Arguments) {
|
|
|
|
elems := args.Get(1).([]rpc.BatchElem)
|
2024-01-08 21:24:30 +00:00
|
|
|
// Simulate parsing of eth_getTransactionReceipt response
|
|
|
|
elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
|
|
|
|
BlockNumber: new(big.Int).SetUint64(1),
|
|
|
|
Status: 1,
|
|
|
|
}
|
2023-08-01 18:50:30 +00:00
|
|
|
})
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
eventChan := make(chan walletevent.Event, 6)
|
|
|
|
sub := eventFeed.Subscribe(eventChan)
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
for i := range txs {
|
2024-03-14 08:39:06 +00:00
|
|
|
err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, AutoDelete, "")
|
2023-08-01 18:50:30 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err := m.Start()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
storeEventCount := 0
|
|
|
|
statusEventCount := 0
|
|
|
|
|
|
|
|
validateStatusChange := func(we *walletevent.Event) {
|
|
|
|
if we.Type == EventPendingTransactionUpdate {
|
|
|
|
storeEventCount++
|
|
|
|
} else if we.Type == EventPendingTransactionStatusChanged {
|
|
|
|
statusEventCount++
|
|
|
|
require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
|
|
|
|
var p StatusChangedPayload
|
|
|
|
err := json.Unmarshal([]byte(we.Message), &p)
|
|
|
|
require.NoError(t, err)
|
2024-01-08 21:24:30 +00:00
|
|
|
require.Equal(t, Success, p.Status)
|
2023-08-30 16:14:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
for i := 0; i < 2; i++ {
|
2023-08-30 16:14:57 +00:00
|
|
|
for j := 0; j < 3; j++ {
|
2023-08-01 18:50:30 +00:00
|
|
|
select {
|
|
|
|
case we := <-eventChan:
|
2023-08-30 16:14:57 +00:00
|
|
|
validateStatusChange(&we)
|
2023-08-01 18:50:30 +00:00
|
|
|
case <-time.After(1 * time.Second):
|
2023-08-30 16:14:57 +00:00
|
|
|
t.Fatal("timeout waiting for event", i, j, storeEventCount, statusEventCount)
|
2023-08-01 18:50:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
require.Equal(t, 4, storeEventCount)
|
|
|
|
require.Equal(t, 2, statusEventCount)
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
err = m.Stop()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
waitForTaskToStop(m)
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
res, err := m.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 0, len(res))
|
|
|
|
|
|
|
|
sub.Unsubscribe()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPendingTxTracker_Watch(t *testing.T) {
|
2023-08-30 16:14:57 +00:00
|
|
|
m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
|
2023-08-01 18:50:30 +00:00
|
|
|
defer stop()
|
|
|
|
|
2024-01-26 04:31:18 +00:00
|
|
|
txs := GenerateTestPendingTransactions(0, 2)
|
2023-08-01 18:50:30 +00:00
|
|
|
// Make the second already confirmed
|
2024-01-08 21:24:30 +00:00
|
|
|
*txs[0].Status = Success
|
2023-08-01 18:50:30 +00:00
|
|
|
|
|
|
|
// Mock the first call to getTransactionByHash
|
2024-01-08 18:31:33 +00:00
|
|
|
chainClient.SetAvailableClients([]common.ChainID{txs[0].ChainID})
|
|
|
|
cl := chainClient.Clients[txs[0].ChainID]
|
2023-08-01 18:50:30 +00:00
|
|
|
cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
|
2024-01-08 21:24:30 +00:00
|
|
|
return len(b) == 1 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[1].Hash
|
2023-08-01 18:50:30 +00:00
|
|
|
})).Return(nil).Once().Run(func(args mock.Arguments) {
|
|
|
|
elems := args.Get(1).([]rpc.BatchElem)
|
2024-01-08 21:24:30 +00:00
|
|
|
// Simulate parsing of eth_getTransactionReceipt response
|
|
|
|
elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
|
|
|
|
BlockNumber: new(big.Int).SetUint64(1),
|
|
|
|
Status: 1,
|
|
|
|
}
|
2023-08-01 18:50:30 +00:00
|
|
|
})
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
eventChan := make(chan walletevent.Event, 3)
|
2023-08-01 18:50:30 +00:00
|
|
|
sub := eventFeed.Subscribe(eventChan)
|
|
|
|
|
|
|
|
// Track the first transaction
|
2024-03-14 08:39:06 +00:00
|
|
|
err := m.TrackPendingTransaction(txs[1].ChainID, txs[1].Hash, txs[1].From, txs[1].To, txs[1].Type, Keep, "")
|
2023-08-01 18:50:30 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Store the confirmed already
|
2023-08-30 16:14:57 +00:00
|
|
|
err = m.StoreAndTrackPendingTx(&txs[0])
|
2023-08-01 18:50:30 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
storeEventCount := 0
|
|
|
|
statusEventCount := 0
|
|
|
|
for j := 0; j < 3; j++ {
|
|
|
|
select {
|
|
|
|
case we := <-eventChan:
|
|
|
|
if EventPendingTransactionUpdate == we.Type {
|
|
|
|
storeEventCount++
|
|
|
|
} else if EventPendingTransactionStatusChanged == we.Type {
|
|
|
|
statusEventCount++
|
|
|
|
var p StatusChangedPayload
|
|
|
|
err := json.Unmarshal([]byte(we.Message), &p)
|
|
|
|
require.NoError(t, err)
|
2023-08-30 16:14:57 +00:00
|
|
|
require.Equal(t, txs[1].ChainID, p.ChainID)
|
|
|
|
require.Equal(t, txs[1].Hash, p.Hash)
|
2024-01-08 21:24:30 +00:00
|
|
|
require.Equal(t, Success, p.Status)
|
2023-08-01 18:50:30 +00:00
|
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Fatal("timeout waiting for the status update event")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.Equal(t, 2, storeEventCount)
|
|
|
|
require.Equal(t, 1, statusEventCount)
|
|
|
|
|
|
|
|
// Stop the next timed call
|
|
|
|
err = m.Stop()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
waitForTaskToStop(m)
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
res, err := m.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
2023-08-30 16:14:57 +00:00
|
|
|
require.Equal(t, 0, len(res), "should have no pending tx")
|
2023-08-01 18:50:30 +00:00
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
status, err := m.Watch(context.Background(), txs[1].ChainID, txs[1].Hash)
|
2023-08-01 18:50:30 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotEqual(t, Pending, status)
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
err = m.Delete(context.Background(), txs[1].ChainID, txs[1].Hash)
|
2023-08-01 18:50:30 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case we := <-eventChan:
|
|
|
|
require.Equal(t, EventPendingTransactionUpdate, we.Type)
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Fatal("timeout waiting for the delete event")
|
|
|
|
}
|
|
|
|
|
|
|
|
sub.Unsubscribe()
|
|
|
|
}
|
|
|
|
|
2023-08-30 16:14:57 +00:00
|
|
|
func TestPendingTxTracker_Watch_StatusChangeIncrementally(t *testing.T) {
|
|
|
|
m, stop, chainClient, eventFeed := setupTestTransactionDB(t, common.NewAndSet(1*time.Nanosecond))
|
|
|
|
defer stop()
|
|
|
|
|
2024-01-26 04:31:18 +00:00
|
|
|
txs := GenerateTestPendingTransactions(0, 2)
|
2023-08-30 16:14:57 +00:00
|
|
|
|
|
|
|
var firsDoneWG sync.WaitGroup
|
|
|
|
firsDoneWG.Add(1)
|
|
|
|
|
|
|
|
// Mock the first call to getTransactionByHash
|
2024-01-08 18:31:33 +00:00
|
|
|
chainClient.SetAvailableClients([]common.ChainID{txs[0].ChainID})
|
|
|
|
cl := chainClient.Clients[txs[0].ChainID]
|
2023-08-30 16:14:57 +00:00
|
|
|
|
|
|
|
cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
|
|
|
|
if len(cl.Calls) == 0 {
|
2024-01-08 21:24:30 +00:00
|
|
|
res := len(b) > 0 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[0].Hash
|
2023-08-30 16:14:57 +00:00
|
|
|
// If the first processing call picked up the second validate this case also
|
|
|
|
if len(b) == 2 {
|
2024-01-08 21:24:30 +00:00
|
|
|
res = res && b[1].Method == GetTransactionReceiptRPCName && b[1].Args[0] == txs[1].Hash
|
2023-08-30 16:14:57 +00:00
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
// Second call we expect only one left
|
2024-01-08 21:24:30 +00:00
|
|
|
return len(b) == 1 && (b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[1].Hash)
|
2023-08-30 16:14:57 +00:00
|
|
|
})).Return(nil).Twice().Run(func(args mock.Arguments) {
|
|
|
|
elems := args.Get(1).([]rpc.BatchElem)
|
|
|
|
if len(cl.Calls) == 2 {
|
|
|
|
firsDoneWG.Wait()
|
|
|
|
}
|
|
|
|
// Only first item is processed, second is left pending
|
2024-01-08 21:24:30 +00:00
|
|
|
// Simulate parsing of eth_getTransactionReceipt response
|
|
|
|
elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
|
|
|
|
BlockNumber: new(big.Int).SetUint64(1),
|
|
|
|
Status: 1,
|
|
|
|
}
|
2023-08-30 16:14:57 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
eventChan := make(chan walletevent.Event, 6)
|
|
|
|
sub := eventFeed.Subscribe(eventChan)
|
|
|
|
|
|
|
|
for i := range txs {
|
|
|
|
// Track the first transaction
|
2024-03-14 08:39:06 +00:00
|
|
|
err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, Keep, "")
|
2023-08-30 16:14:57 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
storeEventCount := 0
|
|
|
|
statusEventCount := 0
|
|
|
|
|
|
|
|
validateStatusChange := func(we *walletevent.Event) {
|
|
|
|
var p StatusChangedPayload
|
|
|
|
err := json.Unmarshal([]byte(we.Message), &p)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
if statusEventCount == 0 {
|
|
|
|
require.Equal(t, txs[0].ChainID, p.ChainID)
|
|
|
|
require.Equal(t, txs[0].Hash, p.Hash)
|
2024-01-08 21:24:30 +00:00
|
|
|
require.Equal(t, Success, p.Status)
|
2023-08-30 16:14:57 +00:00
|
|
|
|
|
|
|
status, err := m.Watch(context.Background(), txs[0].ChainID, txs[0].Hash)
|
|
|
|
require.NoError(t, err)
|
2024-01-08 21:24:30 +00:00
|
|
|
require.Equal(t, Success, *status)
|
2023-08-30 16:14:57 +00:00
|
|
|
err = m.Delete(context.Background(), txs[0].ChainID, txs[0].Hash)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
status, err = m.Watch(context.Background(), txs[1].ChainID, txs[1].Hash)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, Pending, *status)
|
|
|
|
firsDoneWG.Done()
|
|
|
|
} else {
|
|
|
|
_, err := m.Watch(context.Background(), txs[0].ChainID, txs[0].Hash)
|
|
|
|
require.Equal(t, err, sql.ErrNoRows)
|
|
|
|
|
|
|
|
status, err := m.Watch(context.Background(), txs[1].ChainID, txs[1].Hash)
|
|
|
|
require.NoError(t, err)
|
2024-01-08 21:24:30 +00:00
|
|
|
require.Equal(t, Success, *status)
|
2023-08-30 16:14:57 +00:00
|
|
|
err = m.Delete(context.Background(), txs[1].ChainID, txs[1].Hash)
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
statusEventCount++
|
|
|
|
}
|
|
|
|
|
|
|
|
for j := 0; j < 6; j++ {
|
|
|
|
select {
|
|
|
|
case we := <-eventChan:
|
|
|
|
if EventPendingTransactionUpdate == we.Type {
|
|
|
|
storeEventCount++
|
|
|
|
} else if EventPendingTransactionStatusChanged == we.Type {
|
|
|
|
validateStatusChange(&we)
|
|
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Fatal("timeout waiting for the status update event")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := m.Watch(context.Background(), txs[1].ChainID, txs[1].Hash)
|
|
|
|
require.Equal(t, err, sql.ErrNoRows)
|
|
|
|
|
|
|
|
// One for add and one for delete
|
|
|
|
require.Equal(t, 4, storeEventCount)
|
|
|
|
require.Equal(t, 2, statusEventCount)
|
|
|
|
|
|
|
|
err = m.Stop()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
waitForTaskToStop(m)
|
|
|
|
|
|
|
|
res, err := m.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 0, len(res), "should have no pending tx")
|
|
|
|
|
|
|
|
sub.Unsubscribe()
|
|
|
|
}
|
|
|
|
|
2023-08-01 18:50:30 +00:00
|
|
|
func TestPendingTransactions(t *testing.T) {
|
2023-08-30 16:14:57 +00:00
|
|
|
manager, stop, _, _ := setupTestTransactionDB(t, nil)
|
2023-08-01 18:50:30 +00:00
|
|
|
defer stop()
|
|
|
|
|
2024-01-26 04:31:18 +00:00
|
|
|
tx := GenerateTestPendingTransactions(0, 1)[0]
|
2023-08-01 18:50:30 +00:00
|
|
|
|
|
|
|
rst, err := manager.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, rst)
|
|
|
|
|
|
|
|
rst, err = manager.GetPendingByAddress([]uint64{777}, tx.From)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, rst)
|
|
|
|
|
2024-11-06 10:14:25 +00:00
|
|
|
err = manager.addPendingAndNotify(&tx)
|
2023-08-01 18:50:30 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
rst, err = manager.GetPendingByAddress([]uint64{777}, tx.From)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, len(rst))
|
|
|
|
require.Equal(t, tx, *rst[0])
|
|
|
|
|
|
|
|
rst, err = manager.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, len(rst))
|
|
|
|
require.Equal(t, tx, *rst[0])
|
|
|
|
|
|
|
|
rst, err = manager.GetPendingByAddress([]uint64{777}, eth.Address{2})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Nil(t, rst)
|
|
|
|
|
|
|
|
err = manager.Delete(context.Background(), common.ChainID(777), tx.Hash)
|
|
|
|
require.Error(t, err, ErrStillPending)
|
|
|
|
|
|
|
|
rst, err = manager.GetPendingByAddress([]uint64{777}, tx.From)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 0, len(rst))
|
|
|
|
|
|
|
|
rst, err = manager.GetAllPending()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 0, len(rst))
|
|
|
|
}
|