rpc.rs 5.56 KB
Newer Older
1
use bitcoin::util::hash::Sha256dHash;
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
2
use serde_json::{from_str, Number, Value};
3
use std::io::{BufRead, BufReader, Write};
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
4 5
use std::net::{SocketAddr, TcpListener, TcpStream};

6
use query::Query;
7
use util;
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
8 9 10

error_chain!{}

11
struct Handler<'a> {
12
    query: &'a Query<'a>,
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
13 14
}

15 16 17 18
impl<'a> Handler<'a> {
    fn blockchain_headers_subscribe(&self) -> Result<Value> {
        Ok(json!({}))
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
19

20 21 22
    fn server_version(&self) -> Result<Value> {
        Ok(json!(["LES 0.1.0", "1.2"]))
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
23

24 25 26
    fn server_banner(&self) -> Result<Value> {
        Ok(json!("Welcome to Local Electrum Server!\n"))
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
27

28 29 30
    fn server_donation_address(&self) -> Result<Value> {
        Ok(json!("No, thanks :)\n"))
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
31

32 33 34
    fn server_peers_subscribe(&self) -> Result<Value> {
        Ok(json!([]))
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
35

36 37 38
    fn mempool_get_fee_histogram(&self) -> Result<Value> {
        Ok(json!([])) // TODO: consult with actual mempool
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
39

40 41 42
    fn blockchain_estimatefee(&self, _params: &[&str]) -> Result<Value> {
        Ok(json!(1e-5)) // TODO: consult with actual mempool
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
43

44 45 46
    fn blockchain_scripthash_subscribe(&self, _params: &[&str]) -> Result<Value> {
        Ok(json!("HEX_STATUS"))
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
47

48
    fn blockchain_scripthash_get_balance(&self, params: &[&str]) -> Result<Value> {
49
        let script_hash_hex = params.get(0).chain_err(|| "missing scripthash")?;
50 51 52 53 54
        let script_hash =
            Sha256dHash::from_hex(script_hash_hex).chain_err(|| "invalid scripthash")?;
        let confirmed = self.query.balance(&script_hash[..]);
        Ok(json!({ "confirmed": confirmed })) // TODO: "unconfirmed"
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
55

56 57 58
    fn blockchain_scripthash_get_history(&self, _params: &[&str]) -> Result<Value> {
        Ok(json!([])) // TODO: list of {tx_hash: "ABC", height: 123}
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
59

60 61 62 63 64
    fn blockchain_transaction_get(&self, params: &[&str]) -> Result<Value> {
        let tx_hash_hex = params.get(0).chain_err(|| "missing tx_hash")?;
        let tx_hash = Sha256dHash::from_hex(tx_hash_hex).chain_err(|| "invalid tx_hash")?;
        let tx_hex = util::hexlify(&self.query.get_tx(tx_hash));
        Ok(json!(tx_hex))
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
65 66
    }

67 68 69
    fn blockchain_transaction_get_merkle(&self, _params: &[&str]) -> Result<Value> {
        Ok(json!({"block_height": 123, "merkle": ["A", "B", "C"], "pos": 45}))
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
70

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
    fn handle_command(&self, method: &str, params_values: &[Value], id: &Number) -> Result<Value> {
        let mut params = Vec::<&str>::new();
        for value in params_values {
            if let Some(s) = value.as_str() {
                params.push(s);
            } else {
                bail!("invalid param: {:?}", value);
            }
        }
        let result = match method {
            "blockchain.headers.subscribe" => self.blockchain_headers_subscribe(),
            "server.version" => self.server_version(),
            "server.banner" => self.server_banner(),
            "server.donation_address" => self.server_donation_address(),
            "server.peers.subscribe" => self.server_peers_subscribe(),
            "mempool.get_fee_histogram" => self.mempool_get_fee_histogram(),
            "blockchain.estimatefee" => self.blockchain_estimatefee(&params),
            "blockchain.scripthash.subscribe" => self.blockchain_scripthash_subscribe(&params),
            "blockchain.scripthash.get_balance" => self.blockchain_scripthash_get_balance(&params),
            "blockchain.scripthash.get_history" => self.blockchain_scripthash_get_history(&params),
            "blockchain.transaction.get" => self.blockchain_transaction_get(&params),
            "blockchain.transaction.get_merkle" => self.blockchain_transaction_get_merkle(&params),
            &_ => bail!("unknown method {} {:?}", method, params),
        }?;
        let reply = json!({"jsonrpc": "2.0", "id": id, "result": result});
        Ok(reply)
    }

    pub fn run(self, mut stream: TcpStream, addr: SocketAddr) -> Result<()> {
        let mut reader = BufReader::new(stream
            .try_clone()
            .chain_err(|| "failed to clone TcpStream")?);
        let mut line = String::new();

        loop {
            line.clear();
            reader
                .read_line(&mut line)
                .chain_err(|| "failed to read a request")?;
            if line.is_empty() {
                break;
            }
            let line = line.trim_right();
            let cmd: Value = from_str(line).chain_err(|| "invalid JSON format")?;

            let reply = match (cmd.get("method"), cmd.get("params"), cmd.get("id")) {
                (
                    Some(&Value::String(ref method)),
                    Some(&Value::Array(ref params)),
                    Some(&Value::Number(ref id)),
                ) => self.handle_command(method, params, id)?,
                _ => bail!("invalid command: {}", cmd),
            };

            debug!("[{}] {} -> {}", addr, cmd, reply);
            let mut line = reply.to_string();
            line.push_str("\n");
            stream
                .write_all(line.as_bytes())
                .chain_err(|| "failed to send response")?;
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
131
        }
132
        Ok(())
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
133 134 135
    }
}

136
pub fn serve(addr: &str, query: &Query) {
137 138
    let listener = TcpListener::bind(addr).unwrap();
    info!("RPC server running on {}", addr);
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
139 140 141
    loop {
        let (stream, addr) = listener.accept().unwrap();
        info!("[{}] connected peer", addr);
142 143
        let handler = Handler { query };
        match handler.run(stream, addr) {
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
144 145 146 147 148 149 150 151 152 153
            Ok(()) => info!("[{}] disconnected peer", addr),
            Err(ref e) => {
                error!("[{}] {}", addr, e);
                for e in e.iter().skip(1) {
                    error!("caused by: {}", e);
                }
            }
        }
    }
}