Files
serai/substrate/node/src/rpc.rs

207 lines
7.4 KiB
Rust
Raw Normal View History

use std::{sync::Arc, ops::Deref, collections::HashSet};
use rand_core::{RngCore, OsRng};
use sp_blockchain::{Error as BlockchainError, HeaderBackend, HeaderMetadata};
use sp_block_builder::BlockBuilder;
use sp_api::ProvideRuntimeApi;
Tokens pallet (#243) * Use Monero-compatible additional TX keys This still sends a fingerprinting flare up if you send to a subaddress which needs to be fixed. Despite that, Monero no should no longer fail to scan TXs from monero-serai regarding additional keys. Previously it failed becuase we supplied one key as THE key, and n-1 as additional. Monero expects n for additional. This does correctly select when to use THE key versus when to use the additional key when sending. That removes the ability for recipients to fingerprint monero-serai by receiving to a standard address yet needing to use an additional key. * Add tokens_primitives Moves OutInstruction from in-instructions. Turns Destination into OutInstruction. * Correct in-instructions DispatchClass * Add initial tokens pallet * Don't allow pallet addresses to equal identity * Add support for InInstruction::transfer Requires a cargo update due to modifications made to serai-dex/substrate. Successfully mints a token to a SeraiAddress. * Bind InInstructions to an amount * Add a call filter to the runtime Prevents worrying about calls to the assets pallet/generally tightens things up. * Restore Destination It was meged into OutInstruction, yet it didn't make sense for OutInstruction to contain a SeraiAddress. Also deletes the excessively dated Scenarios doc. * Split PublicKey/SeraiAddress Lets us define a custom Display/ToString for SeraiAddress. Also resolves an oddity where PublicKey would be encoded as String, not [u8; 32]. * Test burning tokens/retrieving OutInstructions Modularizes processor_coinUpdates into a shared testing utility. * Misc lint * Don't use PolkadotExtrinsicParams
2023-01-28 01:47:13 -05:00
use serai_runtime::{
in_instructions::primitives::Shorthand,
primitives::{ExternalNetworkId, NetworkId, PublicKey, SubstrateAmount, QuotePriceParams},
validator_sets::ValidatorSetsApi,
dex::DexApi,
Block, Nonce,
Tokens pallet (#243) * Use Monero-compatible additional TX keys This still sends a fingerprinting flare up if you send to a subaddress which needs to be fixed. Despite that, Monero no should no longer fail to scan TXs from monero-serai regarding additional keys. Previously it failed becuase we supplied one key as THE key, and n-1 as additional. Monero expects n for additional. This does correctly select when to use THE key versus when to use the additional key when sending. That removes the ability for recipients to fingerprint monero-serai by receiving to a standard address yet needing to use an additional key. * Add tokens_primitives Moves OutInstruction from in-instructions. Turns Destination into OutInstruction. * Correct in-instructions DispatchClass * Add initial tokens pallet * Don't allow pallet addresses to equal identity * Add support for InInstruction::transfer Requires a cargo update due to modifications made to serai-dex/substrate. Successfully mints a token to a SeraiAddress. * Bind InInstructions to an amount * Add a call filter to the runtime Prevents worrying about calls to the assets pallet/generally tightens things up. * Restore Destination It was meged into OutInstruction, yet it didn't make sense for OutInstruction to contain a SeraiAddress. Also deletes the excessively dated Scenarios doc. * Split PublicKey/SeraiAddress Lets us define a custom Display/ToString for SeraiAddress. Also resolves an oddity where PublicKey would be encoded as String, not [u8; 32]. * Test burning tokens/retrieving OutInstructions Modularizes processor_coinUpdates into a shared testing utility. * Misc lint * Don't use PolkadotExtrinsicParams
2023-01-28 01:47:13 -05:00
};
use tokio::sync::RwLock;
use jsonrpsee::{RpcModule, core::Error};
use scale::Encode;
Initial In Instructions pallet and Serai client lib (#233) * Initial work on an In Inherents pallet * Add an event for when a batch is executed * Add a dummy provider for InInstructions * Add in-instructions to the node * Add the Serai runtime API to the processor * Move processor tests around * Build a subxt Client around Serai * Successfully get Batch events from Serai Renamed processor/substrate to processor/serai. * Much more robust InInstruction pallet * Implement the workaround from https://github.com/paritytech/subxt/issues/602 * Initial prototype of processor generated InInstructions * Correct PendingCoins data flow for InInstructions * Minor lint to in-instructions * Remove the global Serai connection for a partial re-impl * Correct ID handling of the processor test * Workaround the delay in the subscription * Make an unwrap an if let Some, remove old comments * Lint the processor toml * Rebase and update * Move substrate/in-instructions to substrate/in-instructions/pallet * Start an in-instructions primitives lib * Properly update processor to subxt 0.24 Also corrects failures from the rebase. * in-instructions cargo update * Implement IsFatalError * is_inherent -> true * Rename in-instructions crates and misc cleanup * Update documentation * cargo update * Misc update fixes * Replace height with block_number * Update processor src to latest subxt * Correct pipeline for InInstructions testing * Remove runtime::AccountId for serai_primitives::NativeAddress * Rewrite the in-instructions pallet Complete with respect to the currently written docs. Drops the custom serializer for just using SCALE. Makes slight tweaks as relevant. * Move instructions' InherentDataProvider to a client crate * Correct doc gen * Add serde to in-instructions-primitives * Add in-instructions-primitives to pallet * Heights -> BlockNumbers * Get batch pub test loop working * Update in instructions pallet terminology Removes the ambiguous Coin for Update. Removes pending/artificial latency for furture client work. Also moves to using serai_primitives::Coin. * Add a BlockNumber primitive * Belated cargo fmt * Further document why DifferentBatch isn't fatal * Correct processor sleeps * Remove metadata at compile time, add test framework for Serai nodes * Remove manual RPC client * Simplify update test * Improve re-exporting behavior of serai-runtime It now re-exports all pallets underneath it. * Add a function to get storage values to the Serai RPC * Update substrate/ to latest substrate * Create a dedicated crate for the Serai RPC * Remove unused dependencies in substrate/ * Remove unused dependencies in coins/ Out of scope for this branch, just minor and path of least resistance. * Use substrate/serai/client for the Serai RPC lib It's a bit out of place, since these client folders are intended for the node to access pallets and so on. This is for end-users to access Serai as a whole. In that sense, it made more sense as a top level folder, yet that also felt out of place. * Move InInstructions test to serai-client for now * Final cleanup * Update deny.toml * Cargo.lock update from merging develop * Update nightly Attempt to work around the current CI failure, which is a Rust ICE. We previously didn't upgrade due to clippy 10134, yet that's been reverted. * clippy * clippy * fmt * NativeAddress -> SeraiAddress * Sec fix on non-provided updates and doc fixes * Add Serai as a Coin Necessary in order to swap to Serai. * Add a BlockHash type, used for batch IDs * Remove origin from InInstruction Makes InInstructionTarget. Adds RefundableInInstruction with origin. * Document storage items in in-instructions * Rename serai/client/tests/serai.rs to updates.rs It only tested publishing updates and their successful acceptance.
2023-01-20 11:00:18 -05:00
pub use sc_rpc_api::DenyUnsafe;
use sc_transaction_pool_api::TransactionPool;
pub struct FullDeps<C, P> {
pub id: String,
pub client: Arc<C>,
pub pool: Arc<P>,
2022-07-15 01:26:07 -04:00
pub deny_unsafe: DenyUnsafe,
pub authority_discovery: Option<sc_authority_discovery::Service>,
}
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 01:26:07 -04:00
deps: FullDeps<C, P>,
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
2022-07-15 01:26:07 -04:00
where
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, PublicKey, Nonce>
Tokens pallet (#243) * Use Monero-compatible additional TX keys This still sends a fingerprinting flare up if you send to a subaddress which needs to be fixed. Despite that, Monero no should no longer fail to scan TXs from monero-serai regarding additional keys. Previously it failed becuase we supplied one key as THE key, and n-1 as additional. Monero expects n for additional. This does correctly select when to use THE key versus when to use the additional key when sending. That removes the ability for recipients to fingerprint monero-serai by receiving to a standard address yet needing to use an additional key. * Add tokens_primitives Moves OutInstruction from in-instructions. Turns Destination into OutInstruction. * Correct in-instructions DispatchClass * Add initial tokens pallet * Don't allow pallet addresses to equal identity * Add support for InInstruction::transfer Requires a cargo update due to modifications made to serai-dex/substrate. Successfully mints a token to a SeraiAddress. * Bind InInstructions to an amount * Add a call filter to the runtime Prevents worrying about calls to the assets pallet/generally tightens things up. * Restore Destination It was meged into OutInstruction, yet it didn't make sense for OutInstruction to contain a SeraiAddress. Also deletes the excessively dated Scenarios doc. * Split PublicKey/SeraiAddress Lets us define a custom Display/ToString for SeraiAddress. Also resolves an oddity where PublicKey would be encoded as String, not [u8; 32]. * Test burning tokens/retrieving OutInstructions Modularizes processor_coinUpdates into a shared testing utility. * Misc lint * Don't use PolkadotExtrinsicParams
2023-01-28 01:47:13 -05:00
+ pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, SubstrateAmount>
+ ValidatorSetsApi<Block>
+ DexApi<Block>
2022-07-17 17:18:56 -04:00
+ BlockBuilder<Block>,
2022-07-15 01:26:07 -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-08-25 09:17:29 -04:00
use ciphersuite::Ciphersuite;
use ciphersuite_kp256::{k256::elliptic_curve::point::AffineCoordinates, Secp256k1};
use dalek_ff_group::Ed25519;
use bitcoin_serai::bitcoin;
let mut module = RpcModule::new(());
let FullDeps { id, client, pool, deny_unsafe, authority_discovery } = deps;
module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?;
module.merge(TransactionPayment::new(client.clone()).into_rpc())?;
if let Some(authority_discovery) = authority_discovery {
let mut authority_discovery_module =
RpcModule::new((id, client.clone(), RwLock::new(authority_discovery)));
authority_discovery_module.register_async_method(
"p2p_validators",
|params, context| async move {
let network: NetworkId = params.parse()?;
let (id, client, authority_discovery) = &*context;
let latest_block = client.info().best_hash;
let validators = client.runtime_api().validators(latest_block, network).map_err(|_| {
Error::to_call_error(std::io::Error::other(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/issues",
)))
})?;
// 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(),
),
);
}
}
Ok(all_p2p_addresses)
},
)?;
module.merge(authority_discovery_module)?;
}
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(
2025-08-25 09:17:29 -04:00
bitcoin::key::TweakedPublicKey::dangerous_assume_tweaked(
bitcoin::key::XOnlyPublicKey::from_slice(key.to_affine().x().as_slice()).map_err(
|_| Error::Custom("x-coordinate for Bitcoin key was invalid".to_string()),
)?,
),
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()))?;
let addr = monero_address::MoneroAddress::new(
monero_address::Network::Mainnet,
monero_address::AddressType::Featured {
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)?;
Ok(module)
}