Skip to content

Commit

Permalink
go/runtime/client: Also expose CheckTx
Browse files Browse the repository at this point in the history
  • Loading branch information
kostko committed Feb 3, 2021
1 parent 349909f commit be25fe2
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 17 deletions.
3 changes: 3 additions & 0 deletions .changelog/3662.feature.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
go/runtime/client: Also expose CheckTx

The `CheckTx` functionality is now also exposed via the runtime client API.
9 changes: 9 additions & 0 deletions go/runtime/client/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type RuntimeClient interface {
// SubmitTx submits a transaction to the runtime transaction scheduler.
SubmitTx(ctx context.Context, request *SubmitTxRequest) ([]byte, error)

// CheckTx asks the local runtime to check the specified transaction.
CheckTx(ctx context.Context, request *CheckTxRequest) error

// GetGenesisBlock returns the genesis block.
GetGenesisBlock(ctx context.Context, runtimeID common.Namespace) (*block.Block, error)

Expand Down Expand Up @@ -96,6 +99,12 @@ type SubmitTxRequest struct {
Data []byte `json:"data"`
}

// CheckTxRequest is a CheckTx request.
type CheckTxRequest struct {
RuntimeID common.Namespace `json:"runtime_id"`
Data []byte `json:"data"`
}

// GetBlockRequest is a GetBlock request.
type GetBlockRequest struct {
RuntimeID common.Namespace `json:"runtime_id"`
Expand Down
33 changes: 33 additions & 0 deletions go/runtime/client/api/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ var (

// methodSubmitTx is the SubmitTx method.
methodSubmitTx = serviceName.NewMethod("SubmitTx", SubmitTxRequest{})
// methodCheckTx is the CheckTx method.
methodCheckTx = serviceName.NewMethod("CheckTx", CheckTxRequest{})
// methodGetGenesisBlock is the GetGenesisBlock method.
methodGetGenesisBlock = serviceName.NewMethod("GetGenesisBlock", common.Namespace{})
// methodGetBlock is the GetBlock method.
Expand Down Expand Up @@ -57,6 +59,10 @@ var (
MethodName: methodSubmitTx.ShortName(),
Handler: handlerSubmitTx,
},
{
MethodName: methodCheckTx.ShortName(),
Handler: handlerCheckTx,
},
{
MethodName: methodGetGenesisBlock.ShortName(),
Handler: handlerGetGenesisBlock,
Expand Down Expand Up @@ -135,6 +141,29 @@ func handlerSubmitTx( // nolint: golint
return interceptor(ctx, &rq, info, handler)
}

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

// wrappedErrNotFound is a wrapped ErrNotFound error so that it corresponds
// to the gRPC NotFound error code. It is required because Rust's gRPC bindings
// do not support fetching error details.
Expand Down Expand Up @@ -477,6 +506,10 @@ func (c *runtimeClient) SubmitTx(ctx context.Context, request *SubmitTxRequest)
return rsp, nil
}

func (c *runtimeClient) CheckTx(ctx context.Context, request *CheckTxRequest) error {
return c.conn.Invoke(ctx, methodCheckTx.FullName(), request, nil)
}

func (c *runtimeClient) GetGenesisBlock(ctx context.Context, runtimeID common.Namespace) (*block.Block, error) {
var rsp block.Block
if err := c.conn.Invoke(ctx, methodGetGenesisBlock.FullName(), runtimeID, &rsp); err != nil {
Expand Down
54 changes: 37 additions & 17 deletions go/runtime/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,24 +98,12 @@ func (c *runtimeClient) SubmitTx(ctx context.Context, request *api.SubmitTxReque

// Perform a local transaction check when a hosted runtime is available.
if hrt, ok := c.hosts[request.RuntimeID]; ok && hrt.GetHostedRuntime() != nil {
// Get current blocks.
rs, err := c.common.consensus.RootHash().GetRuntimeState(ctx, request.RuntimeID, consensus.HeightLatest)
if err != nil {
return nil, fmt.Errorf("client: failed to get runtime %s state: %w", request.RuntimeID, err)
}
lb, err := c.common.consensus.GetLightBlock(ctx, rs.CurrentBlockHeight)
err := c.CheckTx(ctx, &api.CheckTxRequest{
RuntimeID: request.RuntimeID,
Data: request.Data,
})
if err != nil {
return nil, fmt.Errorf("client: failed to get light block at height %d: %w", rs.CurrentBlockHeight, err)
}

// Perform transaction checks.
err = hrt.GetHostedRuntime().CheckTx(ctx, rs.CurrentBlock, lb, request.Data)
switch {
case err == nil:
case errors.Is(err, host.ErrCheckTxFailed):
return nil, fmt.Errorf("%w: %s", api.ErrCheckTxFailed, err)
default:
return nil, fmt.Errorf("client: local transaction check failed: %w", err)
return nil, err
}
}

Expand Down Expand Up @@ -193,6 +181,38 @@ func (c *runtimeClient) SubmitTx(ctx context.Context, request *api.SubmitTxReque
}
}

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

// Get current blocks.
rs, err := c.common.consensus.RootHash().GetRuntimeState(ctx, request.RuntimeID, consensus.HeightLatest)
if err != nil {
return fmt.Errorf("client: failed to get runtime %s state: %w", request.RuntimeID, err)
}
lb, err := c.common.consensus.GetLightBlock(ctx, rs.CurrentBlockHeight)
if err != nil {
return fmt.Errorf("client: failed to get light block at height %d: %w", rs.CurrentBlockHeight, err)
}

err = rt.CheckTx(ctx, rs.CurrentBlock, lb, request.Data)
switch {
case err == nil:
return nil
case errors.Is(err, host.ErrCheckTxFailed):
return fmt.Errorf("%w: %s", api.ErrCheckTxFailed, err)
default:
return fmt.Errorf("client: local transaction check failed: %w", err)
}
}

// Implements api.RuntimeClient.
func (c *runtimeClient) WatchBlocks(ctx context.Context, runtimeID common.Namespace) (<-chan *roothash.AnnotatedBlock, pubsub.ClosableSubscription, error) {
return c.common.consensus.RootHash().WatchBlocks(runtimeID)
Expand Down
7 changes: 7 additions & 0 deletions go/runtime/client/tests/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,11 @@ func testQuery(
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")

// Execute CheckTx using the mock runtime host.
err := c.CheckTx(ctx, &api.CheckTxRequest{
RuntimeID: runtimeID,
Data: []byte("test checktx request"),
})
require.NoError(t, err, "CheckTx")
}

0 comments on commit be25fe2

Please sign in to comment.