github.com/status-im/status-go@v1.1.0/transactions/pendingtxtracker_test.go (about)

     1  package transactions
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"math/big"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/golang/mock/gomock"
    13  	"github.com/stretchr/testify/mock"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	eth "github.com/ethereum/go-ethereum/common"
    17  	"github.com/ethereum/go-ethereum/core/types"
    18  	"github.com/ethereum/go-ethereum/event"
    19  	"github.com/ethereum/go-ethereum/rpc"
    20  	"github.com/status-im/status-go/rpc/chain"
    21  	mock_rpcclient "github.com/status-im/status-go/rpc/mock/client"
    22  
    23  	"github.com/status-im/status-go/services/wallet/common"
    24  	"github.com/status-im/status-go/services/wallet/walletevent"
    25  	"github.com/status-im/status-go/t/helpers"
    26  	"github.com/status-im/status-go/walletdatabase"
    27  )
    28  
    29  // setupTestTransactionDB will use the default pending check interval if checkInterval is nil
    30  func setupTestTransactionDB(t *testing.T, checkInterval *time.Duration) (*PendingTxTracker, func(), *MockChainClient, *event.Feed) {
    31  	db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
    32  	require.NoError(t, err)
    33  
    34  	chainClient := NewMockChainClient()
    35  	ctrl := gomock.NewController(t)
    36  	defer ctrl.Finish()
    37  	eventFeed := &event.Feed{}
    38  	pendingCheckInterval := PendingCheckInterval
    39  	if checkInterval != nil {
    40  		pendingCheckInterval = *checkInterval
    41  	}
    42  	rpcClient := mock_rpcclient.NewMockClientInterface(ctrl)
    43  	rpcClient.EXPECT().EthClient(common.EthereumMainnet).Return(chainClient, nil).AnyTimes()
    44  
    45  	// Delegate the call to the fake implementation
    46  	rpcClient.EXPECT().AbstractEthClient(gomock.Any()).DoAndReturn(func(chainID common.ChainID) (chain.BatchCallClient, error) {
    47  		return chainClient.AbstractEthClient(chainID)
    48  	}).AnyTimes()
    49  	return NewPendingTxTracker(db, rpcClient, nil, eventFeed, pendingCheckInterval), func() {
    50  		require.NoError(t, db.Close())
    51  	}, chainClient, eventFeed
    52  }
    53  
    54  func waitForTaskToStop(pt *PendingTxTracker) {
    55  	for pt.taskRunner.IsRunning() {
    56  		time.Sleep(1 * time.Microsecond)
    57  	}
    58  }
    59  
    60  func TestPendingTxTracker_ValidateConfirmedWithSuccessStatus(t *testing.T) {
    61  	m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
    62  	defer stop()
    63  
    64  	txs := MockTestTransactions(t, chainClient, []TestTxSummary{{}})
    65  
    66  	eventChan := make(chan walletevent.Event, 3)
    67  	sub := eventFeed.Subscribe(eventChan)
    68  
    69  	err := m.StoreAndTrackPendingTx(&txs[0])
    70  	require.NoError(t, err)
    71  
    72  	for i := 0; i < 3; i++ {
    73  		select {
    74  		case we := <-eventChan:
    75  			if i == 0 || i == 1 {
    76  				// Check add and delete
    77  				require.Equal(t, EventPendingTransactionUpdate, we.Type)
    78  			} else {
    79  				require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
    80  				var p StatusChangedPayload
    81  				err = json.Unmarshal([]byte(we.Message), &p)
    82  				require.NoError(t, err)
    83  				require.Equal(t, txs[0].Hash, p.Hash)
    84  				require.Equal(t, Success, p.Status)
    85  			}
    86  		case <-time.After(1 * time.Second):
    87  			t.Fatal("timeout waiting for event")
    88  		}
    89  	}
    90  
    91  	// Wait for the answer to be processed
    92  	err = m.Stop()
    93  	require.NoError(t, err)
    94  
    95  	waitForTaskToStop(m)
    96  
    97  	res, err := m.GetAllPending()
    98  	require.NoError(t, err)
    99  	require.Equal(t, 0, len(res))
   100  
   101  	sub.Unsubscribe()
   102  }
   103  
   104  func TestPendingTxTracker_ValidateConfirmedWithFailedStatus(t *testing.T) {
   105  	m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
   106  	defer stop()
   107  
   108  	txs := MockTestTransactions(t, chainClient, []TestTxSummary{{failStatus: true}})
   109  
   110  	eventChan := make(chan walletevent.Event, 3)
   111  	sub := eventFeed.Subscribe(eventChan)
   112  
   113  	err := m.StoreAndTrackPendingTx(&txs[0])
   114  	require.NoError(t, err)
   115  
   116  	for i := 0; i < 3; i++ {
   117  		select {
   118  		case we := <-eventChan:
   119  			if i == 0 || i == 1 {
   120  				// Check add and delete
   121  				require.Equal(t, EventPendingTransactionUpdate, we.Type)
   122  			} else {
   123  				require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
   124  				var p StatusChangedPayload
   125  				err = json.Unmarshal([]byte(we.Message), &p)
   126  				require.NoError(t, err)
   127  				require.Equal(t, txs[0].Hash, p.Hash)
   128  				require.Equal(t, Failed, p.Status)
   129  			}
   130  		case <-time.After(1 * time.Second):
   131  			t.Fatal("timeout waiting for event")
   132  		}
   133  	}
   134  
   135  	// Wait for the answer to be processed
   136  	err = m.Stop()
   137  	require.NoError(t, err)
   138  
   139  	waitForTaskToStop(m)
   140  
   141  	res, err := m.GetAllPending()
   142  	require.NoError(t, err)
   143  	require.Equal(t, 0, len(res))
   144  
   145  	sub.Unsubscribe()
   146  }
   147  
   148  func TestPendingTxTracker_InterruptWatching(t *testing.T) {
   149  	m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
   150  	defer stop()
   151  
   152  	txs := GenerateTestPendingTransactions(0, 2)
   153  
   154  	// Mock the first call to getTransactionByHash
   155  	chainClient.SetAvailableClients([]common.ChainID{txs[0].ChainID})
   156  	cl := chainClient.Clients[txs[0].ChainID]
   157  	cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
   158  		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)
   159  	})).Return(nil).Once().Run(func(args mock.Arguments) {
   160  		elems := args.Get(1).([]rpc.BatchElem)
   161  
   162  		// Simulate still pending due to "null" return from eth_getTransactionReceipt
   163  		elems[0].Result.(*nullableReceipt).Receipt = nil
   164  
   165  		// Simulate parsing of eth_getTransactionReceipt response
   166  		elems[1].Result.(*nullableReceipt).Receipt = &types.Receipt{
   167  			BlockNumber: new(big.Int).SetUint64(1),
   168  			Status:      1,
   169  		}
   170  	})
   171  
   172  	eventChan := make(chan walletevent.Event, 2)
   173  	sub := eventFeed.Subscribe(eventChan)
   174  
   175  	for i := range txs {
   176  		err := m.addPending(&txs[i])
   177  		require.NoError(t, err)
   178  	}
   179  
   180  	// Check add
   181  	for i := 0; i < 2; i++ {
   182  		select {
   183  		case we := <-eventChan:
   184  			require.Equal(t, EventPendingTransactionUpdate, we.Type)
   185  		case <-time.After(1 * time.Second):
   186  			t.Fatal("timeout waiting for event")
   187  		}
   188  	}
   189  
   190  	err := m.Start()
   191  	require.NoError(t, err)
   192  
   193  	for i := 0; i < 2; i++ {
   194  		select {
   195  		case we := <-eventChan:
   196  			if i == 0 {
   197  				require.Equal(t, EventPendingTransactionUpdate, we.Type)
   198  			} else {
   199  				require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
   200  				var p StatusChangedPayload
   201  				err := json.Unmarshal([]byte(we.Message), &p)
   202  				require.NoError(t, err)
   203  				require.Equal(t, txs[1].Hash, p.Hash)
   204  				require.Equal(t, txs[1].ChainID, p.ChainID)
   205  				require.Equal(t, Success, p.Status)
   206  			}
   207  		case <-time.After(1 * time.Second):
   208  			t.Fatal("timeout waiting for event")
   209  		}
   210  	}
   211  
   212  	// Stop the next timed call
   213  	err = m.Stop()
   214  	require.NoError(t, err)
   215  
   216  	waitForTaskToStop(m)
   217  
   218  	res, err := m.GetAllPending()
   219  	require.NoError(t, err)
   220  	require.Equal(t, 1, len(res), "should have only one pending tx")
   221  
   222  	// Restart the tracker to process leftovers
   223  	//
   224  	cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
   225  		return (len(b) == 1 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[0].Hash)
   226  	})).Return(nil).Once().Run(func(args mock.Arguments) {
   227  		elems := args.Get(1).([]rpc.BatchElem)
   228  		// Simulate parsing of eth_getTransactionReceipt response
   229  		elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
   230  			BlockNumber: new(big.Int).SetUint64(1),
   231  			Status:      1,
   232  		}
   233  	})
   234  
   235  	err = m.Start()
   236  	require.NoError(t, err)
   237  
   238  	for i := 0; i < 2; i++ {
   239  		select {
   240  		case we := <-eventChan:
   241  			if i == 0 {
   242  				require.Equal(t, EventPendingTransactionUpdate, we.Type)
   243  			} else {
   244  				require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
   245  				var p StatusChangedPayload
   246  				err := json.Unmarshal([]byte(we.Message), &p)
   247  				require.NoError(t, err)
   248  				require.Equal(t, txs[0].ChainID, p.ChainID)
   249  				require.Equal(t, txs[0].Hash, p.Hash)
   250  				require.Equal(t, Success, p.Status)
   251  			}
   252  		case <-time.After(1 * time.Second):
   253  			t.Fatal("timeout waiting for event")
   254  		}
   255  	}
   256  
   257  	err = m.Stop()
   258  	require.NoError(t, err)
   259  
   260  	waitForTaskToStop(m)
   261  
   262  	res, err = m.GetAllPending()
   263  	require.NoError(t, err)
   264  	require.Equal(t, 0, len(res))
   265  
   266  	sub.Unsubscribe()
   267  }
   268  
   269  func TestPendingTxTracker_MultipleClients(t *testing.T) {
   270  	m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
   271  	defer stop()
   272  
   273  	txs := GenerateTestPendingTransactions(0, 2)
   274  	txs[1].ChainID++
   275  
   276  	// Mock the both clients to be available
   277  	chainClient.SetAvailableClients([]common.ChainID{txs[0].ChainID, txs[1].ChainID})
   278  	cl := chainClient.Clients[txs[0].ChainID]
   279  	cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
   280  		return (len(b) == 1 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[0].Hash)
   281  	})).Return(nil).Once().Run(func(args mock.Arguments) {
   282  		elems := args.Get(1).([]rpc.BatchElem)
   283  		// Simulate parsing of eth_getTransactionReceipt response
   284  		elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
   285  			BlockNumber: new(big.Int).SetUint64(1),
   286  			Status:      1,
   287  		}
   288  	})
   289  	cl = chainClient.Clients[txs[1].ChainID]
   290  	cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
   291  		return (len(b) == 1 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[1].Hash)
   292  	})).Return(nil).Once().Run(func(args mock.Arguments) {
   293  		elems := args.Get(1).([]rpc.BatchElem)
   294  		// Simulate parsing of eth_getTransactionReceipt response
   295  		elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
   296  			BlockNumber: new(big.Int).SetUint64(1),
   297  			Status:      1,
   298  		}
   299  	})
   300  
   301  	eventChan := make(chan walletevent.Event, 6)
   302  	sub := eventFeed.Subscribe(eventChan)
   303  
   304  	for i := range txs {
   305  		err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, AutoDelete, "")
   306  		require.NoError(t, err)
   307  	}
   308  
   309  	err := m.Start()
   310  	require.NoError(t, err)
   311  
   312  	storeEventCount := 0
   313  	statusEventCount := 0
   314  
   315  	validateStatusChange := func(we *walletevent.Event) {
   316  		if we.Type == EventPendingTransactionUpdate {
   317  			storeEventCount++
   318  		} else if we.Type == EventPendingTransactionStatusChanged {
   319  			statusEventCount++
   320  			require.Equal(t, EventPendingTransactionStatusChanged, we.Type)
   321  			var p StatusChangedPayload
   322  			err := json.Unmarshal([]byte(we.Message), &p)
   323  			require.NoError(t, err)
   324  			require.Equal(t, Success, p.Status)
   325  		}
   326  	}
   327  
   328  	for i := 0; i < 2; i++ {
   329  		for j := 0; j < 3; j++ {
   330  			select {
   331  			case we := <-eventChan:
   332  				validateStatusChange(&we)
   333  			case <-time.After(1 * time.Second):
   334  				t.Fatal("timeout waiting for event", i, j, storeEventCount, statusEventCount)
   335  			}
   336  		}
   337  	}
   338  
   339  	require.Equal(t, 4, storeEventCount)
   340  	require.Equal(t, 2, statusEventCount)
   341  
   342  	err = m.Stop()
   343  	require.NoError(t, err)
   344  
   345  	waitForTaskToStop(m)
   346  
   347  	res, err := m.GetAllPending()
   348  	require.NoError(t, err)
   349  	require.Equal(t, 0, len(res))
   350  
   351  	sub.Unsubscribe()
   352  }
   353  
   354  func TestPendingTxTracker_Watch(t *testing.T) {
   355  	m, stop, chainClient, eventFeed := setupTestTransactionDB(t, nil)
   356  	defer stop()
   357  
   358  	txs := GenerateTestPendingTransactions(0, 2)
   359  	// Make the second already confirmed
   360  	*txs[0].Status = Success
   361  
   362  	// Mock the first call to getTransactionByHash
   363  	chainClient.SetAvailableClients([]common.ChainID{txs[0].ChainID})
   364  	cl := chainClient.Clients[txs[0].ChainID]
   365  	cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
   366  		return len(b) == 1 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[1].Hash
   367  	})).Return(nil).Once().Run(func(args mock.Arguments) {
   368  		elems := args.Get(1).([]rpc.BatchElem)
   369  		// Simulate parsing of eth_getTransactionReceipt response
   370  		elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
   371  			BlockNumber: new(big.Int).SetUint64(1),
   372  			Status:      1,
   373  		}
   374  	})
   375  
   376  	eventChan := make(chan walletevent.Event, 3)
   377  	sub := eventFeed.Subscribe(eventChan)
   378  
   379  	// Track the first transaction
   380  	err := m.TrackPendingTransaction(txs[1].ChainID, txs[1].Hash, txs[1].From, txs[1].To, txs[1].Type, Keep, "")
   381  	require.NoError(t, err)
   382  
   383  	// Store the confirmed already
   384  	err = m.StoreAndTrackPendingTx(&txs[0])
   385  	require.NoError(t, err)
   386  
   387  	storeEventCount := 0
   388  	statusEventCount := 0
   389  	for j := 0; j < 3; j++ {
   390  		select {
   391  		case we := <-eventChan:
   392  			if EventPendingTransactionUpdate == we.Type {
   393  				storeEventCount++
   394  			} else if EventPendingTransactionStatusChanged == we.Type {
   395  				statusEventCount++
   396  				var p StatusChangedPayload
   397  				err := json.Unmarshal([]byte(we.Message), &p)
   398  				require.NoError(t, err)
   399  				require.Equal(t, txs[1].ChainID, p.ChainID)
   400  				require.Equal(t, txs[1].Hash, p.Hash)
   401  				require.Equal(t, Success, p.Status)
   402  			}
   403  		case <-time.After(1 * time.Second):
   404  			t.Fatal("timeout waiting for the status update event")
   405  		}
   406  	}
   407  	require.Equal(t, 2, storeEventCount)
   408  	require.Equal(t, 1, statusEventCount)
   409  
   410  	// Stop the next timed call
   411  	err = m.Stop()
   412  	require.NoError(t, err)
   413  
   414  	waitForTaskToStop(m)
   415  
   416  	res, err := m.GetAllPending()
   417  	require.NoError(t, err)
   418  	require.Equal(t, 0, len(res), "should have no pending tx")
   419  
   420  	status, err := m.Watch(context.Background(), txs[1].ChainID, txs[1].Hash)
   421  	require.NoError(t, err)
   422  	require.NotEqual(t, Pending, status)
   423  
   424  	err = m.Delete(context.Background(), txs[1].ChainID, txs[1].Hash)
   425  	require.NoError(t, err)
   426  
   427  	select {
   428  	case we := <-eventChan:
   429  		require.Equal(t, EventPendingTransactionUpdate, we.Type)
   430  	case <-time.After(1 * time.Second):
   431  		t.Fatal("timeout waiting for the delete event")
   432  	}
   433  
   434  	sub.Unsubscribe()
   435  }
   436  
   437  func TestPendingTxTracker_Watch_StatusChangeIncrementally(t *testing.T) {
   438  	m, stop, chainClient, eventFeed := setupTestTransactionDB(t, common.NewAndSet(1*time.Nanosecond))
   439  	defer stop()
   440  
   441  	txs := GenerateTestPendingTransactions(0, 2)
   442  
   443  	var firsDoneWG sync.WaitGroup
   444  	firsDoneWG.Add(1)
   445  
   446  	// Mock the first call to getTransactionByHash
   447  	chainClient.SetAvailableClients([]common.ChainID{txs[0].ChainID})
   448  	cl := chainClient.Clients[txs[0].ChainID]
   449  
   450  	cl.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool {
   451  		if len(cl.Calls) == 0 {
   452  			res := len(b) > 0 && b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[0].Hash
   453  			// If the first processing call picked up the second validate this case also
   454  			if len(b) == 2 {
   455  				res = res && b[1].Method == GetTransactionReceiptRPCName && b[1].Args[0] == txs[1].Hash
   456  			}
   457  			return res
   458  		}
   459  		// Second call we expect only one left
   460  		return len(b) == 1 && (b[0].Method == GetTransactionReceiptRPCName && b[0].Args[0] == txs[1].Hash)
   461  	})).Return(nil).Twice().Run(func(args mock.Arguments) {
   462  		elems := args.Get(1).([]rpc.BatchElem)
   463  		if len(cl.Calls) == 2 {
   464  			firsDoneWG.Wait()
   465  		}
   466  		// Only first item is processed, second is left pending
   467  		// Simulate parsing of eth_getTransactionReceipt response
   468  		elems[0].Result.(*nullableReceipt).Receipt = &types.Receipt{
   469  			BlockNumber: new(big.Int).SetUint64(1),
   470  			Status:      1,
   471  		}
   472  	})
   473  
   474  	eventChan := make(chan walletevent.Event, 6)
   475  	sub := eventFeed.Subscribe(eventChan)
   476  
   477  	for i := range txs {
   478  		// Track the first transaction
   479  		err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, Keep, "")
   480  		require.NoError(t, err)
   481  	}
   482  
   483  	storeEventCount := 0
   484  	statusEventCount := 0
   485  
   486  	validateStatusChange := func(we *walletevent.Event) {
   487  		var p StatusChangedPayload
   488  		err := json.Unmarshal([]byte(we.Message), &p)
   489  		require.NoError(t, err)
   490  
   491  		if statusEventCount == 0 {
   492  			require.Equal(t, txs[0].ChainID, p.ChainID)
   493  			require.Equal(t, txs[0].Hash, p.Hash)
   494  			require.Equal(t, Success, p.Status)
   495  
   496  			status, err := m.Watch(context.Background(), txs[0].ChainID, txs[0].Hash)
   497  			require.NoError(t, err)
   498  			require.Equal(t, Success, *status)
   499  			err = m.Delete(context.Background(), txs[0].ChainID, txs[0].Hash)
   500  			require.NoError(t, err)
   501  
   502  			status, err = m.Watch(context.Background(), txs[1].ChainID, txs[1].Hash)
   503  			require.NoError(t, err)
   504  			require.Equal(t, Pending, *status)
   505  			firsDoneWG.Done()
   506  		} else {
   507  			_, err := m.Watch(context.Background(), txs[0].ChainID, txs[0].Hash)
   508  			require.Equal(t, err, sql.ErrNoRows)
   509  
   510  			status, err := m.Watch(context.Background(), txs[1].ChainID, txs[1].Hash)
   511  			require.NoError(t, err)
   512  			require.Equal(t, Success, *status)
   513  			err = m.Delete(context.Background(), txs[1].ChainID, txs[1].Hash)
   514  			require.NoError(t, err)
   515  		}
   516  
   517  		statusEventCount++
   518  	}
   519  
   520  	for j := 0; j < 6; j++ {
   521  		select {
   522  		case we := <-eventChan:
   523  			if EventPendingTransactionUpdate == we.Type {
   524  				storeEventCount++
   525  			} else if EventPendingTransactionStatusChanged == we.Type {
   526  				validateStatusChange(&we)
   527  			}
   528  		case <-time.After(1 * time.Second):
   529  			t.Fatal("timeout waiting for the status update event")
   530  		}
   531  	}
   532  
   533  	_, err := m.Watch(context.Background(), txs[1].ChainID, txs[1].Hash)
   534  	require.Equal(t, err, sql.ErrNoRows)
   535  
   536  	// One for add and one for delete
   537  	require.Equal(t, 4, storeEventCount)
   538  	require.Equal(t, 2, statusEventCount)
   539  
   540  	err = m.Stop()
   541  	require.NoError(t, err)
   542  
   543  	waitForTaskToStop(m)
   544  
   545  	res, err := m.GetAllPending()
   546  	require.NoError(t, err)
   547  	require.Equal(t, 0, len(res), "should have no pending tx")
   548  
   549  	sub.Unsubscribe()
   550  }
   551  
   552  func TestPendingTransactions(t *testing.T) {
   553  	manager, stop, _, _ := setupTestTransactionDB(t, nil)
   554  	defer stop()
   555  
   556  	tx := GenerateTestPendingTransactions(0, 1)[0]
   557  
   558  	rst, err := manager.GetAllPending()
   559  	require.NoError(t, err)
   560  	require.Nil(t, rst)
   561  
   562  	rst, err = manager.GetPendingByAddress([]uint64{777}, tx.From)
   563  	require.NoError(t, err)
   564  	require.Nil(t, rst)
   565  
   566  	err = manager.addPending(&tx)
   567  	require.NoError(t, err)
   568  
   569  	rst, err = manager.GetPendingByAddress([]uint64{777}, tx.From)
   570  	require.NoError(t, err)
   571  	require.Equal(t, 1, len(rst))
   572  	require.Equal(t, tx, *rst[0])
   573  
   574  	rst, err = manager.GetAllPending()
   575  	require.NoError(t, err)
   576  	require.Equal(t, 1, len(rst))
   577  	require.Equal(t, tx, *rst[0])
   578  
   579  	rst, err = manager.GetPendingByAddress([]uint64{777}, eth.Address{2})
   580  	require.NoError(t, err)
   581  	require.Nil(t, rst)
   582  
   583  	err = manager.Delete(context.Background(), common.ChainID(777), tx.Hash)
   584  	require.Error(t, err, ErrStillPending)
   585  
   586  	rst, err = manager.GetPendingByAddress([]uint64{777}, tx.From)
   587  	require.NoError(t, err)
   588  	require.Equal(t, 0, len(rst))
   589  
   590  	rst, err = manager.GetAllPending()
   591  	require.NoError(t, err)
   592  	require.Equal(t, 0, len(rst))
   593  }