status-go/services/wallet/history/service_test.go

438 lines
9.0 KiB
Go

package history
import (
"errors"
"math/big"
"reflect"
"sync"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/event"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/accounts/accountsevent"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/t/utils"
"github.com/status-im/status-go/transactions/fake"
"github.com/status-im/status-go/walletdatabase"
)
func Test_entriesToDataPoints(t *testing.T) {
type args struct {
data []*entry
}
tests := []struct {
name string
args args
want []*DataPoint
wantErr bool
}{
{
name: "zeroAllChainsSameTimestamp",
args: args{
data: []*entry{
{
chainID: 1,
balance: big.NewInt(0),
timestamp: 1,
block: big.NewInt(1),
},
{
chainID: 2,
balance: big.NewInt(0),
timestamp: 1,
block: big.NewInt(5),
},
},
},
want: []*DataPoint{
{
Balance: (*hexutil.Big)(big.NewInt(0)),
Timestamp: 1,
},
},
wantErr: false,
},
{
name: "oneZeroAllChainsDifferentTimestamp",
args: args{
data: []*entry{
{
chainID: 2,
balance: big.NewInt(0),
timestamp: 1,
block: big.NewInt(1),
},
{
chainID: 1,
balance: big.NewInt(2),
timestamp: 2,
block: big.NewInt(2),
},
},
},
want: []*DataPoint{
{
Balance: (*hexutil.Big)(big.NewInt(0)),
Timestamp: 1,
},
{
Balance: (*hexutil.Big)(big.NewInt(2)),
Timestamp: 2,
},
},
wantErr: false,
},
{
name: "nonZeroAllChainsDifferentTimestamp",
args: args{
data: []*entry{
{
chainID: 2,
balance: big.NewInt(1),
timestamp: 1,
},
{
chainID: 1,
balance: big.NewInt(2),
timestamp: 2,
},
},
},
want: []*DataPoint{
{
Balance: (*hexutil.Big)(big.NewInt(1)),
Timestamp: 1,
},
{
Balance: (*hexutil.Big)(big.NewInt(3)),
Timestamp: 2,
},
},
wantErr: false,
},
{
name: "sameChainDifferentTimestamp",
args: args{
data: []*entry{
{
chainID: 1,
balance: big.NewInt(1),
timestamp: 1,
block: big.NewInt(1),
},
{
chainID: 1,
balance: big.NewInt(2),
timestamp: 2,
block: big.NewInt(2),
},
{
chainID: 1,
balance: big.NewInt(0),
timestamp: 3,
},
},
},
want: []*DataPoint{
{
Balance: (*hexutil.Big)(big.NewInt(1)),
Timestamp: 1,
},
{
Balance: (*hexutil.Big)(big.NewInt(2)),
Timestamp: 2,
},
{
Balance: (*hexutil.Big)(big.NewInt(0)),
Timestamp: 3,
},
},
wantErr: false,
},
{
name: "sameChainDifferentTimestampOtherChainsEmpty",
args: args{
data: []*entry{
{
chainID: 1,
balance: big.NewInt(1),
timestamp: 1,
block: big.NewInt(1),
},
{
chainID: 1,
balance: big.NewInt(2),
timestamp: 2,
block: big.NewInt(2),
},
{
chainID: 2,
balance: big.NewInt(0),
timestamp: 2,
block: big.NewInt(2),
},
{
chainID: 1,
balance: big.NewInt(2),
timestamp: 3,
},
},
},
want: []*DataPoint{
{
Balance: (*hexutil.Big)(big.NewInt(1)),
Timestamp: 1,
},
{
Balance: (*hexutil.Big)(big.NewInt(2)),
Timestamp: 2,
},
{
Balance: (*hexutil.Big)(big.NewInt(2)),
Timestamp: 3,
},
},
wantErr: false,
},
{
name: "onlyEdgePointsOnManyChainsWithPadding",
args: args{
data: []*entry{
// Left edge - same timestamp
{
chainID: 1,
balance: big.NewInt(1),
timestamp: 1,
},
{
chainID: 2,
balance: big.NewInt(2),
timestamp: 1,
},
{
chainID: 3,
balance: big.NewInt(3),
timestamp: 1,
},
// Padding
{
chainID: 0,
balance: big.NewInt(6),
timestamp: 2,
},
{
chainID: 0,
balance: big.NewInt(6),
timestamp: 3,
},
{
chainID: 0,
balance: big.NewInt(6),
timestamp: 4,
},
// Right edge - same timestamp
{
chainID: 1,
balance: big.NewInt(1),
timestamp: 5,
},
{
chainID: 2,
balance: big.NewInt(2),
timestamp: 5,
},
{
chainID: 3,
balance: big.NewInt(3),
timestamp: 5,
},
},
},
want: []*DataPoint{
{
Balance: (*hexutil.Big)(big.NewInt(6)),
Timestamp: 1,
},
{
Balance: (*hexutil.Big)(big.NewInt(6)),
Timestamp: 2,
},
{
Balance: (*hexutil.Big)(big.NewInt(6)),
Timestamp: 3,
},
{
Balance: (*hexutil.Big)(big.NewInt(6)),
Timestamp: 4,
},
{
Balance: (*hexutil.Big)(big.NewInt(6)),
Timestamp: 5,
},
},
wantErr: false,
},
{
name: "multipleAddresses",
args: args{
data: []*entry{
{
chainID: 2,
balance: big.NewInt(5),
timestamp: 1,
address: common.Address{1},
},
{
chainID: 1,
balance: big.NewInt(6),
timestamp: 1,
address: common.Address{2},
},
{
chainID: 1,
balance: big.NewInt(1),
timestamp: 2,
address: common.Address{1},
},
{
chainID: 1,
balance: big.NewInt(2),
timestamp: 3,
address: common.Address{2},
},
{
chainID: 1,
balance: big.NewInt(4),
timestamp: 4,
address: common.Address{2},
},
},
},
want: []*DataPoint{
{
Balance: (*hexutil.Big)(big.NewInt(11)),
Timestamp: 1,
},
{
Balance: (*hexutil.Big)(big.NewInt(12)),
Timestamp: 2,
},
{
Balance: (*hexutil.Big)(big.NewInt(8)),
Timestamp: 3,
},
{
Balance: (*hexutil.Big)(big.NewInt(10)),
Timestamp: 4,
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := entriesToDataPoints(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("entriesToDataPoints() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("entriesToDataPoints() = %v, want %v", got, tt.want)
}
})
}
}
func Test_removeBalanceHistoryOnEventAccountRemoved(t *testing.T) {
appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(t, err)
walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
accountsDB, err := accounts.NewDB(appDB)
require.NoError(t, err)
address := common.HexToAddress("0x1234")
accountFeed := event.Feed{}
walletFeed := event.Feed{}
chainID := uint64(1)
txServiceMockCtrl := gomock.NewController(t)
server, _ := fake.NewTestServer(txServiceMockCtrl)
client := gethrpc.DialInProc(server)
rpcClient, _ := rpc.NewClient(client, chainID, params.UpstreamRPCConfig{}, nil, appDB)
rpcClient.UpstreamChainID = chainID
service := NewService(walletDB, accountsDB, &accountFeed, &walletFeed, rpcClient, nil, nil, nil)
// Insert balances for address
database := service.balance.db
err = database.add(&entry{
chainID: chainID,
address: address,
block: big.NewInt(1),
balance: big.NewInt(1),
timestamp: 1,
tokenSymbol: "ETH",
})
require.NoError(t, err)
err = database.add(&entry{
chainID: chainID,
address: address,
block: big.NewInt(2),
balance: big.NewInt(2),
tokenSymbol: "ETH",
timestamp: 2,
})
require.NoError(t, err)
entries, err := database.getNewerThan(&assetIdentity{chainID, []common.Address{address}, "ETH"}, 0)
require.NoError(t, err)
require.Len(t, entries, 2)
// Start service
service.startAccountWatcher()
// Watching accounts must start before sending event.
// To avoid running goroutine immediately and let the controller subscribe first,
// use any delay.
group := sync.WaitGroup{}
group.Add(1)
go func() {
defer group.Done()
time.Sleep(1 * time.Millisecond)
accountFeed.Send(accountsevent.Event{
Type: accountsevent.EventTypeRemoved,
Accounts: []common.Address{address},
})
err := utils.Eventually(func() error {
entries, err := database.getNewerThan(&assetIdentity{1, []common.Address{address}, "ETH"}, 0)
if err == nil && len(entries) == 0 {
return nil
}
return errors.New("data is not removed")
}, 100*time.Millisecond, 10*time.Millisecond)
require.NoError(t, err)
}()
group.Wait()
// Stop service
txServiceMockCtrl.Finish()
server.Stop()
service.stopAccountWatcher()
}