You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
4.3 KiB
185 lines
4.3 KiB
package apr
|
|
|
|
import (
|
|
"math/big"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/harmony-one/harmony/block"
|
|
"github.com/harmony-one/harmony/core/state"
|
|
"github.com/harmony-one/harmony/internal/params"
|
|
"github.com/harmony-one/harmony/internal/utils"
|
|
"github.com/harmony-one/harmony/numeric"
|
|
"github.com/harmony-one/harmony/shard"
|
|
staking "github.com/harmony-one/harmony/staking/types"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Reader ..
|
|
type Reader interface {
|
|
Config() *params.ChainConfig
|
|
GetHeaderByHash(hash common.Hash) *block.Header
|
|
CurrentHeader() *block.Header
|
|
ReadValidatorSnapshotAtEpoch(
|
|
epoch *big.Int,
|
|
addr common.Address,
|
|
) (*staking.ValidatorWrapper, error)
|
|
}
|
|
|
|
const (
|
|
secondsInYear = int64(3.154e+7)
|
|
)
|
|
|
|
var (
|
|
oneYear = big.NewInt(int64(secondsInYear))
|
|
)
|
|
|
|
func expectedRewardPerYear(
|
|
oneEpochAgo, twoEpochAgo *block.Header,
|
|
oneSnapshotAgo, twoSnapshotAgo *staking.ValidatorWrapper,
|
|
blocksPerEpoch uint64,
|
|
) (*big.Int, error) {
|
|
oneTAgo, twoTAgo := oneEpochAgo.Time(), twoEpochAgo.Time()
|
|
diffTime, diffReward :=
|
|
new(big.Int).Sub(twoTAgo, oneTAgo),
|
|
new(big.Int).Sub(twoSnapshotAgo.BlockReward, oneSnapshotAgo.BlockReward)
|
|
|
|
// impossibility but keep sane
|
|
if diffTime.Sign() == -1 {
|
|
return nil, errors.New("time stamp diff cannot be negative")
|
|
}
|
|
if diffTime.Cmp(common.Big0) == 0 {
|
|
return nil, errors.New("cannot div by zero of diff in time")
|
|
}
|
|
|
|
// TODO some more sanity checks of some sort?
|
|
expectedValue := new(big.Int).Div(diffReward, diffTime)
|
|
return new(big.Int).Mul(expectedValue, oneYear), nil
|
|
}
|
|
|
|
func pastTwoEpochHeaders(
|
|
bc Reader,
|
|
) (*block.Header, *block.Header, error) {
|
|
current := bc.CurrentHeader()
|
|
epochNow := current.Epoch()
|
|
oneEpochAgo, twoEpochAgo :=
|
|
new(big.Int).Sub(epochNow, common.Big1),
|
|
new(big.Int).Sub(epochNow, common.Big2)
|
|
|
|
bottomOut := new(big.Int).Add(
|
|
bc.Config().StakingEpoch,
|
|
common.Big3,
|
|
)
|
|
|
|
var oneAgoHeader, twoAgoHeader **block.Header
|
|
|
|
for e1, e2 := false, false; ; {
|
|
current = bc.GetHeaderByHash(current.ParentHash())
|
|
|
|
if current == nil {
|
|
return nil, nil, errors.New("could not go up parent")
|
|
}
|
|
|
|
if current.Epoch().Cmp(bottomOut) == 0 {
|
|
if twoAgoHeader == nil || oneAgoHeader == nil {
|
|
return nil, nil, errors.New(
|
|
"could not find headers for apr computation",
|
|
)
|
|
}
|
|
}
|
|
|
|
switch {
|
|
// haven't found either epoch yet
|
|
case !e1 && !e2:
|
|
if current.Epoch().Cmp(oneEpochAgo) == 0 {
|
|
e1 = true
|
|
oneAgoHeader = ¤t
|
|
continue
|
|
}
|
|
case e1 && !e2:
|
|
if current.Epoch().Cmp(twoEpochAgo) == 0 {
|
|
e2 = true
|
|
twoAgoHeader = ¤t
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return *oneAgoHeader, *twoAgoHeader, nil
|
|
}
|
|
|
|
// ComputeForValidator ..
|
|
func ComputeForValidator(
|
|
bc Reader,
|
|
now *big.Int,
|
|
state *state.DB,
|
|
validatorNow *staking.ValidatorWrapper,
|
|
blocksPerEpoch uint64,
|
|
) (*numeric.Dec, error) {
|
|
twoEpochAgo, oneEpochAgo, zero :=
|
|
new(big.Int).Sub(now, common.Big2),
|
|
new(big.Int).Sub(now, common.Big1),
|
|
numeric.ZeroDec()
|
|
|
|
twoSnapshotAgo, err := bc.ReadValidatorSnapshotAtEpoch(
|
|
twoEpochAgo,
|
|
validatorNow.Address,
|
|
)
|
|
|
|
if err != nil {
|
|
return &zero, nil
|
|
}
|
|
|
|
oneSnapshotAgo, err := bc.ReadValidatorSnapshotAtEpoch(
|
|
oneEpochAgo,
|
|
validatorNow.Address,
|
|
)
|
|
|
|
if err != nil {
|
|
return &zero, nil
|
|
}
|
|
|
|
blockNumAtTwoEpochAgo, blockNumAtOneEpochAgo :=
|
|
shard.Schedule.EpochLastBlock(twoEpochAgo.Uint64()),
|
|
shard.Schedule.EpochLastBlock(oneEpochAgo.Uint64())
|
|
|
|
headerOneEpochAgo, headerTwoEpochAgo, err := pastTwoEpochHeaders(bc)
|
|
|
|
if err != nil {
|
|
return &zero, nil
|
|
}
|
|
|
|
// TODO Figure out why this is happening
|
|
if headerOneEpochAgo == nil || headerTwoEpochAgo == nil || err != nil {
|
|
utils.Logger().Debug().
|
|
Msgf("apr compute headers epochs ago %+v %+v %+v %+v %+v %+v",
|
|
twoEpochAgo, oneEpochAgo,
|
|
blockNumAtTwoEpochAgo, blockNumAtOneEpochAgo,
|
|
headerOneEpochAgo, headerTwoEpochAgo,
|
|
)
|
|
return &zero, nil
|
|
}
|
|
|
|
estimatedRewardPerYear, err := expectedRewardPerYear(
|
|
headerOneEpochAgo, headerTwoEpochAgo,
|
|
oneSnapshotAgo, twoSnapshotAgo,
|
|
blocksPerEpoch,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if estimatedRewardPerYear.Cmp(common.Big0) == 0 {
|
|
return &zero, nil
|
|
}
|
|
|
|
total := numeric.NewDecFromBigInt(validatorNow.TotalDelegation())
|
|
if total.IsZero() {
|
|
return nil, errors.New("zero total delegation will cause div by zero")
|
|
}
|
|
|
|
result := numeric.NewDecFromBigInt(estimatedRewardPerYear).Quo(
|
|
total,
|
|
)
|
|
return &result, nil
|
|
}
|
|
|