diff --git a/core/staking_verifier.go b/core/staking_verifier.go index e4aba938d..761ebd050 100644 --- a/core/staking_verifier.go +++ b/core/staking_verifier.go @@ -201,22 +201,23 @@ var ( // 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, redelegation bool, -) ([]*staking.ValidatorWrapper, *big.Int, error) { +) ([]*staking.ValidatorWrapper, *big.Int, map[common.Address]*big.Int, error) { if stateDB == nil { - return nil, nil, errStateDBIsMissing + return nil, nil, nil, errStateDBIsMissing } if !stateDB.IsValidator(msg.ValidatorAddress) { - return nil, nil, errValidatorNotExist + return nil, nil, nil, errValidatorNotExist } if msg.Amount.Sign() == -1 { - return nil, nil, errNegativeAmount + return nil, nil, nil, errNegativeAmount } if msg.Amount.Cmp(minimumDelegation) < 0 { - return nil, nil, errDelegationTooSmall + 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 redelegation { @@ -225,7 +226,7 @@ func VerifyAndDelegateFromMsg( delegationIndex := &delegations[i] wrapper, err := stateDB.ValidatorWrapperCopy(delegationIndex.ValidatorAddress) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if uint64(len(wrapper.Delegations)) <= delegationIndex.Index { utils.Logger().Warn(). @@ -233,7 +234,7 @@ func VerifyAndDelegateFromMsg( 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") + return nil, nil, nil, errors.New("Delegation index out of bound") } delegation := &wrapper.Delegations[delegationIndex.Index] @@ -260,13 +261,14 @@ func VerifyAndDelegateFromMsg( // Used undelegated token for redelegation delegation.Undelegations = delegation.Undelegations[curIndex:] if err := wrapper.SanityCheck(); err != nil { - return nil, nil, err + 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) } } } @@ -275,7 +277,7 @@ func VerifyAndDelegateFromMsg( var err error delegateeWrapper, err = stateDB.ValidatorWrapperCopy(msg.ValidatorAddress) if err != nil { - return nil, nil, err + return nil, nil, nil, err } updatedValidatorWrappers = append(updatedValidatorWrappers, delegateeWrapper) } @@ -287,7 +289,7 @@ func VerifyAndDelegateFromMsg( 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, err + return nil, nil, nil, err } found = true } @@ -301,24 +303,24 @@ func VerifyAndDelegateFromMsg( ), ) if err := delegateeWrapper.SanityCheck(); err != nil { - return nil, nil, err + 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), nil + 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, errors.Wrapf( + 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, nil + return updatedValidatorWrappers, delegateBalance, fromLockedTokens, nil } // VerifyAndUndelegateFromMsg verifies the undelegate validator message diff --git a/core/staking_verifier_test.go b/core/staking_verifier_test.go index 5be7fb0bc..41607c848 100644 --- a/core/staking_verifier_test.go +++ b/core/staking_verifier_test.go @@ -729,6 +729,7 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) { expVWrappers []staking.ValidatorWrapper expAmt *big.Int + expRedel map[common.Address]*big.Int expErr error }{ { @@ -875,6 +876,10 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) { 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 @@ -892,6 +897,9 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) { return wrappers }(), expAmt: fiveKOnes, + expRedel: map[common.Address]*big.Int{ + validatorAddr: fiveKOnes, + }, }, { // 12: redelegate with not enough undelegated token, have to use some balance @@ -911,6 +919,10 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) { return wrappers }(), expAmt: tenKOnes, + expRedel: map[common.Address]*big.Int{ + validatorAddr: fiveKOnes, + validatorAddr2: fiveKOnes, + }, }, { // 13: no redelegation and full balance used @@ -989,7 +1001,7 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) { }, } for i, test := range tests { - ws, amt, err := VerifyAndDelegateFromMsg(test.sdb, test.epoch, &test.msg, test.ds, test.redelegate) + 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) @@ -1002,6 +1014,19 @@ func TestVerifyAndDelegateFromMsg(t *testing.T) { 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) diff --git a/core/state_transition.go b/core/state_transition.go index 2184001db..b2fc22dc5 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -364,16 +364,7 @@ func (st *StateTransition) StakingTransitionDb() (usedGas uint64, err error) { if msg.From() != stkMsg.DelegatorAddress { return 0, errInvalidSigner } - collectedRewards, tempErr := st.verifyAndApplyCollectRewards(stkMsg) - err = tempErr - if err == nil { - st.state.AddLog(&types.Log{ - Address: stkMsg.DelegatorAddress, - Topics: []common.Hash{staking2.CollectRewardsTopic}, - Data: collectedRewards.Bytes(), - BlockNumber: st.evm.BlockNumber.Uint64(), - }) - } + _, err = st.verifyAndApplyCollectRewards(stkMsg) default: return 0, staking.ErrInvalidStakingKind } @@ -420,7 +411,7 @@ func (st *StateTransition) verifyAndApplyDelegateTx(delegate *staking.Delegate) if err != nil { return err } - updatedValidatorWrappers, balanceToBeDeducted, err := VerifyAndDelegateFromMsg( + updatedValidatorWrappers, balanceToBeDeducted, fromLockedTokens, err := VerifyAndDelegateFromMsg( st.state, st.evm.EpochNumber, delegate, delegations, st.evm.ChainConfig().IsRedelegation(st.evm.EpochNumber)) if err != nil { return err @@ -434,6 +425,22 @@ func (st *StateTransition) verifyAndApplyDelegateTx(delegate *staking.Delegate) st.state.SubBalance(delegate.DelegatorAddress, balanceToBeDeducted) + // Add log if everything is good + for valAddr, redelegatedToken := range fromLockedTokens { + encodedRedelegationData := []byte{} + addrBytes := valAddr.Bytes() + encodedRedelegationData = append(encodedRedelegationData, addrBytes...) + encodedRedelegationData = append(encodedRedelegationData, redelegatedToken.Bytes()...) + // The data field format is: + // [first 20 bytes]: Validator address from which the locked token is used for redelegation. + // [rest of the bytes]: the bigInt serialized bytes for the token amount. + st.state.AddLog(&types.Log{ + Address: delegate.DelegatorAddress, + Topics: []common.Hash{staking2.DelegateTopic}, + Data: encodedRedelegationData, + BlockNumber: st.evm.BlockNumber.Uint64(), + }) + } return nil } @@ -467,5 +474,14 @@ func (st *StateTransition) verifyAndApplyCollectRewards(collectRewards *staking. } } st.state.AddBalance(collectRewards.DelegatorAddress, totalRewards) + + // Add log if everything is good + st.state.AddLog(&types.Log{ + Address: collectRewards.DelegatorAddress, + Topics: []common.Hash{staking2.CollectRewardsTopic}, + Data: totalRewards.Bytes(), + BlockNumber: st.evm.BlockNumber.Uint64(), + }) + return totalRewards, nil } diff --git a/core/tx_pool.go b/core/tx_pool.go index 634cdd650..956d66d03 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -836,7 +836,7 @@ func (pool *TxPool) validateStakingTx(tx *staking.StakingTransaction) error { if shard.Schedule.IsLastBlock(pool.chain.CurrentBlock().Number().Uint64()) { pendingEpoch = new(big.Int).Add(pendingEpoch, big.NewInt(1)) } - _, _, err = VerifyAndDelegateFromMsg( + _, _, _, err = VerifyAndDelegateFromMsg( pool.currentState, pendingEpoch, stkMsg, delegations, pool.chainconfig.IsRedelegation(pendingEpoch)) return err case staking.DirectiveUndelegate: diff --git a/staking/params.go b/staking/params.go index f9bb8f5d5..aef5c64d5 100644 --- a/staking/params.go +++ b/staking/params.go @@ -8,6 +8,7 @@ const ( isValidatorKeyStr = "Harmony/IsValidator/Key/v1" isValidatorStr = "Harmony/IsValidator/Value/v1" collectRewardsStr = "Harmony/CollectRewards" + delegateStr = "Harmony/Delegate" ) // keys used to retrieve staking related informatio @@ -15,4 +16,5 @@ var ( IsValidatorKey = crypto.Keccak256Hash([]byte(isValidatorKeyStr)) IsValidator = crypto.Keccak256Hash([]byte(isValidatorStr)) CollectRewardsTopic = crypto.Keccak256Hash([]byte(collectRewardsStr)) + DelegateTopic = crypto.Keccak256Hash([]byte(delegateStr)) )