github.com/klaytn/klaytn@v1.10.2/reward/staking_manager.go (about) 1 // Copyright 2019 The klaytn Authors 2 // This file is part of the klaytn library. 3 // 4 // The klaytn library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The klaytn library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>. 16 17 package reward 18 19 import ( 20 "errors" 21 "sync" 22 23 "github.com/klaytn/klaytn/blockchain" 24 "github.com/klaytn/klaytn/blockchain/state" 25 "github.com/klaytn/klaytn/blockchain/types" 26 "github.com/klaytn/klaytn/common" 27 "github.com/klaytn/klaytn/event" 28 "github.com/klaytn/klaytn/params" 29 ) 30 31 const ( 32 chainHeadChanSize = 100 33 ) 34 35 // blockChain is an interface for blockchain.Blockchain used in reward package. 36 type blockChain interface { 37 SubscribeChainHeadEvent(ch chan<- blockchain.ChainHeadEvent) event.Subscription 38 GetBlockByNumber(number uint64) *types.Block 39 StateAt(root common.Hash) (*state.StateDB, error) 40 Config() *params.ChainConfig 41 42 blockchain.ChainContext 43 } 44 45 type StakingManager struct { 46 addressBookConnector *addressBookConnector 47 stakingInfoCache *stakingInfoCache 48 stakingInfoDB stakingInfoDB 49 governanceHelper governanceHelper 50 blockchain blockChain 51 chainHeadChan chan blockchain.ChainHeadEvent 52 chainHeadSub event.Subscription 53 } 54 55 var ( 56 // variables for sole StakingManager 57 once sync.Once 58 stakingManager *StakingManager 59 60 // errors for staking manager 61 ErrStakingManagerNotSet = errors.New("staking manager is not set") 62 ErrChainHeadChanNotSet = errors.New("chain head channel is not set") 63 ) 64 65 // NewStakingManager creates and returns StakingManager. 66 // 67 // On the first call, a StakingManager is created with given parameters. 68 // From next calls, the existing StakingManager is returned. (Parameters 69 // from the next calls will not affect.) 70 func NewStakingManager(bc blockChain, gh governanceHelper, db stakingInfoDB) *StakingManager { 71 if bc != nil && gh != nil { 72 // this is only called once 73 once.Do(func() { 74 stakingManager = &StakingManager{ 75 addressBookConnector: newAddressBookConnector(bc, gh), 76 stakingInfoCache: newStakingInfoCache(), 77 stakingInfoDB: db, 78 governanceHelper: gh, 79 blockchain: bc, 80 chainHeadChan: make(chan blockchain.ChainHeadEvent, chainHeadChanSize), 81 } 82 83 // Before migration, staking information of current and before should be stored in DB. 84 // 85 // Staking information from block of StakingUpdateInterval ahead is needed to create a block. 86 // If there is no staking info in either cache, db or state trie, the node cannot make a block. 87 // The information in state trie is deleted after state trie migration. 88 blockchain.RegisterMigrationPrerequisites(func(blockNum uint64) error { 89 if err := CheckStakingInfoStored(blockNum); err != nil { 90 return err 91 } 92 return CheckStakingInfoStored(blockNum + params.StakingUpdateInterval()) 93 }) 94 }) 95 } else { 96 logger.Error("unable to set StakingManager", "blockchain", bc, "governanceHelper", gh) 97 } 98 99 return stakingManager 100 } 101 102 func GetStakingManager() *StakingManager { 103 return stakingManager 104 } 105 106 // GetStakingInfo returns a stakingInfo on the staking block of the given block number. 107 // Note that staking block is the block on which the associated staking information is stored and used during an interval. 108 func GetStakingInfo(blockNum uint64) *StakingInfo { 109 stakingBlockNumber := params.CalcStakingBlockNumber(blockNum) 110 logger.Debug("Staking information is requested", "blockNum", blockNum, "staking block number", stakingBlockNumber) 111 return GetStakingInfoOnStakingBlock(stakingBlockNumber) 112 } 113 114 // GetStakingInfoOnStakingBlock returns a corresponding StakingInfo for a staking block number. 115 // If the given number is not on the staking block, it returns nil. 116 // 117 // Fixup for Gini coefficients: 118 // Klaytn core stores Gini: -1 in its database. 119 // We ensure GetStakingInfoOnStakingBlock() to always return meaningful Gini. 120 // - If cache hit -> fillMissingGini -> modifies cached in-memory object 121 // - If db hit -> fillMissingGini -> write to cache 122 // - If read contract -> write to db (gini: -1) -> fillMissingGini -> write to cache 123 func GetStakingInfoOnStakingBlock(stakingBlockNumber uint64) *StakingInfo { 124 if stakingManager == nil { 125 logger.Error("unable to GetStakingInfo", "err", ErrStakingManagerNotSet) 126 return nil 127 } 128 129 // shortcut if given block is not on staking update interval 130 if !params.IsStakingUpdateInterval(stakingBlockNumber) { 131 return nil 132 } 133 134 // Get staking info from cache 135 if cachedStakingInfo := stakingManager.stakingInfoCache.get(stakingBlockNumber); cachedStakingInfo != nil { 136 logger.Debug("StakingInfoCache hit.", "staking block number", stakingBlockNumber, "stakingInfo", cachedStakingInfo) 137 // Fill in Gini coeff if not set. Modifies the cached object. 138 if err := fillMissingGiniCoefficient(cachedStakingInfo, stakingBlockNumber); err != nil { 139 logger.Warn("Cannot fill in gini coefficient", "staking block number", stakingBlockNumber, "err", err) 140 } 141 return cachedStakingInfo 142 } 143 144 // Get staking info from DB 145 if storedStakingInfo, err := getStakingInfoFromDB(stakingBlockNumber); storedStakingInfo != nil && err == nil { 146 logger.Debug("StakingInfoDB hit.", "staking block number", stakingBlockNumber, "stakingInfo", storedStakingInfo) 147 // Fill in Gini coeff before adding to cache. 148 if err := fillMissingGiniCoefficient(storedStakingInfo, stakingBlockNumber); err != nil { 149 logger.Warn("Cannot fill in gini coefficient", "staking block number", stakingBlockNumber, "err", err) 150 } 151 stakingManager.stakingInfoCache.add(storedStakingInfo) 152 return storedStakingInfo 153 } else { 154 logger.Debug("failed to get stakingInfo from DB", "err", err, "staking block number", stakingBlockNumber) 155 } 156 157 // Calculate staking info from block header and updates it to cache and db 158 calcStakingInfo, err := updateStakingInfo(stakingBlockNumber) 159 if calcStakingInfo == nil { 160 logger.Error("failed to update stakingInfo", "staking block number", stakingBlockNumber, "err", err) 161 return nil 162 } 163 164 logger.Debug("Get stakingInfo from header.", "staking block number", stakingBlockNumber, "stakingInfo", calcStakingInfo) 165 return calcStakingInfo 166 } 167 168 // updateStakingInfo updates staking info in cache and db created from given block number. 169 func updateStakingInfo(blockNum uint64) (*StakingInfo, error) { 170 if stakingManager == nil { 171 return nil, ErrStakingManagerNotSet 172 } 173 174 stakingInfo, err := stakingManager.addressBookConnector.getStakingInfoFromAddressBook(blockNum) 175 if err != nil { 176 return nil, err 177 } 178 179 // Add to DB before setting Gini; DB will contain {Gini: -1} 180 if err := AddStakingInfoToDB(stakingInfo); err != nil { 181 logger.Debug("failed to write staking info to db", "err", err, "stakingInfo", stakingInfo) 182 return stakingInfo, err 183 } 184 185 // Fill in Gini coeff before adding to cache 186 if err := fillMissingGiniCoefficient(stakingInfo, blockNum); err != nil { 187 logger.Warn("Cannot fill in gini coefficient", "blockNum", blockNum, "err", err) 188 } 189 190 // Add to cache after setting Gini 191 stakingManager.stakingInfoCache.add(stakingInfo) 192 193 logger.Info("Add a new stakingInfo to stakingInfoCache and stakingInfoDB", "blockNum", blockNum) 194 logger.Debug("Added stakingInfo", "stakingInfo", stakingInfo) 195 return stakingInfo, nil 196 } 197 198 // CheckStakingInfoStored makes sure the given staking info is stored in cache and DB 199 func CheckStakingInfoStored(blockNum uint64) error { 200 if stakingManager == nil { 201 return ErrStakingManagerNotSet 202 } 203 204 stakingBlockNumber := params.CalcStakingBlockNumber(blockNum) 205 206 // skip checking if staking info is stored in DB 207 if _, err := getStakingInfoFromDB(stakingBlockNumber); err == nil { 208 return nil 209 } 210 211 // update staking info in DB and cache from address book 212 _, err := updateStakingInfo(stakingBlockNumber) 213 return err 214 } 215 216 // Fill in StakingInfo.Gini value if not set. 217 func fillMissingGiniCoefficient(stakingInfo *StakingInfo, number uint64) error { 218 if !stakingInfo.UseGini { 219 return nil 220 } 221 if stakingInfo.Gini >= 0 { 222 return nil 223 } 224 225 // We reach here if UseGini == true && Gini == -1. There are two such cases. 226 // - Gini was never been calculated, so it is DefaultGiniCoefficient. 227 // - Gini was calculated but there was no eligible node, so Gini = -1. 228 // For the second case, in theory we won't have to recalculalte Gini, 229 // but there is no way to distinguish both. So we just recalculate. 230 pset, err := stakingManager.governanceHelper.EffectiveParams(number) 231 if err != nil { 232 return err 233 } 234 minStaking := pset.MinimumStakeBig().Uint64() 235 236 c := stakingInfo.GetConsolidatedStakingInfo() 237 if c == nil { 238 return errors.New("Cannot create ConsolidatedStakingInfo") 239 } 240 241 stakingInfo.Gini = c.CalcGiniCoefficientMinStake(minStaking) 242 logger.Debug("Calculated missing Gini for stored StakingInfo", "number", number, "gini", stakingInfo.Gini) 243 return nil 244 } 245 246 // StakingManagerSubscribe setups a channel to listen chain head event and starts a goroutine to update staking cache. 247 func StakingManagerSubscribe() { 248 if stakingManager == nil { 249 logger.Warn("unable to subscribe; this can slow down node", "err", ErrStakingManagerNotSet) 250 return 251 } 252 253 stakingManager.chainHeadSub = stakingManager.blockchain.SubscribeChainHeadEvent(stakingManager.chainHeadChan) 254 255 go handleChainHeadEvent() 256 } 257 258 func handleChainHeadEvent() { 259 if stakingManager == nil { 260 logger.Warn("unable to start chain head event", "err", ErrStakingManagerNotSet) 261 return 262 } else if stakingManager.chainHeadSub == nil { 263 logger.Info("unable to start chain head event", "err", ErrChainHeadChanNotSet) 264 return 265 } 266 267 defer StakingManagerUnsubscribe() 268 269 logger.Info("Start listening chain head event to update stakingInfoCache.") 270 271 for { 272 // A real event arrived, process interesting content 273 select { 274 // Handle ChainHeadEvent 275 case ev := <-stakingManager.chainHeadChan: 276 pset, err := stakingManager.governanceHelper.EffectiveParams(ev.Block.NumberU64() + 1) 277 if err != nil { 278 logger.Error("unable to fetch parameters at", "blockNum", ev.Block.NumberU64()+1) 279 continue 280 } 281 if pset.Policy() == params.WeightedRandom { 282 // check and update if staking info is not valid before for the next update interval blocks 283 stakingInfo := GetStakingInfo(ev.Block.NumberU64() + pset.StakeUpdateInterval()) 284 if stakingInfo == nil { 285 logger.Error("unable to fetch staking info", "blockNum", ev.Block.NumberU64()) 286 } 287 } 288 case <-stakingManager.chainHeadSub.Err(): 289 return 290 } 291 } 292 } 293 294 // StakingManagerUnsubscribe can unsubscribe a subscription on chain head event. 295 func StakingManagerUnsubscribe() { 296 if stakingManager == nil { 297 logger.Warn("unable to start chain head event", "err", ErrStakingManagerNotSet) 298 return 299 } else if stakingManager.chainHeadSub == nil { 300 logger.Info("unable to start chain head event", "err", ErrChainHeadChanNotSet) 301 return 302 } 303 304 stakingManager.chainHeadSub.Unsubscribe() 305 } 306 307 // TODO-Klaytn-Reward the following methods are used for testing purpose, it needs to be moved into test files. 308 // Unlike NewStakingManager(), SetTestStakingManager*() do not trigger once.Do(). 309 // This way you can avoid irreversible side effects during tests. 310 311 // SetTestStakingManagerWithChain sets a full-featured staking manager with blockchain, database and cache. 312 // Note that this method is used only for testing purpose. 313 func SetTestStakingManagerWithChain(bc blockChain, gh governanceHelper, db stakingInfoDB) { 314 SetTestStakingManager(&StakingManager{ 315 addressBookConnector: newAddressBookConnector(bc, gh), 316 stakingInfoCache: newStakingInfoCache(), 317 stakingInfoDB: db, 318 governanceHelper: gh, 319 blockchain: bc, 320 chainHeadChan: make(chan blockchain.ChainHeadEvent, chainHeadChanSize), 321 }) 322 } 323 324 // SetTestStakingManagerWithDB sets the staking manager with the given database. 325 // Note that this method is used only for testing purpose. 326 func SetTestStakingManagerWithDB(testDB stakingInfoDB) { 327 SetTestStakingManager(&StakingManager{ 328 stakingInfoDB: testDB, 329 }) 330 } 331 332 // SetTestStakingManagerWithStakingInfoCache sets the staking manager with the given test staking information. 333 // Note that this method is used only for testing purpose. 334 func SetTestStakingManagerWithStakingInfoCache(testInfo *StakingInfo) { 335 cache := newStakingInfoCache() 336 cache.add(testInfo) 337 SetTestStakingManager(&StakingManager{ 338 stakingInfoCache: cache, 339 }) 340 } 341 342 // SetTestStakingManager sets the staking manager for testing purpose. 343 // Note that this method is used only for testing purpose. 344 func SetTestStakingManager(sm *StakingManager) { 345 stakingManager = sm 346 }