parent
c4a9b77bee
commit
cd50682097
@ -0,0 +1,86 @@ |
||||
package quorum |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/harmony-one/harmony/consensus/votepower" |
||||
bls_cosi "github.com/harmony-one/harmony/crypto/bls" |
||||
"github.com/harmony-one/harmony/numeric" |
||||
"github.com/harmony-one/harmony/shard" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
// Verifier is the interface to verify the whether the quorum is achieved by mask at each epoch.
|
||||
// TODO: Add some unit tests to make sure Verifier get exactly the same result as Decider
|
||||
type Verifier interface { |
||||
IsQuorumAchievedByMask(mask *bls_cosi.Mask) bool |
||||
} |
||||
|
||||
// NewVerifier creates the quorum verifier for the given committee, epoch and whether the scenario
|
||||
// is staking.
|
||||
func NewVerifier(committee *shard.Committee, epoch *big.Int, isStaking bool) (Verifier, error) { |
||||
if isStaking { |
||||
return newStakeVerifier(committee, epoch) |
||||
} |
||||
return newUniformVerifier(committee) |
||||
} |
||||
|
||||
// stakeVerifier is the quorum verifier for staking period. Each validator has staked token as
|
||||
// a voting power of the final result.
|
||||
type stakeVerifier struct { |
||||
r votepower.Roster |
||||
} |
||||
|
||||
// newStakeVerifier creates a stake verifier from the given committee
|
||||
func newStakeVerifier(committee *shard.Committee, epoch *big.Int) (*stakeVerifier, error) { |
||||
r, err := votepower.Compute(committee, epoch) |
||||
if err != nil { |
||||
return nil, errors.Wrap(err, "compute staking vote-power") |
||||
} |
||||
return &stakeVerifier{ |
||||
r: *r, |
||||
}, nil |
||||
} |
||||
|
||||
// IsQuorumAchievedByMask returns whether the quorum is achieved with the provided mask
|
||||
func (sv *stakeVerifier) IsQuorumAchievedByMask(mask *bls_cosi.Mask) bool { |
||||
if mask == nil { |
||||
return false |
||||
} |
||||
vp := sv.r.VotePowerByMask(mask) |
||||
return vp.GT(sv.threshold()) |
||||
} |
||||
|
||||
func (sv *stakeVerifier) threshold() numeric.Dec { |
||||
return twoThird |
||||
} |
||||
|
||||
// uniformVerifier is the quorum verifier for non-staking period. All nodes has a uniform voting power.
|
||||
type uniformVerifier struct { |
||||
pubKeyCnt int64 |
||||
} |
||||
|
||||
func newUniformVerifier(committee *shard.Committee) (*uniformVerifier, error) { |
||||
keys, err := committee.BLSPublicKeys() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &uniformVerifier{ |
||||
pubKeyCnt: int64(len(keys)), |
||||
}, nil |
||||
} |
||||
|
||||
// IsQuorumAchievedByMask returns whether the quorum is achieved with the provided mask,
|
||||
// which is whether more than (2/3+1) nodes is included in mask.
|
||||
func (uv *uniformVerifier) IsQuorumAchievedByMask(mask *bls_cosi.Mask) bool { |
||||
got := int64(len(mask.Publics)) |
||||
exp := uv.thresholdKeyCount() |
||||
// Theoretically speaking, greater or equal will do the work. But current logic is more strict
|
||||
// without equal, thus conform to current logic implemented.
|
||||
// (engineImpl.VerifySeal, uniformVoteWeight.IsQuorumAchievedByMask)
|
||||
return got > exp |
||||
} |
||||
|
||||
func (uv *uniformVerifier) thresholdKeyCount() int64 { |
||||
return uv.pubKeyCnt*2/3 + 1 |
||||
} |
@ -1,220 +0,0 @@ |
||||
package downloader |
||||
|
||||
import ( |
||||
"fmt" |
||||
"hash" |
||||
"math/big" |
||||
"sync" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
lru "github.com/hashicorp/golang-lru" |
||||
"github.com/pkg/errors" |
||||
"golang.org/x/crypto/sha3" |
||||
|
||||
bls_core "github.com/harmony-one/bls/ffi/go/bls" |
||||
"github.com/harmony-one/harmony/consensus/quorum" |
||||
"github.com/harmony-one/harmony/consensus/signature" |
||||
"github.com/harmony-one/harmony/core/types" |
||||
bls_cosi "github.com/harmony-one/harmony/crypto/bls" |
||||
"github.com/harmony-one/harmony/internal/chain" |
||||
"github.com/harmony-one/harmony/multibls" |
||||
"github.com/harmony-one/harmony/shard" |
||||
) |
||||
|
||||
// sigVerifyError is the error type of failing verify the signature of the current block.
|
||||
// Since this is a sanity field and is not included the block hash, it needs extra verification.
|
||||
// The error types is used to differentiate the error of signature verification VS insert error.
|
||||
type sigVerifyError struct { |
||||
err error |
||||
} |
||||
|
||||
func (err *sigVerifyError) Error() string { |
||||
return fmt.Sprintf("failed verify signature: %v", err.err.Error()) |
||||
} |
||||
|
||||
// insertHelperImpl helps to verify and insert blocks, along with some caching mechanism.
|
||||
type insertHelperImpl struct { |
||||
bc blockChain |
||||
|
||||
deciderCache *lru.Cache // Epoch -> quorum.Decider
|
||||
shardStateCache *lru.Cache // Epoch -> *shard.State
|
||||
verifiedSigCache *lru.Cache // verifiedSigKey -> struct{}{}
|
||||
} |
||||
|
||||
func newInsertHelper(bc blockChain) insertHelper { |
||||
deciderCache, _ := lru.New(5) |
||||
shardStateCache, _ := lru.New(5) |
||||
sigCache, _ := lru.New(20) |
||||
return &insertHelperImpl{ |
||||
bc: bc, |
||||
deciderCache: deciderCache, |
||||
shardStateCache: shardStateCache, |
||||
verifiedSigCache: sigCache, |
||||
} |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) verifyAndInsertBlocks(blocks types.Blocks) (int, error) { |
||||
for i, block := range blocks { |
||||
if err := ch.verifyAndInsertBlock(block); err != nil { |
||||
return i, err |
||||
} |
||||
} |
||||
return len(blocks), nil |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) verifyAndInsertBlock(block *types.Block) error { |
||||
// verify the commit sig of current block
|
||||
if err := ch.verifyBlockSignature(block); err != nil { |
||||
return &sigVerifyError{err} |
||||
} |
||||
ch.markBlockSigVerified(block, block.GetCurrentCommitSig()) |
||||
|
||||
// verify header. Skip verify the previous seal if we have already verified
|
||||
verifySeal := !ch.isBlockLastSigVerified(block) |
||||
if err := ch.bc.Engine().VerifyHeader(ch.bc, block.Header(), verifySeal); err != nil { |
||||
return err |
||||
} |
||||
// Insert chain.
|
||||
if _, err := ch.bc.InsertChain(types.Blocks{block}, false); err != nil { |
||||
return err |
||||
} |
||||
// Write commit sig data
|
||||
return ch.bc.WriteCommitSig(block.NumberU64(), block.GetCurrentCommitSig()) |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) verifyBlockSignature(block *types.Block) error { |
||||
// TODO: This is the duplicate logic to the implementation of verifySeal and consensus.
|
||||
// Better refactor to the blockchain or engine structure
|
||||
decider, err := ch.getDeciderByEpoch(block.Epoch()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
sig, mask, err := decodeCommitSig(block.GetCurrentCommitSig(), decider.Participants()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if !decider.IsQuorumAchievedByMask(mask) { |
||||
return errors.New("quorum not achieved") |
||||
} |
||||
|
||||
commitSigBytes := signature.ConstructCommitPayload(ch.bc, block.Epoch(), block.Hash(), |
||||
block.NumberU64(), block.Header().ViewID().Uint64()) |
||||
if !sig.VerifyHash(mask.AggregatePublic, commitSigBytes) { |
||||
return errors.New("aggregate signature failed verification") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) writeBlockSignature(block *types.Block) error { |
||||
return ch.bc.WriteCommitSig(block.NumberU64(), block.GetCurrentCommitSig()) |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) getDeciderByEpoch(epoch *big.Int) (quorum.Decider, error) { |
||||
epochUint := epoch.Uint64() |
||||
if decider, ok := ch.deciderCache.Get(epochUint); ok && decider != nil { |
||||
return decider.(quorum.Decider), nil |
||||
} |
||||
decider, err := ch.readDeciderByEpoch(epoch) |
||||
if err != nil { |
||||
return nil, errors.Wrapf(err, "unable to read quorum of epoch %v", epoch.Uint64()) |
||||
} |
||||
ch.deciderCache.Add(epochUint, decider) |
||||
return decider, nil |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) readDeciderByEpoch(epoch *big.Int) (quorum.Decider, error) { |
||||
isStaking := ch.bc.Config().IsStaking(epoch) |
||||
decider := ch.getNewDecider(isStaking) |
||||
ss, err := ch.getShardState(epoch) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
subComm, err := ss.FindCommitteeByID(ch.shardID()) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
pubKeys, err := subComm.BLSPublicKeys() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
decider.UpdateParticipants(pubKeys) |
||||
if _, err := decider.SetVoters(subComm, epoch); err != nil { |
||||
return nil, err |
||||
} |
||||
return decider, nil |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) getNewDecider(isStaking bool) quorum.Decider { |
||||
if isStaking { |
||||
return quorum.NewDecider(quorum.SuperMajorityVote, ch.bc.ShardID()) |
||||
} else { |
||||
return quorum.NewDecider(quorum.SuperMajorityStake, ch.bc.ShardID()) |
||||
} |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) getShardState(epoch *big.Int) (*shard.State, error) { |
||||
if ss, ok := ch.shardStateCache.Get(epoch.Uint64()); ok && ss != nil { |
||||
return ss.(*shard.State), nil |
||||
} |
||||
ss, err := ch.bc.ReadShardState(epoch) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
ch.shardStateCache.Add(epoch.Uint64(), ss) |
||||
return ss, nil |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) markBlockSigVerified(block *types.Block, sigAndBitmap []byte) { |
||||
key := newVerifiedSigKey(block.Hash(), sigAndBitmap) |
||||
ch.verifiedSigCache.Add(key, struct{}{}) |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) isBlockLastSigVerified(block *types.Block) bool { |
||||
lastSig := block.Header().LastCommitSignature() |
||||
lastBM := block.Header().LastCommitBitmap() |
||||
lastSigAndBM := append(lastSig[:], lastBM...) |
||||
|
||||
key := newVerifiedSigKey(block.Hash(), lastSigAndBM) |
||||
_, ok := ch.verifiedSigCache.Get(key) |
||||
return ok |
||||
} |
||||
|
||||
func (ch *insertHelperImpl) shardID() uint32 { |
||||
return ch.bc.ShardID() |
||||
} |
||||
|
||||
func decodeCommitSig(commitBytes []byte, publicKeys multibls.PublicKeys) (*bls_core.Sign, *bls_cosi.Mask, error) { |
||||
if len(commitBytes) < bls_cosi.BLSSignatureSizeInBytes { |
||||
return nil, nil, fmt.Errorf("unexpected signature bytes size: %v / %v", len(commitBytes), |
||||
bls_cosi.BLSSignatureSizeInBytes) |
||||
} |
||||
return chain.ReadSignatureBitmapByPublicKeys(commitBytes, publicKeys) |
||||
} |
||||
|
||||
type verifiedSigKey struct { |
||||
blockHash common.Hash |
||||
sbHash common.Hash // hash of block signature + bitmap
|
||||
} |
||||
|
||||
var hasherPool = sync.Pool{ |
||||
New: func() interface{} { |
||||
return sha3.New256() |
||||
}, |
||||
} |
||||
|
||||
func newVerifiedSigKey(blockHash common.Hash, sigAndBitmap []byte) verifiedSigKey { |
||||
hasher := hasherPool.Get().(hash.Hash) |
||||
defer func() { |
||||
hasher.Reset() |
||||
hasherPool.Put(hasher) |
||||
}() |
||||
|
||||
var sbHash common.Hash |
||||
hasher.Write(sigAndBitmap) |
||||
hasher.Sum(sbHash[0:]) |
||||
|
||||
return verifiedSigKey{ |
||||
blockHash: blockHash, |
||||
sbHash: sbHash, |
||||
} |
||||
} |
@ -1,19 +0,0 @@ |
||||
package downloader |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
) |
||||
|
||||
func BenchmarkNewVerifiedSigKey(b *testing.B) { |
||||
var bh common.Hash |
||||
commitSig := make([]byte, 100) |
||||
for i := 0; i != len(commitSig); i++ { |
||||
commitSig[i] = 0xf |
||||
} |
||||
|
||||
for i := 0; i != b.N; i++ { |
||||
newVerifiedSigKey(bh, commitSig) |
||||
} |
||||
} |
Loading…
Reference in new issue