github.com/iotexproject/iotex-core@v1.14.1-rc1/action/protocol/staking/vote_bucket.go (about) 1 // Copyright (c) 2020 IoTeX 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 staking 7 8 import ( 9 "math" 10 "math/big" 11 "time" 12 13 "github.com/iotexproject/iotex-address/address" 14 "github.com/iotexproject/iotex-proto/golang/iotextypes" 15 "github.com/pkg/errors" 16 "google.golang.org/protobuf/proto" 17 "google.golang.org/protobuf/types/known/timestamppb" 18 19 "github.com/iotexproject/iotex-core/action" 20 "github.com/iotexproject/iotex-core/action/protocol/staking/stakingpb" 21 "github.com/iotexproject/iotex-core/blockchain/genesis" 22 "github.com/iotexproject/iotex-core/pkg/util/byteutil" 23 ) 24 25 const ( 26 maxBlockNumber = math.MaxUint64 27 ) 28 29 type ( 30 // VoteBucket represents a vote 31 VoteBucket struct { 32 Index uint64 33 Candidate address.Address 34 Owner address.Address 35 StakedAmount *big.Int 36 StakedDuration time.Duration 37 CreateTime time.Time 38 StakeStartTime time.Time 39 UnstakeStartTime time.Time 40 AutoStake bool 41 ContractAddress string // Corresponding contract address; Empty if it's native staking 42 // only used for contract staking buckets 43 StakedDurationBlockNumber uint64 44 CreateBlockHeight uint64 45 StakeStartBlockHeight uint64 46 UnstakeStartBlockHeight uint64 47 } 48 49 // totalBucketCount stores the total bucket count 50 totalBucketCount struct { 51 count uint64 52 } 53 ) 54 55 // NewVoteBucket creates a new vote bucket 56 func NewVoteBucket(cand, owner address.Address, amount *big.Int, duration uint32, ctime time.Time, autoStake bool) *VoteBucket { 57 return &VoteBucket{ 58 Candidate: cand, 59 Owner: owner, 60 StakedAmount: amount, 61 StakedDuration: time.Duration(duration) * 24 * time.Hour, 62 CreateTime: ctime.UTC(), 63 StakeStartTime: ctime.UTC(), 64 UnstakeStartTime: time.Unix(0, 0).UTC(), 65 AutoStake: autoStake, 66 } 67 } 68 69 // Deserialize deserializes bytes into bucket 70 func (vb *VoteBucket) Deserialize(buf []byte) error { 71 pb := &stakingpb.Bucket{} 72 if err := proto.Unmarshal(buf, pb); err != nil { 73 return errors.Wrap(err, "failed to unmarshal bucket") 74 } 75 76 return vb.fromProto(pb) 77 } 78 79 func (vb *VoteBucket) fromProto(pb *stakingpb.Bucket) error { 80 vote, ok := new(big.Int).SetString(pb.GetStakedAmount(), 10) 81 if !ok { 82 return action.ErrInvalidAmount 83 } 84 85 if vote.Sign() <= 0 { 86 return action.ErrInvalidAmount 87 } 88 89 candAddr, err := address.FromString(pb.GetCandidateAddress()) 90 if err != nil { 91 return err 92 } 93 ownerAddr, err := address.FromString(pb.GetOwner()) 94 if err != nil { 95 return err 96 } 97 98 if err := pb.GetCreateTime().CheckValid(); err != nil { 99 return err 100 } 101 createTime := pb.GetCreateTime().AsTime() 102 103 if err := pb.GetStakeStartTime().CheckValid(); err != nil { 104 return err 105 } 106 stakeTime := pb.GetStakeStartTime().AsTime() 107 108 if err := pb.GetUnstakeStartTime().CheckValid(); err != nil { 109 return err 110 } 111 unstakeTime := pb.GetUnstakeStartTime().AsTime() 112 113 vb.Index = pb.GetIndex() 114 vb.Candidate = candAddr 115 vb.Owner = ownerAddr 116 vb.StakedAmount = vote 117 vb.StakedDuration = time.Duration(pb.GetStakedDuration()) * 24 * time.Hour 118 vb.CreateTime = createTime 119 vb.StakeStartTime = stakeTime 120 vb.UnstakeStartTime = unstakeTime 121 vb.AutoStake = pb.GetAutoStake() 122 vb.ContractAddress = pb.GetContractAddress() 123 vb.StakedDurationBlockNumber = pb.GetStakedDurationBlockNumber() 124 vb.CreateBlockHeight = pb.GetCreateBlockHeight() 125 vb.StakeStartBlockHeight = pb.GetStakeStartBlockHeight() 126 vb.UnstakeStartBlockHeight = pb.GetUnstakeStartBlockHeight() 127 return nil 128 } 129 130 func (vb *VoteBucket) toProto() (*stakingpb.Bucket, error) { 131 if vb.Candidate == nil || vb.Owner == nil || vb.StakedAmount == nil { 132 return nil, ErrMissingField 133 } 134 createTime := timestamppb.New(vb.CreateTime) 135 stakeTime := timestamppb.New(vb.StakeStartTime) 136 unstakeTime := timestamppb.New(vb.UnstakeStartTime) 137 138 return &stakingpb.Bucket{ 139 Index: vb.Index, 140 CandidateAddress: vb.Candidate.String(), 141 Owner: vb.Owner.String(), 142 StakedAmount: vb.StakedAmount.String(), 143 StakedDuration: uint32(vb.StakedDuration / 24 / time.Hour), 144 CreateTime: createTime, 145 StakeStartTime: stakeTime, 146 UnstakeStartTime: unstakeTime, 147 AutoStake: vb.AutoStake, 148 ContractAddress: vb.ContractAddress, 149 StakedDurationBlockNumber: vb.StakedDurationBlockNumber, 150 CreateBlockHeight: vb.CreateBlockHeight, 151 StakeStartBlockHeight: vb.StakeStartBlockHeight, 152 UnstakeStartBlockHeight: vb.UnstakeStartBlockHeight, 153 }, nil 154 } 155 156 func (vb *VoteBucket) toIoTeXTypes() (*iotextypes.VoteBucket, error) { 157 createTime := timestamppb.New(vb.CreateTime) 158 stakeTime := timestamppb.New(vb.StakeStartTime) 159 unstakeTime := timestamppb.New(vb.UnstakeStartTime) 160 161 return &iotextypes.VoteBucket{ 162 Index: vb.Index, 163 CandidateAddress: vb.Candidate.String(), 164 Owner: vb.Owner.String(), 165 StakedAmount: vb.StakedAmount.String(), 166 StakedDuration: uint32(vb.StakedDuration / 24 / time.Hour), 167 CreateTime: createTime, 168 StakeStartTime: stakeTime, 169 UnstakeStartTime: unstakeTime, 170 AutoStake: vb.AutoStake, 171 ContractAddress: vb.ContractAddress, 172 StakedDurationBlockNumber: vb.StakedDurationBlockNumber, 173 CreateBlockHeight: vb.CreateBlockHeight, 174 StakeStartBlockHeight: vb.StakeStartBlockHeight, 175 UnstakeStartBlockHeight: vb.UnstakeStartBlockHeight, 176 }, nil 177 } 178 179 // Serialize serializes bucket into bytes 180 func (vb *VoteBucket) Serialize() ([]byte, error) { 181 pb, err := vb.toProto() 182 if err != nil { 183 return nil, err 184 } 185 return proto.Marshal(pb) 186 } 187 188 func (vb *VoteBucket) isUnstaked() bool { 189 if vb.isNative() { 190 return vb.UnstakeStartTime.After(vb.StakeStartTime) 191 } 192 return vb.UnstakeStartBlockHeight < maxBlockNumber 193 } 194 195 func (vb *VoteBucket) isNative() bool { 196 return vb.ContractAddress == "" 197 } 198 199 // Deserialize deserializes bytes into bucket count 200 func (tc *totalBucketCount) Deserialize(data []byte) error { 201 tc.count = byteutil.BytesToUint64BigEndian(data) 202 return nil 203 } 204 205 // Serialize serializes bucket count into bytes 206 func (tc *totalBucketCount) Serialize() ([]byte, error) { 207 data := byteutil.Uint64ToBytesBigEndian(tc.count) 208 return data, nil 209 } 210 211 func (tc *totalBucketCount) Count() uint64 { 212 return tc.count 213 } 214 215 func bucketKey(index uint64) []byte { 216 key := []byte{_bucket} 217 return append(key, byteutil.Uint64ToBytesBigEndian(index)...) 218 } 219 220 // CalculateVoteWeight calculates the vote weight 221 func CalculateVoteWeight(c genesis.VoteWeightCalConsts, v *VoteBucket, selfStake bool) *big.Int { 222 remainingTime := v.StakedDuration.Seconds() 223 weight := float64(1) 224 var m float64 225 if v.AutoStake { 226 m = c.AutoStake 227 } 228 if remainingTime > 0 { 229 weight += math.Log(math.Ceil(remainingTime/86400)*(1+m)) / math.Log(c.DurationLg) / 100 230 } 231 if selfStake && v.AutoStake && v.StakedDuration >= time.Duration(91)*24*time.Hour { 232 // self-stake extra bonus requires enable auto-stake for at least 3 months 233 weight *= c.SelfStake 234 } 235 236 amount := new(big.Float).SetInt(v.StakedAmount) 237 weightedAmount, _ := amount.Mul(amount, big.NewFloat(weight)).Int(nil) 238 return weightedAmount 239 }