Factor out quorum logic from consensus, undo prior consolidation of values under core (#1717)
* [consensus] Factor out enums to core/values, begin factor out of consensus mechanisms * [consensus] Make Mechanism explicit * [consensus] Add ViewChange to QuorumPhase * Update core/values/consensus.go Co-Authored-By: Eugene Kim <ek@harmony.one> * Update core/values/consensus.go Co-Authored-By: Eugene Kim <ek@harmony.one> * [mainnet-release] Address code comments * [staking][consensus][project] Remove txgen, factor out consensus * [consensus] Factor out PublicKeys * [txgen] Bring back txgen * [project] Undo prior consolidation of error values under core * [consensus] Update tests using quorum decider * [consensus] Fix overlooked resets during refactor * [consensus] Fix wrong check of quorum phase * [consensus] Address leftover TODO for prepare count * [consensus] Simplfy reset switch * [consensus] Fix mistake of wrong ReadSignature in ViewChange, need sender, not node PubKeypull/1731/head
parent
8b5a3235fd
commit
9f00923ac3
@ -0,0 +1,55 @@ |
||||
package consensus |
||||
|
||||
import "fmt" |
||||
|
||||
// Mode is the current
|
||||
type Mode byte |
||||
|
||||
const ( |
||||
// Normal ..
|
||||
Normal Mode = iota |
||||
// ViewChanging ..
|
||||
ViewChanging |
||||
// Syncing ..
|
||||
Syncing |
||||
// Listening ..
|
||||
Listening |
||||
) |
||||
|
||||
// FBFTPhase : different phases of consensus
|
||||
type FBFTPhase byte |
||||
|
||||
// Enum for FBFTPhase
|
||||
const ( |
||||
FBFTAnnounce FBFTPhase = iota |
||||
FBFTPrepare |
||||
FBFTCommit |
||||
) |
||||
|
||||
var ( |
||||
modeNames = map[Mode]string{ |
||||
Normal: "Normal", |
||||
ViewChanging: "ViewChanging", |
||||
Syncing: "Syncing", |
||||
Listening: "Listening", |
||||
} |
||||
phaseNames = map[FBFTPhase]string{ |
||||
FBFTAnnounce: "Announce", |
||||
FBFTPrepare: "Prepare", |
||||
FBFTCommit: "Commit", |
||||
} |
||||
) |
||||
|
||||
func (m Mode) String() string { |
||||
if name, ok := modeNames[m]; ok { |
||||
return name |
||||
} |
||||
return fmt.Sprintf("Mode %+v", byte(m)) |
||||
} |
||||
|
||||
func (p FBFTPhase) String() string { |
||||
if name, ok := phaseNames[p]; ok { |
||||
return name |
||||
} |
||||
return fmt.Sprintf("FBFTPhase %+v", byte(p)) |
||||
} |
@ -0,0 +1,235 @@ |
||||
package quorum |
||||
|
||||
import ( |
||||
"github.com/harmony-one/bls/ffi/go/bls" |
||||
) |
||||
|
||||
// Phase is a phase that needs quorum to proceed
|
||||
type Phase byte |
||||
|
||||
const ( |
||||
// Prepare ..
|
||||
Prepare Phase = iota |
||||
// Commit ..
|
||||
Commit |
||||
// ViewChange ..
|
||||
ViewChange |
||||
) |
||||
|
||||
// Policy is the rule we used to decide is quorum achieved
|
||||
type Policy byte |
||||
|
||||
const ( |
||||
// SuperMajorityVote is a 2/3s voting mechanism, pre-PoS
|
||||
SuperMajorityVote Policy = iota |
||||
// SuperMajorityStake is 2/3s of total staked amount for epoch
|
||||
SuperMajorityStake |
||||
) |
||||
|
||||
// ParticipantTracker ..
|
||||
type ParticipantTracker interface { |
||||
Participants() []*bls.PublicKey |
||||
IndexOf(*bls.PublicKey) int |
||||
ParticipantsCount() int64 |
||||
NextAfter(*bls.PublicKey) (bool, *bls.PublicKey) |
||||
UpdateParticipants(pubKeys []*bls.PublicKey) |
||||
DumpParticipants() []string |
||||
} |
||||
|
||||
// SignatoryTracker ..
|
||||
type SignatoryTracker interface { |
||||
ParticipantTracker |
||||
AddSignature(p Phase, PubKey *bls.PublicKey, sig *bls.Sign) |
||||
// Caller assumes concurrency protection
|
||||
SignatoriesCount(Phase) int64 |
||||
Reset([]Phase) |
||||
} |
||||
|
||||
// SignatureReader ..
|
||||
type SignatureReader interface { |
||||
SignatoryTracker |
||||
ReadAllSignatures(Phase) []*bls.Sign |
||||
ReadSignature(p Phase, PubKey *bls.PublicKey) *bls.Sign |
||||
} |
||||
|
||||
// These maps represent the signatories (validators), keys are BLS public keys
|
||||
// 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 |
||||
// viewIDSigs: every validator
|
||||
// sign on |viewID|blockHash| in view changing message
|
||||
viewID map[string]*bls.Sign |
||||
} |
||||
|
||||
func (s *cIdentities) IndexOf(pubKey *bls.PublicKey) int { |
||||
idx := -1 |
||||
for k, v := range s.publicKeys { |
||||
if v.IsEqual(pubKey) { |
||||
idx = k |
||||
} |
||||
} |
||||
return idx |
||||
} |
||||
|
||||
func (s *cIdentities) NextAfter(pubKey *bls.PublicKey) (bool, *bls.PublicKey) { |
||||
found := false |
||||
idx := s.IndexOf(pubKey) |
||||
if idx != -1 { |
||||
found = true |
||||
} |
||||
idx = (idx + 1) % int(s.ParticipantsCount()) |
||||
return found, s.publicKeys[idx] |
||||
} |
||||
|
||||
func (s *cIdentities) Participants() []*bls.PublicKey { |
||||
return s.publicKeys |
||||
} |
||||
|
||||
func (s *cIdentities) UpdateParticipants(pubKeys []*bls.PublicKey) { |
||||
s.publicKeys = append(pubKeys[:0:0], pubKeys...) |
||||
} |
||||
|
||||
func (s *cIdentities) DumpParticipants() []string { |
||||
keys := make([]string, len(s.publicKeys)) |
||||
for i := 0; i < len(s.publicKeys); i++ { |
||||
keys[i] = s.publicKeys[i].SerializeToHexStr() |
||||
} |
||||
return keys |
||||
} |
||||
|
||||
func (s *cIdentities) ParticipantsCount() int64 { |
||||
return int64(len(s.publicKeys)) |
||||
} |
||||
|
||||
func (s *cIdentities) SignatoriesCount(p Phase) int64 { |
||||
switch p { |
||||
case Prepare: |
||||
return int64(len(s.prepare)) |
||||
case Commit: |
||||
return int64(len(s.commit)) |
||||
case ViewChange: |
||||
return int64(len(s.viewID)) |
||||
default: |
||||
return 0 |
||||
|
||||
} |
||||
} |
||||
|
||||
func (s *cIdentities) AddSignature(p Phase, PubKey *bls.PublicKey, sig *bls.Sign) { |
||||
hex := PubKey.SerializeToHexStr() |
||||
switch p { |
||||
case Prepare: |
||||
s.prepare[hex] = sig |
||||
case Commit: |
||||
s.commit[hex] = sig |
||||
case ViewChange: |
||||
s.viewID[hex] = sig |
||||
} |
||||
} |
||||
|
||||
func (s *cIdentities) Reset(ps []Phase) { |
||||
for _, p := range ps { |
||||
switch m := map[string]*bls.Sign{}; p { |
||||
case Prepare: |
||||
s.prepare = m |
||||
case Commit: |
||||
s.commit = m |
||||
case ViewChange: |
||||
s.viewID = m |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (s *cIdentities) ReadSignature(p Phase, PubKey *bls.PublicKey) *bls.Sign { |
||||
m := map[string]*bls.Sign{} |
||||
hex := PubKey.SerializeToHexStr() |
||||
|
||||
switch p { |
||||
case Prepare: |
||||
m = s.prepare |
||||
case Commit: |
||||
m = s.commit |
||||
case ViewChange: |
||||
m = s.viewID |
||||
} |
||||
|
||||
payload, ok := m[hex] |
||||
if !ok { |
||||
return nil |
||||
} |
||||
return payload |
||||
} |
||||
|
||||
func (s *cIdentities) ReadAllSignatures(p Phase) []*bls.Sign { |
||||
sigs := []*bls.Sign{} |
||||
m := map[string]*bls.Sign{} |
||||
|
||||
switch p { |
||||
case Prepare: |
||||
m = s.prepare |
||||
case Commit: |
||||
m = s.commit |
||||
case ViewChange: |
||||
m = s.viewID |
||||
} |
||||
|
||||
for _, sig := range m { |
||||
sigs = append(sigs, sig) |
||||
} |
||||
return sigs |
||||
} |
||||
|
||||
func newMapBackedSignatureReader() SignatureReader { |
||||
return &cIdentities{ |
||||
[]*bls.PublicKey{}, map[string]*bls.Sign{}, |
||||
map[string]*bls.Sign{}, map[string]*bls.Sign{}, |
||||
} |
||||
} |
||||
|
||||
// Decider ..
|
||||
type Decider interface { |
||||
SignatureReader |
||||
Policy() Policy |
||||
IsQuorumAchieved(Phase) bool |
||||
QuorumThreshold() int64 |
||||
IsRewardThresholdAchieved() bool |
||||
} |
||||
|
||||
type uniformVoteWeight struct { |
||||
SignatureReader |
||||
} |
||||
|
||||
// NewDecider ..
|
||||
func NewDecider(p Policy) Decider { |
||||
switch p { |
||||
case SuperMajorityVote: |
||||
return &uniformVoteWeight{newMapBackedSignatureReader()} |
||||
// case SuperMajorityStake:
|
||||
default: |
||||
// Should not be possible
|
||||
return nil |
||||
} |
||||
} |
||||
|
||||
// Policy ..
|
||||
func (v *uniformVoteWeight) Policy() Policy { |
||||
return SuperMajorityVote |
||||
} |
||||
|
||||
// IsQuorumAchieved ..
|
||||
func (v *uniformVoteWeight) IsQuorumAchieved(p Phase) bool { |
||||
return v.SignatoriesCount(p) >= v.QuorumThreshold() |
||||
} |
||||
|
||||
// QuorumThreshold ..
|
||||
func (v *uniformVoteWeight) QuorumThreshold() int64 { |
||||
return v.ParticipantsCount()*2/3 + 1 |
||||
} |
||||
|
||||
// RewardThreshold ..
|
||||
func (v *uniformVoteWeight) IsRewardThresholdAchieved() bool { |
||||
return v.SignatoriesCount(Commit) >= (v.ParticipantsCount() * 9 / 10) |
||||
} |
@ -0,0 +1,40 @@ |
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core |
||||
|
||||
import ( |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
var ( |
||||
// ErrKnownBlock is returned when a block to import is already known locally.
|
||||
ErrKnownBlock = errors.New("block already known") |
||||
|
||||
// ErrGasLimitReached is returned by the gas pool if the amount of gas required
|
||||
// by a transaction is higher than what's left in the block.
|
||||
ErrGasLimitReached = errors.New("gas limit reached") |
||||
|
||||
// ErrBlacklistedHash is returned if a block to import is on the blacklist.
|
||||
ErrBlacklistedHash = errors.New("blacklisted hash") |
||||
|
||||
// ErrNonceTooHigh is returned if the nonce of a transaction is higher than the
|
||||
// next one expected based on the local chain.
|
||||
ErrNonceTooHigh = errors.New("nonce too high") |
||||
|
||||
// ErrShardStateNotMatch is returned if the calculated shardState hash not equal that in the block header
|
||||
ErrShardStateNotMatch = errors.New("shard state root hash not match") |
||||
) |
@ -1,79 +0,0 @@ |
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package values |
||||
|
||||
import ( |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
var ( |
||||
// ErrKnownBlock is returned when a block to import is already known locally.
|
||||
ErrKnownBlock = errors.New("block already known") |
||||
|
||||
// ErrGasLimitReached is returned by the gas pool if the amount of gas required
|
||||
// by a transaction is higher than what's left in the block.
|
||||
ErrGasLimitReached = errors.New("gas limit reached") |
||||
|
||||
// ErrBlacklistedHash is returned if a block to import is on the blacklist.
|
||||
ErrBlacklistedHash = errors.New("blacklisted hash") |
||||
|
||||
// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
|
||||
// one present in the local chain.
|
||||
ErrNonceTooLow = errors.New("nonce too low") |
||||
|
||||
// ErrNonceTooHigh is returned if the nonce of a transaction is higher than the
|
||||
// next one expected based on the local chain.
|
||||
ErrNonceTooHigh = errors.New("nonce too high") |
||||
|
||||
// ErrShardStateNotMatch is returned if the calculated shardState hash not equal that in the block header
|
||||
ErrShardStateNotMatch = errors.New("shard state root hash not match") |
||||
|
||||
// ErrInvalidChainID when ChainID of signer does not match that of running node
|
||||
ErrInvalidChainID = errors.New("invalid chain id for signer") |
||||
|
||||
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
||||
ErrInvalidSender = errors.New("invalid sender") |
||||
|
||||
// ErrUnderpriced is returned if a transaction's gas price is below the minimum
|
||||
// configured for the transaction pool.
|
||||
ErrUnderpriced = errors.New("transaction underpriced") |
||||
|
||||
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
|
||||
// with a different one without the required price bump.
|
||||
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") |
||||
|
||||
// ErrInsufficientFunds is returned if the total cost of executing a transaction
|
||||
// is higher than the balance of the user's account.
|
||||
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") |
||||
|
||||
// ErrIntrinsicGas is returned if the transaction is specified to use less gas
|
||||
// than required to start the invocation.
|
||||
ErrIntrinsicGas = errors.New("intrinsic gas too low") |
||||
|
||||
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
|
||||
// maximum allowance of the current block.
|
||||
ErrGasLimit = errors.New("exceeds block gas limit") |
||||
|
||||
// ErrNegativeValue is a sanity error to ensure noone is able to specify a
|
||||
// transaction with a negative value.
|
||||
ErrNegativeValue = errors.New("negative value") |
||||
|
||||
// ErrOversizedData is returned if the input data of a transaction is greater
|
||||
// than some meaningful limit a user might use. This is not a consensus error
|
||||
// making the transaction invalid, rather a DOS protection.
|
||||
ErrOversizedData = errors.New("oversized data") |
||||
) |
Loading…
Reference in new issue