From 6019c3234ba63f7671e949ad1baf828e80bf1d91 Mon Sep 17 00:00:00 2001 From: Davidson Souza <40968167+Davidson-Souza@users.noreply.github.com> Date: Wed, 8 Mar 2023 14:19:29 -0300 Subject: [PATCH] implement all electrum protocol (#13) * Add all missing electrum methods * Refactor to only use get_arg macro * Fix a divergence with json-rpc spec This codebase used to treat id as i32, but the spec says it can be any json-valid object. * Fix a clippy warn --- src/electrum/electrum_protocol.rs | 210 ++++++++++++++---------------- src/electrum/request.rs | 4 +- 2 files changed, 102 insertions(+), 112 deletions(-) diff --git a/src/electrum/electrum_protocol.rs b/src/electrum/electrum_protocol.rs index 017ef53..3af238d 100644 --- a/src/electrum/electrum_protocol.rs +++ b/src/electrum/electrum_protocol.rs @@ -89,58 +89,18 @@ impl ElectrumServer { peer: Arc, request: Request, ) -> Result { + println!("{}", request.method); + // Methods are in alphabetical order match request.method.as_str() { - "blockchain.estimatefee" => json_rpc_res!(request, 0.0001), - "blockchain.headers.subscribe" => { - let (height, hash) = self.chain.get_best_block()?; - let header = self.chain.get_block_header(&hash)?; - let result = json!({ - "height": height, - "hex": serialize(&header).to_hex() - }); - json_rpc_res!(request, result) - } - "server.version" => json_rpc_res!(request, ["ElectrumX 1.16.0", "1.4"]), - // TODO: Create an actual histogram - "mempool.get_fee_histogram" => json_rpc_res!(request, []), - "blockchain.scripthash.subscribe" => { - if let Some(hash) = request.params.get(0) { - let hash = serde_json::from_value::(hash.clone())?; - self.peer_addresses.insert(hash, peer); - - let history = self.address_cache.read().await.get_address_history(&hash); - - if history.is_empty() { - return json_rpc_res!(request, null); - } - let status_hash = get_status(history); - return json_rpc_res!(request, status_hash); - } - - Err(super::error::Error::InvalidParams) - } - "server.banner" => json_rpc_res!(request, "Welcome to Electrum"), - "server.donation_address" => { - json_rpc_res!(request, "bcrt1q9d4zjf92nvd3zhg6cvyckzaqumk4zre2c0k8hv") - } - "server.ping" => json_rpc_res!(request, null), - // TODO: Return peers? - "server.peers.subscribe" => json_rpc_res!(request, []), - // TODO: Ask Backend for fees - "blockchain.relayfee" => json_rpc_res!(request, 0.00001), "blockchain.block.header" => { - if let Some(height) = request.params.get(0) { - let height = height.as_u64().unwrap(); - let hash = self - .chain - .get_block_hash(height as u32) - .map_err(|_| super::error::Error::InvalidParams)?; - let header = self.chain.get_block_header(&hash)?; - let header = serialize(&header).to_hex(); - json_rpc_res!(request, header) - } else { - Err(super::error::Error::InvalidParams) - } + let height = get_arg!(request, u64, 0); + let hash = self + .chain + .get_block_hash(height as u32) + .map_err(|_| super::error::Error::InvalidParams)?; + let header = self.chain.get_block_header(&hash)?; + let header = serialize(&header).to_hex(); + json_rpc_res!(request, header) } "blockchain.block.headers" => { let start_height = get_arg!(request, u64, 0); @@ -163,28 +123,67 @@ impl ElectrumServer { "max": 2016 }) } + "blockchain.estimatefee" => json_rpc_res!(request, 0.0001), + "blockchain.headers.subscribe" => { + let (height, hash) = self.chain.get_best_block()?; + let header = self.chain.get_block_header(&hash)?; + let result = json!({ + "height": height, + "hex": serialize(&header).to_hex() + }); + json_rpc_res!(request, result) + } + // TODO: Ask Backend for fees + "blockchain.relayfee" => json_rpc_res!(request, 0.00001), + "blockchain.scripthash.get_balance" => { + let script_hash = get_arg!(request, sha256::Hash, 0); + let balance = self + .address_cache + .read() + .await + .get_address_balance(&script_hash); + let result = json!({ + "confirmed": balance, + "unconfirmed": 0 + }); + json_rpc_res!(request, result) + } "blockchain.scripthash.get_history" => { - if let Some(script_hash) = request.params.get(0) { - let script_hash = - serde_json::from_value::(script_hash.to_owned())?; - let transactions = self - .address_cache - .read() - .await - .get_address_history(&script_hash); - let mut res = vec![]; - for transaction in transactions { - let entry = TransactionHistoryEntry { - tx_hash: transaction.hash, - height: transaction.height, - }; - res.push(entry); - } - - return json_rpc_res!(request, res); + let script_hash = get_arg!(request, sha256::Hash, 0); + let transactions = self + .address_cache + .read() + .await + .get_address_history(&script_hash); + let mut res = vec![]; + for transaction in transactions { + let entry = TransactionHistoryEntry { + tx_hash: transaction.hash, + height: transaction.height, + }; + res.push(entry); } - Err(super::error::Error::InvalidParams) + json_rpc_res!(request, res) + } + "blockchain.scripthash.get_mempool" => json_rpc_res!(request, []), + "blockchain.scripthash.listunspent" => todo!(), + "blockchain.scripthash.subscribe" => { + let hash = get_arg!(request, sha256::Hash, 0); + self.peer_addresses.insert(hash, peer); + + let history = self.address_cache.read().await.get_address_history(&hash); + + if history.is_empty() { + return json_rpc_res!(request, null); + } + let status_hash = get_status(history); + json_rpc_res!(request, status_hash) + } + "blockchain.scripthash.unsubscribe" => { + let address = get_arg!(request, sha256::Hash, 0); + self.peer_addresses.remove(&address); + json_rpc_res!(request, true) } "blockchain.transaction.broadcast" => { let tx = get_arg!(request, String, 0); @@ -197,57 +196,48 @@ impl ElectrumServer { json_rpc_res!(request, id) } "blockchain.transaction.get" => { - if let Some(script_hash) = request.params.get(0) { - let tx_id = serde_json::from_value::(script_hash.to_owned())?; - let tx = self - .address_cache - .read() - .await - .get_cached_transaction(&tx_id); - if let Some(tx) = tx { - return json_rpc_res!(request, tx); - } + let tx_id = get_arg!(request, Txid, 0); + let tx = self + .address_cache + .read() + .await + .get_cached_transaction(&tx_id); + if let Some(tx) = tx { + return json_rpc_res!(request, tx); } + Err(super::error::Error::InvalidParams) } "blockchain.transaction.get_merkle" => { - if let Some(script_hash) = request.params.get(0) { - let tx_id = serde_json::from_value::(script_hash.to_owned()); - let tx_id = tx_id?; - let proof = self.address_cache.read().await.get_merkle_proof(&tx_id); - let height = self.address_cache.read().await.get_height(&tx_id); - if let Some((proof, position)) = proof { - let result = json!({ - "merkle": proof, - "block_height": height.unwrap_or(0), - "pos": position - }); - return json_rpc_res!(request, result); - } - } - Err(super::error::Error::InvalidParams) - } - "blockchain.scripthash.get_balance" => { - if let Some(script_hash) = request.params.get(0) { - let script_hash = - serde_json::from_value::(script_hash.to_owned())?; - let balance = self - .address_cache - .read() - .await - .get_address_balance(&script_hash); + let tx_id = get_arg!(request, Txid, 0); + let proof = self.address_cache.read().await.get_merkle_proof(&tx_id); + let height = self.address_cache.read().await.get_height(&tx_id); + if let Some((proof, position)) = proof { let result = json!({ - "confirmed": balance, - "unconfirmed": 0 + "merkle": proof, + "block_height": height.unwrap_or(0), + "pos": position }); return json_rpc_res!(request, result); } + Err(super::error::Error::InvalidParams) } - method => { - // TODO: Remove this when all methods are implemented - unimplemented!("Unsupported method: {method}"); + //blockchain.transaction.id_from_pos + // TODO: Create an actual histogram + "mempool.get_fee_histogram" => json_rpc_res!(request, []), + "server.add_peer" => json_rpc_res!(request, true), + "server.banner" => json_rpc_res!(request, "Welcome to Floresta's Electrum Server."), + "server.donation_address" => { + json_rpc_res!(request, "") } + //server.features + // TODO: Return peers? + "server.peers.subscribe" => json_rpc_res!(request, []), + "server.ping" => json_rpc_res!(request, null), + "server.version" => json_rpc_res!(request, ["Floresta 0.1.2", "1.4"]), + + _ => Err(super::error::Error::InvalidParams), } } @@ -318,7 +308,7 @@ impl ElectrumServer { return Ok(()); } let peer = peer.unwrap().to_owned(); - let id = req.id; + let id = req.id.to_owned(); let res = self.handle_blockchain_request(peer.clone(), req).await; if let Ok(res) = res { diff --git a/src/electrum/request.rs b/src/electrum/request.rs index 07c91e0..4c0293b 100644 --- a/src/electrum/request.rs +++ b/src/electrum/request.rs @@ -1,9 +1,9 @@ use serde::Deserialize; use serde_json::Value; -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Request { - pub id: i32, + pub id: Value, pub method: String, pub jsonrpc: String,