From fa0ed4b180fb3e611fccd4fded2f29398eeceadc Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 16 Nov 2025 17:27:58 -0500 Subject: [PATCH] Add validator sets RPC functions necessary for the coordinator --- substrate/client/serai/src/validator_sets.rs | 68 ++++++++++++++++++ substrate/node/src/rpc/validator_sets.rs | 72 ++++++++++++++++++- substrate/runtime/src/lib.rs | 25 ++++++- substrate/runtime/src/wasm/mod.rs | 18 +++++ .../src/embedded_elliptic_curve_keys.rs | 26 ++++++- substrate/validator-sets/src/lib.rs | 18 ++++- 6 files changed, 219 insertions(+), 8 deletions(-) diff --git a/substrate/client/serai/src/validator_sets.rs b/substrate/client/serai/src/validator_sets.rs index 52d2c1d2..e4162b92 100644 --- a/substrate/client/serai/src/validator_sets.rs +++ b/substrate/client/serai/src/validator_sets.rs @@ -1,3 +1,5 @@ +use core::str::FromStr; + use borsh::BorshDeserialize; pub use serai_abi::{ @@ -145,6 +147,72 @@ impl<'serai> ValidatorSets<'serai> { .map_err(|_| RpcError::InvalidNode("validator set's keys weren't a valid key pair".to_string())) } + /// The current validators for the specified network. + pub async fn current_validators( + &self, + network: NetworkId, + ) -> Result>, RpcError> { + self + .0 + .call::>>( + "validator-sets/current_validators", + &format!(r#", "network": {} "#, rpc_network(network)?), + ) + .await? + .map(|validators| { + validators + .into_iter() + .map(|addr| { + SeraiAddress::from_str(&addr) + .map_err(|_| RpcError::InvalidNode("validator's address was invalid".to_string())) + }) + .collect() + }) + .transpose() + } + + /// If the prior validators for this network is still expected to publish a slash report. + pub async fn pending_slash_report(&self, network: ExternalNetworkId) -> Result { + self + .0 + .call( + "validator-sets/pending_slash_report", + &format!(r#", "network": {} "#, rpc_network(network)?), + ) + .await + } + + /// The key on an embedded elliptic curve for the specified validator. + pub async fn embedded_elliptic_curve_keys( + &self, + validator: SeraiAddress, + network: ExternalNetworkId, + ) -> Result, RpcError> { + let Some(keys) = self + .0 + .call::>( + "validator-sets/embedded_elliptic_curve_keys", + &format!(r#", "validator": {validator}, "network": {} "#, rpc_network(network)?), + ) + .await? + else { + return Ok(None); + }; + EmbeddedEllipticCurveKeys::deserialize( + &mut hex::decode(keys) + .map_err(|_| { + RpcError::InvalidNode( + "validator's embedded elliptic curve keys weren't valid hex".to_string(), + ) + })? + .as_slice(), + ) + .map(Some) + .map_err(|_| { + RpcError::InvalidNode("validator's embedded elliptic curve keys weren't valid".to_string()) + }) + } + /// Create a transaction to set a validator set's keys. pub fn set_keys( network: ExternalNetworkId, diff --git a/substrate/node/src/rpc/validator_sets.rs b/substrate/node/src/rpc/validator_sets.rs index 70ee006b..79c65e83 100644 --- a/substrate/node/src/rpc/validator_sets.rs +++ b/substrate/node/src/rpc/validator_sets.rs @@ -1,4 +1,5 @@ -use std::{sync::Arc, ops::Deref, convert::AsRef, collections::HashSet}; +use core::{ops::Deref, convert::AsRef, str::FromStr}; +use std::{sync::Arc, collections::HashSet}; use rand_core::{RngCore, OsRng}; @@ -111,5 +112,74 @@ pub(crate) fn module< Ok(key_pair.map(|key_pair| hex::encode(borsh::to_vec(&key_pair).unwrap()))) }); + module.register_method( + "validator-sets/current_validators", + |params, client, _ext| -> Result<_, Error> { + let Some(block_hash) = block_hash(&**client, ¶ms)? else { + Err(Error::InvalidStateReference)? + }; + let network = network(¶ms)?; + let Ok(validators) = client.runtime_api().current_validators(block_hash, network) else { + Err(Error::Internal("couldn't fetch the current validators for the requested network"))? + }; + Ok( + validators.map(|validators| validators.iter().map(ToString::to_string).collect::>()), + ) + }, + ); + + module.register_method( + "validator-sets/pending_slash_report", + |params, client, _ext| -> Result<_, Error> { + let Some(block_hash) = block_hash(&**client, ¶ms)? else { + Err(Error::InvalidStateReference)? + }; + let Ok(network) = ExternalNetworkId::try_from(network(¶ms)?) else { + Err(Error::InvalidRequest( + "asking if a non-external validator set has a pending slash report", + ))? + }; + client + .runtime_api() + .pending_slash_report(block_hash, network) + .map_err(|_| Error::Internal("couldn't fetch if this network has a pending slash report")) + }, + ); + + module.register_method( + "validator-sets/embedded_elliptic_curve_keys", + |params, client, _ext| -> Result<_, Error> { + let Some(block_hash) = block_hash(&**client, ¶ms)? else { + Err(Error::InvalidStateReference)? + }; + + #[derive(sp_core::serde::Deserialize)] + #[serde(crate = "sp_core::serde")] + struct Validator { + validator: String, + } + let Ok(validator) = params.parse::() else { + Err(Error::InvalidRequest(r#"missing `string` "validator" field"#))? + }; + let Ok(validator) = SeraiAddress::from_str(&validator.validator) else { + Err(Error::InvalidRequest(r#"validator had an invalid address"#))? + }; + + let Ok(network) = ExternalNetworkId::try_from(network(¶ms)?) else { + Err(Error::InvalidRequest( + "asking for the embedded elliptic curve keys for a non-external network", + ))? + }; + let Ok(embedded_elliptic_curve_keys) = + client.runtime_api().embedded_elliptic_curve_keys(block_hash, validator, network) + else { + Err(Error::Internal("couldn't fetch the keys for the requested validator set"))? + }; + Ok(embedded_elliptic_curve_keys.map(|embedded_elliptic_curve_keys| { + hex::encode(borsh::to_vec(&embedded_elliptic_curve_keys).unwrap()) + })) + }, + ); + module } diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 4129c27c..35bc10cb 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -6,10 +6,11 @@ extern crate alloc; use alloc::vec::Vec; use serai_abi::{ primitives::{ - crypto::{Public, SignedEmbeddedEllipticCurveKeys, KeyPair}, - network_id::NetworkId, + crypto::{Public, EmbeddedEllipticCurveKeys, SignedEmbeddedEllipticCurveKeys, KeyPair}, + network_id::{ExternalNetworkId, NetworkId}, validator_sets::{Session, ExternalValidatorSet, ValidatorSet}, balance::{Amount, Balance}, + address::SeraiAddress, }, Event, }; @@ -39,6 +40,12 @@ sp_api::decl_runtime_apis! { fn current_session(network: NetworkId) -> Option; fn current_stake(network: NetworkId) -> Option; fn keys(set: ExternalValidatorSet) -> Option; + fn current_validators(network: NetworkId) -> Option>; + fn pending_slash_report(network: ExternalNetworkId) -> bool; + fn embedded_elliptic_curve_keys( + validator: SeraiAddress, + network: ExternalNetworkId, + ) -> Option; } } @@ -183,7 +190,7 @@ mod apis { unimplemented!("runtime is only implemented when WASM") } fn validators( - network: serai_abi::primitives::network_id::NetworkId + network: NetworkId ) -> Vec { unimplemented!("runtime is only implemented when WASM") } @@ -196,6 +203,18 @@ mod apis { fn keys(set: ExternalValidatorSet) -> Option { unimplemented!("runtime is only implemented when WASM") } + fn current_validators(network: NetworkId) -> Option> { + unimplemented!("runtime is only implemented when WASM") + } + fn pending_slash_report(network: ExternalNetworkId) -> bool { + unimplemented!("runtime is only implemented when WASM") + } + fn embedded_elliptic_curve_keys( + validator: SeraiAddress, + network: ExternalNetworkId, + ) -> Option { + unimplemented!("runtime is only implemented when WASM") + } } } } diff --git a/substrate/runtime/src/wasm/mod.rs b/substrate/runtime/src/wasm/mod.rs index 28bb816c..9d5bcf4b 100644 --- a/substrate/runtime/src/wasm/mod.rs +++ b/substrate/runtime/src/wasm/mod.rs @@ -10,6 +10,7 @@ use sp_version::RuntimeVersion; use serai_abi::{ primitives::{ + crypto::EmbeddedEllipticCurveKeys, network_id::{ExternalNetworkId, NetworkId}, balance::{Amount, ExternalBalance}, validator_sets::{Session, ExternalValidatorSet, ValidatorSet}, @@ -582,6 +583,23 @@ sp_api::impl_runtime_apis! { }) }) } + fn current_validators(network: NetworkId) -> Option> { + let session = ValidatorSets::current_session(network)?; + Some( + ValidatorSets::selected_validators(ValidatorSet { network, session }) + .map(|(key, _key_shares)| SeraiAddress::from(key)) + .collect() + ) + } + fn pending_slash_report(network: ExternalNetworkId) -> bool { + ValidatorSets::pending_slash_report(network) + } + fn embedded_elliptic_curve_keys( + validator: SeraiAddress, + network: ExternalNetworkId, + ) -> Option { + ValidatorSets::embedded_elliptic_curve_keys(validator.into(), network) + } } } diff --git a/substrate/validator-sets/src/embedded_elliptic_curve_keys.rs b/substrate/validator-sets/src/embedded_elliptic_curve_keys.rs index b00cab26..22f68b0d 100644 --- a/substrate/validator-sets/src/embedded_elliptic_curve_keys.rs +++ b/substrate/validator-sets/src/embedded_elliptic_curve_keys.rs @@ -1,6 +1,11 @@ use sp_core::sr25519::Public; -use serai_abi::primitives::{crypto::SignedEmbeddedEllipticCurveKeys, network_id::*}; +use serai_abi::primitives::{ + crypto::{ + EmbeddedEllipticCurveKeys as EmbeddedEllipticCurveKeysStruct, SignedEmbeddedEllipticCurveKeys, + }, + network_id::*, +}; use frame_support::storage::StorageDoubleMap; @@ -11,8 +16,8 @@ pub(crate) trait EmbeddedEllipticCurveKeysStorage { type EmbeddedEllipticCurveKeys: StorageDoubleMap< ExternalNetworkId, Public, - serai_abi::primitives::crypto::EmbeddedEllipticCurveKeys, - Query = Option, + EmbeddedEllipticCurveKeysStruct, + Query = Option, >; } @@ -23,6 +28,13 @@ pub(crate) trait EmbeddedEllipticCurveKeys { validator: Public, keys: SignedEmbeddedEllipticCurveKeys, ) -> Result<(), ()>; + + /// Get a validator's embedded elliptic curve keys, for an external network. + fn embedded_elliptic_curve_keys( + validator: Public, + network: ExternalNetworkId, + ) -> Option; + /// Check if a validator still needs to set embedded elliptic curve keys. fn still_needs_to_set_embedded_elliptic_curve_keys(network: NetworkId, validator: Public) -> bool; @@ -39,6 +51,14 @@ impl EmbeddedEllipticCurveKeys for S { Ok(()) } + /// Get a validator's embedded elliptic curve keys, for an external network. + fn embedded_elliptic_curve_keys( + validator: Public, + network: ExternalNetworkId, + ) -> Option { + S::EmbeddedEllipticCurveKeys::get(network, validator) + } + /// Check if a validator still needs to set embedded elliptic curve keys. fn still_needs_to_set_embedded_elliptic_curve_keys( network: NetworkId, diff --git a/substrate/validator-sets/src/lib.rs b/substrate/validator-sets/src/lib.rs index ac44b478..54178f37 100644 --- a/substrate/validator-sets/src/lib.rs +++ b/substrate/validator-sets/src/lib.rs @@ -32,7 +32,10 @@ mod pallet { use serai_abi::{ primitives::{ - crypto::{SignedEmbeddedEllipticCurveKeys, ExternalKey, KeyPair, Signature}, + crypto::{ + EmbeddedEllipticCurveKeys as EmbeddedEllipticCurveKeysStruct, + SignedEmbeddedEllipticCurveKeys, ExternalKey, KeyPair, Signature, + }, network_id::*, coin::*, balance::*, @@ -315,6 +318,19 @@ mod pallet { Abstractions::::external_key(set) } + pub fn pending_slash_report(network: ExternalNetworkId) -> bool { + Abstractions::::waiting_for_slash_report(network).is_some() + } + + pub fn embedded_elliptic_curve_keys( + validator: Public, + network: ExternalNetworkId, + ) -> Option { + as crate::EmbeddedEllipticCurveKeys>::embedded_elliptic_curve_keys( + validator, network, + ) + } + /* TODO pub fn distribute_block_rewards( network: NetworkId,