-
Notifications
You must be signed in to change notification settings - Fork 328
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
Conversation
) | ||
|
||
const ( | ||
executionProtocolID = "smart_contract" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
execution.Name()
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the concern?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
// 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) | ||
} |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
} | ||
exeAct, err := action.NewExecution( | ||
contractAddress, | ||
act.Nonce(), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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,
bd17ea8
to
0ca40b9
Compare
@@ -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) { |
There was a problem hiding this comment.
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
action/protocol/staking/protocol.go
Outdated
@@ -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) |
There was a problem hiding this comment.
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
// 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)) | ||
} |
There was a problem hiding this comment.
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
bb6f4cd
to
45ffc14
Compare
22a299c
to
65d977d
Compare
920381f
to
072f6d8
Compare
action/protocol/staking/protocol.go
Outdated
return p.handle(ctx, act, csm) | ||
} | ||
|
||
func (p *Protocol) constructCandidateStateManager(ctx context.Context, sm protocol.StateManager) (CandidateStateManager, error) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
action/protocol/staking/protocol.go
Outdated
@@ -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) |
There was a problem hiding this comment.
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 ... =
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 | ||
) |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
aa13852
to
e81cb70
Compare
} | ||
// 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
action/protocol/staking/protocol.go
Outdated
) (*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)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
e2etest/native_staking_test.go
Outdated
expect: []actionExpect{&basicActionExpect{nil, uint64(iotextypes.ReceiptStatus_ErrUnauthorizedOperator), ""}}, | ||
}, | ||
{ | ||
name: "success to migrate stake", |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
action/protocol/staking/protocol.go
Outdated
case *action.MigrateStake: | ||
var nonceUpdated bool | ||
logs, tLogs, gasConsumed, nonceUpdated, err = p.handleStakeMigrate(ctx, act, csm) | ||
if nonceUpdated { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, | ||
}, |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
action/protocol/managers.go
Outdated
// until the candidate state manager and candidate state reader are refactored | ||
StateManagerWrapper struct { | ||
StateReader | ||
} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 (
)
Quality Gate passedIssues Measures |
Description
Introduce the new action
MigrateStake
to migrate a native bucket to nft bucket with same stake amount and durationbased on #4305 , iotexproject/iotex-proto#153
Type of change
Please delete options that are not relevant.
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
Test Configuration:
Checklist: