Merge pull request #1850 from fxfactorial/switch-committee

Switch committee, thread slashing, implement staked quorum
pull/1853/head
Rongjian Lan 5 years ago committed by GitHub
commit 1e80e8c462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 55
      cmd/staking/root.go
  2. 1
      consensus/consensus_service.go
  3. 11
      consensus/engine/consensus_engine.go
  4. 31
      consensus/quorum/one-node-one-vote.go
  5. 150
      consensus/quorum/one-node-staked-vote.go
  6. 86
      consensus/quorum/quorum.go
  7. 6
      core/chain_makers.go
  8. 3
      core/state_transition.go
  9. 35
      internal/chain/engine.go
  10. 53
      internal/chain/reward.go
  11. 6
      node/node.go
  12. 37
      node/node_handler.go
  13. 3
      node/node_newblock.go
  14. 9
      shard/committee/assignment.go
  15. 4
      shard/shard_state.go
  16. 19
      staking/slash/slasher.go
  17. 13
      staking/types/validator.go
  18. 2
      test/deploy.sh

@ -170,6 +170,21 @@ func (s *staker) run(cmd *cobra.Command, args []string) error {
rlp.DecodeBytes(enc, tx) rlp.DecodeBytes(enc, tx)
hexSignature := hexutil.Encode(enc) hexSignature := hexutil.Encode(enc)
param := []interface{}{hexSignature} param := []interface{}{hexSignature}
// Check Double check if have enough balance for gas fees
balanceR, _ := baseRequest("hmy_getBalance", "http://localhost:9500", []interface{}{testAccounts[index], "latest"})
m := map[string]interface{}{}
json.Unmarshal(balanceR, &m)
balance, _ := m["result"].(string)
bln, _ := big.NewInt(0).SetString(balance[2:], 16)
if bln.Cmp(big.NewInt(0)) == 0 {
fmt.Println("Balance for ", testAccounts[index], "is zero, tx will be rejected b/c not enough for gas fee, exiting")
os.Exit(-1)
}
fmt.Println("balance", convertBalanceIntoReadableFormat(bln))
result, reqOops := baseRequest(stakingRPC, "http://localhost:9500", param) result, reqOops := baseRequest(stakingRPC, "http://localhost:9500", param)
fmt.Println(string(result)) fmt.Println(string(result))
return reqOops return reqOops
@ -245,3 +260,43 @@ var (
}, },
} }
) )
func convertBalanceIntoReadableFormat(balance *big.Int) string {
balance = balance.Div(balance, big.NewInt(denominations.Nano))
strBalance := fmt.Sprintf("%d", balance.Uint64())
bytes := []byte(strBalance)
hasDecimal := false
for i := 0; i < 11; i++ {
if len(bytes)-1-i < 0 {
bytes = append([]byte{'0'}, bytes...)
}
if bytes[len(bytes)-1-i] != '0' && i < 9 {
hasDecimal = true
}
if i == 9 {
newBytes := append([]byte{'.'}, bytes[len(bytes)-i:]...)
bytes = append(bytes[:len(bytes)-i], newBytes...)
}
}
zerosToRemove := 0
for i := 0; i < len(bytes); i++ {
if hasDecimal {
if bytes[len(bytes)-1-i] == '0' {
bytes = bytes[:len(bytes)-1-i]
i--
} else {
break
}
} else {
if zerosToRemove < 5 {
bytes = bytes[:len(bytes)-1-i]
i--
zerosToRemove++
} else {
break
}
}
}
return string(bytes)
}

@ -460,7 +460,6 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode {
curPubKeys := committee.WithStakingEnabled.ComputePublicKeys( curPubKeys := committee.WithStakingEnabled.ComputePublicKeys(
epoch, consensus.ChainReader, epoch, consensus.ChainReader,
)[int(header.ShardID())] )[int(header.ShardID())]
consensus.numPrevPubKeys = len(curPubKeys) consensus.numPrevPubKeys = len(curPubKeys)
consensus.getLogger().Info().Msg("[UpdateConsensusInformation] Updating.....") consensus.getLogger().Info().Msg("[UpdateConsensusInformation] Updating.....")
if shard.Schedule.IsLastBlock(header.Number().Uint64()) { if shard.Schedule.IsLastBlock(header.Number().Uint64()) {

@ -10,6 +10,8 @@ import (
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/shard/committee"
"github.com/harmony-one/harmony/staking/slash"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
) )
@ -42,6 +44,9 @@ type ChainReader interface {
// ReadActiveValidatorList retrieves the list of active validators // ReadActiveValidatorList retrieves the list of active validators
ReadActiveValidatorList() ([]common.Address, error) ReadActiveValidatorList() ([]common.Address, error)
// Methods needed for EPoS committee assignment calculation
committee.StakingCandidatesReader
} }
// Engine is an algorithm agnostic consensus engine. // Engine is an algorithm agnostic consensus engine.
@ -83,6 +88,12 @@ type Engine interface {
// SetRewarder assigns the Distributor used in block reward // SetRewarder assigns the Distributor used in block reward
SetRewarder(reward.Distributor) SetRewarder(reward.Distributor)
// Slasher handles slashing accounts due to inavailibility or double-signing
Slasher() slash.Slasher
// SetSlasher assigns the slasher used
SetSlasher(slash.Slasher)
// 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

@ -1,17 +1,20 @@
package quorum package quorum
import ( import (
"encoding/json"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "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/internal/utils" "github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard"
// "github.com/harmony-one/harmony/staking/effective" // "github.com/harmony-one/harmony/staking/effective"
) )
type uniformVoteWeight struct { type uniformVoteWeight struct {
SignatureReader
DependencyInjectionWriter DependencyInjectionWriter
DependencyInjectionReader
SignatureReader
} }
// Policy .. // Policy ..
@ -40,9 +43,9 @@ func (v *uniformVoteWeight) IsRewardThresholdAchieved() bool {
return v.SignersCount(Commit) >= (v.ParticipantsCount() * 9 / 10) return v.SignersCount(Commit) >= (v.ParticipantsCount() * 9 / 10)
} }
// func (v *uniformVoteWeight) UpdateVotingPower(effective.StakeKeeper) { func (v *uniformVoteWeight) UpdateVotingPower(shard.SlotList) {
// NO-OP do not add anything here // NO-OP do not add anything here
// } }
// ToggleActive for uniform vote is a no-op, always says that voter is active // ToggleActive for uniform vote is a no-op, always says that voter is active
func (v *uniformVoteWeight) ToggleActive(*bls.PublicKey) bool { func (v *uniformVoteWeight) ToggleActive(*bls.PublicKey) bool {
@ -71,3 +74,23 @@ func (v *uniformVoteWeight) Award(
return payout return payout
} }
func (v *uniformVoteWeight) ShouldSlash(k shard.BlsPublicKey) bool {
// No-op, no semantic meaning in one-slot-one-vote
return false
}
func (v *uniformVoteWeight) JSON() string {
s, _ := v.ShardIDProvider()()
type t struct {
Policy string `json"policy"`
ShardID uint32 `json:"shard-id"`
Count int `json:"count"`
Participants []string `json:"committee-members"`
}
members := v.DumpParticipants()
b1, _ := json.Marshal(t{v.Policy().String(), s, len(members), members})
return string(b1)
}

@ -1,29 +1,35 @@
package quorum package quorum
import ( import (
"encoding/json"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "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/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"
"github.com/harmony-one/harmony/staking/slash"
) )
var ( var (
twoThirds = numeric.NewDec(2).QuoInt64(3).Int twoThirds = numeric.NewDec(2).QuoInt64(3)
hSentinel = numeric.ZeroDec()
) )
type stakedVoter struct { type stakedVoter struct {
isActive, isHarmonyNode bool isActive, isHarmonyNode bool
earningAccount common.Address
effective numeric.Dec effective numeric.Dec
} }
type stakedVoteWeight struct { type stakedVoteWeight struct {
SignatureReader SignatureReader
DependencyInjectionWriter DependencyInjectionWriter
// EPOS based staking DependencyInjectionReader
validatorStakes map[[shard.PublicKeySizeInBytes]byte]stakedVoter slash.ThresholdDecider
totalEffectiveStakedAmount *big.Int validatorStakes map[shard.BlsPublicKey]stakedVoter
total numeric.Dec
} }
// Policy .. // Policy ..
@ -31,45 +37,141 @@ func (v *stakedVoteWeight) Policy() Policy {
return SuperMajorityStake return SuperMajorityStake
} }
// We must maintain 2/3 quoroum, so whatever is 2/3 staked amount,
// we divide that out & you
// IsQuorumAchieved .. // IsQuorumAchieved ..
func (v *stakedVoteWeight) IsQuorumAchieved(p Phase) bool { func (v *stakedVoteWeight) IsQuorumAchieved(p Phase) bool {
// TODO Implement this logic // TODO Implement this logic w/Chao
// soFar := numeric.ZeroDec()
w := shard.BlsPublicKey{}
members := v.Participants()
for i := range members {
w.FromLibBLSPublicKey(members[i])
// isHMY := v.validatorStakes[w].isHarmonyNode
if v.ReadSignature(p, members[i]) == nil {
// TODO TODO finish this logic
}
}
return true return true
} }
// QuorumThreshold .. // QuorumThreshold ..
func (v *stakedVoteWeight) QuorumThreshold() *big.Int { func (v *stakedVoteWeight) QuorumThreshold() *big.Int {
return new(big.Int).Mul(v.totalEffectiveStakedAmount, twoThirds) return v.total.Mul(twoThirds).Ceil().RoundInt()
} }
// RewardThreshold .. // RewardThreshold ..
func (v *stakedVoteWeight) IsRewardThresholdAchieved() bool { func (v *stakedVoteWeight) IsRewardThresholdAchieved() bool {
// TODO Implement // TODO Implement
return false return true
} }
// HACK
var (
hSentinel = big.NewInt(0)
hEffectiveSentinel = numeric.ZeroDec()
)
// 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 *big.Int, earners []common.Address, hook func(earner common.Address, due *big.Int),
) *big.Int { ) *big.Int {
// TODO Implement payout := big.NewInt(0)
return nil last := big.NewInt(0)
count := big.NewInt(int64(len(earners)))
proportional := map[common.Address]numeric.Dec{}
for _, details := range v.validatorStakes {
if details.isHarmonyNode == false {
proportional[details.earningAccount] = details.effective.QuoTruncate(
v.total,
)
}
}
// TODO Finish implementing this logic w/Chao
for i := range earners {
cur := big.NewInt(0)
cur.Mul(Pie, big.NewInt(int64(i+1))).Div(cur, count)
diff := big.NewInt(0).Sub(cur, last)
// hook(common.Address(account), diff)
payout = big.NewInt(0).Add(payout, diff)
last = cur
}
return payout
} }
// UpdateVotingPower called only at epoch change, prob need to move to CalculateShardState func (v *stakedVoteWeight) UpdateVotingPower(staked shard.SlotList) {
// func (v *stakedVoteWeight) UpdateVotingPower(keeper effective.StakeKeeper) { s, _ := v.ShardIDProvider()()
// TODO Implement
// }
func (v *stakedVoteWeight) ToggleActive(*bls.PublicKey) bool { v.validatorStakes = map[shard.BlsPublicKey]stakedVoter{}
// TODO Implement v.Reset([]Phase{Prepare, Commit, ViewChange})
return true
for i := range staked {
if staked[i].StakeWithDelegationApplied != nil {
v.validatorStakes[staked[i].BlsPublicKey] = stakedVoter{
true, false, staked[i].EcdsaAddress, *staked[i].StakeWithDelegationApplied,
}
v.total = v.total.Add(*staked[i].StakeWithDelegationApplied)
} else {
v.validatorStakes[staked[i].BlsPublicKey] = stakedVoter{
true, true, staked[i].EcdsaAddress, hSentinel,
}
}
}
utils.Logger().Info().
Uint32("on-shard", s).
Str("Staked", v.total.String()).
Msg("Total staked")
}
func (v *stakedVoteWeight) ToggleActive(k *bls.PublicKey) bool {
w := shard.BlsPublicKey{}
w.FromLibBLSPublicKey(k)
g := v.validatorStakes[w]
g.isActive = !g.isActive
v.validatorStakes[w] = g
return v.validatorStakes[w].isActive
}
func (v *stakedVoteWeight) ShouldSlash(key shard.BlsPublicKey) bool {
s, _ := v.ShardIDProvider()()
switch s {
case shard.BeaconChainShardID:
return v.SlashThresholdMet(key)
default:
return false
}
}
func (v *stakedVoteWeight) JSON() string {
s, _ := v.ShardIDProvider()()
type t struct {
Policy string `json"policy"`
ShardID uint32 `json:"shard-id"`
Count int `json:"count"`
Participants []string `json:"committee-members"`
TotalStaked string `json:"total-staked"`
}
members := v.DumpParticipants()
parts := []string{}
for i := range members {
k := bls.PublicKey{}
k.DeserializeHexStr(members[i])
w := shard.BlsPublicKey{}
w.FromLibBLSPublicKey(&k)
staker := v.validatorStakes[w]
if staker.isHarmonyNode {
parts = append(parts, members[i])
} else {
parts = append(parts, members[i]+"-"+staker.effective.String())
}
}
b1, _ := json.Marshal(t{
v.Policy().String(), s, len(members), parts, v.total.String(),
})
return string(b1)
} }

@ -5,7 +5,9 @@ import (
"math/big" "math/big"
"github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/bls/ffi/go/bls"
"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/effective" // "github.com/harmony-one/harmony/staking/effective"
) )
@ -44,6 +46,19 @@ const (
SuperMajorityStake SuperMajorityStake
) )
var policyNames = map[Policy]string{
SuperMajorityStake: "SuperMajorityStake",
SuperMajorityVote: "SuperMajorityVote",
}
func (p Policy) String() string {
if name, ok := policyNames[p]; ok {
return name
}
return fmt.Sprintf("Unknown Quorum Policy %+v", byte(p))
}
// ParticipantTracker .. // ParticipantTracker ..
type ParticipantTracker interface { type ParticipantTracker interface {
Participants() []*bls.PublicKey Participants() []*bls.PublicKey
@ -75,12 +90,24 @@ type DependencyInjectionWriter interface {
SetShardIDProvider(func() (uint32, error)) SetShardIDProvider(func() (uint32, error))
} }
// DependencyInjectionReader ..
type DependencyInjectionReader interface {
ShardIDProvider() func() (uint32, error)
}
//WithJSONDump representation dump
type WithJSONDump interface {
JSON() string
}
// Decider .. // Decider ..
type Decider interface { type Decider interface {
SignatureReader SignatureReader
DependencyInjectionWriter DependencyInjectionWriter
slash.Slasher
WithJSONDump
ToggleActive(*bls.PublicKey) bool ToggleActive(*bls.PublicKey) bool
// UpdateVotingPower(keeper effective.StakeKeeper) UpdateVotingPower(shard.SlotList)
Policy() Policy Policy() Policy
IsQuorumAchieved(Phase) bool IsQuorumAchieved(Phase) bool
QuorumThreshold() *big.Int QuorumThreshold() *big.Int
@ -97,16 +124,13 @@ type cIdentities struct {
// viewIDSigs: every validator // viewIDSigs: every validator
// sign on |viewID|blockHash| in view changing message // sign on |viewID|blockHash| in view changing message
viewID map[string]*bls.Sign viewID map[string]*bls.Sign
seenCounter map[[shard.PublicKeySizeInBytes]byte]int
} }
type depInject struct { type depInject struct {
shardIDProvider func() (uint32, error) shardIDProvider func() (uint32, error)
} }
func (d *depInject) SetShardIDProvider(p func() (uint32, error)) {
d.shardIDProvider = p
}
func (s *cIdentities) IndexOf(pubKey *bls.PublicKey) int { func (s *cIdentities) IndexOf(pubKey *bls.PublicKey) int {
idx := -1 idx := -1
for k, v := range s.publicKeys { for k, v := range s.publicKeys {
@ -132,12 +156,24 @@ func (s *cIdentities) Participants() []*bls.PublicKey {
} }
func (s *cIdentities) UpdateParticipants(pubKeys []*bls.PublicKey) { func (s *cIdentities) UpdateParticipants(pubKeys []*bls.PublicKey) {
// TODO - might need to put reset of seen counter in separate method
s.seenCounter = make(map[[shard.PublicKeySizeInBytes]byte]int, len(pubKeys))
for i := range pubKeys {
k := shard.BlsPublicKey{}
k.FromLibBLSPublicKey(pubKeys[i])
s.seenCounter[k] = 0
}
s.publicKeys = append(pubKeys[:0:0], pubKeys...) s.publicKeys = append(pubKeys[:0:0], pubKeys...)
} }
func (s *cIdentities) SlashThresholdMet(key shard.BlsPublicKey) bool {
s.seenCounter[key]++
return s.seenCounter[key] == slash.UnavailabilityInConsecutiveBlockSigning
}
func (s *cIdentities) DumpParticipants() []string { func (s *cIdentities) DumpParticipants() []string {
keys := make([]string, len(s.publicKeys)) keys := make([]string, len(s.publicKeys))
for i := 0; i < len(s.publicKeys); i++ { for i := range s.publicKeys {
keys[i] = s.publicKeys[i].SerializeToHexStr() keys[i] = s.publicKeys[i].SerializeToHexStr()
} }
return keys return keys
@ -174,8 +210,8 @@ func (s *cIdentities) AddSignature(p Phase, PubKey *bls.PublicKey, sig *bls.Sign
} }
func (s *cIdentities) Reset(ps []Phase) { func (s *cIdentities) Reset(ps []Phase) {
for _, p := range ps { for i := range ps {
switch m := map[string]*bls.Sign{}; p { switch m := map[string]*bls.Sign{}; ps[i] {
case Prepare: case Prepare:
s.prepare = m s.prepare = m
case Commit: case Commit:
@ -223,26 +259,46 @@ func (s *cIdentities) ReadAllSignatures(p Phase) []*bls.Sign {
return sigs return sigs
} }
func newMapBackedSignatureReader() SignatureReader { func newMapBackedSignatureReader() *cIdentities {
return &cIdentities{ return &cIdentities{
[]*bls.PublicKey{}, map[string]*bls.Sign{}, []*bls.PublicKey{}, map[string]*bls.Sign{},
map[string]*bls.Sign{}, map[string]*bls.Sign{}, map[string]*bls.Sign{}, map[string]*bls.Sign{},
map[[shard.PublicKeySizeInBytes]byte]int{},
} }
} }
type composite struct {
DependencyInjectionWriter
DependencyInjectionReader
SignatureReader
}
func (d *depInject) SetShardIDProvider(p func() (uint32, error)) {
d.shardIDProvider = p
}
func (d *depInject) ShardIDProvider() func() (uint32, error) {
return d.shardIDProvider
}
// NewDecider .. // NewDecider ..
func NewDecider(p Policy) Decider { func NewDecider(p Policy) Decider {
signatureStore := newMapBackedSignatureReader() signatureStore := newMapBackedSignatureReader()
dependencies := &depInject{} deps := &depInject{}
c := &composite{deps, deps, signatureStore}
switch p { switch p {
case SuperMajorityVote: case SuperMajorityVote:
return &uniformVoteWeight{signatureStore, dependencies} return &uniformVoteWeight{
c.DependencyInjectionWriter, c.DependencyInjectionReader, c,
}
case SuperMajorityStake: case SuperMajorityStake:
return &stakedVoteWeight{ return &stakedVoteWeight{
signatureStore, c.SignatureReader,
dependencies, c.DependencyInjectionWriter,
map[[shard.PublicKeySizeInBytes]byte]stakedVoter{}, c.DependencyInjectionWriter.(DependencyInjectionReader),
big.NewInt(0), c.SignatureReader.(slash.ThresholdDecider),
map[shard.BlsPublicKey]stakedVoter{},
numeric.ZeroDec(),
} }
default: default:
// Should not be possible // Should not be possible

@ -271,3 +271,9 @@ func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *block.Hea
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil } func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
func (cr *fakeChainReader) ReadShardState(epoch *big.Int) (shard.State, error) { return nil, nil } func (cr *fakeChainReader) ReadShardState(epoch *big.Int) (shard.State, error) { return nil, nil }
func (cr *fakeChainReader) ReadActiveValidatorList() ([]common.Address, error) { return nil, nil } func (cr *fakeChainReader) ReadActiveValidatorList() ([]common.Address, error) { return nil, nil }
func (cr *fakeChainReader) ValidatorCandidates() []common.Address { return nil }
func (cr *fakeChainReader) ReadValidatorData(addr common.Address) (*staking.ValidatorWrapper, error) {
return nil, nil
}
func (cr *fakeChainReader) ValidatorStakingWithDelegation(addr common.Address) *big.Int { return nil }

@ -285,6 +285,9 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) {
// Pay intrinsic gas // Pay intrinsic gas
// TODO: propose staking-specific formula for staking transaction // TODO: propose staking-specific formula for staking transaction
gas, err := IntrinsicGas(st.data, false, homestead) gas, err := IntrinsicGas(st.data, false, homestead)
// TODO Remove this logging
utils.Logger().Info().Uint64("Using", gas).Msg("Gas cost of staking transaction being processed")
if err != nil { if err != nil {
return 0, err return 0, err
} }

@ -11,10 +11,12 @@ import (
"github.com/harmony-one/harmony/consensus/reward" "github.com/harmony-one/harmony/consensus/reward"
"github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
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/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/shard/committee" "github.com/harmony-one/harmony/shard/committee"
"github.com/harmony-one/harmony/staking/slash"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
@ -22,10 +24,11 @@ import (
type engineImpl struct { type engineImpl struct {
d reward.Distributor d reward.Distributor
s slash.Slasher
} }
// Engine is an algorithm-agnostic consensus engine. // Engine is an algorithm-agnostic consensus engine.
var Engine = &engineImpl{nil} var Engine = &engineImpl{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 {
@ -37,6 +40,16 @@ func (e *engineImpl) SetRewarder(d reward.Distributor) {
e.d = d e.d = d
} }
// Slasher handles slashing accounts due to inavailibility or double-signing
func (e *engineImpl) Slasher() slash.Slasher {
return e.s
}
// SetSlasher assigns the slasher used
func (e *engineImpl) SetSlasher(s slash.Slasher) {
e.s = s
}
// 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()
@ -164,13 +177,16 @@ func (e *engineImpl) VerifySeal(chain engine.ChainReader, header *block.Header)
// Finalize implements Engine, accumulating the block rewards, // Finalize implements Engine, accumulating the block rewards,
// setting the final state and assembling the block. // setting the final state and assembling the block.
func (e *engineImpl) Finalize( func (e *engineImpl) Finalize(
chain engine.ChainReader, header *block.Header, state *state.DB, txs []*types.Transaction, chain engine.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) {
// 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
// TODO: Block rewards should be done only in beacon chain based on cross-links if err := AccumulateRewards(
if err := AccumulateRewards(chain, state, header, e.Rewarder()); err != nil { chain, state, header, e.Rewarder(), e.Slasher(),
); err != nil {
return nil, ctxerror.New("cannot pay block reward").WithCause(err) return nil, ctxerror.New("cannot pay block reward").WithCause(err)
} }
@ -194,6 +210,7 @@ func (e *engineImpl) Finalize(
return nil, ctxerror.New("failed update validator info").WithCause(err) return nil, ctxerror.New("failed update validator info").WithCause(err)
} }
} else { } else {
err = errors.New("validator came back empty" + common2.MustAddressToBech32(validator))
return nil, ctxerror.New("failed getting validator info").WithCause(err) return nil, ctxerror.New("failed getting validator info").WithCause(err)
} }
} }
@ -203,10 +220,12 @@ func (e *engineImpl) Finalize(
} }
// QuorumForBlock returns the quorum for the given block header. // QuorumForBlock returns the quorum for the given block header.
func QuorumForBlock(chain engine.ChainReader, h *block.Header, reCalculate bool) (quorum int, err error) { func QuorumForBlock(
chain engine.ChainReader, h *block.Header, reCalculate bool,
) (quorum int, err error) {
var ss shard.State var ss shard.State
if reCalculate { if reCalculate {
ss, _ = committee.WithStakingEnabled.Compute(h.Epoch(), *chain.Config(), nil) ss, _ = committee.WithStakingEnabled.Compute(h.Epoch(), *chain.Config(), chain)
} else { } else {
ss, err = chain.ReadShardState(h.Epoch()) ss, err = chain.ReadShardState(h.Epoch())
if err != nil { if err != nil {
@ -265,7 +284,7 @@ func GetPublicKeys(chain engine.ChainReader, header *block.Header, reCalculate b
var shardState shard.State var shardState shard.State
var err error var err error
if reCalculate { if reCalculate {
shardState, _ = committee.WithStakingEnabled.Compute(header.Epoch(), *chain.Config(), nil) shardState, _ = committee.WithStakingEnabled.Compute(header.Epoch(), *chain.Config(), chain)
} else { } else {
shardState, err = chain.ReadShardState(header.Epoch()) shardState, err = chain.ReadShardState(header.Epoch())
if err != nil { if err != nil {

@ -2,6 +2,7 @@ package chain
import ( import (
"math/big" "math/big"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/bls/ffi/go/bls"
@ -14,6 +15,8 @@ import (
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/shard"
"github.com/harmony-one/harmony/staking/slash"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -27,7 +30,9 @@ var (
// reward. The total reward consists of the static block reward and rewards for // reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded. // included uncles. The coinbase of each uncle block is also rewarded.
func AccumulateRewards( func AccumulateRewards(
bc engine.ChainReader, state *state.DB, header *block.Header, rewarder reward.Distributor, bc engine.ChainReader, state *state.DB,
header *block.Header, rewarder reward.Distributor,
slasher slash.Slasher,
) error { ) error {
blockNum := header.Number().Uint64() blockNum := header.Number().Uint64()
if blockNum == 0 { if blockNum == 0 {
@ -79,29 +84,54 @@ func AccumulateRewards(
} }
accounts := []common.Address{} accounts := []common.Address{}
missing := shard.SlotList{}
for idx, member := range parentCommittee.Slots { for idx, member := range parentCommittee.Slots {
if signed, err := mask.IndexEnabled(idx); err != nil { switch signed, err := mask.IndexEnabled(idx); true {
case err != nil:
return ctxerror.New("cannot check for committer bit", return ctxerror.New("cannot check for committer bit",
"committerIndex", idx, "committerIndex", idx,
).WithCause(err) ).WithCause(err)
} else if signed { case signed:
accounts = append(accounts, member.EcdsaAddress) accounts = append(accounts, member.EcdsaAddress)
default:
missing = append(missing, member)
} }
} }
type t struct { // do it quickly
w := sync.WaitGroup{}
for i := range missing {
w.Add(1)
go func(member int) {
defer w.Done()
// Slash if missing block was long enough
if slasher.ShouldSlash(missing[member].BlsPublicKey) {
// TODO Logic
}
}(i)
}
w.Wait()
payable := []struct {
string
common.Address common.Address
*big.Int *big.Int
} }{}
signers := []string{}
payable := []t{}
totalAmount := rewarder.Award( totalAmount := rewarder.Award(
BlockReward, accounts, func(receipient common.Address, amount *big.Int) { BlockReward, accounts, func(receipient common.Address, amount *big.Int) {
signers = append(signers, common2.MustAddressToBech32(receipient)) payable = append(payable, struct {
payable = append(payable, t{receipient, amount}) string
}) common.Address
*big.Int
}{
common2.MustAddressToBech32(receipient), receipient, amount,
},
)
},
)
if totalAmount.Cmp(BlockReward) != 0 { if totalAmount.Cmp(BlockReward) != 0 {
utils.Logger().Error(). utils.Logger().Error().
@ -111,7 +141,10 @@ func AccumulateRewards(
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)
} }

@ -33,6 +33,7 @@ import (
p2p_host "github.com/harmony-one/harmony/p2p/host" p2p_host "github.com/harmony-one/harmony/p2p/host"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/shard/committee" "github.com/harmony-one/harmony/shard/committee"
"github.com/harmony-one/harmony/staking/slash"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
) )
@ -372,7 +373,8 @@ func (node *Node) GetSyncID() [SyncIDLength]byte {
} }
// New creates a new node. // New creates a new node.
func New(host p2p.Host, consensusObj *consensus.Consensus, chainDBFactory shardchain.DBFactory, isArchival bool) *Node { func New(host p2p.Host, consensusObj *consensus.Consensus,
chainDBFactory shardchain.DBFactory, isArchival bool) *Node {
node := Node{} node := Node{}
node.syncFreq = SyncFrequency node.syncFreq = SyncFrequency
@ -431,6 +433,8 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, chainDBFactory shardc
node.pendingStakingTransactions = make(map[common.Hash]*staking.StakingTransaction) node.pendingStakingTransactions = make(map[common.Hash]*staking.StakingTransaction)
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))
// 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)

@ -8,18 +8,17 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
bls2 "github.com/harmony-one/harmony/crypto/bls" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/bls/ffi/go/bls"
libp2p_peer "github.com/libp2p/go-libp2p-core/peer"
"github.com/harmony-one/harmony/api/proto" "github.com/harmony-one/harmony/api/proto"
proto_discovery "github.com/harmony-one/harmony/api/proto/discovery" proto_discovery "github.com/harmony-one/harmony/api/proto/discovery"
proto_node "github.com/harmony-one/harmony/api/proto/node" proto_node "github.com/harmony-one/harmony/api/proto/node"
"github.com/harmony-one/harmony/block" "github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/consensus/quorum"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
bls2 "github.com/harmony-one/harmony/crypto/bls"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node" nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"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"
@ -27,7 +26,9 @@ import (
"github.com/harmony-one/harmony/p2p" "github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/p2p/host" "github.com/harmony-one/harmony/p2p/host"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/shard/committee"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
libp2p_peer "github.com/libp2p/go-libp2p-core/peer"
) )
const ( const (
@ -377,10 +378,36 @@ func (node *Node) PostConsensusProcessing(newBlock *types.Block, commitSigAndBit
} }
} }
} }
// Update consensus keys at last so the change of leader status doesn't mess up normal flow // Update consensus keys at last so the change of leader status doesn't mess up normal flow
if shard.Schedule.IsLastBlock(newBlock.Number().Uint64()) { if shard.Schedule.IsLastBlock(newBlock.Number().Uint64()) {
next := new(big.Int).Add(newBlock.Epoch(), common.Big1)
if node.chainConfig.StakingEpoch.Cmp(next) == 0 &&
node.Consensus.Decider.Policy() != quorum.SuperMajorityStake {
node.Consensus.Decider = quorum.NewDecider(quorum.SuperMajorityStake)
node.Consensus.Decider.SetShardIDProvider(func() (uint32, error) {
return node.Consensus.ShardID, nil
})
s, _ := committee.WithStakingEnabled.Compute(
next, node.chainConfig, node.Consensus.ChainReader,
)
node.Consensus.Decider.UpdateVotingPower(
s.FindCommitteeByID(node.Consensus.ShardID).Slots,
)
}
// TODO Need to refactor UpdateConsensusInformation so can fold the following logic
// into UCI - todo because UCI mutates state & called in overloaded contexts
node.Consensus.UpdateConsensusInformation() node.Consensus.UpdateConsensusInformation()
if shard.Schedule.IsLastBlock(newBlock.Number().Uint64()) {
if node.chainConfig.StakingEpoch.Cmp(next) == 0 {
// Hit this case again, need after UpdateConsensus
curPubKeys := committee.WithStakingEnabled.ComputePublicKeys(
next, node.Consensus.ChainReader,
)[int(node.Consensus.ShardID)]
node.Consensus.Decider.UpdateParticipants(curPubKeys)
}
}
} }
// TODO chao: uncomment this after beacon syncing is stable // TODO chao: uncomment this after beacon syncing is stable

@ -120,7 +120,7 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
// Prepare shard state // Prepare shard state
shardState, err := node.Worker.SuperCommitteeForNextEpoch( shardState, err := node.Worker.SuperCommitteeForNextEpoch(
node.Consensus.ShardID, node.Beaconchain(), node.Consensus.ShardID, node.Blockchain(),
) )
if err != nil { if err != nil {
@ -136,6 +136,7 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
return node.Worker.FinalizeNewBlock(sig, mask, node.Consensus.GetViewID(), coinbase, crossLinks, shardState) return node.Worker.FinalizeNewBlock(sig, mask, node.Consensus.GetViewID(), coinbase, crossLinks, shardState)
} }
// TODO is this still needed?
func (node *Node) proposeLocalShardState(block *types.Block) { func (node *Node) proposeLocalShardState(block *types.Block) {
logger := block.Logger(utils.Logger()) logger := block.Logger(utils.Logger())
// TODO ek – read this from beaconchain once BC sync is fixed // TODO ek – read this from beaconchain once BC sync is fixed

@ -10,6 +10,7 @@ import (
shardingconfig "github.com/harmony-one/harmony/internal/configs/sharding" shardingconfig "github.com/harmony-one/harmony/internal/configs/sharding"
"github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/effective" "github.com/harmony-one/harmony/staking/effective"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
@ -119,6 +120,8 @@ func eposStakedCommittee(
candidates := stakerReader.ValidatorCandidates() candidates := stakerReader.ValidatorCandidates()
essentials := map[common.Address]effective.SlotOrder{} essentials := map[common.Address]effective.SlotOrder{}
utils.Logger().Info().Int("staked-candidates", len(candidates)).Msg("preparing epos staked committee")
// TODO benchmark difference if went with data structure that sorts on insert // TODO benchmark difference if went with data structure that sorts on insert
for i := range candidates { for i := range candidates {
// TODO Should be using .ValidatorStakingWithDelegation, not implemented yet // TODO Should be using .ValidatorStakingWithDelegation, not implemented yet
@ -170,7 +173,11 @@ func eposStakedCommittee(
&slot.Dec, &slot.Dec,
}) })
} }
if c := len(candidates); c != 0 {
utils.Logger().Info().Int("staked-candidates", c).
RawJSON("staked-super-committee", []byte(superComm.JSON())).
Msg("EPoS based super-committe")
}
return superComm, nil return superComm, nil
} }

@ -38,7 +38,7 @@ type BlsPublicKey [PublicKeySizeInBytes]byte
type Slot struct { type Slot struct {
EcdsaAddress common.Address `json:"ecdsa-address"` EcdsaAddress common.Address `json:"ecdsa-address"`
BlsPublicKey BlsPublicKey `json:"bls-pubkey"` BlsPublicKey BlsPublicKey `json:"bls-pubkey"`
// nil means not active, 0 means our node, >= 0 means staked node // nil means our node, 0 means not active, >= 0 means staked node
StakeWithDelegationApplied *numeric.Dec `json:"staked-validator" rlp:"nil"` StakeWithDelegationApplied *numeric.Dec `json:"staked-validator" rlp:"nil"`
} }
@ -55,7 +55,7 @@ type Committee struct {
func (ss State) JSON() string { func (ss State) JSON() string {
type t struct { type t struct {
Slot Slot
EcdsaAddress string `json:"one-address"` EcdsaAddress string `json:"ecdsa-address"`
} }
type v struct { type v struct {
Committee Committee

@ -0,0 +1,19 @@
package slash
import "github.com/harmony-one/harmony/shard"
const (
// UnavailabilityInConsecutiveBlockSigning is how many blocks in a row
// before "slashing by unavailability" occurs
UnavailabilityInConsecutiveBlockSigning = 1380
)
// Slasher ..
type Slasher interface {
ShouldSlash(shard.BlsPublicKey) bool
}
// ThresholdDecider ..
type ThresholdDecider interface {
SlashThresholdMet(shard.BlsPublicKey) bool
}

@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
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/numeric" "github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
) )
@ -56,6 +57,7 @@ type Validator struct {
Address common.Address `json:"address" yaml:"address"` Address common.Address `json:"address" yaml:"address"`
// The BLS public key of the validator for consensus // The BLS public key of the validator for consensus
SlotPubKeys []shard.BlsPublicKey `json:"slot_pub_keys" yaml:"slot_pub_keys"` SlotPubKeys []shard.BlsPublicKey `json:"slot_pub_keys" yaml:"slot_pub_keys"`
// TODO Need to remove this .Stake Field
// The stake put by the validator itself // The stake put by the validator itself
Stake *big.Int `json:"stake" yaml:"stake"` Stake *big.Int `json:"stake" yaml:"stake"`
// if unbonding, height at which this validator has begun unbonding // if unbonding, height at which this validator has begun unbonding
@ -118,6 +120,13 @@ func (w *ValidatorWrapper) SanityCheck() error {
hundredPercent := numeric.NewDec(1) hundredPercent := numeric.NewDec(1)
zeroPercent := numeric.NewDec(0) zeroPercent := numeric.NewDec(0)
utils.Logger().Info().
Str("rate", w.Validator.Rate.String()).
Str("max-rate", w.Validator.MaxRate.String()).
Str("max-change-rate", w.Validator.MaxChangeRate.String()).
Msg("Sanity check on validator commission rates, should all be in [0, 1]")
if w.Validator.Rate.LT(zeroPercent) || w.Validator.Rate.GT(hundredPercent) { if w.Validator.Rate.LT(zeroPercent) || w.Validator.Rate.GT(hundredPercent) {
return errInvalidComissionRate return errInvalidComissionRate
} }
@ -227,12 +236,12 @@ func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Valida
commission := Commission{val.CommissionRates, blockNum} commission := Commission{val.CommissionRates, blockNum}
pubKeys := []shard.BlsPublicKey{} pubKeys := []shard.BlsPublicKey{}
pubKeys = append(pubKeys, val.SlotPubKeys...) pubKeys = append(pubKeys, val.SlotPubKeys...)
// TODO: a new validator should have a minimum of 1 token as self delegation, and that should be added as a delegation entry here. // TODO: a new validator should have a minimum of 1 token as self delegation, and that should be added as a delegation entry here.
v := Validator{ v := Validator{
val.ValidatorAddress, pubKeys, val.ValidatorAddress, pubKeys,
val.Amount, new(big.Int), val.MinSelfDelegation, val.MaxTotalDelegation, false, val.Amount, new(big.Int), val.MinSelfDelegation, val.MaxTotalDelegation, false,
commission, desc, blockNum} commission, desc, blockNum,
}
return &v, nil return &v, nil
} }

@ -212,7 +212,7 @@ while IFS='' read -r line || [[ -n "$line" ]]; do
done < $config done < $config
if [ "$DOTEST" == "true" ]; then if [ "$DOTEST" == "true" ]; then
debug_staking # debug_staking
echo "waiting for some block rewards" echo "waiting for some block rewards"
sleep 60 sleep 60
i=1 i=1

Loading…
Cancel
Save