Skip to content

Commit

Permalink
runtime: Add support for runtime queries
Browse files Browse the repository at this point in the history
  • Loading branch information
kostko committed Feb 1, 2021
1 parent b9a4c6b commit 93e5976
Show file tree
Hide file tree
Showing 11 changed files with 410 additions and 193 deletions.
19 changes: 19 additions & 0 deletions go/runtime/client/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"math"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
"github.com/oasisprotocol/oasis-core/go/common/errors"
"github.com/oasisprotocol/oasis-core/go/common/pubsub"
Expand Down Expand Up @@ -34,6 +35,8 @@ var (
ErrNotSynced = errors.New(ModuleName, 4, "client: not finished initial sync")
// ErrCheckTxFailed is an error returned if the local transaction check fails.
ErrCheckTxFailed = errors.New(ModuleName, 5, "client: transaction check failed")
// ErrNoHostedRuntime is returned when the hosted runtime is not available locally.
ErrNoHostedRuntime = errors.New(ModuleName, 6, "client: no hosted runtime is available")
)

// RuntimeClient is the runtime client interface.
Expand Down Expand Up @@ -65,6 +68,9 @@ type RuntimeClient interface {
// GetEvents returns all events emitted in a given block.
GetEvents(ctx context.Context, request *GetEventsRequest) ([]*Event, error)

// Query makes a runtime-specific query.
Query(ctx context.Context, request *QueryRequest) (*QueryResponse, error)

// QueryTx queries the indexer for a specific runtime transaction.
QueryTx(ctx context.Context, request *QueryTxRequest) (*TxResult, error)

Expand Down Expand Up @@ -146,6 +152,19 @@ type Event struct {
TxHash hash.Hash `json:"tx_hash"`
}

// QueryRequest is a Query request.
type QueryRequest struct {
RuntimeID common.Namespace `json:"runtime_id"`
Round uint64 `json:"round"`
Method string `json:"method"`
Args cbor.RawMessage `json:"args"`
}

// QueryResponse is a response to the runtime query.
type QueryResponse struct {
Data cbor.RawMessage `json:"data"`
}

// QueryTxRequest is a QueryTx request.
type QueryTxRequest struct {
RuntimeID common.Namespace `json:"runtime_id"`
Expand Down
39 changes: 39 additions & 0 deletions go/runtime/client/api/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ var (
methodGetTxs = serviceName.NewMethod("GetTxs", GetTxsRequest{})
// methodGetEvents is the GetEvents method.
methodGetEvents = serviceName.NewMethod("GetEvents", GetEventsRequest{})
// methodQuery is the Query method.
methodQuery = serviceName.NewMethod("Query", QueryRequest{})
// methodQueryTx is the QueryTx method.
methodQueryTx = serviceName.NewMethod("QueryTx", QueryTxRequest{})
// methodQueryTxs is the QueryTxs method.
Expand Down Expand Up @@ -83,6 +85,10 @@ var (
MethodName: methodGetEvents.ShortName(),
Handler: handlerGetEvents,
},
{
MethodName: methodQuery.ShortName(),
Handler: handlerQuery,
},
{
MethodName: methodQueryTx.ShortName(),
Handler: handlerQueryTx,
Expand Down Expand Up @@ -327,6 +333,31 @@ func handlerGetEvents( // nolint: golint
return interceptor(ctx, &rq, info, handler)
}

func handlerQuery( // nolint: golint
srv interface{},
ctx context.Context,
dec func(interface{}) error,
interceptor grpc.UnaryServerInterceptor,
) (interface{}, error) {
var rq QueryRequest
if err := dec(&rq); err != nil {
return nil, err
}
if interceptor == nil {
rsp, err := srv.(RuntimeClient).Query(ctx, &rq)
return rsp, errorWrapNotFound(err)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: methodQuery.FullName(),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
rsp, err := srv.(RuntimeClient).Query(ctx, req.(*QueryRequest))
return rsp, errorWrapNotFound(err)
}
return interceptor(ctx, &rq, info, handler)
}

func handlerQueryTx( // nolint: golint
srv interface{},
ctx context.Context,
Expand Down Expand Up @@ -502,6 +533,14 @@ func (c *runtimeClient) GetEvents(ctx context.Context, request *GetEventsRequest
return rsp, nil
}

func (c *runtimeClient) Query(ctx context.Context, request *QueryRequest) (*QueryResponse, error) {
var rsp QueryResponse
if err := c.conn.Invoke(ctx, methodQuery.FullName(), request, &rsp); err != nil {
return nil, err
}
return &rsp, nil
}

func (c *runtimeClient) QueryTx(ctx context.Context, request *QueryTxRequest) (*TxResult, error) {
var rsp TxResult
if err := c.conn.Invoke(ctx, methodQueryTx.FullName(), request, &rsp); err != nil {
Expand Down
23 changes: 23 additions & 0 deletions go/runtime/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,29 @@ func (c *runtimeClient) GetBlockByHash(ctx context.Context, request *api.GetBloc
return c.GetBlock(ctx, &api.GetBlockRequest{RuntimeID: request.RuntimeID, Round: round})
}

// Implements api.RuntimeClient.
func (c *runtimeClient) Query(ctx context.Context, request *api.QueryRequest) (*api.QueryResponse, error) {
hrt, ok := c.hosts[request.RuntimeID]
if !ok {
return nil, api.ErrNoHostedRuntime
}
rt := hrt.GetHostedRuntime()
if rt == nil {
return nil, api.ErrNoHostedRuntime
}

blk, err := c.GetBlock(ctx, &api.GetBlockRequest{RuntimeID: request.RuntimeID, Round: request.Round})
if err != nil {
return nil, err
}

data, err := rt.Query(ctx, blk, request.Method, request.Args)
if err != nil {
return nil, err
}
return &api.QueryResponse{Data: data}, nil
}

// Implements api.RuntimeClient.
func (c *runtimeClient) QueryTx(ctx context.Context, request *api.QueryTxRequest) (*api.TxResult, error) {
tagIndexer, err := c.tagIndexer(request.RuntimeID)
Expand Down
15 changes: 15 additions & 0 deletions go/runtime/client/tests/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
"github.com/oasisprotocol/oasis-core/go/runtime/client/api"
)
Expand Down Expand Up @@ -178,4 +179,18 @@ func testQuery(
genBlk2, err := c.GetGenesisBlock(ctx, runtimeID)
require.NoError(t, err, "GetGenesisBlock2")
require.EqualValues(t, genBlk, genBlk2, "GetGenesisBlock should match previous GetGenesisBlock")

// Query runtime.
// Since we are using the mock runtime host the response should be a CBOR-serialized method name
// with the added " world" string.
rsp, err := c.Query(ctx, &api.QueryRequest{
RuntimeID: runtimeID,
Round: blk.Header.Round,
Method: "hello",
})
require.NoError(t, err, "Query")
var decMethod string
err = cbor.Unmarshal(rsp.Data, &decMethod)
require.NoError(t, err, "cbor.Unmarshal(<QueryResponse.Data>)")
require.EqualValues(t, "hello world", decMethod, "Query response should be correct")
}
26 changes: 26 additions & 0 deletions go/runtime/host/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"

"github.com/oasisprotocol/oasis-core/go/common/cbor"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
"github.com/oasisprotocol/oasis-core/go/roothash/api/block"
"github.com/oasisprotocol/oasis-core/go/runtime/host/protocol"
Expand All @@ -26,6 +27,9 @@ type RichRuntime interface {

// CheckTx requests the runtime to check a given transaction.
CheckTx(ctx context.Context, rb *block.Block, lb *consensus.LightBlock, tx []byte) error

// Query requests the runtime to answer a runtime-specific query.
Query(ctx context.Context, rb *block.Block, method string, args cbor.RawMessage) (cbor.RawMessage, error)
}

type richRuntime struct {
Expand Down Expand Up @@ -63,6 +67,28 @@ func (r *richRuntime) CheckTx(ctx context.Context, rb *block.Block, lb *consensu
return nil
}

// Implements RichRuntime.
func (r *richRuntime) Query(ctx context.Context, rb *block.Block, method string, args cbor.RawMessage) (cbor.RawMessage, error) {
if rb == nil {
return nil, ErrInvalidArgument
}

resp, err := r.Call(ctx, &protocol.Body{
RuntimeQueryRequest: &protocol.RuntimeQueryRequest{
Method: method,
Header: rb.Header,
Args: args,
},
})
switch {
case err != nil:
return nil, err
case resp.RuntimeQueryResponse == nil:
return nil, fmt.Errorf("%w: malformed runtime response", ErrInternal)
}
return resp.RuntimeQueryResponse.Data, nil
}

// NewRichRuntime creates a new higher-level wrapper for a given runtime. It provides additional
// convenience functions for talking with a runtime.
func NewRichRuntime(rt Runtime) RichRuntime {
Expand Down
6 changes: 6 additions & 0 deletions go/runtime/host/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
"github.com/oasisprotocol/oasis-core/go/common/errors"
"github.com/oasisprotocol/oasis-core/go/common/pubsub"
Expand Down Expand Up @@ -104,6 +105,11 @@ func (r *runtime) Call(ctx context.Context, body *protocol.Body) (*protocol.Body
return &protocol.Body{RuntimeCheckTxBatchResponse: &protocol.RuntimeCheckTxBatchResponse{
Results: results,
}}, nil
case body.RuntimeQueryRequest != nil:
rq := body.RuntimeQueryRequest
return &protocol.Body{RuntimeQueryResponse: &protocol.RuntimeQueryResponse{
Data: cbor.Marshal(rq.Method + " world"),
}}, nil
default:
return nil, fmt.Errorf("(mock) method not supported")
}
Expand Down
14 changes: 14 additions & 0 deletions go/runtime/host/protocol/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ type Body struct {
RuntimeAbortResponse *Empty `json:",omitempty"`
RuntimeKeyManagerPolicyUpdateRequest *RuntimeKeyManagerPolicyUpdateRequest `json:",omitempty"`
RuntimeKeyManagerPolicyUpdateResponse *Empty `json:",omitempty"`
RuntimeQueryRequest *RuntimeQueryRequest `json:",omitempty"`
RuntimeQueryResponse *RuntimeQueryResponse `json:",omitempty"`

// Host interface.
HostRPCCallRequest *HostRPCCallRequest `json:",omitempty"`
Expand Down Expand Up @@ -269,6 +271,18 @@ type RuntimeKeyManagerPolicyUpdateRequest struct {
SignedPolicyRaw []byte `json:"signed_policy_raw"`
}

// RuntimeQueryRequest is a runtime query request message body.
type RuntimeQueryRequest struct {
Method string `json:"method"`
Header block.Header `json:"header"`
Args cbor.RawMessage `json:"args,omitempty"`
}

// RuntimeQueryRequest is a runtime query response message body.
type RuntimeQueryResponse struct {
Data cbor.RawMessage `json:"data,omitempty"`
}

// HostRPCCallRequest is a host RPC call request message body.
type HostRPCCallRequest struct {
Endpoint string `json:"endpoint"`
Expand Down
Loading

0 comments on commit 93e5976

Please sign in to comment.