mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Compare commits
4 Commits
46b1f1b7ec
...
d219b77bd0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d219b77bd0 | ||
|
|
fce26eaee1 | ||
|
|
3cfbd9add7 | ||
|
|
609cf06393 |
@@ -22,6 +22,7 @@ blake2 = { version = "0.11.0-rc.0", default-features = false, features = ["alloc
|
|||||||
|
|
||||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||||
serai-abi = { path = "../../substrate/abi", default-features = false, features = ["std"] }
|
serai-abi = { path = "../../substrate/abi", default-features = false, features = ["std"] }
|
||||||
|
serai-client-serai = { path = "../../substrate/client/serai", default-features = false }
|
||||||
|
|
||||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
use core::future::Future;
|
use core::future::Future;
|
||||||
use std::{sync::Arc, collections::HashMap};
|
use std::{sync::Arc, collections::HashMap};
|
||||||
|
|
||||||
|
use blake2::{Digest, Blake2b256};
|
||||||
|
|
||||||
use serai_abi::primitives::{
|
use serai_abi::primitives::{
|
||||||
balance::Amount, validator_sets::ExternalValidatorSet, address::SeraiAddress,
|
balance::Amount, validator_sets::ExternalValidatorSet, address::SeraiAddress,
|
||||||
|
merkle::IncrementalUnbalancedMerkleTree,
|
||||||
};
|
};
|
||||||
use serai_client::Serai;
|
use serai_client_serai::Serai;
|
||||||
|
|
||||||
use serai_db::*;
|
use serai_db::*;
|
||||||
use serai_task::ContinuallyRan;
|
use serai_task::ContinuallyRan;
|
||||||
@@ -14,6 +17,7 @@ use crate::*;
|
|||||||
create_db!(
|
create_db!(
|
||||||
CosignIntend {
|
CosignIntend {
|
||||||
ScanCosignFrom: () -> u64,
|
ScanCosignFrom: () -> u64,
|
||||||
|
BuildsUpon: () -> IncrementalUnbalancedMerkleTree,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -40,9 +44,9 @@ async fn block_has_events_justifying_a_cosign(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("{e:?}"))?
|
.map_err(|e| format!("{e:?}"))?
|
||||||
.ok_or_else(|| "couldn't get block which should've been finalized".to_string())?;
|
.ok_or_else(|| "couldn't get block which should've been finalized".to_string())?;
|
||||||
let serai = serai.as_of(block.hash());
|
let serai = serai.as_of(block.header.hash()).await.map_err(|e| format!("{e:?}"))?;
|
||||||
|
|
||||||
if !serai.validator_sets().key_gen_events().await.map_err(|e| format!("{e:?}"))?.is_empty() {
|
if !serai.validator_sets().set_keys_events().await.map_err(|e| format!("{e:?}"))?.is_empty() {
|
||||||
return Ok((block, HasEvents::Notable));
|
return Ok((block, HasEvents::Notable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +70,7 @@ impl<D: Db> ContinuallyRan for CosignIntendTask<D> {
|
|||||||
async move {
|
async move {
|
||||||
let start_block_number = ScanCosignFrom::get(&self.db).unwrap_or(1);
|
let start_block_number = ScanCosignFrom::get(&self.db).unwrap_or(1);
|
||||||
let latest_block_number =
|
let latest_block_number =
|
||||||
self.serai.latest_finalized_block_number().await.map_err(|e| format!("{e:?}"))?.number();
|
self.serai.latest_finalized_block_number().await.map_err(|e| format!("{e:?}"))?;
|
||||||
|
|
||||||
for block_number in start_block_number ..= latest_block_number {
|
for block_number in start_block_number ..= latest_block_number {
|
||||||
let mut txn = self.db.txn();
|
let mut txn = self.db.txn();
|
||||||
@@ -76,26 +80,35 @@ impl<D: Db> ContinuallyRan for CosignIntendTask<D> {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("{e:?}"))?;
|
.map_err(|e| format!("{e:?}"))?;
|
||||||
|
|
||||||
|
let mut builds_upon =
|
||||||
|
BuildsUpon::get(&txn).unwrap_or(IncrementalUnbalancedMerkleTree::new());
|
||||||
|
|
||||||
// Check we are indexing a linear chain
|
// Check we are indexing a linear chain
|
||||||
if (block_number > 1) &&
|
if block.header.builds_upon() !=
|
||||||
(<[u8; 32]>::from(block.header.parent_hash) !=
|
builds_upon.clone().calculate(serai_abi::BLOCK_HEADER_BRANCH_TAG)
|
||||||
SubstrateBlockHash::get(&txn, block_number - 1)
|
|
||||||
.expect("indexing a block but haven't indexed its parent"))
|
|
||||||
{
|
{
|
||||||
Err(format!(
|
Err(format!(
|
||||||
"node's block #{block_number} doesn't build upon the block #{} prior indexed",
|
"node's block #{block_number} doesn't build upon the block #{} prior indexed",
|
||||||
block_number - 1
|
block_number - 1
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
let block_hash = block.hash();
|
let block_hash = block.header.hash();
|
||||||
SubstrateBlockHash::set(&mut txn, block_number, &block_hash);
|
SubstrateBlockHash::set(&mut txn, block_number, &block_hash);
|
||||||
|
builds_upon.append(
|
||||||
|
serai_abi::BLOCK_HEADER_BRANCH_TAG,
|
||||||
|
Blake2b256::new_with_prefix([serai_abi::BLOCK_HEADER_LEAF_TAG])
|
||||||
|
.chain_update(block_hash.0)
|
||||||
|
.finalize()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
BuildsUpon::set(&mut txn, &builds_upon);
|
||||||
|
|
||||||
let global_session_for_this_block = LatestGlobalSessionIntended::get(&txn);
|
let global_session_for_this_block = LatestGlobalSessionIntended::get(&txn);
|
||||||
|
|
||||||
// If this is notable, it creates a new global session, which we index into the database
|
// If this is notable, it creates a new global session, which we index into the database
|
||||||
// now
|
// now
|
||||||
if has_events == HasEvents::Notable {
|
if has_events == HasEvents::Notable {
|
||||||
let serai = self.serai.as_of(block_hash);
|
let serai = self.serai.as_of(block_hash).await.map_err(|e| format!("{e:?}"))?;
|
||||||
let sets_and_keys = cosigning_sets(&serai).await?;
|
let sets_and_keys = cosigning_sets(&serai).await?;
|
||||||
let global_session =
|
let global_session =
|
||||||
GlobalSession::id(sets_and_keys.iter().map(|(set, _key)| *set).collect());
|
GlobalSession::id(sets_and_keys.iter().map(|(set, _key)| *set).collect());
|
||||||
@@ -109,7 +122,7 @@ impl<D: Db> ContinuallyRan for CosignIntendTask<D> {
|
|||||||
keys.insert(set.network, SeraiAddress::from(*key));
|
keys.insert(set.network, SeraiAddress::from(*key));
|
||||||
let stake = serai
|
let stake = serai
|
||||||
.validator_sets()
|
.validator_sets()
|
||||||
.total_allocated_stake(set.network.into())
|
.current_stake(set.network.into())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("{e:?}"))?
|
.map_err(|e| format!("{e:?}"))?
|
||||||
.unwrap_or(Amount(0))
|
.unwrap_or(Amount(0))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use borsh::{BorshSerialize, BorshDeserialize};
|
|||||||
|
|
||||||
use serai_abi::{
|
use serai_abi::{
|
||||||
primitives::{
|
primitives::{
|
||||||
|
BlockHash,
|
||||||
crypto::{Public, KeyPair},
|
crypto::{Public, KeyPair},
|
||||||
network_id::ExternalNetworkId,
|
network_id::ExternalNetworkId,
|
||||||
validator_sets::{Session, ExternalValidatorSet},
|
validator_sets::{Session, ExternalValidatorSet},
|
||||||
@@ -18,7 +19,7 @@ use serai_abi::{
|
|||||||
},
|
},
|
||||||
Block,
|
Block,
|
||||||
};
|
};
|
||||||
use serai_client::{Serai, TemporalSerai};
|
use serai_client_serai::{Serai, TemporalSerai};
|
||||||
|
|
||||||
use serai_db::*;
|
use serai_db::*;
|
||||||
use serai_task::*;
|
use serai_task::*;
|
||||||
@@ -86,7 +87,7 @@ create_db! {
|
|||||||
// The following are populated by the intend task and used throughout the library
|
// The following are populated by the intend task and used throughout the library
|
||||||
|
|
||||||
// An index of Substrate blocks
|
// An index of Substrate blocks
|
||||||
SubstrateBlockHash: (block_number: u64) -> [u8; 32],
|
SubstrateBlockHash: (block_number: u64) -> BlockHash,
|
||||||
// A mapping from a global session's ID to its relevant information.
|
// A mapping from a global session's ID to its relevant information.
|
||||||
GlobalSessions: (global_session: [u8; 32]) -> GlobalSession,
|
GlobalSessions: (global_session: [u8; 32]) -> GlobalSession,
|
||||||
// The last block to be cosigned by a global session.
|
// The last block to be cosigned by a global session.
|
||||||
@@ -124,7 +125,7 @@ async fn keys_for_network(
|
|||||||
network: ExternalNetworkId,
|
network: ExternalNetworkId,
|
||||||
) -> Result<Option<(Session, KeyPair)>, String> {
|
) -> Result<Option<(Session, KeyPair)>, String> {
|
||||||
let Some(latest_session) =
|
let Some(latest_session) =
|
||||||
serai.validator_sets().session(network.into()).await.map_err(|e| format!("{e:?}"))?
|
serai.validator_sets().current_session(network.into()).await.map_err(|e| format!("{e:?}"))?
|
||||||
else {
|
else {
|
||||||
// If this network hasn't had a session declared, move on
|
// If this network hasn't had a session declared, move on
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -272,7 +273,10 @@ impl<D: Db> Cosigning<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch a cosigned Substrate block's hash by its block number.
|
/// Fetch a cosigned Substrate block's hash by its block number.
|
||||||
pub fn cosigned_block(getter: &impl Get, block_number: u64) -> Result<Option<[u8; 32]>, Faulted> {
|
pub fn cosigned_block(
|
||||||
|
getter: &impl Get,
|
||||||
|
block_number: u64,
|
||||||
|
) -> Result<Option<BlockHash>, Faulted> {
|
||||||
if block_number > Self::latest_cosigned_block_number(getter)? {
|
if block_number > Self::latest_cosigned_block_number(getter)? {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! Types used when cosigning Serai. For more info, please see `serai-cosign`.
|
//! Types used when cosigning Serai. For more info, please see `serai-cosign`.
|
||||||
use borsh::{BorshSerialize, BorshDeserialize};
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
|
||||||
use serai_primitives::{crypto::Public, network_id::ExternalNetworkId};
|
use serai_primitives::{BlockHash, crypto::Public, network_id::ExternalNetworkId};
|
||||||
|
|
||||||
/// The schnorrkel context to used when signing a cosign.
|
/// The schnorrkel context to used when signing a cosign.
|
||||||
pub const COSIGN_CONTEXT: &[u8] = b"/serai/coordinator/cosign";
|
pub const COSIGN_CONTEXT: &[u8] = b"/serai/coordinator/cosign";
|
||||||
@@ -16,7 +16,7 @@ pub struct CosignIntent {
|
|||||||
/// The number of the block to cosign.
|
/// The number of the block to cosign.
|
||||||
pub block_number: u64,
|
pub block_number: u64,
|
||||||
/// The hash of the block to cosign.
|
/// The hash of the block to cosign.
|
||||||
pub block_hash: [u8; 32],
|
pub block_hash: BlockHash,
|
||||||
/// If this cosign must be handled before further cosigns are.
|
/// If this cosign must be handled before further cosigns are.
|
||||||
pub notable: bool,
|
pub notable: bool,
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ pub struct Cosign {
|
|||||||
/// The number of the block to cosign.
|
/// The number of the block to cosign.
|
||||||
pub block_number: u64,
|
pub block_number: u64,
|
||||||
/// The hash of the block to cosign.
|
/// The hash of the block to cosign.
|
||||||
pub block_hash: [u8; 32],
|
pub block_hash: BlockHash,
|
||||||
/// The actual cosigner.
|
/// The actual cosigner.
|
||||||
pub cosigner: ExternalNetworkId,
|
pub cosigner: ExternalNetworkId,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use borsh::{BorshSerialize, BorshDeserialize};
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
|
||||||
use serai_primitives::{
|
use serai_primitives::{
|
||||||
@@ -96,6 +98,8 @@ pub enum Event {
|
|||||||
SetDecided {
|
SetDecided {
|
||||||
/// The set decided.
|
/// The set decided.
|
||||||
set: ValidatorSet,
|
set: ValidatorSet,
|
||||||
|
/// The validators decided to be included in the set.
|
||||||
|
validators: Vec<(SeraiAddress, KeyShares)>,
|
||||||
},
|
},
|
||||||
/// A validator set has set their keys.
|
/// A validator set has set their keys.
|
||||||
SetKeys {
|
SetKeys {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ async-lock = "3"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
blake2 = { version = "0.11.0-rc.3", default-features = false }
|
blake2 = { version = "0.11.0-rc.3", default-features = false }
|
||||||
|
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "e01101b68c5b0f588dd4cdee48f801a2c1f75b84" }
|
||||||
|
|
||||||
tokio = { version = "1", default-features = false, features = ["rt", "macros"] }
|
tokio = { version = "1", default-features = false, features = ["rt", "macros"] }
|
||||||
dockertest = "0.5"
|
dockertest = "0.5"
|
||||||
|
|||||||
76
substrate/client/serai/src/coins.rs
Normal file
76
substrate/client/serai/src/coins.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
pub use serai_abi::coins::Event;
|
||||||
|
|
||||||
|
use crate::{RpcError, TemporalSerai};
|
||||||
|
|
||||||
|
/// A `TemporalSerai` scoped to the coins module.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Coins<'a>(pub(super) &'a TemporalSerai<'a>);
|
||||||
|
|
||||||
|
impl<'a> Coins<'a> {
|
||||||
|
/// 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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,10 @@ use abi::{
|
|||||||
|
|
||||||
use async_lock::RwLock;
|
use async_lock::RwLock;
|
||||||
|
|
||||||
|
/// RPC client functionality for the coins module.
|
||||||
|
pub mod coins;
|
||||||
|
use coins::*;
|
||||||
|
|
||||||
/// RPC client functionality for the validator sets module.
|
/// RPC client functionality for the validator sets module.
|
||||||
pub mod validator_sets;
|
pub mod validator_sets;
|
||||||
use validator_sets::*;
|
use validator_sets::*;
|
||||||
@@ -134,25 +138,29 @@ impl Serai {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn block_internal(
|
async fn block_internal(
|
||||||
block: impl Future<Output = Result<String, RpcError>>,
|
block: impl Future<Output = Result<Option<String>, RpcError>>,
|
||||||
) -> Result<Block, RpcError> {
|
) -> Result<Option<Block>, RpcError> {
|
||||||
let bin = block.await?;
|
let bin = block.await?;
|
||||||
Block::deserialize(
|
bin
|
||||||
&mut hex::decode(&bin)
|
.map(|bin| {
|
||||||
.map_err(|_| RpcError::InvalidNode("node returned non-hex-encoded block".to_string()))?
|
Block::deserialize(
|
||||||
.as_slice(),
|
&mut hex::decode(&bin)
|
||||||
)
|
.map_err(|_| RpcError::InvalidNode("node returned non-hex-encoded block".to_string()))?
|
||||||
.map_err(|_| RpcError::InvalidNode("node returned invalid block".to_string()))
|
.as_slice(),
|
||||||
|
)
|
||||||
|
.map_err(|_| RpcError::InvalidNode("node returned invalid block".to_string()))
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch a block from the Serai blockchain.
|
/// Fetch a block from the Serai blockchain.
|
||||||
pub async fn block(&self, block: BlockHash) -> Result<Block, RpcError> {
|
pub async fn block(&self, block: BlockHash) -> Result<Option<Block>, RpcError> {
|
||||||
Self::block_internal(self.call("blockchain/block", &format!(r#"{{ "block": "{block}" }}"#)))
|
Self::block_internal(self.call("blockchain/block", &format!(r#"{{ "block": "{block}" }}"#)))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch a block from the Serai blockchain by its number.
|
/// Fetch a block from the Serai blockchain by its number.
|
||||||
pub async fn block_by_number(&self, block: u64) -> Result<Block, RpcError> {
|
pub async fn block_by_number(&self, block: u64) -> Result<Option<Block>, RpcError> {
|
||||||
Self::block_internal(self.call("blockchain/block", &format!(r#"{{ "block": {block} }}"#))).await
|
Self::block_internal(self.call("blockchain/block", &format!(r#"{{ "block": {block} }}"#))).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +249,11 @@ impl<'a> TemporalSerai<'a> {
|
|||||||
Ok(self.events_borrowed().await?.clone().expect("`TemporalSerai::events` returned None"))
|
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.
|
/// Scope to the validator sets module.
|
||||||
pub fn validator_sets(&self) -> ValidatorSets<'_> {
|
pub fn validator_sets(&self) -> ValidatorSets<'_> {
|
||||||
ValidatorSets(self)
|
ValidatorSets(self)
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ async fn blockchain() {
|
|||||||
let test_finalized_block = |number| {
|
let test_finalized_block = |number| {
|
||||||
let serai = &serai;
|
let serai = &serai;
|
||||||
async move {
|
async move {
|
||||||
let block = serai.block_by_number(number).await.unwrap();
|
let block = serai.block_by_number(number).await.unwrap().unwrap();
|
||||||
assert_eq!(serai.block(block.header.hash()).await.unwrap(), block);
|
assert_eq!(serai.block(block.header.hash()).await.unwrap().unwrap(), block);
|
||||||
assert!(serai.finalized(block.header.hash()).await.unwrap());
|
assert!(serai.finalized(block.header.hash()).await.unwrap());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -70,7 +70,7 @@ async fn blockchain() {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
// Check if it's considered finalized
|
// Check if it's considered finalized
|
||||||
let considered_finalized = serai.finalized(block.header.hash()).await.unwrap();
|
let considered_finalized = serai.finalized(block.unwrap().header.hash()).await.unwrap();
|
||||||
// Ensure the finalized block is the same, meaning this block didn't become finalized as
|
// Ensure the finalized block is the same, meaning this block didn't become finalized as
|
||||||
// we made these RPC requests
|
// we made these RPC requests
|
||||||
if latest_finalized != serai.latest_finalized_block_number().await.unwrap() {
|
if latest_finalized != serai.latest_finalized_block_number().await.unwrap() {
|
||||||
@@ -108,7 +108,7 @@ async fn blockchain() {
|
|||||||
let mut observed_consensus_commitments = HashSet::new();
|
let mut observed_consensus_commitments = HashSet::new();
|
||||||
let mut tagged_block_hashes = vec![];
|
let mut tagged_block_hashes = vec![];
|
||||||
for i in 0 ..= last_block_number {
|
for i in 0 ..= last_block_number {
|
||||||
let block = serai.block_by_number(i).await.unwrap();
|
let block = serai.block_by_number(i).await.unwrap().unwrap();
|
||||||
|
|
||||||
assert_eq!(block.header.number(), i);
|
assert_eq!(block.header.number(), i);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use serai_abi::{
|
use serai_abi::{
|
||||||
primitives::{
|
primitives::{
|
||||||
|
address::SeraiAddress,
|
||||||
network_id::{ExternalNetworkId, NetworkId},
|
network_id::{ExternalNetworkId, NetworkId},
|
||||||
balance::Amount,
|
balance::Amount,
|
||||||
validator_sets::{Session, ExternalValidatorSet, ValidatorSet},
|
validator_sets::{Session, ExternalValidatorSet, ValidatorSet, KeyShares},
|
||||||
},
|
},
|
||||||
validator_sets::Event,
|
validator_sets::Event,
|
||||||
};
|
};
|
||||||
@@ -43,11 +44,16 @@ async fn validator_sets() {
|
|||||||
panic!("finalized block remained the genesis block for over five minutes");
|
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
|
let mut events = serai
|
||||||
.as_of(serai.block_by_number(0).await.unwrap().header.hash())
|
.as_of(serai.block_by_number(0).await.unwrap().unwrap().header.hash())
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.validator_sets()
|
.validator_sets()
|
||||||
@@ -58,27 +64,32 @@ async fn validator_sets() {
|
|||||||
let mut expected = vec![
|
let mut expected = vec![
|
||||||
Event::SetDecided {
|
Event::SetDecided {
|
||||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(0) },
|
set: ValidatorSet { network: NetworkId::Serai, session: Session(0) },
|
||||||
|
validators: genesis_validators.clone(),
|
||||||
},
|
},
|
||||||
Event::SetDecided {
|
Event::SetDecided {
|
||||||
set: ValidatorSet { network: NetworkId::Serai, session: Session(1) },
|
set: ValidatorSet { network: NetworkId::Serai, session: Session(1) },
|
||||||
|
validators: genesis_validators.clone(),
|
||||||
},
|
},
|
||||||
Event::SetDecided {
|
Event::SetDecided {
|
||||||
set: ValidatorSet {
|
set: ValidatorSet {
|
||||||
network: NetworkId::External(ExternalNetworkId::Bitcoin),
|
network: NetworkId::External(ExternalNetworkId::Bitcoin),
|
||||||
session: Session(0),
|
session: Session(0),
|
||||||
},
|
},
|
||||||
|
validators: genesis_validators.clone(),
|
||||||
},
|
},
|
||||||
Event::SetDecided {
|
Event::SetDecided {
|
||||||
set: ValidatorSet {
|
set: ValidatorSet {
|
||||||
network: NetworkId::External(ExternalNetworkId::Ethereum),
|
network: NetworkId::External(ExternalNetworkId::Ethereum),
|
||||||
session: Session(0),
|
session: Session(0),
|
||||||
},
|
},
|
||||||
|
validators: genesis_validators.clone(),
|
||||||
},
|
},
|
||||||
Event::SetDecided {
|
Event::SetDecided {
|
||||||
set: ValidatorSet {
|
set: ValidatorSet {
|
||||||
network: NetworkId::External(ExternalNetworkId::Monero),
|
network: NetworkId::External(ExternalNetworkId::Monero),
|
||||||
session: Session(0),
|
session: Session(0),
|
||||||
},
|
},
|
||||||
|
validators: genesis_validators.clone(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
expected.sort_by_key(|event| borsh::to_vec(event).unwrap());
|
expected.sort_by_key(|event| borsh::to_vec(event).unwrap());
|
||||||
@@ -87,7 +98,7 @@ async fn validator_sets() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai
|
serai
|
||||||
.as_of(serai.block_by_number(0).await.unwrap().header.hash())
|
.as_of(serai.block_by_number(0).await.unwrap().unwrap().header.hash())
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.validator_sets()
|
.validator_sets()
|
||||||
@@ -104,7 +115,7 @@ async fn validator_sets() {
|
|||||||
{
|
{
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai
|
serai
|
||||||
.as_of(serai.block_by_number(1).await.unwrap().header.hash())
|
.as_of(serai.block_by_number(1).await.unwrap().unwrap().header.hash())
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.validator_sets()
|
.validator_sets()
|
||||||
@@ -115,7 +126,7 @@ async fn validator_sets() {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai
|
serai
|
||||||
.as_of(serai.block_by_number(1).await.unwrap().header.hash())
|
.as_of(serai.block_by_number(1).await.unwrap().unwrap().header.hash())
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.validator_sets()
|
.validator_sets()
|
||||||
@@ -127,8 +138,10 @@ async fn validator_sets() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let serai =
|
let serai = serai
|
||||||
serai.as_of(serai.block_by_number(0).await.unwrap().header.hash()).await.unwrap();
|
.as_of(serai.block_by_number(0).await.unwrap().unwrap().header.hash())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
let serai = serai.validator_sets();
|
let serai = serai.validator_sets();
|
||||||
for network in NetworkId::all() {
|
for network in NetworkId::all() {
|
||||||
match network {
|
match network {
|
||||||
|
|||||||
@@ -37,10 +37,12 @@ pub(crate) fn module<
|
|||||||
module.register_method(
|
module.register_method(
|
||||||
"blockchain/is_finalized",
|
"blockchain/is_finalized",
|
||||||
|params, client, _ext| -> Result<_, Error> {
|
|params, client, _ext| -> Result<_, Error> {
|
||||||
let block_hash = block_hash(&**client, ¶ms)?;
|
let Some(block_hash) = block_hash(&**client, ¶ms)? else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
let finalized = client.info().finalized_number;
|
let finalized = client.info().finalized_number;
|
||||||
let Ok(Some(number)) = client.number(block_hash) else {
|
let Ok(Some(number)) = client.number(block_hash) else {
|
||||||
Err(Error::Missing("failed to fetch block's number"))?
|
return Ok(false);
|
||||||
};
|
};
|
||||||
let Ok(status) = client.block_status(block_hash) else {
|
let Ok(status) = client.block_status(block_hash) else {
|
||||||
Err(Error::Internal("failed to fetch block's status"))?
|
Err(Error::Internal("failed to fetch block's status"))?
|
||||||
@@ -53,18 +55,22 @@ pub(crate) fn module<
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
module.register_method("blockchain/block", |params, client, _ext| -> Result<_, Error> {
|
module.register_method("blockchain/block", |params, client, _ext| -> Result<_, Error> {
|
||||||
let block_hash = block_hash(&**client, ¶ms)?;
|
let Some(block_hash) = block_hash(&**client, ¶ms)? else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
let Ok(Some(block)) = client.block(block_hash) else {
|
let Ok(Some(block)) = client.block(block_hash) else {
|
||||||
Err(Error::Missing("couldn't find requested block"))?
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(hex::encode(borsh::to_vec(&serai_abi::Block::from(block.block)).unwrap()))
|
Ok(Some(hex::encode(borsh::to_vec(&serai_abi::Block::from(block.block)).unwrap())))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
module.register_method("blockchain/events", |params, client, _ext| -> Result<_, Error> {
|
module.register_method("blockchain/events", |params, client, _ext| -> Result<_, Error> {
|
||||||
let block_hash = block_hash(&**client, ¶ms)?;
|
let Some(block_hash) = block_hash(&**client, ¶ms)? else {
|
||||||
|
Err(Error::InvalidStateReference)?
|
||||||
|
};
|
||||||
let Ok(events) = client.runtime_api().events(block_hash) else {
|
let Ok(events) = client.runtime_api().events(block_hash) else {
|
||||||
Err(Error::Missing("couldn't fetch the events for the requested block"))?
|
Err(Error::InvalidStateReference)?
|
||||||
};
|
};
|
||||||
Ok(
|
Ok(
|
||||||
events
|
events
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use serai_abi::{primitives::prelude::*, SubstrateBlock as Block};
|
|||||||
pub(super) enum Error {
|
pub(super) enum Error {
|
||||||
Internal(&'static str),
|
Internal(&'static str),
|
||||||
InvalidRequest(&'static str),
|
InvalidRequest(&'static str),
|
||||||
Missing(&'static str),
|
InvalidStateReference,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for jsonrpsee::types::error::ErrorObjectOwned {
|
impl From<Error> for jsonrpsee::types::error::ErrorObjectOwned {
|
||||||
@@ -18,9 +18,11 @@ impl From<Error> for jsonrpsee::types::error::ErrorObjectOwned {
|
|||||||
Error::InvalidRequest(str) => {
|
Error::InvalidRequest(str) => {
|
||||||
jsonrpsee::types::error::ErrorObjectOwned::owned(-2, str, Option::<()>::None)
|
jsonrpsee::types::error::ErrorObjectOwned::owned(-2, str, Option::<()>::None)
|
||||||
}
|
}
|
||||||
Error::Missing(str) => {
|
Error::InvalidStateReference => jsonrpsee::types::error::ErrorObjectOwned::owned(
|
||||||
jsonrpsee::types::error::ErrorObjectOwned::owned(-3, str, Option::<()>::None)
|
-4,
|
||||||
}
|
"the block used as the reference was not locally held",
|
||||||
|
Option::<()>::None,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +32,7 @@ pub(super) fn block_hash<
|
|||||||
>(
|
>(
|
||||||
client: &C,
|
client: &C,
|
||||||
params: &jsonrpsee::types::params::Params,
|
params: &jsonrpsee::types::params::Params,
|
||||||
) -> Result<<Block as sp_runtime::traits::Block>::Hash, Error> {
|
) -> Result<Option<<Block as sp_runtime::traits::Block>::Hash>, Error> {
|
||||||
#[derive(sp_core::serde::Deserialize)]
|
#[derive(sp_core::serde::Deserialize)]
|
||||||
#[serde(crate = "sp_core::serde")]
|
#[serde(crate = "sp_core::serde")]
|
||||||
struct BlockByHash {
|
struct BlockByHash {
|
||||||
@@ -50,13 +52,13 @@ pub(super) fn block_hash<
|
|||||||
}) else {
|
}) else {
|
||||||
return Err(Error::InvalidRequest("requested block hash wasn't a valid hash"));
|
return Err(Error::InvalidRequest("requested block hash wasn't a valid hash"));
|
||||||
};
|
};
|
||||||
block_hash
|
Some(block_hash)
|
||||||
} else {
|
} else {
|
||||||
let Ok(block_number) = params.parse::<BlockByNumber>() else {
|
let Ok(block_number) = params.parse::<BlockByNumber>() else {
|
||||||
return Err(Error::InvalidRequest("requested block wasn't a valid hash nor number"));
|
return Err(Error::InvalidRequest("requested block wasn't a valid hash nor number"));
|
||||||
};
|
};
|
||||||
let Ok(Some(block_hash)) = client.block_hash(block_number.block) else {
|
let Ok(block_hash) = client.block_hash(block_number.block) else {
|
||||||
return Err(Error::Missing("no block hash for that block number"));
|
return Err(Error::Internal("couldn't fetch block hash for block number"));
|
||||||
};
|
};
|
||||||
block_hash
|
block_hash
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -72,7 +72,9 @@ pub(crate) fn module<
|
|||||||
module.register_method(
|
module.register_method(
|
||||||
"validator-sets/current_session",
|
"validator-sets/current_session",
|
||||||
|params, client, _ext| -> Result<_, Error> {
|
|params, client, _ext| -> Result<_, Error> {
|
||||||
let block_hash = block_hash(&**client, ¶ms)?;
|
let Some(block_hash) = block_hash(&**client, ¶ms)? else {
|
||||||
|
Err(Error::InvalidStateReference)?
|
||||||
|
};
|
||||||
let network = network(¶ms)?;
|
let network = network(¶ms)?;
|
||||||
let Ok(session) = client.runtime_api().current_session(block_hash, network) else {
|
let Ok(session) = client.runtime_api().current_session(block_hash, network) else {
|
||||||
Err(Error::Internal("couldn't fetch the session for the requested network"))?
|
Err(Error::Internal("couldn't fetch the session for the requested network"))?
|
||||||
@@ -84,7 +86,9 @@ pub(crate) fn module<
|
|||||||
module.register_method(
|
module.register_method(
|
||||||
"validator-sets/current_stake",
|
"validator-sets/current_stake",
|
||||||
|params, client, _ext| -> Result<_, Error> {
|
|params, client, _ext| -> Result<_, Error> {
|
||||||
let block_hash = block_hash(&**client, ¶ms)?;
|
let Some(block_hash) = block_hash(&**client, ¶ms)? else {
|
||||||
|
Err(Error::InvalidStateReference)?
|
||||||
|
};
|
||||||
let network = network(¶ms)?;
|
let network = network(¶ms)?;
|
||||||
let Ok(stake) = client.runtime_api().current_stake(block_hash, network) else {
|
let Ok(stake) = client.runtime_api().current_stake(block_hash, network) else {
|
||||||
Err(Error::Internal("couldn't fetch the total allocated stake for the requested network"))?
|
Err(Error::Internal("couldn't fetch the total allocated stake for the requested network"))?
|
||||||
@@ -94,7 +98,9 @@ pub(crate) fn module<
|
|||||||
);
|
);
|
||||||
|
|
||||||
module.register_method("validator-sets/keys", |params, client, _ext| -> Result<_, Error> {
|
module.register_method("validator-sets/keys", |params, client, _ext| -> Result<_, Error> {
|
||||||
let block_hash = block_hash(&**client, ¶ms)?;
|
let Some(block_hash) = block_hash(&**client, ¶ms)? else {
|
||||||
|
Err(Error::InvalidStateReference)?
|
||||||
|
};
|
||||||
let set = set(¶ms)?;
|
let set = set(¶ms)?;
|
||||||
let Ok(set) = ExternalValidatorSet::try_from(set) else {
|
let Ok(set) = ExternalValidatorSet::try_from(set) else {
|
||||||
Err(Error::InvalidRequest("requested keys for a non-external validator set"))?
|
Err(Error::InvalidRequest("requested keys for a non-external validator set"))?
|
||||||
|
|||||||
@@ -65,6 +65,17 @@ impl From<SeraiAddress> for Public {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::crypto::Public> for SeraiAddress {
|
||||||
|
fn from(key: crate::crypto::Public) -> Self {
|
||||||
|
Public::from(key).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<SeraiAddress> for crate::crypto::Public {
|
||||||
|
fn from(address: SeraiAddress) -> Self {
|
||||||
|
Public::from(address).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We use Bech32m to encode addresses
|
// We use Bech32m to encode addresses
|
||||||
impl core::fmt::Display for SeraiAddress {
|
impl core::fmt::Display for SeraiAddress {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ impl UnbalancedMerkleTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An unbalanced Merkle tree which is incrementally created.
|
/// An unbalanced Merkle tree which is incrementally created.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "non_canonical_scale_derivations",
|
feature = "non_canonical_scale_derivations",
|
||||||
derive(scale::Encode, scale::Decode, scale::DecodeWithMemTracking)
|
derive(scale::Encode, scale::Decode, scale::DecodeWithMemTracking)
|
||||||
|
|||||||
@@ -326,14 +326,20 @@ impl<Storage: SessionsStorage> Sessions for Storage {
|
|||||||
latest_decided_set,
|
latest_decided_set,
|
||||||
KeySharesStruct::try_from(total_key_shares).expect("amortization failure"),
|
KeySharesStruct::try_from(total_key_shares).expect("amortization failure"),
|
||||||
);
|
);
|
||||||
for (key, key_shares) in selected_validators {
|
for (key, key_shares) in &selected_validators {
|
||||||
Storage::SelectedValidators::insert(
|
Storage::SelectedValidators::insert(
|
||||||
selected_validators_key(latest_decided_set, key),
|
selected_validators_key(latest_decided_set, *key),
|
||||||
key_shares,
|
key_shares,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::<Storage::Config>::emit_event(Event::SetDecided { set: latest_decided_set });
|
Core::<Storage::Config>::emit_event(Event::SetDecided {
|
||||||
|
set: latest_decided_set,
|
||||||
|
validators: selected_validators
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, key_shares)| (key.into(), key_shares))
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user