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: WrappedPreBlocker #567

Merged
merged 10 commits into from
Jul 2, 2024
81 changes: 81 additions & 0 deletions abci/preblock/oracle/preblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"math/big"
"time"

"github.com/cosmos/cosmos-sdk/types/module"

"cosmossdk.io/log"
cometabci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -69,9 +71,88 @@ func NewOraclePreBlockHandler(
}
}

// WrappedPreBlocker is called by the base app before the block is finalized. It
// is responsible for calling the module manager's PreBlock method, aggregating oracle data from each validator and
// writing the oracle data to the store.
func (h *PreBlockHandler) WrappedPreBlocker(mm *module.Manager) sdk.PreBlocker {
return func(ctx sdk.Context, req *cometabci.RequestFinalizeBlock) (response *sdk.ResponsePreBlock, err error) {
if req == nil {
ctx.Logger().Error(
"received nil RequestFinalizeBlock in oracle preblocker",
"height", ctx.BlockHeight(),
)

return &sdk.ResponsePreBlock{}, fmt.Errorf("received nil RequestFinalizeBlock in oracle preblocker: height %d", ctx.BlockHeight())
}

// call module manager's PreBlocker first in case there is changes made on upgrades
// that can modify state and lead to serialization/deserialization issues
response, err = mm.PreBlock(ctx)
if err != nil {
return response, err
}

start := time.Now()
var prices map[slinkytypes.CurrencyPair]*big.Int
defer func() {
// only measure latency in Finalize
if ctx.ExecMode() == sdk.ExecModeFinalize {
latency := time.Since(start)
h.logger.Debug(
"finished executing the pre-block hook",
"height", ctx.BlockHeight(),
"latency (seconds)", latency.Seconds(),
)
slinkyabcitypes.RecordLatencyAndStatus(h.metrics, latency, err, servicemetrics.PreBlock)

// record prices + ticker metrics per validator (only do so if there was no error writing the prices)
if err == nil && prices != nil {
// record price metrics
h.recordPrices(prices)

// record validator report metrics
h.recordValidatorReports(ctx, req.DecidedLastCommit)
}
}
}()

// If vote extensions are not enabled, then we don't need to do anything.
if !ve.VoteExtensionsEnabled(ctx) {
h.logger.Info(
"vote extensions are not enabled",
"height", ctx.BlockHeight(),
)

return response, nil
}

h.logger.Debug(
"executing the pre-finalize block hook",
"height", req.Height,
)

// decode vote-extensions + apply prices to state
prices, err = h.pa.ApplyPricesFromVoteExtensions(ctx, req)
if err != nil {
h.logger.Error(
"failed to apply prices from vote extensions",
"height", req.Height,
"error", err,
)

return response, err
}

return response, nil
}
}

// PreBlocker is called by the base app before the block is finalized. It
// is responsible for aggregating oracle data from each validator and writing
// the oracle data to the store.
//
// Deprecated: using PreBlocker requires wrapping module Manager's PreBlock call. This method should no longer be used.
// Use WrappedPreBlocker instead to handle this functionality automatically.
func (h *PreBlockHandler) PreBlocker() sdk.PreBlocker {
return func(ctx sdk.Context, req *cometabci.RequestFinalizeBlock) (_ *sdk.ResponsePreBlock, err error) {
if req == nil {
Expand Down
71 changes: 57 additions & 14 deletions abci/preblock/oracle/preblock_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package oracle_test

import (
"context"
"math/big"
"testing"

"cosmossdk.io/core/appmodule"
"cosmossdk.io/log"
"cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
cometabci "github.com/cometbft/cometbft/abci/types"
cometproto "github.com/cometbft/cometbft/proto/tendermint/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
Expand Down Expand Up @@ -50,15 +53,16 @@ type PreBlockTestSuite struct {
veCodec compression.VoteExtensionCodec
commitCodec compression.ExtendedCommitCodec
mockMetrics *metricmock.Metrics
mm *module.Manager
}

func TestPreBlockTestSuite(t *testing.T) {
suite.Run(t, new(PreBlockTestSuite))
}

func (s *PreBlockTestSuite) SetupTest() {
s.mm = &module.Manager{Modules: map[string]interface{}{}, OrderPreBlockers: make([]string, 0)}
s.myVal = sdk.ConsAddress("myVal")

s.currencyPairs = []slinkytypes.CurrencyPair{
{
Base: "BTC",
Expand Down Expand Up @@ -138,6 +142,23 @@ func (s *PreBlockTestSuite) SetupSubTest() {
)
}

type fakeModule struct{ called int }

func (f *fakeModule) IsOnePerModuleType() {}

func (f *fakeModule) IsAppModule() {}

func (f *fakeModule) Name() string { return "fake" }

type response struct{}

func (r response) IsConsensusParamsChanged() bool { return true }

func (f *fakeModule) PreBlock(_ context.Context) (appmodule.ResponsePreBlock, error) {
f.called++
return &response{}, nil
}

func (s *PreBlockTestSuite) TestPreBlocker() {
mockValidatorStore := voteweightedmocks.NewValidatorStore(s.T())
aggregationFn := voteweighted.MedianFromContext(
Expand All @@ -157,7 +178,7 @@ func (s *PreBlockTestSuite) TestPreBlocker() {
s.commitCodec,
)

_, err := s.handler.PreBlocker()(s.ctx, nil)
_, err := s.handler.WrappedPreBlocker(s.mm)(s.ctx, nil)
s.Require().Error(err)

// require no updates
Expand All @@ -183,7 +204,7 @@ func (s *PreBlockTestSuite) TestPreBlocker() {
s.commitCodec,
)

_, err := s.handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{})
_, err := s.handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{})
s.Require().NoError(err)

// require no updates
Expand All @@ -196,6 +217,28 @@ func (s *PreBlockTestSuite) TestPreBlocker() {
}
})

s.Run("manager PreBlock is called", func() {
s.ctx = testutils.UpdateContextWithVEHeight(s.ctx, 2).WithBlockHeight(1)

s.handler = preblock.NewOraclePreBlockHandler(
log.NewTestLogger(s.T()),
aggregationFn,
&s.oracleKeeper,
servicemetrics.NewNopMetrics(),
s.cpID,
s.veCodec,
s.commitCodec,
)
// setup fake module.
fake := &fakeModule{}
s.mm.OrderPreBlockers = []string{fake.Name()}
s.mm.Modules[fake.Name()] = fake
res, err := s.handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{})
s.Require().NoError(err)
s.Require().Equal(fake.called, 1)
s.Require().True(res.IsConsensusParamsChanged()) // should return the response set above the fake module def.
})

// update ctx to enable ves
s.ctx = s.ctx.WithBlockHeight(3)

Expand Down Expand Up @@ -264,7 +307,7 @@ func (s *PreBlockTestSuite) TestPreBlocker() {

mockValidatorStore.On("TotalBondedTokens", s.ctx).Return(math.NewInt(2), nil)

_, err = s.handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{
_, err = s.handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{
Txs: [][]byte{extCommitBz},
})
s.Require().NoError(err)
Expand Down Expand Up @@ -347,7 +390,7 @@ func (s *PreBlockTestSuite) TestPreBlocker() {

mockValidatorStore.On("TotalBondedTokens", s.ctx).Return(math.NewInt(2), nil)

_, err = s.handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{
_, err = s.handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{
Txs: [][]byte{extCommitBz},
})
s.Require().NoError(err)
Expand Down Expand Up @@ -376,7 +419,7 @@ func (s *PreBlockTestSuite) TestPreblockLatency() {
)

// run preblocker
_, err := s.handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{})
_, err := s.handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{})
s.Require().NoError(err)
})

Expand All @@ -394,7 +437,7 @@ func (s *PreBlockTestSuite) TestPreblockLatency() {
s.mockMetrics.On("ObserveABCIMethodLatency", servicemetrics.PreBlock, mock.Anything).Return()
s.mockMetrics.On("AddABCIRequest", servicemetrics.PreBlock, mock.Anything)
// run preblocker
_, err := s.handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{
_, err := s.handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{
Height: 1,
})
s.Require().NoError(err)
Expand All @@ -419,7 +462,7 @@ func (s *PreBlockTestSuite) TestPreBlockStatus() {
)

// run preblocker
_, err := handler.PreBlocker()(s.ctx, nil)
_, err := handler.WrappedPreBlocker(s.mm)(s.ctx, nil)
s.Require().Error(err)
})

Expand All @@ -442,7 +485,7 @@ func (s *PreBlockTestSuite) TestPreBlockStatus() {
metrics.On("ObserveABCIMethodLatency", servicemetrics.PreBlock, mock.Anything).Return()
metrics.On("AddABCIRequest", servicemetrics.PreBlock, servicemetrics.Success{}).Return()
// run preblocker
_, err := handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{})
_, err := handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{})
s.Require().NoError(err)
})

Expand Down Expand Up @@ -470,7 +513,7 @@ func (s *PreBlockTestSuite) TestPreBlockStatus() {
s.ctx = testutils.UpdateContextWithVEHeight(s.ctx, 2)
s.ctx = s.ctx.WithBlockHeight(4)
// run preblocker
_, err := handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{
_, err := handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{
Txs: [][]byte{},
})
s.Require().Error(err, expErr)
Expand Down Expand Up @@ -520,7 +563,7 @@ func (s *PreBlockTestSuite) TestPreBlockStatus() {
mockOracleKeeper.On("GetAllCurrencyPairs", s.ctx).Return(nil)

// run preblocker
_, err := handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{
_, err := handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{
Txs: [][]byte{
[]byte("abc"),
},
Expand Down Expand Up @@ -553,7 +596,7 @@ func (s *PreBlockTestSuite) TestValidatorReports() {
// enable vote-extensions
s.ctx = testutils.UpdateContextWithVEHeight(s.ctx, 2)
req := &cometabci.RequestFinalizeBlock{}
_, err := handler.PreBlocker()(s.ctx, req)
_, err := handler.WrappedPreBlocker(s.mm)(s.ctx, req)
s.Require().NoError(err)
})

Expand Down Expand Up @@ -584,7 +627,7 @@ func (s *PreBlockTestSuite) TestValidatorReports() {
s.ctx = s.ctx.WithBlockHeight(4)
s.ctx = s.ctx.WithExecMode(sdk.ExecModeFinalize)
// run preblocker
_, err := handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{})
_, err := handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{})
s.Require().Error(err, types.MissingCommitInfoError{})
})

Expand Down Expand Up @@ -678,7 +721,7 @@ func (s *PreBlockTestSuite) TestValidatorReports() {
metrics.On("AddValidatorReportForTicker", val3.String(), mogUsd, servicemetrics.Absent)

// run preblocker
_, err = handler.PreBlocker()(s.ctx, &cometabci.RequestFinalizeBlock{
_, err = handler.WrappedPreBlocker(s.mm)(s.ctx, &cometabci.RequestFinalizeBlock{
Txs: [][]byte{extCommitBz},
DecidedLastCommit: cometabci.CommitInfo{
Votes: []cometabci.VoteInfo{
Expand Down
2 changes: 1 addition & 1 deletion tests/simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ func NewSimApp(
),
)

app.SetPreBlocker(oraclePreBlockHandler.PreBlocker())
app.SetPreBlocker(oraclePreBlockHandler.WrappedPreBlocker(app.ModuleManager))

// Create the vote extensions handler that will be used to extend and verify
// vote extensions (i.e. oracle data).
Expand Down
Loading