From aeb360cb83be8afe57079417b884ff67d9ad44c5 Mon Sep 17 00:00:00 2001 From: janet-harmony <56005637+janet-harmony@users.noreply.github.com> Date: Sun, 1 Dec 2019 12:50:40 -0800 Subject: [PATCH 1/5] [unit-testing] one-node-staked-vote (#1897) * [unit-testing] Add test for when there are 33 Harmony nodes * Add test for Policy * Add test for QuorumThreshold * [quroum] Give diff to last staked voter, make sure sum to one * [quorum] Fix indent for travis --- consensus/quorum/one-node-staked-vote.go | 7 -- consensus/quorum/one-node-staked-vote_test.go | 102 ++++++++++++++++++ consensus/votepower/roster.go | 19 +++- 3 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 consensus/quorum/one-node-staked-vote_test.go diff --git a/consensus/quorum/one-node-staked-vote.go b/consensus/quorum/one-node-staked-vote.go index 590d7242d..c539b71b6 100644 --- a/consensus/quorum/one-node-staked-vote.go +++ b/consensus/quorum/one-node-staked-vote.go @@ -193,13 +193,6 @@ func (v *stakedVoteWeight) SetVoters( Str("Raw-Staked", roster.RawStakedTotal.String()). Msg("Total staked") - //switch { - //case roster.totalStakedPercent.Equal(totalShare) == false: - // return nil, errSumOfVotingPowerNotOne - //case roster.ourPercentage.Add(theirPercentage).Equal(totalShare) == false: - // return nil, errSumOfOursAndTheirsNotOne - //} - // Hold onto this calculation v.roster = *roster return &TallyResult{ diff --git a/consensus/quorum/one-node-staked-vote_test.go b/consensus/quorum/one-node-staked-vote_test.go new file mode 100644 index 000000000..205672140 --- /dev/null +++ b/consensus/quorum/one-node-staked-vote_test.go @@ -0,0 +1,102 @@ +package quorum + +import ( + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/harmony-one/bls/ffi/go/bls" + "github.com/harmony-one/harmony/consensus/votepower" + "github.com/harmony-one/harmony/numeric" + "github.com/harmony-one/harmony/shard" +) + +var ( + secretKeyMap map[shard.BlsPublicKey]bls.SecretKey + slotList shard.SlotList + roster *votepower.Roster + stakedVote Decider + result *TallyResult + harmonyNodes = 33 + stakedNodes = 33 + maxAccountGen = int64(98765654323123134) + accountGen = rand.New(rand.NewSource(1337)) + maxKeyGen = int64(98765654323123134) + keyGen = rand.New(rand.NewSource(42)) + maxStakeGen = int64(200) + stakeGen = rand.New(rand.NewSource(541)) +) + +func init() { + slotList = shard.SlotList{} + secretKeyMap = make(map[shard.BlsPublicKey]bls.SecretKey) + for i := 0; i < harmonyNodes; i++ { + newSlot, sKey := generateRandomSlot() + newSlot.TotalStake = nil + slotList = append(slotList, newSlot) + secretKeyMap[newSlot.BlsPublicKey] = sKey + } + + for j := 0; j < stakedNodes; j++ { + newSlot, sKey := generateRandomSlot() + slotList = append(slotList, newSlot) + secretKeyMap[newSlot.BlsPublicKey] = sKey + } + + stakedVote = NewDecider(SuperMajorityStake) + stakedVote.SetShardIDProvider(func() (uint32, error) { return 0, nil }) + r, err := stakedVote.SetVoters(slotList) + if err != nil { + panic("Unable to SetVoters") + } + result = r +} + +func generateRandomSlot() (shard.Slot, bls.SecretKey) { + addr := common.Address{} + addr.SetBytes(big.NewInt(int64(accountGen.Int63n(maxAccountGen))).Bytes()) + secretKey := bls.SecretKey{} + secretKey.Deserialize(big.NewInt(int64(keyGen.Int63n(maxKeyGen))).Bytes()) + key := shard.BlsPublicKey{} + key.FromLibBLSPublicKey(secretKey.GetPublicKey()) + stake := numeric.NewDecFromBigInt(big.NewInt(int64(stakeGen.Int63n(maxStakeGen)))) + return shard.Slot{addr, key, &stake}, secretKey +} + +func Test33(t *testing.T) { + sum := result.ourPercent.Add(result.theirPercent) + if !sum.Equal(numeric.OneDec()) { + t.Errorf("Total voting power does not equal 1. Harmony voting power: %s, Staked voting power: %s, Sum: %s", + result.ourPercent, result.theirPercent, sum) + } +} + +func TestPolicy(t *testing.T) { + expectedPolicy := SuperMajorityStake + policy := stakedVote.Policy() + if expectedPolicy != policy { + t.Errorf("Expected: %s, Got: %s", expectedPolicy.String(), policy.String()) + } +} + +func TestIsQuorumAchieved(t *testing.T) { + // +} + +func TestQuorumThreshold(t *testing.T) { + expectedThreshold := numeric.NewDec(2).Quo(numeric.NewDec(3)) + quorumThreshold := stakedVote.QuorumThreshold() + if !expectedThreshold.Equal(quorumThreshold) { + t.Errorf("Expected: %s, Got: %s", expectedThreshold.String(), quorumThreshold.String()) + } +} + +func TestIsRewardThresholdAchieved(t *testing.T) { + // +} + +// ???? +func TestShouldSlash(t *testing.T) { + // +} diff --git a/consensus/votepower/roster.go b/consensus/votepower/roster.go index d46430be2..43f7ce63c 100644 --- a/consensus/votepower/roster.go +++ b/consensus/votepower/roster.go @@ -2,6 +2,7 @@ package votepower import ( "github.com/ethereum/go-ethereum/common" + "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/shard" ) @@ -16,6 +17,7 @@ var ( type stakedVoter struct { IsActive, IsHarmonyNode bool EarningAccount common.Address + Identity shard.BlsPublicKey EffectivePercent numeric.Dec RawStake numeric.Dec } @@ -29,7 +31,7 @@ type Roster struct { HmySlotCount int64 } -// Compute .. +// Compute creates a new roster based off the shard.SlotList func Compute(staked shard.SlotList) *Roster { roster := NewRoster() for i := range staked { @@ -45,12 +47,14 @@ func Compute(staked shard.SlotList) *Roster { ourCount := numeric.NewDec(roster.HmySlotCount) ourPercentage := numeric.ZeroDec() theirPercentage := numeric.ZeroDec() + var lastStakedVoter *stakedVoter = nil for i := range staked { member := stakedVoter{ IsActive: true, IsHarmonyNode: true, EarningAccount: staked[i].EcdsaAddress, + Identity: staked[i].BlsPublicKey, EffectivePercent: numeric.ZeroDec(), RawStake: numeric.ZeroDec(), } @@ -63,6 +67,7 @@ func Compute(staked shard.SlotList) *Roster { Quo(roster.RawStakedTotal). Mul(StakersShare) theirPercentage = theirPercentage.Add(member.EffectivePercent) + lastStakedVoter = &member } else { // Our node // TODO See the todo on where this called in one-node-staked-vote, // need to have these two values of our @@ -74,6 +79,18 @@ func Compute(staked shard.SlotList) *Roster { roster.Voters[staked[i].BlsPublicKey] = member } + // NOTE Enforce voting power sums to one, give diff (expect tiny amt) to last staked voter + if diff := numeric.OneDec().Sub( + ourPercentage.Add(theirPercentage), + ); diff.GT(numeric.ZeroDec()) && lastStakedVoter != nil { + lastStakedVoter.EffectivePercent = lastStakedVoter.EffectivePercent.Add(diff) + theirPercentage = theirPercentage.Add(diff) + utils.Logger().Info(). + Str("diff", diff.String()). + Str("bls-public-key-of-receipent", lastStakedVoter.Identity.Hex()). + Msg("sum of voting power of hmy & staked slots not equal to 1, gave diff to staked slot") + } + roster.OurVotingPowerTotalPercentage = ourPercentage roster.TheirVotingPowerTotalPercentage = theirPercentage From 74a4dd0906442a700da23ab925065d3bc10cbca4 Mon Sep 17 00:00:00 2001 From: Edgar Aroutiounian Date: Sun, 1 Dec 2019 15:02:14 -0800 Subject: [PATCH 2/5] [block][staking] Add staking transaction as input to hash of merkle root (#1928) --- core/block_validator.go | 6 ++++-- core/types/block.go | 11 ++++++++--- core/types/derive_sha.go | 24 +++++++++++++++++------- staking/types/transaction.go | 9 +++++++++ 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index 8e97501d0..d6009b986 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -70,8 +70,10 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { //if err := v.engine.VerifyUncles(v.bc, block); err != nil { // return err //} - // TODO: Add staking transactions into txns root - if hash := types.DeriveSha(block.Transactions()); hash != header.TxHash() { + if hash := types.DeriveSha( + block.Transactions(), + block.StakingTransactions(), + ); hash != header.TxHash() { return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash()) } return nil diff --git a/core/types/block.go b/core/types/block.go index 35d814919..5543cb5e2 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -278,7 +278,10 @@ func init() { // The values of TxHash, UncleHash, ReceiptHash and Bloom in header // are ignored and set to values derived from the given txs, // and receipts. -func NewBlock(header *block.Header, txs []*Transaction, receipts []*Receipt, outcxs []*CXReceipt, incxs []*CXReceiptsProof, stks []*staking.StakingTransaction) *Block { +func NewBlock( + header *block.Header, txs []*Transaction, + receipts []*Receipt, outcxs []*CXReceipt, incxs []*CXReceiptsProof, + stks []*staking.StakingTransaction) *Block { b := &Block{header: CopyHeader(header)} if len(receipts) != len(txs)+len(stks) { @@ -294,8 +297,10 @@ func NewBlock(header *block.Header, txs []*Transaction, receipts []*Receipt, out b.stakingTransactions = make(staking.StakingTransactions, len(stks)) copy(b.stakingTransactions, stks) - // TODO: Add staking transactions into txns root - b.header.SetTxHash(DeriveSha(Transactions(txs))) + b.header.SetTxHash(DeriveSha( + Transactions(txs), + staking.StakingTransactions(stks), + )) } if len(receipts) == 0 { diff --git a/core/types/derive_sha.go b/core/types/derive_sha.go index 37f4986d8..b04e712df 100644 --- a/core/types/derive_sha.go +++ b/core/types/derive_sha.go @@ -26,22 +26,32 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -// DerivableList is the interface of DerivableList. -type DerivableList interface { +// DerivableBase .. +type DerivableBase interface { Len() int GetRlp(i int) []byte +} + +// DerivableList is the interface of DerivableList. +type DerivableList interface { + DerivableBase ToShardID(i int) uint32 MaxToShardID() uint32 // return the maximum non-empty destination shardID } // DeriveSha calculates the hash of the trie generated by DerivableList. -func DeriveSha(list DerivableList) common.Hash { +func DeriveSha(list ...DerivableBase) common.Hash { keybuf := new(bytes.Buffer) trie := new(trie.Trie) - for i := 0; i < list.Len(); i++ { - keybuf.Reset() - rlp.Encode(keybuf, uint(i)) - trie.Update(keybuf.Bytes(), list.GetRlp(i)) + var num uint = 0 + + for j := range list { + for i := 0; i < list[j].Len(); i++ { + keybuf.Reset() + rlp.Encode(keybuf, num) + trie.Update(keybuf.Bytes(), list[j].GetRlp(i)) + num++ + } } return trie.Hash() } diff --git a/staking/types/transaction.go b/staking/types/transaction.go index f798db833..cfa69fc07 100644 --- a/staking/types/transaction.go +++ b/staking/types/transaction.go @@ -103,6 +103,15 @@ var ( // StakingTransactions is a stake slice type for basic sorting. type StakingTransactions []*StakingTransaction +// Len .. +func (s StakingTransactions) Len() int { return len(s) } + +// GetRlp implements Rlpable and returns the i'th element of s in rlp. +func (s StakingTransactions) GetRlp(i int) []byte { + enc, _ := rlp.EncodeToBytes(s[i]) + return enc +} + // Hash hashes the RLP encoding of tx. // It uniquely identifies the transaction. func (tx *StakingTransaction) Hash() common.Hash { From 954c50a5611a9cf74afa78376bcdd24988b94ae4 Mon Sep 17 00:00:00 2001 From: Edgar Aroutiounian Date: Mon, 2 Dec 2019 07:08:45 -0800 Subject: [PATCH 3/5] [reward] Add reward Schedule table from spreadsheet (#1929) * [reward] Add reward Schedule table from spreadsheet * [reward][schedule] Make denom a function of time block rather than fixed 12.6 billion * [reward] Fix latent mistaken because of .UnixNano usage, header uses seconds --- consensus/reward/schedule.go | 147 +++++++++++++++++++++++++++++++++++ internal/chain/reward.go | 10 ++- 2 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 consensus/reward/schedule.go diff --git a/consensus/reward/schedule.go b/consensus/reward/schedule.go new file mode 100644 index 000000000..3ef4d75df --- /dev/null +++ b/consensus/reward/schedule.go @@ -0,0 +1,147 @@ +package reward + +import ( + "sort" + "time" + + "github.com/harmony-one/harmony/internal/utils" + "github.com/harmony-one/harmony/numeric" +) + +type pair struct { + ts int64 + share numeric.Dec +} + +var ( + + // schedule is the Token Release Schedule of Harmony + releasePlan = map[int64]numeric.Dec{ + //2019 + mustParse("2019-May-31"): numeric.MustNewDecFromStr("0.2429"), + mustParse("2019-Jun-30"): numeric.MustNewDecFromStr("0.2488"), + mustParse("2019-Jul-31"): numeric.MustNewDecFromStr("0.2547"), + mustParse("2019-Aug-31"): numeric.MustNewDecFromStr("0.2607"), + mustParse("2019-Sep-30"): numeric.MustNewDecFromStr("0.2666"), + mustParse("2019-Oct-31"): numeric.MustNewDecFromStr("0.2726"), + mustParse("2019-Nov-30"): numeric.MustNewDecFromStr("0.3785"), + mustParse("2019-Dec-31"): numeric.MustNewDecFromStr("0.3844"), + // 2020 + mustParse("2020-Jan-31"): numeric.MustNewDecFromStr("0.3951"), + mustParse("2020-Feb-29"): numeric.MustNewDecFromStr("0.4047"), + mustParse("2020-Mar-31"): numeric.MustNewDecFromStr("0.4143"), + mustParse("2020-Apr-30"): numeric.MustNewDecFromStr("0.4239"), + mustParse("2020-May-31"): numeric.MustNewDecFromStr("0.5335"), + mustParse("2020-Jun-30"): numeric.MustNewDecFromStr("0.5431"), + mustParse("2020-Jul-31"): numeric.MustNewDecFromStr("0.5527"), + mustParse("2020-Aug-31"): numeric.MustNewDecFromStr("0.5623"), + mustParse("2020-Sep-30"): numeric.MustNewDecFromStr("0.5719"), + mustParse("2020-Oct-31"): numeric.MustNewDecFromStr("0.5815"), + mustParse("2020-Nov-30"): numeric.MustNewDecFromStr("0.6882"), + mustParse("2020-Dec-31"): numeric.MustNewDecFromStr("0.6948"), + // 2021 + mustParse("2021-Jan-31"): numeric.MustNewDecFromStr("0.7015"), + mustParse("2021-Feb-28"): numeric.MustNewDecFromStr("0.7082"), + mustParse("2021-Mar-31"): numeric.MustNewDecFromStr("0.7148"), + mustParse("2021-Apr-30"): numeric.MustNewDecFromStr("0.7215"), + mustParse("2021-May-31"): numeric.MustNewDecFromStr("0.7721"), + mustParse("2021-Jun-30"): numeric.MustNewDecFromStr("0.7788"), + mustParse("2021-Jul-31"): numeric.MustNewDecFromStr("0.7855"), + mustParse("2021-Aug-31"): numeric.MustNewDecFromStr("0.7922"), + mustParse("2021-Sep-30"): numeric.MustNewDecFromStr("0.7988"), + mustParse("2021-Oct-31"): numeric.MustNewDecFromStr("0.8055"), + mustParse("2021-Nov-30"): numeric.MustNewDecFromStr("0.8561"), + mustParse("2021-Dec-31"): numeric.MustNewDecFromStr("0.8628"), + // 2022 + mustParse("2022-Jan-31"): numeric.MustNewDecFromStr("0.8695"), + mustParse("2022-Feb-28"): numeric.MustNewDecFromStr("0.8761"), + mustParse("2022-Mar-31"): numeric.MustNewDecFromStr("0.8828"), + mustParse("2022-Apr-30"): numeric.MustNewDecFromStr("0.8895"), + mustParse("2022-May-31"): numeric.MustNewDecFromStr("0.8962"), + mustParse("2022-Jun-30"): numeric.MustNewDecFromStr("0.9028"), + mustParse("2022-Jul-31"): numeric.MustNewDecFromStr("0.9095"), + mustParse("2022-Aug-31"): numeric.MustNewDecFromStr("0.9162"), + mustParse("2022-Sep-30"): numeric.MustNewDecFromStr("0.9228"), + mustParse("2022-Oct-31"): numeric.MustNewDecFromStr("0.9295"), + mustParse("2022-Nov-30"): numeric.MustNewDecFromStr("0.9362"), + mustParse("2022-Dec-31"): numeric.MustNewDecFromStr("0.9429"), + // 2023 + mustParse("2023-Jan-31"): numeric.MustNewDecFromStr("0.9448"), + mustParse("2023-Feb-28"): numeric.MustNewDecFromStr("0.9468"), + mustParse("2023-Mar-31"): numeric.MustNewDecFromStr("0.9488"), + mustParse("2023-Apr-30"): numeric.MustNewDecFromStr("0.9507"), + mustParse("2023-May-31"): numeric.MustNewDecFromStr("0.9527"), + mustParse("2023-Jun-30"): numeric.MustNewDecFromStr("0.9547"), + mustParse("2023-Jul-31"): numeric.MustNewDecFromStr("0.9567"), + mustParse("2023-Aug-31"): numeric.MustNewDecFromStr("0.9586"), + mustParse("2023-Sep-30"): numeric.MustNewDecFromStr("0.9606"), + mustParse("2023-Oct-31"): numeric.MustNewDecFromStr("0.9626"), + mustParse("2023-Nov-30"): numeric.MustNewDecFromStr("0.9645"), + mustParse("2023-Dec-31"): numeric.MustNewDecFromStr("0.9665"), + // 2024 + mustParse("2024-Jan-31"): numeric.MustNewDecFromStr("0.9685"), + mustParse("2024-Feb-29"): numeric.MustNewDecFromStr("0.9704"), + mustParse("2024-Mar-31"): numeric.MustNewDecFromStr("0.9724"), + mustParse("2024-Apr-30"): numeric.MustNewDecFromStr("0.9744"), + mustParse("2024-May-31"): numeric.MustNewDecFromStr("0.9764"), + mustParse("2024-Jun-30"): numeric.MustNewDecFromStr("0.9783"), + mustParse("2024-Jul-31"): numeric.MustNewDecFromStr("0.9803"), + mustParse("2024-Aug-31"): numeric.MustNewDecFromStr("0.9823"), + mustParse("2024-Sep-30"): numeric.MustNewDecFromStr("0.9842"), + mustParse("2024-Oct-31"): numeric.MustNewDecFromStr("0.9862"), + mustParse("2024-Nov-30"): numeric.MustNewDecFromStr("0.9882"), + mustParse("2024-Dec-31"): numeric.MustNewDecFromStr("0.9901"), + // 2025 + mustParse("2025-Jan-31"): numeric.MustNewDecFromStr("0.9921"), + mustParse("2025-Feb-28"): numeric.MustNewDecFromStr("0.9941"), + mustParse("2025-Mar-31"): numeric.MustNewDecFromStr("0.9961"), + mustParse("2025-Apr-30"): numeric.MustNewDecFromStr("0.9980"), + mustParse("2025-May-30"): numeric.MustNewDecFromStr("1.0000"), + } + sorted = func() []pair { + s := []pair{} + for k, v := range releasePlan { + s = append(s, pair{k, v}) + } + sort.SliceStable( + s, + func(i, j int) bool { return s[i].ts < s[j].ts }, + ) + return s + }() +) + +func mustParse(ts string) int64 { + const shortForm = "2006-Jan-02" + t, err := time.Parse(shortForm, ts) + if err != nil { + panic("could not parse timestamp") + } + return t.Unix() +} + +// PercentageForTimeStamp .. +func PercentageForTimeStamp(ts int64) numeric.Dec { + bucket := pair{} + i, j := 0, 1 + + for range sorted { + if i == len(sorted) { + bucket = sorted[i-1] + break + } + if ts >= sorted[i].ts && ts < sorted[j].ts { + bucket = sorted[i] + break + } + i++ + j++ + } + + utils.Logger().Info(). + Str("percent of total-supply used", bucket.share.Mul(numeric.NewDec(100)).String()). + Str("for-time", time.Unix(ts, 0).String()). + Msg("Picked Percentage for timestamp") + + return bucket.share +} diff --git a/internal/chain/reward.go b/internal/chain/reward.go index 34b016f71..e6d10b45b 100644 --- a/internal/chain/reward.go +++ b/internal/chain/reward.go @@ -131,6 +131,7 @@ func ballotResultBeaconchain( func whatPercentStakedNow( beaconchain engine.ChainReader, + timestamp int64, ) (*numeric.Dec, error) { stakedNow := numeric.ZeroDec() // Only active validators' stake is counted in stake ratio because only their stake is under slashing risk @@ -154,8 +155,9 @@ func whatPercentStakedNow( numeric.NewDecFromBigInt(wrapper.TotalDelegation()), ) } - - percentage := stakedNow.Quo(totalTokens.Add( + percentage := stakedNow.Quo(totalTokens.Mul( + reward.PercentageForTimeStamp(timestamp), + ).Add( numeric.NewDecFromBigInt(soFarDoledOut)), ) return &percentage, nil @@ -169,8 +171,8 @@ func AccumulateRewards( rewarder reward.Distributor, slasher slash.Slasher, beaconChain engine.ChainReader, ) error { - blockNum := header.Number().Uint64() + if blockNum == 0 { // genesis block has no parent to reward. return nil @@ -188,7 +190,7 @@ func AccumulateRewards( defaultReward := BaseStakedReward // TODO Use cached result in off-chain db instead of full computation - percentageStaked, err := whatPercentStakedNow(beaconChain) + percentageStaked, err := whatPercentStakedNow(beaconChain, header.Time().Int64()) if err != nil { return err } From a8a9f7c95199ccc8b8a7f2b0c641543d4c9b9889 Mon Sep 17 00:00:00 2001 From: Edgar Aroutiounian Date: Mon, 2 Dec 2019 09:43:03 -0800 Subject: [PATCH 4/5] [votepower] Allow negative diff, explicit check sum to one (#1930) * [votepower] Allow negative diff, explicit check sum to one * [votepower] Handle error value from compute in test * [votepower] Change log as suggested in PR comment * [votepower] Check sum of voting power is 1 only when have at least one staked validator --- consensus/quorum/one-node-staked-vote.go | 5 ++++- consensus/votepower/roster.go | 18 +++++++++++++++--- consensus/votepower/roster_test.go | 6 +++++- internal/chain/reward.go | 10 ++++++++-- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/consensus/quorum/one-node-staked-vote.go b/consensus/quorum/one-node-staked-vote.go index c539b71b6..c659ec3fc 100644 --- a/consensus/quorum/one-node-staked-vote.go +++ b/consensus/quorum/one-node-staked-vote.go @@ -185,7 +185,10 @@ func (v *stakedVoteWeight) SetVoters( s, _ := v.ShardIDProvider()() v.Reset([]Phase{Prepare, Commit, ViewChange}) - roster := votepower.Compute(staked) + roster, err := votepower.Compute(staked) + if err != nil { + return nil, err + } utils.Logger().Info(). Str("our-percentage", roster.OurVotingPowerTotalPercentage.String()). Str("their-percentage", roster.TheirVotingPowerTotalPercentage.String()). diff --git a/consensus/votepower/roster.go b/consensus/votepower/roster.go index 43f7ce63c..8fd829ef2 100644 --- a/consensus/votepower/roster.go +++ b/consensus/votepower/roster.go @@ -5,6 +5,7 @@ import ( "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/shard" + "github.com/pkg/errors" ) var ( @@ -12,6 +13,8 @@ var ( HarmonysShare = numeric.MustNewDecFromStr("0.68") // StakersShare .. StakersShare = numeric.MustNewDecFromStr("0.32") + // ErrVotingPowerNotEqualOne .. + ErrVotingPowerNotEqualOne = errors.New("voting power not equal to one") ) type stakedVoter struct { @@ -32,7 +35,7 @@ type Roster struct { } // Compute creates a new roster based off the shard.SlotList -func Compute(staked shard.SlotList) *Roster { +func Compute(staked shard.SlotList) (*Roster, error) { roster := NewRoster() for i := range staked { if staked[i].TotalStake == nil { @@ -82,7 +85,7 @@ func Compute(staked shard.SlotList) *Roster { // NOTE Enforce voting power sums to one, give diff (expect tiny amt) to last staked voter if diff := numeric.OneDec().Sub( ourPercentage.Add(theirPercentage), - ); diff.GT(numeric.ZeroDec()) && lastStakedVoter != nil { + ); lastStakedVoter != nil { lastStakedVoter.EffectivePercent = lastStakedVoter.EffectivePercent.Add(diff) theirPercentage = theirPercentage.Add(diff) utils.Logger().Info(). @@ -91,10 +94,19 @@ func Compute(staked shard.SlotList) *Roster { Msg("sum of voting power of hmy & staked slots not equal to 1, gave diff to staked slot") } + if lastStakedVoter != nil && + !ourPercentage.Add(theirPercentage).Equal(numeric.OneDec()) { + utils.Logger().Error(). + Str("theirs", theirPercentage.String()). + Str("ours", ourPercentage.String()). + Msg("Total voting power not equal 100 percent") + return nil, ErrVotingPowerNotEqualOne + } + roster.OurVotingPowerTotalPercentage = ourPercentage roster.TheirVotingPowerTotalPercentage = theirPercentage - return roster + return roster, nil } diff --git a/consensus/votepower/roster_test.go b/consensus/votepower/roster_test.go index 88f143619..38bc99e47 100644 --- a/consensus/votepower/roster_test.go +++ b/consensus/votepower/roster_test.go @@ -81,7 +81,11 @@ func TestCompute(t *testing.T) { expectedRoster.Voters[slot.BlsPublicKey] = newMember } - computedRoster := Compute(slotList) + computedRoster, err := Compute(slotList) + if err != nil { + t.Error("Computed Roster failed on vote summation to one") + } + if !compareRosters(expectedRoster, computedRoster, t) { t.Errorf("Compute Roster mismatch with expected Roster") } diff --git a/internal/chain/reward.go b/internal/chain/reward.go index e6d10b45b..23ed40c32 100644 --- a/internal/chain/reward.go +++ b/internal/chain/reward.go @@ -219,7 +219,10 @@ func AccumulateRewards( return err } - votingPower := votepower.Compute(members) + votingPower, err := votepower.Compute(members) + if err != nil { + return err + } for beaconMember := range payable { // TODO Give out whatever leftover to the last voter/handle @@ -271,7 +274,10 @@ func AccumulateRewards( return err } - votingPower := votepower.Compute(payableSigners) + votingPower, err := votepower.Compute(payableSigners) + if err != nil { + return err + } for j := range payableSigners { voter := votingPower.Voters[payableSigners[j].BlsPublicKey] if !voter.IsHarmonyNode && !voter.EffectivePercent.IsZero() { From a8a977e88cf6cab18c0d036748f0d0bbd6ce654f Mon Sep 17 00:00:00 2001 From: Chao Ma Date: Sat, 30 Nov 2019 17:36:09 -0800 Subject: [PATCH 5/5] add getECDSAFromCoinbase for leader reward collect txFee for leader --- core/blockchain.go | 33 +++++++++++++++++++++++++++++++++ core/evm.go | 10 +++++++++- core/state_transition.go | 7 +++++-- internal/chain/reward.go | 3 +-- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index a972af5d6..97a616c1c 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2954,3 +2954,36 @@ func (bc *BlockChain) ValidatorCandidates() []common.Address { func (bc *BlockChain) DelegatorsInformation(addr common.Address) []*staking.Delegation { return make([]*staking.Delegation, 0) } + +// GetECDSAFromCoinbase retrieve corresponding ecdsa address from Coinbase Address +func (bc *BlockChain) GetECDSAFromCoinbase(header *block.Header) (common.Address, error) { + // backward compatibility: before isStaking epoch, coinbase address is the ecdsa address + isStaking := bc.Config().IsStaking(header.Epoch()) + if !isStaking { + return header.Coinbase(), nil + } + + shardState, err := bc.ReadShardState(header.Epoch()) + if err != nil { + return common.Address{}, ctxerror.New("cannot read shard state", + "epoch", header.Epoch(), + "coinbaseAddr", header.Coinbase(), + ).WithCause(err) + } + + committee := shardState.FindCommitteeByID(header.ShardID()) + if committee == nil { + return common.Address{}, ctxerror.New("cannot find shard in the shard state", + "blockNum", header.Number(), + "shardID", header.ShardID(), + "coinbaseAddr", header.Coinbase(), + ) + } + for _, member := range committee.Slots { + // After staking the coinbase address will be the address of bls public key + if utils.GetAddressFromBlsPubKeyBytes(member.BlsPublicKey[:]) == header.Coinbase() { + return member.EcdsaAddress, nil + } + } + return common.Address{}, ctxerror.New("cannot find corresponding ECDSA Address", "coinbaseAddr", header.Coinbase()) +} diff --git a/core/evm.go b/core/evm.go index 0e529ba20..cc85d8aea 100644 --- a/core/evm.go +++ b/core/evm.go @@ -27,6 +27,7 @@ import ( consensus_engine "github.com/harmony-one/harmony/consensus/engine" "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/vm" + "github.com/harmony-one/harmony/internal/utils" ) // ChainContext supports retrieving headers and consensus parameters from the @@ -43,14 +44,21 @@ type ChainContext interface { // ReadValidatorSnapshot returns the snapshot of validator at the beginning of current epoch. ReadValidatorSnapshot(common.Address) (*types2.ValidatorWrapper, error) + + // GetECDSAFromCoinbase retrieves corresponding ECDSA address from the coinbase (BLS Address) + GetECDSAFromCoinbase(*block.Header) (common.Address, error) } // NewEVMContext creates a new context for use in the EVM. func NewEVMContext(msg Message, header *block.Header, chain ChainContext, author *common.Address) vm.Context { // If we don't have an explicit author (i.e. not mining), extract from the header var beneficiary common.Address + var err error if author == nil { - beneficiary, _ = chain.Engine().Author(header) // Ignore error, we're past header validation + beneficiary, err = chain.GetECDSAFromCoinbase(header) // Ignore error, we're past header validation + if err != nil { + utils.Logger().Warn().Msg("oops! We cannot find any beneficiary from header") + } } else { beneficiary = *author } diff --git a/core/state_transition.go b/core/state_transition.go index 3a6e0a142..4a47c9ffb 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -245,8 +245,8 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo } } st.refundGas() - // TODO: need to move the gas fee to the general block rewards - st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) + txFee := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice) + st.state.AddBalance(st.evm.Coinbase, txFee) return ret, st.gasUsed(), vmerr != nil, err } @@ -360,6 +360,9 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { } st.refundGas() + txFee := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice) + st.state.AddBalance(st.evm.Coinbase, txFee) + return st.gasUsed(), err } diff --git a/internal/chain/reward.go b/internal/chain/reward.go index e3685dc13..309506df6 100644 --- a/internal/chain/reward.go +++ b/internal/chain/reward.go @@ -164,8 +164,7 @@ func whatPercentStakedNow( } // AccumulateRewards credits the coinbase of the given block with the mining -// reward. The total reward consists of the static block reward and rewards for -// included uncles. The coinbase of each uncle block is also rewarded. +// reward. The total reward consists of the static block reward func AccumulateRewards( bc engine.ChainReader, state *state.DB, header *block.Header, rewarder reward.Distributor, slasher slash.Slasher,