diff --git a/consensus/consensus_service.go b/consensus/consensus_service.go index 278e4aea8..d206aa876 100644 --- a/consensus/consensus_service.go +++ b/consensus/consensus_service.go @@ -457,10 +457,13 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode { hasError := false header := consensus.ChainReader.CurrentHeader() epoch := header.Epoch() - curPubKeys := committee.WithStakingEnabled.ComputePublicKeys( - epoch, consensus.ChainReader, - )[int(header.ShardID())] - + if consensus.Decider.Policy() != quorum.SuperMajorityStake && + consensus.ChainReader.Config().IsStaking(epoch) { + consensus.Decider = quorum.NewDecider(quorum.SuperMajorityStake) + } + _, curPubKeys := committee.WithStakingEnabled.ComputePublicKeys( + epoch, consensus.ChainReader, int(header.ShardID()), + ) consensus.numPrevPubKeys = len(curPubKeys) consensus.getLogger().Info().Msg("[UpdateConsensusInformation] Updating.....") if shard.Schedule.IsLastBlock(header.Number().Uint64()) { @@ -468,9 +471,11 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode { consensus.SetEpochNum(epoch.Uint64() + 1) consensus.getLogger().Info().Uint64("headerNum", header.Number().Uint64()). Msg("[UpdateConsensusInformation] Epoch updated for next epoch") - pubKeys = committee.WithStakingEnabled.ComputePublicKeys( - new(big.Int).Add(epoch, common.Big1), consensus.ChainReader, - )[int(header.ShardID())] + _, pubKeys = committee.WithStakingEnabled.ComputePublicKeys( + new(big.Int).Add(epoch, common.Big1), + consensus.ChainReader, + int(header.ShardID()), + ) } else { consensus.SetEpochNum(epoch.Uint64()) pubKeys = curPubKeys diff --git a/consensus/quorum/one-node-one-vote.go b/consensus/quorum/one-node-one-vote.go index 8595a9c31..e1ca4bbbc 100644 --- a/consensus/quorum/one-node-one-vote.go +++ b/consensus/quorum/one-node-one-vote.go @@ -6,12 +6,13 @@ import ( "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/shard" // "github.com/harmony-one/harmony/staking/effective" ) type uniformVoteWeight struct { - SignatureReader DependencyInjectionWriter + SignatureReader } // Policy .. @@ -71,3 +72,9 @@ func (v *uniformVoteWeight) Award( return payout } + +func (v *uniformVoteWeight) ShouldSlash(k shard.BlsPublicKey) bool { + // No-op, no semantic meaning in one-slot-one-vote + // fmt.Println("Called here for key:", k.Hex()) + return false +} diff --git a/consensus/quorum/one-node-staked-vote.go b/consensus/quorum/one-node-staked-vote.go index efd2b385b..1d6eb9020 100644 --- a/consensus/quorum/one-node-staked-vote.go +++ b/consensus/quorum/one-node-staked-vote.go @@ -7,6 +7,7 @@ import ( "github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/shard" + "github.com/harmony-one/harmony/staking/slash" ) var ( @@ -21,6 +22,8 @@ type stakedVoter struct { type stakedVoteWeight struct { SignatureReader DependencyInjectionWriter + DependencyInjectionReader + slash.ThresholdDecider // EPOS based staking validatorStakes map[[shard.PublicKeySizeInBytes]byte]stakedVoter totalEffectiveStakedAmount *big.Int @@ -73,3 +76,13 @@ func (v *stakedVoteWeight) ToggleActive(*bls.PublicKey) bool { // TODO Implement return true } + +func (v *stakedVoteWeight) ShouldSlash(key shard.BlsPublicKey) bool { + s, _ := v.ShardIDProvider()() + switch s { + case shard.BeaconChainShardID: + return v.SlashThresholdMet(key) + default: + return false + } +} diff --git a/consensus/quorum/quorum.go b/consensus/quorum/quorum.go index 03a86acd0..34ac736c7 100644 --- a/consensus/quorum/quorum.go +++ b/consensus/quorum/quorum.go @@ -6,6 +6,7 @@ import ( "github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/harmony/shard" + "github.com/harmony-one/harmony/staking/slash" // "github.com/harmony-one/harmony/staking/effective" ) @@ -75,10 +76,16 @@ type DependencyInjectionWriter interface { SetShardIDProvider(func() (uint32, error)) } +// DependencyInjectionReader .. +type DependencyInjectionReader interface { + ShardIDProvider() func() (uint32, error) +} + // Decider .. type Decider interface { SignatureReader DependencyInjectionWriter + slash.Slasher ToggleActive(*bls.PublicKey) bool // UpdateVotingPower(keeper effective.StakeKeeper) Policy() Policy @@ -96,17 +103,14 @@ type cIdentities struct { commit map[string]*bls.Sign // viewIDSigs: every validator // sign on |viewID|blockHash| in view changing message - viewID map[string]*bls.Sign + viewID map[string]*bls.Sign + seenCounter map[[shard.PublicKeySizeInBytes]byte]int } type depInject struct { shardIDProvider func() (uint32, error) } -func (d *depInject) SetShardIDProvider(p func() (uint32, error)) { - d.shardIDProvider = p -} - func (s *cIdentities) IndexOf(pubKey *bls.PublicKey) int { idx := -1 for k, v := range s.publicKeys { @@ -132,9 +136,23 @@ func (s *cIdentities) Participants() []*bls.PublicKey { } func (s *cIdentities) UpdateParticipants(pubKeys []*bls.PublicKey) { + // TODO - might need to put this in separate method + s.seenCounter = make(map[[shard.PublicKeySizeInBytes]byte]int, len(pubKeys)) + for i := range pubKeys { + k := shard.BlsPublicKey{} + k.FromLibBLSPublicKey(pubKeys[i]) + s.seenCounter[k] = 0 + } s.publicKeys = append(pubKeys[:0:0], pubKeys...) } +func (s *cIdentities) SlashThresholdMet(key shard.BlsPublicKey) bool { + s.seenCounter[key]++ + fmt.Println("Slash Map", s.seenCounter) + return s.seenCounter[key] == slash.UnavailabilityInConsecutiveBlockSigning + +} + func (s *cIdentities) DumpParticipants() []string { keys := make([]string, len(s.publicKeys)) for i := 0; i < len(s.publicKeys); i++ { @@ -174,8 +192,8 @@ func (s *cIdentities) AddSignature(p Phase, PubKey *bls.PublicKey, sig *bls.Sign } func (s *cIdentities) Reset(ps []Phase) { - for _, p := range ps { - switch m := map[string]*bls.Sign{}; p { + for i := range ps { + switch m := map[string]*bls.Sign{}; ps[i] { case Prepare: s.prepare = m case Commit: @@ -223,43 +241,45 @@ func (s *cIdentities) ReadAllSignatures(p Phase) []*bls.Sign { return sigs } -func newMapBackedSignatureReader() cIdentities { - return cIdentities{ +func newMapBackedSignatureReader() *cIdentities { + return &cIdentities{ []*bls.PublicKey{}, map[string]*bls.Sign{}, map[string]*bls.Sign{}, map[string]*bls.Sign{}, + map[[shard.PublicKeySizeInBytes]byte]int{}, } } -func (c *composite) ShouldSlash(shard.BlsPublicKey) bool { - s, _ := c.shardIDProvider() - switch s { - case shard.BeaconChainShardID: - return true - default: - return false - } +type composite struct { + DependencyInjectionWriter + SignatureReader } -type composite struct { - cIdentities - depInject +func (d *depInject) SetShardIDProvider(p func() (uint32, error)) { + d.shardIDProvider = p +} + +func (d *depInject) ShardIDProvider() func() (uint32, error) { + return d.shardIDProvider } // NewDecider .. func NewDecider(p Policy) Decider { signatureStore := newMapBackedSignatureReader() - dependencies := depInject{} - c := &composite{signatureStore, dependencies} + deps := &depInject{} + c := &composite{deps, signatureStore} switch p { case SuperMajorityVote: - return &uniformVoteWeight{&c.cIdentities, &c.depInject} + return &uniformVoteWeight{c.DependencyInjectionWriter, c} case SuperMajorityStake: + fmt.Println("HRS") return &stakedVoteWeight{ - &c.cIdentities, &c.depInject, + c.SignatureReader, + c.DependencyInjectionWriter, + c.DependencyInjectionWriter.(DependencyInjectionReader), + c.SignatureReader.(slash.ThresholdDecider), map[[shard.PublicKeySizeInBytes]byte]stakedVoter{}, big.NewInt(0), } - default: // Should not be possible return nil diff --git a/internal/chain/engine.go b/internal/chain/engine.go index 0a1df170e..75ec56964 100644 --- a/internal/chain/engine.go +++ b/internal/chain/engine.go @@ -177,12 +177,16 @@ func (e *engineImpl) VerifySeal(chain engine.ChainReader, header *block.Header) // Finalize implements Engine, accumulating the block rewards, // setting the final state and assembling the block. func (e *engineImpl) Finalize( - chain engine.ChainReader, header *block.Header, state *state.DB, txs []*types.Transaction, + chain engine.ChainReader, header *block.Header, + state *state.DB, txs []*types.Transaction, receipts []*types.Receipt, outcxs []*types.CXReceipt, - incxs []*types.CXReceiptsProof, stks []*staking.StakingTransaction) (*types.Block, error) { + incxs []*types.CXReceiptsProof, stks []*staking.StakingTransaction, +) (*types.Block, error) { // Accumulate any block and uncle rewards and commit the final state root // Header seems complete, assemble into a block and return - if err := AccumulateRewards(chain, state, header, e.Rewarder(), e.Slasher()); err != nil { + if err := AccumulateRewards( + chain, state, header, e.Rewarder(), e.Slasher(), + ); err != nil { return nil, ctxerror.New("cannot pay block reward").WithCause(err) } @@ -221,7 +225,9 @@ func (e *engineImpl) Finalize( } // QuorumForBlock returns the quorum for the given block header. -func QuorumForBlock(chain engine.ChainReader, h *block.Header, reCalculate bool) (quorum int, err error) { +func QuorumForBlock( + chain engine.ChainReader, h *block.Header, reCalculate bool, +) (quorum int, err error) { var ss shard.State if reCalculate { ss, _ = committee.WithStakingEnabled.Compute(h.Epoch(), *chain.Config(), nil) diff --git a/internal/chain/reward.go b/internal/chain/reward.go index 37523a723..9b56d4260 100644 --- a/internal/chain/reward.go +++ b/internal/chain/reward.go @@ -2,6 +2,7 @@ package chain import ( "math/big" + "sync" "github.com/ethereum/go-ethereum/common" "github.com/harmony-one/bls/ffi/go/bls" @@ -14,6 +15,7 @@ import ( common2 "github.com/harmony-one/harmony/internal/common" "github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/utils" + "github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/staking/slash" "github.com/pkg/errors" ) @@ -82,29 +84,54 @@ func AccumulateRewards( } accounts := []common.Address{} + missing := shard.NodeIDList{} for idx, member := range parentCommittee.NodeList { - if signed, err := mask.IndexEnabled(idx); err != nil { + switch signed, err := mask.IndexEnabled(idx); true { + case err != nil: return ctxerror.New("cannot check for committer bit", "committerIndex", idx, ).WithCause(err) - } else if signed { + case signed: accounts = append(accounts, member.EcdsaAddress) + default: + missing = append(missing, member) } } - type t struct { + // do it quickly + w := sync.WaitGroup{} + for i := range missing { + w.Add(1) + go func(member int) { + defer w.Add(-1) + // Slash if missing block was long enough + if slasher.ShouldSlash(missing[member].BlsPublicKey) { + // TODO Logic + } + }(i) + } + + w.Wait() + + payable := []struct { + string common.Address *big.Int - } - signers := []string{} - payable := []t{} + }{} totalAmount := rewarder.Award( BlockReward, accounts, func(receipient common.Address, amount *big.Int) { - signers = append(signers, common2.MustAddressToBech32(receipient)) - payable = append(payable, t{receipient, amount}) - }) + payable = append(payable, struct { + string + common.Address + *big.Int + }{ + common2.MustAddressToBech32(receipient), receipient, amount, + }, + ) + }, + ) if totalAmount.Cmp(BlockReward) != 0 { utils.Logger().Error(). @@ -114,7 +141,10 @@ func AccumulateRewards( return errors.Wrapf(errPayoutNotEqualBlockReward, "payout "+totalAmount.String()) } + signers := make([]string, len(payable)) + for i := range payable { + signers[i] = payable[i].string state.AddBalance(payable[i].Address, payable[i].Int) } diff --git a/staking/slash/slasher.go b/staking/slash/slasher.go index 2d6da48e2..c6e1e4047 100644 --- a/staking/slash/slasher.go +++ b/staking/slash/slasher.go @@ -2,7 +2,18 @@ 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 +}