From df1ec4985965c58a6307c957fee288d92b054f1b Mon Sep 17 00:00:00 2001 From: Edgar Aroutiounian Date: Wed, 11 Dec 2019 16:31:09 -0800 Subject: [PATCH] [staking] Richer errors (#2012) * [state] Add more information to insufficient balance for stake errors * [staking] Wrap Sanity check errors with more information * [errors] Add gas information to insufficient balance in gas to pay fee error message * [errors] Fix mistaken use of Wrapf --- core/state_transition.go | 34 ++++++++++------ staking/types/validator.go | 80 ++++++++++++++++++++++++++++---------- 2 files changed, 83 insertions(+), 31 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 569cc7fb1..2c4641539 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -18,7 +18,6 @@ package core import ( "bytes" - "errors" "math" "math/big" @@ -26,9 +25,11 @@ import ( "github.com/ethereum/go-ethereum/rlp" "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/params" "github.com/harmony-one/harmony/internal/utils" staking "github.com/harmony-one/harmony/staking/types" + "github.com/pkg/errors" ) var ( @@ -173,8 +174,11 @@ func (st *StateTransition) useGas(amount uint64) error { func (st *StateTransition) buyGas() error { mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) - if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { - return errInsufficientBalanceForGas + if have := st.state.GetBalance(st.msg.From()); have.Cmp(mgval) < 0 { + return errors.Wrapf( + errInsufficientBalanceForGas, + "had: %s but need: %s", have.String(), mgval.String(), + ) } if err := st.gp.SubGas(st.msg.Gas()); err != nil { return err @@ -372,8 +376,8 @@ func (st *StateTransition) applyCreateValidatorTx(createValidator *staking.Creat return errNegativeAmount } - if st.state.IsValidator(createValidator.ValidatorAddress) { - return errValidatorExist + if val := createValidator.ValidatorAddress; st.state.IsValidator(val) { + return errors.Wrapf(errValidatorExist, common2.MustAddressToBech32(val)) } v, err := staking.CreateValidatorFromNewMsg(createValidator, blockNum) @@ -381,14 +385,15 @@ func (st *StateTransition) applyCreateValidatorTx(createValidator *staking.Creat return err } - delegations := []staking.Delegation{} - delegations = append(delegations, staking.NewDelegation(v.Address, createValidator.Amount)) - + delegations := []staking.Delegation{ + staking.NewDelegation(v.Address, createValidator.Amount), + } wrapper := staking.ValidatorWrapper{*v, delegations} if err := st.state.UpdateStakingInfo(v.Address, &wrapper); err != nil { return err } + st.state.SetValidatorFlag(v.Address) return nil } @@ -453,8 +458,9 @@ func (st *StateTransition) applyDelegateTx(delegate *staking.Delegate) error { if bytes.Equal(delegation.DelegatorAddress.Bytes(), delegate.DelegatorAddress.Bytes()) { delegatorExist = true totalInUndelegation := delegation.TotalInUndelegation() + balance := stateDB.GetBalance(delegate.DelegatorAddress) // If the sum of normal balance and the total amount of tokens in undelegation is greater than the amount to delegate - if big.NewInt(0).Add(totalInUndelegation, stateDB.GetBalance(delegate.DelegatorAddress)).Cmp(delegate.Amount) >= 0 { + if big.NewInt(0).Add(totalInUndelegation, balance).Cmp(delegate.Amount) >= 0 { // Firstly use the tokens in undelegation to delegate (redelegate) delegateBalance := big.NewInt(0).Set(delegate.Amount) // Use the latest undelegated token first as it has the longest remaining locking time. @@ -468,8 +474,8 @@ func (st *StateTransition) applyDelegateTx(delegate *staking.Delegate) error { break } } - delegation.Undelegations = delegation.Undelegations[:i+1] + delegation.Undelegations = delegation.Undelegations[:i+1] delegation.Amount.Add(delegation.Amount, delegate.Amount) err := stateDB.UpdateStakingInfo(wrapper.Validator.Address, wrapper) @@ -479,7 +485,13 @@ func (st *StateTransition) applyDelegateTx(delegate *staking.Delegate) error { } return err } - return errInsufficientBalanceForStake + return errors.Wrapf( + errInsufficientBalanceForStake, + "total-delegated %s own-current-balance %s amount-to-delegate %s", + totalInUndelegation.String(), + balance.String(), + delegate.Amount.String(), + ) } } diff --git a/staking/types/validator.go b/staking/types/validator.go index 86984763f..5d4b27888 100644 --- a/staking/types/validator.go +++ b/staking/types/validator.go @@ -1,20 +1,19 @@ package types import ( - "errors" "fmt" "math/big" - "github.com/harmony-one/bls/ffi/go/bls" - "github.com/harmony-one/harmony/common/denominations" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" + "github.com/harmony-one/bls/ffi/go/bls" + "github.com/harmony-one/harmony/common/denominations" "github.com/harmony-one/harmony/crypto/hash" common2 "github.com/harmony-one/harmony/internal/common" "github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/shard" + "github.com/pkg/errors" ) // Define validator staking related const @@ -37,6 +36,7 @@ var ( errInvalidComissionRate = errors.New("commission rate, change rate and max rate should be within 0-100 percent") errNeedAtLeastOneSlotKey = errors.New("need at least one slot key") errBLSKeysNotMatchSigs = errors.New("bls keys and corresponding signatures could not be verified") + errNilMinSelfDelegation = errors.New("nil min self delegation") ) // ValidatorWrapper contains validator and its delegation information @@ -113,55 +113,96 @@ func (w *ValidatorWrapper) TotalDelegation() *big.Int { return total } +var ( + hundredPercent = numeric.NewDec(1) + zeroPercent = numeric.NewDec(0) +) + // SanityCheck checks the basic requirements func (w *ValidatorWrapper) SanityCheck() error { if len(w.SlotPubKeys) == 0 { return errNeedAtLeastOneSlotKey } + if w.Validator.MinSelfDelegation == nil { + return errNilMinSelfDelegation + } + // MinSelfDelegation must be >= 1 ONE - if w.Validator.MinSelfDelegation == nil || w.Validator.MinSelfDelegation.Cmp(big.NewInt(denominations.One)) < 0 { - return errMinSelfDelegationTooSmall + if w.Validator.MinSelfDelegation.Cmp(big.NewInt(denominations.One)) < 0 { + return errors.Wrapf( + errMinSelfDelegationTooSmall, + "delegation-given %s", w.Validator.MinSelfDelegation.String(), + ) } // Self delegation must be >= MinSelfDelegation - if len(w.Delegations) == 0 || w.Delegations[0].Amount.Cmp(w.Validator.MinSelfDelegation) < 0 { - return errInvalidSelfDelegation + switch len(w.Delegations) { + case 0: + return errors.Wrapf( + errInvalidSelfDelegation, "no self delegation given at all", + ) + default: + if w.Delegations[0].Amount.Cmp(w.Validator.MinSelfDelegation) < 0 { + return errors.Wrapf( + errInvalidSelfDelegation, + "have %s want %s", w.Delegations[0].Amount.String(), w.Validator.MinSelfDelegation, + ) + } } // Only enforce rules on MaxTotalDelegation is it's > 0; 0 means no limit for max total delegation if w.Validator.MaxTotalDelegation != nil && w.Validator.MaxTotalDelegation.Cmp(big.NewInt(0)) > 0 { // MaxTotalDelegation must not be less than MinSelfDelegation if w.Validator.MaxTotalDelegation.Cmp(w.Validator.MinSelfDelegation) < 0 { - return errInvalidMaxTotalDelegation + return errors.Wrapf( + errInvalidMaxTotalDelegation, + "max-total-delegation %s min-self-delegation %s", + w.Validator.MaxTotalDelegation.String(), + w.Validator.MinSelfDelegation.String(), + ) } totalDelegation := w.TotalDelegation() // Total delegation must be <= MaxTotalDelegation if totalDelegation.Cmp(w.Validator.MaxTotalDelegation) > 0 { - return errInvalidTotalDelegation + return errors.Wrapf( + errInvalidTotalDelegation, + "total %s max-total %s", + totalDelegation.String(), + w.Validator.MaxTotalDelegation.String(), + ) } } - hundredPercent := numeric.NewDec(1) - zeroPercent := numeric.NewDec(0) - if w.Validator.Rate.LT(zeroPercent) || w.Validator.Rate.GT(hundredPercent) { - return errInvalidComissionRate + return errors.Wrapf( + errInvalidComissionRate, "rate:%s", w.Validator.Rate.String(), + ) } + if w.Validator.MaxRate.LT(zeroPercent) || w.Validator.MaxRate.GT(hundredPercent) { - return errInvalidComissionRate + return errors.Wrapf( + errInvalidComissionRate, "rate:%s", w.Validator.MaxRate.String(), + ) } + if w.Validator.MaxChangeRate.LT(zeroPercent) || w.Validator.MaxChangeRate.GT(hundredPercent) { - return errInvalidComissionRate + return errors.Wrapf( + errInvalidComissionRate, "rate:%s", w.Validator.MaxChangeRate.String(), + ) } if w.Validator.Rate.GT(w.Validator.MaxRate) { - return errCommissionRateTooLarge + return errors.Wrapf( + errCommissionRateTooLarge, "rate:%s", w.Validator.MaxRate.String(), + ) } if w.Validator.MaxChangeRate.GT(w.Validator.MaxRate) { - return errCommissionRateTooLarge + return errors.Wrapf( + errCommissionRateTooLarge, "rate:%s", w.Validator.MaxChangeRate.String(), + ) } return nil @@ -289,8 +330,7 @@ func CreateValidatorFromNewMsg(val *CreateValidator, blockNum *big.Int) (*Valida return nil, err } commission := Commission{val.CommissionRates, blockNum} - pubKeys := []shard.BlsPublicKey{} - pubKeys = append(pubKeys, val.SlotPubKeys...) + pubKeys := append(val.SlotPubKeys[0:0], val.SlotPubKeys...) if err = verifyBLSKeys(pubKeys, val.SlotKeySigs); err != nil { return nil, err