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",
|
||||
"serai-primitives",
|
||||
"serde",
|
||||
"sp-core",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
|
|
@ -30,9 +30,7 @@ use serai_runtime::{
|
|||
|
||||
pub mod tokens;
|
||||
pub mod in_instructions;
|
||||
pub mod validator_sets {
|
||||
pub use serai_runtime::validator_sets::primitives;
|
||||
}
|
||||
pub mod validator_sets;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Encode, Decode)]
|
||||
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 tokio::time::sleep;
|
||||
|
||||
use sp_core::{sr25519::Signature, Pair};
|
||||
use subxt::{config::extrinsic_params::BaseExtrinsicParamsBuilder};
|
||||
|
||||
|
@ -21,7 +17,7 @@ use serai_client::{
|
|||
};
|
||||
|
||||
mod runner;
|
||||
use runner::{URL, provide_batch};
|
||||
use runner::{URL, publish_tx, provide_batch};
|
||||
|
||||
serai_test!(
|
||||
async fn burn() {
|
||||
|
@ -75,22 +71,15 @@ serai_test!(
|
|||
let burn = Serai::burn(balance, out.clone());
|
||||
|
||||
let signer = PairSigner::new(pair);
|
||||
serai
|
||||
.publish(&serai.sign(&signer, &burn, 0, BaseExtrinsicParamsBuilder::new()).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let block = publish_tx(
|
||||
&serai,
|
||||
&serai.sign(&signer, &burn, 0, BaseExtrinsicParamsBuilder::new()).unwrap(),
|
||||
)
|
||||
.await;
|
||||
|
||||
loop {
|
||||
let block = serai.get_latest_block_hash().await.unwrap();
|
||||
let events = serai.get_burn_events(block).await.unwrap();
|
||||
if events.is_empty() {
|
||||
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;
|
||||
}
|
||||
let events = serai.get_burn_events(block).await.unwrap();
|
||||
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));
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ use lazy_static::lazy_static;
|
|||
use tokio::{sync::Mutex, time::sleep};
|
||||
|
||||
use serai_client::{
|
||||
subxt::config::Header,
|
||||
subxt::{config::Header, utils::Encoded},
|
||||
in_instructions::{primitives::SignedBatch, InInstructionsEvent},
|
||||
Serai,
|
||||
};
|
||||
|
@ -17,9 +17,7 @@ lazy_static! {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn provide_batch(batch: SignedBatch) -> [u8; 32] {
|
||||
let serai = Serai::new(URL).await.unwrap();
|
||||
|
||||
pub async fn publish_tx(serai: &Serai, tx: &Encoded) -> [u8; 32] {
|
||||
let mut latest = serai
|
||||
.get_block(serai.get_latest_block_hash().await.unwrap())
|
||||
.await
|
||||
|
@ -28,16 +26,15 @@ pub async fn provide_batch(batch: SignedBatch) -> [u8; 32] {
|
|||
.header
|
||||
.number();
|
||||
|
||||
let execution = serai.execute_batch(batch.clone()).unwrap();
|
||||
serai.publish(&execution).await.unwrap();
|
||||
serai.publish(tx).await.unwrap();
|
||||
|
||||
// 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;
|
||||
'get_block: loop {
|
||||
loop {
|
||||
latest += 1;
|
||||
|
||||
block = {
|
||||
let block = {
|
||||
let mut block;
|
||||
while {
|
||||
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 {
|
||||
if extrinsic.0 == execution.0[2 ..] {
|
||||
break 'get_block;
|
||||
if extrinsic.0 == tx.0[2 ..] {
|
||||
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();
|
||||
// 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-info/std",
|
||||
|
||||
"sp-core/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ pub mod pallet {
|
|||
use scale::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use sp_core::sr25519;
|
||||
|
||||
use frame_system::pallet_prelude::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
|
@ -15,7 +13,7 @@ pub mod pallet {
|
|||
use primitives::*;
|
||||
|
||||
#[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>>;
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
pub struct Pallet<T>(PhantomData<T>);
|
||||
|
||||
|
@ -60,16 +44,11 @@ pub mod pallet {
|
|||
#[pallet::storage]
|
||||
#[pallet::getter(fn validator_set)]
|
||||
pub type ValidatorSets<T: Config> =
|
||||
StorageMap<_, Twox64Concat, ValidatorSet, ValidatorSetData<T>, 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);
|
||||
StorageMap<_, Twox64Concat, ValidatorSet, ValidatorSetData, OptionQuery>;
|
||||
|
||||
/// The key pair for a given validator set instance.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn key)]
|
||||
#[pallet::getter(fn keys)]
|
||||
pub type Keys<T: Config> = StorageMap<_, Twox64Concat, ValidatorSet, KeyPair, OptionQuery>;
|
||||
|
||||
/// 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
|
||||
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)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ scale-info = { version = "2", default-features = false, features = ["derive"] }
|
|||
|
||||
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 }
|
||||
|
||||
[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"]
|
||||
|
|
|
@ -9,7 +9,12 @@ use scale_info::TypeInfo;
|
|||
#[cfg(feature = "std")]
|
||||
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.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
|
@ -23,3 +28,22 @@ pub struct ValidatorSet {
|
|||
pub session: Session,
|
||||
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