Extend RPC client for monerod with binary requests

This commit is contained in:
Thomas Eizinger 2021-04-29 13:43:03 +10:00
parent 1820139786
commit dd6bfd3bf4
No known key found for this signature in database
GPG Key ID: 651AC83A6C6C8B96
4 changed files with 245 additions and 69 deletions

103
Cargo.lock generated
View File

@ -129,28 +129,7 @@ dependencies = [
"futures-core", "futures-core",
"memchr", "memchr",
"pin-project-lite 0.2.6", "pin-project-lite 0.2.6",
"tokio 1.5.0", "tokio",
]
[[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",
] ]
[[package]] [[package]]
@ -220,7 +199,7 @@ dependencies = [
"instant", "instant",
"pin-project 1.0.5", "pin-project 1.0.5",
"rand 0.8.3", "rand 0.8.3",
"tokio 1.5.0", "tokio",
] ]
[[package]] [[package]]
@ -237,15 +216,11 @@ checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
[[package]] [[package]]
name = "base58-monero" name = "base58-monero"
version = "0.2.1" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4b40d07a9459c8d0d60cf7e7935748fae3f401263c38a8a120be6c0a2be566d" checksum = "465ba1f408efdef4d9379bdfa7340899b63e472d50c7fb666480ccfd5a893e53"
dependencies = [ dependencies = [
"async-stream",
"futures-util",
"thiserror", "thiserror",
"tiny-keccak",
"tokio 0.2.25",
] ]
[[package]] [[package]]
@ -296,7 +271,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sled", "sled",
"tokio 1.5.0", "tokio",
] ]
[[package]] [[package]]
@ -377,7 +352,7 @@ dependencies = [
"serde_json", "serde_json",
"testcontainers 0.11.0", "testcontainers 0.11.0",
"thiserror", "thiserror",
"tokio 1.5.0", "tokio",
"tracing", "tracing",
"url 2.2.1", "url 2.2.1",
] ]
@ -491,7 +466,7 @@ checksum = "c2beb18ef6d59c6aa23181cb6d4ac75e564ce15ed62a66974179a394d386ec27"
dependencies = [ dependencies = [
"futures", "futures",
"loom", "loom",
"tokio 1.5.0", "tokio",
] ]
[[package]] [[package]]
@ -1318,7 +1293,7 @@ dependencies = [
"http", "http",
"indexmap", "indexmap",
"slab", "slab",
"tokio 1.5.0", "tokio",
"tokio-util", "tokio-util",
"tracing", "tracing",
] ]
@ -1496,7 +1471,7 @@ dependencies = [
"itoa", "itoa",
"pin-project 1.0.5", "pin-project 1.0.5",
"socket2 0.4.0", "socket2 0.4.0",
"tokio 1.5.0", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
"want", "want",
@ -1512,7 +1487,7 @@ dependencies = [
"hyper 0.14.7", "hyper 0.14.7",
"log 0.4.14", "log 0.4.14",
"rustls 0.19.0", "rustls 0.19.0",
"tokio 1.5.0", "tokio",
"tokio-rustls", "tokio-rustls",
"webpki", "webpki",
] ]
@ -1929,7 +1904,7 @@ dependencies = [
"libp2p-core", "libp2p-core",
"log 0.4.14", "log 0.4.14",
"socket2 0.4.0", "socket2 0.4.0",
"tokio 1.5.0", "tokio",
] ]
[[package]] [[package]]
@ -2180,9 +2155,9 @@ dependencies = [
[[package]] [[package]]
name = "monero" name = "monero"
version = "0.11.2" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad9cdd100bffa1b21e9b1052394dd78246c6977b9e6f801b4acfd53ba62311e" checksum = "2c73108ba5cf025e437600990935234241f95ada3c4621960d50912cde739af6"
dependencies = [ dependencies = [
"base58-monero", "base58-monero",
"curve25519-dalek", "curve25519-dalek",
@ -2195,6 +2170,16 @@ dependencies = [
"thiserror", "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]] [[package]]
name = "monero-harness" name = "monero-harness"
version = "0.1.0" version = "0.1.0"
@ -2205,7 +2190,7 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"spectral", "spectral",
"testcontainers 0.12.0", "testcontainers 0.12.0",
"tokio 1.5.0", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -2215,11 +2200,17 @@ name = "monero-rpc"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"curve25519-dalek",
"hex 0.4.3",
"hex-literal",
"jsonrpc_client 0.6.0", "jsonrpc_client 0.6.0",
"monero", "monero",
"monero-epee-bin-serde",
"rand 0.7.3",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"tokio",
"tracing", "tracing",
] ]
@ -3079,7 +3070,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"tokio 1.5.0", "tokio",
"tokio-rustls", "tokio-rustls",
"tokio-socks", "tokio-socks",
"url 2.2.1", "url 2.2.1",
@ -3773,7 +3764,7 @@ dependencies = [
"testcontainers 0.12.0", "testcontainers 0.12.0",
"thiserror", "thiserror",
"time 0.2.26", "time 0.2.26",
"tokio 1.5.0", "tokio",
"tokio-socks", "tokio-socks",
"tokio-tar", "tokio-tar",
"tokio-tungstenite", "tokio-tungstenite",
@ -4000,18 +3991,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 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]] [[package]]
name = "tokio" name = "tokio"
version = "1.5.0" version = "1.5.0"
@ -4050,7 +4029,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
dependencies = [ dependencies = [
"rustls 0.19.0", "rustls 0.19.0",
"tokio 1.5.0", "tokio",
"webpki", "webpki",
] ]
@ -4063,7 +4042,7 @@ dependencies = [
"either", "either",
"futures-util", "futures-util",
"thiserror", "thiserror",
"tokio 1.5.0", "tokio",
] ]
[[package]] [[package]]
@ -4074,7 +4053,7 @@ checksum = "c535f53c0cfa1acace62995a8994fc9cc1f12d202420da96ff306ee24d576469"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"pin-project-lite 0.2.6", "pin-project-lite 0.2.6",
"tokio 1.5.0", "tokio",
] ]
[[package]] [[package]]
@ -4086,7 +4065,7 @@ dependencies = [
"libc", "libc",
"redox_syscall 0.2.5", "redox_syscall 0.2.5",
"tempfile", "tempfile",
"tokio 1.5.0", "tokio",
"tokio-stream", "tokio-stream",
"xattr", "xattr",
] ]
@ -4101,7 +4080,7 @@ dependencies = [
"log 0.4.14", "log 0.4.14",
"pin-project 1.0.5", "pin-project 1.0.5",
"rustls 0.19.0", "rustls 0.19.0",
"tokio 1.5.0", "tokio",
"tokio-rustls", "tokio-rustls",
"tungstenite", "tungstenite",
"webpki", "webpki",
@ -4119,7 +4098,7 @@ dependencies = [
"futures-sink", "futures-sink",
"log 0.4.14", "log 0.4.14",
"pin-project-lite 0.2.6", "pin-project-lite 0.2.6",
"tokio 1.5.0", "tokio",
] ]
[[package]] [[package]]
@ -4145,7 +4124,7 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"sha2 0.8.2", "sha2 0.8.2",
"sha3", "sha3",
"tokio 1.5.0", "tokio",
] ]
[[package]] [[package]]
@ -4265,7 +4244,7 @@ dependencies = [
"smallvec", "smallvec",
"thiserror", "thiserror",
"tinyvec", "tinyvec",
"tokio 1.5.0", "tokio",
"url 2.2.1", "url 2.2.1",
] ]
@ -4285,7 +4264,7 @@ dependencies = [
"resolv-conf", "resolv-conf",
"smallvec", "smallvec",
"thiserror", "thiserror",
"tokio 1.5.0", "tokio",
"trust-dns-proto", "trust-dns-proto",
] ]

View File

@ -6,9 +6,17 @@ edition = "2018"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
curve25519-dalek = "3.1"
hex = "0.4"
jsonrpc_client = { version = "0.6", features = [ "reqwest" ] } 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" ] } reqwest = { version = "0.11", default-features = false, features = [ "json" ] }
serde = { version = "1.0", features = [ "derive" ] } serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0" serde_json = "1.0"
tracing = "0.1" tracing = "0.1"
[dev-dependencies]
hex-literal = "0.3"
tokio = { version = "1", features = [ "full" ] }

View File

@ -1,5 +1,6 @@
use anyhow::{Context, Result}; 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")] #[jsonrpc_client::api(version = "2.0")]
pub trait MonerodRpc { pub trait MonerodRpc {
@ -7,6 +8,7 @@ pub trait MonerodRpc {
-> GenerateBlocks; -> GenerateBlocks;
async fn get_block_header_by_height(&self, height: u32) -> BlockHeader; async fn get_block_header_by_height(&self, height: u32) -> BlockHeader;
async fn get_block_count(&self) -> BlockCount; async fn get_block_count(&self) -> BlockCount;
async fn get_block(&self, height: u32) -> GetBlockResponse;
} }
#[jsonrpc_client::implement(MonerodRpc)] #[jsonrpc_client::implement(MonerodRpc)]
@ -14,33 +16,76 @@ pub trait MonerodRpc {
pub struct Client { pub struct Client {
inner: reqwest::Client, inner: reqwest::Client,
base_url: reqwest::Url, base_url: reqwest::Url,
get_o_indexes_bin_url: reqwest::Url,
get_outs_bin_url: reqwest::Url,
} }
impl Client { impl Client {
/// New local host monerod RPC client. /// New local host monerod RPC client.
pub fn localhost(port: u16) -> Result<Self> { pub fn localhost(port: u16) -> Result<Self> {
Self::new("127.0.0.1".to_owned(), port)
}
fn new(host: String, port: u16) -> Result<Self> {
Ok(Self { Ok(Self {
inner: reqwest::ClientBuilder::new() inner: reqwest::ClientBuilder::new()
.connection_verbose(true) .connection_verbose(true)
.build()?, .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() .parse()
.context("url is well formed")?, .context("url is well formed")?,
}) })
} }
pub async fn get_o_indexes(&self, txid: Hash) -> Result<GetOIndexesResponse> {
self.binary_request(self.get_o_indexes_bin_url.clone(), GetOIndexesPayload {
txid,
})
.await
}
pub async fn get_outs(&self, outputs: Vec<GetOutputsOut>) -> Result<GetOutsResponse> {
self.binary_request(self.get_outs_bin_url.clone(), GetOutsPayload { outputs })
.await
}
async fn binary_request<Req, Res>(&self, url: reqwest::Url, request: Req) -> Result<Res>
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)] #[derive(Clone, Debug, Deserialize)]
pub struct GenerateBlocks { pub struct GenerateBlocks {
pub blocks: Vec<String>, pub blocks: Vec<String>,
pub height: u32, pub height: u32,
pub status: String,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Copy, Debug, Deserialize)]
pub struct BlockCount { pub struct BlockCount {
pub count: u32, pub count: u32,
pub status: String,
} }
// We should be able to use monero-rs for this but it does not include all // 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 reward: u64,
pub timestamp: u32, 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<u32>,
}
#[derive(Clone, Debug, Serialize)]
struct GetOIndexesPayload {
#[serde(with = "byte_array")]
txid: Hash,
}
#[derive(Clone, Debug, Serialize)]
struct GetOutsPayload {
outputs: Vec<GetOutputsOut>,
}
#[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<OutKey>,
}
#[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<u64>,
}
#[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<monero::Block, D::Error>
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<S, B>(bytes: B, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
B: AsRef<[u8]>,
{
serializer.serialize_bytes(bytes.as_ref())
}
pub fn deserialize<'de, D, B, const N: usize>(deserializer: D) -> Result<B, D::Error>
where
D: Deserializer<'de>,
B: TryFrom<[u8; N]>,
{
struct Visitor<T, const N: usize> {
phantom: PhantomData<(T, [u8; N])>,
}
impl<'de, T, const N: usize> serde::de::Visitor<'de> for Visitor<T, N>
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<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
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,
})
}
}

View File

@ -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 = { 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" } libp2p-async-await = { git = "https://github.com/comit-network/rust-libp2p-async-await" }
miniscript = { version = "5", features = [ "serde" ] } miniscript = { version = "5", features = [ "serde" ] }
monero = { version = "0.11", features = [ "serde_support" ] } monero = { version = "0.12", features = [ "serde_support" ] }
monero-rpc = { path = "../monero-rpc" } monero-rpc = { path = "../monero-rpc" }
pem = "0.8" pem = "0.8"
prettytable-rs = "0.8" prettytable-rs = "0.8"