From 221d51b9fd2713024062aabc0d128fc6533bf641 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 16 May 2023 13:56:29 +0200 Subject: [PATCH 1/3] Handle error string returned in the JSON-RPC response Here we provide a workaround for servers that don't follow the JSON-RPC 2.0 specification for error objects. According to the specification, an error should be an object containing `code`, `message`, and `data` properties (see: https://www.jsonrpc.org/specification#error_object). Unfortunately, Electrs returns an error as a string (see: https://github.com/Blockstream/esplora/issues/453). We define an error unmarshaling function to handle both types of errors. --- electrum/network.go | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/electrum/network.go b/electrum/network.go index 41825f9..25eb7aa 100644 --- a/electrum/network.go +++ b/electrum/network.go @@ -112,6 +112,8 @@ func NewClientSSL(ctx context.Context, addr string, config *tls.Config) (*Client return c, nil } +// JSON-RPC 2.0 Error Object +// See: https://www.jsonrpc.org/specificationJSON#error_object type apiErr struct { Code int `json:"code"` Message string `json:"message"` @@ -121,10 +123,43 @@ func (e *apiErr) Error() string { return fmt.Sprintf("errNo: %d, errMsg: %s", e.Code, e.Message) } +// UnmarshalJSON defines a workaround for servers that respond with error +// that doesn't follow the JSON-RPC 2.0 Error Object format, i.e. electrs/esplora. +// See: https://github.com/Blockstream/esplora/issues/453 +func (e *apiErr) UnmarshalJSON(data []byte) error { + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return fmt.Errorf("failed to unmarshal error [%s]: %v", data, err) + } + + switch v := v.(type) { + case string: + e.Message = v + case map[string]interface{}: + if _, ok := v["code"]; ok { + e.Code = int(v["code"].(float64)) + } + + if _, ok := v["message"]; ok { + e.Message = fmt.Sprint(v["message"]) + } + + // if _, ok := v["data"]; ok { + // e.Data = fmt.Sprint(v["data"]) + // } + default: + return fmt.Errorf("unsupported type: %v", v) + } + + return nil +} + +// JSON-RPC 2.0 Response Object +// See: https://www.jsonrpc.org/specification#response_object type response struct { - ID uint64 `json:"id"` - Method string `json:"method"` - Error string `json:"error"` + ID uint64 `json:"id"` + Method string `json:"method"` + Error *apiErr `json:"error"` } func (s *Client) listen() { @@ -153,8 +188,8 @@ func (s *Client) listen() { log.Printf("Unmarshal received message failed: %v", err) } result.err = fmt.Errorf("Unmarshal received message failed: %v", err) - } else if msg.Error != "" { - result.err = errors.New(msg.Error) + } else if msg.Error != nil { + result.err = msg.Error } if len(msg.Method) > 0 { From 447e9df5318d3238386348422ce915b3b6e36971 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 16 May 2023 15:00:36 +0200 Subject: [PATCH 2/3] Add more context in unmarshall failure debug message It would be usefull to include value that failed unmarshalling in the debug mode log message. --- electrum/network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrum/network.go b/electrum/network.go index 25eb7aa..64d7c27 100644 --- a/electrum/network.go +++ b/electrum/network.go @@ -185,7 +185,7 @@ func (s *Client) listen() { err := json.Unmarshal(bytes, msg) if err != nil { if DebugMode { - log.Printf("Unmarshal received message failed: %v", err) + log.Printf("unmarshal received message [%s] failed: [%v]", bytes, err) } result.err = fmt.Errorf("Unmarshal received message failed: %v", err) } else if msg.Error != nil { From 94663e52d0628860155d358b151a96a912241dc5 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 16 May 2023 16:40:50 +0200 Subject: [PATCH 3/3] Remove commented code --- electrum/network.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/electrum/network.go b/electrum/network.go index 64d7c27..69aee93 100644 --- a/electrum/network.go +++ b/electrum/network.go @@ -143,10 +143,6 @@ func (e *apiErr) UnmarshalJSON(data []byte) error { if _, ok := v["message"]; ok { e.Message = fmt.Sprint(v["message"]) } - - // if _, ok := v["data"]; ok { - // e.Data = fmt.Sprint(v["data"]) - // } default: return fmt.Errorf("unsupported type: %v", v) }