From e998814c2e18307c7edc5eeed0894570947b4070 Mon Sep 17 00:00:00 2001 From: zhi Date: Fri, 11 Jul 2025 17:38:31 +0800 Subject: [PATCH 1/2] store contract staking data in trie --- action/protocol/context.go | 2 + action/protocol/mock_protocol.go | 2 +- action/protocol/protocol.go | 6 +- action/protocol/staking/bucket_index_test.go | 12 +- action/protocol/staking/bucket_pool.go | 2 +- action/protocol/staking/candidate_center.go | 22 +- .../protocol/staking/candidate_center_test.go | 8 +- .../staking/candidate_statemanager.go | 18 +- .../protocol/staking/candidate_statereader.go | 86 ++- action/protocol/staking/candidate_test.go | 6 +- .../staking/contractstake_bucket_type.go | 56 +- .../protocol/staking/contractstake_indexer.go | 7 +- .../staking/contractstake_indexer_mock.go | 33 +- .../staking/contractstake_indexer_test.go | 6 +- .../staking/contractstaking/bucket.go | 112 ++++ .../staking/contractstaking/bucket_type.go | 67 +++ .../staking/contractstaking/contract.go | 52 ++ .../staking/contractstaking/statemanager.go | 65 +++ .../contractstaking/statemanager_test.go | 183 +++++++ .../staking/contractstaking/statereader.go | 149 ++++++ .../contractstaking/statereader_test.go | 138 +++++ .../handler_candidate_endorsement_test.go | 6 +- .../handler_candidate_selfstake_test.go | 4 +- .../handler_candidate_transfer_ownership.go | 2 +- ...ndler_candidate_transfer_ownership_test.go | 2 +- .../staking/handler_stake_migrate_test.go | 4 +- action/protocol/staking/handlers.go | 4 +- action/protocol/staking/handlers_test.go | 86 +-- action/protocol/staking/protocol.go | 74 +-- action/protocol/staking/protocol_test.go | 13 +- action/protocol/staking/read_state.go | 2 +- action/protocol/staking/stakeview_builder.go | 4 +- .../protocol/staking/staking_statereader.go | 2 +- .../staking/staking_statereader_test.go | 2 +- .../protocol/staking/stakingpb/staking.pb.go | 499 ++++++++++++------ .../protocol/staking/stakingpb/staking.proto | 15 + action/protocol/staking/util.go | 39 ++ action/protocol/staking/viewdata.go | 66 ++- action/protocol/staking/viewdata_test.go | 42 +- action/protocol/staking/vote_bucket_test.go | 14 +- action/protocol/staking/vote_reviser.go | 4 +- action/protocol/staking/vote_reviser_test.go | 6 +- blockindex/contractstaking/bucket_info.go | 5 +- blockindex/contractstaking/cache.go | 81 ++- blockindex/contractstaking/cache_test.go | 13 +- blockindex/contractstaking/dirty_cache.go | 24 +- .../contractstaking/dirty_cache_test.go | 9 +- blockindex/contractstaking/event_handler.go | 18 + blockindex/contractstaking/indexer.go | 145 +++-- blockindex/contractstaking/indexer_test.go | 62 +-- blockindex/contractstaking/stakeview.go | 96 +++- blockindex/contractstaking/wrappedcache.go | 29 +- chainservice/builder.go | 12 +- state/factory/statedb.go | 11 +- state/factory/util.go | 4 +- state/factory/workingset.go | 6 + state/factory/workingset_test.go | 2 +- systemcontractindex/common.go | 7 +- systemcontractindex/stakingindex/bucket.go | 16 +- .../stakingindex/bucket_test.go | 114 ++-- systemcontractindex/stakingindex/cache.go | 42 +- .../stakingindex/cache_test.go | 74 +++ .../stakingindex/event_handler.go | 89 +++- systemcontractindex/stakingindex/index.go | 161 +++--- systemcontractindex/stakingindex/stakeview.go | 76 ++- .../stakingindex/wrappedcache.go | 25 +- 66 files changed, 2222 insertions(+), 821 deletions(-) create mode 100644 action/protocol/staking/contractstaking/bucket.go create mode 100644 action/protocol/staking/contractstaking/bucket_type.go create mode 100644 action/protocol/staking/contractstaking/contract.go create mode 100644 action/protocol/staking/contractstaking/statemanager.go create mode 100644 action/protocol/staking/contractstaking/statemanager_test.go create mode 100644 action/protocol/staking/contractstaking/statereader.go create mode 100644 action/protocol/staking/contractstaking/statereader_test.go create mode 100644 action/protocol/staking/util.go create mode 100644 systemcontractindex/stakingindex/cache_test.go diff --git a/action/protocol/context.go b/action/protocol/context.go index 503e85aa6e..4153b97d7b 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -162,6 +162,7 @@ type ( NotSlashUnproductiveDelegates bool CandidateBLSPublicKey bool NotUseMinSelfStakeToBeActive bool + LoadContractStakingFromIndexer bool } // FeatureWithHeightCtx provides feature check functions. @@ -327,6 +328,7 @@ func WithFeatureCtx(ctx context.Context) context.Context { NotSlashUnproductiveDelegates: !g.IsToBeEnabled(height), CandidateBLSPublicKey: g.IsToBeEnabled(height), NotUseMinSelfStakeToBeActive: !g.IsToBeEnabled(height), + LoadContractStakingFromIndexer: !g.IsToBeEnabled(height), }, ) } diff --git a/action/protocol/mock_protocol.go b/action/protocol/mock_protocol.go index a32ece6851..ebdf0ed9dd 100644 --- a/action/protocol/mock_protocol.go +++ b/action/protocol/mock_protocol.go @@ -503,7 +503,7 @@ func (mr *MockViewMockRecorder) Clone() *gomock.Call { } // Commit mocks base method. -func (m *MockView) Commit(arg0 context.Context, arg1 StateReader) error { +func (m *MockView) Commit(arg0 context.Context, arg1 StateManager) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Commit", arg0, arg1) ret0, _ := ret[0].(error) diff --git a/action/protocol/protocol.go b/action/protocol/protocol.go index 39607e1928..4d609ddd80 100644 --- a/action/protocol/protocol.go +++ b/action/protocol/protocol.go @@ -115,7 +115,7 @@ type ( Fork() View Snapshot() int Revert(int) error - Commit(context.Context, StateReader) error + Commit(context.Context, StateManager) error } // Views stores the view for all protocols @@ -138,9 +138,9 @@ func (views *Views) Fork() *Views { return fork } -func (views *Views) Commit(ctx context.Context, sr StateReader) error { +func (views *Views) Commit(ctx context.Context, sm StateManager) error { for _, view := range views.vm { - if err := view.Commit(ctx, sr); err != nil { + if err := view.Commit(ctx, sm); err != nil { return err } } diff --git a/action/protocol/staking/bucket_index_test.go b/action/protocol/staking/bucket_index_test.go index 0665811a0c..0762b31929 100644 --- a/action/protocol/staking/bucket_index_test.go +++ b/action/protocol/staking/bucket_index_test.go @@ -123,18 +123,18 @@ func TestGetPutBucketIndex(t *testing.T) { // put buckets and get for i, e := range tests { - _, _, err := csr.voterBucketIndices(e.voterAddr) + _, _, err := csr.NativeBucketIndicesByVoter(e.voterAddr) if i == 0 { require.Equal(state.ErrStateNotExist, errors.Cause(err)) } - _, _, err = csr.candBucketIndices(e.candAddr) + _, _, err = csr.NativeBucketIndicesByCandidate(e.candAddr) if i == 0 { require.Equal(state.ErrStateNotExist, errors.Cause(err)) } // put voter bucket index require.NoError(csm.putVoterBucketIndex(e.voterAddr, e.index)) - bis, _, err := csr.voterBucketIndices(e.voterAddr) + bis, _, err := csr.NativeBucketIndicesByVoter(e.voterAddr) require.NoError(err) bucketIndices := *bis require.Equal(e.voterIndexSize, len(bucketIndices)) @@ -142,7 +142,7 @@ func TestGetPutBucketIndex(t *testing.T) { // put candidate bucket index require.NoError(csm.putCandBucketIndex(e.candAddr, e.index)) - bis, _, err = csr.candBucketIndices(e.candAddr) + bis, _, err = csr.NativeBucketIndicesByCandidate(e.candAddr) require.NoError(err) bucketIndices = *bis require.Equal(e.candIndexSize, len(bucketIndices)) @@ -152,7 +152,7 @@ func TestGetPutBucketIndex(t *testing.T) { for _, e := range tests { // delete voter bucket index require.NoError(csm.delVoterBucketIndex(e.voterAddr, e.index)) - bis, _, err := csr.voterBucketIndices(e.voterAddr) + bis, _, err := csr.NativeBucketIndicesByVoter(e.voterAddr) if e.voterIndexSize != indexSize { bucketIndices := *bis require.Equal(indexSize-e.voterIndexSize, len(bucketIndices)) @@ -162,7 +162,7 @@ func TestGetPutBucketIndex(t *testing.T) { // delete candidate bucket index require.NoError(csm.delCandBucketIndex(e.candAddr, e.index)) - bis, _, err = csr.candBucketIndices(e.candAddr) + bis, _, err = csr.NativeBucketIndicesByCandidate(e.candAddr) if e.candIndexSize != indexSize { bucketIndices := *bis require.Equal(indexSize-e.candIndexSize, len(bucketIndices)) diff --git a/action/protocol/staking/bucket_pool.go b/action/protocol/staking/bucket_pool.go index 9f970437a7..c5d998edcb 100644 --- a/action/protocol/staking/bucket_pool.go +++ b/action/protocol/staking/bucket_pool.go @@ -124,7 +124,7 @@ func (bp *BucketPool) Clone() *BucketPool { } // Commit is called upon workingset commit -func (bp *BucketPool) Commit(sr protocol.StateReader) error { +func (bp *BucketPool) Commit() error { bp.dirty = false return nil } diff --git a/action/protocol/staking/candidate_center.go b/action/protocol/staking/candidate_center.go index e6ff939694..9c8caeb8c6 100644 --- a/action/protocol/staking/candidate_center.go +++ b/action/protocol/staking/candidate_center.go @@ -126,15 +126,21 @@ func (m *CandidateCenter) commit() error { } // Commit writes the change into base -func (m *CandidateCenter) Commit(ctx context.Context, sr protocol.StateReader) error { - height, err := sr.Height() +func (m *CandidateCenter) Commit(ctx context.Context, sm protocol.StateManager) error { + height, err := sm.Height() if err != nil { return err } if featureWithHeightCtx, ok := protocol.GetFeatureWithHeightCtx(ctx); ok && featureWithHeightCtx.CandCenterHasAlias(height) { - return m.legacyCommit() + if err := m.legacyCommit(); err != nil { + return err + } + } else { + if err := m.commit(); err != nil { + return err + } } - return m.commit() + return m.writeToStateDB(sm) } // legacyCommit writes the change into base with legacy logic @@ -286,14 +292,14 @@ func (m *CandidateCenter) Upsert(d *Candidate) error { return nil } -// WriteToStateDB writes the candidate center to stateDB -func (m *CandidateCenter) WriteToStateDB(sm protocol.StateManager) error { +// writeToStateDB writes the candidate center to stateDB +func (m *CandidateCenter) writeToStateDB(sm protocol.StateManager) error { // persist nameMap/operatorMap and ownerList to stateDB name := m.base.candsInNameMap() op := m.base.candsInOperatorMap() owners := m.base.ownersList() - if len(name) == 0 || len(op) == 0 { - return ErrNilParameters + if len(name) == 0 || len(op) == 0 || len(owners) == 0 { + return nil } if _, err := sm.PutState(name, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_nameKey)); err != nil { return err diff --git a/action/protocol/staking/candidate_center_test.go b/action/protocol/staking/candidate_center_test.go index e85d53096c..4c192528d9 100644 --- a/action/protocol/staking/candidate_center_test.go +++ b/action/protocol/staking/candidate_center_test.go @@ -325,7 +325,7 @@ func TestFixAlias(t *testing.T) { } else { r.NoError(m.commit()) } - views.Write(_protocolID, &ViewData{ + views.Write(_protocolID, &viewData{ candCenter: m, }) @@ -376,7 +376,7 @@ func TestFixAlias(t *testing.T) { } else { r.NoError(center.commit()) } - views.Write(_protocolID, &ViewData{ + views.Write(_protocolID, &viewData{ candCenter: center, }) } @@ -476,7 +476,7 @@ func TestMultipleNonStakingCandidate(t *testing.T) { r.True(testEqual(candcenter, CandidateList(cands))) // from state manager views := protocol.NewViews() - views.Write(_protocolID, &ViewData{ + views.Write(_protocolID, &viewData{ candCenter: candcenter, }) candcenter = candCenterFromNewCandidateStateManager(r, views) @@ -538,7 +538,7 @@ func candCenterFromNewCandidateStateManager(r *require.Assertions, views *protoc // get cand center: csm.ConstructBaseView v, err := views.Read(_protocolID) r.NoError(err) - return v.(*ViewData).candCenter + return v.(*viewData).candCenter } func TestCandidateUpsert(t *testing.T) { diff --git a/action/protocol/staking/candidate_statemanager.go b/action/protocol/staking/candidate_statemanager.go index b51e248796..580bc833e0 100644 --- a/action/protocol/staking/candidate_statemanager.go +++ b/action/protocol/staking/candidate_statemanager.go @@ -40,10 +40,10 @@ type ( // CandidateStateManager is candidate state manager on top of StateManager CandidateStateManager interface { BucketSet - BucketGetByIndex + NativeBucketGetByIndex CandidateSet // candidate and bucket pool related - DirtyView() *ViewData + DirtyView() *viewData ContainsName(string) bool ContainsOwner(address.Address) bool ContainsOperator(address.Address) bool @@ -64,7 +64,7 @@ type ( ContainsSelfStakingBucket(uint64) bool GetByIdentifier(address.Address) *Candidate SR() protocol.StateReader - BucketGetByIndex + NativeBucketGetByIndex } candSM struct { @@ -108,15 +108,15 @@ func (csm *candSM) SR() protocol.StateReader { } // DirtyView is csm's current state, which reflects base view + applying delta saved in csm's dock -func (csm *candSM) DirtyView() *ViewData { +func (csm *candSM) DirtyView() *viewData { v, err := csm.StateManager.ReadView(_protocolID) if err != nil { log.S().Panic("failed to read view", zap.Error(err)) } - return &ViewData{ + return &viewData{ candCenter: csm.candCenter, bucketPool: csm.bucketPool, - contractsStake: v.(*ViewData).contractsStake, + contractsStake: v.(*viewData).contractsStake, } } @@ -175,12 +175,12 @@ func (csm *candSM) Commit(ctx context.Context) error { return csm.WriteView(_protocolID, view) } -func (csm *candSM) getBucket(index uint64) (*VoteBucket, error) { - return newCandidateStateReader(csm).getBucket(index) +func (csm *candSM) NativeBucket(index uint64) (*VoteBucket, error) { + return newCandidateStateReader(csm).NativeBucket(index) } func (csm *candSM) updateBucket(index uint64, bucket *VoteBucket) error { - if _, err := csm.getBucket(index); err != nil { + if _, err := csm.NativeBucket(index); err != nil { return err } diff --git a/action/protocol/staking/candidate_statereader.go b/action/protocol/staking/candidate_statereader.go index d41e7d6613..d12d498ffe 100644 --- a/action/protocol/staking/candidate_statereader.go +++ b/action/protocol/staking/candidate_statereader.go @@ -20,24 +20,9 @@ import ( ) type ( - // BucketGetByIndex related to obtaining bucket by index - BucketGetByIndex interface { - getBucket(index uint64) (*VoteBucket, error) - } - // BucketGet related to obtaining bucket - BucketGet interface { - BucketGetByIndex - getTotalBucketCount() (uint64, error) - getAllBuckets() ([]*VoteBucket, uint64, error) - getBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) - getBucketIndices(addr address.Address, prefix byte) (*BucketIndices, uint64, error) - voterBucketIndices(addr address.Address) (*BucketIndices, uint64, error) - candBucketIndices(addr address.Address) (*BucketIndices, uint64, error) - } - // CandidateGet related to obtaining Candidate - CandidateGet interface { - getCandidate(name address.Address) (*Candidate, uint64, error) - getAllCandidates() (CandidateList, uint64, error) + // NativeBucketGetByIndex related to obtaining bucket by index + NativeBucketGetByIndex interface { + NativeBucket(index uint64) (*VoteBucket, error) } // ReadState related to read bucket and candidate by request ReadState interface { @@ -53,12 +38,19 @@ type ( } // CandidateStateReader contains candidate center and bucket pool CandidateStateReader interface { - BucketGet - CandidateGet + NativeBucketGetByIndex + NumOfNativeBucket() (uint64, error) + NativeBuckets() ([]*VoteBucket, uint64, error) + NativeBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) + NativeBucketIndices(addr address.Address, prefix byte) (*BucketIndices, uint64, error) + NativeBucketIndicesByVoter(addr address.Address) (*BucketIndices, uint64, error) + NativeBucketIndicesByCandidate(addr address.Address) (*BucketIndices, uint64, error) + CandidateByAddress(name address.Address) (*Candidate, uint64, error) + getAllCandidates() (CandidateList, uint64, error) ReadState Height() uint64 SR() protocol.StateReader - BaseView() *ViewData + BaseView() *viewData NewBucketPool(enableSMStorage bool) (*BucketPool, error) GetCandidateByName(string) *Candidate GetCandidateByOwner(address.Address) *Candidate @@ -72,7 +64,7 @@ type ( candSR struct { protocol.StateReader height uint64 - view *ViewData + view *viewData } ) @@ -90,7 +82,7 @@ func (c *candSR) SR() protocol.StateReader { return c.StateReader } -func (c *candSR) BaseView() *ViewData { +func (c *candSR) BaseView() *viewData { return c.view } @@ -138,14 +130,14 @@ func ConstructBaseView(sr protocol.StateReader) (CandidateStateReader, error) { return nil, err } - view, ok := v.(*ViewData) + view, ok := v.(*viewData) if !ok { return nil, errors.Wrap(ErrTypeAssertion, "expecting *ViewData") } return &candSR{ StateReader: sr, height: height, - view: &ViewData{ + view: &viewData{ candCenter: view.candCenter, bucketPool: view.bucketPool, contractsStake: view.contractsStake, @@ -154,7 +146,7 @@ func ConstructBaseView(sr protocol.StateReader) (CandidateStateReader, error) { } // CreateBaseView creates the base view from state reader -func CreateBaseView(sr protocol.StateReader, enableSMStorage bool) (*ViewData, uint64, error) { +func CreateBaseView(sr protocol.StateReader, enableSMStorage bool) (*viewData, uint64, error) { if sr == nil { return nil, 0, ErrMissingField } @@ -175,13 +167,13 @@ func CreateBaseView(sr protocol.StateReader, enableSMStorage bool) (*ViewData, u return nil, height, err } - return &ViewData{ + return &viewData{ candCenter: center, bucketPool: pool, }, height, nil } -func (c *candSR) getTotalBucketCount() (uint64, error) { +func (c *candSR) NumOfNativeBucket() (uint64, error) { var tc totalBucketCount _, err := c.State( &tc, @@ -190,7 +182,7 @@ func (c *candSR) getTotalBucketCount() (uint64, error) { return tc.count, err } -func (c *candSR) getBucket(index uint64) (*VoteBucket, error) { +func (c *candSR) NativeBucket(index uint64) (*VoteBucket, error) { var ( vb VoteBucket err error @@ -214,12 +206,12 @@ func (c *candSR) getBucket(index uint64) (*VoteBucket, error) { return &vb, nil } -func (c *candSR) getAllBuckets() ([]*VoteBucket, uint64, error) { +func (c *candSR) NativeBuckets() ([]*VoteBucket, uint64, error) { height, iter, err := c.States( protocol.NamespaceOption(_stakingNameSpace), protocol.KeysOption(func() ([][]byte, error) { // TODO (zhi): fix potential racing issue - count, err := c.getTotalBucketCount() + count, err := c.NumOfNativeBucket() if err != nil { return nil, err } @@ -248,10 +240,10 @@ func (c *candSR) getAllBuckets() ([]*VoteBucket, uint64, error) { return buckets, height, nil } -func (c *candSR) getBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) { +func (c *candSR) NativeBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) { buckets := make([]*VoteBucket, 0, len(indices)) for _, i := range indices { - b, err := c.getBucket(i) + b, err := c.NativeBucket(i) if err != nil && err != ErrWithdrawnBucket { return buckets, err } @@ -265,7 +257,7 @@ func (c *candSR) getBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, er func (c *candSR) getExistingBucketsWithIndices(indices BucketIndices) ([]*VoteBucket, error) { buckets := make([]*VoteBucket, 0, len(indices)) for _, i := range indices { - b, err := c.getBucket(i) + b, err := c.NativeBucket(i) if err != nil { if errors.Is(err, state.ErrStateNotExist) { continue @@ -277,7 +269,7 @@ func (c *candSR) getExistingBucketsWithIndices(indices BucketIndices) ([]*VoteBu return buckets, nil } -func (c *candSR) getBucketIndices(addr address.Address, prefix byte) (*BucketIndices, uint64, error) { +func (c *candSR) NativeBucketIndices(addr address.Address, prefix byte) (*BucketIndices, uint64, error) { var ( bis BucketIndices key = AddrKeyWithPrefix(addr, prefix) @@ -292,15 +284,15 @@ func (c *candSR) getBucketIndices(addr address.Address, prefix byte) (*BucketInd return &bis, height, nil } -func (c *candSR) voterBucketIndices(addr address.Address) (*BucketIndices, uint64, error) { - return c.getBucketIndices(addr, _voterIndex) +func (c *candSR) NativeBucketIndicesByVoter(addr address.Address) (*BucketIndices, uint64, error) { + return c.NativeBucketIndices(addr, _voterIndex) } -func (c *candSR) candBucketIndices(addr address.Address) (*BucketIndices, uint64, error) { - return c.getBucketIndices(addr, _candIndex) +func (c *candSR) NativeBucketIndicesByCandidate(addr address.Address) (*BucketIndices, uint64, error) { + return c.NativeBucketIndices(addr, _candIndex) } -func (c *candSR) getCandidate(name address.Address) (*Candidate, uint64, error) { +func (c *candSR) CandidateByAddress(name address.Address) (*Candidate, uint64, error) { if name == nil { return nil, 0, ErrNilParameters } @@ -346,7 +338,7 @@ func (c *candSR) NewBucketPool(enableSMStorage bool) (*BucketPool, error) { } // sum up all existing buckets - all, _, err := c.getAllBuckets() + all, _, err := c.NativeBuckets() if err != nil && errors.Cause(err) != state.ErrStateNotExist { return nil, err } @@ -362,7 +354,7 @@ func (c *candSR) NewBucketPool(enableSMStorage bool) (*BucketPool, error) { } func (c *candSR) readStateBuckets(ctx context.Context, req *iotexapi.ReadStakingDataRequest_VoteBuckets) (*iotextypes.VoteBucketList, uint64, error) { - all, height, err := c.getAllBuckets() + all, height, err := c.NativeBuckets() if err != nil { return nil, height, err } @@ -380,14 +372,14 @@ func (c *candSR) readStateBucketsByVoter(ctx context.Context, req *iotexapi.Read return nil, 0, err } - indices, height, err := c.voterBucketIndices(voter) + indices, height, err := c.NativeBucketIndicesByVoter(voter) if errors.Cause(err) == state.ErrStateNotExist { return &iotextypes.VoteBucketList{}, height, nil } if indices == nil || err != nil { return nil, height, err } - buckets, err := c.getBucketsWithIndices(*indices) + buckets, err := c.NativeBucketsWithIndices(*indices) if err != nil { return nil, height, err } @@ -405,14 +397,14 @@ func (c *candSR) readStateBucketsByCandidate(ctx context.Context, req *iotexapi. return &iotextypes.VoteBucketList{}, 0, nil } - indices, height, err := c.candBucketIndices(cand.GetIdentifier()) + indices, height, err := c.NativeBucketIndicesByCandidate(cand.GetIdentifier()) if errors.Cause(err) == state.ErrStateNotExist { return &iotextypes.VoteBucketList{}, height, nil } if indices == nil || err != nil { return nil, height, err } - buckets, err := c.getBucketsWithIndices(*indices) + buckets, err := c.NativeBucketsWithIndices(*indices) if err != nil { return nil, height, err } @@ -438,7 +430,7 @@ func (c *candSR) readStateBucketByIndices(ctx context.Context, req *iotexapi.Rea } func (c *candSR) readStateBucketCount(ctx context.Context, _ *iotexapi.ReadStakingDataRequest_BucketsCount) (*iotextypes.BucketsCount, uint64, error) { - total, err := c.getTotalBucketCount() + total, err := c.NumOfNativeBucket() if errors.Cause(err) == state.ErrStateNotExist { return &iotextypes.BucketsCount{}, c.Height(), nil } diff --git a/action/protocol/staking/candidate_test.go b/action/protocol/staking/candidate_test.go index 2715a372a5..56ddad626b 100644 --- a/action/protocol/staking/candidate_test.go +++ b/action/protocol/staking/candidate_test.go @@ -205,10 +205,10 @@ func TestGetPutCandidate(t *testing.T) { // put candidates and get for _, e := range testCandidates { - _, _, err := csr.getCandidate(e.d.Owner) + _, _, err := csr.CandidateByAddress(e.d.Owner) require.Equal(state.ErrStateNotExist, errors.Cause(err)) require.NoError(csm.putCandidate(e.d)) - d1, _, err := csr.getCandidate(e.d.Owner) + d1, _, err := csr.CandidateByAddress(e.d.Owner) require.NoError(err) require.Equal(e.d, d1) } @@ -229,7 +229,7 @@ func TestGetPutCandidate(t *testing.T) { // delete buckets and get for _, e := range testCandidates { require.NoError(csm.delCandidate(e.d.GetIdentifier())) - _, _, err := csr.getCandidate(e.d.Owner) + _, _, err := csr.CandidateByAddress(e.d.Owner) require.Equal(state.ErrStateNotExist, errors.Cause(err)) } } diff --git a/action/protocol/staking/contractstake_bucket_type.go b/action/protocol/staking/contractstake_bucket_type.go index 83cfb1ee9b..53b5534c89 100644 --- a/action/protocol/staking/contractstake_bucket_type.go +++ b/action/protocol/staking/contractstake_bucket_type.go @@ -6,62 +6,10 @@ package staking import ( - "math/big" - - "github.com/pkg/errors" - "google.golang.org/protobuf/proto" - - "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" - "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) type ( // ContractStakingBucketType defines the type of contract staking bucket - ContractStakingBucketType struct { - Amount *big.Int - Duration uint64 // block numbers - ActivatedAt uint64 // block height - } + ContractStakingBucketType = contractstaking.BucketType ) - -// Serialize serializes the bucket type -func (bt *ContractStakingBucketType) Serialize() []byte { - return byteutil.Must(proto.Marshal(bt.toProto())) -} - -// Deserialize deserializes the bucket type -func (bt *ContractStakingBucketType) Deserialize(b []byte) error { - m := stakingpb.BucketType{} - if err := proto.Unmarshal(b, &m); err != nil { - return err - } - return bt.loadProto(&m) -} - -// Clone clones the bucket type -func (bt *ContractStakingBucketType) Clone() *ContractStakingBucketType { - return &ContractStakingBucketType{ - Amount: big.NewInt(0).Set(bt.Amount), - Duration: bt.Duration, - ActivatedAt: bt.ActivatedAt, - } -} - -func (bt *ContractStakingBucketType) toProto() *stakingpb.BucketType { - return &stakingpb.BucketType{ - Amount: bt.Amount.String(), - Duration: bt.Duration, - ActivatedAt: bt.ActivatedAt, - } -} - -func (bt *ContractStakingBucketType) loadProto(p *stakingpb.BucketType) error { - amount, ok := big.NewInt(0).SetString(p.Amount, 10) - if !ok { - return errors.New("failed to parse amount") - } - bt.Amount = amount - bt.Duration = p.Duration - bt.ActivatedAt = p.ActivatedAt - return nil -} diff --git a/action/protocol/staking/contractstake_indexer.go b/action/protocol/staking/contractstake_indexer.go index b293494925..4d8568389e 100644 --- a/action/protocol/staking/contractstake_indexer.go +++ b/action/protocol/staking/contractstake_indexer.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" ) var ( @@ -37,9 +38,9 @@ type ( // TotalBucketCount returns the total number of buckets including burned buckets TotalBucketCount(height uint64) (uint64, error) // ContractAddress returns the contract address - ContractAddress() string - // StartView returns the contract stake view - StartView(ctx context.Context) (ContractStakeView, error) + ContractAddress() address.Address + // LoadStakeView loads the contract stake view from state reader + LoadStakeView(context.Context, protocol.StateReader) (ContractStakeView, error) } // ContractStakingIndexerWithBucketType defines the interface of contract staking reader with bucket type ContractStakingIndexerWithBucketType interface { diff --git a/action/protocol/staking/contractstake_indexer_mock.go b/action/protocol/staking/contractstake_indexer_mock.go index eb2f9a3ff9..1c049bc48d 100644 --- a/action/protocol/staking/contractstake_indexer_mock.go +++ b/action/protocol/staking/contractstake_indexer_mock.go @@ -14,6 +14,7 @@ import ( reflect "reflect" address "github.com/iotexproject/iotex-address/address" + protocol "github.com/iotexproject/iotex-core/v2/action/protocol" gomock "go.uber.org/mock/gomock" ) @@ -87,10 +88,10 @@ func (mr *MockContractStakingIndexerMockRecorder) BucketsByIndices(arg0, arg1 an } // ContractAddress mocks base method. -func (m *MockContractStakingIndexer) ContractAddress() string { +func (m *MockContractStakingIndexer) ContractAddress() address.Address { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ContractAddress") - ret0, _ := ret[0].(string) + ret0, _ := ret[0].(address.Address) return ret0 } @@ -115,19 +116,19 @@ func (mr *MockContractStakingIndexerMockRecorder) Height() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Height", reflect.TypeOf((*MockContractStakingIndexer)(nil).Height)) } -// StartView mocks base method. -func (m *MockContractStakingIndexer) StartView(ctx context.Context) (ContractStakeView, error) { +// LoadStakeView mocks base method. +func (m *MockContractStakingIndexer) LoadStakeView(arg0 context.Context, arg1 protocol.StateReader) (ContractStakeView, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StartView", ctx) + ret := m.ctrl.Call(m, "LoadStakeView", arg0, arg1) ret0, _ := ret[0].(ContractStakeView) ret1, _ := ret[1].(error) return ret0, ret1 } -// StartView indicates an expected call of StartView. -func (mr *MockContractStakingIndexerMockRecorder) StartView(ctx any) *gomock.Call { +// LoadStakeView indicates an expected call of LoadStakeView. +func (mr *MockContractStakingIndexerMockRecorder) LoadStakeView(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartView", reflect.TypeOf((*MockContractStakingIndexer)(nil).StartView), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadStakeView", reflect.TypeOf((*MockContractStakingIndexer)(nil).LoadStakeView), arg0, arg1) } // TotalBucketCount mocks base method. @@ -230,10 +231,10 @@ func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) BucketsByIndices } // ContractAddress mocks base method. -func (m *MockContractStakingIndexerWithBucketType) ContractAddress() string { +func (m *MockContractStakingIndexerWithBucketType) ContractAddress() address.Address { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ContractAddress") - ret0, _ := ret[0].(string) + ret0, _ := ret[0].(address.Address) return ret0 } @@ -258,19 +259,19 @@ func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) Height() *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Height", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).Height)) } -// StartView mocks base method. -func (m *MockContractStakingIndexerWithBucketType) StartView(ctx context.Context) (ContractStakeView, error) { +// LoadStakeView mocks base method. +func (m *MockContractStakingIndexerWithBucketType) LoadStakeView(arg0 context.Context, arg1 protocol.StateReader) (ContractStakeView, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StartView", ctx) + ret := m.ctrl.Call(m, "LoadStakeView", arg0, arg1) ret0, _ := ret[0].(ContractStakeView) ret1, _ := ret[1].(error) return ret0, ret1 } -// StartView indicates an expected call of StartView. -func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) StartView(ctx any) *gomock.Call { +// LoadStakeView indicates an expected call of LoadStakeView. +func (mr *MockContractStakingIndexerWithBucketTypeMockRecorder) LoadStakeView(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartView", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).StartView), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadStakeView", reflect.TypeOf((*MockContractStakingIndexerWithBucketType)(nil).LoadStakeView), arg0, arg1) } // TotalBucketCount mocks base method. diff --git a/action/protocol/staking/contractstake_indexer_test.go b/action/protocol/staking/contractstake_indexer_test.go index 85cf317174..d862ba8014 100644 --- a/action/protocol/staking/contractstake_indexer_test.go +++ b/action/protocol/staking/contractstake_indexer_test.go @@ -83,10 +83,12 @@ func TestDelayTolerantIndexer(t *testing.T) { count, err := delayIndexer.TotalBucketCount(delayHeight) r.NoError(err) r.Equal(uint64(len(indexerBuckets)), count) + addr, err := address.FromString(indexerAddress) + r.NoError(err) // ContractAddress - indexer.EXPECT().ContractAddress().Return(indexerAddress).AnyTimes() + indexer.EXPECT().ContractAddress().Return(addr).AnyTimes() ca := delayIndexer.ContractAddress() - r.Equal(indexerAddress, ca) + r.Equal(indexerAddress, ca.String()) // BucketTypes indexer.EXPECT().BucketTypes(gomock.Any()).DoAndReturn(func(height uint64) ([]*ContractStakingBucketType, error) { if height <= atomic.LoadUint64(&indexerHeight) { diff --git a/action/protocol/staking/contractstaking/bucket.go b/action/protocol/staking/contractstaking/bucket.go new file mode 100644 index 0000000000..91429ed37c --- /dev/null +++ b/action/protocol/staking/contractstaking/bucket.go @@ -0,0 +1,112 @@ +package contractstaking + +import ( + "math/big" + + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/pkg/errors" + "google.golang.org/protobuf/proto" +) + +type ( + // Bucket is the structure that holds information about a staking bucket. + Bucket struct { + // Candidate is the address of the candidate that this bucket is staking for. + Candidate address.Address + // Owner is the address of the owner of this bucket. + Owner address.Address + // StakedAmount is the amount of tokens staked in this bucket. + StakedAmount *big.Int + + // StakedDuration is the duration for which the tokens have been staked. + StakedDuration uint64 // in seconds if timestamped, in block number if not + // CreatedAt is the time when the bucket was created. + CreatedAt uint64 // in unix timestamp if timestamped, in block height if not + // UnlockedAt is the time when the bucket can be unlocked. + UnlockedAt uint64 // in unix timestamp if timestamped, in block height if not + // UnstakedAt is the time when the bucket was unstaked. + UnstakedAt uint64 // in unix timestamp if timestamped, in block height if not + + // IsTimestampBased indicates whether the bucket is timestamp-based + IsTimestampBased bool + // Muted indicates whether the bucket is vote weight muted + Muted bool + } +) + +func (b *Bucket) toProto() *stakingpb.SystemStakingBucket { + if b == nil { + return nil + } + return &stakingpb.SystemStakingBucket{ + Owner: b.Owner.Bytes(), + Candidate: b.Candidate.Bytes(), + Amount: b.StakedAmount.Bytes(), + Duration: b.StakedDuration, + CreatedAt: b.CreatedAt, + UnlockedAt: b.UnlockedAt, + UnstakedAt: b.UnstakedAt, + Muted: b.Muted, + } +} + +// LoadBucketFromProto converts a protobuf representation of a staking bucket to a Bucket struct. +func LoadBucketFromProto(pb *stakingpb.SystemStakingBucket) (*Bucket, error) { + if pb == nil { + return nil, nil + } + b := &Bucket{} + owner, err := address.FromBytes(pb.Owner) + if err != nil { + return nil, errors.Wrap(err, "failed to convert owner bytes to address") + } + b.Owner = owner + cand, err := address.FromBytes(pb.Candidate) + if err != nil { + return nil, errors.Wrap(err, "failed to convert candidate bytes to address") + } + b.Candidate = cand + b.StakedAmount = new(big.Int).SetBytes(pb.Amount) + b.StakedDuration = pb.Duration + b.CreatedAt = pb.CreatedAt + b.UnlockedAt = pb.UnlockedAt + b.UnstakedAt = pb.UnstakedAt + b.Muted = pb.Muted + + return b, nil +} + +// Serialize serializes the bucket to a byte slice. +func (b *Bucket) Serialize() ([]byte, error) { + return proto.Marshal(b.toProto()) +} + +// Deserialize deserializes the bucket from a byte slice. +func (b *Bucket) Deserialize(data []byte) error { + m := stakingpb.SystemStakingBucket{} + if err := proto.Unmarshal(data, &m); err != nil { + return errors.Wrap(err, "failed to unmarshal bucket data") + } + bucket, err := LoadBucketFromProto(&m) + if err != nil { + return errors.Wrap(err, "failed to load bucket from proto") + } + *b = *bucket + return nil +} + +// Clone creates a deep copy of the Bucket. +func (b *Bucket) Clone() *Bucket { + return &Bucket{ + Candidate: b.Candidate, + Owner: b.Owner, + StakedAmount: new(big.Int).Set(b.StakedAmount), + StakedDuration: b.StakedDuration, + CreatedAt: b.CreatedAt, + UnlockedAt: b.UnlockedAt, + UnstakedAt: b.UnstakedAt, + IsTimestampBased: b.IsTimestampBased, + Muted: b.Muted, + } +} diff --git a/action/protocol/staking/contractstaking/bucket_type.go b/action/protocol/staking/contractstaking/bucket_type.go new file mode 100644 index 0000000000..489f4245df --- /dev/null +++ b/action/protocol/staking/contractstaking/bucket_type.go @@ -0,0 +1,67 @@ +package contractstaking + +import ( + "math/big" + + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/pkg/errors" + "google.golang.org/protobuf/proto" +) + +type ( + // BucketType defines the type of contract staking bucket + BucketType struct { + Amount *big.Int + Duration uint64 + ActivatedAt uint64 + } +) + +func (bt *BucketType) toProto() *stakingpb.BucketType { + return &stakingpb.BucketType{ + Amount: bt.Amount.String(), + Duration: bt.Duration, + ActivatedAt: bt.ActivatedAt, + } +} + +// LoadBucketTypeFromProto converts a protobuf representation of a staking bucket type to a BucketType struct. +func LoadBucketTypeFromProto(pb *stakingpb.BucketType) (*BucketType, error) { + bt := &BucketType{} + amount, ok := new(big.Int).SetString(pb.Amount, 10) + if !ok { + return nil, errors.New("failed to parse amount from string") + } + bt.Amount = amount + bt.Duration = pb.Duration + bt.ActivatedAt = pb.ActivatedAt + return bt, nil +} + +// Serialize serializes the bucket type +func (bt *BucketType) Serialize() ([]byte, error) { + return proto.Marshal(bt.toProto()) +} + +// Deserialize deserializes the bucket type +func (bt *BucketType) Deserialize(b []byte) error { + m := stakingpb.BucketType{} + if err := proto.Unmarshal(b, &m); err != nil { + return err + } + loaded, err := LoadBucketTypeFromProto(&m) + if err != nil { + return errors.Wrap(err, "failed to load bucket type from proto") + } + *bt = *loaded + return nil +} + +// Clone clones the bucket type +func (bt *BucketType) Clone() *BucketType { + return &BucketType{ + Amount: big.NewInt(0).Set(bt.Amount), + Duration: bt.Duration, + ActivatedAt: bt.ActivatedAt, + } +} diff --git a/action/protocol/staking/contractstaking/contract.go b/action/protocol/staking/contractstaking/contract.go new file mode 100644 index 0000000000..81bfb1ac60 --- /dev/null +++ b/action/protocol/staking/contractstaking/contract.go @@ -0,0 +1,52 @@ +package contractstaking + +import ( + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/pkg/errors" + "google.golang.org/protobuf/proto" +) + +// StakingContract represents the staking contract in the system +type StakingContract struct { + // NumOfBuckets is the number of buckets in the staking contract + NumOfBuckets uint64 +} + +func (sc *StakingContract) toProto() *stakingpb.SystemStakingContract { + if sc == nil { + return nil + } + return &stakingpb.SystemStakingContract{ + NumOfBuckets: sc.NumOfBuckets, + } +} + +// LoadStakingContractFromProto converts a protobuf representation of a staking contract to a StakingContract struct. +func LoadStakingContractFromProto(pb *stakingpb.SystemStakingContract) (*StakingContract, error) { + if pb == nil { + return nil, nil + } + sc := &StakingContract{ + NumOfBuckets: pb.NumOfBuckets, + } + return sc, nil +} + +// Serialize serializes the staking contract +func (sc *StakingContract) Serialize() ([]byte, error) { + return proto.Marshal(sc.toProto()) +} + +// Deserialize deserializes the staking contract +func (sc *StakingContract) Deserialize(b []byte) error { + m := stakingpb.SystemStakingContract{} + if err := proto.Unmarshal(b, &m); err != nil { + return err + } + loaded, err := LoadStakingContractFromProto(&m) + if err != nil { + return errors.Wrap(err, "failed to load staking contract from proto") + } + *sc = *loaded + return nil +} diff --git a/action/protocol/staking/contractstaking/statemanager.go b/action/protocol/staking/contractstaking/statemanager.go new file mode 100644 index 0000000000..42964cb90e --- /dev/null +++ b/action/protocol/staking/contractstaking/statemanager.go @@ -0,0 +1,65 @@ +package contractstaking + +import ( + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" +) + +// ContractStakingStateManager wraps a state manager to provide staking contract-specific writes. +type ContractStakingStateManager struct { + ContractStakingStateReader + sm protocol.StateManager +} + +// NewContractStakingStateManager creates a new ContractStakingStateManager +func NewContractStakingStateManager(sm protocol.StateManager) *ContractStakingStateManager { + return &ContractStakingStateManager{ + ContractStakingStateReader: ContractStakingStateReader{sr: sm}, + sm: sm, + } +} + +// UpsertBucketType inserts or updates a bucket type for a given contract and bucket ID. +func (cs *ContractStakingStateManager) UpsertBucketType(contractAddr address.Address, bucketID uint64, bucketType *BucketType) error { + _, err := cs.sm.PutState( + bucketType, + bucketTypeNamespaceOption(contractAddr), + bucketIDKeyOption(bucketID), + ) + + return err +} + +// DeleteBucket removes a bucket for a given contract and bucket ID. +func (cs *ContractStakingStateManager) DeleteBucket(contractAddr address.Address, bucketID uint64) error { + _, err := cs.sm.DelState( + bucketTypeNamespaceOption(contractAddr), + bucketIDKeyOption(bucketID), + ) + + return err +} + +// UpsertBucket inserts or updates a bucket for a given contract and bid. +func (cs *ContractStakingStateManager) UpsertBucket(contractAddr address.Address, bid uint64, bucket *Bucket) error { + _, err := cs.sm.PutState( + bucket, + contractNamespaceOption(contractAddr), + bucketIDKeyOption(bid), + ) + + return err +} + +// UpdateNumOfBuckets updates the number of buckets. +func (cs *ContractStakingStateManager) UpdateNumOfBuckets(contractAddr address.Address, numOfBuckets uint64) error { + _, err := cs.sm.PutState( + &StakingContract{ + NumOfBuckets: uint64(numOfBuckets), + }, + metaNamespaceOption(), + contractKeyOption(contractAddr), + ) + + return err +} diff --git a/action/protocol/staking/contractstaking/statemanager_test.go b/action/protocol/staking/contractstaking/statemanager_test.go new file mode 100644 index 0000000000..c134c02866 --- /dev/null +++ b/action/protocol/staking/contractstaking/statemanager_test.go @@ -0,0 +1,183 @@ +package contractstaking + +import ( + "errors" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/iotexproject/iotex-core/v2/test/identityset" + "github.com/iotexproject/iotex-core/v2/test/mock/mock_chainmanager" +) + +// TestNewContractStakingStateManager tests the creation of a new ContractStakingStateManager. +func TestNewContractStakingStateManager(t *testing.T) { + mockSM := &mock_chainmanager.MockStateManager{} + csm := NewContractStakingStateManager(mockSM) + assert.NotNil(t, csm) + assert.Equal(t, mockSM, csm.sm) +} + +// TestUpsertBucketType_Success tests the successful insertion of a bucket type. +func TestUpsertBucketType_Success(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), nil) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(0) + bucketID := uint64(123) + bucketType := &BucketType{ + Amount: big.NewInt(1000), + Duration: 3600, + ActivatedAt: 1622547800, + } + + err := csm.UpsertBucketType(contractAddr, bucketID, bucketType) + require.NoError(t, err) +} + +// TestUpsertBucketType_Error tests the error case for inserting a bucket type. +func TestUpsertBucketType_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("putstate error")) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(1) + bucketID := uint64(456) + bucketType := &BucketType{ + Amount: big.NewInt(2000), + Duration: 7200, + ActivatedAt: 1622548100, + } + + err := csm.UpsertBucketType(contractAddr, bucketID, bucketType) + assert.Error(t, err) + assert.Equal(t, "putstate error", err.Error()) +} + +func TestDeleteBucket_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(errors.New("delstate error")) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(1) + bucketID := uint64(456) + + err := csm.DeleteBucket(contractAddr, bucketID) + assert.Error(t, err) + assert.Equal(t, "delstate error", err.Error()) +} + +// TestDeleteBucket_Success tests the successful deletion of a bucket. +func TestDeleteBucket_Success(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(nil) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(1) + bucketID := uint64(456) + + err := csm.DeleteBucket(contractAddr, bucketID) + require.NoError(t, err) +} + +// TestUpsertBucket_Success tests the successful insertion of a bucket. +func TestUpsertBucket_Success(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), nil) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(0) + bid := uint64(123) + bucket := &Bucket{ + Owner: identityset.Address(1), + Candidate: identityset.Address(2), + StakedAmount: big.NewInt(1000), + StakedDuration: 3600, + CreatedAt: 1622547800, + UnlockedAt: 1622547900, + UnstakedAt: 1622548000, + Muted: false, + } + + err := csm.UpsertBucket(contractAddr, bid, bucket) + require.NoError(t, err) +} + +// TestUpsertBucket_Error tests the error case for inserting a bucket. +func TestUpsertBucket_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("putstate error")) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(0) + bid := uint64(456) + bucket := &Bucket{ + Owner: identityset.Address(3), + Candidate: identityset.Address(4), + StakedAmount: big.NewInt(2000), + StakedDuration: 7200, + CreatedAt: 1622548100, + UnlockedAt: 1622548200, + UnstakedAt: 1622548300, + Muted: true, + } + + err := csm.UpsertBucket(contractAddr, bid, bucket) + assert.Error(t, err) + assert.Equal(t, "putstate error", err.Error()) +} + +// TestUpdateContractMeta_Success tests the successful update of contract metadata. +func TestUpdateContractMeta_Success(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), nil) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(5) + numOfBuckets := uint64(10) + + err := csm.UpdateNumOfBuckets(contractAddr, numOfBuckets) + require.NoError(t, err) +} + +// TestUpdateContractMeta_Error tests the error case for updating contract metadata. +func TestUpdateContractMeta_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSM := mock_chainmanager.NewMockStateManager(ctrl) + mockSM.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("meta error")) + csm := NewContractStakingStateManager(mockSM) + + contractAddr := identityset.Address(6) + numOfBuckets := uint64(20) + + err := csm.UpdateNumOfBuckets(contractAddr, numOfBuckets) + assert.Error(t, err) + assert.Equal(t, "meta error", err.Error()) +} diff --git a/action/protocol/staking/contractstaking/statereader.go b/action/protocol/staking/contractstaking/statereader.go new file mode 100644 index 0000000000..3058b07c85 --- /dev/null +++ b/action/protocol/staking/contractstaking/statereader.go @@ -0,0 +1,149 @@ +package contractstaking + +import ( + "fmt" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-address/address" +) + +// ContractStakingStateReader wraps a state reader to provide staking contract-specific reads. +type ContractStakingStateReader struct { + sr protocol.StateReader +} + +// NewStateReader creates a new ContractStakingStateReader. +func NewStateReader(sr protocol.StateReader) *ContractStakingStateReader { + return &ContractStakingStateReader{ + sr: sr, + } +} + +func contractNamespaceOption(contractAddr address.Address) protocol.StateOption { + return protocol.NamespaceOption(fmt.Sprintf("cs_bucket_%x", contractAddr.Bytes())) +} + +func bucketTypeNamespaceOption(contractAddr address.Address) protocol.StateOption { + return protocol.NamespaceOption(fmt.Sprintf("cs_bucket_type_%x", contractAddr.Bytes())) +} + +func contractKeyOption(contractAddr address.Address) protocol.StateOption { + return protocol.KeyOption(contractAddr.Bytes()) +} + +func bucketIDKeyOption(bucketID uint64) protocol.StateOption { + return protocol.KeyOption(byteutil.Uint64ToBytes(bucketID)) +} + +// metaNamespaceOption is the namespace for meta information (e.g., total number of buckets). +func metaNamespaceOption() protocol.StateOption { + return protocol.NamespaceOption("staking_contract_meta") +} + +func (r *ContractStakingStateReader) contract(contractAddr address.Address) (*StakingContract, error) { + var contract StakingContract + _, err := r.sr.State( + &contract, + metaNamespaceOption(), + contractKeyOption(contractAddr), + ) + if err != nil { + return nil, err + } + return &contract, nil +} + +// NumOfBuckets returns the total number of buckets for a contract. +func (r *ContractStakingStateReader) NumOfBuckets(contractAddr address.Address) (uint64, error) { + contract, err := r.contract(contractAddr) + if err != nil { + return 0, err + } + return contract.NumOfBuckets, nil +} + +// BucketType returns the BucketType for a given contract and bucket id. +func (r *ContractStakingStateReader) BucketType(contractAddr address.Address, tID uint64) (*BucketType, error) { + var bktType stakingpb.BucketType + if _, err := r.sr.State( + &bktType, + bucketTypeNamespaceOption(contractAddr), + bucketIDKeyOption(tID), + ); err != nil { + return nil, fmt.Errorf("failed to get bucket type %d for contract %s: %w", tID, contractAddr.String(), err) + } + return LoadBucketTypeFromProto(&bktType) +} + +// Bucket returns the Bucket for a given contract and bucket id. +func (r *ContractStakingStateReader) Bucket(contractAddr address.Address, bucketID uint64) (*Bucket, error) { + var ssb Bucket + if _, err := r.sr.State( + &ssb, + contractNamespaceOption(contractAddr), + bucketIDKeyOption(bucketID), + ); err != nil { + return nil, err + } + + return &ssb, nil +} + +// BucketTypes returns all BucketType for a given contract and bucket id. +func (r *ContractStakingStateReader) BucketTypes(contractAddr address.Address) ([]uint64, []*BucketType, error) { + _, iter, err := r.sr.States(bucketTypeNamespaceOption(contractAddr)) + if err != nil { + return nil, nil, fmt.Errorf("failed to get bucket types for contract %s: %w", contractAddr.String(), err) + } + ids := make([]uint64, 0, iter.Size()) + types := make([]*BucketType, 0, iter.Size()) + for i := 0; i < iter.Size(); i++ { + var bktType stakingpb.BucketType + switch key, err := iter.Next(&bktType); err { + case nil: + bt, err := LoadBucketTypeFromProto(&bktType) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to load bucket type from proto") + } + ids = append(ids, byteutil.BytesToUint64(key)) + types = append(types, bt) + case state.ErrNilValue: + default: + return nil, nil, fmt.Errorf("failed to read bucket type %d for contract %s: %w", byteutil.BytesToUint64(key), contractAddr.String(), err) + } + } + return ids, types, nil +} + +// Buckets returns all BucketInfo for a given contract. +func (r *ContractStakingStateReader) Buckets(contractAddr address.Address) ([]uint64, []*Bucket, error) { + _, iter, err := r.sr.States(contractNamespaceOption(contractAddr)) + if err != nil { + return nil, nil, fmt.Errorf("failed to get buckets for contract %s: %w", contractAddr.String(), err) + } + ids := make([]uint64, 0, iter.Size()) + buckets := make([]*Bucket, 0, iter.Size()) + for i := 0; i < iter.Size(); i++ { + var ssb stakingpb.SystemStakingBucket + switch key, err := iter.Next(&ssb); err { + case nil: + bucket, err := LoadBucketFromProto(&ssb) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to load bucket from proto") + } + if bucket != nil { + ids = append(ids, byteutil.BytesToUint64(key)) + buckets = append(buckets, bucket) + } + case state.ErrNilValue: + default: + return nil, nil, fmt.Errorf("failed to read bucket %d for contract %s: %w", byteutil.BytesToUint64(key), contractAddr.String(), err) + } + } + return ids, buckets, nil +} diff --git a/action/protocol/staking/contractstaking/statereader_test.go b/action/protocol/staking/contractstaking/statereader_test.go new file mode 100644 index 0000000000..8f607c844e --- /dev/null +++ b/action/protocol/staking/contractstaking/statereader_test.go @@ -0,0 +1,138 @@ +package contractstaking + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/stakingpb" + "github.com/iotexproject/iotex-core/v2/state" + "github.com/iotexproject/iotex-core/v2/test/identityset" + "github.com/iotexproject/iotex-core/v2/test/mock/mock_chainmanager" +) + +func TestNewContractStateReader(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + require.NotNil(t, reader) + require.Equal(t, mockSR, reader.sr) +} + +func TestNumOfBuckets(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + expected := &StakingContract{NumOfBuckets: 10} + mockSR.EXPECT().State(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(s interface{}, _ ...protocol.StateOption) (uint64, error) { + *(s.(*StakingContract)) = *expected + return 0, nil + }, + ) + num, err := reader.NumOfBuckets(addr) + require.NoError(t, err) + require.Equal(t, uint64(10), num) +} + +func TestNumOfBuckets_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + mockSR.EXPECT().State(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("err")) + num, err := reader.NumOfBuckets(addr) + require.Error(t, err) + require.Equal(t, uint64(0), num) +} + +func TestBucket(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + bucketID := uint64(123) + ssb := &Bucket{} + mockSR.EXPECT().State(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(s interface{}, _ ...protocol.StateOption) (uint64, error) { + *(s.(*Bucket)) = *ssb + return 0, nil + }, + ) + + bucket, err := reader.Bucket(addr, bucketID) + require.NoError(t, err) + require.NotNil(t, bucket) +} + +func TestBucket_ErrorOnState(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + bucketID := uint64(123) + mockSR.EXPECT().State(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("err")) + bucket, err := reader.Bucket(addr, bucketID) + require.Error(t, err) + require.Nil(t, bucket) +} + +type dummyIter struct { + size int + idx int + keys [][]byte +} + +func (d *dummyIter) Size() int { return d.size } +func (d *dummyIter) Next(s interface{}) ([]byte, error) { + if d.idx >= d.size { + return nil, state.ErrNilValue + } + *(s.(*stakingpb.SystemStakingBucket)) = stakingpb.SystemStakingBucket{} + key := d.keys[d.idx] + d.idx++ + return key, nil +} + +func TestBuckets(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + iter := &dummyIter{size: 2, keys: [][]byte{{1, 2, 3, 4, 5, 6, 7, 8}, {2, 3, 4, 5, 6, 7, 8, 9}}} + mockSR.EXPECT().States(gomock.Any()).Return(uint64(0), iter, nil) + + ids, buckets, err := reader.Buckets(addr) + require.NoError(t, err) + require.Len(t, ids, 2) + require.Len(t, buckets, 2) +} + +func TestBuckets_StatesError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockSR := mock_chainmanager.NewMockStateReader(ctrl) + reader := NewStateReader(mockSR) + + addr := identityset.Address(0) + mockSR.EXPECT().States(gomock.Any()).Return(uint64(0), nil, errors.New("states error")) + ids, buckets, err := reader.Buckets(addr) + require.Error(t, err) + require.Nil(t, ids) + require.Nil(t, buckets) +} diff --git a/action/protocol/staking/handler_candidate_endorsement_test.go b/action/protocol/staking/handler_candidate_endorsement_test.go index 4dd3516b07..9e26abcea3 100644 --- a/action/protocol/staking/handler_candidate_endorsement_test.go +++ b/action/protocol/staking/handler_candidate_endorsement_test.go @@ -347,7 +347,7 @@ func TestProtocol_HandleCandidateEndorsement(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) esm := NewEndorsementStateManager(csm.SM()) - bucket, err := csm.getBucket(1) + bucket, err := csm.NativeBucket(1) require.NoError(err) endorsement, err := esm.Get(bucket.Index) require.NoError(err) @@ -385,7 +385,7 @@ func TestProtocol_HandleCandidateEndorsement(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) esm := NewEndorsementStateManager(csm.SM()) - bucket, err := csm.getBucket(1) + bucket, err := csm.NativeBucket(1) require.NoError(err) endorsement, err := esm.Get(bucket.Index) require.NoError(err) @@ -573,7 +573,7 @@ func TestProtocol_HandleCandidateEndorsement(t *testing.T) { // check buckets esm := NewEndorsementStateManager(csm.SM()) for _, expectBkt := range test.expectBuckets { - bkt, err := csm.getBucket(expectBkt.id) + bkt, err := csm.NativeBucket(expectBkt.id) require.NoError(err) require.Equal(expectBkt.candidate, bkt.Candidate) endorse, err := esm.Get(expectBkt.id) diff --git a/action/protocol/staking/handler_candidate_selfstake_test.go b/action/protocol/staking/handler_candidate_selfstake_test.go index 52f808109b..8ca0d334ce 100644 --- a/action/protocol/staking/handler_candidate_selfstake_test.go +++ b/action/protocol/staking/handler_candidate_selfstake_test.go @@ -161,7 +161,7 @@ func initTestStateWithHeight(t *testing.T, ctrl *gomock.Controller, bucketCfgs [ ctx = protocol.WithFeatureWithHeightCtx(ctx) v, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := v.(*ViewData) + cc, ok := v.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) @@ -474,7 +474,7 @@ func TestProtocol_HandleCandidateSelfStake(t *testing.T) { } // check buckets for _, expectBkt := range test.expectBuckets { - bkt, err := csm.getBucket(expectBkt.id) + bkt, err := csm.NativeBucket(expectBkt.id) require.NoError(err) require.Equal(expectBkt.candidate, bkt.Candidate) } diff --git a/action/protocol/staking/handler_candidate_transfer_ownership.go b/action/protocol/staking/handler_candidate_transfer_ownership.go index c444a4f6b1..247ca8e874 100644 --- a/action/protocol/staking/handler_candidate_transfer_ownership.go +++ b/action/protocol/staking/handler_candidate_transfer_ownership.go @@ -39,7 +39,7 @@ func (p *Protocol) handleCandidateTransferOwnership(ctx context.Context, act *ac candidate.Owner = act.NewOwner() // clear selfstake needClear := func() (bool, *big.Int, error) { - bucket, err := csm.getBucket(candidate.SelfStakeBucketIdx) + bucket, err := csm.NativeBucket(candidate.SelfStakeBucketIdx) if err == nil { // keep the self-stake bucket if it's endorse bucket esm := NewEndorsementStateReader(csm.SR()) diff --git a/action/protocol/staking/handler_candidate_transfer_ownership_test.go b/action/protocol/staking/handler_candidate_transfer_ownership_test.go index 1d0861418a..9731caff3f 100644 --- a/action/protocol/staking/handler_candidate_transfer_ownership_test.go +++ b/action/protocol/staking/handler_candidate_transfer_ownership_test.go @@ -80,7 +80,7 @@ func TestProtocol_HandleCandidateTransferOwnership(t *testing.T) { ctx = protocol.WithFeatureWithHeightCtx(ctx) vv, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := vv.(*ViewData) + cc, ok := vv.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) diff --git a/action/protocol/staking/handler_stake_migrate_test.go b/action/protocol/staking/handler_stake_migrate_test.go index 0521780358..0b98ca9fb1 100644 --- a/action/protocol/staking/handler_stake_migrate_test.go +++ b/action/protocol/staking/handler_stake_migrate_test.go @@ -286,7 +286,7 @@ func TestHandleStakeMigrate(t *testing.T) { csm, err := NewCandidateStateManager(sm) r.NoError(err) preVotes := csm.GetByOwner(identityset.Address(candOwnerID)).Votes - bkt, err := csm.getBucket(bktIdx) + bkt, err := csm.NativeBucket(bktIdx) r.NoError(err) receipt := &action.Receipt{ Status: uint64(iotextypes.ReceiptStatus_Success), @@ -339,7 +339,7 @@ func TestHandleStakeMigrate(t *testing.T) { // native bucket burned csm, err = NewCandidateStateManager(sm) r.NoError(err) - _, err = csm.getBucket(bktIdx) + _, err = csm.NativeBucket(bktIdx) r.ErrorIs(err, state.ErrStateNotExist) // votes reduced for staking indexer not enabled cand := csm.GetByOwner(identityset.Address(candOwnerID)) diff --git a/action/protocol/staking/handlers.go b/action/protocol/staking/handlers.go index e2a05d5602..95978d3e6b 100644 --- a/action/protocol/staking/handlers.go +++ b/action/protocol/staking/handlers.go @@ -893,8 +893,8 @@ func (p *Protocol) handleCandidateUpdate(ctx context.Context, act *action.Candid return log, nil } -func (p *Protocol) fetchBucket(csm BucketGetByIndex, index uint64) (*VoteBucket, ReceiptError) { - bucket, err := csm.getBucket(index) +func (p *Protocol) fetchBucket(csm NativeBucketGetByIndex, index uint64) (*VoteBucket, ReceiptError) { + bucket, err := csm.NativeBucket(index) if err != nil { fetchErr := &handleError{ err: errors.Wrapf(err, "failed to fetch bucket by index %d", index), diff --git a/action/protocol/staking/handlers_test.go b/action/protocol/staking/handlers_test.go index 9902192b61..7aa9bf5236 100644 --- a/action/protocol/staking/handlers_test.go +++ b/action/protocol/staking/handlers_test.go @@ -112,7 +112,7 @@ func TestProtocol_HandleCreateStake(t *testing.T) { ctx = protocol.WithFeatureWithHeightCtx(ctx) v, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := v.(*ViewData) + cc, ok := v.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) @@ -245,21 +245,21 @@ func TestProtocol_HandleCreateStake(t *testing.T) { require.Equal(test.amount, cLog.Amount.String()) // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(candidateAddr) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidateAddr) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(stakerAddr) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(stakerAddr) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidateAddr, bucket.Candidate) require.Equal(stakerAddr, bucket.Owner) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err := csr.getCandidate(candidateAddr) + candidate, _, err := csr.CandidateByAddress(candidateAddr) require.NoError(err) require.LessOrEqual(test.amount, candidate.Votes.String()) csm, err := NewCandidateStateManager(sm) @@ -644,11 +644,11 @@ func TestProtocol_HandleCandidateRegister(t *testing.T) { } // test candidate - candidate, _, err := csr.getCandidate(act.OwnerAddress()) + candidate, _, err := csr.CandidateByAddress(act.OwnerAddress()) if act.OwnerAddress() == nil { require.Nil(candidate) require.Equal(ErrNilParameters, errors.Cause(err)) - candidate, _, err = csr.getCandidate(test.caller) + candidate, _, err = csr.CandidateByAddress(test.caller) require.NoError(err) require.Equal(test.caller.String(), candidate.Owner.String()) } else { @@ -959,11 +959,11 @@ func TestProtocol_HandleCandidateUpdate(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test candidate - candidate, _, err := csr.getCandidate(act.OwnerAddress()) + candidate, _, err := csr.CandidateByAddress(act.OwnerAddress()) if act.OwnerAddress() == nil { require.Nil(candidate) require.Equal(ErrNilParameters, errors.Cause(err)) - candidate, _, err = csr.getCandidate(test.caller) + candidate, _, err = csr.CandidateByAddress(test.caller) require.NoError(err) require.Equal(test.caller.String(), candidate.Owner.String()) } else { @@ -1203,21 +1203,21 @@ func TestProtocol_HandleUnstake(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket csr = newCandidateStateReader(sm) - bucketIndices, _, err := csr.candBucketIndices(candidate.Owner) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidate.Owner.String(), bucket.Candidate.String()) require.Equal(test.caller.String(), bucket.Owner.String()) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err = csr.getCandidate(candidate.Owner) + candidate, _, err = csr.CandidateByAddress(candidate.Owner) require.NoError(err) require.Equal(test.afterUnstake, candidate.Votes.String()) csm, err := NewCandidateStateManager(sm) @@ -1238,7 +1238,7 @@ func TestProtocol_HandleUnstake(t *testing.T) { } // verify bucket unstaked - vb, err := csr.getBucket(0) + vb, err := csr.NativeBucket(0) require.NoError(err) require.True(vb.isUnstaked()) @@ -1294,7 +1294,7 @@ func TestProtocol_HandleUnstake(t *testing.T) { if !v.greenland { // pre-Greenland allows restaking an unstaked bucket, and it is considered staked afterwards - vb, err := csr.getBucket(0) + vb, err := csr.NativeBucket(0) require.NoError(err) require.True(vb.StakeStartTime.Unix() != 0) require.True(vb.UnstakeStartTime.Unix() != 0) @@ -1501,9 +1501,9 @@ func TestProtocol_HandleWithdrawStake(t *testing.T) { require.Equal(test.amount, wLog.Amount.String()) // test bucket index and bucket - _, _, err := csr.candBucketIndices(candidate.Owner) + _, _, err := csr.NativeBucketIndicesByCandidate(candidate.Owner) require.Error(err) - _, _, err = csr.voterBucketIndices(candidate.Owner) + _, _, err = csr.NativeBucketIndicesByVoter(candidate.Owner) require.Error(err) // test staker's account @@ -1776,21 +1776,21 @@ func TestProtocol_HandleChangeCandidate(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(identityset.Address(1)) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(identityset.Address(1)) require.NoError(err) require.Equal(2, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(identityset.Address(1)) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(identityset.Address(1)) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(identityset.Address(1).String(), bucket.Candidate.String()) require.Equal(identityset.Address(1).String(), bucket.Owner.String()) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err := csr.getCandidate(candidate.Owner) + candidate, _, err := csr.CandidateByAddress(candidate.Owner) require.NotNil(candidate) require.NoError(err) require.Equal(test.afterChange, candidate.Votes.String()) @@ -2126,21 +2126,21 @@ func TestProtocol_HandleTransferStake(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(candidate2.GetIdentifier()) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidate2.GetIdentifier()) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(test.to) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(test.to) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidate2.GetIdentifier(), bucket.Candidate) require.Equal(test.to.String(), bucket.Owner.String()) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err := csr.getCandidate(candi.Owner) + candidate, _, err := csr.CandidateByAddress(candi.Owner) require.NoError(err) require.Equal(test.afterTransfer, candidate.Votes.Uint64()) csm, err := NewCandidateStateManager(sm) @@ -2359,21 +2359,21 @@ func TestProtocol_HandleConsignmentTransfer(t *testing.T) { if test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(cand2.GetIdentifier()) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(cand2.GetIdentifier()) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(test.to) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(test.to) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(cand2.GetIdentifier(), bucket.Candidate) require.Equal(test.to.String(), bucket.Owner.String()) require.Equal(stakeAmount, bucket.StakedAmount.String()) // test candidate - candidate, _, err := csr.getCandidate(cand1.GetIdentifier()) + candidate, _, err := csr.CandidateByAddress(cand1.GetIdentifier()) require.NoError(err) require.LessOrEqual(uint64(0), candidate.Votes.Uint64()) csm, err := NewCandidateStateManager(sm) @@ -2628,21 +2628,21 @@ func TestProtocol_HandleRestake(t *testing.T) { if test.err == nil && test.status == iotextypes.ReceiptStatus_Success { // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(candidate.Owner) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidate.Owner.String(), bucket.Candidate.String()) require.Equal(test.caller.String(), bucket.Owner.String()) require.Equal(test.amount, bucket.StakedAmount.String()) // test candidate - candidate, _, err = csr.getCandidate(candidate.Owner) + candidate, _, err = csr.CandidateByAddress(candidate.Owner) require.NoError(err) require.Equal(test.afterRestake, candidate.Votes.String()) csm, err := NewCandidateStateManager(sm) @@ -2844,15 +2844,15 @@ func TestProtocol_HandleDepositToStake(t *testing.T) { require.Equal(test.amount, dLog.Amount.String()) // test bucket index and bucket - bucketIndices, _, err := csr.candBucketIndices(candidate.Owner) + bucketIndices, _, err := csr.NativeBucketIndicesByCandidate(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) - bucketIndices, _, err = csr.voterBucketIndices(candidate.Owner) + bucketIndices, _, err = csr.NativeBucketIndicesByVoter(candidate.Owner) require.NoError(err) require.Equal(1, len(*bucketIndices)) indices := *bucketIndices - bucket, err := csr.getBucket(indices[0]) + bucket, err := csr.NativeBucket(indices[0]) require.NoError(err) require.Equal(candidate.Owner.String(), bucket.Candidate.String()) require.Equal(test.caller.String(), bucket.Owner.String()) @@ -2860,7 +2860,7 @@ func TestProtocol_HandleDepositToStake(t *testing.T) { require.Zero(new(big.Int).Mul(amount, big.NewInt(2)).Cmp(bucket.StakedAmount)) // test candidate - candidate, _, err = csr.getCandidate(candidate.Owner) + candidate, _, err = csr.CandidateByAddress(candidate.Owner) require.NoError(err) require.Equal(test.afterDeposit, candidate.Votes.String()) csm, err := NewCandidateStateManager(sm) @@ -2889,7 +2889,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { t.Run("bucket not exist", func(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) - patches := gomonkey.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { + patches := gomonkey.ApplyPrivateMethod(csm, "NativeBucket", func(index uint64) (*VoteBucket, error) { return nil, state.ErrStateNotExist }) defer patches.Reset() @@ -2899,7 +2899,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { t.Run("validate owner", func(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) - patches := gomonkey.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { + patches := gomonkey.ApplyPrivateMethod(csm, "NativeBucket", func(index uint64) (*VoteBucket, error) { return &VoteBucket{ Owner: identityset.Address(1), }, nil @@ -2915,7 +2915,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { require.NoError(err) patches := gomonkey.NewPatches() defer patches.Reset() - patches.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { + patches.ApplyPrivateMethod(csm, "NativeBucket", func(index uint64) (*VoteBucket, error) { return &VoteBucket{ Owner: identityset.Address(1), }, nil @@ -2938,7 +2938,7 @@ func TestProtocol_FetchBucketAndValidate(t *testing.T) { csm, err := NewCandidateStateManager(sm) require.NoError(err) patches := gomonkey.NewPatches() - patches.ApplyPrivateMethod(csm, "getBucket", func(index uint64) (*VoteBucket, error) { + patches.ApplyPrivateMethod(csm, "NativeBucket", func(index uint64) (*VoteBucket, error) { return &VoteBucket{ Owner: identityset.Address(1), }, nil @@ -3309,7 +3309,7 @@ func initCreateStake(t *testing.T, sm protocol.StateManager, callerAddr address. ctx = protocol.WithFeatureCtx(protocol.WithFeatureWithHeightCtx(ctx)) v, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := v.(*ViewData) + cc, ok := v.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) _, err = p.Handle(ctx, elp, sm) @@ -3355,7 +3355,7 @@ func initAll(t *testing.T, ctrl *gomock.Controller) (protocol.StateManager, *Pro ctx = protocol.WithFeatureWithHeightCtx(ctx) v, err := p.Start(ctx, sm) require.NoError(err) - cc, ok := v.(*ViewData) + cc, ok := v.(*viewData) require.True(ok) require.NoError(sm.WriteView(_protocolID, cc)) return sm, p, candidate, candidate2 diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index cb82fd60aa..bf1ae914a8 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -119,7 +119,7 @@ type ( func WithContractStakingIndexerV3(indexer ContractStakingIndexer) Option { return func(p *Protocol) { p.contractStakingIndexerV3 = indexer - p.config.TimestampedMigrateContractAddress = indexer.ContractAddress() + p.config.TimestampedMigrateContractAddress = indexer.ContractAddress().String() return } } @@ -186,7 +186,7 @@ func NewProtocol( voteReviser := NewVoteReviser(cfg.Revise) migrateContractAddress := "" if contractStakingIndexerV2 != nil { - migrateContractAddress = contractStakingIndexerV2.ContractAddress() + migrateContractAddress = contractStakingIndexerV2.ContractAddress().String() } p := &Protocol{ addr: addr, @@ -252,23 +252,23 @@ func (p *Protocol) Start(ctx context.Context, sr protocol.StateReader) (protocol c.contractsStake = &contractStakeView{} if p.contractStakingIndexer != nil { - view, err := NewContractStakeViewBuilder(p.contractStakingIndexer, p.blockStore).Build(ctx, height) + view, err := NewContractStakeViewBuilder(p.contractStakingIndexer, p.blockStore).Build(ctx, sr, height) if err != nil { - return nil, errors.Wrap(err, "failed to start contract staking indexer") + return nil, errors.Wrapf(err, "failed to create stake view for contract %s", p.contractStakingIndexer.ContractAddress()) } c.contractsStake.v1 = view } if p.contractStakingIndexerV2 != nil { - view, err := NewContractStakeViewBuilder(p.contractStakingIndexerV2, p.blockStore).Build(ctx, height) + view, err := NewContractStakeViewBuilder(p.contractStakingIndexerV2, p.blockStore).Build(ctx, sr, height) if err != nil { - return nil, errors.Wrap(err, "failed to start contract staking indexer v2") + return nil, errors.Wrapf(err, "failed to create stake view for contract %s", p.contractStakingIndexerV2.ContractAddress()) } c.contractsStake.v2 = view } if p.contractStakingIndexerV3 != nil { - view, err := NewContractStakeViewBuilder(p.contractStakingIndexerV3, p.blockStore).Build(ctx, height) + view, err := NewContractStakeViewBuilder(p.contractStakingIndexerV3, p.blockStore).Build(ctx, sr, height) if err != nil { - return nil, errors.Wrap(err, "failed to start contract staking indexer v3") + return nil, errors.Wrapf(err, "failed to create stake view for contract %s", p.contractStakingIndexerV3.ContractAddress()) } c.contractsStake.v3 = view } @@ -428,7 +428,12 @@ func (p *Protocol) CreatePreStates(ctx context.Context, sm protocol.StateManager if err != nil { return err } - if err = v.(*ViewData).contractsStake.CreatePreStates(ctx); err != nil { + if blkCtx.BlockHeight == g.ToBeEnabledBlockHeight { + if err := v.(*viewData).contractsStake.FlushBuckets(sm); err != nil { + return errors.Wrap(err, "failed to write buckets") + } + } + if err = v.(*viewData).contractsStake.CreatePreStates(ctx); err != nil { return err } @@ -455,7 +460,7 @@ func (p *Protocol) handleStakingIndexer(ctx context.Context, epochStartHeight ui if err != nil { return err } - allBuckets, _, err := csr.getAllBuckets() + allBuckets, _, err := csr.NativeBuckets() if err != nil && errors.Cause(err) != state.ErrStateNotExist { return err } @@ -491,15 +496,11 @@ func (p *Protocol) PreCommit(ctx context.Context, sm protocol.StateManager) erro if err != nil { return err } - vd := view.(*ViewData) + vd := view.(*viewData) if !vd.IsDirty() { return nil } - clone := vd.candCenter.Clone() - if err := clone.Commit(ctx, sm); err != nil { - return err - } - return clone.WriteToStateDB(sm) + return vd.Commit(ctx, sm) } // Commit commits the last change @@ -508,7 +509,7 @@ func (p *Protocol) Commit(ctx context.Context, sm protocol.StateManager) error { if err != nil { return err } - if !view.(*ViewData).IsDirty() { + if !view.(*viewData).IsDirty() { return nil } @@ -611,7 +612,7 @@ func (p *Protocol) HandleReceipt(ctx context.Context, elp action.Envelope, sm pr if err != nil { return err } - return v.(*ViewData).contractsStake.Handle(ctx, receipt) + return v.(*viewData).contractsStake.Handle(ctx, receipt) } // Validate validates a staking message @@ -665,7 +666,7 @@ func (p *Protocol) isActiveCandidate(ctx context.Context, csr CandidiateStateCom // before endorsement feature, candidates with enough amount must be active return true, nil } - bucket, err := csr.getBucket(cand.SelfStakeBucketIdx) + bucket, err := csr.NativeBucket(cand.SelfStakeBucketIdx) switch { case errors.Cause(err) == state.ErrStateNotExist: // endorse bucket has been withdrawn @@ -933,7 +934,7 @@ func (p *Protocol) contractStakingVotesFromIndexer(ctx context.Context, candidat return votes, nil } -func (p *Protocol) contractStakingVotesFromView(ctx context.Context, candidate address.Address, view *ViewData) (*big.Int, error) { +func (p *Protocol) contractStakingVotesFromView(ctx context.Context, candidate address.Address, view *viewData) (*big.Int, error) { featureCtx := protocol.MustGetFeatureCtx(ctx) votes := big.NewInt(0) views := []ContractStakeView{} @@ -997,36 +998,3 @@ func writeCandCenterStateToStateDB(sm protocol.StateManager, name, op, owners Ca _, err := sm.PutState(owners, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_ownerKey)) return err } - -// isSelfStakeBucket returns true if the bucket is self-stake bucket and not expired -func isSelfStakeBucket(featureCtx protocol.FeatureCtx, csc CandidiateStateCommon, bucket *VoteBucket) (bool, error) { - // bucket index should be settled in one of candidates - selfStake := csc.ContainsSelfStakingBucket(bucket.Index) - if featureCtx.DisableDelegateEndorsement || !selfStake { - return selfStake, nil - } - - // bucket should not be unstaked if it is self-owned - if isSelfOwnedBucket(csc, bucket) { - return !bucket.isUnstaked(), nil - } - // otherwise bucket should be an endorse bucket which is not expired - esm := NewEndorsementStateReader(csc.SR()) - height, err := esm.Height() - if err != nil { - return false, err - } - status, err := esm.Status(featureCtx, bucket.Index, height) - if err != nil { - return false, err - } - return status != EndorseExpired, nil -} - -func isSelfOwnedBucket(csc CandidiateStateCommon, bucket *VoteBucket) bool { - cand := csc.GetByIdentifier(bucket.Candidate) - if cand == nil { - return false - } - return address.Equal(bucket.Owner, cand.Owner) -} diff --git a/action/protocol/staking/protocol_test.go b/action/protocol/staking/protocol_test.go index 1b127bbf40..6a62ebeafe 100644 --- a/action/protocol/staking/protocol_test.go +++ b/action/protocol/staking/protocol_test.go @@ -99,7 +99,7 @@ func TestProtocol(t *testing.T) { }, nil, nil, nil) r.NotNil(stk) r.NoError(err) - buckets, _, err := csr.getAllBuckets() + buckets, _, err := csr.NativeBuckets() r.NoError(err) r.Equal(0, len(buckets)) c, _, err := csr.getAllCandidates() @@ -129,7 +129,7 @@ func TestProtocol(t *testing.T) { v, err := stk.Start(ctx, sm) r.NoError(err) r.NoError(sm.WriteView(_protocolID, v)) - _, ok := v.(*ViewData) + _, ok := v.(*viewData) r.True(ok) csm, err := NewCandidateStateManager(sm) @@ -182,14 +182,14 @@ func TestProtocol(t *testing.T) { r.Equal(c1, c2) // load buckets from stateDB and verify - buckets, _, err = csr.getAllBuckets() + buckets, _, err = csr.NativeBuckets() r.NoError(err) r.Equal(len(tests), len(buckets)) // delete one bucket r.NoError(csm.delBucket(1)) - buckets, _, err = csr.getAllBuckets() + buckets, _, err = csr.NativeBuckets() r.NoError(csm.delBucket(1)) - buckets, _, err = csr.getAllBuckets() + buckets, _, err = csr.NativeBuckets() for _, e := range tests { for i := range buckets { if buckets[i].StakedAmount == e.amount { @@ -462,8 +462,9 @@ func TestProtocol_ActiveCandidates(t *testing.T) { sm.EXPECT().Height().DoAndReturn(func() (uint64, error) { return blkHeight, nil }).AnyTimes() - csIndexer.EXPECT().StartView(gomock.Any()).Return(nil, nil) + // csIndexer.EXPECT().StartView(gomock.Any()).Return(nil, nil) csIndexer.EXPECT().Height().Return(uint64(blkHeight), nil).AnyTimes() + csIndexer.EXPECT().LoadStakeView(gomock.Any(), gomock.Any()).Return(nil, nil) v, err := p.Start(ctx, sm) require.NoError(err) diff --git a/action/protocol/staking/read_state.go b/action/protocol/staking/read_state.go index b72c56be55..3fa16f3cce 100644 --- a/action/protocol/staking/read_state.go +++ b/action/protocol/staking/read_state.go @@ -69,7 +69,7 @@ func toIoTeXTypesCandidateV2(csr CandidateStateReader, cand *Candidate, featureC if !c.isSelfStakeBucketSettled() { return false, nil } - vb, err := csr.getBucket(c.SelfStakeBucketIdx) + vb, err := csr.NativeBucket(c.SelfStakeBucketIdx) if err != nil { if errors.Is(err, state.ErrStateNotExist) { return true, nil diff --git a/action/protocol/staking/stakeview_builder.go b/action/protocol/staking/stakeview_builder.go index b5e55cbb71..1dc1bedcf7 100644 --- a/action/protocol/staking/stakeview_builder.go +++ b/action/protocol/staking/stakeview_builder.go @@ -32,8 +32,8 @@ func NewContractStakeViewBuilder( } } -func (b *contractStakeViewBuilder) Build(ctx context.Context, height uint64) (ContractStakeView, error) { - view, err := b.indexer.StartView(ctx) +func (b *contractStakeViewBuilder) Build(ctx context.Context, sr protocol.StateReader, height uint64) (ContractStakeView, error) { + view, err := b.indexer.LoadStakeView(ctx, sr) if err != nil { return nil, err } diff --git a/action/protocol/staking/staking_statereader.go b/action/protocol/staking/staking_statereader.go index b7eb3293b0..25f1edcad5 100644 --- a/action/protocol/staking/staking_statereader.go +++ b/action/protocol/staking/staking_statereader.go @@ -330,7 +330,7 @@ func (c *compositeStakingStateReader) readStateContractStakingBucketTypes(ctx co height := c.nativeSR.Height() var targetIndexer ContractStakingIndexerWithBucketType for _, indexer := range c.contractIndexers { - if indexer.ContractAddress() == req.GetContractAddress() { + if indexer.ContractAddress().String() == req.GetContractAddress() { if bt, ok := indexer.(ContractStakingIndexerWithBucketType); ok { targetIndexer = bt } diff --git a/action/protocol/staking/staking_statereader_test.go b/action/protocol/staking/staking_statereader_test.go index 307eb9e1df..c82a65b0a7 100644 --- a/action/protocol/staking/staking_statereader_test.go +++ b/action/protocol/staking/staking_statereader_test.go @@ -110,7 +110,7 @@ func TestStakingStateReader(t *testing.T) { sf.EXPECT().Height().Return(uint64(1), nil).AnyTimes() candCenter, err := NewCandidateCenter(testCandidates) r.NoError(err) - testNativeData := &ViewData{ + testNativeData := &viewData{ candCenter: candCenter, bucketPool: &BucketPool{ total: &totalAmount{ diff --git a/action/protocol/staking/stakingpb/staking.pb.go b/action/protocol/staking/stakingpb/staking.pb.go index fe4c0587cb..665c795614 100644 --- a/action/protocol/staking/stakingpb/staking.pb.go +++ b/action/protocol/staking/stakingpb/staking.pb.go @@ -8,9 +8,9 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.26.0 -// protoc v4.23.3 -// source: action/protocol/staking/stakingpb/staking.proto +// protoc-gen-go v1.34.2 +// protoc v5.29.3 +// source: staking.proto package stakingpb @@ -53,7 +53,7 @@ type Bucket struct { func (x *Bucket) Reset() { *x = Bucket{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[0] + mi := &file_staking_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -66,7 +66,7 @@ func (x *Bucket) String() string { func (*Bucket) ProtoMessage() {} func (x *Bucket) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[0] + mi := &file_staking_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -79,7 +79,7 @@ func (x *Bucket) ProtoReflect() protoreflect.Message { // Deprecated: Use Bucket.ProtoReflect.Descriptor instead. func (*Bucket) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{0} + return file_staking_proto_rawDescGZIP(), []int{0} } func (x *Bucket) GetIndex() uint64 { @@ -191,7 +191,7 @@ type BucketIndices struct { func (x *BucketIndices) Reset() { *x = BucketIndices{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[1] + mi := &file_staking_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -204,7 +204,7 @@ func (x *BucketIndices) String() string { func (*BucketIndices) ProtoMessage() {} func (x *BucketIndices) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[1] + mi := &file_staking_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -217,7 +217,7 @@ func (x *BucketIndices) ProtoReflect() protoreflect.Message { // Deprecated: Use BucketIndices.ProtoReflect.Descriptor instead. func (*BucketIndices) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{1} + return file_staking_proto_rawDescGZIP(), []int{1} } func (x *BucketIndices) GetIndices() []uint64 { @@ -246,7 +246,7 @@ type Candidate struct { func (x *Candidate) Reset() { *x = Candidate{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[2] + mi := &file_staking_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -259,7 +259,7 @@ func (x *Candidate) String() string { func (*Candidate) ProtoMessage() {} func (x *Candidate) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[2] + mi := &file_staking_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -272,7 +272,7 @@ func (x *Candidate) ProtoReflect() protoreflect.Message { // Deprecated: Use Candidate.ProtoReflect.Descriptor instead. func (*Candidate) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{2} + return file_staking_proto_rawDescGZIP(), []int{2} } func (x *Candidate) GetOwnerAddress() string { @@ -349,7 +349,7 @@ type Candidates struct { func (x *Candidates) Reset() { *x = Candidates{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[3] + mi := &file_staking_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -362,7 +362,7 @@ func (x *Candidates) String() string { func (*Candidates) ProtoMessage() {} func (x *Candidates) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[3] + mi := &file_staking_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -375,7 +375,7 @@ func (x *Candidates) ProtoReflect() protoreflect.Message { // Deprecated: Use Candidates.ProtoReflect.Descriptor instead. func (*Candidates) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{3} + return file_staking_proto_rawDescGZIP(), []int{3} } func (x *Candidates) GetCandidates() []*Candidate { @@ -397,7 +397,7 @@ type TotalAmount struct { func (x *TotalAmount) Reset() { *x = TotalAmount{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[4] + mi := &file_staking_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -410,7 +410,7 @@ func (x *TotalAmount) String() string { func (*TotalAmount) ProtoMessage() {} func (x *TotalAmount) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[4] + mi := &file_staking_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -423,7 +423,7 @@ func (x *TotalAmount) ProtoReflect() protoreflect.Message { // Deprecated: Use TotalAmount.ProtoReflect.Descriptor instead. func (*TotalAmount) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{4} + return file_staking_proto_rawDescGZIP(), []int{4} } func (x *TotalAmount) GetAmount() string { @@ -453,7 +453,7 @@ type BucketType struct { func (x *BucketType) Reset() { *x = BucketType{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[5] + mi := &file_staking_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -466,7 +466,7 @@ func (x *BucketType) String() string { func (*BucketType) ProtoMessage() {} func (x *BucketType) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[5] + mi := &file_staking_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -479,7 +479,7 @@ func (x *BucketType) ProtoReflect() protoreflect.Message { // Deprecated: Use BucketType.ProtoReflect.Descriptor instead. func (*BucketType) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{5} + return file_staking_proto_rawDescGZIP(), []int{5} } func (x *BucketType) GetAmount() string { @@ -514,7 +514,7 @@ type Endorsement struct { func (x *Endorsement) Reset() { *x = Endorsement{} if protoimpl.UnsafeEnabled { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[6] + mi := &file_staking_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -527,7 +527,7 @@ func (x *Endorsement) String() string { func (*Endorsement) ProtoMessage() {} func (x *Endorsement) ProtoReflect() protoreflect.Message { - mi := &file_action_protocol_staking_stakingpb_staking_proto_msgTypes[6] + mi := &file_staking_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -540,7 +540,7 @@ func (x *Endorsement) ProtoReflect() protoreflect.Message { // Deprecated: Use Endorsement.ProtoReflect.Descriptor instead. func (*Endorsement) Descriptor() ([]byte, []int) { - return file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP(), []int{6} + return file_staking_proto_rawDescGZIP(), []int{6} } func (x *Endorsement) GetExpireHeight() uint64 { @@ -550,118 +550,285 @@ func (x *Endorsement) GetExpireHeight() uint64 { return 0 } -var File_action_protocol_staking_stakingpb_staking_proto protoreflect.FileDescriptor - -var file_action_protocol_staking_stakingpb_staking_proto_rawDesc = []byte{ - 0x0a, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, - 0x67, 0x70, 0x62, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x09, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x1a, 0x1f, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x05, - 0x0a, 0x06, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, - 0x0a, 0x10, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, - 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x74, - 0x61, 0x6b, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, - 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x54, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, - 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x54, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, - 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x75, 0x6e, - 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1c, - 0x0a, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3c, 0x0a, 0x19, - 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x19, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x6b, - 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, - 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, - 0x0a, 0x17, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x17, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x29, 0x0a, 0x0d, 0x42, 0x75, 0x63, 0x6b, - 0x65, 0x74, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x64, - 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x69, 0x6e, 0x64, 0x69, - 0x63, 0x65, 0x73, 0x22, 0xbd, 0x02, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, - 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x74, - 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x12, - 0x2e, 0x0a, 0x12, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, - 0x65, 0x74, 0x49, 0x64, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x73, 0x65, 0x6c, - 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x78, 0x12, - 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2c, 0x0a, - 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, - 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x22, 0x42, 0x0a, 0x0a, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, - 0x73, 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, - 0x62, 0x2e, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x63, 0x61, 0x6e, - 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3b, 0x0a, 0x0b, 0x54, 0x6f, 0x74, 0x61, 0x6c, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, - 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x62, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x63, 0x74, - 0x69, 0x76, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x31, 0x0a, 0x0b, 0x45, 0x6e, 0x64, 0x6f, - 0x72, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x42, 0x46, 0x5a, 0x44, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x70, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x2d, 0x63, 0x6f, 0x72, - 0x65, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, - 0x67, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +type SystemStakingContract struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NumOfBuckets uint64 `protobuf:"varint,1,opt,name=numOfBuckets,proto3" json:"numOfBuckets,omitempty"` +} + +func (x *SystemStakingContract) Reset() { + *x = SystemStakingContract{} + if protoimpl.UnsafeEnabled { + mi := &file_staking_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SystemStakingContract) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemStakingContract) ProtoMessage() {} + +func (x *SystemStakingContract) ProtoReflect() protoreflect.Message { + mi := &file_staking_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemStakingContract.ProtoReflect.Descriptor instead. +func (*SystemStakingContract) Descriptor() ([]byte, []int) { + return file_staking_proto_rawDescGZIP(), []int{7} +} + +func (x *SystemStakingContract) GetNumOfBuckets() uint64 { + if x != nil { + return x.NumOfBuckets + } + return 0 +} + +type SystemStakingBucket struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Owner []byte `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` + Candidate []byte `protobuf:"bytes,2,opt,name=candidate,proto3" json:"candidate,omitempty"` + Amount []byte `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + Duration uint64 `protobuf:"varint,4,opt,name=duration,proto3" json:"duration,omitempty"` + CreatedAt uint64 `protobuf:"varint,5,opt,name=createdAt,proto3" json:"createdAt,omitempty"` + UnlockedAt uint64 `protobuf:"varint,6,opt,name=unlockedAt,proto3" json:"unlockedAt,omitempty"` + UnstakedAt uint64 `protobuf:"varint,7,opt,name=unstakedAt,proto3" json:"unstakedAt,omitempty"` + Muted bool `protobuf:"varint,8,opt,name=muted,proto3" json:"muted,omitempty"` +} + +func (x *SystemStakingBucket) Reset() { + *x = SystemStakingBucket{} + if protoimpl.UnsafeEnabled { + mi := &file_staking_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SystemStakingBucket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemStakingBucket) ProtoMessage() {} + +func (x *SystemStakingBucket) ProtoReflect() protoreflect.Message { + mi := &file_staking_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemStakingBucket.ProtoReflect.Descriptor instead. +func (*SystemStakingBucket) Descriptor() ([]byte, []int) { + return file_staking_proto_rawDescGZIP(), []int{8} +} + +func (x *SystemStakingBucket) GetOwner() []byte { + if x != nil { + return x.Owner + } + return nil +} + +func (x *SystemStakingBucket) GetCandidate() []byte { + if x != nil { + return x.Candidate + } + return nil +} + +func (x *SystemStakingBucket) GetAmount() []byte { + if x != nil { + return x.Amount + } + return nil +} + +func (x *SystemStakingBucket) GetDuration() uint64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *SystemStakingBucket) GetCreatedAt() uint64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *SystemStakingBucket) GetUnlockedAt() uint64 { + if x != nil { + return x.UnlockedAt + } + return 0 +} + +func (x *SystemStakingBucket) GetUnstakedAt() uint64 { + if x != nil { + return x.UnstakedAt + } + return 0 +} + +func (x *SystemStakingBucket) GetMuted() bool { + if x != nil { + return x.Muted + } + return false +} + +var File_staking_proto protoreflect.FileDescriptor + +var file_staking_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x09, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x05, 0x0a, 0x06, + 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, 0x0a, 0x10, + 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x6b, + 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0e, + 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x42, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x75, 0x6e, 0x73, 0x74, + 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x61, 0x75, 0x74, 0x6f, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3c, 0x0a, 0x19, 0x73, 0x74, + 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x19, 0x73, + 0x74, 0x61, 0x6b, 0x65, 0x64, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, 0x0a, 0x17, + 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x75, + 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x29, 0x0a, 0x0d, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, + 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x64, 0x69, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x65, + 0x73, 0x22, 0xbd, 0x02, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x22, 0x0a, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x24, 0x0a, + 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x2e, 0x0a, + 0x12, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, + 0x49, 0x64, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x73, 0x65, 0x6c, 0x66, 0x53, + 0x74, 0x61, 0x6b, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x78, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x22, 0x42, 0x0a, 0x0a, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, + 0x34, 0x0a, 0x0a, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x70, 0x62, 0x2e, + 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x63, 0x61, 0x6e, 0x64, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3b, 0x0a, 0x0b, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x62, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x31, 0x0a, 0x0b, 0x45, 0x6e, 0x64, 0x6f, 0x72, 0x73, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x48, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3b, 0x0a, 0x15, 0x53, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x42, 0x75, 0x63, 0x6b, 0x65, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x42, + 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x22, 0xf1, 0x01, 0x0a, 0x13, 0x53, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x53, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6f, + 0x77, 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, + 0x41, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x64, + 0x41, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x75, 0x6e, 0x73, 0x74, 0x61, 0x6b, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x70, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x69, 0x6f, 0x74, 0x65, 0x78, 0x2d, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, + 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_action_protocol_staking_stakingpb_staking_proto_rawDescOnce sync.Once - file_action_protocol_staking_stakingpb_staking_proto_rawDescData = file_action_protocol_staking_stakingpb_staking_proto_rawDesc + file_staking_proto_rawDescOnce sync.Once + file_staking_proto_rawDescData = file_staking_proto_rawDesc ) -func file_action_protocol_staking_stakingpb_staking_proto_rawDescGZIP() []byte { - file_action_protocol_staking_stakingpb_staking_proto_rawDescOnce.Do(func() { - file_action_protocol_staking_stakingpb_staking_proto_rawDescData = protoimpl.X.CompressGZIP(file_action_protocol_staking_stakingpb_staking_proto_rawDescData) +func file_staking_proto_rawDescGZIP() []byte { + file_staking_proto_rawDescOnce.Do(func() { + file_staking_proto_rawDescData = protoimpl.X.CompressGZIP(file_staking_proto_rawDescData) }) - return file_action_protocol_staking_stakingpb_staking_proto_rawDescData + return file_staking_proto_rawDescData } -var file_action_protocol_staking_stakingpb_staking_proto_msgTypes = make([]protoimpl.MessageInfo, 7) -var file_action_protocol_staking_stakingpb_staking_proto_goTypes = []interface{}{ +var file_staking_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_staking_proto_goTypes = []any{ (*Bucket)(nil), // 0: stakingpb.Bucket (*BucketIndices)(nil), // 1: stakingpb.BucketIndices (*Candidate)(nil), // 2: stakingpb.Candidate @@ -669,12 +836,14 @@ var file_action_protocol_staking_stakingpb_staking_proto_goTypes = []interface{} (*TotalAmount)(nil), // 4: stakingpb.TotalAmount (*BucketType)(nil), // 5: stakingpb.BucketType (*Endorsement)(nil), // 6: stakingpb.Endorsement - (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp -} -var file_action_protocol_staking_stakingpb_staking_proto_depIdxs = []int32{ - 7, // 0: stakingpb.Bucket.createTime:type_name -> google.protobuf.Timestamp - 7, // 1: stakingpb.Bucket.stakeStartTime:type_name -> google.protobuf.Timestamp - 7, // 2: stakingpb.Bucket.unstakeStartTime:type_name -> google.protobuf.Timestamp + (*SystemStakingContract)(nil), // 7: stakingpb.SystemStakingContract + (*SystemStakingBucket)(nil), // 8: stakingpb.SystemStakingBucket + (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp +} +var file_staking_proto_depIdxs = []int32{ + 9, // 0: stakingpb.Bucket.createTime:type_name -> google.protobuf.Timestamp + 9, // 1: stakingpb.Bucket.stakeStartTime:type_name -> google.protobuf.Timestamp + 9, // 2: stakingpb.Bucket.unstakeStartTime:type_name -> google.protobuf.Timestamp 2, // 3: stakingpb.Candidates.candidates:type_name -> stakingpb.Candidate 4, // [4:4] is the sub-list for method output_type 4, // [4:4] is the sub-list for method input_type @@ -683,13 +852,13 @@ var file_action_protocol_staking_stakingpb_staking_proto_depIdxs = []int32{ 0, // [0:4] is the sub-list for field type_name } -func init() { file_action_protocol_staking_stakingpb_staking_proto_init() } -func file_action_protocol_staking_stakingpb_staking_proto_init() { - if File_action_protocol_staking_stakingpb_staking_proto != nil { +func init() { file_staking_proto_init() } +func file_staking_proto_init() { + if File_staking_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Bucket); i { case 0: return &v.state @@ -701,7 +870,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*BucketIndices); i { case 0: return &v.state @@ -713,7 +882,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*Candidate); i { case 0: return &v.state @@ -725,7 +894,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*Candidates); i { case 0: return &v.state @@ -737,7 +906,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*TotalAmount); i { case 0: return &v.state @@ -749,7 +918,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*BucketType); i { case 0: return &v.state @@ -761,7 +930,7 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } - file_action_protocol_staking_stakingpb_staking_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_staking_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*Endorsement); i { case 0: return &v.state @@ -773,23 +942,47 @@ func file_action_protocol_staking_stakingpb_staking_proto_init() { return nil } } + file_staking_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*SystemStakingContract); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_staking_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*SystemStakingBucket); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_action_protocol_staking_stakingpb_staking_proto_rawDesc, + RawDescriptor: file_staking_proto_rawDesc, NumEnums: 0, - NumMessages: 7, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_action_protocol_staking_stakingpb_staking_proto_goTypes, - DependencyIndexes: file_action_protocol_staking_stakingpb_staking_proto_depIdxs, - MessageInfos: file_action_protocol_staking_stakingpb_staking_proto_msgTypes, + GoTypes: file_staking_proto_goTypes, + DependencyIndexes: file_staking_proto_depIdxs, + MessageInfos: file_staking_proto_msgTypes, }.Build() - File_action_protocol_staking_stakingpb_staking_proto = out.File - file_action_protocol_staking_stakingpb_staking_proto_rawDesc = nil - file_action_protocol_staking_stakingpb_staking_proto_goTypes = nil - file_action_protocol_staking_stakingpb_staking_proto_depIdxs = nil + File_staking_proto = out.File + file_staking_proto_rawDesc = nil + file_staking_proto_goTypes = nil + file_staking_proto_depIdxs = nil } diff --git a/action/protocol/staking/stakingpb/staking.proto b/action/protocol/staking/stakingpb/staking.proto index 3a2e2c8cb1..236279f1c9 100644 --- a/action/protocol/staking/stakingpb/staking.proto +++ b/action/protocol/staking/stakingpb/staking.proto @@ -62,3 +62,18 @@ message BucketType { message Endorsement { uint64 expireHeight = 1; } + +message SystemStakingContract { + uint64 numOfBuckets = 1; +} + +message SystemStakingBucket { + bytes owner = 1; + bytes candidate = 2; + bytes amount = 3; + uint64 duration = 4; + uint64 createdAt = 5; + uint64 unlockedAt = 6; + uint64 unstakedAt = 7; + bool muted = 8; +} diff --git a/action/protocol/staking/util.go b/action/protocol/staking/util.go new file mode 100644 index 0000000000..7280557b83 --- /dev/null +++ b/action/protocol/staking/util.go @@ -0,0 +1,39 @@ +package staking + +import ( + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" +) + +// isSelfStakeBucket returns true if the bucket is self-stake bucket and not expired +func isSelfStakeBucket(featureCtx protocol.FeatureCtx, csc CandidiateStateCommon, bucket *VoteBucket) (bool, error) { + // bucket index should be settled in one of candidates + selfStake := csc.ContainsSelfStakingBucket(bucket.Index) + if featureCtx.DisableDelegateEndorsement || !selfStake { + return selfStake, nil + } + + // bucket should not be unstaked if it is self-owned + if isSelfOwnedBucket(csc, bucket) { + return !bucket.isUnstaked(), nil + } + // otherwise bucket should be an endorse bucket which is not expired + esm := NewEndorsementStateReader(csc.SR()) + height, err := esm.Height() + if err != nil { + return false, err + } + status, err := esm.Status(featureCtx, bucket.Index, height) + if err != nil { + return false, err + } + return status != EndorseExpired, nil +} + +func isSelfOwnedBucket(csc CandidiateStateCommon, bucket *VoteBucket) bool { + cand := csc.GetByIdentifier(bucket.Candidate) + if cand == nil { + return false + } + return address.Equal(bucket.Owner, cand.Owner) +} diff --git a/action/protocol/staking/viewdata.go b/action/protocol/staking/viewdata.go index f2b7edebee..108b33bf6d 100644 --- a/action/protocol/staking/viewdata.go +++ b/action/protocol/staking/viewdata.go @@ -23,17 +23,19 @@ type ( // Fork forks the contract stake view, commit will not affect the original view Fork() ContractStakeView // Commit commits the contract stake view - Commit() + Commit(context.Context, protocol.StateManager) error // CreatePreStates creates pre states for the contract stake view CreatePreStates(ctx context.Context) error // Handle handles the receipt for the contract stake view Handle(ctx context.Context, receipt *action.Receipt) error + // WriteBuckets writes the buckets to the state manager + WriteBuckets(protocol.StateManager) error // BucketsByCandidate returns the buckets by candidate address BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error } - // ViewData is the data that need to be stored in protocol's view - ViewData struct { + // viewData is the data that need to be stored in protocol's view + viewData struct { candCenter *CandidateCenter bucketPool *BucketPool snapshots []Snapshot @@ -53,8 +55,8 @@ type ( } ) -func (v *ViewData) Fork() protocol.View { - fork := &ViewData{} +func (v *viewData) Fork() protocol.View { + fork := &viewData{} fork.candCenter = v.candCenter.Clone() fork.bucketPool = v.bucketPool.Clone() fork.snapshots = make([]Snapshot, len(v.snapshots)) @@ -71,26 +73,28 @@ func (v *ViewData) Fork() protocol.View { return fork } -func (v *ViewData) Commit(ctx context.Context, sr protocol.StateReader) error { - if err := v.candCenter.Commit(ctx, sr); err != nil { +func (v *viewData) Commit(ctx context.Context, sm protocol.StateManager) error { + if err := v.candCenter.Commit(ctx, sm); err != nil { return err } - if err := v.bucketPool.Commit(sr); err != nil { + if err := v.bucketPool.Commit(); err != nil { return err } if v.contractsStake != nil { - v.contractsStake.Commit() + if err := v.contractsStake.Commit(ctx, sm); err != nil { + return err + } } v.snapshots = []Snapshot{} return nil } -func (v *ViewData) IsDirty() bool { +func (v *viewData) IsDirty() bool { return v.candCenter.IsDirty() || v.bucketPool.IsDirty() } -func (v *ViewData) Snapshot() int { +func (v *viewData) Snapshot() int { snapshot := len(v.snapshots) wrapped := v.contractsStake.Wrap() v.snapshots = append(v.snapshots, Snapshot{ @@ -104,7 +108,7 @@ func (v *ViewData) Snapshot() int { return snapshot } -func (v *ViewData) Revert(snapshot int) error { +func (v *viewData) Revert(snapshot int) error { if snapshot < 0 || snapshot >= len(v.snapshots) { return errors.Errorf("invalid snapshot index %d", snapshot) } @@ -122,6 +126,25 @@ func (v *ViewData) Revert(snapshot int) error { return nil } +func (csv *contractStakeView) FlushBuckets(sm protocol.StateManager) error { + if csv.v1 != nil { + if err := csv.v1.WriteBuckets(sm); err != nil { + return err + } + } + if csv.v2 != nil { + if err := csv.v2.WriteBuckets(sm); err != nil { + return err + } + } + if csv.v3 != nil { + if err := csv.v3.WriteBuckets(sm); err != nil { + return err + } + } + return nil +} + func (csv *contractStakeView) Wrap() *contractStakeView { if csv == nil { return nil @@ -175,16 +198,27 @@ func (csv *contractStakeView) CreatePreStates(ctx context.Context) error { return nil } -func (csv *contractStakeView) Commit() { +func (csv *contractStakeView) Commit(ctx context.Context, sm protocol.StateManager) error { + featureCtx, ok := protocol.GetFeatureCtx(ctx) + if !ok || featureCtx.LoadContractStakingFromIndexer { + sm = nil + } if csv.v1 != nil { - csv.v1.Commit() + if err := csv.v1.Commit(ctx, sm); err != nil { + return err + } } if csv.v2 != nil { - csv.v2.Commit() + if err := csv.v2.Commit(ctx, sm); err != nil { + return err + } } if csv.v3 != nil { - csv.v3.Commit() + if err := csv.v3.Commit(ctx, sm); err != nil { + return err + } } + return nil } func (csv *contractStakeView) Handle(ctx context.Context, receipt *action.Receipt) error { diff --git a/action/protocol/staking/viewdata_test.go b/action/protocol/staking/viewdata_test.go index 447769d14f..db506df05f 100644 --- a/action/protocol/staking/viewdata_test.go +++ b/action/protocol/staking/viewdata_test.go @@ -13,32 +13,32 @@ import ( ) func TestViewData_Fork(t *testing.T) { - viewData, _ := prepareViewData(t) - fork, ok := viewData.Fork().(*ViewData) + vd, _ := prepareViewData(t) + fork, ok := vd.Fork().(*viewData) require.True(t, ok) require.NotNil(t, fork) - require.Equal(t, viewData.candCenter.size, fork.candCenter.size) - require.Equal(t, viewData.candCenter.base, fork.candCenter.base) - require.Equal(t, viewData.candCenter.change, fork.candCenter.change) - require.NotSame(t, viewData.bucketPool, fork.bucketPool) - require.Equal(t, viewData.snapshots, fork.snapshots) + require.Equal(t, vd.candCenter.size, fork.candCenter.size) + require.Equal(t, vd.candCenter.base, fork.candCenter.base) + require.Equal(t, vd.candCenter.change, fork.candCenter.change) + require.NotSame(t, vd.bucketPool, fork.bucketPool) + require.Equal(t, vd.snapshots, fork.snapshots) - sr := mock_chainmanager.NewMockStateReader(gomock.NewController(t)) - sr.EXPECT().Height().Return(uint64(100), nil).Times(1) - require.NoError(t, viewData.Commit(context.Background(), sr)) + sm := mock_chainmanager.NewMockStateManager(gomock.NewController(t)) + sm.EXPECT().Height().Return(uint64(100), nil).Times(1) + require.NoError(t, vd.Commit(context.Background(), sm)) - fork, ok = viewData.Fork().(*ViewData) + fork, ok = vd.Fork().(*viewData) require.True(t, ok) require.NotNil(t, fork) - require.Equal(t, viewData.candCenter.size, fork.candCenter.size) - require.Equal(t, viewData.candCenter.base, fork.candCenter.base) - require.Equal(t, viewData.candCenter.change, fork.candCenter.change) - require.Equal(t, viewData.bucketPool, fork.bucketPool) - require.Equal(t, viewData.snapshots, fork.snapshots) + require.Equal(t, vd.candCenter.size, fork.candCenter.size) + require.Equal(t, vd.candCenter.base, fork.candCenter.base) + require.Equal(t, vd.candCenter.change, fork.candCenter.change) + require.Equal(t, vd.bucketPool, fork.bucketPool) + require.Equal(t, vd.snapshots, fork.snapshots) } -func prepareViewData(t *testing.T) (*ViewData, int) { +func prepareViewData(t *testing.T) (*viewData, int) { owner := identityset.Address(0) cand := &Candidate{ Owner: owner, @@ -61,7 +61,7 @@ func prepareViewData(t *testing.T) (*ViewData, int) { count: 1, }, } - viewData := &ViewData{ + viewData := &viewData{ candCenter: candCenter, bucketPool: bucketPool, snapshots: []Snapshot{}, @@ -72,9 +72,9 @@ func prepareViewData(t *testing.T) (*ViewData, int) { func TestViewData_Commit(t *testing.T) { viewData, _ := prepareViewData(t) require.True(t, viewData.IsDirty()) - mockStateReader := mock_chainmanager.NewMockStateReader(gomock.NewController(t)) - mockStateReader.EXPECT().Height().Return(uint64(100), nil).Times(1) - require.NoError(t, viewData.Commit(context.Background(), mockStateReader)) + mockStateManager := mock_chainmanager.NewMockStateManager(gomock.NewController(t)) + mockStateManager.EXPECT().Height().Return(uint64(100), nil).Times(1) + require.NoError(t, viewData.Commit(context.Background(), mockStateManager)) require.False(t, viewData.IsDirty()) require.Empty(t, viewData.candCenter.change.dirty) require.False(t, viewData.bucketPool.dirty) diff --git a/action/protocol/staking/vote_bucket_test.go b/action/protocol/staking/vote_bucket_test.go index 3c0738b44c..52b2a24d42 100644 --- a/action/protocol/staking/vote_bucket_test.go +++ b/action/protocol/staking/vote_bucket_test.go @@ -66,41 +66,41 @@ func TestGetPutStaking(t *testing.T) { // put buckets and get for _, e := range tests { addr, _ := address.FromBytes(e.name[:]) - _, err := csr.getBucket(e.index) + _, err := csr.NativeBucket(e.index) require.Equal(state.ErrStateNotExist, errors.Cause(err)) vb := NewVoteBucket(addr, identityset.Address(1), big.NewInt(2100000000), 21*uint32(e.index+1), time.Now(), true) - count, err := csr.getTotalBucketCount() + count, err := csr.NumOfNativeBucket() require.NoError(err) require.Equal(e.index, count) count, err = csm.putBucket(vb) require.NoError(err) require.Equal(e.index, count) - count, err = csr.getTotalBucketCount() + count, err = csr.NumOfNativeBucket() require.NoError(err) require.Equal(e.index+1, count) - vb1, err := csr.getBucket(e.index) + vb1, err := csr.NativeBucket(e.index) require.NoError(err) require.Equal(e.index, vb1.Index) require.Equal(vb, vb1) } - vb, err := csr.getBucket(2) + vb, err := csr.NativeBucket(2) require.NoError(err) vb.AutoStake = false vb.StakedAmount.Sub(vb.StakedAmount, big.NewInt(100)) vb.UnstakeStartTime = time.Now().UTC() require.True(vb.isUnstaked()) require.NoError(csm.updateBucket(2, vb)) - vb1, err := csr.getBucket(2) + vb1, err := csr.NativeBucket(2) require.NoError(err) require.Equal(vb, vb1) // delete buckets and get for _, e := range tests { require.NoError(csm.delBucket(e.index)) - _, err := csr.getBucket(e.index) + _, err := csr.NativeBucket(e.index) require.Equal(state.ErrStateNotExist, errors.Cause(err)) } } diff --git a/action/protocol/staking/vote_reviser.go b/action/protocol/staking/vote_reviser.go index ebfd4acc98..844d0c324a 100644 --- a/action/protocol/staking/vote_reviser.go +++ b/action/protocol/staking/vote_reviser.go @@ -130,7 +130,7 @@ func (vr *VoteReviser) correctCandSelfStake(ctx protocol.FeatureCtx, csm Candida cand.SelfStake = big.NewInt(0) continue } - sb, err := csm.getBucket(cand.SelfStakeBucketIdx) + sb, err := csm.NativeBucket(cand.SelfStakeBucketIdx) switch errors.Cause(err) { case state.ErrStateNotExist: // bucket has been withdrawn @@ -194,7 +194,7 @@ func (vr *VoteReviser) calculateVoteWeight(csm CandidateStateManager, cands Cand candm[cand.GetIdentifier().String()].Votes = new(big.Int) candm[cand.GetIdentifier().String()].SelfStake = new(big.Int) } - buckets, _, err := csr.getAllBuckets() + buckets, _, err := csr.NativeBuckets() switch { case errors.Cause(err) == state.ErrStateNotExist: case err != nil: diff --git a/action/protocol/staking/vote_reviser_test.go b/action/protocol/staking/vote_reviser_test.go index 72db8ef6f2..2dc1307d52 100644 --- a/action/protocol/staking/vote_reviser_test.go +++ b/action/protocol/staking/vote_reviser_test.go @@ -152,7 +152,7 @@ func TestVoteReviser(t *testing.T) { v, err := stk.Start(ctx, sm) r.NoError(err) r.NoError(sm.WriteView(_protocolID, v)) - _, ok := v.(*ViewData) + _, ok := v.(*viewData) r.True(ok) csm, err = NewCandidateStateManager(sm) @@ -207,10 +207,10 @@ func TestVoteReviser(t *testing.T) { } for _, v := range tests { if address.Equal(v.cand, c.Owner) && v.index != c.SelfStakeBucketIdx { - bucket, err := csr.getBucket(v.index) + bucket, err := csr.NativeBucket(v.index) r.NoError(err) total := CalculateVoteWeight(cv, bucket, false) - bucket, err = csr.getBucket(c.SelfStakeBucketIdx) + bucket, err = csr.NativeBucket(c.SelfStakeBucketIdx) r.NoError(err) total.Add(total, CalculateVoteWeight(cv, bucket, true)) r.Equal(0, total.Cmp(c.Votes)) diff --git a/blockindex/contractstaking/bucket_info.go b/blockindex/contractstaking/bucket_info.go index 0359050d5c..a357f79895 100644 --- a/blockindex/contractstaking/bucket_info.go +++ b/blockindex/contractstaking/bucket_info.go @@ -10,7 +10,6 @@ import ( "google.golang.org/protobuf/proto" "github.com/iotexproject/iotex-core/v2/blockindex/contractstaking/contractstakingpb" - "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) type ( @@ -26,8 +25,8 @@ type ( ) // Serialize serializes the bucket info -func (bi *bucketInfo) Serialize() []byte { - return byteutil.Must(proto.Marshal(bi.toProto())) +func (bi *bucketInfo) Serialize() ([]byte, error) { + return proto.Marshal(bi.toProto()) } // Deserialize deserializes the bucket info diff --git a/blockindex/contractstaking/cache.go b/blockindex/contractstaking/cache.go index 39aae467d6..68ca596fda 100644 --- a/blockindex/contractstaking/cache.go +++ b/blockindex/contractstaking/cache.go @@ -6,12 +6,16 @@ package contractstaking import ( + "context" + "log" "math/big" "sync" "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) @@ -24,6 +28,7 @@ type ( MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) BucketType(id uint64) (*BucketType, bool) BucketTypeCount() int + Buckets() ([]uint64, []*BucketType, []*bucketInfo) BucketsByCandidate(candidate address.Address) ([]uint64, []*BucketType, []*bucketInfo) TotalBucketCount() uint64 IsDirty() bool @@ -31,7 +36,7 @@ type ( PutBucketType(id uint64, bt *BucketType) PutBucketInfo(id uint64, bi *bucketInfo) DeleteBucketInfo(id uint64) - Commit() stakingCache + Commit(context.Context, address.Address, protocol.StateManager) (stakingCache, error) Clone() stakingCache } contractStakingCache struct { @@ -42,6 +47,9 @@ type ( totalBucketCount uint64 // total number of buckets including burned buckets height uint64 // current block height, it's put in cache for consistency on merge mutex sync.RWMutex // a RW mutex for the cache to protect concurrent access + + deltaBucketTypes map[uint64]*BucketType + deltaBuckets map[uint64]*contractstaking.Bucket } ) @@ -58,6 +66,8 @@ func newContractStakingCache() *contractStakingCache { bucketTypeMap: make(map[uint64]*BucketType), propertyBucketTypeMap: make(map[int64]map[uint64]uint64), candidateBucketMap: make(map[string]map[uint64]bool), + deltaBucketTypes: make(map[uint64]*BucketType), + deltaBuckets: make(map[uint64]*contractstaking.Bucket), } } @@ -170,6 +180,7 @@ func (s *contractStakingCache) PutBucketType(id uint64, bt *BucketType) { defer s.mutex.Unlock() s.putBucketType(id, bt) + s.deltaBucketTypes[id] = bt } func (s *contractStakingCache) PutBucketInfo(id uint64, bi *bucketInfo) { @@ -177,6 +188,18 @@ func (s *contractStakingCache) PutBucketInfo(id uint64, bi *bucketInfo) { defer s.mutex.Unlock() s.putBucketInfo(id, bi) + bt := s.mustGetBucketType(bi.TypeIndex) + s.deltaBuckets[id] = &contractstaking.Bucket{ + Candidate: bi.Delegate, + Owner: bi.Owner, + StakedAmount: bt.Amount, + StakedDuration: bt.Duration, + CreatedAt: bi.CreatedAt, + UnstakedAt: bi.UnstakedAt, + UnlockedAt: bi.UnlockedAt, + Muted: false, + IsTimestampBased: false, + } } func (s *contractStakingCache) DeleteBucketInfo(id uint64) { @@ -184,6 +207,7 @@ func (s *contractStakingCache) DeleteBucketInfo(id uint64) { defer s.mutex.Unlock() s.deleteBucketInfo(id) + s.deltaBuckets[id] = nil } func (s *contractStakingCache) MatchBucketType(amount *big.Int, duration uint64) (uint64, *BucketType, bool) { @@ -271,6 +295,14 @@ func (s *contractStakingCache) Clone() stakingCache { c.propertyBucketTypeMap[k][k1] = v1 } } + c.deltaBucketTypes = make(map[uint64]*BucketType, len(s.deltaBucketTypes)) + for k, v := range s.deltaBucketTypes { + c.deltaBucketTypes[k] = v.Clone() + } + c.deltaBuckets = make(map[uint64]*contractstaking.Bucket, len(s.deltaBuckets)) + for k, v := range s.deltaBuckets { + c.deltaBuckets[k] = v.Clone() + } return c } @@ -294,7 +326,7 @@ func (s *contractStakingCache) getBucketType(id uint64) (*BucketType, bool) { func (s *contractStakingCache) mustGetBucketType(id uint64) *BucketType { bt, ok := s.getBucketType(id) if !ok { - panic("bucket type not found") + log.Panicf("bucket type not found: %d", id) } return bt } @@ -401,8 +433,45 @@ func (s *contractStakingCache) IsDirty() bool { return false } -func (s *contractStakingCache) Commit() stakingCache { - s.mutex.RLock() - defer s.mutex.RUnlock() - return s +func (s *contractStakingCache) Commit(ctx context.Context, ca address.Address, sm protocol.StateManager) (stakingCache, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + if sm == nil { + s.deltaBucketTypes = make(map[uint64]*BucketType) + s.deltaBuckets = make(map[uint64]*contractstaking.Bucket) + return s, nil + } + featureCtx, ok := protocol.GetFeatureCtx(ctx) + if !ok { + return s, nil + } + if featureCtx.LoadContractStakingFromIndexer { + return s, nil + } + if len(s.deltaBucketTypes) == 0 && len(s.deltaBuckets) == 0 { + return s, nil + } + cssm := contractstaking.NewContractStakingStateManager(sm) + for id, bt := range s.deltaBucketTypes { + if err := cssm.UpsertBucketType(ca, id, bt); err != nil { + return nil, errors.Wrapf(err, "failed to upsert bucket type %d", id) + } + } + for id, bucket := range s.deltaBuckets { + if bucket == nil { + if err := cssm.DeleteBucket(ca, id); err != nil { + return nil, errors.Wrapf(err, "failed to delete bucket %d", id) + } + } else { + if err := cssm.UpsertBucket(ca, id, bucket); err != nil { + return nil, errors.Wrapf(err, "failed to upsert bucket %d", id) + } + } + } + if err := cssm.UpdateNumOfBuckets(ca, s.totalBucketCount); err != nil { + return nil, errors.Wrapf(err, "failed to update total bucket count %d", s.totalBucketCount) + } + s.deltaBucketTypes = make(map[uint64]*BucketType) + s.deltaBuckets = make(map[uint64]*contractstaking.Bucket) + return s, nil } diff --git a/blockindex/contractstaking/cache_test.go b/blockindex/contractstaking/cache_test.go index 415e532b7c..f96d609908 100644 --- a/blockindex/contractstaking/cache_test.go +++ b/blockindex/contractstaking/cache_test.go @@ -514,9 +514,13 @@ func TestContractStakingCache_LoadFromDB(t *testing.T) { // load from db with bucket bucketInfo := &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)} - kvstore.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(1), bucketInfo.Serialize()) + bidata, err := bucketInfo.Serialize() + require.NoError(err) + kvstore.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(1), bidata) bucketType := &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1} - kvstore.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(1), bucketType.Serialize()) + btdata, err := bucketType.Serialize() + require.NoError(err) + kvstore.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(1), btdata) err = cache.LoadFromDB(kvstore) require.NoError(err) require.Equal(uint64(10), cache.TotalBucketCount()) @@ -560,6 +564,7 @@ func checkBucket(r *require.Assertions, id uint64, bt *BucketType, bucket *bucke func TestContractStakingCache_MustGetBucketInfo(t *testing.T) { // build test condition to add a bucketInfo cache := newContractStakingCache() + cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) cache.PutBucketInfo(1, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) tryCatchMustGetBucketInfo := func(i uint64) (v *bucketInfo, err error) { @@ -606,12 +611,14 @@ func TestContractStakingCache_MustGetBucketType(t *testing.T) { v, err = tryCatchMustGetBucketType(2) r.Nil(v) r.Error(err) - r.Equal(err.Error(), "bucket type not found") + r.Equal(err.Error(), "bucket type not found: 2") } func TestContractStakingCache_DeleteBucketInfo(t *testing.T) { // build test condition to add a bucketInfo cache := newContractStakingCache() + cache.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) + cache.PutBucketType(2, &BucketType{Amount: big.NewInt(200), Duration: 200, ActivatedAt: 1}) bi1 := &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(1)} bi2 := &bucketInfo{TypeIndex: 2, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)} cache.PutBucketInfo(1, bi1) diff --git a/blockindex/contractstaking/dirty_cache.go b/blockindex/contractstaking/dirty_cache.go index 171e362881..8f2ce90972 100644 --- a/blockindex/contractstaking/dirty_cache.go +++ b/blockindex/contractstaking/dirty_cache.go @@ -50,12 +50,20 @@ func newContractStakingDirty(clean stakingCache) *contractStakingDirty { } func (dirty *contractStakingDirty) addBucketInfo(id uint64, bi *bucketInfo) { - dirty.batch.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(id), bi.Serialize(), "failed to put bucket info") + data, err := bi.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket info")) + } + dirty.batch.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket info") dirty.cache.PutBucketInfo(id, bi) } func (dirty *contractStakingDirty) updateBucketInfo(id uint64, bi *bucketInfo) { - dirty.batch.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(id), bi.Serialize(), "failed to put bucket info") + data, err := bi.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket info")) + } + dirty.batch.Put(_StakingBucketInfoNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket info") dirty.cache.PutBucketInfo(id, bi) } @@ -96,7 +104,11 @@ func (dirty *contractStakingDirty) finalizeBatch() batch.KVStoreBatch { } func (dirty *contractStakingDirty) addBucketType(id uint64, bt *BucketType) { - dirty.batch.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(id), bt.Serialize(), "failed to put bucket type") + data, err := bt.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket type")) + } + dirty.batch.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket type") dirty.cache.PutBucketType(id, bt) } @@ -109,6 +121,10 @@ func (dirty *contractStakingDirty) getBucketTypeCount() uint64 { } func (dirty *contractStakingDirty) updateBucketType(id uint64, bt *BucketType) { - dirty.batch.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(id), bt.Serialize(), "failed to put bucket type") + data, err := bt.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket type")) + } + dirty.batch.Put(_StakingBucketTypeNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket type") dirty.cache.PutBucketType(id, bt) } diff --git a/blockindex/contractstaking/dirty_cache_test.go b/blockindex/contractstaking/dirty_cache_test.go index 4151263f0f..b6070a28d6 100644 --- a/blockindex/contractstaking/dirty_cache_test.go +++ b/blockindex/contractstaking/dirty_cache_test.go @@ -168,7 +168,9 @@ func TestContractStakingDirty_finalize(t *testing.T) { require.EqualValues(_StakingBucketTypeNS, info.Namespace()) require.EqualValues(batch.Put, info.WriteType()) require.EqualValues(byteutil.Uint64ToBytesBigEndian(1), info.Key()) - require.EqualValues(bt.Serialize(), info.Value()) + btdata, err := bt.Serialize() + require.NoError(err) + require.EqualValues(btdata, info.Value()) require.Equal(1, cache.BucketTypeCount()) // add bucket info @@ -181,7 +183,9 @@ func TestContractStakingDirty_finalize(t *testing.T) { require.EqualValues(_StakingBucketInfoNS, info.Namespace()) require.EqualValues(batch.Put, info.WriteType()) require.EqualValues(byteutil.Uint64ToBytesBigEndian(1), info.Key()) - require.EqualValues(bi.Serialize(), info.Value()) + bidata, err := bi.Serialize() + require.NoError(err) + require.EqualValues(bidata, info.Value()) totalCnt = cache.TotalBucketCount() require.EqualValues(1, totalCnt) } @@ -219,6 +223,7 @@ func TestContractStakingDirty_noSideEffectOnClean(t *testing.T) { require.False(ok) require.Nil(bi) + clean.PutBucketType(1, &BucketType{Amount: big.NewInt(100), Duration: 100, ActivatedAt: 1}) // update bucket info existed in clean cache clean.PutBucketInfo(2, &bucketInfo{TypeIndex: 1, CreatedAt: 1, UnlockedAt: maxBlockNumber, UnstakedAt: maxBlockNumber, Delegate: identityset.Address(1), Owner: identityset.Address(2)}) // update bucket info in dirty cache diff --git a/blockindex/contractstaking/event_handler.go b/blockindex/contractstaking/event_handler.go index 16cee07398..c9b4aae092 100644 --- a/blockindex/contractstaking/event_handler.go +++ b/blockindex/contractstaking/event_handler.go @@ -14,6 +14,7 @@ import ( "github.com/pkg/errors" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/db/batch" @@ -373,6 +374,23 @@ func newContractStakingEventHandler(cache stakingCache) *contractStakingEventHan } } +func (eh *contractStakingEventHandler) HandleReceipts(ctx context.Context, height uint64, receipts []*action.Receipt, contractAddr string) error { + for _, receipt := range receipts { + if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { + continue + } + for _, log := range receipt.Logs() { + if log.Address != contractAddr { + continue + } + if err := eh.HandleEvent(ctx, height, log); err != nil { + return err + } + } + } + return nil +} + func (eh *contractStakingEventHandler) HandleEvent(ctx context.Context, height uint64, log *action.Log) error { // get event abi abiEvent, err := _stakingInterface.EventByID(common.Hash(log.Topics[0])) diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index 8bb29e76a2..d9adc6dfc8 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -13,12 +13,11 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/iotexproject/iotex-address/address" - "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" - "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" @@ -35,11 +34,12 @@ type ( // 1. handle contract staking contract events when new block comes to generate index data // 2. provide query interface for contract staking index data Indexer struct { - kvstore db.KVStore // persistent storage, used to initialize index cache at startup - cache *contractStakingCache // in-memory index for clean data, used to query index data - config Config // indexer config - height uint64 - mu sync.RWMutex + kvstore db.KVStore // persistent storage, used to initialize index cache at startup + cache *contractStakingCache // in-memory index for clean data, used to query index data + config Config // indexer config + height uint64 + mu sync.RWMutex + contractAddr address.Address lifecycle.Readiness } @@ -62,16 +62,18 @@ func NewContractStakingIndexer(kvStore db.KVStore, config Config) (*Indexer, err if kvStore == nil { return nil, errors.New("kv store is nil") } - if _, err := address.FromString(config.ContractAddress); err != nil { + contractAddr, err := address.FromString(config.ContractAddress) + if err != nil { return nil, errors.Wrapf(err, "invalid contract address %s", config.ContractAddress) } if config.CalculateVoteWeight == nil { return nil, errors.New("calculate vote weight function is nil") } return &Indexer{ - kvstore: kvStore, - cache: newContractStakingCache(), - config: config, + kvstore: kvStore, + cache: newContractStakingCache(), + config: config, + contractAddr: contractAddr, }, nil } @@ -83,17 +85,73 @@ func (s *Indexer) Start(ctx context.Context) error { return s.start(ctx) } -// StartView starts the indexer view -func (s *Indexer) StartView(ctx context.Context) (staking.ContractStakeView, error) { +// LoadStakeView loads the contract stake view +func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { if !s.IsReady() { if err := s.start(ctx); err != nil { return nil, err } } + featureCtx, ok := protocol.GetFeatureCtx(ctx) + if !ok || featureCtx.LoadContractStakingFromIndexer { + return &stakeView{ + contractAddr: s.contractAddr, + config: s.config, + cache: s.cache.Clone(), + height: s.height, + genBlockDurationFn: s.genBlockDurationFn, + }, nil + } + cssr := contractstaking.NewStateReader(sr) + tids, types, err := cssr.BucketTypes(s.contractAddr) + if err != nil { + return nil, errors.Wrapf(err, "failed to get bucket types for contract %s", s.contractAddr) + } + if len(tids) != len(types) { + return nil, errors.Errorf("length of tids (%d) does not match length of types (%d)", len(tids), len(types)) + } + ids, buckets, err := contractstaking.NewStateReader(sr).Buckets(s.contractAddr) + if err != nil { + return nil, errors.Wrapf(err, "failed to get buckets for contract %s", s.contractAddr) + } + if len(ids) != len(buckets) { + return nil, errors.Errorf("length of ids (%d) does not match length of buckets (%d)", len(ids), len(buckets)) + } + cache := &contractStakingCache{} + for i, id := range tids { + if types[i] == nil { + return nil, errors.Errorf("bucket type %d is nil", id) + } + cache.PutBucketType(id, types[i]) + } + for i, id := range ids { + if buckets[i] == nil { + return nil, errors.New("bucket is nil") + } + tid, _, ok := cache.MatchBucketType(buckets[i].StakedAmount, buckets[i].StakedDuration) + if !ok { + return nil, errors.Errorf( + "no bucket type found for bucket %d with staked amount %s and duration %d", + id, + buckets[i].StakedAmount.String(), + buckets[i].StakedDuration, + ) + } + cache.PutBucketInfo(id, &bucketInfo{ + TypeIndex: tid, + CreatedAt: buckets[i].CreatedAt, + UnlockedAt: buckets[i].UnlockedAt, + UnstakedAt: buckets[i].UnstakedAt, + Delegate: buckets[i].Candidate, + Owner: buckets[i].Owner, + }) + } + return &stakeView{ - helper: s, - cache: s.cache.Clone(), - height: s.height, + cache: cache, + height: s.height, + config: s.config, + contractAddr: s.contractAddr, }, nil } @@ -133,8 +191,8 @@ func (s *Indexer) StartHeight() uint64 { } // ContractAddress returns the contract address -func (s *Indexer) ContractAddress() string { - return s.config.ContractAddress +func (s *Indexer) ContractAddress() address.Address { + return s.contractAddr } // CandidateVotes returns the candidate votes @@ -145,7 +203,7 @@ func (s *Indexer) CandidateVotes(ctx context.Context, candidate address.Address, if err := s.validateHeight(height); err != nil { return nil, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() ids, types, infos := s.cache.BucketsByCandidate(candidate) s.mu.RUnlock() @@ -172,10 +230,7 @@ func (s *Indexer) CandidateVotes(ctx context.Context, candidate address.Address, return votes, nil } -func (s *Indexer) genBlockDurationFn() func(start, end uint64) time.Duration { - s.mu.RLock() - height := s.height - s.mu.RUnlock() +func (s *Indexer) genBlockDurationFn(height uint64) blocksDurationFn { return func(start, end uint64) time.Duration { return s.config.BlocksToDuration(start, end, height) } @@ -189,7 +244,7 @@ func (s *Indexer) Buckets(height uint64) ([]*Bucket, error) { if err := s.validateHeight(height); err != nil { return nil, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() ids, types, infos := s.cache.Buckets() s.mu.RUnlock() @@ -219,7 +274,7 @@ func (s *Indexer) Bucket(id uint64, height uint64) (*Bucket, bool, error) { if err := s.validateHeight(height); err != nil { return nil, false, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() bt, bi := s.cache.Bucket(id) s.mu.RUnlock() @@ -238,7 +293,7 @@ func (s *Indexer) BucketsByIndices(indices []uint64, height uint64) ([]*Bucket, if err := s.validateHeight(height); err != nil { return nil, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() ts, infos := s.cache.BucketsByIndices(indices) s.mu.RUnlock() @@ -267,7 +322,7 @@ func (s *Indexer) BucketsByCandidate(candidate address.Address, height uint64) ( if err := s.validateHeight(height); err != nil { return nil, err } - fn := s.genBlockDurationFn() + fn := s.genBlockDurationFn(height) s.mu.RLock() ids, types, infos := s.cache.BucketsByCandidate(candidate) s.mu.RUnlock() @@ -327,43 +382,25 @@ func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { if blk.Height() > expectHeight { return errors.Errorf("invalid block height %d, expect %d", blk.Height(), expectHeight) } - handler, err := handleReceipts(ctx, blk.Height(), blk.Receipts, &s.config, cache) - if err != nil { - return errors.Wrapf(err, "failed to put block %d", blk.Height()) + handler := newContractStakingEventHandler(cache) + if err := handler.HandleReceipts(ctx, blk.Height(), blk.Receipts, s.contractAddr.String()); err != nil { + return errors.Wrapf(err, "failed to handle receipts at height %d", blk.Height()) } s.mu.Lock() defer s.mu.Unlock() // commit the result - if err := s.commit(handler, blk.Height()); err != nil { + if err := s.commit(ctx, handler, blk.Height()); err != nil { return errors.Wrapf(err, "failed to commit block %d", blk.Height()) } return nil } -func handleReceipts(ctx context.Context, height uint64, receipts []*action.Receipt, cfg *Config, cache stakingCache) (*contractStakingEventHandler, error) { - // new event handler for this block - handler := newContractStakingEventHandler(cache) - - // handle events of block - for _, receipt := range receipts { - if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { - continue - } - for _, log := range receipt.Logs() { - if log.Address != cfg.ContractAddress { - continue - } - if err := handler.HandleEvent(ctx, height, log); err != nil { - return nil, err - } - } - } - return handler, nil -} - -func (s *Indexer) commit(handler *contractStakingEventHandler, height uint64) error { +func (s *Indexer) commit(ctx context.Context, handler *contractStakingEventHandler, height uint64) error { batch, delta := handler.Result() - cache := delta.Commit() + cache, err := delta.Commit(ctx, s.contractAddr, nil) + if err != nil { + return errors.Wrapf(err, "failed to commit delta") + } base, ok := cache.(*contractStakingCache) if !ok { return errors.New("invalid cache type of base") diff --git a/blockindex/contractstaking/indexer_test.go b/blockindex/contractstaking/indexer_test.go index f0b846b784..fcad0fa702 100644 --- a/blockindex/contractstaking/indexer_test.go +++ b/blockindex/contractstaking/indexer_test.go @@ -108,7 +108,7 @@ func TestContractStakingIndexerLoadCache(t *testing.T) { owner := identityset.Address(0) delegate := identityset.Address(1) stake(r, handler, owner, delegate, 1, 10, 100, height) - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) buckets, err := indexer.Buckets(height) r.NoError(err) @@ -173,7 +173,7 @@ func TestContractStakingIndexerDirty(t *testing.T) { r.NoError(err) r.EqualValues(0, gotHeight) // after commit dirty, the cache should be updated - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) gotHeight, err = indexer.Height() r.NoError(err) @@ -230,14 +230,14 @@ func TestContractStakingIndexerThreadSafe(t *testing.T) { indexer.mu.Lock() handler := newContractStakingEventHandler(indexer.cache) activateBucketType(r, handler, 10, 100, 1) - r.NoError(indexer.commit(handler, 1)) + r.NoError(indexer.commit(context.Background(), handler, 1)) indexer.mu.Unlock() for i := 2; i < 1000; i++ { height := uint64(i) indexer.mu.Lock() handler := newContractStakingEventHandler(indexer.cache) stake(r, handler, owner, delegate, int64(i), 10, 100, height) - err := indexer.commit(handler, height) + err := indexer.commit(context.Background(), handler, height) r.NoError(err) indexer.mu.Unlock() } @@ -286,7 +286,7 @@ func TestContractStakingIndexerBucketType(t *testing.T) { for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) bucketTypes, err := indexer.BucketTypes(height) r.NoError(err) @@ -302,7 +302,7 @@ func TestContractStakingIndexerBucketType(t *testing.T) { data := bucketTypeData[i] deactivateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) bucketTypes, err = indexer.BucketTypes(height) r.NoError(err) @@ -318,7 +318,7 @@ func TestContractStakingIndexerBucketType(t *testing.T) { data := bucketTypeData[i] activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) bucketTypes, err = indexer.BucketTypes(height) r.NoError(err) @@ -364,7 +364,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(genesis.WithGenesisContext(context.Background(), genesis.TestDefault()), protocol.BlockCtx{BlockHeight: 1})) @@ -376,7 +376,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) stake(r, handler, owner, delegate, 1, 10, 100, height) r.NoError(err) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err := indexer.Bucket(1, height) r.NoError(err) r.True(ok) @@ -402,7 +402,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) transfer(r, handler, newOwner, int64(bucket.Index)) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -412,7 +412,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) unlock(r, handler, int64(bucket.Index), height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -436,7 +436,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) lock(r, handler, int64(bucket.Index), int64(10)) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -462,7 +462,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { unlock(r, handler, int64(bucket.Index), height) t.Log("unstake bucket", bucket.Index, "at height", height) unstake(r, handler, int64(bucket.Index), height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -486,7 +486,7 @@ func TestContractStakingIndexerBucketInfo(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) withdraw(r, handler, int64(bucket.Index)) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.False(ok) @@ -527,7 +527,7 @@ func TestContractStakingIndexerChangeBucketType(t *testing.T) { for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) t.Run("expand bucket type", func(t *testing.T) { @@ -537,13 +537,13 @@ func TestContractStakingIndexerChangeBucketType(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) stake(r, handler, owner, delegate, 1, 10, 100, height) r.NoError(err) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err := indexer.Bucket(1, height) r.NoError(err) r.True(ok) expandBucketType(r, handler, int64(bucket.Index), 20, 100) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bucket, ok, err = indexer.Bucket(bucket.Index, height) r.NoError(err) r.True(ok) @@ -581,7 +581,7 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { for _, data := range bucketTypeData { activateBucketType(r, handler, data[0], data[1], height) } - err = indexer.commit(handler, height) + err = indexer.commit(context.Background(), handler, height) r.NoError(err) // stake @@ -602,7 +602,7 @@ func TestContractStakingIndexerReadBuckets(t *testing.T) { stake(r, handler, identityset.Address(data.owner), identityset.Address(data.delegate), int64(i+1), int64(data.amount), int64(data.duration), height) } r.NoError(err) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) t.Run("Buckets", func(t *testing.T) { buckets, err := indexer.Buckets(height) @@ -693,7 +693,7 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { r.Len(ids, 0) r.Len(bts, 0) r.Len(bis, 0) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) abt = indexer.cache.ActiveBucketTypes() r.Len(abt, 2) ids, bts, bis = indexer.cache.Buckets() @@ -713,7 +713,7 @@ func TestContractStakingIndexerCacheClean(t *testing.T) { r.NoError(err) r.True(ok) r.Equal(owner.String(), bt.Owner.String()) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) bt, ok, err = indexer.Bucket(3, height) r.NoError(err) r.True(ok) @@ -757,7 +757,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate1, 2, 20, 20, height) stake(r, handler, owner, delegate2, 3, 20, 20, height) stake(r, handler, owner, delegate2, 4, 20, 20, height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err := indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(30, votes.Uint64()) @@ -771,7 +771,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) changeDelegate(r, handler, delegate1, 3) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -784,7 +784,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) unlock(r, handler, 1, height) unlock(r, handler, 4, height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -797,7 +797,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) unstake(r, handler, 1, height) lock(r, handler, 4, 20) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(uint64(40), votes.Uint64()) @@ -809,7 +809,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) expandBucketType(r, handler, 2, 30, 20) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -821,7 +821,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) transfer(r, handler, delegate2, 4) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -835,7 +835,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate2, 5, 20, 20, height) stake(r, handler, owner, delegate2, 6, 20, 20, height) stake(r, handler, owner, delegate2, 7, 20, 20, height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -847,7 +847,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { height++ handler = newContractStakingEventHandler(indexer.cache) mergeBuckets(r, handler, []int64{5, 6, 7}, 60, 20) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -860,7 +860,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { handler = newContractStakingEventHandler(indexer.cache) unlock(r, handler, 5, height) unstake(r, handler, 5, height) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(50, votes.Uint64()) @@ -875,7 +875,7 @@ func TestContractStakingIndexerVotes(t *testing.T) { stake(r, handler, owner, delegate2, 9, 20, 20, height) stake(r, handler, owner, delegate2, 10, 20, 20, height) mergeBuckets(r, handler, []int64{8, 9, 10}, 60, 20) - r.NoError(indexer.commit(handler, height)) + r.NoError(indexer.commit(context.Background(), handler, height)) votes, err = indexer.CandidateVotes(ctx, delegate1, height) r.NoError(err) r.EqualValues(110, votes.Uint64()) diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go index b93b000fe2..44bd1d2724 100644 --- a/blockindex/contractstaking/stakeview.go +++ b/blockindex/contractstaking/stakeview.go @@ -2,7 +2,7 @@ package contractstaking import ( "context" - "time" + "slices" "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-proto/golang/iotextypes" @@ -11,32 +11,37 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) type stakeView struct { - helper *Indexer - cache stakingCache - height uint64 + contractAddr address.Address + config Config + cache stakingCache + genBlockDurationFn func(view uint64) blocksDurationFn + height uint64 } func (s *stakeView) Wrap() staking.ContractStakeView { return &stakeView{ - helper: s.helper, - cache: newWrappedCache(s.cache), - height: s.height, + contractAddr: s.contractAddr, + config: s.config, + cache: newWrappedCache(s.cache), + height: s.height, + genBlockDurationFn: s.genBlockDurationFn, } } func (s *stakeView) Fork() staking.ContractStakeView { return &stakeView{ - helper: s.helper, - cache: newWrappedCacheWithCloneInCommit(s.cache), - height: s.height, + contractAddr: s.contractAddr, + cache: newWrappedCacheWithCloneInCommit(s.cache), + height: s.height, + genBlockDurationFn: s.genBlockDurationFn, } } -func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*Bucket, error) { - ids, types, infos := s.cache.BucketsByCandidate(candidate) +func (s *stakeView) assembleBuckets(ids []uint64, types []*BucketType, infos []*bucketInfo) []*Bucket { vbs := make([]*Bucket, 0, len(ids)) for i, id := range ids { bt := types[i] @@ -45,17 +50,49 @@ func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*Bucket, er vbs = append(vbs, s.assembleBucket(id, info, bt)) } } - return vbs, nil + return vbs } -func (s *stakeView) assembleBucket(token uint64, bi *bucketInfo, bt *BucketType) *Bucket { - return assembleBucket(token, bi, bt, s.helper.config.ContractAddress, s.genBlockDurationFn(s.height)) +func (s *stakeView) WriteBuckets(sm protocol.StateManager) error { + ids, types, infos := s.cache.Buckets() + cssm := contractstaking.NewContractStakingStateManager(sm) + bucketMap := make(map[uint64]*bucketInfo, len(ids)) + typeMap := make(map[uint64]*BucketType, len(ids)) + for i, id := range ids { + bucketMap[id] = infos[i] + typeMap[id] = types[i] + } + slices.Sort(ids) + for _, id := range ids { + info, ok := bucketMap[id] + if !ok { + continue + } + bt := typeMap[id] + if err := cssm.UpsertBucket(s.contractAddr, id, &contractstaking.Bucket{ + Candidate: info.Delegate, + Owner: info.Owner, + StakedAmount: bt.Amount, + StakedDuration: bt.Duration, + CreatedAt: info.CreatedAt, + UnstakedAt: info.UnstakedAt, + UnlockedAt: info.UnlockedAt, + Muted: false, + IsTimestampBased: false, + }); err != nil { + return err + } + } + return cssm.UpdateNumOfBuckets(s.contractAddr, s.cache.TotalBucketCount()) } -func (s *stakeView) genBlockDurationFn(view uint64) blocksDurationFn { - return func(start, end uint64) time.Duration { - return s.helper.config.BlocksToDuration(start, end, view) - } +func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*Bucket, error) { + ids, types, infos := s.cache.BucketsByCandidate(candidate) + return s.assembleBuckets(ids, types, infos), nil +} + +func (s *stakeView) assembleBucket(token uint64, bi *bucketInfo, bt *BucketType) *Bucket { + return assembleBucket(token, bi, bt, s.contractAddr.String(), s.genBlockDurationFn(s.height)) } func (s *stakeView) CreatePreStates(ctx context.Context) error { @@ -74,7 +111,7 @@ func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { return nil } for _, log := range receipt.Logs() { - if log.Address != s.helper.config.ContractAddress { + if log.Address != s.contractAddr.String() { continue } if err := handler.HandleEvent(ctx, blkCtx.BlockHeight, log); err != nil { @@ -87,16 +124,21 @@ func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { return nil } -func (s *stakeView) Commit() { - s.cache = s.cache.Commit() +func (s *stakeView) Commit(ctx context.Context, sm protocol.StateManager) error { + cache, err := s.cache.Commit(ctx, s.contractAddr, sm) + if err != nil { + return err + } + s.cache = cache + return nil } func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error { blkCtx := protocol.MustGetBlockCtx(ctx) height := blkCtx.BlockHeight expectHeight := s.height + 1 - if expectHeight < s.helper.config.ContractDeployHeight { - expectHeight = s.helper.config.ContractDeployHeight + if expectHeight < s.config.ContractDeployHeight { + expectHeight = s.config.ContractDeployHeight } if height < expectHeight { return nil @@ -105,12 +147,12 @@ func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Rec return errors.Errorf("invalid block height %d, expect %d", height, expectHeight) } - handler, err := handleReceipts(ctx, height, receipts, &s.helper.config, s.cache) - if err != nil { + handler := newContractStakingEventHandler(newWrappedCache(s.cache)) + if err := handler.HandleReceipts(ctx, height, receipts, s.contractAddr.String()); err != nil { return err } _, delta := handler.Result() - s.cache = delta.Commit() + s.cache = delta s.height = height return nil } diff --git a/blockindex/contractstaking/wrappedcache.go b/blockindex/contractstaking/wrappedcache.go index 3c8301a5f5..058515d75b 100644 --- a/blockindex/contractstaking/wrappedcache.go +++ b/blockindex/contractstaking/wrappedcache.go @@ -6,11 +6,13 @@ package contractstaking import ( + "context" "math/big" "sort" "sync" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" ) type ( @@ -109,6 +111,27 @@ func (wc *wrappedCache) bucketType(id uint64) (*BucketType, bool) { return bt, ok } +func (wc *wrappedCache) Buckets() ([]uint64, []*BucketType, []*bucketInfo) { + wc.mu.RLock() + defer wc.mu.RUnlock() + ids, types, infos := wc.base.Buckets() + reverseMap := make(map[uint64]int, len(ids)) + for i, id := range ids { + reverseMap[id] = i + } + for id, info := range wc.updatedBucketInfos { + if i, ok := reverseMap[id]; ok { + infos[i] = info.Clone() + } else { + ids = append(ids, id) + infos = append(infos, info.Clone()) + types = append(types, wc.mustGetBucketType(info.TypeIndex)) + reverseMap[id] = len(infos) - 1 + } + } + return ids, types, infos +} + func (wc *wrappedCache) BucketsByCandidate(candidate address.Address) ([]uint64, []*BucketType, []*bucketInfo) { wc.mu.RLock() defer wc.mu.RUnlock() @@ -248,7 +271,7 @@ func (wc *wrappedCache) Clone() stakingCache { } } -func (wc *wrappedCache) Commit() stakingCache { +func (wc *wrappedCache) Commit(ctx context.Context, ca address.Address, sm protocol.StateManager) (stakingCache, error) { wc.mu.Lock() defer wc.mu.Unlock() if wc.commitWithClone { @@ -264,13 +287,13 @@ func (wc *wrappedCache) Commit() stakingCache { wc.base.PutBucketInfo(id, bi) } } - return wc.base.Commit() + return wc.base.Commit(ctx, ca, sm) } func (wc *wrappedCache) IsDirty() bool { wc.mu.RLock() defer wc.mu.RUnlock() - return len(wc.updatedBucketInfos) > 0 || len(wc.updatedBucketTypes) > 0 + return len(wc.updatedBucketInfos) > 0 || len(wc.updatedBucketTypes) > 0 || len(wc.updatedCandidates) > 0 || wc.base.IsDirty() } func (wc *wrappedCache) DeleteBucketInfo(id uint64) { diff --git a/chainservice/builder.go b/chainservice/builder.go index a3ec0ff691..0f9603c02e 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -382,9 +382,13 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { } // build contract staking indexer v2 if builder.cs.contractStakingIndexerV2 == nil && len(builder.cfg.Genesis.SystemStakingContractV2Address) > 0 { + contractAddr, err := address.FromString(builder.cfg.Genesis.SystemStakingContractV2Address) + if err != nil { + return errors.Wrapf(err, "failed to parse contract address %s", builder.cfg.Genesis.SystemStakingContractV2Address) + } indexer := stakingindex.NewIndexer( kvstore, - builder.cfg.Genesis.SystemStakingContractV2Address, + contractAddr, builder.cfg.Genesis.SystemStakingContractV2Height, blockDurationFn, stakingindex.WithMuteHeight(builder.cfg.Genesis.WakeBlockHeight), @@ -393,9 +397,13 @@ func (builder *Builder) buildContractStakingIndexer(forTest bool) error { } // build contract staking indexer v3 if builder.cs.contractStakingIndexerV3 == nil && len(builder.cfg.Genesis.SystemStakingContractV3Address) > 0 { + contractAddr, err := address.FromString(builder.cfg.Genesis.SystemStakingContractV3Address) + if err != nil { + return errors.Wrapf(err, "failed to parse contract address %s", builder.cfg.Genesis.SystemStakingContractV3Address) + } indexer := stakingindex.NewIndexer( kvstore, - builder.cfg.Genesis.SystemStakingContractV3Address, + contractAddr, builder.cfg.Genesis.SystemStakingContractV3Height, blockDurationFn, stakingindex.EnableTimestamped(), diff --git a/state/factory/statedb.go b/state/factory/statedb.go index 1e0e6613ab..de98e315fe 100644 --- a/state/factory/statedb.go +++ b/state/factory/statedb.go @@ -157,10 +157,6 @@ func (sdb *stateDB) Start(ctx context.Context) error { if err = sdb.dao.putHeight(0); err != nil { return errors.Wrap(err, "failed to init statedb's height") } - // start all protocols - if sdb.protocolViews, err = sdb.registry.StartAll(ctx, sdb); err != nil { - return err - } ctx = protocol.WithBlockCtx( ctx, protocol.BlockCtx{ @@ -169,6 +165,10 @@ func (sdb *stateDB) Start(ctx context.Context) error { GasLimit: sdb.cfg.Genesis.BlockGasLimitByHeight(0), }) ctx = protocol.WithFeatureCtx(ctx) + // start all protocols + if sdb.protocolViews, err = sdb.registry.StartAll(ctx, sdb); err != nil { + return err + } // init the state factory if err = sdb.createGenesisStates(ctx); err != nil { return errors.Wrap(err, "failed to create genesis states") @@ -244,8 +244,7 @@ func (sdb *stateDB) newWorkingSetWithKVStore(ctx context.Context, height uint64, if err := store.Start(ctx); err != nil { return nil, err } - views := sdb.protocolViews.Fork() - return newWorkingSet(height, views, store, sdb), nil + return newWorkingSet(height, sdb.protocolViews.Fork(), store, sdb), nil } func (sdb *stateDB) CreateWorkingSetStore(ctx context.Context, height uint64, kvstore db.KVStore) (workingSetStore, error) { diff --git a/state/factory/util.go b/state/factory/util.go index 157e1b3c6c..24ae4e9fba 100644 --- a/state/factory/util.go +++ b/state/factory/util.go @@ -103,12 +103,12 @@ func protocolPreCommit(ctx context.Context, sr protocol.StateManager) error { return nil } -func protocolCommit(ctx context.Context, sr protocol.StateManager) error { +func protocolCommit(ctx context.Context, sm protocol.StateManager) error { if reg, ok := protocol.GetRegistry(ctx); ok { for _, p := range reg.All() { post, ok := p.(protocol.Committer) if ok { - if err := post.Commit(ctx, sr); err != nil { + if err := post.Commit(ctx, sm); err != nil { return err } } diff --git a/state/factory/workingset.go b/state/factory/workingset.go index 38b08b12b7..beacc73233 100644 --- a/state/factory/workingset.go +++ b/state/factory/workingset.go @@ -945,6 +945,9 @@ func (ws *workingSet) ValidateBlock(ctx context.Context, blk *block.Block) error log.L().Error("Failed to update state.", zap.Uint64("height", ws.height), zap.Error(err)) return err } + if err := ws.views.Commit(ctx, ws); err != nil { + return err + } digest, err := ws.digest() if err != nil { @@ -971,6 +974,9 @@ func (ws *workingSet) CreateBuilder( if err != nil { return nil, err } + if err := ws.views.Commit(ctx, ws); err != nil { + return nil, err + } var ( blkCtx = protocol.MustGetBlockCtx(ctx) diff --git a/state/factory/workingset_test.go b/state/factory/workingset_test.go index f5c621de66..dd6c5cf837 100644 --- a/state/factory/workingset_test.go +++ b/state/factory/workingset_test.go @@ -78,7 +78,7 @@ func (v mockView) Revert(int) error { return nil } -func (v mockView) Commit(context.Context, protocol.StateReader) error { +func (v mockView) Commit(context.Context, protocol.StateManager) error { return nil } diff --git a/systemcontractindex/common.go b/systemcontractindex/common.go index 0f0c14cbb5..17484e8eee 100644 --- a/systemcontractindex/common.go +++ b/systemcontractindex/common.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" + "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/db/batch" "github.com/iotexproject/iotex-core/v2/pkg/lifecycle" @@ -22,12 +23,12 @@ type IndexerCommon struct { key []byte startHeight uint64 height uint64 - contractAddress string + contractAddress address.Address lifecycle.Readiness } // NewIndexerCommon creates a new IndexerCommon -func NewIndexerCommon(kvstore db.KVStore, ns string, key []byte, contractAddress string, startHeight uint64) *IndexerCommon { +func NewIndexerCommon(kvstore db.KVStore, ns string, key []byte, contractAddress address.Address, startHeight uint64) *IndexerCommon { return &IndexerCommon{ kvstore: kvstore, ns: ns, @@ -69,7 +70,7 @@ func (s *IndexerCommon) Stop(ctx context.Context) error { func (s *IndexerCommon) KVStore() db.KVStore { return s.kvstore } // ContractAddress returns the contract address -func (s *IndexerCommon) ContractAddress() string { return s.contractAddress } +func (s *IndexerCommon) ContractAddress() address.Address { return s.contractAddress } // Height returns the tip block height func (s *IndexerCommon) Height() uint64 { diff --git a/systemcontractindex/stakingindex/bucket.go b/systemcontractindex/stakingindex/bucket.go index 1c6b4ad10c..8a292912a0 100644 --- a/systemcontractindex/stakingindex/bucket.go +++ b/systemcontractindex/stakingindex/bucket.go @@ -1,21 +1,20 @@ package stakingindex import ( - "math/big" "time" "github.com/iotexproject/iotex-address/address" - "github.com/pkg/errors" - "google.golang.org/protobuf/proto" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" - "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" - "github.com/iotexproject/iotex-core/v2/systemcontractindex/stakingindex/stakingpb" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) type VoteBucket = staking.VoteBucket -type Bucket struct { +type Bucket = contractstaking.Bucket + +/* +struct { Candidate address.Address Owner address.Address StakedAmount *big.Int @@ -102,6 +101,7 @@ func (b *Bucket) Clone() *Bucket { clone.StakedAmount = new(big.Int).Set(b.StakedAmount) return clone } +*/ func assembleVoteBucket(token uint64, bkt *Bucket, contractAddr string, blocksToDurationFn blocksDurationFn) *VoteBucket { vb := VoteBucket{ @@ -111,9 +111,9 @@ func assembleVoteBucket(token uint64, bkt *Bucket, contractAddr string, blocksTo Candidate: bkt.Candidate, Owner: bkt.Owner, ContractAddress: contractAddr, - Timestamped: bkt.Timestamped, + Timestamped: bkt.IsTimestampBased, } - if bkt.Timestamped { + if bkt.IsTimestampBased { vb.StakedDuration = time.Duration(bkt.StakedDuration) * time.Second vb.StakeStartTime = time.Unix(int64(bkt.CreatedAt), 0) vb.CreateTime = time.Unix(int64(bkt.CreatedAt), 0) diff --git a/systemcontractindex/stakingindex/bucket_test.go b/systemcontractindex/stakingindex/bucket_test.go index 7c5dc3c0f7..879633db76 100644 --- a/systemcontractindex/stakingindex/bucket_test.go +++ b/systemcontractindex/stakingindex/bucket_test.go @@ -20,20 +20,22 @@ func TestBucket_SerializeDeserialize(t *testing.T) { owner := identityset.Address(2) original := &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(123456), - Timestamped: true, - StakedDuration: 3600, - CreatedAt: 111111, - UnlockedAt: 222222, - UnstakedAt: 333333, - Muted: true, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(123456), + IsTimestampBased: true, + StakedDuration: 3600, + CreatedAt: 111111, + UnlockedAt: 222222, + UnstakedAt: 333333, + Muted: true, } - data := original.Serialize() + data, err := original.Serialize() + r.NoError(err, "Serialize failed") var deserialized Bucket r.NoError(deserialized.Deserialize(data), "Deserialize failed") + deserialized.IsTimestampBased = original.IsTimestampBased // IsTimestampBased is not serialized, so we set it manually r.Equal(*original, deserialized) } @@ -42,15 +44,15 @@ func TestBucket_Clone(t *testing.T) { candidate := identityset.Address(1) owner := identityset.Address(2) original := &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(123456), - Muted: true, - Timestamped: true, - StakedDuration: 3600, - CreatedAt: 111111, - UnlockedAt: 222222, - UnstakedAt: 333333, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(123456), + Muted: true, + IsTimestampBased: true, + StakedDuration: 3600, + CreatedAt: 111111, + UnlockedAt: 222222, + UnstakedAt: 333333, } clone := original.Clone() @@ -71,48 +73,48 @@ func TestAssembleVoteBucket(t *testing.T) { bucket *Bucket }{ {"timestamped", &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(1000), - Timestamped: true, - StakedDuration: 3600, - CreatedAt: 111111, - UnlockedAt: 222222, - UnstakedAt: 333333, - Muted: false, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(1000), + IsTimestampBased: true, + StakedDuration: 3600, + CreatedAt: 111111, + UnlockedAt: 222222, + UnstakedAt: 333333, + Muted: false, }}, {"timestamped/muted", &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(1000), - Timestamped: true, - StakedDuration: 3600, - CreatedAt: 111111, - UnlockedAt: 222222, - UnstakedAt: 333333, - Muted: true, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(1000), + IsTimestampBased: true, + StakedDuration: 3600, + CreatedAt: 111111, + UnlockedAt: 222222, + UnstakedAt: 333333, + Muted: true, }}, {"block-based", &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(2000), - Timestamped: false, - StakedDuration: 100, - CreatedAt: 10, - UnlockedAt: 20, - UnstakedAt: 110, - Muted: false, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(2000), + IsTimestampBased: false, + StakedDuration: 100, + CreatedAt: 10, + UnlockedAt: 20, + UnstakedAt: 110, + Muted: false, }}, {"block-based/muted", &Bucket{ - Candidate: candidate, - Owner: owner, - StakedAmount: big.NewInt(2000), - Timestamped: false, - StakedDuration: 100, - CreatedAt: 10, - UnlockedAt: 20, - UnstakedAt: 110, - Muted: true, + Candidate: candidate, + Owner: owner, + StakedAmount: big.NewInt(2000), + IsTimestampBased: false, + StakedDuration: 100, + CreatedAt: 10, + UnlockedAt: 20, + UnstakedAt: 110, + Muted: true, }}, } @@ -124,7 +126,7 @@ func TestAssembleVoteBucket(t *testing.T) { if c.bucket.Muted { expectCandidate, _ = address.FromString(address.ZeroAddress) } - if c.bucket.Timestamped { + if c.bucket.IsTimestampBased { stakeStartTime := time.Unix(int64(c.bucket.CreatedAt), 0) unstakeStartTime := time.Unix(0, 0) if c.bucket.UnlockedAt != maxStakingNumber { diff --git a/systemcontractindex/stakingindex/cache.go b/systemcontractindex/stakingindex/cache.go index d8912272a4..8882f7c53e 100644 --- a/systemcontractindex/stakingindex/cache.go +++ b/systemcontractindex/stakingindex/cache.go @@ -1,11 +1,14 @@ package stakingindex import ( + "context" "errors" "sync" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) @@ -20,7 +23,7 @@ type ( BucketIdsByCandidate(candidate address.Address) []uint64 TotalBucketCount() uint64 Clone() indexerCache - Commit() indexerCache + Commit(context.Context, address.Address, bool, protocol.StateManager) (indexerCache, error) IsDirty() bool } // base is the in-memory base for staking index @@ -30,6 +33,7 @@ type ( bucketsByCandidate map[string]map[uint64]struct{} totalBucketCount uint64 mu sync.RWMutex + delta map[uint64]*Bucket } ) @@ -37,6 +41,7 @@ func newCache() *base { return &base{ buckets: make(map[uint64]*Bucket), bucketsByCandidate: make(map[string]map[uint64]struct{}), + delta: make(map[uint64]*Bucket), } } @@ -84,6 +89,15 @@ func (s *base) Clone() indexerCache { } } c.totalBucketCount = s.totalBucketCount + + for k, v := range s.delta { + if v == nil { + c.delta[k] = nil + } else { + c.delta[k] = v.Clone() + } + } + return c } @@ -105,6 +119,8 @@ func (s *base) PutBucket(id uint64, bkt *Bucket) { s.bucketsByCandidate[cand] = make(map[uint64]struct{}) } s.bucketsByCandidate[cand][id] = struct{}{} + + s.delta[id] = bkt } func (s *base) DeleteBucket(id uint64) { @@ -120,6 +136,8 @@ func (s *base) DeleteBucket(id uint64) { delete(s.bucketsByCandidate, cand) } delete(s.buckets, id) + + s.delta[id] = nil } func (s *base) BucketIdxs() []uint64 { @@ -176,8 +194,26 @@ func (s *base) IsDirty() bool { return false } -func (s *base) Commit() indexerCache { +func (s *base) Commit(ctx context.Context, ca address.Address, timestamp bool, sm protocol.StateManager) (indexerCache, error) { s.mu.Lock() defer s.mu.Unlock() - return s + if sm == nil { + s.delta = make(map[uint64]*Bucket) + return s, nil + } + cssm := contractstaking.NewContractStakingStateManager(sm) + for id, bkt := range s.delta { + if bkt == nil { + if err := cssm.DeleteBucket(ca, id); err != nil { + return nil, err + } + } else { + bkt.IsTimestampBased = timestamp + if err := cssm.UpsertBucket(ca, id, bkt); err != nil { + return nil, err + } + } + } + s.delta = make(map[uint64]*Bucket) + return s, nil } diff --git a/systemcontractindex/stakingindex/cache_test.go b/systemcontractindex/stakingindex/cache_test.go new file mode 100644 index 0000000000..7b6a579a58 --- /dev/null +++ b/systemcontractindex/stakingindex/cache_test.go @@ -0,0 +1,74 @@ +package stakingindex + +import ( + "context" + "math/big" + "reflect" + "testing" + + "go.uber.org/mock/gomock" + + "github.com/iotexproject/iotex-core/v2/test/identityset" + "github.com/iotexproject/iotex-core/v2/test/mock/mock_chainmanager" + "github.com/stretchr/testify/require" +) + +func TestBase(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + cache := newCache() + t.Run("empty", func(t *testing.T) { + require.Equal(0, len(cache.BucketIdxs())) + require.False(cache.IsDirty()) + }) + t.Run("clone", func(t *testing.T) { + clone := cache.Clone() + if clone == cache { + t.Error("Expected clone to be a different instance") + } + if !reflect.DeepEqual(clone, cache) { + t.Error("Expected clone to be equal to original") + } + }) + t.Run("put bucket", func(t *testing.T) { + require.Nil(cache.Bucket(1)) + bkt1 := &Bucket{ + Candidate: identityset.Address(1), + Owner: identityset.Address(2), + StakedAmount: big.NewInt(1000), + StakedDuration: 3600, + CreatedAt: 123456, + } + bkt2 := &Bucket{ + Candidate: identityset.Address(3), + Owner: identityset.Address(4), + StakedAmount: big.NewInt(2000), + StakedDuration: 7200, + CreatedAt: 654321, + } + cache.PutBucket(1, bkt1) + cache.PutBucket(2, bkt2) + require.Equal(2, len(cache.BucketIdxs())) + require.Equal(bkt1, cache.Bucket(1)) + require.Equal(bkt2, cache.Bucket(2)) + bkts := cache.Buckets([]uint64{1, 2, 3}) + require.Equal(2, len(bkts)) + require.Equal(bkt1, bkts[0]) + require.Equal(bkt2, bkts[1]) + require.Equal([]uint64{1}, cache.BucketIdsByCandidate(identityset.Address(1))) + require.Equal([]uint64{2}, cache.BucketIdsByCandidate(identityset.Address(3))) + t.Run("delete bucket", func(t *testing.T) { + cache.DeleteBucket(1) + require.Nil(cache.Bucket(1)) + require.Equal(1, len(cache.BucketIdxs())) + t.Run("commit", func(t *testing.T) { + sm := mock_chainmanager.NewMockStateManager(ctrl) + sm.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(uint64(0), nil).Times(1) + sm.EXPECT().PutState(gomock.Any(), gomock.Any(), gomock.Any()).Return(uint64(0), nil).Times(1) + _, err := cache.Commit(context.Background(), identityset.Address(10), false, sm) + require.NoError(err) + }) + }) + }) +} diff --git a/systemcontractindex/stakingindex/event_handler.go b/systemcontractindex/stakingindex/event_handler.go index 5b79dff2b4..8b383b205a 100644 --- a/systemcontractindex/stakingindex/event_handler.go +++ b/systemcontractindex/stakingindex/event_handler.go @@ -1,17 +1,23 @@ package stakingindex import ( + "context" _ "embed" "math" "strings" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" + "go.uber.org/zap" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/db/batch" + "github.com/iotexproject/iotex-core/v2/pkg/log" "github.com/iotexproject/iotex-core/v2/pkg/util/abiutil" "github.com/iotexproject/iotex-core/v2/pkg/util/byteutil" ) @@ -87,15 +93,15 @@ func (eh *eventHandler) HandleStakedEvent(event *abiutil.EventParam) error { createdAt = uint64(eh.blockCtx.BlockTimeStamp.Unix()) } bucket := &Bucket{ - Candidate: delegateParam, - Owner: owner, - StakedAmount: amountParam, - StakedDuration: durationParam.Uint64(), - CreatedAt: createdAt, - UnlockedAt: maxStakingNumber, - UnstakedAt: maxStakingNumber, - Timestamped: eh.timestamped, - Muted: eh.muted, + Candidate: delegateParam, + Owner: owner, + StakedAmount: amountParam, + StakedDuration: durationParam.Uint64(), + CreatedAt: createdAt, + UnlockedAt: maxStakingNumber, + UnstakedAt: maxStakingNumber, + IsTimestampBased: eh.timestamped, + Muted: eh.muted, } eh.putBucket(tokenIDParam.Uint64(), bucket) return nil @@ -291,11 +297,74 @@ func (eh *eventHandler) Finalize() (batch.KVStoreBatch, indexerCache) { } func (eh *eventHandler) putBucket(id uint64, bkt *Bucket) { + data, err := bkt.Serialize() + if err != nil { + panic(errors.Wrap(err, "failed to serialize bucket")) + } eh.dirty.PutBucket(id, bkt) - eh.delta.Put(eh.stakingBucketNS, byteutil.Uint64ToBytesBigEndian(id), bkt.Serialize(), "failed to put bucket") + eh.delta.Put(eh.stakingBucketNS, byteutil.Uint64ToBytesBigEndian(id), data, "failed to put bucket") } func (eh *eventHandler) delBucket(id uint64) { eh.dirty.DeleteBucket(id) eh.delta.Delete(eh.stakingBucketNS, byteutil.Uint64ToBytesBigEndian(id), "failed to delete bucket") } + +func (eh *eventHandler) handleReceipt(ctx context.Context, contractAddr string, receipt *action.Receipt) error { + if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { + return nil + } + for _, log := range receipt.Logs() { + if log.Address != contractAddr { + continue + } + if err := eh.handleEvent(ctx, log); err != nil { + return err + } + } + return nil +} + +func (eh *eventHandler) handleEvent(ctx context.Context, actLog *action.Log) error { + // get event abi + abiEvent, err := StakingContractABI.EventByID(common.Hash(actLog.Topics[0])) + if err != nil { + return errors.Wrapf(err, "get event abi from topic %v failed", actLog.Topics[0]) + } + + // unpack event data + event, err := abiutil.UnpackEventParam(abiEvent, actLog) + if err != nil { + return err + } + log.L().Debug("handle staking event", zap.String("event", abiEvent.Name), zap.Any("event", event)) + // handle different kinds of event + switch abiEvent.Name { + case "Staked": + return eh.HandleStakedEvent(event) + case "Locked": + return eh.HandleLockedEvent(event) + case "Unlocked": + return eh.HandleUnlockedEvent(event) + case "Unstaked": + return eh.HandleUnstakedEvent(event) + case "Merged": + return eh.HandleMergedEvent(event) + case "BucketExpanded": + return eh.HandleBucketExpandedEvent(event) + case "DelegateChanged": + return eh.HandleDelegateChangedEvent(event) + case "Withdrawal": + return eh.HandleWithdrawalEvent(event) + case "Donated": + return eh.HandleDonatedEvent(event) + case "Transfer": + return eh.HandleTransferEvent(event) + case "Approval", "ApprovalForAll", "OwnershipTransferred", "Paused", "Unpaused", "BeneficiaryChanged", + "Migrated": + // not require handling events + return nil + default: + return errors.Errorf("unknown event name %s", abiEvent.Name) + } +} diff --git a/systemcontractindex/stakingindex/index.go b/systemcontractindex/stakingindex/index.go index 7f69c9fca0..00327cebc0 100644 --- a/systemcontractindex/stakingindex/index.go +++ b/systemcontractindex/stakingindex/index.go @@ -5,15 +5,13 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/common" "github.com/iotexproject/iotex-address/address" - "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "go.uber.org/zap" - "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" "github.com/iotexproject/iotex-core/v2/blockchain/block" "github.com/iotexproject/iotex-core/v2/db" "github.com/iotexproject/iotex-core/v2/db/batch" @@ -39,14 +37,14 @@ type ( lifecycle.StartStopper Height() (uint64, error) StartHeight() uint64 - ContractAddress() string + ContractAddress() address.Address Buckets(height uint64) ([]*VoteBucket, error) Bucket(id uint64, height uint64) (*VoteBucket, bool, error) BucketsByIndices(indices []uint64, height uint64) ([]*VoteBucket, error) BucketsByCandidate(candidate address.Address, height uint64) ([]*VoteBucket, error) TotalBucketCount(height uint64) (uint64, error) PutBlock(ctx context.Context, blk *block.Block) error - StartView(ctx context.Context) (staking.ContractStakeView, error) + LoadStakeView(context.Context, protocol.StateReader) (staking.ContractStakeView, error) } stakingEventHandler interface { HandleStakedEvent(event *abiutil.EventParam) error @@ -94,9 +92,9 @@ func EnableTimestamped() IndexerOption { } // NewIndexer creates a new staking indexer -func NewIndexer(kvstore db.KVStore, contractAddr string, startHeight uint64, blocksToDurationFn blocksDurationAtFn, opts ...IndexerOption) *Indexer { - bucketNS := contractAddr + "#" + stakingBucketNS - ns := contractAddr + "#" + stakingNS +func NewIndexer(kvstore db.KVStore, contractAddr address.Address, startHeight uint64, blocksToDurationFn blocksDurationAtFn, opts ...IndexerOption) *Indexer { + bucketNS := contractAddr.String() + "#" + stakingBucketNS + ns := contractAddr.String() + "#" + stakingNS idx := &Indexer{ common: systemcontractindex.NewIndexerCommon(kvstore, ns, stakingHeightKey, contractAddr, startHeight), cache: newCache(), @@ -120,21 +118,6 @@ func (s *Indexer) Start(ctx context.Context) error { return s.start(ctx) } -func (s *Indexer) StartView(ctx context.Context) (staking.ContractStakeView, error) { - s.mutex.Lock() - defer s.mutex.Unlock() - if !s.common.Started() { - if err := s.start(ctx); err != nil { - return nil, err - } - } - return &stakeView{ - helper: s, - cache: s.cache.Clone(), - height: s.common.Height(), - }, nil -} - func (s *Indexer) start(ctx context.Context) error { if err := s.common.Start(ctx); err != nil { return err @@ -149,6 +132,55 @@ func (s *Indexer) Stop(ctx context.Context) error { return s.common.Stop(ctx) } +// LoadStakeView loads the contract stake view from state reader +func (s *Indexer) LoadStakeView(ctx context.Context, sr protocol.StateReader) (staking.ContractStakeView, error) { + s.mutex.RLock() + defer s.mutex.RUnlock() + if !s.common.Started() { + if err := s.start(ctx); err != nil { + return nil, err + } + } + if protocol.MustGetFeatureCtx(ctx).LoadContractStakingFromIndexer { + return &stakeView{ + cache: s.cache.Clone(), + height: s.common.Height(), + contractAddr: s.common.ContractAddress(), + muteHeight: s.muteHeight, + timestamped: s.timestamped, + startHeight: s.common.StartHeight(), + bucketNS: s.bucketNS, + genBlockDurationFn: s.genBlockDurationFn, + }, nil + } + contractAddr := s.common.ContractAddress() + ids, buckets, err := contractstaking.NewStateReader(sr).Buckets(contractAddr) + if err != nil { + return nil, errors.Wrapf(err, "failed to get buckets for contract %s", contractAddr) + } + if len(ids) != len(buckets) { + return nil, errors.Errorf("length of ids (%d) does not match length of buckets (%d)", len(ids), len(buckets)) + } + cache := &base{} + for i, b := range buckets { + if b == nil { + return nil, errors.New("bucket is nil") + } + b.IsTimestampBased = s.timestamped + cache.PutBucket(ids[i], b) + } + return &stakeView{ + cache: cache, + height: s.common.Height(), + contractAddr: s.common.ContractAddress(), + muteHeight: s.muteHeight, + startHeight: s.common.StartHeight(), + timestamped: s.timestamped, + bucketNS: s.bucketNS, + genBlockDurationFn: s.genBlockDurationFn, + }, nil +} + // Height returns the tip block height func (s *Indexer) Height() (uint64, error) { s.mutex.RLock() @@ -164,7 +196,7 @@ func (s *Indexer) StartHeight() uint64 { } // ContractAddress returns the contract address -func (s *Indexer) ContractAddress() string { +func (s *Indexer) ContractAddress() address.Address { s.mutex.RLock() defer s.mutex.RUnlock() return s.common.ContractAddress() @@ -182,7 +214,7 @@ func (s *Indexer) Buckets(height uint64) ([]*VoteBucket, error) { } idxs := s.cache.BucketIdxs() bkts := s.cache.Buckets(idxs) - vbs := batchAssembleVoteBucket(idxs, bkts, s.common.ContractAddress(), s.genBlockDurationFn(height)) + vbs := batchAssembleVoteBucket(idxs, bkts, s.common.ContractAddress().String(), s.genBlockDurationFn(height)) return vbs, nil } @@ -200,7 +232,7 @@ func (s *Indexer) Bucket(id uint64, height uint64) (*VoteBucket, bool, error) { if bkt == nil { return nil, false, nil } - vbs := assembleVoteBucket(id, bkt, s.common.ContractAddress(), s.genBlockDurationFn(height)) + vbs := assembleVoteBucket(id, bkt, s.common.ContractAddress().String(), s.genBlockDurationFn(height)) return vbs, true, nil } @@ -215,7 +247,7 @@ func (s *Indexer) BucketsByIndices(indices []uint64, height uint64) ([]*VoteBuck return nil, nil } bkts := s.cache.Buckets(indices) - vbs := batchAssembleVoteBucket(indices, bkts, s.common.ContractAddress(), s.genBlockDurationFn(height)) + vbs := batchAssembleVoteBucket(indices, bkts, s.common.ContractAddress().String(), s.genBlockDurationFn(height)) return vbs, nil } @@ -240,7 +272,7 @@ func (s *Indexer) BucketsByCandidate(candidate address.Address, height uint64) ( bktsFiltered = append(bktsFiltered, bkts[i]) } } - vbs := batchAssembleVoteBucket(idxsFiltered, bktsFiltered, s.common.ContractAddress(), s.genBlockDurationFn(height)) + vbs := batchAssembleVoteBucket(idxsFiltered, bktsFiltered, s.common.ContractAddress().String(), s.genBlockDurationFn(height)) return vbs, nil } @@ -273,85 +305,30 @@ func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { muted := s.muteHeight > 0 && blk.Height() >= s.muteHeight handler := newEventHandler(s.bucketNS, newWrappedCache(s.cache), protocol.MustGetBlockCtx(ctx), s.timestamped, muted) for _, receipt := range blk.Receipts { - if err := s.handleReceipt(ctx, handler, receipt); err != nil { + if err := handler.handleReceipt(ctx, s.common.ContractAddress().String(), receipt); err != nil { return errors.Wrapf(err, "handle receipt %x failed", receipt.ActionHash) } } // commit - return s.commit(handler, blk.Height()) -} - -func (s *Indexer) handleReceipt(ctx context.Context, eh stakingEventHandler, receipt *action.Receipt) error { - if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) { - return nil - } - for _, log := range receipt.Logs() { - if log.Address != s.common.ContractAddress() { - continue - } - if err := s.handleEvent(ctx, eh, log); err != nil { - return err - } - } - return nil + return s.commit(ctx, handler, blk.Height()) } -func (s *Indexer) handleEvent(ctx context.Context, eh stakingEventHandler, actLog *action.Log) error { - // get event abi - abiEvent, err := StakingContractABI.EventByID(common.Hash(actLog.Topics[0])) - if err != nil { - return errors.Wrapf(err, "get event abi from topic %v failed", actLog.Topics[0]) - } - - // unpack event data - event, err := abiutil.UnpackEventParam(abiEvent, actLog) - if err != nil { - return err - } - log.L().Debug("handle staking event", zap.String("event", abiEvent.Name), zap.Any("event", event)) - // handle different kinds of event - switch abiEvent.Name { - case "Staked": - return eh.HandleStakedEvent(event) - case "Locked": - return eh.HandleLockedEvent(event) - case "Unlocked": - return eh.HandleUnlockedEvent(event) - case "Unstaked": - return eh.HandleUnstakedEvent(event) - case "Merged": - return eh.HandleMergedEvent(event) - case "BucketExpanded": - return eh.HandleBucketExpandedEvent(event) - case "DelegateChanged": - return eh.HandleDelegateChangedEvent(event) - case "Withdrawal": - return eh.HandleWithdrawalEvent(event) - case "Donated": - return eh.HandleDonatedEvent(event) - case "Transfer": - return eh.HandleTransferEvent(event) - case "Approval", "ApprovalForAll", "OwnershipTransferred", "Paused", "Unpaused", "BeneficiaryChanged", - "Migrated": - // not require handling events - return nil - default: - return errors.Errorf("unknown event name %s", abiEvent.Name) - } -} - -func (s *Indexer) commit(handler stakingEventHandler, height uint64) error { +func (s *Indexer) commit(ctx context.Context, handler stakingEventHandler, height uint64) error { delta, dirty := handler.Finalize() // update db if err := s.common.Commit(height, delta); err != nil { return err } - cache, ok := dirty.Commit().(*base) + cache, err := dirty.Commit(ctx, s.common.ContractAddress(), s.timestamped, nil) + if err != nil { + return errors.Wrapf(err, "failed to commit dirty cache at height %d", height) + } + base, ok := cache.(*base) if !ok { return errors.Errorf("unexpected cache type %T, expect *base", dirty) } // update cache - s.cache = cache + s.cache = base return nil } diff --git a/systemcontractindex/stakingindex/stakeview.go b/systemcontractindex/stakingindex/stakeview.go index 274b15cbd6..829a478cf6 100644 --- a/systemcontractindex/stakingindex/stakeview.go +++ b/systemcontractindex/stakingindex/stakeview.go @@ -2,6 +2,7 @@ package stakingindex import ( "context" + "slices" "github.com/iotexproject/iotex-address/address" "github.com/pkg/errors" @@ -9,30 +10,59 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" "github.com/iotexproject/iotex-core/v2/action/protocol/staking" + "github.com/iotexproject/iotex-core/v2/action/protocol/staking/contractstaking" ) type stakeView struct { - helper *Indexer - cache indexerCache - height uint64 + cache indexerCache + height uint64 + startHeight uint64 + contractAddr address.Address + muteHeight uint64 + timestamped bool + bucketNS string + genBlockDurationFn func(view uint64) blocksDurationFn } func (s *stakeView) Wrap() staking.ContractStakeView { return &stakeView{ - helper: s.helper, - cache: newWrappedCache(s.cache), - height: s.height, + cache: newWrappedCache(s.cache), + height: s.height, + startHeight: s.startHeight, + contractAddr: s.contractAddr, + muteHeight: s.muteHeight, + timestamped: s.timestamped, + bucketNS: s.bucketNS, + genBlockDurationFn: s.genBlockDurationFn, } } func (s *stakeView) Fork() staking.ContractStakeView { return &stakeView{ - helper: s.helper, - cache: newWrappedCacheWithCloneInCommit(s.cache), - height: s.height, + cache: newWrappedCacheWithCloneInCommit(s.cache), + height: s.height, + startHeight: s.startHeight, + contractAddr: s.contractAddr, + muteHeight: s.muteHeight, + timestamped: s.timestamped, + bucketNS: s.bucketNS, + genBlockDurationFn: s.genBlockDurationFn, } } +func (s *stakeView) WriteBuckets(sm protocol.StateManager) error { + ids := s.cache.BucketIdxs() + slices.Sort(ids) + buckets := s.cache.Buckets(ids) + cssm := contractstaking.NewContractStakingStateManager(sm) + for _, id := range ids { + if err := cssm.UpsertBucket(s.contractAddr, id, buckets[id]); err != nil { + return err + } + } + return cssm.UpdateNumOfBuckets(s.contractAddr, s.cache.TotalBucketCount()) +} + func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*VoteBucket, error) { idxs := s.cache.BucketIdsByCandidate(candidate) bkts := s.cache.Buckets(idxs) @@ -45,7 +75,7 @@ func (s *stakeView) BucketsByCandidate(candidate address.Address) ([]*VoteBucket bktsFiltered = append(bktsFiltered, bkts[i]) } } - vbs := batchAssembleVoteBucket(idxsFiltered, bktsFiltered, s.helper.common.ContractAddress(), s.helper.genBlockDurationFn(s.height)) + vbs := batchAssembleVoteBucket(idxsFiltered, bktsFiltered, s.contractAddr.String(), s.genBlockDurationFn(s.height)) return vbs, nil } @@ -57,25 +87,25 @@ func (s *stakeView) CreatePreStates(ctx context.Context) error { func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { blkCtx := protocol.MustGetBlockCtx(ctx) - muted := s.helper.muteHeight > 0 && blkCtx.BlockHeight >= s.helper.muteHeight - handler := newEventHandler(s.helper.bucketNS, s.cache, blkCtx, s.helper.timestamped, muted) - return s.helper.handleReceipt(ctx, handler, receipt) + muted := s.muteHeight > 0 && blkCtx.BlockHeight >= s.muteHeight + handler := newEventHandler(s.bucketNS, s.cache, blkCtx, s.timestamped, muted) + return handler.handleReceipt(ctx, s.contractAddr.String(), receipt) } func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Receipt) error { blkCtx := protocol.MustGetBlockCtx(ctx) height := blkCtx.BlockHeight - if height < s.helper.common.StartHeight() { + if height < s.startHeight { return nil } - if height != s.height+1 && height != s.helper.StartHeight() { + if height != s.height+1 && height != s.startHeight { return errors.Errorf("block height %d does not match stake view height %d", height, s.height+1) } ctx = protocol.WithBlockCtx(ctx, blkCtx) - muted := s.helper.muteHeight > 0 && height >= s.helper.muteHeight - handler := newEventHandler(s.helper.bucketNS, s.cache, blkCtx, s.helper.timestamped, muted) + muted := s.muteHeight > 0 && height >= s.muteHeight + handler := newEventHandler(s.bucketNS, s.cache, blkCtx, s.timestamped, muted) for _, receipt := range receipts { - if err := s.helper.handleReceipt(ctx, handler, receipt); err != nil { + if err := handler.handleReceipt(ctx, s.contractAddr.String(), receipt); err != nil { return errors.Wrapf(err, "failed to handle receipt at height %d", height) } } @@ -83,6 +113,12 @@ func (s *stakeView) AddBlockReceipts(ctx context.Context, receipts []*action.Rec return nil } -func (s *stakeView) Commit() { - s.cache = s.cache.Commit() +func (s *stakeView) Commit(ctx context.Context, sm protocol.StateManager) error { + cache, err := s.cache.Commit(ctx, s.contractAddr, s.timestamped, sm) + if err != nil { + return err + } + s.cache = cache + + return nil } diff --git a/systemcontractindex/stakingindex/wrappedcache.go b/systemcontractindex/stakingindex/wrappedcache.go index 6d1a91077c..274c87caf3 100644 --- a/systemcontractindex/stakingindex/wrappedcache.go +++ b/systemcontractindex/stakingindex/wrappedcache.go @@ -1,10 +1,12 @@ package stakingindex import ( + "context" "slices" "sync" "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-core/v2/action/protocol" ) type wrappedCache struct { @@ -14,6 +16,7 @@ type wrappedCache struct { mu sync.RWMutex commitWithClone bool // whether to commit with deep clone + readOnly bool } func newWrappedCache(cache indexerCache) *wrappedCache { @@ -36,6 +39,9 @@ func newWrappedCacheWithCloneInCommit(cache indexerCache) *wrappedCache { func (w *wrappedCache) PutBucket(id uint64, bkt *Bucket) { w.mu.Lock() defer w.mu.Unlock() + if w.readOnly { + panic("cannot delete bucket in read-only mode") + } oldBucket, ok := w.updatedBuckets[id] if !ok { oldBucket = w.cache.Bucket(id) @@ -58,6 +64,9 @@ func (w *wrappedCache) PutBucket(id uint64, bkt *Bucket) { func (w *wrappedCache) DeleteBucket(id uint64) { w.mu.Lock() defer w.mu.Unlock() + if w.readOnly { + panic("cannot delete bucket in read-only mode") + } w.updatedBuckets[id] = nil } @@ -158,7 +167,7 @@ func (w *wrappedCache) TotalBucketCount() uint64 { func (w *wrappedCache) IsDirty() bool { w.mu.RLock() defer w.mu.RUnlock() - return w.cache.IsDirty() || w.isDirty() + return w.isDirty() || w.cache.IsDirty() } func (w *wrappedCache) Clone() indexerCache { @@ -180,29 +189,29 @@ func (w *wrappedCache) Clone() indexerCache { } } wc.commitWithClone = w.commitWithClone + wc.readOnly = w.readOnly return wc } -func (w *wrappedCache) Commit() indexerCache { +func (w *wrappedCache) Commit(ctx context.Context, ca address.Address, timestamp bool, sm protocol.StateManager) (indexerCache, error) { w.mu.Lock() defer w.mu.Unlock() if w.isDirty() { - cache := w.cache if w.commitWithClone { - cache = w.cache.Clone() + w.cache = w.cache.Clone() } for id, bkt := range w.updatedBuckets { if bkt == nil { - cache.DeleteBucket(id) + w.cache.DeleteBucket(id) } else { - cache.PutBucket(id, bkt) + w.cache.PutBucket(id, bkt) } } w.updatedBuckets = make(map[uint64]*Bucket) w.bucketsByCandidate = make(map[string]map[uint64]bool) - w.cache = cache } - return w.cache.Commit() + w.readOnly = true + return w.cache.Commit(ctx, ca, timestamp, sm) } func (w *wrappedCache) isDirty() bool { From 5ff053579788cbe64bf6f0a51759a8021bde0fea Mon Sep 17 00:00:00 2001 From: zhi Date: Tue, 2 Sep 2025 13:55:03 +0800 Subject: [PATCH 2/2] fix unit test and address comment --- action/protocol/staking/candidate_center.go | 14 ++++---------- .../staking/contractstaking/statemanager_test.go | 4 ++-- action/protocol/staking/protocol.go | 16 ++++------------ 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/action/protocol/staking/candidate_center.go b/action/protocol/staking/candidate_center.go index 9c8caeb8c6..8df12e7ecc 100644 --- a/action/protocol/staking/candidate_center.go +++ b/action/protocol/staking/candidate_center.go @@ -132,15 +132,9 @@ func (m *CandidateCenter) Commit(ctx context.Context, sm protocol.StateManager) return err } if featureWithHeightCtx, ok := protocol.GetFeatureWithHeightCtx(ctx); ok && featureWithHeightCtx.CandCenterHasAlias(height) { - if err := m.legacyCommit(); err != nil { - return err - } - } else { - if err := m.commit(); err != nil { - return err - } + return m.legacyCommit() } - return m.writeToStateDB(sm) + return m.commit() } // legacyCommit writes the change into base with legacy logic @@ -292,8 +286,8 @@ func (m *CandidateCenter) Upsert(d *Candidate) error { return nil } -// writeToStateDB writes the candidate center to stateDB -func (m *CandidateCenter) writeToStateDB(sm protocol.StateManager) error { +// WriteToStateDB writes the candidate center to stateDB +func (m *CandidateCenter) WriteToStateDB(sm protocol.StateManager) error { // persist nameMap/operatorMap and ownerList to stateDB name := m.base.candsInNameMap() op := m.base.candsInOperatorMap() diff --git a/action/protocol/staking/contractstaking/statemanager_test.go b/action/protocol/staking/contractstaking/statemanager_test.go index c134c02866..c7714a6fcd 100644 --- a/action/protocol/staking/contractstaking/statemanager_test.go +++ b/action/protocol/staking/contractstaking/statemanager_test.go @@ -69,7 +69,7 @@ func TestDeleteBucket_Error(t *testing.T) { defer ctrl.Finish() mockSM := mock_chainmanager.NewMockStateManager(ctrl) - mockSM.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(errors.New("delstate error")) + mockSM.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(uint64(0), errors.New("delstate error")) csm := NewContractStakingStateManager(mockSM) contractAddr := identityset.Address(1) @@ -86,7 +86,7 @@ func TestDeleteBucket_Success(t *testing.T) { defer ctrl.Finish() mockSM := mock_chainmanager.NewMockStateManager(ctrl) - mockSM.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(nil) + mockSM.EXPECT().DelState(gomock.Any(), gomock.Any()).Return(uint64(0), nil) csm := NewContractStakingStateManager(mockSM) contractAddr := identityset.Address(1) diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index bf1ae914a8..3436021c0c 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -500,7 +500,10 @@ func (p *Protocol) PreCommit(ctx context.Context, sm protocol.StateManager) erro if !vd.IsDirty() { return nil } - return vd.Commit(ctx, sm) + if err := vd.Commit(ctx, sm); err != nil { + return err + } + return vd.candCenter.WriteToStateDB(sm) } // Commit commits the last change @@ -987,14 +990,3 @@ func readCandCenterStateFromStateDB(sr protocol.StateReader) (CandidateList, Can } return name, operator, owner, nil } - -func writeCandCenterStateToStateDB(sm protocol.StateManager, name, op, owners CandidateList) error { - if _, err := sm.PutState(name, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_nameKey)); err != nil { - return err - } - if _, err := sm.PutState(op, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_operatorKey)); err != nil { - return err - } - _, err := sm.PutState(owners, protocol.NamespaceOption(CandsMapNS), protocol.KeyOption(_ownerKey)) - return err -}