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 @@ -165,6 +165,7 @@ type (
StoreVoteOfNFTBucketIntoView bool
CandidateSlashByOwner bool
CandidateBLSPublicKeyNotCopied bool
OnlyOwnerCanUpdateBLSPublicKey bool
}

// FeatureWithHeightCtx provides feature check functions.
Expand Down Expand Up @@ -333,6 +334,7 @@ func WithFeatureCtx(ctx context.Context) context.Context {
StoreVoteOfNFTBucketIntoView: !g.IsXingu(height),
CandidateSlashByOwner: !g.IsXinguBeta(height),
CandidateBLSPublicKeyNotCopied: !g.IsXinguBeta(height),
OnlyOwnerCanUpdateBLSPublicKey: !g.IsToBeEnabled(height),
},
)
}
Expand Down
49 changes: 48 additions & 1 deletion action/protocol/staking/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@
c.BLSPubKey = act.BLSPubKey()
topics, eventData, err := action.PackCandidateRegisteredEvent(c.GetIdentifier(), c.Operator, c.Owner, c.Name, c.Reward, act.BLSPubKey())
if err != nil {
return log, nil, errors.Wrap(err, "failed to pack candidate register with BLS event")

Check failure on line 782 in action/protocol/staking/handlers.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "failed to pack candidate register with BLS event" 3 times.

See more on https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&issues=AZraR3BuJnciFo8Sdq7i&open=AZraR3BuJnciFo8Sdq7i&pullRequest=4759
}
log.AddEvent(topics, eventData)
if withSelfStake {
Expand Down Expand Up @@ -856,7 +856,14 @@
// only owner can update candidate
c := csm.GetByOwner(actCtx.Caller)
if c == nil {
return log, errCandNotExist
if featureCtx.OnlyOwnerCanUpdateBLSPublicKey {
return log, errCandNotExist
}
c = csm.GetByOperator(actCtx.Caller)
if c == nil {
return log, errCandNotExist
}
return p.handleCandidateUpdateByOperator(ctx, act, csm, c, log)
}

if len(act.Name()) != 0 {
Expand Down Expand Up @@ -893,6 +900,46 @@
return log, nil
}

func (p *Protocol) handleCandidateUpdateByOperator(ctx context.Context, act *action.CandidateUpdate, csm CandidateStateManager, c *Candidate, log *receiptLog) (*receiptLog, error) {

Check warning on line 903 in action/protocol/staking/handlers.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unused parameter 'ctx' should be removed.

See more on https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&issues=AZraR3BuJnciFo8Sdq7h&open=AZraR3BuJnciFo8Sdq7h&pullRequest=4759
// operator can only update BLS public key
if !act.WithBLS() {
return log, &handleError{
err: errors.New("BLS public key must be provided when updating by operator"),
failureStatus: iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
}
}
if len(act.Name()) > 0 && act.Name() != c.Name {
return log, &handleError{
err: errors.New("candidate name cannot be updated by operator"),
failureStatus: iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
}
}
if act.OperatorAddress() != nil && !address.Equal(act.OperatorAddress(), c.Operator) {
return log, &handleError{
err: errors.New("candidate operator cannot be updated by operator"),
failureStatus: iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
}
}
if act.RewardAddress() != nil && !address.Equal(act.RewardAddress(), c.Reward) {
return log, &handleError{
err: errors.New("candidate reward address cannot be updated by operator"),
failureStatus: iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
}
}
// update BLS public key
c.BLSPubKey = act.BLSPubKey()
topics, eventData, err := action.PackCandidateUpdatedEvent(c.GetIdentifier(), c.Operator, c.Owner, c.Name, c.Reward, act.BLSPubKey())
if err != nil {
return log, errors.Wrap(err, "failed to pack candidate register with BLS event")
}
log.AddEvent(topics, eventData)
log.AddTopics(c.GetIdentifier().Bytes())
if err := csm.Upsert(c); err != nil {
return log, csmErrorToHandleError(c.GetIdentifier().String(), err)
}
return log, nil
}

func (p *Protocol) fetchBucket(csm NativeBucketGetByIndex, index uint64) (*VoteBucket, ReceiptError) {
bucket, err := csm.NativeBucket(index)
if err != nil {
Expand Down
30 changes: 30 additions & 0 deletions action/signedaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,36 @@
return selp, nil
}

// SignedCandidateUpdateWithBLS returns a signed candidate update with BLS public key
func SignedCandidateUpdateWithBLS(

Check warning on line 165 in action/signedaction.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This function has 9 parameters, which is greater than the 7 authorized.

See more on https://sonarcloud.io/project/issues?id=iotexproject_iotex-core&issues=AZraR3FMJnciFo8Sdq7j&open=AZraR3FMJnciFo8Sdq7j&pullRequest=4759
nonce uint64,
name, operatorAddrStr, rewardAddrStr string,
blsPubKey []byte,
gasLimit uint64,
gasPrice *big.Int,
registererPriKey crypto.PrivateKey,
options ...SignedActionOption,
) (*SealedEnvelope, error) {
cu, err := NewCandidateUpdateWithBLS(name, operatorAddrStr, rewardAddrStr, blsPubKey)
if err != nil {
return nil, err
}
bd := &EnvelopeBuilder{}
bd = bd.SetNonce(nonce).
SetGasPrice(gasPrice).
SetGasLimit(gasLimit).
SetAction(cu)
for _, opt := range options {
opt(bd)
}
elp := bd.Build()
selp, err := Sign(elp, registererPriKey)
if err != nil {
return nil, errors.Wrapf(err, "failed to sign candidate update %v", elp)
}
return selp, nil
}

// SignedCandidateActivate returns a signed candidate selfstake
func SignedCandidateActivate(
nonce uint64,
Expand Down
41 changes: 35 additions & 6 deletions e2etest/native_staking_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,8 @@ func TestCandidateBLSPublicKey(t *testing.T) {
cfg := initCfg(require)
cfg.Genesis.WakeBlockHeight = 1
cfg.Genesis.XinguBlockHeight = 10 // enable CandidateBLSPublicKey feature
cfg.Genesis.XinguBetaBlockHeight = 11
cfg.Genesis.ToBeEnabledBlockHeight = 20 // enable candidate BLS key update by operator feature
cfg.Genesis.SystemStakingContractAddress = ""
cfg.Genesis.SystemStakingContractV2Address = ""
cfg.Genesis.SystemStakingContractV3Address = ""
Expand All @@ -1592,10 +1594,12 @@ func TestCandidateBLSPublicKey(t *testing.T) {
test := newE2ETest(t, cfg)

var (
chainID = test.cfg.Chain.ID
registerAmount = unit.ConvertIotxToRau(1200000)
candOwnerID = 3
candOwnerID2 = 4
chainID = test.cfg.Chain.ID
registerAmount = unit.ConvertIotxToRau(1200000)
candOwnerID = 3
candOperatorID = 1
candOwnerID2 = 4
candOperatorID2 = 2
)
genTransferActionsWithPrice := func(n int, price *big.Int) []*actionWithTime {
acts := make([]*actionWithTime, n)
Expand All @@ -1611,7 +1615,7 @@ func TestCandidateBLSPublicKey(t *testing.T) {
{
name: "register without bls key",
acts: []*actionWithTime{
{mustNoErr(action.SignedCandidateRegister(test.nonceMgr.pop(identityset.Address(candOwnerID).String()), "cand1", identityset.Address(1).String(), identityset.Address(1).String(), identityset.Address(candOwnerID).String(), registerAmount.String(), 1, true, nil, gasLimit, gasPrice1559, identityset.PrivateKey(candOwnerID), action.WithChainID(chainID))), time.Now()},
{mustNoErr(action.SignedCandidateRegister(test.nonceMgr.pop(identityset.Address(candOwnerID).String()), "cand1", identityset.Address(candOperatorID).String(), identityset.Address(1).String(), identityset.Address(candOwnerID).String(), registerAmount.String(), 1, true, nil, gasLimit, gasPrice1559, identityset.PrivateKey(candOwnerID), action.WithChainID(chainID))), time.Now()},
},
blockExpect: func(test *e2etest, blk *block.Block, err error) {
require.NoError(err)
Expand All @@ -1627,7 +1631,7 @@ func TestCandidateBLSPublicKey(t *testing.T) {
name: "register with bls key",
preActs: genTransferActionsWithPrice(int(cfg.Genesis.XinguBlockHeight), gasPrice1559),
acts: []*actionWithTime{
{mustNoErr(action.SignedCandidateRegisterWithBLS(test.nonceMgr.pop(identityset.Address(candOwnerID2).String()), "cand2", identityset.Address(2).String(), identityset.Address(2).String(), identityset.Address(candOwnerID2).String(), registerAmount.String(), 1, true, blsPubKey, []byte{1, 2, 3}, gasLimit, gasPrice, identityset.PrivateKey(candOwnerID2), action.WithChainID(chainID))), time.Now()},
{mustNoErr(action.SignedCandidateRegisterWithBLS(test.nonceMgr.pop(identityset.Address(candOwnerID2).String()), "cand2", identityset.Address(candOperatorID2).String(), identityset.Address(2).String(), identityset.Address(candOwnerID2).String(), registerAmount.String(), 1, true, blsPubKey, []byte{1, 2, 3}, gasLimit, gasPrice, identityset.PrivateKey(candOwnerID2), action.WithChainID(chainID))), time.Now()},
},
blockExpect: func(test *e2etest, blk *block.Block, err error) {
require.NoError(err)
Expand All @@ -1640,6 +1644,31 @@ func TestCandidateBLSPublicKey(t *testing.T) {
},
},
})
height, err := test.cs.BlockDAO().Height()
require.NoError(err)
jumps := int(cfg.Genesis.ToBeEnabledBlockHeight - height)
if jumps <= 0 {
jumps = 1
}
blsPrivKey2, err := crypto.GenerateBLS12381PrivateKey(identityset.PrivateKey(candOperatorID).Bytes())
require.NoError(err)
test.run([]*testcase{
{
name: "update bls key by operator",
preActs: genTransferActionsWithPrice(jumps, gasPrice1559),
acts: []*actionWithTime{
{mustNoErr(action.SignedCandidateUpdateWithBLS(test.nonceMgr.pop(identityset.Address(candOperatorID).String()), "cand1", identityset.Address(candOperatorID).String(), "", blsPrivKey2.PublicKey().Bytes(), gasLimit, gasPrice, identityset.PrivateKey(candOperatorID), action.WithChainID(chainID))), time.Now()},
},
blockExpect: func(test *e2etest, blk *block.Block, err error) {
require.NoError(err)
require.EqualValues(2, len(blk.Receipts))
require.EqualValues(iotextypes.ReceiptStatus_Success, blk.Receipts[0].Status)
cand, err := test.getCandidateByName("cand1")
require.NoError(err)
require.EqualValues(blsPrivKey2.PublicKey().Bytes(), cand.BlsPubKey)
},
},
})
}

func parseNativeStakedBucketIndex(receipt *action.Receipt) []uint64 {
Expand Down