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",
"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",
]

View File

@ -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" ] }

View File

@ -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> {
Self::new("127.0.0.1".to_owned(), port)
}
fn new(host: String, port: u16) -> Result<Self> {
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<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)]
pub struct GenerateBlocks {
pub blocks: Vec<String>,
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<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-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"