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  }