[EPoS] Checkpoint w/RJ

pull/1827/head
Edgar Aroutiounian 5 years ago
parent 32f4db4c4f
commit 09c427f080
  1. 113
      core/blockchain.go
  2. 2
      internal/configs/sharding/localnet.go
  3. 7
      internal/params/config.go
  4. 10
      numeric/decimal.go
  5. 171
      shard/committee/assignment.go
  6. 91
      staking/effective/calculate.go
  7. 20
      staking/effective/values.go
  8. 5
      staking/types/validator.go

@ -37,13 +37,13 @@ import (
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/block" "github.com/harmony-one/harmony/block"
consensus_engine "github.com/harmony-one/harmony/consensus/engine" consensus_engine "github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/core/rawdb" "github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm" "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/ctxerror"
"github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/internal/utils"
@ -2441,74 +2441,65 @@ func (bc *BlockChain) CurrentValidatorAddresses() []common.Address {
} }
const ( const (
a = "one1x4080kv34a4s3s886vs3rvvhm28pk3r0fnfh6n" blsKey1 = "9a010ea58079ddcd31d5d10ac85e9b5a7732972ee5478d7296091b2af96fcd673c49d6dd7a4a9e7acd2e4aab0add0e00"
b = "one1pjq82a97vfxvmlm60htyc7kafhyjm59f7wgz22" blsKey2 = "9275355e60f8c78337d538eb15e3f8eda120c28635f996d8a13f544d118db32749bcc0ed02a795770141d979a8b9de14"
c = "one1yc06ghr2p8xnl2380kpfayweguuhxdtupkhqzw" // Pick big number for interesting looking one addresses
d = "one1wh4p0kuc7unxez2z8f82zfnhsg4ty6dupqyjt2" amount = 400
e = "one16qsd5ant9v94jrs89mruzx62h7ekcfxmduh2rx" fixedRandomGen = 98765654323
f = "one1spshr72utf6rwxseaz339j09ed8p6f8ke370zj" fixedRandomGenStakeL = 40
g = "one1v788ag7ukh6wjujvpempss6h9ugvfxmnsyklr5" fixedRandomGenStakeH = 150
h = "one1hx3gy5e864eycmjtpcvsuz59m3lu7y89q0ddhl"
i = "one1mmvhupm7ejytype664mhs2m95vn9pfdjrqfadv"
) )
// ValidatorCandidates returns the up to date validator candidates for next epoch var (
func (bc *BlockChain) ValidatorCandidates() []common.Address { // By fixing the source, we have predicable sequence
// TODO Turn this into 400 length generated slice sequenceL = rand.New(rand.NewSource(42))
return []common.Address{ sequenceH = rand.New(rand.NewSource(84))
common2.ParseAddr(a), accountGenerator = rand.New(rand.NewSource(1337))
common2.ParseAddr(b), )
common2.ParseAddr(c),
common2.ParseAddr(d),
common2.ParseAddr(e),
common2.ParseAddr(f),
common2.ParseAddr(g),
common2.ParseAddr(h),
}
}
var ( 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 // ValidatorInformation returns the information of validator
func (bc *BlockChain) ValidatorInformation(addr common.Address) (*staking.Validator, error) { func (bc *BlockChain) ValidatorInformation(addr common.Address) (*staking.Validator, error) {
// EDGAR 0x355E77D991Af6B08C0e7d32111b197da8e1B446f return tempBank[addr], nil
// 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
} }
// DelegatorsInformation returns up to date information of delegators of a given validator address // DelegatorsInformation returns up to date information of delegators of a given validator address

@ -17,7 +17,7 @@ const (
localnetV1Epoch = 1 localnetV1Epoch = 1
localnetV2Epoch = 2 localnetV2Epoch = 2
localnetEpochBlock1 = 10 localnetEpochBlock1 = 5
twoOne = 5 twoOne = 5
localnetVdfDifficulty = 5000 // This takes about 10s to finish the vdf localnetVdfDifficulty = 5000 // This takes about 10s to finish the vdf

@ -36,9 +36,10 @@ var (
ChainID: TestnetChainID, ChainID: TestnetChainID,
CrossTxEpoch: big.NewInt(0), CrossTxEpoch: big.NewInt(0),
CrossLinkEpoch: big.NewInt(0), CrossLinkEpoch: big.NewInt(0),
StakingEpoch: big.NewInt(5), // MinEpoch needed is at least 1, crashes on 0
EIP155Epoch: big.NewInt(0), StakingEpoch: big.NewInt(1),
S3Epoch: big.NewInt(0), EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
} }
// PangaeaChainConfig contains the chain parameters for the Pangaea network. // PangaeaChainConfig contains the chain parameters for the Pangaea network.

@ -483,11 +483,6 @@ func (d Dec) RoundInt64() int64 {
return chopped.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 // similar to chopPrecisionAndRound, but always rounds down
@ -509,11 +504,6 @@ func (d Dec) TruncateInt64() int64 {
return chopped.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 // TruncateDec truncates the decimals from the number and returns a Dec
func (d Dec) TruncateDec() Dec { func (d Dec) TruncateDec() Dec {
return NewDecFromBigInt(chopPrecisionAndTruncateNonMutative(d.Int)) return NewDecFromBigInt(chopPrecisionAndTruncateNonMutative(d.Int))

@ -1,6 +1,7 @@
package committee package committee
import ( import (
"encoding/json"
"fmt" "fmt"
"math/big" "math/big"
@ -16,6 +17,10 @@ import (
staking "github.com/harmony-one/harmony/staking/types" 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 .. // ValidatorListProvider ..
type ValidatorListProvider interface { type ValidatorListProvider interface {
Compute( Compute(
@ -26,7 +31,12 @@ type ValidatorListProvider interface {
// PublicKeysProvider per epoch // PublicKeysProvider per epoch
type PublicKeysProvider interface { 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( ReadPublicKeysFromDB(
hash common.Hash, reader DataProvider, hash common.Hash, reader DataProvider,
) ([]*bls.PublicKey, error) ) ([]*bls.PublicKey, error)
@ -113,13 +123,12 @@ func preStakingEnabledCommittee(s shardingconfig.Instance) shard.State {
return shardState return shardState
} }
func eposStakedCommittee( func with400Stakers(
s shardingconfig.Instance, stakerReader DataProvider, stakedSlotsCount int, s shardingconfig.Instance, stakerReader DataProvider,
) (shard.State, error) { ) (shard.State, error) {
// TODO Nervous about this because overtime the list will become quite large // TODO Nervous about this because overtime the list will become quite large
candidates := stakerReader.ValidatorCandidates() 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 // TODO benchmark difference if went with data structure that sorts on insert
for i := range candidates { for i := range candidates {
// TODO Should be using .ValidatorStakingWithDelegation, not implemented yet // 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()) shardCount := int(s.NumShards())
superComm := make(shard.State, shardCount) superComm := make(shard.State, shardCount)
hAccounts := s.HmyAccounts() fillCount := make([]int, shardCount)
for i := 0; i < shardCount; i++ { for i := 0; i < shardCount; i++ {
superComm[i] = shard.Committee{uint32(i), shard.NodeIDList{}} superComm[i] = shard.Committee{
} uint32(i), make(shard.NodeIDList, s.NumNodesPerShard()),
}
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,
})
} }
staked := effective.Apply(essentials, stakedSlotsCount) shardBig := big.NewInt(int64(s.NumShards()))
shardBig := big.NewInt(int64(shardCount))
if l := len(staked); l < stakedSlotsCount { for i := 0; i < len(s.FnAccounts()); i++ {
// WARN unlikely to happen in production but will happen as we are developing bucket := int(new(big.Int).Mod(stakers[i].Address.Big(), shardBig).Int64())
stakedSlotsCount = l 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++ { hAccounts := s.HmyAccounts()
bucket := int(new(big.Int).Mod(staked[i].BlsPublicKey.Big(), shardBig).Int64()) offset := 0
slot := staked[i]
superComm[bucket].NodeList = append(superComm[bucket].NodeList, shard.NodeID{ for i := range fillCount {
slot.Address, missing := s.NumNodesPerShard() - fillCount[i]
staked[i].BlsPublicKey, for j := 0; j < missing; j++ {
&slot.Dec, 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 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( func (def partialStakingEnabled) ComputePublicKeys(
epoch *big.Int, d DataProvider, epoch *big.Int, d DataProvider, shardID int,
) [][]*bls.PublicKey { ) ([]*bls.PublicKey, []*bls.PublicKey) {
config := d.Config() config := d.Config()
instance := shard.Schedule.InstanceForEpoch(epoch) instance := shard.Schedule.InstanceForEpoch(epoch)
superComm := shard.State{} superComm := shard.State{}
if config.IsStaking(epoch) { if config.IsStaking(epoch) {
superComm, _ = eposStakedCommittee(instance, d, 320) superComm, _ = with400Stakers(instance, d)
} else { } else {
superComm = preStakingEnabledCommittee(instance) 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 { for i := range superComm {
allIdentities[i] = make([]*bls.PublicKey, len(superComm[i].NodeList))
for j := range superComm[i].NodeList { for j := range superComm[i].NodeList {
identity := &bls.PublicKey{} identity := &bls.PublicKey{}
superComm[i].NodeList[j].BlsPublicKey.ToLibBLSPublicKey(identity) 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( func (def partialStakingEnabled) ReadPublicKeysFromDB(

@ -1,105 +1,54 @@
package effective package effective
import ( import (
"encoding/json" "fmt"
"math/big" "math/big"
"sort" "sort"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
) )
// medium.com/harmony-one/introducing-harmonys-effective-proof-of-stake-epos-2d39b4b8d58 // medium.com/harmony-one/introducing-harmonys-effective-proof-of-stake-epos-2d39b4b8d58
var ( var (
two = numeric.NewDecFromBigInt(big.NewInt(2))
c, _ = numeric.NewDecFromStr("0.15") c, _ = numeric.NewDecFromStr("0.15")
onePlusC = numeric.OneDec().Add(c) onePlusC = numeric.OneDec().Add(c)
oneMinusC = numeric.OneDec().Sub(c) oneMinusC = numeric.OneDec().Sub(c)
) )
func effectiveStake(median, actual numeric.Dec) numeric.Dec { // Stake computes the effective proof of stake as descibed in whitepaper
left := numeric.MinDec(onePlusC.Mul(median), actual) func Stake(median, actual *big.Int) numeric.Dec {
right := oneMinusC.Mul(median) medianDec := numeric.NewDecFromBigInt(median)
actualDec := numeric.NewDecFromBigInt(actual)
left := numeric.MinDec(onePlusC.Mul(medianDec), actualDec)
right := oneMinusC.Mul(medianDec)
return numeric.MaxDec(left, right) return numeric.MaxDec(left, right)
} }
// SlotPurchase .. // Median find the median stake
type SlotPurchase struct { func Median(stakes []*big.Int) *big.Int {
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 {
sort.SliceStable( sort.SliceStable(
stakes, 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 const isEven = 0
switch l := len(stakes); l % 2 { switch l := len(stakes); l % 2 {
case isEven: 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: default:
return stakes[l/2].Dec return stakes[l/2]
} }
} }
// Apply .. // Apply ..
func Apply(shortHand map[common.Address]SlotOrder, pull int) Slots { func Apply(stakes []*big.Int) []numeric.Dec {
eposedSlots := Slots{} asNumeric := make([]numeric.Dec, len(stakes))
if len(shortHand) == 0 { median := Median(stakes)
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) },
)
if l := len(eposedSlots); l < pull { fmt.Println("Median is:", median.Uint64())
pull = l
}
picks := eposedSlots[:pull]
median := median(picks)
for i := range picks { for i := range stakes {
picks[i].Dec = effectiveStake(median, picks[i].Dec) asNumeric[i] = Stake(median, stakes[i])
} }
return asNumeric
return picks
} }

@ -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
}
}

@ -198,12 +198,11 @@ func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Valida
commission := Commission{val.CommissionRates, blockNum} commission := Commission{val.CommissionRates, blockNum}
pubKeys := []shard.BlsPublicKey{} pubKeys := []shard.BlsPublicKey{}
pubKeys = append(pubKeys, val.SlotPubKeys...) 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{ v := Validator{
val.ValidatorAddress, pubKeys, val.ValidatorAddress, pubKeys,
val.Amount, new(big.Int), val.MinSelfDelegation, val.MaxTotalDelegation, false, val.Amount, new(big.Int), val.MinSelfDelegation, val.MaxTotalDelegation, false,
commission, desc, blockNum} commission, desc, big.NewInt(0),
}
return &v, nil return &v, nil
} }

Loading…
Cancel
Save