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

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

error_chain!{}

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

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

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

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

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

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

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

41 42 43 44 45 46 47 48 49 50 51
    fn blockchain_block_get_chunk(&self, params: &[Value]) -> Result<Value> {
        const CHUNK_SIZE: usize = 2016;
        let index = params.get(0).chain_err(|| "missing index")?;
        let index = index.as_u64().chain_err(|| "non-number index")? as usize;
        let heights: Vec<usize> = (0..CHUNK_SIZE).map(|h| index * CHUNK_SIZE + h).collect();
        let headers = self.query.get_headers(&heights);
        let result = itertools::join(headers.into_iter().map(|x| util::hexlify(&x)), "");
        Ok(json!(result))
    }

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

55
    fn blockchain_scripthash_subscribe(&self, _params: &[Value]) -> Result<Value> {
56 57
        Ok(json!("HEX_STATUS"))
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
58

59 60 61 62
    fn blockchain_scripthash_get_balance(&self, params: &[Value]) -> Result<Value> {
        let script_hash = params.get(0).chain_err(|| "missing scripthash")?;
        let script_hash = script_hash.as_str().chain_err(|| "non-string scripthash")?;
        let script_hash = Sha256dHash::from_hex(script_hash).chain_err(|| "non-hex scripthash")?;
63 64 65
        let confirmed = self.query.balance(&script_hash[..]);
        Ok(json!({ "confirmed": confirmed })) // TODO: "unconfirmed"
    }
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
66

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

71
    fn blockchain_transaction_get(&self, params: &[Value]) -> Result<Value> {
Roman Zeyde's avatar
Roman Zeyde committed
72
        // TODO: handle 'verbose' param
73 74 75 76
        let tx_hash = params.get(0).chain_err(|| "missing tx_hash")?;
        let tx_hash = tx_hash.as_str().chain_err(|| "non-string tx_hash")?;
        let tx_hash = Sha256dHash::from_hex(tx_hash).chain_err(|| "non-hex tx_hash")?;
        let tx_hex = util::hexlify(&self.query.get_tx(&tx_hash));
77
        Ok(json!(tx_hex))
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
78 79
    }

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

84
    fn handle_command(&self, method: &str, params: &[Value], id: &Number) -> Result<Value> {
85 86 87 88 89 90 91
        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(),
92
            "blockchain.block.get_chunk" => self.blockchain_block_get_chunk(&params),
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 131 132 133 134 135 136
            "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
137
        }
138
        Ok(())
Roman Zeyde's avatar
WiP  
Roman Zeyde committed
139 140 141
    }
}

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