Skip to content

Commit 1a63b13

Browse files
authored
[blockindex] restrict height for contract_staking_indexer during read operation (#3927)
1 parent 7a09f4b commit 1a63b13

File tree

12 files changed

+668
-269
lines changed

12 files changed

+668
-269
lines changed

action/protocol/staking/contractstake_indexer.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ type (
1717
ContractStakingIndexer interface {
1818
// CandidateVotes returns the total staked votes of a candidate
1919
// candidate identified by owner address
20-
CandidateVotes(ownerAddr address.Address) *big.Int
20+
CandidateVotes(ownerAddr address.Address, height uint64) (*big.Int, error)
2121
// Buckets returns active buckets
22-
Buckets() ([]*VoteBucket, error)
22+
Buckets(height uint64) ([]*VoteBucket, error)
2323
// BucketsByIndices returns active buckets by indices
24-
BucketsByIndices([]uint64) ([]*VoteBucket, error)
24+
BucketsByIndices([]uint64, uint64) ([]*VoteBucket, error)
2525
// BucketsByCandidate returns active buckets by candidate
26-
BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error)
26+
BucketsByCandidate(ownerAddr address.Address, height uint64) ([]*VoteBucket, error)
2727
// TotalBucketCount returns the total number of buckets including burned buckets
28-
TotalBucketCount() uint64
28+
TotalBucketCount(height uint64) (uint64, error)
2929
// BucketTypes returns the active bucket types
30-
BucketTypes() ([]*ContractStakingBucketType, error)
30+
BucketTypes(height uint64) ([]*ContractStakingBucketType, error)
3131
}
3232
)

action/protocol/staking/contractstake_indexer_mock.go

Lines changed: 28 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

action/protocol/staking/protocol.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,10 @@ func (p *Protocol) Validate(ctx context.Context, act action.Action, sr protocol.
467467

468468
// ActiveCandidates returns all active candidates in candidate center
469469
func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader, height uint64) (state.CandidateList, error) {
470+
srHeight, err := sr.Height()
471+
if err != nil {
472+
return nil, errors.Wrap(err, "failed to get StateReader height")
473+
}
470474
c, err := ConstructBaseView(sr)
471475
if err != nil {
472476
return nil, errors.Wrap(err, "failed to get ActiveCandidates")
@@ -476,7 +480,14 @@ func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader
476480
featureCtx := protocol.MustGetFeatureCtx(ctx)
477481
for i := range list {
478482
if p.contractStakingIndexer != nil && featureCtx.AddContractStakingVotes {
479-
list[i].Votes.Add(list[i].Votes, p.contractStakingIndexer.CandidateVotes(list[i].Owner))
483+
// specifying the height param instead of query latest from indexer directly, aims to cause error when indexer falls behind
484+
// currently there are two possible sr (i.e. factory or workingSet), it means the height could be chain height or current block height
485+
// using height-1 will cover the two scenario while detect whether the indexer is lagging behind
486+
contractVotes, err := p.contractStakingIndexer.CandidateVotes(list[i].Owner, srHeight-1)
487+
if err != nil {
488+
return nil, errors.Wrap(err, "failed to get CandidateVotes from contractStakingIndexer")
489+
}
490+
list[i].Votes.Add(list[i].Votes, contractVotes)
480491
}
481492
if list[i].SelfStake.Cmp(p.config.RegistrationConsts.MinSelfStake) >= 0 {
482493
cand = append(cand, list[i])

action/protocol/staking/protocol_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,72 @@ func Test_CreateGenesisStates(t *testing.T) {
394394
}
395395
}
396396
}
397+
398+
func TestProtocol_ActiveCandidates(t *testing.T) {
399+
require := require.New(t)
400+
ctrl := gomock.NewController(t)
401+
sm := testdb.NewMockStateManagerWithoutHeightFunc(ctrl)
402+
csIndexer := NewMockContractStakingIndexer(ctrl)
403+
404+
selfStake, _ := new(big.Int).SetString("1200000000000000000000000", 10)
405+
cfg := genesis.Default.Staking
406+
cfg.BootstrapCandidates = []genesis.BootstrapCandidate{
407+
{
408+
OwnerAddress: identityset.Address(22).String(),
409+
OperatorAddress: identityset.Address(23).String(),
410+
RewardAddress: identityset.Address(23).String(),
411+
Name: "test1",
412+
SelfStakingTokens: selfStake.String(),
413+
},
414+
}
415+
p, err := NewProtocol(nil, &BuilderConfig{
416+
Staking: cfg,
417+
PersistStakingPatchBlock: math.MaxUint64,
418+
}, nil, csIndexer, genesis.Default.GreenlandBlockHeight)
419+
require.NoError(err)
420+
421+
blkHeight := genesis.Default.QuebecBlockHeight + 1
422+
ctx := protocol.WithBlockCtx(
423+
genesis.WithGenesisContext(context.Background(), genesis.Default),
424+
protocol.BlockCtx{
425+
BlockHeight: blkHeight,
426+
},
427+
)
428+
ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
429+
sm.EXPECT().Height().Return(blkHeight, nil).AnyTimes()
430+
431+
v, err := p.Start(ctx, sm)
432+
require.NoError(err)
433+
require.NoError(sm.WriteView(_protocolID, v))
434+
435+
err = p.CreateGenesisStates(ctx, sm)
436+
require.NoError(err)
437+
438+
var csIndexerHeight, csVotes uint64
439+
csIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) {
440+
if height != csIndexerHeight {
441+
return nil, errors.Errorf("invalid height")
442+
}
443+
return big.NewInt(int64(csVotes)), nil
444+
}).AnyTimes()
445+
446+
t.Run("contract staking indexer falls behind", func(t *testing.T) {
447+
csIndexerHeight = 10
448+
_, err := p.ActiveCandidates(ctx, sm, 0)
449+
require.ErrorContains(err, "invalid height")
450+
})
451+
452+
t.Run("contract staking indexer up to date", func(t *testing.T) {
453+
csIndexerHeight = blkHeight - 1
454+
csVotes = 0
455+
cands, err := p.ActiveCandidates(ctx, sm, 0)
456+
require.NoError(err)
457+
require.Len(cands, 1)
458+
originCandVotes := cands[0].Votes
459+
csVotes = 100
460+
cands, err = p.ActiveCandidates(ctx, sm, 0)
461+
require.NoError(err)
462+
require.Len(cands, 1)
463+
require.EqualValues(100, cands[0].Votes.Sub(cands[0].Votes, originCandVotes).Uint64())
464+
})
465+
}

action/protocol/staking/staking_statereader.go

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (c *compositeStakingStateReader) readStateBuckets(ctx context.Context, req
7373
}
7474

7575
// read LSD buckets
76-
lsdBuckets, err := c.contractIndexer.Buckets()
76+
lsdBuckets, err := c.contractIndexer.Buckets(inputHeight)
7777
if err != nil {
7878
return nil, 0, err
7979
}
@@ -99,7 +99,7 @@ func (c *compositeStakingStateReader) readStateBucketsByVoter(ctx context.Contex
9999
}
100100

101101
// read LSD buckets
102-
lsdBuckets, err := c.contractIndexer.Buckets()
102+
lsdBuckets, err := c.contractIndexer.Buckets(height)
103103
if err != nil {
104104
return nil, 0, err
105105
}
@@ -131,7 +131,7 @@ func (c *compositeStakingStateReader) readStateBucketsByCandidate(ctx context.Co
131131
if candidate == nil {
132132
return &iotextypes.VoteBucketList{}, height, nil
133133
}
134-
lsdBuckets, err := c.contractIndexer.BucketsByCandidate(candidate.Owner)
134+
lsdBuckets, err := c.contractIndexer.BucketsByCandidate(candidate.Owner, height)
135135
if err != nil {
136136
return nil, 0, err
137137
}
@@ -156,7 +156,7 @@ func (c *compositeStakingStateReader) readStateBucketByIndices(ctx context.Conte
156156
}
157157

158158
// read LSD buckets
159-
lsdBuckets, err := c.contractIndexer.BucketsByIndices(req.GetIndex())
159+
lsdBuckets, err := c.contractIndexer.BucketsByIndices(req.GetIndex(), height)
160160
if err != nil {
161161
return nil, 0, err
162162
}
@@ -177,12 +177,16 @@ func (c *compositeStakingStateReader) readStateBucketCount(ctx context.Context,
177177
if !c.isContractStakingEnabled() {
178178
return bucketCnt, height, nil
179179
}
180-
buckets, err := c.contractIndexer.Buckets()
180+
buckets, err := c.contractIndexer.Buckets(height)
181181
if err != nil {
182182
return nil, 0, err
183183
}
184184
bucketCnt.Active += uint64(len(buckets))
185-
bucketCnt.Total += c.contractIndexer.TotalBucketCount()
185+
tbc, err := c.contractIndexer.TotalBucketCount(height)
186+
if err != nil {
187+
return nil, 0, err
188+
}
189+
bucketCnt.Total += tbc
186190
return bucketCnt, height, nil
187191
}
188192

@@ -220,7 +224,7 @@ func (c *compositeStakingStateReader) readStateCandidates(ctx context.Context, r
220224
return candidates, height, nil
221225
}
222226
for _, candidate := range candidates.Candidates {
223-
if err = addContractStakingVotes(candidate, c.contractIndexer); err != nil {
227+
if err = addContractStakingVotes(candidate, c.contractIndexer, height); err != nil {
224228
return nil, 0, err
225229
}
226230
}
@@ -238,7 +242,7 @@ func (c *compositeStakingStateReader) readStateCandidateByName(ctx context.Conte
238242
if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes {
239243
return candidate, height, nil
240244
}
241-
if err := addContractStakingVotes(candidate, c.contractIndexer); err != nil {
245+
if err := addContractStakingVotes(candidate, c.contractIndexer, height); err != nil {
242246
return nil, 0, err
243247
}
244248
return candidate, height, nil
@@ -255,7 +259,7 @@ func (c *compositeStakingStateReader) readStateCandidateByAddress(ctx context.Co
255259
if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes {
256260
return candidate, height, nil
257261
}
258-
if err := addContractStakingVotes(candidate, c.contractIndexer); err != nil {
262+
if err := addContractStakingVotes(candidate, c.contractIndexer, height); err != nil {
259263
return nil, 0, err
260264
}
261265
return candidate, height, nil
@@ -275,7 +279,7 @@ func (c *compositeStakingStateReader) readStateTotalStakingAmount(ctx context.Co
275279
return accountMeta, height, nil
276280
}
277281
// add contract staking amount
278-
buckets, err := c.contractIndexer.Buckets()
282+
buckets, err := c.contractIndexer.Buckets(height)
279283
if err != nil {
280284
return nil, 0, err
281285
}
@@ -291,7 +295,8 @@ func (c *compositeStakingStateReader) readStateContractStakingBucketTypes(ctx co
291295
if !c.isContractStakingEnabled() {
292296
return &iotextypes.ContractStakingBucketTypeList{}, c.nativeSR.Height(), nil
293297
}
294-
bts, err := c.contractIndexer.BucketTypes()
298+
height := c.nativeSR.Height()
299+
bts, err := c.contractIndexer.BucketTypes(height)
295300
if err != nil {
296301
return nil, 0, err
297302
}
@@ -302,14 +307,14 @@ func (c *compositeStakingStateReader) readStateContractStakingBucketTypes(ctx co
302307
StakedDuration: uint32(bt.Duration),
303308
})
304309
}
305-
return &iotextypes.ContractStakingBucketTypeList{BucketTypes: pbBts}, c.nativeSR.Height(), nil
310+
return &iotextypes.ContractStakingBucketTypeList{BucketTypes: pbBts}, height, nil
306311
}
307312

308313
func (c *compositeStakingStateReader) isContractStakingEnabled() bool {
309314
return c.contractIndexer != nil
310315
}
311316

312-
func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingSR ContractStakingIndexer) error {
317+
func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingSR ContractStakingIndexer, height uint64) error {
313318
votes, ok := big.NewInt(0).SetString(candidate.TotalWeightedVotes, 10)
314319
if !ok {
315320
return errors.Errorf("invalid total weighted votes %s", candidate.TotalWeightedVotes)
@@ -318,7 +323,11 @@ func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingS
318323
if err != nil {
319324
return err
320325
}
321-
votes.Add(votes, contractStakingSR.CandidateVotes(addr))
326+
contractVotes, err := contractStakingSR.CandidateVotes(addr, height)
327+
if err != nil {
328+
return err
329+
}
330+
votes.Add(votes, contractVotes)
322331
candidate.TotalWeightedVotes = votes.String()
323332
return nil
324333
}

0 commit comments

Comments
 (0)