github.com/iotexproject/iotex-core@v1.14.1-rc1/blockindex/contractstaking/indexer_test.go (about) 1 // Copyright (c) 2023 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package contractstaking 7 8 import ( 9 "context" 10 "math/big" 11 "strconv" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/ethereum/go-ethereum/common" 17 "github.com/iotexproject/iotex-address/address" 18 "github.com/stretchr/testify/require" 19 "golang.org/x/exp/slices" 20 21 "github.com/iotexproject/iotex-core/action/protocol" 22 "github.com/iotexproject/iotex-core/action/protocol/staking" 23 "github.com/iotexproject/iotex-core/blockchain/block" 24 "github.com/iotexproject/iotex-core/blockchain/genesis" 25 "github.com/iotexproject/iotex-core/config" 26 "github.com/iotexproject/iotex-core/consensus/consensusfsm" 27 "github.com/iotexproject/iotex-core/db" 28 "github.com/iotexproject/iotex-core/test/identityset" 29 "github.com/iotexproject/iotex-core/testutil" 30 ) 31 32 const ( 33 _testStakingContractAddress = "io19ys8f4uhwms6lq6ulexr5fwht9gsjes8mvuugd" 34 ) 35 36 var ( 37 _blockInterval = consensusfsm.DefaultDardanellesUpgradeConfig.BlockInterval 38 ) 39 40 func TestNewContractStakingIndexer(t *testing.T) { 41 r := require.New(t) 42 43 t.Run("kvStore is nil", func(t *testing.T) { 44 _, err := NewContractStakingIndexer(nil, Config{ 45 ContractAddress: "io19ys8f4uhwms6lq6ulexr5fwht9gsjes8mvuugd", 46 ContractDeployHeight: 0, 47 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 48 BlockInterval: _blockInterval, 49 }) 50 r.Error(err) 51 r.Contains(err.Error(), "kv store is nil") 52 }) 53 54 t.Run("invalid contract address", func(t *testing.T) { 55 kvStore := db.NewMemKVStore() 56 _, err := NewContractStakingIndexer(kvStore, Config{ 57 ContractAddress: "invalid address", 58 ContractDeployHeight: 0, 59 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 60 BlockInterval: _blockInterval, 61 }) 62 r.Error(err) 63 r.Contains(err.Error(), "invalid contract address") 64 }) 65 66 t.Run("valid input", func(t *testing.T) { 67 contractAddr, err := address.FromString("io19ys8f4uhwms6lq6ulexr5fwht9gsjes8mvuugd") 68 r.NoError(err) 69 indexer, err := NewContractStakingIndexer(db.NewMemKVStore(), Config{ 70 ContractAddress: contractAddr.String(), 71 ContractDeployHeight: 0, 72 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 73 BlockInterval: _blockInterval, 74 }) 75 r.NoError(err) 76 r.NotNil(indexer) 77 }) 78 } 79 80 func TestContractStakingIndexerLoadCache(t *testing.T) { 81 r := require.New(t) 82 testDBPath, err := testutil.PathOfTempFile("staking.db") 83 r.NoError(err) 84 defer testutil.CleanupPath(testDBPath) 85 cfg := db.DefaultConfig 86 cfg.DbPath = testDBPath 87 kvStore := db.NewBoltDB(cfg) 88 indexer, err := NewContractStakingIndexer(kvStore, Config{ 89 ContractAddress: _testStakingContractAddress, 90 ContractDeployHeight: 0, 91 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 92 BlockInterval: _blockInterval, 93 }) 94 r.NoError(err) 95 r.NoError(indexer.Start(context.Background())) 96 97 // create a stake 98 height := uint64(1) 99 startHeight := uint64(1) 100 handler := newContractStakingEventHandler(indexer.cache) 101 activateBucketType(r, handler, 10, 100, height) 102 owner := identityset.Address(0) 103 delegate := identityset.Address(1) 104 stake(r, handler, owner, delegate, 1, 10, 100, height) 105 err = indexer.commit(handler, height) 106 r.NoError(err) 107 buckets, err := indexer.Buckets(height) 108 r.NoError(err) 109 r.EqualValues(1, len(buckets)) 110 tbc, err := indexer.TotalBucketCount(height) 111 r.EqualValues(1, tbc) 112 r.NoError(err) 113 h, err := indexer.Height() 114 r.NoError(err) 115 r.EqualValues(height, h) 116 117 r.NoError(indexer.Stop(context.Background())) 118 119 // load cache from db 120 newIndexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), Config{ 121 ContractAddress: _testStakingContractAddress, 122 ContractDeployHeight: startHeight, 123 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 124 BlockInterval: _blockInterval, 125 }) 126 r.NoError(err) 127 r.NoError(newIndexer.Start(context.Background())) 128 129 // check cache 130 newBuckets, err := newIndexer.Buckets(height) 131 r.NoError(err) 132 r.Equal(len(buckets), len(newBuckets)) 133 for i := range buckets { 134 r.EqualValues(buckets[i], newBuckets[i]) 135 } 136 newHeight, err := newIndexer.Height() 137 r.NoError(err) 138 r.Equal(height, newHeight) 139 r.Equal(startHeight, newIndexer.StartHeight()) 140 tbc, err = newIndexer.TotalBucketCount(height) 141 r.EqualValues(1, tbc) 142 r.NoError(err) 143 r.NoError(newIndexer.Stop(context.Background())) 144 } 145 146 func TestContractStakingIndexerDirty(t *testing.T) { 147 r := require.New(t) 148 testDBPath, err := testutil.PathOfTempFile("staking.db") 149 r.NoError(err) 150 defer testutil.CleanupPath(testDBPath) 151 cfg := db.DefaultConfig 152 cfg.DbPath = testDBPath 153 kvStore := db.NewBoltDB(cfg) 154 indexer, err := NewContractStakingIndexer(kvStore, Config{ 155 ContractAddress: _testStakingContractAddress, 156 ContractDeployHeight: 0, 157 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 158 BlockInterval: _blockInterval, 159 }) 160 r.NoError(err) 161 r.NoError(indexer.Start(context.Background())) 162 163 // before commit dirty, the cache should be empty 164 height := uint64(1) 165 handler := newContractStakingEventHandler(indexer.cache) 166 gotHeight, err := indexer.Height() 167 r.NoError(err) 168 r.EqualValues(0, gotHeight) 169 // after commit dirty, the cache should be updated 170 err = indexer.commit(handler, height) 171 r.NoError(err) 172 gotHeight, err = indexer.Height() 173 r.NoError(err) 174 r.EqualValues(height, gotHeight) 175 176 r.NoError(indexer.Stop(context.Background())) 177 } 178 179 func TestContractStakingIndexerThreadSafe(t *testing.T) { 180 r := require.New(t) 181 testDBPath, err := testutil.PathOfTempFile("staking.db") 182 r.NoError(err) 183 defer testutil.CleanupPath(testDBPath) 184 cfg := db.DefaultConfig 185 cfg.DbPath = testDBPath 186 kvStore := db.NewBoltDB(cfg) 187 indexer, err := NewContractStakingIndexer(kvStore, Config{ 188 ContractAddress: _testStakingContractAddress, 189 ContractDeployHeight: 0, 190 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 191 BlockInterval: _blockInterval, 192 }) 193 r.NoError(err) 194 r.NoError(indexer.Start(context.Background())) 195 196 wait := sync.WaitGroup{} 197 wait.Add(6) 198 owner := identityset.Address(0) 199 delegate := identityset.Address(1) 200 ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) 201 // read concurrently 202 for i := 0; i < 5; i++ { 203 go func() { 204 defer wait.Done() 205 for i := 0; i < 1000; i++ { 206 _, err := indexer.Buckets(0) 207 r.NoError(err) 208 _, err = indexer.BucketTypes(0) 209 r.NoError(err) 210 _, err = indexer.BucketsByCandidate(delegate, 0) 211 r.NoError(err) 212 indexer.CandidateVotes(ctx, delegate, 0) 213 _, err = indexer.Height() 214 r.NoError(err) 215 indexer.TotalBucketCount(0) 216 } 217 }() 218 } 219 // write 220 go func() { 221 defer wait.Done() 222 // activate bucket type 223 handler := newContractStakingEventHandler(indexer.cache) 224 activateBucketType(r, handler, 10, 100, 1) 225 r.NoError(indexer.commit(handler, 1)) 226 for i := 2; i < 1000; i++ { 227 height := uint64(i) 228 handler := newContractStakingEventHandler(indexer.cache) 229 stake(r, handler, owner, delegate, int64(i), 10, 100, height) 230 err := indexer.commit(handler, height) 231 r.NoError(err) 232 } 233 }() 234 wait.Wait() 235 r.NoError(indexer.Stop(context.Background())) 236 // no panic means thread safe 237 } 238 239 func TestContractStakingIndexerBucketType(t *testing.T) { 240 r := require.New(t) 241 testDBPath, err := testutil.PathOfTempFile("staking.db") 242 r.NoError(err) 243 defer testutil.CleanupPath(testDBPath) 244 cfg := db.DefaultConfig 245 cfg.DbPath = testDBPath 246 kvStore := db.NewBoltDB(cfg) 247 indexer, err := NewContractStakingIndexer(kvStore, Config{ 248 ContractAddress: _testStakingContractAddress, 249 ContractDeployHeight: 0, 250 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 251 BlockInterval: _blockInterval, 252 }) 253 r.NoError(err) 254 r.NoError(indexer.Start(context.Background())) 255 256 // activate 257 bucketTypeData := [][2]int64{ 258 {10, 10}, 259 {20, 10}, 260 {10, 100}, 261 {20, 100}, 262 } 263 existInBucketTypeData := func(bt *BucketType) int { 264 idx := slices.IndexFunc(bucketTypeData, func(data [2]int64) bool { 265 if bt.Amount.Int64() == data[0] && int64(bt.Duration) == data[1] { 266 return true 267 } 268 return false 269 }) 270 return idx 271 } 272 273 height := uint64(1) 274 handler := newContractStakingEventHandler(indexer.cache) 275 for _, data := range bucketTypeData { 276 activateBucketType(r, handler, data[0], data[1], height) 277 } 278 err = indexer.commit(handler, height) 279 r.NoError(err) 280 bucketTypes, err := indexer.BucketTypes(height) 281 r.NoError(err) 282 r.Equal(len(bucketTypeData), len(bucketTypes)) 283 for _, bt := range bucketTypes { 284 r.True(existInBucketTypeData(bt) >= 0) 285 r.EqualValues(height, bt.ActivatedAt) 286 } 287 // deactivate 288 height++ 289 handler = newContractStakingEventHandler(indexer.cache) 290 for i := 0; i < 2; i++ { 291 data := bucketTypeData[i] 292 deactivateBucketType(r, handler, data[0], data[1], height) 293 } 294 err = indexer.commit(handler, height) 295 r.NoError(err) 296 bucketTypes, err = indexer.BucketTypes(height) 297 r.NoError(err) 298 r.Equal(len(bucketTypeData)-2, len(bucketTypes)) 299 for _, bt := range bucketTypes { 300 r.True(existInBucketTypeData(bt) >= 0) 301 r.EqualValues(1, bt.ActivatedAt) 302 } 303 // reactivate 304 height++ 305 handler = newContractStakingEventHandler(indexer.cache) 306 for i := 0; i < 2; i++ { 307 data := bucketTypeData[i] 308 activateBucketType(r, handler, data[0], data[1], height) 309 } 310 err = indexer.commit(handler, height) 311 r.NoError(err) 312 bucketTypes, err = indexer.BucketTypes(height) 313 r.NoError(err) 314 r.Equal(len(bucketTypeData), len(bucketTypes)) 315 for _, bt := range bucketTypes { 316 idx := existInBucketTypeData(bt) 317 r.True(idx >= 0) 318 if idx < 2 { 319 r.EqualValues(height, bt.ActivatedAt) 320 } else { 321 r.EqualValues(1, bt.ActivatedAt) 322 } 323 } 324 r.NoError(indexer.Stop(context.Background())) 325 } 326 327 func TestContractStakingIndexerBucketInfo(t *testing.T) { 328 r := require.New(t) 329 testDBPath, err := testutil.PathOfTempFile("staking.db") 330 r.NoError(err) 331 defer testutil.CleanupPath(testDBPath) 332 cfg := db.DefaultConfig 333 cfg.DbPath = testDBPath 334 kvStore := db.NewBoltDB(cfg) 335 indexer, err := NewContractStakingIndexer(kvStore, Config{ 336 ContractAddress: _testStakingContractAddress, 337 ContractDeployHeight: 0, 338 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 339 BlockInterval: _blockInterval, 340 }) 341 r.NoError(err) 342 r.NoError(indexer.Start(context.Background())) 343 344 // init bucket type 345 bucketTypeData := [][2]int64{ 346 {10, 10}, 347 {20, 10}, 348 {10, 100}, 349 {20, 100}, 350 } 351 height := uint64(1) 352 handler := newContractStakingEventHandler(indexer.cache) 353 for _, data := range bucketTypeData { 354 activateBucketType(r, handler, data[0], data[1], height) 355 } 356 err = indexer.commit(handler, height) 357 r.NoError(err) 358 ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) 359 360 // stake 361 owner := identityset.Address(0) 362 delegate := identityset.Address(1) 363 height++ 364 createHeight := height 365 handler = newContractStakingEventHandler(indexer.cache) 366 stake(r, handler, owner, delegate, 1, 10, 100, height) 367 r.NoError(err) 368 r.NoError(indexer.commit(handler, height)) 369 bucket, ok, err := indexer.Bucket(1, height) 370 r.NoError(err) 371 r.True(ok) 372 r.EqualValues(1, bucket.Index) 373 r.EqualValues(owner, bucket.Owner) 374 r.EqualValues(delegate, bucket.Candidate) 375 r.EqualValues(10, bucket.StakedAmount.Int64()) 376 r.EqualValues(100, bucket.StakedDurationBlockNumber) 377 r.EqualValues(height, bucket.StakeStartBlockHeight) 378 r.True(bucket.AutoStake) 379 r.EqualValues(height, bucket.CreateBlockHeight) 380 r.EqualValues(maxBlockNumber, bucket.UnstakeStartBlockHeight) 381 r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) 382 votes, err := indexer.CandidateVotes(ctx, delegate, height) 383 r.NoError(err) 384 r.EqualValues(10, votes.Uint64()) 385 tbc, err := indexer.TotalBucketCount(height) 386 r.NoError(err) 387 r.EqualValues(1, tbc) 388 389 // transfer 390 newOwner := identityset.Address(2) 391 height++ 392 handler = newContractStakingEventHandler(indexer.cache) 393 transfer(r, handler, newOwner, int64(bucket.Index)) 394 r.NoError(indexer.commit(handler, height)) 395 bucket, ok, err = indexer.Bucket(bucket.Index, height) 396 r.NoError(err) 397 r.True(ok) 398 r.EqualValues(newOwner, bucket.Owner) 399 400 // unlock 401 height++ 402 handler = newContractStakingEventHandler(indexer.cache) 403 unlock(r, handler, int64(bucket.Index), height) 404 r.NoError(indexer.commit(handler, height)) 405 bucket, ok, err = indexer.Bucket(bucket.Index, height) 406 r.NoError(err) 407 r.True(ok) 408 r.EqualValues(1, bucket.Index) 409 r.EqualValues(newOwner, bucket.Owner) 410 r.EqualValues(delegate, bucket.Candidate) 411 r.EqualValues(10, bucket.StakedAmount.Int64()) 412 r.EqualValues(100, bucket.StakedDurationBlockNumber) 413 r.EqualValues(height, bucket.StakeStartBlockHeight) 414 r.False(bucket.AutoStake) 415 r.EqualValues(createHeight, bucket.CreateBlockHeight) 416 r.EqualValues(maxBlockNumber, bucket.UnstakeStartBlockHeight) 417 r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) 418 votes, err = indexer.CandidateVotes(ctx, delegate, height) 419 r.NoError(err) 420 r.EqualValues(10, votes.Uint64()) 421 tbc, err = indexer.TotalBucketCount(height) 422 r.NoError(err) 423 r.EqualValues(1, tbc) 424 425 // lock again 426 height++ 427 handler = newContractStakingEventHandler(indexer.cache) 428 lock(r, handler, int64(bucket.Index), int64(10)) 429 r.NoError(indexer.commit(handler, height)) 430 bucket, ok, err = indexer.Bucket(bucket.Index, height) 431 r.NoError(err) 432 r.True(ok) 433 r.EqualValues(1, bucket.Index) 434 r.EqualValues(newOwner, bucket.Owner) 435 r.EqualValues(delegate, bucket.Candidate) 436 r.EqualValues(10, bucket.StakedAmount.Int64()) 437 r.EqualValues(10, bucket.StakedDurationBlockNumber) 438 r.EqualValues(createHeight, bucket.StakeStartBlockHeight) 439 r.True(bucket.AutoStake) 440 r.EqualValues(createHeight, bucket.CreateBlockHeight) 441 r.EqualValues(maxBlockNumber, bucket.UnstakeStartBlockHeight) 442 r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) 443 votes, err = indexer.CandidateVotes(ctx, delegate, height) 444 r.NoError(err) 445 r.EqualValues(10, votes.Uint64()) 446 tbc, err = indexer.TotalBucketCount(height) 447 r.NoError(err) 448 r.EqualValues(1, tbc) 449 450 // unstake 451 height++ 452 handler = newContractStakingEventHandler(indexer.cache) 453 unlock(r, handler, int64(bucket.Index), height) 454 unstake(r, handler, int64(bucket.Index), height) 455 r.NoError(indexer.commit(handler, height)) 456 bucket, ok, err = indexer.Bucket(bucket.Index, height) 457 r.NoError(err) 458 r.True(ok) 459 r.EqualValues(1, bucket.Index) 460 r.EqualValues(newOwner, bucket.Owner) 461 r.EqualValues(delegate, bucket.Candidate) 462 r.EqualValues(10, bucket.StakedAmount.Int64()) 463 r.EqualValues(10, bucket.StakedDurationBlockNumber) 464 r.EqualValues(height, bucket.StakeStartBlockHeight) 465 r.False(bucket.AutoStake) 466 r.EqualValues(createHeight, bucket.CreateBlockHeight) 467 r.EqualValues(height, bucket.UnstakeStartBlockHeight) 468 r.EqualValues(_testStakingContractAddress, bucket.ContractAddress) 469 votes, err = indexer.CandidateVotes(ctx, delegate, height) 470 r.NoError(err) 471 r.EqualValues(0, votes.Uint64()) 472 tbc, err = indexer.TotalBucketCount(height) 473 r.NoError(err) 474 r.EqualValues(1, tbc) 475 476 // withdraw 477 height++ 478 handler = newContractStakingEventHandler(indexer.cache) 479 withdraw(r, handler, int64(bucket.Index)) 480 r.NoError(indexer.commit(handler, height)) 481 bucket, ok, err = indexer.Bucket(bucket.Index, height) 482 r.NoError(err) 483 r.False(ok) 484 votes, err = indexer.CandidateVotes(ctx, delegate, height) 485 r.NoError(err) 486 r.EqualValues(0, votes.Uint64()) 487 tbc, err = indexer.TotalBucketCount(height) 488 r.NoError(err) 489 r.EqualValues(1, tbc) 490 } 491 492 func TestContractStakingIndexerChangeBucketType(t *testing.T) { 493 r := require.New(t) 494 testDBPath, err := testutil.PathOfTempFile("staking.db") 495 r.NoError(err) 496 defer testutil.CleanupPath(testDBPath) 497 cfg := db.DefaultConfig 498 cfg.DbPath = testDBPath 499 kvStore := db.NewBoltDB(cfg) 500 indexer, err := NewContractStakingIndexer(kvStore, Config{ 501 ContractAddress: _testStakingContractAddress, 502 ContractDeployHeight: 0, 503 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 504 BlockInterval: _blockInterval, 505 }) 506 r.NoError(err) 507 r.NoError(indexer.Start(context.Background())) 508 509 // init bucket type 510 bucketTypeData := [][2]int64{ 511 {10, 10}, 512 {20, 10}, 513 {10, 100}, 514 {20, 100}, 515 } 516 height := uint64(1) 517 handler := newContractStakingEventHandler(indexer.cache) 518 for _, data := range bucketTypeData { 519 activateBucketType(r, handler, data[0], data[1], height) 520 } 521 err = indexer.commit(handler, height) 522 r.NoError(err) 523 524 t.Run("expand bucket type", func(t *testing.T) { 525 owner := identityset.Address(0) 526 delegate := identityset.Address(1) 527 height++ 528 handler = newContractStakingEventHandler(indexer.cache) 529 stake(r, handler, owner, delegate, 1, 10, 100, height) 530 r.NoError(err) 531 r.NoError(indexer.commit(handler, height)) 532 bucket, ok, err := indexer.Bucket(1, height) 533 r.NoError(err) 534 r.True(ok) 535 536 expandBucketType(r, handler, int64(bucket.Index), 20, 100) 537 r.NoError(indexer.commit(handler, height)) 538 bucket, ok, err = indexer.Bucket(bucket.Index, height) 539 r.NoError(err) 540 r.True(ok) 541 r.EqualValues(20, bucket.StakedAmount.Int64()) 542 r.EqualValues(100, bucket.StakedDurationBlockNumber) 543 }) 544 } 545 546 func TestContractStakingIndexerReadBuckets(t *testing.T) { 547 r := require.New(t) 548 testDBPath, err := testutil.PathOfTempFile("staking.db") 549 r.NoError(err) 550 defer testutil.CleanupPath(testDBPath) 551 cfg := db.DefaultConfig 552 cfg.DbPath = testDBPath 553 kvStore := db.NewBoltDB(cfg) 554 indexer, err := NewContractStakingIndexer(kvStore, Config{ 555 ContractAddress: _testStakingContractAddress, 556 ContractDeployHeight: 0, 557 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 558 BlockInterval: _blockInterval, 559 }) 560 r.NoError(err) 561 r.NoError(indexer.Start(context.Background())) 562 563 // init bucket type 564 bucketTypeData := [][2]int64{ 565 {10, 10}, 566 {20, 10}, 567 {10, 100}, 568 {20, 100}, 569 } 570 height := uint64(1) 571 handler := newContractStakingEventHandler(indexer.cache) 572 for _, data := range bucketTypeData { 573 activateBucketType(r, handler, data[0], data[1], height) 574 } 575 err = indexer.commit(handler, height) 576 r.NoError(err) 577 578 // stake 579 stakeData := []struct { 580 owner, delegate int 581 amount, duration uint64 582 }{ 583 {1, 2, 10, 10}, 584 {1, 2, 20, 10}, 585 {1, 2, 10, 100}, 586 {1, 2, 20, 100}, 587 {1, 3, 10, 100}, 588 {1, 3, 20, 100}, 589 } 590 height++ 591 handler = newContractStakingEventHandler(indexer.cache) 592 for i, data := range stakeData { 593 stake(r, handler, identityset.Address(data.owner), identityset.Address(data.delegate), int64(i), int64(data.amount), int64(data.duration), height) 594 } 595 r.NoError(err) 596 r.NoError(indexer.commit(handler, height)) 597 598 t.Run("Buckets", func(t *testing.T) { 599 buckets, err := indexer.Buckets(height) 600 r.NoError(err) 601 r.Len(buckets, len(stakeData)) 602 }) 603 604 t.Run("BucketsByCandidate", func(t *testing.T) { 605 candidateMap := make(map[int]int) 606 for i := range stakeData { 607 candidateMap[stakeData[i].delegate]++ 608 } 609 for cand := range candidateMap { 610 buckets, err := indexer.BucketsByCandidate(identityset.Address(cand), height) 611 r.NoError(err) 612 r.Len(buckets, candidateMap[cand]) 613 } 614 }) 615 616 t.Run("BucketsByIndices", func(t *testing.T) { 617 indices := []uint64{0, 1, 2, 3, 4, 5, 6} 618 buckets, err := indexer.BucketsByIndices(indices, height) 619 r.NoError(err) 620 expectedLen := 0 621 for _, idx := range indices { 622 if int(idx) < len(stakeData) { 623 expectedLen++ 624 } 625 } 626 r.Len(buckets, expectedLen) 627 }) 628 629 t.Run("TotalBucketCount", func(t *testing.T) { 630 tbc, err := indexer.TotalBucketCount(height) 631 r.NoError(err) 632 r.EqualValues(len(stakeData), tbc) 633 }) 634 635 t.Run("CandidateVotes", func(t *testing.T) { 636 ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) 637 candidateMap := make(map[int]int64) 638 for i := range stakeData { 639 candidateMap[stakeData[i].delegate] += int64(stakeData[i].amount) 640 } 641 candidates := []int{1, 2, 3} 642 for _, cand := range candidates { 643 votes := candidateMap[cand] 644 cvotes, err := indexer.CandidateVotes(ctx, identityset.Address(cand), height) 645 r.NoError(err) 646 r.EqualValues(votes, cvotes.Uint64()) 647 } 648 }) 649 } 650 651 func TestContractStakingIndexerCacheClean(t *testing.T) { 652 r := require.New(t) 653 testDBPath, err := testutil.PathOfTempFile("staking.db") 654 r.NoError(err) 655 defer testutil.CleanupPath(testDBPath) 656 cfg := db.DefaultConfig 657 cfg.DbPath = testDBPath 658 kvStore := db.NewBoltDB(cfg) 659 indexer, err := NewContractStakingIndexer(kvStore, Config{ 660 ContractAddress: _testStakingContractAddress, 661 ContractDeployHeight: 0, 662 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 663 BlockInterval: _blockInterval, 664 }) 665 r.NoError(err) 666 r.NoError(indexer.Start(context.Background())) 667 668 // init bucket type 669 height := uint64(1) 670 handler := newContractStakingEventHandler(indexer.cache) 671 activateBucketType(r, handler, 10, 10, height) 672 activateBucketType(r, handler, 20, 20, height) 673 // create bucket 674 owner := identityset.Address(10) 675 delegate1 := identityset.Address(1) 676 delegate2 := identityset.Address(2) 677 stake(r, handler, owner, delegate1, 1, 10, 10, height) 678 stake(r, handler, owner, delegate1, 2, 20, 20, height) 679 stake(r, handler, owner, delegate2, 3, 20, 20, height) 680 stake(r, handler, owner, delegate2, 4, 20, 20, height) 681 abt, err := indexer.cache.ActiveBucketTypes(height - 1) 682 r.NoError(err) 683 r.Len(abt, 0) 684 bts, err := indexer.cache.Buckets(height - 1) 685 r.NoError(err) 686 r.Len(bts, 0) 687 r.NoError(indexer.commit(handler, height)) 688 abt, err = indexer.cache.ActiveBucketTypes(height) 689 r.NoError(err) 690 r.Len(abt, 2) 691 bts, err = indexer.cache.Buckets(height) 692 r.NoError(err) 693 r.Len(bts, 4) 694 695 height++ 696 handler = newContractStakingEventHandler(indexer.cache) 697 changeDelegate(r, handler, delegate1, 3) 698 transfer(r, handler, delegate1, 1) 699 bt, ok, err := indexer.Bucket(3, height-1) 700 r.NoError(err) 701 r.True(ok) 702 r.Equal(delegate2.String(), bt.Candidate.String()) 703 bt, ok, err = indexer.Bucket(1, height-1) 704 r.NoError(err) 705 r.True(ok) 706 r.Equal(owner.String(), bt.Owner.String()) 707 r.NoError(indexer.commit(handler, height)) 708 bt, ok, err = indexer.Bucket(3, height) 709 r.NoError(err) 710 r.True(ok) 711 r.Equal(delegate1.String(), bt.Candidate.String()) 712 bt, ok, err = indexer.Bucket(1, height) 713 r.NoError(err) 714 r.True(ok) 715 r.Equal(delegate1.String(), bt.Owner.String()) 716 } 717 718 func TestContractStakingIndexerVotes(t *testing.T) { 719 r := require.New(t) 720 testDBPath, err := testutil.PathOfTempFile("staking.db") 721 r.NoError(err) 722 defer testutil.CleanupPath(testDBPath) 723 cfg := db.DefaultConfig 724 cfg.DbPath = testDBPath 725 kvStore := db.NewBoltDB(cfg) 726 indexer, err := NewContractStakingIndexer(kvStore, Config{ 727 ContractAddress: _testStakingContractAddress, 728 ContractDeployHeight: 0, 729 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 730 BlockInterval: _blockInterval, 731 }) 732 r.NoError(err) 733 r.NoError(indexer.Start(context.Background())) 734 ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) 735 736 // init bucket type 737 height := uint64(1) 738 handler := newContractStakingEventHandler(indexer.cache) 739 activateBucketType(r, handler, 10, 10, height) 740 activateBucketType(r, handler, 20, 20, height) 741 activateBucketType(r, handler, 30, 20, height) 742 activateBucketType(r, handler, 60, 20, height) 743 // create bucket 744 owner := identityset.Address(10) 745 delegate1 := identityset.Address(1) 746 delegate2 := identityset.Address(2) 747 stake(r, handler, owner, delegate1, 1, 10, 10, height) 748 stake(r, handler, owner, delegate1, 2, 20, 20, height) 749 stake(r, handler, owner, delegate2, 3, 20, 20, height) 750 stake(r, handler, owner, delegate2, 4, 20, 20, height) 751 r.NoError(indexer.commit(handler, height)) 752 votes, err := indexer.CandidateVotes(ctx, delegate1, height) 753 r.NoError(err) 754 r.EqualValues(30, votes.Uint64()) 755 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 756 r.NoError(err) 757 r.EqualValues(40, votes.Uint64()) 758 votes, err = indexer.CandidateVotes(ctx, owner, height) 759 r.EqualValues(0, votes.Uint64()) 760 761 // change delegate bucket 3 to delegate1 762 height++ 763 handler = newContractStakingEventHandler(indexer.cache) 764 changeDelegate(r, handler, delegate1, 3) 765 r.NoError(indexer.commit(handler, height)) 766 votes, err = indexer.CandidateVotes(ctx, delegate1, height) 767 r.NoError(err) 768 r.EqualValues(50, votes.Uint64()) 769 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 770 r.NoError(err) 771 r.EqualValues(20, votes.Uint64()) 772 773 // unlock bucket 1 & 4 774 height++ 775 handler = newContractStakingEventHandler(indexer.cache) 776 unlock(r, handler, 1, height) 777 unlock(r, handler, 4, height) 778 r.NoError(indexer.commit(handler, height)) 779 votes, err = indexer.CandidateVotes(ctx, delegate1, height) 780 r.NoError(err) 781 r.EqualValues(50, votes.Uint64()) 782 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 783 r.NoError(err) 784 r.EqualValues(20, votes.Uint64()) 785 786 // unstake bucket 1 & lock 4 787 height++ 788 handler = newContractStakingEventHandler(indexer.cache) 789 unstake(r, handler, 1, height) 790 lock(r, handler, 4, 20) 791 r.NoError(indexer.commit(handler, height)) 792 votes, err = indexer.CandidateVotes(ctx, delegate1, height) 793 r.NoError(err) 794 r.EqualValues(40, votes.Uint64()) 795 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 796 r.NoError(err) 797 r.EqualValues(20, votes.Uint64()) 798 799 // expand bucket 2 800 height++ 801 handler = newContractStakingEventHandler(indexer.cache) 802 expandBucketType(r, handler, 2, 30, 20) 803 r.NoError(indexer.commit(handler, height)) 804 votes, err = indexer.CandidateVotes(ctx, delegate1, height) 805 r.NoError(err) 806 r.EqualValues(50, votes.Uint64()) 807 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 808 r.NoError(err) 809 r.EqualValues(20, votes.Uint64()) 810 811 // transfer bucket 4 812 height++ 813 handler = newContractStakingEventHandler(indexer.cache) 814 transfer(r, handler, delegate2, 4) 815 r.NoError(indexer.commit(handler, height)) 816 votes, err = indexer.CandidateVotes(ctx, delegate1, height) 817 r.NoError(err) 818 r.EqualValues(50, votes.Uint64()) 819 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 820 r.NoError(err) 821 r.EqualValues(20, votes.Uint64()) 822 823 // create bucket 5, 6, 7 824 height++ 825 handler = newContractStakingEventHandler(indexer.cache) 826 stake(r, handler, owner, delegate2, 5, 20, 20, height) 827 stake(r, handler, owner, delegate2, 6, 20, 20, height) 828 stake(r, handler, owner, delegate2, 7, 20, 20, height) 829 r.NoError(indexer.commit(handler, height)) 830 votes, err = indexer.CandidateVotes(ctx, delegate1, height) 831 r.NoError(err) 832 r.EqualValues(50, votes.Uint64()) 833 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 834 r.NoError(err) 835 r.EqualValues(80, votes.Uint64()) 836 837 // merge bucket 5, 6, 7 838 height++ 839 handler = newContractStakingEventHandler(indexer.cache) 840 mergeBuckets(r, handler, []int64{5, 6, 7}, 60, 20) 841 r.NoError(indexer.commit(handler, height)) 842 votes, err = indexer.CandidateVotes(ctx, delegate1, height) 843 r.NoError(err) 844 r.EqualValues(50, votes.Uint64()) 845 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 846 r.NoError(err) 847 r.EqualValues(80, votes.Uint64()) 848 849 // unlock & unstake 5 850 height++ 851 handler = newContractStakingEventHandler(indexer.cache) 852 unlock(r, handler, 5, height) 853 unstake(r, handler, 5, height) 854 r.NoError(indexer.commit(handler, height)) 855 votes, err = indexer.CandidateVotes(ctx, delegate1, height) 856 r.NoError(err) 857 r.EqualValues(50, votes.Uint64()) 858 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 859 r.NoError(err) 860 r.EqualValues(20, votes.Uint64()) 861 862 // create & merge bucket 8, 9, 10 863 height++ 864 handler = newContractStakingEventHandler(indexer.cache) 865 stake(r, handler, owner, delegate1, 8, 20, 20, height) 866 stake(r, handler, owner, delegate2, 9, 20, 20, height) 867 stake(r, handler, owner, delegate2, 10, 20, 20, height) 868 mergeBuckets(r, handler, []int64{8, 9, 10}, 60, 20) 869 r.NoError(indexer.commit(handler, height)) 870 votes, err = indexer.CandidateVotes(ctx, delegate1, height) 871 r.NoError(err) 872 r.EqualValues(110, votes.Uint64()) 873 votes, err = indexer.CandidateVotes(ctx, delegate2, height) 874 r.NoError(err) 875 r.EqualValues(20, votes.Uint64()) 876 877 t.Run("Height", func(t *testing.T) { 878 h, err := indexer.Height() 879 r.NoError(err) 880 r.EqualValues(height, h) 881 }) 882 883 t.Run("BucketTypes", func(t *testing.T) { 884 bts, err := indexer.BucketTypes(height) 885 r.NoError(err) 886 r.Len(bts, 4) 887 slices.SortFunc(bts, func(i, j *staking.ContractStakingBucketType) bool { 888 return i.Amount.Int64() < j.Amount.Int64() 889 }) 890 r.EqualValues(10, bts[0].Duration) 891 r.EqualValues(20, bts[1].Duration) 892 r.EqualValues(20, bts[2].Duration) 893 r.EqualValues(20, bts[3].Duration) 894 r.EqualValues(10, bts[0].Amount.Int64()) 895 r.EqualValues(20, bts[1].Amount.Int64()) 896 r.EqualValues(30, bts[2].Amount.Int64()) 897 r.EqualValues(60, bts[3].Amount.Int64()) 898 }) 899 900 t.Run("Buckets", func(t *testing.T) { 901 bts, err := indexer.Buckets(height) 902 r.NoError(err) 903 r.Len(bts, 6) 904 slices.SortFunc(bts, func(i, j *staking.VoteBucket) bool { 905 return i.Index < j.Index 906 }) 907 r.EqualValues(1, bts[0].Index) 908 r.EqualValues(2, bts[1].Index) 909 r.EqualValues(3, bts[2].Index) 910 r.EqualValues(4, bts[3].Index) 911 r.EqualValues(5, bts[4].Index) 912 r.EqualValues(8, bts[5].Index) 913 r.EqualValues(10*_blockInterval, bts[0].StakedDuration) 914 r.EqualValues(20*_blockInterval, bts[1].StakedDuration) 915 r.EqualValues(20*_blockInterval, bts[2].StakedDuration) 916 r.EqualValues(20*_blockInterval, bts[3].StakedDuration) 917 r.EqualValues(20*_blockInterval, bts[4].StakedDuration) 918 r.EqualValues(20*_blockInterval, bts[5].StakedDuration) 919 r.EqualValues(10, bts[0].StakedDurationBlockNumber) 920 r.EqualValues(20, bts[1].StakedDurationBlockNumber) 921 r.EqualValues(20, bts[2].StakedDurationBlockNumber) 922 r.EqualValues(20, bts[3].StakedDurationBlockNumber) 923 r.EqualValues(20, bts[4].StakedDurationBlockNumber) 924 r.EqualValues(20, bts[5].StakedDurationBlockNumber) 925 r.EqualValues(10, bts[0].StakedAmount.Int64()) 926 r.EqualValues(30, bts[1].StakedAmount.Int64()) 927 r.EqualValues(20, bts[2].StakedAmount.Int64()) 928 r.EqualValues(20, bts[3].StakedAmount.Int64()) 929 r.EqualValues(60, bts[4].StakedAmount.Int64()) 930 r.EqualValues(60, bts[5].StakedAmount.Int64()) 931 r.EqualValues(delegate1.String(), bts[0].Candidate.String()) 932 r.EqualValues(delegate1.String(), bts[1].Candidate.String()) 933 r.EqualValues(delegate1.String(), bts[2].Candidate.String()) 934 r.EqualValues(delegate2.String(), bts[3].Candidate.String()) 935 r.EqualValues(delegate2.String(), bts[4].Candidate.String()) 936 r.EqualValues(delegate1.String(), bts[5].Candidate.String()) 937 r.EqualValues(owner.String(), bts[0].Owner.String()) 938 r.EqualValues(owner.String(), bts[1].Owner.String()) 939 r.EqualValues(owner.String(), bts[2].Owner.String()) 940 r.EqualValues(delegate2.String(), bts[3].Owner.String()) 941 r.EqualValues(owner.String(), bts[4].Owner.String()) 942 r.EqualValues(owner.String(), bts[5].Owner.String()) 943 r.False(bts[0].AutoStake) 944 r.True(bts[1].AutoStake) 945 r.True(bts[2].AutoStake) 946 r.True(bts[3].AutoStake) 947 r.False(bts[4].AutoStake) 948 r.True(bts[5].AutoStake) 949 r.EqualValues(1, bts[0].CreateBlockHeight) 950 r.EqualValues(1, bts[1].CreateBlockHeight) 951 r.EqualValues(1, bts[2].CreateBlockHeight) 952 r.EqualValues(1, bts[3].CreateBlockHeight) 953 r.EqualValues(7, bts[4].CreateBlockHeight) 954 r.EqualValues(10, bts[5].CreateBlockHeight) 955 r.EqualValues(3, bts[0].StakeStartBlockHeight) 956 r.EqualValues(1, bts[1].StakeStartBlockHeight) 957 r.EqualValues(1, bts[2].StakeStartBlockHeight) 958 r.EqualValues(1, bts[3].StakeStartBlockHeight) 959 r.EqualValues(9, bts[4].StakeStartBlockHeight) 960 r.EqualValues(10, bts[5].StakeStartBlockHeight) 961 r.EqualValues(4, bts[0].UnstakeStartBlockHeight) 962 r.EqualValues(maxBlockNumber, bts[1].UnstakeStartBlockHeight) 963 r.EqualValues(maxBlockNumber, bts[2].UnstakeStartBlockHeight) 964 r.EqualValues(maxBlockNumber, bts[3].UnstakeStartBlockHeight) 965 r.EqualValues(9, bts[4].UnstakeStartBlockHeight) 966 r.EqualValues(maxBlockNumber, bts[5].UnstakeStartBlockHeight) 967 for _, b := range bts { 968 r.EqualValues(time.Time{}, b.CreateTime) 969 r.EqualValues(time.Time{}, b.StakeStartTime) 970 r.EqualValues(time.Time{}, b.UnstakeStartTime) 971 r.EqualValues(_testStakingContractAddress, b.ContractAddress) 972 } 973 }) 974 975 t.Run("BucketsByCandidate", func(t *testing.T) { 976 d1Bts, err := indexer.BucketsByCandidate(delegate1, height) 977 r.NoError(err) 978 r.Len(d1Bts, 4) 979 slices.SortFunc(d1Bts, func(i, j *staking.VoteBucket) bool { 980 return i.Index < j.Index 981 }) 982 r.EqualValues(1, d1Bts[0].Index) 983 r.EqualValues(2, d1Bts[1].Index) 984 r.EqualValues(3, d1Bts[2].Index) 985 r.EqualValues(8, d1Bts[3].Index) 986 d2Bts, err := indexer.BucketsByCandidate(delegate2, height) 987 r.NoError(err) 988 r.Len(d2Bts, 2) 989 slices.SortFunc(d2Bts, func(i, j *staking.VoteBucket) bool { 990 return i.Index < j.Index 991 }) 992 r.EqualValues(4, d2Bts[0].Index) 993 r.EqualValues(5, d2Bts[1].Index) 994 }) 995 996 t.Run("BucketsByIndices", func(t *testing.T) { 997 bts, err := indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, height) 998 r.NoError(err) 999 r.Len(bts, 6) 1000 }) 1001 } 1002 1003 func TestIndexer_ReadHeightRestriction(t *testing.T) { 1004 r := require.New(t) 1005 1006 cases := []struct { 1007 startHeight uint64 1008 height uint64 1009 readHeight uint64 1010 valid bool 1011 }{ 1012 {0, 0, 0, true}, 1013 {0, 0, 1, false}, 1014 {0, 2, 0, true}, 1015 {0, 2, 1, true}, 1016 {0, 2, 2, true}, 1017 {0, 2, 3, false}, 1018 {10, 0, 0, true}, 1019 {10, 0, 1, true}, 1020 {10, 0, 9, true}, 1021 {10, 0, 10, false}, 1022 {10, 0, 11, false}, 1023 {10, 10, 0, true}, 1024 {10, 10, 1, true}, 1025 {10, 10, 9, true}, 1026 {10, 10, 10, true}, 1027 {10, 10, 11, false}, 1028 } 1029 1030 for idx, c := range cases { 1031 name := strconv.FormatInt(int64(idx), 10) 1032 t.Run(name, func(t *testing.T) { 1033 // Create a new Indexer 1034 height := c.height 1035 startHeight := c.startHeight 1036 cfg := config.Default.DB 1037 dbPath, err := testutil.PathOfTempFile("db") 1038 r.NoError(err) 1039 cfg.DbPath = dbPath 1040 indexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), Config{ 1041 ContractAddress: identityset.Address(1).String(), 1042 ContractDeployHeight: startHeight, 1043 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 1044 BlockInterval: _blockInterval, 1045 }) 1046 r.NoError(err) 1047 r.NoError(indexer.Start(context.Background())) 1048 defer func() { 1049 r.NoError(indexer.Stop(context.Background())) 1050 testutil.CleanupPath(dbPath) 1051 }() 1052 indexer.cache.putHeight(height) 1053 // check read api 1054 ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1})) 1055 h := c.readHeight 1056 delegate := identityset.Address(1) 1057 if c.valid { 1058 _, err = indexer.Buckets(h) 1059 r.NoError(err) 1060 _, err = indexer.BucketTypes(h) 1061 r.NoError(err) 1062 _, err = indexer.BucketsByCandidate(delegate, h) 1063 r.NoError(err) 1064 _, err = indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h) 1065 r.NoError(err) 1066 _, err = indexer.CandidateVotes(ctx, delegate, h) 1067 r.NoError(err) 1068 _, _, err = indexer.Bucket(1, h) 1069 r.NoError(err) 1070 _, err = indexer.TotalBucketCount(h) 1071 r.NoError(err) 1072 } else { 1073 _, err = indexer.Buckets(h) 1074 r.ErrorIs(err, ErrInvalidHeight) 1075 _, err = indexer.BucketTypes(h) 1076 r.ErrorIs(err, ErrInvalidHeight) 1077 _, err = indexer.BucketsByCandidate(delegate, h) 1078 r.ErrorIs(err, ErrInvalidHeight) 1079 _, err = indexer.BucketsByIndices([]uint64{1, 2, 3, 4, 5, 8}, h) 1080 r.ErrorIs(err, ErrInvalidHeight) 1081 _, err = indexer.CandidateVotes(ctx, delegate, h) 1082 r.ErrorIs(err, ErrInvalidHeight) 1083 _, _, err = indexer.Bucket(1, h) 1084 r.ErrorIs(err, ErrInvalidHeight) 1085 _, err = indexer.TotalBucketCount(h) 1086 r.ErrorIs(err, ErrInvalidHeight) 1087 } 1088 }) 1089 } 1090 } 1091 1092 func TestIndexer_PutBlock(t *testing.T) { 1093 r := require.New(t) 1094 1095 cases := []struct { 1096 name string 1097 height uint64 1098 startHeight uint64 1099 blockHeight uint64 1100 expectedHeight uint64 1101 errMsg string 1102 }{ 1103 {"block < height < start", 10, 20, 9, 10, ""}, 1104 {"block = height < start", 10, 20, 10, 10, ""}, 1105 {"height < block < start", 10, 20, 11, 10, ""}, 1106 {"height < block = start", 10, 20, 20, 20, ""}, 1107 {"height < start < block", 10, 20, 21, 10, "invalid block height 21, expect 20"}, 1108 {"block < start < height", 20, 10, 9, 20, ""}, 1109 {"block = start < height", 20, 10, 10, 20, ""}, 1110 {"start < block < height", 20, 10, 11, 20, ""}, 1111 {"start < block = height", 20, 10, 20, 20, ""}, 1112 {"start < height < block", 20, 10, 21, 21, ""}, 1113 {"start < height < block+", 20, 10, 22, 20, "invalid block height 22, expect 21"}, 1114 } 1115 1116 for _, c := range cases { 1117 t.Run(c.name, func(t *testing.T) { 1118 // Create a new Indexer 1119 height := c.height 1120 startHeight := c.startHeight 1121 cfg := config.Default.DB 1122 dbPath, err := testutil.PathOfTempFile("db") 1123 r.NoError(err) 1124 cfg.DbPath = dbPath 1125 indexer, err := NewContractStakingIndexer(db.NewBoltDB(cfg), Config{ 1126 ContractAddress: identityset.Address(1).String(), 1127 ContractDeployHeight: startHeight, 1128 CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), 1129 BlockInterval: _blockInterval, 1130 }) 1131 r.NoError(err) 1132 r.NoError(indexer.Start(context.Background())) 1133 defer func() { 1134 r.NoError(indexer.Stop(context.Background())) 1135 testutil.CleanupPath(dbPath) 1136 }() 1137 indexer.cache.putHeight(height) 1138 // Create a mock block 1139 builder := block.NewBuilder(block.NewRunnableActionsBuilder().Build()) 1140 builder.SetHeight(c.blockHeight) 1141 blk, err := builder.SignAndBuild(identityset.PrivateKey(1)) 1142 r.NoError(err) 1143 // Put the block 1144 err = indexer.PutBlock(context.Background(), &blk) 1145 if c.errMsg != "" { 1146 r.ErrorContains(err, c.errMsg) 1147 } else { 1148 r.NoError(err) 1149 } 1150 // Check the block height 1151 r.EqualValues(c.expectedHeight, indexer.cache.Height()) 1152 }) 1153 } 1154 1155 } 1156 1157 func BenchmarkIndexer_PutBlockBeforeContractHeight(b *testing.B) { 1158 // Create a new Indexer with a contract height of 100 1159 indexer := &Indexer{config: Config{ContractDeployHeight: 100}} 1160 1161 // Create a mock block with a height of 50 1162 blk := &block.Block{} 1163 1164 // Run the benchmark 1165 b.ResetTimer() 1166 for i := 0; i < b.N; i++ { 1167 err := indexer.PutBlock(context.Background(), blk) 1168 if err != nil { 1169 b.Fatal(err) 1170 } 1171 } 1172 } 1173 1174 func activateBucketType(r *require.Assertions, handler *contractStakingEventHandler, amount, duration int64, height uint64) { 1175 err := handler.handleBucketTypeActivatedEvent(eventParam{ 1176 "amount": big.NewInt(amount), 1177 "duration": big.NewInt(duration), 1178 }, height) 1179 r.NoError(err) 1180 } 1181 1182 func deactivateBucketType(r *require.Assertions, handler *contractStakingEventHandler, amount, duration int64, height uint64) { 1183 err := handler.handleBucketTypeDeactivatedEvent(eventParam{ 1184 "amount": big.NewInt(amount), 1185 "duration": big.NewInt(duration), 1186 }, height) 1187 r.NoError(err) 1188 } 1189 1190 func stake(r *require.Assertions, handler *contractStakingEventHandler, owner, candidate address.Address, token, amount, duration int64, height uint64) { 1191 err := handler.handleTransferEvent(eventParam{ 1192 "to": common.BytesToAddress(owner.Bytes()), 1193 "tokenId": big.NewInt(token), 1194 }) 1195 r.NoError(err) 1196 err = handler.handleStakedEvent(eventParam{ 1197 "tokenId": big.NewInt(token), 1198 "delegate": common.BytesToAddress(candidate.Bytes()), 1199 "amount": big.NewInt(amount), 1200 "duration": big.NewInt(duration), 1201 }, height) 1202 r.NoError(err) 1203 } 1204 1205 func unlock(r *require.Assertions, handler *contractStakingEventHandler, token int64, height uint64) { 1206 err := handler.handleUnlockedEvent(eventParam{ 1207 "tokenId": big.NewInt(token), 1208 }, height) 1209 r.NoError(err) 1210 } 1211 1212 func lock(r *require.Assertions, handler *contractStakingEventHandler, token, duration int64) { 1213 err := handler.handleLockedEvent(eventParam{ 1214 "tokenId": big.NewInt(token), 1215 "duration": big.NewInt(duration), 1216 }) 1217 r.NoError(err) 1218 } 1219 1220 func unstake(r *require.Assertions, handler *contractStakingEventHandler, token int64, height uint64) { 1221 err := handler.handleUnstakedEvent(eventParam{ 1222 "tokenId": big.NewInt(token), 1223 }, height) 1224 r.NoError(err) 1225 } 1226 1227 func withdraw(r *require.Assertions, handler *contractStakingEventHandler, token int64) { 1228 err := handler.handleWithdrawalEvent(eventParam{ 1229 "tokenId": big.NewInt(token), 1230 }) 1231 r.NoError(err) 1232 } 1233 1234 func expandBucketType(r *require.Assertions, handler *contractStakingEventHandler, token, amount, duration int64) { 1235 err := handler.handleBucketExpandedEvent(eventParam{ 1236 "tokenId": big.NewInt(token), 1237 "amount": big.NewInt(amount), 1238 "duration": big.NewInt(duration), 1239 }) 1240 r.NoError(err) 1241 } 1242 1243 func transfer(r *require.Assertions, handler *contractStakingEventHandler, owner address.Address, token int64) { 1244 err := handler.handleTransferEvent(eventParam{ 1245 "to": common.BytesToAddress(owner.Bytes()), 1246 "tokenId": big.NewInt(token), 1247 }) 1248 r.NoError(err) 1249 } 1250 1251 func changeDelegate(r *require.Assertions, handler *contractStakingEventHandler, delegate address.Address, token int64) { 1252 err := handler.handleDelegateChangedEvent(eventParam{ 1253 "newDelegate": common.BytesToAddress(delegate.Bytes()), 1254 "tokenId": big.NewInt(token), 1255 }) 1256 r.NoError(err) 1257 } 1258 1259 func mergeBuckets(r *require.Assertions, handler *contractStakingEventHandler, tokenIds []int64, amount, duration int64) { 1260 tokens := make([]*big.Int, len(tokenIds)) 1261 for i, token := range tokenIds { 1262 tokens[i] = big.NewInt(token) 1263 } 1264 err := handler.handleMergedEvent(eventParam{ 1265 "amount": big.NewInt(amount), 1266 "duration": big.NewInt(duration), 1267 "tokenIds": tokens, 1268 }) 1269 r.NoError(err) 1270 }