Restore publish_transaction RPC to Serai

This commit is contained in:
Luke Parker
2025-11-16 12:50:12 -05:00
parent df4aee2d59
commit f1f166c168
4 changed files with 60 additions and 12 deletions

View File

@@ -14,18 +14,16 @@ use borsh::BorshDeserialize;
pub use serai_abi as abi;
use abi::{
primitives::{BlockHash, network_id::ExternalNetworkId},
Block, Event,
Transaction, Block, Event,
};
use async_lock::RwLock;
/// RPC client functionality for the coins module.
pub mod coins;
use coins::*;
mod coins;
pub use coins::Coins;
/// RPC client functionality for the validator sets module.
pub mod validator_sets;
use validator_sets::*;
mod validator_sets;
pub use validator_sets::ValidatorSets;
/// An error from the RPC.
#[derive(Debug, Error)]
@@ -164,6 +162,16 @@ impl Serai {
Self::block_internal(self.call("blockchain/block", &format!(r#"{{ "block": {block} }}"#))).await
}
/// Publish a transaction onto the Serai blockchain.
pub async fn publish_transaction(&self, transaction: &Transaction) -> Result<(), RpcError> {
self
.call(
"blockchain/publish_transaction",
&format!(r#"{{ "transaction": {} }}"#, hex::encode(borsh::to_vec(transaction).unwrap())),
)
.await
}
/// Scope this RPC client to the state as of a specific block.
///
/// This will yield an error if the block chosen isn't finalized. This ensures, given an honest

View File

@@ -1,4 +1,5 @@
use std::{sync::Arc, ops::Deref, collections::HashSet};
use core::{ops::Deref, future::Future};
use std::{sync::Arc, collections::HashSet};
use rand_core::{RngCore, OsRng};
@@ -8,8 +9,9 @@ use sp_consensus::BlockStatus;
use sp_block_builder::BlockBuilder;
use sp_api::ProvideRuntimeApi;
use sc_client_api::BlockBackend;
use sc_transaction_pool_api::TransactionPool;
use serai_abi::{primitives::prelude::*, SubstrateBlock as Block};
use serai_abi::{primitives::prelude::*, Transaction, SubstrateBlock as Block};
use serai_runtime::SeraiApi;
@@ -27,6 +29,7 @@ pub(crate) fn module<
+ ProvideRuntimeApi<Block, Api: SeraiApi<Block>>,
>(
client: Arc<C>,
pool: Arc<impl 'static + TransactionPool<Block = Block>>,
) -> Result<RpcModule<impl 'static + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
let mut module = RpcModule::new(client);
@@ -80,5 +83,36 @@ pub(crate) fn module<
)
})?;
module.register_async_method("blockchain/publish_transaction", move |params, client, _ext| {
let pool = pool.clone();
async move {
#[derive(sp_core::serde::Deserialize)]
#[serde(crate = "sp_core::serde")]
struct TransactionRequest {
transaction: String,
};
let Ok(transaction) = params.parse::<TransactionRequest>() else {
return Err(Error::InvalidRequest(r#"missing `string` "transaction" field"#));
};
let Ok(transaction) = hex::decode(transaction.transaction) else {
Err(Error::InvalidRequest(r#"transaction was not hex-encoded"#))?
};
let Ok(transaction) =
<Transaction as borsh::BorshDeserialize>::deserialize_reader(&mut transaction.as_slice())
else {
Err(Error::InvalidRequest(r#"transaction could not be deserialized"#))?
};
pool
.submit_one(
client.info().best_hash,
sc_transaction_pool_api::TransactionSource::External,
transaction,
)
.await
.map_err(|e| Error::InvalidTransaction(format!("{e}")))?;
Ok(())
}
})?;
Ok(module)
}

View File

@@ -36,14 +36,14 @@ pub fn create_full<
+ HeaderBackend<Block>
+ HeaderMetadata<Block, Error = BlockchainError>
+ BlockBackend<Block>,
P: 'static + TransactionPool,
P: 'static + TransactionPool<Block = Block>,
>(
deps: FullDeps<C, P>,
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>> {
let FullDeps { id, client, pool, authority_discovery } = deps;
let mut root = RpcModule::new(());
root.merge(blockchain::module(client.clone())?)?;
root.merge(blockchain::module(client.clone(), pool)?)?;
root.merge(validator_sets::module(client.clone()))?;
if let Some(authority_discovery) = authority_discovery {
root.merge(p2p_validators::module(id, client, authority_discovery)?)?;

View File

@@ -7,6 +7,7 @@ pub(super) enum Error {
Internal(&'static str),
InvalidRequest(&'static str),
InvalidStateReference,
InvalidTransaction(String),
}
impl From<Error> for jsonrpsee::types::error::ErrorObjectOwned {
@@ -19,10 +20,15 @@ impl From<Error> for jsonrpsee::types::error::ErrorObjectOwned {
jsonrpsee::types::error::ErrorObjectOwned::owned(-2, str, Option::<()>::None)
}
Error::InvalidStateReference => jsonrpsee::types::error::ErrorObjectOwned::owned(
-4,
-3,
"the block used as the reference was not locally held",
Option::<()>::None,
),
Error::InvalidTransaction(str) => jsonrpsee::types::error::ErrorObjectOwned::owned(
-4,
format!("transaction was not accepted to the mempool: {str}"),
Option::<()>::None,
),
}
}
}