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.
439 lines
14 KiB
439 lines
14 KiB
package core
|
|
|
|
import (
|
|
"bytes"
|
|
"math/big"
|
|
|
|
"github.com/harmony-one/harmony/staking/availability"
|
|
|
|
"github.com/harmony-one/harmony/internal/params"
|
|
|
|
"github.com/harmony-one/harmony/crypto/bls"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/harmony-one/harmony/common/denominations"
|
|
"github.com/harmony-one/harmony/core/vm"
|
|
common2 "github.com/harmony-one/harmony/internal/common"
|
|
"github.com/harmony-one/harmony/internal/utils"
|
|
"github.com/harmony-one/harmony/staking/effective"
|
|
staking "github.com/harmony-one/harmony/staking/types"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
errStateDBIsMissing = errors.New("no stateDB was provided")
|
|
errChainContextMissing = errors.New("no chain context was provided")
|
|
errEpochMissing = errors.New("no epoch was provided")
|
|
errBlockNumMissing = errors.New("no block number was provided")
|
|
)
|
|
|
|
func checkDuplicateFields(
|
|
bc ChainContext, state vm.StateDB,
|
|
validator common.Address, identity string, blsKeys []bls.SerializedPublicKey,
|
|
) error {
|
|
addrs, err := bc.ReadValidatorList()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
checkIdentity := identity != ""
|
|
checkBlsKeys := len(blsKeys) != 0
|
|
|
|
blsKeyMap := map[bls.SerializedPublicKey]struct{}{}
|
|
for _, key := range blsKeys {
|
|
blsKeyMap[key] = struct{}{}
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
if !bytes.Equal(validator.Bytes(), addr.Bytes()) {
|
|
wrapper, err := state.ValidatorWrapper(addr, true, false)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if checkIdentity && wrapper.Identity == identity {
|
|
return errors.Wrapf(errDupIdentity, "duplicate identity %s", identity)
|
|
}
|
|
if checkBlsKeys {
|
|
for _, existingKey := range wrapper.SlotPubKeys {
|
|
if _, ok := blsKeyMap[existingKey]; ok {
|
|
return errors.Wrapf(errDupBlsKey, "duplicate bls key %x", existingKey)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TODO: add unit tests to check staking msg verification
|
|
|
|
// VerifyAndCreateValidatorFromMsg verifies the create validator message using
|
|
// the stateDB, epoch, & blocknumber and returns the validatorWrapper created
|
|
// in the process.
|
|
//
|
|
// Note that this function never updates the stateDB, it only reads from stateDB.
|
|
func VerifyAndCreateValidatorFromMsg(
|
|
stateDB vm.StateDB, chainContext ChainContext, epoch *big.Int, blockNum *big.Int, msg *staking.CreateValidator,
|
|
) (*staking.ValidatorWrapper, error) {
|
|
if stateDB == nil {
|
|
return nil, errStateDBIsMissing
|
|
}
|
|
if chainContext == nil {
|
|
return nil, errChainContextMissing
|
|
}
|
|
if epoch == nil {
|
|
return nil, errEpochMissing
|
|
}
|
|
if blockNum == nil {
|
|
return nil, errBlockNumMissing
|
|
}
|
|
if msg.Amount.Sign() == -1 {
|
|
return nil, errNegativeAmount
|
|
}
|
|
if stateDB.IsValidator(msg.ValidatorAddress) {
|
|
return nil, errors.Wrapf(
|
|
errValidatorExist, common2.MustAddressToBech32(msg.ValidatorAddress),
|
|
)
|
|
}
|
|
if err := checkDuplicateFields(
|
|
chainContext, stateDB,
|
|
msg.ValidatorAddress,
|
|
msg.Identity,
|
|
msg.SlotPubKeys); err != nil {
|
|
return nil, err
|
|
}
|
|
if !CanTransfer(stateDB, msg.ValidatorAddress, msg.Amount) {
|
|
return nil, errInsufficientBalanceForStake
|
|
}
|
|
v, err := staking.CreateValidatorFromNewMsg(msg, blockNum, epoch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
wrapper := &staking.ValidatorWrapper{}
|
|
wrapper.Validator = *v
|
|
wrapper.Delegations = []staking.Delegation{
|
|
staking.NewDelegation(v.Address, msg.Amount),
|
|
}
|
|
wrapper.Counters.NumBlocksSigned = big.NewInt(0)
|
|
wrapper.Counters.NumBlocksToSign = big.NewInt(0)
|
|
wrapper.BlockReward = big.NewInt(0)
|
|
if err := wrapper.SanityCheck(); err != nil {
|
|
return nil, err
|
|
}
|
|
return wrapper, nil
|
|
}
|
|
|
|
// VerifyAndEditValidatorFromMsg verifies the edit validator message using
|
|
// the stateDB, chainContext and returns the edited validatorWrapper.
|
|
//
|
|
// Note that this function never updates the stateDB, it only reads from stateDB.
|
|
func VerifyAndEditValidatorFromMsg(
|
|
stateDB vm.StateDB, chainContext ChainContext,
|
|
epoch, blockNum *big.Int, msg *staking.EditValidator,
|
|
) (*staking.ValidatorWrapper, error) {
|
|
if stateDB == nil {
|
|
return nil, errStateDBIsMissing
|
|
}
|
|
if chainContext == nil {
|
|
return nil, errChainContextMissing
|
|
}
|
|
if blockNum == nil {
|
|
return nil, errBlockNumMissing
|
|
}
|
|
if !stateDB.IsValidator(msg.ValidatorAddress) {
|
|
return nil, errValidatorNotExist
|
|
}
|
|
newBlsKeys := []bls.SerializedPublicKey{}
|
|
if msg.SlotKeyToAdd != nil {
|
|
newBlsKeys = append(newBlsKeys, *msg.SlotKeyToAdd)
|
|
}
|
|
if err := checkDuplicateFields(
|
|
chainContext, stateDB,
|
|
msg.ValidatorAddress,
|
|
msg.Identity,
|
|
newBlsKeys); err != nil {
|
|
return nil, err
|
|
}
|
|
// request a copy, but delegations are not being changed so do not deep copy them
|
|
wrapper, err := stateDB.ValidatorWrapper(msg.ValidatorAddress, false, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := staking.UpdateValidatorFromEditMsg(&wrapper.Validator, msg, epoch); err != nil {
|
|
return nil, err
|
|
}
|
|
newRate := wrapper.Validator.Rate
|
|
if newRate.GT(wrapper.Validator.MaxRate) {
|
|
return nil, errCommissionRateChangeTooHigh
|
|
}
|
|
|
|
if chainContext.Config().IsMinCommissionRate(epoch) && newRate.LT(availability.MinCommissionRate) {
|
|
firstEpoch := stateDB.GetValidatorFirstElectionEpoch(msg.ValidatorAddress)
|
|
promoPeriod := chainContext.Config().MinCommissionPromoPeriod.Int64()
|
|
if firstEpoch.Uint64() != 0 && big.NewInt(0).Sub(epoch, firstEpoch).Int64() >= promoPeriod {
|
|
return nil, errCommissionRateChangeTooLow
|
|
}
|
|
}
|
|
|
|
snapshotValidator, err := chainContext.ReadValidatorSnapshot(wrapper.Address)
|
|
if err != nil {
|
|
return nil, errors.WithMessage(err, "validator snapshot not found.")
|
|
}
|
|
rateAtBeginningOfEpoch := snapshotValidator.Validator.Rate
|
|
|
|
if rateAtBeginningOfEpoch.IsNil() ||
|
|
(!newRate.IsNil() && !rateAtBeginningOfEpoch.Equal(newRate)) {
|
|
wrapper.Validator.UpdateHeight = blockNum
|
|
}
|
|
|
|
if newRate.Sub(rateAtBeginningOfEpoch).Abs().GT(
|
|
wrapper.Validator.MaxChangeRate,
|
|
) {
|
|
return nil, errCommissionRateChangeTooFast
|
|
}
|
|
if err := wrapper.SanityCheck(); err != nil {
|
|
return nil, err
|
|
}
|
|
return wrapper, nil
|
|
}
|
|
|
|
const oneThousand = 1000
|
|
const oneHundred = 100
|
|
|
|
var (
|
|
oneAsBigInt = big.NewInt(denominations.One)
|
|
minimumDelegation = new(big.Int).Mul(oneAsBigInt, big.NewInt(oneThousand))
|
|
minimumDelegationV2 = new(big.Int).Mul(oneAsBigInt, big.NewInt(oneHundred))
|
|
errDelegationTooSmall = errors.New("minimum delegation amount for a delegator has to be greater than or equal to 1000 ONE")
|
|
errDelegationTooSmallV2 = errors.New("minimum delegation amount for a delegator has to be greater than or equal to 100 ONE")
|
|
)
|
|
|
|
// VerifyAndDelegateFromMsg verifies the delegate message using the stateDB
|
|
// and returns the balance to be deducted by the delegator as well as the
|
|
// validatorWrapper with the delegation applied to it.
|
|
//
|
|
// Note that this function never updates the stateDB, it only reads from stateDB.
|
|
func VerifyAndDelegateFromMsg(
|
|
stateDB vm.StateDB, epoch *big.Int, msg *staking.Delegate, delegations []staking.DelegationIndex, chainConfig *params.ChainConfig,
|
|
) ([]*staking.ValidatorWrapper, *big.Int, map[common.Address]*big.Int, error) {
|
|
if stateDB == nil {
|
|
return nil, nil, nil, errStateDBIsMissing
|
|
}
|
|
if !stateDB.IsValidator(msg.ValidatorAddress) {
|
|
return nil, nil, nil, errValidatorNotExist
|
|
}
|
|
if msg.Amount.Sign() == -1 {
|
|
return nil, nil, nil, errNegativeAmount
|
|
}
|
|
if msg.Amount.Cmp(minimumDelegation) < 0 {
|
|
if chainConfig.IsMinDelegation100(epoch) {
|
|
if msg.Amount.Cmp(minimumDelegationV2) < 0 {
|
|
return nil, nil, nil, errDelegationTooSmallV2
|
|
}
|
|
} else {
|
|
return nil, nil, nil, errDelegationTooSmall
|
|
}
|
|
}
|
|
|
|
updatedValidatorWrappers := []*staking.ValidatorWrapper{}
|
|
delegateBalance := big.NewInt(0).Set(msg.Amount)
|
|
fromLockedTokens := map[common.Address]*big.Int{}
|
|
|
|
var delegateeWrapper *staking.ValidatorWrapper
|
|
if chainConfig.IsRedelegation(epoch) {
|
|
// Check if we can use tokens in undelegation to delegate (redelegate)
|
|
for i := range delegations {
|
|
delegationIndex := &delegations[i]
|
|
// request a copy, and since delegations will be changed, copy them too
|
|
wrapper, err := stateDB.ValidatorWrapper(delegationIndex.ValidatorAddress, false, true)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if uint64(len(wrapper.Delegations)) <= delegationIndex.Index {
|
|
utils.Logger().Warn().
|
|
Str("validator", delegationIndex.ValidatorAddress.String()).
|
|
Uint64("delegation index", delegationIndex.Index).
|
|
Int("delegations length", len(wrapper.Delegations)).
|
|
Msg("Delegation index out of bound")
|
|
return nil, nil, nil, errors.New("Delegation index out of bound")
|
|
}
|
|
|
|
delegation := &wrapper.Delegations[delegationIndex.Index]
|
|
|
|
startBalance := big.NewInt(0).Set(delegateBalance)
|
|
// Start from the oldest undelegated tokens
|
|
curIndex := 0
|
|
for ; curIndex < len(delegation.Undelegations); curIndex++ {
|
|
if delegation.Undelegations[curIndex].Epoch.Cmp(epoch) >= 0 {
|
|
break
|
|
}
|
|
if delegation.Undelegations[curIndex].Amount.Cmp(delegateBalance) <= 0 {
|
|
delegateBalance.Sub(delegateBalance, delegation.Undelegations[curIndex].Amount)
|
|
} else {
|
|
delegation.Undelegations[curIndex].Amount.Sub(
|
|
delegation.Undelegations[curIndex].Amount, delegateBalance,
|
|
)
|
|
delegateBalance = big.NewInt(0)
|
|
break
|
|
}
|
|
}
|
|
|
|
if startBalance.Cmp(delegateBalance) > 0 {
|
|
// Used undelegated token for redelegation
|
|
delegation.Undelegations = delegation.Undelegations[curIndex:]
|
|
if err := wrapper.SanityCheck(); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
if bytes.Equal(delegationIndex.ValidatorAddress[:], msg.ValidatorAddress[:]) {
|
|
delegateeWrapper = wrapper
|
|
}
|
|
updatedValidatorWrappers = append(updatedValidatorWrappers, wrapper)
|
|
fromLockedTokens[delegationIndex.ValidatorAddress] = big.NewInt(0).Sub(startBalance, delegateBalance)
|
|
}
|
|
}
|
|
}
|
|
|
|
if delegateeWrapper == nil {
|
|
var err error
|
|
// request a copy, and since delegations will be changed, copy them too
|
|
delegateeWrapper, err = stateDB.ValidatorWrapper(msg.ValidatorAddress, false, true)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
updatedValidatorWrappers = append(updatedValidatorWrappers, delegateeWrapper)
|
|
}
|
|
|
|
// Add to existing delegation if any
|
|
found := false
|
|
for i := range delegateeWrapper.Delegations {
|
|
delegation := &delegateeWrapper.Delegations[i]
|
|
if bytes.Equal(delegation.DelegatorAddress.Bytes(), msg.DelegatorAddress.Bytes()) {
|
|
delegation.Amount.Add(delegation.Amount, msg.Amount)
|
|
if err := delegateeWrapper.SanityCheck(); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
// Add new delegation
|
|
delegateeWrapper.Delegations = append(
|
|
delegateeWrapper.Delegations, staking.NewDelegation(
|
|
msg.DelegatorAddress, msg.Amount,
|
|
),
|
|
)
|
|
if err := delegateeWrapper.SanityCheck(); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
}
|
|
|
|
if delegateBalance.Cmp(big.NewInt(0)) == 0 {
|
|
// delegation fully from undelegated tokens, no need to deduct from balance.
|
|
return updatedValidatorWrappers, big.NewInt(0), fromLockedTokens, nil
|
|
}
|
|
|
|
// Still need to deduct tokens from balance for delegation
|
|
// Check if there is enough liquid token to delegate
|
|
if !CanTransfer(stateDB, msg.DelegatorAddress, delegateBalance) {
|
|
return nil, nil, nil, errors.Wrapf(
|
|
errInsufficientBalanceForStake, "totalRedelegatable: %v, balance: %v; trying to stake %v",
|
|
big.NewInt(0).Sub(msg.Amount, delegateBalance), stateDB.GetBalance(msg.DelegatorAddress), msg.Amount)
|
|
}
|
|
|
|
return updatedValidatorWrappers, delegateBalance, fromLockedTokens, nil
|
|
}
|
|
|
|
// VerifyAndUndelegateFromMsg verifies the undelegate validator message
|
|
// using the stateDB & chainContext and returns the edited validatorWrapper
|
|
// with the undelegation applied to it.
|
|
//
|
|
// Note that this function never updates the stateDB, it only reads from stateDB.
|
|
func VerifyAndUndelegateFromMsg(
|
|
stateDB vm.StateDB, epoch *big.Int, msg *staking.Undelegate,
|
|
) (*staking.ValidatorWrapper, error) {
|
|
if stateDB == nil {
|
|
return nil, errStateDBIsMissing
|
|
}
|
|
if epoch == nil {
|
|
return nil, errEpochMissing
|
|
}
|
|
|
|
if msg.Amount.Sign() == -1 {
|
|
return nil, errNegativeAmount
|
|
}
|
|
|
|
if !stateDB.IsValidator(msg.ValidatorAddress) {
|
|
return nil, errValidatorNotExist
|
|
}
|
|
|
|
wrapper, err := stateDB.ValidatorWrapper(msg.ValidatorAddress, false, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i := range wrapper.Delegations {
|
|
delegation := &wrapper.Delegations[i]
|
|
if bytes.Equal(delegation.DelegatorAddress.Bytes(), msg.DelegatorAddress.Bytes()) {
|
|
if err := delegation.Undelegate(epoch, msg.Amount); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := wrapper.SanityCheck(); err != nil {
|
|
// allow self delegation to go below min self delegation
|
|
// but set the status to inactive
|
|
if errors.Cause(err) == staking.ErrInvalidSelfDelegation {
|
|
wrapper.Status = effective.Inactive
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
return wrapper, nil
|
|
}
|
|
}
|
|
return nil, errNoDelegationToUndelegate
|
|
}
|
|
|
|
// VerifyAndCollectRewardsFromDelegation verifies and collects rewards
|
|
// from the given delegation slice using the stateDB. It returns all of the
|
|
// edited validatorWrappers and the sum total of the rewards.
|
|
//
|
|
// Note that this function never updates the stateDB, it only reads from stateDB.
|
|
func VerifyAndCollectRewardsFromDelegation(
|
|
stateDB vm.StateDB, delegations []staking.DelegationIndex,
|
|
) ([]*staking.ValidatorWrapper, *big.Int, error) {
|
|
if stateDB == nil {
|
|
return nil, nil, errStateDBIsMissing
|
|
}
|
|
updatedValidatorWrappers := []*staking.ValidatorWrapper{}
|
|
totalRewards := big.NewInt(0)
|
|
for i := range delegations {
|
|
delegation := &delegations[i]
|
|
// request a copy, and since delegations will be changed (.Reward.Set), copy them too
|
|
wrapper, err := stateDB.ValidatorWrapper(delegation.ValidatorAddress, false, true)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if uint64(len(wrapper.Delegations)) > delegation.Index {
|
|
delegation := &wrapper.Delegations[delegation.Index]
|
|
if delegation.Reward.Cmp(common.Big0) > 0 {
|
|
totalRewards.Add(totalRewards, delegation.Reward)
|
|
delegation.Reward.SetUint64(0)
|
|
}
|
|
} else {
|
|
utils.Logger().Warn().
|
|
Str("validator", delegation.ValidatorAddress.String()).
|
|
Uint64("delegation index", delegation.Index).
|
|
Int("delegations length", len(wrapper.Delegations)).
|
|
Msg("Delegation index out of bound")
|
|
return nil, nil, errors.New("Delegation index out of bound")
|
|
}
|
|
updatedValidatorWrappers = append(updatedValidatorWrappers, wrapper)
|
|
}
|
|
if totalRewards.Int64() == 0 {
|
|
return nil, nil, errNoRewardsToCollect
|
|
}
|
|
return updatedValidatorWrappers, totalRewards, nil
|
|
}
|
|
|