mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Remove historical state access from Serai
Resolves https://github.com/serai-dex/serai/issues/694.
This commit is contained in:
@@ -3,7 +3,7 @@ use alloc::vec::Vec;
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use serai_primitives::{
|
||||
crypto::{SignedEmbeddedEllipticCurveKeys, KeyPair, Signature},
|
||||
crypto::{EmbeddedEllipticCurveKeys, SignedEmbeddedEllipticCurveKeys, KeyPair, Signature},
|
||||
address::SeraiAddress,
|
||||
balance::Amount,
|
||||
network_id::*,
|
||||
@@ -125,8 +125,8 @@ pub enum Event {
|
||||
SetEmbeddedEllipticCurveKeys {
|
||||
/// The validator which set their keys.
|
||||
validator: SeraiAddress,
|
||||
/// The network which they set embedded elliptic curve keys for.
|
||||
network: ExternalNetworkId,
|
||||
/// The embedded elliptic curve keys they set.
|
||||
keys: EmbeddedEllipticCurveKeys,
|
||||
},
|
||||
/// A validator's allocation to a network has increased.
|
||||
Allocation {
|
||||
|
||||
@@ -28,8 +28,6 @@ borsh = { version = "1", default-features = false, features = ["std"] }
|
||||
bitvec = { version = "1", default-features = false, features = ["alloc", "std"] }
|
||||
serai-abi = { path = "../../abi", version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
async-lock = "3"
|
||||
|
||||
[dev-dependencies]
|
||||
blake2 = { version = "0.11.0-rc.3", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "e01101b68c5b0f588dd4cdee48f801a2c1f75b84" }
|
||||
|
||||
@@ -1,76 +1,37 @@
|
||||
pub use serai_abi::coins::Event;
|
||||
|
||||
use crate::{RpcError, TemporalSerai};
|
||||
use crate::{RpcError, Events};
|
||||
|
||||
/// A `TemporalSerai` scoped to the coins module.
|
||||
/// An `Events` scoped to the coins module.
|
||||
#[derive(Clone)]
|
||||
pub struct Coins<'a>(pub(super) &'a TemporalSerai<'a>);
|
||||
pub struct Coins(pub(super) Events);
|
||||
|
||||
impl<'a> Coins<'a> {
|
||||
impl Coins {
|
||||
/// The events from the coins module.
|
||||
pub async fn events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.events_borrowed()
|
||||
.await?
|
||||
.as_ref()
|
||||
.expect("`TemporalSerai::events` returned None")
|
||||
.iter()
|
||||
.flat_map(IntoIterator::into_iter)
|
||||
.filter_map(|event| match event {
|
||||
serai_abi::Event::Coins(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
pub fn events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.0.events().flatten().filter_map(|event| match event {
|
||||
serai_abi::Event::Coins(event) => Some(event),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The `Mint` events from the coins module.
|
||||
pub async fn mint_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::Mint { .. }))
|
||||
.collect(),
|
||||
)
|
||||
pub fn mint_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::Mint { .. }))
|
||||
}
|
||||
|
||||
/// The `Transfer` events from the coins module.
|
||||
pub async fn transfer_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::Transfer { .. }))
|
||||
.collect(),
|
||||
)
|
||||
pub fn transfer_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::Transfer { .. }))
|
||||
}
|
||||
|
||||
/// The `Burn` events from the coins module.
|
||||
pub async fn burn_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::Burn { .. }))
|
||||
.collect(),
|
||||
)
|
||||
pub fn burn_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::Burn { .. }))
|
||||
}
|
||||
|
||||
/// The `BurnWithInstruction` events from the coins module.
|
||||
pub async fn burn_with_instruction_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::BurnWithInstruction { .. }))
|
||||
.collect(),
|
||||
)
|
||||
pub fn burn_with_instruction_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::BurnWithInstruction { .. }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,42 +4,24 @@ pub use serai_abi::{
|
||||
UnsignedCall, Transaction,
|
||||
};
|
||||
|
||||
use crate::{RpcError, TemporalSerai};
|
||||
use crate::{RpcError, Events};
|
||||
|
||||
/// A `TemporalSerai` scoped to the in instructions module.
|
||||
/// An `Events` scoped to the in instructions module.
|
||||
#[derive(Clone)]
|
||||
pub struct InInstructions<'serai>(pub(super) &'serai TemporalSerai<'serai>);
|
||||
pub struct InInstructions(pub(super) Events);
|
||||
|
||||
impl<'serai> InInstructions<'serai> {
|
||||
impl InInstructions {
|
||||
/// The events from the in instructions module.
|
||||
pub async fn events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.events_borrowed()
|
||||
.await?
|
||||
.as_ref()
|
||||
.expect("`TemporalSerai::events` returned None")
|
||||
.iter()
|
||||
.flat_map(IntoIterator::into_iter)
|
||||
.filter_map(|event| match event {
|
||||
serai_abi::Event::InInstructions(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
fn events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.0.events().flatten().filter_map(|event| match event {
|
||||
serai_abi::Event::InInstructions(event) => Some(event),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The `Batch` events from the in instructions module.
|
||||
pub async fn batch_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::Batch { .. }))
|
||||
.collect(),
|
||||
)
|
||||
pub fn batch_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::Batch { .. }))
|
||||
}
|
||||
|
||||
/// Create a transaction to execute a batch.
|
||||
|
||||
@@ -17,8 +17,6 @@ use abi::{
|
||||
Transaction, Block, Event,
|
||||
};
|
||||
|
||||
use async_lock::RwLock;
|
||||
|
||||
mod coins;
|
||||
pub use coins::Coins;
|
||||
|
||||
@@ -55,16 +53,18 @@ pub struct Serai {
|
||||
client: TokioClient,
|
||||
}
|
||||
|
||||
/// An RPC client to a Serai node, scoped to a specific block.
|
||||
///
|
||||
/// Upon any request being made for the events emitted by this block, the entire list of events
|
||||
/// from this block will be cached within this. This allows future calls for events to be done
|
||||
/// cheaply.
|
||||
/// The events from a specific block.
|
||||
#[derive(Clone)]
|
||||
pub struct TemporalSerai<'serai> {
|
||||
pub struct Events {
|
||||
// These are cached within an `Arc` for cheap `Clone`s
|
||||
events: Arc<Vec<Vec<Event>>>,
|
||||
}
|
||||
|
||||
/// An RPC client to a Serai node, scoped to a specific block.
|
||||
#[derive(Clone)]
|
||||
pub struct State<'serai> {
|
||||
serai: &'serai Serai,
|
||||
block: BlockHash,
|
||||
events: Arc<RwLock<Option<Vec<Vec<Event>>>>>,
|
||||
}
|
||||
|
||||
impl Serai {
|
||||
@@ -175,28 +175,41 @@ impl Serai {
|
||||
.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
|
||||
/// node, that this scope will be available for the lifetime of this object.
|
||||
pub async fn as_of(&self, block: BlockHash) -> Result<TemporalSerai<'_>, RpcError> {
|
||||
if !self.finalized(block).await? {
|
||||
Err(RpcError::NotFinalized)?;
|
||||
}
|
||||
Ok(TemporalSerai { serai: self, block, events: Arc::new(RwLock::new(None)) })
|
||||
/// Fetch the events of a specific block.
|
||||
pub async fn events(&self, block: BlockHash) -> Result<Events, RpcError> {
|
||||
Ok(Events {
|
||||
events: Arc::new(
|
||||
self
|
||||
.call::<Vec<Vec<String>>>("blockchain/events", &format!(r#"{{ "block": "{block}" }}"#))
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|events_per_tx| {
|
||||
events_per_tx
|
||||
.into_iter()
|
||||
.map(|event| {
|
||||
Event::deserialize(
|
||||
&mut hex::decode(&event)
|
||||
.map_err(|_| {
|
||||
RpcError::InvalidNode("node returned non-hex-encoded event".to_string())
|
||||
})?
|
||||
.as_slice(),
|
||||
)
|
||||
.map_err(|_| RpcError::InvalidNode("node returned invalid event".to_string()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// Scope this RPC client to the state as of the latest finalized block.
|
||||
pub async fn as_of_latest_finalized_block(&self) -> Result<TemporalSerai<'_>, RpcError> {
|
||||
pub async fn state(&self) -> Result<State<'_>, RpcError> {
|
||||
let block = self
|
||||
.block_by_number(self.latest_finalized_block_number().await?)
|
||||
.await?
|
||||
.ok_or_else(|| RpcError::InvalidNode("couldn't fetch latest finalized block".to_string()))?;
|
||||
Ok(TemporalSerai {
|
||||
serai: self,
|
||||
block: block.header.hash(),
|
||||
events: Arc::new(RwLock::new(None)),
|
||||
})
|
||||
Ok(State { serai: self, block: block.header.hash() })
|
||||
}
|
||||
|
||||
/// Return the P2P addresses for the validators of the specified network.
|
||||
@@ -208,14 +221,38 @@ impl Serai {
|
||||
ExternalNetworkId::Bitcoin => r#"["bitcoin"]"#,
|
||||
ExternalNetworkId::Ethereum => r#"["ethereum"]"#,
|
||||
ExternalNetworkId::Monero => r#"["monero"]"#,
|
||||
_ => Err(RpcError::InternalError("unrecognized external network ID".to_string()))?,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'serai> TemporalSerai<'serai> {
|
||||
impl Events {
|
||||
/// The events within this container.
|
||||
///
|
||||
/// This will yield the events for each transaction within the block, including the implicit
|
||||
/// transactions at the start and end of each block, within an outer container.
|
||||
pub fn events(&self) -> impl Iterator<Item: IntoIterator<Item = &Event>> {
|
||||
self.events.iter()
|
||||
}
|
||||
|
||||
/// Scope to the coins module.
|
||||
pub fn coins(&self) -> Coins {
|
||||
Coins(self.clone())
|
||||
}
|
||||
|
||||
/// Scope to the validator sets module.
|
||||
pub fn validator_sets(&self) -> ValidatorSets {
|
||||
ValidatorSets(self.clone())
|
||||
}
|
||||
|
||||
/// Scope to the in instructions module.
|
||||
pub fn in_instructions(&self) -> InInstructions {
|
||||
InInstructions(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'serai> State<'serai> {
|
||||
async fn call<ResponseValue: Default + JsonDeserialize>(
|
||||
&self,
|
||||
method: &str,
|
||||
@@ -223,68 +260,4 @@ impl<'serai> TemporalSerai<'serai> {
|
||||
) -> Result<ResponseValue, RpcError> {
|
||||
self.serai.call(method, &format!(r#"{{ "block": "{}" {params} }}"#, self.block)).await
|
||||
}
|
||||
|
||||
/// Fetch the events for this block.
|
||||
///
|
||||
/// The returned `Option` will always be `Some(_)`.
|
||||
async fn events_borrowed(
|
||||
&self,
|
||||
) -> Result<async_lock::RwLockReadGuard<'_, Option<Vec<Vec<Event>>>>, RpcError> {
|
||||
let mut events = self.events.read().await;
|
||||
if events.is_none() {
|
||||
drop(events);
|
||||
{
|
||||
let mut events_mut = self.events.write().await;
|
||||
if events_mut.is_none() {
|
||||
*events_mut = Some(
|
||||
self
|
||||
.call::<Vec<Vec<String>>>("blockchain/events", "")
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|events_per_tx| {
|
||||
events_per_tx
|
||||
.into_iter()
|
||||
.map(|event| {
|
||||
Event::deserialize(
|
||||
&mut hex::decode(&event)
|
||||
.map_err(|_| {
|
||||
RpcError::InvalidNode("node returned non-hex-encoded event".to_string())
|
||||
})?
|
||||
.as_slice(),
|
||||
)
|
||||
.map_err(|_| RpcError::InvalidNode("node returned invalid event".to_string()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
);
|
||||
}
|
||||
}
|
||||
events = self.events.read().await;
|
||||
}
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
/// Fetch the events for this block.
|
||||
///
|
||||
/// These will be grouped by the transactions which emitted them, including the inherent
|
||||
/// transactions at the start and end of every block.
|
||||
pub async fn events(&self) -> Result<Vec<Vec<Event>>, RpcError> {
|
||||
Ok(self.events_borrowed().await?.clone().expect("`TemporalSerai::events` returned None"))
|
||||
}
|
||||
|
||||
/// Scope to the coins module.
|
||||
pub fn coins(&self) -> Coins<'_> {
|
||||
Coins(self)
|
||||
}
|
||||
|
||||
/// Scope to the validator sets module.
|
||||
pub fn validator_sets(&self) -> ValidatorSets<'_> {
|
||||
ValidatorSets(self)
|
||||
}
|
||||
|
||||
/// Scope to the in instructions module.
|
||||
pub fn in_instructions(&self) -> InInstructions<'_> {
|
||||
InInstructions(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,203 +14,68 @@ pub use serai_abi::{
|
||||
UnsignedCall, Transaction,
|
||||
};
|
||||
|
||||
use crate::{RpcError, TemporalSerai};
|
||||
use crate::{RpcError, Events, State};
|
||||
|
||||
fn rpc_network(network: impl Into<NetworkId>) -> Result<&'static str, RpcError> {
|
||||
Ok(match network.into() {
|
||||
fn rpc_network(network: impl Into<NetworkId>) -> &'static str {
|
||||
match network.into() {
|
||||
NetworkId::Serai => r#""serai""#,
|
||||
NetworkId::External(ExternalNetworkId::Bitcoin) => r#""bitcoin""#,
|
||||
NetworkId::External(ExternalNetworkId::Ethereum) => r#""ethereum""#,
|
||||
NetworkId::External(ExternalNetworkId::Monero) => r#""monero""#,
|
||||
_ => Err(RpcError::InternalError("unrecognized network ID".to_string()))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A `TemporalSerai` scoped to the validator sets module.
|
||||
/// An `Events` scoped to the validator sets module.
|
||||
#[derive(Clone)]
|
||||
pub struct ValidatorSets<'serai>(pub(super) &'serai TemporalSerai<'serai>);
|
||||
pub struct ValidatorSets(pub(super) Events);
|
||||
|
||||
impl<'serai> ValidatorSets<'serai> {
|
||||
impl ValidatorSets {
|
||||
/// The events from the validator sets module.
|
||||
pub async fn events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.events_borrowed()
|
||||
.await?
|
||||
.as_ref()
|
||||
.expect("`TemporalSerai::events` returned None")
|
||||
.iter()
|
||||
.flat_map(IntoIterator::into_iter)
|
||||
.filter_map(|event| match event {
|
||||
serai_abi::Event::ValidatorSets(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
pub fn events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.0.events().flatten().filter_map(|event| match event {
|
||||
serai_abi::Event::ValidatorSets(event) => Some(event),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The `SetDecided` events from the validator sets module.
|
||||
pub async fn set_decided_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::SetDecided { .. }))
|
||||
.collect(),
|
||||
)
|
||||
pub fn set_decided_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::SetDecided { .. }))
|
||||
}
|
||||
|
||||
/// The `SetKeys` events from the validator sets module.
|
||||
pub async fn set_keys_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::SetKeys { .. }))
|
||||
.collect(),
|
||||
)
|
||||
pub fn set_keys_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::SetKeys { .. }))
|
||||
}
|
||||
|
||||
/// The `AcceptedHandover` events from the validator sets module.
|
||||
pub async fn accepted_handover_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::AcceptedHandover { .. }))
|
||||
.collect(),
|
||||
)
|
||||
pub fn accepted_handover_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::AcceptedHandover { .. }))
|
||||
}
|
||||
|
||||
/// The `SlashReport` events from the validator sets module.
|
||||
pub async fn slash_report_events(&self) -> Result<Vec<Event>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.events()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|event| matches!(event, Event::SlashReport { .. }))
|
||||
.collect(),
|
||||
)
|
||||
pub fn slash_report_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::SlashReport { .. }))
|
||||
}
|
||||
|
||||
/// The current session for the specified network.
|
||||
pub async fn current_session(&self, network: NetworkId) -> Result<Option<Session>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.call::<Option<_>>(
|
||||
"validator-sets/current_session",
|
||||
&format!(r#", "network": {} "#, rpc_network(network)?),
|
||||
)
|
||||
.await?
|
||||
.map(Session),
|
||||
)
|
||||
/// The `SetEmbeddedEllipticCurveKeys` events from the validator sets module.
|
||||
pub fn set_embedded_elliptic_curve_keys_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::SetEmbeddedEllipticCurveKeys { .. }))
|
||||
}
|
||||
|
||||
/// The stake for the current validators for the specified network.
|
||||
pub async fn current_stake(&self, network: NetworkId) -> Result<Option<Amount>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.0
|
||||
.call::<Option<_>>(
|
||||
"validator-sets/current_stake",
|
||||
&format!(r#", "network": {} "#, rpc_network(network)?),
|
||||
)
|
||||
.await?
|
||||
.map(Amount),
|
||||
)
|
||||
/// The `Allocation` events from the validator sets module.
|
||||
pub fn allocation_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::Allocation { .. }))
|
||||
}
|
||||
|
||||
/// The keys for the specified validator set.
|
||||
pub async fn keys(&self, set: ExternalValidatorSet) -> Result<Option<KeyPair>, RpcError> {
|
||||
let Some(key_pair) = self
|
||||
.0
|
||||
.call::<Option<String>>(
|
||||
"validator-sets/keys",
|
||||
&format!(r#", "network": {}, "session": {} "#, rpc_network(set.network)?, set.session.0),
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
KeyPair::deserialize(
|
||||
&mut hex::decode(key_pair)
|
||||
.map_err(|_| RpcError::InvalidNode("validator set's keys weren't valid hex".to_string()))?
|
||||
.as_slice(),
|
||||
)
|
||||
.map(Some)
|
||||
.map_err(|_| RpcError::InvalidNode("validator set's keys weren't a valid key pair".to_string()))
|
||||
/// The `Deallocation` events from the validator sets module.
|
||||
pub fn deallocation_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::Deallocation { .. }))
|
||||
}
|
||||
|
||||
/// 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())
|
||||
})
|
||||
/// The `DelayedDeallocationClaimed` events from the validator sets module.
|
||||
pub fn delayed_deallocation_claimed_events(&self) -> impl Iterator<Item = &Event> {
|
||||
self.events().filter(|event| matches!(event, Event::DelayedDeallocationClaimed { .. }))
|
||||
}
|
||||
|
||||
/// Create a transaction to set a validator set's keys.
|
||||
@@ -247,3 +112,114 @@ impl<'serai> ValidatorSets<'serai> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'serai> State<'serai> {
|
||||
/// The current session for the specified network.
|
||||
pub async fn current_session(&self, network: NetworkId) -> Result<Option<Session>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.call::<Option<_>>(
|
||||
"validator-sets/current_session",
|
||||
&format!(r#", "network": {} "#, rpc_network(network)),
|
||||
)
|
||||
.await?
|
||||
.map(Session),
|
||||
)
|
||||
}
|
||||
|
||||
/// The stake for the current validators for the specified network.
|
||||
pub async fn current_stake(&self, network: NetworkId) -> Result<Option<Amount>, RpcError> {
|
||||
Ok(
|
||||
self
|
||||
.call::<Option<_>>(
|
||||
"validator-sets/current_stake",
|
||||
&format!(r#", "network": {} "#, rpc_network(network)),
|
||||
)
|
||||
.await?
|
||||
.map(Amount),
|
||||
)
|
||||
}
|
||||
|
||||
/// The keys for the specified validator set.
|
||||
pub async fn keys(&self, set: ExternalValidatorSet) -> Result<Option<KeyPair>, RpcError> {
|
||||
let Some(key_pair) = self
|
||||
.call::<Option<String>>(
|
||||
"validator-sets/keys",
|
||||
&format!(r#", "network": {}, "session": {} "#, rpc_network(set.network), set.session.0),
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
KeyPair::deserialize(
|
||||
&mut hex::decode(key_pair)
|
||||
.map_err(|_| RpcError::InvalidNode("validator set's keys weren't valid hex".to_string()))?
|
||||
.as_slice(),
|
||||
)
|
||||
.map(Some)
|
||||
.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
|
||||
.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
|
||||
.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
|
||||
.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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,11 @@ async fn blockchain() {
|
||||
.chain(block.transactions.iter().map(serai_abi::Transaction::hash))
|
||||
.chain(core::iter::once(end_transaction));
|
||||
|
||||
let events = serai.as_of(block.header.hash()).await.unwrap().events().await.unwrap();
|
||||
let events = serai.events(block.header.hash()).await.unwrap();
|
||||
let events = events
|
||||
.events()
|
||||
.map(|iter| iter.into_iter().cloned().collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(events.len(), 2 + block.transactions.len());
|
||||
|
||||
let mut transaction_leaves = vec![];
|
||||
|
||||
@@ -3,6 +3,7 @@ use serai_abi::{
|
||||
address::SeraiAddress,
|
||||
network_id::{ExternalNetworkId, NetworkId},
|
||||
balance::Amount,
|
||||
crypto::EmbeddedEllipticCurveKeys,
|
||||
validator_sets::{Session, ExternalValidatorSet, ValidatorSet, KeyShares},
|
||||
},
|
||||
validator_sets::Event,
|
||||
@@ -44,67 +45,94 @@ async fn validator_sets() {
|
||||
panic!("finalized block remained the genesis block for over five minutes");
|
||||
};
|
||||
|
||||
// The genesis block should have the expected events
|
||||
{
|
||||
use sp_core::{Pair as _, sr25519::Pair};
|
||||
let genesis_validators = vec![(
|
||||
SeraiAddress::from(Pair::from_string("//Alice", None).unwrap().public()),
|
||||
KeyShares(1),
|
||||
)];
|
||||
// The genesis block should have the expected events
|
||||
|
||||
{
|
||||
let mut events = serai
|
||||
.as_of(serai.block_by_number(0).await.unwrap().unwrap().header.hash())
|
||||
let events = serai
|
||||
.events(serai.block_by_number(0).await.unwrap().unwrap().header.hash())
|
||||
.await
|
||||
.unwrap()
|
||||
.validator_sets()
|
||||
.set_decided_events()
|
||||
.await
|
||||
.unwrap();
|
||||
events.sort_by_key(|event| borsh::to_vec(event).unwrap());
|
||||
let mut expected = vec![
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(0) },
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(1) },
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet {
|
||||
network: NetworkId::External(ExternalNetworkId::Bitcoin),
|
||||
session: Session(0),
|
||||
.set_embedded_elliptic_curve_keys_events()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(events.len(), ExternalNetworkId::all().collect::<Vec<_>>().len());
|
||||
|
||||
let state = serai.state().await.unwrap();
|
||||
for (event, network) in events.into_iter().zip(ExternalNetworkId::all()) {
|
||||
assert_eq!(
|
||||
event,
|
||||
Event::SetEmbeddedEllipticCurveKeys {
|
||||
validator: genesis_validators[0].0,
|
||||
keys: state
|
||||
.embedded_elliptic_curve_keys(genesis_validators[0].0, network)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
assert_eq!(
|
||||
serai
|
||||
.events(serai.block_by_number(0).await.unwrap().unwrap().header.hash())
|
||||
.await
|
||||
.unwrap()
|
||||
.validator_sets()
|
||||
.set_decided_events()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(0) },
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet {
|
||||
network: NetworkId::External(ExternalNetworkId::Ethereum),
|
||||
session: Session(0),
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet {
|
||||
network: NetworkId::External(ExternalNetworkId::Bitcoin),
|
||||
session: Session(0)
|
||||
},
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet {
|
||||
network: NetworkId::External(ExternalNetworkId::Monero),
|
||||
session: Session(0),
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet {
|
||||
network: NetworkId::External(ExternalNetworkId::Ethereum),
|
||||
session: Session(0)
|
||||
},
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
];
|
||||
expected.sort_by_key(|event| borsh::to_vec(event).unwrap());
|
||||
assert_eq!(events, expected);
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet {
|
||||
network: NetworkId::External(ExternalNetworkId::Monero),
|
||||
session: Session(0)
|
||||
},
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
Event::SetDecided {
|
||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(1) },
|
||||
validators: genesis_validators.clone(),
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
serai
|
||||
.as_of(serai.block_by_number(0).await.unwrap().unwrap().header.hash())
|
||||
.events(serai.block_by_number(0).await.unwrap().unwrap().header.hash())
|
||||
.await
|
||||
.unwrap()
|
||||
.validator_sets()
|
||||
.accepted_handover_events()
|
||||
.await
|
||||
.unwrap(),
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Event::AcceptedHandover {
|
||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(0) }
|
||||
}]
|
||||
@@ -115,34 +143,30 @@ async fn validator_sets() {
|
||||
{
|
||||
assert_eq!(
|
||||
serai
|
||||
.as_of(serai.block_by_number(1).await.unwrap().unwrap().header.hash())
|
||||
.events(serai.block_by_number(1).await.unwrap().unwrap().header.hash())
|
||||
.await
|
||||
.unwrap()
|
||||
.validator_sets()
|
||||
.set_decided_events()
|
||||
.await
|
||||
.unwrap(),
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(
|
||||
serai
|
||||
.as_of(serai.block_by_number(1).await.unwrap().unwrap().header.hash())
|
||||
.events(serai.block_by_number(1).await.unwrap().unwrap().header.hash())
|
||||
.await
|
||||
.unwrap()
|
||||
.validator_sets()
|
||||
.accepted_handover_events()
|
||||
.await
|
||||
.unwrap(),
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let serai = serai
|
||||
.as_of(serai.block_by_number(0).await.unwrap().unwrap().header.hash())
|
||||
.await
|
||||
.unwrap();
|
||||
let serai = serai.validator_sets();
|
||||
let serai = serai.state().await.unwrap();
|
||||
for network in NetworkId::all() {
|
||||
match network {
|
||||
NetworkId::Serai => {
|
||||
|
||||
@@ -30,7 +30,6 @@ pub enum EmbeddedEllipticCurve {
|
||||
feature = "non_canonical_scale_derivations",
|
||||
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen, scale::DecodeWithMemTracking)
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub enum ExternalNetworkId {
|
||||
/// The Bitcoin network.
|
||||
Bitcoin = 1,
|
||||
|
||||
@@ -27,7 +27,7 @@ pub(crate) trait EmbeddedEllipticCurveKeys {
|
||||
fn set_embedded_elliptic_curve_keys(
|
||||
validator: Public,
|
||||
keys: SignedEmbeddedEllipticCurveKeys,
|
||||
) -> Result<(), ()>;
|
||||
) -> Result<EmbeddedEllipticCurveKeysStruct, ()>;
|
||||
|
||||
/// Get a validator's embedded elliptic curve keys, for an external network.
|
||||
fn embedded_elliptic_curve_keys(
|
||||
@@ -45,10 +45,10 @@ impl<S: EmbeddedEllipticCurveKeysStorage> EmbeddedEllipticCurveKeys for S {
|
||||
fn set_embedded_elliptic_curve_keys(
|
||||
validator: Public,
|
||||
keys: SignedEmbeddedEllipticCurveKeys,
|
||||
) -> Result<(), ()> {
|
||||
) -> Result<EmbeddedEllipticCurveKeysStruct, ()> {
|
||||
let keys = keys.verify(validator.into()).ok_or(())?;
|
||||
S::EmbeddedEllipticCurveKeys::set(keys.network(), validator, Some(keys));
|
||||
Ok(())
|
||||
S::EmbeddedEllipticCurveKeys::insert(keys.network(), validator, keys);
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
/// Get a validator's embedded elliptic curve keys, for an external network.
|
||||
|
||||
@@ -193,11 +193,8 @@ mod pallet {
|
||||
for (participant, keys) in &self.participants {
|
||||
for (network, keys) in ExternalNetworkId::all().zip(keys.iter().cloned()) {
|
||||
assert_eq!(network, keys.network());
|
||||
<Abstractions<T> as crate::EmbeddedEllipticCurveKeys>::set_embedded_elliptic_curve_keys(
|
||||
*participant,
|
||||
keys,
|
||||
)
|
||||
.expect("genesis embedded elliptic curve keys weren't valid");
|
||||
Pallet::<T>::set_embedded_elliptic_curve_keys_internal(*participant, keys)
|
||||
.expect("genesis embedded elliptic curve keys weren't valid");
|
||||
}
|
||||
}
|
||||
for network in NetworkId::all() {
|
||||
@@ -331,6 +328,23 @@ mod pallet {
|
||||
)
|
||||
}
|
||||
|
||||
fn set_embedded_elliptic_curve_keys_internal(
|
||||
validator: Public,
|
||||
keys: SignedEmbeddedEllipticCurveKeys,
|
||||
) -> DispatchResult {
|
||||
let network = keys.network();
|
||||
let keys =
|
||||
<Abstractions<T> as crate::EmbeddedEllipticCurveKeys>::set_embedded_elliptic_curve_keys(
|
||||
validator, keys,
|
||||
)
|
||||
.map_err(|()| Error::<T>::InvalidEmbeddedEllipticCurveKeys)?;
|
||||
Core::<T>::emit_event(Event::SetEmbeddedEllipticCurveKeys {
|
||||
validator: validator.into(),
|
||||
keys,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/* TODO
|
||||
pub fn distribute_block_rewards(
|
||||
network: NetworkId,
|
||||
@@ -492,16 +506,7 @@ mod pallet {
|
||||
keys: SignedEmbeddedEllipticCurveKeys,
|
||||
) -> DispatchResult {
|
||||
let validator = ensure_signed(origin)?;
|
||||
let network = keys.network();
|
||||
<Abstractions<T> as crate::EmbeddedEllipticCurveKeys>::set_embedded_elliptic_curve_keys(
|
||||
validator, keys,
|
||||
)
|
||||
.map_err(|()| Error::<T>::InvalidEmbeddedEllipticCurveKeys)?;
|
||||
Core::<T>::emit_event(Event::SetEmbeddedEllipticCurveKeys {
|
||||
validator: validator.into(),
|
||||
network,
|
||||
});
|
||||
Ok(())
|
||||
Self::set_embedded_elliptic_curve_keys_internal(validator, keys)
|
||||
}
|
||||
|
||||
#[pallet::call_index(3)]
|
||||
|
||||
Reference in New Issue
Block a user