From 09c427f0806fb55e62e9669ef4115aa0459bff0b Mon Sep 17 00:00:00 2001 From: Edgar Aroutiounian Date: Tue, 12 Nov 2019 16:31:46 -0800 Subject: [PATCH] [EPoS] Checkpoint w/RJ --- core/blockchain.go | 113 ++++++++--------- internal/configs/sharding/localnet.go | 2 +- internal/params/config.go | 7 +- numeric/decimal.go | 10 -- shard/committee/assignment.go | 171 +++++++++++++++++++------- staking/effective/calculate.go | 91 +++----------- staking/effective/values.go | 20 +++ staking/types/validator.go | 5 +- 8 files changed, 228 insertions(+), 191 deletions(-) create mode 100644 staking/effective/values.go diff --git a/core/blockchain.go b/core/blockchain.go index 00838bef3..54e27dd79 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -37,13 +37,13 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/harmony-one/bls/ffi/go/bls" "github.com/harmony-one/harmony/block" consensus_engine "github.com/harmony-one/harmony/consensus/engine" "github.com/harmony-one/harmony/core/rawdb" "github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/vm" - common2 "github.com/harmony-one/harmony/internal/common" "github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/internal/utils" @@ -2441,74 +2441,65 @@ func (bc *BlockChain) CurrentValidatorAddresses() []common.Address { } const ( - a = "one1x4080kv34a4s3s886vs3rvvhm28pk3r0fnfh6n" - b = "one1pjq82a97vfxvmlm60htyc7kafhyjm59f7wgz22" - c = "one1yc06ghr2p8xnl2380kpfayweguuhxdtupkhqzw" - d = "one1wh4p0kuc7unxez2z8f82zfnhsg4ty6dupqyjt2" - e = "one16qsd5ant9v94jrs89mruzx62h7ekcfxmduh2rx" - f = "one1spshr72utf6rwxseaz339j09ed8p6f8ke370zj" - g = "one1v788ag7ukh6wjujvpempss6h9ugvfxmnsyklr5" - h = "one1hx3gy5e864eycmjtpcvsuz59m3lu7y89q0ddhl" - i = "one1mmvhupm7ejytype664mhs2m95vn9pfdjrqfadv" + blsKey1 = "9a010ea58079ddcd31d5d10ac85e9b5a7732972ee5478d7296091b2af96fcd673c49d6dd7a4a9e7acd2e4aab0add0e00" + blsKey2 = "9275355e60f8c78337d538eb15e3f8eda120c28635f996d8a13f544d118db32749bcc0ed02a795770141d979a8b9de14" + // Pick big number for interesting looking one addresses + amount = 400 + fixedRandomGen = 98765654323 + fixedRandomGenStakeL = 40 + fixedRandomGenStakeH = 150 ) -// ValidatorCandidates returns the up to date validator candidates for next epoch -func (bc *BlockChain) ValidatorCandidates() []common.Address { - // TODO Turn this into 400 length generated slice - return []common.Address{ - common2.ParseAddr(a), - common2.ParseAddr(b), - common2.ParseAddr(c), - common2.ParseAddr(d), - common2.ParseAddr(e), - common2.ParseAddr(f), - common2.ParseAddr(g), - common2.ParseAddr(h), - } -} +var ( + // By fixing the source, we have predicable sequence + sequenceL = rand.New(rand.NewSource(42)) + sequenceH = rand.New(rand.NewSource(84)) + accountGenerator = rand.New(rand.NewSource(1337)) +) var ( - sequence = rand.New(rand.NewSource(42)) + tempBank map[common.Address]*staking.Validator = map[common.Address]*staking.Validator{} + addrs []common.Address ) +func init() { + addrs = make([]common.Address, amount) + for i := 0; i < amount; i++ { + addr := common.Address{} + addr.SetBytes( + big.NewInt(int64(accountGenerator.Intn(fixedRandomGen))).Bytes(), + ) + addrs[i] = addr + someValidator := &staking.Validator{} + someValidator.Address = addr + low := sequenceL.Intn(fixedRandomGenStakeL) + high := sequenceL.Intn(fixedRandomGenStakeH) + r := sequenceL.Intn(fixedRandomGenStakeH) + 1 + modBy := high - low + 1 + if modBy <= 0 { + modBy *= -1 + modBy++ + } + someValidator.Stake = new(big.Int).Abs(big.NewInt(int64( + (r % modBy) + low, + ))) + k := shard.BlsPublicKey{} + j := bls.PublicKey{} + j.DeserializeHexStr(blsKey1) + k.FromLibBLSPublicKey(&j) + someValidator.SlotPubKeys = []shard.BlsPublicKey{k} + tempBank[addr] = someValidator + } +} + +// ValidatorCandidates returns the up to date validator candidates for next epoch +func (bc *BlockChain) ValidatorCandidates() []common.Address { + return addrs +} + // ValidatorInformation returns the information of validator func (bc *BlockChain) ValidatorInformation(addr common.Address) (*staking.Validator, error) { - // EDGAR 0x355E77D991Af6B08C0e7d32111b197da8e1B446f - // EDGAR 0x0C807574BE624CcdFf7A7dD64c7ADd4dC92dd0a9 - // EDGAR 0x261fa45c6A09cD3Faa277d829e91d9473973357C - // EDGAR 0x75eA17DB98F7266C89423A4EA12677822Ab269Bc - // EDGAR 0xD020dA766b2b0b590E072ec7c11b4AbFb36c24DB - // EDGAR 0x806171f95C5a74371a19e8a312c9e5Cb4E1D24f6 - // EDGAR 0x678E7Ea3DCb5f4e9724C0e761843572f10c49B73 - // EDGAR 0xB9A2825327D5724C6E4b0e190E0a85dc7FCf10e5 - someValidator := &staking.Validator{} - someValidator.Address = addr - someValidator.Stake = big.NewInt(int64(sequence.Intn(100))) - switch B := common2.MustAddressToBech32(addr); true { - case B == a: - fmt.Println("Called here:", B, a) - return someValidator, nil - case B == b: - case B == c: - case B == d: - case B == e: - case B == f: - case B == g: - case B == h: - - } - - fmt.Println("EDGAR", addr.String()) - return nil, nil - // state, err := bc.StateAt(bc.CurrentBlock().Root()) - // if err != nil || state == nil { - // return nil, err - // } - // wrapper := state.GetStakingInfo(addr) - // if wrapper == nil { - // return nil, fmt.Errorf("ValidatorInformation not found: %v", addr) - // } - // return &wrapper.Validator, nil + return tempBank[addr], nil } // DelegatorsInformation returns up to date information of delegators of a given validator address diff --git a/internal/configs/sharding/localnet.go b/internal/configs/sharding/localnet.go index 0bbf18182..0e20ae7b2 100644 --- a/internal/configs/sharding/localnet.go +++ b/internal/configs/sharding/localnet.go @@ -17,7 +17,7 @@ const ( localnetV1Epoch = 1 localnetV2Epoch = 2 - localnetEpochBlock1 = 10 + localnetEpochBlock1 = 5 twoOne = 5 localnetVdfDifficulty = 5000 // This takes about 10s to finish the vdf diff --git a/internal/params/config.go b/internal/params/config.go index c75efc202..7848a9a0c 100644 --- a/internal/params/config.go +++ b/internal/params/config.go @@ -36,9 +36,10 @@ var ( ChainID: TestnetChainID, CrossTxEpoch: big.NewInt(0), CrossLinkEpoch: big.NewInt(0), - StakingEpoch: big.NewInt(5), - EIP155Epoch: big.NewInt(0), - S3Epoch: big.NewInt(0), + // MinEpoch needed is at least 1, crashes on 0 + StakingEpoch: big.NewInt(1), + EIP155Epoch: big.NewInt(0), + S3Epoch: big.NewInt(0), } // PangaeaChainConfig contains the chain parameters for the Pangaea network. diff --git a/numeric/decimal.go b/numeric/decimal.go index d09e3591c..631976d26 100644 --- a/numeric/decimal.go +++ b/numeric/decimal.go @@ -483,11 +483,6 @@ func (d Dec) RoundInt64() int64 { return chopped.Int64() } -// RoundInt round the decimal using bankers rounding -func (d Dec) RoundInt() *big.Int { - return chopPrecisionAndRoundNonMutative(d.Int) -} - //___________________________________________________________________________________ // similar to chopPrecisionAndRound, but always rounds down @@ -509,11 +504,6 @@ func (d Dec) TruncateInt64() int64 { return chopped.Int64() } -// TruncateInt truncates the decimals from the number and returns an Int -func (d Dec) TruncateInt() *big.Int { - return chopPrecisionAndTruncateNonMutative(d.Int) -} - // TruncateDec truncates the decimals from the number and returns a Dec func (d Dec) TruncateDec() Dec { return NewDecFromBigInt(chopPrecisionAndTruncateNonMutative(d.Int)) diff --git a/shard/committee/assignment.go b/shard/committee/assignment.go index 6bc3a78f9..e0c40143c 100644 --- a/shard/committee/assignment.go +++ b/shard/committee/assignment.go @@ -1,6 +1,7 @@ package committee import ( + "encoding/json" "fmt" "math/big" @@ -16,6 +17,10 @@ import ( staking "github.com/harmony-one/harmony/staking/types" ) +// StateID means reading off whole network when using calls that accept +// a shardID parameter +const StateID = -1 + // ValidatorListProvider .. type ValidatorListProvider interface { Compute( @@ -26,7 +31,12 @@ type ValidatorListProvider interface { // PublicKeysProvider per epoch type PublicKeysProvider interface { - ComputePublicKeys(epoch *big.Int, reader DataProvider) [][]*bls.PublicKey + // If call shardID with StateID then only superCommittee is non-nil, + // otherwise get back the shardSpecific slice as well. + ComputePublicKeys( + epoch *big.Int, reader DataProvider, shardID int, + ) (superCommittee, shardSpecific []*bls.PublicKey) + ReadPublicKeysFromDB( hash common.Hash, reader DataProvider, ) ([]*bls.PublicKey, error) @@ -113,13 +123,12 @@ func preStakingEnabledCommittee(s shardingconfig.Instance) shard.State { return shardState } -func eposStakedCommittee( - s shardingconfig.Instance, stakerReader DataProvider, stakedSlotsCount int, +func with400Stakers( + s shardingconfig.Instance, stakerReader DataProvider, ) (shard.State, error) { // TODO Nervous about this because overtime the list will become quite large candidates := stakerReader.ValidatorCandidates() - essentials := map[common.Address]effective.SlotOrder{} - + stakers := make([]*staking.Validator, len(candidates)) // TODO benchmark difference if went with data structure that sorts on insert for i := range candidates { // TODO Should be using .ValidatorStakingWithDelegation, not implemented yet @@ -133,74 +142,152 @@ func eposStakedCommittee( } } + for i := range stakers { + staker := stakers[i] + stakers[i].Stake = new(big.Int).Div( + staker.Stake, big.NewInt(int64(len(staker.SlotPubKeys))), + ) + } + + unsortedStakes := make([]int, len(stakers)) + eposStakes := make([]*big.Int, len(stakers)) + + for i, j := range stakers { + unsortedStakes[i] = int(j.Stake.Int64()) + eposStakes[i] = j.Stake + } + + s3 := effective.Apply(eposStakes) + + sort.SliceStable( + stakers, + func(i, j int) bool { return stakers[i].Stake.Cmp(stakers[j].Stake) >= 0 }, + ) + + // for i, j := range stakers { + // sortedStakes[i] = int(j.Stake.Int64()) + // } + + type t struct { + Stakes []int + } + + t2 := t{make([]int, len(eposStakes))} + for i := range s3 { + t2.Stakes[i] = int(s3[i].TruncateInt64()) + } + + s1, _ := json.Marshal(t{unsortedStakes}) + s2, _ := json.Marshal(t2) + + fmt.Println("Unsorted") + fmt.Println(string(s1)) + fmt.Println("as EPOS") + fmt.Println(string(s2)) + + // fmt.Println("Sorted stakers %+v\n", stakers) + shardCount := int(s.NumShards()) superComm := make(shard.State, shardCount) - hAccounts := s.HmyAccounts() + fillCount := make([]int, shardCount) for i := 0; i < shardCount; i++ { - superComm[i] = shard.Committee{uint32(i), shard.NodeIDList{}} - } - - for i := range hAccounts { - spot := i % shardCount - pub := &bls.PublicKey{} - pub.DeserializeHexStr(hAccounts[i].BlsPublicKey) - pubKey := shard.BlsPublicKey{} - pubKey.FromLibBLSPublicKey(pub) - superComm[spot].NodeList = append(superComm[spot].NodeList, shard.NodeID{ - common2.ParseAddr(hAccounts[i].Address), - pubKey, - nil, - }) + superComm[i] = shard.Committee{ + uint32(i), make(shard.NodeIDList, s.NumNodesPerShard()), + } } - staked := effective.Apply(essentials, stakedSlotsCount) - shardBig := big.NewInt(int64(shardCount)) + shardBig := big.NewInt(int64(s.NumShards())) - if l := len(staked); l < stakedSlotsCount { - // WARN unlikely to happen in production but will happen as we are developing - stakedSlotsCount = l + for i := 0; i < len(s.FnAccounts()); i++ { + bucket := int(new(big.Int).Mod(stakers[i].Address.Big(), shardBig).Int64()) + org := stakers[i].Stake + epos := big.NewInt(s3[i].TruncateInt64()) + fmt.Println("stakes", org, epos) + superComm[bucket].NodeList[fillCount[bucket]] = shard.NodeID{ + stakers[i].Address, + stakers[i].SlotPubKeys[0], + epos, + } + fillCount[bucket]++ } - for i := 0; i < stakedSlotsCount; i++ { - bucket := int(new(big.Int).Mod(staked[i].BlsPublicKey.Big(), shardBig).Int64()) - slot := staked[i] - superComm[bucket].NodeList = append(superComm[bucket].NodeList, shard.NodeID{ - slot.Address, - staked[i].BlsPublicKey, - &slot.Dec, - }) + hAccounts := s.HmyAccounts() + offset := 0 + + for i := range fillCount { + missing := s.NumNodesPerShard() - fillCount[i] + for j := 0; j < missing; j++ { + pub := &bls.PublicKey{} + pub.DeserializeHexStr(hAccounts[offset].BlsPublicKey) + pubKey := shard.BlsPublicKey{} + pubKey.FromLibBLSPublicKey(pub) + superComm[i].NodeList[fillCount[i]+j] = shard.NodeID{ + common2.ParseAddr(hAccounts[offset].Address), + pubKey, + nil, + } + offset++ + } } + fmt.Println("Final", superComm.JSON()) + fmt.Println("stakers", fillCount) return superComm, nil } -// ComputePublicKeys produces publicKeys of entire supercommittee per epoch +// ComputePublicKeys produces publicKeys of entire supercommittee per epoch, optionally providing a +// shard specific subcommittee func (def partialStakingEnabled) ComputePublicKeys( - epoch *big.Int, d DataProvider, -) [][]*bls.PublicKey { - + epoch *big.Int, d DataProvider, shardID int, +) ([]*bls.PublicKey, []*bls.PublicKey) { config := d.Config() instance := shard.Schedule.InstanceForEpoch(epoch) superComm := shard.State{} + if config.IsStaking(epoch) { - superComm, _ = eposStakedCommittee(instance, d, 320) + superComm, _ = with400Stakers(instance, d) } else { superComm = preStakingEnabledCommittee(instance) } - allIdentities := make([][]*bls.PublicKey, len(superComm)) + spot := 0 + shouldBe := int(instance.NumShards()) * instance.NumNodesPerShard() + + total := 0 + for i := range superComm { + total += len(superComm[i].NodeList) + } + + if shouldBe != total { + fmt.Println("Count mismatch", shouldBe, total) + } + allIdentities := make([]*bls.PublicKey, shouldBe) for i := range superComm { - allIdentities[i] = make([]*bls.PublicKey, len(superComm[i].NodeList)) for j := range superComm[i].NodeList { identity := &bls.PublicKey{} superComm[i].NodeList[j].BlsPublicKey.ToLibBLSPublicKey(identity) - allIdentities[i][j] = identity + allIdentities[spot] = identity + spot++ } } - return allIdentities + if shardID == StateID { + return allIdentities, nil + } + + subCommittee := superComm.FindCommitteeByID(uint32(shardID)) + subCommitteeIdentities := make([]*bls.PublicKey, len(subCommittee.NodeList)) + spot = 0 + for i := range subCommittee.NodeList { + identity := &bls.PublicKey{} + subCommittee.NodeList[i].BlsPublicKey.ToLibBLSPublicKey(identity) + subCommitteeIdentities[spot] = identity + spot++ + } + + return allIdentities, subCommitteeIdentities } func (def partialStakingEnabled) ReadPublicKeysFromDB( diff --git a/staking/effective/calculate.go b/staking/effective/calculate.go index 96dca579e..448a492f5 100644 --- a/staking/effective/calculate.go +++ b/staking/effective/calculate.go @@ -1,105 +1,54 @@ package effective import ( - "encoding/json" + "fmt" "math/big" "sort" - "github.com/ethereum/go-ethereum/common" "github.com/harmony-one/harmony/numeric" - "github.com/harmony-one/harmony/shard" ) // medium.com/harmony-one/introducing-harmonys-effective-proof-of-stake-epos-2d39b4b8d58 var ( - two = numeric.NewDecFromBigInt(big.NewInt(2)) c, _ = numeric.NewDecFromStr("0.15") onePlusC = numeric.OneDec().Add(c) oneMinusC = numeric.OneDec().Sub(c) ) -func effectiveStake(median, actual numeric.Dec) numeric.Dec { - left := numeric.MinDec(onePlusC.Mul(median), actual) - right := oneMinusC.Mul(median) +// Stake computes the effective proof of stake as descibed in whitepaper +func Stake(median, actual *big.Int) numeric.Dec { + medianDec := numeric.NewDecFromBigInt(median) + actualDec := numeric.NewDecFromBigInt(actual) + left := numeric.MinDec(onePlusC.Mul(medianDec), actualDec) + right := oneMinusC.Mul(medianDec) return numeric.MaxDec(left, right) } -// SlotPurchase .. -type SlotPurchase struct { - common.Address `json:"slot-owner"` - shard.BlsPublicKey `json:"bls-public-key"` - numeric.Dec `json:"eposed-stake"` -} - -// SlotOrder .. -type SlotOrder struct { - Stake *big.Int - SpreadAmong []shard.BlsPublicKey -} - -// Slots .. -type Slots []SlotPurchase - -// JSON is a plain JSON dump -func (s Slots) JSON() string { - type t struct { - Slots []SlotPurchase `json:"slots"` - } - b, _ := json.Marshal(t{s}) - return string(b) -} - -func median(stakes []SlotPurchase) numeric.Dec { +// Median find the median stake +func Median(stakes []*big.Int) *big.Int { sort.SliceStable( stakes, - func(i, j int) bool { return stakes[i].Dec.LTE(stakes[j].Dec) }, + func(i, j int) bool { return stakes[i].Cmp(stakes[j]) <= 0 }, ) const isEven = 0 switch l := len(stakes); l % 2 { case isEven: - return stakes[(l/2)-1].Dec.Add(stakes[(l/2)+1].Dec).Quo(two) + middle := new(big.Int).Add(stakes[(l/2)-1], stakes[(l/2)+1]) + return new(big.Int).Div(middle, big.NewInt(2)) default: - return stakes[l/2].Dec + return stakes[l/2] } } // Apply .. -func Apply(shortHand map[common.Address]SlotOrder, pull int) Slots { - eposedSlots := Slots{} - if len(shortHand) == 0 { - return eposedSlots - } - // Expand - for staker := range shortHand { - slotsCount := len(shortHand[staker].SpreadAmong) - spread := numeric.NewDecFromBigInt(shortHand[staker].Stake). - QuoInt64(int64(slotsCount)) - for i := 0; i < slotsCount; i++ { - eposedSlots = append(eposedSlots, SlotPurchase{ - staker, - shortHand[staker].SpreadAmong[i], - spread, - }) - } - } - if len(eposedSlots) < len(shortHand) { - // WARN Should never happen - } - - sort.SliceStable( - eposedSlots, - func(i, j int) bool { return eposedSlots[i].Dec.GT(eposedSlots[j].Dec) }, - ) +func Apply(stakes []*big.Int) []numeric.Dec { + asNumeric := make([]numeric.Dec, len(stakes)) + median := Median(stakes) - if l := len(eposedSlots); l < pull { - pull = l - } - picks := eposedSlots[:pull] - median := median(picks) + fmt.Println("Median is:", median.Uint64()) - for i := range picks { - picks[i].Dec = effectiveStake(median, picks[i].Dec) + for i := range stakes { + asNumeric[i] = Stake(median, stakes[i]) } - - return picks + return asNumeric } diff --git a/staking/effective/values.go b/staking/effective/values.go new file mode 100644 index 000000000..6e0840dc4 --- /dev/null +++ b/staking/effective/values.go @@ -0,0 +1,20 @@ +package effective + +import "github.com/harmony-one/harmony/numeric" + +const ( + // ValidatorsPerShard .. + ValidatorsPerShard = 400 + // AllValidatorsCount .. + AllValidatorsCount = ValidatorsPerShard * 4 +) + +// StakeKeeper .. +type StakeKeeper interface { + // Activity + Inventory() struct { + BLSPublicKeys [][48]byte `json:"bls_pubkey"` + WithDelegationApplied []numeric.Dec `json:"with-delegation-applied,omitempty"` + // CurrentlyActive []bool + } +} diff --git a/staking/types/validator.go b/staking/types/validator.go index 9d7d93458..ce387c001 100644 --- a/staking/types/validator.go +++ b/staking/types/validator.go @@ -198,12 +198,11 @@ 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, big.NewInt(0), + } return &v, nil }