diff --git a/CHANGELOG.md b/CHANGELOG.md index e8aae8a050..c474f3a02f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/baseapp/abci.go b/baseapp/abci.go index 355d970310..427819f3c6 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -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" @@ -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 +} diff --git a/baseapp/grpcserver.go b/baseapp/grpcserver.go index ba4e136f5f..2efe96fae1 100644 --- a/baseapp/grpcserver.go +++ b/baseapp/grpcserver.go @@ -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 { @@ -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 diff --git a/server/grpc/server_test.go b/server/grpc/server_test.go index c014b521f0..017fd3cded 100644 --- a/server/grpc/server_test.go +++ b/server/grpc/server_test.go @@ -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" ) @@ -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)) } diff --git a/types/grpc/headers.go b/types/grpc/headers.go index 0915307958..f947993d9a 100644 --- a/types/grpc/headers.go +++ b/types/grpc/headers.go @@ -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" )