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.
1725 lines
47 KiB
1725 lines
47 KiB
package core
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/harmony-one/harmony/crypto/bls"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/harmony-one/harmony/block"
|
|
consensus_engine "github.com/harmony-one/harmony/consensus/engine"
|
|
"github.com/harmony-one/harmony/core/state"
|
|
"github.com/harmony-one/harmony/core/vm"
|
|
"github.com/harmony-one/harmony/crypto/hash"
|
|
"github.com/harmony-one/harmony/numeric"
|
|
"github.com/harmony-one/harmony/staking/effective"
|
|
staking "github.com/harmony-one/harmony/staking/types"
|
|
staketest "github.com/harmony-one/harmony/staking/types/test"
|
|
)
|
|
|
|
const (
|
|
defNumWrappersInState = 5
|
|
defNumPubPerAddr = 2
|
|
|
|
validatorIndex = 0
|
|
validator2Index = 1
|
|
delegatorIndex = 6
|
|
)
|
|
|
|
var (
|
|
blsKeys = makeKeyPairs(20)
|
|
|
|
createValidatorAddr = makeTestAddr("validator")
|
|
validatorAddr = makeTestAddr(validatorIndex)
|
|
validatorAddr2 = makeTestAddr(validator2Index)
|
|
delegatorAddr = makeTestAddr(delegatorIndex)
|
|
)
|
|
|
|
var (
|
|
oneBig = big.NewInt(1e18)
|
|
fiveKOnes = new(big.Int).Mul(big.NewInt(5000), oneBig)
|
|
tenKOnes = new(big.Int).Mul(big.NewInt(10000), oneBig)
|
|
twelveKOnes = new(big.Int).Mul(big.NewInt(12000), oneBig)
|
|
fifteenKOnes = new(big.Int).Mul(big.NewInt(15000), oneBig)
|
|
twentyKOnes = new(big.Int).Mul(big.NewInt(20000), oneBig)
|
|
twentyFiveKOnes = new(big.Int).Mul(big.NewInt(25000), oneBig)
|
|
thirtyKOnes = new(big.Int).Mul(big.NewInt(30000), oneBig)
|
|
hundredKOnes = new(big.Int).Mul(big.NewInt(100000), oneBig)
|
|
|
|
negRate = numeric.NewDecWithPrec(-1, 10)
|
|
pointOneDec = numeric.NewDecWithPrec(1, 1)
|
|
pointTwoDec = numeric.NewDecWithPrec(2, 1)
|
|
pointFiveDec = numeric.NewDecWithPrec(5, 1)
|
|
pointSevenDec = numeric.NewDecWithPrec(7, 1)
|
|
pointEightFiveDec = numeric.NewDecWithPrec(85, 2)
|
|
pointNineDec = numeric.NewDecWithPrec(9, 1)
|
|
oneDec = numeric.OneDec()
|
|
)
|
|
|
|
const (
|
|
defaultEpoch = 5
|
|
defaultNextEpoch = 6
|
|
defaultSnapBlockNumber = 90
|
|
defaultBlockNumber = 100
|
|
)
|
|
|
|
var (
|
|
defaultDesc = staking.Description{
|
|
Name: "SuperHero",
|
|
Identity: "YouWouldNotKnow",
|
|
Website: "Secret Website",
|
|
SecurityContact: "LicenseToKill",
|
|
Details: "blah blah blah",
|
|
}
|
|
|
|
defaultCommissionRates = staking.CommissionRates{
|
|
Rate: pointOneDec,
|
|
MaxRate: pointNineDec,
|
|
MaxChangeRate: pointFiveDec,
|
|
}
|
|
)
|
|
|
|
func TestCheckDuplicateFields(t *testing.T) {
|
|
tests := []struct {
|
|
bc ChainContext
|
|
sdb *state.DB
|
|
validator common.Address
|
|
identity string
|
|
pubs []bls.SerializedPublicKey
|
|
|
|
expErr error
|
|
}{
|
|
{
|
|
// new validator
|
|
bc: makeFakeChainContextForStake(),
|
|
sdb: makeStateDBForStake(t),
|
|
validator: createValidatorAddr,
|
|
identity: makeIdentityStr("new validator"),
|
|
pubs: []bls.SerializedPublicKey{blsKeys[11].pub},
|
|
|
|
expErr: nil,
|
|
},
|
|
{
|
|
// validator skip self check
|
|
bc: makeFakeChainContextForStake(),
|
|
sdb: makeStateDBForStake(t),
|
|
validator: makeTestAddr(0),
|
|
identity: makeIdentityStr(0),
|
|
pubs: []bls.SerializedPublicKey{blsKeys[0].pub, blsKeys[1].pub},
|
|
|
|
expErr: nil,
|
|
},
|
|
{
|
|
// empty bls keys
|
|
bc: makeFakeChainContextForStake(),
|
|
sdb: makeStateDBForStake(t),
|
|
validator: createValidatorAddr,
|
|
identity: makeIdentityStr("new validator"),
|
|
pubs: []bls.SerializedPublicKey{},
|
|
|
|
expErr: nil,
|
|
},
|
|
{
|
|
// empty identity will not collide
|
|
bc: makeFakeChainContextForStake(),
|
|
sdb: func(t *testing.T) *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
vw, err := sdb.ValidatorWrapper(makeTestAddr(0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
vw.Identity = ""
|
|
|
|
err = sdb.UpdateValidatorWrapper(makeTestAddr(0), vw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return sdb
|
|
}(t),
|
|
validator: createValidatorAddr,
|
|
identity: makeIdentityStr("new validator"),
|
|
pubs: []bls.SerializedPublicKey{blsKeys[11].pub},
|
|
|
|
expErr: nil,
|
|
},
|
|
{
|
|
// chain error
|
|
bc: &fakeErrChainContext{},
|
|
sdb: makeStateDBForStake(t),
|
|
validator: createValidatorAddr,
|
|
identity: makeIdentityStr("new validator"),
|
|
pubs: []bls.SerializedPublicKey{blsKeys[11].pub},
|
|
|
|
expErr: errors.New("error intended"),
|
|
},
|
|
{
|
|
// validators read from chain not in state
|
|
bc: func() *fakeChainContext {
|
|
chain := makeFakeChainContextForStake()
|
|
addr := makeTestAddr("not exist in state")
|
|
w := staketest.GetDefaultValidatorWrapper()
|
|
chain.vWrappers[addr] = &w
|
|
return chain
|
|
}(),
|
|
sdb: makeStateDBForStake(t),
|
|
validator: createValidatorAddr,
|
|
identity: makeIdentityStr("new validator"),
|
|
pubs: []bls.SerializedPublicKey{blsKeys[11].pub},
|
|
|
|
expErr: errors.New("address not present in state"),
|
|
},
|
|
{
|
|
// duplicate identity
|
|
bc: makeFakeChainContextForStake(),
|
|
sdb: makeStateDBForStake(t),
|
|
validator: createValidatorAddr,
|
|
identity: makeIdentityStr(0),
|
|
pubs: []bls.SerializedPublicKey{blsKeys[11].pub},
|
|
|
|
expErr: errDupIdentity,
|
|
},
|
|
{
|
|
// bls key duplication
|
|
bc: makeFakeChainContextForStake(),
|
|
sdb: makeStateDBForStake(t),
|
|
validator: createValidatorAddr,
|
|
identity: makeIdentityStr("new validator"),
|
|
pubs: []bls.SerializedPublicKey{blsKeys[0].pub},
|
|
|
|
expErr: errDupBlsKey,
|
|
},
|
|
}
|
|
for i, test := range tests {
|
|
err := checkDuplicateFields(test.bc, test.sdb, test.validator, test.identity, test.pubs)
|
|
|
|
if assErr := assertError(err, test.expErr); assErr != nil {
|
|
t.Errorf("Test %v: %v", i, assErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVerifyAndCreateValidatorFromMsg(t *testing.T) {
|
|
tests := []struct {
|
|
sdb vm.StateDB
|
|
chain ChainContext
|
|
epoch *big.Int
|
|
blockNum *big.Int
|
|
msg staking.CreateValidator
|
|
|
|
expWrapper staking.ValidatorWrapper
|
|
expErr error
|
|
}{
|
|
{
|
|
// 0: valid request
|
|
sdb: makeStateDBForStake(t),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgCreateValidator(),
|
|
|
|
expWrapper: defaultExpWrapperCreateValidator(),
|
|
},
|
|
{
|
|
// 1: nil state db
|
|
sdb: nil,
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgCreateValidator(),
|
|
|
|
expErr: errStateDBIsMissing,
|
|
},
|
|
{
|
|
// 2: nil chain context
|
|
sdb: makeStateDBForStake(t),
|
|
chain: nil,
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgCreateValidator(),
|
|
|
|
expErr: errChainContextMissing,
|
|
},
|
|
{
|
|
// 3: nil epoch
|
|
sdb: makeStateDBForStake(t),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: nil,
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgCreateValidator(),
|
|
|
|
expErr: errEpochMissing,
|
|
},
|
|
{
|
|
// 4: nil block number
|
|
sdb: makeStateDBForStake(t),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: nil,
|
|
msg: defaultMsgCreateValidator(),
|
|
|
|
expErr: errBlockNumMissing,
|
|
},
|
|
{
|
|
// 5: negative amount
|
|
sdb: makeStateDBForStake(t),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.CreateValidator {
|
|
m := defaultMsgCreateValidator()
|
|
m.Amount = big.NewInt(-1)
|
|
return m
|
|
}(),
|
|
expErr: errNegativeAmount,
|
|
},
|
|
{
|
|
// 6: the address isValidatorFlag is true
|
|
sdb: func() *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
sdb.SetValidatorFlag(createValidatorAddr)
|
|
return sdb
|
|
}(),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgCreateValidator(),
|
|
|
|
expErr: errValidatorExist,
|
|
},
|
|
{
|
|
// 7: bls collision (checkDuplicateFields)
|
|
sdb: makeStateDBForStake(t),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.CreateValidator {
|
|
m := defaultMsgCreateValidator()
|
|
m.SlotPubKeys = []bls.SerializedPublicKey{blsKeys[0].pub}
|
|
return m
|
|
}(),
|
|
|
|
expErr: errors.New("BLS key exists"),
|
|
},
|
|
{
|
|
// 8: insufficient balance
|
|
sdb: func() *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
bal := new(big.Int).Sub(staketest.DefaultDelAmount, common.Big1)
|
|
sdb.SetBalance(createValidatorAddr, bal)
|
|
return sdb
|
|
}(),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgCreateValidator(),
|
|
|
|
expErr: errInsufficientBalanceForStake,
|
|
},
|
|
{
|
|
// 9: incorrect signature
|
|
sdb: makeStateDBForStake(t),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.CreateValidator {
|
|
m := defaultMsgCreateValidator()
|
|
m.SlotKeySigs = []bls.SerializedSignature{blsKeys[12].sig}
|
|
return m
|
|
}(),
|
|
|
|
expErr: errors.New("bls keys and corresponding signatures"),
|
|
},
|
|
{
|
|
// 10: small self delegation amount (fail sanity check)
|
|
sdb: makeStateDBForStake(t),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.CreateValidator {
|
|
m := defaultMsgCreateValidator()
|
|
m.Amount = new(big.Int).Sub(m.MinSelfDelegation, common.Big1)
|
|
return m
|
|
}(),
|
|
|
|
expErr: errors.New("self delegation can not be less than min_self_delegation"),
|
|
},
|
|
{
|
|
// 11: amount exactly minSelfDelegation. Should not return error
|
|
sdb: makeStateDBForStake(t),
|
|
chain: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.CreateValidator {
|
|
m := defaultMsgCreateValidator()
|
|
m.Amount = new(big.Int).Set(m.MinSelfDelegation)
|
|
return m
|
|
}(),
|
|
|
|
expWrapper: func() staking.ValidatorWrapper {
|
|
w := defaultExpWrapperCreateValidator()
|
|
w.Delegations[0].Amount = new(big.Int).Set(w.MinSelfDelegation)
|
|
return w
|
|
}(),
|
|
},
|
|
}
|
|
for i, test := range tests {
|
|
w, err := VerifyAndCreateValidatorFromMsg(test.sdb, test.chain, test.epoch,
|
|
test.blockNum, &test.msg)
|
|
|
|
if assErr := assertError(err, test.expErr); assErr != nil {
|
|
t.Errorf("Test %v: %v", i, err)
|
|
}
|
|
if err != nil || test.expErr != nil {
|
|
continue
|
|
}
|
|
|
|
if err := staketest.CheckValidatorWrapperEqual(*w, test.expWrapper); err != nil {
|
|
t.Errorf("Test %v: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func defaultMsgCreateValidator() staking.CreateValidator {
|
|
pub, sig := blsKeys[11].pub, blsKeys[11].sig
|
|
cv := staking.CreateValidator{
|
|
ValidatorAddress: createValidatorAddr,
|
|
Description: defaultDesc,
|
|
CommissionRates: defaultCommissionRates,
|
|
MinSelfDelegation: staketest.DefaultMinSelfDel,
|
|
MaxTotalDelegation: staketest.DefaultMaxTotalDel,
|
|
SlotPubKeys: []bls.SerializedPublicKey{pub},
|
|
SlotKeySigs: []bls.SerializedSignature{sig},
|
|
Amount: staketest.DefaultDelAmount,
|
|
}
|
|
return cv
|
|
}
|
|
|
|
func defaultExpWrapperCreateValidator() staking.ValidatorWrapper {
|
|
pub := blsKeys[11].pub
|
|
v := staking.Validator{
|
|
Address: createValidatorAddr,
|
|
SlotPubKeys: []bls.SerializedPublicKey{pub},
|
|
LastEpochInCommittee: new(big.Int),
|
|
MinSelfDelegation: staketest.DefaultMinSelfDel,
|
|
MaxTotalDelegation: staketest.DefaultMaxTotalDel,
|
|
Status: effective.Active,
|
|
Commission: staking.Commission{
|
|
CommissionRates: defaultCommissionRates,
|
|
UpdateHeight: big.NewInt(defaultBlockNumber),
|
|
},
|
|
Description: defaultDesc,
|
|
CreationHeight: big.NewInt(defaultBlockNumber),
|
|
}
|
|
ds := staking.Delegations{
|
|
staking.NewDelegation(createValidatorAddr, staketest.DefaultDelAmount),
|
|
}
|
|
w := staking.ValidatorWrapper{
|
|
Validator: v,
|
|
Delegations: ds,
|
|
BlockReward: big.NewInt(0),
|
|
}
|
|
w.Counters.NumBlocksSigned = common.Big0
|
|
w.Counters.NumBlocksToSign = common.Big0
|
|
return w
|
|
}
|
|
|
|
func TestVerifyAndEditValidatorFromMsg(t *testing.T) {
|
|
tests := []struct {
|
|
sdb vm.StateDB
|
|
bc ChainContext
|
|
epoch, blockNum *big.Int
|
|
msg staking.EditValidator
|
|
expWrapper staking.ValidatorWrapper
|
|
expErr error
|
|
}{
|
|
{
|
|
// 0: positive case
|
|
sdb: makeStateDBForStake(t),
|
|
bc: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgEditValidator(),
|
|
|
|
expWrapper: defaultExpWrapperEditValidator(),
|
|
},
|
|
{
|
|
// 1: If the rate is not changed, UpdateHeight is not update
|
|
sdb: makeStateDBForStake(t),
|
|
bc: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.EditValidator {
|
|
msg := defaultMsgEditValidator()
|
|
msg.CommissionRate = nil
|
|
return msg
|
|
}(),
|
|
|
|
expWrapper: func() staking.ValidatorWrapper {
|
|
vw := defaultExpWrapperEditValidator()
|
|
vw.UpdateHeight = big.NewInt(defaultSnapBlockNumber)
|
|
vw.Rate = pointFiveDec
|
|
return vw
|
|
}(),
|
|
},
|
|
{
|
|
// 2: nil state db
|
|
sdb: nil,
|
|
bc: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgEditValidator(),
|
|
|
|
expErr: errStateDBIsMissing,
|
|
},
|
|
{
|
|
// 3: nil chain
|
|
sdb: makeStateDBForStake(t),
|
|
bc: nil,
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgEditValidator(),
|
|
|
|
expErr: errChainContextMissing,
|
|
},
|
|
{
|
|
// 4: nil block number
|
|
sdb: makeStateDBForStake(t),
|
|
bc: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: nil,
|
|
msg: defaultMsgEditValidator(),
|
|
|
|
expErr: errBlockNumMissing,
|
|
},
|
|
{
|
|
// 5: edited validator flag not set in state
|
|
sdb: makeStateDBForStake(t),
|
|
bc: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.EditValidator {
|
|
msg := defaultMsgEditValidator()
|
|
msg.ValidatorAddress = makeTestAddr("addr not in chain")
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errValidatorNotExist,
|
|
},
|
|
{
|
|
// 6: bls key collision
|
|
sdb: makeStateDBForStake(t),
|
|
bc: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.EditValidator {
|
|
msg := defaultMsgEditValidator()
|
|
msg.SlotKeyToAdd = &blsKeys[3].pub
|
|
msg.SlotKeyToAddSig = &blsKeys[3].sig
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errDupBlsKey,
|
|
},
|
|
{
|
|
// 7: validatorWrapper not in state
|
|
sdb: func() *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
sdb.SetValidatorFlag(makeTestAddr("someone"))
|
|
return sdb
|
|
}(),
|
|
bc: func() *fakeChainContext {
|
|
chain := makeFakeChainContextForStake()
|
|
addr := makeTestAddr("someone")
|
|
w := staketest.GetDefaultValidatorWrapperWithAddr(addr, nil)
|
|
chain.vWrappers[addr] = &w
|
|
return chain
|
|
}(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.EditValidator {
|
|
msg := defaultMsgEditValidator()
|
|
msg.ValidatorAddress = makeTestAddr("someone")
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errors.New("address not present in state"),
|
|
},
|
|
{
|
|
// 8: signature cannot be verified
|
|
sdb: makeStateDBForStake(t),
|
|
bc: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.EditValidator {
|
|
msg := defaultMsgEditValidator()
|
|
msg.SlotKeyToAddSig = &blsKeys[13].sig
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errors.New("bls keys and corresponding signatures could not be verified"),
|
|
},
|
|
{
|
|
// 9: Rate is greater the maxRate
|
|
sdb: makeStateDBForStake(t),
|
|
bc: func() *fakeChainContext {
|
|
chain := makeFakeChainContextForStake()
|
|
vw := chain.vWrappers[validatorAddr]
|
|
vw.Rate = pointSevenDec
|
|
chain.vWrappers[validatorAddr] = vw
|
|
return chain
|
|
}(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.EditValidator {
|
|
msg := defaultMsgEditValidator()
|
|
msg.CommissionRate = &oneDec
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errCommissionRateChangeTooHigh,
|
|
},
|
|
{
|
|
// 10: validator not in snapshot
|
|
sdb: makeStateDBForStake(t),
|
|
bc: func() *fakeChainContext {
|
|
chain := makeFakeChainContextForStake()
|
|
delete(chain.vWrappers, validatorAddr)
|
|
return chain
|
|
}(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgEditValidator(),
|
|
|
|
expErr: errors.New("validator snapshot not found"),
|
|
},
|
|
{
|
|
// 11: rate is greater than maxChangeRate
|
|
sdb: makeStateDBForStake(t),
|
|
bc: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.EditValidator {
|
|
msg := defaultMsgEditValidator()
|
|
msg.CommissionRate = &pointEightFiveDec
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errCommissionRateChangeTooFast,
|
|
},
|
|
{
|
|
// 12: fails in sanity check (rate below zero)
|
|
sdb: makeStateDBForStake(t),
|
|
bc: func() *fakeChainContext {
|
|
chain := makeFakeChainContextForStake()
|
|
vw := chain.vWrappers[validatorAddr]
|
|
vw.Rate = pointOneDec
|
|
chain.vWrappers[validatorAddr] = vw
|
|
return chain
|
|
}(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: func() staking.EditValidator {
|
|
msg := defaultMsgEditValidator()
|
|
msg.CommissionRate = &negRate
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errors.New("rate should be a value ranging from 0.0 to 1.0"),
|
|
},
|
|
{
|
|
// 13: cannot update a banned validator
|
|
sdb: func(t *testing.T) *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
vw, err := sdb.ValidatorWrapper(validatorAddr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
vw.Status = effective.Banned
|
|
if err := sdb.UpdateValidatorWrapper(validatorAddr, vw); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return sdb
|
|
}(t),
|
|
bc: makeFakeChainContextForStake(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
blockNum: big.NewInt(defaultBlockNumber),
|
|
msg: defaultMsgEditValidator(),
|
|
|
|
expErr: errors.New("banned status"),
|
|
},
|
|
}
|
|
for i, test := range tests {
|
|
w, err := VerifyAndEditValidatorFromMsg(test.sdb, test.bc, test.epoch, test.blockNum,
|
|
&test.msg)
|
|
|
|
if assErr := assertError(err, test.expErr); assErr != nil {
|
|
t.Errorf("Test %v: unexpected Error: %v", i, assErr)
|
|
}
|
|
if err != nil || test.expErr != nil {
|
|
continue
|
|
}
|
|
|
|
if err := staketest.CheckValidatorWrapperEqual(*w, test.expWrapper); err != nil {
|
|
t.Errorf("Test %v: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
editDesc = staking.Description{
|
|
Name: "batman",
|
|
Identity: "batman",
|
|
Website: "",
|
|
SecurityContact: "",
|
|
Details: "",
|
|
}
|
|
|
|
editExpDesc = staking.Description{
|
|
Name: "batman",
|
|
Identity: "batman",
|
|
Website: "Secret Website",
|
|
SecurityContact: "LicenseToKill",
|
|
Details: "blah blah blah",
|
|
}
|
|
)
|
|
|
|
func defaultMsgEditValidator() staking.EditValidator {
|
|
var (
|
|
pub0Copy bls.SerializedPublicKey
|
|
pub12Copy bls.SerializedPublicKey
|
|
sig12Copy bls.SerializedSignature
|
|
)
|
|
copy(pub0Copy[:], blsKeys[0].pub[:])
|
|
copy(pub12Copy[:], blsKeys[12].pub[:])
|
|
copy(sig12Copy[:], blsKeys[12].sig[:])
|
|
|
|
return staking.EditValidator{
|
|
ValidatorAddress: validatorAddr,
|
|
Description: editDesc,
|
|
CommissionRate: &pointTwoDec,
|
|
SlotKeyToRemove: &pub0Copy,
|
|
SlotKeyToAdd: &pub12Copy,
|
|
SlotKeyToAddSig: &sig12Copy,
|
|
EPOSStatus: effective.Inactive,
|
|
}
|
|
}
|
|
|
|
func defaultExpWrapperEditValidator() staking.ValidatorWrapper {
|
|
w := makeVWrapperByIndex(validatorIndex)
|
|
w.SlotPubKeys = append(w.SlotPubKeys[1:], blsKeys[12].pub)
|
|
w.Description = editExpDesc
|
|
w.Rate = pointTwoDec
|
|
w.UpdateHeight = big.NewInt(defaultBlockNumber)
|
|
w.Status = effective.Inactive
|
|
return w
|
|
}
|
|
|
|
func TestVerifyAndDelegateFromMsg(t *testing.T) {
|
|
tests := []struct {
|
|
sdb vm.StateDB
|
|
msg staking.Delegate
|
|
ds []staking.DelegationIndex
|
|
epoch *big.Int
|
|
redelegate bool
|
|
|
|
expVWrappers []staking.ValidatorWrapper
|
|
expAmt *big.Int
|
|
expRedel map[common.Address]*big.Int
|
|
expErr error
|
|
}{
|
|
{
|
|
// 0: new delegate
|
|
sdb: makeStateDBForStake(t),
|
|
msg: defaultMsgDelegate(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expVWrappers: []staking.ValidatorWrapper{defaultExpVWrapperDelegate()},
|
|
expAmt: tenKOnes,
|
|
},
|
|
{
|
|
// 1: add amount to current delegate
|
|
sdb: makeStateDBForStake(t),
|
|
msg: defaultMsgSelfDelegate(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expVWrappers: []staking.ValidatorWrapper{defaultExpVWrapperSelfDelegate()},
|
|
expAmt: tenKOnes,
|
|
},
|
|
{
|
|
// 2: nil state db
|
|
sdb: nil,
|
|
msg: defaultMsgDelegate(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expErr: errStateDBIsMissing,
|
|
},
|
|
{
|
|
// 3: validatorFlag not set
|
|
sdb: makeStateDBForStake(t),
|
|
ds: makeMsgCollectRewards(),
|
|
msg: func() staking.Delegate {
|
|
msg := defaultMsgDelegate()
|
|
msg.ValidatorAddress = makeTestAddr("not in state")
|
|
return msg
|
|
}(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expErr: errValidatorNotExist,
|
|
},
|
|
{
|
|
// 4: negative amount
|
|
sdb: makeStateDBForStake(t),
|
|
msg: func() staking.Delegate {
|
|
msg := defaultMsgDelegate()
|
|
msg.Amount = big.NewInt(-1)
|
|
return msg
|
|
}(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expErr: errNegativeAmount,
|
|
},
|
|
{
|
|
// 5: small amount
|
|
sdb: makeStateDBForStake(t),
|
|
msg: func() staking.Delegate {
|
|
msg := defaultMsgDelegate()
|
|
msg.Amount = big.NewInt(100)
|
|
return msg
|
|
}(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expErr: errDelegationTooSmall,
|
|
},
|
|
{
|
|
// 6: missing validator wrapper
|
|
sdb: func() *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
sdb.SetValidatorFlag(createValidatorAddr)
|
|
return sdb
|
|
}(),
|
|
msg: func() staking.Delegate {
|
|
d := defaultMsgDelegate()
|
|
d.ValidatorAddress = createValidatorAddr
|
|
return d
|
|
}(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expErr: errors.New("address not present in state"),
|
|
},
|
|
{
|
|
// 7: cannot transfer since not enough amount
|
|
sdb: func() *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
sdb.SetBalance(delegatorAddr, big.NewInt(100))
|
|
return sdb
|
|
}(),
|
|
msg: defaultMsgDelegate(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expErr: errInsufficientBalanceForStake,
|
|
},
|
|
{
|
|
// 8: self delegation not pass sanity check
|
|
sdb: makeStateDBForStake(t),
|
|
msg: func() staking.Delegate {
|
|
d := defaultMsgSelfDelegate()
|
|
d.Amount = hundredKOnes
|
|
return d
|
|
}(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expErr: errors.New(" total delegation can not be bigger than max_total_delegation"),
|
|
},
|
|
{
|
|
// 9: Delegation does not pass sanity check
|
|
sdb: makeStateDBForStake(t),
|
|
msg: func() staking.Delegate {
|
|
d := defaultMsgDelegate()
|
|
d.Amount = hundredKOnes
|
|
return d
|
|
}(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: false,
|
|
|
|
expErr: errors.New(" total delegation can not be bigger than max_total_delegation"),
|
|
},
|
|
{
|
|
// 10: full redelegate without using balance
|
|
sdb: makeStateForRedelegate(t),
|
|
msg: defaultMsgDelegate(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(7),
|
|
redelegate: true,
|
|
|
|
expVWrappers: defaultExpVWrappersRedelegate(),
|
|
expAmt: big.NewInt(0),
|
|
expRedel: map[common.Address]*big.Int{
|
|
validatorAddr: fiveKOnes,
|
|
validatorAddr2: fiveKOnes,
|
|
},
|
|
},
|
|
{
|
|
// 11: redelegate with undelegation epoch too recent, have to use some balance
|
|
sdb: makeStateForRedelegate(t),
|
|
msg: defaultMsgDelegate(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(6),
|
|
redelegate: true,
|
|
|
|
expVWrappers: func() []staking.ValidatorWrapper {
|
|
wrappers := defaultExpVWrappersRedelegate()
|
|
wrappers[1].Delegations[1].Undelegations = append(
|
|
wrappers[1].Delegations[1].Undelegations,
|
|
staking.Undelegation{Amount: fiveKOnes, Epoch: big.NewInt(defaultNextEpoch)})
|
|
return wrappers
|
|
}(),
|
|
expAmt: fiveKOnes,
|
|
expRedel: map[common.Address]*big.Int{
|
|
validatorAddr: fiveKOnes,
|
|
},
|
|
},
|
|
{
|
|
// 12: redelegate with not enough undelegated token, have to use some balance
|
|
sdb: makeStateForRedelegate(t),
|
|
msg: func() staking.Delegate {
|
|
del := defaultMsgDelegate()
|
|
del.Amount = twentyKOnes
|
|
return del
|
|
}(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(7),
|
|
redelegate: true,
|
|
|
|
expVWrappers: func() []staking.ValidatorWrapper {
|
|
wrappers := defaultExpVWrappersRedelegate()
|
|
wrappers[0].Delegations[1].Amount = big.NewInt(0).Add(twentyKOnes, twentyKOnes)
|
|
return wrappers
|
|
}(),
|
|
expAmt: tenKOnes,
|
|
expRedel: map[common.Address]*big.Int{
|
|
validatorAddr: fiveKOnes,
|
|
validatorAddr2: fiveKOnes,
|
|
},
|
|
},
|
|
{
|
|
// 13: no redelegation and full balance used
|
|
sdb: makeStateForRedelegate(t),
|
|
msg: defaultMsgDelegate(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(5),
|
|
redelegate: true,
|
|
|
|
expVWrappers: func() []staking.ValidatorWrapper {
|
|
wrappers := defaultExpVWrappersRedelegate()
|
|
wrappers[0].Delegations[1].Undelegations = append(
|
|
wrappers[0].Delegations[1].Undelegations,
|
|
staking.Undelegation{Amount: fiveKOnes, Epoch: big.NewInt(defaultEpoch)})
|
|
wrappers[1].Delegations[1].Undelegations = append(
|
|
wrappers[1].Delegations[1].Undelegations,
|
|
staking.Undelegation{Amount: fiveKOnes, Epoch: big.NewInt(defaultNextEpoch)})
|
|
return wrappers
|
|
}(),
|
|
expAmt: tenKOnes,
|
|
},
|
|
{
|
|
// 14: redelegate error delegation index out of bound
|
|
sdb: makeStateForRedelegate(t),
|
|
msg: defaultMsgSelfDelegate(),
|
|
ds: func() []staking.DelegationIndex {
|
|
dis := makeMsgCollectRewards()
|
|
dis[1].Index = 2
|
|
return dis
|
|
}(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: true,
|
|
|
|
expErr: errors.New("index out of bound"),
|
|
},
|
|
{
|
|
// 15: redelegate error delegation index out of bound
|
|
sdb: makeStateForRedelegate(t),
|
|
msg: defaultMsgSelfDelegate(),
|
|
ds: func() []staking.DelegationIndex {
|
|
dis := makeMsgCollectRewards()
|
|
dis[1].Index = 2
|
|
return dis
|
|
}(),
|
|
epoch: big.NewInt(0),
|
|
redelegate: true,
|
|
|
|
expErr: errors.New("index out of bound"),
|
|
},
|
|
{
|
|
// 16: no redelegation and full balance used (validator delegate to self)
|
|
sdb: makeStateForRedelegate(t),
|
|
msg: func() staking.Delegate {
|
|
del := defaultMsgDelegate()
|
|
del.DelegatorAddress = del.ValidatorAddress
|
|
return del
|
|
}(),
|
|
ds: makeMsgCollectRewards(),
|
|
epoch: big.NewInt(5),
|
|
redelegate: true,
|
|
|
|
expVWrappers: func() []staking.ValidatorWrapper {
|
|
wrappers := defaultExpVWrappersRedelegate()
|
|
wrappers[0].Delegations[1].Undelegations = append(
|
|
wrappers[0].Delegations[1].Undelegations,
|
|
staking.Undelegation{Amount: fiveKOnes, Epoch: big.NewInt(defaultEpoch)})
|
|
wrappers[1].Delegations[1].Undelegations = append(
|
|
wrappers[1].Delegations[1].Undelegations,
|
|
staking.Undelegation{Amount: fiveKOnes, Epoch: big.NewInt(defaultNextEpoch)})
|
|
|
|
wrappers[0].Delegations[1].Amount = twentyKOnes
|
|
wrappers[0].Delegations[0].Amount = big.NewInt(0).Add(twentyKOnes, tenKOnes)
|
|
return wrappers
|
|
}(),
|
|
expAmt: tenKOnes,
|
|
},
|
|
}
|
|
for i, test := range tests {
|
|
ws, amt, amtRedel, err := VerifyAndDelegateFromMsg(test.sdb, test.epoch, &test.msg, test.ds, test.redelegate)
|
|
|
|
if assErr := assertError(err, test.expErr); assErr != nil {
|
|
t.Errorf("Test %v: %v", i, assErr)
|
|
}
|
|
if err != nil || test.expErr != nil {
|
|
continue
|
|
}
|
|
|
|
if amt.Cmp(test.expAmt) != 0 {
|
|
t.Errorf("Test %v: unexpected amount %v / %v", i, amt, test.expAmt)
|
|
}
|
|
|
|
if len(amtRedel) != len(test.expRedel) {
|
|
t.Errorf("Test %v: wrong expected redelegation length %d / %d", i, len(amtRedel), len(test.expRedel))
|
|
} else {
|
|
for key, value := range test.expRedel {
|
|
actValue, ok := amtRedel[key]
|
|
if !ok {
|
|
t.Errorf("Test %v: missing expected redelegation key/value %v / %v", i, key, value)
|
|
}
|
|
if value.Cmp(actValue) != 0 {
|
|
t.Errorf("Test %v: unexpeced redelegation value %v / %v", i, actValue, value)
|
|
}
|
|
}
|
|
}
|
|
for j := range ws {
|
|
if err := staketest.CheckValidatorWrapperEqual(*ws[j], test.expVWrappers[j]); err != nil {
|
|
t.Errorf("Test %v: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeStateForRedelegate(t *testing.T) *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
|
|
if err := addStateUndelegationForAddr(sdb, validatorAddr, big.NewInt(defaultEpoch)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := addStateUndelegationForAddr(sdb, validatorAddr2, big.NewInt(defaultNextEpoch)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sdb.IntermediateRoot(true)
|
|
return sdb
|
|
}
|
|
|
|
func addStateUndelegationForAddr(sdb *state.DB, addr common.Address, epoch *big.Int) error {
|
|
w, err := sdb.ValidatorWrapper(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.Delegations = append(w.Delegations,
|
|
staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)),
|
|
)
|
|
w.Delegations[1].Undelegations = staking.Undelegations{staking.Undelegation{Amount: fiveKOnes, Epoch: epoch}}
|
|
|
|
return sdb.UpdateValidatorWrapper(addr, w)
|
|
}
|
|
|
|
func defaultExpVWrappersRedelegate() []staking.ValidatorWrapper {
|
|
w1 := makeVWrapperByIndex(validatorIndex)
|
|
w1.Delegations = append(w1.Delegations,
|
|
staking.NewDelegation(delegatorAddr, new(big.Int).Set(thirtyKOnes)),
|
|
)
|
|
|
|
w2 := makeVWrapperByIndex(validator2Index)
|
|
w2.Delegations = append(w2.Delegations,
|
|
staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)),
|
|
)
|
|
return []staking.ValidatorWrapper{w1, w2}
|
|
}
|
|
|
|
func defaultMsgDelegate() staking.Delegate {
|
|
return staking.Delegate{
|
|
DelegatorAddress: delegatorAddr,
|
|
ValidatorAddress: validatorAddr,
|
|
Amount: new(big.Int).Set(tenKOnes),
|
|
}
|
|
}
|
|
|
|
func defaultExpVWrapperDelegate() staking.ValidatorWrapper {
|
|
w := makeVWrapperByIndex(validatorIndex)
|
|
w.Delegations = append(w.Delegations, staking.NewDelegation(delegatorAddr, tenKOnes))
|
|
return w
|
|
}
|
|
|
|
func defaultMsgSelfDelegate() staking.Delegate {
|
|
return staking.Delegate{
|
|
DelegatorAddress: validatorAddr,
|
|
ValidatorAddress: validatorAddr,
|
|
Amount: new(big.Int).Set(tenKOnes),
|
|
}
|
|
}
|
|
|
|
func defaultExpVWrapperSelfDelegate() staking.ValidatorWrapper {
|
|
w := makeVWrapperByIndex(validatorIndex)
|
|
w.Delegations[0].Amount = new(big.Int).Add(tenKOnes, staketest.DefaultDelAmount)
|
|
return w
|
|
}
|
|
|
|
func TestVerifyAndUndelegateFromMsg(t *testing.T) {
|
|
tests := []struct {
|
|
sdb vm.StateDB
|
|
epoch *big.Int
|
|
msg staking.Undelegate
|
|
|
|
expVWrapper staking.ValidatorWrapper
|
|
expErr error
|
|
}{
|
|
{
|
|
// 0: Undelegate at delegation with an entry already exist at the same epoch.
|
|
// Will increase the amount in undelegate entry
|
|
sdb: makeDefaultStateForUndelegate(t),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: defaultMsgUndelegate(),
|
|
|
|
expVWrapper: defaultExpVWrapperUndelegateSameEpoch(t),
|
|
},
|
|
{
|
|
// 1: Undelegate with undelegation entry exist but not in same epoch.
|
|
// Will create a new undelegate entry
|
|
sdb: makeDefaultStateForUndelegate(t),
|
|
epoch: big.NewInt(defaultNextEpoch),
|
|
msg: defaultMsgUndelegate(),
|
|
|
|
expVWrapper: defaultExpVWrapperUndelegateNextEpoch(t),
|
|
},
|
|
{
|
|
// 2: Undelegate from a delegation record with no undelegation entry.
|
|
// Will create a new undelegate entry
|
|
sdb: makeDefaultStateForUndelegate(t),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: defaultMsgSelfUndelegate(),
|
|
|
|
expVWrapper: defaultVWrapperSelfUndelegate(t),
|
|
},
|
|
{
|
|
// 3: Self delegation below min self delegation, change status to Inactive
|
|
sdb: makeDefaultStateForUndelegate(t),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: func() staking.Undelegate {
|
|
msg := defaultMsgSelfUndelegate()
|
|
msg.Amount = new(big.Int).Set(fifteenKOnes)
|
|
return msg
|
|
}(),
|
|
|
|
expVWrapper: func(t *testing.T) staking.ValidatorWrapper {
|
|
w := defaultVWrapperSelfUndelegate(t)
|
|
|
|
w.Delegations[0].Amount = new(big.Int).Set(fiveKOnes)
|
|
w.Delegations[0].Undelegations[0].Amount = new(big.Int).Set(fifteenKOnes)
|
|
w.Status = effective.Inactive
|
|
|
|
return w
|
|
}(t),
|
|
},
|
|
{
|
|
// 4: Extract tokens from banned validator
|
|
sdb: func(t *testing.T) *state.DB {
|
|
sdb := makeDefaultStateForUndelegate(t)
|
|
w, err := sdb.ValidatorWrapper(validatorAddr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w.Status = effective.Banned
|
|
if err := sdb.UpdateValidatorWrapper(validatorAddr, w); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return sdb
|
|
}(t),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: func() staking.Undelegate {
|
|
msg := defaultMsgSelfUndelegate()
|
|
msg.Amount = new(big.Int).Set(fifteenKOnes)
|
|
return msg
|
|
}(),
|
|
|
|
expVWrapper: func(t *testing.T) staking.ValidatorWrapper {
|
|
w := defaultVWrapperSelfUndelegate(t)
|
|
|
|
w.Delegations[0].Amount = new(big.Int).Set(fiveKOnes)
|
|
w.Delegations[0].Undelegations[0].Amount = new(big.Int).Set(fifteenKOnes)
|
|
w.Status = effective.Banned
|
|
|
|
return w
|
|
}(t),
|
|
},
|
|
{
|
|
// 5: nil state db
|
|
sdb: nil,
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: defaultMsgUndelegate(),
|
|
|
|
expErr: errStateDBIsMissing,
|
|
},
|
|
{
|
|
// 6: nil epoch
|
|
sdb: makeDefaultStateForUndelegate(t),
|
|
epoch: nil,
|
|
msg: defaultMsgUndelegate(),
|
|
|
|
expErr: errEpochMissing,
|
|
},
|
|
{
|
|
// 7: negative amount
|
|
sdb: makeDefaultStateForUndelegate(t),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: func() staking.Undelegate {
|
|
msg := defaultMsgUndelegate()
|
|
msg.Amount = big.NewInt(-1)
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errNegativeAmount,
|
|
},
|
|
{
|
|
// 8: validator flag not set
|
|
sdb: func() *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
w := makeVWrapperByIndex(6)
|
|
if err := sdb.UpdateValidatorWrapper(makeTestAddr(6), &w); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return sdb
|
|
}(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: func() staking.Undelegate {
|
|
msg := defaultMsgUndelegate()
|
|
msg.ValidatorAddress = makeTestAddr(6)
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errValidatorNotExist,
|
|
},
|
|
{
|
|
// 9: vWrapper not in state
|
|
sdb: func() *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
sdb.SetValidatorFlag(makeTestAddr(6))
|
|
return sdb
|
|
}(),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: func() staking.Undelegate {
|
|
msg := defaultMsgUndelegate()
|
|
msg.ValidatorAddress = makeTestAddr(6)
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errors.New("address not present in state"),
|
|
},
|
|
{
|
|
// 10: Insufficient balance to undelegate
|
|
sdb: makeDefaultStateForUndelegate(t),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: func() staking.Undelegate {
|
|
msg := defaultMsgUndelegate()
|
|
msg.Amount = new(big.Int).Set(hundredKOnes)
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errors.New("insufficient balance to undelegate"),
|
|
},
|
|
{
|
|
// 11: No delegation record
|
|
sdb: makeDefaultStateForUndelegate(t),
|
|
epoch: big.NewInt(defaultEpoch),
|
|
msg: func() staking.Undelegate {
|
|
msg := defaultMsgUndelegate()
|
|
msg.DelegatorAddress = makeTestAddr("not exist")
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errNoDelegationToUndelegate,
|
|
},
|
|
}
|
|
for i, test := range tests {
|
|
w, err := VerifyAndUndelegateFromMsg(test.sdb, test.epoch, &test.msg)
|
|
|
|
if assErr := assertError(err, test.expErr); assErr != nil {
|
|
t.Errorf("Test %v: %v", i, assErr)
|
|
}
|
|
if err != nil || test.expErr != nil {
|
|
continue
|
|
}
|
|
|
|
if err := staketest.CheckValidatorWrapperEqual(*w, test.expVWrapper); err != nil {
|
|
t.Errorf("Test %v: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeDefaultSnapVWrapperForUndelegate(t *testing.T) staking.ValidatorWrapper {
|
|
w := makeVWrapperByIndex(validatorIndex)
|
|
|
|
newDelegation := staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes))
|
|
if err := newDelegation.Undelegate(big.NewInt(defaultEpoch), fiveKOnes); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w.Delegations = append(w.Delegations, newDelegation)
|
|
|
|
return w
|
|
}
|
|
|
|
func makeDefaultStateForUndelegate(t *testing.T) *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
w := makeDefaultSnapVWrapperForUndelegate(t)
|
|
|
|
if err := sdb.UpdateValidatorWrapper(validatorAddr, &w); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sdb.IntermediateRoot(true)
|
|
return sdb
|
|
}
|
|
|
|
// undelegate from delegator which has already go one entry for undelegation
|
|
func defaultMsgUndelegate() staking.Undelegate {
|
|
return staking.Undelegate{
|
|
DelegatorAddress: delegatorAddr,
|
|
ValidatorAddress: validatorAddr,
|
|
Amount: fiveKOnes,
|
|
}
|
|
}
|
|
|
|
func defaultExpVWrapperUndelegateSameEpoch(t *testing.T) staking.ValidatorWrapper {
|
|
w := makeDefaultSnapVWrapperForUndelegate(t)
|
|
|
|
amt := w.Delegations[1].Undelegations[0].Amount
|
|
w.Delegations[1].Undelegations[0].Amount = new(big.Int).
|
|
Add(w.Delegations[1].Undelegations[0].Amount, amt)
|
|
w.Delegations[1].Amount = new(big.Int).Sub(w.Delegations[1].Amount, fiveKOnes)
|
|
|
|
return w
|
|
}
|
|
|
|
func defaultExpVWrapperUndelegateNextEpoch(t *testing.T) staking.ValidatorWrapper {
|
|
w := makeDefaultSnapVWrapperForUndelegate(t)
|
|
|
|
w.Delegations[1].Undelegations = append(w.Delegations[1].Undelegations,
|
|
staking.Undelegation{Amount: fiveKOnes, Epoch: big.NewInt(defaultNextEpoch)})
|
|
w.Delegations[1].Amount = new(big.Int).Sub(w.Delegations[1].Amount, fiveKOnes)
|
|
|
|
return w
|
|
}
|
|
|
|
// undelegate from self undelegation (new undelegates)
|
|
func defaultMsgSelfUndelegate() staking.Undelegate {
|
|
return staking.Undelegate{
|
|
DelegatorAddress: validatorAddr,
|
|
ValidatorAddress: validatorAddr,
|
|
Amount: fiveKOnes,
|
|
}
|
|
}
|
|
|
|
func defaultVWrapperSelfUndelegate(t *testing.T) staking.ValidatorWrapper {
|
|
w := makeDefaultSnapVWrapperForUndelegate(t)
|
|
|
|
w.Delegations[0].Undelegations = staking.Undelegations{
|
|
staking.Undelegation{Amount: fiveKOnes, Epoch: big.NewInt(defaultEpoch)},
|
|
}
|
|
w.Delegations[0].Amount = new(big.Int).Sub(w.Delegations[0].Amount, fiveKOnes)
|
|
|
|
return w
|
|
}
|
|
|
|
var (
|
|
reward00 = twentyKOnes
|
|
reward01 = tenKOnes
|
|
reward10 = thirtyKOnes
|
|
reward11 = twentyFiveKOnes
|
|
)
|
|
|
|
func TestVerifyAndCollectRewardsFromDelegation(t *testing.T) {
|
|
tests := []struct {
|
|
sdb vm.StateDB
|
|
ds []staking.DelegationIndex
|
|
|
|
expVWrappers []*staking.ValidatorWrapper
|
|
expTotalRewards *big.Int
|
|
expErr error
|
|
}{
|
|
{
|
|
// 0: Positive test case
|
|
sdb: makeStateForReward(t),
|
|
ds: makeMsgCollectRewards(),
|
|
|
|
expVWrappers: expVWrappersForReward(),
|
|
expTotalRewards: new(big.Int).Add(reward01, reward11),
|
|
},
|
|
{
|
|
// 1: No rewards to collect
|
|
sdb: makeStateDBForStake(t),
|
|
ds: []staking.DelegationIndex{{ValidatorAddress: validatorAddr2, Index: 0}},
|
|
|
|
expErr: errNoRewardsToCollect,
|
|
},
|
|
{
|
|
// 2: nil state db
|
|
sdb: nil,
|
|
ds: makeMsgCollectRewards(),
|
|
|
|
expErr: errStateDBIsMissing,
|
|
},
|
|
{
|
|
// 3: ValidatorWrapper not in state
|
|
sdb: makeStateForReward(t),
|
|
ds: func() []staking.DelegationIndex {
|
|
msg := makeMsgCollectRewards()
|
|
msg[1].ValidatorAddress = makeTestAddr("addr not exist")
|
|
return msg
|
|
}(),
|
|
|
|
expErr: errors.New("address not present in state"),
|
|
},
|
|
{
|
|
// 4: Wrong input message - index out of range
|
|
sdb: makeStateForReward(t),
|
|
ds: func() []staking.DelegationIndex {
|
|
dis := makeMsgCollectRewards()
|
|
dis[1].Index = 2
|
|
return dis
|
|
}(),
|
|
|
|
expErr: errors.New("index out of bound"),
|
|
},
|
|
}
|
|
for i, test := range tests {
|
|
ws, tReward, err := VerifyAndCollectRewardsFromDelegation(test.sdb, test.ds)
|
|
|
|
if assErr := assertError(err, test.expErr); assErr != nil {
|
|
t.Fatalf("Test %v: %v", i, err)
|
|
}
|
|
if err != nil || test.expErr != nil {
|
|
continue
|
|
}
|
|
|
|
if len(ws) != len(test.expVWrappers) {
|
|
t.Fatalf("vwrapper size unexpected: %v / %v", len(ws), len(test.expVWrappers))
|
|
}
|
|
for wi := range ws {
|
|
if err := staketest.CheckValidatorWrapperEqual(*ws[wi], *test.expVWrappers[wi]); err != nil {
|
|
t.Errorf("%v wrapper: %v", wi, err)
|
|
}
|
|
}
|
|
if tReward.Cmp(test.expTotalRewards) != 0 {
|
|
t.Errorf("Test %v: total Rewards unexpected: %v / %v", i, tReward, test.expTotalRewards)
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeMsgCollectRewards() []staking.DelegationIndex {
|
|
dis := []staking.DelegationIndex{
|
|
{
|
|
ValidatorAddress: validatorAddr,
|
|
Index: 1,
|
|
BlockNum: big.NewInt(defaultBlockNumber),
|
|
}, {
|
|
ValidatorAddress: validatorAddr2,
|
|
Index: 1,
|
|
BlockNum: big.NewInt(defaultBlockNumber),
|
|
},
|
|
}
|
|
return dis
|
|
}
|
|
|
|
func makeStateForReward(t *testing.T) *state.DB {
|
|
sdb := makeStateDBForStake(t)
|
|
|
|
rewards0 := []*big.Int{reward00, reward01}
|
|
if err := addStateRewardForAddr(sdb, validatorAddr, rewards0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rewards1 := []*big.Int{reward10, reward11}
|
|
if err := addStateRewardForAddr(sdb, validatorAddr2, rewards1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sdb.IntermediateRoot(true)
|
|
return sdb
|
|
}
|
|
|
|
func addStateRewardForAddr(sdb *state.DB, addr common.Address, rewards []*big.Int) error {
|
|
w, err := sdb.ValidatorWrapper(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.Delegations = append(w.Delegations,
|
|
staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)),
|
|
)
|
|
w.Delegations[1].Undelegations = staking.Undelegations{}
|
|
w.Delegations[0].Reward = new(big.Int).Set(rewards[0])
|
|
w.Delegations[1].Reward = new(big.Int).Set(rewards[1])
|
|
|
|
return sdb.UpdateValidatorWrapper(addr, w)
|
|
}
|
|
|
|
func expVWrappersForReward() []*staking.ValidatorWrapper {
|
|
w1 := makeVWrapperByIndex(validatorIndex)
|
|
w1.Delegations = append(w1.Delegations,
|
|
staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)),
|
|
)
|
|
w1.Delegations[1].Undelegations = staking.Undelegations{}
|
|
w1.Delegations[0].Reward = new(big.Int).Set(reward00)
|
|
w1.Delegations[1].Reward = new(big.Int).SetUint64(0)
|
|
|
|
w2 := makeVWrapperByIndex(validator2Index)
|
|
w2.Delegations = append(w2.Delegations,
|
|
staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)),
|
|
)
|
|
w2.Delegations[1].Undelegations = staking.Undelegations{}
|
|
w2.Delegations[0].Reward = new(big.Int).Set(reward10)
|
|
w2.Delegations[1].Reward = new(big.Int).SetUint64(0)
|
|
return []*staking.ValidatorWrapper{&w1, &w2}
|
|
}
|
|
|
|
// makeFakeChainContextForStake makes the default fakeChainContext for staking test
|
|
func makeFakeChainContextForStake() *fakeChainContext {
|
|
ws := makeVWrappersForStake(defNumWrappersInState, defNumPubPerAddr)
|
|
return makeFakeChainContext(ws)
|
|
}
|
|
|
|
// makeStateDBForStake make the default state db for staking test
|
|
func makeStateDBForStake(t *testing.T) *state.DB {
|
|
sdb, err := newTestStateDB()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ws := makeVWrappersForStake(defNumWrappersInState, defNumPubPerAddr)
|
|
if err := updateStateValidators(sdb, ws); err != nil {
|
|
t.Fatalf("make default state: %v", err)
|
|
}
|
|
sdb.AddBalance(createValidatorAddr, hundredKOnes)
|
|
sdb.AddBalance(delegatorAddr, hundredKOnes)
|
|
|
|
sdb.IntermediateRoot(true)
|
|
|
|
return sdb
|
|
}
|
|
|
|
func updateStateValidators(sdb *state.DB, ws []*staking.ValidatorWrapper) error {
|
|
for i, w := range ws {
|
|
sdb.SetValidatorFlag(w.Address)
|
|
sdb.AddBalance(w.Address, hundredKOnes)
|
|
if err := sdb.UpdateValidatorWrapper(w.Address, w); err != nil {
|
|
return fmt.Errorf("update %v vw error: %v", i, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func makeVWrapperByIndex(index int) staking.ValidatorWrapper {
|
|
pubGetter := newBLSPubGetter(blsKeys[index*defNumPubPerAddr:])
|
|
|
|
return makeStateVWrapperFromGetter(index, defNumPubPerAddr, pubGetter)
|
|
}
|
|
|
|
func newTestStateDB() (*state.DB, error) {
|
|
return state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
|
}
|
|
|
|
// makeVWrappersForStake makes the default staking.ValidatorWrappers for
|
|
// initialization of default state db for staking test
|
|
func makeVWrappersForStake(num, numPubsPerVal int) []*staking.ValidatorWrapper {
|
|
ws := make([]*staking.ValidatorWrapper, 0, num)
|
|
pubGetter := newBLSPubGetter(blsKeys)
|
|
for i := 0; i != num; i++ {
|
|
w := makeStateVWrapperFromGetter(i, numPubsPerVal, pubGetter)
|
|
ws = append(ws, &w)
|
|
}
|
|
return ws
|
|
}
|
|
|
|
func makeStateVWrapperFromGetter(index int, numPubs int, pubGetter *BLSPubGetter) staking.ValidatorWrapper {
|
|
addr := makeTestAddr(index)
|
|
pubs := make([]bls.SerializedPublicKey, 0, numPubs)
|
|
for i := 0; i != numPubs; i++ {
|
|
pubs = append(pubs, pubGetter.getPub())
|
|
}
|
|
w := staketest.GetDefaultValidatorWrapperWithAddr(addr, pubs)
|
|
w.Identity = makeIdentityStr(index)
|
|
w.UpdateHeight = big.NewInt(defaultSnapBlockNumber)
|
|
return w
|
|
}
|
|
|
|
type BLSPubGetter struct {
|
|
keys []blsPubSigPair
|
|
index int
|
|
}
|
|
|
|
func newBLSPubGetter(keys []blsPubSigPair) *BLSPubGetter {
|
|
return &BLSPubGetter{
|
|
keys: keys,
|
|
index: 0,
|
|
}
|
|
}
|
|
|
|
func (g *BLSPubGetter) getPub() bls.SerializedPublicKey {
|
|
key := g.keys[g.index]
|
|
g.index++
|
|
return key.pub
|
|
}
|
|
|
|
// fakeChainContext is the fake structure of ChainContext for testing
|
|
type fakeChainContext struct {
|
|
vWrappers map[common.Address]*staking.ValidatorWrapper
|
|
}
|
|
|
|
func makeFakeChainContext(ws []*staking.ValidatorWrapper) *fakeChainContext {
|
|
m := make(map[common.Address]*staking.ValidatorWrapper)
|
|
for _, w := range ws {
|
|
wCpy := staketest.CopyValidatorWrapper(*w)
|
|
m[w.Address] = &wCpy
|
|
}
|
|
return &fakeChainContext{
|
|
vWrappers: m,
|
|
}
|
|
}
|
|
|
|
func (chain *fakeChainContext) ReadValidatorList() ([]common.Address, error) {
|
|
vs := make([]common.Address, 0, len(chain.vWrappers))
|
|
for addr := range chain.vWrappers {
|
|
vs = append(vs, addr)
|
|
}
|
|
return vs, nil
|
|
}
|
|
|
|
func (chain *fakeChainContext) Engine() consensus_engine.Engine {
|
|
return nil
|
|
}
|
|
|
|
func (chain *fakeChainContext) GetHeader(common.Hash, uint64) *block.Header {
|
|
return nil
|
|
}
|
|
|
|
func (chain *fakeChainContext) ReadDelegationsByDelegator(common.Address) (staking.DelegationIndexes, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (chain *fakeChainContext) ReadValidatorSnapshot(addr common.Address) (*staking.ValidatorSnapshot, error) {
|
|
w, ok := chain.vWrappers[addr]
|
|
if !ok {
|
|
return nil, fmt.Errorf("addr not exist in snapshot")
|
|
}
|
|
cp := staketest.CopyValidatorWrapper(*w)
|
|
return &staking.ValidatorSnapshot{
|
|
Validator: &cp,
|
|
Epoch: big.NewInt(defaultEpoch),
|
|
}, nil
|
|
}
|
|
|
|
type fakeErrChainContext struct{}
|
|
|
|
func (chain *fakeErrChainContext) ReadValidatorList() ([]common.Address, error) {
|
|
return nil, errors.New("error intended from chain")
|
|
}
|
|
|
|
func (chain *fakeErrChainContext) Engine() consensus_engine.Engine {
|
|
return nil
|
|
}
|
|
|
|
func (chain *fakeErrChainContext) GetHeader(common.Hash, uint64) *block.Header {
|
|
return nil
|
|
}
|
|
|
|
func (chain *fakeErrChainContext) ReadDelegationsByDelegator(common.Address) (staking.DelegationIndexes, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (chain *fakeErrChainContext) ReadValidatorSnapshot(common.Address) (*staking.ValidatorSnapshot, error) {
|
|
return nil, errors.New("error intended")
|
|
}
|
|
|
|
func makeIdentityStr(item interface{}) string {
|
|
return fmt.Sprintf("harmony-one-%v", item)
|
|
}
|
|
|
|
func makeTestAddr(item interface{}) common.Address {
|
|
s := fmt.Sprintf("harmony-one-%v", item)
|
|
return common.BytesToAddress([]byte(s))
|
|
}
|
|
|
|
func makeKeyPairs(size int) []blsPubSigPair {
|
|
pairs := make([]blsPubSigPair, 0, size)
|
|
for i := 0; i != size; i++ {
|
|
pairs = append(pairs, makeBLSKeyPair())
|
|
}
|
|
return pairs
|
|
}
|
|
|
|
type blsPubSigPair struct {
|
|
pub bls.SerializedPublicKey
|
|
sig bls.SerializedSignature
|
|
}
|
|
|
|
func makeBLSKeyPair() blsPubSigPair {
|
|
blsPriv := bls.RandPrivateKey()
|
|
blsPub := blsPriv.GetPublicKey()
|
|
msgHash := hash.Keccak256([]byte(staking.BLSVerificationStr))
|
|
sig := blsPriv.SignHash(msgHash)
|
|
|
|
var shardPub bls.SerializedPublicKey
|
|
copy(shardPub[:], blsPub.Serialize())
|
|
|
|
var shardSig bls.SerializedSignature
|
|
copy(shardSig[:], sig.Serialize())
|
|
|
|
return blsPubSigPair{shardPub, shardSig}
|
|
}
|
|
|
|
func assertError(got, expect error) error {
|
|
if (got == nil) != (expect == nil) {
|
|
return fmt.Errorf("unexpected error [%v] / [%v]", got, expect)
|
|
}
|
|
if (got == nil) || (expect == nil) {
|
|
return nil
|
|
}
|
|
if !strings.Contains(got.Error(), expect.Error()) {
|
|
return fmt.Errorf("unexpected error [%v] / [%v]", got, expect)
|
|
}
|
|
return nil
|
|
}
|
|
|