diff --git a/action/protocol/staking/protocol.go b/action/protocol/staking/protocol.go index daf87286e0..7b26f23a1e 100644 --- a/action/protocol/staking/protocol.go +++ b/action/protocol/staking/protocol.go @@ -74,17 +74,25 @@ type ( ReceiptStatus() uint64 } + ContractStakeViewBuilder interface { + Build(ctx context.Context, target uint64) (ContractStakeView, error) + } + // Protocol defines the protocol of handling staking Protocol struct { - addr address.Address - config Configuration - candBucketsIndexer *CandidatesBucketsIndexer - contractStakingIndexer ContractStakingIndexerWithBucketType - contractStakingIndexerV2 ContractStakingIndexer - contractStakingIndexerV3 ContractStakingIndexer - voteReviser *VoteReviser - patch *PatchStore - helperCtx HelperCtx + addr address.Address + config Configuration + candBucketsIndexer *CandidatesBucketsIndexer + contractStakingIndexer ContractStakingIndexerWithBucketType + contractStakingIndexerV2 ContractStakingIndexer + contractStakingIndexerV3 ContractStakingIndexer + voteReviser *VoteReviser + patch *PatchStore + helperCtx HelperCtx + contractStakingViewBuilder ContractStakeViewBuilder + contractStakingViewV2Builder ContractStakeViewBuilder + contractStakingViewV3Builder ContractStakeViewBuilder + blockStore BlockStore } // Configuration is the staking protocol configuration. @@ -118,6 +126,12 @@ func WithContractStakingIndexerV3(indexer ContractStakingIndexer) Option { } } +func WithBlockStore(bs BlockStore) Option { + return func(p *Protocol) { + p.blockStore = bs + } +} + // FindProtocol return a registered protocol from registry func FindProtocol(registry *protocol.Registry) *Protocol { if registry == nil { @@ -196,6 +210,15 @@ func NewProtocol( for _, opt := range opts { opt(p) } + if p.contractStakingIndexer != nil { + p.contractStakingViewBuilder = NewContractStakeViewBuilder(p.contractStakingIndexer, p.blockStore) + } + if p.contractStakingIndexerV2 != nil { + p.contractStakingViewV2Builder = NewContractStakeViewBuilder(p.contractStakingIndexerV2, p.blockStore) + } + if p.contractStakingIndexerV3 != nil { + p.contractStakingViewV3Builder = NewContractStakeViewBuilder(p.contractStakingIndexerV3, p.blockStore) + } return p, nil } @@ -232,22 +255,22 @@ func (p *Protocol) Start(ctx context.Context, sr protocol.StateReader) (protocol } c.contractsStake = &contractStakeView{} - if p.contractStakingIndexer != nil { - view, err := p.contractStakingIndexer.StartView(ctx) + if p.contractStakingViewBuilder != nil { + view, err := p.contractStakingViewBuilder.Build(ctx, height) if err != nil { return nil, errors.Wrap(err, "failed to start contract staking indexer") } c.contractsStake.v1 = view } - if p.contractStakingIndexerV2 != nil { - view, err := p.contractStakingIndexerV2.StartView(ctx) + if p.contractStakingViewV2Builder != nil { + view, err := p.contractStakingViewV2Builder.Build(ctx, height) if err != nil { return nil, errors.Wrap(err, "failed to start contract staking indexer v2") } c.contractsStake.v2 = view } - if p.contractStakingIndexerV3 != nil { - view, err := p.contractStakingIndexerV3.StartView(ctx) + if p.contractStakingViewV3Builder != nil { + view, err := p.contractStakingViewV3Builder.Build(ctx, height) if err != nil { return nil, errors.Wrap(err, "failed to start contract staking indexer v3") } diff --git a/action/protocol/staking/protocol_test.go b/action/protocol/staking/protocol_test.go index 1e62c5baa1..d3e55a7303 100644 --- a/action/protocol/staking/protocol_test.go +++ b/action/protocol/staking/protocol_test.go @@ -463,6 +463,7 @@ func TestProtocol_ActiveCandidates(t *testing.T) { return blkHeight, nil }).AnyTimes() csIndexer.EXPECT().StartView(gomock.Any()).Return(nil, nil) + csIndexer.EXPECT().Height().Return(uint64(blkHeight), nil).AnyTimes() v, err := p.Start(ctx, sm) require.NoError(err) diff --git a/action/protocol/staking/stakeview_builder.go b/action/protocol/staking/stakeview_builder.go new file mode 100644 index 0000000000..60c3708fd5 --- /dev/null +++ b/action/protocol/staking/stakeview_builder.go @@ -0,0 +1,68 @@ +package staking + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/iotexproject/iotex-core/v2/action" + "github.com/iotexproject/iotex-core/v2/blockchain/block" +) + +type ( + BlockStore interface { + GetBlockByHeight(uint64) (*block.Block, error) + GetReceipts(uint64) ([]*action.Receipt, error) + } + + contractStakeViewBuilder struct { + indexer ContractStakingIndexer + blockdao BlockStore + } +) + +func NewContractStakeViewBuilder( + indexer ContractStakingIndexer, + blockdao BlockStore, +) *contractStakeViewBuilder { + return &contractStakeViewBuilder{ + indexer: indexer, + blockdao: blockdao, + } +} + +func (b *contractStakeViewBuilder) Build(ctx context.Context, height uint64) (ContractStakeView, error) { + view, err := b.indexer.StartView(ctx) + if err != nil { + return nil, err + } + indexerHeight, err := b.indexer.Height() + if err != nil { + return nil, err + } + if indexerHeight == height { + return view, nil + } else if indexerHeight > height { + return nil, errors.Errorf("indexer height %d is greater than requested height %d", indexerHeight, height) + } + if b.blockdao == nil { + return nil, errors.Errorf("blockdao is nil, cannot build view for height %d", height) + } + for h := indexerHeight + 1; h <= height; h++ { + blk, err := b.blockdao.GetBlockByHeight(h) + if err != nil { + return nil, errors.Wrapf(err, "failed to get block at height %d", h) + } + if blk.Receipts == nil { + receipts, err := b.blockdao.GetReceipts(h) + if err != nil { + return nil, errors.Wrapf(err, "failed to get receipts at height %d", h) + } + blk.Receipts = receipts + } + if err = view.BuildWithBlock(ctx, blk); err != nil { + return nil, errors.Wrapf(err, "failed to build view with block at height %d", h) + } + } + return view, nil +} diff --git a/action/protocol/staking/viewdata.go b/action/protocol/staking/viewdata.go index e1ddee51b1..7269d135a2 100644 --- a/action/protocol/staking/viewdata.go +++ b/action/protocol/staking/viewdata.go @@ -14,6 +14,7 @@ import ( "github.com/iotexproject/iotex-core/v2/action" "github.com/iotexproject/iotex-core/v2/action/protocol" + "github.com/iotexproject/iotex-core/v2/blockchain/block" ) type ( @@ -24,6 +25,7 @@ type ( Handle(ctx context.Context, receipt *action.Receipt) error Commit() BucketsByCandidate(ownerAddr address.Address) ([]*VoteBucket, error) + BuildWithBlock(ctx context.Context, blk *block.Block) error } // ViewData is the data that need to be stored in protocol's view ViewData struct { diff --git a/blockchain/blockdao/blockdao.go b/blockchain/blockdao/blockdao.go index c19c12b976..b69856064a 100644 --- a/blockchain/blockdao/blockdao.go +++ b/blockchain/blockdao/blockdao.go @@ -128,7 +128,7 @@ func NewBlockDAOWithIndexersAndCache(blkStore BlockStore, indexers []BlockIndexe // Start starts block DAO and initiates the top height if it doesn't exist func (dao *blockDAO) Start(ctx context.Context) error { - err := dao.lifecycle.OnStart(ctx) + err := dao.lifecycle.OnStartSequentially(ctx) if err != nil { return errors.Wrap(err, "failed to start child services") } diff --git a/blockchain/blockdao/blockdao_test.go b/blockchain/blockdao/blockdao_test.go index 029c3cecae..4a664ada46 100644 --- a/blockchain/blockdao/blockdao_test.go +++ b/blockchain/blockdao/blockdao_test.go @@ -83,7 +83,7 @@ func Test_blockDAO_Start(t *testing.T) { p := gomonkey.NewPatches() defer p.Reset() - p.ApplyMethodReturn(&lifecycle.Lifecycle{}, "OnStart", errors.New(t.Name())) + p.ApplyMethodReturn(&lifecycle.Lifecycle{}, "OnStartSequentially", errors.New(t.Name())) err := blockdao.Start(context.Background()) r.ErrorContains(err, t.Name()) @@ -93,7 +93,7 @@ func Test_blockDAO_Start(t *testing.T) { p := gomonkey.NewPatches() defer p.Reset() - p.ApplyMethodReturn(&lifecycle.Lifecycle{}, "OnStart", nil) + p.ApplyMethodReturn(&lifecycle.Lifecycle{}, "OnStartSequentially", nil) mockblockdao.EXPECT().Height().Return(uint64(0), errors.New(t.Name())).Times(1) err := blockdao.Start(context.Background()) @@ -106,7 +106,7 @@ func Test_blockDAO_Start(t *testing.T) { expectedHeight := uint64(1) - p.ApplyMethodReturn(&lifecycle.Lifecycle{}, "OnStart", nil) + p.ApplyMethodReturn(&lifecycle.Lifecycle{}, "OnStartSequentially", nil) mockblockdao.EXPECT().Height().Return(expectedHeight, nil).Times(1) p.ApplyPrivateMethod(&blockDAO{}, "checkIndexers", func(*blockDAO, context.Context) error { return nil }) diff --git a/blockindex/contractstaking/indexer.go b/blockindex/contractstaking/indexer.go index d012d87602..35591dc689 100644 --- a/blockindex/contractstaking/indexer.go +++ b/blockindex/contractstaking/indexer.go @@ -204,8 +204,16 @@ 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 := handleBlock(ctx, blk, &s.config, s.cache) + if err != nil { + return errors.Wrapf(err, "failed to put block %d", blk.Height()) + } + return s.commit(handler, blk.Height()) +} + +func handleBlock(ctx context.Context, blk *block.Block, cfg *Config, cache *contractStakingCache) (*contractStakingEventHandler, error) { // new event handler for this block - handler := newContractStakingEventHandler(s.cache) + handler := newContractStakingEventHandler(cache) // handle events of block for _, receipt := range blk.Receipts { @@ -213,17 +221,15 @@ func (s *Indexer) PutBlock(ctx context.Context, blk *block.Block) error { continue } for _, log := range receipt.Logs() { - if log.Address != s.config.ContractAddress { + if log.Address != cfg.ContractAddress { continue } if err := handler.HandleEvent(ctx, blk.Height(), log); err != nil { - return err + return handler, err } } } - - // commit the result - return s.commit(handler, blk.Height()) + return handler, nil } func (s *Indexer) commit(handler *contractStakingEventHandler, height uint64) error { diff --git a/blockindex/contractstaking/stakeview.go b/blockindex/contractstaking/stakeview.go index 6ba7e87431..2e3cad27f7 100644 --- a/blockindex/contractstaking/stakeview.go +++ b/blockindex/contractstaking/stakeview.go @@ -6,10 +6,12 @@ import ( "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/blockchain/block" ) type stakeView struct { @@ -95,3 +97,25 @@ func (s *stakeView) Commit() { s.dirty = nil } } + +func (s *stakeView) BuildWithBlock(ctx context.Context, blk *block.Block) error { + s.mu.Lock() + defer s.mu.Unlock() + expectHeight := s.clean.Height() + 1 + if expectHeight < s.helper.config.ContractDeployHeight { + expectHeight = s.helper.config.ContractDeployHeight + } + if blk.Height() < expectHeight { + return nil + } + if blk.Height() > expectHeight { + return errors.Errorf("invalid block height %d, expect %d", blk.Height(), expectHeight) + } + + handler, err := handleBlock(ctx, blk, &s.helper.config, s.clean) + if err != nil { + return err + } + _, delta := handler.Result() + return s.clean.Merge(delta, blk.Height()) +} diff --git a/chainservice/builder.go b/chainservice/builder.go index 4881cc9c86..a3ec0ff691 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -685,6 +685,7 @@ func (builder *Builder) registerStakingProtocol() error { if builder.cs.contractStakingIndexerV3 != nil { opts = append(opts, staking.WithContractStakingIndexerV3(builder.cs.contractStakingIndexerV3)) } + opts = append(opts, staking.WithBlockStore(builder.cs.blockdao)) stakingProtocol, err := staking.NewProtocol( staking.HelperCtx{ DepositGas: rewarding.DepositGas, diff --git a/e2etest/contract_staking_v2_test.go b/e2etest/contract_staking_v2_test.go index 29c8144e67..c4ba1e8559 100644 --- a/e2etest/contract_staking_v2_test.go +++ b/e2etest/contract_staking_v2_test.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "math" "math/big" + "strings" "testing" "time" @@ -20,8 +21,10 @@ import ( "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/action/protocol/staking" "github.com/iotexproject/iotex-core/v2/blockchain/block" + "github.com/iotexproject/iotex-core/v2/blockchain/genesis" "github.com/iotexproject/iotex-core/v2/config" "github.com/iotexproject/iotex-core/v2/pkg/unit" "github.com/iotexproject/iotex-core/v2/pkg/util/assertions" @@ -57,7 +60,6 @@ func TestContractStakingV2(t *testing.T) { cfg.DardanellesUpgrade.BlockInterval = time.Second * 8640 cfg.Plugins[config.GatewayPlugin] = nil test := newE2ETest(t, cfg) - defer test.teardown() var ( successExpect = &basicActionExpect{nil, uint64(iotextypes.ReceiptStatus_Success), ""} @@ -673,6 +675,8 @@ func TestContractStakingV2(t *testing.T) { }, }, }) + + checkStakingViewInit(test, require) } func TestContractStakingV3(t *testing.T) { @@ -692,7 +696,6 @@ func TestContractStakingV3(t *testing.T) { cfg.DardanellesUpgrade.BlockInterval = time.Second * 8640 cfg.Plugins[config.GatewayPlugin] = nil test := newE2ETest(t, cfg) - defer test.teardown() var ( successExpect = &basicActionExpect{nil, uint64(iotextypes.ReceiptStatus_Success), ""} @@ -731,6 +734,13 @@ func TestContractStakingV3(t *testing.T) { require.NoError(err) return data } + genTransferActionsWithPrice := func(n int, price *big.Int) []*actionWithTime { + acts := make([]*actionWithTime, n) + for i := 0; i < n; i++ { + acts[i] = &actionWithTime{mustNoErr(action.SignedTransfer(identityset.Address(1).String(), identityset.PrivateKey(2), test.nonceMgr.pop(identityset.Address(2).String()), unit.ConvertIotxToRau(1), nil, gasLimit, price, action.WithChainID(chainID))), time.Now()} + } + return acts + } test.run([]*testcase{ { name: "deploy_contract_v2", @@ -947,6 +957,13 @@ func TestContractStakingV3(t *testing.T) { }, }, }) + + test.run([]*testcase{ + { + preActs: genTransferActionsWithPrice(int(cfg.Genesis.WakeBlockHeight), gasPrice1559), + }, + }) + checkStakingViewInit(test, require) } func TestMigrateStake(t *testing.T) { @@ -1194,6 +1211,119 @@ func TestMigrateStake(t *testing.T) { }) } +func TestStakingViewInit(t *testing.T) { + require := require.New(t) + contractAddress := "io1dkqh5mu9djfas3xyrmzdv9frsmmytel4mp7a64" + cfg := initCfg(require) + cfg.Genesis.WakeBlockHeight = 10 // mute staking v2 & enable staking v3 + cfg.Genesis.SystemStakingContractAddress = contractAddress + cfg.Genesis.SystemStakingContractHeight = 1 + cfg.DardanellesUpgrade.BlockInterval = time.Second * 8640 + cfg.Plugins[config.GatewayPlugin] = nil + test := newE2ETest(t, cfg) + + var ( + chainID = test.cfg.Chain.ID + contractCreator = 1 + registerAmount = unit.ConvertIotxToRau(1200000) + stakeTime = time.Now() + candOwnerID = 3 + blocksPerDay = 24 * time.Hour / cfg.DardanellesUpgrade.BlockInterval + stakeDurationBlocks = big.NewInt(int64(blocksPerDay)) + ) + bytecodeV2, err := hex.DecodeString(_stakingContractByteCode) + require.NoError(err) + v1ABI, err := abi.JSON(strings.NewReader(_stakingContractABI)) + require.NoError(err) + mustCallData := func(m string, args ...any) []byte { + data, err := abiCall(v1ABI, m, args...) + require.NoError(err) + return data + } + genTransferActionsWithPrice := func(n int, price *big.Int) []*actionWithTime { + acts := make([]*actionWithTime, n) + for i := 0; i < n; i++ { + acts[i] = &actionWithTime{mustNoErr(action.SignedTransfer(identityset.Address(1).String(), identityset.PrivateKey(2), test.nonceMgr.pop(identityset.Address(2).String()), unit.ConvertIotxToRau(1), nil, gasLimit, price, action.WithChainID(chainID))), time.Now()} + } + return acts + } + test.run([]*testcase{ + { + name: "deploy_contract", + preActs: []*actionWithTime{ + {mustNoErr(action.SignedCandidateRegister(test.nonceMgr.pop(identityset.Address(candOwnerID).String()), "cand1", identityset.Address(1).String(), identityset.Address(1).String(), identityset.Address(candOwnerID).String(), registerAmount.String(), 1, true, nil, gasLimit, gasPrice, identityset.PrivateKey(candOwnerID), action.WithChainID(chainID))), time.Now()}, + }, + acts: []*actionWithTime{ + {mustNoErr(action.SignedExecution("", identityset.PrivateKey(contractCreator), test.nonceMgr.pop(identityset.Address(contractCreator).String()), big.NewInt(0), gasLimit, gasPrice, append(bytecodeV2, mustCallData("")...), action.WithChainID(chainID))), time.Now()}, + {mustNoErr(action.SignedExecution(contractAddress, identityset.PrivateKey(contractCreator), test.nonceMgr.pop(identityset.Address(contractCreator).String()), big.NewInt(0), gasLimit, gasPrice, mustCallData("addBucketType(uint256,uint256)", big.NewInt(100), stakeDurationBlocks), action.WithChainID(chainID))), stakeTime}, + }, + blockExpect: func(test *e2etest, blk *block.Block, err error) { + require.NoError(err) + require.EqualValues(3, len(blk.Receipts)) + for _, receipt := range blk.Receipts { + require.Equal(uint64(iotextypes.ReceiptStatus_Success), receipt.Status) + } + require.Equal(contractAddress, blk.Receipts[0].ContractAddress) + }, + }, + { + name: "stake", + acts: []*actionWithTime{ + {mustNoErr(action.SignedExecution(contractAddress, identityset.PrivateKey(contractCreator), test.nonceMgr.pop(identityset.Address(contractCreator).String()), big.NewInt(100), gasLimit, gasPrice, mustCallData("stake(uint256,address)", stakeDurationBlocks, common.BytesToAddress(identityset.Address(candOwnerID).Bytes())), action.WithChainID(chainID))), stakeTime}, + }, + blockExpect: func(test *e2etest, blk *block.Block, err error) { + require.NoError(err) + require.EqualValues(2, len(blk.Receipts)) + for _, receipt := range blk.Receipts { + require.Equal(uint64(iotextypes.ReceiptStatus_Success), receipt.Status) + } + }, + }, + }) + + test.run([]*testcase{ + { + preActs: genTransferActionsWithPrice(int(cfg.Genesis.WakeBlockHeight), gasPrice1559), + }, + }) + checkStakingViewInit(test, require) +} + +func checkStakingViewInit(test *e2etest, require *require.Assertions) { + tipHeight, err := test.cs.BlockDAO().Height() + require.NoError(err) + test.t.Log("tip height:", tipHeight) + tipHeader, err := test.cs.BlockDAO().HeaderByHeight(tipHeight) + require.NoError(err) + + stkProtocol, ok := test.cs.Registry().Find("staking") + require.True(ok, "staking protocol should be registered") + stk := stkProtocol.(*staking.Protocol) + ctx := context.Background() + ctx = genesis.WithGenesisContext(ctx, test.cfg.Genesis) + ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ + BlockHeight: tipHeight, + BlockTimeStamp: tipHeader.Timestamp(), + }) + ctx = protocol.WithFeatureCtx(ctx) + cands, err := stk.ActiveCandidates(ctx, test.cs.StateFactory(), 0) + require.NoError(err) + + require.NoError(test.svr.Stop(ctx)) + testutil.CleanupPath(test.cfg.Chain.ContractStakingIndexDBPath) + test.cfg.Chain.ContractStakingIndexDBPath, err = testutil.PathOfTempFile("contractindex.db") + require.NoError(err) + + test = newE2ETestWithCtx(ctx, test.t, test.cfg) + defer test.teardown() + stkProtocol, ok = test.cs.Registry().Find("staking") + require.True(ok, "staking protocol should be registered") + stk = stkProtocol.(*staking.Protocol) + newCands, err := stk.ActiveCandidates(ctx, test.cs.StateFactory(), 0) + require.NoError(err) + require.ElementsMatch(cands, newCands, "candidates should be the same after restart") +} + func methodSignToID(sign string) []byte { hash := crypto.Keccak256Hash([]byte(sign)) return hash.Bytes()[:4] diff --git a/e2etest/e2etest.go b/e2etest/e2etest.go index e65ff6113f..96402b438d 100644 --- a/e2etest/e2etest.go +++ b/e2etest/e2etest.go @@ -73,11 +73,14 @@ func (m accountNonceManager) pop(addr string) uint64 { } func newE2ETest(t *testing.T, cfg config.Config) *e2etest { + return newE2ETestWithCtx(context.Background(), t, cfg) +} + +func newE2ETestWithCtx(ctx context.Context, t *testing.T, cfg config.Config) *e2etest { require := require.New(t) // Create a new blockchain svr, err := itx.NewServer(cfg) require.NoError(err) - ctx := context.Background() require.NoError(svr.Start(ctx)) // Create a new API service client conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", cfg.API.GRPCPort), grpc.WithTransportCredentials(insecure.NewCredentials())) diff --git a/systemcontractindex/stakingindex/stakeview.go b/systemcontractindex/stakingindex/stakeview.go index dcdb51eeea..71c413769a 100644 --- a/systemcontractindex/stakingindex/stakeview.go +++ b/systemcontractindex/stakingindex/stakeview.go @@ -5,10 +5,13 @@ import ( "sync" "github.com/iotexproject/iotex-address/address" + "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/blockchain/block" + "github.com/iotexproject/iotex-core/v2/blockchain/genesis" ) type stakeView struct { @@ -63,3 +66,36 @@ func (s *stakeView) Handle(ctx context.Context, receipt *action.Receipt) error { } func (s *stakeView) Commit() {} + +func (s *stakeView) BuildWithBlock(ctx context.Context, blk *block.Block) error { + s.mu.Lock() + defer s.mu.Unlock() + if blk.Height() < s.helper.common.StartHeight() { + return nil + } + if blk.Height() != s.height+1 && blk.Height() != s.helper.StartHeight() { + return errors.Errorf("block height %d does not match stake view height %d", blk.Height(), s.height+1) + } + g, ok := genesis.ExtractGenesisContext(ctx) + if !ok { + return errors.New("failed to extract genesis context") + } + blkCtx := protocol.BlockCtx{ + BlockHeight: blk.Height(), + BlockTimeStamp: blk.Timestamp(), + GasLimit: g.BlockGasLimitByHeight(blk.Height()), + Producer: blk.PublicKey().Address(), + BaseFee: blk.BaseFee(), + ExcessBlobGas: blk.BlobGasUsed(), + } + ctx = protocol.WithBlockCtx(ctx, blkCtx) + muted := s.helper.muteHeight > 0 && blk.Height() >= s.helper.muteHeight + handler := newEventHandler(s.helper.bucketNS, s.cache, blkCtx, s.helper.timestamped, muted) + for _, receipt := range blk.Receipts { + if err := s.helper.handleReceipt(ctx, handler, receipt); err != nil { + return errors.Wrapf(err, "failed to handle receipt at height %d", blk.Height()) + } + } + s.height = blk.Height() + return nil +}