github.com/iotexproject/iotex-core@v1.14.1-rc1/blockindex/contractstaking/cache_test.go (about) 1 package contractstaking 2 3 import ( 4 "context" 5 "fmt" 6 "math/big" 7 "testing" 8 9 "github.com/golang/mock/gomock" 10 "github.com/iotexproject/iotex-address/address" 11 "github.com/pkg/errors" 12 "github.com/stretchr/testify/require" 13 14 "github.com/iotexproject/iotex-core/action/protocol" 15 "github.com/iotexproject/iotex-core/action/protocol/staking" 16 "github.com/iotexproject/iotex-core/blockchain/genesis" 17 "github.com/iotexproject/iotex-core/config" 18 "github.com/iotexproject/iotex-core/db" 19 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 20 "github.com/iotexproject/iotex-core/test/identityset" 21 "github.com/iotexproject/iotex-core/testutil" 22 ) 23 24 func _checkCacheCandidateVotes(ctx context.Context, r *require.Assertions, cache *contractStakingCache, height uint64, addr address.Address, expectVotes int64) { 25 votes, err := cache.CandidateVotes(ctx, addr, height) 26 r.NoError(err) 27 r.EqualValues(expectVotes, votes.Int64()) 28 } 29 30 func calculateVoteWeightGen(c genesis.VoteWeightCalConsts) calculateVoteWeightFunc { 31 return func(v *Bucket) *big.Int { 32 return staking.CalculateVoteWeight(c, v, false) 33 } 34 } 35 36 func TestContractStakingCache_CandidateVotes(t *testing.T) { 37 checkCacheCandidateVotesGen := func(ctx context.Context) func(r *require.Assertions, cache *contractStakingCache, height uint64, addr address.Address, expectVotes int64) { 38 return func(r *require.Assertions, cache *contractStakingCache, height uint64, addr address.Address, expectVotes int64) { 39 _checkCacheCandidateVotes(ctx, r, cache, height, addr, expectVotes) 40 } 41 } 42 require := require.New(t) 43 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(1).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 44 checkCacheCandidateVotes := checkCacheCandidateVotesGen(protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: 1}))) 45 checkCacheCandidateVotesAfterRedsea := checkCacheCandidateVotesGen(protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: genesis.Default.RedseaBlockHeight}))) 46 // no bucket 47 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 0) 48 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 0) 49 50 // one bucket 51 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 52 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 53 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 100) 54 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 103) 55 56 // two buckets 57 cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 58 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 200) 59 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 206) 60 61 // add one bucket with different delegate 62 cache.PutBucketInfo(3, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(2)}) 63 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 200) 64 checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 100) 65 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 206) 66 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(3), 103) 67 68 // add one bucket with different owner 69 cache.PutBucketInfo(4, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(4)}) 70 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 300) 71 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 309) 72 73 // add one bucket with different amount 74 cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 100, ActivatedAt: 1}) 75 cache.PutBucketInfo(5, &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 76 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 500) 77 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 516) 78 79 // add one bucket with different duration 80 cache.PutBucketType(3, &BucketType{Amount: big.NewInt(300), Duration: 200, ActivatedAt: 1}) 81 cache.PutBucketInfo(6, &bucketInfo{TypeIndex: 3, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 82 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) 83 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 827) 84 85 // add one bucket that is unstaked 86 cache.PutBucketInfo(7, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: 1, UnstakedAt: 1, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 87 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) 88 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 827) 89 90 // add one bucket that is unlocked and staked 91 cache.PutBucketInfo(8, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: 100, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 92 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 900) 93 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 927) 94 95 // change delegate of bucket 1 96 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(2)}) 97 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) 98 checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 200) 99 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 824) 100 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(3), 206) 101 102 // change owner of bucket 1 103 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(4)}) 104 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) 105 checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 200) 106 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 824) 107 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(3), 206) 108 109 // change amount of bucket 1 110 cache.putBucketInfo(1, &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(4)}) 111 checkCacheCandidateVotes(require, cache, 0, identityset.Address(1), 800) 112 checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 300) 113 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(1), 824) 114 checkCacheCandidateVotesAfterRedsea(require, cache, 0, identityset.Address(3), 310) 115 116 // put bucket info and make it disabled 117 bi := &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(4)} 118 cache.PutBucketInfo(9, bi) 119 cache.candidateBucketMap[bi.Delegate.String()][9] = false 120 checkCacheCandidateVotes(require, cache, 0, identityset.Address(3), 300) 121 } 122 123 func TestContractStakingCache_Buckets(t *testing.T) { 124 require := require.New(t) 125 contractAddr := identityset.Address(27).String() 126 cache := newContractStakingCache(Config{ContractAddress: contractAddr, CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 127 128 height := uint64(0) 129 // no bucket 130 bts, err := cache.Buckets(height) 131 require.NoError(err) 132 require.Empty(bts) 133 134 // add one bucket 135 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 136 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 137 buckets, err := cache.Buckets(height) 138 require.NoError(err) 139 require.Len(buckets, 1) 140 checkVoteBucket(require, buckets[0], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 141 bucket, ok, err := cache.Bucket(1, height) 142 require.NoError(err) 143 require.True(ok) 144 checkVoteBucket(require, bucket, 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 145 146 // add one bucket with different index 147 cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 1}) 148 cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(4)}) 149 bts, err = cache.Buckets(height) 150 require.NoError(err) 151 bucketMaps := bucketsToMap(bts) 152 require.Len(bucketMaps, 2) 153 checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 154 checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(3).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) 155 bucket, ok, err = cache.Bucket(1, height) 156 require.NoError(err) 157 require.True(ok) 158 checkVoteBucket(require, bucket, 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 159 bucket, ok, err = cache.Bucket(2, height) 160 require.NoError(err) 161 require.True(ok) 162 checkVoteBucket(require, bucket, 2, identityset.Address(3).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) 163 164 // update delegate of bucket 2 165 cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(5), Owner: identityset.Address(4)}) 166 bts, err = cache.Buckets(height) 167 require.NoError(err) 168 bucketMaps = bucketsToMap(bts) 169 require.Len(bucketMaps, 2) 170 checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 171 checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(5).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) 172 bucket, ok, err = cache.Bucket(1, height) 173 require.NoError(err) 174 require.True(ok) 175 checkVoteBucket(require, bucket, 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 176 bucket, ok, err = cache.Bucket(2, height) 177 require.NoError(err) 178 require.True(ok) 179 checkVoteBucket(require, bucket, 2, identityset.Address(5).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) 180 181 // delete bucket 1 182 cache.DeleteBucketInfo(1) 183 bts, err = cache.Buckets(height) 184 require.NoError(err) 185 bucketMaps = bucketsToMap(bts) 186 require.Len(bucketMaps, 1) 187 checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(5).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) 188 _, ok, err = cache.Bucket(1, height) 189 require.NoError(err) 190 require.False(ok) 191 bucket, ok, err = cache.Bucket(2, height) 192 require.NoError(err) 193 require.True(ok) 194 checkVoteBucket(require, bucket, 2, identityset.Address(5).String(), identityset.Address(4).String(), 200, 200, 1, 1, maxBlockNumber, true, contractAddr) 195 } 196 197 func TestContractStakingCache_BucketsByCandidate(t *testing.T) { 198 require := require.New(t) 199 contractAddr := identityset.Address(27).String() 200 cache := newContractStakingCache(Config{ContractAddress: contractAddr, CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 201 202 height := uint64(0) 203 // no bucket 204 buckets, err := cache.BucketsByCandidate(identityset.Address(1), height) 205 require.NoError(err) 206 require.Len(buckets, 0) 207 208 // one bucket 209 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 210 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 211 bts, err := cache.BucketsByCandidate(identityset.Address(1), height) 212 require.NoError(err) 213 bucketMaps := bucketsToMap(bts) 214 require.Len(bucketMaps, 1) 215 checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 216 217 // two buckets 218 cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 219 bts, err = cache.BucketsByCandidate(identityset.Address(1), height) 220 require.NoError(err) 221 bucketMaps = bucketsToMap(bts) 222 require.Len(bucketMaps, 2) 223 checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 224 checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 225 226 // add one bucket with different delegate 227 cache.PutBucketInfo(3, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(2)}) 228 bts, err = cache.BucketsByCandidate(identityset.Address(1), height) 229 require.NoError(err) 230 bucketMaps = bucketsToMap(bts) 231 require.Len(bucketMaps, 2) 232 require.Nil(bucketMaps[3]) 233 bts, err = cache.BucketsByCandidate(identityset.Address(3), height) 234 require.NoError(err) 235 bucketMaps = bucketsToMap(bts) 236 require.Len(bucketMaps, 1) 237 checkVoteBucket(require, bucketMaps[3], 3, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 238 239 // change delegate of bucket 1 240 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(2)}) 241 bts, err = cache.BucketsByCandidate(identityset.Address(1), height) 242 require.NoError(err) 243 bucketMaps = bucketsToMap(bts) 244 require.Len(bucketMaps, 1) 245 require.Nil(bucketMaps[1]) 246 checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 247 bts, err = cache.BucketsByCandidate(identityset.Address(3), height) 248 require.NoError(err) 249 bucketMaps = bucketsToMap(bts) 250 require.Len(bucketMaps, 2) 251 checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 252 checkVoteBucket(require, bucketMaps[3], 3, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 253 254 // delete bucket 2 255 cache.DeleteBucketInfo(2) 256 bts, err = cache.BucketsByCandidate(identityset.Address(1), height) 257 require.NoError(err) 258 bucketMaps = bucketsToMap(bts) 259 require.Len(bucketMaps, 0) 260 bts, err = cache.BucketsByCandidate(identityset.Address(3), height) 261 require.NoError(err) 262 bucketMaps = bucketsToMap(bts) 263 require.Len(bucketMaps, 2) 264 checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 265 checkVoteBucket(require, bucketMaps[3], 3, identityset.Address(3).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 266 267 } 268 269 func TestContractStakingCache_BucketsByIndices(t *testing.T) { 270 require := require.New(t) 271 contractAddr := identityset.Address(27).String() 272 cache := newContractStakingCache(Config{ContractAddress: contractAddr, CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 273 274 height := uint64(0) 275 // no bucket 276 buckets, err := cache.BucketsByIndices([]uint64{1}, height) 277 require.NoError(err) 278 require.Len(buckets, 0) 279 280 // one bucket 281 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 282 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 283 buckets, err = cache.BucketsByIndices([]uint64{1}, height) 284 require.NoError(err) 285 require.Len(buckets, 1) 286 bucketMaps := bucketsToMap(buckets) 287 checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 288 289 // two buckets 290 cache.PutBucketInfo(2, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 291 buckets, err = cache.BucketsByIndices([]uint64{1, 2}, height) 292 require.NoError(err) 293 require.Len(buckets, 2) 294 bucketMaps = bucketsToMap(buckets) 295 checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 296 checkVoteBucket(require, bucketMaps[2], 2, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 297 298 // one bucket not found 299 buckets, err = cache.BucketsByIndices([]uint64{3}, height) 300 require.NoError(err) 301 require.Len(buckets, 0) 302 303 // one bucket found, one not found 304 buckets, err = cache.BucketsByIndices([]uint64{1, 3}, height) 305 require.NoError(err) 306 require.Len(buckets, 1) 307 bucketMaps = bucketsToMap(buckets) 308 checkVoteBucket(require, bucketMaps[1], 1, identityset.Address(1).String(), identityset.Address(2).String(), 100, 100, 1, 1, maxBlockNumber, true, contractAddr) 309 310 // delete bucket 1 311 cache.DeleteBucketInfo(1) 312 buckets, err = cache.BucketsByIndices([]uint64{1}, height) 313 require.NoError(err) 314 require.Len(buckets, 0) 315 } 316 317 func TestContractStakingCache_TotalBucketCount(t *testing.T) { 318 require := require.New(t) 319 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 320 321 height := uint64(0) 322 // no bucket 323 tbc, err := cache.TotalBucketCount(height) 324 require.NoError(err) 325 require.EqualValues(0, tbc) 326 327 // one bucket 328 cache.putTotalBucketCount(1) 329 tbc, err = cache.TotalBucketCount(height) 330 require.NoError(err) 331 require.EqualValues(1, tbc) 332 333 // two buckets 334 cache.putTotalBucketCount(2) 335 tbc, err = cache.TotalBucketCount(height) 336 require.NoError(err) 337 require.EqualValues(2, tbc) 338 339 // delete bucket 1 340 cache.DeleteBucketInfo(1) 341 tbc, err = cache.TotalBucketCount(height) 342 require.NoError(err) 343 require.EqualValues(2, tbc) 344 } 345 346 func TestContractStakingCache_ActiveBucketTypes(t *testing.T) { 347 require := require.New(t) 348 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 349 350 height := uint64(0) 351 // no bucket type 352 abt, err := cache.ActiveBucketTypes(height) 353 require.NoError(err) 354 require.Empty(abt) 355 356 // one bucket type 357 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 358 activeBucketTypes, err := cache.ActiveBucketTypes(height) 359 require.NoError(err) 360 require.Len(activeBucketTypes, 1) 361 require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) 362 require.EqualValues(100, activeBucketTypes[1].Duration) 363 require.EqualValues(1, activeBucketTypes[1].ActivatedAt) 364 365 // two bucket types 366 cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 2}) 367 activeBucketTypes, err = cache.ActiveBucketTypes(height) 368 require.NoError(err) 369 require.Len(activeBucketTypes, 2) 370 require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) 371 require.EqualValues(100, activeBucketTypes[1].Duration) 372 require.EqualValues(1, activeBucketTypes[1].ActivatedAt) 373 require.EqualValues(200, activeBucketTypes[2].Amount.Int64()) 374 require.EqualValues(200, activeBucketTypes[2].Duration) 375 require.EqualValues(2, activeBucketTypes[2].ActivatedAt) 376 377 // add one inactive bucket type 378 cache.PutBucketType(3, &BucketType{Amount: big.NewInt(300), Duration: 300, ActivatedAt: maxBlockNumber}) 379 activeBucketTypes, err = cache.ActiveBucketTypes(height) 380 require.NoError(err) 381 require.Len(activeBucketTypes, 2) 382 require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) 383 require.EqualValues(100, activeBucketTypes[1].Duration) 384 require.EqualValues(1, activeBucketTypes[1].ActivatedAt) 385 require.EqualValues(200, activeBucketTypes[2].Amount.Int64()) 386 require.EqualValues(200, activeBucketTypes[2].Duration) 387 require.EqualValues(2, activeBucketTypes[2].ActivatedAt) 388 389 // deactivate bucket type 1 390 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: maxBlockNumber}) 391 activeBucketTypes, err = cache.ActiveBucketTypes(height) 392 require.NoError(err) 393 require.Len(activeBucketTypes, 1) 394 require.EqualValues(200, activeBucketTypes[2].Amount.Int64()) 395 require.EqualValues(200, activeBucketTypes[2].Duration) 396 require.EqualValues(2, activeBucketTypes[2].ActivatedAt) 397 398 // reactivate bucket type 1 399 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 400 activeBucketTypes, err = cache.ActiveBucketTypes(height) 401 require.NoError(err) 402 require.Len(activeBucketTypes, 2) 403 require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) 404 require.EqualValues(100, activeBucketTypes[1].Duration) 405 require.EqualValues(1, activeBucketTypes[1].ActivatedAt) 406 require.EqualValues(200, activeBucketTypes[2].Amount.Int64()) 407 require.EqualValues(200, activeBucketTypes[2].Duration) 408 require.EqualValues(2, activeBucketTypes[2].ActivatedAt) 409 } 410 411 func TestContractStakingCache_Merge(t *testing.T) { 412 require := require.New(t) 413 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 414 height := uint64(1) 415 ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{BlockHeight: height})) 416 417 // create delta with one bucket type 418 delta := newContractStakingDelta() 419 delta.AddBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 420 // merge delta into cache 421 err := cache.Merge(delta, height) 422 require.NoError(err) 423 // check that bucket type was added to cache 424 activeBucketTypes, err := cache.ActiveBucketTypes(height) 425 require.NoError(err) 426 require.Len(activeBucketTypes, 1) 427 require.EqualValues(100, activeBucketTypes[1].Amount.Int64()) 428 require.EqualValues(100, activeBucketTypes[1].Duration) 429 require.EqualValues(1, activeBucketTypes[1].ActivatedAt) 430 require.EqualValues(height, cache.Height()) 431 432 // create delta with one bucket 433 delta = newContractStakingDelta() 434 delta.AddBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 435 // merge delta into cache 436 err = cache.Merge(delta, height) 437 require.NoError(err) 438 // check that bucket was added to cache and vote count is correct 439 votes, err := cache.CandidateVotes(ctx, identityset.Address(1), height) 440 require.NoError(err) 441 require.EqualValues(100, votes.Int64()) 442 443 // create delta with updated bucket delegate 444 delta = newContractStakingDelta() 445 delta.UpdateBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(3), Owner: identityset.Address(2)}) 446 // merge delta into cache 447 err = cache.Merge(delta, height) 448 require.NoError(err) 449 // check that bucket delegate was updated and vote count is correct 450 votes, err = cache.CandidateVotes(ctx, identityset.Address(1), height) 451 require.NoError(err) 452 require.EqualValues(0, votes.Int64()) 453 votes, err = cache.CandidateVotes(ctx, identityset.Address(3), height) 454 require.NoError(err) 455 require.EqualValues(100, votes.Int64()) 456 457 // create delta with deleted bucket 458 delta = newContractStakingDelta() 459 delta.DeleteBucketInfo(1) 460 // merge delta into cache 461 err = cache.Merge(delta, height) 462 require.NoError(err) 463 // check that bucket was deleted from cache and vote count is 0 464 votes, err = cache.CandidateVotes(ctx, identityset.Address(3), height) 465 require.NoError(err) 466 require.EqualValues(0, votes.Int64()) 467 468 // invalid delta 469 err = cache.Merge(nil, height) 470 require.Error(err) 471 require.Equal(err.Error(), "invalid contract staking delta") 472 } 473 474 func TestContractStakingCache_MatchBucketType(t *testing.T) { 475 require := require.New(t) 476 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 477 478 // no bucket types 479 _, bucketType, ok := cache.MatchBucketType(big.NewInt(100), 100) 480 require.False(ok) 481 require.Nil(bucketType) 482 483 // one bucket type 484 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 485 // match exact bucket type 486 id, bucketType, ok := cache.MatchBucketType(big.NewInt(100), 100) 487 require.True(ok) 488 require.EqualValues(1, id) 489 require.EqualValues(100, bucketType.Amount.Int64()) 490 require.EqualValues(100, bucketType.Duration) 491 require.EqualValues(1, bucketType.ActivatedAt) 492 493 // match bucket type with different amount 494 _, bucketType, ok = cache.MatchBucketType(big.NewInt(200), 100) 495 require.False(ok) 496 require.Nil(bucketType) 497 498 // match bucket type with different duration 499 _, bucketType, ok = cache.MatchBucketType(big.NewInt(100), 200) 500 require.False(ok) 501 require.Nil(bucketType) 502 503 // no match 504 _, bucketType, ok = cache.MatchBucketType(big.NewInt(200), 200) 505 require.False(ok) 506 require.Nil(bucketType) 507 } 508 509 func TestContractStakingCache_BucketTypeCount(t *testing.T) { 510 require := require.New(t) 511 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 512 513 height := uint64(0) 514 // no bucket type 515 btc, err := cache.BucketTypeCount(height) 516 require.NoError(err) 517 require.EqualValues(0, btc) 518 519 // one bucket type 520 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 521 btc, err = cache.BucketTypeCount(height) 522 require.NoError(err) 523 require.EqualValues(1, btc) 524 525 // two bucket types 526 cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 2}) 527 btc, err = cache.BucketTypeCount(height) 528 require.NoError(err) 529 require.EqualValues(2, btc) 530 531 // deactivate bucket type 1 532 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: maxBlockNumber}) 533 btc, err = cache.BucketTypeCount(height) 534 require.NoError(err) 535 require.EqualValues(2, btc) 536 537 btc, err = cache.BucketTypeCount(1) 538 require.Error(err) 539 require.Contains(err.Error(), "invalid height") 540 } 541 542 func TestContractStakingCache_LoadFromDB(t *testing.T) { 543 require := require.New(t) 544 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(27).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 545 546 // mock kvstore exception 547 ctrl := gomock.NewController(t) 548 mockKvStore := db.NewMockKVStore(ctrl) 549 550 // kvstore exception at load height 551 mockKvStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, errors.New("err1")).Times(1) 552 err := cache.LoadFromDB(mockKvStore) 553 require.Error(err) 554 require.Equal(err.Error(), "err1") 555 556 // kvstore exception at load total bucket count 557 mockKvStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, db.ErrNotExist).Times(1) 558 mockKvStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, errors.New("err2")).Times(1) 559 err = cache.LoadFromDB(mockKvStore) 560 require.Error(err) 561 require.Equal(err.Error(), "err2") 562 563 // kvstore exception at load bucket info 564 mockKvStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, db.ErrNotExist).Times(2) 565 mockKvStore.EXPECT().Filter(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, errors.New("err3")).Times(1) 566 err = cache.LoadFromDB(mockKvStore) 567 require.Error(err) 568 require.Equal(err.Error(), "err3") 569 570 // mock bucketInfo Deserialize failed 571 mockKvStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, db.ErrNotExist).Times(2) 572 mockKvStore.EXPECT().Filter(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, [][]byte{nil}, nil).Times(1) 573 err = cache.LoadFromDB(mockKvStore) 574 require.Error(err) 575 576 // kvstore exception at load bucket type 577 mockKvStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, db.ErrNotExist).Times(2) 578 mockKvStore.EXPECT().Filter(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, db.ErrBucketNotExist).Times(1) 579 mockKvStore.EXPECT().Filter(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, errors.New("err4")).Times(1) 580 err = cache.LoadFromDB(mockKvStore) 581 require.Error(err) 582 require.Equal(err.Error(), "err4") 583 584 mockKvStore.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, db.ErrNotExist).Times(2) 585 mockKvStore.EXPECT().Filter(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil, db.ErrBucketNotExist).Times(1) 586 mockKvStore.EXPECT().Filter(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, [][]byte{nil}, nil).Times(1) 587 err = cache.LoadFromDB(mockKvStore) 588 require.Error(err) 589 590 // load from empty db 591 path, err := testutil.PathOfTempFile("staking.db") 592 require.NoError(err) 593 defer testutil.CleanupPath(path) 594 cfg := config.Default.DB 595 cfg.DbPath = path 596 kvstore := db.NewBoltDB(cfg) 597 require.NoError(kvstore.Start(context.Background())) 598 defer kvstore.Stop(context.Background()) 599 600 height := uint64(0) 601 err = cache.LoadFromDB(kvstore) 602 require.NoError(err) 603 tbc, err := cache.TotalBucketCount(height) 604 require.NoError(err) 605 require.Equal(uint64(0), tbc) 606 bts, err := cache.Buckets(height) 607 require.NoError(err) 608 require.Equal(0, len(bts)) 609 btc, err := cache.BucketTypeCount(height) 610 require.NoError(err) 611 require.EqualValues(0, btc) 612 613 // load from db with height and total bucket count 614 height = 12345 615 kvstore.Put(_StakingNS, _stakingHeightKey, byteutil.Uint64ToBytesBigEndian(height)) 616 kvstore.Put(_StakingNS, _stakingTotalBucketCountKey, byteutil.Uint64ToBytesBigEndian(10)) 617 err = cache.LoadFromDB(kvstore) 618 require.NoError(err) 619 tbc, err = cache.TotalBucketCount(height) 620 require.NoError(err) 621 require.Equal(uint64(10), tbc) 622 bts, err = cache.Buckets(height) 623 require.NoError(err) 624 require.Equal(0, len(bts)) 625 btc, err = cache.BucketTypeCount(height) 626 require.NoError(err) 627 require.EqualValues(0, btc) 628 629 // load from db with bucket 630 bucketInfo := &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)} 631 kvstore.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(1), bucketInfo.Serialize()) 632 bucketType := &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1} 633 kvstore.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(1), bucketType.Serialize()) 634 err = cache.LoadFromDB(kvstore) 635 require.NoError(err) 636 tbc, err = cache.TotalBucketCount(height) 637 require.NoError(err) 638 require.Equal(uint64(10), tbc) 639 bi, ok := cache.BucketInfo(1) 640 require.True(ok) 641 bts, err = cache.Buckets(height) 642 require.NoError(err) 643 require.Equal(1, len(bts)) 644 require.Equal(bucketInfo, bi) 645 btc, err = cache.BucketTypeCount(height) 646 require.NoError(err) 647 require.EqualValues(1, btc) 648 id, bt, ok := cache.MatchBucketType(big.NewInt(100), 100) 649 require.True(ok) 650 require.EqualValues(1, id) 651 require.EqualValues(100, bt.Amount.Int64()) 652 require.EqualValues(100, bt.Duration) 653 require.EqualValues(1, bt.ActivatedAt) 654 } 655 656 func bucketsToMap(buckets []*staking.VoteBucket) map[uint64]*staking.VoteBucket { 657 m := make(map[uint64]*staking.VoteBucket) 658 for _, bucket := range buckets { 659 m[bucket.Index] = bucket 660 } 661 return m 662 } 663 664 func checkVoteBucket(r *require.Assertions, bucket *staking.VoteBucket, index uint64, candidate, owner string, amount, duration, createHeight, startHeight, unstakeHeight uint64, autoStake bool, contractAddr string) { 665 r.EqualValues(index, bucket.Index) 666 r.EqualValues(candidate, bucket.Candidate.String()) 667 r.EqualValues(owner, bucket.Owner.String()) 668 r.EqualValues(amount, bucket.StakedAmount.Int64()) 669 r.EqualValues(duration, bucket.StakedDurationBlockNumber) 670 r.EqualValues(createHeight, bucket.CreateBlockHeight) 671 r.EqualValues(startHeight, bucket.StakeStartBlockHeight) 672 r.EqualValues(unstakeHeight, bucket.UnstakeStartBlockHeight) 673 r.EqualValues(autoStake, bucket.AutoStake) 674 r.EqualValues(contractAddr, bucket.ContractAddress) 675 } 676 677 func TestContractStakingCache_MustGetBucketInfo(t *testing.T) { 678 // build test condition to add a bucketInfo 679 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(1).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 680 cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) 681 682 tryCatchMustGetBucketInfo := func(i uint64) (v *bucketInfo, err error) { 683 defer func() { 684 if r := recover(); r != nil { 685 err = errors.New(fmt.Sprint(r)) 686 } 687 }() 688 v = cache.MustGetBucketInfo(i) 689 return 690 } 691 692 r := require.New(t) 693 v, err := tryCatchMustGetBucketInfo(1) 694 r.NotNil(v) 695 r.NoError(err) 696 697 v, err = tryCatchMustGetBucketInfo(2) 698 r.Nil(v) 699 r.Error(err) 700 r.Equal(err.Error(), "bucket info not found") 701 } 702 703 func TestContractStakingCache_MustGetBucketType(t *testing.T) { 704 // build test condition to add a bucketType 705 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(1).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 706 cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) 707 708 tryCatchMustGetBucketType := func(i uint64) (v *BucketType, err error) { 709 defer func() { 710 if r := recover(); r != nil { 711 err = errors.New(fmt.Sprint(r)) 712 } 713 }() 714 v = cache.MustGetBucketType(i) 715 return 716 } 717 718 r := require.New(t) 719 v, err := tryCatchMustGetBucketType(1) 720 r.NotNil(v) 721 r.NoError(err) 722 723 v, err = tryCatchMustGetBucketType(2) 724 r.Nil(v) 725 r.Error(err) 726 r.Equal(err.Error(), "bucket type not found") 727 } 728 729 func TestContractStakingCache_DeleteBucketInfo(t *testing.T) { 730 // build test condition to add a bucketInfo 731 cache := newContractStakingCache(Config{ContractAddress: identityset.Address(1).String(), CalculateVoteWeight: calculateVoteWeightGen(genesis.Default.VoteWeightCalConsts), BlockInterval: _blockInterval}) 732 bi1 := &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(1)} 733 bi2 := &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)} 734 cache.PutBucketInfo(1, bi1) 735 cache.PutBucketInfo(2, bi2) 736 737 cache.DeleteBucketInfo(1) 738 739 // remove candidate bucket before 740 delete(cache.candidateBucketMap, bi2.Delegate.String()) 741 cache.DeleteBucketInfo(2) 742 }