Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[blockindex] restrict height for contract_staking_indexer during read operation #3927

Merged
merged 6 commits into from
Aug 25, 2023
Merged
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
12 changes: 6 additions & 6 deletions action/protocol/staking/contractstake_indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ type (
ContractStakingIndexer interface {
// CandidateVotes returns the total staked votes of a candidate
// candidate identified by owner address
CandidateVotes(ownerAddr address.Address) *big.Int
CandidateVotes(ownerAddr address.Address, height uint64) (*big.Int, error)
// Buckets returns active buckets
Buckets() ([]*VoteBucket, error)
Buckets(height uint64) ([]*VoteBucket, error)
// BucketsByIndices returns active buckets by indices
BucketsByIndices([]uint64) ([]*VoteBucket, error)
BucketsByIndices([]uint64, uint64) ([]*VoteBucket, error)
// BucketsByCandidate returns active buckets by candidate
BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error)
BucketsByCandidate(ownerAddr address.Address, height uint64) ([]*VoteBucket, error)
// TotalBucketCount returns the total number of buckets including burned buckets
TotalBucketCount() uint64
TotalBucketCount(height uint64) (uint64, error)
// BucketTypes returns the active bucket types
BucketTypes() ([]*ContractStakingBucketType, error)
BucketTypes(height uint64) ([]*ContractStakingBucketType, error)
}
)
54 changes: 28 additions & 26 deletions action/protocol/staking/contractstake_indexer_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion action/protocol/staking/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@ func (p *Protocol) Validate(ctx context.Context, act action.Action, sr protocol.

// ActiveCandidates returns all active candidates in candidate center
func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader, height uint64) (state.CandidateList, error) {
srHeight, err := sr.Height()
if err != nil {
return nil, errors.Wrap(err, "failed to get StateReader height")
}
c, err := ConstructBaseView(sr)
if err != nil {
return nil, errors.Wrap(err, "failed to get ActiveCandidates")
Expand All @@ -476,7 +480,14 @@ func (p *Protocol) ActiveCandidates(ctx context.Context, sr protocol.StateReader
featureCtx := protocol.MustGetFeatureCtx(ctx)
for i := range list {
if p.contractStakingIndexer != nil && featureCtx.AddContractStakingVotes {
list[i].Votes.Add(list[i].Votes, p.contractStakingIndexer.CandidateVotes(list[i].Owner))
// specifying the height param instead of query latest from indexer directly, aims to cause error when indexer falls behind
// currently there are two possible sr (i.e. factory or workingSet), it means the height could be chain height or current block height
// using height-1 will cover the two scenario while detect whether the indexer is lagging behind
contractVotes, err := p.contractStakingIndexer.CandidateVotes(list[i].Owner, srHeight-1)
if err != nil {
return nil, errors.Wrap(err, "failed to get CandidateVotes from contractStakingIndexer")
}
list[i].Votes.Add(list[i].Votes, contractVotes)
}
if list[i].SelfStake.Cmp(p.config.RegistrationConsts.MinSelfStake) >= 0 {
cand = append(cand, list[i])
Expand Down
69 changes: 69 additions & 0 deletions action/protocol/staking/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,72 @@ func Test_CreateGenesisStates(t *testing.T) {
}
}
}

func TestProtocol_ActiveCandidates(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
sm := testdb.NewMockStateManagerWithoutHeightFunc(ctrl)
csIndexer := NewMockContractStakingIndexer(ctrl)

selfStake, _ := new(big.Int).SetString("1200000000000000000000000", 10)
cfg := genesis.Default.Staking
cfg.BootstrapCandidates = []genesis.BootstrapCandidate{
{
OwnerAddress: identityset.Address(22).String(),
OperatorAddress: identityset.Address(23).String(),
RewardAddress: identityset.Address(23).String(),
Name: "test1",
SelfStakingTokens: selfStake.String(),
},
}
p, err := NewProtocol(nil, &BuilderConfig{
Staking: cfg,
PersistStakingPatchBlock: math.MaxUint64,
}, nil, csIndexer, genesis.Default.GreenlandBlockHeight)
require.NoError(err)

blkHeight := genesis.Default.QuebecBlockHeight + 1
ctx := protocol.WithBlockCtx(
genesis.WithGenesisContext(context.Background(), genesis.Default),
protocol.BlockCtx{
BlockHeight: blkHeight,
},
)
ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx))
sm.EXPECT().Height().Return(blkHeight, nil).AnyTimes()

v, err := p.Start(ctx, sm)
require.NoError(err)
require.NoError(sm.WriteView(_protocolID, v))

err = p.CreateGenesisStates(ctx, sm)
require.NoError(err)

var csIndexerHeight, csVotes uint64
csIndexer.EXPECT().CandidateVotes(gomock.Any(), gomock.Any()).DoAndReturn(func(ownerAddr address.Address, height uint64) (*big.Int, error) {
if height != csIndexerHeight {
return nil, errors.Errorf("invalid height")
}
return big.NewInt(int64(csVotes)), nil
}).AnyTimes()

t.Run("contract staking indexer falls behind", func(t *testing.T) {
csIndexerHeight = 10
_, err := p.ActiveCandidates(ctx, sm, 0)
require.ErrorContains(err, "invalid height")
})

t.Run("contract staking indexer up to date", func(t *testing.T) {
csIndexerHeight = blkHeight - 1
csVotes = 0
cands, err := p.ActiveCandidates(ctx, sm, 0)
require.NoError(err)
require.Len(cands, 1)
originCandVotes := cands[0].Votes
csVotes = 100
cands, err = p.ActiveCandidates(ctx, sm, 0)
require.NoError(err)
require.Len(cands, 1)
require.EqualValues(100, cands[0].Votes.Sub(cands[0].Votes, originCandVotes).Uint64())
})
}
37 changes: 23 additions & 14 deletions action/protocol/staking/staking_statereader.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (c *compositeStakingStateReader) readStateBuckets(ctx context.Context, req
}

// read LSD buckets
lsdBuckets, err := c.contractIndexer.Buckets()
lsdBuckets, err := c.contractIndexer.Buckets(inputHeight)
if err != nil {
return nil, 0, err
}
Expand All @@ -99,7 +99,7 @@ func (c *compositeStakingStateReader) readStateBucketsByVoter(ctx context.Contex
}

// read LSD buckets
lsdBuckets, err := c.contractIndexer.Buckets()
lsdBuckets, err := c.contractIndexer.Buckets(height)
if err != nil {
return nil, 0, err
}
Expand Down Expand Up @@ -131,7 +131,7 @@ func (c *compositeStakingStateReader) readStateBucketsByCandidate(ctx context.Co
if candidate == nil {
return &iotextypes.VoteBucketList{}, height, nil
}
lsdBuckets, err := c.contractIndexer.BucketsByCandidate(candidate.Owner)
lsdBuckets, err := c.contractIndexer.BucketsByCandidate(candidate.Owner, height)
if err != nil {
return nil, 0, err
}
Expand All @@ -156,7 +156,7 @@ func (c *compositeStakingStateReader) readStateBucketByIndices(ctx context.Conte
}

// read LSD buckets
lsdBuckets, err := c.contractIndexer.BucketsByIndices(req.GetIndex())
lsdBuckets, err := c.contractIndexer.BucketsByIndices(req.GetIndex(), height)
if err != nil {
return nil, 0, err
}
Expand All @@ -177,12 +177,16 @@ func (c *compositeStakingStateReader) readStateBucketCount(ctx context.Context,
if !c.isContractStakingEnabled() {
return bucketCnt, height, nil
}
buckets, err := c.contractIndexer.Buckets()
buckets, err := c.contractIndexer.Buckets(height)
if err != nil {
return nil, 0, err
}
bucketCnt.Active += uint64(len(buckets))
bucketCnt.Total += c.contractIndexer.TotalBucketCount()
tbc, err := c.contractIndexer.TotalBucketCount(height)
if err != nil {
return nil, 0, err
}
bucketCnt.Total += tbc
return bucketCnt, height, nil
}

Expand Down Expand Up @@ -220,7 +224,7 @@ func (c *compositeStakingStateReader) readStateCandidates(ctx context.Context, r
return candidates, height, nil
}
for _, candidate := range candidates.Candidates {
if err = addContractStakingVotes(candidate, c.contractIndexer); err != nil {
if err = addContractStakingVotes(candidate, c.contractIndexer, height); err != nil {
return nil, 0, err
}
}
Expand All @@ -238,7 +242,7 @@ func (c *compositeStakingStateReader) readStateCandidateByName(ctx context.Conte
if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes {
return candidate, height, nil
}
if err := addContractStakingVotes(candidate, c.contractIndexer); err != nil {
if err := addContractStakingVotes(candidate, c.contractIndexer, height); err != nil {
return nil, 0, err
}
return candidate, height, nil
Expand All @@ -255,7 +259,7 @@ func (c *compositeStakingStateReader) readStateCandidateByAddress(ctx context.Co
if !protocol.MustGetFeatureCtx(ctx).AddContractStakingVotes {
return candidate, height, nil
}
if err := addContractStakingVotes(candidate, c.contractIndexer); err != nil {
if err := addContractStakingVotes(candidate, c.contractIndexer, height); err != nil {
return nil, 0, err
}
return candidate, height, nil
Expand All @@ -275,7 +279,7 @@ func (c *compositeStakingStateReader) readStateTotalStakingAmount(ctx context.Co
return accountMeta, height, nil
}
// add contract staking amount
buckets, err := c.contractIndexer.Buckets()
buckets, err := c.contractIndexer.Buckets(height)
if err != nil {
return nil, 0, err
}
Expand All @@ -291,7 +295,8 @@ func (c *compositeStakingStateReader) readStateContractStakingBucketTypes(ctx co
if !c.isContractStakingEnabled() {
return &iotextypes.ContractStakingBucketTypeList{}, c.nativeSR.Height(), nil
}
bts, err := c.contractIndexer.BucketTypes()
height := c.nativeSR.Height()
bts, err := c.contractIndexer.BucketTypes(height)
if err != nil {
return nil, 0, err
}
Expand All @@ -302,14 +307,14 @@ func (c *compositeStakingStateReader) readStateContractStakingBucketTypes(ctx co
StakedDuration: uint32(bt.Duration),
})
}
return &iotextypes.ContractStakingBucketTypeList{BucketTypes: pbBts}, c.nativeSR.Height(), nil
return &iotextypes.ContractStakingBucketTypeList{BucketTypes: pbBts}, height, nil
}

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

func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingSR ContractStakingIndexer) error {
func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingSR ContractStakingIndexer, height uint64) error {
votes, ok := big.NewInt(0).SetString(candidate.TotalWeightedVotes, 10)
if !ok {
return errors.Errorf("invalid total weighted votes %s", candidate.TotalWeightedVotes)
Expand All @@ -318,7 +323,11 @@ func addContractStakingVotes(candidate *iotextypes.CandidateV2, contractStakingS
if err != nil {
return err
}
votes.Add(votes, contractStakingSR.CandidateVotes(addr))
contractVotes, err := contractStakingSR.CandidateVotes(addr, height)
if err != nil {
return err
}
votes.Add(votes, contractVotes)
candidate.TotalWeightedVotes = votes.String()
return nil
}
Expand Down
Loading