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,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"
|
||||
|
||||
|
||||
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};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
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 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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
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 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")]
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user