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:
Luke Parker 2023-03-30 20:24:11 -04:00
parent 9615caf3bb
commit 1610383649
No known key found for this signature in database
10 changed files with 186 additions and 65 deletions

1
Cargo.lock generated
View file

@ -10895,6 +10895,7 @@ dependencies = [
"scale-info", "scale-info",
"serai-primitives", "serai-primitives",
"serde", "serde",
"sp-core",
"zeroize", "zeroize",
] ]

View file

@ -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 {

View 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 }),
)
}
}

View file

@ -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;
}
} }
); );

View file

@ -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?

View 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));
}
);

View file

@ -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",

View file

@ -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)?;
} }

View file

@ -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"]

View file

@ -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);