From 8540a7f088e7e44c931a597e4d961d2e801072bd Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sat, 10 Nov 2018 16:03:40 +0100 Subject: [PATCH] rpc, signer: implement jsonrpc2.0 notifications (responseless) --- rpc/client.go | 36 ++++++++++++++++++++++++++++++++++++ rpc/http.go | 6 ++++++ signer/core/stdioui.go | 19 ++++++++++++++----- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/rpc/client.go b/rpc/client.go index 6254c95ffd..a7fe0a181d 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -245,6 +245,28 @@ func (c *Client) Call(result interface{}, method string, args ...interface{}) er return c.CallContext(ctx, result, method, args...) } +// Notify sends a JSON-RPC notification with the given arguments, and does not wait for a response, +// effectively ignoring any responses sent +// +// https://www.jsonrpc.org/specification#request_object: +// A Notification is a Request object without an "id" member. +// A Request object that is a Notification signifies the Client's lack of interest in the +// corresponding Response object, and as such no Response object needs to be returned to the client. +// The Server MUST NOT reply to a Notification, including those that are within a batch request. +func (c *Client) Notify(method string, args ...interface{}) error { + ctx := context.Background() + msg, err := c.newNotification(method, args...) + if err != nil { + return err + } + if c.isHTTP { + err = c.sendHTTP(ctx, nil, msg) + } else { + err = c.send(ctx, nil, msg) + } + return err +} + // CallContext performs a JSON-RPC call with the given arguments. If the context is // canceled before the call has successfully returned, CallContext returns immediately. // @@ -416,9 +438,23 @@ func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMes return &jsonrpcMessage{Version: "2.0", ID: c.nextID(), Method: method, Params: params}, nil } +func (c *Client) newNotification(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) { + params, err := json.Marshal(paramsIn) + if err != nil { + return nil, err + } + return &jsonrpcMessage{Version: "2.0", Method: method, Params: params}, nil +} + // send registers op with the dispatch loop, then sends msg on the connection. // if sending fails, op is deregistered. func (c *Client) send(ctx context.Context, op *requestOp, msg interface{}) error { + if jsonrequest, ok := msg.(jsonrpcMessage); ok { + if jsonrequest.isNotification() { + // If we send a json-rpc notification, we expect no response + return c.write(ctx, msg) + } + } select { case c.requestOp <- op: log.Trace("", "msg", log.Lazy{Fn: func() string { diff --git a/rpc/http.go b/rpc/http.go index af79858e2b..f00a7e5a3c 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -136,6 +136,12 @@ func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) e } return err } + if jsonrequest, ok := msg.(jsonrpcMessage); ok { + if jsonrequest.isNotification() { + // If we send a json-rpc notification, we expect no response + return nil + } + } var respmsg jsonrpcMessage if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { return err diff --git a/signer/core/stdioui.go b/signer/core/stdioui.go index 64032386fc..4445565186 100644 --- a/signer/core/stdioui.go +++ b/signer/core/stdioui.go @@ -49,6 +49,15 @@ func (ui *StdIOUI) dispatch(serviceMethod string, args interface{}, reply interf return err } +// notify sends a request over the stdio, and does not listen for a response +func (ui *StdIOUI) notify(serviceMethod string, args interface{}) error { + err := ui.client.Notify(serviceMethod, args) + if err != nil { + log.Info("Error", "exc", err.Error()) + } + return err +} + func (ui *StdIOUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { var result SignTxResponse err := ui.dispatch("ApproveTx", request, &result) @@ -86,34 +95,34 @@ func (ui *StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResp } func (ui *StdIOUI) ShowError(message string) { - err := ui.dispatch("ShowError", &Message{message}, nil) + err := ui.notify("ShowError", &Message{message}) if err != nil { log.Info("Error calling 'ShowError'", "exc", err.Error(), "msg", message) } } func (ui *StdIOUI) ShowInfo(message string) { - err := ui.dispatch("ShowInfo", Message{message}, nil) + err := ui.notify("ShowInfo", Message{message}) if err != nil { log.Info("Error calling 'ShowInfo'", "exc", err.Error(), "msg", message) } } func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) { - err := ui.dispatch("OnApprovedTx", tx, nil) + err := ui.notify("OnApprovedTx", tx) if err != nil { log.Info("Error calling 'OnApprovedTx'", "exc", err.Error(), "tx", tx) } } func (ui *StdIOUI) OnSignerStartup(info StartupInfo) { - err := ui.dispatch("OnSignerStartup", info, nil) + err := ui.notify("OnSignerStartup", info) if err != nil { log.Info("Error calling 'OnSignerStartup'", "exc", err.Error(), "info", info) } } func (ui *StdIOUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) { var result UserInputResponse - err := ui.dispatch("OnInputRequired", info, &result) + err := ui.notify("OnInputRequired", info) if err != nil { log.Info("Error calling 'OnInputRequired'", "exc", err.Error(), "info", info) }