Begin work on the new RPC for the new node

This commit is contained in:
Luke Parker
2025-11-06 03:08:43 -05:00
parent aff2065c31
commit 1866bb7ae3
20 changed files with 378 additions and 118 deletions

View File

@@ -1,11 +1,11 @@
[package]
name = "serai-client-bitcoin"
version = "0.1.0"
description = "Bitcoin client library for the Serai network"
description = "A client for the Serai network's Bitcoin functionality"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/bitcoin"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["serai"]
keywords = ["serai", "bitcoin"]
edition = "2021"
rust-version = "1.85"

View File

@@ -0,0 +1,3 @@
# serai-client Bitcoin
A client for the Serai network's Bitcoin functionality.

View File

@@ -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};

View File

@@ -1,11 +1,11 @@
[package]
name = "serai-client-ethereum"
version = "0.1.0"
description = "Ethereum client library for the Serai network"
description = "A client for the Serai network's Ethereum functionality"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/ethereum"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["serai"]
keywords = ["serai", "ethereum"]
edition = "2021"
rust-version = "1.85"

View File

@@ -0,0 +1,3 @@
# serai-client Ethereum
A client for the Serai network's Ethereum functionality.

View File

@@ -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 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.
pub const ADDRESS_GAS_LIMIT: u32 = 950_000;
/// A contract to deploy, enabling executing arbitrary code.
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub struct ContractDeployment {
/// The gas limit to use for this contract's execution.
@@ -26,8 +30,8 @@ pub struct ContractDeployment {
code: Vec<u8>,
}
/// A contract to deploy, enabling executing arbitrary code.
impl ContractDeployment {
/// Create a new `ContractDeployment`.
pub fn new(gas_limit: u32, code: Vec<u8>) -> Option<Self> {
// Check the gas limit is less the address gas limit
if gas_limit > ADDRESS_GAS_LIMIT {
@@ -44,9 +48,11 @@ impl ContractDeployment {
Some(Self { gas_limit, code })
}
/// The gas limit to use when deploying (and executing) this contract.
pub fn gas_limit(&self) -> u32 {
self.gas_limit
}
/// The code for the contract to deploy.
pub fn code(&self) -> &[u8] {
&self.code
}

View File

@@ -1,11 +1,11 @@
[package]
name = "serai-client-monero"
version = "0.1.0"
description = "Monero client library for the Serai network"
description = "A client for the Serai network's Monero functionality"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/client/monero"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["serai"]
keywords = ["serai", "monero"]
edition = "2021"
rust-version = "1.85"

View File

@@ -0,0 +1,3 @@
# serai-client Monero
A client for the Serai network's Monero functionality.

View File

@@ -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 dalek_ff_group::{EdwardsPoint, Ed25519};

View 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"

View 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.

View File

@@ -0,0 +1,3 @@
# serai-client Serai
A client for the Serai network itself.

View 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
}
}

View File

@@ -1,13 +1,12 @@
#[cfg(feature = "networks")]
pub mod networks;
#[cfg(feature = "bitcoin")]
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")]
mod serai;
#[cfg(feature = "serai")]
pub use serai::*;
#[cfg(not(feature = "serai"))]
pub use serai_abi::primitives;
pub use serai_client_serai as serai;
#[cfg(test)]
mod tests;

View File

@@ -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;