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 }