github.com/status-im/status-go@v1.1.0/services/wallet/history/service_test.go (about)

     1  package history
     2  
     3  import (
     4  	"errors"
     5  	"math/big"
     6  	"reflect"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/golang/mock/gomock"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethereum/go-ethereum/common/hexutil"
    16  	"github.com/ethereum/go-ethereum/event"
    17  	gethrpc "github.com/ethereum/go-ethereum/rpc"
    18  	"github.com/status-im/status-go/appdatabase"
    19  	"github.com/status-im/status-go/multiaccounts/accounts"
    20  	"github.com/status-im/status-go/params"
    21  	"github.com/status-im/status-go/rpc"
    22  	"github.com/status-im/status-go/services/accounts/accountsevent"
    23  	"github.com/status-im/status-go/t/helpers"
    24  	"github.com/status-im/status-go/t/utils"
    25  	"github.com/status-im/status-go/transactions/fake"
    26  	"github.com/status-im/status-go/walletdatabase"
    27  )
    28  
    29  func Test_entriesToDataPoints(t *testing.T) {
    30  	type args struct {
    31  		data []*entry
    32  	}
    33  	tests := []struct {
    34  		name    string
    35  		args    args
    36  		want    []*DataPoint
    37  		wantErr bool
    38  	}{
    39  		{
    40  			name: "zeroAllChainsSameTimestamp",
    41  			args: args{
    42  				data: []*entry{
    43  					{
    44  						chainID:   1,
    45  						balance:   big.NewInt(0),
    46  						timestamp: 1,
    47  						block:     big.NewInt(1),
    48  					},
    49  					{
    50  						chainID:   2,
    51  						balance:   big.NewInt(0),
    52  						timestamp: 1,
    53  						block:     big.NewInt(5),
    54  					},
    55  				},
    56  			},
    57  			want: []*DataPoint{
    58  				{
    59  					Balance:   (*hexutil.Big)(big.NewInt(0)),
    60  					Timestamp: 1,
    61  				},
    62  			},
    63  			wantErr: false,
    64  		},
    65  		{
    66  			name: "oneZeroAllChainsDifferentTimestamp",
    67  			args: args{
    68  				data: []*entry{
    69  					{
    70  						chainID:   2,
    71  						balance:   big.NewInt(0),
    72  						timestamp: 1,
    73  						block:     big.NewInt(1),
    74  					},
    75  					{
    76  						chainID:   1,
    77  						balance:   big.NewInt(2),
    78  						timestamp: 2,
    79  						block:     big.NewInt(2),
    80  					},
    81  				},
    82  			},
    83  			want: []*DataPoint{
    84  				{
    85  					Balance:   (*hexutil.Big)(big.NewInt(0)),
    86  					Timestamp: 1,
    87  				},
    88  				{
    89  					Balance:   (*hexutil.Big)(big.NewInt(2)),
    90  					Timestamp: 2,
    91  				},
    92  			},
    93  			wantErr: false,
    94  		},
    95  		{
    96  			name: "nonZeroAllChainsDifferentTimestamp",
    97  			args: args{
    98  				data: []*entry{
    99  					{
   100  						chainID:   2,
   101  						balance:   big.NewInt(1),
   102  						timestamp: 1,
   103  					},
   104  					{
   105  						chainID:   1,
   106  						balance:   big.NewInt(2),
   107  						timestamp: 2,
   108  					},
   109  				},
   110  			},
   111  			want: []*DataPoint{
   112  				{
   113  					Balance:   (*hexutil.Big)(big.NewInt(1)),
   114  					Timestamp: 1,
   115  				},
   116  				{
   117  					Balance:   (*hexutil.Big)(big.NewInt(3)),
   118  					Timestamp: 2,
   119  				},
   120  			},
   121  			wantErr: false,
   122  		},
   123  		{
   124  			name: "sameChainDifferentTimestamp",
   125  			args: args{
   126  				data: []*entry{
   127  					{
   128  						chainID:   1,
   129  						balance:   big.NewInt(1),
   130  						timestamp: 1,
   131  						block:     big.NewInt(1),
   132  					},
   133  					{
   134  						chainID:   1,
   135  						balance:   big.NewInt(2),
   136  						timestamp: 2,
   137  						block:     big.NewInt(2),
   138  					},
   139  					{
   140  						chainID:   1,
   141  						balance:   big.NewInt(0),
   142  						timestamp: 3,
   143  					},
   144  				},
   145  			},
   146  			want: []*DataPoint{
   147  				{
   148  					Balance:   (*hexutil.Big)(big.NewInt(1)),
   149  					Timestamp: 1,
   150  				},
   151  				{
   152  					Balance:   (*hexutil.Big)(big.NewInt(2)),
   153  					Timestamp: 2,
   154  				},
   155  				{
   156  					Balance:   (*hexutil.Big)(big.NewInt(0)),
   157  					Timestamp: 3,
   158  				},
   159  			},
   160  			wantErr: false,
   161  		},
   162  		{
   163  			name: "sameChainDifferentTimestampOtherChainsEmpty",
   164  			args: args{
   165  				data: []*entry{
   166  					{
   167  						chainID:   1,
   168  						balance:   big.NewInt(1),
   169  						timestamp: 1,
   170  						block:     big.NewInt(1),
   171  					},
   172  					{
   173  						chainID:   1,
   174  						balance:   big.NewInt(2),
   175  						timestamp: 2,
   176  						block:     big.NewInt(2),
   177  					},
   178  					{
   179  						chainID:   2,
   180  						balance:   big.NewInt(0),
   181  						timestamp: 2,
   182  						block:     big.NewInt(2),
   183  					},
   184  					{
   185  						chainID:   1,
   186  						balance:   big.NewInt(2),
   187  						timestamp: 3,
   188  					},
   189  				},
   190  			},
   191  			want: []*DataPoint{
   192  				{
   193  					Balance:   (*hexutil.Big)(big.NewInt(1)),
   194  					Timestamp: 1,
   195  				},
   196  				{
   197  					Balance:   (*hexutil.Big)(big.NewInt(2)),
   198  					Timestamp: 2,
   199  				},
   200  				{
   201  					Balance:   (*hexutil.Big)(big.NewInt(2)),
   202  					Timestamp: 3,
   203  				},
   204  			},
   205  			wantErr: false,
   206  		},
   207  		{
   208  			name: "onlyEdgePointsOnManyChainsWithPadding",
   209  			args: args{
   210  				data: []*entry{
   211  					// Left edge - same timestamp
   212  					{
   213  						chainID:   1,
   214  						balance:   big.NewInt(1),
   215  						timestamp: 1,
   216  					},
   217  					{
   218  						chainID:   2,
   219  						balance:   big.NewInt(2),
   220  						timestamp: 1,
   221  					},
   222  					{
   223  						chainID:   3,
   224  						balance:   big.NewInt(3),
   225  						timestamp: 1,
   226  					},
   227  					// Padding
   228  					{
   229  						chainID:   3,
   230  						balance:   big.NewInt(3),
   231  						timestamp: 2,
   232  					},
   233  					{
   234  						chainID:   3,
   235  						balance:   big.NewInt(3),
   236  						timestamp: 3,
   237  					},
   238  					{
   239  						chainID:   3,
   240  						balance:   big.NewInt(3),
   241  						timestamp: 4,
   242  					},
   243  					// Right edge - same timestamp
   244  					{
   245  						chainID:   1,
   246  						balance:   big.NewInt(1),
   247  						timestamp: 5,
   248  					},
   249  					{
   250  						chainID:   2,
   251  						balance:   big.NewInt(2),
   252  						timestamp: 5,
   253  					},
   254  					{
   255  						chainID:   3,
   256  						balance:   big.NewInt(3),
   257  						timestamp: 5,
   258  					},
   259  				},
   260  			},
   261  			want: []*DataPoint{
   262  				{
   263  					Balance:   (*hexutil.Big)(big.NewInt(6)),
   264  					Timestamp: 1,
   265  				},
   266  				{
   267  					Balance:   (*hexutil.Big)(big.NewInt(6)),
   268  					Timestamp: 2,
   269  				},
   270  				{
   271  					Balance:   (*hexutil.Big)(big.NewInt(6)),
   272  					Timestamp: 3,
   273  				},
   274  				{
   275  					Balance:   (*hexutil.Big)(big.NewInt(6)),
   276  					Timestamp: 4,
   277  				},
   278  				{
   279  					Balance:   (*hexutil.Big)(big.NewInt(6)),
   280  					Timestamp: 5,
   281  				},
   282  			},
   283  			wantErr: false,
   284  		},
   285  		{
   286  			name: "multipleAddresses",
   287  			args: args{
   288  				data: []*entry{
   289  					{
   290  						chainID:   2,
   291  						balance:   big.NewInt(5),
   292  						timestamp: 1,
   293  						address:   common.Address{1},
   294  					},
   295  					{
   296  						chainID:   1,
   297  						balance:   big.NewInt(6),
   298  						timestamp: 1,
   299  						address:   common.Address{2},
   300  					},
   301  					{
   302  						chainID:   1,
   303  						balance:   big.NewInt(1),
   304  						timestamp: 2,
   305  						address:   common.Address{1},
   306  					},
   307  					// padding - duplicate last point, just update timestamp
   308  					{
   309  						chainID:   1,
   310  						balance:   big.NewInt(1),
   311  						timestamp: 3,
   312  						address:   common.Address{1},
   313  					},
   314  					{
   315  						chainID:   1,
   316  						balance:   big.NewInt(1),
   317  						timestamp: 4,
   318  						address:   common.Address{1},
   319  					},
   320  					{
   321  						chainID:   1,
   322  						balance:   big.NewInt(1),
   323  						timestamp: 5,
   324  						address:   common.Address{1},
   325  					},
   326  					// real points
   327  					{
   328  						chainID:   1,
   329  						balance:   big.NewInt(2),
   330  						timestamp: 6,
   331  						address:   common.Address{2},
   332  					},
   333  					{
   334  						chainID:   1,
   335  						balance:   big.NewInt(4),
   336  						timestamp: 7,
   337  						address:   common.Address{2},
   338  					},
   339  				},
   340  			},
   341  			want: []*DataPoint{
   342  				{
   343  					Balance:   (*hexutil.Big)(big.NewInt(11)),
   344  					Timestamp: 1,
   345  				},
   346  				{
   347  					Balance:   (*hexutil.Big)(big.NewInt(12)),
   348  					Timestamp: 2,
   349  				},
   350  				// padding
   351  				{
   352  					Balance:   (*hexutil.Big)(big.NewInt(12)),
   353  					Timestamp: 3,
   354  				},
   355  				{
   356  					Balance:   (*hexutil.Big)(big.NewInt(12)),
   357  					Timestamp: 4,
   358  				},
   359  				{
   360  					Balance:   (*hexutil.Big)(big.NewInt(12)),
   361  					Timestamp: 5,
   362  				},
   363  				// real points
   364  				{
   365  					Balance:   (*hexutil.Big)(big.NewInt(8)),
   366  					Timestamp: 6,
   367  				},
   368  				{
   369  					Balance:   (*hexutil.Big)(big.NewInt(10)),
   370  					Timestamp: 7,
   371  				},
   372  			},
   373  			wantErr: false,
   374  		},
   375  	}
   376  
   377  	for _, tt := range tests {
   378  		t.Run(tt.name, func(t *testing.T) {
   379  			got, err := entriesToDataPoints(tt.args.data)
   380  			if (err != nil) != tt.wantErr {
   381  				t.Errorf("entriesToDataPoints() name: %s, error = %v, wantErr = %v", tt.name, err, tt.wantErr)
   382  				return
   383  			}
   384  			if !reflect.DeepEqual(got, tt.want) {
   385  				t.Errorf("entriesToDataPoints() name: %s, got: %v, want: %v", tt.name, got, tt.want)
   386  			}
   387  		})
   388  	}
   389  }
   390  
   391  func Test_removeBalanceHistoryOnEventAccountRemoved(t *testing.T) {
   392  	appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
   393  	require.NoError(t, err)
   394  
   395  	walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
   396  	require.NoError(t, err)
   397  
   398  	accountsDB, err := accounts.NewDB(appDB)
   399  	require.NoError(t, err)
   400  
   401  	address := common.HexToAddress("0x1234")
   402  	accountFeed := event.Feed{}
   403  	walletFeed := event.Feed{}
   404  	chainID := uint64(1)
   405  	txServiceMockCtrl := gomock.NewController(t)
   406  	server, _ := fake.NewTestServer(txServiceMockCtrl)
   407  	client := gethrpc.DialInProc(server)
   408  	rpcClient, _ := rpc.NewClient(client, chainID, params.UpstreamRPCConfig{}, nil, appDB, nil)
   409  	rpcClient.UpstreamChainID = chainID
   410  
   411  	service := NewService(walletDB, accountsDB, &accountFeed, &walletFeed, rpcClient, nil, nil, nil)
   412  
   413  	// Insert balances for address
   414  	database := service.balance.db
   415  	err = database.add(&entry{
   416  		chainID:     chainID,
   417  		address:     address,
   418  		block:       big.NewInt(1),
   419  		balance:     big.NewInt(1),
   420  		timestamp:   1,
   421  		tokenSymbol: "ETH",
   422  	})
   423  	require.NoError(t, err)
   424  	err = database.add(&entry{
   425  		chainID:     chainID,
   426  		address:     address,
   427  		block:       big.NewInt(2),
   428  		balance:     big.NewInt(2),
   429  		tokenSymbol: "ETH",
   430  		timestamp:   2,
   431  	})
   432  	require.NoError(t, err)
   433  
   434  	entries, err := database.getNewerThan(&assetIdentity{chainID, []common.Address{address}, "ETH"}, 0)
   435  	require.NoError(t, err)
   436  	require.Len(t, entries, 2)
   437  
   438  	// Start service
   439  	service.startAccountWatcher()
   440  
   441  	// Watching accounts must start before sending event.
   442  	// To avoid running goroutine immediately and let the controller subscribe first,
   443  	// use any delay.
   444  	group := sync.WaitGroup{}
   445  	group.Add(1)
   446  	go func() {
   447  		defer group.Done()
   448  		time.Sleep(1 * time.Millisecond)
   449  
   450  		accountFeed.Send(accountsevent.Event{
   451  			Type:     accountsevent.EventTypeRemoved,
   452  			Accounts: []common.Address{address},
   453  		})
   454  
   455  		err := utils.Eventually(func() error {
   456  			entries, err := database.getNewerThan(&assetIdentity{1, []common.Address{address}, "ETH"}, 0)
   457  			if err == nil && len(entries) == 0 {
   458  				return nil
   459  			}
   460  			return errors.New("data is not removed")
   461  		}, 100*time.Millisecond, 10*time.Millisecond)
   462  		require.NoError(t, err)
   463  	}()
   464  
   465  	group.Wait()
   466  
   467  	// Stop service
   468  	txServiceMockCtrl.Finish()
   469  	server.Stop()
   470  	service.stopAccountWatcher()
   471  }