[slash][quorum][consensus] Extend existing data structures for slashing needs (#2148)

* [slashing] Add Slashing fields in header, only available from v3 onward

* [slashing] Add Banned field to validator struct

* [consensus] Remove dead code

* [node] Use named value for shardID of beaconchain

* [consensus] No need to memory thrash Buffer

* [slashing] Add slash record type, stub out BroadcastSlash

* [cmd] Bump 2019 -> 2020

* [votepower] Add ballot results structs

* [quorum][slash] Refactor quorum to accomdate extra data points needed for slashing

* [consensus] Begin refactor of consensus leader messages

* [consensus] Accomdate consensus for changed method signatures in quorum

* [project] Whitespace and helper function

* [block] More comments and logs for fields that do not make sense in earlier v version

* [slashing] More comments, field renames, tag Edgar TODO

* [quorum] Undo name change of Prepare to Announce, was mislead by other existing code
pull/2161/head
Edgar Aroutiounian 5 years ago committed by GitHub
parent e87b338ddc
commit 44c5704fc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      block/interface/header.go
  2. 29
      block/v0/header.go
  3. 25
      block/v1/header.go
  4. 18
      block/v2/header.go
  5. 11
      block/v3/header.go
  6. 17
      cmd/harmony/main.go
  7. 37
      consensus/consensus.go
  8. 60
      consensus/consensus_leader_msg.go
  9. 8
      consensus/consensus_leader_msg_test.go
  10. 33
      consensus/consensus_v2.go
  11. 13
      consensus/quorum/one-node-staked-vote.go
  12. 3
      consensus/quorum/one-node-staked-vote_test.go
  13. 88
      consensus/quorum/quorum.go
  14. 6
      consensus/view_change.go
  15. 20
      consensus/votepower/roster.go
  16. 23
      internal/utils/singleton.go
  17. 6
      node/node_explorer.go
  18. 52
      node/node_handler.go
  19. 10
      shard/shard_state.go
  20. 30
      staking/slash/double-sign.go
  21. 12
      staking/slash/double-sign_test.go
  22. 19
      staking/slash/slasher.go
  23. 4
      staking/types/validator.go

@ -228,4 +228,12 @@ type Header interface {
// Copy returns a copy of the header.
Copy() Header
// Slashes is the RLP-encoded form of []slash.Record,
// The returned slice is a copy; the caller may do anything with it
Slashes() []byte
// SetSlashes sets the RLP-encoded form of slashes
// It stores a copy; the caller may freely modify the original.
SetSlashes(newSlashes []byte)
}

@ -9,12 +9,11 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/rs/zerolog"
blockif "github.com/harmony-one/harmony/block/interface"
"github.com/harmony-one/harmony/crypto/hash"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard"
"github.com/rs/zerolog"
)
// Header is the V1 block header.
@ -365,6 +364,8 @@ func (h *Header) SetShardState(newShardState []byte) {
//
// The returned slice is a copy; the caller may do anything with it.
func (h *Header) CrossLinks() []byte {
h.Logger(utils.Logger()).Error().
Msg("No crosslinks in V0 header")
return nil
}
@ -373,11 +374,25 @@ func (h *Header) CrossLinks() []byte {
//
// It stores a copy; the caller may freely modify the original.
func (h *Header) SetCrossLinks(newCrossLinks []byte) {
if len(newCrossLinks) > 0 {
h.Logger(utils.Logger()).Warn().
Hex("crossLinks", newCrossLinks).
Msg("cannot store cross-chain links in V0 header")
}
h.Logger(utils.Logger()).Warn().
Hex("crossLinks", newCrossLinks).
Msg("cannot store cross-chain links in V0 header")
}
// Slashes is the RLP-encoded form of []slash.Record,
// The returned slice is a copy; the caller may do anything with it
func (h *Header) Slashes() []byte {
h.Logger(utils.Logger()).Error().
Msg("No slashes in V0 header")
return nil
}
// SetSlashes sets the RLP-encoded form of slashes
// It stores a copy; the caller may freely modify the original.
func (h *Header) SetSlashes(newSlashes []byte) {
h.Logger(utils.Logger()).Error().
Hex("slashes", newSlashes).
Msg("cannot store slashes in V0 header")
}
// field type overrides for gencodec

@ -9,12 +9,11 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/rs/zerolog"
blockif "github.com/harmony-one/harmony/block/interface"
"github.com/harmony-one/harmony/crypto/hash"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard"
"github.com/rs/zerolog"
)
// Header is the V1 block header.
@ -361,11 +360,23 @@ func (h *Header) CrossLinks() []byte {
//
// It stores a copy; the caller may freely modify the original.
func (h *Header) SetCrossLinks(newCrossLinks []byte) {
if len(newCrossLinks) > 0 {
h.Logger(utils.Logger()).Warn().
Hex("crossLinks", newCrossLinks).
Msg("cannot store cross-chain links in V1 header")
}
h.Logger(utils.Logger()).Warn().
Hex("crossLinks", newCrossLinks).
Msg("cannot store cross-chain links in V1 header")
}
// Slashes ..
func (h *Header) Slashes() []byte {
h.Logger(utils.Logger()).Error().
Msg("No slashes in V1 header")
return nil
}
// SetSlashes ..
func (h *Header) SetSlashes(newSlashes []byte) {
h.Logger(utils.Logger()).Error().
Hex("slashes", newSlashes).
Msg("cannot store slashes in V1 header")
}
// field type overrides for gencodec

@ -9,11 +9,11 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/rs/zerolog"
blockif "github.com/harmony-one/harmony/block/interface"
"github.com/harmony-one/harmony/crypto/hash"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard"
"github.com/rs/zerolog"
)
// Header is the V2 block header.
@ -364,6 +364,20 @@ func (h *Header) SetCrossLinks(newCrossLinks []byte) {
h.fields.CrossLinks = append(newCrossLinks[:0:0], newCrossLinks...)
}
// Slashes ..
func (h *Header) Slashes() []byte {
h.Logger(utils.Logger()).Error().
Msg("No slashes in V2 header")
return nil
}
// SetSlashes ..
func (h *Header) SetSlashes(newSlashes []byte) {
h.Logger(utils.Logger()).Error().
Hex("slashes", newSlashes).
Msg("cannot store slashes in V2 header")
}
// field type overrides for gencodec
type headerMarshaling struct {
Difficulty *hexutil.Big

@ -71,6 +71,7 @@ type headerFields struct {
Vdf []byte `json:"vdf"`
ShardState []byte `json:"shardState"`
CrossLinks []byte `json:"crossLink"`
Slashes []byte `json:slashes`
}
// ParentHash is the header hash of the parent block. For the genesis block
@ -370,6 +371,16 @@ func (h *Header) SetCrossLinks(newCrossLinks []byte) {
h.fields.CrossLinks = append(newCrossLinks[:0:0], newCrossLinks...)
}
// Slashes ..
func (h *Header) Slashes() []byte {
return append(h.fields.Slashes[:0:0], h.fields.Slashes...)
}
// SetSlashes ..
func (h *Header) SetSlashes(newSlashes []byte) {
h.fields.Slashes = append(newSlashes[:0:0], newSlashes...)
}
// field type overrides for gencodec
type headerMarshaling struct {
Difficulty *hexutil.Big

@ -401,8 +401,7 @@ func setupConsensusAndNode(nodeConfig *nodeconfig.ConfigType) *node.Node {
currentNode.State = node.NodeWaitToJoin
// update consensus information based on the blockchain
mode := currentConsensus.UpdateConsensusInformation()
currentConsensus.SetMode(mode)
currentConsensus.SetMode(currentConsensus.UpdateConsensusInformation())
// Watching currentNode and currentConsensus.
memprofiling.GetMemProfiling().Add("currentNode", currentNode)
@ -429,7 +428,10 @@ func main() {
}
nodeconfig.SetPublicRPC(*publicRPC)
nodeconfig.SetVersion(fmt.Sprintf("Harmony (C) 2019. %v, version %v-%v (%v %v)", path.Base(os.Args[0]), version, commit, builtBy, builtAt))
nodeconfig.SetVersion(
fmt.Sprintf("Harmony (C) 2020. %v, version %v-%v (%v %v)",
path.Base(os.Args[0]), version, commit, builtBy, builtAt),
)
if *versionFlag {
printVersion()
}
@ -503,9 +505,12 @@ func main() {
currentNode.SetSyncFreq(*syncFreq)
currentNode.SetBeaconSyncFreq(*beaconSyncFreq)
if nodeConfig.ShardID != shard.BeaconChainShardID && currentNode.NodeConfig.Role() != nodeconfig.ExplorerNode {
utils.Logger().Info().Uint32("shardID", currentNode.Blockchain().ShardID()).Uint32("shardID", nodeConfig.ShardID).Msg("SupportBeaconSyncing")
go currentNode.SupportBeaconSyncing()
if nodeConfig.ShardID != shard.BeaconChainShardID &&
currentNode.NodeConfig.Role() != nodeconfig.ExplorerNode {
utils.Logger().Info().
Uint32("shardID", currentNode.Blockchain().ShardID()).
Uint32("shardID", nodeConfig.ShardID).Msg("SupportBeaconSyncing")
currentNode.SupportBeaconSyncing()
}
if uint64(*doRevertBefore) != 0 && uint64(*revertTo) != 0 {

@ -25,38 +25,30 @@ const (
// Consensus is the main struct with all states and data related to consensus process.
type Consensus struct {
Decider quorum.Decider
// FBFTLog stores the pbft messages and blocks during FBFT process
FBFTLog *FBFTLog
// phase: different phase of FBFT protocol: pre-prepare, prepare, commit, finish etc
phase FBFTPhase
// current indicates what state a node is in
current State
// epoch: current epoch number
epoch uint64
// blockNum: the next blockNumber that FBFT is going to agree on,
// should be equal to the blockNumber of next block
blockNum uint64
// channel to receive consensus message
MsgChan chan []byte
// How long to delay sending commit messages.
delayCommit time.Duration
// Consensus rounds whose commit phase finished
commitFinishChan chan uint64
// 2 types of timeouts: normal and viewchange
consensusTimeout map[TimeoutType]*utils.Timeout
// Commits collected from validators.
aggregatedPrepareSig *bls.Sign
aggregatedCommitSig *bls.Sign
prepareBitmap *bls_cosi.Mask
commitBitmap *bls_cosi.Mask
// Commits collected from view change
// for each viewID, we need keep track of corresponding sigs and bitmap
// until one of the viewID has enough votes (>=2f+1)
@ -73,29 +65,21 @@ type Consensus struct {
viewIDBitmap map[uint64]*bls_cosi.Mask
m1Payload []byte // message payload for type m1 := |vcBlockHash|prepared_agg_sigs|prepared_bitmap|, new leader only need one
vcLock sync.Mutex // mutex for view change
// The chain reader for the blockchain this consensus is working on
ChainReader *core.BlockChain
// map of nodeID to validator Peer object
validators sync.Map // key is the hex string of the blsKey, value is p2p.Peer
// Minimal number of peers in the shard
// If the number of validators is less than minPeers, the consensus won't start
MinPeers int
MinPeers int
pubKeyLock sync.Mutex
// private/public keys of current node
priKey *bls.SecretKey
PubKey *bls.PublicKey
priKey *bls.SecretKey
PubKey *bls.PublicKey
SelfAddress common.Address
// the publickey of leader
LeaderPubKey *bls.PublicKey
viewID uint64
viewID uint64
// Blockhash - 32 byte
blockHash [32]byte
// Block to run consensus on
@ -108,13 +92,10 @@ type Consensus struct {
ShardID uint32
// whether to ignore viewID check
ignoreViewIDCheck bool
// global consensus mutex
mutex sync.Mutex
// consensus information update mutex
infoMutex sync.Mutex
// Signal channel for starting a new consensus process
ReadySignal chan struct{}
// The post-consensus processing func passed from Node object
@ -122,13 +103,10 @@ type Consensus struct {
OnConsensusDone func(*types.Block, []byte)
// The verifier func passed from Node object
BlockVerifier func(*types.Block) error
// verified block to state sync broadcast
VerifiedNewBlock chan *types.Block
// will trigger state syncing when blockNum is low
blockNumLowChan chan struct{}
// Channel for DRG protocol to send pRnd (preimage of randomness resulting from combined vrf
// randomnesses) to consensus. The first 32 bytes are randomness, the rest is for bitmap.
PRndChannel chan []byte
@ -136,22 +114,16 @@ type Consensus struct {
// bytes are the seed for deriving VDF
RndChannel chan [vdfAndSeedSize]byte
pendingRnds [][vdfAndSeedSize]byte // A list of pending randomness
uniqueIDInstance *utils.UniqueValidatorID
// The p2p host used to send/receive p2p messages
host p2p.Host
// MessageSender takes are of sending consensus message and the corresponding retry logic.
msgSender *MessageSender
// Used to convey to the consensus main loop that block syncing has finished.
syncReadyChan chan struct{}
// Used to convey to the consensus main loop that node is out of sync
syncNotReadyChan chan struct{}
// If true, this consensus will not propose view change.
disableViewChange bool
// last node block reward for metrics
lastBlockReward *big.Int
}
@ -241,7 +213,6 @@ func New(
consensus.lastBlockReward = big.NewInt(0)
// channel for receiving newly generated VDF
consensus.RndChannel = make(chan [vdfAndSeedSize]byte)
consensus.uniqueIDInstance = utils.GetUniqueValidatorIDInstance()
memprofiling.GetMemProfiling().Add("consensus.FBFTLog", consensus.FBFTLog)
return &consensus, nil
}

@ -11,6 +11,54 @@ import (
"github.com/harmony-one/harmony/internal/utils"
)
// LeaderNetworkMessage is a message intended to be
// created only by the leader for distribution to
// all the other quorum members.
type LeaderNetworkMessage struct {
Phase quorum.Phase
Bytes []byte
OptionalAggregateSignature *bls.Sign
}
// TODO(Edgar) Finish refactoring other three message constructions folded into this function.
func (consensus *Consensus) construct(p quorum.Phase) *LeaderNetworkMessage {
msgType := msg_pb.MessageType_ANNOUNCE
switch p {
case quorum.Commit:
msgType = msg_pb.MessageType_COMMITTED
case quorum.Prepare:
msgType = msg_pb.MessageType_PREPARED
}
message := &msg_pb.Message{
ServiceType: msg_pb.ServiceType_CONSENSUS,
Type: msgType,
Request: &msg_pb.Message_Consensus{
Consensus: &msg_pb.ConsensusRequest{},
},
}
consensusMsg := message.GetConsensus()
consensus.populateMessageFields(consensusMsg)
consensusMsg.Payload = consensus.blockHeader
marshaledMessage, err := consensus.signAndMarshalConsensusMessage(message)
if err != nil {
utils.Logger().Info().
Str("phase", p.String()).
Str("reason", err.Error()).
Msg("Failed to sign and marshal consensus message")
}
return &LeaderNetworkMessage{
Phase: p,
Bytes: proto.ConstructConsensusMessage(marshaledMessage),
OptionalAggregateSignature: nil,
}
}
// Constructs the announce message
func (consensus *Consensus) constructAnnounceMessage() []byte {
message := &msg_pb.Message{
@ -46,18 +94,20 @@ func (consensus *Consensus) constructPreparedMessage() ([]byte, *bls.Sign) {
// add block content in prepared message for slow validators to catchup
consensusMsg.Block = consensus.block
//// Payload
buffer := bytes.NewBuffer([]byte{})
// Payload
buffer := bytes.Buffer{}
// 96 bytes aggregated signature
aggSig := bls_cosi.AggregateSig(consensus.Decider.ReadAllSignatures(quorum.Prepare))
// TODO(Edgar) Finish refactoring with this API
// aggSig := consensus.Decider.AggregateVotes(quorum.Announce)
buffer.Write(aggSig.Serialize())
// Bitmap
buffer.Write(consensus.prepareBitmap.Bitmap)
consensusMsg.Payload = buffer.Bytes()
//// END Payload
// END Payload
marshaledMessage, err := consensus.signAndMarshalConsensusMessage(message)
if err != nil {
@ -80,7 +130,7 @@ func (consensus *Consensus) constructCommittedMessage() ([]byte, *bls.Sign) {
consensus.populateMessageFields(consensusMsg)
//// Payload
buffer := bytes.NewBuffer([]byte{})
buffer := bytes.Buffer{}
// 96 bytes aggregated signature
aggSig := bls_cosi.AggregateSig(consensus.Decider.ReadAllSignatures(quorum.Commit))
@ -90,7 +140,7 @@ func (consensus *Consensus) constructCommittedMessage() ([]byte, *bls.Sign) {
buffer.Write(consensus.commitBitmap.Bitmap)
consensusMsg.Payload = buffer.Bytes()
//// END Payload
// END Payload
marshaledMessage, err := consensus.signAndMarshalConsensusMessage(message)
if err != nil {

@ -66,8 +66,12 @@ func TestConstructPreparedMessage(test *testing.T) {
consensus.blockHash = [32]byte{}
message := "test string"
consensus.Decider.AddSignature(quorum.Prepare, leaderPubKey, leaderPriKey.Sign(message))
consensus.Decider.AddSignature(quorum.Prepare, validatorPubKey, validatorPriKey.Sign(message))
consensus.Decider.AddSignature(
quorum.Prepare, leaderPubKey, leaderPriKey.Sign(message), leaderPubKey, 9999,
)
consensus.Decider.AddSignature(
quorum.Prepare, validatorPubKey, validatorPriKey.Sign(message), leaderPubKey, 9999,
)
// According to RJ these failures are benign.
if err := consensus.prepareBitmap.SetKey(leaderPubKey, true); err != nil {
test.Log(ctxerror.New("prepareBitmap.SetKey").WithCause(err))

@ -111,9 +111,13 @@ func (consensus *Consensus) announce(block *types.Block) {
consensus.block = encodedBlock
consensus.blockHeader = encodedBlockHeader
msgToSend := consensus.constructAnnounceMessage()
// TODO Finish using this refactored way
// msgToSend := consensus.construct(quorum.Announce)
// save announce message to FBFTLog
// msgPayload, _ := proto.GetConsensusMessagePayload(msgToSend.Bytes)
msgPayload, _ := proto.GetConsensusMessagePayload(msgToSend)
// TODO(chao): don't unmarshall the message here and direclty pass the original object.
msg := &msg_pb.Message{}
_ = protobuf.Unmarshal(msgPayload, msg)
@ -134,7 +138,11 @@ func (consensus *Consensus) announce(block *types.Block) {
// Leader sign the block hash itself
consensus.Decider.AddSignature(
quorum.Prepare, consensus.PubKey, consensus.priKey.SignHash(consensus.blockHash[:]),
quorum.Prepare,
consensus.PubKey,
consensus.priKey.SignHash(consensus.blockHash[:]),
consensus.LeaderPubKey,
consensus.blockNum,
)
if err := consensus.prepareBitmap.SetKey(consensus.PubKey, true); err != nil {
consensus.getLogger().Warn().Err(err).Msg("[Announce] Leader prepareBitmap SetKey failed")
@ -302,8 +310,6 @@ func (consensus *Consensus) onAnnounce(msg *msg_pb.Message) {
return
}
consensus.prepare()
return
}
// tryPrepare will try to send prepare message
@ -408,7 +414,9 @@ func (consensus *Consensus) onPrepare(msg *msg_pb.Message) {
Int64("NumReceivedSoFar", consensus.Decider.SignersCount(quorum.Prepare)).
Int64("PublicKeys", consensus.Decider.ParticipantsCount()).Logger()
logger.Info().Msg("[OnPrepare] Received New Prepare Signature")
consensus.Decider.AddSignature(quorum.Prepare, validatorPubKey, &sign)
consensus.Decider.AddSignature(
quorum.Prepare, validatorPubKey, &sign, consensus.LeaderPubKey, consensus.blockNum,
)
// Set the bitmap indicating that this validator signed.
if err := prepareBitmap.SetKey(recvMsg.SenderPubkey, true); err != nil {
consensus.getLogger().Warn().Err(err).Msg("[OnPrepare] prepareBitmap.SetKey failed")
@ -439,7 +447,11 @@ func (consensus *Consensus) onPrepare(msg *msg_pb.Message) {
binary.LittleEndian.PutUint64(blockNumHash, consensus.blockNum)
commitPayload := append(blockNumHash, consensus.blockHash[:]...)
consensus.Decider.AddSignature(
quorum.Commit, consensus.PubKey, consensus.priKey.SignHash(commitPayload),
quorum.Commit,
consensus.PubKey,
consensus.priKey.SignHash(commitPayload),
consensus.LeaderPubKey,
consensus.blockNum,
)
if err := consensus.commitBitmap.SetKey(consensus.PubKey, true); err != nil {
@ -471,7 +483,6 @@ func (consensus *Consensus) onPrepare(msg *msg_pb.Message) {
Msg("[OnPrepare] Switching phase")
consensus.switchPhase(FBFTCommit, true)
}
return
}
func (consensus *Consensus) onPrepared(msg *msg_pb.Message) {
@ -751,7 +762,9 @@ func (consensus *Consensus) onCommit(msg *msg_pb.Message) {
Int64("numReceivedSoFar", consensus.Decider.SignersCount(quorum.Commit)).
Logger()
logger.Info().Msg("[OnCommit] Received new commit message")
consensus.Decider.AddSignature(quorum.Commit, validatorPubKey, &sign)
consensus.Decider.AddSignature(
quorum.Commit, validatorPubKey, &sign, consensus.LeaderPubKey, consensus.blockNum,
)
// Set the bitmap indicating that this validator signed.
if err := commitBitmap.SetKey(recvMsg.SenderPubkey, true); err != nil {
consensus.getLogger().Warn().Err(err).Msg("[OnCommit] commitBitmap.SetKey failed")
@ -808,6 +821,7 @@ func (consensus *Consensus) finalizeCommits() {
Msg("[FinalizeCommits] Cannot find block by hash")
return
}
consensus.tryCatchup()
if consensus.blockNum-beforeCatchupNum != 1 {
consensus.getLogger().Warn().
@ -1080,13 +1094,14 @@ func (consensus *Consensus) tryCatchup() {
}
// Start waits for the next new block and run consensus
func (consensus *Consensus) Start(blockChannel chan *types.Block, stopChan chan struct{}, stoppedChan chan struct{}, startChannel chan struct{}) {
func (consensus *Consensus) Start(
blockChannel chan *types.Block, stopChan, stoppedChan, startChannel chan struct{},
) {
go func() {
toStart := false
isInitialLeader := consensus.IsLeader()
if isInitialLeader {
consensus.getLogger().Info().Time("time", time.Now()).Msg("[ConsensusMainLoop] Waiting for consensus start")
// send a signal to indicate it's ready to run consensus
// this signal is consumed by node object to create a new block and in turn trigger a new consensus on it
go func() {

@ -9,7 +9,6 @@ import (
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/slash"
"github.com/pkg/errors"
)
@ -41,7 +40,6 @@ type stakedVoteWeight struct {
SignatureReader
DependencyInjectionWriter
DependencyInjectionReader
slash.ThresholdDecider
roster votepower.Roster
ballotBox box
}
@ -92,7 +90,6 @@ func (v *stakedVoteWeight) IsQuorumAchievedByMask(mask *bls_cosi.Mask, debug boo
}
return (*currentTotalPower).GT(threshold)
}
func (v *stakedVoteWeight) computeCurrentTotalPower(p Phase) (*numeric.Dec, error) {
w := shard.BlsPublicKey{}
members := v.Participants()
@ -208,16 +205,6 @@ func (v *stakedVoteWeight) ToggleActive(k *bls.PublicKey) bool {
return v.roster.Voters[w].IsActive
}
func (v *stakedVoteWeight) ShouldSlash(key shard.BlsPublicKey) bool {
s, _ := v.ShardIDProvider()()
switch s {
case shard.BeaconChainShardID:
return v.SlashThresholdMet(key)
default:
return false
}
}
func (v *stakedVoteWeight) JSON() string {
s, _ := v.ShardIDProvider()()
voterCount := len(v.roster.Voters)

@ -103,7 +103,8 @@ func sign(d Decider, k secretKeyMap, p Phase) {
for _, v := range k {
pubKey := v.GetPublicKey()
sig := v.Sign(msg)
d.AddSignature(p, pubKey, sig)
// TODO Make upstream test provide meaningful test values
d.AddSignature(p, pubKey, sig, pubKey, 99999)
}
}

@ -8,8 +8,6 @@ import (
bls_cosi "github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/slash"
// "github.com/harmony-one/harmony/staking/effective"
)
// Phase is a phase that needs quorum to proceed
@ -25,7 +23,7 @@ const (
)
var phaseNames = map[Phase]string{
Prepare: "Announce",
Prepare: "Prepare",
Commit: "Prepare",
ViewChange: "Commit",
}
@ -73,7 +71,10 @@ type ParticipantTracker interface {
// SignatoryTracker ..
type SignatoryTracker interface {
ParticipantTracker
AddSignature(p Phase, PubKey *bls.PublicKey, sig *bls.Sign)
AddSignature(
p Phase, PubKey *bls.PublicKey,
sig *bls.Sign, roundLeader *bls.PublicKey, roundNumber uint64,
)
// Caller assumes concurrency protection
SignersCount(Phase) int64
reset([]Phase)
@ -85,6 +86,8 @@ type SignatureReader interface {
ReadAllSignatures(Phase) []*bls.Sign
ReadSignature(p Phase, PubKey *bls.PublicKey) *bls.Sign
TwoThirdsSignersCount() int64
// 96 bytes aggregated signature
AggregateVotes(p Phase) *bls.Sign
}
// DependencyInjectionWriter ..
@ -108,7 +111,6 @@ type WithJSONDump interface {
type Decider interface {
SignatureReader
DependencyInjectionWriter
slash.Slasher
WithJSONDump
ToggleActive(*bls.PublicKey) bool
SetVoters(shard.SlotList, bool) (*TallyResult, error)
@ -126,12 +128,12 @@ type Decider interface {
// and values are BLS private key signed signatures
type cIdentities struct {
// Public keys of the committee including leader and validators
publicKeys []*bls.PublicKey
prepare map[string]*bls.Sign
commit map[string]*bls.Sign
publicKeys []*bls.PublicKey
announcement *votepower.Round
commit *votepower.Round
// viewIDSigs: every validator
// sign on |viewID|blockHash| in view changing message
viewID map[string]*bls.Sign
viewID *votepower.Round
seenCounter map[[shard.PublicKeySizeInBytes]byte]int
}
@ -140,6 +142,10 @@ type depInject struct {
publicKeyProvider func() (*bls.PublicKey, error)
}
func (s *cIdentities) AggregateVotes(p Phase) *bls.Sign {
return bls_cosi.AggregateSig(s.ReadAllSignatures(p))
}
func (s *cIdentities) IndexOf(pubKey *bls.PublicKey) int {
idx := -1
for k, v := range s.publicKeys {
@ -175,11 +181,6 @@ func (s *cIdentities) UpdateParticipants(pubKeys []*bls.PublicKey) {
s.publicKeys = append(pubKeys[:0:0], pubKeys...)
}
func (s *cIdentities) SlashThresholdMet(key shard.BlsPublicKey) bool {
s.seenCounter[key]++
return s.seenCounter[key] == slash.UnavailabilityInConsecutiveBlockSigning
}
func (s *cIdentities) DumpParticipants() []string {
keys := make([]string, len(s.publicKeys))
for i := range s.publicKeys {
@ -195,34 +196,44 @@ func (s *cIdentities) ParticipantsCount() int64 {
func (s *cIdentities) SignersCount(p Phase) int64 {
switch p {
case Prepare:
return int64(len(s.prepare))
return int64(len(s.announcement.BallotBox))
case Commit:
return int64(len(s.commit))
return int64(len(s.commit.BallotBox))
case ViewChange:
return int64(len(s.viewID))
return int64(len(s.viewID.BallotBox))
default:
return 0
}
}
func (s *cIdentities) AddSignature(p Phase, PubKey *bls.PublicKey, sig *bls.Sign) {
func (s *cIdentities) AddSignature(
p Phase, PubKey *bls.PublicKey,
sig *bls.Sign, roundLeader *bls.PublicKey, roundNumber uint64,
) {
hex := PubKey.SerializeToHexStr()
ballot := &votepower.Ballot{
*shard.FromLibBLSPublicKeyUnsafe(PubKey),
*shard.FromLibBLSPublicKeyUnsafe(roundLeader),
roundNumber,
sig,
}
switch p {
case Prepare:
s.prepare[hex] = sig
s.announcement.BallotBox[hex] = ballot
case Commit:
s.commit[hex] = sig
s.commit.BallotBox[hex] = ballot
case ViewChange:
s.viewID[hex] = sig
s.viewID.BallotBox[hex] = ballot
}
}
func (s *cIdentities) reset(ps []Phase) {
for i := range ps {
switch m := map[string]*bls.Sign{}; ps[i] {
switch m := votepower.NewRound(); ps[i] {
case Prepare:
s.prepare = m
s.announcement = m
case Commit:
s.commit = m
case ViewChange:
@ -236,47 +247,49 @@ func (s *cIdentities) TwoThirdsSignersCount() int64 {
}
func (s *cIdentities) ReadSignature(p Phase, PubKey *bls.PublicKey) *bls.Sign {
m := map[string]*bls.Sign{}
var ballotBox map[string]*votepower.Ballot
hex := PubKey.SerializeToHexStr()
switch p {
case Prepare:
m = s.prepare
ballotBox = s.announcement.BallotBox
case Commit:
m = s.commit
ballotBox = s.commit.BallotBox
case ViewChange:
m = s.viewID
ballotBox = s.viewID.BallotBox
}
payload, ok := m[hex]
payload, ok := ballotBox[hex]
if !ok {
return nil
}
return payload
return payload.Signature
}
func (s *cIdentities) ReadAllSignatures(p Phase) []*bls.Sign {
m := map[string]*bls.Sign{}
var m map[string]*votepower.Ballot
switch p {
case Prepare:
m = s.prepare
m = s.announcement.BallotBox
case Commit:
m = s.commit
m = s.commit.BallotBox
case ViewChange:
m = s.viewID
m = s.viewID.BallotBox
}
sigs := make([]*bls.Sign, 0, len(m))
for _, value := range m {
sigs = append(sigs, value)
sigs = append(sigs, value.Signature)
}
return sigs
}
func newMapBackedSignatureReader() *cIdentities {
return &cIdentities{
[]*bls.PublicKey{}, map[string]*bls.Sign{},
map[string]*bls.Sign{}, map[string]*bls.Sign{},
map[[shard.PublicKeySizeInBytes]byte]int{},
publicKeys: []*bls.PublicKey{},
announcement: votepower.NewRound(),
commit: votepower.NewRound(),
viewID: votepower.NewRound(),
seenCounter: map[[shard.PublicKeySizeInBytes]byte]int{},
}
}
@ -317,7 +330,6 @@ func NewDecider(p Policy) Decider {
c.SignatureReader,
c.DependencyInjectionWriter,
c.DependencyInjectionWriter.(DependencyInjectionReader),
c.SignatureReader.(slash.ThresholdDecider),
*votepower.NewRoster(),
newBallotBox(),
}

@ -378,7 +378,11 @@ func (consensus *Consensus) onViewChange(msg *msg_pb.Message) {
binary.LittleEndian.PutUint64(blockNumBytes, consensus.blockNum)
commitPayload := append(blockNumBytes, consensus.blockHash[:]...)
consensus.Decider.AddSignature(
quorum.Commit, consensus.PubKey, consensus.priKey.SignHash(commitPayload),
quorum.Commit,
consensus.PubKey,
consensus.priKey.SignHash(commitPayload),
consensus.LeaderPubKey,
consensus.blockNum,
)
if err = consensus.commitBitmap.SetKey(consensus.PubKey, true); err != nil {

@ -5,6 +5,7 @@ import (
"sort"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
@ -21,6 +22,25 @@ var (
ErrVotingPowerNotEqualOne = errors.New("voting power not equal to one")
)
// Ballot is a vote cast by a validator
type Ballot struct {
SignerPubKey shard.BlsPublicKey `json:"bls-public-key"`
BlockLeader shard.BlsPublicKey `json:"leader-when-signed"`
BlockHeightHeight uint64 `json:"block-height"`
Signature *bls.Sign `json:"signature"`
}
// Round is a round of voting in any FBFT phase
type Round struct {
AggregatedVote *bls.Sign
BallotBox map[string]*Ballot
}
// NewRound ..
func NewRound() *Round {
return &Round{AggregatedVote: nil, BallotBox: map[string]*Ballot{}}
}
type stakedVoter struct {
IsActive bool `json:"is-active"`
IsHarmonyNode bool `json:"is-harmony"`

@ -8,7 +8,6 @@ import (
"os"
"path"
"sync"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/log"
@ -18,8 +17,6 @@ import (
var (
// Validator ID
validatorIDInstance *UniqueValidatorID
onceForUniqueValidatorID sync.Once
// Global port and ip for logging.
port string
ip string
@ -73,26 +70,6 @@ func AddLogHandler(handler log.Handler) {
}
}
// UniqueValidatorID defines the structure of unique validator ID
type UniqueValidatorID struct {
uniqueID uint32
}
// GetUniqueValidatorIDInstance returns a singleton instance
func GetUniqueValidatorIDInstance() *UniqueValidatorID {
onceForUniqueValidatorID.Do(func() {
validatorIDInstance = &UniqueValidatorID{
uniqueID: 0,
}
})
return validatorIDInstance
}
// GetUniqueID returns a unique ID and increment the internal variable
func (s *UniqueValidatorID) GetUniqueID() uint32 {
return atomic.AddUint32(&s.uniqueID, 1)
}
// GetLogInstance returns logging singleton.
func GetLogInstance() log.Logger {
onceForLog.Do(func() {

@ -89,7 +89,11 @@ func (node *Node) ExplorerMessageHandler(payload []byte) {
// Add the block into FBFT log.
node.Consensus.FBFTLog.AddBlock(blockObj)
// Try to search for MessageType_COMMITTED message from pbft log.
msgs := node.Consensus.FBFTLog.GetMessagesByTypeSeqHash(msg_pb.MessageType_COMMITTED, blockObj.NumberU64(), blockObj.Hash())
msgs := node.Consensus.FBFTLog.GetMessagesByTypeSeqHash(
msg_pb.MessageType_COMMITTED,
blockObj.NumberU64(),
blockObj.Hash(),
)
// If found, then add the new block into blockchain db.
if len(msgs) > 0 {
node.AddNewBlockForExplorer(blockObj)

@ -21,6 +21,7 @@ import (
"github.com/harmony-one/harmony/p2p"
"github.com/harmony-one/harmony/p2p/host"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/slash"
staking "github.com/harmony-one/harmony/staking/types"
libp2p_peer "github.com/libp2p/go-libp2p-core/peer"
)
@ -219,6 +220,11 @@ func (node *Node) BroadcastNewBlock(newBlock *types.Block) {
}
}
// BroadcastSlash ..
func (node *Node) BroadcastSlash(witness *slash.Record) {
//
}
// BroadcastCrossLink is called by consensus leader to send the new header as cross link to beacon chain.
func (node *Node) BroadcastCrossLink(newBlock *types.Block) {
// no point to broadcast the crosslink if we aren't even in the right epoch yet
@ -228,7 +234,10 @@ func (node *Node) BroadcastCrossLink(newBlock *types.Block) {
return
}
utils.Logger().Info().Msgf("Construct and Broadcasting new crosslink to beacon chain groupID %s", nodeconfig.NewGroupIDByShardID(0))
utils.Logger().Info().Msgf(
"Construct and Broadcasting new crosslink to beacon chain groupID %s",
nodeconfig.NewGroupIDByShardID(shard.BeaconChainShardID),
)
headers := []*block.Header{}
lastLink, err := node.Beaconchain().ReadShardLastCrossLink(newBlock.ShardID())
var latestBlockNum uint64
@ -261,12 +270,21 @@ func (node *Node) BroadcastCrossLink(newBlock *types.Block) {
utils.Logger().Info().Msgf("[BroadcastCrossLink] Broadcasting Block Headers, latestBlockNum %d, currentBlockNum %d, Number of Headers %d", latestBlockNum, newBlock.NumberU64(), len(headers))
for _, header := range headers {
utils.Logger().Debug().Msgf("[BroadcastCrossLink] Broadcasting %d", header.Number().Uint64())
utils.Logger().Debug().Msgf(
"[BroadcastCrossLink] Broadcasting %d",
header.Number().Uint64(),
)
}
node.host.SendMessageToGroups([]nodeconfig.GroupID{nodeconfig.NewGroupIDByShardID(0)}, host.ConstructP2pMessage(byte(0), proto_node.ConstructCrossLinkMessage(node.Consensus.ChainReader, headers)))
node.host.SendMessageToGroups(
[]nodeconfig.GroupID{nodeconfig.NewGroupIDByShardID(shard.BeaconChainShardID)},
host.ConstructP2pMessage(
byte(0),
proto_node.ConstructCrossLinkMessage(node.Consensus.ChainReader, headers)),
)
}
// VerifyNewBlock is called by consensus participants to verify the block (account model) they are running consensus on
// VerifyNewBlock is called by consensus participants to verify the block (account model) they are
// running consensus on
func (node *Node) VerifyNewBlock(newBlock *types.Block) error {
err := node.Blockchain().Validator().ValidateHeader(newBlock, true)
if err != nil {
@ -274,7 +292,11 @@ func (node *Node) VerifyNewBlock(newBlock *types.Block) error {
Str("blockHash", newBlock.Hash().Hex()).
Err(err).
Msg("cannot ValidateHeader for the new block")
return ctxerror.New("cannot ValidateHeader for the new block", "blockHash", newBlock.Hash()).WithCause(err)
return ctxerror.New(
"cannot ValidateHeader for the new block",
"blockHash",
newBlock.Hash(),
).WithCause(err)
}
if newBlock.ShardID() != node.Blockchain().ShardID() {
utils.Logger().Error().
@ -286,13 +308,18 @@ func (node *Node) VerifyNewBlock(newBlock *types.Block) error {
"new block's shard ID", newBlock.ShardID())
}
err = node.Blockchain().Engine().VerifyShardState(node.Blockchain(), node.Beaconchain(), newBlock.Header())
err = node.Blockchain().Engine().VerifyShardState(
node.Blockchain(), node.Beaconchain(), newBlock.Header(),
)
if err != nil {
utils.Logger().Error().
Str("blockHash", newBlock.Hash().Hex()).
Err(err).
Msg("cannot VerifyShardState for the new block")
return ctxerror.New("cannot VerifyShardState for the new block", "blockHash", newBlock.Hash()).WithCause(err)
return ctxerror.New(
"cannot VerifyShardState for the new block", "blockHash",
newBlock.Hash(),
).WithCause(err)
}
err = node.Blockchain().ValidateNewBlock(newBlock)
@ -342,7 +369,9 @@ var BigMaxUint64 = new(big.Int).SetBytes([]byte{
// 1. add the new block to blockchain
// 2. [leader] send new block to the client
// 3. [leader] send cross shard tx receipts to destination shard
func (node *Node) PostConsensusProcessing(newBlock *types.Block, commitSigAndBitmap []byte) {
func (node *Node) PostConsensusProcessing(
newBlock *types.Block, commitSigAndBitmap []byte,
) {
if _, err := node.Blockchain().InsertChain([]*types.Block{newBlock}, true); err != nil {
utils.Logger().Error().
Err(err).
@ -362,10 +391,11 @@ func (node *Node) PostConsensusProcessing(newBlock *types.Block, commitSigAndBit
// TODO: refactor the asynchronous calls to separate go routine.
node.lastConsensusTime = time.Now().Unix()
if node.Consensus.PubKey.IsEqual(node.Consensus.LeaderPubKey) {
if node.NodeConfig.ShardID == 0 {
if node.NodeConfig.ShardID == shard.BeaconChainShardID {
node.BroadcastNewBlock(newBlock)
}
if node.NodeConfig.ShardID != shard.BeaconChainShardID && node.Blockchain().Config().IsCrossLink(newBlock.Epoch()) {
if node.NodeConfig.ShardID != shard.BeaconChainShardID &&
node.Blockchain().Config().IsCrossLink(newBlock.Epoch()) {
node.BroadcastCrossLink(newBlock)
}
node.BroadcastCXReceipts(newBlock, commitSigAndBitmap)
@ -383,7 +413,7 @@ func (node *Node) PostConsensusProcessing(newBlock *types.Block, commitSigAndBit
rnd := rand.Intn(100)
if rnd < 1 {
// Beacon validators also broadcast new blocks to make sure beacon sync is strong.
if node.NodeConfig.ShardID == 0 {
if node.NodeConfig.ShardID == shard.BeaconChainShardID {
node.BroadcastNewBlock(newBlock)
}
node.BroadcastCXReceipts(newBlock, commitSigAndBitmap)

@ -213,6 +213,16 @@ func (pk BlsPublicKey) MarshalJSON() ([]byte, error) {
return buf.Bytes(), nil
}
// FromLibBLSPublicKeyUnsafe could give back nil, use only in cases when
// have invariant that return value won't be nil
func FromLibBLSPublicKeyUnsafe(key *bls.PublicKey) *BlsPublicKey {
result := &BlsPublicKey{}
if err := result.FromLibBLSPublicKey(key); err != nil {
return nil
}
return result
}
// FromLibBLSPublicKey replaces the key contents with the given key,
func (pk *BlsPublicKey) FromLibBLSPublicKey(key *bls.PublicKey) error {
bytes := key.Serialize()

@ -0,0 +1,30 @@
package slash
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/consensus/quorum"
"github.com/harmony-one/harmony/shard"
)
// Slasher ..
type Slasher interface {
ShouldSlash(shard.BlsPublicKey) bool
}
// Record is an proof of a slashing made by a witness of a double-signing event
type Record struct {
BlockHash common.Hash
BlockNumber *big.Int
Signature [96]byte // (aggregated) signature
Bitmap []byte // corresponding bitmap mask for agg signature
ShardID uint32
Epoch *big.Int
Beneficiary common.Address // the reporter who will get rewarded
}
// DidAnyoneDoubleSign ..
func DidAnyoneDoubleSign(d quorum.Decider) bool {
return false
}

@ -0,0 +1,12 @@
package slash
import (
"testing"
"github.com/harmony-one/harmony/consensus/quorum"
)
func TestDidAnyoneDoubleSign(t *testing.T) {
d := quorum.NewDecider(quorum.SuperMajorityStake)
t.Log("Unimplemented", d)
}

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

@ -98,6 +98,8 @@ type Validator struct {
Description
// CreationHeight is the height of creation
CreationHeight *big.Int
// Banned records whether this validator is banned from the network because they double-signed
Banned bool
}
// MarshalJSON ..
@ -390,7 +392,7 @@ func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Valida
v := Validator{
val.ValidatorAddress, pubKeys,
new(big.Int), val.MinSelfDelegation, val.MaxTotalDelegation, true,
commission, desc, blockNum,
commission, desc, blockNum, false,
}
return &v, nil
}

Loading…
Cancel
Save