Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jrpc: response format for rpc_command hooks #14

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/plugin/pl_example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func OnHtlcAccepted(event *glightning.HtlcAcceptedEvent) (*glightning.HtlcAccept
return event.Continue(), nil
}

func OnRpcCommand(event *glightning.RpcCommandEvent) (*glightning.RpcCommandResponse, error) {
func OnRpcCommand(event *glightning.RpcCommandEvent) (*jrpc2.RpcCommandResponse, error) {
cmd := event.Cmd
id, _ := cmd.Id()
log.Printf("command %s called id %s", cmd.MethodName, id)
Expand Down
5 changes: 3 additions & 2 deletions examples/plugin/pl_rpccmd/rpccmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"

"github.com/elementsproject/glightning/glightning"
"github.com/elementsproject/glightning/jrpc2"
)

func main() {
Expand All @@ -23,7 +24,7 @@ func onInit(plugin *glightning.Plugin, options map[string]glightning.Option, con
log.Printf("successfully init'd! %s\n", config.RpcFile)
}

func OnRpcCommand(event *glightning.RpcCommandEvent) (*glightning.RpcCommandResponse, error) {
func OnRpcCommand(event *glightning.RpcCommandEvent) (*jrpc2.RpcCommandResponse, error) {
cmd := event.Cmd
id, _ := cmd.Id()
log.Printf("command %s called id %s", cmd.MethodName, id)
Expand All @@ -50,7 +51,7 @@ func OnRpcCommand(event *glightning.RpcCommandEvent) (*glightning.RpcCommandResp
return event.Continue(), nil
}

func handleNewAddrRequest(event *glightning.RpcCommandEvent, req *glightning.NewAddrRequest) (*glightning.RpcCommandResponse, error) {
func handleNewAddrRequest(event *glightning.RpcCommandEvent, req *glightning.NewAddrRequest) (*jrpc2.RpcCommandResponse, error) {
// alway set address type to bech32
req.AddressType = "bech32"

Expand Down
52 changes: 22 additions & 30 deletions glightning/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const (
var lightningMethodRegistry map[string]*jrpc2.Method

// The custommsg plugin hook is the receiving counterpart to the dev-sendcustommsg RPC method
//and allows plugins to handle messages that are not handled internally.
// and allows plugins to handle messages that are not handled internally.
type CustomMsgReceivedEvent struct {
PeerId string `json:"peer_id"`
Payload string `json:"payload"`
Expand Down Expand Up @@ -81,8 +81,9 @@ func (pc *CustomMsgReceivedEvent) Fail() *CustomMsgReceivedResponse {
}

// This hook is called whenever a peer has connected and successfully completed
// the cryptographic handshake. The parameters have the following structure if
// there is a channel with the peer:
//
// the cryptographic handshake. The parameters have the following structure if
// there is a channel with the peer:
type PeerConnectedEvent struct {
Peer PeerEvent `json:"peer"`
hook func(*PeerConnectedEvent) (*PeerConnectedResponse, error)
Expand Down Expand Up @@ -293,7 +294,7 @@ func (oc *OpenChannelEvent) ContinueWithCloseTo(address string) *OpenChannelResp

type RpcCommandEvent struct {
Cmd RpcCmd `json:"rpc_command"`
hook func(*RpcCommandEvent) (*RpcCommandResponse, error)
hook func(*RpcCommandEvent) (*jrpc2.RpcCommandResponse, error)
}

type RpcCmd struct {
Expand Down Expand Up @@ -375,44 +376,33 @@ func (r *RpcCmd) Get() (jrpc2.Method, error) {
return r.m, err
}

// the result can be any of the following. providing more than
// one's behavior is undefined. the API around this should protect you
// from that, however
type RpcCommandResponse struct {
// deprecated in v0.8.1
Continue *bool `json:"continue,omitempty"`
Result _RpcCommand_Result `json:"result,omitempty"`
ReplaceObj *jrpc2.Request `json:"replace,omitempty"`
ReturnObj json.RawMessage `json:"return,omitempty"`
}

type RpcCommand_Return interface{}
type _RpcCommand_Result string

const (
_RpcCommand_Continue _RpcCommand_Result = "continue"
)

func (rc *RpcCommandEvent) Continue() *RpcCommandResponse {
return &RpcCommandResponse{
Result: _RpcCommand_Continue,
func (rc *RpcCommandEvent) Continue() *jrpc2.RpcCommandResponse {
return &jrpc2.RpcCommandResponse{
Result: string(_RpcCommand_Continue),
}
}

// Replace the existing command with a new command. for usability reasons, we
// unilaterally reuse the id of the original command
func (rc *RpcCommandEvent) ReplaceWith(m jrpc2.Method) *RpcCommandResponse {
func (rc *RpcCommandEvent) ReplaceWith(m jrpc2.Method) *jrpc2.RpcCommandResponse {
// the marshalling call on this also includes a version field
// which shouldn't affect parsing
id, _ := rc.Cmd.Id()
req := &jrpc2.Request{id, m}

return &RpcCommandResponse{
return &jrpc2.RpcCommandResponse{
ReplaceObj: req,
}
}

func (rc *RpcCommandEvent) ReturnResult(resp RpcCommand_Return) (*RpcCommandResponse, error) {
func (rc *RpcCommandEvent) ReturnResult(resp RpcCommand_Return) (*jrpc2.RpcCommandResponse, error) {
result := &struct {
Result RpcCommand_Return `json:"result"`
}{
Expand All @@ -422,12 +412,12 @@ func (rc *RpcCommandEvent) ReturnResult(resp RpcCommand_Return) (*RpcCommandResp
if err != nil {
return nil, err
}
return &RpcCommandResponse{
return &jrpc2.RpcCommandResponse{
ReturnObj: marshaled,
}, nil
}

func (rc *RpcCommandEvent) ReturnError(errMsg string, errCode int) (*RpcCommandResponse, error) {
func (rc *RpcCommandEvent) ReturnError(errMsg string, errCode int) (*jrpc2.RpcCommandResponse, error) {
type ErrResp struct {
Message string `json:"message"`
Code int `json:"code"`
Expand All @@ -441,7 +431,7 @@ func (rc *RpcCommandEvent) ReturnError(errMsg string, errCode int) (*RpcCommandR
if err != nil {
return nil, err
}
return &RpcCommandResponse{
return &jrpc2.RpcCommandResponse{
ReturnObj: marshaled,
}, nil
}
Expand All @@ -450,10 +440,11 @@ func (rc *RpcCommandEvent) ReturnError(errMsg string, errCode int) (*RpcCommandR
// its result determines how `lightningd` should treat that HTLC.
//
// Warning: `lightningd` will replay the HTLCs for which it doesn't have a final
// verdict during startup. This means that, if the plugin response wasn't
// processed before the HTLC was forwarded, failed, or resolved, then the plugin
// may see the same HTLC again during startup. It is therefore paramount that the
// plugin is idempotent if it talks to an external system.
//
// verdict during startup. This means that, if the plugin response wasn't
// processed before the HTLC was forwarded, failed, or resolved, then the plugin
// may see the same HTLC again during startup. It is therefore paramount that the
// plugin is idempotent if it talks to an external system.
type HtlcAcceptedEvent struct {
Onion Onion `json:"onion"`
Htlc HtlcOffer `json:"htlc"`
Expand Down Expand Up @@ -1182,14 +1173,15 @@ func (p *Plugin) Log(message string, level LogLevel) {
}

// Map for registering hooks. Not the *most* elegant but
// it'll do for now.
//
// it'll do for now.
type Hooks struct {
PeerConnected func(*PeerConnectedEvent) (*PeerConnectedResponse, error)
DbWrite func(*DbWriteEvent) (*DbWriteResponse, error)
InvoicePayment func(*InvoicePaymentEvent) (*InvoicePaymentResponse, error)
OpenChannel func(*OpenChannelEvent) (*OpenChannelResponse, error)
HtlcAccepted func(*HtlcAcceptedEvent) (*HtlcAcceptedResponse, error)
RpcCommand func(*RpcCommandEvent) (*RpcCommandResponse, error)
RpcCommand func(*RpcCommandEvent) (*jrpc2.RpcCommandResponse, error)
CustomMsgReceived func(*CustomMsgReceivedEvent) (*CustomMsgReceivedResponse, error)
}

Expand Down
18 changes: 18 additions & 0 deletions glightning/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,24 @@ func TestHook_MessageFail(t *testing.T) {
runTest(t, plugin, msg+"\n\n", resp)
}

func TestHook_rpc(t *testing.T) {
initFn := getInitFunc(t, func(t *testing.T, options map[string]glightning.Option, config *glightning.Config) {
t.Error("Should not have called init when calling get manifest")
})
plugin := glightning.NewPlugin(initFn)
plugin.RegisterMethod(glightning.NewRpcMethod(NewHiMethod(plugin), "Send a greeting."))
plugin.RegisterOption(glightning.NewOption("greeting", "How you'd like to be called", "Mary"))

plugin.RegisterHooks(&glightning.Hooks{
RpcCommand: func(rce *glightning.RpcCommandEvent) (*jrpc2.RpcCommandResponse, error) {
return rce.ReturnResult("You can't call this command")
},
})
msg := `{"jsonrpc":"2.0", "id":"aloha", "method":"rpc_command"}`
resp := `{"jsonrpc":"2.0","result":{"return":{"result":"You can't call this command"}},"id":"aloha"}`
runTest(t, plugin, msg+"\n\n", resp)
}

func TestSubscription_SendPaySuccess(t *testing.T) {
var wg sync.WaitGroup
defer await(t, &wg)
Expand Down
12 changes: 12 additions & 0 deletions jrpc2/jsonrpc2.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ type Response struct {
Result Result `json:"result,omitempty"`
Error *RpcError `json:"error,omitempty"`
Id *Id `json:"id"`
RpcCommandResponse
}

// RawResponses are what the client gets back
Expand All @@ -120,6 +121,17 @@ type RawResponse struct {

type Result interface{}

// the result can be any of the following. providing more than
// one's behavior is undefined. the API around this should protect you
// from that, however
type RpcCommandResponse struct {
// deprecated in v0.8.1
Continue *bool `json:"continue,omitempty"`
Result string `json:"result,omitempty"`
ReplaceObj *Request `json:"replace,omitempty"`
ReturnObj json.RawMessage `json:"return,omitempty"`
}

type RpcError struct {
Code int `json:"code"`
Message string `json:"message"`
Expand Down
17 changes: 17 additions & 0 deletions jrpc2/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ func processMsg(s *Server, data []byte) {
request.Method.(ServerMethod).Call()
return
}
if request.Method.Name() == "rpc_command" {
s.outQueue <- ExecuteHook(request.Id, request.Method.(ServerMethod))
return
}
// ok we've successfully gotten the method call out..
s.outQueue <- Execute(request.Id, request.Method.(ServerMethod))
}
Expand All @@ -203,7 +207,20 @@ func Execute(id *Id, method ServerMethod) *Response {
} else {
resp.Result = result
}
return resp
}

func ExecuteHook(id *Id, method ServerMethod) *Response {
result, err := method.Call()
resp := &Response{
Id: id,
}
if err != nil {
// todo: data object for errors?
resp.Error = constructError(err)
} else {
resp.RpcCommandResponse = *result.(*RpcCommandResponse)
}
return resp
}

Expand Down