mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-22 19:49:22 +00:00
Remove async-trait from monero-rpc
This commit is contained in:
parent
875c669a7a
commit
6b270bc6aa
5 changed files with 742 additions and 665 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -4949,7 +4949,6 @@ dependencies = [
|
||||||
name = "monero-rpc"
|
name = "monero-rpc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"hex",
|
"hex",
|
||||||
"monero-address",
|
"monero-address",
|
||||||
|
@ -5013,7 +5012,6 @@ dependencies = [
|
||||||
name = "monero-simple-request-rpc"
|
name = "monero-simple-request-rpc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
|
||||||
"digest_auth",
|
"digest_auth",
|
||||||
"hex",
|
"hex",
|
||||||
"monero-address",
|
"monero-address",
|
||||||
|
|
|
@ -18,7 +18,6 @@ workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
|
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
|
||||||
|
|
||||||
async-trait = { version = "0.1", default-features = false }
|
|
||||||
thiserror = { version = "1", default-features = false, optional = true }
|
thiserror = { version = "1", default-features = false, optional = true }
|
||||||
|
|
||||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
||||||
|
|
|
@ -16,8 +16,6 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = { version = "0.1", default-features = false }
|
|
||||||
|
|
||||||
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||||
digest_auth = { version = "0.3", default-features = false }
|
digest_auth = { version = "0.3", default-features = false }
|
||||||
simple-request = { path = "../../../../common/request", version = "0.1", default-features = false, features = ["tls"] }
|
simple-request = { path = "../../../../common/request", version = "0.1", default-features = false, features = ["tls"] }
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use core::future::Future;
|
||||||
use std::{sync::Arc, io::Read, time::Duration};
|
use std::{sync::Arc, io::Read, time::Duration};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use digest_auth::{WwwAuthenticateHeader, AuthContext};
|
use digest_auth::{WwwAuthenticateHeader, AuthContext};
|
||||||
|
@ -280,11 +279,16 @@ impl SimpleRequestRpc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Rpc for SimpleRequestRpc {
|
impl Rpc for SimpleRequestRpc {
|
||||||
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError> {
|
fn post(
|
||||||
|
&self,
|
||||||
|
route: &str,
|
||||||
|
body: Vec<u8>,
|
||||||
|
) -> impl Send + Future<Output = Result<Vec<u8>, RpcError>> {
|
||||||
|
async move {
|
||||||
tokio::time::timeout(self.request_timeout, self.inner_post(route, body))
|
tokio::time::timeout(self.request_timeout, self.inner_post(route, body))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| RpcError::ConnectionError(format!("{e:?}")))?
|
.map_err(|e| RpcError::ConnectionError(format!("{e:?}")))?
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use core::{
|
use core::{
|
||||||
|
future::Future,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
ops::{Bound, RangeBounds},
|
ops::{Bound, RangeBounds},
|
||||||
};
|
};
|
||||||
use std_shims::{
|
use std_shims::{
|
||||||
alloc::{boxed::Box, format},
|
alloc::format,
|
||||||
vec,
|
vec,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
io,
|
io,
|
||||||
|
@ -17,8 +18,6 @@ use std_shims::{
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
|
use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
||||||
|
@ -237,22 +236,26 @@ fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
|
||||||
/// While no implementors are directly provided, [monero-simple-request-rpc](
|
/// While no implementors are directly provided, [monero-simple-request-rpc](
|
||||||
/// https://github.com/serai-dex/serai/tree/develop/networks/monero/rpc/simple-request
|
/// https://github.com/serai-dex/serai/tree/develop/networks/monero/rpc/simple-request
|
||||||
/// ) is recommended.
|
/// ) is recommended.
|
||||||
#[async_trait]
|
|
||||||
pub trait Rpc: Sync + Clone + Debug {
|
pub trait Rpc: Sync + Clone + Debug {
|
||||||
/// Perform a POST request to the specified route with the specified body.
|
/// Perform a POST request to the specified route with the specified body.
|
||||||
///
|
///
|
||||||
/// The implementor is left to handle anything such as authentication.
|
/// The implementor is left to handle anything such as authentication.
|
||||||
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError>;
|
fn post(
|
||||||
|
&self,
|
||||||
|
route: &str,
|
||||||
|
body: Vec<u8>,
|
||||||
|
) -> impl Send + Future<Output = Result<Vec<u8>, RpcError>>;
|
||||||
|
|
||||||
/// Perform a RPC call to the specified route with the provided parameters.
|
/// Perform a RPC call to the specified route with the provided parameters.
|
||||||
///
|
///
|
||||||
/// This is NOT a JSON-RPC call. They use a route of "json_rpc" and are available via
|
/// This is NOT a JSON-RPC call. They use a route of "json_rpc" and are available via
|
||||||
/// `json_rpc_call`.
|
/// `json_rpc_call`.
|
||||||
async fn rpc_call<Params: Send + Serialize + Debug, Response: DeserializeOwned + Debug>(
|
fn rpc_call<Params: Send + Serialize + Debug, Response: DeserializeOwned + Debug>(
|
||||||
&self,
|
&self,
|
||||||
route: &str,
|
route: &str,
|
||||||
params: Option<Params>,
|
params: Option<Params>,
|
||||||
) -> Result<Response, RpcError> {
|
) -> impl Send + Future<Output = Result<Response, RpcError>> {
|
||||||
|
async move {
|
||||||
let res = self
|
let res = self
|
||||||
.post(
|
.post(
|
||||||
route,
|
route,
|
||||||
|
@ -268,29 +271,37 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
serde_json::from_str(res_str)
|
serde_json::from_str(res_str)
|
||||||
.map_err(|_| RpcError::InvalidNode(format!("response wasn't the expected json: {res_str}")))
|
.map_err(|_| RpcError::InvalidNode(format!("response wasn't the expected json: {res_str}")))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform a JSON-RPC call with the specified method with the provided parameters.
|
/// Perform a JSON-RPC call with the specified method with the provided parameters.
|
||||||
async fn json_rpc_call<Response: DeserializeOwned + Debug>(
|
fn json_rpc_call<Response: DeserializeOwned + Debug>(
|
||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
params: Option<Value>,
|
params: Option<Value>,
|
||||||
) -> Result<Response, RpcError> {
|
) -> impl Send + Future<Output = Result<Response, RpcError>> {
|
||||||
|
async move {
|
||||||
let mut req = json!({ "method": method });
|
let mut req = json!({ "method": method });
|
||||||
if let Some(params) = params {
|
if let Some(params) = params {
|
||||||
req.as_object_mut().unwrap().insert("params".into(), params);
|
req.as_object_mut().unwrap().insert("params".into(), params);
|
||||||
}
|
}
|
||||||
Ok(self.rpc_call::<_, JsonRpcResponse<Response>>("json_rpc", Some(req)).await?.result)
|
Ok(self.rpc_call::<_, JsonRpcResponse<Response>>("json_rpc", Some(req)).await?.result)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform a binary call to the specified route with the provided parameters.
|
/// Perform a binary call to the specified route with the provided parameters.
|
||||||
async fn bin_call(&self, route: &str, params: Vec<u8>) -> Result<Vec<u8>, RpcError> {
|
fn bin_call(
|
||||||
self.post(route, params).await
|
&self,
|
||||||
|
route: &str,
|
||||||
|
params: Vec<u8>,
|
||||||
|
) -> impl Send + Future<Output = Result<Vec<u8>, RpcError>> {
|
||||||
|
async move { self.post(route, params).await }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the active blockchain protocol version.
|
/// Get the active blockchain protocol version.
|
||||||
///
|
///
|
||||||
/// This is specifically the major version within the most recent block header.
|
/// This is specifically the major version within the most recent block header.
|
||||||
async fn get_hardfork_version(&self) -> Result<u8, RpcError> {
|
fn get_hardfork_version(&self) -> impl Send + Future<Output = Result<u8, RpcError>> {
|
||||||
|
async move {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct HeaderResponse {
|
struct HeaderResponse {
|
||||||
major_version: u8,
|
major_version: u8,
|
||||||
|
@ -309,12 +320,14 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
.major_version,
|
.major_version,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the height of the Monero blockchain.
|
/// Get the height of the Monero blockchain.
|
||||||
///
|
///
|
||||||
/// The height is defined as the amount of blocks on the blockchain. For a blockchain with only
|
/// The height is defined as the amount of blocks on the blockchain. For a blockchain with only
|
||||||
/// its genesis block, the height will be 1.
|
/// its genesis block, the height will be 1.
|
||||||
async fn get_height(&self) -> Result<usize, RpcError> {
|
fn get_height(&self) -> impl Send + Future<Output = Result<usize, RpcError>> {
|
||||||
|
async move {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct HeightResponse {
|
struct HeightResponse {
|
||||||
height: usize,
|
height: usize,
|
||||||
|
@ -325,12 +338,17 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
}
|
}
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the specified transactions.
|
/// Get the specified transactions.
|
||||||
///
|
///
|
||||||
/// The received transactions will be hashed in order to verify the correct transactions were
|
/// The received transactions will be hashed in order to verify the correct transactions were
|
||||||
/// returned.
|
/// returned.
|
||||||
async fn get_transactions(&self, hashes: &[[u8; 32]]) -> Result<Vec<Transaction>, RpcError> {
|
fn get_transactions(
|
||||||
|
&self,
|
||||||
|
hashes: &[[u8; 32]],
|
||||||
|
) -> impl Send + Future<Output = Result<Vec<Transaction>, RpcError>> {
|
||||||
|
async move {
|
||||||
if hashes.is_empty() {
|
if hashes.is_empty() {
|
||||||
return Ok(vec![]);
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
|
@ -396,12 +414,14 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the specified transactions in their pruned format.
|
/// Get the specified transactions in their pruned format.
|
||||||
async fn get_pruned_transactions(
|
fn get_pruned_transactions(
|
||||||
&self,
|
&self,
|
||||||
hashes: &[[u8; 32]],
|
hashes: &[[u8; 32]],
|
||||||
) -> Result<Vec<Transaction<Pruned>>, RpcError> {
|
) -> impl Send + Future<Output = Result<Vec<Transaction<Pruned>>, RpcError>> {
|
||||||
|
async move {
|
||||||
if hashes.is_empty() {
|
if hashes.is_empty() {
|
||||||
return Ok(vec![]);
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
|
@ -447,25 +467,36 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the specified transaction.
|
/// Get the specified transaction.
|
||||||
///
|
///
|
||||||
/// The received transaction will be hashed in order to verify the correct transaction was
|
/// The received transaction will be hashed in order to verify the correct transaction was
|
||||||
/// returned.
|
/// returned.
|
||||||
async fn get_transaction(&self, tx: [u8; 32]) -> Result<Transaction, RpcError> {
|
fn get_transaction(
|
||||||
self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0))
|
&self,
|
||||||
|
tx: [u8; 32],
|
||||||
|
) -> impl Send + Future<Output = Result<Transaction, RpcError>> {
|
||||||
|
async move { self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the specified transaction in its pruned format.
|
/// Get the specified transaction in its pruned format.
|
||||||
async fn get_pruned_transaction(&self, tx: [u8; 32]) -> Result<Transaction<Pruned>, RpcError> {
|
fn get_pruned_transaction(
|
||||||
self.get_pruned_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0))
|
&self,
|
||||||
|
tx: [u8; 32],
|
||||||
|
) -> impl Send + Future<Output = Result<Transaction<Pruned>, RpcError>> {
|
||||||
|
async move { self.get_pruned_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the hash of a block from the node.
|
/// Get the hash of a block from the node.
|
||||||
///
|
///
|
||||||
/// `number` is the block's zero-indexed position on the blockchain (`0` for the genesis block,
|
/// `number` is the block's zero-indexed position on the blockchain (`0` for the genesis block,
|
||||||
/// `height - 1` for the latest block).
|
/// `height - 1` for the latest block).
|
||||||
async fn get_block_hash(&self, number: usize) -> Result<[u8; 32], RpcError> {
|
fn get_block_hash(
|
||||||
|
&self,
|
||||||
|
number: usize,
|
||||||
|
) -> impl Send + Future<Output = Result<[u8; 32], RpcError>> {
|
||||||
|
async move {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct BlockHeaderResponse {
|
struct BlockHeaderResponse {
|
||||||
hash: String,
|
hash: String,
|
||||||
|
@ -479,11 +510,13 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
self.json_rpc_call("get_block_header_by_height", Some(json!({ "height": number }))).await?;
|
self.json_rpc_call("get_block_header_by_height", Some(json!({ "height": number }))).await?;
|
||||||
hash_hex(&header.block_header.hash)
|
hash_hex(&header.block_header.hash)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a block from the node by its hash.
|
/// Get a block from the node by its hash.
|
||||||
///
|
///
|
||||||
/// The received block will be hashed in order to verify the correct block was returned.
|
/// The received block will be hashed in order to verify the correct block was returned.
|
||||||
async fn get_block(&self, hash: [u8; 32]) -> Result<Block, RpcError> {
|
fn get_block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, RpcError>> {
|
||||||
|
async move {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct BlockResponse {
|
struct BlockResponse {
|
||||||
blob: String,
|
blob: String,
|
||||||
|
@ -499,12 +532,17 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
}
|
}
|
||||||
Ok(block)
|
Ok(block)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a block from the node by its number.
|
/// Get a block from the node by its number.
|
||||||
///
|
///
|
||||||
/// `number` is the block's zero-indexed position on the blockchain (`0` for the genesis block,
|
/// `number` is the block's zero-indexed position on the blockchain (`0` for the genesis block,
|
||||||
/// `height - 1` for the latest block).
|
/// `height - 1` for the latest block).
|
||||||
async fn get_block_by_number(&self, number: usize) -> Result<Block, RpcError> {
|
fn get_block_by_number(
|
||||||
|
&self,
|
||||||
|
number: usize,
|
||||||
|
) -> impl Send + Future<Output = Result<Block, RpcError>> {
|
||||||
|
async move {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct BlockResponse {
|
struct BlockResponse {
|
||||||
blob: String,
|
blob: String,
|
||||||
|
@ -530,13 +568,18 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the currently estimated fee rate from the node.
|
/// Get the currently estimated fee rate from the node.
|
||||||
///
|
///
|
||||||
/// This may be manipulated to unsafe levels and MUST be sanity checked.
|
/// This may be manipulated to unsafe levels and MUST be sanity checked.
|
||||||
///
|
///
|
||||||
/// This MUST NOT be expected to be deterministic in any way.
|
/// This MUST NOT be expected to be deterministic in any way.
|
||||||
async fn get_fee_rate(&self, priority: FeePriority) -> Result<FeeRate, RpcError> {
|
fn get_fee_rate(
|
||||||
|
&self,
|
||||||
|
priority: FeePriority,
|
||||||
|
) -> impl Send + Future<Output = Result<FeeRate, RpcError>> {
|
||||||
|
async move {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct FeeResponse {
|
struct FeeResponse {
|
||||||
status: String,
|
status: String,
|
||||||
|
@ -576,8 +619,11 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
// src/wallet/wallet2.cpp#L7569-L7584
|
// src/wallet/wallet2.cpp#L7569-L7584
|
||||||
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
|
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
|
||||||
// src/wallet/wallet2.cpp#L7660-L7661
|
// src/wallet/wallet2.cpp#L7660-L7661
|
||||||
let priority_idx =
|
let priority_idx = usize::try_from(if priority.fee_priority() == 0 {
|
||||||
usize::try_from(if priority.fee_priority() == 0 { 1 } else { priority.fee_priority() - 1 })
|
1
|
||||||
|
} else {
|
||||||
|
priority.fee_priority() - 1
|
||||||
|
})
|
||||||
.map_err(|_| RpcError::InvalidPriority)?;
|
.map_err(|_| RpcError::InvalidPriority)?;
|
||||||
let multipliers = [1, 5, 25, 1000];
|
let multipliers = [1, 5, 25, 1000];
|
||||||
if priority_idx >= multipliers.len() {
|
if priority_idx >= multipliers.len() {
|
||||||
|
@ -589,9 +635,14 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
FeeRate::new(res.fee * fee_multiplier, res.quantization_mask)
|
FeeRate::new(res.fee * fee_multiplier, res.quantization_mask)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Publish a transaction.
|
/// Publish a transaction.
|
||||||
async fn publish_transaction(&self, tx: &Transaction) -> Result<(), RpcError> {
|
fn publish_transaction(
|
||||||
|
&self,
|
||||||
|
tx: &Transaction,
|
||||||
|
) -> impl Send + Future<Output = Result<(), RpcError>> {
|
||||||
|
async move {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct SendRawResponse {
|
struct SendRawResponse {
|
||||||
|
@ -621,15 +672,17 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate blocks, with the specified address receiving the block reward.
|
/// Generate blocks, with the specified address receiving the block reward.
|
||||||
///
|
///
|
||||||
/// Returns the hashes of the generated blocks and the last block's number.
|
/// Returns the hashes of the generated blocks and the last block's number.
|
||||||
async fn generate_blocks<const ADDR_BYTES: u128>(
|
fn generate_blocks<const ADDR_BYTES: u128>(
|
||||||
&self,
|
&self,
|
||||||
address: &Address<ADDR_BYTES>,
|
address: &Address<ADDR_BYTES>,
|
||||||
block_count: usize,
|
block_count: usize,
|
||||||
) -> Result<(Vec<[u8; 32]>, usize), RpcError> {
|
) -> impl Send + Future<Output = Result<(Vec<[u8; 32]>, usize), RpcError>> {
|
||||||
|
async move {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct BlocksResponse {
|
struct BlocksResponse {
|
||||||
blocks: Vec<String>,
|
blocks: Vec<String>,
|
||||||
|
@ -652,11 +705,16 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
}
|
}
|
||||||
Ok((blocks, res.height))
|
Ok((blocks, res.height))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the output indexes of the specified transaction.
|
/// Get the output indexes of the specified transaction.
|
||||||
async fn get_o_indexes(&self, hash: [u8; 32]) -> Result<Vec<u64>, RpcError> {
|
fn get_o_indexes(
|
||||||
// Given the immaturity of Rust epee libraries, this is a homegrown one which is only validated
|
&self,
|
||||||
// to work against this specific function
|
hash: [u8; 32],
|
||||||
|
) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>> {
|
||||||
|
async move {
|
||||||
|
// Given the immaturity of Rust epee libraries, this is a homegrown one which is only
|
||||||
|
// validated to work against this specific function
|
||||||
|
|
||||||
// Header for EPEE, an 8-byte magic and a version
|
// Header for EPEE, an 8-byte magic and a version
|
||||||
const EPEE_HEADER: &[u8] = b"\x01\x11\x01\x01\x01\x01\x02\x01\x01";
|
const EPEE_HEADER: &[u8] = b"\x01\x11\x01\x01\x01\x01\x02\x01\x01";
|
||||||
|
@ -735,7 +793,9 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
// claim this to be a complete deserialization function
|
// claim this to be a complete deserialization function
|
||||||
// To ensure it works for this specific use case, it's best to ensure it's limited
|
// To ensure it works for this specific use case, it's best to ensure it's limited
|
||||||
// to this specific use case (ensuring we have less variables to deal with)
|
// to this specific use case (ensuring we have less variables to deal with)
|
||||||
_ => Err(io::Error::other(format!("unrecognized field in get_o_indexes: {name:?}")))?,
|
_ => {
|
||||||
|
Err(io::Error::other(format!("unrecognized field in get_o_indexes: {name:?}")))?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (expected_type != kind) || (expected_array_flag != has_array_flag) {
|
if (expected_type != kind) || (expected_array_flag != has_array_flag) {
|
||||||
let fmt_array_bool = |array_bool| if array_bool { "array" } else { "not array" };
|
let fmt_array_bool = |array_bool| if array_bool { "array" } else { "not array" };
|
||||||
|
@ -833,6 +893,7 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
})()
|
})()
|
||||||
.map_err(|e| RpcError::InvalidNode(format!("invalid binary response: {e:?}")))
|
.map_err(|e| RpcError::InvalidNode(format!("invalid binary response: {e:?}")))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for any object which can be used to select RingCT decoys.
|
/// A trait for any object which can be used to select RingCT decoys.
|
||||||
|
@ -840,25 +901,29 @@ pub trait Rpc: Sync + Clone + Debug {
|
||||||
/// An implementation is provided for any satisfier of `Rpc`. It is not recommended to use an `Rpc`
|
/// An implementation is provided for any satisfier of `Rpc`. It is not recommended to use an `Rpc`
|
||||||
/// object to satisfy this. This should be satisfied by a local store of the output distribution,
|
/// object to satisfy this. This should be satisfied by a local store of the output distribution,
|
||||||
/// both for performance and to prevent potential attacks a remote node can perform.
|
/// both for performance and to prevent potential attacks a remote node can perform.
|
||||||
#[async_trait]
|
|
||||||
pub trait DecoyRpc: Sync + Clone + Debug {
|
pub trait DecoyRpc: Sync + Clone + Debug {
|
||||||
/// Get the height the output distribution ends at.
|
/// Get the height the output distribution ends at.
|
||||||
///
|
///
|
||||||
/// This is equivalent to the hight of the blockchain it's for. This is intended to be cheaper
|
/// This is equivalent to the hight of the blockchain it's for. This is intended to be cheaper
|
||||||
/// than fetching the entire output distribution.
|
/// than fetching the entire output distribution.
|
||||||
async fn get_output_distribution_end_height(&self) -> Result<usize, RpcError>;
|
fn get_output_distribution_end_height(
|
||||||
|
&self,
|
||||||
|
) -> impl Send + Future<Output = Result<usize, RpcError>>;
|
||||||
|
|
||||||
/// Get the RingCT (zero-amount) output distribution.
|
/// Get the RingCT (zero-amount) output distribution.
|
||||||
///
|
///
|
||||||
/// `range` is in terms of block numbers. The result may be smaller than the requested range if
|
/// `range` is in terms of block numbers. The result may be smaller than the requested range if
|
||||||
/// the range starts before RingCT outputs were created on-chain.
|
/// the range starts before RingCT outputs were created on-chain.
|
||||||
async fn get_output_distribution(
|
fn get_output_distribution(
|
||||||
&self,
|
&self,
|
||||||
range: impl Send + RangeBounds<usize>,
|
range: impl Send + RangeBounds<usize>,
|
||||||
) -> Result<Vec<u64>, RpcError>;
|
) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>>;
|
||||||
|
|
||||||
/// Get the specified outputs from the RingCT (zero-amount) pool.
|
/// Get the specified outputs from the RingCT (zero-amount) pool.
|
||||||
async fn get_outs(&self, indexes: &[u64]) -> Result<Vec<OutputInformation>, RpcError>;
|
fn get_outs(
|
||||||
|
&self,
|
||||||
|
indexes: &[u64],
|
||||||
|
) -> impl Send + Future<Output = Result<Vec<OutputInformation>, RpcError>>;
|
||||||
|
|
||||||
/// Get the specified outputs from the RingCT (zero-amount) pool, but only return them if their
|
/// Get the specified outputs from the RingCT (zero-amount) pool, but only return them if their
|
||||||
/// timelock has been satisfied.
|
/// timelock has been satisfied.
|
||||||
|
@ -871,24 +936,26 @@ pub trait DecoyRpc: Sync + Clone + Debug {
|
||||||
/// used, yet the transaction's timelock is checked to be unlocked at the specified `height`.
|
/// used, yet the transaction's timelock is checked to be unlocked at the specified `height`.
|
||||||
/// This offers a deterministic decoy selection, yet is fingerprintable as time-based timelocks
|
/// This offers a deterministic decoy selection, yet is fingerprintable as time-based timelocks
|
||||||
/// aren't evaluated (and considered locked, preventing their selection).
|
/// aren't evaluated (and considered locked, preventing their selection).
|
||||||
async fn get_unlocked_outputs(
|
fn get_unlocked_outputs(
|
||||||
&self,
|
&self,
|
||||||
indexes: &[u64],
|
indexes: &[u64],
|
||||||
height: usize,
|
height: usize,
|
||||||
fingerprintable_deterministic: bool,
|
fingerprintable_deterministic: bool,
|
||||||
) -> Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError>;
|
) -> impl Send + Future<Output = Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<R: Rpc> DecoyRpc for R {
|
impl<R: Rpc> DecoyRpc for R {
|
||||||
async fn get_output_distribution_end_height(&self) -> Result<usize, RpcError> {
|
fn get_output_distribution_end_height(
|
||||||
<Self as Rpc>::get_height(self).await
|
&self,
|
||||||
|
) -> impl Send + Future<Output = Result<usize, RpcError>> {
|
||||||
|
async move { <Self as Rpc>::get_height(self).await }
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_output_distribution(
|
fn get_output_distribution(
|
||||||
&self,
|
&self,
|
||||||
range: impl Send + RangeBounds<usize>,
|
range: impl Send + RangeBounds<usize>,
|
||||||
) -> Result<Vec<u64>, RpcError> {
|
) -> impl Send + Future<Output = Result<Vec<u64>, RpcError>> {
|
||||||
|
async move {
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Debug, Deserialize)]
|
||||||
struct Distribution {
|
struct Distribution {
|
||||||
distribution: Vec<u64>,
|
distribution: Vec<u64>,
|
||||||
|
@ -904,9 +971,9 @@ impl<R: Rpc> DecoyRpc for R {
|
||||||
|
|
||||||
let from = match range.start_bound() {
|
let from = match range.start_bound() {
|
||||||
Bound::Included(from) => *from,
|
Bound::Included(from) => *from,
|
||||||
Bound::Excluded(from) => from
|
Bound::Excluded(from) => from.checked_add(1).ok_or_else(|| {
|
||||||
.checked_add(1)
|
RpcError::InternalError("range's from wasn't representable".to_string())
|
||||||
.ok_or_else(|| RpcError::InternalError("range's from wasn't representable".to_string()))?,
|
})?,
|
||||||
Bound::Unbounded => 0,
|
Bound::Unbounded => 0,
|
||||||
};
|
};
|
||||||
let to = match range.end_bound() {
|
let to = match range.end_bound() {
|
||||||
|
@ -947,8 +1014,8 @@ impl<R: Rpc> DecoyRpc for R {
|
||||||
let Distribution { start_height, mut distribution } = core::mem::take(&mut distributions[0]);
|
let Distribution { start_height, mut distribution } = core::mem::take(&mut distributions[0]);
|
||||||
// start_height is also actually a block number, and it should be at least `from`
|
// start_height is also actually a block number, and it should be at least `from`
|
||||||
// It may be after depending on when these outputs first appeared on the blockchain
|
// It may be after depending on when these outputs first appeared on the blockchain
|
||||||
// Unfortunately, we can't validate without a binary search to find the RingCT activation block
|
// Unfortunately, we can't validate without a binary search to find the RingCT activation
|
||||||
// and an iterative search from there, so we solely sanity check it
|
// block and an iterative search from there, so we solely sanity check it
|
||||||
if start_height < from {
|
if start_height < from {
|
||||||
Err(RpcError::InvalidNode(format!(
|
Err(RpcError::InvalidNode(format!(
|
||||||
"requested distribution from {from} and got from {start_height}"
|
"requested distribution from {from} and got from {start_height}"
|
||||||
|
@ -971,14 +1038,20 @@ impl<R: Rpc> DecoyRpc for R {
|
||||||
)))?;
|
)))?;
|
||||||
}
|
}
|
||||||
// Requesting to = 0 returns the distribution for the entire chain
|
// Requesting to = 0 returns the distribution for the entire chain
|
||||||
// We work-around this by requesting 0, 1 (yielding two blocks), then popping the second block
|
// We work around this by requesting 0, 1 (yielding two blocks), then popping the second
|
||||||
|
// block
|
||||||
if zero_zero_case {
|
if zero_zero_case {
|
||||||
distribution.pop();
|
distribution.pop();
|
||||||
}
|
}
|
||||||
Ok(distribution)
|
Ok(distribution)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_outs(&self, indexes: &[u64]) -> Result<Vec<OutputInformation>, RpcError> {
|
fn get_outs(
|
||||||
|
&self,
|
||||||
|
indexes: &[u64],
|
||||||
|
) -> impl Send + Future<Output = Result<Vec<OutputInformation>, RpcError>> {
|
||||||
|
async move {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct OutputResponse {
|
struct OutputResponse {
|
||||||
height: usize,
|
height: usize,
|
||||||
|
@ -1040,13 +1113,15 @@ impl<R: Rpc> DecoyRpc for R {
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_unlocked_outputs(
|
fn get_unlocked_outputs(
|
||||||
&self,
|
&self,
|
||||||
indexes: &[u64],
|
indexes: &[u64],
|
||||||
height: usize,
|
height: usize,
|
||||||
fingerprintable_deterministic: bool,
|
fingerprintable_deterministic: bool,
|
||||||
) -> Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError> {
|
) -> impl Send + Future<Output = Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError>> {
|
||||||
|
async move {
|
||||||
let outs = self.get_outs(indexes).await?;
|
let outs = self.get_outs(indexes).await?;
|
||||||
|
|
||||||
// Only need to fetch txs to do deterministic check on timelock
|
// Only need to fetch txs to do deterministic check on timelock
|
||||||
|
@ -1061,8 +1136,8 @@ impl<R: Rpc> DecoyRpc for R {
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, out)| {
|
.map(|(i, out)| {
|
||||||
// Allow keys to be invalid, though if they are, return None to trigger selection of a new
|
// Allow keys to be invalid, though if they are, return None to trigger selection of a
|
||||||
// decoy
|
// new decoy
|
||||||
// Only valid keys can be used in CLSAG proofs, hence the need for re-selection, yet
|
// Only valid keys can be used in CLSAG proofs, hence the need for re-selection, yet
|
||||||
// invalid keys may honestly exist on the blockchain
|
// invalid keys may honestly exist on the blockchain
|
||||||
let Some(key) = out.key.decompress() else {
|
let Some(key) = out.key.decompress() else {
|
||||||
|
@ -1071,11 +1146,13 @@ impl<R: Rpc> DecoyRpc for R {
|
||||||
Ok(Some([key, out.commitment]).filter(|_| {
|
Ok(Some([key, out.commitment]).filter(|_| {
|
||||||
if fingerprintable_deterministic {
|
if fingerprintable_deterministic {
|
||||||
// https://github.com/monero-project/monero/blob
|
// https://github.com/monero-project/monero/blob
|
||||||
// /cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_core/blockchain.cpp#L90
|
// /cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_core
|
||||||
|
// /blockchain.cpp#L90
|
||||||
const ACCEPTED_TIMELOCK_DELTA: usize = 1;
|
const ACCEPTED_TIMELOCK_DELTA: usize = 1;
|
||||||
|
|
||||||
// https://github.com/monero-project/monero/blob
|
// https://github.com/monero-project/monero/blob
|
||||||
// /cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_core/blockchain.cpp#L3836
|
// /cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_core
|
||||||
|
// /blockchain.cpp#L3836
|
||||||
((out.height + DEFAULT_LOCK_WINDOW) <= height) &&
|
((out.height + DEFAULT_LOCK_WINDOW) <= height) &&
|
||||||
(Timelock::Block(height - 1 + ACCEPTED_TIMELOCK_DELTA) >=
|
(Timelock::Block(height - 1 + ACCEPTED_TIMELOCK_DELTA) >=
|
||||||
txs[i].prefix().additional_timelock)
|
txs[i].prefix().additional_timelock)
|
||||||
|
@ -1086,4 +1163,5 @@ impl<R: Rpc> DecoyRpc for R {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue