@@ -34,17 +34,27 @@ const MAX_ARRAY_BATCH: usize = 20;
3434#[ cfg( feature = "electrum-discovery" ) ]
3535use crate :: electrum:: { DiscoveryManager , ServerFeatures } ;
3636
37+ fn invalid_params ( msg : impl Into < String > ) -> Error {
38+ ErrorKind :: InvalidParams ( msg. into ( ) ) . into ( )
39+ }
40+
3741// TODO: Sha256dHash should be a generic hash-container (since script hash is single SHA256)
3842fn hash_from_value ( val : Option < & Value > ) -> Result < Sha256dHash > {
39- let script_hash = val. chain_err ( || "missing hash" ) ?;
40- let script_hash = script_hash. as_str ( ) . chain_err ( || "non-string hash" ) ?;
41- let script_hash = script_hash. parse ( ) . chain_err ( || "non-hex hash" ) ?;
43+ let script_hash = val. ok_or_else ( || invalid_params ( "missing hash" ) ) ?;
44+ let script_hash = script_hash
45+ . as_str ( )
46+ . ok_or_else ( || invalid_params ( "non-string hash" ) ) ?;
47+ let script_hash = script_hash
48+ . parse ( )
49+ . map_err ( |_| invalid_params ( "non-hex hash" ) ) ?;
4250 Ok ( script_hash)
4351}
4452
4553fn usize_from_value ( val : Option < & Value > , name : & str ) -> Result < usize > {
46- let val = val. chain_err ( || format ! ( "missing {}" , name) ) ?;
47- let val = val. as_u64 ( ) . chain_err ( || format ! ( "non-integer {}" , name) ) ?;
54+ let val = val. ok_or_else ( || invalid_params ( format ! ( "missing {}" , name) ) ) ?;
55+ let val = val
56+ . as_u64 ( )
57+ . ok_or_else ( || invalid_params ( format ! ( "non-integer {}" , name) ) ) ?;
4858 Ok ( val as usize )
4959}
5060
@@ -56,8 +66,10 @@ fn usize_from_value_or(val: Option<&Value>, name: &str, default: usize) -> Resul
5666}
5767
5868fn bool_from_value ( val : Option < & Value > , name : & str ) -> Result < bool > {
59- let val = val. chain_err ( || format ! ( "missing {}" , name) ) ?;
60- let val = val. as_bool ( ) . chain_err ( || format ! ( "not a bool {}" , name) ) ?;
69+ let val = val. ok_or_else ( || invalid_params ( format ! ( "missing {}" , name) ) ) ?;
70+ let val = val
71+ . as_bool ( )
72+ . ok_or_else ( || invalid_params ( format ! ( "not a bool {}" , name) ) ) ?;
6173 Ok ( val)
6274}
6375
@@ -68,6 +80,52 @@ fn bool_from_value_or(val: Option<&Value>, name: &str, default: bool) -> Result<
6880 bool_from_value ( val, name)
6981}
7082
83+ // JSON-RPC 2.0 error codes (https://www.jsonrpc.org/specification#error_object),
84+ // plus the application-level codes used by ElectrumX and romanz/electrs.
85+ #[ repr( i16 ) ]
86+ #[ derive( Clone , Copy , PartialEq , Eq ) ]
87+ enum JsonRpcV2Error {
88+ ParseError = -32700 ,
89+ InvalidRequest = -32600 ,
90+ MethodNotFound = -32601 ,
91+ InvalidParams = -32602 ,
92+ InternalError = -32603 ,
93+ BadRequest = 1 ,
94+ DaemonError = 2 ,
95+ }
96+
97+ impl JsonRpcV2Error {
98+ #[ inline]
99+ fn into_i16 ( self ) -> i16 {
100+ self as i16
101+ }
102+ }
103+
104+ fn jsonrpc_code ( e : & Error ) -> JsonRpcV2Error {
105+ match e. kind ( ) {
106+ ErrorKind :: InvalidParams ( _) => JsonRpcV2Error :: InvalidParams ,
107+ ErrorKind :: TooPopular => JsonRpcV2Error :: BadRequest ,
108+ ErrorKind :: RpcError ( ..) => JsonRpcV2Error :: DaemonError ,
109+ _ => JsonRpcV2Error :: InternalError ,
110+ }
111+ }
112+
113+ #[ inline]
114+ fn json_rpc_error (
115+ input : impl core:: fmt:: Display ,
116+ id : Option < & Value > ,
117+ code : JsonRpcV2Error ,
118+ ) -> Value {
119+ json ! ( {
120+ "jsonrpc" : "2.0" ,
121+ "id" : id. unwrap_or( & Value :: Null ) ,
122+ "error" : {
123+ "code" : code. into_i16( ) ,
124+ "message" : format!( "{}" , input) ,
125+ } ,
126+ } )
127+ }
128+
71129// TODO: implement caching and delta updates
72130#[ trace]
73131fn get_status_hash ( txs : Vec < ( Txid , Option < BlockId > ) > , query : & Query ) -> Option < FullHash > {
@@ -200,9 +258,10 @@ impl Connection {
200258
201259 let features = params
202260 . get ( 0 )
203- . chain_err ( || "missing features param" ) ?
261+ . ok_or_else ( || invalid_params ( "missing features param" ) ) ?
204262 . clone ( ) ;
205- let features = serde_json:: from_value ( features) . chain_err ( || "invalid features" ) ?;
263+ let features =
264+ serde_json:: from_value ( features) . map_err ( |_| invalid_params ( "invalid features" ) ) ?;
206265
207266 discovery. add_server_request ( self . addr . ip ( ) , features) ?;
208267 Ok ( json ! ( true ) )
@@ -288,7 +347,7 @@ impl Connection {
288347 }
289348
290349 fn blockchain_scripthash_subscribe ( & mut self , params : & [ Value ] ) -> Result < Value > {
291- let script_hash = hash_from_value ( params. get ( 0 ) ) . chain_err ( || "bad script_hash" ) ?;
350+ let script_hash = hash_from_value ( params. get ( 0 ) ) ?;
292351
293352 let history_txids = get_history ( & self . query , & script_hash[ ..] , self . txs_limit ) ?;
294353 let status_hash = get_status_hash ( history_txids, & self . query )
@@ -301,7 +360,7 @@ impl Connection {
301360 }
302361
303362 fn blockchain_scripthash_unsubscribe ( & mut self , params : & [ Value ] ) -> Result < Value > {
304- let script_hash = hash_from_value ( params. get ( 0 ) ) . chain_err ( || "bad script_hash" ) ?;
363+ let script_hash = hash_from_value ( params. get ( 0 ) ) ?;
305364
306365 match self . status_hashes . remove ( & script_hash) {
307366 None => Ok ( json ! ( false ) ) ,
@@ -314,7 +373,7 @@ impl Connection {
314373
315374 #[ cfg( not( feature = "liquid" ) ) ]
316375 fn blockchain_scripthash_get_balance ( & self , params : & [ Value ] ) -> Result < Value > {
317- let script_hash = hash_from_value ( params. get ( 0 ) ) . chain_err ( || "bad script_hash" ) ?;
376+ let script_hash = hash_from_value ( params. get ( 0 ) ) ?;
318377 let ( chain_stats, mempool_stats) = self . query . stats ( & script_hash[ ..] ) ;
319378
320379 Ok ( json ! ( {
@@ -324,7 +383,7 @@ impl Connection {
324383 }
325384
326385 fn blockchain_scripthash_get_history ( & self , params : & [ Value ] ) -> Result < Value > {
327- let script_hash = hash_from_value ( params. get ( 0 ) ) . chain_err ( || "bad script_hash" ) ?;
386+ let script_hash = hash_from_value ( params. get ( 0 ) ) ?;
328387 let history_txids = get_history ( & self . query , & script_hash[ ..] , self . txs_limit ) ?;
329388
330389 Ok ( json ! ( history_txids
@@ -342,7 +401,7 @@ impl Connection {
342401 }
343402
344403 fn blockchain_scripthash_listunspent ( & self , params : & [ Value ] ) -> Result < Value > {
345- let script_hash = hash_from_value ( params. get ( 0 ) ) . chain_err ( || "bad script_hash" ) ?;
404+ let script_hash = hash_from_value ( params. get ( 0 ) ) ?;
346405 let utxos = self . query . utxo ( & script_hash[ ..] ) ?;
347406
348407 let to_json = |utxo : Utxo | {
@@ -370,8 +429,11 @@ impl Connection {
370429 }
371430
372431 fn blockchain_transaction_broadcast ( & self , params : & [ Value ] ) -> Result < Value > {
373- let tx = params. get ( 0 ) . chain_err ( || "missing tx" ) ?;
374- let tx = tx. as_str ( ) . chain_err ( || "non-string tx" ) ?. to_string ( ) ;
432+ let tx = params. get ( 0 ) . ok_or_else ( || invalid_params ( "missing tx" ) ) ?;
433+ let tx = tx
434+ . as_str ( )
435+ . ok_or_else ( || invalid_params ( "non-string tx" ) ) ?
436+ . to_string ( ) ;
375437 let txid = self . query . broadcast_raw ( & tx) ?;
376438 if let Err ( e) = self . sender . try_send ( Message :: PeriodicUpdate ) {
377439 warn ! ( "failed to issue PeriodicUpdate after broadcast: {}" , e) ;
@@ -380,9 +442,11 @@ impl Connection {
380442 }
381443
382444 fn blockchain_transaction_get ( & self , params : & [ Value ] ) -> Result < Value > {
383- let tx_hash = Txid :: from ( hash_from_value ( params. get ( 0 ) ) . chain_err ( || "bad tx_hash" ) ?) ;
445+ let tx_hash = Txid :: from ( hash_from_value ( params. get ( 0 ) ) ?) ;
384446 let verbose = match params. get ( 1 ) {
385- Some ( value) => value. as_bool ( ) . chain_err ( || "non-bool verbose value" ) ?,
447+ Some ( value) => value
448+ . as_bool ( )
449+ . ok_or_else ( || invalid_params ( "non-bool verbose value" ) ) ?,
386450 None => false ,
387451 } ;
388452
@@ -400,15 +464,15 @@ impl Connection {
400464
401465 #[ trace]
402466 fn blockchain_transaction_get_merkle ( & self , params : & [ Value ] ) -> Result < Value > {
403- let txid = Txid :: from ( hash_from_value ( params. get ( 0 ) ) . chain_err ( || "bad tx_hash" ) ?) ;
467+ let txid = Txid :: from ( hash_from_value ( params. get ( 0 ) ) ?) ;
404468 let height = usize_from_value ( params. get ( 1 ) , "height" ) ?;
405469 let blockid = self
406470 . query
407471 . chain ( )
408472 . tx_confirming_block ( & txid)
409473 . ok_or_else ( || "tx not found or is unconfirmed" ) ?;
410474 if blockid. height != height {
411- bail ! ( "invalid confirmation height provided" ) ;
475+ return Err ( invalid_params ( "invalid confirmation height provided" ) ) ;
412476 }
413477 let ( merkle, pos) = get_tx_merkle_proof ( self . query . chain ( ) , & txid, & blockid. hash )
414478 . chain_err ( || "cannot create merkle proof" ) ?;
@@ -474,10 +538,16 @@ impl Connection {
474538 #[ cfg( feature = "electrum-discovery" ) ]
475539 "server.add_peer" => self . server_add_peer ( & params) ,
476540
477- & _ => bail ! ( "unknown method {} {:?}" , method, params) ,
541+ & _ => {
542+ warn ! ( "rpc #{} unknown method {} {:?}" , id, method, params) ;
543+ return Ok ( json_rpc_error (
544+ format ! ( "unknown method {}" , method) ,
545+ Some ( id) ,
546+ JsonRpcV2Error :: MethodNotFound ,
547+ ) ) ;
548+ }
478549 } ;
479550 timer. observe_duration ( ) ;
480- // TODO: return application errors should be sent to the client
481551 Ok ( match result {
482552 Ok ( result) => json ! ( { "jsonrpc" : "2.0" , "id" : id, "result" : result} ) ,
483553 Err ( e) => {
@@ -488,7 +558,7 @@ impl Connection {
488558 params,
489559 e. display_chain( )
490560 ) ;
491- json ! ( { "jsonrpc" : "2.0" , "id" : id , "error" : format! ( "{}" , e ) } )
561+ json_rpc_error ( & e , Some ( id ) , jsonrpc_code ( & e ) )
492562 }
493563 } )
494564 }
@@ -566,25 +636,28 @@ impl Connection {
566636 trace ! ( "RPC {:?}" , msg) ;
567637 match msg {
568638 Message :: Request ( line) => {
569- let cmd: Value = from_str ( & line) . chain_err ( || "invalid JSON format" ) ?;
570- if let Value :: Array ( arr) = cmd {
571- if arr. len ( ) > MAX_ARRAY_BATCH {
572- bail ! (
573- "Too many elements in batch requests {} max:{}" ,
574- arr. len( ) ,
575- MAX_ARRAY_BATCH
576- ) ;
639+ let reply = match from_str :: < Value > ( & line) {
640+ Ok ( Value :: Array ( arr) ) => {
641+ if arr. len ( ) > MAX_ARRAY_BATCH {
642+ bail ! (
643+ "Too many elements in batch requests {} max:{}" ,
644+ arr. len( ) ,
645+ MAX_ARRAY_BATCH
646+ ) ;
647+ }
648+ let mut result = Vec :: with_capacity ( arr. len ( ) ) ;
649+ for el in arr {
650+ result. push ( self . handle_value ( el, & empty_params) ) ;
651+ }
652+ Value :: Array ( result)
577653 }
578- let mut result = Vec :: with_capacity ( arr . len ( ) ) ;
579- for el in arr {
580- let reply = self . handle_value ( el , & empty_params ) ? ;
581- result . push ( reply )
654+ Ok ( cmd ) => self . handle_value ( cmd , & empty_params ) ,
655+ Err ( err ) => {
656+ warn ! ( "[{}] invalid JSON request: {}" , self . addr , err ) ;
657+ json_rpc_error ( "parse error" , None , JsonRpcV2Error :: ParseError )
582658 }
583- self . send_values ( & [ Value :: Array ( result) ] ) ?
584- } else {
585- let reply = self . handle_value ( cmd, & empty_params) ?;
586- self . send_values ( & [ reply] ) ?
587- }
659+ } ;
660+ self . send_values ( & [ reply] ) ?
588661 }
589662 Message :: PeriodicUpdate => {
590663 let values = self
@@ -597,41 +670,46 @@ impl Connection {
597670 }
598671 }
599672
600- fn handle_value ( & mut self , cmd : Value , empty_params : & Value ) -> Result < Value > {
673+ fn handle_value ( & mut self , cmd : Value , empty_params : & Value ) -> Value {
601674 let start_time = Instant :: now ( ) ;
602- Ok (
603- match (
604- cmd. get ( "method" ) ,
605- cmd. get ( "params" ) . unwrap_or_else ( || empty_params) ,
606- cmd. get ( "id" ) ,
607- ) {
608- ( Some ( & Value :: String ( ref method) ) , & Value :: Array ( ref params) , Some ( ref id) ) => {
609- let reply = self . handle_command ( method, params, id) ?;
610-
611- conditionally_log_rpc_event ! (
612- self ,
613- json!( {
614- "event" : "rpc_response" ,
615- "method" : method,
616- "params" : if self . rpc_logging. hide_params {
617- Value :: Null
618- } else {
619- json!( params)
620- } ,
621- "request_size" : serde_json:: to_vec( & cmd) . map( |v| v. len( ) ) . unwrap_or( 0 ) ,
622- "response_size" : reply. to_string( ) . as_bytes( ) . len( ) ,
623- "duration_micros" : start_time. elapsed( ) . as_micros( ) ,
624- "id" : id,
625- } )
626- ) ;
675+ match (
676+ cmd. get ( "method" ) ,
677+ cmd. get ( "params" ) . unwrap_or_else ( || empty_params) ,
678+ cmd. get ( "id" ) ,
679+ ) {
680+ ( Some ( & Value :: String ( ref method) ) , & Value :: Array ( ref params) , Some ( ref id) ) => {
681+ let reply = self . handle_command ( method, params, id) . unwrap_or_else ( |e| {
682+ json_rpc_error (
683+ format ! ( "{} failed: {}" , method, e) ,
684+ Some ( id) ,
685+ JsonRpcV2Error :: InternalError ,
686+ )
687+ } ) ;
688+
689+ conditionally_log_rpc_event ! (
690+ self ,
691+ json!( {
692+ "event" : "rpc_response" ,
693+ "method" : method,
694+ "params" : if self . rpc_logging. hide_params {
695+ Value :: Null
696+ } else {
697+ json!( params)
698+ } ,
699+ "request_size" : serde_json:: to_vec( & cmd) . map( |v| v. len( ) ) . unwrap_or( 0 ) ,
700+ "response_size" : reply. to_string( ) . as_bytes( ) . len( ) ,
701+ "duration_micros" : start_time. elapsed( ) . as_micros( ) ,
702+ "id" : id,
703+ } )
704+ ) ;
627705
628- reply
629- }
630- _ => {
631- bail ! ( "invalid command : {}" , cmd)
632- }
633- } ,
634- )
706+ reply
707+ }
708+ _ => {
709+ warn ! ( "[{}] invalid request : {}" , self . addr , cmd) ;
710+ json_rpc_error ( "invalid request" , cmd . get ( "id" ) , JsonRpcV2Error :: InvalidRequest )
711+ }
712+ }
635713 }
636714
637715 #[ trace]
0 commit comments