Add validator sets RPC functions necessary for the coordinator

This commit is contained in:
Luke Parker
2025-11-16 17:27:58 -05:00
parent 0ea16f9e01
commit fa0ed4b180
6 changed files with 219 additions and 8 deletions

View File

@@ -1,3 +1,5 @@
use core::str::FromStr;
use borsh::BorshDeserialize; use borsh::BorshDeserialize;
pub use serai_abi::{ 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())) .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<Option<Vec<SeraiAddress>>, RpcError> {
self
.0
.call::<Option<Vec<String>>>(
"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<bool, RpcError> {
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<Option<EmbeddedEllipticCurveKeys>, RpcError> {
let Some(keys) = self
.0
.call::<Option<String>>(
"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. /// Create a transaction to set a validator set's keys.
pub fn set_keys( pub fn set_keys(
network: ExternalNetworkId, network: ExternalNetworkId,

View File

@@ -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}; 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()))) 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, &params)? else {
Err(Error::InvalidStateReference)?
};
let network = network(&params)?;
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::<Vec<_>>()),
)
},
);
module.register_method(
"validator-sets/pending_slash_report",
|params, client, _ext| -> Result<_, Error> {
let Some(block_hash) = block_hash(&**client, &params)? else {
Err(Error::InvalidStateReference)?
};
let Ok(network) = ExternalNetworkId::try_from(network(&params)?) 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, &params)? else {
Err(Error::InvalidStateReference)?
};
#[derive(sp_core::serde::Deserialize)]
#[serde(crate = "sp_core::serde")]
struct Validator {
validator: String,
}
let Ok(validator) = params.parse::<Validator>() 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(&params)?) 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 module
} }

View File

@@ -6,10 +6,11 @@ extern crate alloc;
use alloc::vec::Vec; use alloc::vec::Vec;
use serai_abi::{ use serai_abi::{
primitives::{ primitives::{
crypto::{Public, SignedEmbeddedEllipticCurveKeys, KeyPair}, crypto::{Public, EmbeddedEllipticCurveKeys, SignedEmbeddedEllipticCurveKeys, KeyPair},
network_id::NetworkId, network_id::{ExternalNetworkId, NetworkId},
validator_sets::{Session, ExternalValidatorSet, ValidatorSet}, validator_sets::{Session, ExternalValidatorSet, ValidatorSet},
balance::{Amount, Balance}, balance::{Amount, Balance},
address::SeraiAddress,
}, },
Event, Event,
}; };
@@ -39,6 +40,12 @@ sp_api::decl_runtime_apis! {
fn current_session(network: NetworkId) -> Option<Session>; fn current_session(network: NetworkId) -> Option<Session>;
fn current_stake(network: NetworkId) -> Option<Amount>; fn current_stake(network: NetworkId) -> Option<Amount>;
fn keys(set: ExternalValidatorSet) -> Option<KeyPair>; fn keys(set: ExternalValidatorSet) -> Option<KeyPair>;
fn current_validators(network: NetworkId) -> Option<Vec<SeraiAddress>>;
fn pending_slash_report(network: ExternalNetworkId) -> bool;
fn embedded_elliptic_curve_keys(
validator: SeraiAddress,
network: ExternalNetworkId,
) -> Option<EmbeddedEllipticCurveKeys>;
} }
} }
@@ -183,7 +190,7 @@ mod apis {
unimplemented!("runtime is only implemented when WASM") unimplemented!("runtime is only implemented when WASM")
} }
fn validators( fn validators(
network: serai_abi::primitives::network_id::NetworkId network: NetworkId
) -> Vec<serai_abi::primitives::crypto::Public> { ) -> Vec<serai_abi::primitives::crypto::Public> {
unimplemented!("runtime is only implemented when WASM") unimplemented!("runtime is only implemented when WASM")
} }
@@ -196,6 +203,18 @@ mod apis {
fn keys(set: ExternalValidatorSet) -> Option<KeyPair> { fn keys(set: ExternalValidatorSet) -> Option<KeyPair> {
unimplemented!("runtime is only implemented when WASM") unimplemented!("runtime is only implemented when WASM")
} }
fn current_validators(network: NetworkId) -> Option<Vec<SeraiAddress>> {
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<EmbeddedEllipticCurveKeys> {
unimplemented!("runtime is only implemented when WASM")
}
} }
} }
} }

View File

@@ -10,6 +10,7 @@ use sp_version::RuntimeVersion;
use serai_abi::{ use serai_abi::{
primitives::{ primitives::{
crypto::EmbeddedEllipticCurveKeys,
network_id::{ExternalNetworkId, NetworkId}, network_id::{ExternalNetworkId, NetworkId},
balance::{Amount, ExternalBalance}, balance::{Amount, ExternalBalance},
validator_sets::{Session, ExternalValidatorSet, ValidatorSet}, validator_sets::{Session, ExternalValidatorSet, ValidatorSet},
@@ -582,6 +583,23 @@ sp_api::impl_runtime_apis! {
}) })
}) })
} }
fn current_validators(network: NetworkId) -> Option<Vec<SeraiAddress>> {
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<EmbeddedEllipticCurveKeys> {
ValidatorSets::embedded_elliptic_curve_keys(validator.into(), network)
}
} }
} }

View File

@@ -1,6 +1,11 @@
use sp_core::sr25519::Public; 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; use frame_support::storage::StorageDoubleMap;
@@ -11,8 +16,8 @@ pub(crate) trait EmbeddedEllipticCurveKeysStorage {
type EmbeddedEllipticCurveKeys: StorageDoubleMap< type EmbeddedEllipticCurveKeys: StorageDoubleMap<
ExternalNetworkId, ExternalNetworkId,
Public, Public,
serai_abi::primitives::crypto::EmbeddedEllipticCurveKeys, EmbeddedEllipticCurveKeysStruct,
Query = Option<serai_abi::primitives::crypto::EmbeddedEllipticCurveKeys>, Query = Option<EmbeddedEllipticCurveKeysStruct>,
>; >;
} }
@@ -23,6 +28,13 @@ pub(crate) trait EmbeddedEllipticCurveKeys {
validator: Public, validator: Public,
keys: SignedEmbeddedEllipticCurveKeys, keys: SignedEmbeddedEllipticCurveKeys,
) -> Result<(), ()>; ) -> Result<(), ()>;
/// Get a validator's embedded elliptic curve keys, for an external network.
fn embedded_elliptic_curve_keys(
validator: Public,
network: ExternalNetworkId,
) -> Option<EmbeddedEllipticCurveKeysStruct>;
/// Check if a validator still needs to set embedded elliptic curve keys. /// 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) fn still_needs_to_set_embedded_elliptic_curve_keys(network: NetworkId, validator: Public)
-> bool; -> bool;
@@ -39,6 +51,14 @@ impl<S: EmbeddedEllipticCurveKeysStorage> EmbeddedEllipticCurveKeys for S {
Ok(()) Ok(())
} }
/// Get a validator's embedded elliptic curve keys, for an external network.
fn embedded_elliptic_curve_keys(
validator: Public,
network: ExternalNetworkId,
) -> Option<EmbeddedEllipticCurveKeysStruct> {
S::EmbeddedEllipticCurveKeys::get(network, validator)
}
/// Check if a validator still needs to set embedded elliptic curve keys. /// Check if a validator still needs to set embedded elliptic curve keys.
fn still_needs_to_set_embedded_elliptic_curve_keys( fn still_needs_to_set_embedded_elliptic_curve_keys(
network: NetworkId, network: NetworkId,

View File

@@ -32,7 +32,10 @@ mod pallet {
use serai_abi::{ use serai_abi::{
primitives::{ primitives::{
crypto::{SignedEmbeddedEllipticCurveKeys, ExternalKey, KeyPair, Signature}, crypto::{
EmbeddedEllipticCurveKeys as EmbeddedEllipticCurveKeysStruct,
SignedEmbeddedEllipticCurveKeys, ExternalKey, KeyPair, Signature,
},
network_id::*, network_id::*,
coin::*, coin::*,
balance::*, balance::*,
@@ -315,6 +318,19 @@ mod pallet {
Abstractions::<T>::external_key(set) Abstractions::<T>::external_key(set)
} }
pub fn pending_slash_report(network: ExternalNetworkId) -> bool {
Abstractions::<T>::waiting_for_slash_report(network).is_some()
}
pub fn embedded_elliptic_curve_keys(
validator: Public,
network: ExternalNetworkId,
) -> Option<EmbeddedEllipticCurveKeysStruct> {
<Abstractions<T> as crate::EmbeddedEllipticCurveKeys>::embedded_elliptic_curve_keys(
validator, network,
)
}
/* TODO /* TODO
pub fn distribute_block_rewards( pub fn distribute_block_rewards(
network: NetworkId, network: NetworkId,