Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add preFinalizeBlockHook to allow VE persistence #16898

Merged
merged 11 commits into from
Jul 14, 2023
58 changes: 28 additions & 30 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@ func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitCha
// On a new chain, we consider the init chain block height as 0, even though
// req.InitialHeight is 1 by default.
initHeader := cmtproto.Header{ChainID: req.ChainId, Time: req.Time}
app.initialHeight = req.InitialHeight

app.logger.Info("InitChain", "initialHeight", req.InitialHeight, "chainID", req.ChainId)

// Set the initial height, which will be used to determine if we are proposing
// or processing the first block or not.
app.initialHeight = req.InitialHeight
if app.initialHeight == 0 { // If initial height is 0, set it to 1
app.initialHeight = 1
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bringing this from the PR that was reverted in order to make sure that PrepareProposal/ProcessProposal get the right context on the first block.


// if req.InitialHeight is > 1, then we set the initial version on all stores
if req.InitialHeight > 1 {
Expand Down Expand Up @@ -675,53 +676,50 @@ func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) (*abci.Respons
AppHash: app.LastCommitID().Hash,
}

// Initialize the FinalizeBlock state. If this is the first block, it should
// already be initialized in InitChain. Otherwise app.finalizeBlockState will be
// nil, since it is reset on Commit.
// finalizeBlockState should be set on InitChain or ProcessProposal. If it is
// nil, it means we are replaying this block and we need to set the state here
// given that during block replay ProcessProposal is not executed by CometBFT.
if app.finalizeBlockState == nil {
app.setState(execModeFinalize, header)
} else {
// In the first block, app.finalizeBlockState.ctx will already be initialized
// by InitChain. Context is now updated with Header information.
app.finalizeBlockState.ctx = app.finalizeBlockState.ctx.
WithBlockHeader(header).
WithBlockHeight(req.Height).
WithHeaderInfo(coreheader.Info{
ChainID: app.chainID,
Height: req.Height,
Time: req.Time,
Hash: req.Hash,
AppHash: app.LastCommitID().Hash,
})
}

gasMeter := app.getBlockGasMeter(app.finalizeBlockState.ctx)

// Context is now updated with Header information.
app.finalizeBlockState.ctx = app.finalizeBlockState.ctx.
WithBlockGasMeter(gasMeter).
WithBlockHeader(header).
WithHeaderHash(req.Hash).
WithConsensusParams(app.GetConsensusParams(app.finalizeBlockState.ctx)).
WithVoteInfos(req.DecidedLastCommit.Votes).
WithExecMode(sdk.ExecModeFinalize).
WithHeaderInfo(coreheader.Info{
ChainID: app.chainID,
Height: req.Height,
Time: req.Time,
Hash: req.Hash,
AppHash: app.LastCommitID().Hash,
}).WithCometInfo(cometInfo{
Misbehavior: req.Misbehavior,
ValidatorsHash: req.NextValidatorsHash,
ProposerAddress: req.ProposerAddress,
LastCommit: req.DecidedLastCommit,
})
}).
WithConsensusParams(app.GetConsensusParams(app.finalizeBlockState.ctx)).
WithVoteInfos(req.DecidedLastCommit.Votes).
WithExecMode(sdk.ExecModeFinalize).
WithCometInfo(cometInfo{
Misbehavior: req.Misbehavior,
ValidatorsHash: req.NextValidatorsHash,
ProposerAddress: req.ProposerAddress,
LastCommit: req.DecidedLastCommit,
})

// GasMeter must be set after we get a context with updated consensus params.
gasMeter := app.getBlockGasMeter(app.finalizeBlockState.ctx)
app.finalizeBlockState.ctx = app.finalizeBlockState.ctx.WithBlockGasMeter(gasMeter)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lines above are to simplify context setting. The stuff done in the else bracket are also done outside of it, so we might as well do it once.


if app.checkState != nil {
app.checkState.ctx = app.checkState.ctx.
WithBlockGasMeter(gasMeter).
WithHeaderHash(req.Hash)
}

if app.preFinalizeBlockHook != nil {
if err := app.preFinalizeBlockHook(app.finalizeBlockState.ctx, req); err != nil {
return nil, err
}
}

beginBlock := app.beginBlock(req)
events = append(events, beginBlock.Events...)

Expand Down
34 changes: 34 additions & 0 deletions baseapp/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1903,3 +1903,37 @@ func TestABCI_HaltChain(t *testing.T) {
})
}
}

func TestBaseApp_PreFinalizeBlockHook(t *testing.T) {
db := dbm.NewMemDB()
name := t.Name()
logger := log.NewTestLogger(t)

app := baseapp.NewBaseApp(name, logger, db, nil)
_, err := app.InitChain(&abci.RequestInitChain{})
require.NoError(t, err)

wasHookCalled := false
app.SetPreFinalizeBlockHook(func(ctx sdk.Context, req *abci.RequestFinalizeBlock) error {
wasHookCalled = true
return nil
})
app.Seal()

_, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1})
require.NoError(t, err)
require.Equal(t, true, wasHookCalled)

// Now try erroring
app = baseapp.NewBaseApp(name, logger, db, nil)
_, err = app.InitChain(&abci.RequestInitChain{})
require.NoError(t, err)

app.SetPreFinalizeBlockHook(func(ctx sdk.Context, req *abci.RequestFinalizeBlock) error {
return errors.New("some error")
})
app.Seal()

_, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1})
require.Error(t, err)
}
19 changes: 10 additions & 9 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,16 @@ type BaseApp struct {
anteHandler sdk.AnteHandler // ante handler for fee and auth
postHandler sdk.PostHandler // post handler, optional, e.g. for tips

initChainer sdk.InitChainer // ABCI InitChain handler
beginBlocker sdk.BeginBlocker // (legacy ABCI) BeginBlock handler
endBlocker sdk.EndBlocker // (legacy ABCI) EndBlock handler
processProposal sdk.ProcessProposalHandler // ABCI ProcessProposal handler
prepareProposal sdk.PrepareProposalHandler // ABCI PrepareProposal
extendVote sdk.ExtendVoteHandler // ABCI ExtendVote handler
verifyVoteExt sdk.VerifyVoteExtensionHandler // ABCI VerifyVoteExtension handler
prepareCheckStater sdk.PrepareCheckStater // logic to run during commit using the checkState
precommiter sdk.Precommiter // logic to run during commit using the deliverState
initChainer sdk.InitChainer // ABCI InitChain handler
beginBlocker sdk.BeginBlocker // (legacy ABCI) BeginBlock handler
endBlocker sdk.EndBlocker // (legacy ABCI) EndBlock handler
processProposal sdk.ProcessProposalHandler // ABCI ProcessProposal handler
prepareProposal sdk.PrepareProposalHandler // ABCI PrepareProposal
extendVote sdk.ExtendVoteHandler // ABCI ExtendVote handler
verifyVoteExt sdk.VerifyVoteExtensionHandler // ABCI VerifyVoteExtension handler
prepareCheckStater sdk.PrepareCheckStater // logic to run during commit using the checkState
precommiter sdk.Precommiter // logic to run during commit using the deliverState
preFinalizeBlockHook sdk.PreFinalizeBlockHook // logic to run before FinalizeBlock

addrPeerFilter sdk.PeerFilter // filter peers by address and port
idPeerFilter sdk.PeerFilter // filter peers by node ID
Expand Down
8 changes: 8 additions & 0 deletions baseapp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,14 @@ func (app *BaseApp) SetPrecommiter(precommiter sdk.Precommiter) {
app.precommiter = precommiter
}

func (app *BaseApp) SetPreFinalizeBlockHook(hook sdk.PreFinalizeBlockHook) {
if app.sealed {
panic("SetPreFinalizeBlockHook() on sealed BaseApp")
}

app.preFinalizeBlockHook = hook
}

func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) {
if app.sealed {
panic("SetAnteHandler() on sealed BaseApp")
Expand Down
8 changes: 8 additions & 0 deletions types/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ type BeginBlocker func(Context) (BeginBlock, error)
// and allows for existing EndBlock functionality within applications.
type EndBlocker func(Context) (EndBlock, error)

// PreFinalizeBlockHook defines a function type alias for executing logic right
// before FinalizeBlock is called (but after its context has been set up). It is
// intended to allow applications to perform computation on vote extensions and
// persist their results in state.
//
// Note: returning an error will make FinalizeBlock fail.
type PreFinalizeBlockHook func(Context, *abci.RequestFinalizeBlock) error

// EndBlock defines a type which contains endblock events and validator set updates
type EndBlock struct {
ValidatorUpdates []abci.ValidatorUpdate
Expand Down