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

[staking] handle typedCandidate registration #3902

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 125 additions & 20 deletions action/protocol/staking/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ import (

// constants
const (
HandleCreateStake = "createStake"
HandleUnstake = "unstake"
HandleWithdrawStake = "withdrawStake"
HandleChangeCandidate = "changeCandidate"
HandleTransferStake = "transferStake"
HandleDepositToStake = "depositToStake"
HandleRestake = "restake"
HandleCandidateRegister = "candidateRegister"
HandleCandidateUpdate = "candidateUpdate"
HandleCreateStake = "createStake"
HandleUnstake = "unstake"
HandleWithdrawStake = "withdrawStake"
HandleChangeCandidate = "changeCandidate"
HandleTransferStake = "transferStake"
HandleDepositToStake = "depositToStake"
HandleRestake = "restake"
HandleCandidateRegister = "candidateRegister"
HandleCandidateUpdate = "candidateUpdate"
HandleTypedCandidateRegister = "typedCandidateRegister"
)

const _withdrawWaitingTime = 14 * 24 * time.Hour // to maintain backward compatibility with r0.11 code
Expand Down Expand Up @@ -66,7 +67,7 @@ func (p *Protocol) handleCreateStake(ctx context.Context, act *action.CreateStak
featureCtx := protocol.MustGetFeatureCtx(ctx)
log := newReceiptLog(p.addr.String(), HandleCreateStake, featureCtx.NewStakingReceiptFormat)

staker, fetchErr := fetchCaller(ctx, csm, act.Amount())
staker, fetchErr := fetchCaller(ctx, csm.SM(), act.Amount())
if fetchErr != nil {
return log, nil, fetchErr
}
Expand Down Expand Up @@ -136,7 +137,7 @@ func (p *Protocol) handleUnstake(ctx context.Context, act *action.Unstake, csm C
featureCtx := protocol.MustGetFeatureCtx(ctx)
log := newReceiptLog(p.addr.String(), HandleUnstake, featureCtx.NewStakingReceiptFormat)

_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
_, fetchErr := fetchCaller(ctx, csm.SM(), big.NewInt(0))
if fetchErr != nil {
return log, fetchErr
}
Expand Down Expand Up @@ -205,7 +206,7 @@ func (p *Protocol) handleWithdrawStake(ctx context.Context, act *action.Withdraw
featureCtx := protocol.MustGetFeatureCtx(ctx)
log := newReceiptLog(p.addr.String(), HandleWithdrawStake, featureCtx.NewStakingReceiptFormat)

withdrawer, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
withdrawer, fetchErr := fetchCaller(ctx, csm.SM(), big.NewInt(0))
if fetchErr != nil {
return log, nil, fetchErr
}
Expand Down Expand Up @@ -282,7 +283,7 @@ func (p *Protocol) handleChangeCandidate(ctx context.Context, act *action.Change
featureCtx := protocol.MustGetFeatureCtx(ctx)
log := newReceiptLog(p.addr.String(), HandleChangeCandidate, featureCtx.NewStakingReceiptFormat)

_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
_, fetchErr := fetchCaller(ctx, csm.SM(), big.NewInt(0))
if fetchErr != nil {
return log, fetchErr
}
Expand Down Expand Up @@ -365,7 +366,7 @@ func (p *Protocol) handleTransferStake(ctx context.Context, act *action.Transfer
featureCtx := protocol.MustGetFeatureCtx(ctx)
log := newReceiptLog(p.addr.String(), HandleTransferStake, featureCtx.NewStakingReceiptFormat)

_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
_, fetchErr := fetchCaller(ctx, csm.SM(), big.NewInt(0))
if fetchErr != nil {
return log, fetchErr
}
Expand Down Expand Up @@ -449,7 +450,7 @@ func (p *Protocol) handleDepositToStake(ctx context.Context, act *action.Deposit
featureCtx := protocol.MustGetFeatureCtx(ctx)
log := newReceiptLog(p.addr.String(), HandleDepositToStake, featureCtx.NewStakingReceiptFormat)

depositor, fetchErr := fetchCaller(ctx, csm, act.Amount())
depositor, fetchErr := fetchCaller(ctx, csm.SM(), act.Amount())
if fetchErr != nil {
return log, nil, fetchErr
}
Expand Down Expand Up @@ -548,7 +549,7 @@ func (p *Protocol) handleRestake(ctx context.Context, act *action.Restake, csm C
featureCtx := protocol.MustGetFeatureCtx(ctx)
log := newReceiptLog(p.addr.String(), HandleRestake, featureCtx.NewStakingReceiptFormat)

_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
_, fetchErr := fetchCaller(ctx, csm.SM(), big.NewInt(0))
if fetchErr != nil {
return log, fetchErr
}
Expand Down Expand Up @@ -628,7 +629,7 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand

registrationFee := new(big.Int).Set(p.config.RegistrationConsts.Fee)

caller, fetchErr := fetchCaller(ctx, csm, new(big.Int).Add(act.Amount(), registrationFee))
caller, fetchErr := fetchCaller(ctx, csm.SM(), new(big.Int).Add(act.Amount(), registrationFee))
if fetchErr != nil {
return log, nil, fetchErr
}
Expand Down Expand Up @@ -739,7 +740,7 @@ func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.Candid
featureCtx := protocol.MustGetFeatureCtx(ctx)
log := newReceiptLog(p.addr.String(), HandleCandidateUpdate, featureCtx.NewStakingReceiptFormat)

_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
_, fetchErr := fetchCaller(ctx, csm.SM(), big.NewInt(0))
if fetchErr != nil {
return log, fetchErr
}
Expand Down Expand Up @@ -775,6 +776,83 @@ func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.Candid
return log, nil
}

func (p *Protocol) handleTypedCandidateRegister(ctx context.Context, act typedCandidateRegister, csm CandidateStateManager, esm *typedCandidateStateManager,
Copy link
Member

Choose a reason for hiding this comment

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

where is handleTypedCandidateRegister called?

Copy link
Member

Choose a reason for hiding this comment

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

Can similar code be shared with handleCandidateRegister

Copy link
Member Author

Choose a reason for hiding this comment

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

handleTypedCandidateRegister will be called when introducing a specific register 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.

handleTypedCandidateRegister uses different candidate state storage than handleCandidateRegister

Copy link
Member

Choose a reason for hiding this comment

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

handleTypedCandidateRegister will be called when introducing a specific register action

Add TODO?

) (*receiptLog, []*action.TransactionLog, error) {
actCtx := protocol.MustGetActionCtx(ctx)
blkCtx := protocol.MustGetBlockCtx(ctx)
featureCtx := protocol.MustGetFeatureCtx(ctx)
log := newReceiptLog(p.addr.String(), HandleTypedCandidateRegister, featureCtx.NewStakingReceiptFormat)
candType := CandidateType(act.CandidateType())
actAmount := act.Amount()
registrationFee := new(big.Int).Set(p.config.RegistrationConsts.Fee)

caller, fetchErr := fetchCaller(ctx, esm, new(big.Int).Add(actAmount, registrationFee))
if fetchErr != nil {
return log, nil, fetchErr
}
owner := actCtx.Caller
if act.OwnerAddress() != nil {
owner = act.OwnerAddress()
}
Copy link
Member

Choose a reason for hiding this comment

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

else {owner := actCtx.Caller}


// cannot collide with existing operator at same type
if esm.isRegistered(candType, act.OperatorAddress()) {
return log, nil, &handleError{
err: ErrInvalidOperator,
failureStatus: iotextypes.ReceiptStatus_ErrCandidateConflict,
}
}

bucket := NewVoteBucket(owner, owner, actAmount, act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake())
Copy link
Member

Choose a reason for hiding this comment

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

separate staking for multi roles?

Copy link
Member

Choose a reason for hiding this comment

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

should amount be verified?

Copy link
Member Author

Choose a reason for hiding this comment

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

separate staking for multi roles?

yes

Copy link
Member Author

Choose a reason for hiding this comment

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

the validation should be implemented in the Validate of staking.Protocol by specific register action, just like other actions do

bucketIdx, err := csm.putBucketAndIndex(bucket)
if err != nil {
return log, nil, err
}
log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), owner.Bytes())

// create candidate
if err := esm.upsert(&TypedCandidate{
Owner: owner,
Operator: act.OperatorAddress(),
Reward: act.RewardAddress(),
Type: candType,
SelfStakeBucketIdx: bucketIdx,
extraData: act.ExtraData(),
}); err != nil {
return log, nil, csmErrorToHandleError(owner.String(), err)
}

// deposit stake
stakeTLog, err := depositStake(csm, actAmount, caller, actCtx.Caller, iotextypes.TransactionLogType_TYPED_CANDIDATE_SELF_STAKE)
if err != nil {
return log, nil, err
}

// deposit gas
gasTLog, err := p.depositGasWithType(ctx, esm, registrationFee, iotextypes.TransactionLogType_TYPED_CANDIDATE_REGISTRATION_FEE)
if err != nil {
return log, nil, errors.Wrap(err, "failed to deposit gas")
}

log.AddAddress(owner)
log.AddAddress(actCtx.Caller)
log.SetData(byteutil.Uint64ToBytesBigEndian(bucketIdx))

return log, []*action.TransactionLog{
stakeTLog,
gasTLog,
}, nil
}

func (p *Protocol) depositGasWithType(ctx context.Context, sm protocol.StateManager, amount *big.Int, logType iotextypes.TransactionLogType) (*action.TransactionLog, error) {
tLog, err := p.depositGas(ctx, sm, amount)
if err != nil {
return nil, err
}
tLog.Type = logType
return tLog, nil
}

func (p *Protocol) fetchBucket(
csm CandidateStateManager,
caller address.Address,
Expand Down Expand Up @@ -812,13 +890,13 @@ func (p *Protocol) fetchBucket(
return bucket, nil
}

func fetchCaller(ctx context.Context, csm CandidateStateManager, amount *big.Int) (*state.Account, ReceiptError) {
func fetchCaller(ctx context.Context, sr protocol.StateReader, amount *big.Int) (*state.Account, ReceiptError) {
actionCtx := protocol.MustGetActionCtx(ctx)
accountCreationOpts := []state.AccountCreationOption{}
if protocol.MustGetFeatureCtx(ctx).CreateLegacyNonceAccount {
accountCreationOpts = append(accountCreationOpts, state.LegacyNonceAccountTypeOption())
}
caller, err := accountutil.LoadAccount(csm.SM(), actionCtx.Caller, accountCreationOpts...)
caller, err := accountutil.LoadAccount(sr, actionCtx.Caller, accountCreationOpts...)
if err != nil {
return nil, &handleError{
err: errors.Wrapf(err, "failed to load the account of caller %s", actionCtx.Caller.String()),
Expand Down Expand Up @@ -887,3 +965,30 @@ func BucketIndexFromReceiptLog(log *iotextypes.Log) (uint64, bool) {
return 0, false
}
}

// depositStake transfer stake from caller account to bucket pool
func depositStake(csm CandidateStateManager, amount *big.Int, callerAccount *state.Account, callerAddr address.Address, logType iotextypes.TransactionLogType) (*action.TransactionLog, error) {
// update bucket pool
if err := csm.DebitBucketPool(amount, true); err != nil {
return nil, &handleError{
err: errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error()),
failureStatus: iotextypes.ReceiptStatus_ErrWriteAccount,
}
}
// update caller account
if err := callerAccount.SubBalance(amount); err != nil {
return nil, &handleError{
err: errors.Wrapf(err, "failed to update the balance of register %s", callerAddr.String()),
failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance,
}
}
if err := accountutil.StoreAccount(csm.SM(), callerAddr, callerAccount); err != nil {
return nil, errors.Wrapf(err, "failed to store account %s", callerAddr.String())
}
return &action.TransactionLog{
Type: logType,
Sender: callerAddr.String(),
Recipient: address.StakingBucketPoolAddr,
Amount: amount,
}, nil
}
45 changes: 45 additions & 0 deletions action/protocol/staking/typedcandidate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2023 IoTeX Foundation
// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
// 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 staking

import (
"math/big"

"github.com/iotexproject/iotex-address/address"
)

type (
// typedCandidateRegister is the interface that every candidate register action should implement
Copy link
Member

Choose a reason for hiding this comment

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

private or public?

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 a interface used within this package, so should be private

typedCandidateRegister interface {
OwnerAddress() address.Address
OperatorAddress() address.Address
RewardAddress() address.Address
Amount() *big.Int
Duration() uint32
AutoStake() bool
CandidateType() uint32
// ExtraData returns the customised data for the candidate
ExtraData() []byte
}

// CandidateType is the type of candidate
CandidateType uint32

// TypedCandidate is the struct of a candidate with type
TypedCandidate struct {
Owner address.Address
Operator address.Address
Reward address.Address
Type CandidateType
SelfStakeBucketIdx uint64
// extraData is the customised data for the candidate
extraData []byte
}
)

func candidateExtra[T interface{ Deserilized([]byte) error }](c *TypedCandidate, extra T) error {
Copy link
Member

Choose a reason for hiding this comment

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

Deserialize()

Copy link
Member

Choose a reason for hiding this comment

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

extra func in this pr

return extra.Deserilized(c.extraData)
}
27 changes: 27 additions & 0 deletions action/protocol/staking/typedcandidate_statemanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2023 IoTeX Foundation
// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
// 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 staking

import (
"github.com/iotexproject/iotex-address/address"

"github.com/iotexproject/iotex-core/action/protocol"
)

// typedCandidateStateManager is the state manager for typed candidate
type typedCandidateStateManager struct {
protocol.StateManager
}

func (csm *typedCandidateStateManager) isRegistered(candType CandidateType, operatorAddr address.Address) bool {
// TODO: implement this
return false
}

func (csm *typedCandidateStateManager) upsert(cand *TypedCandidate) error {
// TODO: implement this
return nil
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,5 @@ require (
replace github.com/ethereum/go-ethereum => github.com/iotexproject/go-ethereum v0.4.2

replace golang.org/x/xerrors => golang.org/x/xerrors v0.0.0-20190212162355-a5947ffaace3

replace github.com/iotexproject/iotex-proto => github.com/envestcc/iotex-proto v0.0.0-20230711101456-44435226235b
Loading