complete rotation test for all networks

This commit is contained in:
akildemir
2024-02-12 14:36:56 +03:00
parent 24ff866684
commit e93dbedd6a
10 changed files with 215 additions and 70 deletions

View File

@@ -143,6 +143,24 @@ services:
volumes: volumes:
- "./serai/scripts:/scripts" - "./serai/scripts:/scripts"
serai-fast-epoch:
restart: unless-stopped
# image: serai:dev
profiles:
- serai
build:
context: ../
dockerfile: ./orchestration/serai/Dockerfile.fast-epoch
args:
TAG: serai
entrypoint: /scripts/entry-dev.sh
volumes:
- "./serai/scripts:/scripts"
hostname: serai
environment:
CHAIN: local
NAME: node
serai: serai:
<<: *serai_defaults <<: *serai_defaults
hostname: serai hostname: serai

View File

@@ -0,0 +1,71 @@
FROM debian:bookworm-slim as mimalloc
RUN apt update && apt upgrade -y && apt install -y gcc g++ make cmake git
RUN git clone https://github.com/microsoft/mimalloc && \
cd mimalloc && \
git checkout 43ce4bd7fd34bcc730c1c7471c99995597415488 && \
mkdir -p out/secure && \
cd out/secure && \
cmake -DMI_SECURE=ON ../.. && \
make && \
cp ./libmimalloc-secure.so ../../../libmimalloc.so
FROM rust:1.75-slim-bookworm as builder
COPY --from=mimalloc libmimalloc.so /usr/lib
RUN echo "/usr/lib/libmimalloc.so" >> /etc/ld.so.preload
RUN apt update && apt upgrade -y && apt autoremove -y && apt clean
# Add dev dependencies
RUN apt install -y pkg-config clang
# Dependencies for the Serai node
RUN apt install -y make protobuf-compiler
# Add the wasm toolchain
RUN rustup target add wasm32-unknown-unknown
# Add files for build
ADD common /serai/common
ADD crypto /serai/crypto
ADD coins /serai/coins
ADD message-queue /serai/message-queue
ADD processor /serai/processor
ADD coordinator /serai/coordinator
ADD substrate /serai/substrate
ADD mini /serai/mini
ADD tests /serai/tests
ADD patches /serai/patches
ADD Cargo.toml /serai
ADD Cargo.lock /serai
ADD AGPL-3.0 /serai
WORKDIR /serai
# Mount the caches and build
RUN --mount=type=cache,target=/root/.cargo \
--mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/serai/target \
mkdir /serai/bin && \
cargo build --release --features fast-epoch -p serai-node && \
mv /serai/target/release/serai-node /serai/bin
FROM debian:bookworm-slim as image
COPY --from=mimalloc libmimalloc.so /usr/lib
RUN echo "/usr/lib/libmimalloc.so" >> /etc/ld.so.preload
RUN apt update && apt upgrade -y && apt autoremove -y && apt clean
# Switch to a non-root user
RUN useradd --system --home /home/serai --shell /sbin/nologin serai
USER serai
WORKDIR /home/serai
# Copy the Serai binary and relevant license
COPY --from=builder --chown=serai /serai/bin/serai-node /bin/
COPY --from=builder --chown=serai /serai/AGPL-3.0 .
# Run node
EXPOSE 30333 9615 9933 9944
CMD ["serai-node"]

View File

@@ -35,23 +35,6 @@ impl<'a> SeraiValidatorSets<'a> {
.await .await
} }
pub async fn extrinsic_failed(&self) -> Result<Vec<serai_abi::system::Event>, SeraiError> {
self
.0
.events(|event| {
if let serai_abi::Event::System(event) = event {
if matches!(event, serai_abi::system::Event::ExtrinsicFailed { .. }) {
Some(event.clone())
} else {
None
}
} else {
None
}
})
.await
}
pub async fn participant_removed_events(&self) -> Result<Vec<ValidatorSetsEvent>, SeraiError> { pub async fn participant_removed_events(&self) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
self self
.0 .0
@@ -160,6 +143,22 @@ impl<'a> SeraiValidatorSets<'a> {
.await .await
} }
pub async fn pending_deallocations(
&self,
network: NetworkId,
account: Public,
session: Session,
) -> Result<Option<Amount>, SeraiError> {
self
.0
.storage(
PALLET,
"PendingDeallocations",
(sp_core::hashing::blake2_128(&(network, account).encode()), (network, account, session)),
)
.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 keys(&self, set: ValidatorSet) -> Result<Option<KeyPair>, SeraiError> { pub async fn keys(&self, set: ValidatorSet) -> Result<Option<KeyPair>, SeraiError> {
self.0.storage(PALLET, "Keys", (sp_core::hashing::twox_64(&set.encode()), set)).await self.0.storage(PALLET, "Keys", (sp_core::hashing::twox_64(&set.encode()), set)).await

View File

@@ -13,7 +13,6 @@ macro_rules! serai_test {
PullPolicy, StartPolicy, LogOptions, LogAction, LogPolicy, LogSource, Image, PullPolicy, StartPolicy, LogOptions, LogAction, LogPolicy, LogSource, Image,
TestBodySpecification, DockerTest, TestBodySpecification, DockerTest,
}; };
use std::collections::HashMap;
serai_docker_tests::build("serai".to_string()); serai_docker_tests::build("serai".to_string());
@@ -29,7 +28,6 @@ macro_rules! serai_test {
"--rpc-cors".to_string(), "--rpc-cors".to_string(),
"all".to_string(), "all".to_string(),
]) ])
.replace_env(HashMap::from([("RUST_LOG=runtime".to_string(), "debug".to_string())]))
.set_publish_all_ports(true) .set_publish_all_ports(true)
.set_handle(handle) .set_handle(handle)
.set_start_policy(StartPolicy::Strict) .set_start_policy(StartPolicy::Strict)

View File

@@ -82,12 +82,14 @@ async fn validator_set_rotation() {
PullPolicy, StartPolicy, LogOptions, LogAction, LogPolicy, LogSource, Image, PullPolicy, StartPolicy, LogOptions, LogAction, LogPolicy, LogSource, Image,
TestBodySpecification, DockerTest, TestBodySpecification, DockerTest,
}; };
use std::collections::HashMap;
serai_docker_tests::build("serai-fast-epoch".to_string());
serai_docker_tests::build("serai".to_string());
let handle = |name| format!("serai_client-serai_node-{name}"); let handle = |name| format!("serai_client-serai_node-{name}");
let composition = |name| { let composition = |name| {
TestBodySpecification::with_image( TestBodySpecification::with_image(
Image::with_repository("serai-dev-serai").pull_policy(PullPolicy::Never), Image::with_repository("serai-dev-serai-fast-epoch").pull_policy(PullPolicy::Never),
) )
.replace_cmd(vec![ .replace_cmd(vec![
"serai-node".to_string(), "serai-node".to_string(),
@@ -98,6 +100,7 @@ async fn validator_set_rotation() {
"local".to_string(), "local".to_string(),
format!("--{name}"), format!("--{name}"),
]) ])
.replace_env(HashMap::from([("RUST_LOG=runtime".to_string(), "debug".to_string())]))
.set_publish_all_ports(true) .set_publish_all_ports(true)
.set_handle(handle(name)) .set_handle(handle(name))
.set_start_policy(StartPolicy::Strict) .set_start_policy(StartPolicy::Strict)
@@ -113,6 +116,7 @@ async fn validator_set_rotation() {
test.provide_container(composition("bob")); test.provide_container(composition("bob"));
test.provide_container(composition("charlie")); test.provide_container(composition("charlie"));
test.provide_container(composition("dave")); test.provide_container(composition("dave"));
test.provide_container(composition("eve"));
test test
.run_async(|ops| async move { .run_async(|ops| async move {
// Sleep until the Substrate RPC starts // Sleep until the Substrate RPC starts
@@ -121,18 +125,10 @@ async fn validator_set_rotation() {
let alice_rpc = format!("http://{}:{}", alice_rpc.0, alice_rpc.1); let alice_rpc = format!("http://{}:{}", alice_rpc.0, alice_rpc.1);
// Sleep for a minute // Sleep for a minute
tokio::time::sleep(core::time::Duration::from_secs(60)).await; tokio::time::sleep(core::time::Duration::from_secs(20)).await;
let serai = Serai::new(alice_rpc.clone()).await.unwrap(); let serai = Serai::new(alice_rpc.clone()).await.unwrap();
// taken from testnet config
let pair1 = insecure_pair_from_name("Alice");
let pair2 = insecure_pair_from_name("Bob");
let pair3 = insecure_pair_from_name("Charlie");
let pair4 = insecure_pair_from_name("Dave");
let single_key_share = Amount(50_000 * 10_u64.pow(8));
// Make sure the genesis is as expected // Make sure the genesis is as expected
let network = NetworkId::Serai;
assert_eq!( assert_eq!(
serai serai
.as_of(serai.finalized_block_by_number(0).await.unwrap().unwrap().hash()) .as_of(serai.finalized_block_by_number(0).await.unwrap().unwrap().hash())
@@ -149,25 +145,77 @@ async fn validator_set_rotation() {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
); );
// we start the chain with 4 default participants that has a single key share each // genesis accounts
let mut participants = vec![pair1.public(), pair2.public(), pair3.public(), pair4.public()]; let pair1 = insecure_pair_from_name("Alice");
participants.sort(); let pair2 = insecure_pair_from_name("Bob");
verfiy_session_and_active_validators(&serai, network, 0, &participants).await; let pair3 = insecure_pair_from_name("Charlie");
let pair4 = insecure_pair_from_name("Dave");
let pair5 = insecure_pair_from_name("Eve");
// remove 1 participant // amounts for single key share per network
let hash = deallocate_stake(&serai, network, single_key_share, &pair2, 0).await; let key_shares = HashMap::from([
participants.remove(1); (NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
(NetworkId::Bitcoin, Amount(1_000_000 * 10_u64.pow(8))),
(NetworkId::Monero, Amount(100_000 * 10_u64.pow(8))),
(NetworkId::Ethereum, Amount(1_000_000 * 10_u64.pow(8))),
]);
// TODO: check pending deallocations // genesis participants per network
let default_participants =
vec![pair1.public(), pair2.public(), pair3.public(), pair4.public()];
let mut participants = HashMap::from([
(NetworkId::Serai, default_participants.clone()),
(NetworkId::Bitcoin, default_participants.clone()),
(NetworkId::Monero, default_participants.clone()),
(NetworkId::Ethereum, default_participants),
]);
// verify for 2 epoch later(it takes 1 extra session for serai net to make the changes active) // test the set rotation
// and since we removed a participant, we also need 1 extra session for cool down period. for (i, network) in NETWORKS.into_iter().enumerate() {
let block_number = serai.block(hash).await.unwrap().unwrap().header.number; let participants = participants.get_mut(&network).unwrap();
let epoch_number = block_number / EPOCH_INTERVAL;
participants.sort();
verfiy_session_and_active_validators(&serai, network, epoch_number + 3, &participants).await;
// TODO: test add valiators // we start the chain with 4 default participants that has a single key share each
participants.sort();
verfiy_session_and_active_validators(&serai, network, 0, &participants).await;
// add 1 participant & verify
let hash =
allocate_stake(&serai, network, key_shares[&network], &pair5, i.try_into().unwrap())
.await;
participants.push(pair5.public());
participants.sort();
verfiy_session_and_active_validators(
&serai,
network,
get_active_session(&serai, network, hash).await,
&participants,
)
.await;
// remove 1 participant & verify
let hash =
deallocate_stake(&serai, network, key_shares[&network], &pair2, i.try_into().unwrap())
.await;
participants.swap_remove(participants.iter().position(|k| *k == pair2.public()).unwrap());
let active_session = get_active_session(&serai, network, hash).await;
participants.sort();
verfiy_session_and_active_validators(&serai, network, active_session, &participants).await;
// check pending deallocations
let pending = serai
.as_of_latest_finalized_block()
.await
.unwrap()
.validator_sets()
.pending_deallocations(
network,
pair2.public(),
Session(u32::try_from(active_session + 1).unwrap()),
)
.await
.unwrap();
assert_eq!(pending, Some(key_shares[&network]));
}
}) })
.await; .await;
} }
@@ -196,5 +244,29 @@ async fn verfiy_session_and_active_validators(
validators.sort(); validators.sort();
assert_eq!(validators, participants); assert_eq!(validators, participants);
// make sure finalization continues as usual after the changes
tokio::time::timeout(tokio::time::Duration::from_secs(60), async move {
let mut finalized_block = serai.latest_finalized_block().await.unwrap().header.number;
while finalized_block <= epoch_block + 2 {
tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
finalized_block = serai.latest_finalized_block().await.unwrap().header.number;
}
})
.await
.unwrap();
// TODO: verfiy key shares as well? // TODO: verfiy key shares as well?
} }
async fn get_active_session(serai: &Serai, network: NetworkId, hash: [u8; 32]) -> u64 {
let block_number = serai.block(hash).await.unwrap().unwrap().header.number;
let epoch = block_number / EPOCH_INTERVAL;
// changes should be active in the next session
if network == NetworkId::Serai {
// it takes 1 extra session for serai net to make the changes active.
epoch + 2
} else {
epoch + 1
}
}

View File

@@ -66,6 +66,7 @@ substrate-build-script-utils = { git = "https://github.com/serai-dex/substrate"
[features] [features]
default = [] default = []
fast-epoch = ["serai-runtime/fast-epoch"]
runtime-benchmarks = [ runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks",

View File

@@ -124,6 +124,8 @@ std = [
"pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment-rpc-runtime-api/std",
] ]
fast-epoch = []
runtime-benchmarks = [ runtime-benchmarks = [
"sp-runtime/runtime-benchmarks", "sp-runtime/runtime-benchmarks",

View File

@@ -301,8 +301,14 @@ pub type MaxAuthorities = ConstU32<{ validator_sets::primitives::MAX_KEY_SHARES_
pub type ReportLongevity = <Runtime as pallet_babe::Config>::EpochDuration; pub type ReportLongevity = <Runtime as pallet_babe::Config>::EpochDuration;
impl babe::Config for Runtime { impl babe::Config for Runtime {
#[cfg(feature = "fast-epoch")]
#[allow(clippy::identity_op)] #[allow(clippy::identity_op)]
type EpochDuration = ConstU64<{ DAYS / (24 * 60 * 2) }>; // 30 seconds type EpochDuration = ConstU64<{ DAYS / (24 * 60 * 2) }>; // 30 seconds
#[cfg(not(feature = "fast-epoch"))]
#[allow(clippy::identity_op)]
type EpochDuration = ConstU64<{ DAYS }>;
type ExpectedBlockTime = ConstU64<{ TARGET_BLOCK_TIME * 1000 }>; type ExpectedBlockTime = ConstU64<{ TARGET_BLOCK_TIME * 1000 }>;
type EpochChangeTrigger = babe::ExternalTrigger; type EpochChangeTrigger = babe::ExternalTrigger;
type DisabledValidators = ValidatorSets; type DisabledValidators = ValidatorSets;

View File

@@ -2,8 +2,6 @@
use core::marker::PhantomData; use core::marker::PhantomData;
use sp_runtime::print;
use scale::{Encode, Decode}; use scale::{Encode, Decode};
use scale_info::TypeInfo; use scale_info::TypeInfo;
@@ -570,7 +568,6 @@ pub mod pallet {
account: T::AccountId, account: T::AccountId,
amount: Amount, amount: Amount,
) -> Result<bool, DispatchError> { ) -> Result<bool, DispatchError> {
print("in daellocate");
// Check it's safe to decrease this set's stake by this amount // Check it's safe to decrease this set's stake by this amount
let new_total_staked = Self::total_allocated_stake(network) let new_total_staked = Self::total_allocated_stake(network)
.unwrap() .unwrap()
@@ -582,8 +579,6 @@ pub mod pallet {
Err(Error::<T>::DeallocationWouldRemoveEconomicSecurity)?; Err(Error::<T>::DeallocationWouldRemoveEconomicSecurity)?;
} }
print("passed stake req");
let old_allocation = let old_allocation =
Self::allocation((network, account)).ok_or(Error::<T>::NonExistentValidator)?.0; Self::allocation((network, account)).ok_or(Error::<T>::NonExistentValidator)?.0;
let new_allocation = let new_allocation =
@@ -596,8 +591,6 @@ pub mod pallet {
Err(Error::<T>::DeallocationWouldRemoveParticipant)?; Err(Error::<T>::DeallocationWouldRemoveParticipant)?;
} }
print("passed DeallocationWouldRemoveParticipant");
let decreased_key_shares = let decreased_key_shares =
(old_allocation / allocation_per_key_share) > (new_allocation / allocation_per_key_share); (old_allocation / allocation_per_key_share) > (new_allocation / allocation_per_key_share);
@@ -620,8 +613,6 @@ pub mod pallet {
} }
} }
print("passed bft");
// If we're not in-set, allow immediate deallocation // If we're not in-set, allow immediate deallocation
if !Self::in_set(network, account) { if !Self::in_set(network, account) {
Self::deposit_event(Event::AllocationDecreased { Self::deposit_event(Event::AllocationDecreased {
@@ -630,12 +621,9 @@ pub mod pallet {
amount, amount,
delayed_until: None, delayed_until: None,
}); });
print("returning ok true");
return Ok(true); return Ok(true);
} }
print("passed in set");
// Set it to PendingDeallocations, letting it be released upon a future session // Set it to PendingDeallocations, letting it be released upon a future session
// This unwrap should be fine as this account is active, meaning a session has occurred // This unwrap should be fine as this account is active, meaning a session has occurred
let to_unlock_on = Self::session_to_unlock_on_for_current_set(network).unwrap(); let to_unlock_on = Self::session_to_unlock_on_for_current_set(network).unwrap();
@@ -647,8 +635,6 @@ pub mod pallet {
Some(Amount(existing.0 + amount.0)), Some(Amount(existing.0 + amount.0)),
); );
print("passed PendingDeallocations");
Self::deposit_event(Event::AllocationDecreased { Self::deposit_event(Event::AllocationDecreased {
validator: account, validator: account,
network, network,
@@ -656,7 +642,6 @@ pub mod pallet {
delayed_until: Some(to_unlock_on), delayed_until: Some(to_unlock_on),
}); });
print("return ok false at the end");
Ok(false) Ok(false)
} }
@@ -745,11 +730,6 @@ pub mod pallet {
.expect("no Serai participants upon rotate_session"); .expect("no Serai participants upon rotate_session");
let prior_serai_session = Self::session(NetworkId::Serai).unwrap(); let prior_serai_session = Self::session(NetworkId::Serai).unwrap();
print("now session:");
print(prior_serai_session.0);
print("now validators: ");
print(now_validators.len());
// TODO: T::SessionHandler::on_before_session_ending() was here. // TODO: T::SessionHandler::on_before_session_ending() was here.
// end the current serai session. // end the current serai session.
Self::retire_set(ValidatorSet { network: NetworkId::Serai, session: prior_serai_session }); Self::retire_set(ValidatorSet { network: NetworkId::Serai, session: prior_serai_session });
@@ -760,10 +740,6 @@ pub mod pallet {
// Update Babe and Grandpa // Update Babe and Grandpa
let session = prior_serai_session.0 + 1; let session = prior_serai_session.0 + 1;
let next_validators = Participants::<T>::get(NetworkId::Serai).unwrap(); let next_validators = Participants::<T>::get(NetworkId::Serai).unwrap();
print("next session:");
print(session);
print("next validators: ");
print(next_validators.len());
Babe::<T>::enact_epoch_change( Babe::<T>::enact_epoch_change(
WeakBoundedVec::force_from( WeakBoundedVec::force_from(
now_validators.iter().copied().map(|(id, w)| (BabeAuthorityId::from(id), w)).collect(), now_validators.iter().copied().map(|(id, w)| (BabeAuthorityId::from(id), w)).collect(),

View File

@@ -74,6 +74,8 @@ pub fn build(name: String) {
.join("processor") .join("processor")
.join(name.split('-').next().unwrap()) .join(name.split('-').next().unwrap())
.join("Dockerfile"); .join("Dockerfile");
} else if name == "serai-fast-epoch" {
dockerfile_path = dockerfile_path.join("serai").join("Dockerfile.fast-epoch");
} else { } else {
dockerfile_path = dockerfile_path.join(&name).join("Dockerfile"); dockerfile_path = dockerfile_path.join(&name).join("Dockerfile");
} }
@@ -108,7 +110,7 @@ pub fn build(name: String) {
meta(repo_path.join("message-queue")), meta(repo_path.join("message-queue")),
meta(repo_path.join("coordinator")), meta(repo_path.join("coordinator")),
], ],
"runtime" | "serai" => vec![ "runtime" | "serai" | "serai-fast-epoch" => vec![
meta(repo_path.join("common")), meta(repo_path.join("common")),
meta(repo_path.join("crypto")), meta(repo_path.join("crypto")),
meta(repo_path.join("substrate")), meta(repo_path.join("substrate")),