github.com/iotexproject/iotex-core@v1.14.1-rc1/blockindex/contractstaking/cache.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 "sync" 12 13 "github.com/iotexproject/iotex-address/address" 14 "github.com/pkg/errors" 15 16 "github.com/iotexproject/iotex-core/action/protocol" 17 "github.com/iotexproject/iotex-core/db" 18 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 19 ) 20 21 type ( 22 contractStakingCache struct { 23 bucketInfoMap map[uint64]*bucketInfo // map[token]bucketInfo 24 candidateBucketMap map[string]map[uint64]bool // map[candidate]bucket 25 bucketTypeMap map[uint64]*BucketType // map[bucketTypeId]BucketType 26 propertyBucketTypeMap map[int64]map[uint64]uint64 // map[amount][duration]index 27 totalBucketCount uint64 // total number of buckets including burned buckets 28 height uint64 // current block height, it's put in cache for consistency on merge 29 mutex sync.RWMutex // a RW mutex for the cache to protect concurrent access 30 config Config 31 } 32 ) 33 34 var ( 35 // ErrBucketNotExist is the error when bucket does not exist 36 ErrBucketNotExist = errors.New("bucket does not exist") 37 // ErrInvalidHeight is the error when height is invalid 38 ErrInvalidHeight = errors.New("invalid height") 39 ) 40 41 func newContractStakingCache(config Config) *contractStakingCache { 42 return &contractStakingCache{ 43 bucketInfoMap: make(map[uint64]*bucketInfo), 44 bucketTypeMap: make(map[uint64]*BucketType), 45 propertyBucketTypeMap: make(map[int64]map[uint64]uint64), 46 candidateBucketMap: make(map[string]map[uint64]bool), 47 config: config, 48 } 49 } 50 51 func (s *contractStakingCache) Height() uint64 { 52 s.mutex.RLock() 53 defer s.mutex.RUnlock() 54 return s.height 55 } 56 57 func (s *contractStakingCache) CandidateVotes(ctx context.Context, candidate address.Address, height uint64) (*big.Int, error) { 58 s.mutex.RLock() 59 defer s.mutex.RUnlock() 60 61 if err := s.validateHeight(height); err != nil { 62 return nil, err 63 } 64 votes := big.NewInt(0) 65 m, ok := s.candidateBucketMap[candidate.String()] 66 if !ok { 67 return votes, nil 68 } 69 featureCtx := protocol.MustGetFeatureCtx(ctx) 70 for id, existed := range m { 71 if !existed { 72 continue 73 } 74 bi := s.mustGetBucketInfo(id) 75 // only count the bucket that is not unstaked 76 if bi.UnstakedAt != maxBlockNumber { 77 continue 78 } 79 bt := s.mustGetBucketType(bi.TypeIndex) 80 if featureCtx.FixContractStakingWeightedVotes { 81 votes.Add(votes, s.config.CalculateVoteWeight(assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval))) 82 } else { 83 votes.Add(votes, bt.Amount) 84 } 85 } 86 return votes, nil 87 } 88 89 func (s *contractStakingCache) Buckets(height uint64) ([]*Bucket, error) { 90 s.mutex.RLock() 91 defer s.mutex.RUnlock() 92 93 if err := s.validateHeight(height); err != nil { 94 return nil, err 95 } 96 97 vbs := []*Bucket{} 98 for id, bi := range s.bucketInfoMap { 99 bt := s.mustGetBucketType(bi.TypeIndex) 100 vb := assembleBucket(id, bi.clone(), bt, s.config.ContractAddress, s.config.BlockInterval) 101 vbs = append(vbs, vb) 102 } 103 return vbs, nil 104 } 105 106 func (s *contractStakingCache) Bucket(id, height uint64) (*Bucket, bool, error) { 107 s.mutex.RLock() 108 defer s.mutex.RUnlock() 109 110 if err := s.validateHeight(height); err != nil { 111 return nil, false, err 112 } 113 bt, ok := s.getBucket(id) 114 return bt, ok, nil 115 } 116 117 func (s *contractStakingCache) BucketInfo(id uint64) (*bucketInfo, bool) { 118 s.mutex.RLock() 119 defer s.mutex.RUnlock() 120 121 return s.getBucketInfo(id) 122 } 123 124 func (s *contractStakingCache) MustGetBucketInfo(id uint64) *bucketInfo { 125 s.mutex.RLock() 126 defer s.mutex.RUnlock() 127 128 return s.mustGetBucketInfo(id) 129 } 130 131 func (s *contractStakingCache) MustGetBucketType(id uint64) *BucketType { 132 s.mutex.RLock() 133 defer s.mutex.RUnlock() 134 135 return s.mustGetBucketType(id) 136 } 137 138 func (s *contractStakingCache) BucketType(id uint64) (*BucketType, bool) { 139 s.mutex.RLock() 140 defer s.mutex.RUnlock() 141 142 return s.getBucketType(id) 143 } 144 145 func (s *contractStakingCache) BucketsByCandidate(candidate address.Address, height uint64) ([]*Bucket, error) { 146 s.mutex.RLock() 147 defer s.mutex.RUnlock() 148 149 if err := s.validateHeight(height); err != nil { 150 return nil, err 151 } 152 bucketMap := s.candidateBucketMap[candidate.String()] 153 vbs := make([]*Bucket, 0, len(bucketMap)) 154 for id := range bucketMap { 155 vb := s.mustGetBucket(id) 156 vbs = append(vbs, vb) 157 } 158 return vbs, nil 159 } 160 161 func (s *contractStakingCache) BucketsByIndices(indices []uint64, height uint64) ([]*Bucket, error) { 162 s.mutex.RLock() 163 defer s.mutex.RUnlock() 164 165 if err := s.validateHeight(height); err != nil { 166 return nil, err 167 } 168 vbs := make([]*Bucket, 0, len(indices)) 169 for _, id := range indices { 170 vb, ok := s.getBucket(id) 171 if ok { 172 vbs = append(vbs, vb) 173 } 174 } 175 return vbs, nil 176 } 177 178 func (s *contractStakingCache) TotalBucketCount(height uint64) (uint64, error) { 179 s.mutex.RLock() 180 defer s.mutex.RUnlock() 181 182 if err := s.validateHeight(height); err != nil { 183 return 0, err 184 } 185 return s.totalBucketCount, nil 186 } 187 188 func (s *contractStakingCache) ActiveBucketTypes(height uint64) (map[uint64]*BucketType, error) { 189 s.mutex.RLock() 190 defer s.mutex.RUnlock() 191 192 if err := s.validateHeight(height); err != nil { 193 return nil, err 194 } 195 m := make(map[uint64]*BucketType) 196 for k, v := range s.bucketTypeMap { 197 if v.ActivatedAt != maxBlockNumber { 198 m[k] = v.Clone() 199 } 200 } 201 return m, nil 202 } 203 204 func (s *contractStakingCache) PutBucketType(id uint64, bt *BucketType) { 205 s.mutex.Lock() 206 defer s.mutex.Unlock() 207 208 s.putBucketType(id, bt) 209 } 210 211 func (s *contractStakingCache) PutBucketInfo(id uint64, bi *bucketInfo) { 212 s.mutex.Lock() 213 defer s.mutex.Unlock() 214 215 s.putBucketInfo(id, bi) 216 } 217 218 func (s *contractStakingCache) DeleteBucketInfo(id uint64) { 219 s.mutex.Lock() 220 defer s.mutex.Unlock() 221 222 s.deleteBucketInfo(id) 223 } 224 225 func (s *contractStakingCache) Merge(delta *contractStakingDelta, height uint64) error { 226 s.mutex.Lock() 227 defer s.mutex.Unlock() 228 229 if err := s.mergeDelta(delta); err != nil { 230 return err 231 } 232 s.putHeight(height) 233 s.putTotalBucketCount(s.totalBucketCount + delta.AddedBucketCnt()) 234 return nil 235 } 236 237 func (s *contractStakingCache) MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) { 238 s.mutex.RLock() 239 defer s.mutex.RUnlock() 240 241 id, ok := s.getBucketTypeIndex(amount, duration) 242 if !ok { 243 return 0, nil, false 244 } 245 return id, s.mustGetBucketType(id), true 246 } 247 248 func (s *contractStakingCache) BucketTypeCount(height uint64) (uint64, error) { 249 s.mutex.RLock() 250 defer s.mutex.RUnlock() 251 252 if err := s.validateHeight(height); err != nil { 253 return 0, err 254 } 255 return uint64(len(s.bucketTypeMap)), nil 256 } 257 258 func (s *contractStakingCache) LoadFromDB(kvstore db.KVStore) error { 259 s.mutex.Lock() 260 defer s.mutex.Unlock() 261 262 // load height 263 var height uint64 264 h, err := kvstore.Get(_StakingNS, _stakingHeightKey) 265 if err != nil { 266 if !errors.Is(err, db.ErrNotExist) { 267 return err 268 } 269 height = 0 270 } else { 271 height = byteutil.BytesToUint64BigEndian(h) 272 273 } 274 s.putHeight(height) 275 276 // load total bucket count 277 var totalBucketCount uint64 278 tbc, err := kvstore.Get(_StakingNS, _stakingTotalBucketCountKey) 279 if err != nil { 280 if !errors.Is(err, db.ErrNotExist) { 281 return err 282 } 283 totalBucketCount = 0 284 } else { 285 totalBucketCount = byteutil.BytesToUint64BigEndian(tbc) 286 } 287 s.putTotalBucketCount(totalBucketCount) 288 289 // load bucket info 290 ks, vs, err := kvstore.Filter(_StakingBucketInfoNS, func(k, v []byte) bool { return true }, nil, nil) 291 if err != nil && !errors.Is(err, db.ErrBucketNotExist) { 292 return err 293 } 294 for i := range vs { 295 var b bucketInfo 296 if err := b.Deserialize(vs[i]); err != nil { 297 return err 298 } 299 s.putBucketInfo(byteutil.BytesToUint64BigEndian(ks[i]), &b) 300 } 301 302 // load bucket type 303 ks, vs, err = kvstore.Filter(_StakingBucketTypeNS, func(k, v []byte) bool { return true }, nil, nil) 304 if err != nil && !errors.Is(err, db.ErrBucketNotExist) { 305 return err 306 } 307 for i := range vs { 308 var b BucketType 309 if err := b.Deserialize(vs[i]); err != nil { 310 return err 311 } 312 s.putBucketType(byteutil.BytesToUint64BigEndian(ks[i]), &b) 313 } 314 return nil 315 } 316 317 func (s *contractStakingCache) getBucketTypeIndex(amount *big.Int, duration uint64) (uint64, bool) { 318 m, ok := s.propertyBucketTypeMap[amount.Int64()] 319 if !ok { 320 return 0, false 321 } 322 id, ok := m[duration] 323 return id, ok 324 } 325 326 func (s *contractStakingCache) getBucketType(id uint64) (*BucketType, bool) { 327 bt, ok := s.bucketTypeMap[id] 328 if !ok { 329 return nil, false 330 } 331 return bt.Clone(), ok 332 } 333 334 func (s *contractStakingCache) mustGetBucketType(id uint64) *BucketType { 335 bt, ok := s.getBucketType(id) 336 if !ok { 337 panic("bucket type not found") 338 } 339 return bt 340 } 341 342 func (s *contractStakingCache) getBucketInfo(id uint64) (*bucketInfo, bool) { 343 bi, ok := s.bucketInfoMap[id] 344 if !ok { 345 return nil, false 346 } 347 return bi.clone(), ok 348 } 349 350 func (s *contractStakingCache) mustGetBucketInfo(id uint64) *bucketInfo { 351 bt, ok := s.getBucketInfo(id) 352 if !ok { 353 panic("bucket info not found") 354 } 355 return bt 356 } 357 358 func (s *contractStakingCache) mustGetBucket(id uint64) *Bucket { 359 bi := s.mustGetBucketInfo(id) 360 bt := s.mustGetBucketType(bi.TypeIndex) 361 return assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval) 362 } 363 364 func (s *contractStakingCache) getBucket(id uint64) (*Bucket, bool) { 365 bi, ok := s.getBucketInfo(id) 366 if !ok { 367 return nil, false 368 } 369 bt := s.mustGetBucketType(bi.TypeIndex) 370 return assembleBucket(id, bi, bt, s.config.ContractAddress, s.config.BlockInterval), true 371 } 372 373 func (s *contractStakingCache) putBucketType(id uint64, bt *BucketType) { 374 // remove old bucket map 375 if oldBt, existed := s.bucketTypeMap[id]; existed { 376 amount := oldBt.Amount.Int64() 377 if _, existed := s.propertyBucketTypeMap[amount]; existed { 378 delete(s.propertyBucketTypeMap[amount], oldBt.Duration) 379 if len(s.propertyBucketTypeMap[amount]) == 0 { 380 delete(s.propertyBucketTypeMap, amount) 381 } 382 } 383 } 384 // add new bucket map 385 s.bucketTypeMap[id] = bt 386 amount := bt.Amount.Int64() 387 m, ok := s.propertyBucketTypeMap[amount] 388 if !ok { 389 s.propertyBucketTypeMap[amount] = make(map[uint64]uint64) 390 m = s.propertyBucketTypeMap[amount] 391 } 392 m[bt.Duration] = id 393 } 394 395 func (s *contractStakingCache) putBucketInfo(id uint64, bi *bucketInfo) { 396 oldBi := s.bucketInfoMap[id] 397 s.bucketInfoMap[id] = bi 398 // update candidate bucket map 399 newDelegate := bi.Delegate.String() 400 if _, ok := s.candidateBucketMap[newDelegate]; !ok { 401 s.candidateBucketMap[newDelegate] = make(map[uint64]bool) 402 } 403 s.candidateBucketMap[newDelegate][id] = true 404 // delete old candidate bucket map 405 if oldBi == nil { 406 return 407 } 408 oldDelegate := oldBi.Delegate.String() 409 if oldDelegate == newDelegate { 410 return 411 } 412 delete(s.candidateBucketMap[oldDelegate], id) 413 if len(s.candidateBucketMap[oldDelegate]) == 0 { 414 delete(s.candidateBucketMap, oldDelegate) 415 } 416 } 417 418 func (s *contractStakingCache) deleteBucketInfo(id uint64) { 419 bi, ok := s.bucketInfoMap[id] 420 if !ok { 421 return 422 } 423 delete(s.bucketInfoMap, id) 424 if _, ok := s.candidateBucketMap[bi.Delegate.String()]; !ok { 425 return 426 } 427 delete(s.candidateBucketMap[bi.Delegate.String()], id) 428 } 429 430 func (s *contractStakingCache) putTotalBucketCount(count uint64) { 431 s.totalBucketCount = count 432 } 433 434 func (s *contractStakingCache) putHeight(height uint64) { 435 s.height = height 436 } 437 438 func (s *contractStakingCache) mergeDelta(delta *contractStakingDelta) error { 439 if delta == nil { 440 return errors.New("invalid contract staking delta") 441 } 442 for state, btMap := range delta.BucketTypeDelta() { 443 if state == deltaStateAdded || state == deltaStateModified { 444 for id, bt := range btMap { 445 s.putBucketType(id, bt) 446 } 447 } 448 } 449 for state, biMap := range delta.BucketInfoDelta() { 450 if state == deltaStateAdded || state == deltaStateModified { 451 for id, bi := range biMap { 452 s.putBucketInfo(id, bi) 453 } 454 } else if state == deltaStateRemoved { 455 for id := range biMap { 456 s.deleteBucketInfo(id) 457 } 458 } 459 } 460 return nil 461 } 462 463 func (s *contractStakingCache) validateHeight(height uint64) error { 464 // means latest height 465 if height == 0 { 466 return nil 467 } 468 // Currently, historical block data query is not supported. 469 // However, the latest data is actually returned when querying historical block data, for the following reasons: 470 // 1. to maintain compatibility with the current code's invocation of ActiveCandidate 471 // 2. to cause consensus errors when the indexer is lagging behind 472 if height > s.height { 473 return errors.Wrapf(ErrInvalidHeight, "expected %d, actual %d", s.height, height) 474 } 475 return nil 476 }