Skip to content

Commit bffba5a

Browse files
authored
Merge pull request Blockstream#98 from mempool/junderw/batch-rpc
Adds support for batch operations in electrum RPC
2 parents ae3310c + 50a6cd7 commit bffba5a

File tree

8 files changed

+81
-51
lines changed

8 files changed

+81
-51
lines changed

rust-toolchain

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.70
1+
1.80

src/electrum/client.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::convert::TryFrom;
33

44
use bitcoin::hashes::Hash;
55
pub use electrum_client::client::Client;
6-
pub use electrum_client::Error as ElectrumError;
76
pub use electrum_client::ServerFeaturesRes;
87

98
use crate::chain::BlockHash;

src/electrum/server.rs

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ impl Connection {
189189
.chain_err(|| "discovery is disabled")?;
190190

191191
let features = params
192-
.get(0)
192+
.first()
193193
.chain_err(|| "missing features param")?
194194
.clone();
195195
let features = serde_json::from_value(features).chain_err(|| "invalid features")?;
@@ -203,7 +203,7 @@ impl Connection {
203203
}
204204

205205
fn blockchain_block_header(&self, params: &[Value]) -> Result<Value> {
206-
let height = usize_from_value(params.get(0), "height")?;
206+
let height = usize_from_value(params.first(), "height")?;
207207
let cp_height = usize_from_value_or(params.get(1), "cp_height", 0)?;
208208

209209
let raw_header_hex: String = self
@@ -226,7 +226,7 @@ impl Connection {
226226
}
227227

228228
fn blockchain_block_headers(&self, params: &[Value]) -> Result<Value> {
229-
let start_height = usize_from_value(params.get(0), "start_height")?;
229+
let start_height = usize_from_value(params.first(), "start_height")?;
230230
let count = MAX_HEADERS.min(usize_from_value(params.get(1), "count")?);
231231
let cp_height = usize_from_value_or(params.get(2), "cp_height", 0)?;
232232
let heights: Vec<usize> = (start_height..(start_height + count)).collect();
@@ -261,7 +261,7 @@ impl Connection {
261261
}
262262

263263
fn blockchain_estimatefee(&self, params: &[Value]) -> Result<Value> {
264-
let conf_target = usize_from_value(params.get(0), "blocks_count")?;
264+
let conf_target = usize_from_value(params.first(), "blocks_count")?;
265265
let fee_rate = self
266266
.query
267267
.estimate_fee(conf_target as u16)
@@ -277,7 +277,7 @@ impl Connection {
277277
}
278278

279279
fn blockchain_scripthash_subscribe(&mut self, params: &[Value]) -> Result<Value> {
280-
let script_hash = hash_from_value(params.get(0)).chain_err(|| "bad script_hash")?;
280+
let script_hash = hash_from_value(params.first()).chain_err(|| "bad script_hash")?;
281281

282282
let history_txids = get_history(&self.query, &script_hash[..], self.txs_limit)?;
283283
let status_hash = get_status_hash(history_txids, &self.query)
@@ -295,7 +295,7 @@ impl Connection {
295295

296296
#[cfg(not(feature = "liquid"))]
297297
fn blockchain_scripthash_get_balance(&self, params: &[Value]) -> Result<Value> {
298-
let script_hash = hash_from_value(params.get(0)).chain_err(|| "bad script_hash")?;
298+
let script_hash = hash_from_value(params.first()).chain_err(|| "bad script_hash")?;
299299
let (chain_stats, mempool_stats) = self.query.stats(&script_hash[..]);
300300

301301
Ok(json!({
@@ -305,7 +305,7 @@ impl Connection {
305305
}
306306

307307
fn blockchain_scripthash_get_history(&self, params: &[Value]) -> Result<Value> {
308-
let script_hash = hash_from_value(params.get(0)).chain_err(|| "bad script_hash")?;
308+
let script_hash = hash_from_value(params.first()).chain_err(|| "bad script_hash")?;
309309
let history_txids = get_history(&self.query, &script_hash[..], self.txs_limit)?;
310310

311311
Ok(json!(history_txids
@@ -323,7 +323,7 @@ impl Connection {
323323
}
324324

325325
fn blockchain_scripthash_listunspent(&self, params: &[Value]) -> Result<Value> {
326-
let script_hash = hash_from_value(params.get(0)).chain_err(|| "bad script_hash")?;
326+
let script_hash = hash_from_value(params.first()).chain_err(|| "bad script_hash")?;
327327
let utxos = self.query.utxo(&script_hash[..])?;
328328

329329
let to_json = |utxo: Utxo| {
@@ -351,7 +351,7 @@ impl Connection {
351351
}
352352

353353
fn blockchain_transaction_broadcast(&self, params: &[Value]) -> Result<Value> {
354-
let tx = params.get(0).chain_err(|| "missing tx")?;
354+
let tx = params.first().chain_err(|| "missing tx")?;
355355
let tx = tx.as_str().chain_err(|| "non-string tx")?.to_string();
356356
let txid = self.query.broadcast_raw(&tx)?;
357357
if let Err(e) = self.chan.sender().try_send(Message::PeriodicUpdate) {
@@ -361,7 +361,7 @@ impl Connection {
361361
}
362362

363363
fn blockchain_transaction_get(&self, params: &[Value]) -> Result<Value> {
364-
let tx_hash = Txid::from(hash_from_value(params.get(0)).chain_err(|| "bad tx_hash")?);
364+
let tx_hash = Txid::from(hash_from_value(params.first()).chain_err(|| "bad tx_hash")?);
365365
let verbose = match params.get(1) {
366366
Some(value) => value.as_bool().chain_err(|| "non-bool verbose value")?,
367367
None => false,
@@ -380,7 +380,7 @@ impl Connection {
380380
}
381381

382382
fn blockchain_transaction_get_merkle(&self, params: &[Value]) -> Result<Value> {
383-
let txid = Txid::from(hash_from_value(params.get(0)).chain_err(|| "bad tx_hash")?);
383+
let txid = Txid::from(hash_from_value(params.first()).chain_err(|| "bad tx_hash")?);
384384
let height = usize_from_value(params.get(1), "height")?;
385385
let blockid = self
386386
.query
@@ -399,7 +399,7 @@ impl Connection {
399399
}
400400

401401
fn blockchain_transaction_id_from_pos(&self, params: &[Value]) -> Result<Value> {
402-
let height = usize_from_value(params.get(0), "height")?;
402+
let height = usize_from_value(params.first(), "height")?;
403403
let tx_pos = usize_from_value(params.get(1), "tx_pos")?;
404404
let want_merkle = bool_from_value_or(params.get(2), "merkle", false)?;
405405

@@ -513,26 +513,15 @@ impl Connection {
513513
}
514514

515515
fn handle_replies(&mut self, shutdown: crossbeam_channel::Receiver<()>) -> Result<()> {
516-
let empty_params = json!([]);
517516
loop {
518517
crossbeam_channel::select! {
519518
recv(self.chan.receiver()) -> msg => {
520519
let msg = msg.chain_err(|| "channel closed")?;
521520
trace!("RPC {:?}", msg);
522521
match msg {
523522
Message::Request(line) => {
524-
let cmd: Value = from_str(&line).chain_err(|| "invalid JSON format")?;
525-
let reply = match (
526-
cmd.get("method"),
527-
cmd.get("params").unwrap_or(&empty_params),
528-
cmd.get("id"),
529-
) {
530-
(Some(Value::String(method)), Value::Array(params), Some(id)) => {
531-
self.handle_command(method, params, id)?
532-
}
533-
_ => bail!("invalid command: {}", cmd),
534-
};
535-
self.send_values(&[reply])?
523+
let result = self.handle_line(&line);
524+
self.send_values(&[result])?
536525
}
537526
Message::PeriodicUpdate => {
538527
let values = self
@@ -554,6 +543,48 @@ impl Connection {
554543
}
555544
}
556545

546+
#[inline]
547+
fn handle_line(&mut self, line: &String) -> Value {
548+
if let Ok(json_value) = from_str(line) {
549+
match json_value {
550+
Value::Array(mut arr) => {
551+
for cmd in &mut arr {
552+
// Replace each cmd with its response in-memory.
553+
*cmd = self.handle_value(cmd);
554+
}
555+
Value::Array(arr)
556+
}
557+
cmd => self.handle_value(&cmd),
558+
}
559+
} else {
560+
// serde_json was unable to parse
561+
invalid_json_rpc(line)
562+
}
563+
}
564+
565+
#[inline]
566+
fn handle_value(&mut self, value: &Value) -> Value {
567+
match (
568+
value.get("method"),
569+
value.get("params").unwrap_or(&json!([])),
570+
value.get("id"),
571+
) {
572+
(Some(Value::String(method)), Value::Array(params), Some(id)) => self
573+
.handle_command(method, params, id)
574+
.unwrap_or_else(|err| {
575+
json!({
576+
"error": {
577+
"code": 1,
578+
"message": format!("{method} RPC error: {err}")
579+
},
580+
"id": id,
581+
"jsonrpc": "2.0"
582+
})
583+
}),
584+
_ => invalid_json_rpc(value),
585+
}
586+
}
587+
557588
fn handle_requests(
558589
mut reader: BufReader<ConnectionStream>,
559590
tx: crossbeam_channel::Sender<Message>,
@@ -629,6 +660,18 @@ impl Connection {
629660
}
630661
}
631662

663+
#[inline]
664+
fn invalid_json_rpc(input: impl core::fmt::Display) -> Value {
665+
json!({
666+
"error": {
667+
"code": -32600,
668+
"message": format!("invalid request: {input}")
669+
},
670+
"id": null,
671+
"jsonrpc": "2.0"
672+
})
673+
}
674+
632675
fn get_history(
633676
query: &Query,
634677
scripthash: &[u8],

src/elements/asset.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,7 @@ pub fn index_mempool_tx_assets(
204204
) {
205205
let (history, issuances) = index_tx_assets(tx, network, parent_network);
206206
for (asset_id, info) in history {
207-
asset_history
208-
.entry(asset_id)
209-
.or_insert_with(Vec::new)
210-
.push(info);
207+
asset_history.entry(asset_id).or_default().push(info);
211208
}
212209
for (asset_id, issuance) in issuances {
213210
asset_issuance.insert(asset_id, issuance);
@@ -386,7 +383,7 @@ pub fn lookup_asset(
386383
Ok(if let Some(row) = row {
387384
let reissuance_token = parse_asset_id(&row.reissuance_token);
388385

389-
let meta = meta.map(Clone::clone).or_else(|| match registry {
386+
let meta = meta.cloned().or_else(|| match registry {
390387
Some(AssetRegistryLock::RwLock(rwlock)) => {
391388
rwlock.read().unwrap().get(asset_id).cloned()
392389
}

src/new_index/mempool.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ impl Mempool {
407407
}
408408

409409
pub fn add_by_txid(&mut self, daemon: &Daemon, txid: &Txid) -> Result<()> {
410-
if self.txstore.get(txid).is_none() {
410+
if !self.txstore.contains_key(txid) {
411411
if let Ok(tx) = daemon.getmempooltx(txid) {
412412
if self.add(vec![tx]) == 0 {
413413
return Err(format!(
@@ -524,10 +524,7 @@ impl Mempool {
524524

525525
// Index funding/spending history entries and spend edges
526526
for (scripthash, entry) in funding.chain(spending) {
527-
self.history
528-
.entry(scripthash)
529-
.or_insert_with(Vec::new)
530-
.push(entry);
527+
self.history.entry(scripthash).or_default().push(entry);
531528
}
532529
for (i, txi) in tx.input.iter().enumerate() {
533530
self.edges.insert(txi.previous_output, (txid, i as u32));

src/new_index/schema.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1654,7 +1654,7 @@ impl TxHistoryRow {
16541654
}
16551655

16561656
fn prefix_end(code: u8, hash: &[u8]) -> Bytes {
1657-
bincode_util::serialize_big(&(code, full_hash(hash), std::u32::MAX)).unwrap()
1657+
bincode_util::serialize_big(&(code, full_hash(hash), u32::MAX)).unwrap()
16581658
}
16591659

16601660
fn prefix_height(code: u8, hash: &[u8], height: u32) -> Bytes {

src/util/block.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ impl HeaderList {
238238
// Use the timestamp as the mtp of the genesis block.
239239
// Matches bitcoind's behaviour: bitcoin-cli getblock `bitcoin-cli getblockhash 0` | jq '.time == .mediantime'
240240
if height == 0 {
241-
self.headers.get(0).unwrap().header.time
241+
self.headers.first().unwrap().header.time
242242
} else if height > self.len() - 1 {
243243
0
244244
} else {

src/util/transaction.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -340,18 +340,12 @@ pub(super) mod sigops {
340340
let last_witness = witness.last();
341341
match (witness_version, witness_program.len()) {
342342
(0, 20) => 1,
343-
(0, 32) => {
344-
if let Some(n) = last_witness
345-
.map(|sl| sl.iter().map(|v| Ok(*v)))
346-
.map(script::Script::from_byte_iter)
347-
// I only return Ok 2 lines up, so there is no way to error
348-
.map(|s| count_sigops(&s.unwrap(), true))
349-
{
350-
n
351-
} else {
352-
0
353-
}
354-
}
343+
(0, 32) => last_witness
344+
.map(|sl| sl.iter().map(|v| Ok(*v)))
345+
.map(script::Script::from_byte_iter)
346+
// I only return Ok 2 lines up, so there is no way to error
347+
.map(|s| count_sigops(&s.unwrap(), true))
348+
.unwrap_or_default(),
355349
_ => 0,
356350
}
357351
}

0 commit comments

Comments
 (0)