From 9eeb590fc0d5bd3019834f31f73a4ae30ad7b06c Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 4 Jan 2024 18:30:36 +0800 Subject: [PATCH 1/8] register without stake --- action/candidate_register.go | 6 +- action/protocol/context.go | 2 + action/protocol/staking/handlers.go | 82 ++++++++++++++------------ action/protocol/staking/validations.go | 10 +++- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/action/candidate_register.go b/action/candidate_register.go index 5dfe9b12a9..2b01f28a73 100644 --- a/action/candidate_register.go +++ b/action/candidate_register.go @@ -13,13 +13,13 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "google.golang.org/protobuf/proto" - "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-core/pkg/util/byteutil" "github.com/iotexproject/iotex-core/pkg/version" - "github.com/iotexproject/iotex-proto/golang/iotextypes" ) const ( @@ -290,7 +290,7 @@ func (cr *CandidateRegister) Cost() (*big.Int, error) { // SanityCheck validates the variables in the action func (cr *CandidateRegister) SanityCheck() error { - if cr.Amount().Sign() <= 0 { + if cr.Amount().Sign() < 0 { return errors.Wrap(ErrInvalidAmount, "negative value") } if !IsValidCandidateName(cr.Name()) { diff --git a/action/protocol/context.go b/action/protocol/context.go index 5de7b937e2..fb25dbf18c 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -116,6 +116,7 @@ type ( ExecutionSizeLimit32KB bool UseZeroNonceForFreshAccount bool SharedGasWithDapp bool + CandidateRegisterMustWithStake bool } // FeatureWithHeightCtx provides feature check functions. @@ -257,6 +258,7 @@ func WithFeatureCtx(ctx context.Context) context.Context { ExecutionSizeLimit32KB: !g.IsSumatra(height), UseZeroNonceForFreshAccount: g.IsSumatra(height), SharedGasWithDapp: g.IsToBeEnabled(height), + CandidateRegisterMustWithStake: !g.IsToBeEnabled(height), }, ) } diff --git a/action/protocol/staking/handlers.go b/action/protocol/staking/handlers.go index 80c1ee27b9..415f7d723b 100644 --- a/action/protocol/staking/handlers.go +++ b/action/protocol/staking/handlers.go @@ -663,19 +663,33 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand } } - bucket := NewVoteBucket(owner, owner, act.Amount(), act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake()) - bucketIdx, err := csm.putBucketAndIndex(bucket) - if err != nil { - return log, nil, err + // register with self-stake + bucketIdx := uint64(candidateNoSelfStakeBucketIndex) + var bucketLogs []*action.TransactionLog + votes := big.NewInt(0) + if act.Amount().Sign() > 0 { + bucket := NewVoteBucket(owner, owner, act.Amount(), act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake()) + bktIdx, err := csm.putBucketAndIndex(bucket) + if err != nil { + return log, nil, err + } + bucketIdx = bktIdx + bucketLogs = append(bucketLogs, &action.TransactionLog{ + Type: iotextypes.TransactionLogType_CANDIDATE_SELF_STAKE, + Sender: actCtx.Caller.String(), + Recipient: address.StakingBucketPoolAddr, + Amount: act.Amount(), + }) + votes = p.calculateVoteWeight(bucket, true) + log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), owner.Bytes()) } - log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), owner.Bytes()) c = &Candidate{ Owner: owner, Operator: act.OperatorAddress(), Reward: act.RewardAddress(), Name: act.Name(), - Votes: p.calculateVoteWeight(bucket, true), + Votes: votes, SelfStakeBucketIdx: bucketIdx, SelfStake: act.Amount(), } @@ -688,28 +702,29 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand csm.DirtyView().candCenter.base.recordOwner(c) } - // update bucket pool - if err := csm.DebitBucketPool(act.Amount(), true); err != nil { - return log, nil, &handleError{ - err: errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error()), - failureStatus: iotextypes.ReceiptStatus_ErrWriteAccount, + if act.Amount().Sign() > 0 { + // update bucket pool + if err := csm.DebitBucketPool(act.Amount(), true); err != nil { + return log, nil, &handleError{ + err: errors.Wrapf(err, "failed to update staking bucket pool %s", err.Error()), + failureStatus: iotextypes.ReceiptStatus_ErrWriteAccount, + } } - } - - // update caller balance - if err := caller.SubBalance(act.Amount()); err != nil { - return log, nil, &handleError{ - err: errors.Wrapf(err, "failed to update the balance of register %s", actCtx.Caller.String()), - failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance, + // update caller balance + if err := caller.SubBalance(act.Amount()); err != nil { + return log, nil, &handleError{ + err: errors.Wrapf(err, "failed to update the balance of register %s", actCtx.Caller.String()), + failureStatus: iotextypes.ReceiptStatus_ErrNotEnoughBalance, + } + } + // put updated caller's account state to trie + if err := accountutil.StoreAccount(csm.SM(), actCtx.Caller, caller); err != nil { + return log, nil, errors.Wrapf(err, "failed to store account %s", actCtx.Caller.String()) } - } - // put updated caller's account state to trie - if err := accountutil.StoreAccount(csm.SM(), actCtx.Caller, caller); err != nil { - return log, nil, errors.Wrapf(err, "failed to store account %s", actCtx.Caller.String()) } // put registrationFee to reward pool - if _, err = p.depositGas(ctx, csm.SM(), registrationFee); err != nil { + if _, err := p.depositGas(ctx, csm.SM(), registrationFee); err != nil { return log, nil, errors.Wrap(err, "failed to deposit gas") } @@ -717,20 +732,13 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand log.AddAddress(actCtx.Caller) log.SetData(byteutil.Uint64ToBytesBigEndian(bucketIdx)) - return log, []*action.TransactionLog{ - { - Type: iotextypes.TransactionLogType_CANDIDATE_SELF_STAKE, - Sender: actCtx.Caller.String(), - Recipient: address.StakingBucketPoolAddr, - Amount: act.Amount(), - }, - { - Type: iotextypes.TransactionLogType_CANDIDATE_REGISTRATION_FEE, - Sender: actCtx.Caller.String(), - Recipient: address.RewardingPoolAddr, - Amount: registrationFee, - }, - }, nil + txLogs := append(bucketLogs, &action.TransactionLog{ + Type: iotextypes.TransactionLogType_CANDIDATE_REGISTRATION_FEE, + Sender: actCtx.Caller.String(), + Recipient: address.RewardingPoolAddr, + Amount: registrationFee, + }) + return log, txLogs, nil } func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.CandidateUpdate, csm CandidateStateManager, diff --git a/action/protocol/staking/validations.go b/action/protocol/staking/validations.go index 20b155442c..577116d03d 100644 --- a/action/protocol/staking/validations.go +++ b/action/protocol/staking/validations.go @@ -7,10 +7,14 @@ package staking import ( "context" + "math/big" "github.com/pkg/errors" + "go.uber.org/zap" "github.com/iotexproject/iotex-core/action" + "github.com/iotexproject/iotex-core/action/protocol" + "github.com/iotexproject/iotex-core/pkg/log" ) // Errors @@ -66,7 +70,11 @@ func (p *Protocol) validateCandidateRegister(ctx context.Context, act *action.Ca } if act.Amount().Cmp(p.config.RegistrationConsts.MinSelfStake) < 0 { - return errors.Wrap(action.ErrInvalidAmount, "self staking amount is not valid") + featureCtx := protocol.MustGetFeatureCtx(ctx) + if featureCtx.CandidateRegisterMustWithStake || act.Amount().Cmp(big.NewInt(0)) != 0 { + log.L().Warn("Candidate register amount is less than the minimum requirement", zap.Bool("feature", featureCtx.CandidateRegisterMustWithStake), zap.String("amount", act.Amount().String())) + return errors.Wrap(action.ErrInvalidAmount, "self staking amount is not valid") + } } return nil } From f02488019cdef82fc74f731c6282867fe47d47b8 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 18 Jan 2024 10:42:01 +0800 Subject: [PATCH 2/8] add test --- action/protocol/staking/handlers_test.go | 54 +++++++++++++++++++----- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/action/protocol/staking/handlers_test.go b/action/protocol/staking/handlers_test.go index 40b194b1ee..244556f1e0 100644 --- a/action/protocol/staking/handlers_test.go +++ b/action/protocol/staking/handlers_test.go @@ -15,6 +15,7 @@ import ( "time" "github.com/golang/mock/gomock" + "github.com/mohae/deepcopy" "github.com/pkg/errors" "github.com/stretchr/testify/require" @@ -531,6 +532,27 @@ func TestProtocol_HandleCandidateRegister(t *testing.T) { nil, iotextypes.ReceiptStatus_ErrCandidateConflict, }, + // register without self-stake + { + 1201000, + identityset.Address(27), + 1, + "test", + identityset.Address(28).String(), + identityset.Address(29).String(), + identityset.Address(30).String(), + "0", + "0", + uint32(10000), + false, + nil, + uint64(1000000), + uint64(1000000), + big.NewInt(1), + true, + nil, + iotextypes.ReceiptStatus_Success, + }, } for _, test := range tests { @@ -553,7 +575,9 @@ func TestProtocol_HandleCandidateRegister(t *testing.T) { BlockTimeStamp: time.Now(), GasLimit: test.blkGasLimit, }) - ctx = genesis.WithGenesisContext(ctx, genesis.Default) + g := deepcopy.Copy(genesis.Default).(genesis.Genesis) + g.ToBeEnabledBlockHeight = 0 + ctx = genesis.WithGenesisContext(ctx, g) ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) require.Equal(test.err, errors.Cause(p.Validate(ctx, act, sm))) if test.err != nil { @@ -570,16 +594,24 @@ func TestProtocol_HandleCandidateRegister(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // check the special create bucket and candidate register log tLogs := r.TransactionLogs() - require.Equal(2, len(tLogs)) - cLog := tLogs[0] - require.Equal(test.caller.String(), cLog.Sender) - require.Equal(address.StakingBucketPoolAddr, cLog.Recipient) - require.Equal(test.amountStr, cLog.Amount.String()) - - cLog = tLogs[1] - require.Equal(test.caller.String(), cLog.Sender) - require.Equal(address.RewardingPoolAddr, cLog.Recipient) - require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String()) + if test.amountStr == "0" { + require.Equal(1, len(tLogs)) + cLog := tLogs[0] + require.Equal(test.caller.String(), cLog.Sender) + require.Equal(address.RewardingPoolAddr, cLog.Recipient) + require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String()) + } else { + require.Equal(2, len(tLogs)) + cLog := tLogs[0] + require.Equal(test.caller.String(), cLog.Sender) + require.Equal(address.StakingBucketPoolAddr, cLog.Recipient) + require.Equal(test.amountStr, cLog.Amount.String()) + + cLog = tLogs[1] + require.Equal(test.caller.String(), cLog.Sender) + require.Equal(address.RewardingPoolAddr, cLog.Recipient) + require.Equal(p.config.RegistrationConsts.Fee.String(), cLog.Amount.String()) + } // test candidate candidate, _, err := csr.getCandidate(act.OwnerAddress()) From fca42c35edbfada812a82fb6b80214a2d8a39bd7 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 25 Jan 2024 22:55:44 +0800 Subject: [PATCH 3/8] address comment --- action/protocol/staking/handlers.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/action/protocol/staking/handlers.go b/action/protocol/staking/handlers.go index 415f7d723b..d17b5e7c62 100644 --- a/action/protocol/staking/handlers.go +++ b/action/protocol/staking/handlers.go @@ -664,25 +664,27 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand } // register with self-stake - bucketIdx := uint64(candidateNoSelfStakeBucketIndex) - var bucketLogs []*action.TransactionLog - votes := big.NewInt(0) + var ( + bucketIdx = uint64(candidateNoSelfStakeBucketIndex) + txLogs []*action.TransactionLog + votes = big.NewInt(0) + err error + ) if act.Amount().Sign() > 0 { bucket := NewVoteBucket(owner, owner, act.Amount(), act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake()) - bktIdx, err := csm.putBucketAndIndex(bucket) + bucketIdx, err = csm.putBucketAndIndex(bucket) if err != nil { return log, nil, err } - bucketIdx = bktIdx - bucketLogs = append(bucketLogs, &action.TransactionLog{ + txLogs = append(txLogs, &action.TransactionLog{ Type: iotextypes.TransactionLogType_CANDIDATE_SELF_STAKE, Sender: actCtx.Caller.String(), Recipient: address.StakingBucketPoolAddr, Amount: act.Amount(), }) votes = p.calculateVoteWeight(bucket, true) - log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), owner.Bytes()) } + log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), owner.Bytes()) c = &Candidate{ Owner: owner, @@ -732,7 +734,7 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand log.AddAddress(actCtx.Caller) log.SetData(byteutil.Uint64ToBytesBigEndian(bucketIdx)) - txLogs := append(bucketLogs, &action.TransactionLog{ + txLogs = append(txLogs, &action.TransactionLog{ Type: iotextypes.TransactionLogType_CANDIDATE_REGISTRATION_FEE, Sender: actCtx.Caller.String(), Recipient: address.RewardingPoolAddr, From 163be847e1479c2255dcc2ff2102322374c020e1 Mon Sep 17 00:00:00 2001 From: envestcc Date: Thu, 7 Mar 2024 11:18:24 +0800 Subject: [PATCH 4/8] address comments --- action/protocol/staking/validations.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/action/protocol/staking/validations.go b/action/protocol/staking/validations.go index 577116d03d..2b0acc41ae 100644 --- a/action/protocol/staking/validations.go +++ b/action/protocol/staking/validations.go @@ -7,14 +7,11 @@ package staking import ( "context" - "math/big" "github.com/pkg/errors" - "go.uber.org/zap" "github.com/iotexproject/iotex-core/action" "github.com/iotexproject/iotex-core/action/protocol" - "github.com/iotexproject/iotex-core/pkg/log" ) // Errors @@ -70,11 +67,11 @@ func (p *Protocol) validateCandidateRegister(ctx context.Context, act *action.Ca } if act.Amount().Cmp(p.config.RegistrationConsts.MinSelfStake) < 0 { - featureCtx := protocol.MustGetFeatureCtx(ctx) - if featureCtx.CandidateRegisterMustWithStake || act.Amount().Cmp(big.NewInt(0)) != 0 { - log.L().Warn("Candidate register amount is less than the minimum requirement", zap.Bool("feature", featureCtx.CandidateRegisterMustWithStake), zap.String("amount", act.Amount().String())) - return errors.Wrap(action.ErrInvalidAmount, "self staking amount is not valid") + if !protocol.MustGetFeatureCtx(ctx).CandidateRegisterMustWithStake && + act.Amount().Sign() == 0 { + return nil } + return errors.Wrap(action.ErrInvalidAmount, "self staking amount is not valid") } return nil } From ff111220943587035acfca7e39ffcd16b7ae6e66 Mon Sep 17 00:00:00 2001 From: envestcc Date: Fri, 8 Mar 2024 22:13:22 +0800 Subject: [PATCH 5/8] address comments --- action/protocol/staking/handlers.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/action/protocol/staking/handlers.go b/action/protocol/staking/handlers.go index 3306a9b9f7..e4e9d2912c 100644 --- a/action/protocol/staking/handlers.go +++ b/action/protocol/staking/handlers.go @@ -702,14 +702,17 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand } } - // register with self-stake var ( + // initial bucket index and votes as register without self-stake bucketIdx = uint64(candidateNoSelfStakeBucketIndex) - txLogs []*action.TransactionLog votes = big.NewInt(0) - err error + + withSelfStake = act.Amount().Sign() > 0 + txLogs []*action.TransactionLog + err error ) - if act.Amount().Sign() > 0 { + if withSelfStake { + // register with self-stake bucket := NewVoteBucket(owner, owner, act.Amount(), act.Duration(), blkCtx.BlockTimeStamp, act.AutoStake()) bucketIdx, err = csm.putBucketAndIndex(bucket) if err != nil { @@ -743,7 +746,7 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand csm.DirtyView().candCenter.base.recordOwner(c) } - if act.Amount().Sign() > 0 { + if withSelfStake { // update bucket pool if err := csm.DebitBucketPool(act.Amount(), true); err != nil { return log, nil, &handleError{ From 2f7bed0dad20674df5d2d993af36c1d5f0a474a1 Mon Sep 17 00:00:00 2001 From: envestcc Date: Mon, 11 Mar 2024 09:02:42 +0800 Subject: [PATCH 6/8] address comments --- action/protocol/staking/handlers.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/action/protocol/staking/handlers.go b/action/protocol/staking/handlers.go index e4e9d2912c..a754b7fcaa 100644 --- a/action/protocol/staking/handlers.go +++ b/action/protocol/staking/handlers.go @@ -703,10 +703,8 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand } var ( - // initial bucket index and votes as register without self-stake - bucketIdx = uint64(candidateNoSelfStakeBucketIndex) - votes = big.NewInt(0) - + bucketIdx uint64 + votes = big.NewInt(0) withSelfStake = act.Amount().Sign() > 0 txLogs []*action.TransactionLog err error @@ -725,6 +723,10 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand Amount: act.Amount(), }) votes = p.calculateVoteWeight(bucket, true) + } else { + // register w/o self-stake, waiting to be endorsed + bucketIdx = uint64(candidateNoSelfStakeBucketIndex) + votes = big.NewInt(0) } log.AddTopics(byteutil.Uint64ToBytesBigEndian(bucketIdx), owner.Bytes()) From 9a01979716bdb9a13ecffff3f9f952fab5e1876f Mon Sep 17 00:00:00 2001 From: envestcc Date: Mon, 11 Mar 2024 09:05:30 +0800 Subject: [PATCH 7/8] address comments --- action/protocol/staking/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action/protocol/staking/handlers.go b/action/protocol/staking/handlers.go index a754b7fcaa..31e43b4527 100644 --- a/action/protocol/staking/handlers.go +++ b/action/protocol/staking/handlers.go @@ -704,7 +704,7 @@ func (p *Protocol) handleCandidateRegister(ctx context.Context, act *action.Cand var ( bucketIdx uint64 - votes = big.NewInt(0) + votes *big.Int withSelfStake = act.Amount().Sign() > 0 txLogs []*action.TransactionLog err error From ef0e99ab2f564be8cdd3611f14414e2f8445034d Mon Sep 17 00:00:00 2001 From: envestcc Date: Mon, 11 Mar 2024 11:00:34 +0800 Subject: [PATCH 8/8] add test --- action/signedaction.go | 43 +++++++++++++ e2etest/native_staking_test.go | 112 +++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/action/signedaction.go b/action/signedaction.go index d521324821..ffecc58f5d 100644 --- a/action/signedaction.go +++ b/action/signedaction.go @@ -107,6 +107,49 @@ func SignedCandidateUpdate( return selp, nil } +// SignedCandidateActivate returns a signed candidate selfstake +func SignedCandidateActivate( + nonce uint64, + bucketIndex uint64, + gasLimit uint64, + gasPrice *big.Int, + registererPriKey crypto.PrivateKey, +) (*SealedEnvelope, error) { + cu := NewCandidateActivate(nonce, gasLimit, gasPrice, bucketIndex) + bd := &EnvelopeBuilder{} + elp := bd.SetNonce(nonce). + SetGasPrice(gasPrice). + SetGasLimit(gasLimit). + SetAction(cu).Build() + selp, err := Sign(elp, registererPriKey) + if err != nil { + return nil, errors.Wrapf(err, "failed to sign candidate selfstake %v", elp) + } + return selp, nil +} + +// SignedCandidateEndorsement returns a signed candidate endorsement +func SignedCandidateEndorsement( + nonce uint64, + bucketIndex uint64, + endorse bool, + gasLimit uint64, + gasPrice *big.Int, + registererPriKey crypto.PrivateKey, +) (*SealedEnvelope, error) { + cu := NewCandidateEndorsement(nonce, gasLimit, gasPrice, bucketIndex, endorse) + bd := &EnvelopeBuilder{} + elp := bd.SetNonce(nonce). + SetGasPrice(gasPrice). + SetGasLimit(gasLimit). + SetAction(cu).Build() + selp, err := Sign(elp, registererPriKey) + if err != nil { + return nil, errors.Wrapf(err, "failed to sign candidate endorsement %v", elp) + } + return selp, nil +} + // SignedCreateStake returns a signed create stake func SignedCreateStake(nonce uint64, candidateName, amount string, diff --git a/e2etest/native_staking_test.go b/e2etest/native_staking_test.go index 8d1c250eb0..861fb364b8 100644 --- a/e2etest/native_staking_test.go +++ b/e2etest/native_staking_test.go @@ -39,6 +39,7 @@ const ( candidate1Name = "candidate1" candidate2Name = "candidate2" + candidate3Name = "candidate3" ) var ( @@ -91,6 +92,9 @@ func TestNativeStaking(t *testing.T) { ap := svr.ChainService(chainID).ActionPool() dao := svr.ChainService(chainID).BlockDAO() require.NotNil(bc) + prtcl, ok := svr.ChainService(chainID).Registry().Find("staking") + require.True(ok) + stkPrtcl := prtcl.(*staking.Protocol) require.True(cfg.Genesis.IsFbkMigration(1)) @@ -101,6 +105,10 @@ func TestNativeStaking(t *testing.T) { cand2Addr := identityset.Address(1) cand2PriKey := identityset.PrivateKey(1) + // create non-stake candidate + cand3Addr := identityset.Address(4) + cand3PriKey := identityset.PrivateKey(4) + fixedTime := time.Unix(cfg.Genesis.Timestamp, 0) addOneTx := func(tx *action.SealedEnvelope, err error) (*action.SealedEnvelope, error) { if err != nil { @@ -392,6 +400,108 @@ func TestNativeStaking(t *testing.T) { // check candidate account state require.NoError(checkAccountState(cfg, sf, ws, true, big.NewInt(0).Sub(initBalance, selfStake), cand1Addr)) + + // register without stake + register3, err := addOneTx(action.SignedCandidateRegister(1, candidate3Name, cand3Addr.String(), cand3Addr.String(), + cand3Addr.String(), "0", 1, false, nil, gasLimit, gasPrice, cand3PriKey)) + require.NoError(err) + register3Hash, err := register3.Hash() + require.NoError(err) + r3, err := dao.GetReceiptByActionHash(register3Hash, bc.TipHeight()) + require.NoError(err) + require.EqualValues(iotextypes.ReceiptStatus_Success, r3.Status) + require.NoError(checkCandidateState(sf, candidate3Name, cand3Addr.String(), big.NewInt(0), big.NewInt(0), cand3Addr)) + require.NoError(checkAccountState(cfg, sf, register3, true, initBalance, cand3Addr)) + + ctx, err = bc.Context(ctx) + require.NoError(err) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: bc.TipHeight() + 1, + }) + ctx = protocol.WithFeatureCtx(ctx) + cands, err := stkPrtcl.ActiveCandidates(ctx, sf, 0) + require.NoError(err) + require.Equal(4, len(cands)) + for _, cand := range cands { + t.Logf("\ncandidate=%+v, %+v\n", string(cand.CanName), cand.Votes.String()) + } + // stake bucket + cs3, err := addOneTx(action.SignedCreateStake(3, candidate3Name, selfStake.String(), 1, false, + nil, gasLimit, gasPrice, voter1PriKey)) + require.NoError(err) + cs3Hash, err := cs3.Hash() + require.NoError(err) + r1, err = dao.GetReceiptByActionHash(cs3Hash, bc.TipHeight()) + require.NoError(err) + require.EqualValues(iotextypes.ReceiptStatus_Success, r1.Status) + logs = r1.Logs() + require.Equal(3, len(logs[0].Topics)) + require.Equal(hash.BytesToHash256([]byte(staking.HandleCreateStake)), logs[0].Topics[0]) + endorseBucketIndex := byteutil.BytesToUint64BigEndian(logs[0].Topics[1][24:]) + t.Logf("endorseBucketIndex=%+v", endorseBucketIndex) + // endorse bucket + es, err := addOneTx(action.SignedCandidateEndorsement(4, endorseBucketIndex, true, gasLimit, gasPrice, voter1PriKey)) + require.NoError(err) + esHash, err := es.Hash() + require.NoError(err) + r1, err = dao.GetReceiptByActionHash(esHash, bc.TipHeight()) + require.NoError(err) + require.EqualValues(iotextypes.ReceiptStatus_Success, r1.Status) + // candidate self stake + css, err := addOneTx(action.SignedCandidateActivate(2, endorseBucketIndex, gasLimit, gasPrice, cand3PriKey)) + require.NoError(err) + cssHash, err := css.Hash() + require.NoError(err) + r1, err = dao.GetReceiptByActionHash(cssHash, bc.TipHeight()) + require.NoError(err) + require.EqualValues(iotextypes.ReceiptStatus_Success, r1.Status) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: bc.TipHeight() + 1, + }) + ctx = protocol.WithFeatureCtx(ctx) + cands, err = stkPrtcl.ActiveCandidates(ctx, sf, 0) + require.NoError(err) + require.Equal(5, len(cands)) + for _, cand := range cands { + t.Logf("\ncandidate=%+v, %+v\n", string(cand.CanName), cand.Votes.String()) + } + // unendorse bucket + es, err = addOneTx(action.SignedCandidateEndorsement(5, endorseBucketIndex, false, gasLimit, gasPrice, voter1PriKey)) + require.NoError(err) + esHash, err = es.Hash() + require.NoError(err) + r1, err = dao.GetReceiptByActionHash(esHash, bc.TipHeight()) + require.NoError(err) + require.EqualValues(iotextypes.ReceiptStatus_Success, r1.Status) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: bc.TipHeight() + 1, + }) + cands, err = stkPrtcl.ActiveCandidates(ctx, sf, 0) + require.NoError(err) + require.Equal(5, len(cands)) + t.Run("endorsement is withdrawing, candidate can also be chosen as delegate", func(t *testing.T) { + ctx, err = bc.Context(ctx) + require.NoError(err) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: bc.TipHeight(), + }) + ctx = protocol.WithFeatureCtx(ctx) + cands, err = stkPrtcl.ActiveCandidates(ctx, sf, 0) + require.NoError(err) + require.Equal(5, len(cands)) + }) + t.Run("endorsement is expired, candidate can not be chosen as delegate any more", func(t *testing.T) { + ctx, err = bc.Context(ctx) + require.NoError(err) + jumpBlocks(bc, int(cfg.Genesis.EndorsementWithdrawWaitingBlocks), require) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: bc.TipHeight(), + }) + ctx = protocol.WithFeatureCtx(ctx) + cands, err = stkPrtcl.ActiveCandidates(ctx, sf, 0) + require.NoError(err) + require.Equal(4, len(cands)) + }) } cfg := config.Default @@ -427,6 +537,8 @@ func TestNativeStaking(t *testing.T) { cfg.Chain.EnableAsyncIndexWrite = false cfg.Genesis.BootstrapCandidates = testInitCands cfg.Genesis.FbkMigrationBlockHeight = 1 + cfg.Genesis.ToBeEnabledBlockHeight = 0 + cfg.Genesis.EndorsementWithdrawWaitingBlocks = 10 t.Run("test native staking", func(t *testing.T) { testNativeStaking(cfg, t)