Skip to content

Commit

Permalink
feat: enable querying based on mempool status (only gRPC) (#840)
Browse files Browse the repository at this point in the history
* take checkState CMS when `x-lbm-checkstate` header is specified

* add latest height case

* add an integration test to validate logics

* add CHANGELOG
  • Loading branch information
tkxkd0159 authored Jan 3, 2023
1 parent 85695a4 commit 06751bd
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased](https://github.com/line/lbm-sdk/compare/v0.46.0...HEAD)

### Features
* (baseapp) [\#840](https://github.com/line/lbm-sdk/pull/840) allow querying the state based on `CheckState`.
* (x/foundation) [\#848](https://github.com/line/lbm-sdk/pull/848) remove `gov mint` for x/foundation proposal

### Improvements
Expand Down
21 changes: 19 additions & 2 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import (
"time"

"github.com/gogo/protobuf/proto"
abci "github.com/line/ostracon/abci/types"
ocproto "github.com/line/ostracon/proto/ostracon/types"
"google.golang.org/grpc/codes"
grpcstatus "google.golang.org/grpc/status"

abci "github.com/line/ostracon/abci/types"
ocproto "github.com/line/ostracon/proto/ostracon/types"

"github.com/line/lbm-sdk/codec"
snapshottypes "github.com/line/lbm-sdk/snapshots/types"
"github.com/line/lbm-sdk/telemetry"
Expand Down Expand Up @@ -920,3 +921,19 @@ func splitPath(requestPath string) (path []string) {

return path
}

// createQueryContext creates a new sdk.Context for a query, taking as args
// the block height and whether the query needs a proof or not.
func (app *BaseApp) createQueryContextWithCheckState() sdk.Context {

cacheMS := app.checkState.CacheMultiStore()

// branch the commit-multistore for safety
app.checkStateMtx.RLock()
ctx := sdk.NewContext(
cacheMS, app.checkState.ctx.BlockHeader(), true, app.logger,
).WithMinGasPrices(app.minGasPrices).WithBlockHeight(app.LastBlockHeight())
app.checkStateMtx.RUnlock()

return ctx
}
28 changes: 22 additions & 6 deletions baseapp/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ func (app *BaseApp) RegisterGRPCServer(server gogogrpc.Server) {
return nil, status.Error(codes.Internal, "unable to retrieve metadata")
}

var sdkCtx sdk.Context
// Get height header from the request context, if present.
var height int64

if heightHeaders := md.Get(grpctypes.GRPCBlockHeightHeader); len(heightHeaders) == 1 {
height, err = strconv.ParseInt(heightHeaders[0], 10, 64)
if err != nil {
Expand All @@ -43,13 +45,27 @@ func (app *BaseApp) RegisterGRPCServer(server gogogrpc.Server) {
if err := checkNegativeHeight(height); err != nil {
return nil, err
}
}

// Create the sdk.Context. Passing false as 2nd arg, as we can't
// actually support proofs with gRPC right now.
sdkCtx, err := app.createQueryContext(height, false)
if err != nil {
return nil, err
// Create the sdk.Context. Passing false as 2nd arg, as we can't
// actually support proofs with gRPC right now.
sdkCtx, err = app.createQueryContext(height, false)
if err != nil {
return nil, err
}
} else if csHeaders := md.Get(grpctypes.GRPCCheckStateHeader); len(csHeaders) == 1 {
isCheck := csHeaders[0]
if isCheck != "on" {
return nil, sdkerrors.Wrapf(
sdkerrors.ErrInvalidRequest,
"Baseapp.RegisterGRPCServer: invalid checkState header %q: %v", grpctypes.GRPCCheckStateHeader, err)
}

sdkCtx = app.createQueryContextWithCheckState()
} else {
sdkCtx, err = app.createQueryContext(height, false)
if err != nil {
return nil, err
}
}

// Add relevant gRPC headers
Expand Down
70 changes: 70 additions & 0 deletions server/grpc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
txtypes "github.com/line/lbm-sdk/types/tx"
"github.com/line/lbm-sdk/types/tx/signing"
authclient "github.com/line/lbm-sdk/x/auth/client"
authtypes "github.com/line/lbm-sdk/x/auth/types"
banktypes "github.com/line/lbm-sdk/x/bank/types"
stakingtypes "github.com/line/lbm-sdk/x/staking/types"
)
Expand Down Expand Up @@ -274,6 +275,75 @@ func (s IntegrationTestSuite) mkTxBuilder() client.TxBuilder {
return txBuilder
}

func (s *IntegrationTestSuite) TestGRPCCheckStateHeader() {
val0 := s.network.Validators[0]
authClient := authtypes.NewQueryClient(s.conn)
initSeq := uint64(1)
AfterCheckStateSeq := uint64(2)

header := make(metadata.MD)
res, err := authClient.Account(
context.Background(),
&authtypes.QueryAccountRequest{Address: val0.Address.String()},
grpc.Header(&header), // Also fetch grpc header
)
s.Require().NoError(err)
var accRes authtypes.AccountI
err = s.app.InterfaceRegistry().UnpackAny(res.Account, &accRes)
s.Require().NoError(err)
s.Require().Equal(
initSeq,
accRes.GetSequence(),
)
blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader)
s.Require().NotEmpty(blockHeight[0]) // Should contain the block height

// Broadcast tx to verify gRPC CheckStateHeader
txBuilder := s.mkTxBuilder()
txBytes, err := val0.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
s.Require().NoError(err)
queryClient := txtypes.NewServiceClient(s.conn)

grpcRes, _ := queryClient.BroadcastTx(
context.Background(),
&txtypes.BroadcastTxRequest{
Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
TxBytes: txBytes,
},
)
s.Require().Equal(uint32(0), grpcRes.TxResponse.Code)

// In order for the block to be mined, even a single node requires at least 1~2 seconds, so the sequence number is not yet increased if we query immediately.
// So we can validate our CheckState querying logic without `WaitForHeight`
ctxWithCheckState := metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCCheckStateHeader, "on")
res, err = authClient.Account(
ctxWithCheckState,
&authtypes.QueryAccountRequest{Address: val0.Address.String()},
)
s.Require().NoError(err)
err = s.app.InterfaceRegistry().UnpackAny(res.Account, &accRes)
s.Require().NoError(err)
s.Require().Equal(
AfterCheckStateSeq,
accRes.GetSequence(),
)

res, _ = authClient.Account(
context.Background(),
&authtypes.QueryAccountRequest{Address: val0.Address.String()},
)
_ = s.app.InterfaceRegistry().UnpackAny(res.Account, &accRes)
s.Require().Equal(initSeq, accRes.GetSequence())

// Wrong header value case. It is deliberately run last to avoid interfering with earlier sequence tests.
ctxWithCheckState = metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCCheckStateHeader, "wrong")
_, err = authClient.Account(
ctxWithCheckState,
&authtypes.QueryAccountRequest{Address: val0.Address.String()},
)
s.Require().ErrorContains(err, "invalid checkState header \"x-lbm-checkstate\"")
}

func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}
3 changes: 3 additions & 0 deletions types/grpc/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ package grpc
const (
// GRPCBlockHeightHeader is the gRPC header for block height.
GRPCBlockHeightHeader = "x-cosmos-block-height"
// GRPCCheckStateHeader is the gRPC header for mempool state. Assign "on" to this header when you want to query checkState values.
// If you use both GRPCBlockHeightHeader and GRPCCheckStateHeader, GRPCCheckStateHeader would be ignored.
GRPCCheckStateHeader = "x-lbm-checkstate"
)

0 comments on commit 06751bd

Please sign in to comment.