mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 13:39:25 +00:00
complete rotation test for all networks
This commit is contained in:
@@ -143,6 +143,24 @@ services:
|
||||
volumes:
|
||||
- "./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_defaults
|
||||
hostname: serai
|
||||
|
||||
71
orchestration/serai/Dockerfile.fast-epoch
Normal file
71
orchestration/serai/Dockerfile.fast-epoch
Normal 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"]
|
||||
@@ -35,23 +35,6 @@ impl<'a> SeraiValidatorSets<'a> {
|
||||
.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> {
|
||||
self
|
||||
.0
|
||||
@@ -160,6 +143,22 @@ impl<'a> SeraiValidatorSets<'a> {
|
||||
.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?
|
||||
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
|
||||
|
||||
@@ -13,7 +13,6 @@ macro_rules! serai_test {
|
||||
PullPolicy, StartPolicy, LogOptions, LogAction, LogPolicy, LogSource, Image,
|
||||
TestBodySpecification, DockerTest,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
serai_docker_tests::build("serai".to_string());
|
||||
|
||||
@@ -29,7 +28,6 @@ macro_rules! serai_test {
|
||||
"--rpc-cors".to_string(),
|
||||
"all".to_string(),
|
||||
])
|
||||
.replace_env(HashMap::from([("RUST_LOG=runtime".to_string(), "debug".to_string())]))
|
||||
.set_publish_all_ports(true)
|
||||
.set_handle(handle)
|
||||
.set_start_policy(StartPolicy::Strict)
|
||||
|
||||
@@ -82,12 +82,14 @@ async fn validator_set_rotation() {
|
||||
PullPolicy, StartPolicy, LogOptions, LogAction, LogPolicy, LogSource, Image,
|
||||
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 composition = |name| {
|
||||
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![
|
||||
"serai-node".to_string(),
|
||||
@@ -98,6 +100,7 @@ async fn validator_set_rotation() {
|
||||
"local".to_string(),
|
||||
format!("--{name}"),
|
||||
])
|
||||
.replace_env(HashMap::from([("RUST_LOG=runtime".to_string(), "debug".to_string())]))
|
||||
.set_publish_all_ports(true)
|
||||
.set_handle(handle(name))
|
||||
.set_start_policy(StartPolicy::Strict)
|
||||
@@ -113,6 +116,7 @@ async fn validator_set_rotation() {
|
||||
test.provide_container(composition("bob"));
|
||||
test.provide_container(composition("charlie"));
|
||||
test.provide_container(composition("dave"));
|
||||
test.provide_container(composition("eve"));
|
||||
test
|
||||
.run_async(|ops| async move {
|
||||
// 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);
|
||||
|
||||
// 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();
|
||||
|
||||
// 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
|
||||
let network = NetworkId::Serai;
|
||||
assert_eq!(
|
||||
serai
|
||||
.as_of(serai.finalized_block_by_number(0).await.unwrap().unwrap().hash())
|
||||
@@ -149,25 +145,77 @@ async fn validator_set_rotation() {
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
// we start the chain with 4 default participants that has a single key share each
|
||||
let mut participants = vec![pair1.public(), pair2.public(), pair3.public(), pair4.public()];
|
||||
participants.sort();
|
||||
verfiy_session_and_active_validators(&serai, network, 0, &participants).await;
|
||||
// genesis accounts
|
||||
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 pair5 = insecure_pair_from_name("Eve");
|
||||
|
||||
// remove 1 participant
|
||||
let hash = deallocate_stake(&serai, network, single_key_share, &pair2, 0).await;
|
||||
participants.remove(1);
|
||||
// amounts for single key share per network
|
||||
let key_shares = HashMap::from([
|
||||
(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)
|
||||
// and since we removed a participant, we also need 1 extra session for cool down period.
|
||||
let block_number = serai.block(hash).await.unwrap().unwrap().header.number;
|
||||
let epoch_number = block_number / EPOCH_INTERVAL;
|
||||
participants.sort();
|
||||
verfiy_session_and_active_validators(&serai, network, epoch_number + 3, &participants).await;
|
||||
// test the set rotation
|
||||
for (i, network) in NETWORKS.into_iter().enumerate() {
|
||||
let participants = participants.get_mut(&network).unwrap();
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -196,5 +244,29 @@ async fn verfiy_session_and_active_validators(
|
||||
validators.sort();
|
||||
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?
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ substrate-build-script-utils = { git = "https://github.com/serai-dex/substrate"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
fast-epoch = ["serai-runtime/fast-epoch"]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
|
||||
|
||||
@@ -124,6 +124,8 @@ std = [
|
||||
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||
]
|
||||
|
||||
fast-epoch = []
|
||||
|
||||
runtime-benchmarks = [
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
|
||||
|
||||
@@ -301,8 +301,14 @@ pub type MaxAuthorities = ConstU32<{ validator_sets::primitives::MAX_KEY_SHARES_
|
||||
pub type ReportLongevity = <Runtime as pallet_babe::Config>::EpochDuration;
|
||||
|
||||
impl babe::Config for Runtime {
|
||||
#[cfg(feature = "fast-epoch")]
|
||||
#[allow(clippy::identity_op)]
|
||||
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 EpochChangeTrigger = babe::ExternalTrigger;
|
||||
type DisabledValidators = ValidatorSets;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use sp_runtime::print;
|
||||
|
||||
use scale::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
@@ -570,7 +568,6 @@ pub mod pallet {
|
||||
account: T::AccountId,
|
||||
amount: Amount,
|
||||
) -> Result<bool, DispatchError> {
|
||||
print("in daellocate");
|
||||
// Check it's safe to decrease this set's stake by this amount
|
||||
let new_total_staked = Self::total_allocated_stake(network)
|
||||
.unwrap()
|
||||
@@ -582,8 +579,6 @@ pub mod pallet {
|
||||
Err(Error::<T>::DeallocationWouldRemoveEconomicSecurity)?;
|
||||
}
|
||||
|
||||
print("passed stake req");
|
||||
|
||||
let old_allocation =
|
||||
Self::allocation((network, account)).ok_or(Error::<T>::NonExistentValidator)?.0;
|
||||
let new_allocation =
|
||||
@@ -596,8 +591,6 @@ pub mod pallet {
|
||||
Err(Error::<T>::DeallocationWouldRemoveParticipant)?;
|
||||
}
|
||||
|
||||
print("passed DeallocationWouldRemoveParticipant");
|
||||
|
||||
let decreased_key_shares =
|
||||
(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 !Self::in_set(network, account) {
|
||||
Self::deposit_event(Event::AllocationDecreased {
|
||||
@@ -630,12 +621,9 @@ pub mod pallet {
|
||||
amount,
|
||||
delayed_until: None,
|
||||
});
|
||||
print("returning ok true");
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
print("passed in set");
|
||||
|
||||
// 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
|
||||
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)),
|
||||
);
|
||||
|
||||
print("passed PendingDeallocations");
|
||||
|
||||
Self::deposit_event(Event::AllocationDecreased {
|
||||
validator: account,
|
||||
network,
|
||||
@@ -656,7 +642,6 @@ pub mod pallet {
|
||||
delayed_until: Some(to_unlock_on),
|
||||
});
|
||||
|
||||
print("return ok false at the end");
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -745,11 +730,6 @@ pub mod pallet {
|
||||
.expect("no Serai participants upon rotate_session");
|
||||
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.
|
||||
// end the current serai session.
|
||||
Self::retire_set(ValidatorSet { network: NetworkId::Serai, session: prior_serai_session });
|
||||
@@ -760,10 +740,6 @@ pub mod pallet {
|
||||
// Update Babe and Grandpa
|
||||
let session = prior_serai_session.0 + 1;
|
||||
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(
|
||||
WeakBoundedVec::force_from(
|
||||
now_validators.iter().copied().map(|(id, w)| (BabeAuthorityId::from(id), w)).collect(),
|
||||
|
||||
@@ -74,6 +74,8 @@ pub fn build(name: String) {
|
||||
.join("processor")
|
||||
.join(name.split('-').next().unwrap())
|
||||
.join("Dockerfile");
|
||||
} else if name == "serai-fast-epoch" {
|
||||
dockerfile_path = dockerfile_path.join("serai").join("Dockerfile.fast-epoch");
|
||||
} else {
|
||||
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("coordinator")),
|
||||
],
|
||||
"runtime" | "serai" => vec![
|
||||
"runtime" | "serai" | "serai-fast-epoch" => vec![
|
||||
meta(repo_path.join("common")),
|
||||
meta(repo_path.join("crypto")),
|
||||
meta(repo_path.join("substrate")),
|
||||
|
||||
Reference in New Issue
Block a user