// Copyright 2015-2019 Parity Technologies (UK) Ltd. // This file is part of Parity Ethereum. // Parity Ethereum is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity Ethereum is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . use cid::{Codec, ToCid}; use error::{Error, Result}; use multihash; use rlp; use IpfsHandler; use bytes::Bytes; use ethcore::client::{BlockId, TransactionId}; use ethereum_types::H256; use multihash::Hash; type Reason = &'static str; /// Keeps the state of the response to send out #[derive(Debug, PartialEq)] pub enum Out { OctetStream(Bytes), NotFound(Reason), Bad(Reason), } impl IpfsHandler { /// Route path + query string to a specialized method pub fn route(&self, path: &str, query: Option<&str>) -> Out { match path { "/api/v0/block/get" => { let arg = query.and_then(|q| get_param(q, "arg")).unwrap_or(""); self.route_cid(arg).unwrap_or_else(Into::into) } _ => Out::NotFound("Route not found"), } } /// Attempt to read Content ID from `arg` query parameter, get a hash and /// route further by the CID's codec. fn route_cid(&self, cid: &str) -> Result { let cid = cid.to_cid()?; let mh = multihash::decode(&cid.hash)?; if mh.alg != Hash::Keccak256 { return Err(Error::UnsupportedHash); } let hash: H256 = mh.digest.into(); match cid.codec { Codec::EthereumBlock => self.block(hash), Codec::EthereumBlockList => self.block_list(hash), Codec::EthereumTx => self.transaction(hash), Codec::EthereumStateTrie => self.state_trie(hash), Codec::Raw => self.contract_code(hash), _ => return Err(Error::UnsupportedCid), } } /// Get block header by hash as raw binary. fn block(&self, hash: H256) -> Result { let block_id = BlockId::Hash(hash); let block = self .client() .block_header(block_id) .ok_or(Error::BlockNotFound)?; Ok(Out::OctetStream(block.into_inner())) } /// Get list of block ommers by hash as raw binary. fn block_list(&self, hash: H256) -> Result { let uncles = self .client() .find_uncles(&hash) .ok_or(Error::BlockNotFound)?; Ok(Out::OctetStream(rlp::encode_list(&uncles))) } /// Get transaction by hash and return as raw binary. fn transaction(&self, hash: H256) -> Result { let tx_id = TransactionId::Hash(hash); let tx = self .client() .transaction(tx_id) .ok_or(Error::TransactionNotFound)?; Ok(Out::OctetStream(rlp::encode(&*tx))) } /// Get state trie node by hash and return as raw binary. fn state_trie(&self, hash: H256) -> Result { let data = self .client() .state_data(&hash) .ok_or(Error::StateRootNotFound)?; Ok(Out::OctetStream(data)) } /// Get state trie node by hash and return as raw binary. fn contract_code(&self, hash: H256) -> Result { let data = self .client() .state_data(&hash) .ok_or(Error::ContractNotFound)?; Ok(Out::OctetStream(data)) } } /// Get a query parameter's value by name. fn get_param<'a>(query: &'a str, name: &str) -> Option<&'a str> { query .split('&') .find(|part| part.starts_with(name) && part[name.len()..].starts_with("=")) .map(|part| &part[name.len() + 1..]) } #[cfg(test)] mod tests { use super::*; use ethcore::client::TestBlockChainClient; use std::sync::Arc; fn get_mocked_handler() -> IpfsHandler { IpfsHandler::new( None.into(), None.into(), Arc::new(TestBlockChainClient::new()), ) } #[test] fn test_get_param() { let query = "foo=100&bar=200&qux=300"; assert_eq!(get_param(query, "foo"), Some("100")); assert_eq!(get_param(query, "bar"), Some("200")); assert_eq!(get_param(query, "qux"), Some("300")); assert_eq!(get_param(query, "bar="), None); assert_eq!(get_param(query, "200"), None); assert_eq!(get_param("", "foo"), None); assert_eq!(get_param("foo", "foo"), None); assert_eq!(get_param("foo&bar", "foo"), None); assert_eq!(get_param("bar&foo", "foo"), None); } #[test] fn cid_route_block() { let handler = get_mocked_handler(); // `eth-block` with Keccak-256 let cid = "z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM"; assert_eq!(Err(Error::BlockNotFound), handler.route_cid(cid)); } #[test] fn cid_route_block_list() { let handler = get_mocked_handler(); // `eth-block-list` with Keccak-256 let cid = "z43c7o7FsNxqdLJW8Ucj19tuCALtnmUb2EkDptj4W6xSkFVTqWs"; assert_eq!(Err(Error::BlockNotFound), handler.route_cid(cid)); } #[test] fn cid_route_tx() { let handler = get_mocked_handler(); // `eth-tx` with Keccak-256 let cid = "z44VCrqbpbPcb8SUBc8Tba4EaKuoDz2grdEoQXx4TP7WYh9ZGBu"; assert_eq!(Err(Error::TransactionNotFound), handler.route_cid(cid)); } #[test] fn cid_route_state_trie() { let handler = get_mocked_handler(); // `eth-state-trie` with Keccak-256 let cid = "z45oqTS7kR2n2peRGJQ4VCJEeaG9sorqcCyfmznZPJM7FMdhQCT"; assert_eq!(Err(Error::StateRootNotFound), handler.route_cid(&cid)); } #[test] fn cid_route_contract_code() { let handler = get_mocked_handler(); // `raw` with Keccak-256 let cid = "zb34WAp1Q5fhtLGZ3w3jhnTWaNbVV5ZZvGq4vuJQzERj6Pu3H"; assert_eq!(Err(Error::ContractNotFound), handler.route_cid(&cid)); } #[test] fn cid_route_invalid_hash() { let handler = get_mocked_handler(); // `eth-block` with SHA3-256 hash let cid = "z43Aa9gr1MM7TENJh4Em9d9Ttr7p3UcfyMpNei6WLVeCmSEPu8F"; assert_eq!(Err(Error::UnsupportedHash), handler.route_cid(cid)); } #[test] fn cid_route_invalid_codec() { let handler = get_mocked_handler(); // `bitcoin-block` with Keccak-256 let cid = "z4HFyHvb8CarYARyxz4cCcPaciduXd49TFPCKLhYmvNxf7Auvwu"; assert_eq!(Err(Error::UnsupportedCid), handler.route_cid(&cid)); } #[test] fn route_block() { let handler = get_mocked_handler(); let out = handler.route( "/api/v0/block/get", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM"), ); assert_eq!(out, Out::NotFound("Block not found")); } #[test] fn route_block_missing_query() { let handler = get_mocked_handler(); let out = handler.route("/api/v0/block/get", None); assert_eq!(out, Out::Bad("CID parsing failed")); } #[test] fn route_block_invalid_query() { let handler = get_mocked_handler(); let out = handler.route( "/api/v0/block/get", Some("arg=foobarz43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM"), ); assert_eq!(out, Out::Bad("CID parsing failed")); } #[test] fn route_invalid_route() { let handler = get_mocked_handler(); let out = handler.route( "/foo/bar/baz", Some("arg=z43AaGF5tmkT9SEX6urrhwpEW5ZSaACY73Vw357ZXTsur2fR8BM"), ); assert_eq!(out, Out::NotFound("Route not found")); } }