diff --git a/cmd/staking/root.go b/cmd/staking/root.go index c5a7bf7ed..053fc106a 100644 --- a/cmd/staking/root.go +++ b/cmd/staking/root.go @@ -170,6 +170,21 @@ func (s *staker) run(cmd *cobra.Command, args []string) error { rlp.DecodeBytes(enc, tx) hexSignature := hexutil.Encode(enc) param := []interface{}{hexSignature} + + // Check Double check if have enough balance for gas fees + balanceR, _ := baseRequest("hmy_getBalance", "http://localhost:9500", []interface{}{testAccounts[index], "latest"}) + m := map[string]interface{}{} + json.Unmarshal(balanceR, &m) + balance, _ := m["result"].(string) + bln, _ := big.NewInt(0).SetString(balance[2:], 16) + + if bln.Cmp(big.NewInt(0)) == 0 { + fmt.Println("Balance for ", testAccounts[index], "is zero, tx will be rejected b/c not enough for gas fee, exiting") + os.Exit(-1) + } + + fmt.Println("balance", convertBalanceIntoReadableFormat(bln)) + result, reqOops := baseRequest(stakingRPC, "http://localhost:9500", param) fmt.Println(string(result)) return reqOops @@ -245,3 +260,43 @@ var ( }, } ) + +func convertBalanceIntoReadableFormat(balance *big.Int) string { + balance = balance.Div(balance, big.NewInt(denominations.Nano)) + strBalance := fmt.Sprintf("%d", balance.Uint64()) + + bytes := []byte(strBalance) + hasDecimal := false + for i := 0; i < 11; i++ { + if len(bytes)-1-i < 0 { + bytes = append([]byte{'0'}, bytes...) + } + if bytes[len(bytes)-1-i] != '0' && i < 9 { + hasDecimal = true + } + if i == 9 { + newBytes := append([]byte{'.'}, bytes[len(bytes)-i:]...) + bytes = append(bytes[:len(bytes)-i], newBytes...) + } + } + zerosToRemove := 0 + for i := 0; i < len(bytes); i++ { + if hasDecimal { + if bytes[len(bytes)-1-i] == '0' { + bytes = bytes[:len(bytes)-1-i] + i-- + } else { + break + } + } else { + if zerosToRemove < 5 { + bytes = bytes[:len(bytes)-1-i] + i-- + zerosToRemove++ + } else { + break + } + } + } + return string(bytes) +} diff --git a/consensus/consensus_service.go b/consensus/consensus_service.go index eda4e804e..624492934 100644 --- a/consensus/consensus_service.go +++ b/consensus/consensus_service.go @@ -460,7 +460,6 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode { 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()) { diff --git a/consensus/engine/consensus_engine.go b/consensus/engine/consensus_engine.go index 76ff47b97..a2fd2101a 100644 --- a/consensus/engine/consensus_engine.go +++ b/consensus/engine/consensus_engine.go @@ -10,6 +10,8 @@ import ( "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/shard" + "github.com/harmony-one/harmony/shard/committee" + "github.com/harmony-one/harmony/staking/slash" staking "github.com/harmony-one/harmony/staking/types" ) @@ -42,6 +44,9 @@ type ChainReader interface { // ReadActiveValidatorList retrieves the list of active validators ReadActiveValidatorList() ([]common.Address, error) + + // Methods needed for EPoS committee assignment calculation + committee.StakingCandidatesReader } // Engine is an algorithm agnostic consensus engine. @@ -83,6 +88,12 @@ type Engine interface { // SetRewarder assigns the Distributor used in block reward SetRewarder(reward.Distributor) + // Slasher handles slashing accounts due to inavailibility or double-signing + Slasher() slash.Slasher + + // SetSlasher assigns the slasher used + SetSlasher(slash.Slasher) + // Finalize runs any post-transaction state modifications (e.g. block rewards) // and assembles the final block. // Note: The block header and state database might be updated to reflect any diff --git a/consensus/quorum/one-node-one-vote.go b/consensus/quorum/one-node-one-vote.go index 8595a9c31..0c065a16e 100644 --- a/consensus/quorum/one-node-one-vote.go +++ b/consensus/quorum/one-node-one-vote.go @@ -1,17 +1,20 @@ package quorum import ( + "encoding/json" "math/big" "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 + DependencyInjectionReader + SignatureReader } // Policy .. @@ -40,9 +43,9 @@ func (v *uniformVoteWeight) IsRewardThresholdAchieved() bool { return v.SignersCount(Commit) >= (v.ParticipantsCount() * 9 / 10) } -// func (v *uniformVoteWeight) UpdateVotingPower(effective.StakeKeeper) { -// NO-OP do not add anything here -// } +func (v *uniformVoteWeight) UpdateVotingPower(shard.SlotList) { + // NO-OP do not add anything here +} // ToggleActive for uniform vote is a no-op, always says that voter is active func (v *uniformVoteWeight) ToggleActive(*bls.PublicKey) bool { @@ -71,3 +74,23 @@ func (v *uniformVoteWeight) Award( return payout } + +func (v *uniformVoteWeight) ShouldSlash(k shard.BlsPublicKey) bool { + // No-op, no semantic meaning in one-slot-one-vote + return false +} + +func (v *uniformVoteWeight) JSON() string { + s, _ := v.ShardIDProvider()() + + type t struct { + Policy string `json"policy"` + ShardID uint32 `json:"shard-id"` + Count int `json:"count"` + Participants []string `json:"committee-members"` + } + + members := v.DumpParticipants() + b1, _ := json.Marshal(t{v.Policy().String(), s, len(members), members}) + return string(b1) +} diff --git a/consensus/quorum/one-node-staked-vote.go b/consensus/quorum/one-node-staked-vote.go index efd2b385b..074f25e89 100644 --- a/consensus/quorum/one-node-staked-vote.go +++ b/consensus/quorum/one-node-staked-vote.go @@ -1,29 +1,35 @@ package quorum import ( + "encoding/json" "math/big" "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" + "github.com/harmony-one/harmony/staking/slash" ) var ( - twoThirds = numeric.NewDec(2).QuoInt64(3).Int + twoThirds = numeric.NewDec(2).QuoInt64(3) + hSentinel = numeric.ZeroDec() ) type stakedVoter struct { isActive, isHarmonyNode bool + earningAccount common.Address effective numeric.Dec } type stakedVoteWeight struct { SignatureReader DependencyInjectionWriter - // EPOS based staking - validatorStakes map[[shard.PublicKeySizeInBytes]byte]stakedVoter - totalEffectiveStakedAmount *big.Int + DependencyInjectionReader + slash.ThresholdDecider + validatorStakes map[shard.BlsPublicKey]stakedVoter + total numeric.Dec } // Policy .. @@ -31,45 +37,141 @@ func (v *stakedVoteWeight) Policy() Policy { return SuperMajorityStake } -// We must maintain 2/3 quoroum, so whatever is 2/3 staked amount, -// we divide that out & you // IsQuorumAchieved .. func (v *stakedVoteWeight) IsQuorumAchieved(p Phase) bool { - // TODO Implement this logic + // TODO Implement this logic w/Chao + // soFar := numeric.ZeroDec() + w := shard.BlsPublicKey{} + members := v.Participants() + + for i := range members { + w.FromLibBLSPublicKey(members[i]) + // isHMY := v.validatorStakes[w].isHarmonyNode + if v.ReadSignature(p, members[i]) == nil { + // TODO TODO finish this logic + } + } + return true } // QuorumThreshold .. func (v *stakedVoteWeight) QuorumThreshold() *big.Int { - return new(big.Int).Mul(v.totalEffectiveStakedAmount, twoThirds) + return v.total.Mul(twoThirds).Ceil().RoundInt() } // RewardThreshold .. func (v *stakedVoteWeight) IsRewardThresholdAchieved() bool { // TODO Implement - return false + return true } -// HACK -var ( - hSentinel = big.NewInt(0) - hEffectiveSentinel = numeric.ZeroDec() -) - // Award .. func (v *stakedVoteWeight) Award( Pie *big.Int, earners []common.Address, hook func(earner common.Address, due *big.Int), ) *big.Int { - // TODO Implement - return nil + payout := big.NewInt(0) + last := big.NewInt(0) + count := big.NewInt(int64(len(earners))) + proportional := map[common.Address]numeric.Dec{} + + for _, details := range v.validatorStakes { + if details.isHarmonyNode == false { + proportional[details.earningAccount] = details.effective.QuoTruncate( + v.total, + ) + } + } + // TODO Finish implementing this logic w/Chao + + for i := range earners { + cur := big.NewInt(0) + + cur.Mul(Pie, big.NewInt(int64(i+1))).Div(cur, count) + + diff := big.NewInt(0).Sub(cur, last) + + // hook(common.Address(account), diff) + + payout = big.NewInt(0).Add(payout, diff) + + last = cur + } + + return payout } -// UpdateVotingPower called only at epoch change, prob need to move to CalculateShardState -// func (v *stakedVoteWeight) UpdateVotingPower(keeper effective.StakeKeeper) { -// TODO Implement -// } +func (v *stakedVoteWeight) UpdateVotingPower(staked shard.SlotList) { + s, _ := v.ShardIDProvider()() -func (v *stakedVoteWeight) ToggleActive(*bls.PublicKey) bool { - // TODO Implement - return true + v.validatorStakes = map[shard.BlsPublicKey]stakedVoter{} + v.Reset([]Phase{Prepare, Commit, ViewChange}) + + for i := range staked { + if staked[i].StakeWithDelegationApplied != nil { + v.validatorStakes[staked[i].BlsPublicKey] = stakedVoter{ + true, false, staked[i].EcdsaAddress, *staked[i].StakeWithDelegationApplied, + } + v.total = v.total.Add(*staked[i].StakeWithDelegationApplied) + } else { + v.validatorStakes[staked[i].BlsPublicKey] = stakedVoter{ + true, true, staked[i].EcdsaAddress, hSentinel, + } + } + } + + utils.Logger().Info(). + Uint32("on-shard", s). + Str("Staked", v.total.String()). + Msg("Total staked") +} + +func (v *stakedVoteWeight) ToggleActive(k *bls.PublicKey) bool { + w := shard.BlsPublicKey{} + w.FromLibBLSPublicKey(k) + g := v.validatorStakes[w] + g.isActive = !g.isActive + v.validatorStakes[w] = g + return v.validatorStakes[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()() + + type t struct { + Policy string `json"policy"` + ShardID uint32 `json:"shard-id"` + Count int `json:"count"` + Participants []string `json:"committee-members"` + TotalStaked string `json:"total-staked"` + } + + members := v.DumpParticipants() + parts := []string{} + for i := range members { + k := bls.PublicKey{} + k.DeserializeHexStr(members[i]) + w := shard.BlsPublicKey{} + w.FromLibBLSPublicKey(&k) + staker := v.validatorStakes[w] + if staker.isHarmonyNode { + parts = append(parts, members[i]) + } else { + parts = append(parts, members[i]+"-"+staker.effective.String()) + } + } + b1, _ := json.Marshal(t{ + v.Policy().String(), s, len(members), parts, v.total.String(), + }) + return string(b1) } diff --git a/consensus/quorum/quorum.go b/consensus/quorum/quorum.go index 61d91cfc6..af2633119 100644 --- a/consensus/quorum/quorum.go +++ b/consensus/quorum/quorum.go @@ -5,7 +5,9 @@ import ( "math/big" "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" // "github.com/harmony-one/harmony/staking/effective" ) @@ -44,6 +46,19 @@ const ( SuperMajorityStake ) +var policyNames = map[Policy]string{ + SuperMajorityStake: "SuperMajorityStake", + SuperMajorityVote: "SuperMajorityVote", +} + +func (p Policy) String() string { + if name, ok := policyNames[p]; ok { + return name + } + return fmt.Sprintf("Unknown Quorum Policy %+v", byte(p)) + +} + // ParticipantTracker .. type ParticipantTracker interface { Participants() []*bls.PublicKey @@ -75,12 +90,24 @@ type DependencyInjectionWriter interface { SetShardIDProvider(func() (uint32, error)) } +// DependencyInjectionReader .. +type DependencyInjectionReader interface { + ShardIDProvider() func() (uint32, error) +} + +//WithJSONDump representation dump +type WithJSONDump interface { + JSON() string +} + // Decider .. type Decider interface { SignatureReader DependencyInjectionWriter + slash.Slasher + WithJSONDump ToggleActive(*bls.PublicKey) bool - // UpdateVotingPower(keeper effective.StakeKeeper) + UpdateVotingPower(shard.SlotList) Policy() Policy IsQuorumAchieved(Phase) bool QuorumThreshold() *big.Int @@ -96,17 +123,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,12 +156,24 @@ func (s *cIdentities) Participants() []*bls.PublicKey { } func (s *cIdentities) UpdateParticipants(pubKeys []*bls.PublicKey) { + // TODO - might need to put reset of seen counter 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]++ + 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++ { + for i := range s.publicKeys { keys[i] = s.publicKeys[i].SerializeToHexStr() } return keys @@ -174,8 +210,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,26 +259,46 @@ func (s *cIdentities) ReadAllSignatures(p Phase) []*bls.Sign { return sigs } -func newMapBackedSignatureReader() SignatureReader { +func newMapBackedSignatureReader() *cIdentities { return &cIdentities{ []*bls.PublicKey{}, map[string]*bls.Sign{}, map[string]*bls.Sign{}, map[string]*bls.Sign{}, + map[[shard.PublicKeySizeInBytes]byte]int{}, } } +type composite struct { + DependencyInjectionWriter + DependencyInjectionReader + SignatureReader +} + +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{} + deps := &depInject{} + c := &composite{deps, deps, signatureStore} switch p { case SuperMajorityVote: - return &uniformVoteWeight{signatureStore, dependencies} + return &uniformVoteWeight{ + c.DependencyInjectionWriter, c.DependencyInjectionReader, c, + } case SuperMajorityStake: return &stakedVoteWeight{ - signatureStore, - dependencies, - map[[shard.PublicKeySizeInBytes]byte]stakedVoter{}, - big.NewInt(0), + c.SignatureReader, + c.DependencyInjectionWriter, + c.DependencyInjectionWriter.(DependencyInjectionReader), + c.SignatureReader.(slash.ThresholdDecider), + map[shard.BlsPublicKey]stakedVoter{}, + numeric.ZeroDec(), } default: // Should not be possible diff --git a/core/chain_makers.go b/core/chain_makers.go index cdd30e907..8d7da2610 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -271,3 +271,9 @@ func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *block.Hea func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil } func (cr *fakeChainReader) ReadShardState(epoch *big.Int) (shard.State, error) { return nil, nil } func (cr *fakeChainReader) ReadActiveValidatorList() ([]common.Address, error) { return nil, nil } + +func (cr *fakeChainReader) ValidatorCandidates() []common.Address { return nil } +func (cr *fakeChainReader) ReadValidatorData(addr common.Address) (*staking.ValidatorWrapper, error) { + return nil, nil +} +func (cr *fakeChainReader) ValidatorStakingWithDelegation(addr common.Address) *big.Int { return nil } diff --git a/core/state_transition.go b/core/state_transition.go index 9cf9a80d2..204007d7c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -285,6 +285,9 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { // Pay intrinsic gas // TODO: propose staking-specific formula for staking transaction gas, err := IntrinsicGas(st.data, false, homestead) + // TODO Remove this logging + utils.Logger().Info().Uint64("Using", gas).Msg("Gas cost of staking transaction being processed") + if err != nil { return 0, err } diff --git a/internal/chain/engine.go b/internal/chain/engine.go index 88f12f375..628843e53 100644 --- a/internal/chain/engine.go +++ b/internal/chain/engine.go @@ -11,10 +11,12 @@ import ( "github.com/harmony-one/harmony/consensus/reward" "github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/types" + 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/shard/committee" + "github.com/harmony-one/harmony/staking/slash" staking "github.com/harmony-one/harmony/staking/types" "github.com/pkg/errors" "golang.org/x/crypto/sha3" @@ -22,10 +24,11 @@ import ( type engineImpl struct { d reward.Distributor + s slash.Slasher } // Engine is an algorithm-agnostic consensus engine. -var Engine = &engineImpl{nil} +var Engine = &engineImpl{nil, nil} // Rewarder handles the distribution of block rewards func (e *engineImpl) Rewarder() reward.Distributor { @@ -37,6 +40,16 @@ func (e *engineImpl) SetRewarder(d reward.Distributor) { e.d = d } +// Slasher handles slashing accounts due to inavailibility or double-signing +func (e *engineImpl) Slasher() slash.Slasher { + return e.s +} + +// SetSlasher assigns the slasher used +func (e *engineImpl) SetSlasher(s slash.Slasher) { + e.s = s +} + // SealHash returns the hash of a block prior to it being sealed. func (e *engineImpl) SealHash(header *block.Header) (hash common.Hash) { hasher := sha3.NewLegacyKeccak256() @@ -164,13 +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 - // TODO: Block rewards should be done only in beacon chain based on cross-links - if err := AccumulateRewards(chain, state, header, e.Rewarder()); err != nil { + if err := AccumulateRewards( + chain, state, header, e.Rewarder(), e.Slasher(), + ); err != nil { return nil, ctxerror.New("cannot pay block reward").WithCause(err) } @@ -194,6 +210,7 @@ func (e *engineImpl) Finalize( return nil, ctxerror.New("failed update validator info").WithCause(err) } } else { + err = errors.New("validator came back empty" + common2.MustAddressToBech32(validator)) return nil, ctxerror.New("failed getting validator info").WithCause(err) } } @@ -203,10 +220,12 @@ 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) + ss, _ = committee.WithStakingEnabled.Compute(h.Epoch(), *chain.Config(), chain) } else { ss, err = chain.ReadShardState(h.Epoch()) if err != nil { @@ -265,7 +284,7 @@ func GetPublicKeys(chain engine.ChainReader, header *block.Header, reCalculate b var shardState shard.State var err error if reCalculate { - shardState, _ = committee.WithStakingEnabled.Compute(header.Epoch(), *chain.Config(), nil) + shardState, _ = committee.WithStakingEnabled.Compute(header.Epoch(), *chain.Config(), chain) } else { shardState, err = chain.ReadShardState(header.Epoch()) if err != nil { diff --git a/internal/chain/reward.go b/internal/chain/reward.go index a6a9e952f..34a5738cc 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,8 @@ 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" ) @@ -27,7 +30,9 @@ var ( // reward. The total reward consists of the static block reward and rewards for // included uncles. The coinbase of each uncle block is also rewarded. func AccumulateRewards( - bc engine.ChainReader, state *state.DB, header *block.Header, rewarder reward.Distributor, + bc engine.ChainReader, state *state.DB, + header *block.Header, rewarder reward.Distributor, + slasher slash.Slasher, ) error { blockNum := header.Number().Uint64() if blockNum == 0 { @@ -79,29 +84,54 @@ func AccumulateRewards( } accounts := []common.Address{} + missing := shard.SlotList{} for idx, member := range parentCommittee.Slots { - 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.Done() + // 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(). @@ -111,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/node/node.go b/node/node.go index eececed4b..7401e3daf 100644 --- a/node/node.go +++ b/node/node.go @@ -33,6 +33,7 @@ import ( p2p_host "github.com/harmony-one/harmony/p2p/host" "github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard/committee" + "github.com/harmony-one/harmony/staking/slash" staking "github.com/harmony-one/harmony/staking/types" ) @@ -372,7 +373,8 @@ func (node *Node) GetSyncID() [SyncIDLength]byte { } // New creates a new node. -func New(host p2p.Host, consensusObj *consensus.Consensus, chainDBFactory shardchain.DBFactory, isArchival bool) *Node { +func New(host p2p.Host, consensusObj *consensus.Consensus, + chainDBFactory shardchain.DBFactory, isArchival bool) *Node { node := Node{} node.syncFreq = SyncFrequency @@ -431,6 +433,8 @@ func New(host p2p.Host, consensusObj *consensus.Consensus, chainDBFactory shardc node.pendingStakingTransactions = make(map[common.Hash]*staking.StakingTransaction) node.Consensus.VerifiedNewBlock = make(chan *types.Block) chain.Engine.SetRewarder(node.Consensus.Decider.(reward.Distributor)) + chain.Engine.SetSlasher(node.Consensus.Decider.(slash.Slasher)) + // the sequence number is the next block number to be added in consensus protocol, which is always one more than current chain header block node.Consensus.SetBlockNum(blockchain.CurrentBlock().NumberU64() + 1) diff --git a/node/node_handler.go b/node/node_handler.go index eb2f2a9cb..cf60f0d34 100644 --- a/node/node_handler.go +++ b/node/node_handler.go @@ -8,18 +8,17 @@ import ( "sync/atomic" "time" - bls2 "github.com/harmony-one/harmony/crypto/bls" - + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" "github.com/harmony-one/bls/ffi/go/bls" - libp2p_peer "github.com/libp2p/go-libp2p-core/peer" - "github.com/harmony-one/harmony/api/proto" proto_discovery "github.com/harmony-one/harmony/api/proto/discovery" proto_node "github.com/harmony-one/harmony/api/proto/node" "github.com/harmony-one/harmony/block" + "github.com/harmony-one/harmony/consensus/quorum" "github.com/harmony-one/harmony/core/types" + bls2 "github.com/harmony-one/harmony/crypto/bls" nodeconfig "github.com/harmony-one/harmony/internal/configs/node" "github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/utils" @@ -27,7 +26,9 @@ 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/shard/committee" staking "github.com/harmony-one/harmony/staking/types" + libp2p_peer "github.com/libp2p/go-libp2p-core/peer" ) const ( @@ -377,10 +378,36 @@ func (node *Node) PostConsensusProcessing(newBlock *types.Block, commitSigAndBit } } } - // Update consensus keys at last so the change of leader status doesn't mess up normal flow if shard.Schedule.IsLastBlock(newBlock.Number().Uint64()) { + next := new(big.Int).Add(newBlock.Epoch(), common.Big1) + if node.chainConfig.StakingEpoch.Cmp(next) == 0 && + node.Consensus.Decider.Policy() != quorum.SuperMajorityStake { + node.Consensus.Decider = quorum.NewDecider(quorum.SuperMajorityStake) + node.Consensus.Decider.SetShardIDProvider(func() (uint32, error) { + return node.Consensus.ShardID, nil + }) + s, _ := committee.WithStakingEnabled.Compute( + next, node.chainConfig, node.Consensus.ChainReader, + ) + node.Consensus.Decider.UpdateVotingPower( + s.FindCommitteeByID(node.Consensus.ShardID).Slots, + ) + } + // TODO Need to refactor UpdateConsensusInformation so can fold the following logic + // into UCI - todo because UCI mutates state & called in overloaded contexts node.Consensus.UpdateConsensusInformation() + + if shard.Schedule.IsLastBlock(newBlock.Number().Uint64()) { + if node.chainConfig.StakingEpoch.Cmp(next) == 0 { + // Hit this case again, need after UpdateConsensus + curPubKeys := committee.WithStakingEnabled.ComputePublicKeys( + next, node.Consensus.ChainReader, + )[int(node.Consensus.ShardID)] + node.Consensus.Decider.UpdateParticipants(curPubKeys) + } + } + } // TODO chao: uncomment this after beacon syncing is stable diff --git a/node/node_newblock.go b/node/node_newblock.go index c6bcf364a..b8dec7191 100644 --- a/node/node_newblock.go +++ b/node/node_newblock.go @@ -120,7 +120,7 @@ func (node *Node) proposeNewBlock() (*types.Block, error) { // Prepare shard state shardState, err := node.Worker.SuperCommitteeForNextEpoch( - node.Consensus.ShardID, node.Beaconchain(), + node.Consensus.ShardID, node.Blockchain(), ) if err != nil { @@ -136,6 +136,7 @@ func (node *Node) proposeNewBlock() (*types.Block, error) { return node.Worker.FinalizeNewBlock(sig, mask, node.Consensus.GetViewID(), coinbase, crossLinks, shardState) } +// TODO is this still needed? func (node *Node) proposeLocalShardState(block *types.Block) { logger := block.Logger(utils.Logger()) // TODO ek – read this from beaconchain once BC sync is fixed diff --git a/shard/committee/assignment.go b/shard/committee/assignment.go index 545bda336..ee24b58c1 100644 --- a/shard/committee/assignment.go +++ b/shard/committee/assignment.go @@ -10,6 +10,7 @@ import ( shardingconfig "github.com/harmony-one/harmony/internal/configs/sharding" "github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/params" + "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/staking/effective" staking "github.com/harmony-one/harmony/staking/types" @@ -119,6 +120,8 @@ func eposStakedCommittee( candidates := stakerReader.ValidatorCandidates() essentials := map[common.Address]effective.SlotOrder{} + utils.Logger().Info().Int("staked-candidates", len(candidates)).Msg("preparing epos staked committee") + // TODO benchmark difference if went with data structure that sorts on insert for i := range candidates { // TODO Should be using .ValidatorStakingWithDelegation, not implemented yet @@ -170,7 +173,11 @@ func eposStakedCommittee( &slot.Dec, }) } - + if c := len(candidates); c != 0 { + utils.Logger().Info().Int("staked-candidates", c). + RawJSON("staked-super-committee", []byte(superComm.JSON())). + Msg("EPoS based super-committe") + } return superComm, nil } diff --git a/shard/shard_state.go b/shard/shard_state.go index 65644b0b0..b850c2dcc 100644 --- a/shard/shard_state.go +++ b/shard/shard_state.go @@ -38,7 +38,7 @@ type BlsPublicKey [PublicKeySizeInBytes]byte type Slot struct { EcdsaAddress common.Address `json:"ecdsa-address"` BlsPublicKey BlsPublicKey `json:"bls-pubkey"` - // nil means not active, 0 means our node, >= 0 means staked node + // nil means our node, 0 means not active, >= 0 means staked node StakeWithDelegationApplied *numeric.Dec `json:"staked-validator" rlp:"nil"` } @@ -55,7 +55,7 @@ type Committee struct { func (ss State) JSON() string { type t struct { Slot - EcdsaAddress string `json:"one-address"` + EcdsaAddress string `json:"ecdsa-address"` } type v struct { Committee diff --git a/staking/slash/slasher.go b/staking/slash/slasher.go new file mode 100644 index 000000000..c6e1e4047 --- /dev/null +++ b/staking/slash/slasher.go @@ -0,0 +1,19 @@ +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 +} diff --git a/staking/types/validator.go b/staking/types/validator.go index 0a5b4f0d8..c2333060f 100644 --- a/staking/types/validator.go +++ b/staking/types/validator.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" 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/numeric" "github.com/harmony-one/harmony/shard" ) @@ -56,6 +57,7 @@ type Validator struct { Address common.Address `json:"address" yaml:"address"` // The BLS public key of the validator for consensus SlotPubKeys []shard.BlsPublicKey `json:"slot_pub_keys" yaml:"slot_pub_keys"` + // TODO Need to remove this .Stake Field // The stake put by the validator itself Stake *big.Int `json:"stake" yaml:"stake"` // if unbonding, height at which this validator has begun unbonding @@ -118,6 +120,13 @@ func (w *ValidatorWrapper) SanityCheck() error { hundredPercent := numeric.NewDec(1) zeroPercent := numeric.NewDec(0) + + utils.Logger().Info(). + Str("rate", w.Validator.Rate.String()). + Str("max-rate", w.Validator.MaxRate.String()). + Str("max-change-rate", w.Validator.MaxChangeRate.String()). + Msg("Sanity check on validator commission rates, should all be in [0, 1]") + if w.Validator.Rate.LT(zeroPercent) || w.Validator.Rate.GT(hundredPercent) { return errInvalidComissionRate } @@ -227,12 +236,12 @@ func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Valida commission := Commission{val.CommissionRates, blockNum} pubKeys := []shard.BlsPublicKey{} pubKeys = append(pubKeys, val.SlotPubKeys...) - // TODO: a new validator should have a minimum of 1 token as self delegation, and that should be added as a delegation entry here. v := Validator{ val.ValidatorAddress, pubKeys, val.Amount, new(big.Int), val.MinSelfDelegation, val.MaxTotalDelegation, false, - commission, desc, blockNum} + commission, desc, blockNum, + } return &v, nil } diff --git a/test/deploy.sh b/test/deploy.sh index 478d322cf..1d47ce1dc 100755 --- a/test/deploy.sh +++ b/test/deploy.sh @@ -212,7 +212,7 @@ while IFS='' read -r line || [[ -n "$line" ]]; do done < $config if [ "$DOTEST" == "true" ]; then - debug_staking + # debug_staking echo "waiting for some block rewards" sleep 60 i=1