2025-01-30 12:23:03 +03:00
|
|
|
use std::{sync::Arc, ops::Deref, collections::HashSet};
|
2022-07-15 00:05:00 -04:00
|
|
|
|
2024-03-23 00:09:23 -04:00
|
|
|
use rand_core::{RngCore, OsRng};
|
|
|
|
|
|
2022-07-15 00:05:00 -04:00
|
|
|
use sp_blockchain::{Error as BlockchainError, HeaderBackend, HeaderMetadata};
|
|
|
|
|
use sp_block_builder::BlockBuilder;
|
|
|
|
|
use sp_api::ProvideRuntimeApi;
|
|
|
|
|
|
2023-01-28 01:47:13 -05:00
|
|
|
use serai_runtime::{
|
2025-01-30 12:23:03 +03:00
|
|
|
in_instructions::primitives::Shorthand,
|
|
|
|
|
primitives::{ExternalNetworkId, NetworkId, PublicKey, SubstrateAmount, QuotePriceParams},
|
|
|
|
|
validator_sets::ValidatorSetsApi,
|
|
|
|
|
dex::DexApi,
|
|
|
|
|
Block, Nonce,
|
2023-01-28 01:47:13 -05:00
|
|
|
};
|
2022-07-15 00:05:00 -04:00
|
|
|
|
2023-12-22 21:09:18 -05:00
|
|
|
use tokio::sync::RwLock;
|
|
|
|
|
|
2025-01-30 12:23:03 +03:00
|
|
|
use jsonrpsee::{RpcModule, core::Error};
|
|
|
|
|
use scale::Encode;
|
2023-12-22 21:09:18 -05:00
|
|
|
|
2023-01-20 11:00:18 -05:00
|
|
|
pub use sc_rpc_api::DenyUnsafe;
|
|
|
|
|
use sc_transaction_pool_api::TransactionPool;
|
2022-07-15 00:05:00 -04:00
|
|
|
|
|
|
|
|
pub struct FullDeps<C, P> {
|
2024-04-12 00:38:40 -04:00
|
|
|
pub id: String,
|
2022-07-15 00:05:00 -04:00
|
|
|
pub client: Arc<C>,
|
|
|
|
|
pub pool: Arc<P>,
|
2022-07-15 01:26:07 -04:00
|
|
|
pub deny_unsafe: DenyUnsafe,
|
2023-12-22 21:09:18 -05:00
|
|
|
pub authority_discovery: Option<sc_authority_discovery::Service>,
|
2022-07-15 00:05:00 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn create_full<
|
2022-07-15 01:26:07 -04:00
|
|
|
C: ProvideRuntimeApi<Block>
|
|
|
|
|
+ HeaderBackend<Block>
|
|
|
|
|
+ HeaderMetadata<Block, Error = BlockchainError>
|
|
|
|
|
+ Send
|
|
|
|
|
+ Sync
|
|
|
|
|
+ 'static,
|
|
|
|
|
P: TransactionPool + 'static,
|
2022-07-15 00:05:00 -04:00
|
|
|
>(
|
2022-07-15 01:26:07 -04:00
|
|
|
deps: FullDeps<C, P>,
|
2022-07-15 00:05:00 -04:00
|
|
|
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
|
2022-07-15 01:26:07 -04:00
|
|
|
where
|
2023-07-18 23:52:37 -04:00
|
|
|
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, PublicKey, Nonce>
|
2023-01-28 01:47:13 -05:00
|
|
|
+ pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, SubstrateAmount>
|
2025-01-30 12:23:03 +03:00
|
|
|
+ ValidatorSetsApi<Block>
|
|
|
|
|
+ DexApi<Block>
|
2022-07-17 17:18:56 -04:00
|
|
|
+ BlockBuilder<Block>,
|
2022-07-15 01:26:07 -04:00
|
|
|
{
|
2022-07-15 00:05:00 -04:00
|
|
|
use substrate_frame_rpc_system::{System, SystemApiServer};
|
2022-07-16 21:06:54 -04:00
|
|
|
use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer};
|
2025-01-30 12:23:03 +03:00
|
|
|
use ciphersuite::{Ciphersuite, Ed25519, Secp256k1};
|
|
|
|
|
use bitcoin_serai::{bitcoin, crypto::x_only};
|
2022-07-15 00:05:00 -04:00
|
|
|
|
|
|
|
|
let mut module = RpcModule::new(());
|
2024-04-12 00:38:40 -04:00
|
|
|
let FullDeps { id, client, pool, deny_unsafe, authority_discovery } = deps;
|
2022-07-15 00:05:00 -04:00
|
|
|
|
2022-07-22 02:34:36 -04:00
|
|
|
module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?;
|
2023-12-22 21:09:18 -05:00
|
|
|
module.merge(TransactionPayment::new(client.clone()).into_rpc())?;
|
|
|
|
|
|
|
|
|
|
if let Some(authority_discovery) = authority_discovery {
|
2024-04-12 00:38:40 -04:00
|
|
|
let mut authority_discovery_module =
|
2025-01-30 12:23:03 +03:00
|
|
|
RpcModule::new((id, client.clone(), RwLock::new(authority_discovery)));
|
2023-12-22 21:09:18 -05:00
|
|
|
authority_discovery_module.register_async_method(
|
|
|
|
|
"p2p_validators",
|
|
|
|
|
|params, context| async move {
|
|
|
|
|
let network: NetworkId = params.parse()?;
|
2024-04-12 00:38:40 -04:00
|
|
|
let (id, client, authority_discovery) = &*context;
|
2023-12-22 21:09:18 -05:00
|
|
|
let latest_block = client.info().best_hash;
|
|
|
|
|
|
|
|
|
|
let validators = client.runtime_api().validators(latest_block, network).map_err(|_| {
|
2025-01-30 12:23:03 +03:00
|
|
|
Error::to_call_error(std::io::Error::other(format!(
|
2023-12-22 21:09:18 -05:00
|
|
|
"couldn't get validators from the latest block, which is likely a fatal bug. {}",
|
2025-01-05 00:55:25 -05:00
|
|
|
"please report this at https://github.com/serai-dex/serai/issues",
|
2023-12-22 21:09:18 -05:00
|
|
|
)))
|
|
|
|
|
})?;
|
2024-04-12 00:38:40 -04:00
|
|
|
// Always return the protocol's bootnodes
|
|
|
|
|
let mut all_p2p_addresses = crate::chain_spec::bootnode_multiaddrs(id);
|
|
|
|
|
// Additionally returns validators found over the DHT
|
2023-12-22 21:09:18 -05:00
|
|
|
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)
|
2024-03-23 00:09:23 -04:00
|
|
|
.into_iter()
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
// Randomly select an address
|
2023-12-22 21:09:18 -05:00
|
|
|
// 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
|
2024-03-23 00:09:23 -04:00
|
|
|
if !returned_addresses.is_empty() {
|
|
|
|
|
all_p2p_addresses.push(
|
|
|
|
|
returned_addresses.remove(
|
|
|
|
|
usize::try_from(OsRng.next_u64() >> 32).unwrap() % returned_addresses.len(),
|
|
|
|
|
),
|
|
|
|
|
);
|
2023-12-22 21:09:18 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(all_p2p_addresses)
|
|
|
|
|
},
|
|
|
|
|
)?;
|
|
|
|
|
module.merge(authority_discovery_module)?;
|
|
|
|
|
}
|
2022-07-15 00:05:00 -04:00
|
|
|
|
2025-01-30 12:23:03 +03:00
|
|
|
let mut serai_json_module = RpcModule::new(client);
|
|
|
|
|
|
|
|
|
|
// add network address rpc
|
|
|
|
|
serai_json_module.register_async_method(
|
|
|
|
|
"external_network_address",
|
|
|
|
|
|params, context| async move {
|
|
|
|
|
let network: ExternalNetworkId = params.parse()?;
|
|
|
|
|
let client = &*context;
|
|
|
|
|
let latest_block = client.info().best_hash;
|
|
|
|
|
|
|
|
|
|
let external_key = client
|
|
|
|
|
.runtime_api()
|
|
|
|
|
.external_network_key(latest_block, network)
|
|
|
|
|
.map_err(|_| Error::Custom("api call error".to_string()))?
|
|
|
|
|
.ok_or(Error::Custom("no address for the network".to_string()))?;
|
|
|
|
|
|
|
|
|
|
match network {
|
|
|
|
|
ExternalNetworkId::Bitcoin => {
|
|
|
|
|
let key = <Secp256k1 as Ciphersuite>::read_G::<&[u8]>(&mut external_key.as_slice())
|
|
|
|
|
.map_err(|_| Error::Custom("invalid key stored in db".to_string()))?;
|
|
|
|
|
|
|
|
|
|
let addr = bitcoin::Address::p2tr_tweaked(
|
|
|
|
|
bitcoin::key::TweakedPublicKey::dangerous_assume_tweaked(x_only(&key)),
|
|
|
|
|
bitcoin::address::KnownHrp::Mainnet,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(addr.to_string())
|
|
|
|
|
}
|
|
|
|
|
// We don't know the eth address before the smart contract is deployed.
|
|
|
|
|
ExternalNetworkId::Ethereum => Ok(String::new()),
|
|
|
|
|
ExternalNetworkId::Monero => {
|
|
|
|
|
let view_private = zeroize::Zeroizing::new(
|
|
|
|
|
<Ed25519 as Ciphersuite>::hash_to_F(
|
|
|
|
|
b"Serai DEX Additional Key",
|
|
|
|
|
&["Monero".as_bytes(), &0u64.to_le_bytes()].concat(),
|
|
|
|
|
)
|
|
|
|
|
.0,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let spend = <Ed25519 as Ciphersuite>::read_G::<&[u8]>(&mut external_key.as_slice())
|
|
|
|
|
.map_err(|_| Error::Custom("invalid key stored in db".to_string()))?;
|
|
|
|
|
|
2025-01-30 05:04:28 -05:00
|
|
|
let addr = monero_address::MoneroAddress::new(
|
|
|
|
|
monero_address::Network::Mainnet,
|
|
|
|
|
monero_address::AddressType::Featured {
|
2025-01-30 12:23:03 +03:00
|
|
|
subaddress: false,
|
|
|
|
|
payment_id: None,
|
|
|
|
|
guaranteed: true,
|
|
|
|
|
},
|
|
|
|
|
*spend,
|
|
|
|
|
view_private.deref() * curve25519_dalek::constants::ED25519_BASEPOINT_TABLE,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(addr.to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
// add shorthand encoding rpc
|
|
|
|
|
serai_json_module.register_async_method("encoded_shorthand", |params, _| async move {
|
|
|
|
|
// decode using serde and encode back using scale
|
|
|
|
|
let shorthand: Shorthand = params.parse()?;
|
|
|
|
|
Ok(shorthand.encode())
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// add simulating a swap path rpc
|
|
|
|
|
serai_json_module.register_async_method("quote_price", |params, context| async move {
|
|
|
|
|
let client = &*context;
|
|
|
|
|
let latest_block = client.info().best_hash;
|
|
|
|
|
let QuotePriceParams { coin1, coin2, amount, include_fee, exact_in } = params.parse()?;
|
|
|
|
|
|
|
|
|
|
let amount = if exact_in {
|
|
|
|
|
client
|
|
|
|
|
.runtime_api()
|
|
|
|
|
.quote_price_exact_tokens_for_tokens(latest_block, coin1, coin2, amount, include_fee)
|
|
|
|
|
.map_err(|_| Error::Custom("api call error".to_string()))?
|
|
|
|
|
.ok_or(Error::Custom("invalid params or empty pool".to_string()))?
|
|
|
|
|
} else {
|
|
|
|
|
client
|
|
|
|
|
.runtime_api()
|
|
|
|
|
.quote_price_tokens_for_exact_tokens(latest_block, coin1, coin2, amount, include_fee)
|
|
|
|
|
.map_err(|_| Error::Custom("api call error".to_string()))?
|
|
|
|
|
.ok_or(Error::Custom("invalid params or empty pool".to_string()))?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(amount)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
module.merge(serai_json_module)?;
|
2022-07-15 00:05:00 -04:00
|
|
|
Ok(module)
|
|
|
|
|
}
|