Skip to content
Open
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
2 changes: 2 additions & 0 deletions action/protocol/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ type (
NotUseMinSelfStakeToBeActive bool
StoreVoteOfNFTBucketIntoView bool
CandidateSlashByOwner bool
AllowUpdateDelegate bool
}

// FeatureWithHeightCtx provides feature check functions.
Expand Down Expand Up @@ -331,6 +332,7 @@ func WithFeatureCtx(ctx context.Context) context.Context {
NotUseMinSelfStakeToBeActive: !g.IsXingu(height),
StoreVoteOfNFTBucketIntoView: !g.IsXingu(height),
CandidateSlashByOwner: !g.IsXinguBeta(height),
AllowUpdateDelegate: !g.IsXingu(height),
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"go.uber.org/mock/gomock"

"github.com/iotexproject/iotex-core/v2/action/protocol"
"github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb"
"github.com/iotexproject/iotex-core/v2/state"
"github.com/iotexproject/iotex-core/v2/test/identityset"
"github.com/iotexproject/iotex-core/v2/test/mock/mock_chainmanager"
Expand Down Expand Up @@ -101,7 +100,7 @@ func (d *dummyIter) Next(s interface{}) ([]byte, error) {
if d.idx >= d.size {
return nil, state.ErrNilValue
}
*(s.(*stakingpb.SystemStakingBucket)) = stakingpb.SystemStakingBucket{}
*(s.(*Bucket)) = Bucket{}
key := d.keys[d.idx]
d.idx++
return key, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (p *Protocol) handleCandidateTransferOwnership(ctx context.Context, act *ac
featureCtx := protocol.MustGetFeatureCtx(ctx)

log := newReceiptLog(p.addr.String(), handleCandidateTransferOwnership, featureCtx.NewStakingReceiptFormat)
_, fetchErr := fetchCaller(ctx, csm, big.NewInt(0))
_, fetchErr := fetchCaller(ctx, csm.SM(), big.NewInt(0))
if fetchErr != nil {
return log, nil, fetchErr
}
Expand Down
2 changes: 1 addition & 1 deletion action/protocol/staking/handler_stake_migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (p *Protocol) handleStakeMigrate(ctx context.Context, elp action.Envelope,
if rErr != nil {
return nil, nil, gasConsumed, gasToBeDeducted, rErr
}
staker, rerr := fetchCaller(ctx, csm, big.NewInt(0))
staker, rerr := fetchCaller(ctx, csm.SM(), big.NewInt(0))
if rerr != nil {
return nil, nil, gasConsumed, gasToBeDeducted, errors.Wrap(rerr, "failed to fetch caller")
}
Expand Down
44 changes: 33 additions & 11 deletions action/protocol/staking/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,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 +136,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 @@ -229,7 +229,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 @@ -307,7 +307,7 @@ func (p *Protocol) handleChangeCandidate(ctx context.Context, act *action.Change
blkCtx := protocol.MustGetBlockCtx(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 @@ -401,7 +401,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 @@ -488,7 +488,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 @@ -593,7 +593,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 @@ -679,7 +679,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 @@ -842,13 +842,27 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand
return log, txLogs, nil
}

func (p *Protocol) assertNotActiveDelegate(ctx context.Context, sr protocol.StateReader, name string) error {
if p.isDelegate == nil {
return nil
}
exists, err := p.isDelegate(ctx, sr, name)
if err != nil {
return errors.Wrap(err, "failed to check whether the candidate is a delegate")
}
if exists {
return errors.New("delegate is an active delegate")
}
return nil
}

func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.CandidateUpdate, csm CandidateStateManager,
) (*receiptLog, error) {
actCtx := protocol.MustGetActionCtx(ctx)
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 All @@ -858,6 +872,14 @@ func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.Candid
if c == nil {
return log, errCandNotExist
}
if !featureCtx.AllowUpdateDelegate {
if err := p.assertNotActiveDelegate(ctx, csm.SM(), c.Name); err != nil {
return log, &handleError{
err: err,
failureStatus: iotextypes.ReceiptStatus_ErrWriteCandidate,
}
}
}

if len(act.Name()) != 0 {
c.Name = act.Name()
Expand Down Expand Up @@ -969,13 +991,13 @@ func (p *Protocol) generateCandidateID(owner address.Address, height uint64, csm
return nil, errors.New("failed to generate candidate ID after max attempts")
}

func fetchCaller(ctx context.Context, csm CandidateStateManager, amount *big.Int) (*state.Account, ReceiptError) {
func fetchCaller(ctx context.Context, sm 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(sm, 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
85 changes: 80 additions & 5 deletions action/protocol/staking/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -699,8 +699,9 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
gasLimit uint64
blkGasLimit uint64
gasPrice *big.Int
newProtocol bool
// candidate update
allowUpdate bool
isActive bool
updateName string
updateOperator string
updateReward string
Expand All @@ -725,6 +726,7 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
uint64(1000000),
big.NewInt(1000),
true,
false,
"update",
identityset.Address(31).String(),
identityset.Address(32).String(),
Expand All @@ -749,6 +751,7 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
uint64(1000000),
big.NewInt(1000),
true,
false,
"update",
identityset.Address(31).String(),
identityset.Address(32).String(),
Expand All @@ -773,6 +776,7 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
uint64(1000000),
big.NewInt(1000),
true,
false,
"",
"",
"",
Expand All @@ -797,6 +801,7 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
uint64(1000000),
big.NewInt(1000),
true,
false,
"test2",
"",
"",
Expand All @@ -821,12 +826,63 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
uint64(1000000),
big.NewInt(1000),
true,
false,
"!invalidname",
identityset.Address(31).String(),
identityset.Address(32).String(),
action.ErrInvalidCanName,
iotextypes.ReceiptStatus_Failure,
},
// invalid update of active delegate
{
1201000,
identityset.Address(27),
1,
"test",
identityset.Address(27).String(),
identityset.Address(29).String(),
identityset.Address(27).String(),
"1200000000000000000000000",
"1806204150552640363969204",
uint32(10000),
false,
[]byte("payload"),
uint64(1000000),
uint64(1000000),
big.NewInt(1000),
false,
true,
"update",
identityset.Address(31).String(),
identityset.Address(32).String(),
nil,
iotextypes.ReceiptStatus_ErrWriteCandidate,
},
// success, update non-active delegate, name, operator and reward address
{
1201000,
identityset.Address(27),
1,
"test",
identityset.Address(27).String(),
identityset.Address(29).String(),
identityset.Address(27).String(),
"1200000000000000000000000",
"1806204150552640363969204",
uint32(10000),
false,
[]byte("payload"),
uint64(1000000),
uint64(1000000),
big.NewInt(1000),
true,
false,
"update",
identityset.Address(31).String(),
identityset.Address(32).String(),
nil,
iotextypes.ReceiptStatus_Success,
},
// success,update name, operator and reward address
{
1201000,
Expand All @@ -845,6 +901,7 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
uint64(1000000),
big.NewInt(1000),
true,
false,
"update",
identityset.Address(31).String(),
identityset.Address(32).String(),
Expand All @@ -868,6 +925,7 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
uint64(1000000),
uint64(1000000),
big.NewInt(1000),
true,
false,
"test1",
"",
Expand All @@ -892,6 +950,7 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
uint64(1000000),
uint64(1000000),
big.NewInt(1000),
true,
false,
"test",
identityset.Address(7).String(),
Expand All @@ -912,25 +971,41 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) {
SetGasPrice(test.gasPrice).SetAction(act).Build()
registerCost, err := elp.Cost()
require.NoError(err)
g := genesis.TestDefault()
ctx := protocol.WithActionCtx(context.Background(), protocol.ActionCtx{
Caller: test.caller,
GasPrice: test.gasPrice,
IntrinsicGas: intrinsic,
Nonce: test.nonce,
})
blockHeight := uint64(1)
var cu *action.CandidateUpdate
if test.allowUpdate {
cu, err = action.NewCandidateUpdate(test.updateName, test.updateOperator, test.updateReward)
require.NoError(err)
p.SetIsDelegateFunc(nil)
} else {
blockHeight = g.XinguBlockHeight
blsPrivKey, err := crypto.GenerateBLS12381PrivateKey(identityset.PrivateKey(0).Bytes())
require.NoError(err)
cu, err = action.NewCandidateUpdateWithBLS(test.updateName, test.updateOperator, test.updateReward, blsPrivKey.PublicKey().Bytes())
require.NoError(err)
p.SetIsDelegateFunc(func(ctx context.Context, sr protocol.StateReader, candidate string) (bool, error) {
return test.isActive, nil
})
}

ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{
BlockHeight: 1,
BlockHeight: blockHeight,
BlockTimeStamp: time.Now(),
GasLimit: test.blkGasLimit,
})
ctx = protocol.WithBlockchainCtx(ctx, protocol.BlockchainCtx{Tip: protocol.TipInfo{}})
ctx = genesis.WithGenesisContext(ctx, genesis.TestDefault())
ctx = genesis.WithGenesisContext(ctx, g)
ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
_, err = p.Handle(ctx, elp, sm)
require.NoError(err)

cu, err := action.NewCandidateUpdate(test.updateName, test.updateOperator, test.updateReward)
require.NoError(err)
intrinsic, _ = cu.IntrinsicGas()
elp = builder.SetNonce(test.nonce + 1).SetGasLimit(test.gasLimit).
SetGasPrice(test.gasPrice).SetAction(cu).Build()
Expand Down
8 changes: 8 additions & 0 deletions action/protocol/staking/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ var (
)

type (
// IsDelegateFunc defines the function to check if a candidate is a delegate
IsDelegateFunc func(ctx context.Context, sr protocol.StateReader, candidate string) (bool, error)
// ReceiptError indicates a non-critical error with corresponding receipt status
ReceiptError interface {
Error() string
Expand All @@ -100,6 +102,7 @@ type (
helperCtx HelperCtx
blockStore BlockStore
blocksToDurationFn func(startHeight, endHeight, currentHeight uint64) time.Duration
isDelegate IsDelegateFunc
}

// Configuration is the staking protocol configuration.
Expand Down Expand Up @@ -342,6 +345,11 @@ func (p *Protocol) Start(ctx context.Context, sr protocol.StateReader) (protocol
return c, nil
}

// SetIsDelegateFunc sets the function to check if an address is a delegate
func (p *Protocol) SetIsDelegateFunc(f IsDelegateFunc) {
p.isDelegate = f
}

// CreateGenesisStates is used to setup BootstrapCandidates from genesis config.
func (p *Protocol) CreateGenesisStates(
ctx context.Context,
Expand Down
Loading