add support for redelegation and bring back 7 epoch locking time (#3296)

* add support for redelegation

* Add test

* Revert "[go.mod] version upgrade of libp2p modules"

This reverts commit ce53468e3e.

* Fix bug that validator self delegation doesn't work

* Restore 7 epoch locking time at redelegation epoch

* Add debug log for chain context
pull/3303/head
Rongjian Lan 4 years ago committed by GitHub
parent 7a6fd23ff8
commit f2fa88a188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 105
      core/staking_verifier.go
  2. 208
      core/staking_verifier_test.go
  3. 15
      core/state_transition.go
  4. 17
      core/tx_pool.go
  5. 4
      internal/chain/engine.go
  6. 17
      internal/params/config.go

@ -200,8 +200,8 @@ var (
//
// Note that this function never updates the stateDB, it only reads from stateDB.
func VerifyAndDelegateFromMsg(
stateDB vm.StateDB, msg *staking.Delegate,
) (*staking.ValidatorWrapper, *big.Int, error) {
stateDB vm.StateDB, epoch *big.Int, msg *staking.Delegate, delegations []staking.DelegationIndex, redelegation bool,
) ([]*staking.ValidatorWrapper, *big.Int, error) {
if stateDB == nil {
return nil, nil, errStateDBIsMissing
}
@ -214,40 +214,111 @@ func VerifyAndDelegateFromMsg(
if msg.Amount.Cmp(minimumDelegation) < 0 {
return nil, nil, errDelegationTooSmall
}
wrapper, err := stateDB.ValidatorWrapperCopy(msg.ValidatorAddress)
updatedValidatorWrappers := []*staking.ValidatorWrapper{}
delegateBalance := big.NewInt(0).Set(msg.Amount)
var delegateeWrapper *staking.ValidatorWrapper
if redelegation {
// Check if we can use tokens in undelegation to delegate (redelegate)
for i := range delegations {
delegationIndex := &delegations[i]
wrapper, err := stateDB.ValidatorWrapperCopy(delegationIndex.ValidatorAddress)
if err != nil {
return 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, errors.New("Delegation index out of bound")
}
// Check if there is enough liquid token to delegate
if !CanTransfer(stateDB, msg.DelegatorAddress, msg.Amount) {
return nil, nil, errors.Wrapf(
errInsufficientBalanceForStake, "had %v, tried to stake %v",
stateDB.GetBalance(msg.DelegatorAddress), msg.Amount)
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
}
}
// Check for existing delegation
for i := range wrapper.Delegations {
delegation := &wrapper.Delegations[i]
if startBalance.Cmp(delegateBalance) > 0 {
// Used undelegated token for redelegation
delegation.Undelegations = delegation.Undelegations[curIndex:]
if err := wrapper.SanityCheck(); err != nil {
return nil, nil, err
}
if bytes.Equal(delegationIndex.ValidatorAddress[:], msg.ValidatorAddress[:]) {
delegateeWrapper = wrapper
}
updatedValidatorWrappers = append(updatedValidatorWrappers, wrapper)
}
}
}
if delegateeWrapper == nil {
var err error
delegateeWrapper, err = stateDB.ValidatorWrapperCopy(msg.ValidatorAddress)
if err != nil {
return 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 := wrapper.SanityCheck(); err != nil {
if err := delegateeWrapper.SanityCheck(); err != nil {
return nil, nil, err
}
return wrapper, msg.Amount, nil
found = true
}
}
if !found {
// Add new delegation
wrapper.Delegations = append(
wrapper.Delegations, staking.NewDelegation(
delegateeWrapper.Delegations = append(
delegateeWrapper.Delegations, staking.NewDelegation(
msg.DelegatorAddress, msg.Amount,
),
)
if err := wrapper.SanityCheck(); err != nil {
if err := delegateeWrapper.SanityCheck(); err != nil {
return nil, nil, err
}
return wrapper, msg.Amount, nil
}
if delegateBalance.Cmp(big.NewInt(0)) == 0 {
// delegation fully from undelegated tokens, no need to deduct from balance.
return updatedValidatorWrappers, big.NewInt(0), 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, 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, nil
}
// VerifyAndUndelegateFromMsg verifies the undelegate validator message

@ -723,8 +723,11 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) {
tests := []struct {
sdb vm.StateDB
msg staking.Delegate
ds []staking.DelegationIndex
epoch *big.Int
redelegate bool
expVWrapper staking.ValidatorWrapper
expVWrappers []staking.ValidatorWrapper
expAmt *big.Int
expErr error
}{
@ -732,33 +735,45 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) {
// 0: new delegate
sdb: makeStateDBForStake(t),
msg: defaultMsgDelegate(),
ds: makeMsgCollectRewards(),
epoch: big.NewInt(0),
redelegate: false,
expVWrapper: defaultExpVWrapperDelegate(),
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,
expVWrapper: defaultExpVWrapperSelfDelegate(),
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,
},
@ -770,6 +785,9 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) {
msg.Amount = big.NewInt(-1)
return msg
}(),
ds: makeMsgCollectRewards(),
epoch: big.NewInt(0),
redelegate: false,
expErr: errNegativeAmount,
},
@ -781,6 +799,9 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) {
msg.Amount = big.NewInt(100)
return msg
}(),
ds: makeMsgCollectRewards(),
epoch: big.NewInt(0),
redelegate: false,
expErr: errDelegationTooSmall,
},
@ -796,6 +817,9 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) {
d.ValidatorAddress = createValidatorAddr
return d
}(),
ds: makeMsgCollectRewards(),
epoch: big.NewInt(0),
redelegate: false,
expErr: errors.New("address not present in state"),
},
@ -807,6 +831,9 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) {
return sdb
}(),
msg: defaultMsgDelegate(),
ds: makeMsgCollectRewards(),
epoch: big.NewInt(0),
redelegate: false,
expErr: errInsufficientBalanceForStake,
},
@ -818,6 +845,9 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) {
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"),
},
@ -829,12 +859,137 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) {
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),
},
{
// 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,
},
{
// 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,
},
{
// 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 {
w, amt, err := VerifyAndDelegateFromMsg(test.sdb, &test.msg)
ws, amt, 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)
@ -846,11 +1001,54 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) {
if amt.Cmp(test.expAmt) != 0 {
t.Errorf("Test %v: unexpected amount %v / %v", i, amt, test.expAmt)
}
if err := staketest.CheckValidatorWrapperEqual(*w, test.expVWrapper); err != nil {
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{

@ -416,14 +416,25 @@ func (st *StateTransition) verifyAndApplyEditValidatorTx(
}
func (st *StateTransition) verifyAndApplyDelegateTx(delegate *staking.Delegate) error {
wrapper, balanceToBeDeducted, err := VerifyAndDelegateFromMsg(st.state, delegate)
delegations, err := st.bc.ReadDelegationsByDelegator(delegate.DelegatorAddress)
if err != nil {
return err
}
updatedValidatorWrappers, balanceToBeDeducted, err := VerifyAndDelegateFromMsg(
st.state, st.evm.EpochNumber, delegate, delegations, st.evm.ChainConfig().IsRedelegation(st.evm.EpochNumber))
if err != nil {
return err
}
for _, wrapper := range updatedValidatorWrappers {
if err := st.state.UpdateValidatorWrapper(wrapper.Address, wrapper); err != nil {
return err
}
}
st.state.SubBalance(delegate.DelegatorAddress, balanceToBeDeducted)
return st.state.UpdateValidatorWrapper(wrapper.Address, wrapper)
return nil
}
func (st *StateTransition) verifyAndApplyUndelegateTx(

@ -823,7 +823,21 @@ func (pool *TxPool) validateStakingTx(tx *staking.StakingTransaction) error {
return errors.WithMessagef(ErrInvalidSender, "staking transaction sender is %s", b32)
}
_, _, err = VerifyAndDelegateFromMsg(pool.currentState, stkMsg)
chain, ok := pool.chain.(ChainContext)
if !ok {
utils.Logger().Debug().Msg("Missing chain context in txPool")
return nil // for testing, chain could be testing blockchain
}
delegations, err := chain.ReadDelegationsByDelegator(stkMsg.DelegatorAddress)
if err != nil {
return err
}
pendingEpoch := pool.chain.CurrentBlock().Epoch()
if shard.Schedule.IsLastBlock(pool.chain.CurrentBlock().Number().Uint64()) {
pendingEpoch = new(big.Int).Add(pendingEpoch, big.NewInt(1))
}
_, _, err = VerifyAndDelegateFromMsg(
pool.currentState, pendingEpoch, stkMsg, delegations, pool.chainconfig.IsRedelegation(pendingEpoch))
return err
case staking.DirectiveUndelegate:
msg, err := staking.RLPDecodeStakeMsg(tx.Data(), staking.DirectiveUndelegate)
@ -858,6 +872,7 @@ func (pool *TxPool) validateStakingTx(tx *staking.StakingTransaction) error {
}
chain, ok := pool.chain.(ChainContext)
if !ok {
utils.Logger().Debug().Msg("Missing chain context in txPool")
return nil // for testing, chain could be testing blockchain
}
delegations, err := chain.ReadDelegationsByDelegator(stkMsg.DelegatorAddress)

@ -293,7 +293,9 @@ func payoutUndelegations(
)
}
lockPeriod := staking.LockPeriodInEpoch
if chain.Config().IsQuickUnlock(header.Epoch()) {
if chain.Config().IsRedelegation(header.Epoch()) {
lockPeriod = staking.LockPeriodInEpoch
} else if chain.Config().IsQuickUnlock(header.Epoch()) {
lockPeriod = staking.LockPeriodInEpochV2
}
for i := range wrapper.Delegations {

@ -32,6 +32,7 @@ var (
PreStakingEpoch: big.NewInt(185),
QuickUnlockEpoch: big.NewInt(191),
FiveSecondsEpoch: big.NewInt(230),
RedelegationEpoch: big.NewInt(10000000),
EIP155Epoch: big.NewInt(28),
S3Epoch: big.NewInt(28),
ReceiptLogEpoch: big.NewInt(101),
@ -46,6 +47,7 @@ var (
PreStakingEpoch: big.NewInt(1),
QuickUnlockEpoch: big.NewInt(0),
FiveSecondsEpoch: big.NewInt(16500),
RedelegationEpoch: big.NewInt(10000000),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
ReceiptLogEpoch: big.NewInt(0),
@ -61,6 +63,7 @@ var (
PreStakingEpoch: big.NewInt(1),
QuickUnlockEpoch: big.NewInt(0),
FiveSecondsEpoch: big.NewInt(0),
RedelegationEpoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
ReceiptLogEpoch: big.NewInt(0),
@ -76,6 +79,7 @@ var (
PreStakingEpoch: big.NewInt(1),
QuickUnlockEpoch: big.NewInt(0),
FiveSecondsEpoch: big.NewInt(0),
RedelegationEpoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
ReceiptLogEpoch: big.NewInt(0),
@ -91,6 +95,7 @@ var (
PreStakingEpoch: big.NewInt(1),
QuickUnlockEpoch: big.NewInt(0),
FiveSecondsEpoch: big.NewInt(0),
RedelegationEpoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
ReceiptLogEpoch: big.NewInt(0),
@ -105,6 +110,7 @@ var (
PreStakingEpoch: big.NewInt(0),
QuickUnlockEpoch: big.NewInt(0),
FiveSecondsEpoch: big.NewInt(0),
RedelegationEpoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
ReceiptLogEpoch: big.NewInt(0),
@ -121,6 +127,7 @@ var (
big.NewInt(0), // PreStakingEpoch
big.NewInt(0), // QuickUnlockEpoch
big.NewInt(0), // FiveSecondsEpoch
big.NewInt(0), // RedelegationEpoch
big.NewInt(0), // EIP155Epoch
big.NewInt(0), // S3Epoch
big.NewInt(0), // ReceiptLogEpoch
@ -137,6 +144,7 @@ var (
big.NewInt(0), // PreStakingEpoch
big.NewInt(0), // QuickUnlockEpoch
big.NewInt(0), // FiveSecondsEpoch
big.NewInt(0), // RedelegationEpoch
big.NewInt(0), // EIP155Epoch
big.NewInt(0), // S3Epoch
big.NewInt(0), // ReceiptLogEpoch
@ -188,6 +196,10 @@ type ChainConfig struct {
// and block rewards adjusted to 17.5 ONE/block
FiveSecondsEpoch *big.Int `json:"five-seconds-epoch,omitempty"`
// RedelegationEpoch is the epoch when redelegation is supported and undelegation locking time
// is restored to 7 epoch
RedelegationEpoch *big.Int `json:"redelegation-epoch,omitempty"`
// EIP155 hard fork epoch (include EIP158 too)
EIP155Epoch *big.Int `json:"eip155-epoch,omitempty"`
@ -245,6 +257,11 @@ func (c *ChainConfig) IsFiveSeconds(epoch *big.Int) bool {
return isForked(c.FiveSecondsEpoch, epoch)
}
// IsRedelegation determines whether it is the epoch to support redelegation
func (c *ChainConfig) IsRedelegation(epoch *big.Int) bool {
return isForked(c.RedelegationEpoch, epoch)
}
// IsPreStaking determines whether staking transactions are allowed
func (c *ChainConfig) IsPreStaking(epoch *big.Int) bool {
return isForked(c.PreStakingEpoch, epoch)

Loading…
Cancel
Save