[staking][reward] Give out block reward for cross links (#1869)

* [staking][reward] Give out block reward for cross links

* [staking][reward] Wrap reward logic based on epoch

* [votepower] Factor out votepower b/c need to compute elsewhere as well

* [reward][votepower] Award beaconchain committee members their percent due

* [reward] Only reward non-harmony slots

* [reward] Factor out only signers by commit bitmap

* [reward][votepower] Fix leftover merge based naming changes, fix nil pointer map

* [reward] Handle cross link payment succicently

* [reward] Remove unnecessary helper

* [reward] Handle legacy case of one-node-one-vote block reward (pre-staking)

* [reward] Incorrect log of reward

* [reward] Adjust back to big.Int for legacy rewarding

* [reward] Add +1 for i nonce

* [reward] Abandon formula for nonce, sizes not reliable, easier to do explicit bucketing

* [reward] Remove Prints, fix mistake on using wrong header

* [quorum] Adjust log print out for IsQuorumAchieved

* [reward] Redundant check in code path that only runs in >= staking-epoch

* [reward] Fix mistake on staked block undoing effect of voting power-sharing
pull/1874/head
Edgar Aroutiounian 5 years ago committed by GitHub
parent 98eee91ecc
commit 1cf258ee06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      consensus/engine/consensus_engine.go
  2. 8
      consensus/quorum/one-node-one-vote.go
  3. 193
      consensus/quorum/one-node-staked-vote.go
  4. 8
      consensus/quorum/quorum.go
  5. 7
      consensus/reward/rewarder.go
  6. 93
      consensus/votepower/roster.go
  7. 16
      internal/chain/engine.go
  8. 300
      internal/chain/reward.go
  9. 8
      node/node.go

@ -94,13 +94,21 @@ type Engine interface {
// SetSlasher assigns the slasher used // SetSlasher assigns the slasher used
SetSlasher(slash.Slasher) SetSlasher(slash.Slasher)
// Beaconchain provides the handle for Beaconchain
Beaconchain() ChainReader
// SetBeaconchain sets the beaconchain handler on engine
SetBeaconchain(ChainReader)
// Finalize runs any post-transaction state modifications (e.g. block rewards) // Finalize runs any post-transaction state modifications (e.g. block rewards)
// and assembles the final block. // and assembles the final block.
// Note: The block header and state database might be updated to reflect any // Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards). // consensus rules that happen at finalization (e.g. block rewards).
Finalize(chain ChainReader, header *block.Header, state *state.DB, txs []*types.Transaction, Finalize(chain ChainReader, header *block.Header,
state *state.DB, txs []*types.Transaction,
receipts []*types.Receipt, outcxs []*types.CXReceipt, receipts []*types.Receipt, outcxs []*types.CXReceipt,
incxs []*types.CXReceiptsProof, stks []*staking.StakingTransaction) (*types.Block, error) incxs []*types.CXReceiptsProof, stks []*staking.StakingTransaction,
) (*types.Block, error)
// Seal generates a new sealing request for the given input block and pushes // Seal generates a new sealing request for the given input block and pushes
// the result into the given channel. // the result into the given channel.

@ -27,7 +27,7 @@ func (v *uniformVoteWeight) IsQuorumAchieved(p Phase) bool {
r := v.SignersCount(p) >= v.TwoThirdsSignersCount() r := v.SignersCount(p) >= v.TwoThirdsSignersCount()
utils.Logger().Info().Str("phase", p.String()). utils.Logger().Info().Str("phase", p.String()).
Int64("signers-count", v.SignersCount(p)). Int64("signers-count", v.SignersCount(p)).
Int64("threshold", v.QuorumThreshold().Int64()). Int64("threshold", v.TwoThirdsSignersCount()).
Int64("participants", v.ParticipantsCount()). Int64("participants", v.ParticipantsCount()).
Msg("Quorum details") Msg("Quorum details")
return r return r
@ -60,7 +60,9 @@ func (v *uniformVoteWeight) ToggleActive(*bls.PublicKey) bool {
func (v *uniformVoteWeight) Award( func (v *uniformVoteWeight) Award(
// Here hook is the callback which gets the amount the earner is due in just reward // Here hook is the callback which gets the amount the earner is due in just reward
// up to the hook to do side-effects like write the statedb // up to the hook to do side-effects like write the statedb
Pie *big.Int, earners []common.Address, hook func(earner common.Address, due *big.Int), Pie *big.Int,
earners shard.SlotList,
hook func(earner common.Address, due *big.Int),
) *big.Int { ) *big.Int {
payout := big.NewInt(0) payout := big.NewInt(0)
last := big.NewInt(0) last := big.NewInt(0)
@ -70,7 +72,7 @@ func (v *uniformVoteWeight) Award(
cur := big.NewInt(0) cur := big.NewInt(0)
cur.Mul(Pie, big.NewInt(int64(i+1))).Div(cur, count) cur.Mul(Pie, big.NewInt(int64(i+1))).Div(cur, count)
diff := big.NewInt(0).Sub(cur, last) diff := big.NewInt(0).Sub(cur, last)
hook(common.Address(account), diff) hook(common.Address(account.EcdsaAddress), diff)
payout = big.NewInt(0).Add(payout, diff) payout = big.NewInt(0).Add(payout, diff)
last = cur last = cur
} }

@ -2,10 +2,9 @@ package quorum
import ( import (
"encoding/json" "encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/consensus/votepower"
"github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
@ -16,8 +15,6 @@ import (
var ( var (
twoThird = numeric.NewDec(2).Quo(numeric.NewDec(3)) twoThird = numeric.NewDec(2).Quo(numeric.NewDec(3))
ninetyPercent = numeric.MustNewDecFromStr("0.90") ninetyPercent = numeric.MustNewDecFromStr("0.90")
harmonysShare = numeric.MustNewDecFromStr("0.68")
stakersShare = numeric.MustNewDecFromStr("0.32")
totalShare = numeric.MustNewDecFromStr("1.00") totalShare = numeric.MustNewDecFromStr("1.00")
) )
@ -31,23 +28,12 @@ type TallyResult struct {
theirPercent numeric.Dec theirPercent numeric.Dec
} }
type stakedVoter struct {
isActive, isHarmonyNode bool
earningAccount common.Address
effectivePercent numeric.Dec
rawStake numeric.Dec
}
type stakedVoteWeight struct { type stakedVoteWeight struct {
SignatureReader SignatureReader
DependencyInjectionWriter DependencyInjectionWriter
DependencyInjectionReader DependencyInjectionReader
slash.ThresholdDecider slash.ThresholdDecider
voters map[shard.BlsPublicKey]stakedVoter roster votepower.Roster
ourVotingPowerTotal numeric.Dec
theirVotingPowerTotal numeric.Dec
stakedTotal numeric.Dec
hmySlotCount int64
} }
// Policy .. // Policy ..
@ -58,7 +44,14 @@ func (v *stakedVoteWeight) Policy() Policy {
// IsQuorumAchieved .. // IsQuorumAchieved ..
func (v *stakedVoteWeight) IsQuorumAchieved(p Phase) bool { func (v *stakedVoteWeight) IsQuorumAchieved(p Phase) bool {
t := v.QuorumThreshold() t := v.QuorumThreshold()
currentTotalPower := v.computeCurrentTotalPower(p) currentTotalPower, err := v.computeCurrentTotalPower(p)
if err != nil {
utils.Logger().Error().
AnErr("bls error", err).
Msg("Failure in attempt bls-key reading")
return false
}
utils.Logger().Info(). utils.Logger().Info().
Str("policy", v.Policy().String()). Str("policy", v.Policy().String()).
@ -69,27 +62,24 @@ func (v *stakedVoteWeight) IsQuorumAchieved(p Phase) bool {
return currentTotalPower.GT(t) return currentTotalPower.GT(t)
} }
func (v *stakedVoteWeight) computeCurrentTotalPower(p Phase) numeric.Dec { func (v *stakedVoteWeight) computeCurrentTotalPower(p Phase) (*numeric.Dec, error) {
w := shard.BlsPublicKey{} w := shard.BlsPublicKey{}
members := v.Participants() members := v.Participants()
currentTotalPower := numeric.ZeroDec() currentTotalPower := numeric.ZeroDec()
for i := range members { for i := range members {
if v.ReadSignature(p, members[i]) != nil { if v.ReadSignature(p, members[i]) != nil {
w.FromLibBLSPublicKey(members[i]) err := w.FromLibBLSPublicKey(members[i])
if err != nil {
voter, ok := v.voters[w] return nil, err
if !ok {
utils.Logger().Error().Bytes("BlsPubKey", w[:]).Msg("No voter found")
return numeric.ZeroDec()
} }
currentTotalPower = currentTotalPower.Add( currentTotalPower = currentTotalPower.Add(
voter.effectivePercent, v.roster.Voters[w].EffectivePercent,
) )
} }
} }
return currentTotalPower return &currentTotalPower, nil
} }
// QuorumThreshold .. // QuorumThreshold ..
@ -99,43 +89,52 @@ func (v *stakedVoteWeight) QuorumThreshold() numeric.Dec {
// RewardThreshold .. // RewardThreshold ..
func (v *stakedVoteWeight) IsRewardThresholdAchieved() bool { func (v *stakedVoteWeight) IsRewardThresholdAchieved() bool {
return v.computeCurrentTotalPower(Commit).GTE(ninetyPercent) reached, err := v.computeCurrentTotalPower(Commit)
if err != nil {
utils.Logger().Error().
AnErr("bls error", err).
Msg("Failure in attempt bls-key reading")
return false
}
return reached.GTE(ninetyPercent)
} }
// Award .. // Award ..
func (v *stakedVoteWeight) Award( // func (v *stakedVoteWeight) Award(
Pie *big.Int, earners []common.Address, hook func(earner common.Address, due *big.Int), // Pie numeric.Dec,
) *big.Int { // earners []common.Address,
payout := big.NewInt(0) // hook func(earner common.Address, due *big.Int),
last := big.NewInt(0) // ) numeric.Dec {
count := big.NewInt(int64(len(earners))) // payout := big.NewInt(0)
// proportional := map[common.Address]numeric.Dec{} // last := big.NewInt(0)
// count := big.NewInt(int64(len(earners)))
for _, voter := range v.voters { // // proportional := map[common.Address]numeric.Dec{}
if voter.isHarmonyNode == false {
// proportional[details.earningAccount] = details.effective.QuoTruncate( // for _, voter := range v.voters {
// v.stakedTotal, // if voter.isHarmonyNode == false {
// ) // // proportional[details.earningAccount] = details.effective.QuoTruncate(
} // // v.stakedTotal,
} // // )
// TODO Finish implementing this logic w/Chao // }
// }
// // TODO Finish implementing this logic w/Chao
for i := range earners { // for i := range earners {
cur := big.NewInt(0) // cur := big.NewInt(0)
cur.Mul(Pie, big.NewInt(int64(i+1))).Div(cur, count) // cur.Mul(Pie, big.NewInt(int64(i+1))).Div(cur, count)
diff := big.NewInt(0).Sub(cur, last) // diff := big.NewInt(0).Sub(cur, last)
// hook(common.Address(account), diff) // // hook(common.Address(account), diff)
payout = big.NewInt(0).Add(payout, diff) // payout = big.NewInt(0).Add(payout, diff)
last = cur // last = cur
} // }
return payout // return payout
} // }
var ( var (
errSumOfVotingPowerNotOne = errors.New("sum of total votes do not sum to 100 percent") errSumOfVotingPowerNotOne = errors.New("sum of total votes do not sum to 100 percent")
@ -148,76 +147,37 @@ func (v *stakedVoteWeight) SetVoters(
staked shard.SlotList, staked shard.SlotList,
) (*TallyResult, error) { ) (*TallyResult, error) {
s, _ := v.ShardIDProvider()() s, _ := v.ShardIDProvider()()
v.voters = map[shard.BlsPublicKey]stakedVoter{}
v.Reset([]Phase{Prepare, Commit, ViewChange}) v.Reset([]Phase{Prepare, Commit, ViewChange})
v.hmySlotCount = 0
v.stakedTotal = numeric.ZeroDec()
for i := range staked {
if staked[i].TotalStake == nil {
v.hmySlotCount++
} else {
v.stakedTotal = v.stakedTotal.Add(*staked[i].TotalStake)
}
}
ourCount := numeric.NewDec(v.hmySlotCount)
ourPercentage := numeric.ZeroDec()
theirPercentage := numeric.ZeroDec()
totalStakedPercent := numeric.ZeroDec()
for i := range staked {
member := stakedVoter{
isActive: true,
isHarmonyNode: true,
earningAccount: staked[i].EcdsaAddress,
effectivePercent: numeric.ZeroDec(),
}
// Real Staker
if staked[i].TotalStake != nil {
member.isHarmonyNode = false
member.effectivePercent = staked[i].TotalStake.
Quo(v.stakedTotal).
Mul(stakersShare)
theirPercentage = theirPercentage.Add(member.effectivePercent)
} else { // Our node
member.effectivePercent = harmonysShare.Quo(ourCount)
ourPercentage = ourPercentage.Add(member.effectivePercent)
}
totalStakedPercent = totalStakedPercent.Add(member.effectivePercent)
v.voters[staked[i].BlsPublicKey] = member
}
roster := votepower.Compute(staked)
utils.Logger().Info(). utils.Logger().Info().
Str("our-percentage", ourPercentage.String()). Str("our-percentage", roster.OurVotingPowerTotalPercentage.String()).
Str("their-percentage", theirPercentage.String()). Str("their-percentage", roster.TheirVotingPowerTotalPercentage.String()).
Uint32("on-shard", s). Uint32("on-shard", s).
Str("Raw-Staked", v.stakedTotal.String()). Str("Raw-Staked", roster.RawStakedTotal.String()).
Msg("Total staked") Msg("Total staked")
//switch { //switch {
//case totalStakedPercent.Equal(totalShare) == false: //case roster.totalStakedPercent.Equal(totalShare) == false:
// return nil, errSumOfVotingPowerNotOne // return nil, errSumOfVotingPowerNotOne
//case ourPercentage.Add(theirPercentage).Equal(totalShare) == false: //case roster.ourPercentage.Add(theirPercentage).Equal(totalShare) == false:
// return nil, errSumOfOursAndTheirsNotOne // return nil, errSumOfOursAndTheirsNotOne
//} //}
// Hold onto this calculation // Hold onto this calculation
v.ourVotingPowerTotal = ourPercentage v.roster = *roster
v.theirVotingPowerTotal = theirPercentage return &TallyResult{
roster.OurVotingPowerTotalPercentage, roster.TheirVotingPowerTotalPercentage,
return &TallyResult{ourPercentage, theirPercentage}, nil }, nil
} }
func (v *stakedVoteWeight) ToggleActive(k *bls.PublicKey) bool { func (v *stakedVoteWeight) ToggleActive(k *bls.PublicKey) bool {
w := shard.BlsPublicKey{} w := shard.BlsPublicKey{}
w.FromLibBLSPublicKey(k) w.FromLibBLSPublicKey(k)
g := v.voters[w] g := v.roster.Voters[w]
g.isActive = !g.isActive g.IsActive = !g.IsActive
v.voters[w] = g v.roster.Voters[w] = g
return v.voters[w].isActive return v.roster.Voters[w].IsActive
} }
func (v *stakedVoteWeight) ShouldSlash(key shard.BlsPublicKey) bool { func (v *stakedVoteWeight) ShouldSlash(key shard.BlsPublicKey) bool {
@ -232,6 +192,7 @@ func (v *stakedVoteWeight) ShouldSlash(key shard.BlsPublicKey) bool {
func (v *stakedVoteWeight) JSON() string { func (v *stakedVoteWeight) JSON() string {
s, _ := v.ShardIDProvider()() s, _ := v.ShardIDProvider()()
voterCount := len(v.roster.Voters)
type u struct { type u struct {
IsHarmony bool `json:"is-harmony-slot"` IsHarmony bool `json:"is-harmony-slot"`
@ -250,18 +211,18 @@ func (v *stakedVoteWeight) JSON() string {
TotalStaked string `json:"total-raw-staked"` TotalStaked string `json:"total-raw-staked"`
} }
parts := make([]u, len(v.voters)) parts := make([]u, voterCount)
i := 0 i := 0
for identity, voter := range v.voters { for identity, voter := range v.roster.Voters {
member := u{ member := u{
voter.isHarmonyNode, voter.IsHarmonyNode,
identity.Hex(), identity.Hex(),
voter.effectivePercent.String(), voter.EffectivePercent.String(),
"", "",
} }
if !voter.isHarmonyNode { if !voter.IsHarmonyNode {
member.RawStake = voter.rawStake.String() member.RawStake = voter.RawStake.String()
} }
parts[i] = member parts[i] = member
i++ i++
@ -270,11 +231,11 @@ func (v *stakedVoteWeight) JSON() string {
b1, _ := json.Marshal(t{ b1, _ := json.Marshal(t{
v.Policy().String(), v.Policy().String(),
s, s,
len(v.voters), voterCount,
parts, parts,
v.ourVotingPowerTotal.String(), v.roster.OurVotingPowerTotalPercentage.String(),
v.theirVotingPowerTotal.String(), v.roster.TheirVotingPowerTotalPercentage.String(),
v.stakedTotal.String(), v.roster.RawStakedTotal.String(),
}) })
return string(b1) return string(b1)
} }

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/consensus/votepower"
"github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/slash" "github.com/harmony-one/harmony/staking/slash"
@ -296,16 +297,13 @@ func NewDecider(p Policy) Decider {
c.DependencyInjectionWriter, c.DependencyInjectionReader, c, c.DependencyInjectionWriter, c.DependencyInjectionReader, c,
} }
case SuperMajorityStake: case SuperMajorityStake:
roster := votepower.NewRoster()
return &stakedVoteWeight{ return &stakedVoteWeight{
c.SignatureReader, c.SignatureReader,
c.DependencyInjectionWriter, c.DependencyInjectionWriter,
c.DependencyInjectionWriter.(DependencyInjectionReader), c.DependencyInjectionWriter.(DependencyInjectionReader),
c.SignatureReader.(slash.ThresholdDecider), c.SignatureReader.(slash.ThresholdDecider),
map[shard.BlsPublicKey]stakedVoter{}, *roster,
numeric.ZeroDec(),
numeric.ZeroDec(),
numeric.ZeroDec(),
0,
} }
default: default:
// Should not be possible // Should not be possible

@ -4,13 +4,14 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/shard"
) )
// Distributor .. // Distributor ..
type Distributor interface { type Distributor interface {
Award( Award(
Pie *big.Int, pie *big.Int,
earners []common.Address, earners shard.SlotList,
hook func(earner common.Address, due *big.Int), hook func(earner common.Address, due *big.Int),
) (payout *big.Int) ) *big.Int
} }

@ -0,0 +1,93 @@
package votepower
import (
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
)
var (
// HarmonysShare ..
HarmonysShare = numeric.MustNewDecFromStr("0.68")
// StakersShare ..
StakersShare = numeric.MustNewDecFromStr("0.32")
)
type stakedVoter struct {
IsActive, IsHarmonyNode bool
EarningAccount common.Address
EffectivePercent numeric.Dec
RawStake numeric.Dec
}
// Roster ..
type Roster struct {
Voters map[shard.BlsPublicKey]stakedVoter
OurVotingPowerTotalPercentage numeric.Dec
TheirVotingPowerTotalPercentage numeric.Dec
RawStakedTotal numeric.Dec
HmySlotCount int64
}
// Compute ..
func Compute(staked shard.SlotList) *Roster {
roster := NewRoster()
for i := range staked {
if staked[i].TotalStake == nil {
roster.HmySlotCount++
} else {
roster.RawStakedTotal = roster.RawStakedTotal.Add(
*staked[i].TotalStake,
)
}
}
ourCount := numeric.NewDec(roster.HmySlotCount)
ourPercentage := numeric.ZeroDec()
theirPercentage := numeric.ZeroDec()
totalStakedPercent := numeric.ZeroDec()
for i := range staked {
member := stakedVoter{
IsActive: true,
IsHarmonyNode: true,
EarningAccount: staked[i].EcdsaAddress,
EffectivePercent: numeric.ZeroDec(),
}
// Real Staker
if staked[i].TotalStake != nil {
member.IsHarmonyNode = false
member.EffectivePercent = staked[i].TotalStake.
Quo(roster.RawStakedTotal).
Mul(StakersShare)
theirPercentage = theirPercentage.Add(member.EffectivePercent)
} else { // Our node
// TODO See the todo on where this called in one-node-staked-vote,
// need to have these two values of our
// percentage and hmy percentage sum to 1
member.EffectivePercent = HarmonysShare.Quo(ourCount)
ourPercentage = ourPercentage.Add(member.EffectivePercent)
}
totalStakedPercent = totalStakedPercent.Add(member.EffectivePercent)
roster.Voters[staked[i].BlsPublicKey] = member
}
roster.OurVotingPowerTotalPercentage = ourPercentage
roster.TheirVotingPowerTotalPercentage = theirPercentage
return roster
}
// NewRoster ..
func NewRoster() *Roster {
return &Roster{
map[shard.BlsPublicKey]stakedVoter{},
numeric.ZeroDec(),
numeric.ZeroDec(),
numeric.ZeroDec(),
0,
}
}

@ -25,10 +25,11 @@ import (
type engineImpl struct { type engineImpl struct {
d reward.Distributor d reward.Distributor
s slash.Slasher s slash.Slasher
beacon engine.ChainReader
} }
// Engine is an algorithm-agnostic consensus engine. // Engine is an algorithm-agnostic consensus engine.
var Engine = &engineImpl{nil, nil} var Engine = &engineImpl{nil, nil, nil}
// Rewarder handles the distribution of block rewards // Rewarder handles the distribution of block rewards
func (e *engineImpl) Rewarder() reward.Distributor { func (e *engineImpl) Rewarder() reward.Distributor {
@ -50,6 +51,15 @@ func (e *engineImpl) SetSlasher(s slash.Slasher) {
e.s = s e.s = s
} }
func (e *engineImpl) Beaconchain() engine.ChainReader {
return e.beacon
}
// SetSlasher assigns the slasher used
func (e *engineImpl) SetBeaconchain(beaconchain engine.ChainReader) {
e.beacon = beaconchain
}
// SealHash returns the hash of a block prior to it being sealed. // SealHash returns the hash of a block prior to it being sealed.
func (e *engineImpl) SealHash(header *block.Header) (hash common.Hash) { func (e *engineImpl) SealHash(header *block.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256() hasher := sha3.NewLegacyKeccak256()
@ -182,14 +192,16 @@ func (e *engineImpl) Finalize(
receipts []*types.Receipt, outcxs []*types.CXReceipt, receipts []*types.Receipt, outcxs []*types.CXReceipt,
incxs []*types.CXReceiptsProof, stks []*staking.StakingTransaction, incxs []*types.CXReceiptsProof, stks []*staking.StakingTransaction,
) (*types.Block, error) { ) (*types.Block, error) {
// Accumulate any block and uncle rewards and commit the final state root // Accumulate any block and uncle rewards and commit the final state root
// Header seems complete, assemble into a block and return // Header seems complete, assemble into a block and return
if err := AccumulateRewards( if err := AccumulateRewards(
chain, state, header, e.Rewarder(), e.Slasher(), chain, state, header, e.Rewarder(), e.Slasher(), e.Beaconchain(),
); err != nil { ); err != nil {
return nil, ctxerror.New("cannot pay block reward").WithCause(err) return nil, ctxerror.New("cannot pay block reward").WithCause(err)
} }
// TODO Shouldnt this logic only apply to beaconchain, right?
// Withdraw unlocked tokens to the delegators' accounts // Withdraw unlocked tokens to the delegators' accounts
// Only do such at the last block of an epoch // Only do such at the last block of an epoch
if len(header.ShardState()) > 0 { if len(header.ShardState()) > 0 {

@ -2,19 +2,24 @@ package chain
import ( import (
"math/big" "math/big"
"sort"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
"github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/block" "github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/common/denominations" "github.com/harmony-one/harmony/common/denominations"
"github.com/harmony-one/harmony/consensus/engine" "github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/consensus/reward" "github.com/harmony-one/harmony/consensus/reward"
"github.com/harmony-one/harmony/consensus/votepower"
"github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types"
bls2 "github.com/harmony-one/harmony/crypto/bls" bls2 "github.com/harmony-one/harmony/crypto/bls"
common2 "github.com/harmony-one/harmony/internal/common" common2 "github.com/harmony-one/harmony/internal/common"
"github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/slash" "github.com/harmony-one/harmony/staking/slash"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -23,96 +28,253 @@ import (
var ( var (
// BlockReward is the block reward, to be split evenly among block signers. // BlockReward is the block reward, to be split evenly among block signers.
BlockReward = new(big.Int).Mul(big.NewInt(24), big.NewInt(denominations.One)) BlockReward = new(big.Int).Mul(big.NewInt(24), big.NewInt(denominations.One))
// BlockRewardStakedCase is the baseline block reward in staked case -
BlockRewardStakedCase = numeric.NewDecFromBigInt(new(big.Int).Mul(
big.NewInt(18), big.NewInt(denominations.One),
))
errPayoutNotEqualBlockReward = errors.New("total payout not equal to blockreward") errPayoutNotEqualBlockReward = errors.New("total payout not equal to blockreward")
) )
// AccumulateRewards credits the coinbase of the given block with the mining func blockSigners(
// reward. The total reward consists of the static block reward and rewards for header *block.Header, parentCommittee *shard.Committee,
// included uncles. The coinbase of each uncle block is also rewarded. ) (shard.SlotList, shard.SlotList, error) {
func AccumulateRewards( committerKeys := []*bls.PublicKey{}
bc engine.ChainReader, state *state.DB,
header *block.Header, rewarder reward.Distributor,
slasher slash.Slasher,
) error {
blockNum := header.Number().Uint64()
if blockNum == 0 {
// Epoch block has no parent to reward.
return nil
}
// TODO ek – retrieving by parent number (blockNum - 1) doesn't work,
// while it is okay with hash. Sounds like DB inconsistency.
// Figure out why.
parentHeader := bc.GetHeaderByHash(header.ParentHash())
if parentHeader == nil {
return ctxerror.New("cannot find parent block header in DB",
"parentHash", header.ParentHash())
}
if parentHeader.Number().Cmp(common.Big0) == 0 {
// Parent is an epoch block,
// which is not signed in the usual manner therefore rewards nothing.
return nil
}
parentShardState, err := bc.ReadShardState(parentHeader.Epoch())
if err != nil {
return ctxerror.New("cannot read shard state",
"epoch", parentHeader.Epoch(),
).WithCause(err)
}
parentCommittee := parentShardState.FindCommitteeByID(parentHeader.ShardID())
if parentCommittee == nil {
return ctxerror.New("cannot find shard in the shard state",
"parentBlockNumber", parentHeader.Number(),
"shardID", parentHeader.ShardID(),
)
}
var committerKeys []*bls.PublicKey
for _, member := range parentCommittee.Slots { for _, member := range parentCommittee.Slots {
committerKey := new(bls.PublicKey) committerKey := new(bls.PublicKey)
err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey) err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey)
if err != nil { if err != nil {
return ctxerror.New("cannot convert BLS public key", return nil, nil, ctxerror.New(
"blsPublicKey", member.BlsPublicKey).WithCause(err) "cannot convert BLS public key",
"blsPublicKey",
member.BlsPublicKey,
).WithCause(err)
} }
committerKeys = append(committerKeys, committerKey) committerKeys = append(committerKeys, committerKey)
} }
mask, err := bls2.NewMask(committerKeys, nil) mask, err := bls2.NewMask(committerKeys, nil)
if err != nil { if err != nil {
return ctxerror.New("cannot create group sig mask").WithCause(err) return nil, nil, ctxerror.New(
"cannot create group sig mask",
).WithCause(err)
} }
if err := mask.SetMask(header.LastCommitBitmap()); err != nil { if err := mask.SetMask(header.LastCommitBitmap()); err != nil {
return ctxerror.New("cannot set group sig mask bits").WithCause(err) return nil, nil, ctxerror.New(
"cannot set group sig mask bits",
).WithCause(err)
} }
accounts := []common.Address{} payable, missing := shard.SlotList{}, shard.SlotList{}
missing := shard.SlotList{}
for idx, member := range parentCommittee.Slots { for idx, member := range parentCommittee.Slots {
switch signed, err := mask.IndexEnabled(idx); true { switch signed, err := mask.IndexEnabled(idx); true {
case err != nil: case err != nil:
return ctxerror.New("cannot check for committer bit", return nil, nil, ctxerror.New("cannot check for committer bit",
"committerIndex", idx, "committerIndex", idx,
).WithCause(err) ).WithCause(err)
case signed: case signed:
accounts = append(accounts, member.EcdsaAddress) payable = append(payable, member)
default: default:
missing = append(missing, member) missing = append(missing, member)
} }
} }
return payable, missing, nil
}
func ballotResult(
bc engine.ChainReader, header *block.Header, shardID uint32,
) (shard.SlotList, shard.SlotList, shard.SlotList, error) {
// TODO ek – retrieving by parent number (blockNum - 1) doesn't work,
// while it is okay with hash. Sounds like DB inconsistency.
// Figure out why.
parentHeader := bc.GetHeaderByHash(header.ParentHash())
if parentHeader == nil {
return nil, nil, nil, ctxerror.New(
"cannot find parent block header in DB",
"parentHash", header.ParentHash(),
)
}
parentShardState, err := bc.ReadShardState(parentHeader.Epoch())
if err != nil {
return nil, nil, nil, ctxerror.New(
"cannot read shard state", "epoch", parentHeader.Epoch(),
).WithCause(err)
}
parentCommittee := parentShardState.FindCommitteeByID(shardID)
if parentCommittee == nil {
return nil, nil, nil, ctxerror.New(
"cannot find shard in the shard state",
"parentBlockNumber", parentHeader.Number(),
"shardID", parentHeader.ShardID(),
)
}
payable, missing, err := blockSigners(header, parentCommittee)
return parentCommittee.Slots, payable, missing, err
}
func ballotResultBeaconchain(
bc engine.ChainReader, header *block.Header,
) (shard.SlotList, shard.SlotList, shard.SlotList, error) {
return ballotResult(bc, header, shard.BeaconChainShardID)
}
// AccumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func AccumulateRewards(
bc engine.ChainReader, state *state.DB, header *block.Header,
rewarder reward.Distributor, slasher slash.Slasher,
beaconChain engine.ChainReader,
) error {
blockNum := header.Number().Uint64()
if blockNum == 0 {
// genesis block has no parent to reward.
return nil
}
if bc.Config().IsStaking(header.Epoch()) &&
bc.CurrentHeader().ShardID() != shard.BeaconChainShardID {
return nil
}
if bc.Config().IsStaking(header.Epoch()) &&
bc.CurrentHeader().ShardID() == shard.BeaconChainShardID {
// Take care of my own beacon chain committee, _ is missing, for slashing
members, payable, _, err := ballotResultBeaconchain(beaconChain, header)
if err != nil {
return err
}
votingPower := votepower.Compute(members)
for beaconMember := range payable {
// TODO Give out whatever leftover to the last voter/handle
// what to do about share of those that didn't sign
voter := votingPower.Voters[payable[beaconMember].BlsPublicKey]
if !voter.IsHarmonyNode {
due := BlockRewardStakedCase.Mul(
voter.EffectivePercent.Quo(votepower.StakersShare),
)
state.AddBalance(voter.EarningAccount, due.RoundInt())
}
}
// Handle rewards for shardchain
if cxLinks := header.CrossLinks(); len(cxLinks) != 0 {
crossLinks := types.CrossLinks{}
err := rlp.DecodeBytes(cxLinks, &crossLinks)
if err != nil {
return err
}
// do it quickly
w := sync.WaitGroup{} w := sync.WaitGroup{}
for i := range missing {
type slotPayable struct {
effective numeric.Dec
payee common.Address
bucket int
index int
oops error
}
payable := make(chan slotPayable)
slotError := func(err error, receive chan slotPayable) {
s := slotPayable{}
s.oops = err
go func() {
receive <- s
}()
}
for i := range crossLinks {
w.Add(1) w.Add(1)
go func(member int) {
go func(i int) {
defer w.Done() defer w.Done()
// Slash if missing block was long enough cxLink := crossLinks[i]
if slasher.ShouldSlash(missing[member].BlsPublicKey) { subCommittee := shard.State{}
// TODO Logic if err := rlp.DecodeBytes(
cxLink.ChainHeader.ShardState(), &subCommittee,
); err != nil {
slotError(err, payable)
return
}
subComm := subCommittee.FindCommitteeByID(cxLink.ShardID())
// _ are the missing signers, later for slashing
payableSigners, _, err := blockSigners(cxLink.Header(), subComm)
votingPower := votepower.Compute(subComm.Slots)
if err != nil {
slotError(err, payable)
return
}
for member := range payableSigners {
voter := votingPower.Voters[payableSigners[member].BlsPublicKey]
if !voter.IsHarmonyNode {
due := BlockRewardStakedCase.Mul(
voter.EffectivePercent.Quo(votepower.StakersShare),
)
to := voter.EarningAccount
go func(signersDue numeric.Dec, addr common.Address, j int) {
payable <- slotPayable{
effective: signersDue,
payee: addr,
bucket: i,
index: j,
oops: nil,
}
}(due, to, member)
}
} }
}(i) }(i)
} }
w.Wait() w.Wait()
resultsHandle := make([][]slotPayable, len(crossLinks))
for i := range resultsHandle {
resultsHandle[i] = []slotPayable{}
}
for payThem := range payable {
bucket := payThem.bucket
resultsHandle[bucket] = append(resultsHandle[bucket], payThem)
}
// Check if any errors and sort each bucket to enforce order
for bucket := range resultsHandle {
for payThem := range resultsHandle[bucket] {
if err := resultsHandle[bucket][payThem].oops; err != nil {
return err
}
}
sort.SliceStable(resultsHandle[bucket],
func(i, j int) bool {
return resultsHandle[bucket][i].index < resultsHandle[bucket][j].index
},
)
}
// Finally do the pay
for bucket := range resultsHandle {
for payThem := range resultsHandle[bucket] {
state.AddBalance(
resultsHandle[bucket][payThem].payee,
resultsHandle[bucket][payThem].effective.TruncateInt(),
)
}
}
}
return nil
}
payable := []struct { payable := []struct {
string string
@ -120,15 +282,26 @@ func AccumulateRewards(
*big.Int *big.Int
}{} }{}
parentHeader := bc.GetHeaderByHash(header.ParentHash())
if parentHeader.Number().Cmp(common.Big0) == 0 {
// Parent is an epoch block,
// which is not signed in the usual manner therefore rewards nothing.
return nil
}
_, signers, _, err := ballotResult(bc, header, header.ShardID())
if err != nil {
return err
}
totalAmount := rewarder.Award( totalAmount := rewarder.Award(
BlockReward, accounts, func(receipient common.Address, amount *big.Int) { BlockReward, signers, func(receipient common.Address, amount *big.Int) {
payable = append(payable, struct { payable = append(payable, struct {
string string
common.Address common.Address
*big.Int *big.Int
}{ }{common2.MustAddressToBech32(receipient), receipient, amount},
common2.MustAddressToBech32(receipient), receipient, amount,
},
) )
}, },
) )
@ -138,20 +311,19 @@ func AccumulateRewards(
Int64("block-reward", BlockReward.Int64()). Int64("block-reward", BlockReward.Int64()).
Int64("total-amount-paid-out", totalAmount.Int64()). Int64("total-amount-paid-out", totalAmount.Int64()).
Msg("Total paid out was not equal to block-reward") Msg("Total paid out was not equal to block-reward")
return errors.Wrapf(errPayoutNotEqualBlockReward, "payout "+totalAmount.String()) return errors.Wrapf(
errPayoutNotEqualBlockReward, "payout "+totalAmount.String(),
)
} }
signers := make([]string, len(payable))
for i := range payable { for i := range payable {
signers[i] = payable[i].string
state.AddBalance(payable[i].Address, payable[i].Int) state.AddBalance(payable[i].Address, payable[i].Int)
} }
header.Logger(utils.Logger()).Debug(). header.Logger(utils.Logger()).Debug().
Int("NumAccounts", len(accounts)). Int("NumAccounts", len(payable)).
Str("TotalAmount", totalAmount.String()). Str("TotalAmount", totalAmount.String()).
Strs("Signers", signers).
Msg("[Block Reward] Successfully paid out block reward") Msg("[Block Reward] Successfully paid out block reward")
return nil return nil
} }

@ -429,7 +429,9 @@ func New(host p2p.Host, consensusObj *consensus.Consensus,
node.Worker = worker.New(node.Blockchain().Config(), blockchain, chain.Engine) node.Worker = worker.New(node.Blockchain().Config(), blockchain, chain.Engine)
if node.Blockchain().ShardID() != shard.BeaconChainShardID { if node.Blockchain().ShardID() != shard.BeaconChainShardID {
node.BeaconWorker = worker.New(node.Beaconchain().Config(), beaconChain, chain.Engine) node.BeaconWorker = worker.New(
node.Beaconchain().Config(), beaconChain, chain.Engine,
)
} }
node.pendingCXReceipts = make(map[string]*types.CXReceiptsProof) node.pendingCXReceipts = make(map[string]*types.CXReceiptsProof)
@ -437,8 +439,10 @@ func New(host p2p.Host, consensusObj *consensus.Consensus,
node.Consensus.VerifiedNewBlock = make(chan *types.Block) node.Consensus.VerifiedNewBlock = make(chan *types.Block)
chain.Engine.SetRewarder(node.Consensus.Decider.(reward.Distributor)) chain.Engine.SetRewarder(node.Consensus.Decider.(reward.Distributor))
chain.Engine.SetSlasher(node.Consensus.Decider.(slash.Slasher)) chain.Engine.SetSlasher(node.Consensus.Decider.(slash.Slasher))
chain.Engine.SetBeaconchain(beaconChain)
// the sequence number is the next block number to be added in consensus protocol, which is always one more than current chain header block // the sequence number is the next block number to be added in consensus protocol, which is
// always one more than current chain header block
node.Consensus.SetBlockNum(blockchain.CurrentBlock().NumberU64() + 1) node.Consensus.SetBlockNum(blockchain.CurrentBlock().NumberU64() + 1)
// Add Faucet contract to all shards, so that on testnet, we can demo wallet in explorer // Add Faucet contract to all shards, so that on testnet, we can demo wallet in explorer

Loading…
Cancel
Save