Skip to content

Commit

Permalink
implement all electrum protocol (mit-dci#13)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Davidson-Souza authored Mar 8, 2023
1 parent c3a3f40 commit 6019c32
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 112 deletions.
210 changes: 100 additions & 110 deletions src/electrum/electrum_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,58 +89,18 @@ impl<Blockchain: BlockchainInterface> ElectrumServer<Blockchain> {
peer: Arc<Peer>,
request: Request,
) -> Result<Value, super::error::Error> {
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::<sha256::Hash>(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);
Expand All @@ -163,28 +123,67 @@ impl<Blockchain: BlockchainInterface> ElectrumServer<Blockchain> {
"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::<sha256::Hash>(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);
Expand All @@ -197,57 +196,48 @@ impl<Blockchain: BlockchainInterface> ElectrumServer<Blockchain> {
json_rpc_res!(request, id)
}
"blockchain.transaction.get" => {
if let Some(script_hash) = request.params.get(0) {
let tx_id = serde_json::from_value::<Txid>(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::<Txid>(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::<sha256::Hash>(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),
}
}

Expand Down Expand Up @@ -318,7 +308,7 @@ impl<Blockchain: BlockchainInterface> ElectrumServer<Blockchain> {
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 {
Expand Down
4 changes: 2 additions & 2 deletions src/electrum/request.rs
Original file line number Diff line number Diff line change
@@ -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,

Expand Down

0 comments on commit 6019c32

Please sign in to comment.