Skip to content

Commit aa809bb

Browse files
authored
operator can update the candidate BLS publickey (#4759)
1 parent 0118335 commit aa809bb

File tree

4 files changed

+115
-7
lines changed

4 files changed

+115
-7
lines changed

action/protocol/context.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ type (
165165
StoreVoteOfNFTBucketIntoView bool
166166
CandidateSlashByOwner bool
167167
CandidateBLSPublicKeyNotCopied bool
168+
OnlyOwnerCanUpdateBLSPublicKey bool
168169
}
169170

170171
// FeatureWithHeightCtx provides feature check functions.
@@ -333,6 +334,7 @@ func WithFeatureCtx(ctx context.Context) context.Context {
333334
StoreVoteOfNFTBucketIntoView: !g.IsXingu(height),
334335
CandidateSlashByOwner: !g.IsXinguBeta(height),
335336
CandidateBLSPublicKeyNotCopied: !g.IsXinguBeta(height),
337+
OnlyOwnerCanUpdateBLSPublicKey: !g.IsToBeEnabled(height),
336338
},
337339
)
338340
}

action/protocol/staking/handlers.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,14 @@ func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.Candid
856856
// only owner can update candidate
857857
c := csm.GetByOwner(actCtx.Caller)
858858
if c == nil {
859-
return log, errCandNotExist
859+
if featureCtx.OnlyOwnerCanUpdateBLSPublicKey {
860+
return log, errCandNotExist
861+
}
862+
c = csm.GetByOperator(actCtx.Caller)
863+
if c == nil {
864+
return log, errCandNotExist
865+
}
866+
return p.handleCandidateUpdateByOperator(ctx, act, csm, c, log)
860867
}
861868

862869
if len(act.Name()) != 0 {
@@ -893,6 +900,46 @@ func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.Candid
893900
return log, nil
894901
}
895902

903+
func (p *Protocol) handleCandidateUpdateByOperator(ctx context.Context, act *action.CandidateUpdate, csm CandidateStateManager, c *Candidate, log *receiptLog) (*receiptLog, error) {
904+
// operator can only update BLS public key
905+
if !act.WithBLS() {
906+
return log, &handleError{
907+
err: errors.New("BLS public key must be provided when updating by operator"),
908+
failureStatus: iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
909+
}
910+
}
911+
if len(act.Name()) > 0 && act.Name() != c.Name {
912+
return log, &handleError{
913+
err: errors.New("candidate name cannot be updated by operator"),
914+
failureStatus: iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
915+
}
916+
}
917+
if act.OperatorAddress() != nil && !address.Equal(act.OperatorAddress(), c.Operator) {
918+
return log, &handleError{
919+
err: errors.New("candidate operator cannot be updated by operator"),
920+
failureStatus: iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
921+
}
922+
}
923+
if act.RewardAddress() != nil && !address.Equal(act.RewardAddress(), c.Reward) {
924+
return log, &handleError{
925+
err: errors.New("candidate reward address cannot be updated by operator"),
926+
failureStatus: iotextypes.ReceiptStatus_ErrUnauthorizedOperator,
927+
}
928+
}
929+
// update BLS public key
930+
c.BLSPubKey = act.BLSPubKey()
931+
topics, eventData, err := action.PackCandidateUpdatedEvent(c.GetIdentifier(), c.Operator, c.Owner, c.Name, c.Reward, act.BLSPubKey())
932+
if err != nil {
933+
return log, errors.Wrap(err, "failed to pack candidate register with BLS event")
934+
}
935+
log.AddEvent(topics, eventData)
936+
log.AddTopics(c.GetIdentifier().Bytes())
937+
if err := csm.Upsert(c); err != nil {
938+
return log, csmErrorToHandleError(c.GetIdentifier().String(), err)
939+
}
940+
return log, nil
941+
}
942+
896943
func (p *Protocol) fetchBucket(csm NativeBucketGetByIndex, index uint64) (*VoteBucket, ReceiptError) {
897944
bucket, err := csm.NativeBucket(index)
898945
if err != nil {

action/signedaction.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,36 @@ func SignedCandidateUpdate(
161161
return selp, nil
162162
}
163163

164+
// SignedCandidateUpdateWithBLS returns a signed candidate update with BLS public key
165+
func SignedCandidateUpdateWithBLS(
166+
nonce uint64,
167+
name, operatorAddrStr, rewardAddrStr string,
168+
blsPubKey []byte,
169+
gasLimit uint64,
170+
gasPrice *big.Int,
171+
registererPriKey crypto.PrivateKey,
172+
options ...SignedActionOption,
173+
) (*SealedEnvelope, error) {
174+
cu, err := NewCandidateUpdateWithBLS(name, operatorAddrStr, rewardAddrStr, blsPubKey)
175+
if err != nil {
176+
return nil, err
177+
}
178+
bd := &EnvelopeBuilder{}
179+
bd = bd.SetNonce(nonce).
180+
SetGasPrice(gasPrice).
181+
SetGasLimit(gasLimit).
182+
SetAction(cu)
183+
for _, opt := range options {
184+
opt(bd)
185+
}
186+
elp := bd.Build()
187+
selp, err := Sign(elp, registererPriKey)
188+
if err != nil {
189+
return nil, errors.Wrapf(err, "failed to sign candidate update %v", elp)
190+
}
191+
return selp, nil
192+
}
193+
164194
// SignedCandidateActivate returns a signed candidate selfstake
165195
func SignedCandidateActivate(
166196
nonce uint64,

e2etest/native_staking_test.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,8 @@ func TestCandidateBLSPublicKey(t *testing.T) {
15811581
cfg := initCfg(require)
15821582
cfg.Genesis.WakeBlockHeight = 1
15831583
cfg.Genesis.XinguBlockHeight = 10 // enable CandidateBLSPublicKey feature
1584+
cfg.Genesis.XinguBetaBlockHeight = 11
1585+
cfg.Genesis.ToBeEnabledBlockHeight = 20 // enable candidate BLS key update by operator feature
15841586
cfg.Genesis.SystemStakingContractAddress = ""
15851587
cfg.Genesis.SystemStakingContractV2Address = ""
15861588
cfg.Genesis.SystemStakingContractV3Address = ""
@@ -1592,10 +1594,12 @@ func TestCandidateBLSPublicKey(t *testing.T) {
15921594
test := newE2ETest(t, cfg)
15931595

15941596
var (
1595-
chainID = test.cfg.Chain.ID
1596-
registerAmount = unit.ConvertIotxToRau(1200000)
1597-
candOwnerID = 3
1598-
candOwnerID2 = 4
1597+
chainID = test.cfg.Chain.ID
1598+
registerAmount = unit.ConvertIotxToRau(1200000)
1599+
candOwnerID = 3
1600+
candOperatorID = 1
1601+
candOwnerID2 = 4
1602+
candOperatorID2 = 2
15991603
)
16001604
genTransferActionsWithPrice := func(n int, price *big.Int) []*actionWithTime {
16011605
acts := make([]*actionWithTime, n)
@@ -1611,7 +1615,7 @@ func TestCandidateBLSPublicKey(t *testing.T) {
16111615
{
16121616
name: "register without bls key",
16131617
acts: []*actionWithTime{
1614-
{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()},
1618+
{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()},
16151619
},
16161620
blockExpect: func(test *e2etest, blk *block.Block, err error) {
16171621
require.NoError(err)
@@ -1627,7 +1631,7 @@ func TestCandidateBLSPublicKey(t *testing.T) {
16271631
name: "register with bls key",
16281632
preActs: genTransferActionsWithPrice(int(cfg.Genesis.XinguBlockHeight), gasPrice1559),
16291633
acts: []*actionWithTime{
1630-
{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()},
1634+
{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()},
16311635
},
16321636
blockExpect: func(test *e2etest, blk *block.Block, err error) {
16331637
require.NoError(err)
@@ -1640,6 +1644,31 @@ func TestCandidateBLSPublicKey(t *testing.T) {
16401644
},
16411645
},
16421646
})
1647+
height, err := test.cs.BlockDAO().Height()
1648+
require.NoError(err)
1649+
jumps := int(cfg.Genesis.ToBeEnabledBlockHeight - height)
1650+
if jumps <= 0 {
1651+
jumps = 1
1652+
}
1653+
blsPrivKey2, err := crypto.GenerateBLS12381PrivateKey(identityset.PrivateKey(candOperatorID).Bytes())
1654+
require.NoError(err)
1655+
test.run([]*testcase{
1656+
{
1657+
name: "update bls key by operator",
1658+
preActs: genTransferActionsWithPrice(jumps, gasPrice1559),
1659+
acts: []*actionWithTime{
1660+
{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()},
1661+
},
1662+
blockExpect: func(test *e2etest, blk *block.Block, err error) {
1663+
require.NoError(err)
1664+
require.EqualValues(2, len(blk.Receipts))
1665+
require.EqualValues(iotextypes.ReceiptStatus_Success, blk.Receipts[0].Status)
1666+
cand, err := test.getCandidateByName("cand1")
1667+
require.NoError(err)
1668+
require.EqualValues(blsPrivKey2.PublicKey().Bytes(), cand.BlsPubKey)
1669+
},
1670+
},
1671+
})
16431672
}
16441673

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

0 commit comments

Comments
 (0)