github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/poll/staking_committee.go (about) 1 // Copyright (c) 2020 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 poll 7 8 import ( 9 "context" 10 "encoding/hex" 11 "math/big" 12 "time" 13 14 "github.com/ethereum/go-ethereum/common" 15 "github.com/ethereum/go-ethereum/common/hexutil" 16 "github.com/ethereum/go-ethereum/crypto" 17 "github.com/iotexproject/go-pkgs/hash" 18 "github.com/iotexproject/iotex-address/address" 19 "github.com/iotexproject/iotex-election/committee" 20 "github.com/iotexproject/iotex-election/types" 21 "github.com/iotexproject/iotex-election/util" 22 "github.com/iotexproject/iotex-proto/golang/iotextypes" 23 "github.com/pkg/errors" 24 "go.uber.org/zap" 25 26 "github.com/iotexproject/iotex-core/action" 27 "github.com/iotexproject/iotex-core/action/protocol" 28 "github.com/iotexproject/iotex-core/action/protocol/execution/evm" 29 "github.com/iotexproject/iotex-core/action/protocol/rolldpos" 30 "github.com/iotexproject/iotex-core/blockchain/genesis" 31 "github.com/iotexproject/iotex-core/pkg/log" 32 "github.com/iotexproject/iotex-core/pkg/prometheustimer" 33 "github.com/iotexproject/iotex-core/state" 34 ) 35 36 var ( 37 _nativeStakingContractCreator = address.ZeroAddress 38 _nativeStakingContractNonce = uint64(0) 39 // this is a special execution that is not signed, set hash = hex-string of "_nativeStakingContractHash" 40 _nativeStakingContractHash, _ = hash.HexStringToHash256("000000000000006e61746976655374616b696e67436f6e747261637448617368") 41 ) 42 43 type stakingCommittee struct { 44 electionCommittee committee.Committee 45 governanceStaking Protocol 46 nativeStaking *NativeStaking 47 scoreThreshold *big.Int 48 currentNativeBuckets []*types.Bucket 49 timerFactory *prometheustimer.TimerFactory 50 } 51 52 // NewStakingCommittee creates a staking committee which fetch result from governance chain and native staking 53 func NewStakingCommittee( 54 ec committee.Committee, 55 gs Protocol, 56 readContract ReadContract, 57 nativeStakingContractAddress string, 58 nativeStakingContractCode string, 59 scoreThreshold *big.Int, 60 ) (Protocol, error) { 61 var ns *NativeStaking 62 if nativeStakingContractAddress != "" || nativeStakingContractCode != "" { 63 var err error 64 if ns, err = NewNativeStaking(readContract); err != nil { 65 return nil, errors.New("failed to create native staking") 66 } 67 if nativeStakingContractAddress != "" { 68 ns.SetContract(nativeStakingContractAddress) 69 } 70 } 71 72 timerFactory, err := prometheustimer.New( 73 "iotex_staking_perf", 74 "Performance of staking module", 75 []string{"type"}, 76 []string{"default"}, 77 ) 78 if err != nil { 79 return nil, err 80 } 81 82 sc := stakingCommittee{ 83 electionCommittee: ec, 84 governanceStaking: gs, 85 nativeStaking: ns, 86 scoreThreshold: scoreThreshold, 87 } 88 sc.timerFactory = timerFactory 89 90 return &sc, nil 91 } 92 93 func (sc *stakingCommittee) CreateGenesisStates(ctx context.Context, sm protocol.StateManager) error { 94 if gsc, ok := sc.governanceStaking.(protocol.GenesisStateCreator); ok { 95 if err := gsc.CreateGenesisStates(ctx, sm); err != nil { 96 return err 97 } 98 } 99 g := genesis.MustExtractGenesisContext(ctx) 100 blkCtx := protocol.MustGetBlockCtx(ctx) 101 if blkCtx.BlockHeight != 0 { 102 return errors.Errorf("Cannot create genesis state for height %d", blkCtx.BlockHeight) 103 } 104 if g.NativeStakingContractCode == "" || g.NativeStakingContractAddress != "" { 105 return nil 106 } 107 blkCtx.Producer, _ = address.FromString(address.ZeroAddress) 108 blkCtx.GasLimit = g.BlockGasLimitByHeight(0) 109 bytes, err := hexutil.Decode(g.NativeStakingContractCode) 110 if err != nil { 111 return err 112 } 113 execution, err := action.NewExecution( 114 "", 115 _nativeStakingContractNonce, 116 big.NewInt(0), 117 g.BlockGasLimitByHeight(0), 118 big.NewInt(0), 119 bytes, 120 ) 121 if err != nil { 122 return err 123 } 124 actionCtx := protocol.ActionCtx{} 125 actionCtx.Caller, err = address.FromString(_nativeStakingContractCreator) 126 if err != nil { 127 return err 128 } 129 actionCtx.Nonce = _nativeStakingContractNonce 130 actionCtx.ActionHash = _nativeStakingContractHash 131 actionCtx.GasPrice = execution.GasPrice() 132 actionCtx.IntrinsicGas, err = execution.IntrinsicGas() 133 if err != nil { 134 return err 135 } 136 ctx = protocol.WithActionCtx(ctx, actionCtx) 137 ctx = protocol.WithBlockCtx(ctx, blkCtx) 138 ctx = evm.WithHelperCtx(ctx, evm.HelperContext{ 139 GetBlockHash: func(height uint64) (hash.Hash256, error) { 140 return hash.ZeroHash256, nil 141 }, 142 GetBlockTime: func(u uint64) (time.Time, error) { 143 // make sure the returned timestamp is after the current block time so that evm upgrades based on timestamp (Shanghai and onwards) are disabled 144 return blkCtx.BlockTimeStamp.Add(5 * time.Second), nil 145 }, 146 DepositGasFunc: func(context.Context, protocol.StateManager, address.Address, *big.Int, *big.Int) (*action.TransactionLog, error) { 147 return nil, nil 148 }, 149 Sgd: nil, 150 }) 151 // deploy native staking contract 152 _, receipt, err := evm.ExecuteContract( 153 ctx, 154 sm, 155 execution, 156 ) 157 if err != nil { 158 return err 159 } 160 if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { 161 return errors.Errorf("error when deploying native staking contract, status=%d", receipt.Status) 162 } 163 sc.SetNativeStakingContract(receipt.ContractAddress) 164 log.L().Info("Deployed native staking contract", zap.String("address", receipt.ContractAddress)) 165 166 return nil 167 } 168 169 func (sc *stakingCommittee) Start(ctx context.Context, sr protocol.StateReader) (interface{}, error) { 170 g := genesis.MustExtractGenesisContext(ctx) 171 if g.NativeStakingContractAddress == "" && g.NativeStakingContractCode != "" { 172 caller, _ := address.FromString(_nativeStakingContractCreator) 173 ethAddr := crypto.CreateAddress(common.BytesToAddress(caller.Bytes()), _nativeStakingContractNonce) 174 iotxAddr, _ := address.FromBytes(ethAddr.Bytes()) 175 sc.SetNativeStakingContract(iotxAddr.String()) 176 log.L().Info("Loaded native staking contract", zap.String("address", iotxAddr.String())) 177 } 178 179 return nil, nil 180 } 181 182 func (sc *stakingCommittee) CreatePreStates(ctx context.Context, sm protocol.StateManager) error { 183 if psc, ok := sc.governanceStaking.(protocol.PreStatesCreator); ok { 184 return psc.CreatePreStates(ctx, sm) 185 } 186 187 return nil 188 } 189 190 func (sc *stakingCommittee) CreatePostSystemActions(ctx context.Context, sr protocol.StateReader) ([]action.Envelope, error) { 191 return createPostSystemActions(ctx, sr, sc) 192 } 193 194 func (sc *stakingCommittee) Handle(ctx context.Context, act action.Action, sm protocol.StateManager) (*action.Receipt, error) { 195 receipt, err := sc.governanceStaking.Handle(ctx, act, sm) 196 if err := sc.persistNativeBuckets(ctx, receipt, err); err != nil { 197 return nil, err 198 } 199 return receipt, err 200 } 201 202 func (sc *stakingCommittee) Validate(ctx context.Context, act action.Action, sr protocol.StateReader) error { 203 return validate(ctx, sr, sc, act) 204 } 205 206 func (sc *stakingCommittee) Name() string { 207 return _protocolID 208 } 209 210 // CalculateCandidatesByHeight calculates delegates with native staking and returns merged list 211 func (sc *stakingCommittee) CalculateCandidatesByHeight(ctx context.Context, sr protocol.StateReader, height uint64) (state.CandidateList, error) { 212 timer := sc.timerFactory.NewTimer("Governance") 213 cand, err := sc.governanceStaking.CalculateCandidatesByHeight(ctx, sr, height) 214 timer.End() 215 if err != nil { 216 return nil, err 217 } 218 219 bcCtx := protocol.MustGetBlockchainCtx(ctx) 220 featureCtx := protocol.MustGetFeatureWithHeightCtx(ctx) 221 rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx)) 222 // convert to epoch start height 223 if !featureCtx.EnableNativeStaking(rp.GetEpochHeight(rp.GetEpochNum(height))) { 224 return sc.filterCandidates(cand), nil 225 } 226 // native staking using contract starts from Cook 227 if sc.nativeStaking == nil { 228 return nil, errors.New("native staking was not set after cook height") 229 } 230 231 // TODO: extract tip info inside of Votes function 232 timer = sc.timerFactory.NewTimer("Native") 233 nativeVotes, err := sc.nativeStaking.Votes(ctx, bcCtx.Tip.Timestamp, featureCtx.StakingCorrectGas(height)) 234 timer.End() 235 if err == ErrNoData { 236 // no native staking data 237 return sc.filterCandidates(cand), nil 238 } 239 if err != nil { 240 return nil, errors.Wrap(err, "failed to get native chain candidates") 241 } 242 sc.currentNativeBuckets = nativeVotes.Buckets 243 244 return sc.mergeCandidates(cand, nativeVotes, bcCtx.Tip.Timestamp), nil 245 } 246 247 func (sc *stakingCommittee) CalculateUnproductiveDelegates( 248 ctx context.Context, 249 sr protocol.StateReader, 250 ) ([]string, error) { 251 return sc.governanceStaking.CalculateUnproductiveDelegates(ctx, sr) 252 } 253 254 func (sc *stakingCommittee) Delegates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) { 255 return sc.governanceStaking.Delegates(ctx, sr) 256 } 257 258 func (sc *stakingCommittee) NextDelegates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) { 259 return sc.governanceStaking.NextDelegates(ctx, sr) 260 } 261 262 func (sc *stakingCommittee) Candidates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) { 263 return sc.governanceStaking.Candidates(ctx, sr) 264 } 265 266 func (sc *stakingCommittee) NextCandidates(ctx context.Context, sr protocol.StateReader) (state.CandidateList, error) { 267 return sc.governanceStaking.NextCandidates(ctx, sr) 268 } 269 270 func (sc *stakingCommittee) ReadState(ctx context.Context, sr protocol.StateReader, method []byte, args ...[]byte) ([]byte, uint64, error) { 271 return sc.governanceStaking.ReadState(ctx, sr, method, args...) 272 } 273 274 // Register registers the protocol with a unique ID 275 func (sc *stakingCommittee) Register(r *protocol.Registry) error { 276 return r.Register(_protocolID, sc) 277 } 278 279 // ForceRegister registers the protocol with a unique ID and force replacing the previous protocol if it exists 280 func (sc *stakingCommittee) ForceRegister(r *protocol.Registry) error { 281 return r.ForceRegister(_protocolID, sc) 282 } 283 284 // SetNativeStakingContract sets the address of native staking contract 285 func (sc *stakingCommittee) SetNativeStakingContract(contract string) { 286 sc.nativeStaking.SetContract(contract) 287 } 288 289 // return candidates whose votes are above threshold 290 func (sc *stakingCommittee) filterCandidates(candidates state.CandidateList) state.CandidateList { 291 var cand state.CandidateList 292 for _, c := range candidates { 293 if c.Votes.Cmp(sc.scoreThreshold) >= 0 { 294 cand = append(cand, c) 295 } 296 } 297 return cand 298 } 299 300 func (sc *stakingCommittee) mergeCandidates(list state.CandidateList, votes *VoteTally, ts time.Time) state.CandidateList { 301 // as of now, native staking does not have register contract, only voting/staking contract 302 // it is assumed that all votes done on native staking target for delegates registered on Ethereum 303 // votes cast to all outside address will not be counted and simply ignored 304 candidates := make(map[string]*state.Candidate) 305 candidateScores := make(map[string]*big.Int) 306 for _, cand := range list { 307 clone := cand.Clone() 308 name := to12Bytes(clone.CanName) 309 if v, ok := votes.Candidates[name]; ok { 310 clone.Votes.Add(clone.Votes, v.Votes) 311 } 312 if clone.Votes.Cmp(sc.scoreThreshold) >= 0 { 313 candidates[hex.EncodeToString(name[:])] = clone 314 candidateScores[hex.EncodeToString(name[:])] = clone.Votes 315 } 316 } 317 sorted := util.Sort(candidateScores, uint64(ts.Unix())) 318 var merged state.CandidateList 319 for _, name := range sorted { 320 merged = append(merged, candidates[name]) 321 } 322 return merged 323 } 324 325 func (sc *stakingCommittee) persistNativeBuckets(ctx context.Context, receipt *action.Receipt, err error) error { 326 // Start to write native buckets archive after cook and only when the action is executed successfully 327 blkCtx := protocol.MustGetBlockCtx(ctx) 328 bcCtx := protocol.MustGetBlockchainCtx(ctx) 329 featureCtx := protocol.MustGetFeatureWithHeightCtx(ctx) 330 rp := rolldpos.MustGetProtocol(protocol.MustGetRegistry(ctx)) 331 epochHeight := rp.GetEpochHeight(rp.GetEpochNum(blkCtx.BlockHeight)) 332 if !featureCtx.EnableNativeStaking(epochHeight) { 333 return nil 334 } 335 if receipt == nil || receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { 336 return nil 337 } 338 log.L().Info("Store native buckets to election db", zap.Int("size", len(sc.currentNativeBuckets))) 339 if err := sc.electionCommittee.PutNativePollByEpoch( 340 rp.GetEpochNum(blkCtx.BlockHeight)+1, // The native buckets recorded in this epoch will be used in next one 341 bcCtx.Tip.Timestamp, // The timestamp of last block is used to represent the current buckets timestamp 342 sc.currentNativeBuckets, 343 ); err != nil { 344 return err 345 } 346 sc.currentNativeBuckets = nil 347 return nil 348 }