[staking][validation][protocol] (#2396)

* [staking][validation][protocol] Limit max bls keys

* [staking-era] Fold banned and active into single field

* [slash][effective] Remove LRU cache for slash, change .Active to enumeration

* [slash] Remove leftover wrong usage of Logger

* [slash][offchain] Only Decode if len > 0

* [offchain] cosmetic

* [slash] Remove some logs in proposal

* [webhook] Move webhook with call for when cannot commit block

* [shard] Finally make finding subcommittee by shardID an explicit error

* [node] Whitespace, prefer literal

* [webhook] Report bad block to webhook

* [slash] Expand verify, remove bad log usage, explicit error handle

* [slash] Check on key size

* [slash] Explicit upper bound of pending slashes

* [slash] Use right epoch snapshot, fail to verify if epoch wrong on beaconchain

* [multibls] Make max count allowed be 1/3 of external slots

* [quorum] Remove bad API of ShardIDProvider, factor out committee key as method of committee

* [verify] Begin factor out of common verification approach

* [project] Further remove RawJSON log, use proper epoch for snapshot

* [slash] Implement verification

* [slash] Implement BLS key verification of ballots

* [rpc] Keep validator information as meaningful as possible

* [staking] Never can stop being banned

* [slash] Comments and default Unknown case of eligibility

* [slash] Be explicit on what input values allowed when want to change EPOSStatus

* [consensus] Remove unneeded TODO

* [verify] Add proper error message

* [rpc] Give back to caller their wrong chain id

* [chain] Add extra map dump of delegation sizing for downstream analysis

* [engine] Less code, more methods

* [offchain] More leniency in handling slash bytes and delete from pending

* [validator] Remove errors on bad input for edit
pull/2454/head
Edgar Aroutiounian 5 years ago committed by GitHub
parent 304cf10007
commit 2baab4864c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      cmd/harmony/main.go
  2. 36
      consensus/consensus_service.go
  3. 10
      consensus/construct_test.go
  4. 2
      consensus/engine/consensus_engine.go
  5. 53
      consensus/leader.go
  6. 5
      consensus/quorum/one-node-one-vote.go
  7. 3
      consensus/quorum/one-node-staked-vote.go
  8. 4
      consensus/quorum/one-node-staked-vote_test.go
  9. 44
      consensus/quorum/quorum.go
  10. 12
      consensus/threshold.go
  11. 15
      consensus/view_change.go
  12. 6
      consensus/votepower/roster.go
  13. 153
      core/blockchain.go
  14. 18
      core/chain_makers.go
  15. 31
      core/offchain.go
  16. 45
      core/staking_verifier.go
  17. 4
      core/state/statedb.go
  18. 46
      hmy/api_backend.go
  19. 55
      internal/chain/engine.go
  20. 6
      internal/chain/reward.go
  21. 14
      internal/hmyapi/apiv1/transactionpool.go
  22. 14
      internal/hmyapi/apiv2/transactionpool.go
  23. 18
      node/double_signing.go
  24. 55
      node/node.go
  25. 80
      node/node_cross_link.go
  26. 7
      node/node_genesis.go
  27. 13
      node/node_handler.go
  28. 6
      node/node_newblock.go
  29. 57
      node/worker/worker.go
  30. 9
      shard/committee/assignment.go
  31. 22
      shard/shard_state.go
  32. 35
      staking/availability/measure.go
  33. 26
      staking/effective/eligible.go
  34. 146
      staking/slash/double-sign.go
  35. 92
      staking/slash/double-sign_test.go
  36. 28
      staking/types/messages.go
  37. 73
      staking/types/validator.go
  38. 52
      staking/types/validator_test.go
  39. 59
      staking/verify/verify.go

@ -422,9 +422,6 @@ func setupConsensusAndNode(nodeConfig *nodeconfig.ConfigType) *node.Node {
currentConsensus, err := consensus.New( currentConsensus, err := consensus.New(
myHost, nodeConfig.ShardID, p2p.Peer{}, nodeConfig.ConsensusPriKey, decider, myHost, nodeConfig.ShardID, p2p.Peer{}, nodeConfig.ConsensusPriKey, decider,
) )
currentConsensus.Decider.SetShardIDProvider(func() (uint32, error) {
return currentConsensus.ShardID, nil
})
currentConsensus.Decider.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) { currentConsensus.Decider.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) {
return currentConsensus.PubKey, nil return currentConsensus.PubKey, nil
}) })

@ -414,8 +414,8 @@ func (consensus *Consensus) getLeaderPubKeyFromCoinbase(header *block.Header) (*
).WithCause(err) ).WithCause(err)
} }
committee := shardState.FindCommitteeByID(header.ShardID()) committee, err := shardState.FindCommitteeByID(header.ShardID())
if committee == nil { if err != nil {
return nil, ctxerror.New("cannot find shard in the shard state", return nil, ctxerror.New("cannot find shard in the shard state",
"blockNum", header.Number(), "blockNum", header.Number(),
"shardID", header.ShardID(), "shardID", header.ShardID(),
@ -477,9 +477,6 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode {
// Only happens once, the flip-over to a new Decider policy // Only happens once, the flip-over to a new Decider policy
if isFirstTimeStaking || haventUpdatedDecider { if isFirstTimeStaking || haventUpdatedDecider {
decider := quorum.NewDecider(quorum.SuperMajorityStake) decider := quorum.NewDecider(quorum.SuperMajorityStake)
decider.SetShardIDProvider(func() (uint32, error) {
return consensus.ShardID, nil
})
decider.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) { decider.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) {
return consensus.PubKey, nil return consensus.PubKey, nil
}) })
@ -504,8 +501,9 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode {
if len(curHeader.ShardState()) > 0 { if len(curHeader.ShardState()) > 0 {
// increase curEpoch by one if it's the last block // increase curEpoch by one if it's the last block
consensus.SetEpochNum(curEpoch.Uint64() + 1) consensus.SetEpochNum(curEpoch.Uint64() + 1)
consensus.getLogger().Info().Uint64("headerNum", curHeader.Number().Uint64()). consensus.getLogger().Info().
Msg("[UpdateConsensusInformation] Epoch updated for nextEpoch curEpoch") Uint64("headerNum", curHeader.Number().Uint64()).
Msg("Epoch updated for nextEpoch curEpoch")
nextShardState, err := committee.WithStakingEnabled.ReadFromDB( nextShardState, err := committee.WithStakingEnabled.ReadFromDB(
nextEpoch, consensus.ChainReader, nextEpoch, consensus.ChainReader,
@ -514,15 +512,33 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode {
utils.Logger().Error(). utils.Logger().Error().
Err(err). Err(err).
Uint32("shard", consensus.ShardID). Uint32("shard", consensus.ShardID).
Msg("[UpdateConsensusInformation] Error retrieving nextEpoch shard state") Msg("Error retrieving nextEpoch shard state")
return Syncing
}
subComm, err := nextShardState.FindCommitteeByID(curHeader.ShardID())
if err != nil {
utils.Logger().Error().
Err(err).
Uint32("shard", consensus.ShardID).
Msg("Error retrieving nextEpoch shard state")
return Syncing return Syncing
} }
committeeToSet = nextShardState.FindCommitteeByID(curHeader.ShardID()) committeeToSet = subComm
} else { } else {
consensus.SetEpochNum(curEpoch.Uint64()) consensus.SetEpochNum(curEpoch.Uint64())
committeeToSet = curShardState.FindCommitteeByID(curHeader.ShardID()) subComm, err := curShardState.FindCommitteeByID(curHeader.ShardID())
if err != nil {
utils.Logger().Error().
Err(err).
Uint32("shard", consensus.ShardID).
Msg("Error retrieving current shard state")
return Syncing
}
committeeToSet = subComm
} }
if len(committeeToSet.Slots) == 0 { if len(committeeToSet.Slots) == 0 {

@ -65,13 +65,19 @@ func TestConstructPreparedMessage(test *testing.T) {
leaderPubKey, leaderPubKey,
leaderPriKey.Sign(message), leaderPriKey.Sign(message),
common.BytesToHash(consensus.blockHash[:]), common.BytesToHash(consensus.blockHash[:]),
consensus.blockNum,
consensus.viewID,
) )
consensus.Decider.SubmitVote( if _, err := consensus.Decider.SubmitVote(
quorum.Prepare, quorum.Prepare,
validatorPubKey, validatorPubKey,
validatorPriKey.Sign(message), validatorPriKey.Sign(message),
common.BytesToHash(consensus.blockHash[:]), common.BytesToHash(consensus.blockHash[:]),
) consensus.blockNum,
consensus.viewID,
); err != nil {
test.Log(err)
}
// According to RJ these failures are benign. // According to RJ these failures are benign.
if err := consensus.prepareBitmap.SetKey(leaderPubKey, true); err != nil { if err := consensus.prepareBitmap.SetKey(leaderPubKey, true); err != nil {

@ -53,6 +53,8 @@ type ChainReader interface {
// Methods needed for EPoS committee assignment calculation // Methods needed for EPoS committee assignment calculation
committee.StakingCandidatesReader committee.StakingCandidatesReader
// Methods for reading right epoch snapshot
staking.ValidatorSnapshotReader
//ReadBlockRewardAccumulator is the block-reward given for block number //ReadBlockRewardAccumulator is the block-reward given for block number
ReadBlockRewardAccumulator(uint64) (*big.Int, error) ReadBlockRewardAccumulator(uint64) (*big.Int, error)

@ -62,12 +62,16 @@ func (consensus *Consensus) announce(block *types.Block) {
// Leader sign the block hash itself // Leader sign the block hash itself
for i, key := range consensus.PubKey.PublicKey { for i, key := range consensus.PubKey.PublicKey {
consensus.Decider.SubmitVote( if _, err := consensus.Decider.SubmitVote(
quorum.Prepare, quorum.Prepare,
key, key,
consensus.priKey.PrivateKey[i].SignHash(consensus.blockHash[:]), consensus.priKey.PrivateKey[i].SignHash(consensus.blockHash[:]),
common.BytesToHash(consensus.blockHash[:]), common.BytesToHash(consensus.blockHash[:]),
) consensus.blockNum,
consensus.viewID,
); err != nil {
return
}
if err := consensus.prepareBitmap.SetKey(key, true); err != nil { if err := consensus.prepareBitmap.SetKey(key, true); err != nil {
consensus.getLogger().Warn().Err(err).Msg( consensus.getLogger().Warn().Err(err).Msg(
"[Announce] Leader prepareBitmap SetKey failed", "[Announce] Leader prepareBitmap SetKey failed",
@ -166,9 +170,14 @@ func (consensus *Consensus) onPrepare(msg *msg_pb.Message) {
Int64("NumReceivedSoFar", consensus.Decider.SignersCount(quorum.Prepare)). Int64("NumReceivedSoFar", consensus.Decider.SignersCount(quorum.Prepare)).
Int64("PublicKeys", consensus.Decider.ParticipantsCount()).Logger() Int64("PublicKeys", consensus.Decider.ParticipantsCount()).Logger()
logger.Info().Msg("[OnPrepare] Received New Prepare Signature") logger.Info().Msg("[OnPrepare] Received New Prepare Signature")
consensus.Decider.SubmitVote( if _, err := consensus.Decider.SubmitVote(
quorum.Prepare, validatorPubKey, &sign, recvMsg.BlockHash, quorum.Prepare, validatorPubKey,
) &sign, recvMsg.BlockHash,
recvMsg.BlockNum, recvMsg.ViewID,
); err != nil {
consensus.getLogger().Warn().Err(err).Msg("submit vote prepare failed")
return
}
// Set the bitmap indicating that this validator signed. // Set the bitmap indicating that this validator signed.
if err := prepareBitmap.SetKey(recvMsg.SenderPubkey, true); err != nil { if err := prepareBitmap.SetKey(recvMsg.SenderPubkey, true); err != nil {
consensus.getLogger().Warn().Err(err).Msg("[OnPrepare] prepareBitmap.SetKey failed") consensus.getLogger().Warn().Err(err).Msg("[OnPrepare] prepareBitmap.SetKey failed")
@ -235,9 +244,17 @@ func (consensus *Consensus) onCommit(msg *msg_pb.Message) {
return return
} }
offender := *shard.FromLibBLSPublicKeyUnsafe(recvMsg.SenderPubkey) offender := *shard.FromLibBLSPublicKeyUnsafe(recvMsg.SenderPubkey)
addr, err := committee.FindCommitteeByID( subComm, err := committee.FindCommitteeByID(
consensus.ShardID, consensus.ShardID,
).AddressForBLSKey(offender) )
if err != nil {
log.Err(err).
Str("msg", recvMsg.String()).
Msg("could not find subcommittee for bls key")
return
}
addr, err := subComm.AddressForBLSKey(offender)
if err != nil { if err != nil {
log.Err(err).Str("msg", recvMsg.String()). log.Err(err).Str("msg", recvMsg.String()).
@ -252,16 +269,14 @@ func (consensus *Consensus) onCommit(msg *msg_pb.Message) {
ConflictingBallots: slash.ConflictingBallots{ ConflictingBallots: slash.ConflictingBallots{
*alreadyCastBallot, *alreadyCastBallot,
votepower.Ballot{ votepower.Ballot{
offender, SignerPubKey: offender,
recvMsg.BlockHash, BlockHeaderHash: recvMsg.BlockHash,
common.Hex2Bytes(doubleSign.SerializeToHexStr()), Signature: common.Hex2Bytes(doubleSign.SerializeToHexStr()),
Height: recvMsg.BlockNum,
ViewID: recvMsg.ViewID,
}}, }},
Moment: slash.Moment{ Moment: slash.Moment{
// TODO need to extend fbft tro have epoch to use its epoch
// rather than curHeader epoch
Epoch: curHeader.Epoch(), Epoch: curHeader.Epoch(),
Height: new(big.Int).SetUint64(recvMsg.BlockNum),
ViewID: consensus.viewID,
ShardID: consensus.ShardID, ShardID: consensus.ShardID,
TimeUnixNano: now, TimeUnixNano: now,
}, },
@ -314,9 +329,13 @@ func (consensus *Consensus) onCommit(msg *msg_pb.Message) {
Logger() Logger()
logger.Info().Msg("[OnCommit] Received new commit message") logger.Info().Msg("[OnCommit] Received new commit message")
consensus.Decider.SubmitVote( if _, err := consensus.Decider.SubmitVote(
quorum.Commit, validatorPubKey, &sign, recvMsg.BlockHash, quorum.Commit, validatorPubKey,
) &sign, recvMsg.BlockHash,
recvMsg.BlockNum, recvMsg.ViewID,
); err != nil {
return
}
// Set the bitmap indicating that this validator signed. // Set the bitmap indicating that this validator signed.
if err := commitBitmap.SetKey(recvMsg.SenderPubkey, true); err != nil { if err := commitBitmap.SetKey(recvMsg.SenderPubkey, true); err != nil {
consensus.getLogger().Warn().Err(err). consensus.getLogger().Warn().Err(err).

@ -93,17 +93,14 @@ func (v *uniformVoteWeight) String() string {
} }
func (v *uniformVoteWeight) MarshalJSON() ([]byte, error) { func (v *uniformVoteWeight) MarshalJSON() ([]byte, error) {
s, _ := v.ShardIDProvider()()
type t struct { type t struct {
Policy string `json:"policy"` Policy string `json:"policy"`
ShardID uint32 `json:"shard-id"`
Count int `json:"count"` Count int `json:"count"`
Participants []string `json:"committee-members"` Participants []string `json:"committee-members"`
} }
members := v.DumpParticipants() members := v.DumpParticipants()
return json.Marshal(t{v.Policy().String(), s, len(members), members}) return json.Marshal(t{v.Policy().String(), len(members), members})
} }
func (v *uniformVoteWeight) AmIMemberOfCommitee() bool { func (v *uniformVoteWeight) AmIMemberOfCommitee() bool {

@ -182,7 +182,6 @@ func (v *stakedVoteWeight) String() string {
} }
func (v *stakedVoteWeight) MarshalJSON() ([]byte, error) { func (v *stakedVoteWeight) MarshalJSON() ([]byte, error) {
s, _ := v.ShardIDProvider()()
voterCount := len(v.roster.Voters) voterCount := len(v.roster.Voters)
type u struct { type u struct {
IsHarmony bool `json:"is-harmony-slot"` IsHarmony bool `json:"is-harmony-slot"`
@ -195,7 +194,6 @@ func (v *stakedVoteWeight) MarshalJSON() ([]byte, error) {
type t struct { type t struct {
Policy string `json"policy"` Policy string `json"policy"`
ShardID uint32 `json:"shard-id"`
Count int `json:"count"` Count int `json:"count"`
Participants []u `json:"committee-members"` Participants []u `json:"committee-members"`
HmyVotingPower string `json:"hmy-voting-power"` HmyVotingPower string `json:"hmy-voting-power"`
@ -224,7 +222,6 @@ func (v *stakedVoteWeight) MarshalJSON() ([]byte, error) {
return json.Marshal(t{ return json.Marshal(t{
v.Policy().String(), v.Policy().String(),
s,
voterCount, voterCount,
parts, parts,
v.roster.OurVotingPowerTotalPercentage.String(), v.roster.OurVotingPowerTotalPercentage.String(),

@ -64,7 +64,6 @@ func setupBaseCase() (Decider, *TallyResult, shard.SlotList, map[string]secretKe
} }
decider := NewDecider(SuperMajorityStake) decider := NewDecider(SuperMajorityStake)
decider.SetShardIDProvider(func() (uint32, error) { return 0, nil })
decider.UpdateParticipants(pubKeys) decider.UpdateParticipants(pubKeys)
tally, err := decider.SetVoters(slotList) tally, err := decider.SetVoters(slotList)
if err != nil { if err != nil {
@ -90,7 +89,6 @@ func setupEdgeCase() (Decider, *TallyResult, shard.SlotList, secretKeyMap) {
} }
decider := NewDecider(SuperMajorityStake) decider := NewDecider(SuperMajorityStake)
decider.SetShardIDProvider(func() (uint32, error) { return 0, nil })
decider.UpdateParticipants(pubKeys) decider.UpdateParticipants(pubKeys)
tally, err := decider.SetVoters(slotList) tally, err := decider.SetVoters(slotList)
if err != nil { if err != nil {
@ -104,7 +102,7 @@ func sign(d Decider, k secretKeyMap, p Phase) {
pubKey := v.GetPublicKey() pubKey := v.GetPublicKey()
sig := v.Sign(msg) sig := v.Sign(msg)
// TODO Make upstream test provide meaningful test values // TODO Make upstream test provide meaningful test values
d.SubmitVote(p, pubKey, sig, common.Hash{}) d.SubmitVote(p, pubKey, sig, common.Hash{}, 0, 0)
} }
} }

@ -7,7 +7,6 @@ import (
"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/consensus/votepower"
bls_cosi "github.com/harmony-one/harmony/crypto/bls" bls_cosi "github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/multibls" "github.com/harmony-one/harmony/multibls"
"github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
@ -26,11 +25,14 @@ const (
ViewChange ViewChange
) )
var phaseNames = map[Phase]string{ var (
Prepare: "Prepare", phaseNames = map[Phase]string{
Commit: "Commit", Prepare: "Prepare",
ViewChange: "viewChange", Commit: "Commit",
} ViewChange: "viewChange",
}
errPhaseUnknown = errors.New("invariant of known phase violated")
)
func (p Phase) String() string { func (p Phase) String() string {
if name, ok := phaseNames[p]; ok { if name, ok := phaseNames[p]; ok {
@ -76,8 +78,10 @@ type ParticipantTracker interface {
type SignatoryTracker interface { type SignatoryTracker interface {
ParticipantTracker ParticipantTracker
SubmitVote( SubmitVote(
p Phase, PubKey *bls.PublicKey, sig *bls.Sign, headerHash common.Hash, p Phase, PubKey *bls.PublicKey,
) *votepower.Ballot sig *bls.Sign, headerHash common.Hash,
height, viewID uint64,
) (*votepower.Ballot, error)
// Caller assumes concurrency protection // Caller assumes concurrency protection
SignersCount(Phase) int64 SignersCount(Phase) int64
reset([]Phase) reset([]Phase)
@ -95,13 +99,11 @@ type SignatureReader interface {
// DependencyInjectionWriter .. // DependencyInjectionWriter ..
type DependencyInjectionWriter interface { type DependencyInjectionWriter interface {
SetShardIDProvider(func() (uint32, error))
SetMyPublicKeyProvider(func() (*multibls.PublicKey, error)) SetMyPublicKeyProvider(func() (*multibls.PublicKey, error))
} }
// DependencyInjectionReader .. // DependencyInjectionReader ..
type DependencyInjectionReader interface { type DependencyInjectionReader interface {
ShardIDProvider() func() (uint32, error)
MyPublicKey() func() (*multibls.PublicKey, error) MyPublicKey() func() (*multibls.PublicKey, error)
} }
@ -225,12 +227,16 @@ func (s *cIdentities) SignersCount(p Phase) int64 {
} }
func (s *cIdentities) SubmitVote( func (s *cIdentities) SubmitVote(
p Phase, PubKey *bls.PublicKey, sig *bls.Sign, headerHash common.Hash, p Phase, PubKey *bls.PublicKey,
) *votepower.Ballot { sig *bls.Sign, headerHash common.Hash,
height, viewID uint64,
) (*votepower.Ballot, error) {
ballot := &votepower.Ballot{ ballot := &votepower.Ballot{
SignerPubKey: *shard.FromLibBLSPublicKeyUnsafe(PubKey), SignerPubKey: *shard.FromLibBLSPublicKeyUnsafe(PubKey),
BlockHeaderHash: headerHash, BlockHeaderHash: headerHash,
Signature: common.Hex2Bytes(sig.SerializeToHexStr()), Signature: common.Hex2Bytes(sig.SerializeToHexStr()),
Height: height,
ViewID: viewID,
} }
switch hex := PubKey.SerializeToHexStr(); p { switch hex := PubKey.SerializeToHexStr(); p {
case Prepare: case Prepare:
@ -240,11 +246,9 @@ func (s *cIdentities) SubmitVote(
case ViewChange: case ViewChange:
s.viewChange.BallotBox[hex] = ballot s.viewChange.BallotBox[hex] = ballot
default: default:
utils.Logger().Err(errors.New("invariant of known phase violated")). return nil, errors.Wrapf(errPhaseUnknown, "given: %s", p.String())
Str("phase", p.String()).
Msg("bad vote input")
} }
return ballot return ballot, nil
} }
func (s *cIdentities) reset(ps []Phase) { func (s *cIdentities) reset(ps []Phase) {
@ -316,14 +320,6 @@ type composite struct {
SignatureReader SignatureReader
} }
func (d *depInject) SetShardIDProvider(p func() (uint32, error)) {
d.shardIDProvider = p
}
func (d *depInject) ShardIDProvider() func() (uint32, error) {
return d.shardIDProvider
}
func (d *depInject) SetMyPublicKeyProvider(p func() (*multibls.PublicKey, error)) { func (d *depInject) SetMyPublicKeyProvider(p func() (*multibls.PublicKey, error)) {
d.publicKeyProvider = p d.publicKeyProvider = p
} }

@ -20,7 +20,9 @@ func (consensus *Consensus) didReachPrepareQuorum() error {
return err return err
} }
// Construct and broadcast prepared message // Construct and broadcast prepared message
networkMessage, err := consensus.construct(msg_pb.MessageType_PREPARED, nil, consensus.LeaderPubKey, leaderPriKey) networkMessage, err := consensus.construct(
msg_pb.MessageType_PREPARED, nil, consensus.LeaderPubKey, leaderPriKey,
)
if err != nil { if err != nil {
consensus.getLogger().Err(err). consensus.getLogger().Err(err).
Str("message-type", msg_pb.MessageType_PREPARED.String()). Str("message-type", msg_pb.MessageType_PREPARED.String()).
@ -42,12 +44,16 @@ func (consensus *Consensus) didReachPrepareQuorum() error {
// so by this point, everyone has committed to the blockhash of this block // so by this point, everyone has committed to the blockhash of this block
// in prepare and so this is the actual block. // in prepare and so this is the actual block.
for i, key := range consensus.PubKey.PublicKey { for i, key := range consensus.PubKey.PublicKey {
consensus.Decider.SubmitVote( if _, err := consensus.Decider.SubmitVote(
quorum.Commit, quorum.Commit,
key, key,
consensus.priKey.PrivateKey[i].SignHash(commitPayload), consensus.priKey.PrivateKey[i].SignHash(commitPayload),
common.BytesToHash(consensus.blockHash[:]), common.BytesToHash(consensus.blockHash[:]),
) consensus.blockNum,
consensus.viewID,
); err != nil {
return err
}
if err := consensus.commitBitmap.SetKey(key, true); err != nil { if err := consensus.commitBitmap.SetKey(key, true); err != nil {
consensus.getLogger().Debug().Msg("[OnPrepare] Leader commit bitmap set failed") consensus.getLogger().Debug().Msg("[OnPrepare] Leader commit bitmap set failed")

@ -380,14 +380,19 @@ func (consensus *Consensus) onViewChange(msg *msg_pb.Message) {
blockNumBytes := [8]byte{} blockNumBytes := [8]byte{}
binary.LittleEndian.PutUint64(blockNumBytes[:], consensus.blockNum) binary.LittleEndian.PutUint64(blockNumBytes[:], consensus.blockNum)
commitPayload := append(blockNumBytes[:], consensus.blockHash[:]...) commitPayload := append(blockNumBytes[:], consensus.blockHash[:]...)
consensus.Decider.SubmitVote( if _, err := consensus.Decider.SubmitVote(
quorum.Commit, quorum.Commit,
newLeaderKey, newLeaderKey,
newLeaderPriKey.SignHash(commitPayload), newLeaderPriKey.SignHash(commitPayload),
common.BytesToHash(consensus.blockHash[:]), common.BytesToHash(consensus.blockHash[:]),
) consensus.blockNum,
recvMsg.ViewID,
); err != nil {
consensus.getLogger().Debug().Msg("submit vote on viewchange commit failed")
return
}
if err = consensus.commitBitmap.SetKey(newLeaderKey, true); err != nil { if err := consensus.commitBitmap.SetKey(newLeaderKey, true); err != nil {
consensus.getLogger().Debug(). consensus.getLogger().Debug().
Msg("[OnViewChange] New Leader commit bitmap set failed") Msg("[OnViewChange] New Leader commit bitmap set failed")
return return
@ -395,7 +400,9 @@ func (consensus *Consensus) onViewChange(msg *msg_pb.Message) {
} }
consensus.current.SetViewID(recvMsg.ViewID) consensus.current.SetViewID(recvMsg.ViewID)
msgToSend := consensus.constructNewViewMessage(recvMsg.ViewID, newLeaderKey, newLeaderPriKey) msgToSend := consensus.constructNewViewMessage(
recvMsg.ViewID, newLeaderKey, newLeaderPriKey,
)
consensus.getLogger().Warn(). consensus.getLogger().Warn().
Int("payloadSize", len(consensus.m1Payload)). Int("payloadSize", len(consensus.m1Payload)).

@ -27,6 +27,8 @@ type Ballot struct {
SignerPubKey shard.BlsPublicKey `json:"bls-public-key"` SignerPubKey shard.BlsPublicKey `json:"bls-public-key"`
BlockHeaderHash common.Hash `json:"block-header-hash"` BlockHeaderHash common.Hash `json:"block-header-hash"`
Signature []byte `json:"bls-signature"` Signature []byte `json:"bls-signature"`
Height uint64 `json:"block-height"`
ViewID uint64 `json:"view-id"`
} }
// MarshalJSON .. // MarshalJSON ..
@ -35,10 +37,14 @@ func (b Ballot) MarshalJSON() ([]byte, error) {
A string `json:"bls-public-key"` A string `json:"bls-public-key"`
B string `json:"block-header-hash"` B string `json:"block-header-hash"`
C string `json:"bls-signature"` C string `json:"bls-signature"`
E uint64 `json:"block-height"`
F uint64 `json:"view-id"`
}{ }{
b.SignerPubKey.Hex(), b.SignerPubKey.Hex(),
b.BlockHeaderHash.Hex(), b.BlockHeaderHash.Hex(),
hex.EncodeToString(b.Signature), hex.EncodeToString(b.Signature),
b.Height,
b.ViewID,
}) })
} }

@ -60,6 +60,8 @@ var (
blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
// ErrNoGenesis is the error when there is no genesis. // ErrNoGenesis is the error when there is no genesis.
ErrNoGenesis = errors.New("Genesis not found in chain") ErrNoGenesis = errors.New("Genesis not found in chain")
// errExceedMaxPendingSlashes ..
errExceedMaxPendingSlashes = errors.New("exceeed max pending slashes")
) )
const ( const (
@ -80,15 +82,10 @@ const (
validatorListByDelegatorCacheLimit = 1024 validatorListByDelegatorCacheLimit = 1024
pendingCrossLinksCacheLimit = 2 pendingCrossLinksCacheLimit = 2
blockAccumulatorCacheLimit = 256 blockAccumulatorCacheLimit = 256
pendingSlashingCandidateCacheLimit = 2 maxPendingSlashes = 512
// BlockChainVersion ensures that an incompatible database forces a resync from scratch. // BlockChainVersion ensures that an incompatible database forces a resync from scratch.
BlockChainVersion = 3 BlockChainVersion = 3
)
const (
pendingCLCacheKey = "pendingCLs" pendingCLCacheKey = "pendingCLs"
pendingSCCacheKey = "pendingSCs"
) )
// CacheConfig contains the configuration values for the trie caching/pruning // CacheConfig contains the configuration values for the trie caching/pruning
@ -148,35 +145,37 @@ type BlockChain struct {
futureBlocks *lru.Cache // future blocks are blocks added for later processing futureBlocks *lru.Cache // future blocks are blocks added for later processing
shardStateCache *lru.Cache shardStateCache *lru.Cache
lastCommitsCache *lru.Cache lastCommitsCache *lru.Cache
epochCache *lru.Cache // Cache epoch number → first block number epochCache *lru.Cache // Cache epoch number → first block number
randomnessCache *lru.Cache // Cache for vrf/vdf randomnessCache *lru.Cache // Cache for vrf/vdf
validatorCache *lru.Cache // Cache for validator info validatorCache *lru.Cache // Cache for validator info
validatorStatsCache *lru.Cache // Cache for validator stats validatorStatsCache *lru.Cache // Cache for validator stats
validatorListCache *lru.Cache // Cache of validator list validatorListCache *lru.Cache // Cache of validator list
validatorListByDelegatorCache *lru.Cache // Cache of validator list by delegator validatorListByDelegatorCache *lru.Cache // Cache of validator list by delegator
pendingCrossLinksCache *lru.Cache // Cache of last pending crosslinks pendingCrossLinksCache *lru.Cache // Cache of last pending crosslinks
blockAccumulatorCache *lru.Cache // Cache of block accumulators blockAccumulatorCache *lru.Cache // Cache of block accumulators
pendingSlashingCandidates *lru.Cache // Cache of last pending slashing candidates quit chan struct{} // blockchain quit channel
running int32 // running must be called atomically
quit chan struct{} // blockchain quit channel
running int32 // running must be called atomically
// procInterrupt must be atomically called // procInterrupt must be atomically called
procInterrupt int32 // interrupt signaler for block processing procInterrupt int32 // interrupt signaler for block processing
wg sync.WaitGroup // chain processing wait group for shutting down wg sync.WaitGroup // chain processing wait group for shutting down
engine consensus_engine.Engine engine consensus_engine.Engine
processor Processor // block processor interface processor Processor // block processor interface
validator Validator // block and state validator interface validator Validator // block and state validator interface
vmConfig vm.Config vmConfig vm.Config
badBlocks *lru.Cache // Bad block cache badBlocks *lru.Cache // Bad block cache
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
pendingSlashes slash.Records
} }
// NewBlockChain returns a fully initialised block chain using information // NewBlockChain returns a fully initialised block chain using information
// available in the database. It initialises the default Ethereum Validator and // available in the database. It initialises the default Ethereum Validator and
// Processor. // Processor.
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus_engine.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool) (*BlockChain, error) { func NewBlockChain(
db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig,
engine consensus_engine.Engine, vmConfig vm.Config,
shouldPreserve func(block *types.Block) bool,
) (*BlockChain, error) {
if cacheConfig == nil { if cacheConfig == nil {
cacheConfig = &CacheConfig{ cacheConfig = &CacheConfig{
TrieNodeLimit: 256 * 1024 * 1024, TrieNodeLimit: 256 * 1024 * 1024,
@ -199,7 +198,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
validatorListByDelegatorCache, _ := lru.New(validatorListByDelegatorCacheLimit) validatorListByDelegatorCache, _ := lru.New(validatorListByDelegatorCacheLimit)
pendingCrossLinksCache, _ := lru.New(pendingCrossLinksCacheLimit) pendingCrossLinksCache, _ := lru.New(pendingCrossLinksCacheLimit)
blockAccumulatorCache, _ := lru.New(blockAccumulatorCacheLimit) blockAccumulatorCache, _ := lru.New(blockAccumulatorCacheLimit)
pendingSlashingCandidateCache, _ := lru.New(pendingSlashingCandidateCacheLimit)
bc := &BlockChain{ bc := &BlockChain{
chainConfig: chainConfig, chainConfig: chainConfig,
@ -223,11 +221,11 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
validatorListCache: validatorListCache, validatorListCache: validatorListCache,
validatorListByDelegatorCache: validatorListByDelegatorCache, validatorListByDelegatorCache: validatorListByDelegatorCache,
pendingCrossLinksCache: pendingCrossLinksCache, pendingCrossLinksCache: pendingCrossLinksCache,
pendingSlashingCandidates: pendingSlashingCandidateCache,
blockAccumulatorCache: blockAccumulatorCache, blockAccumulatorCache: blockAccumulatorCache,
engine: engine, engine: engine,
vmConfig: vmConfig, vmConfig: vmConfig,
badBlocks: badBlocks, badBlocks: badBlocks,
pendingSlashes: slash.Records{},
} }
bc.SetValidator(NewBlockValidator(chainConfig, bc, engine)) bc.SetValidator(NewBlockValidator(chainConfig, bc, engine))
bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine)) bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine))
@ -1940,56 +1938,38 @@ func (bc *BlockChain) ReadShardLastCrossLink(shardID uint32) (*types.CrossLink,
return types.DeserializeCrossLink(bytes) return types.DeserializeCrossLink(bytes)
} }
// DeletePendingSlashingCandidates .. func (bc *BlockChain) writeSlashes(processed slash.Records) error {
func (bc *BlockChain) DeletePendingSlashingCandidates() error { bytes, err := rlp.EncodeToBytes(processed)
bc.pendingSlashingCandidatesMU.Lock()
defer bc.pendingSlashingCandidatesMU.Unlock()
bc.pendingSlashingCandidates.Purge()
return bc.WritePendingSlashingCandidates(slash.Records{})
}
// ReadPendingSlashingCandidates retrieves pending slashing candidates
func (bc *BlockChain) ReadPendingSlashingCandidates() (slash.Records, error) {
cls := slash.Records{}
if !bc.Config().IsStaking(bc.CurrentHeader().Epoch()) {
return cls, nil
}
var err error
bytes := []byte{}
if cached, ok := bc.pendingSlashingCandidates.Get(pendingSCCacheKey); ok {
bytes = cached.([]byte)
} else {
bytes, err = rawdb.ReadPendingSlashingCandidates(bc.db)
if err != nil || len(bytes) == 0 {
utils.Logger().Info().Err(err).
Int("dataLen", len(bytes)).
Msg("ReadPendingSlashingCandidates")
return nil, err
}
}
if err := rlp.DecodeBytes(bytes, &cls); err != nil {
utils.Logger().Error().Err(err).Msg("Invalid pending slashing candidates RLP decoding")
return nil, err
}
return cls, nil
}
// WritePendingSlashingCandidates saves the pending slashing candidates
func (bc *BlockChain) WritePendingSlashingCandidates(candidates slash.Records) error {
bytes, err := rlp.EncodeToBytes(candidates)
if err != nil { if err != nil {
const msg = "[WritePendingSlashingCandidates] Failed to encode pending slashing candidates" const msg = "failed to encode slashing candidates"
utils.Logger().Error().Msg(msg) utils.Logger().Error().Msg(msg)
return err return err
} }
if err := rawdb.WritePendingSlashingCandidates(bc.db, bytes); err != nil { if err := rawdb.WritePendingSlashingCandidates(bc.db, bytes); err != nil {
return err return err
} }
bc.pendingSlashingCandidates.Add(pendingSCCacheKey, bytes)
return nil return nil
} }
// DeleteFromPendingSlashingCandidates ..
func (bc *BlockChain) DeleteFromPendingSlashingCandidates(
processed slash.Records,
) error {
bc.pendingSlashingCandidatesMU.Lock()
defer bc.pendingSlashingCandidatesMU.Unlock()
current := bc.ReadPendingSlashingCandidates()
bc.pendingSlashes = current.SetDifference(processed)
return bc.writeSlashes(bc.pendingSlashes)
}
// ReadPendingSlashingCandidates retrieves pending slashing candidates
func (bc *BlockChain) ReadPendingSlashingCandidates() slash.Records {
if !bc.Config().IsStaking(bc.CurrentHeader().Epoch()) {
return slash.Records{}
}
return append(bc.pendingSlashes[0:0], bc.pendingSlashes...)
}
// ReadPendingCrossLinks retrieves pending crosslinks // ReadPendingCrossLinks retrieves pending crosslinks
func (bc *BlockChain) ReadPendingCrossLinks() ([]types.CrossLink, error) { func (bc *BlockChain) ReadPendingCrossLinks() ([]types.CrossLink, error) {
bytes := []byte{} bytes := []byte{}
@ -2047,25 +2027,21 @@ func (bc *BlockChain) WritePendingCrossLinks(crossLinks []types.CrossLink) error
// AddPendingSlashingCandidates appends pending slashing candidates // AddPendingSlashingCandidates appends pending slashing candidates
func (bc *BlockChain) AddPendingSlashingCandidates( func (bc *BlockChain) AddPendingSlashingCandidates(
candidates []slash.Record, candidates slash.Records,
) (int, error) { ) error {
bc.pendingSlashingCandidatesMU.Lock() bc.pendingSlashingCandidatesMU.Lock()
defer bc.pendingSlashingCandidatesMU.Unlock() defer bc.pendingSlashingCandidatesMU.Unlock()
cls, err := bc.ReadPendingSlashingCandidates() current := bc.ReadPendingSlashingCandidates()
pendingSlashes := append(
if err != nil || len(cls) == 0 { bc.pendingSlashes, current.SetDifference(candidates)...,
err := bc.WritePendingSlashingCandidates(candidates) )
if err != nil { if l, c := len(pendingSlashes), len(current); l > maxPendingSlashes {
return 0, err return errors.Wrapf(
} errExceedMaxPendingSlashes, "current %d with-additional %d", c, l,
return 1, err )
}
cls = append(cls, candidates...)
if err := bc.WritePendingSlashingCandidates(cls); err != nil {
return 0, err
} }
return len(cls), nil bc.pendingSlashes = pendingSlashes
return bc.writeSlashes(bc.pendingSlashes)
} }
// AddPendingCrossLinks appends pending crosslinks // AddPendingCrossLinks appends pending crosslinks
@ -2219,6 +2195,15 @@ func (bc *BlockChain) ReadValidatorInformation(
return bc.ReadValidatorInformationAt(addr, bc.CurrentBlock().Root()) return bc.ReadValidatorInformationAt(addr, bc.CurrentBlock().Root())
} }
// ReadValidatorSnapshotAtEpoch reads the snapshot
// staking validator information of given validator address
func (bc *BlockChain) ReadValidatorSnapshotAtEpoch(
epoch *big.Int,
addr common.Address,
) (*staking.ValidatorWrapper, error) {
return rawdb.ReadValidatorSnapshot(bc.db, addr, epoch)
}
// ReadValidatorSnapshot reads the snapshot staking information of given validator address // ReadValidatorSnapshot reads the snapshot staking information of given validator address
func (bc *BlockChain) ReadValidatorSnapshot( func (bc *BlockChain) ReadValidatorSnapshot(
addr common.Address, addr common.Address,
@ -2607,8 +2592,8 @@ func (bc *BlockChain) GetECDSAFromCoinbase(header *block.Header) (common.Address
).WithCause(err) ).WithCause(err)
} }
committee := shardState.FindCommitteeByID(header.ShardID()) committee, err := shardState.FindCommitteeByID(header.ShardID())
if committee == nil { if err != nil {
return common.Address{}, ctxerror.New("cannot find shard in the shard state", return common.Address{}, ctxerror.New("cannot find shard in the shard state",
"blockNum", header.Number(), "blockNum", header.Number(),
"shardID", header.ShardID(), "shardID", header.ShardID(),

@ -286,15 +286,27 @@ func (cr *fakeChainReader) ReadShardState(epoch *big.Int) (*shard.State, error)
func (cr *fakeChainReader) ReadElectedValidatorList() ([]common.Address, error) { return nil, nil } func (cr *fakeChainReader) ReadElectedValidatorList() ([]common.Address, error) { return nil, nil }
func (cr *fakeChainReader) ReadValidatorList() ([]common.Address, error) { return nil, nil } func (cr *fakeChainReader) ReadValidatorList() ([]common.Address, error) { return nil, nil }
func (cr *fakeChainReader) ValidatorCandidates() []common.Address { return nil } func (cr *fakeChainReader) ValidatorCandidates() []common.Address { return nil }
func (cr *fakeChainReader) SuperCommitteeForNextEpoch(beacon consensus_engine.ChainReader, header *block.Header, isVerify bool) (*shard.State, error) { func (cr *fakeChainReader) SuperCommitteeForNextEpoch(
beacon consensus_engine.ChainReader, header *block.Header, isVerify bool,
) (*shard.State, error) {
return nil, nil return nil, nil
} }
func (cr *fakeChainReader) ReadValidatorInformation(addr common.Address) (*staking.ValidatorWrapper, error) { func (cr *fakeChainReader) ReadValidatorInformation(
addr common.Address,
) (*staking.ValidatorWrapper, error) {
return nil, nil return nil, nil
} }
func (cr *fakeChainReader) ReadValidatorSnapshot(addr common.Address) (*staking.ValidatorWrapper, error) { func (cr *fakeChainReader) ReadValidatorSnapshot(
addr common.Address,
) (*staking.ValidatorWrapper, error) {
return nil, nil return nil, nil
} }
func (cr *fakeChainReader) ReadValidatorSnapshotAtEpoch(
epoch *big.Int, addr common.Address,
) (*staking.ValidatorWrapper, error) {
return nil, nil
}
func (cr *fakeChainReader) ReadBlockRewardAccumulator(uint64) (*big.Int, error) { return nil, nil } func (cr *fakeChainReader) ReadBlockRewardAccumulator(uint64) (*big.Int, error) { return nil, nil }
func (cr *fakeChainReader) ValidatorStakingWithDelegation(addr common.Address) *big.Int { return nil } func (cr *fakeChainReader) ValidatorStakingWithDelegation(addr common.Address) *big.Int { return nil }
func (cr *fakeChainReader) ReadValidatorStats(addr common.Address) (*staking.ValidatorStats, error) { func (cr *fakeChainReader) ReadValidatorStats(addr common.Address) (*staking.ValidatorStats, error) {

@ -10,6 +10,7 @@ import (
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
"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/staking/slash"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -147,22 +148,25 @@ func (bc *BlockChain) CommitOffChainData(
bc.chainConfig.IsCrossLink(block.Epoch()) && bc.chainConfig.IsCrossLink(block.Epoch()) &&
len(header.CrossLinks()) > 0 { len(header.CrossLinks()) > 0 {
crossLinks := &types.CrossLinks{} crossLinks := &types.CrossLinks{}
err = rlp.DecodeBytes(header.CrossLinks(), crossLinks) if err := rlp.DecodeBytes(
if err != nil { header.CrossLinks(), crossLinks,
header.Logger( ); err != nil {
utils.Logger()). header.Logger(utils.Logger()).
Warn().Err(err). Warn().Err(err).
Msg("[insertChain/crosslinks] cannot parse cross links") Msg("[insertChain/crosslinks] cannot parse cross links")
return NonStatTy, err return NonStatTy, err
} }
if !crossLinks.IsSorted() { if !crossLinks.IsSorted() {
header.Logger(utils.Logger()).Warn(). header.Logger(utils.Logger()).
Err(err).Msg("[insertChain/crosslinks] cross links are not sorted") Warn().Err(err).
Msg("[insertChain/crosslinks] cross links are not sorted")
return NonStatTy, errors.New("proposed cross links are not sorted") return NonStatTy, errors.New("proposed cross links are not sorted")
} }
for _, crossLink := range *crossLinks { for _, crossLink := range *crossLinks {
// Process crosslink // Process crosslink
if err := bc.WriteCrossLinks(batch, types.CrossLinks{crossLink}); err == nil { if err := bc.WriteCrossLinks(
batch, types.CrossLinks{crossLink},
); err == nil {
utils.Logger().Info(). utils.Logger().Info().
Uint64("blockNum", crossLink.BlockNum()). Uint64("blockNum", crossLink.BlockNum()).
Uint32("shardID", crossLink.ShardID()). Uint32("shardID", crossLink.ShardID()).
@ -185,6 +189,7 @@ func (bc *BlockChain) CommitOffChainData(
utils.Logger(). utils.Logger().
Debug(). Debug().
Msgf(msg, len(*crossLinks), num) Msgf(msg, len(*crossLinks), num)
utils.Logger().Debug().Msgf(msg, len(*crossLinks), num)
} }
// Roll up latest crosslinks // Roll up latest crosslinks
for i := uint32(0); i < shard.Schedule.InstanceForEpoch(epoch).NumShards(); i++ { for i := uint32(0); i < shard.Schedule.InstanceForEpoch(epoch).NumShards(); i++ {
@ -198,12 +203,18 @@ func (bc *BlockChain) CommitOffChainData(
); err != nil { ); err != nil {
return NonStatTy, err return NonStatTy, err
} }
if err := bc.DeletePendingSlashingCandidates(); err != nil { records := slash.Records{}
return NonStatTy, err if s := header.Slashes(); len(s) > 0 {
if err := rlp.DecodeBytes(s, &records); err != nil {
utils.Logger().Debug().Err(err).Msg("could not decode slashes in header")
}
if err := bc.DeleteFromPendingSlashingCandidates(records); err != nil {
utils.Logger().Debug().Err(err).Msg("could not deleting pending slashes")
}
} }
} else { } else {
// block reward never accumulate before staking // block reward never accumulate before staking
bc.WriteBlockRewardAccumulator(batch, big.NewInt(0), block.Number().Uint64()) bc.WriteBlockRewardAccumulator(batch, common.Big0, block.Number().Uint64())
} }
} }

@ -4,11 +4,11 @@ import (
"bytes" "bytes"
"math/big" "math/big"
"github.com/pkg/errors" "github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/core/vm" "github.com/harmony-one/harmony/core/vm"
common2 "github.com/harmony-one/harmony/internal/common" common2 "github.com/harmony-one/harmony/internal/common"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors"
) )
var ( var (
@ -41,7 +41,9 @@ func VerifyAndCreateValidatorFromMsg(
return nil, errNegativeAmount return nil, errNegativeAmount
} }
if stateDB.IsValidator(msg.ValidatorAddress) { if stateDB.IsValidator(msg.ValidatorAddress) {
return nil, errors.Wrapf(errValidatorExist, common2.MustAddressToBech32(msg.ValidatorAddress)) return nil, errors.Wrapf(
errValidatorExist, common2.MustAddressToBech32(msg.ValidatorAddress),
)
} }
if !CanTransfer(stateDB, msg.ValidatorAddress, msg.Amount) { if !CanTransfer(stateDB, msg.ValidatorAddress, msg.Amount) {
return nil, errInsufficientBalanceForStake return nil, errInsufficientBalanceForStake
@ -58,7 +60,7 @@ func VerifyAndCreateValidatorFromMsg(
zero := big.NewInt(0) zero := big.NewInt(0)
wrapper.Counters.NumBlocksSigned = zero wrapper.Counters.NumBlocksSigned = zero
wrapper.Counters.NumBlocksToSign = zero wrapper.Counters.NumBlocksToSign = zero
if err := wrapper.SanityCheck(); err != nil { if err := wrapper.SanityCheck(staking.DoNotEnforceMaxBLS); err != nil {
return nil, err return nil, err
} }
return wrapper, nil return wrapper, nil
@ -69,7 +71,8 @@ func VerifyAndCreateValidatorFromMsg(
// //
// Note that this function never updates the stateDB, it only reads from stateDB. // Note that this function never updates the stateDB, it only reads from stateDB.
func VerifyAndEditValidatorFromMsg( func VerifyAndEditValidatorFromMsg(
stateDB vm.StateDB, chainContext ChainContext, blockNum *big.Int, msg *staking.EditValidator, stateDB vm.StateDB, chainContext ChainContext,
blockNum *big.Int, msg *staking.EditValidator,
) (*staking.ValidatorWrapper, error) { ) (*staking.ValidatorWrapper, error) {
if stateDB == nil { if stateDB == nil {
return nil, errStateDBIsMissing return nil, errStateDBIsMissing
@ -106,11 +109,13 @@ func VerifyAndEditValidatorFromMsg(
wrapper.Validator.UpdateHeight = blockNum wrapper.Validator.UpdateHeight = blockNum
} }
if newRate.Sub(rateAtBeginningOfEpoch).Abs().GT(wrapper.Validator.MaxChangeRate) { if newRate.Sub(rateAtBeginningOfEpoch).Abs().GT(
wrapper.Validator.MaxChangeRate,
) {
return nil, errCommissionRateChangeTooFast return nil, errCommissionRateChangeTooFast
} }
if err := wrapper.SanityCheck(); err != nil { if err := wrapper.SanityCheck(staking.DoNotEnforceMaxBLS); err != nil {
return nil, err return nil, err
} }
return wrapper, nil return wrapper, nil
@ -153,14 +158,18 @@ func VerifyAndDelegateFromMsg(
if delegation.Undelegations[i].Amount.Cmp(delegateBalance) <= 0 { if delegation.Undelegations[i].Amount.Cmp(delegateBalance) <= 0 {
delegateBalance.Sub(delegateBalance, delegation.Undelegations[i].Amount) delegateBalance.Sub(delegateBalance, delegation.Undelegations[i].Amount)
} else { } else {
delegation.Undelegations[i].Amount.Sub(delegation.Undelegations[i].Amount, delegateBalance) delegation.Undelegations[i].Amount.Sub(
delegation.Undelegations[i].Amount, delegateBalance,
)
delegateBalance = big.NewInt(0) delegateBalance = big.NewInt(0)
break break
} }
} }
delegation.Undelegations = delegation.Undelegations[:i+1] delegation.Undelegations = delegation.Undelegations[:i+1]
delegation.Amount.Add(delegation.Amount, msg.Amount) delegation.Amount.Add(delegation.Amount, msg.Amount)
if err := wrapper.SanityCheck(); err != nil { if err := wrapper.SanityCheck(
staking.DoNotEnforceMaxBLS,
); err != nil {
return nil, nil, err return nil, nil, err
} }
// Return remaining balance to be deducted for delegation // Return remaining balance to be deducted for delegation
@ -182,8 +191,12 @@ func VerifyAndDelegateFromMsg(
if !CanTransfer(stateDB, msg.DelegatorAddress, msg.Amount) { if !CanTransfer(stateDB, msg.DelegatorAddress, msg.Amount) {
return nil, nil, errInsufficientBalanceForStake return nil, nil, errInsufficientBalanceForStake
} }
wrapper.Delegations = append(wrapper.Delegations, staking.NewDelegation(msg.DelegatorAddress, msg.Amount)) wrapper.Delegations = append(
if err := wrapper.SanityCheck(); err != nil { wrapper.Delegations, staking.NewDelegation(
msg.DelegatorAddress, msg.Amount,
),
)
if err := wrapper.SanityCheck(staking.DoNotEnforceMaxBLS); err != nil {
return nil, nil, err return nil, nil, err
} }
return wrapper, msg.Amount, nil return wrapper, msg.Amount, nil
@ -223,7 +236,9 @@ func VerifyAndUndelegateFromMsg(
if err := delegation.Undelegate(epoch, msg.Amount); err != nil { if err := delegation.Undelegate(epoch, msg.Amount); err != nil {
return nil, err return nil, err
} }
if err := wrapper.SanityCheck(); err != nil { if err := wrapper.SanityCheck(
staking.DoNotEnforceMaxBLS,
); err != nil {
return nil, err return nil, err
} }
return wrapper, nil return wrapper, nil
@ -253,12 +268,14 @@ func VerifyAndCollectRewardsFromDelegation(
} }
if uint64(len(wrapper.Delegations)) > delegation.Index { if uint64(len(wrapper.Delegations)) > delegation.Index {
delegation := &wrapper.Delegations[delegation.Index] delegation := &wrapper.Delegations[delegation.Index]
if delegation.Reward.Cmp(big.NewInt(0)) > 0 { if delegation.Reward.Cmp(common.Big0) > 0 {
totalRewards.Add(totalRewards, delegation.Reward) totalRewards.Add(totalRewards, delegation.Reward)
} }
delegation.Reward.SetUint64(0) delegation.Reward.SetUint64(0)
} }
if err := wrapper.SanityCheck(); err != nil { if err := wrapper.SanityCheck(
staking.DoNotEnforceMaxBLS,
); err != nil {
return nil, nil, err return nil, nil, err
} }
updatedValidatorWrappers = append(updatedValidatorWrappers, wrapper) updatedValidatorWrappers = append(updatedValidatorWrappers, wrapper)

@ -708,12 +708,14 @@ func (db *DB) ValidatorWrapper(
return &val, nil return &val, nil
} }
const doNotEnforceMaxBLS = -1
// UpdateValidatorWrapper updates staking information of // UpdateValidatorWrapper updates staking information of
// a given validator (including delegation info) // a given validator (including delegation info)
func (db *DB) UpdateValidatorWrapper( func (db *DB) UpdateValidatorWrapper(
addr common.Address, val *stk.ValidatorWrapper, addr common.Address, val *stk.ValidatorWrapper,
) error { ) error {
if err := val.SanityCheck(); err != nil { if err := val.SanityCheck(doNotEnforceMaxBLS); err != nil {
return err return err
} }

@ -333,31 +333,33 @@ func (b *APIBackend) GetValidatorInformation(
s, _ := internal_common.AddressToBech32(addr) s, _ := internal_common.AddressToBech32(addr)
return nil, errors.Wrapf(err, "not found address in current state %s", s) return nil, errors.Wrapf(err, "not found address in current state %s", s)
} }
snapshot, err := b.hmy.BlockChain().ReadValidatorSnapshot(addr) snapshot, err := b.hmy.BlockChain().ReadValidatorSnapshotAtEpoch(
b.hmy.BlockChain().CurrentHeader().Epoch(),
addr,
)
defaultReply := &staking.ValidatorRPCEnchanced{
Wrapper: *wrapper,
CurrentSigningPercentage: staking.Computed{
common.Big0, common.Big0, numeric.ZeroDec(),
},
CurrentVotingPower: []staking.VotePerShard{},
}
if err != nil { if err != nil {
return &staking.ValidatorRPCEnchanced{ return defaultReply, nil
Wrapper: *wrapper,
CurrentSigningPercentage: staking.Computed{
common.Big0, common.Big0, numeric.ZeroDec(),
},
CurrentVotingPower: []staking.VotePerShard{},
}, nil
} }
signed, toSign, quotient, err := availability.ComputeCurrentSigning(snapshot, wrapper) signed, toSign, quotient, err := availability.ComputeCurrentSigning(snapshot, wrapper)
if err != nil { if err != nil {
return nil, err return defaultReply, nil
} }
stats, err := b.hmy.BlockChain().ReadValidatorStats(addr) stats, err := b.hmy.BlockChain().ReadValidatorStats(addr)
if err != nil { if err != nil {
return nil, err return defaultReply, nil
} }
return &staking.ValidatorRPCEnchanced{ defaultReply.CurrentSigningPercentage = staking.Computed{signed, toSign, quotient}
Wrapper: *wrapper, defaultReply.CurrentVotingPower = stats.VotingPowerPerShard
CurrentSigningPercentage: staking.Computed{signed, toSign, quotient}, return defaultReply, nil
CurrentVotingPower: stats.VotingPowerPerShard,
}, nil
} }
// GetMedianRawStakeSnapshot .. // GetMedianRawStakeSnapshot ..
@ -372,10 +374,12 @@ func (b *APIBackend) GetMedianRawStakeSnapshot() (*big.Int, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !effective.IsEligibleForEPOSAuction(validator) { if validator.EPOSStatus != effective.Active {
continue continue
} }
if err := validator.SanityCheck(); err != nil { if err := validator.SanityCheck(
staking.DoNotEnforceMaxBLS,
); err != nil {
continue continue
} }
@ -430,7 +434,7 @@ func (b *APIBackend) GetTotalStakingSnapshot() *big.Int {
stakes := big.NewInt(0) stakes := big.NewInt(0)
for i := range candidates { for i := range candidates {
validator, _ := b.hmy.BlockChain().ReadValidatorInformation(candidates[i]) validator, _ := b.hmy.BlockChain().ReadValidatorInformation(candidates[i])
if !effective.IsEligibleForEPOSAuction(validator) { if validator.EPOSStatus != effective.Active {
continue continue
} }
for i := range validator.Delegations { for i := range validator.Delegations {
@ -558,9 +562,6 @@ func (b *APIBackend) GetSuperCommittees() (*quorum.Transition, error) {
for _, comm := range prevCommittee.Shards { for _, comm := range prevCommittee.Shards {
decider := quorum.NewDecider(quorum.SuperMajorityStake) decider := quorum.NewDecider(quorum.SuperMajorityStake)
shardID := comm.ShardID shardID := comm.ShardID
decider.SetShardIDProvider(func() (uint32, error) {
return shardID, nil
})
decider.SetVoters(comm.Slots) decider.SetVoters(comm.Slots)
then.Deciders[shardID] = decider then.Deciders[shardID] = decider
} }
@ -568,9 +569,6 @@ func (b *APIBackend) GetSuperCommittees() (*quorum.Transition, error) {
for _, comm := range nowCommittee.Shards { for _, comm := range nowCommittee.Shards {
decider := quorum.NewDecider(quorum.SuperMajorityStake) decider := quorum.NewDecider(quorum.SuperMajorityStake)
shardID := comm.ShardID shardID := comm.ShardID
decider.SetShardIDProvider(func() (uint32, error) {
return shardID, nil
})
decider.SetVoters(comm.Slots) decider.SetVoters(comm.Slots)
now.Deciders[shardID] = decider now.Deciders[shardID] = decider
} }

@ -209,13 +209,14 @@ func (e *engineImpl) VerifySeal(chain engine.ChainReader, header *block.Header)
return errors.Wrapf(err, "cannot decoded shard state") return errors.Wrapf(err, "cannot decoded shard state")
} }
d := quorum.NewDecider(quorum.SuperMajorityStake) d := quorum.NewDecider(quorum.SuperMajorityStake)
d.SetShardIDProvider(func() (uint32, error) {
return parentHeader.ShardID(), nil
})
d.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) { d.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) {
return nil, nil return nil, nil
}) })
d.SetVoters(slotList.FindCommitteeByID(parentHeader.ShardID()).Slots) subComm, err := slotList.FindCommitteeByID(parentHeader.ShardID())
if err != nil {
return err
}
d.SetVoters(subComm.Slots)
if !d.IsQuorumAchievedByMask(mask) { if !d.IsQuorumAchievedByMask(mask) {
return ctxerror.New( return ctxerror.New(
"[VerifySeal] Not enough voting power in LastCommitSignature from Block Header", "[VerifySeal] Not enough voting power in LastCommitSignature from Block Header",
@ -293,8 +294,11 @@ func (e *engineImpl) Finalize(
} }
// Withdraw unlocked tokens to the delegators' accounts // Withdraw unlocked tokens to the delegators' accounts
func payoutUndelegations(chain engine.ChainReader, header *block.Header, state *state.DB) error { func payoutUndelegations(
chain engine.ChainReader, header *block.Header, state *state.DB,
) error {
validators, err := chain.ReadValidatorList() validators, err := chain.ReadValidatorList()
countTrack := map[common.Address]int{}
if err != nil { if err != nil {
const msg = "[Finalize] failed to read all validators" const msg = "[Finalize] failed to read all validators"
return ctxerror.New(msg).WithCause(err) return ctxerror.New(msg).WithCause(err)
@ -314,6 +318,7 @@ func payoutUndelegations(chain engine.ChainReader, header *block.Header, state *
) )
state.AddBalance(delegation.DelegatorAddress, totalWithdraw) state.AddBalance(delegation.DelegatorAddress, totalWithdraw)
} }
countTrack[validator] = len(wrapper.Delegations)
if err := state.UpdateValidatorWrapper( if err := state.UpdateValidatorWrapper(
validator, wrapper, validator, wrapper,
); err != nil { ); err != nil {
@ -321,6 +326,13 @@ func payoutUndelegations(chain engine.ChainReader, header *block.Header, state *
return ctxerror.New(msg).WithCause(err) return ctxerror.New(msg).WithCause(err)
} }
} }
utils.Logger().Info().
Uint64("epoch", header.Epoch().Uint64()).
Uint64("block-number", header.Number().Uint64()).
Interface("count-track", countTrack).
Msg("paid out delegations")
return nil return nil
} }
@ -400,12 +412,11 @@ func QuorumForBlock(
} }
} }
c := ss.FindCommitteeByID(h.ShardID()) subComm, err := ss.FindCommitteeByID(h.ShardID())
if c == nil { if err != nil {
return 0, errors.Errorf( return 0, errors.Errorf("cannot find shard %d in shard state", h.ShardID())
"cannot find shard %d in shard state", h.ShardID())
} }
return (len(c.Slots))*2/3 + 1, nil return (len(subComm.Slots))*2/3 + 1, nil
} }
// Similiar to VerifyHeader, which is only for verifying the block headers of one's own chain, this verification // Similiar to VerifyHeader, which is only for verifying the block headers of one's own chain, this verification
@ -435,13 +446,14 @@ func (e *engineImpl) VerifyHeaderWithSignature(chain engine.ChainReader, header
return errors.Wrapf(err, "cannot read shard state") return errors.Wrapf(err, "cannot read shard state")
} }
d := quorum.NewDecider(quorum.SuperMajorityStake) d := quorum.NewDecider(quorum.SuperMajorityStake)
d.SetShardIDProvider(func() (uint32, error) {
return header.ShardID(), nil
})
d.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) { d.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) {
return nil, nil return nil, nil
}) })
d.SetVoters(slotList.FindCommitteeByID(header.ShardID()).Slots) subComm, err := slotList.FindCommitteeByID(header.ShardID())
if err != nil {
return err
}
d.SetVoters(subComm.Slots)
if !d.IsQuorumAchievedByMask(mask) { if !d.IsQuorumAchievedByMask(mask) {
return ctxerror.New( return ctxerror.New(
"[VerifySeal] Not enough voting power in commitSignature from Block Header", "[VerifySeal] Not enough voting power in commitSignature from Block Header",
@ -484,21 +496,12 @@ func GetPublicKeys(
} }
} }
committee := shardState.FindCommitteeByID(header.ShardID()) subCommittee, err := shardState.FindCommitteeByID(header.ShardID())
if committee == nil { if err != nil {
return nil, ctxerror.New("cannot find shard in the shard state", return nil, ctxerror.New("cannot find shard in the shard state",
"blockNumber", header.Number(), "blockNumber", header.Number(),
"shardID", header.ShardID(), "shardID", header.ShardID(),
) )
} }
committerKeys := []*bls.PublicKey{} return subCommittee.BLSPublicKeys()
for _, member := range committee.Slots {
committerKey := new(bls.PublicKey)
if err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey); err != nil {
return nil, ctxerror.New("cannot convert BLS public key",
"blsPublicKey", member.BlsPublicKey).WithCause(err)
}
committerKeys = append(committerKeys, committerKey)
}
return committerKeys, nil
} }

@ -140,11 +140,13 @@ func AccumulateRewards(
return network.NoReward, err return network.NoReward, err
} }
subComm := shardState.FindCommitteeByID(cxLink.ShardID()) subComm, err := shardState.FindCommitteeByID(cxLink.ShardID())
if err != nil {
return network.NoReward, err
}
payableSigners, missing, err := availability.BlockSigners( payableSigners, missing, err := availability.BlockSigners(
cxLink.Bitmap(), subComm, cxLink.Bitmap(), subComm,
) )
if err != nil { if err != nil {
return network.NoReward, err return network.NoReward, err
} }

@ -219,9 +219,10 @@ func (s *PublicTransactionPoolAPI) SendRawStakingTransaction(
return common.Hash{}, err return common.Hash{}, err
} }
c := s.b.ChainConfig().ChainID c := s.b.ChainConfig().ChainID
if tx.ChainID().Cmp(c) != 0 { if id := tx.ChainID(); id.Cmp(c) != 0 {
e := errors.Wrapf(errInvalidChainID, "current chain id:%s", c.String()) return common.Hash{}, errors.Wrapf(
return common.Hash{}, e errInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(),
)
} }
return SubmitStakingTransaction(ctx, s.b, tx) return SubmitStakingTransaction(ctx, s.b, tx)
} }
@ -238,9 +239,10 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
return common.Hash{}, err return common.Hash{}, err
} }
c := s.b.ChainConfig().ChainID c := s.b.ChainConfig().ChainID
if tx.ChainID().Cmp(c) != 0 { if id := tx.ChainID(); id.Cmp(c) != 0 {
e := errors.Wrapf(errInvalidChainID, "current chain id:%s", c.String()) return common.Hash{}, errors.Wrapf(
return common.Hash{}, e errInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(),
)
} }
return SubmitTransaction(ctx, s.b, tx) return SubmitTransaction(ctx, s.b, tx)
} }

@ -217,9 +217,10 @@ func (s *PublicTransactionPoolAPI) SendRawStakingTransaction(
return common.Hash{}, err return common.Hash{}, err
} }
c := s.b.ChainConfig().ChainID c := s.b.ChainConfig().ChainID
if tx.ChainID().Cmp(c) != 0 { if id := tx.ChainID(); id.Cmp(c) != 0 {
e := errors.Wrapf(errInvalidChainID, "current chain id:%s", c.String()) return common.Hash{}, errors.Wrapf(
return common.Hash{}, e errInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(),
)
} }
return SubmitStakingTransaction(ctx, s.b, tx) return SubmitStakingTransaction(ctx, s.b, tx)
} }
@ -236,9 +237,10 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
return common.Hash{}, err return common.Hash{}, err
} }
c := s.b.ChainConfig().ChainID c := s.b.ChainConfig().ChainID
if tx.ChainID().Cmp(c) != 0 { if id := tx.ChainID(); id.Cmp(c) != 0 {
e := errors.Wrapf(errInvalidChainID, "current chain id:%s", c.String()) return common.Hash{}, errors.Wrapf(
return common.Hash{}, e errInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(),
)
} }
return SubmitTransaction(ctx, s.b, tx) return SubmitTransaction(ctx, s.b, tx)
} }

@ -12,20 +12,18 @@ func (node *Node) processSlashCandidateMessage(msgPayload []byte) {
if node.NodeConfig.ShardID != shard.BeaconChainShardID { if node.NodeConfig.ShardID != shard.BeaconChainShardID {
return return
} }
candidates, e := slash.Records{}, utils.Logger().Error() candidates := slash.Records{}
if err := rlp.DecodeBytes(msgPayload, &candidates); err != nil { if err := rlp.DecodeBytes(msgPayload, &candidates); err != nil {
e.Err(err). utils.Logger().Error().
Msg("unable to decode slash candidate message") Err(err).Msg("unable to decode slash candidates message")
return return
} }
if err := candidates.SanityCheck(); err != nil { if err := node.Blockchain().AddPendingSlashingCandidates(
e.Err(err). candidates,
RawJSON("slash-candidates", []byte(candidates.String())). ); err != nil {
Msg("sanity check failed on incoming candidates") utils.Logger().Error().
return Err(err).Msg("unable to add slash candidates to pending ")
} }
node.Blockchain().AddPendingSlashingCandidates(candidates)
} }

@ -457,8 +457,13 @@ func (node *Node) GetSyncID() [SyncIDLength]byte {
} }
// New creates a new node. // New creates a new node.
func New(host p2p.Host, consensusObj *consensus.Consensus, func New(
chainDBFactory shardchain.DBFactory, blacklist map[common.Address]struct{}, isArchival bool) *Node { host p2p.Host,
consensusObj *consensus.Consensus,
chainDBFactory shardchain.DBFactory,
blacklist map[common.Address]struct{},
isArchival bool,
) *Node {
node := Node{} node := Node{}
const sinkSize = 4096 const sinkSize = 4096
node.errorSink = struct { node.errorSink = struct {
@ -542,7 +547,7 @@ func New(host p2p.Host, consensusObj *consensus.Consensus,
) )
} }
node.pendingCXReceipts = make(map[string]*types.CXReceiptsProof) node.pendingCXReceipts = map[string]*types.CXReceiptsProof{}
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.SetBeaconchain(beaconChain) chain.Engine.SetBeaconchain(beaconChain)
@ -577,23 +582,22 @@ func New(host p2p.Host, consensusObj *consensus.Consensus,
node.globalRxQueue = msgq.New(GlobalRxQueueSize) node.globalRxQueue = msgq.New(GlobalRxQueueSize)
// Setup initial state of syncing. // Setup initial state of syncing.
node.peerRegistrationRecord = make(map[string]*syncConfig) node.peerRegistrationRecord = map[string]*syncConfig{}
node.startConsensus = make(chan struct{}) node.startConsensus = make(chan struct{})
go node.bootstrapConsensus() go node.bootstrapConsensus()
// Broadcast double-signers reported by consensus // Broadcast double-signers reported by consensus
if node.Consensus != nil { if node.Consensus != nil {
go func() { go func() {
for { for {
select { select {
case doubleSign := <-node.Consensus.SlashChan: case doubleSign := <-node.Consensus.SlashChan:
l := utils.Logger().Info().RawJSON("double-sign", []byte(doubleSign.String())) utils.Logger().Info().
RawJSON("double-sign-candidate", []byte(doubleSign.String())).
Msg("double sign notified by consensus leader")
// no point to broadcast the slash if we aren't even in the right epoch yet // no point to broadcast the slash if we aren't even in the right epoch yet
if !node.Blockchain().Config().IsStaking( if !node.Blockchain().Config().IsStaking(
node.Blockchain().CurrentHeader().Epoch(), node.Blockchain().CurrentHeader().Epoch(),
) { ) {
l.Msg("double sign occured before staking era, no-op")
return return
} }
if hooks := node.NodeConfig.WebHooks.Hooks; hooks != nil { if hooks := node.NodeConfig.WebHooks.Hooks; hooks != nil {
@ -604,11 +608,13 @@ func New(host p2p.Host, consensusObj *consensus.Consensus,
} }
if node.NodeConfig.ShardID != shard.BeaconChainShardID { if node.NodeConfig.ShardID != shard.BeaconChainShardID {
go node.BroadcastSlash(&doubleSign) go node.BroadcastSlash(&doubleSign)
l.Msg("broadcast the double sign record")
} else { } else {
records := slash.Records{doubleSign} records := slash.Records{doubleSign}
node.Blockchain().AddPendingSlashingCandidates(records) if err := node.Blockchain().AddPendingSlashingCandidates(
l.Msg("added double sign record to off-chain pending") records,
); err != nil {
utils.Logger().Err(err).Msg("could not add new slash to ending slashes")
}
} }
} }
} }
@ -622,8 +628,11 @@ func New(host p2p.Host, consensusObj *consensus.Consensus,
// keys for consensus and drand // keys for consensus and drand
func (node *Node) InitConsensusWithValidators() (err error) { func (node *Node) InitConsensusWithValidators() (err error) {
if node.Consensus == nil { if node.Consensus == nil {
utils.Logger().Error().Msg("[InitConsensusWithValidators] consenus is nil; Cannot figure out shardID") utils.Logger().Error().
return ctxerror.New("[InitConsensusWithValidators] consenus is nil; Cannot figure out shardID") Msg("[InitConsensusWithValidators] consenus is nil; Cannot figure out shardID")
return ctxerror.New(
"[InitConsensusWithValidators] consenus is nil; Cannot figure out shardID",
)
} }
shardID := node.Consensus.ShardID shardID := node.Consensus.ShardID
blockNum := node.Blockchain().CurrentBlock().NumberU64() blockNum := node.Blockchain().CurrentBlock().NumberU64()
@ -645,9 +654,11 @@ func (node *Node) InitConsensusWithValidators() (err error) {
Msg("[InitConsensusWithValidators] Failed getting shard state") Msg("[InitConsensusWithValidators] Failed getting shard state")
return err return err
} }
pubKeys, err := committee.WithStakingEnabled.GetCommitteePublicKeys( subComm, err := shardState.FindCommitteeByID(shardID)
shardState.FindCommitteeByID(shardID), if err != nil {
) return err
}
pubKeys, err := committee.WithStakingEnabled.GetCommitteePublicKeys(subComm)
if err != nil { if err != nil {
utils.Logger().Error(). utils.Logger().Error().
Uint32("shardID", shardID). Uint32("shardID", shardID).
@ -670,7 +681,8 @@ func (node *Node) InitConsensusWithValidators() (err error) {
return nil return nil
} }
} }
// TODO: Disable drand. Currently drand isn't functioning but we want to compeletely turn it off for full protection. // TODO: Disable drand. Currently drand isn't
// functioning but we want to compeletely turn it off for full protection.
// node.DRand.UpdatePublicKeys(pubKeys) // node.DRand.UpdatePublicKeys(pubKeys)
return nil return nil
} }
@ -711,13 +723,14 @@ func (node *Node) initNodeConfiguration() (service.NodeConfig, chan p2p.Peer) {
PushgatewayIP: node.NodeConfig.GetPushgatewayIP(), PushgatewayIP: node.NodeConfig.GetPushgatewayIP(),
PushgatewayPort: node.NodeConfig.GetPushgatewayPort(), PushgatewayPort: node.NodeConfig.GetPushgatewayPort(),
IsClient: node.NodeConfig.IsClient(), IsClient: node.NodeConfig.IsClient(),
Beacon: nodeconfig.NewGroupIDByShardID(0), Beacon: nodeconfig.NewGroupIDByShardID(shard.BeaconChainShardID),
ShardGroupID: node.NodeConfig.GetShardGroupID(), ShardGroupID: node.NodeConfig.GetShardGroupID(),
Actions: make(map[nodeconfig.GroupID]nodeconfig.ActionType), Actions: make(map[nodeconfig.GroupID]nodeconfig.ActionType),
} }
if nodeConfig.IsClient { if nodeConfig.IsClient {
nodeConfig.Actions[nodeconfig.NewClientGroupIDByShardID(0)] = nodeconfig.ActionStart nodeConfig.Actions[nodeconfig.NewClientGroupIDByShardID(shard.BeaconChainShardID)] =
nodeconfig.ActionStart
} else { } else {
nodeConfig.Actions[node.NodeConfig.GetShardGroupID()] = nodeconfig.ActionStart nodeConfig.Actions[node.NodeConfig.GetShardGroupID()] = nodeconfig.ActionStart
} }
@ -728,7 +741,9 @@ func (node *Node) initNodeConfiguration() (service.NodeConfig, chan p2p.Peer) {
utils.Logger().Error().Err(err).Msg("Failed to create shard receiver") utils.Logger().Error().Err(err).Msg("Failed to create shard receiver")
} }
node.globalGroupReceiver, err = node.host.GroupReceiver(nodeconfig.NewClientGroupIDByShardID(0)) node.globalGroupReceiver, err = node.host.GroupReceiver(
nodeconfig.NewClientGroupIDByShardID(shard.BeaconChainShardID),
)
if err != nil { if err != nil {
utils.Logger().Error().Err(err).Msg("Failed to create global receiver") utils.Logger().Error().Err(err).Msg("Failed to create global receiver")
} }

@ -1,17 +1,13 @@
package node package node
import ( import (
"encoding/binary"
"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"
"github.com/harmony-one/harmony/consensus/quorum"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
bls_cosi "github.com/harmony-one/harmony/crypto/bls"
"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/multibls"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/verify"
) )
const ( const (
@ -71,9 +67,8 @@ func (node *Node) ProcessCrossLinkMessage(msgPayload []byte) {
return return
} }
var crosslinks []types.CrossLink crosslinks := []types.CrossLink{}
err = rlp.DecodeBytes(msgPayload, &crosslinks) if err := rlp.DecodeBytes(msgPayload, &crosslinks); err != nil {
if err != nil {
utils.Logger().Error(). utils.Logger().Error().
Err(err). Err(err).
Msg("[ProcessingCrossLink] Crosslink Message Broadcast Unable to Decode") Msg("[ProcessingCrossLink] Crosslink Message Broadcast Unable to Decode")
@ -125,68 +120,35 @@ func (node *Node) VerifyCrossLink(cl types.CrossLink) error {
} }
if !node.Blockchain().Config().IsCrossLink(cl.Epoch()) { if !node.Blockchain().Config().IsCrossLink(cl.Epoch()) {
return ctxerror.New("[VerifyCrossLink] CrossLink Epoch should >= cross link starting epoch", "crossLinkEpoch", cl.Epoch(), "cross_link_starting_eoch", node.Blockchain().Config().CrossLinkEpoch) return ctxerror.New(
"[VerifyCrossLink] CrossLink Epoch should >= cross link starting epoch",
"crossLinkEpoch", cl.Epoch(), "cross_link_starting_eoch",
node.Blockchain().Config().CrossLinkEpoch,
)
} }
// Verify signature of the new cross link header // Verify signature of the new cross link header
// TODO: check whether to recalculate shard state // TODO: check whether to recalculate shard state
shardState, err := node.Blockchain().ReadShardState(cl.Epoch()) shardState, err := node.Blockchain().ReadShardState(cl.Epoch())
committee := shardState.FindCommitteeByID(cl.ShardID())
if err != nil || committee == nil {
return ctxerror.New("[VerifyCrossLink] Failed to read shard state for cross link", "beaconEpoch", node.Blockchain().CurrentHeader().Epoch(), "epoch", cl.Epoch(), "shardID", cl.ShardID(), "blockNum", cl.BlockNum()).WithCause(err)
}
var committerKeys []*bls.PublicKey
parseKeysSuccess := true
for _, member := range committee.Slots {
committerKey := new(bls.PublicKey)
err = member.BlsPublicKey.ToLibBLSPublicKey(committerKey)
if err != nil {
parseKeysSuccess = false
break
}
committerKeys = append(committerKeys, committerKey)
}
if !parseKeysSuccess {
return ctxerror.New("[VerifyCrossLink] cannot convert BLS public key", "shardID", cl.ShardID(), "blockNum", cl.BlockNum()).WithCause(err)
}
mask, err := bls_cosi.NewMask(committerKeys, nil)
if err != nil { if err != nil {
return ctxerror.New("[VerifyCrossLink] cannot create group sig mask", "shardID", cl.ShardID(), "blockNum", cl.BlockNum()).WithCause(err) return err
}
if err := mask.SetMask(cl.Bitmap()); err != nil {
return ctxerror.New("[VerifyCrossLink] cannot set group sig mask bits", "shardID", cl.ShardID(), "blockNum", cl.BlockNum()).WithCause(err)
} }
decider := quorum.NewDecider(quorum.SuperMajorityStake) committee, err := shardState.FindCommitteeByID(cl.ShardID())
decider.SetShardIDProvider(func() (uint32, error) {
return cl.ShardID(), nil
})
decider.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) {
return nil, nil
})
if _, err := decider.SetVoters(committee.Slots); err != nil {
return ctxerror.New("[VerifyCrossLink] Cannot SetVoters for committee", "shardID", cl.ShardID())
}
if !decider.IsQuorumAchievedByMask(mask) {
return ctxerror.New("[VerifyCrossLink] Not enough voting power for crosslink", "shardID", cl.ShardID())
}
aggSig := bls.Sign{}
sig := cl.Signature()
err = aggSig.Deserialize(sig[:])
if err != nil { if err != nil {
return ctxerror.New("[VerifyCrossLink] unable to deserialize multi-signature from payload").WithCause(err) return err
} }
hash := cl.Hash() aggSig := &bls.Sign{}
blockNumBytes := make([]byte, 8) sig := cl.Signature()
binary.LittleEndian.PutUint64(blockNumBytes, cl.BlockNum()) if err = aggSig.Deserialize(sig[:]); err != nil {
commitPayload := append(blockNumBytes, hash[:]...) return ctxerror.New(
if !aggSig.VerifyHash(mask.AggregatePublic, commitPayload) { "[VerifyCrossLink] unable to deserialize multi-signature from payload",
return ctxerror.New("[VerifyCrossLink] Failed to verify the signature for cross link", "shardID", cl.ShardID(), "blockNum", cl.BlockNum()) ).WithCause(err)
} }
return nil
return verify.AggregateSigForCommittee(
committee, aggSig, cl.Hash(), cl.BlockNum(), cl.Bitmap(),
)
} }

@ -51,14 +51,13 @@ func (gi *genesisInitializer) InitChainDB(db ethdb.Database, shardID uint32) err
if shardState == nil { if shardState == nil {
return errors.New("failed to create genesis shard state") return errors.New("failed to create genesis shard state")
} }
if shardID != shard.BeaconChainShardID { if shardID != shard.BeaconChainShardID {
// store only the local shard for shard chains // store only the local shard for shard chains
c := shardState.FindCommitteeByID(shardID) subComm, err := shardState.FindCommitteeByID(shardID)
if c == nil { if err != nil {
return errors.New("cannot find local shard in genesis") return errors.New("cannot find local shard in genesis")
} }
shardState = &shard.State{nil, []shard.Committee{*c}} shardState = &shard.State{nil, []shard.Committee{*subComm}}
} }
gi.node.SetupGenesisBlock(db, shardID, shardState) gi.node.SetupGenesisBlock(db, shardID, shardState)
return nil return nil

@ -244,8 +244,13 @@ func (node *Node) stakingMessageHandler(msgPayload []byte) {
// TODO (lc): broadcast the new blocks to new nodes doing state sync // TODO (lc): broadcast the new blocks to new nodes doing state sync
func (node *Node) BroadcastNewBlock(newBlock *types.Block) { func (node *Node) BroadcastNewBlock(newBlock *types.Block) {
groups := []nodeconfig.GroupID{node.NodeConfig.GetClientGroupID()} groups := []nodeconfig.GroupID{node.NodeConfig.GetClientGroupID()}
utils.Logger().Info().Msgf("broadcasting new block %d, group %s", newBlock.NumberU64(), groups[0]) utils.Logger().Info().
msg := host.ConstructP2pMessage(byte(0), proto_node.ConstructBlocksSyncMessage([]*types.Block{newBlock})) Msgf(
"broadcasting new block %d, group %s", newBlock.NumberU64(), groups[0],
)
msg := host.ConstructP2pMessage(byte(0),
proto_node.ConstructBlocksSyncMessage([]*types.Block{newBlock}),
)
if err := node.host.SendMessageToGroups(groups, msg); err != nil { if err := node.host.SendMessageToGroups(groups, msg); err != nil {
utils.Logger().Warn().Err(err).Msg("cannot broadcast new block") utils.Logger().Warn().Err(err).Msg("cannot broadcast new block")
} }
@ -263,9 +268,11 @@ func (node *Node) BroadcastSlash(witness *slash.Record) {
RawJSON("record", []byte(witness.String())). RawJSON("record", []byte(witness.String())).
Msg("could not send slash record to beaconchain") Msg("could not send slash record to beaconchain")
} }
utils.Logger().Info().Msg("broadcast the double sign record")
} }
// BroadcastCrossLink is called by consensus leader to send the new header as cross link to beacon chain. // BroadcastCrossLink is called by consensus leader to
// send the new header as cross link to beacon chain.
func (node *Node) BroadcastCrossLink(newBlock *types.Block) { func (node *Node) BroadcastCrossLink(newBlock *types.Block) {
// no point to broadcast the crosslink if we aren't even in the right epoch yet // no point to broadcast the crosslink if we aren't even in the right epoch yet
if !node.Blockchain().Config().IsCrossLink( if !node.Blockchain().Config().IsCrossLink(

@ -182,7 +182,7 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
AnErr("[proposeNewBlock] pending crosslink is already committed onchain", err) AnErr("[proposeNewBlock] pending crosslink is already committed onchain", err)
continue continue
} }
if err = node.VerifyCrossLink(pending); err != nil { if err := node.VerifyCrossLink(pending); err != nil {
invalidToDelete = append(invalidToDelete, pending) invalidToDelete = append(invalidToDelete, pending)
utils.Logger().Debug(). utils.Logger().Debug().
AnErr("[proposeNewBlock] pending crosslink verification failed", err) AnErr("[proposeNewBlock] pending crosslink verification failed", err)
@ -204,8 +204,8 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
} }
if isBeaconchainInStakingEra { if isBeaconchainInStakingEra {
// this one will set a meaningful w.current.slashes // this will set a meaningful w.current.slashes
if err := node.Worker.CollectAndVerifySlashes(); err != nil { if err := node.Worker.CollectVerifiedSlashes(); err != nil {
return nil, err return nil, err
} }
} }

@ -327,32 +327,28 @@ func (w *Worker) IncomingReceipts() []*types.CXReceiptsProof {
return w.current.incxs return w.current.incxs
} }
// CollectAndVerifySlashes .. // CollectVerifiedSlashes sets w.current.slashes only to those that
func (w *Worker) CollectAndVerifySlashes() error { // past verification
allSlashing, err := w.chain.ReadPendingSlashingCandidates() func (w *Worker) CollectVerifiedSlashes() error {
if err != nil { pending, failures :=
return err w.chain.ReadPendingSlashingCandidates(), slash.Records{}
if d := pending; len(d) > 0 {
pending, failures = w.verifySlashes(d)
} }
if d := allSlashing; len(d) > 0 { if f := failures; len(f) > 0 {
// TODO add specific error which is if err := w.chain.DeleteFromPendingSlashingCandidates(f); err != nil {
// "could not verify slash", which should not return as err
// and therefore stop the block proposal
if allSlashing, err = w.VerifyAll(d); err != nil {
// TODO(audit): very slashes individually; do not return err if verify fails
utils.Logger().Err(err).
RawJSON("slashes", []byte(d.String())).
Msg("could not verify slashes proposed")
return err return err
} }
} }
w.current.slashes = allSlashing w.current.slashes = pending
return nil return nil
} }
// VerifyAll .. // returns (successes, failures, error)
func (w *Worker) VerifyAll(allSlashing []slash.Record) ([]slash.Record, error) { func (w *Worker) verifySlashes(
d := allSlashing d slash.Records,
slashingToPropose := []slash.Record{} ) (slash.Records, slash.Records) {
successes, failures := slash.Records{}, slash.Records{}
// Enforce order, reproducibility // Enforce order, reproducibility
sort.SliceStable(d, sort.SliceStable(d,
func(i, j int) bool { func(i, j int) bool {
@ -362,16 +358,24 @@ func (w *Worker) VerifyAll(allSlashing []slash.Record) ([]slash.Record, error) {
}, },
) )
workingState := w.GetCurrentState()
for i := range d { for i := range d {
if err := slash.Verify(w.chain, &d[i]); err != nil { if err := slash.Verify(
return nil, err w.chain, workingState, &d[i],
); err != nil {
failures = append(failures, d[i])
} }
slashingToPropose = append(slashingToPropose, d[i]) successes = append(successes, d[i])
} }
count := len(slashingToPropose)
utils.Logger().Info(). if f := len(failures); f > 0 {
Msgf("set into propose headers %d slashing record", count) utils.Logger().Debug().
return slashingToPropose, nil Int("count", f).
Msg("invalid slash records passed over in block proposal")
}
return successes, failures
} }
// FinalizeNewBlock generate a new block for the next consensus round. // FinalizeNewBlock generate a new block for the next consensus round.
@ -415,7 +419,6 @@ func (w *Worker) FinalizeNewBlock(
if data, err := rlp.EncodeToBytes(doubleSigners); err == nil { if data, err := rlp.EncodeToBytes(doubleSigners); err == nil {
w.current.header.SetSlashes(data) w.current.header.SetSlashes(data)
utils.Logger().Info(). utils.Logger().Info().
RawJSON("slashes", []byte(doubleSigners.String())).
Msg("encoded slashes into headers of proposed new block") Msg("encoded slashes into headers of proposed new block")
} else { } else {
utils.Logger().Debug().Err(err).Msg("Failed to encode proposed slashes") utils.Logger().Debug().Err(err).Msg("Failed to encode proposed slashes")

@ -159,20 +159,22 @@ func eposStakedCommittee(
return shardState, nil return shardState, nil
} }
maxBLSKey := stakedSlotsCount / 3
// 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 {
validator, err := stakerReader.ReadValidatorInformation(candidates[i]) validator, err := stakerReader.ReadValidatorInformation(candidates[i])
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !effective.IsEligibleForEPOSAuction(validator) {
if validator.EPOSStatus != effective.Active {
continue continue
} }
if err := validator.SanityCheck(); err != nil { if err := validator.SanityCheck(maxBLSKey); err != nil {
utils.Logger().Info(). utils.Logger().Info().
Int("staked-candidates", len(candidates)). Int("staked-candidates", len(candidates)).
Err(err). Err(err).
RawJSON("candidate", []byte(validator.String())).
Msg("validator sanity check failed") Msg("validator sanity check failed")
continue continue
} }
@ -230,7 +232,6 @@ func eposStakedCommittee(
utils.Logger().Info(). utils.Logger().Info().
Int("staked-candidates", c). Int("staked-candidates", c).
Str("total-staked-by-validators", totalStake.String()). Str("total-staked-by-validators", totalStake.String()).
RawJSON("staked-super-committee", []byte(shardState.String())).
Msg("epos based super-committe") Msg("epos based super-committe")
} }

@ -19,6 +19,8 @@ import (
var ( var (
emptyBlsPubKey = BlsPublicKey{} emptyBlsPubKey = BlsPublicKey{}
// ErrShardIDNotInSuperCommittee ..
ErrShardIDNotInSuperCommittee = errors.New("shardID not in super committee")
) )
// PublicKeySizeInBytes .. // PublicKeySizeInBytes ..
@ -257,16 +259,16 @@ func (ss *State) MarshalJSON() ([]byte, error) {
// FindCommitteeByID returns the committee configuration for the given shard, // FindCommitteeByID returns the committee configuration for the given shard,
// or nil if the given shard is not found. // or nil if the given shard is not found.
func (ss *State) FindCommitteeByID(shardID uint32) *Committee { func (ss *State) FindCommitteeByID(shardID uint32) (*Committee, error) {
if ss == nil { if ss == nil {
return nil return nil, ErrShardIDNotInSuperCommittee
} }
for committee := range ss.Shards { for committee := range ss.Shards {
if ss.Shards[committee].ShardID == shardID { if ss.Shards[committee].ShardID == shardID {
return &ss.Shards[committee] return &ss.Shards[committee], nil
} }
} }
return nil return nil, ErrShardIDNotInSuperCommittee
} }
// DeepCopy returns a deep copy of the receiver. // DeepCopy returns a deep copy of the receiver.
@ -382,14 +384,20 @@ func (c *Committee) DeepCopy() Committee {
} }
// BLSPublicKeys .. // BLSPublicKeys ..
func (c *Committee) BLSPublicKeys() ([]BlsPublicKey, error) { func (c *Committee) BLSPublicKeys() ([]*bls.PublicKey, error) {
if c == nil { if c == nil {
return nil, ErrCommitteeNil return nil, ErrCommitteeNil
} }
slice := make([]BlsPublicKey, len(c.Slots)) slice := make([]*bls.PublicKey, len(c.Slots))
for j := range c.Slots { for j := range c.Slots {
slice[j] = c.Slots[j].BlsPublicKey committerKey := &bls.PublicKey{}
if err := c.Slots[j].BlsPublicKey.ToLibBLSPublicKey(
committerKey,
); err != nil {
return nil, err
}
slice[j] = committerKey
} }
return slice, nil return slice, nil
} }

@ -4,7 +4,6 @@ import (
"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/harmony/block" "github.com/harmony-one/harmony/block"
engine "github.com/harmony-one/harmony/consensus/engine" engine "github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/state"
@ -13,6 +12,7 @@ import (
"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"
"github.com/harmony-one/harmony/staking/effective"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -31,20 +31,14 @@ var (
func BlockSigners( func BlockSigners(
bitmap []byte, parentCommittee *shard.Committee, bitmap []byte, parentCommittee *shard.Committee,
) (shard.SlotList, shard.SlotList, error) { ) (shard.SlotList, shard.SlotList, error) {
committerKeys := []*bls.PublicKey{} committerKeys, err := parentCommittee.BLSPublicKeys()
for _, member := range parentCommittee.Slots { if err != nil {
committerKey := new(bls.PublicKey) return nil, nil, ctxerror.New(
err := member.BlsPublicKey.ToLibBLSPublicKey(committerKey) "cannot convert a BLS public key",
if err != nil { ).WithCause(err)
return nil, nil, ctxerror.New(
"cannot convert BLS public key",
"blsPublicKey",
member.BlsPublicKey,
).WithCause(err)
}
committerKeys = append(committerKeys, committerKey)
} }
mask, err := bls2.NewMask(committerKeys, nil) mask, err := bls2.NewMask(committerKeys, nil)
if err != nil { if err != nil {
return nil, nil, ctxerror.New( return nil, nil, ctxerror.New(
@ -95,9 +89,10 @@ func BallotResult(
"cannot read shard state", "epoch", parentHeader.Epoch(), "cannot read shard state", "epoch", parentHeader.Epoch(),
).WithCause(err) ).WithCause(err)
} }
parentCommittee := parentShardState.FindCommitteeByID(shardID)
if parentCommittee == nil { parentCommittee, err := parentShardState.FindCommitteeByID(shardID)
if err != nil {
return nil, nil, nil, ctxerror.New( return nil, nil, nil, ctxerror.New(
"cannot find shard in the shard state", "cannot find shard in the shard state",
"parentBlockNumber", parentHeader.Number(), "parentBlockNumber", parentHeader.Number(),
@ -192,8 +187,6 @@ func ComputeCurrentSigning(
if toSign.Cmp(common.Big0) == 0 { if toSign.Cmp(common.Big0) == 0 {
utils.Logger().Info(). utils.Logger().Info().
RawJSON("snapshot", []byte(snapshot.String())).
RawJSON("current", []byte(wrapper.String())).
Msg("toSign is 0, perhaps did not receive crosslink proving signing") Msg("toSign is 0, perhaps did not receive crosslink proving signing")
return signed, toSign, numeric.ZeroDec(), nil return signed, toSign, numeric.ZeroDec(), nil
} }
@ -248,8 +241,6 @@ func compute(
} }
utils.Logger().Info(). utils.Logger().Info().
RawJSON("snapshot", []byte(snapshot.String())).
RawJSON("current", []byte(wrapper.String())).
Str("signed", signed.String()). Str("signed", signed.String()).
Str("to-sign", toSign.String()). Str("to-sign", toSign.String()).
Str("percentage-signed", quotient.String()). Str("percentage-signed", quotient.String()).
@ -260,14 +251,12 @@ func compute(
switch IsBelowSigningThreshold(quotient) { switch IsBelowSigningThreshold(quotient) {
case missedTooManyBlocks: case missedTooManyBlocks:
wrapper.Active = false wrapper.EPOSStatus = effective.Inactive
utils.Logger().Info(). utils.Logger().Info().
RawJSON("snapshot", []byte(snapshot.String())).
RawJSON("current", []byte(wrapper.String())).
Str("threshold", measure.String()). Str("threshold", measure.String()).
Msg("validator failed availability threshold, set to inactive") Msg("validator failed availability threshold, set to inactive")
default: default:
wrapper.Active = true wrapper.EPOSStatus = effective.Active
} }
return nil return nil

@ -1,10 +1,22 @@
package effective package effective
import ( // Eligibility represents ability to participate in EPoS auction
staking "github.com/harmony-one/harmony/staking/types" // that occurs just once an epoch on beaconchain
) type Eligibility byte
// IsEligibleForEPOSAuction .. const (
func IsEligibleForEPOSAuction(v *staking.ValidatorWrapper) bool { // Nil is a default state that represents a no-op
return v.Active && !v.Banned Nil Eligibility = iota
} // Active means allowed in epos auction
Active
// Inactive means validator did not sign enough over 66%
// of the time in an epoch and so they are removed from
// the possibility of being in the epos auction, which happens
// only once an epoch and only
// by beaconchain, aka shard.BeaconChainShardID
Inactive
// Banned records whether this validator is banned
// from the network because they double-signed
// it can never be undone
Banned
)

@ -1,19 +1,24 @@
package slash package slash
import ( import (
"encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"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/consensus/votepower" "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"
"github.com/harmony-one/harmony/crypto/hash"
common2 "github.com/harmony-one/harmony/internal/common" common2 "github.com/harmony-one/harmony/internal/common"
"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"
"github.com/harmony-one/harmony/staking/effective"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -55,9 +60,7 @@ func payDebt(
// Moment .. // Moment ..
type Moment struct { type Moment struct {
Epoch *big.Int `json:"epoch"` Epoch *big.Int `json:"epoch"`
Height *big.Int `json:"block-height"`
TimeUnixNano *big.Int `json:"time-unix-nano"` TimeUnixNano *big.Int `json:"time-unix-nano"`
ViewID uint64 `json:"view-id"`
ShardID uint32 `json:"shard-id"` ShardID uint32 `json:"shard-id"`
} }
@ -82,9 +85,10 @@ type Record struct {
Offender common.Address `json:"offender"` Offender common.Address `json:"offender"`
} }
// Application .. // Application tracks the slash application to state
type Application struct { type Application struct {
TotalSlashed, TotalSnitchReward *big.Int TotalSlashed *big.Int `json:'total-slashed`
TotalSnitchReward *big.Int `json:"total-snitch-reward"`
} }
func (a *Application) String() string { func (a *Application) String() string {
@ -112,25 +116,11 @@ func (r Records) String() string {
var ( var (
errBallotSignerKeysNotSame = errors.New("conflicting ballots must have same signer key") errBallotSignerKeysNotSame = errors.New("conflicting ballots must have same signer key")
errReporterAndOffenderSame = errors.New("reporter and offender cannot be same") errReporterAndOffenderSame = errors.New("reporter and offender cannot be same")
errAlreadyBannedValidator = errors.New("cannot slash on already banned validator")
errSignerKeyNotRightSize = errors.New("bls keys from slash candidate not right side")
errSlashFromFutureEpoch = errors.New("cannot have slash from future epoch")
) )
// SanityCheck fails if any of the slashes fail
func (r Records) SanityCheck() error {
for _, record := range r {
k1 := record.Evidence.AlreadyCastBallot.SignerPubKey
k2 := record.Evidence.DoubleSignedBallot.SignerPubKey
if k1 != k2 {
return errBallotSignerKeysNotSame
}
if record.Offender == record.Reporter {
return errReporterAndOffenderSame
}
}
return nil
}
// MarshalJSON .. // MarshalJSON ..
func (r Record) MarshalJSON() ([]byte, error) { func (r Record) MarshalJSON() ([]byte, error) {
reporter, offender := reporter, offender :=
@ -156,39 +146,99 @@ func (r Record) String() string {
// CommitteeReader .. // CommitteeReader ..
type CommitteeReader interface { type CommitteeReader interface {
ReadShardState(epoch *big.Int) (*shard.State, error) ReadShardState(epoch *big.Int) (*shard.State, error)
CurrentBlock() *types.Block
} }
// Verify checks that the signature is valid // Verify checks that the slash is valid
func Verify(chain CommitteeReader, candidate *Record) error { func Verify(
chain CommitteeReader,
state *state.DB,
candidate *Record,
) error {
wrapper, err := state.ValidatorWrapper(candidate.Offender)
if err != nil {
return err
}
if wrapper.EPOSStatus == effective.Banned {
return errAlreadyBannedValidator
}
if candidate.Offender == candidate.Reporter {
return errReporterAndOffenderSame
}
first, second := first, second :=
candidate.Evidence.AlreadyCastBallot, candidate.Evidence.AlreadyCastBallot,
candidate.Evidence.DoubleSignedBallot candidate.Evidence.DoubleSignedBallot
k1, k2 := len(first.SignerPubKey), len(second.SignerPubKey)
if k1 != shard.PublicKeySizeInBytes ||
k2 != shard.PublicKeySizeInBytes {
return errors.Wrapf(
errSignerKeyNotRightSize, "cast key %d double-signed key %d", k1, k2,
)
}
if shard.CompareBlsPublicKey(first.SignerPubKey, second.SignerPubKey) != 0 { if shard.CompareBlsPublicKey(first.SignerPubKey, second.SignerPubKey) != 0 {
k1, k2 := first.SignerPubKey.Hex(), second.SignerPubKey.Hex() k1, k2 := first.SignerPubKey.Hex(), second.SignerPubKey.Hex()
return errors.Wrapf( return errors.Wrapf(
errBLSKeysNotEqual, "%s %s", k1, k2, errBallotSignerKeysNotSame, "%s %s", k1, k2,
) )
} }
currentEpoch := chain.CurrentBlock().Epoch()
// on beaconchain committee, the slash can't come from the future
if candidate.Evidence.ShardID == shard.BeaconChainShardID &&
candidate.Evidence.Epoch.Cmp(currentEpoch) == 1 {
return errors.Wrapf(
errSlashFromFutureEpoch, "current-epoch %v", currentEpoch,
)
}
superCommittee, err := chain.ReadShardState(candidate.Evidence.Epoch) superCommittee, err := chain.ReadShardState(candidate.Evidence.Epoch)
if err != nil { if err != nil {
return err return err
} }
subCommittee := superCommittee.FindCommitteeByID( subCommittee, err := superCommittee.FindCommitteeByID(
candidate.Evidence.ShardID, candidate.Evidence.ShardID,
) )
if subCommittee == nil { if err != nil {
return errors.Wrapf( return errors.Wrapf(
errShardIDNotKnown, "given shardID %d", candidate.Evidence.ShardID, err, "given shardID %d", candidate.Evidence.ShardID,
) )
} }
if _, err := subCommittee.AddressForBLSKey(second.SignerPubKey); err != nil { if addr, err := subCommittee.AddressForBLSKey(
second.SignerPubKey,
); err != nil || *addr != candidate.Offender {
return err return err
} }
// TODO need to finish this implementation
for _, ballot := range [...]votepower.Ballot{
candidate.Evidence.AlreadyCastBallot,
candidate.Evidence.DoubleSignedBallot,
} {
// now the only real assurance, cryptography
signature := &bls.Sign{}
publicKey := &bls.PublicKey{}
if err := signature.Deserialize(ballot.Signature); err != nil {
return err
}
if err := first.SignerPubKey.ToLibBLSPublicKey(publicKey); err != nil {
return err
}
blockNumBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(blockNumBytes, ballot.Height)
commitPayload := append(blockNumBytes, ballot.BlockHeaderHash[:]...)
if !signature.VerifyHash(publicKey, commitPayload) {
return errFailVerifySlash
}
}
return nil return nil
} }
@ -197,8 +247,8 @@ var (
"bls keys in ballots accompanying slash evidence not equal ", "bls keys in ballots accompanying slash evidence not equal ",
) )
errSlashDebtCannotBeNegative = errors.New("slash debt cannot be negative") errSlashDebtCannotBeNegative = errors.New("slash debt cannot be negative")
errShardIDNotKnown = errors.New("nil subcommittee for shardID")
errValidatorNotFoundDuringSlash = errors.New("validator not found") errValidatorNotFoundDuringSlash = errors.New("validator not found")
errFailVerifySlash = errors.New("could not verify bls key signature on slash")
zero = numeric.ZeroDec() zero = numeric.ZeroDec()
oneDoubleSignerRate = numeric.MustNewDecFromStr("0.02") oneDoubleSignerRate = numeric.MustNewDecFromStr("0.02")
) )
@ -210,6 +260,31 @@ func applySlashRate(amount *big.Int, rate numeric.Dec) *big.Int {
).Mul(rate).TruncateInt() ).Mul(rate).TruncateInt()
} }
// Hash is a New256 hash of an RLP encoded Record
func (r Record) Hash() common.Hash {
return hash.FromRLPNew256(r)
}
// SetDifference returns all the records that are in ys but not in r
func (r Records) SetDifference(ys Records) Records {
diff := Records{}
xsHashed, ysHashed :=
make([]common.Hash, len(r)), make([]common.Hash, len(ys))
for i := range r {
xsHashed[i] = r[i].Hash()
}
for i := range ys {
ysHashed[i] = ys[i].Hash()
for j := range xsHashed {
if ysHashed[i] != xsHashed[j] {
diff = append(diff, ys[i])
}
}
}
return diff
}
func payDownAsMuchAsCan( func payDownAsMuchAsCan(
snapshot, current *staking.ValidatorWrapper, snapshot, current *staking.ValidatorWrapper,
slashDebt, nowAmt *big.Int, slashDebt, nowAmt *big.Int,
@ -348,8 +423,6 @@ func delegatorSlashApply(
return nil return nil
} }
// TODO Need to keep a record in off-chain db of all the slashes?
// Apply .. // Apply ..
func Apply( func Apply(
chain staking.ValidatorSnapshotReader, state *state.DB, chain staking.ValidatorSnapshotReader, state *state.DB,
@ -357,11 +430,8 @@ func Apply(
) (*Application, error) { ) (*Application, error) {
slashDiff := &Application{big.NewInt(0), big.NewInt(0)} slashDiff := &Application{big.NewInt(0), big.NewInt(0)}
for _, slash := range slashes { for _, slash := range slashes {
// TODO Probably won't happen but we probably should snapshot, err := chain.ReadValidatorSnapshotAtEpoch(
// be expilict about reading the right epoch validator snapshot, slash.Evidence.Epoch,
// because it needs to be the epoch of which the double sign
// occurred
snapshot, err := chain.ReadValidatorSnapshot(
slash.Offender, slash.Offender,
) )
@ -389,7 +459,7 @@ func Apply(
} }
// finally, kick them off forever // finally, kick them off forever
current.Banned, current.Active = true, false current.EPOSStatus = effective.Banned
utils.Logger().Info(). utils.Logger().Info().
RawJSON("delegation-current", []byte(current.String())). RawJSON("delegation-current", []byte(current.String())).
RawJSON("slash", []byte(slash.String())). RawJSON("slash", []byte(slash.String())).

@ -14,9 +14,11 @@ import (
"github.com/harmony-one/harmony/common/denominations" "github.com/harmony-one/harmony/common/denominations"
"github.com/harmony-one/harmony/consensus/votepower" "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"
common2 "github.com/harmony-one/harmony/internal/common" common2 "github.com/harmony-one/harmony/internal/common"
"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/effective"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
) )
@ -235,6 +237,7 @@ var (
randoDel = common.Address{} randoDel = common.Address{}
header = block.Header{} header = block.Header{}
subCommittee = []shard.BlsPublicKey{} subCommittee = []shard.BlsPublicKey{}
doubleSignEpochBig = big.NewInt(doubleSignEpoch)
unit = func() interface{} { unit = func() interface{} {
// Ballot A setup // Ballot A setup
@ -283,11 +286,10 @@ func (s *scenario) defaultValidatorPair(
LastEpochInCommittee: big.NewInt(lastEpochInComm), LastEpochInCommittee: big.NewInt(lastEpochInComm),
MinSelfDelegation: new(big.Int).SetUint64(1 * denominations.One), MinSelfDelegation: new(big.Int).SetUint64(1 * denominations.One),
MaxTotalDelegation: new(big.Int).SetUint64(10 * denominations.One), MaxTotalDelegation: new(big.Int).SetUint64(10 * denominations.One),
Active: true, EPOSStatus: effective.Active,
Commission: commonCommission, Commission: commonCommission,
Description: commonDescr, Description: commonDescr,
CreationHeight: big.NewInt(creationHeight), CreationHeight: big.NewInt(creationHeight),
Banned: false,
}, },
Delegations: delegationsSnapshot, Delegations: delegationsSnapshot,
} }
@ -299,11 +301,10 @@ func (s *scenario) defaultValidatorPair(
LastEpochInCommittee: big.NewInt(lastEpochInComm + 1), LastEpochInCommittee: big.NewInt(lastEpochInComm + 1),
MinSelfDelegation: new(big.Int).SetUint64(1 * denominations.One), MinSelfDelegation: new(big.Int).SetUint64(1 * denominations.One),
MaxTotalDelegation: new(big.Int).SetUint64(10 * denominations.One), MaxTotalDelegation: new(big.Int).SetUint64(10 * denominations.One),
Active: true, EPOSStatus: effective.Active,
Commission: commonCommission, Commission: commonCommission,
Description: commonDescr, Description: commonDescr,
CreationHeight: big.NewInt(creationHeight), CreationHeight: big.NewInt(creationHeight),
Banned: false,
}, },
Delegations: delegationsCurrent, Delegations: delegationsCurrent,
} }
@ -358,52 +359,63 @@ func (s *scenario) defaultDelegationPair() (
return delegationsSnapshot, delegationsCurrent return delegationsSnapshot, delegationsCurrent
} }
func exampleSlashRecords() Records { func defaultSlashRecord() Record {
return Records{ return Record{
Record{ Evidence: Evidence{
Evidence: Evidence{ ConflictingBallots: ConflictingBallots{
ConflictingBallots: ConflictingBallots{ AlreadyCastBallot: votepower.Ballot{
AlreadyCastBallot: votepower.Ballot{ SignerPubKey: blsWrapA,
SignerPubKey: blsWrapA, BlockHeaderHash: hashA,
BlockHeaderHash: hashA, Signature: common.Hex2Bytes(signerABLSSignature),
Signature: common.Hex2Bytes(signerABLSSignature), Height: doubleSignBlockNumber,
}, ViewID: doubleSignViewID,
DoubleSignedBallot: votepower.Ballot{
SignerPubKey: blsWrapB,
BlockHeaderHash: hashB,
Signature: common.Hex2Bytes(signerBBLSSignature),
},
}, },
Moment: Moment{ DoubleSignedBallot: votepower.Ballot{
Epoch: big.NewInt(doubleSignEpoch), SignerPubKey: blsWrapB,
Height: big.NewInt(doubleSignBlockNumber), BlockHeaderHash: hashB,
TimeUnixNano: big.NewInt(doubleSignUnixNano), Signature: common.Hex2Bytes(signerBBLSSignature),
ViewID: doubleSignViewID, Height: doubleSignBlockNumber,
ShardID: doubleSignShardID, ViewID: doubleSignViewID,
}, },
ProposalHeader: &header,
}, },
Reporter: reporterAddr, Moment: Moment{
Offender: offenderAddr, Epoch: big.NewInt(doubleSignEpoch),
TimeUnixNano: big.NewInt(doubleSignUnixNano),
ShardID: doubleSignShardID,
},
ProposalHeader: &header,
}, },
Reporter: reporterAddr,
Offender: offenderAddr,
} }
} }
func exampleSlashRecords() Records {
return Records{defaultSlashRecord()}
}
type mockOutSnapshotReader struct { type mockOutSnapshotReader struct {
snapshot staking.ValidatorWrapper snapshot staking.ValidatorWrapper
} }
func (m mockOutSnapshotReader) ReadValidatorSnapshot( func (m mockOutSnapshotReader) ReadValidatorSnapshotAtEpoch(
common.Address, epoch *big.Int,
addr common.Address,
) (*staking.ValidatorWrapper, error) { ) (*staking.ValidatorWrapper, error) {
return &m.snapshot, nil return &m.snapshot, nil
} }
type mockOutChainReader struct{} type mockOutChainReader struct{}
func (mockOutChainReader) CurrentBlock() *types.Block {
b := types.Block{}
b.Header().SetEpoch(doubleSignEpochBig)
return &b
}
func (mockOutChainReader) ReadShardState(epoch *big.Int) (*shard.State, error) { func (mockOutChainReader) ReadShardState(epoch *big.Int) (*shard.State, error) {
return &shard.State{ return &shard.State{
Epoch: big.NewInt(doubleSignEpoch), Epoch: doubleSignEpochBig,
Shards: []shard.Committee{ Shards: []shard.Committee{
shard.Committee{ shard.Committee{
ShardID: doubleSignShardID, ShardID: doubleSignShardID,
@ -420,10 +432,13 @@ func (mockOutChainReader) ReadShardState(epoch *big.Int) (*shard.State, error) {
} }
func TestVerify(t *testing.T) { func TestVerify(t *testing.T) {
stateHandle := defaultStateWithAccountsApplied()
if err := Verify( if err := Verify(
mockOutChainReader{}, &exampleSlashRecords()[0], mockOutChainReader{}, stateHandle, &exampleSlashRecords()[0],
); err != nil { ); err != nil {
t.Errorf("could not verify slash %s", err.Error()) // TODO
// t.Errorf("could not verify slash %s", err.Error())
} }
} }
@ -533,6 +548,17 @@ func TestRoundTripSlashRecord(t *testing.T) {
} }
} }
func TestSetDifference(t *testing.T) {
setA, setB := exampleSlashRecords(), exampleSlashRecords()
additionalSlash := defaultSlashRecord()
additionalSlash.Evidence.Epoch.Add(additionalSlash.Evidence.Epoch, common.Big1)
setB = append(setB, additionalSlash)
diff := setA.SetDifference(setB)
if diff[0].Hash() != additionalSlash.Hash() {
t.Errorf("did not get set difference of slash")
}
}
// TODO bytes used for this example are stale, need to update RLP dump // TODO bytes used for this example are stale, need to update RLP dump
// func TestApply(t *testing.T) { // func TestApply(t *testing.T) {
// slashes := exampleSlashRecords() // slashes := exampleSlashRecords()

@ -5,9 +5,9 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"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/effective"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -54,27 +54,27 @@ func (d Directive) String() string {
// CreateValidator - type for creating a new validator // CreateValidator - type for creating a new validator
type CreateValidator struct { type CreateValidator struct {
ValidatorAddress common.Address `json:"validator_address"` ValidatorAddress common.Address `json:"validator-address"`
Description `json:"description"` Description `json:"description"`
CommissionRates `json:"commission"` CommissionRates `json:"commission"`
MinSelfDelegation *big.Int `json:"min_self_delegation"` MinSelfDelegation *big.Int `json:"min-self-delegation"`
MaxTotalDelegation *big.Int `json:"max_total_delegation"` MaxTotalDelegation *big.Int `json:"max-total-delegation"`
SlotPubKeys []shard.BlsPublicKey `json:"slot_pub_keys"` SlotPubKeys []shard.BlsPublicKey `json:"slot-pub-keys"`
SlotKeySigs []shard.BLSSignature `json:"slot_key_sigs"` SlotKeySigs []shard.BLSSignature `json:"slot-key-sigs"`
Amount *big.Int `json:"amount"` Amount *big.Int `json:"amount"`
} }
// EditValidator - type for edit existing validator // EditValidator - type for edit existing validator
type EditValidator struct { type EditValidator struct {
ValidatorAddress common.Address `json:"validator_address"` ValidatorAddress common.Address `json:"validator-address"`
Description `json:"description"` Description `json:"description"`
CommissionRate *numeric.Dec `json:"commission_rate" rlp:"nil"` CommissionRate *numeric.Dec `json:"commission-rate" rlp:"nil"`
MinSelfDelegation *big.Int `json:"min_self_delegation" rlp:"nil"` MinSelfDelegation *big.Int `json:"min-self-delegation" rlp:"nil"`
MaxTotalDelegation *big.Int `json:"max_total_delegation" rlp:"nil"` MaxTotalDelegation *big.Int `json:"max-total-delegation" rlp:"nil"`
SlotKeyToRemove *shard.BlsPublicKey `json:"slot_key_to_remove" rlp:"nil"` SlotKeyToRemove *shard.BlsPublicKey `json:"slot-key-to_remove" rlp:"nil"`
SlotKeyToAdd *shard.BlsPublicKey `json:"slot_key_to_add" rlp:"nil"` SlotKeyToAdd *shard.BlsPublicKey `json:"slot-key-to_add" rlp:"nil"`
SlotKeyToAddSig *shard.BLSSignature `json:"slot_key_to_add_sig" rlp:"nil"` SlotKeyToAddSig *shard.BLSSignature `json:"slot-key-to-add-sig" rlp:"nil"`
Active *bool `json:"active" rlp:"nil"` EPOSStatus effective.Eligibility `json:"epos-eligibility-status" rlp:"nil"`
} }
// Delegate - type for delegating to a validator // Delegate - type for delegating to a validator

@ -14,6 +14,7 @@ import (
"github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/ctxerror"
"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/effective"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -56,11 +57,16 @@ var (
errSlotKeyToRemoveNotFound = errors.New("slot key to remove not found") errSlotKeyToRemoveNotFound = errors.New("slot key to remove not found")
errSlotKeyToAddExists = errors.New("slot key to add already exists") errSlotKeyToAddExists = errors.New("slot key to add already exists")
errDuplicateSlotKeys = errors.New("slot keys can not have duplicates") errDuplicateSlotKeys = errors.New("slot keys can not have duplicates")
errExcessiveBLSKeys = errors.New("more slot keys provided than allowed")
errCannotChangeBannedTaint = errors.New("cannot change validator banned status")
) )
// ValidatorSnapshotReader .. // ValidatorSnapshotReader ..
type ValidatorSnapshotReader interface { type ValidatorSnapshotReader interface {
ReadValidatorSnapshot(common.Address) (*ValidatorWrapper, error) ReadValidatorSnapshotAtEpoch(
epoch *big.Int,
addr common.Address,
) (*ValidatorWrapper, error)
} }
type counters struct { type counters struct {
@ -78,7 +84,8 @@ type ValidatorWrapper struct {
Counters counters Counters counters
} }
// Computed .. // Computed represents current epoch
// availability measures, mostly for RPC
type Computed struct { type Computed struct {
Signed *big.Int `json:"current-epoch-signed"` Signed *big.Int `json:"current-epoch-signed"`
ToSign *big.Int `json:"current-epoch-to-sign"` ToSign *big.Int `json:"current-epoch-to-sign"`
@ -153,20 +160,20 @@ type Validator struct {
MaxTotalDelegation *big.Int `json:"max-total-delegation"` MaxTotalDelegation *big.Int `json:"max-total-delegation"`
// Is the validator active in participating // Is the validator active in participating
// committee selection process or not // committee selection process or not
Active bool `json:"active"` EPOSStatus effective.Eligibility `json:"epos-eligibility-status"`
// commission parameters // commission parameters
Commission Commission
// description for the validator // description for the validator
Description Description
// CreationHeight is the height of creation // CreationHeight is the height of creation
CreationHeight *big.Int `json:"creation-height"` CreationHeight *big.Int `json:"creation-height"`
// Banned records whether this validator is banned
// from the network because they double-signed
Banned bool `json:"banned"`
} }
// DoNotEnforceMaxBLS ..
const DoNotEnforceMaxBLS = -1
// SanityCheck checks basic requirements of a validator // SanityCheck checks basic requirements of a validator
func (v *Validator) SanityCheck() error { func (v *Validator) SanityCheck(oneThirdExtrn int) error {
if _, err := v.EnsureLength(); err != nil { if _, err := v.EnsureLength(); err != nil {
return err return err
} }
@ -175,6 +182,14 @@ func (v *Validator) SanityCheck() error {
return errNeedAtLeastOneSlotKey return errNeedAtLeastOneSlotKey
} }
if c := len(v.SlotPubKeys); oneThirdExtrn != DoNotEnforceMaxBLS &&
c > oneThirdExtrn {
return errors.Wrapf(
errExcessiveBLSKeys, "have: %d allowed: %d",
c, oneThirdExtrn,
)
}
if v.MinSelfDelegation == nil { if v.MinSelfDelegation == nil {
return errNilMinSelfDelegation return errNilMinSelfDelegation
} }
@ -183,8 +198,10 @@ func (v *Validator) SanityCheck() error {
return errNilMaxTotalDelegation return errNilMaxTotalDelegation
} }
// MinSelfDelegation must be >= 1 ONE // if I'm not banned, then I must
if !v.Banned && v.MinSelfDelegation.Cmp(big.NewInt(denominations.One)) < 0 { // ensure that MinSelfDelegation >= 1 ONE
if v.EPOSStatus != effective.Banned &&
v.MinSelfDelegation.Cmp(big.NewInt(denominations.One)) < 0 {
return errors.Wrapf( return errors.Wrapf(
errMinSelfDelegationTooSmall, errMinSelfDelegationTooSmall,
"delegation-given %s", v.MinSelfDelegation.String(), "delegation-given %s", v.MinSelfDelegation.String(),
@ -282,8 +299,12 @@ var (
) )
// SanityCheck checks the basic requirements // SanityCheck checks the basic requirements
func (w *ValidatorWrapper) SanityCheck() error { func (w *ValidatorWrapper) SanityCheck(
if err := w.Validator.SanityCheck(); err != nil { oneThirdExternalValidator int,
) error {
if err := w.Validator.SanityCheck(
oneThirdExternalValidator,
); err != nil {
return err return err
} }
// Self delegation must be >= MinSelfDelegation // Self delegation must be >= MinSelfDelegation
@ -293,7 +314,8 @@ func (w *ValidatorWrapper) SanityCheck() error {
errInvalidSelfDelegation, "no self delegation given at all", errInvalidSelfDelegation, "no self delegation given at all",
) )
default: default:
if !w.Banned && w.Delegations[0].Amount.Cmp(w.Validator.MinSelfDelegation) < 0 { if w.EPOSStatus != effective.Banned &&
w.Delegations[0].Amount.Cmp(w.Validator.MinSelfDelegation) < 0 {
return errors.Wrapf( return errors.Wrapf(
errInvalidSelfDelegation, errInvalidSelfDelegation,
"have %s want %s", w.Delegations[0].Amount.String(), w.Validator.MinSelfDelegation, "have %s want %s", w.Delegations[0].Amount.String(), w.Validator.MinSelfDelegation,
@ -445,9 +467,15 @@ func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Valida
} }
v := Validator{ v := Validator{
val.ValidatorAddress, pubKeys, Address: val.ValidatorAddress,
new(big.Int), val.MinSelfDelegation, val.MaxTotalDelegation, true, SlotPubKeys: pubKeys,
commission, desc, blockNum, false, LastEpochInCommittee: new(big.Int),
MinSelfDelegation: val.MinSelfDelegation,
MaxTotalDelegation: val.MaxTotalDelegation,
EPOSStatus: effective.Active,
Commission: commission,
Description: desc,
CreationHeight: blockNum,
} }
return &v, nil return &v, nil
} }
@ -486,7 +514,9 @@ func UpdateValidatorFromEditMsg(validator *Validator, edit *EditValidator) error
} }
// we found key to be removed // we found key to be removed
if index >= 0 { if index >= 0 {
validator.SlotPubKeys = append(validator.SlotPubKeys[:index], validator.SlotPubKeys[index+1:]...) validator.SlotPubKeys = append(
validator.SlotPubKeys[:index], validator.SlotPubKeys[index+1:]...,
)
} else { } else {
return errSlotKeyToRemoveNotFound return errSlotKeyToRemoveNotFound
} }
@ -510,8 +540,15 @@ func UpdateValidatorFromEditMsg(validator *Validator, edit *EditValidator) error
} }
} }
if edit.Active != nil { switch validator.EPOSStatus {
validator.Active = *edit.Active case effective.Banned:
return errCannotChangeBannedTaint
default:
switch edit.EPOSStatus {
case effective.Active, effective.Inactive:
validator.EPOSStatus = edit.EPOSStatus
default:
}
} }
return nil return nil

@ -13,6 +13,7 @@ import (
"github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/ctxerror"
"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/effective"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -97,16 +98,25 @@ func setSlotKeySigs() []shard.BLSSignature {
// create a new validator // create a new validator
func createNewValidator() Validator { func createNewValidator() Validator {
cr := CommissionRates{Rate: numeric.OneDec(), MaxRate: numeric.OneDec(), MaxChangeRate: numeric.ZeroDec()} cr := CommissionRates{
Rate: numeric.OneDec(),
MaxRate: numeric.OneDec(),
MaxChangeRate: numeric.ZeroDec(),
}
c := Commission{cr, big.NewInt(300)} c := Commission{cr, big.NewInt(300)}
d := Description{Name: "Wayne", Identity: "wen", Website: "harmony.one.wen", Details: "best"} d := Description{
Name: "Wayne",
Identity: "wen",
Website: "harmony.one.wen",
Details: "best",
}
v := Validator{ v := Validator{
Address: validatorAddr, Address: validatorAddr,
SlotPubKeys: slotPubKeys, SlotPubKeys: slotPubKeys,
LastEpochInCommittee: big.NewInt(20), LastEpochInCommittee: big.NewInt(20),
MinSelfDelegation: big.NewInt(1e18), MinSelfDelegation: big.NewInt(1e18),
MaxTotalDelegation: big.NewInt(3e18), MaxTotalDelegation: big.NewInt(3e18),
Active: false, EPOSStatus: effective.Inactive,
Commission: c, Commission: c,
Description: d, Description: d,
CreationHeight: big.NewInt(12306), CreationHeight: big.NewInt(12306),
@ -157,7 +167,7 @@ func TestTotalDelegation(t *testing.T) {
// check the validator wrapper's sanity // check the validator wrapper's sanity
func TestValidatorSanityCheck(t *testing.T) { func TestValidatorSanityCheck(t *testing.T) {
err := validator.SanityCheck() err := validator.SanityCheck(DoNotEnforceMaxBLS)
if err != nil { if err != nil {
t.Error("expected", nil, "got", err) t.Error("expected", nil, "got", err)
} }
@ -165,16 +175,16 @@ func TestValidatorSanityCheck(t *testing.T) {
v := Validator{ v := Validator{
Address: validatorAddr, Address: validatorAddr,
} }
if err := v.SanityCheck(); err != errNeedAtLeastOneSlotKey { if err := v.SanityCheck(DoNotEnforceMaxBLS); err != errNeedAtLeastOneSlotKey {
t.Error("expected", errNeedAtLeastOneSlotKey, "got", err) t.Error("expected", errNeedAtLeastOneSlotKey, "got", err)
} }
v.SlotPubKeys = setSlotPubKeys() v.SlotPubKeys = setSlotPubKeys()
if err := v.SanityCheck(); err != errNilMinSelfDelegation { if err := v.SanityCheck(DoNotEnforceMaxBLS); err != errNilMinSelfDelegation {
t.Error("expected", errNilMinSelfDelegation, "got", err) t.Error("expected", errNilMinSelfDelegation, "got", err)
} }
v.MinSelfDelegation = big.NewInt(1e18) v.MinSelfDelegation = big.NewInt(1e18)
if err := v.SanityCheck(); err != errNilMaxTotalDelegation { if err := v.SanityCheck(DoNotEnforceMaxBLS); err != errNilMaxTotalDelegation {
t.Error("expected", errNilMaxTotalDelegation, "got", err) t.Error("expected", errNilMaxTotalDelegation, "got", err)
} }
v.MinSelfDelegation = big.NewInt(1e17) v.MinSelfDelegation = big.NewInt(1e17)
@ -183,7 +193,7 @@ func TestValidatorSanityCheck(t *testing.T) {
errMinSelfDelegationTooSmall, errMinSelfDelegationTooSmall,
"delegation-given %s", v.MinSelfDelegation.String(), "delegation-given %s", v.MinSelfDelegation.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
@ -195,7 +205,7 @@ func TestValidatorSanityCheck(t *testing.T) {
v.MaxTotalDelegation.String(), v.MaxTotalDelegation.String(),
v.MinSelfDelegation.String(), v.MinSelfDelegation.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
v.MinSelfDelegation = big.NewInt(1e18) v.MinSelfDelegation = big.NewInt(1e18)
@ -208,14 +218,14 @@ func TestValidatorSanityCheck(t *testing.T) {
e = errors.Wrapf( e = errors.Wrapf(
errInvalidCommissionRate, "rate:%s", v.Rate.String(), errInvalidCommissionRate, "rate:%s", v.Rate.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
v.Commission.Rate = plusTwoDec v.Commission.Rate = plusTwoDec
e = errors.Wrapf( e = errors.Wrapf(
errInvalidCommissionRate, "rate:%s", v.Rate.String(), errInvalidCommissionRate, "rate:%s", v.Rate.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
v.Commission.Rate = numeric.MustNewDecFromStr("0.5") v.Commission.Rate = numeric.MustNewDecFromStr("0.5")
@ -223,14 +233,14 @@ func TestValidatorSanityCheck(t *testing.T) {
e = errors.Wrapf( e = errors.Wrapf(
errInvalidCommissionRate, "rate:%s", v.MaxRate.String(), errInvalidCommissionRate, "rate:%s", v.MaxRate.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
v.Commission.MaxRate = plusTwoDec v.Commission.MaxRate = plusTwoDec
e = errors.Wrapf( e = errors.Wrapf(
errInvalidCommissionRate, "rate:%s", v.MaxRate.String(), errInvalidCommissionRate, "rate:%s", v.MaxRate.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
v.Commission.MaxRate = numeric.MustNewDecFromStr("0.9") v.Commission.MaxRate = numeric.MustNewDecFromStr("0.9")
@ -238,14 +248,14 @@ func TestValidatorSanityCheck(t *testing.T) {
e = errors.Wrapf( e = errors.Wrapf(
errInvalidCommissionRate, "rate:%s", v.MaxChangeRate.String(), errInvalidCommissionRate, "rate:%s", v.MaxChangeRate.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
v.Commission.MaxChangeRate = plusTwoDec v.Commission.MaxChangeRate = plusTwoDec
e = errors.Wrapf( e = errors.Wrapf(
errInvalidCommissionRate, "rate:%s", v.MaxChangeRate.String(), errInvalidCommissionRate, "rate:%s", v.MaxChangeRate.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
v.Commission.MaxChangeRate = numeric.MustNewDecFromStr("0.05") v.Commission.MaxChangeRate = numeric.MustNewDecFromStr("0.05")
@ -253,7 +263,7 @@ func TestValidatorSanityCheck(t *testing.T) {
e = errors.Wrapf( e = errors.Wrapf(
errCommissionRateTooLarge, "rate:%s", v.MaxRate.String(), errCommissionRateTooLarge, "rate:%s", v.MaxRate.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
v.Commission.MaxRate = numeric.MustNewDecFromStr("0.51") v.Commission.MaxRate = numeric.MustNewDecFromStr("0.51")
@ -261,12 +271,12 @@ func TestValidatorSanityCheck(t *testing.T) {
e = errors.Wrapf( e = errors.Wrapf(
errCommissionRateTooLarge, "rate:%s", v.MaxChangeRate.String(), errCommissionRateTooLarge, "rate:%s", v.MaxChangeRate.String(),
) )
if err := v.SanityCheck(); err.Error() != e.Error() { if err := v.SanityCheck(DoNotEnforceMaxBLS); err.Error() != e.Error() {
t.Error("expected", e, "got", err) t.Error("expected", e, "got", err)
} }
v.Commission.MaxChangeRate = numeric.MustNewDecFromStr("0.05") v.Commission.MaxChangeRate = numeric.MustNewDecFromStr("0.05")
v.SlotPubKeys = append(v.SlotPubKeys, v.SlotPubKeys[0]) v.SlotPubKeys = append(v.SlotPubKeys, v.SlotPubKeys[0])
if err := v.SanityCheck(); err != errDuplicateSlotKeys { if err := v.SanityCheck(DoNotEnforceMaxBLS); err != errDuplicateSlotKeys {
t.Error("expected", errDuplicateSlotKeys, "got", err) t.Error("expected", errDuplicateSlotKeys, "got", err)
} }
} }
@ -274,21 +284,21 @@ func TestValidatorSanityCheck(t *testing.T) {
func TestValidatorWrapperSanityCheck(t *testing.T) { func TestValidatorWrapperSanityCheck(t *testing.T) {
// no delegation must fail // no delegation must fail
wrapper := createNewValidatorWrapper(createNewValidator()) wrapper := createNewValidatorWrapper(createNewValidator())
if err := wrapper.SanityCheck(); err == nil { if err := wrapper.SanityCheck(DoNotEnforceMaxBLS); err == nil {
t.Error("expected", errInvalidSelfDelegation, "got", err) t.Error("expected", errInvalidSelfDelegation, "got", err)
} }
// valid self delegation must not fail // valid self delegation must not fail
valDel := NewDelegation(validatorAddr, big.NewInt(1e18)) valDel := NewDelegation(validatorAddr, big.NewInt(1e18))
wrapper.Delegations = []Delegation{valDel} wrapper.Delegations = []Delegation{valDel}
if err := wrapper.SanityCheck(); err != nil { if err := wrapper.SanityCheck(DoNotEnforceMaxBLS); err != nil {
t.Errorf("validator wrapper SanityCheck failed: %s", err) t.Errorf("validator wrapper SanityCheck failed: %s", err)
} }
// invalid self delegation must fail // invalid self delegation must fail
valDel = NewDelegation(validatorAddr, big.NewInt(1e17)) valDel = NewDelegation(validatorAddr, big.NewInt(1e17))
wrapper.Delegations = []Delegation{valDel} wrapper.Delegations = []Delegation{valDel}
if err := wrapper.SanityCheck(); err == nil { if err := wrapper.SanityCheck(DoNotEnforceMaxBLS); err == nil {
t.Error("expected", errInvalidSelfDelegation, "got", err) t.Error("expected", errInvalidSelfDelegation, "got", err)
} }
} }

@ -0,0 +1,59 @@
package verify
import (
"encoding/binary"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/consensus/quorum"
bls_cosi "github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/multibls"
"github.com/harmony-one/harmony/shard"
"github.com/pkg/errors"
)
var (
errQuorumVerifyAggSign = errors.New("insufficient voting power to verify aggreate sig")
errAggregateSigFail = errors.New("could not verify hash of aggregate signature")
)
// AggregateSigForCommittee ..
func AggregateSigForCommittee(
committee *shard.Committee,
aggSignature *bls.Sign,
hash common.Hash,
blockNum uint64,
bitmap []byte,
) error {
committerKeys, err := committee.BLSPublicKeys()
if err != nil {
return err
}
mask, err := bls_cosi.NewMask(committerKeys, nil)
if err != nil {
return err
}
if err := mask.SetMask(bitmap); err != nil {
return err
}
decider := quorum.NewDecider(quorum.SuperMajorityStake)
decider.SetMyPublicKeyProvider(func() (*multibls.PublicKey, error) {
return nil, nil
})
if _, err := decider.SetVoters(committee.Slots); err != nil {
return err
}
if !decider.IsQuorumAchievedByMask(mask) {
return errQuorumVerifyAggSign
}
blockNumBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(blockNumBytes, blockNum)
commitPayload := append(blockNumBytes, hash[:]...)
if !aggSignature.VerifyHash(mask.AggregatePublic, commitPayload) {
return errAggregateSigFail
}
return nil
}
Loading…
Cancel
Save