mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Begin work on the new RPC for the new node
This commit is contained in:
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@@ -109,4 +109,5 @@ jobs:
|
|||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-bitcoin
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-bitcoin
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-ethereum
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-ethereum
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-monero
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-monero
|
||||||
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-serai
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ members = [
|
|||||||
"substrate/client/bitcoin",
|
"substrate/client/bitcoin",
|
||||||
"substrate/client/ethereum",
|
"substrate/client/ethereum",
|
||||||
"substrate/client/monero",
|
"substrate/client/monero",
|
||||||
|
"substrate/client/serai",
|
||||||
"substrate/client",
|
"substrate/client",
|
||||||
|
|
||||||
"orchestration",
|
"orchestration",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "serai-client-bitcoin"
|
name = "serai-client-bitcoin"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Bitcoin client library for the Serai network"
|
description = "A client for the Serai network's Bitcoin functionality"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/bitcoin"
|
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/bitcoin"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["serai"]
|
keywords = ["serai", "bitcoin"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.85"
|
rust-version = "1.85"
|
||||||
|
|
||||||
|
|||||||
3
substrate/client/bitcoin/README.md
Normal file
3
substrate/client/bitcoin/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# serai-client Bitcoin
|
||||||
|
|
||||||
|
A client for the Serai network's Bitcoin functionality.
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
#![no_std]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use core::{str::FromStr, fmt};
|
use core::{str::FromStr, fmt};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "serai-client-ethereum"
|
name = "serai-client-ethereum"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Ethereum client library for the Serai network"
|
description = "A client for the Serai network's Ethereum functionality"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/ethereum"
|
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/ethereum"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["serai"]
|
keywords = ["serai", "ethereum"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.85"
|
rust-version = "1.85"
|
||||||
|
|
||||||
|
|||||||
3
substrate/client/ethereum/README.md
Normal file
3
substrate/client/ethereum/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# serai-client Ethereum
|
||||||
|
|
||||||
|
A client for the Serai network's Ethereum functionality.
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
#![no_std]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
use std_shims::{vec::Vec, io::Read};
|
use std_shims::{vec::Vec, io::Read};
|
||||||
@@ -12,6 +15,7 @@ use serai_primitives::address::ExternalAddress;
|
|||||||
/// Payments to an address with a gas limit which exceed this value will be dropped entirely.
|
/// Payments to an address with a gas limit which exceed this value will be dropped entirely.
|
||||||
pub const ADDRESS_GAS_LIMIT: u32 = 950_000;
|
pub const ADDRESS_GAS_LIMIT: u32 = 950_000;
|
||||||
|
|
||||||
|
/// A contract to deploy, enabling executing arbitrary code.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub struct ContractDeployment {
|
pub struct ContractDeployment {
|
||||||
/// The gas limit to use for this contract's execution.
|
/// The gas limit to use for this contract's execution.
|
||||||
@@ -26,8 +30,8 @@ pub struct ContractDeployment {
|
|||||||
code: Vec<u8>,
|
code: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A contract to deploy, enabling executing arbitrary code.
|
|
||||||
impl ContractDeployment {
|
impl ContractDeployment {
|
||||||
|
/// Create a new `ContractDeployment`.
|
||||||
pub fn new(gas_limit: u32, code: Vec<u8>) -> Option<Self> {
|
pub fn new(gas_limit: u32, code: Vec<u8>) -> Option<Self> {
|
||||||
// Check the gas limit is less the address gas limit
|
// Check the gas limit is less the address gas limit
|
||||||
if gas_limit > ADDRESS_GAS_LIMIT {
|
if gas_limit > ADDRESS_GAS_LIMIT {
|
||||||
@@ -44,9 +48,11 @@ impl ContractDeployment {
|
|||||||
Some(Self { gas_limit, code })
|
Some(Self { gas_limit, code })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The gas limit to use when deploying (and executing) this contract.
|
||||||
pub fn gas_limit(&self) -> u32 {
|
pub fn gas_limit(&self) -> u32 {
|
||||||
self.gas_limit
|
self.gas_limit
|
||||||
}
|
}
|
||||||
|
/// The code for the contract to deploy.
|
||||||
pub fn code(&self) -> &[u8] {
|
pub fn code(&self) -> &[u8] {
|
||||||
&self.code
|
&self.code
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "serai-client-monero"
|
name = "serai-client-monero"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Monero client library for the Serai network"
|
description = "A client for the Serai network's Monero functionality"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/monero"
|
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/monero"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
keywords = ["serai"]
|
keywords = ["serai", "monero"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.85"
|
rust-version = "1.85"
|
||||||
|
|
||||||
|
|||||||
3
substrate/client/monero/README.md
Normal file
3
substrate/client/monero/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# serai-client Monero
|
||||||
|
|
||||||
|
A client for the Serai network's Monero functionality.
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use core::{str::FromStr, fmt};
|
use core::{str::FromStr, fmt};
|
||||||
|
|
||||||
use dalek_ff_group::{EdwardsPoint, Ed25519};
|
use dalek_ff_group::{EdwardsPoint, Ed25519};
|
||||||
|
|||||||
29
substrate/client/serai/Cargo.toml
Normal file
29
substrate/client/serai/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "serai-client-serai"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A client for the Serai network itself"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/serai"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
keywords = ["serai"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.85"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = { version = "2", default-features = false, features = ["std"] }
|
||||||
|
core-json-traits = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||||
|
core-json-derive = { version = "0.4", default-features = false }
|
||||||
|
simple-request = { path = "../../../common/request", version = "0.3" }
|
||||||
|
|
||||||
|
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||||
|
borsh = { version = "1", default-features = false, features = ["std"] }
|
||||||
|
serai-abi = { path = "../../abi", version = "0.1" }
|
||||||
|
|
||||||
|
async-lock = "3"
|
||||||
21
substrate/client/serai/LICENSE
Normal file
21
substrate/client/serai/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022-2025 Luke Parker
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
3
substrate/client/serai/README.md
Normal file
3
substrate/client/serai/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# serai-client Serai
|
||||||
|
|
||||||
|
A client for the Serai network itself.
|
||||||
140
substrate/client/serai/src/lib.rs
Normal file
140
substrate/client/serai/src/lib.rs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
use core_json_traits::{JsonDeserialize, JsonStructure};
|
||||||
|
use core_json_derive::JsonDeserialize;
|
||||||
|
use simple_request::{hyper, Request, TokioClient};
|
||||||
|
|
||||||
|
use borsh::BorshDeserialize;
|
||||||
|
pub use serai_abi as abi;
|
||||||
|
use abi::{primitives::network_id::ExternalNetworkId, Event};
|
||||||
|
|
||||||
|
use async_lock::RwLock;
|
||||||
|
|
||||||
|
/// An error from the RPC.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum RpcError {
|
||||||
|
/// An internal error occured.
|
||||||
|
#[error("internal error: {0}")]
|
||||||
|
InternalError(String),
|
||||||
|
/// A failure with the connection occurred.
|
||||||
|
#[error("failed to communicate with serai")]
|
||||||
|
ConnectionError,
|
||||||
|
/// The node provided an invalid response.
|
||||||
|
#[error("node is faulty: {0}")]
|
||||||
|
InvalidNode(String),
|
||||||
|
/// The response contained an error.
|
||||||
|
#[error("error in response: {0}")]
|
||||||
|
ErrorInResponse(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An RPC client to a Serai node.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Serai {
|
||||||
|
url: String,
|
||||||
|
client: TokioClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An RPC client to a Serai node, scoped to a specific block.
|
||||||
|
pub struct TemporalSerai<'a> {
|
||||||
|
serai: &'a Serai,
|
||||||
|
block: [u8; 32],
|
||||||
|
events: RwLock<Option<Vec<Event>>>,
|
||||||
|
}
|
||||||
|
impl Clone for TemporalSerai<'_> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { serai: self.serai, block: self.block, events: RwLock::new(None) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serai {
|
||||||
|
async fn call<ResponseValue: Default + JsonDeserialize>(
|
||||||
|
&self,
|
||||||
|
method: &str,
|
||||||
|
params: &str,
|
||||||
|
) -> Result<ResponseValue, RpcError> {
|
||||||
|
let request =
|
||||||
|
format!(r#"{{ "jsonrpc": "2.0", "id": 0, "method": {method}, "params": {params} }}"#);
|
||||||
|
let request = hyper::Request::post(&self.url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(request.as_bytes().to_vec().into())
|
||||||
|
.map_err(|e| RpcError::InternalError(format!("{e:?}")))?;
|
||||||
|
|
||||||
|
#[derive(Default, JsonDeserialize)]
|
||||||
|
pub struct Error {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
#[derive(Default, JsonDeserialize)]
|
||||||
|
struct Response<ResponseValue: Default + JsonDeserialize> {
|
||||||
|
result: Option<ResponseValue>,
|
||||||
|
error: Option<Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response_reader = self
|
||||||
|
.client
|
||||||
|
.request(request)
|
||||||
|
.await
|
||||||
|
.map_err(|_| RpcError::ConnectionError)?
|
||||||
|
.body()
|
||||||
|
.await
|
||||||
|
.map_err(|_| RpcError::ConnectionError)?;
|
||||||
|
let mut response_vec = Vec::with_capacity(1024);
|
||||||
|
response_reader.read_to_end(&mut response_vec).map_err(|_| {
|
||||||
|
RpcError::InternalError("couldn't read response from `simple-request` into `Vec`".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// TODO: Map `std::io::Read` into `core_json::Read` with an adapter
|
||||||
|
let response = Response::<ResponseValue>::deserialize_structure::<
|
||||||
|
_,
|
||||||
|
core_json_traits::ConstStack<32>,
|
||||||
|
>(response_vec.as_slice())
|
||||||
|
.map_err(|e| RpcError::InvalidNode(format!("{e:?}")))?;
|
||||||
|
match response {
|
||||||
|
Response { result: Some(result), error: None } => Ok(result),
|
||||||
|
Response { result: None, error: Some(error) } => {
|
||||||
|
Err(RpcError::ErrorInResponse(error.message))
|
||||||
|
}
|
||||||
|
Response { result: Some(_), error: Some(_) } | Response { result: None, error: None } => {
|
||||||
|
Err(RpcError::InvalidNode(
|
||||||
|
"node didn't exclusively provide either `result` or `error`".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new RPC client.
|
||||||
|
pub fn new(url: String) -> Result<Self, RpcError> {
|
||||||
|
let client = TokioClient::with_connection_pool().map_err(|_| RpcError::ConnectionError)?;
|
||||||
|
Ok(Serai { url, client })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch a block from the Serai blockchain.
|
||||||
|
pub async fn block(&self, hash: [u8; 32]) -> Result<serai_abi::Block, RpcError> {
|
||||||
|
let bin: String = self.call("serai_block", &format!("[{}]", hex::encode(hash))).await?;
|
||||||
|
serai_abi::Block::deserialize(
|
||||||
|
&mut hex::decode(&bin)
|
||||||
|
.map_err(|_| RpcError::InvalidNode("node returned non-hex-encoded block".to_string()))?
|
||||||
|
.as_slice(),
|
||||||
|
)
|
||||||
|
.map_err(|_| RpcError::InvalidNode("node returned invalid block".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the P2P addresses for the validators of the specified network.
|
||||||
|
pub async fn p2p_validators(&self, network: ExternalNetworkId) -> Result<Vec<String>, RpcError> {
|
||||||
|
self
|
||||||
|
.call(
|
||||||
|
"p2p_validators",
|
||||||
|
match network {
|
||||||
|
ExternalNetworkId::Bitcoin => "[bitcoin]",
|
||||||
|
ExternalNetworkId::Ethereum => "[ethereum]",
|
||||||
|
ExternalNetworkId::Monero => "[monero]",
|
||||||
|
_ => Err(RpcError::InternalError("unrecognized external network ID".to_string()))?,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
#[cfg(feature = "networks")]
|
#[cfg(feature = "bitcoin")]
|
||||||
pub mod networks;
|
pub use serai_client_bitcoin as bitcoin;
|
||||||
|
#[cfg(feature = "ethereum")]
|
||||||
|
pub mod serai_client_ethereum as ethereum;
|
||||||
|
#[cfg(feature = "monero")]
|
||||||
|
pub mod serai_client_monero as monero;
|
||||||
|
|
||||||
#[cfg(feature = "serai")]
|
#[cfg(feature = "serai")]
|
||||||
mod serai;
|
pub use serai_client_serai as serai;
|
||||||
#[cfg(feature = "serai")]
|
|
||||||
pub use serai::*;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "serai"))]
|
|
||||||
pub use serai_abi::primitives;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
#[cfg(feature = "bitcoin")]
|
|
||||||
pub use serai_client_bitcoin;
|
|
||||||
|
|
||||||
#[cfg(feature = "ethereum")]
|
|
||||||
pub mod serai_client_ethereum;
|
|
||||||
|
|
||||||
#[cfg(feature = "monero")]
|
|
||||||
pub mod serai_client_monero;
|
|
||||||
52
substrate/node/src/rpc/blockchain.rs
Normal file
52
substrate/node/src/rpc/blockchain.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use std::{sync::Arc, ops::Deref, collections::HashSet};
|
||||||
|
|
||||||
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
|
use sp_core::Encode;
|
||||||
|
use sp_blockchain::{Error as BlockchainError, HeaderMetadata, HeaderBackend};
|
||||||
|
use sp_block_builder::BlockBuilder;
|
||||||
|
use sp_api::ProvideRuntimeApi;
|
||||||
|
|
||||||
|
use serai_abi::{primitives::prelude::*, SubstrateBlock as Block};
|
||||||
|
use serai_runtime::*;
|
||||||
|
|
||||||
|
use jsonrpsee::RpcModule;
|
||||||
|
|
||||||
|
use sc_client_api::BlockBackend;
|
||||||
|
|
||||||
|
pub(crate) fn module<
|
||||||
|
C: 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ HeaderMetadata<Block, Error = BlockchainError>
|
||||||
|
+ HeaderBackend<Block>
|
||||||
|
+ BlockBackend<Block>
|
||||||
|
+ ProvideRuntimeApi<Block>,
|
||||||
|
>(
|
||||||
|
client: Arc<C>,
|
||||||
|
) -> Result<RpcModule<impl 'static + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let mut module = RpcModule::new(client);
|
||||||
|
module.register_async_method("serai_block", |params, client, _ext| async move {
|
||||||
|
let [block_hash]: [String; 1] = params.parse()?;
|
||||||
|
let Some(block_hash) = hex::decode(&block_hash).ok().and_then(|bytes| {
|
||||||
|
<[u8; 32]>::try_from(bytes.as_slice())
|
||||||
|
.map(<Block as sp_runtime::traits::Block>::Hash::from)
|
||||||
|
.ok()
|
||||||
|
}) else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-1,
|
||||||
|
"requested block hash wasn't a valid hash",
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let Some(block) = client.block(block_hash).ok().flatten() else {
|
||||||
|
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-2,
|
||||||
|
"couldn't find requested block",
|
||||||
|
Option::<()>::None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(hex::encode(block.block.encode()))
|
||||||
|
})?;
|
||||||
|
Ok(module)
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
#![expect(unused_imports)]
|
|
||||||
|
|
||||||
use std::{sync::Arc, ops::Deref, collections::HashSet};
|
use std::{sync::Arc, ops::Deref, collections::HashSet};
|
||||||
|
|
||||||
use rand_core::{RngCore, OsRng};
|
use rand_core::{RngCore, OsRng};
|
||||||
@@ -19,6 +17,9 @@ use jsonrpsee::RpcModule;
|
|||||||
use sc_client_api::BlockBackend;
|
use sc_client_api::BlockBackend;
|
||||||
use sc_transaction_pool_api::TransactionPool;
|
use sc_transaction_pool_api::TransactionPool;
|
||||||
|
|
||||||
|
mod blockchain;
|
||||||
|
mod p2p_validators;
|
||||||
|
|
||||||
pub struct FullDeps<C, P> {
|
pub struct FullDeps<C, P> {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub client: Arc<C>,
|
pub client: Arc<C>,
|
||||||
@@ -38,74 +39,14 @@ pub fn create_full<
|
|||||||
>(
|
>(
|
||||||
deps: FullDeps<C, P>,
|
deps: FullDeps<C, P>,
|
||||||
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let mut module = RpcModule::new(());
|
|
||||||
let FullDeps { id, client, pool, authority_discovery } = deps;
|
let FullDeps { id, client, pool, authority_discovery } = deps;
|
||||||
|
|
||||||
|
let mut root = RpcModule::new(());
|
||||||
|
root.merge(blockchain::module(client.clone())?)?;
|
||||||
if let Some(authority_discovery) = authority_discovery {
|
if let Some(authority_discovery) = authority_discovery {
|
||||||
let mut authority_discovery_module =
|
root.merge(p2p_validators::module(id, client, authority_discovery)?)?;
|
||||||
RpcModule::new((id, client.clone(), RwLock::new(authority_discovery)));
|
|
||||||
authority_discovery_module.register_async_method(
|
|
||||||
"p2p_validators",
|
|
||||||
|params, context, _ext| async move {
|
|
||||||
let [network]: [String; 1] = params.parse()?;
|
|
||||||
let network = match network.to_lowercase().as_str() {
|
|
||||||
"serai" => NetworkId::Serai,
|
|
||||||
"bitcoin" => ExternalNetworkId::Bitcoin.into(),
|
|
||||||
"ethereum" => ExternalNetworkId::Ethereum.into(),
|
|
||||||
"monero" => ExternalNetworkId::Monero.into(),
|
|
||||||
_ => Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
|
||||||
-1,
|
|
||||||
"network to fetch the `p2p_validators` of was unrecognized".to_string(),
|
|
||||||
Option::<()>::None,
|
|
||||||
))?,
|
|
||||||
};
|
|
||||||
let (id, client, authority_discovery) = &*context;
|
|
||||||
let latest_block = client.info().best_hash;
|
|
||||||
|
|
||||||
let validators = client.runtime_api().validators(latest_block, network).map_err(|_| {
|
|
||||||
jsonrpsee::types::error::ErrorObjectOwned::owned(
|
|
||||||
-2,
|
|
||||||
format!(
|
|
||||||
"couldn't get validators from the latest block, which is likely a fatal bug. {}",
|
|
||||||
"please report this at https://github.com/serai-dex/serai",
|
|
||||||
),
|
|
||||||
Option::<()>::None,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let validators = match validators {
|
|
||||||
Ok(validators) => validators,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
|
||||||
// Always return the protocol's bootnodes
|
|
||||||
let mut all_p2p_addresses = crate::chain_spec::bootnode_multiaddrs(id);
|
|
||||||
// Additionally returns validators found over the DHT
|
|
||||||
for validator in validators {
|
|
||||||
let mut returned_addresses = authority_discovery
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.get_addresses_by_authority_id(validator.into())
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(HashSet::new)
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
// Randomly select an address
|
|
||||||
// There should be one, there may be two if their IP address changed, and more should only
|
|
||||||
// occur if they have multiple proxies/an IP address changing frequently/some issue
|
|
||||||
// preventing consistent self-identification
|
|
||||||
// It isn't beneficial to use multiple addresses for a single peer here
|
|
||||||
if !returned_addresses.is_empty() {
|
|
||||||
all_p2p_addresses.push(
|
|
||||||
returned_addresses
|
|
||||||
.remove(usize::try_from(OsRng.next_u64() >> 32).unwrap() % returned_addresses.len())
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(all_p2p_addresses)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
module.merge(authority_discovery_module)?;
|
|
||||||
}
|
}
|
||||||
|
Ok(root)
|
||||||
|
|
||||||
/* TODO
|
/* TODO
|
||||||
use ciphersuite::{GroupIo, WithPreferredHash};
|
use ciphersuite::{GroupIo, WithPreferredHash};
|
||||||
@@ -205,31 +146,4 @@ pub fn create_full<
|
|||||||
|
|
||||||
module.merge(serai_json_module)?;
|
module.merge(serai_json_module)?;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let mut block_bin_module = RpcModule::new(client);
|
|
||||||
block_bin_module.register_async_method("serai_block", |params, client, _ext| async move {
|
|
||||||
let [block_hash]: [String; 1] = params.parse()?;
|
|
||||||
let Some(block_hash) = hex::decode(&block_hash).ok().and_then(|bytes| {
|
|
||||||
<[u8; 32]>::try_from(bytes.as_slice())
|
|
||||||
.map(<Block as sp_runtime::traits::Block>::Hash::from)
|
|
||||||
.ok()
|
|
||||||
}) else {
|
|
||||||
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
|
||||||
-1,
|
|
||||||
"requested block hash wasn't a valid hash",
|
|
||||||
Option::<()>::None,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let Some(block) = client.block(block_hash).ok().flatten() else {
|
|
||||||
return Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
|
||||||
-1,
|
|
||||||
"couldn't find requested block",
|
|
||||||
Option::<()>::None,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
Ok(hex::encode(block.block.encode()))
|
|
||||||
})?;
|
|
||||||
module.merge(block_bin_module)?;
|
|
||||||
|
|
||||||
Ok(module)
|
|
||||||
}
|
}
|
||||||
86
substrate/node/src/rpc/p2p_validators.rs
Normal file
86
substrate/node/src/rpc/p2p_validators.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
use std::{sync::Arc, ops::Deref, collections::HashSet};
|
||||||
|
|
||||||
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
|
use sp_core::Encode;
|
||||||
|
use sp_blockchain::{Error as BlockchainError, HeaderBackend};
|
||||||
|
use sp_api::ProvideRuntimeApi;
|
||||||
|
|
||||||
|
use serai_abi::{primitives::prelude::*, SubstrateBlock as Block};
|
||||||
|
use serai_runtime::*;
|
||||||
|
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use jsonrpsee::RpcModule;
|
||||||
|
|
||||||
|
pub(crate) fn module<
|
||||||
|
C: 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ HeaderBackend<Block>
|
||||||
|
+ ProvideRuntimeApi<Block, Api: serai_runtime::SeraiApi<Block>>,
|
||||||
|
>(
|
||||||
|
id: String,
|
||||||
|
client: Arc<C>,
|
||||||
|
authority_discovery: sc_authority_discovery::Service,
|
||||||
|
) -> Result<RpcModule<impl 'static + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let mut module = RpcModule::new((id, client, RwLock::new(authority_discovery)));
|
||||||
|
module.register_async_method("p2p_validators", |params, context, _ext| async move {
|
||||||
|
let [network]: [String; 1] = params.parse()?;
|
||||||
|
let network = match network.to_lowercase().as_str() {
|
||||||
|
"serai" => NetworkId::Serai,
|
||||||
|
"bitcoin" => ExternalNetworkId::Bitcoin.into(),
|
||||||
|
"ethereum" => ExternalNetworkId::Ethereum.into(),
|
||||||
|
"monero" => ExternalNetworkId::Monero.into(),
|
||||||
|
_ => Err(jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-1,
|
||||||
|
"network to fetch the `p2p_validators` of was unrecognized".to_string(),
|
||||||
|
Option::<()>::None,
|
||||||
|
))?,
|
||||||
|
};
|
||||||
|
let (id, client, authority_discovery) = &*context;
|
||||||
|
let latest_block = client.info().best_hash;
|
||||||
|
|
||||||
|
let validators = client.runtime_api().validators(latest_block, network).map_err(|_| {
|
||||||
|
jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
|
-2,
|
||||||
|
format!(
|
||||||
|
"couldn't get validators from the latest block, which is likely a fatal bug. {}",
|
||||||
|
"please report this at https://github.com/serai-dex/serai",
|
||||||
|
),
|
||||||
|
Option::<()>::None,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let validators = match validators {
|
||||||
|
Ok(validators) => validators,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
// Always return the protocol's bootnodes
|
||||||
|
let mut all_p2p_addresses = crate::chain_spec::bootnode_multiaddrs(id);
|
||||||
|
// Additionally returns validators found over the DHT
|
||||||
|
for validator in validators {
|
||||||
|
let mut returned_addresses = authority_discovery
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.get_addresses_by_authority_id(validator.into())
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(HashSet::new)
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// Randomly select an address
|
||||||
|
// There should be one, there may be two if their IP address changed, and more should only
|
||||||
|
// occur if they have multiple proxies/an IP address changing frequently/some issue
|
||||||
|
// preventing consistent self-identification
|
||||||
|
// It isn't beneficial to use multiple addresses for a single peer here
|
||||||
|
if !returned_addresses.is_empty() {
|
||||||
|
all_p2p_addresses.push(
|
||||||
|
returned_addresses
|
||||||
|
.remove(usize::try_from(OsRng.next_u64() >> 32).unwrap() % returned_addresses.len())
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(all_p2p_addresses)
|
||||||
|
})?;
|
||||||
|
Ok(module)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user