mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 12:09:37 +00:00
Test validator set's voting on a key
Needed for the in-instructions pallet to verify in-instructions are appropriately signed and continue developing that. Fixes a bug in the validator-sets pallet, moves several items from the pallet to primitives.
This commit is contained in:
parent
9615caf3bb
commit
1610383649
10 changed files with 186 additions and 65 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -10895,6 +10895,7 @@ dependencies = [
|
||||||
"scale-info",
|
"scale-info",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sp-core",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,7 @@ use serai_runtime::{
|
||||||
|
|
||||||
pub mod tokens;
|
pub mod tokens;
|
||||||
pub mod in_instructions;
|
pub mod in_instructions;
|
||||||
pub mod validator_sets {
|
pub mod validator_sets;
|
||||||
pub use serai_runtime::validator_sets::primitives;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Encode, Decode)]
|
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Encode, Decode)]
|
||||||
pub struct Tip {
|
pub struct Tip {
|
||||||
|
|
59
substrate/serai/client/src/serai/validator_sets.rs
Normal file
59
substrate/serai/client/src/serai/validator_sets.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use serai_runtime::{validator_sets, ValidatorSets, Runtime};
|
||||||
|
pub use validator_sets::primitives;
|
||||||
|
use primitives::{ValidatorSet, ValidatorSetData, KeyPair};
|
||||||
|
|
||||||
|
use subxt::tx::{self, DynamicTxPayload};
|
||||||
|
|
||||||
|
use crate::{primitives::NetworkId, Serai, SeraiError, scale_value, scale_composite};
|
||||||
|
|
||||||
|
const PALLET: &str = "ValidatorSets";
|
||||||
|
|
||||||
|
pub type ValidatorSetsEvent = validator_sets::Event<Runtime>;
|
||||||
|
|
||||||
|
impl Serai {
|
||||||
|
pub async fn get_vote_events(
|
||||||
|
&self,
|
||||||
|
block: [u8; 32],
|
||||||
|
) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
||||||
|
self
|
||||||
|
.events::<ValidatorSets, _>(block, |event| matches!(event, ValidatorSetsEvent::Vote { .. }))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_key_gen_events(
|
||||||
|
&self,
|
||||||
|
block: [u8; 32],
|
||||||
|
) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
||||||
|
self
|
||||||
|
.events::<ValidatorSets, _>(block, |event| matches!(event, ValidatorSetsEvent::KeyGen { .. }))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_validator_set(
|
||||||
|
&self,
|
||||||
|
set: ValidatorSet,
|
||||||
|
) -> Result<Option<ValidatorSetData>, SeraiError> {
|
||||||
|
self
|
||||||
|
.storage(
|
||||||
|
PALLET,
|
||||||
|
"ValidatorSets",
|
||||||
|
Some(vec![scale_value(set)]),
|
||||||
|
self.get_latest_block_hash().await?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_keys(&self, set: ValidatorSet) -> Result<Option<KeyPair>, SeraiError> {
|
||||||
|
self
|
||||||
|
.storage(PALLET, "Keys", Some(vec![scale_value(set)]), self.get_latest_block_hash().await?)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vote(network: NetworkId, key_pair: KeyPair) -> DynamicTxPayload<'static> {
|
||||||
|
tx::dynamic(
|
||||||
|
PALLET,
|
||||||
|
"vote",
|
||||||
|
scale_composite(validator_sets::Call::<Runtime>::vote { network, key_pair }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,5 @@
|
||||||
use core::time::Duration;
|
|
||||||
|
|
||||||
use rand_core::{RngCore, OsRng};
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
use tokio::time::sleep;
|
|
||||||
|
|
||||||
use sp_core::{sr25519::Signature, Pair};
|
use sp_core::{sr25519::Signature, Pair};
|
||||||
use subxt::{config::extrinsic_params::BaseExtrinsicParamsBuilder};
|
use subxt::{config::extrinsic_params::BaseExtrinsicParamsBuilder};
|
||||||
|
|
||||||
|
@ -21,7 +17,7 @@ use serai_client::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod runner;
|
mod runner;
|
||||||
use runner::{URL, provide_batch};
|
use runner::{URL, publish_tx, provide_batch};
|
||||||
|
|
||||||
serai_test!(
|
serai_test!(
|
||||||
async fn burn() {
|
async fn burn() {
|
||||||
|
@ -75,22 +71,15 @@ serai_test!(
|
||||||
let burn = Serai::burn(balance, out.clone());
|
let burn = Serai::burn(balance, out.clone());
|
||||||
|
|
||||||
let signer = PairSigner::new(pair);
|
let signer = PairSigner::new(pair);
|
||||||
serai
|
let block = publish_tx(
|
||||||
.publish(&serai.sign(&signer, &burn, 0, BaseExtrinsicParamsBuilder::new()).unwrap())
|
&serai,
|
||||||
.await
|
&serai.sign(&signer, &burn, 0, BaseExtrinsicParamsBuilder::new()).unwrap(),
|
||||||
.unwrap();
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
loop {
|
let events = serai.get_burn_events(block).await.unwrap();
|
||||||
let block = serai.get_latest_block_hash().await.unwrap();
|
assert_eq!(events, vec![TokensEvent::Burn { address, balance, instruction: out }]);
|
||||||
let events = serai.get_burn_events(block).await.unwrap();
|
assert_eq!(serai.get_token_supply(block, coin).await.unwrap(), Amount(0));
|
||||||
if events.is_empty() {
|
assert_eq!(serai.get_token_balance(block, coin, address).await.unwrap(), Amount(0));
|
||||||
sleep(Duration::from_millis(50)).await;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
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.get_token_balance(block, coin, address).await.unwrap(), Amount(0));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lazy_static::lazy_static;
|
||||||
use tokio::{sync::Mutex, time::sleep};
|
use tokio::{sync::Mutex, time::sleep};
|
||||||
|
|
||||||
use serai_client::{
|
use serai_client::{
|
||||||
subxt::config::Header,
|
subxt::{config::Header, utils::Encoded},
|
||||||
in_instructions::{primitives::SignedBatch, InInstructionsEvent},
|
in_instructions::{primitives::SignedBatch, InInstructionsEvent},
|
||||||
Serai,
|
Serai,
|
||||||
};
|
};
|
||||||
|
@ -17,9 +17,7 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn provide_batch(batch: SignedBatch) -> [u8; 32] {
|
pub async fn publish_tx(serai: &Serai, tx: &Encoded) -> [u8; 32] {
|
||||||
let serai = Serai::new(URL).await.unwrap();
|
|
||||||
|
|
||||||
let mut latest = serai
|
let mut latest = serai
|
||||||
.get_block(serai.get_latest_block_hash().await.unwrap())
|
.get_block(serai.get_latest_block_hash().await.unwrap())
|
||||||
.await
|
.await
|
||||||
|
@ -28,16 +26,15 @@ pub async fn provide_batch(batch: SignedBatch) -> [u8; 32] {
|
||||||
.header
|
.header
|
||||||
.number();
|
.number();
|
||||||
|
|
||||||
let execution = serai.execute_batch(batch.clone()).unwrap();
|
serai.publish(tx).await.unwrap();
|
||||||
serai.publish(&execution).await.unwrap();
|
|
||||||
|
|
||||||
// Get the block it was included in
|
// Get the block it was included in
|
||||||
let mut block;
|
// TODO: Add an RPC method for this/check the guarantee on the subscription
|
||||||
let mut ticks = 0;
|
let mut ticks = 0;
|
||||||
'get_block: loop {
|
loop {
|
||||||
latest += 1;
|
latest += 1;
|
||||||
|
|
||||||
block = {
|
let block = {
|
||||||
let mut block;
|
let mut block;
|
||||||
while {
|
while {
|
||||||
block = serai.get_block_by_number(latest).await.unwrap();
|
block = serai.get_block_by_number(latest).await.unwrap();
|
||||||
|
@ -54,12 +51,19 @@ pub async fn provide_batch(batch: SignedBatch) -> [u8; 32] {
|
||||||
};
|
};
|
||||||
|
|
||||||
for extrinsic in block.extrinsics {
|
for extrinsic in block.extrinsics {
|
||||||
if extrinsic.0 == execution.0[2 ..] {
|
if extrinsic.0 == tx.0[2 ..] {
|
||||||
break 'get_block;
|
return block.header.hash().into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let block = block.header.hash().into();
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn provide_batch(batch: SignedBatch) -> [u8; 32] {
|
||||||
|
let serai = Serai::new(URL).await.unwrap();
|
||||||
|
|
||||||
|
let execution = serai.execute_batch(batch.clone()).unwrap();
|
||||||
|
let block = publish_tx(&serai, &execution).await;
|
||||||
|
|
||||||
let batches = serai.get_batch_events(block).await.unwrap();
|
let batches = serai.get_batch_events(block).await.unwrap();
|
||||||
// TODO: impl From<Batch> for BatchEvent?
|
// TODO: impl From<Batch> for BatchEvent?
|
||||||
|
|
67
substrate/serai/client/tests/validator_sets.rs
Normal file
67
substrate/serai/client/tests/validator_sets.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
|
use sp_core::{sr25519::Public, Pair};
|
||||||
|
use subxt::{config::extrinsic_params::BaseExtrinsicParamsBuilder};
|
||||||
|
|
||||||
|
use serai_client::{
|
||||||
|
primitives::{BITCOIN_NET_ID, BITCOIN_NET, insecure_pair_from_name},
|
||||||
|
validator_sets::{
|
||||||
|
primitives::{Session, ValidatorSet},
|
||||||
|
ValidatorSetsEvent,
|
||||||
|
},
|
||||||
|
PairSigner, Serai,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod runner;
|
||||||
|
use runner::{URL, publish_tx};
|
||||||
|
|
||||||
|
serai_test!(
|
||||||
|
async fn vote_keys() {
|
||||||
|
let network = BITCOIN_NET_ID;
|
||||||
|
let set = ValidatorSet { session: Session(0), network };
|
||||||
|
|
||||||
|
// Neither of these keys are validated
|
||||||
|
// The external key is infeasible to validate on-chain, the Ristretto key is feasible
|
||||||
|
// TODO: Should the Ristretto key be validated?
|
||||||
|
let mut ristretto_key = [0; 32];
|
||||||
|
OsRng.fill_bytes(&mut ristretto_key);
|
||||||
|
let mut external_key = vec![0; 33];
|
||||||
|
OsRng.fill_bytes(&mut external_key);
|
||||||
|
let key_pair = (Public(ristretto_key), external_key.try_into().unwrap());
|
||||||
|
|
||||||
|
let pair = insecure_pair_from_name("Alice");
|
||||||
|
let public = pair.public();
|
||||||
|
|
||||||
|
let serai = Serai::new(URL).await.unwrap();
|
||||||
|
|
||||||
|
// Make sure the genesis is as expected
|
||||||
|
let set_data = serai.get_validator_set(set).await.unwrap().unwrap();
|
||||||
|
assert_eq!(set_data.network, *BITCOIN_NET);
|
||||||
|
let participants_ref: &[_] = set_data.participants.as_ref();
|
||||||
|
assert_eq!(participants_ref, [(public, set_data.bond)].as_ref());
|
||||||
|
|
||||||
|
// Vote in a key pair
|
||||||
|
let block = publish_tx(
|
||||||
|
&serai,
|
||||||
|
&serai
|
||||||
|
.sign(
|
||||||
|
&PairSigner::new(pair),
|
||||||
|
&Serai::vote(network, key_pair.clone()),
|
||||||
|
0,
|
||||||
|
BaseExtrinsicParamsBuilder::new(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serai.get_vote_events(block).await.unwrap(),
|
||||||
|
vec![ValidatorSetsEvent::Vote { voter: public, set, key_pair: key_pair.clone(), votes: 1 }]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
serai.get_key_gen_events(block).await.unwrap(),
|
||||||
|
vec![ValidatorSetsEvent::KeyGen { set, key_pair: key_pair.clone() }]
|
||||||
|
);
|
||||||
|
assert_eq!(serai.get_keys(set).await.unwrap(), Some(key_pair));
|
||||||
|
}
|
||||||
|
);
|
|
@ -28,8 +28,6 @@ std = [
|
||||||
"scale/std",
|
"scale/std",
|
||||||
"scale-info/std",
|
"scale-info/std",
|
||||||
|
|
||||||
"sp-core/std",
|
|
||||||
|
|
||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ pub mod pallet {
|
||||||
use scale::{Encode, Decode};
|
use scale::{Encode, Decode};
|
||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
|
|
||||||
use sp_core::sr25519;
|
|
||||||
|
|
||||||
use frame_system::pallet_prelude::*;
|
use frame_system::pallet_prelude::*;
|
||||||
use frame_support::pallet_prelude::*;
|
use frame_support::pallet_prelude::*;
|
||||||
|
|
||||||
|
@ -15,7 +13,7 @@ pub mod pallet {
|
||||||
use primitives::*;
|
use primitives::*;
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
pub trait Config: frame_system::Config + TypeInfo {
|
pub trait Config: frame_system::Config<AccountId = sp_core::sr25519::Public> + TypeInfo {
|
||||||
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
|
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,20 +37,6 @@ pub mod pallet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support keys up to 96 bytes (BLS12-381 G2)
|
|
||||||
const MAX_KEY_LEN: u32 = 96;
|
|
||||||
type MaxKeyLen = ConstU32<MAX_KEY_LEN>;
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
|
||||||
pub struct ValidatorSetData<T: Config> {
|
|
||||||
bond: Amount,
|
|
||||||
network: Network,
|
|
||||||
|
|
||||||
// Participant and their amount bonded to this set
|
|
||||||
// Limit each set to 100 participants for now
|
|
||||||
participants: BoundedVec<(T::AccountId, Amount), ConstU32<100>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
pub struct Pallet<T>(PhantomData<T>);
|
pub struct Pallet<T>(PhantomData<T>);
|
||||||
|
|
||||||
|
@ -60,16 +44,11 @@ pub mod pallet {
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn validator_set)]
|
#[pallet::getter(fn validator_set)]
|
||||||
pub type ValidatorSets<T: Config> =
|
pub type ValidatorSets<T: Config> =
|
||||||
StorageMap<_, Twox64Concat, ValidatorSet, ValidatorSetData<T>, OptionQuery>;
|
StorageMap<_, Twox64Concat, ValidatorSet, ValidatorSetData, OptionQuery>;
|
||||||
|
|
||||||
type Key = BoundedVec<u8, MaxKeyLen>;
|
|
||||||
// A validator set's key pair is defined as their Ristretto key, used for signing InInstructions,
|
|
||||||
// and their key on the external network
|
|
||||||
type KeyPair = (sr25519::Public, Key);
|
|
||||||
|
|
||||||
/// The key pair for a given validator set instance.
|
/// The key pair for a given validator set instance.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn key)]
|
#[pallet::getter(fn keys)]
|
||||||
pub type Keys<T: Config> = StorageMap<_, Twox64Concat, ValidatorSet, KeyPair, OptionQuery>;
|
pub type Keys<T: Config> = StorageMap<_, Twox64Concat, ValidatorSet, KeyPair, OptionQuery>;
|
||||||
|
|
||||||
/// If an account has voted for a specific key pair or not.
|
/// If an account has voted for a specific key pair or not.
|
||||||
|
@ -153,7 +132,7 @@ pub mod pallet {
|
||||||
|
|
||||||
// Confirm the signer is a validator in the set
|
// Confirm the signer is a validator in the set
|
||||||
let data = ValidatorSets::<T>::get(set).ok_or(Error::<T>::NonExistentValidatorSet)?;
|
let data = ValidatorSets::<T>::get(set).ok_or(Error::<T>::NonExistentValidatorSet)?;
|
||||||
if data.participants.iter().any(|participant| participant.0 == signer) {
|
if !data.participants.iter().any(|participant| participant.0 == signer) {
|
||||||
Err(Error::<T>::NotValidator)?;
|
Err(Error::<T>::NotValidator)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,10 @@ scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
serai-primitives = { path = "../../serai/primitives", default-features = false }
|
serai-primitives = { path = "../../serai/primitives", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["zeroize", "scale/std", "scale-info/std", "serde", "serai-primitives/std"]
|
std = ["zeroize", "scale/std", "scale-info/std", "serde", "sp-core/std", "serai-primitives/std"]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|
|
@ -9,7 +9,12 @@ use scale_info::TypeInfo;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use serai_primitives::NetworkId;
|
use sp_core::{ConstU32, sr25519, bounded::BoundedVec};
|
||||||
|
|
||||||
|
use serai_primitives::{NetworkId, Network, Amount};
|
||||||
|
|
||||||
|
// Support keys up to 96 bytes (BLS12-381 G2).
|
||||||
|
const MAX_KEY_LEN: u32 = 96;
|
||||||
|
|
||||||
/// The type used to identify a specific session of validators.
|
/// The type used to identify a specific session of validators.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||||
|
@ -23,3 +28,22 @@ pub struct ValidatorSet {
|
||||||
pub session: Session,
|
pub session: Session,
|
||||||
pub network: NetworkId,
|
pub network: NetworkId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The data for a validator set.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||||
|
pub struct ValidatorSetData {
|
||||||
|
pub bond: Amount,
|
||||||
|
pub network: Network,
|
||||||
|
|
||||||
|
// Participant and their amount bonded to this set
|
||||||
|
// Limit each set to 100 participants for now
|
||||||
|
pub participants: BoundedVec<(sr25519::Public, Amount), ConstU32<100>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type MaxKeyLen = ConstU32<MAX_KEY_LEN>;
|
||||||
|
/// The type representing a Key from an external network.
|
||||||
|
pub type ExternalKey = BoundedVec<u8, MaxKeyLen>;
|
||||||
|
|
||||||
|
/// A Validator Set's Ristretto key, used for signing InInstructions, and their key on the external
|
||||||
|
/// network.
|
||||||
|
pub type KeyPair = (sr25519::Public, ExternalKey);
|
||||||
|
|
Loading…
Reference in a new issue