From dd6bfd3bf4bc8bfa40724302e9caed6dd0257907 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 29 Apr 2021 13:43:03 +1000 Subject: [PATCH] Extend RPC client for monerod with binary requests --- Cargo.lock | 103 ++++++++------------ monero-rpc/Cargo.toml | 10 +- monero-rpc/src/monerod.rs | 199 +++++++++++++++++++++++++++++++++++++- swap/Cargo.toml | 2 +- 4 files changed, 245 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8242769..8a954856 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,28 +129,7 @@ dependencies = [ "futures-core", "memchr", "pin-project-lite 0.2.6", - "tokio 1.5.0", -] - -[[package]] -name = "async-stream" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" -dependencies = [ - "proc-macro2 1.0.24", - "quote 1.0.9", - "syn 1.0.64", + "tokio", ] [[package]] @@ -220,7 +199,7 @@ dependencies = [ "instant", "pin-project 1.0.5", "rand 0.8.3", - "tokio 1.5.0", + "tokio", ] [[package]] @@ -237,15 +216,11 @@ checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" [[package]] name = "base58-monero" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b40d07a9459c8d0d60cf7e7935748fae3f401263c38a8a120be6c0a2be566d" +checksum = "465ba1f408efdef4d9379bdfa7340899b63e472d50c7fb666480ccfd5a893e53" dependencies = [ - "async-stream", - "futures-util", "thiserror", - "tiny-keccak", - "tokio 0.2.25", ] [[package]] @@ -296,7 +271,7 @@ dependencies = [ "serde", "serde_json", "sled", - "tokio 1.5.0", + "tokio", ] [[package]] @@ -377,7 +352,7 @@ dependencies = [ "serde_json", "testcontainers 0.11.0", "thiserror", - "tokio 1.5.0", + "tokio", "tracing", "url 2.2.1", ] @@ -491,7 +466,7 @@ checksum = "c2beb18ef6d59c6aa23181cb6d4ac75e564ce15ed62a66974179a394d386ec27" dependencies = [ "futures", "loom", - "tokio 1.5.0", + "tokio", ] [[package]] @@ -1318,7 +1293,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.5.0", + "tokio", "tokio-util", "tracing", ] @@ -1496,7 +1471,7 @@ dependencies = [ "itoa", "pin-project 1.0.5", "socket2 0.4.0", - "tokio 1.5.0", + "tokio", "tower-service", "tracing", "want", @@ -1512,7 +1487,7 @@ dependencies = [ "hyper 0.14.7", "log 0.4.14", "rustls 0.19.0", - "tokio 1.5.0", + "tokio", "tokio-rustls", "webpki", ] @@ -1929,7 +1904,7 @@ dependencies = [ "libp2p-core", "log 0.4.14", "socket2 0.4.0", - "tokio 1.5.0", + "tokio", ] [[package]] @@ -2180,9 +2155,9 @@ dependencies = [ [[package]] name = "monero" -version = "0.11.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad9cdd100bffa1b21e9b1052394dd78246c6977b9e6f801b4acfd53ba62311e" +checksum = "2c73108ba5cf025e437600990935234241f95ada3c4621960d50912cde739af6" dependencies = [ "base58-monero", "curve25519-dalek", @@ -2195,6 +2170,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "monero-epee-bin-serde" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13be5b525af150f294b98d4291b0ec01e5bc157db740de2822827c17561d3960" +dependencies = [ + "byteorder", + "serde", +] + [[package]] name = "monero-harness" version = "0.1.0" @@ -2205,7 +2190,7 @@ dependencies = [ "rand 0.7.3", "spectral", "testcontainers 0.12.0", - "tokio 1.5.0", + "tokio", "tracing", "tracing-subscriber", ] @@ -2215,11 +2200,17 @@ name = "monero-rpc" version = "0.1.0" dependencies = [ "anyhow", + "curve25519-dalek", + "hex 0.4.3", + "hex-literal", "jsonrpc_client 0.6.0", "monero", + "monero-epee-bin-serde", + "rand 0.7.3", "reqwest", "serde", "serde_json", + "tokio", "tracing", ] @@ -3079,7 +3070,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.5.0", + "tokio", "tokio-rustls", "tokio-socks", "url 2.2.1", @@ -3773,7 +3764,7 @@ dependencies = [ "testcontainers 0.12.0", "thiserror", "time 0.2.26", - "tokio 1.5.0", + "tokio", "tokio-socks", "tokio-tar", "tokio-tungstenite", @@ -4000,18 +3991,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" -dependencies = [ - "bytes 0.5.6", - "futures-core", - "memchr", - "pin-project-lite 0.1.12", -] - [[package]] name = "tokio" version = "1.5.0" @@ -4050,7 +4029,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ "rustls 0.19.0", - "tokio 1.5.0", + "tokio", "webpki", ] @@ -4063,7 +4042,7 @@ dependencies = [ "either", "futures-util", "thiserror", - "tokio 1.5.0", + "tokio", ] [[package]] @@ -4074,7 +4053,7 @@ checksum = "c535f53c0cfa1acace62995a8994fc9cc1f12d202420da96ff306ee24d576469" dependencies = [ "futures-core", "pin-project-lite 0.2.6", - "tokio 1.5.0", + "tokio", ] [[package]] @@ -4086,7 +4065,7 @@ dependencies = [ "libc", "redox_syscall 0.2.5", "tempfile", - "tokio 1.5.0", + "tokio", "tokio-stream", "xattr", ] @@ -4101,7 +4080,7 @@ dependencies = [ "log 0.4.14", "pin-project 1.0.5", "rustls 0.19.0", - "tokio 1.5.0", + "tokio", "tokio-rustls", "tungstenite", "webpki", @@ -4119,7 +4098,7 @@ dependencies = [ "futures-sink", "log 0.4.14", "pin-project-lite 0.2.6", - "tokio 1.5.0", + "tokio", ] [[package]] @@ -4145,7 +4124,7 @@ dependencies = [ "rand 0.7.3", "sha2 0.8.2", "sha3", - "tokio 1.5.0", + "tokio", ] [[package]] @@ -4265,7 +4244,7 @@ dependencies = [ "smallvec", "thiserror", "tinyvec", - "tokio 1.5.0", + "tokio", "url 2.2.1", ] @@ -4285,7 +4264,7 @@ dependencies = [ "resolv-conf", "smallvec", "thiserror", - "tokio 1.5.0", + "tokio", "trust-dns-proto", ] diff --git a/monero-rpc/Cargo.toml b/monero-rpc/Cargo.toml index 78d2d2e3..54b3d64c 100644 --- a/monero-rpc/Cargo.toml +++ b/monero-rpc/Cargo.toml @@ -6,9 +6,17 @@ edition = "2018" [dependencies] anyhow = "1" +curve25519-dalek = "3.1" +hex = "0.4" jsonrpc_client = { version = "0.6", features = [ "reqwest" ] } -monero = "0.11" +monero = "0.12" +monero-epee-bin-serde = "1" +rand = "0.7" reqwest = { version = "0.11", default-features = false, features = [ "json" ] } serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" tracing = "0.1" + +[dev-dependencies] +hex-literal = "0.3" +tokio = { version = "1", features = [ "full" ] } diff --git a/monero-rpc/src/monerod.rs b/monero-rpc/src/monerod.rs index 8d044134..bf917a1a 100644 --- a/monero-rpc/src/monerod.rs +++ b/monero-rpc/src/monerod.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result}; -use serde::Deserialize; +use monero::{cryptonote::hash::Hash, util::ringct, PublicKey}; +use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer}; #[jsonrpc_client::api(version = "2.0")] pub trait MonerodRpc { @@ -7,6 +8,7 @@ pub trait MonerodRpc { -> GenerateBlocks; async fn get_block_header_by_height(&self, height: u32) -> BlockHeader; async fn get_block_count(&self) -> BlockCount; + async fn get_block(&self, height: u32) -> GetBlockResponse; } #[jsonrpc_client::implement(MonerodRpc)] @@ -14,33 +16,76 @@ pub trait MonerodRpc { pub struct Client { inner: reqwest::Client, base_url: reqwest::Url, + get_o_indexes_bin_url: reqwest::Url, + get_outs_bin_url: reqwest::Url, } impl Client { /// New local host monerod RPC client. pub fn localhost(port: u16) -> Result { + Self::new("127.0.0.1".to_owned(), port) + } + + fn new(host: String, port: u16) -> Result { Ok(Self { inner: reqwest::ClientBuilder::new() .connection_verbose(true) .build()?, - base_url: format!("http://127.0.0.1:{}/json_rpc", port) + base_url: format!("http://{}:{}/json_rpc", host, port) + .parse() + .context("url is well formed")?, + get_o_indexes_bin_url: format!("http://{}:{}/get_o_indexes.bin", host, port) + .parse() + .context("url is well formed")?, + get_outs_bin_url: format!("http://{}:{}/get_outs.bin", host, port) .parse() .context("url is well formed")?, }) } + + pub async fn get_o_indexes(&self, txid: Hash) -> Result { + self.binary_request(self.get_o_indexes_bin_url.clone(), GetOIndexesPayload { + txid, + }) + .await + } + + pub async fn get_outs(&self, outputs: Vec) -> Result { + self.binary_request(self.get_outs_bin_url.clone(), GetOutsPayload { outputs }) + .await + } + + async fn binary_request(&self, url: reqwest::Url, request: Req) -> Result + where + Req: Serialize, + Res: DeserializeOwned, + { + let response = self + .inner + .post(url) + .body(monero_epee_bin_serde::to_bytes(&request)?) + .send() + .await?; + + if !response.status().is_success() { + anyhow::bail!("Request failed with status code {}", response.status()) + } + + let body = response.bytes().await?; + + Ok(monero_epee_bin_serde::from_bytes(body)?) + } } #[derive(Clone, Debug, Deserialize)] pub struct GenerateBlocks { pub blocks: Vec, pub height: u32, - pub status: String, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Copy, Debug, Deserialize)] pub struct BlockCount { pub count: u32, - pub status: String, } // We should be able to use monero-rs for this but it does not include all @@ -61,3 +106,147 @@ pub struct BlockHeader { pub reward: u64, pub timestamp: u32, } + +#[derive(Debug, Deserialize)] +pub struct GetBlockResponse { + #[serde(with = "monero_serde_hex_block")] + pub blob: monero::Block, +} + +#[derive(Debug, Deserialize)] +pub struct GetIndexesResponse { + pub o_indexes: Vec, +} + +#[derive(Clone, Debug, Serialize)] +struct GetOIndexesPayload { + #[serde(with = "byte_array")] + txid: Hash, +} + +#[derive(Clone, Debug, Serialize)] +struct GetOutsPayload { + outputs: Vec, +} + +#[derive(Clone, Copy, Debug, Serialize)] +pub struct GetOutputsOut { + pub amount: u64, + pub index: u64, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct GetOutsResponse { + #[serde(flatten)] + pub base: BaseResponse, + pub outs: Vec, +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] +pub struct OutKey { + pub height: u64, + #[serde(with = "byte_array")] + pub key: PublicKey, + #[serde(with = "byte_array")] + pub mask: ringct::Key, + #[serde(with = "byte_array")] + pub txid: Hash, + pub unlocked: bool, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct BaseResponse { + pub credits: u64, + pub status: Status, + pub top_hash: String, + pub untrusted: bool, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct GetOIndexesResponse { + #[serde(flatten)] + pub base: BaseResponse, + #[serde(default)] + pub o_indexes: Vec, +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] +pub enum Status { + #[serde(rename = "OK")] + Ok, + #[serde(rename = "Failed")] + Failed, +} + +mod monero_serde_hex_block { + use super::*; + use monero::consensus::Decodable; + use serde::{de::Error, Deserialize, Deserializer}; + use std::io::Cursor; + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let hex = String::deserialize(deserializer)?; + + let bytes = hex::decode(&hex).map_err(D::Error::custom)?; + let mut cursor = Cursor::new(bytes); + + let block = monero::Block::consensus_decode(&mut cursor).map_err(D::Error::custom)?; + + Ok(block) + } +} + +mod byte_array { + use super::*; + use serde::{de::Error, Deserializer}; + use std::{convert::TryFrom, fmt, marker::PhantomData}; + + pub fn serialize(bytes: B, serializer: S) -> Result + where + S: Serializer, + B: AsRef<[u8]>, + { + serializer.serialize_bytes(bytes.as_ref()) + } + + pub fn deserialize<'de, D, B, const N: usize>(deserializer: D) -> Result + where + D: Deserializer<'de>, + B: TryFrom<[u8; N]>, + { + struct Visitor { + phantom: PhantomData<(T, [u8; N])>, + } + + impl<'de, T, const N: usize> serde::de::Visitor<'de> for Visitor + where + T: TryFrom<[u8; N]>, + { + type Value = T; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "a byte buffer") + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: Error, + { + let bytes = <[u8; N]>::try_from(v).map_err(|_| { + E::custom(format!("Failed to construct [u8; {}] from buffer", N)) + })?; + let result = T::try_from(bytes) + .map_err(|_| E::custom(format!("Failed to construct T from [u8; {}]", N)))?; + + Ok(result) + } + } + + deserializer.deserialize_byte_buf(Visitor { + phantom: PhantomData, + }) + } +} diff --git a/swap/Cargo.toml b/swap/Cargo.toml index fbcefff4..88c782dd 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -32,7 +32,7 @@ itertools = "0.10" libp2p = { version = "0.37", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket" ] } libp2p-async-await = { git = "https://github.com/comit-network/rust-libp2p-async-await" } miniscript = { version = "5", features = [ "serde" ] } -monero = { version = "0.11", features = [ "serde_support" ] } +monero = { version = "0.12", features = [ "serde_support" ] } monero-rpc = { path = "../monero-rpc" } pem = "0.8" prettytable-rs = "0.8"