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

Adds support for batch operations in electrum RPC #98

Merged
merged 5 commits into from
Aug 26, 2024
Merged
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 rust-toolchain
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.70
1.80
1 change: 0 additions & 1 deletion src/electrum/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::convert::TryFrom;

use bitcoin::hashes::Hash;
pub use electrum_client::client::Client;
pub use electrum_client::Error as ElectrumError;
pub use electrum_client::ServerFeaturesRes;

use crate::chain::BlockHash;
Expand Down
93 changes: 68 additions & 25 deletions src/electrum/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ impl Connection {
.chain_err(|| "discovery is disabled")?;

let features = params
.get(0)
.first()
.chain_err(|| "missing features param")?
.clone();
let features = serde_json::from_value(features).chain_err(|| "invalid features")?;
Expand All @@ -203,7 +203,7 @@ impl Connection {
}

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

let raw_header_hex: String = self
Expand All @@ -226,7 +226,7 @@ impl Connection {
}

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

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

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

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

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

Ok(json!({
Expand All @@ -305,7 +305,7 @@ impl Connection {
}

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

Ok(json!(history_txids
Expand All @@ -323,7 +323,7 @@ impl Connection {
}

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

let to_json = |utxo: Utxo| {
Expand Down Expand Up @@ -351,7 +351,7 @@ impl Connection {
}

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

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

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

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

Expand Down Expand Up @@ -513,26 +513,15 @@ impl Connection {
}

fn handle_replies(&mut self, shutdown: crossbeam_channel::Receiver<()>) -> Result<()> {
let empty_params = json!([]);
loop {
crossbeam_channel::select! {
recv(self.chan.receiver()) -> msg => {
let msg = msg.chain_err(|| "channel closed")?;
trace!("RPC {:?}", msg);
match msg {
Message::Request(line) => {
let cmd: Value = from_str(&line).chain_err(|| "invalid JSON format")?;
let reply = match (
cmd.get("method"),
cmd.get("params").unwrap_or(&empty_params),
cmd.get("id"),
) {
(Some(Value::String(method)), Value::Array(params), Some(id)) => {
self.handle_command(method, params, id)?
}
_ => bail!("invalid command: {}", cmd),
};
self.send_values(&[reply])?
let result = self.handle_line(&line);
self.send_values(&[result])?
}
Message::PeriodicUpdate => {
let values = self
Expand All @@ -554,6 +543,48 @@ impl Connection {
}
}

#[inline]
fn handle_line(&mut self, line: &String) -> Value {
if let Ok(json_value) = from_str(line) {
match json_value {
Value::Array(mut arr) => {
for cmd in &mut arr {
// Replace each cmd with its response in-memory.
*cmd = self.handle_value(cmd);
}
Value::Array(arr)
}
cmd => self.handle_value(&cmd),
}
} else {
// serde_json was unable to parse
invalid_json_rpc(line)
}
}

#[inline]
fn handle_value(&mut self, value: &Value) -> Value {
match (
value.get("method"),
value.get("params").unwrap_or(&json!([])),
value.get("id"),
) {
(Some(Value::String(method)), Value::Array(params), Some(id)) => self
.handle_command(method, params, id)
.unwrap_or_else(|err| {
json!({
"error": {
"code": 1,
"message": format!("{method} RPC error: {err}")
},
"id": id,
"jsonrpc": "2.0"
})
}),
_ => invalid_json_rpc(value),
}
}

fn handle_requests(
mut reader: BufReader<ConnectionStream>,
tx: crossbeam_channel::Sender<Message>,
Expand Down Expand Up @@ -629,6 +660,18 @@ impl Connection {
}
}

#[inline]
fn invalid_json_rpc(input: impl core::fmt::Display) -> Value {
json!({
"error": {
"code": -32600,
"message": format!("invalid request: {input}")
},
"id": null,
"jsonrpc": "2.0"
})
}

fn get_history(
query: &Query,
scripthash: &[u8],
Expand Down
7 changes: 2 additions & 5 deletions src/elements/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,7 @@ pub fn index_mempool_tx_assets(
) {
let (history, issuances) = index_tx_assets(tx, network, parent_network);
for (asset_id, info) in history {
asset_history
.entry(asset_id)
.or_insert_with(Vec::new)
.push(info);
asset_history.entry(asset_id).or_default().push(info);
}
for (asset_id, issuance) in issuances {
asset_issuance.insert(asset_id, issuance);
Expand Down Expand Up @@ -386,7 +383,7 @@ pub fn lookup_asset(
Ok(if let Some(row) = row {
let reissuance_token = parse_asset_id(&row.reissuance_token);

let meta = meta.map(Clone::clone).or_else(|| match registry {
let meta = meta.cloned().or_else(|| match registry {
Some(AssetRegistryLock::RwLock(rwlock)) => {
rwlock.read().unwrap().get(asset_id).cloned()
}
Expand Down
7 changes: 2 additions & 5 deletions src/new_index/mempool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ impl Mempool {
}

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

// Index funding/spending history entries and spend edges
for (scripthash, entry) in funding.chain(spending) {
self.history
.entry(scripthash)
.or_insert_with(Vec::new)
.push(entry);
self.history.entry(scripthash).or_default().push(entry);
}
for (i, txi) in tx.input.iter().enumerate() {
self.edges.insert(txi.previous_output, (txid, i as u32));
Expand Down
2 changes: 1 addition & 1 deletion src/new_index/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1654,7 +1654,7 @@ impl TxHistoryRow {
}

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

fn prefix_height(code: u8, hash: &[u8], height: u32) -> Bytes {
Expand Down
2 changes: 1 addition & 1 deletion src/util/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ impl HeaderList {
// Use the timestamp as the mtp of the genesis block.
// Matches bitcoind's behaviour: bitcoin-cli getblock `bitcoin-cli getblockhash 0` | jq '.time == .mediantime'
if height == 0 {
self.headers.get(0).unwrap().header.time
self.headers.first().unwrap().header.time
} else if height > self.len() - 1 {
0
} else {
Expand Down
18 changes: 6 additions & 12 deletions src/util/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,18 +340,12 @@ pub(super) mod sigops {
let last_witness = witness.last();
match (witness_version, witness_program.len()) {
(0, 20) => 1,
(0, 32) => {
if let Some(n) = last_witness
.map(|sl| sl.iter().map(|v| Ok(*v)))
.map(script::Script::from_byte_iter)
// I only return Ok 2 lines up, so there is no way to error
.map(|s| count_sigops(&s.unwrap(), true))
{
n
} else {
0
}
}
(0, 32) => last_witness
.map(|sl| sl.iter().map(|v| Ok(*v)))
.map(script::Script::from_byte_iter)
// I only return Ok 2 lines up, so there is no way to error
.map(|s| count_sigops(&s.unwrap(), true))
.unwrap_or_default(),
_ => 0,
}
}
Expand Down
Loading