mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Reorganize serai-client
Instead of functions taking a block hash, has a scope to a block hash before functions can be called. Separates functions by pallets.
This commit is contained in:
94
substrate/client/src/serai/coins.rs
Normal file
94
substrate/client/src/serai/coins.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
use sp_core::sr25519::Public;
|
||||||
|
use serai_runtime::{
|
||||||
|
primitives::{SeraiAddress, SubstrateAmount, Amount, Coin, Balance},
|
||||||
|
assets::{AssetDetails, AssetAccount},
|
||||||
|
tokens, Tokens, Runtime,
|
||||||
|
};
|
||||||
|
pub use tokens::primitives;
|
||||||
|
use primitives::OutInstruction;
|
||||||
|
|
||||||
|
use subxt::tx::Payload;
|
||||||
|
|
||||||
|
use crate::{TemporalSerai, SeraiError, Composite, scale_value, scale_composite};
|
||||||
|
|
||||||
|
const PALLET: &str = "Tokens";
|
||||||
|
|
||||||
|
pub type TokensEvent = tokens::Event<Runtime>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct SeraiCoins<'a>(pub(crate) TemporalSerai<'a>);
|
||||||
|
impl<'a> SeraiCoins<'a> {
|
||||||
|
pub fn into_inner(self) -> TemporalSerai<'a> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn mint_events(&self) -> Result<Vec<TokensEvent>, SeraiError> {
|
||||||
|
self.0.events::<Tokens, _>(|event| matches!(event, TokensEvent::Mint { .. })).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn burn_events(&self) -> Result<Vec<TokensEvent>, SeraiError> {
|
||||||
|
self.0.events::<Tokens, _>(|event| matches!(event, TokensEvent::Burn { .. })).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sri_balance(&self, address: SeraiAddress) -> Result<u64, SeraiError> {
|
||||||
|
let data: Option<
|
||||||
|
serai_runtime::system::AccountInfo<u32, serai_runtime::balances::AccountData<u64>>,
|
||||||
|
> = self.0.storage("System", "Account", Some(vec![scale_value(address)])).await?;
|
||||||
|
Ok(data.map(|data| data.data.free).unwrap_or(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn token_supply(&self, coin: Coin) -> Result<Amount, SeraiError> {
|
||||||
|
Ok(Amount(
|
||||||
|
self
|
||||||
|
.0
|
||||||
|
.storage::<AssetDetails<SubstrateAmount, SeraiAddress, SubstrateAmount>>(
|
||||||
|
"Assets",
|
||||||
|
"Asset",
|
||||||
|
Some(vec![scale_value(coin)]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.map(|token| token.supply)
|
||||||
|
.unwrap_or(0),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn token_balance(
|
||||||
|
&self,
|
||||||
|
coin: Coin,
|
||||||
|
address: SeraiAddress,
|
||||||
|
) -> Result<Amount, SeraiError> {
|
||||||
|
Ok(Amount(
|
||||||
|
self
|
||||||
|
.0
|
||||||
|
.storage::<AssetAccount<SubstrateAmount, SubstrateAmount, (), Public>>(
|
||||||
|
"Assets",
|
||||||
|
"Account",
|
||||||
|
Some(vec![scale_value(coin), scale_value(address)]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.map(|account| account.balance())
|
||||||
|
.unwrap_or(0),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_sri(to: SeraiAddress, amount: Amount) -> Payload<Composite<()>> {
|
||||||
|
Payload::new(
|
||||||
|
"Balances",
|
||||||
|
// TODO: Use transfer_allow_death?
|
||||||
|
// TODO: Replace the Balances pallet with something much simpler
|
||||||
|
"transfer",
|
||||||
|
scale_composite(serai_runtime::balances::Call::<Runtime>::transfer {
|
||||||
|
dest: to,
|
||||||
|
value: amount.0,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn burn(balance: Balance, instruction: OutInstruction) -> Payload<Composite<()>> {
|
||||||
|
Payload::new(
|
||||||
|
PALLET,
|
||||||
|
"burn",
|
||||||
|
scale_composite(tokens::Call::<Runtime>::burn { balance, instruction }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,42 +6,42 @@ use subxt::utils::Encoded;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
primitives::{BlockHash, NetworkId},
|
primitives::{BlockHash, NetworkId},
|
||||||
SeraiError, Serai, scale_value,
|
SeraiError, Serai, TemporalSerai, scale_value,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type InInstructionsEvent = in_instructions::Event<Runtime>;
|
pub type InInstructionsEvent = in_instructions::Event<Runtime>;
|
||||||
|
|
||||||
const PALLET: &str = "InInstructions";
|
const PALLET: &str = "InInstructions";
|
||||||
|
|
||||||
impl Serai {
|
#[derive(Clone, Copy)]
|
||||||
pub async fn get_latest_block_for_network(
|
pub struct SeraiInInstructions<'a>(pub(crate) TemporalSerai<'a>);
|
||||||
|
impl<'a> SeraiInInstructions<'a> {
|
||||||
|
pub fn into_inner(self) -> TemporalSerai<'a> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn latest_block_for_network(
|
||||||
&self,
|
&self,
|
||||||
hash: [u8; 32],
|
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
) -> Result<Option<BlockHash>, SeraiError> {
|
) -> Result<Option<BlockHash>, SeraiError> {
|
||||||
self.storage(PALLET, "LatestNetworkBlock", Some(vec![scale_value(network)]), hash).await
|
self.0.storage(PALLET, "LatestNetworkBlock", Some(vec![scale_value(network)])).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_last_batch_for_network(
|
pub async fn last_batch_for_network(
|
||||||
&self,
|
&self,
|
||||||
hash: [u8; 32],
|
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
) -> Result<Option<u32>, SeraiError> {
|
) -> Result<Option<u32>, SeraiError> {
|
||||||
self.storage(PALLET, "LastBatch", Some(vec![scale_value(network)]), hash).await
|
self.0.storage(PALLET, "LastBatch", Some(vec![scale_value(network)])).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_batch_events(
|
pub async fn batch_events(&self) -> Result<Vec<InInstructionsEvent>, SeraiError> {
|
||||||
&self,
|
|
||||||
block: [u8; 32],
|
|
||||||
) -> Result<Vec<InInstructionsEvent>, SeraiError> {
|
|
||||||
self
|
self
|
||||||
.events::<InInstructions, _>(block, |event| {
|
.0
|
||||||
matches!(event, InInstructionsEvent::Batch { .. })
|
.events::<InInstructions, _>(|event| matches!(event, InInstructionsEvent::Batch { .. }))
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_batch(batch: SignedBatch) -> Encoded {
|
pub fn execute_batch(batch: SignedBatch) -> Encoded {
|
||||||
Self::unsigned::<InInstructions, _>(&in_instructions::Call::<Runtime>::execute_batch { batch })
|
Serai::unsigned::<InInstructions, _>(&in_instructions::Call::<Runtime>::execute_batch { batch })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,12 @@ use serai_runtime::{
|
|||||||
system::Config, support::traits::PalletInfo as PalletInfoTrait, PalletInfo, Runtime,
|
system::Config, support::traits::PalletInfo as PalletInfoTrait, PalletInfo, Runtime,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod tokens;
|
pub mod coins;
|
||||||
|
pub use coins::SeraiCoins;
|
||||||
pub mod in_instructions;
|
pub mod in_instructions;
|
||||||
|
pub use in_instructions::SeraiInInstructions;
|
||||||
pub mod validator_sets;
|
pub mod validator_sets;
|
||||||
|
pub use validator_sets::SeraiValidatorSets;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Encode, Decode)]
|
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Encode, Decode)]
|
||||||
pub struct Tip {
|
pub struct Tip {
|
||||||
@@ -136,153 +139,14 @@ pub enum SeraiError {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Serai(OnlineClient<SeraiConfig>);
|
pub struct Serai(OnlineClient<SeraiConfig>);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct TemporalSerai<'a>(pub(crate) &'a Serai, pub(crate) [u8; 32]);
|
||||||
|
|
||||||
impl Serai {
|
impl Serai {
|
||||||
pub async fn new(url: &str) -> Result<Self, SeraiError> {
|
pub async fn new(url: &str) -> Result<Self, SeraiError> {
|
||||||
Ok(Serai(OnlineClient::<SeraiConfig>::from_url(url).await.map_err(SeraiError::RpcError)?))
|
Ok(Serai(OnlineClient::<SeraiConfig>::from_url(url).await.map_err(SeraiError::RpcError)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn storage<R: Decode>(
|
|
||||||
&self,
|
|
||||||
pallet: &'static str,
|
|
||||||
name: &'static str,
|
|
||||||
keys: Option<Vec<Value>>,
|
|
||||||
block: [u8; 32],
|
|
||||||
) -> Result<Option<R>, SeraiError> {
|
|
||||||
let storage = self.0.storage();
|
|
||||||
#[allow(clippy::unwrap_or_default)]
|
|
||||||
let address = subxt::dynamic::storage(pallet, name, keys.unwrap_or(vec![]));
|
|
||||||
debug_assert!(storage.validate(&address).is_ok(), "invalid storage address");
|
|
||||||
|
|
||||||
storage
|
|
||||||
.at(block.into())
|
|
||||||
.fetch(&address)
|
|
||||||
.await
|
|
||||||
.map_err(SeraiError::RpcError)?
|
|
||||||
.map(|res| R::decode(&mut res.encoded()).map_err(|_| SeraiError::InvalidRuntime))
|
|
||||||
.transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn events<P: 'static, E: Decode>(
|
|
||||||
&self,
|
|
||||||
block: [u8; 32],
|
|
||||||
filter: impl Fn(&E) -> bool,
|
|
||||||
) -> Result<Vec<E>, SeraiError> {
|
|
||||||
let mut res = vec![];
|
|
||||||
for event in self.0.events().at(block.into()).await.map_err(SeraiError::RpcError)?.iter() {
|
|
||||||
let event = event.map_err(|_| SeraiError::InvalidRuntime)?;
|
|
||||||
if PalletInfo::index::<P>().unwrap() == usize::from(event.pallet_index()) {
|
|
||||||
let mut with_variant: &[u8] =
|
|
||||||
&[[event.variant_index()].as_ref(), event.field_bytes()].concat();
|
|
||||||
let event = E::decode(&mut with_variant).map_err(|_| SeraiError::InvalidRuntime)?;
|
|
||||||
if filter(&event) {
|
|
||||||
res.push(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_latest_block_hash(&self) -> Result<[u8; 32], SeraiError> {
|
|
||||||
Ok(self.0.rpc().finalized_head().await.map_err(SeraiError::RpcError)?.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_latest_block(&self) -> Result<Block, SeraiError> {
|
|
||||||
Block::new(
|
|
||||||
self
|
|
||||||
.0
|
|
||||||
.rpc()
|
|
||||||
.block(Some(self.0.rpc().finalized_head().await.map_err(SeraiError::RpcError)?))
|
|
||||||
.await
|
|
||||||
.map_err(SeraiError::RpcError)?
|
|
||||||
.ok_or(SeraiError::InvalidNode)?
|
|
||||||
.block,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is no provided method for this
|
|
||||||
// TODO: Add one to Serai
|
|
||||||
pub async fn is_finalized(&self, header: &Header) -> Result<Option<bool>, SeraiError> {
|
|
||||||
// Get the latest finalized block
|
|
||||||
let finalized = self.get_latest_block_hash().await?.into();
|
|
||||||
// If the latest finalized block is this block, return true
|
|
||||||
if finalized == header.hash() {
|
|
||||||
return Ok(Some(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(finalized) =
|
|
||||||
self.0.rpc().header(Some(finalized)).await.map_err(SeraiError::RpcError)?
|
|
||||||
else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the finalized block has a lower number, this block can't be finalized
|
|
||||||
if finalized.number() < header.number() {
|
|
||||||
return Ok(Some(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This block, if finalized, comes before the finalized block
|
|
||||||
// If we request the hash of this block's number, Substrate will return the hash on the main
|
|
||||||
// chain
|
|
||||||
// If that hash is this hash, this block is finalized
|
|
||||||
let Some(hash) =
|
|
||||||
self.0.rpc().block_hash(Some(header.number().into())).await.map_err(SeraiError::RpcError)?
|
|
||||||
else {
|
|
||||||
// This is an error since there is a block at this index
|
|
||||||
Err(SeraiError::InvalidNode)?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(header.hash() == hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_block(&self, hash: [u8; 32]) -> Result<Option<Block>, SeraiError> {
|
|
||||||
let Some(res) = self.0.rpc().block(Some(hash.into())).await.map_err(SeraiError::RpcError)?
|
|
||||||
else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only return finalized blocks
|
|
||||||
if self.is_finalized(&res.block.header).await? != Some(true) {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(Block::new(res.block)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ideally, this would be get_block_hash, not get_block_by_number
|
|
||||||
// Unfortunately, in order to only operate over only finalized data, we have to check the
|
|
||||||
// returned hash is for a finalized block. We can only do that by calling the extensive
|
|
||||||
// is_finalized method, which at least requires the header
|
|
||||||
// In practice, the block is likely more useful than the header
|
|
||||||
pub async fn get_block_by_number(&self, number: u64) -> Result<Option<Block>, SeraiError> {
|
|
||||||
let Some(hash) =
|
|
||||||
self.0.rpc().block_hash(Some(number.into())).await.map_err(SeraiError::RpcError)?
|
|
||||||
else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
self.get_block(hash.into()).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A stream which yields whenever new block(s) have been finalized.
|
|
||||||
pub async fn newly_finalized_block(
|
|
||||||
&self,
|
|
||||||
) -> Result<impl Stream<Item = Result<(), SeraiError>>, SeraiError> {
|
|
||||||
Ok(self.0.rpc().subscribe_finalized_block_headers().await.map_err(SeraiError::RpcError)?.map(
|
|
||||||
|next| {
|
|
||||||
next.map_err(SeraiError::RpcError)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_nonce(&self, address: &SeraiAddress) -> Result<u32, SeraiError> {
|
|
||||||
self
|
|
||||||
.0
|
|
||||||
.rpc()
|
|
||||||
.system_account_next_index(&sp_core::sr25519::Public(address.0).to_string())
|
|
||||||
.await
|
|
||||||
.map_err(SeraiError::RpcError)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unsigned<P: 'static, C: Encode>(call: &C) -> Encoded {
|
fn unsigned<P: 'static, C: Encode>(call: &C) -> Encoded {
|
||||||
// TODO: Should Serai purge the old transaction code AND set this to 0/1?
|
// TODO: Should Serai purge the old transaction code AND set this to 0/1?
|
||||||
const TRANSACTION_VERSION: u8 = 4;
|
const TRANSACTION_VERSION: u8 = 4;
|
||||||
@@ -322,29 +186,173 @@ impl Serai {
|
|||||||
self.0.rpc().submit_extrinsic(tx).await.map(|_| ()).map_err(SeraiError::RpcError)
|
self.0.rpc().submit_extrinsic(tx).await.map(|_| ()).map_err(SeraiError::RpcError)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_sri_balance(
|
pub async fn latest_block_hash(&self) -> Result<[u8; 32], SeraiError> {
|
||||||
&self,
|
Ok(self.0.rpc().finalized_head().await.map_err(SeraiError::RpcError)?.into())
|
||||||
block: [u8; 32],
|
|
||||||
address: SeraiAddress,
|
|
||||||
) -> Result<u64, SeraiError> {
|
|
||||||
let data: Option<
|
|
||||||
serai_runtime::system::AccountInfo<u32, serai_runtime::balances::AccountData<u64>>,
|
|
||||||
> = self.storage("System", "Account", Some(vec![scale_value(address)]), block).await?;
|
|
||||||
Ok(data.map(|data| data.data.free).unwrap_or(0))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transfer_sri(to: SeraiAddress, amount: Amount) -> Payload<Composite<()>> {
|
pub async fn latest_block(&self) -> Result<Block, SeraiError> {
|
||||||
Payload::new(
|
Block::new(
|
||||||
"Balances",
|
self
|
||||||
// TODO: Use transfer_allow_death?
|
.0
|
||||||
// TODO: Replace the Balances pallet with something much simpler
|
.rpc()
|
||||||
"transfer",
|
.block(Some(self.0.rpc().finalized_head().await.map_err(SeraiError::RpcError)?))
|
||||||
scale_composite(serai_runtime::balances::Call::<Runtime>::transfer {
|
.await
|
||||||
dest: to,
|
.map_err(SeraiError::RpcError)?
|
||||||
value: amount.0,
|
.ok_or(SeraiError::InvalidNode)?
|
||||||
}),
|
.block,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There is no provided method for this
|
||||||
|
// TODO: Add one to Serai
|
||||||
|
pub async fn is_finalized(&self, header: &Header) -> Result<Option<bool>, SeraiError> {
|
||||||
|
// Get the latest finalized block
|
||||||
|
let finalized = self.latest_block_hash().await?.into();
|
||||||
|
// If the latest finalized block is this block, return true
|
||||||
|
if finalized == header.hash() {
|
||||||
|
return Ok(Some(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(finalized) =
|
||||||
|
self.0.rpc().header(Some(finalized)).await.map_err(SeraiError::RpcError)?
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the finalized block has a lower number, this block can't be finalized
|
||||||
|
if finalized.number() < header.number() {
|
||||||
|
return Ok(Some(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This block, if finalized, comes before the finalized block
|
||||||
|
// If we request the hash of this block's number, Substrate will return the hash on the main
|
||||||
|
// chain
|
||||||
|
// If that hash is this hash, this block is finalized
|
||||||
|
let Some(hash) =
|
||||||
|
self.0.rpc().block_hash(Some(header.number().into())).await.map_err(SeraiError::RpcError)?
|
||||||
|
else {
|
||||||
|
// This is an error since there is a block at this index
|
||||||
|
Err(SeraiError::InvalidNode)?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(header.hash() == hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn block(&self, hash: [u8; 32]) -> Result<Option<Block>, SeraiError> {
|
||||||
|
let Some(res) = self.0.rpc().block(Some(hash.into())).await.map_err(SeraiError::RpcError)?
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only return finalized blocks
|
||||||
|
if self.is_finalized(&res.block.header).await? != Some(true) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(Block::new(res.block)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ideally, this would be block_hash, not block_by_number
|
||||||
|
// Unfortunately, in order to only operate over only finalized data, we have to check the
|
||||||
|
// returned hash is for a finalized block. We can only do that by calling the extensive
|
||||||
|
// is_finalized method, which at least requires the header
|
||||||
|
// In practice, the block is likely more useful than the header
|
||||||
|
pub async fn block_by_number(&self, number: u64) -> Result<Option<Block>, SeraiError> {
|
||||||
|
let Some(hash) =
|
||||||
|
self.0.rpc().block_hash(Some(number.into())).await.map_err(SeraiError::RpcError)?
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
self.block(hash.into()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stream which yields whenever new block(s) have been finalized.
|
||||||
|
pub async fn newly_finalized_block(
|
||||||
|
&self,
|
||||||
|
) -> Result<impl Stream<Item = Result<(), SeraiError>>, SeraiError> {
|
||||||
|
Ok(self.0.rpc().subscribe_finalized_block_headers().await.map_err(SeraiError::RpcError)?.map(
|
||||||
|
|next| {
|
||||||
|
next.map_err(SeraiError::RpcError)?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn nonce(&self, address: &SeraiAddress) -> Result<u32, SeraiError> {
|
||||||
|
self
|
||||||
|
.0
|
||||||
|
.rpc()
|
||||||
|
.system_account_next_index(&sp_core::sr25519::Public(address.0).to_string())
|
||||||
|
.await
|
||||||
|
.map_err(SeraiError::RpcError)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with_current_latest_block(&self) -> Result<TemporalSerai, SeraiError> {
|
||||||
|
let latest = self.latest_block_hash().await?;
|
||||||
|
Ok(TemporalSerai(self, latest))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a TemporalSerai able to retrieve state as of the specified block.
|
||||||
|
pub fn as_of(&self, block: [u8; 32]) -> TemporalSerai {
|
||||||
|
TemporalSerai(self, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TemporalSerai<'a> {
|
||||||
|
pub fn into_inner(&self) -> &Serai {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn events<P: 'static, E: Decode>(
|
||||||
|
&self,
|
||||||
|
filter: impl Fn(&E) -> bool,
|
||||||
|
) -> Result<Vec<E>, SeraiError> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for event in self.0 .0.events().at(self.1.into()).await.map_err(SeraiError::RpcError)?.iter() {
|
||||||
|
let event = event.map_err(|_| SeraiError::InvalidRuntime)?;
|
||||||
|
if PalletInfo::index::<P>().unwrap() == usize::from(event.pallet_index()) {
|
||||||
|
let mut with_variant: &[u8] =
|
||||||
|
&[[event.variant_index()].as_ref(), event.field_bytes()].concat();
|
||||||
|
let event = E::decode(&mut with_variant).map_err(|_| SeraiError::InvalidRuntime)?;
|
||||||
|
if filter(&event) {
|
||||||
|
res.push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn storage<R: Decode>(
|
||||||
|
&self,
|
||||||
|
pallet: &'static str,
|
||||||
|
name: &'static str,
|
||||||
|
keys: Option<Vec<Value>>,
|
||||||
|
) -> Result<Option<R>, SeraiError> {
|
||||||
|
let storage = self.0 .0.storage();
|
||||||
|
#[allow(clippy::unwrap_or_default)]
|
||||||
|
let address = subxt::dynamic::storage(pallet, name, keys.unwrap_or(vec![]));
|
||||||
|
debug_assert!(storage.validate(&address).is_ok(), "invalid storage address");
|
||||||
|
|
||||||
|
storage
|
||||||
|
.at(self.1.into())
|
||||||
|
.fetch(&address)
|
||||||
|
.await
|
||||||
|
.map_err(SeraiError::RpcError)?
|
||||||
|
.map(|res| R::decode(&mut res.encoded()).map_err(|_| SeraiError::InvalidRuntime))
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn coins(self) -> SeraiCoins<'a> {
|
||||||
|
SeraiCoins(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn in_instructions(self) -> SeraiInInstructions<'a> {
|
||||||
|
SeraiInInstructions(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validator_sets(self) -> SeraiValidatorSets<'a> {
|
||||||
|
SeraiValidatorSets(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
use sp_core::sr25519::Public;
|
|
||||||
use serai_runtime::{
|
|
||||||
primitives::{SeraiAddress, SubstrateAmount, Amount, Coin, Balance},
|
|
||||||
assets::{AssetDetails, AssetAccount},
|
|
||||||
tokens, Tokens, Runtime,
|
|
||||||
};
|
|
||||||
pub use tokens::primitives;
|
|
||||||
use primitives::OutInstruction;
|
|
||||||
|
|
||||||
use subxt::tx::Payload;
|
|
||||||
|
|
||||||
use crate::{Serai, SeraiError, Composite, scale_value, scale_composite};
|
|
||||||
|
|
||||||
const PALLET: &str = "Tokens";
|
|
||||||
|
|
||||||
pub type TokensEvent = tokens::Event<Runtime>;
|
|
||||||
|
|
||||||
impl Serai {
|
|
||||||
pub async fn get_mint_events(&self, block: [u8; 32]) -> Result<Vec<TokensEvent>, SeraiError> {
|
|
||||||
self.events::<Tokens, _>(block, |event| matches!(event, TokensEvent::Mint { .. })).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_token_supply(&self, block: [u8; 32], coin: Coin) -> Result<Amount, SeraiError> {
|
|
||||||
Ok(Amount(
|
|
||||||
self
|
|
||||||
.storage::<AssetDetails<SubstrateAmount, SeraiAddress, SubstrateAmount>>(
|
|
||||||
"Assets",
|
|
||||||
"Asset",
|
|
||||||
Some(vec![scale_value(coin)]),
|
|
||||||
block,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.map(|token| token.supply)
|
|
||||||
.unwrap_or(0),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_token_balance(
|
|
||||||
&self,
|
|
||||||
block: [u8; 32],
|
|
||||||
coin: Coin,
|
|
||||||
address: SeraiAddress,
|
|
||||||
) -> Result<Amount, SeraiError> {
|
|
||||||
Ok(Amount(
|
|
||||||
self
|
|
||||||
.storage::<AssetAccount<SubstrateAmount, SubstrateAmount, (), Public>>(
|
|
||||||
"Assets",
|
|
||||||
"Account",
|
|
||||||
Some(vec![scale_value(coin), scale_value(address)]),
|
|
||||||
block,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.map(|account| account.balance())
|
|
||||||
.unwrap_or(0),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn burn(balance: Balance, instruction: OutInstruction) -> Payload<Composite<()>> {
|
|
||||||
Payload::new(
|
|
||||||
PALLET,
|
|
||||||
"burn",
|
|
||||||
scale_composite(tokens::Call::<Runtime>::burn { balance, instruction }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_burn_events(&self, block: [u8; 32]) -> Result<Vec<TokensEvent>, SeraiError> {
|
|
||||||
self.events::<Tokens, _>(block, |event| matches!(event, TokensEvent::Burn { .. })).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,89 +6,67 @@ use primitives::{Session, ValidatorSet, KeyPair};
|
|||||||
|
|
||||||
use subxt::utils::Encoded;
|
use subxt::utils::Encoded;
|
||||||
|
|
||||||
use crate::{primitives::NetworkId, Serai, SeraiError, scale_value};
|
use crate::{primitives::NetworkId, Serai, TemporalSerai, SeraiError, scale_value};
|
||||||
|
|
||||||
const PALLET: &str = "ValidatorSets";
|
const PALLET: &str = "ValidatorSets";
|
||||||
|
|
||||||
pub type ValidatorSetsEvent = validator_sets::Event<Runtime>;
|
pub type ValidatorSetsEvent = validator_sets::Event<Runtime>;
|
||||||
|
|
||||||
impl Serai {
|
#[derive(Clone, Copy)]
|
||||||
pub async fn get_new_set_events(
|
pub struct SeraiValidatorSets<'a>(pub(crate) TemporalSerai<'a>);
|
||||||
&self,
|
impl<'a> SeraiValidatorSets<'a> {
|
||||||
block: [u8; 32],
|
pub fn into_inner(self) -> TemporalSerai<'a> {
|
||||||
) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new_set_events(&self) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
||||||
self
|
self
|
||||||
.events::<ValidatorSets, _>(block, |event| matches!(event, ValidatorSetsEvent::NewSet { .. }))
|
.0
|
||||||
|
.events::<ValidatorSets, _>(|event| matches!(event, ValidatorSetsEvent::NewSet { .. }))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_key_gen_events(
|
pub async fn key_gen_events(&self) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
||||||
&self,
|
|
||||||
block: [u8; 32],
|
|
||||||
) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
|
||||||
self
|
self
|
||||||
.events::<ValidatorSets, _>(block, |event| matches!(event, ValidatorSetsEvent::KeyGen { .. }))
|
.0
|
||||||
|
.events::<ValidatorSets, _>(|event| matches!(event, ValidatorSetsEvent::KeyGen { .. }))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_session(
|
pub async fn session(&self, network: NetworkId) -> Result<Option<Session>, SeraiError> {
|
||||||
&self,
|
self.0.storage(PALLET, "CurrentSession", Some(vec![scale_value(network)])).await
|
||||||
network: NetworkId,
|
|
||||||
at_hash: [u8; 32],
|
|
||||||
) -> Result<Option<Session>, SeraiError> {
|
|
||||||
self.storage(PALLET, "CurrentSession", Some(vec![scale_value(network)]), at_hash).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_validator_set_participants(
|
pub async fn participants(&self, network: NetworkId) -> Result<Option<Vec<Public>>, SeraiError> {
|
||||||
&self,
|
self.0.storage(PALLET, "Participants", Some(vec![scale_value(network)])).await
|
||||||
network: NetworkId,
|
|
||||||
at_hash: [u8; 32],
|
|
||||||
) -> Result<Option<Vec<Public>>, SeraiError> {
|
|
||||||
self.storage(PALLET, "Participants", Some(vec![scale_value(network)]), at_hash).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_allocation_per_key_share(
|
pub async fn allocation_per_key_share(
|
||||||
&self,
|
&self,
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
at_hash: [u8; 32],
|
|
||||||
) -> Result<Option<Amount>, SeraiError> {
|
) -> Result<Option<Amount>, SeraiError> {
|
||||||
self.storage(PALLET, "AllocationPerKeyShare", Some(vec![scale_value(network)]), at_hash).await
|
self.0.storage(PALLET, "AllocationPerKeyShare", Some(vec![scale_value(network)])).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_allocation(
|
pub async fn allocation(
|
||||||
&self,
|
&self,
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
key: Public,
|
key: Public,
|
||||||
at_hash: [u8; 32],
|
|
||||||
) -> Result<Option<Amount>, SeraiError> {
|
) -> Result<Option<Amount>, SeraiError> {
|
||||||
self
|
self.0.storage(PALLET, "Allocations", Some(vec![scale_value(network), scale_value(key)])).await
|
||||||
.storage(PALLET, "Allocations", Some(vec![scale_value(network), scale_value(key)]), at_hash)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_validator_set_musig_key(
|
pub async fn musig_key(&self, set: ValidatorSet) -> Result<Option<[u8; 32]>, SeraiError> {
|
||||||
&self,
|
self.0.storage(PALLET, "MuSigKeys", Some(vec![scale_value(set)])).await
|
||||||
set: ValidatorSet,
|
|
||||||
at_hash: [u8; 32],
|
|
||||||
) -> Result<Option<[u8; 32]>, SeraiError> {
|
|
||||||
self.storage(PALLET, "MuSigKeys", Some(vec![scale_value(set)]), at_hash).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Store these separately since we almost never need both at once?
|
// TODO: Store these separately since we almost never need both at once?
|
||||||
pub async fn get_keys(
|
pub async fn keys(&self, set: ValidatorSet) -> Result<Option<KeyPair>, SeraiError> {
|
||||||
&self,
|
self.0.storage(PALLET, "Keys", Some(vec![scale_value(set)])).await
|
||||||
set: ValidatorSet,
|
|
||||||
at_hash: [u8; 32],
|
|
||||||
) -> Result<Option<KeyPair>, SeraiError> {
|
|
||||||
self.storage(PALLET, "Keys", Some(vec![scale_value(set)]), at_hash).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_validator_set_keys(
|
pub fn set_keys(network: NetworkId, key_pair: KeyPair, signature: Signature) -> Encoded {
|
||||||
network: NetworkId,
|
Serai::unsigned::<ValidatorSets, _>(&validator_sets::Call::<Runtime>::set_keys {
|
||||||
key_pair: KeyPair,
|
|
||||||
signature: Signature,
|
|
||||||
) -> Encoded {
|
|
||||||
Self::unsigned::<ValidatorSets, _>(&validator_sets::Call::<Runtime>::set_keys {
|
|
||||||
network,
|
network,
|
||||||
key_pair,
|
key_pair,
|
||||||
signature,
|
signature,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use serai_client::{
|
|||||||
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
||||||
InInstructionsEvent,
|
InInstructionsEvent,
|
||||||
},
|
},
|
||||||
tokens::TokensEvent,
|
coins::TokensEvent,
|
||||||
Serai,
|
Serai,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,8 +48,11 @@ serai_test!(
|
|||||||
let block = provide_batch(batch.clone()).await;
|
let block = provide_batch(batch.clone()).await;
|
||||||
|
|
||||||
let serai = serai().await;
|
let serai = serai().await;
|
||||||
assert_eq!(serai.get_latest_block_for_network(block, network).await.unwrap(), Some(block_hash));
|
let serai = serai.as_of(block);
|
||||||
let batches = serai.get_batch_events(block).await.unwrap();
|
{
|
||||||
|
let serai = serai.in_instructions();
|
||||||
|
assert_eq!(serai.latest_block_for_network(network).await.unwrap(), Some(block_hash));
|
||||||
|
let batches = serai.batch_events().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
batches,
|
batches,
|
||||||
vec![InInstructionsEvent::Batch {
|
vec![InInstructionsEvent::Batch {
|
||||||
@@ -59,12 +62,11 @@ serai_test!(
|
|||||||
instructions_hash: Blake2b::<U32>::digest(batch.instructions.encode()).into(),
|
instructions_hash: Blake2b::<U32>::digest(batch.instructions.encode()).into(),
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(
|
let serai = serai.coins();
|
||||||
serai.get_mint_events(block).await.unwrap(),
|
assert_eq!(serai.mint_events().await.unwrap(), vec![TokensEvent::Mint { address, balance }],);
|
||||||
vec![TokensEvent::Mint { address, balance }],
|
assert_eq!(serai.token_supply(coin).await.unwrap(), amount);
|
||||||
);
|
assert_eq!(serai.token_balance(coin, address).await.unwrap(), amount);
|
||||||
assert_eq!(serai.get_token_supply(block, coin).await.unwrap(), amount);
|
|
||||||
assert_eq!(serai.get_token_balance(block, coin, address).await.unwrap(), amount);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ use serai_client::{
|
|||||||
InInstructionsEvent,
|
InInstructionsEvent,
|
||||||
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
||||||
},
|
},
|
||||||
tokens::{primitives::OutInstruction, TokensEvent},
|
coins::{primitives::OutInstruction, TokensEvent},
|
||||||
PairSigner, Serai,
|
PairSigner, Serai, SeraiCoins,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
@@ -55,7 +55,8 @@ serai_test!(
|
|||||||
let block = provide_batch(batch.clone()).await;
|
let block = provide_batch(batch.clone()).await;
|
||||||
|
|
||||||
let serai = serai().await;
|
let serai = serai().await;
|
||||||
let batches = serai.get_batch_events(block).await.unwrap();
|
let serai = serai.as_of(block);
|
||||||
|
let batches = serai.in_instructions().batch_events().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
batches,
|
batches,
|
||||||
vec![InInstructionsEvent::Batch {
|
vec![InInstructionsEvent::Batch {
|
||||||
@@ -67,11 +68,11 @@ serai_test!(
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai.get_mint_events(block).await.unwrap(),
|
serai.coins().mint_events().await.unwrap(),
|
||||||
vec![TokensEvent::Mint { address, balance }]
|
vec![TokensEvent::Mint { address, balance }]
|
||||||
);
|
);
|
||||||
assert_eq!(serai.get_token_supply(block, coin).await.unwrap(), amount);
|
assert_eq!(serai.coins().token_supply(coin).await.unwrap(), amount);
|
||||||
assert_eq!(serai.get_token_balance(block, coin, address).await.unwrap(), amount);
|
assert_eq!(serai.coins().token_balance(coin, address).await.unwrap(), amount);
|
||||||
|
|
||||||
// Now burn it
|
// Now burn it
|
||||||
let mut rand_bytes = vec![0; 32];
|
let mut rand_bytes = vec![0; 32];
|
||||||
@@ -83,11 +84,12 @@ serai_test!(
|
|||||||
let data = Data::new(rand_bytes).unwrap();
|
let data = Data::new(rand_bytes).unwrap();
|
||||||
|
|
||||||
let out = OutInstruction { address: external_address, data: Some(data) };
|
let out = OutInstruction { address: external_address, data: Some(data) };
|
||||||
|
let serai = serai.into_inner();
|
||||||
let block = publish_tx(
|
let block = publish_tx(
|
||||||
&serai
|
&serai
|
||||||
.sign(
|
.sign(
|
||||||
&PairSigner::new(pair),
|
&PairSigner::new(pair),
|
||||||
&Serai::burn(balance, out.clone()),
|
&SeraiCoins::burn(balance, out.clone()),
|
||||||
0,
|
0,
|
||||||
BaseExtrinsicParamsBuilder::new(),
|
BaseExtrinsicParamsBuilder::new(),
|
||||||
)
|
)
|
||||||
@@ -95,9 +97,10 @@ serai_test!(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let events = serai.get_burn_events(block).await.unwrap();
|
let serai = serai.as_of(block).coins();
|
||||||
|
let events = serai.burn_events().await.unwrap();
|
||||||
assert_eq!(events, vec![TokensEvent::Burn { address, balance, instruction: out }]);
|
assert_eq!(events, vec![TokensEvent::Burn { address, balance, instruction: out }]);
|
||||||
assert_eq!(serai.get_token_supply(block, coin).await.unwrap(), Amount(0));
|
assert_eq!(serai.token_supply(coin).await.unwrap(), Amount(0));
|
||||||
assert_eq!(serai.get_token_balance(block, coin, address).await.unwrap(), Amount(0));
|
assert_eq!(serai.token_balance(coin, address).await.unwrap(), Amount(0));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ use serai_client::{
|
|||||||
primitives::{Batch, SignedBatch, batch_message},
|
primitives::{Batch, SignedBatch, batch_message},
|
||||||
InInstructionsEvent,
|
InInstructionsEvent,
|
||||||
},
|
},
|
||||||
Serai,
|
SeraiInInstructions,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::{serai, tx::publish_tx, validator_sets::set_validator_set_keys};
|
use crate::common::{serai, tx::publish_tx, validator_sets::set_keys};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn provide_batch(batch: Batch) -> [u8; 32] {
|
pub async fn provide_batch(batch: Batch) -> [u8; 32] {
|
||||||
@@ -27,23 +27,23 @@ pub async fn provide_batch(batch: Batch) -> [u8; 32] {
|
|||||||
let set = ValidatorSet { session: Session(0), network: batch.network };
|
let set = ValidatorSet { session: Session(0), network: batch.network };
|
||||||
let pair = insecure_pair_from_name(&format!("ValidatorSet {:?}", set));
|
let pair = insecure_pair_from_name(&format!("ValidatorSet {:?}", set));
|
||||||
let keys = if let Some(keys) =
|
let keys = if let Some(keys) =
|
||||||
serai.get_keys(set, serai.get_latest_block_hash().await.unwrap()).await.unwrap()
|
serai.with_current_latest_block().await.unwrap().validator_sets().keys(set).await.unwrap()
|
||||||
{
|
{
|
||||||
keys
|
keys
|
||||||
} else {
|
} else {
|
||||||
let keys = (pair.public(), vec![].try_into().unwrap());
|
let keys = (pair.public(), vec![].try_into().unwrap());
|
||||||
set_validator_set_keys(set, keys.clone()).await;
|
set_keys(set, keys.clone()).await;
|
||||||
keys
|
keys
|
||||||
};
|
};
|
||||||
assert_eq!(keys.0, pair.public());
|
assert_eq!(keys.0, pair.public());
|
||||||
|
|
||||||
let block = publish_tx(&Serai::execute_batch(SignedBatch {
|
let block = publish_tx(&SeraiInInstructions::execute_batch(SignedBatch {
|
||||||
batch: batch.clone(),
|
batch: batch.clone(),
|
||||||
signature: pair.sign(&batch_message(&batch)),
|
signature: pair.sign(&batch_message(&batch)),
|
||||||
}))
|
}))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let batches = serai.get_batch_events(block).await.unwrap();
|
let batches = serai.as_of(block).in_instructions().batch_events().await.unwrap();
|
||||||
// TODO: impl From<Batch> for BatchEvent?
|
// TODO: impl From<Batch> for BatchEvent?
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
batches,
|
batches,
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ macro_rules! serai_test {
|
|||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
let serai = serai().await;
|
let serai = serai().await;
|
||||||
while serai.get_latest_block_hash().await.is_err() {
|
while serai.latest_block_hash().await.is_err() {
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
// TODO: https://github.com/serai-dex/serai/247
|
// TODO: https://github.com/serai-dex/serai/247
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub async fn publish_tx(tx: &Encoded) -> [u8; 32] {
|
|||||||
let serai = serai().await;
|
let serai = serai().await;
|
||||||
|
|
||||||
let mut latest =
|
let mut latest =
|
||||||
serai.get_block(serai.get_latest_block_hash().await.unwrap()).await.unwrap().unwrap().number();
|
serai.block(serai.latest_block_hash().await.unwrap()).await.unwrap().unwrap().number();
|
||||||
|
|
||||||
serai.publish(tx).await.unwrap();
|
serai.publish(tx).await.unwrap();
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ pub async fn publish_tx(tx: &Encoded) -> [u8; 32] {
|
|||||||
let block = {
|
let block = {
|
||||||
let mut block;
|
let mut block;
|
||||||
while {
|
while {
|
||||||
block = serai.get_block_by_number(latest).await.unwrap();
|
block = serai.block_by_number(latest).await.unwrap();
|
||||||
block.is_none()
|
block.is_none()
|
||||||
} {
|
} {
|
||||||
sleep(Duration::from_secs(1)).await;
|
sleep(Duration::from_secs(1)).await;
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ use serai_client::{
|
|||||||
primitives::{ValidatorSet, KeyPair, musig_context, musig_key, set_keys_message},
|
primitives::{ValidatorSet, KeyPair, musig_context, musig_key, set_keys_message},
|
||||||
ValidatorSetsEvent,
|
ValidatorSetsEvent,
|
||||||
},
|
},
|
||||||
Serai,
|
SeraiValidatorSets,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::common::{serai, tx::publish_tx};
|
use crate::common::{serai, tx::publish_tx};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn set_validator_set_keys(set: ValidatorSet, key_pair: KeyPair) -> [u8; 32] {
|
pub async fn set_keys(set: ValidatorSet, key_pair: KeyPair) -> [u8; 32] {
|
||||||
let pair = insecure_pair_from_name("Alice");
|
let pair = insecure_pair_from_name("Alice");
|
||||||
let public = pair.public();
|
let public = pair.public();
|
||||||
|
|
||||||
@@ -29,7 +29,11 @@ pub async fn set_validator_set_keys(set: ValidatorSet, key_pair: KeyPair) -> [u8
|
|||||||
let public_key = <Ristretto as Ciphersuite>::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap();
|
let public_key = <Ristretto as Ciphersuite>::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai
|
serai
|
||||||
.get_validator_set_musig_key(set, serai.get_latest_block_hash().await.unwrap())
|
.with_current_latest_block()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.validator_sets()
|
||||||
|
.musig_key(set)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -45,7 +49,11 @@ pub async fn set_validator_set_keys(set: ValidatorSet, key_pair: KeyPair) -> [u8
|
|||||||
musig::<Ristretto>(&musig_context(set), &Zeroizing::new(secret_key), &[public_key]).unwrap();
|
musig::<Ristretto>(&musig_context(set), &Zeroizing::new(secret_key), &[public_key]).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai
|
serai
|
||||||
.get_validator_set_musig_key(set, serai.get_latest_block_hash().await.unwrap())
|
.with_current_latest_block()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.validator_sets()
|
||||||
|
.musig_key(set)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -63,7 +71,7 @@ pub async fn set_validator_set_keys(set: ValidatorSet, key_pair: KeyPair) -> [u8
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Vote in a key pair
|
// Vote in a key pair
|
||||||
let block = publish_tx(&Serai::set_validator_set_keys(
|
let block = publish_tx(&SeraiValidatorSets::set_keys(
|
||||||
set.network,
|
set.network,
|
||||||
key_pair.clone(),
|
key_pair.clone(),
|
||||||
Signature(sig.to_bytes()),
|
Signature(sig.to_bytes()),
|
||||||
@@ -71,10 +79,10 @@ pub async fn set_validator_set_keys(set: ValidatorSet, key_pair: KeyPair) -> [u8
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai.get_key_gen_events(block).await.unwrap(),
|
serai.as_of(block).validator_sets().key_gen_events().await.unwrap(),
|
||||||
vec![ValidatorSetsEvent::KeyGen { set, key_pair: key_pair.clone() }]
|
vec![ValidatorSetsEvent::KeyGen { set, key_pair: key_pair.clone() }]
|
||||||
);
|
);
|
||||||
assert_eq!(serai.get_keys(set, block).await.unwrap(), Some(key_pair));
|
assert_eq!(serai.as_of(block).validator_sets().keys(set).await.unwrap(), Some(key_pair));
|
||||||
|
|
||||||
block
|
block
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ serai_test!(
|
|||||||
async fn time() {
|
async fn time() {
|
||||||
let serai = serai().await;
|
let serai = serai().await;
|
||||||
|
|
||||||
let mut number = serai.get_latest_block().await.unwrap().number();
|
let mut number = serai.latest_block().await.unwrap().number();
|
||||||
let mut done = 0;
|
let mut done = 0;
|
||||||
while done < 3 {
|
while done < 3 {
|
||||||
// Wait for the next block
|
// Wait for the next block
|
||||||
let block = serai.get_latest_block().await.unwrap();
|
let block = serai.latest_block().await.unwrap();
|
||||||
if block.number() == number {
|
if block.number() == number {
|
||||||
sleep(Duration::from_secs(1)).await;
|
sleep(Duration::from_secs(1)).await;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ use serai_client::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
use common::{serai, validator_sets::set_validator_set_keys};
|
use common::{serai, validator_sets::set_keys};
|
||||||
|
|
||||||
serai_test!(
|
serai_test!(
|
||||||
async fn set_validator_set_keys_test() {
|
async fn set_keys_test() {
|
||||||
let network = NetworkId::Bitcoin;
|
let network = NetworkId::Bitcoin;
|
||||||
let set = ValidatorSet { session: Session(0), network };
|
let set = ValidatorSet { session: Session(0), network };
|
||||||
|
|
||||||
@@ -35,7 +35,9 @@ serai_test!(
|
|||||||
// Make sure the genesis is as expected
|
// Make sure the genesis is as expected
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai
|
serai
|
||||||
.get_new_set_events(serai.get_block_by_number(0).await.unwrap().unwrap().hash())
|
.as_of(serai.block_by_number(0).await.unwrap().unwrap().hash())
|
||||||
|
.validator_sets()
|
||||||
|
.new_set_events()
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
NETWORKS
|
NETWORKS
|
||||||
@@ -47,30 +49,23 @@ serai_test!(
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let participants = serai
|
{
|
||||||
.get_validator_set_participants(set.network, serai.get_latest_block_hash().await.unwrap())
|
let vs_serai = serai.with_current_latest_block().await.unwrap().validator_sets();
|
||||||
.await
|
let participants = vs_serai.participants(set.network).await.unwrap().unwrap();
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
let participants_ref: &[_] = participants.as_ref();
|
let participants_ref: &[_] = participants.as_ref();
|
||||||
assert_eq!(participants_ref, [public].as_ref());
|
assert_eq!(participants_ref, [public].as_ref());
|
||||||
assert_eq!(
|
assert_eq!(vs_serai.musig_key(set).await.unwrap().unwrap(), musig_key(set, &[public]).0);
|
||||||
serai
|
}
|
||||||
.get_validator_set_musig_key(set, serai.get_latest_block_hash().await.unwrap())
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.unwrap(),
|
|
||||||
musig_key(set, &[public]).0
|
|
||||||
);
|
|
||||||
|
|
||||||
let block = set_validator_set_keys(set, key_pair.clone()).await;
|
let block = set_keys(set, key_pair.clone()).await;
|
||||||
|
|
||||||
// While the set_validator_set_keys function should handle this, it's beneficial to
|
// While the set_keys function should handle this, it's beneficial to
|
||||||
// independently test it
|
// independently test it
|
||||||
|
let serai = serai.as_of(block).validator_sets();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai.get_key_gen_events(block).await.unwrap(),
|
serai.key_gen_events().await.unwrap(),
|
||||||
vec![ValidatorSetsEvent::KeyGen { set, key_pair: key_pair.clone() }]
|
vec![ValidatorSetsEvent::KeyGen { set, key_pair: key_pair.clone() }]
|
||||||
);
|
);
|
||||||
assert_eq!(serai.get_keys(set, block).await.unwrap(), Some(key_pair));
|
assert_eq!(serai.keys(set).await.unwrap(), Some(key_pair));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user