github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/toolbar/vote_reward/settlementvotereward/settlementreward.go (about)

     1  package settlementvotereward
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"math/big"
     7  
     8  	"github.com/jinzhu/gorm"
     9  
    10  	"github.com/bytom/bytom/consensus"
    11  	"github.com/bytom/bytom/errors"
    12  	"github.com/bytom/bytom/toolbar/apinode"
    13  	"github.com/bytom/bytom/toolbar/common"
    14  	"github.com/bytom/bytom/toolbar/vote_reward/config"
    15  )
    16  
    17  var (
    18  	errNotFoundReward = errors.New("No reward found")
    19  	errNotRewardTx    = errors.New("No reward transaction")
    20  )
    21  
    22  type voteResult struct {
    23  	VoteAddress string
    24  	VoteNum     uint64
    25  }
    26  
    27  type SettlementReward struct {
    28  	rewardCfg   *config.RewardConfig
    29  	node        *apinode.Node
    30  	db          *gorm.DB
    31  	rewards     map[string]uint64
    32  	startHeight uint64
    33  	endHeight   uint64
    34  }
    35  
    36  type memo struct {
    37  	StartHeight uint64 `json:"start_height"`
    38  	EndHeight   uint64 `json:"end_height"`
    39  	NodePubkey  string `json:"node_pubkey"`
    40  	RewardRatio uint64 `json:"reward_ratio"`
    41  }
    42  
    43  func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward {
    44  	return &SettlementReward{
    45  		db:          db,
    46  		rewardCfg:   cfg.RewardConf,
    47  		node:        apinode.NewNode(cfg.NodeIP),
    48  		rewards:     make(map[string]uint64),
    49  		startHeight: startHeight,
    50  		endHeight:   endHeight,
    51  	}
    52  }
    53  
    54  func (s *SettlementReward) getVoteResultFromDB(height uint64) (voteResults []*voteResult, err error) {
    55  	query := s.db.Table("utxos").Select("vote_address, sum(vote_num) as vote_num")
    56  	query = query.Where("(veto_height >= ? or veto_height = 0) and vote_height <= ? and xpub = ?", height-consensus.ActiveNetParams.BlocksOfEpoch+1, height-consensus.ActiveNetParams.BlocksOfEpoch, s.rewardCfg.XPub)
    57  	query = query.Group("vote_address")
    58  	if err := query.Scan(&voteResults).Error; err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	return voteResults, nil
    63  }
    64  
    65  func (s *SettlementReward) Settlement() error {
    66  	for height := s.startHeight + consensus.ActiveNetParams.BlocksOfEpoch; height <= s.endHeight; height += consensus.ActiveNetParams.BlocksOfEpoch {
    67  		totalReward, err := s.getCoinbaseReward(height + 1)
    68  		if err == errNotFoundReward {
    69  			continue
    70  		}
    71  
    72  		if err != nil {
    73  			return errors.Wrapf(err, "get total reward at height: %d", height)
    74  		}
    75  
    76  		voteResults, err := s.getVoteResultFromDB(height)
    77  		if err != nil {
    78  			return err
    79  		}
    80  
    81  		s.calcVoterRewards(voteResults, totalReward)
    82  	}
    83  
    84  	if len(s.rewards) == 0 {
    85  		return errNotRewardTx
    86  	}
    87  
    88  	data, err := json.Marshal(&memo{
    89  		StartHeight: s.startHeight,
    90  		EndHeight:   s.endHeight,
    91  		NodePubkey:  s.rewardCfg.XPub,
    92  		RewardRatio: s.rewardCfg.RewardRatio,
    93  	})
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	// send transactions
    99  	return s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards, data)
   100  }
   101  
   102  func (s *SettlementReward) getCoinbaseReward(height uint64) (uint64, error) {
   103  	block, err := s.node.GetBlockByHeight(height)
   104  	if err != nil {
   105  		return 0, err
   106  	}
   107  
   108  	miningControl, err := common.GetControlProgramFromAddress(s.rewardCfg.MiningAddress)
   109  	if err != nil {
   110  		return 0, err
   111  	}
   112  
   113  	for _, output := range block.Transactions[0].Outputs {
   114  		if output.Amount == 0 {
   115  			continue
   116  		}
   117  
   118  		if bytes.Equal(miningControl, output.ControlProgram) {
   119  			amount := big.NewInt(0).SetUint64(output.Amount)
   120  			rewardRatio := big.NewInt(0).SetUint64(s.rewardCfg.RewardRatio)
   121  			amount.Mul(amount, rewardRatio).Div(amount, big.NewInt(100))
   122  
   123  			return amount.Uint64(), nil
   124  		}
   125  	}
   126  	return 0, errNotFoundReward
   127  }
   128  
   129  func (s *SettlementReward) calcVoterRewards(voteResults []*voteResult, totalReward uint64) {
   130  	totalVoteNum := uint64(0)
   131  	for _, voteResult := range voteResults {
   132  		totalVoteNum += voteResult.VoteNum
   133  	}
   134  
   135  	for _, voteResult := range voteResults {
   136  		// voteNum / totalVoteNum  * totalReward
   137  		voteNum := big.NewInt(0).SetUint64(voteResult.VoteNum)
   138  		total := big.NewInt(0).SetUint64(totalVoteNum)
   139  		reward := big.NewInt(0).SetUint64(totalReward)
   140  
   141  		amount := voteNum.Mul(voteNum, reward).Div(voteNum, total).Uint64()
   142  
   143  		if amount != 0 {
   144  			s.rewards[voteResult.VoteAddress] += amount
   145  		}
   146  	}
   147  }