Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[action] New MigrateStake Action #4299

Merged
merged 21 commits into from
Jun 19, 2024
Merged

[action] New MigrateStake Action #4299

merged 21 commits into from
Jun 19, 2024

Conversation

envestcc
Copy link
Member

@envestcc envestcc commented Jun 11, 2024

Description

Introduce the new action MigrateStake to migrate a native bucket to nft bucket with same stake amount and duration

based on #4305 , iotexproject/iotex-proto#153

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • [] make test
  • [] fullsync
  • [] Other test (please specify)

Test Configuration:

  • Firmware version:
  • Hardware:
  • Toolchain:
  • SDK:

Checklist:

  • [] My code follows the style guidelines of this project
  • [] I have performed a self-review of my code
  • [] I have commented my code, particularly in hard-to-understand areas
  • [] I have made corresponding changes to the documentation
  • [] My changes generate no new warnings
  • [] I have added tests that prove my fix is effective or that my feature works
  • [] New and existing unit tests pass locally with my changes
  • [] Any dependent changes have been merged and published in downstream modules

@envestcc envestcc changed the title [action] New StakeMigrate Action [action] New MigrateStake Action Jun 11, 2024
)

const (
executionProtocolID = "smart_contract"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execution.Name()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import execution pkg will cause cycle-dependency

)

type (
executionProtocol interface {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no difference from protocol.ActionHandler

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to call HandleCrossProtocol, which will reset caller nonce, avoiding nonce set error

return nil, nil, 0, errors.Wrap(err, "failed to handle execution action")
}
if excReceipt.Status != uint64(iotextypes.ReceiptStatus_Success) {
// TODO: return err or handle error?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what kind of error could be handled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether return error or receipt with faliure status if contract call status is not success, but we can keep it simply to return error

// add sub-receipts logs
actLogs = append(actLogs, excReceipt.Logs()...)
transferLogs = append(transferLogs, excReceipt.TransactionLogs()...)
return actLogs, transferLogs, excReceipt.GasConsumed, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gasConsumed + 10000, 10000 is the base fee to withdraw a native bucket

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

if err := validateBucketOwner(bucket, protocol.MustGetActionCtx(ctx).Caller); err != nil {
return err
}
if !bucket.AutoStake {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the concern?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If unlocked is permitted, an additional unlock contract call is required, which may makes the code more complex.

failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
}
}
// clear candidate's self stake if the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self stake bucket cannot be migrated

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, here is to clear stale status(e.g. endorsement has been expired)

Comment on lines +107 to +135
// delete bucket and bucket index
if err := csm.delBucketAndIndex(bucket.Owner, bucket.Candidate, bucket.Index); err != nil {
return nil, nil, errors.Wrapf(err, "failed to delete bucket for candidate %s", bucket.Candidate.String())
}

// update bucket pool
if err := csm.CreditBucketPool(bucket.StakedAmount); err != nil {
return nil, nil, errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error())
}
// update candidate vote
weightedVote := p.calculateVoteWeight(bucket, false)
if err := cand.SubVote(weightedVote); err != nil {
return nil, nil, &handleError{
err: errors.Wrapf(err, "failed to subtract vote for candidate %s", bucket.Candidate.String()),
failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
}
}
// clear candidate's self stake if the
if cand.SelfStakeBucketIdx == bucket.Index {
cand.SelfStake = big.NewInt(0)
cand.SelfStakeBucketIdx = candidateNoSelfStakeBucketIndex
}
if err := csm.Upsert(cand); err != nil {
return nil, nil, csmErrorToHandleError(cand.GetIdentifier().String(), err)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we wrap it into a function defined in csm?

return validateBucketSelfStake(protocol.MustGetFeatureCtx(ctx), csm, bucket, false)
}

func (p *Protocol) withdrawBucket(ctx context.Context, withdrawer *state.Account, bucket *VoteBucket, cand *Candidate, csm CandidateStateManager) (*receiptLog, *action.TransactionLog, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

share with withdraw bucket action

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not apply to handle withdraw action, as it involves the reduction of votes which is handled in unstake action

action/protocol/staking/handler_stake_migrate.go Outdated Show resolved Hide resolved
}
exeAct, err := action.NewExecution(
contractAddress,
act.Nonce(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is nonce important? if not, can we leave it as 0?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's critical to SetNonce of the caller,

@@ -83,6 +84,25 @@ func (p *Protocol) Handle(ctx context.Context, act action.Action, sm protocol.St
return receipt, nil
}

// HandleCrossProtocol handles an execution from another protocol
func (p *Protocol) HandleCrossProtocol(ctx context.Context, act action.Action, sm protocol.StateManager) (*action.Receipt, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may have a function for this protocol only as

    return p.handle(ctx, exec, sm)
}

and this function could be reused by Handle

@@ -427,22 +439,25 @@ func (p *Protocol) handle(ctx context.Context, act action.Action, csm CandidateS
rLog, tLogs, err = p.handleCandidateEndorsement(ctx, act, csm)
case *action.CandidateTransferOwnership:
rLog, tLogs, err = p.handleCandidateTransferOwnership(ctx, act, csm)
case *action.MigrateStake:
logs, tLogs, gasConsumed, err = p.handleStakeMigrate(ctx, act, csm)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settleAction and skip setting nonce

Comment on lines 94 to 102
// reset caller nonce
acc, err := accountutil.AccountState(ctx, sm, protocol.MustGetActionCtx(ctx).Caller)
if err != nil {
log.L().Panic("failed to get account state", zap.Error(err))
}
acc.DecreaseNonce()
if err := accountutil.StoreAccount(sm, protocol.MustGetActionCtx(ctx).Caller, acc); err != nil {
log.L().Panic("failed to store account", zap.Error(err))
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thus, we can skip nonce reset

@envestcc envestcc force-pushed the native-migration branch 2 times, most recently from bb6f4cd to 45ffc14 Compare June 17, 2024 03:47
@envestcc envestcc marked this pull request as ready for review June 17, 2024 04:11
@envestcc envestcc requested review from dustinxie, Liuhaai and a team as code owners June 17, 2024 04:11
@envestcc envestcc force-pushed the native-migration branch 2 times, most recently from 22a299c to 65d977d Compare June 17, 2024 04:30
@envestcc envestcc force-pushed the native-migration branch 2 times, most recently from 920381f to 072f6d8 Compare June 18, 2024 01:31
return p.handle(ctx, act, csm)
}

func (p *Protocol) constructCandidateStateManager(ctx context.Context, sm protocol.StateManager) (CandidateStateManager, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why need this change? seems no real change instead of just adding this new func?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's reused in two places

@@ -433,22 +453,29 @@ func (p *Protocol) handle(ctx context.Context, act action.Action, csm CandidateS
rLog, tLogs, err = p.handleCandidateEndorsement(ctx, act, csm)
case *action.CandidateTransferOwnership:
rLog, tLogs, err = p.handleCandidateTransferOwnership(ctx, act, csm)
case *action.MigrateStake:
var nonceUpdated bool
logs, tLogs, gasConsumed, nonceUpdated, err = p.handleStakeMigrate(ctx, act, csm)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not follow other actions rLog, tLogs ... =

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rLog can only represent log for one address, whereas here it will include logs for multiple addresses (i.e. NativeStakingAddress and ContractStakingAddress).


if l := rLog.Build(ctx, err); l != nil {
logs = append(logs, l)
if rLog != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and use the same rLog.Build() here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as explained above

const (
updateNonce nonceUpdateType = true
noUpdateNonce nonceUpdateType = false
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feel there's no need for this, using bool is enough, the following logic should be clear

if nonceUpdated {
   // updated in createNFTBucket, no need to update again in settleAction
   nonceUpdated = false
}

}
if err := accountutil.StoreAccount(sm, actionCtx.Caller, acc); err != nil {
return nil, errors.Wrap(err, "failed to update nonce")
if updateNonce {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so if thecreateNFTBucket executed successfully, the nonce is increased by EVM, then we don't increase nonce again? would that be considered the same nonce (actionCtx.Nonce + 1) did 2 actions: withdraw bucket + create NFT bucket?

Copy link
Member Author

@envestcc envestcc Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only one action, not two from user perspective , so we should set nonce+1

}
r := action.Receipt{
Status: status,
BlockHeight: blkCtx.BlockHeight,
ActionHash: actionCtx.ActionHash,
GasConsumed: actionCtx.IntrinsicGas,
GasConsumed: gasConsumed,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should still use IntrinsicGas? the EVM part gas is consumed inside EVM?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gasConsumed of Receipt should represent the total gas consumed in the StakeMigrate action, so it should include gas in evm

}
// add sub-receipts logs
actLogs = append(actLogs, excReceipt.Logs()...)
transferLogs = append(transferLogs, excReceipt.TransactionLogs()...)
return actLogs, transferLogs, excReceipt.GasConsumed + insGas, nonceUpdated, nil
return actLogs, transferLogs, insGas, nonceUpdated, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return actLogs, transferLogs, insGas, true, nil
it seems that if err != nil, nonceUpdated is always false, else it is always true.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, seems like nonceUpdated is not necessary, and just not update nonce again if returned error == nil

@@ -284,7 +310,7 @@ func TestHandleStakeMigrate(t *testing.T) {
r.Equal(uint64(iotextypes.ReceiptStatus_Success), receipts[0].Status)
// gas = instrinsic + contract call
instriGas, _ := act.IntrinsicGas()
r.Equal(receipt.GasConsumed+instriGas, receipts[0].GasConsumed)
r.Equal(instriGas, receipts[0].GasConsumed)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

receipts[0].GasConsumed should be intrinsicGas + execution.GasConsumed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

) (*action.Receipt, error) {
actionCtx := protocol.MustGetActionCtx(ctx)
blkCtx := protocol.MustGetBlockCtx(ctx)
gasFee := big.NewInt(0).Mul(actionCtx.GasPrice, big.NewInt(0).SetUint64(actionCtx.IntrinsicGas))
gasFee := big.NewInt(0).Mul(actionCtx.GasPrice, big.NewInt(0).SetUint64(gasConsumed))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gas reported in receipt is different from the gas to be charged. the gas to be charged here is an extra gas

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, has separated into the gasConsumed and gasToBeDeducted

expect: []actionExpect{&basicActionExpect{nil, uint64(iotextypes.ReceiptStatus_ErrUnauthorizedOperator), ""}},
},
{
name: "success to migrate stake",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the e2e test doesn't covered the gas consumption calculation, which should be:
the balance diff of the sender account is equal to intrinsic gas + evm gas, and the value is also identical to receipt.GasConsumed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

case *action.MigrateStake:
var nonceUpdated bool
logs, tLogs, gasConsumed, nonceUpdated, err = p.handleStakeMigrate(ctx, act, csm)
if nonceUpdated {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if err == nil {
    nonceUpdateOption = noUpdateNonce
}

@@ -3,7 +3,7 @@
// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
// This source code is governed by Apache License 2.0 that can be found in the LICENSE file.

package execution
package execution_test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why need to change this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to solve import cycle issue between execution and staking

),
action: MustNoErrorV(NewMigrateStake(nonce, 1, gasLimit, gasPrice)),
builder: elpbuilder.BuildStakingAction,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a testcase in TestEthTxDecodeVerify, which tests more


func (p *Protocol) ConstructExecution(ctx context.Context, act *action.MigrateStake, sr protocol.StateReader) (*action.Execution, error) {
sm := protocol.NewStateManagerWrapper(sr)
csm, err := p.constructCandidateStateManager(ctx, sm)
Copy link
Member

@dustinxie dustinxie Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think all you need is:

csr, err := ConstructBaseView(sr)

so we can remove all the constructCandidateStateManager and NewStateManagerWrapper code

Copy link
Member

@dustinxie dustinxie Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and with this change:

func (p *Protocol) fetchBucket(bucketGetter BucketGetByIndex, index uint64) (*VoteBucket, ReceiptError) {

}

func (p *Protocol) createNFTBucket(ctx context.Context, exeAct *action.Execution, sm protocol.StateManager) (*action.Receipt, error) {
exctPtl := execution.FindProtocol(protocol.MustGetRegistry(ctx))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this part can move to Start() to get the exctPtl and keep it, and directly use it here

// until the candidate state manager and candidate state reader are refactored
StateManagerWrapper struct {
StateReader
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no needed, so comments above

// force-withdraw native bucket
actLog, tLog, err := p.withdrawBucket(ctx, staker, bucket, candidate, csm)
if err != nil {
return nil, nil, gasConsumed, gasToBeDeducted, err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to revertSM() here?
if the last error to put action caller into SM failed, it won't cause change to SM?

return nil, nil, 0, 0, err
}
gasConsumed := insGas
gasToBeDeducted := insGas
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

group these 2 and above 2 logs into

var (
)

Copy link

sonarcloud bot commented Jun 19, 2024

Quality Gate Passed Quality Gate passed

Issues
3 New issues
0 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.8% Duplication on New Code

See analysis details on SonarCloud

@envestcc envestcc merged commit 5a6871d into master Jun 19, 2024
3 checks passed
@envestcc envestcc deleted the native-migration branch June 19, 2024 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants