Skip to content

Commit f5ceb05

Browse files
romanzRoman Zeyde
authored and
Roman Zeyde
committed
Refactor parsing and handling of batched Electrum RPC
Related to #464
1 parent f4afe88 commit f5ceb05

File tree

1 file changed

+122
-72
lines changed

1 file changed

+122
-72
lines changed

src/electrum.rs

+122-72
Original file line numberDiff line numberDiff line change
@@ -396,65 +396,58 @@ impl Rpc {
396396
}
397397

398398
pub fn handle_requests(&self, client: &mut Client, lines: &[String]) -> Vec<String> {
399-
lines
399+
let parsed: Vec<Result<Calls, Value>> = lines
400400
.iter()
401-
.map(|line| self.handle_request(client, &line))
401+
.map(|line| {
402+
parse_requests(&line)
403+
.map(Calls::parse)
404+
.map_err(error_msg_no_id)
405+
})
406+
.collect();
407+
408+
parsed
409+
.into_iter()
410+
.map(|calls| self.handle_calls(client, calls).to_string())
402411
.collect()
403412
}
404413

405-
fn handle_request(&self, client: &mut Client, line: &str) -> String {
406-
let error_msg_no_id = |err| error_msg(Value::Null, RpcError::Standard(err));
407-
let response: Value = match serde_json::from_str(line) {
408-
// parse JSON from str
409-
Ok(value) => match serde_json::from_value(value) {
410-
// parse RPC from JSON
411-
Ok(requests) => match requests {
412-
Requests::Single(request) => self.call(client, request),
413-
Requests::Batch(requests) => json!(requests
414-
.into_iter()
415-
.map(|request| self.call(client, request))
416-
.collect::<Vec<Value>>()),
417-
},
418-
Err(err) => {
419-
warn!("invalid RPC request ({:?}): {}", line, err);
420-
error_msg_no_id(StandardError::InvalidRequest)
421-
}
422-
},
423-
Err(err) => {
424-
warn!("invalid JSON ({:?}): {}", line, err);
425-
error_msg_no_id(StandardError::ParseError)
426-
}
427-
};
428-
response.to_string()
414+
fn handle_calls(&self, client: &mut Client, calls: Result<Calls, Value>) -> Value {
415+
match calls {
416+
Ok(Calls::Batch(batch)) => json!(batch
417+
.into_iter()
418+
.map(|call| self.single_call(client, call))
419+
.collect::<Vec<_>>()),
420+
Ok(Calls::Single(call)) => json!(self.single_call(client, call)),
421+
Err(response) => response, // JSON parsing may fail - the response does not contain request id
422+
}
429423
}
430424

431-
fn call(&self, client: &mut Client, request: Request) -> Value {
432-
let Request { id, method, params } = request;
433-
let call = match Call::parse(&method, params) {
425+
fn single_call(&self, client: &mut Client, call: Result<Call, Value>) -> Value {
426+
let Call { id, method, params } = match call {
434427
Ok(call) => call,
435-
Err(err) => return error_msg(id, RpcError::Standard(err)),
428+
Err(response) => return response, // params parsing may fail - the response contains request id
436429
};
437430
self.rpc_duration.observe_duration(&method, || {
438-
let result = match call {
439-
Call::Banner => Ok(json!(self.banner)),
440-
Call::BlockHeader(args) => self.block_header(args),
441-
Call::BlockHeaders(args) => self.block_headers(args),
442-
Call::Donation => Ok(Value::Null),
443-
Call::EstimateFee(args) => self.estimate_fee(args),
444-
Call::Features => self.features(),
445-
Call::HeadersSubscribe => self.headers_subscribe(client),
446-
Call::MempoolFeeHistogram => self.get_fee_histogram(),
447-
Call::PeersSubscribe => Ok(json!([])),
448-
Call::Ping => Ok(Value::Null),
449-
Call::RelayFee => self.relayfee(),
450-
Call::ScriptHashGetBalance(args) => self.scripthash_get_balance(client, args),
451-
Call::ScriptHashGetHistory(args) => self.scripthash_get_history(client, args),
452-
Call::ScriptHashListUnspent(args) => self.scripthash_list_unspent(client, args),
453-
Call::ScriptHashSubscribe(args) => self.scripthash_subscribe(client, args),
454-
Call::TransactionBroadcast(args) => self.transaction_broadcast(args),
455-
Call::TransactionGet(args) => self.transaction_get(args),
456-
Call::TransactionGetMerkle(args) => self.transaction_get_merkle(args),
457-
Call::Version(args) => self.version(args),
431+
let result = match params {
432+
Params::Banner => Ok(json!(self.banner)),
433+
Params::BlockHeader(args) => self.block_header(args),
434+
Params::BlockHeaders(args) => self.block_headers(args),
435+
Params::Donation => Ok(Value::Null),
436+
Params::EstimateFee(args) => self.estimate_fee(args),
437+
Params::Features => self.features(),
438+
Params::HeadersSubscribe => self.headers_subscribe(client),
439+
Params::MempoolFeeHistogram => self.get_fee_histogram(),
440+
Params::PeersSubscribe => Ok(json!([])),
441+
Params::Ping => Ok(Value::Null),
442+
Params::RelayFee => self.relayfee(),
443+
Params::ScriptHashGetBalance(args) => self.scripthash_get_balance(client, args),
444+
Params::ScriptHashGetHistory(args) => self.scripthash_get_history(client, args),
445+
Params::ScriptHashListUnspent(args) => self.scripthash_list_unspent(client, args),
446+
Params::ScriptHashSubscribe(args) => self.scripthash_subscribe(client, args),
447+
Params::TransactionBroadcast(args) => self.transaction_broadcast(args),
448+
Params::TransactionGet(args) => self.transaction_get(args),
449+
Params::TransactionGetMerkle(args) => self.transaction_get_merkle(args),
450+
Params::Version(args) => self.version(args),
458451
};
459452
match result {
460453
Ok(value) => result_msg(id, value),
@@ -474,7 +467,7 @@ impl Rpc {
474467
}
475468

476469
#[derive(Deserialize)]
477-
enum Call {
470+
enum Params {
478471
Banner,
479472
BlockHeader((usize,)),
480473
BlockHeaders((usize, usize)),
@@ -496,28 +489,28 @@ enum Call {
496489
Version((String, Version)),
497490
}
498491

499-
impl Call {
500-
fn parse(method: &str, params: Value) -> std::result::Result<Call, StandardError> {
492+
impl Params {
493+
fn parse(method: &str, params: Value) -> std::result::Result<Params, StandardError> {
501494
Ok(match method {
502-
"blockchain.block.header" => Call::BlockHeader(convert(params)?),
503-
"blockchain.block.headers" => Call::BlockHeaders(convert(params)?),
504-
"blockchain.estimatefee" => Call::EstimateFee(convert(params)?),
505-
"blockchain.headers.subscribe" => Call::HeadersSubscribe,
506-
"blockchain.relayfee" => Call::RelayFee,
507-
"blockchain.scripthash.get_balance" => Call::ScriptHashGetBalance(convert(params)?),
508-
"blockchain.scripthash.get_history" => Call::ScriptHashGetHistory(convert(params)?),
509-
"blockchain.scripthash.listunspent" => Call::ScriptHashListUnspent(convert(params)?),
510-
"blockchain.scripthash.subscribe" => Call::ScriptHashSubscribe(convert(params)?),
511-
"blockchain.transaction.broadcast" => Call::TransactionBroadcast(convert(params)?),
512-
"blockchain.transaction.get" => Call::TransactionGet(convert(params)?),
513-
"blockchain.transaction.get_merkle" => Call::TransactionGetMerkle(convert(params)?),
514-
"mempool.get_fee_histogram" => Call::MempoolFeeHistogram,
515-
"server.banner" => Call::Banner,
516-
"server.donation_address" => Call::Donation,
517-
"server.features" => Call::Features,
518-
"server.peers.subscribe" => Call::PeersSubscribe,
519-
"server.ping" => Call::Ping,
520-
"server.version" => Call::Version(convert(params)?),
495+
"blockchain.block.header" => Params::BlockHeader(convert(params)?),
496+
"blockchain.block.headers" => Params::BlockHeaders(convert(params)?),
497+
"blockchain.estimatefee" => Params::EstimateFee(convert(params)?),
498+
"blockchain.headers.subscribe" => Params::HeadersSubscribe,
499+
"blockchain.relayfee" => Params::RelayFee,
500+
"blockchain.scripthash.get_balance" => Params::ScriptHashGetBalance(convert(params)?),
501+
"blockchain.scripthash.get_history" => Params::ScriptHashGetHistory(convert(params)?),
502+
"blockchain.scripthash.listunspent" => Params::ScriptHashListUnspent(convert(params)?),
503+
"blockchain.scripthash.subscribe" => Params::ScriptHashSubscribe(convert(params)?),
504+
"blockchain.transaction.broadcast" => Params::TransactionBroadcast(convert(params)?),
505+
"blockchain.transaction.get" => Params::TransactionGet(convert(params)?),
506+
"blockchain.transaction.get_merkle" => Params::TransactionGetMerkle(convert(params)?),
507+
"mempool.get_fee_histogram" => Params::MempoolFeeHistogram,
508+
"server.banner" => Params::Banner,
509+
"server.donation_address" => Params::Donation,
510+
"server.features" => Params::Features,
511+
"server.peers.subscribe" => Params::PeersSubscribe,
512+
"server.ping" => Params::Ping,
513+
"server.version" => Params::Version(convert(params)?),
521514
_ => {
522515
warn!("unknown method {}", method);
523516
return Err(StandardError::MethodNotFound);
@@ -526,6 +519,41 @@ impl Call {
526519
}
527520
}
528521

522+
struct Call {
523+
id: Value,
524+
method: String,
525+
params: Params,
526+
}
527+
528+
impl Call {
529+
fn parse(request: Request) -> Result<Call, Value> {
530+
match Params::parse(&request.method, request.params) {
531+
Ok(params) => Ok(Call {
532+
id: request.id,
533+
method: request.method,
534+
params,
535+
}),
536+
Err(e) => Err(error_msg(request.id, RpcError::Standard(e))),
537+
}
538+
}
539+
}
540+
541+
enum Calls {
542+
Batch(Vec<Result<Call, Value>>),
543+
Single(Result<Call, Value>),
544+
}
545+
546+
impl Calls {
547+
fn parse(requests: Requests) -> Calls {
548+
match requests {
549+
Requests::Single(request) => Calls::Single(Call::parse(request)),
550+
Requests::Batch(batch) => {
551+
Calls::Batch(batch.into_iter().map(Call::parse).collect::<Vec<_>>())
552+
}
553+
}
554+
}
555+
}
556+
529557
fn convert<T>(params: Value) -> std::result::Result<T, StandardError>
530558
where
531559
T: serde::de::DeserializeOwned,
@@ -548,3 +576,25 @@ fn result_msg(id: Value, result: Value) -> Value {
548576
fn error_msg(id: Value, error: RpcError) -> Value {
549577
json!({"jsonrpc": "2.0", "id": id, "error": error.to_value()})
550578
}
579+
580+
fn error_msg_no_id(err: StandardError) -> Value {
581+
error_msg(Value::Null, RpcError::Standard(err))
582+
}
583+
584+
fn parse_requests(line: &str) -> Result<Requests, StandardError> {
585+
match serde_json::from_str(line) {
586+
// parse JSON from str
587+
Ok(value) => match serde_json::from_value(value) {
588+
// parse RPC from JSON
589+
Ok(requests) => Ok(requests),
590+
Err(err) => {
591+
warn!("invalid RPC request ({:?}): {}", line, err);
592+
Err(StandardError::InvalidRequest)
593+
}
594+
},
595+
Err(err) => {
596+
warn!("invalid JSON ({:?}): {}", line, err);
597+
Err(StandardError::ParseError)
598+
}
599+
}
600+
}

0 commit comments

Comments
 (0)