Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Starlight <> Ethereum: Slashes mapping #793

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pallets/external-validator-slashes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ tp-bridge = { workspace = true }
tp-traits = { workspace = true }

[dev-dependencies]
pallet-timestamp = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }

Expand Down
106 changes: 89 additions & 17 deletions pallets/external-validator-slashes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ use {
offence::{OffenceDetails, OnOffenceHandler},
EraIndex, SessionIndex,
},
sp_std::collections::vec_deque::VecDeque,
sp_std::vec,
sp_std::vec::Vec,
tp_traits::{EraIndexProvider, InvulnerablesProvider, OnEraStart},
};

use snowbridge_core::ChannelId;
use tp_bridge::{Command, Message, ValidateMessage};
use tp_bridge::{Command, DeliverMessage, Message, ValidateMessage};

pub use pallet::*;

Expand All @@ -67,7 +68,6 @@ pub mod weights;
pub mod pallet {
use super::*;
pub use crate::weights::WeightInfo;
use tp_bridge::DeliverMessage;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
Expand Down Expand Up @@ -136,6 +136,12 @@ pub mod pallet {
Ticket = <<Self as pallet::Config>::ValidateMessage as ValidateMessage>::Ticket,
>;

/// Provider to retrieve the current block timestamp.
type TimestampProvider: Get<u64>;

/// How many queued slashes are being processed per block.
type QueuedSlashesProcessedPerBlock: Get<u32>;

/// The weight information of this pallet.
type WeightInfo: WeightInfo;
}
Expand Down Expand Up @@ -191,6 +197,13 @@ pub mod pallet {
pub type Slashes<T: Config> =
StorageMap<_, Twox64Concat, EraIndex, Vec<Slash<T::AccountId, T::SlashId>>, ValueQuery>;

/// All unreported slashes that will be processed in the future.
#[pallet::storage]
#[pallet::unbounded]
#[pallet::getter(fn unreported_slashes)]
pub type UnreportedSlashesQueue<T: Config> =
StorageValue<_, VecDeque<Slash<T::AccountId, T::SlashId>>, ValueQuery>;

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Cancel a slash that was deferred for a later era
Expand Down Expand Up @@ -267,7 +280,7 @@ pub mod pallet {

// If we defer duration is 0, we immediately apply and confirm
let era_to_consider = if slash_defer_duration == 0 {
era
era.saturating_add(One::one())
} else {
era.saturating_add(slash_defer_duration)
.saturating_add(One::one())
Expand Down Expand Up @@ -330,6 +343,19 @@ pub mod pallet {
Ok(())
}
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
let weight = Weight::zero();

Self::process_slashes_queue_page();

// TODO: Weight

weight
}
}
}

/// This is intended to be used with `FilterHistoricalOffences`.
Expand Down Expand Up @@ -422,9 +448,13 @@ where
);

// Cover slash defer duration equal to 0
// Slashes are applied at the end of the current era
if slash_defer_duration == 0 {
Slashes::<T>::mutate(slash_era, move |for_now| for_now.push(slash));
Slashes::<T>::mutate(active_era.saturating_add(One::one()), move |for_now| {
for_now.push(slash)
});
} else {
// Else, slashes are applied after slash_defer_period since the slashed era
Slashes::<T>::mutate(
slash_era
.saturating_add(slash_defer_duration)
Expand Down Expand Up @@ -481,24 +511,66 @@ impl<T: Config> OnEraStart for Pallet<T> {
}
});

Self::confirm_unconfirmed_slashes(era_index);
Self::add_era_slashes_to_queue(era_index);
}
}

impl<T: Config> Pallet<T> {
/// Apply previously-unapplied slashes on the beginning of a new era, after a delay.
fn confirm_unconfirmed_slashes(active_era: EraIndex) {
Slashes::<T>::mutate(&active_era, |era_slashes| {
log!(
log::Level::Debug,
"found {} slashes scheduled to be confirmed in era {:?}",
era_slashes.len(),
active_era,
);
for slash in era_slashes {
slash.confirmed = true;
fn add_era_slashes_to_queue(active_era: EraIndex) {
let mut slashes: VecDeque<_> = Slashes::<T>::take(&active_era).into();

UnreportedSlashesQueue::<T>::mutate(|queue| queue.append(&mut slashes));
}

fn process_slashes_queue_page() {
let mut slashes_to_send: Vec<_> = vec![];
let era_index = T::EraIndexProvider::active_era().index;

// prepare up to QueuedSlashesProcessedPerBlock slashes to be sent
for _ in 0..(T::QueuedSlashesProcessedPerBlock::get() as usize) {
let Some(slash) = UnreportedSlashesQueue::<T>::mutate(VecDeque::pop_front) else {
girazoki marked this conversation as resolved.
Show resolved Hide resolved
// no more slashes to process in the queue
break;
};

// TODO: check if validator.clone().encode() matches with the actual account bytes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not, use validator.as_ref().to_vec()

slashes_to_send.push((
slash.validator.clone().encode(),
slash.percentage.deconstruct(),
));
}

if slashes_to_send.is_empty() {
return;
}

// Build command with slashes.
let command = Command::ReportSlashes {
// TODO: change this
timestamp: T::TimestampProvider::get(),
era_index,
slashes: slashes_to_send,
};

let channel_id: ChannelId = snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL;

let outbound_message = Message {
id: None,
channel_id,
command,
};

// Validate and deliver the message
match T::ValidateMessage::validate(&outbound_message) {
Ok((ticket, _fee)) => {
if let Err(err) = T::OutboundQueue::deliver(ticket) {
log::error!(target: "ext_validators_slashes", "OutboundQueue delivery of message failed. {err:?}");
}
}
});
Err(err) => {
log::error!(target: "ext_validators_slashes", "OutboundQueue validation of message failed. {err:?}");
}
};
}
}

Expand Down
39 changes: 38 additions & 1 deletion pallets/external-validator-slashes/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use {
crate as external_validator_slashes,
frame_support::{
parameter_types,
traits::{ConstU16, ConstU64, Get},
traits::{ConstU16, ConstU32, ConstU64, Get, Hooks},
},
frame_system as system,
snowbridge_core::outbound::{SendError, SendMessageFeeProvider},
Expand All @@ -43,9 +43,17 @@ frame_support::construct_runtime!(
Session: pallet_session,
Historical: pallet_session::historical,
ExternalValidatorSlashes: external_validator_slashes,
Timestamp: pallet_timestamp,
}
);

impl pallet_timestamp::Config for Test {
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = ConstU64<5>;
type WeightInfo = ();
}

impl system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
Expand Down Expand Up @@ -116,6 +124,8 @@ pub struct MockEraIndexProvider;
thread_local! {
pub static ERA_INDEX: RefCell<EraIndex> = const { RefCell::new(0) };
pub static DEFER_PERIOD: RefCell<EraIndex> = const { RefCell::new(2) };
pub static SENT_ETHEREUM_MESSAGE_NONCE: RefCell<u64> = const { RefCell::new(0) };

}

impl MockEraIndexProvider {
Expand Down Expand Up @@ -197,11 +207,18 @@ impl DeferPeriodGetter {
}
}

pub fn sent_ethereum_message_nonce() -> u64 {
SENT_ETHEREUM_MESSAGE_NONCE.with(|q| (*q.borrow()).clone())
}

pub struct MockOkOutboundQueue;
impl tp_bridge::DeliverMessage for MockOkOutboundQueue {
type Ticket = ();

fn deliver(_: Self::Ticket) -> Result<H256, SendError> {
// Every time we hit deliver, increment the nonce
SENT_ETHEREUM_MESSAGE_NONCE.with(|r| *r.borrow_mut() = r.take() + 1);

Ok(H256::zero())
}
}
Expand All @@ -214,6 +231,13 @@ impl SendMessageFeeProvider for MockOkOutboundQueue {
}
}

pub struct TimestampProvider;
impl Get<u64> for TimestampProvider {
fn get() -> u64 {
Timestamp::get()
}
}

parameter_types! {
pub const BondingDuration: u32 = 5u32;
}
Expand All @@ -230,6 +254,8 @@ impl external_validator_slashes::Config for Test {
type InvulnerablesProvider = MockInvulnerableProvider;
type ValidateMessage = ();
type OutboundQueue = MockOkOutboundQueue;
type TimestampProvider = TimestampProvider;
type QueuedSlashesProcessedPerBlock = ConstU32<20>;
type WeightInfo = ();
}

Expand Down Expand Up @@ -258,3 +284,14 @@ impl sp_runtime::traits::Convert<u64, Option<u64>> for IdentityValidator {
Some(a)
}
}

/// Rolls forward one block. Returns the new block number.
#[allow(dead_code)]
pub(crate) fn roll_one_block() -> u64 {
ExternalValidatorSlashes::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
ExternalValidatorSlashes::on_initialize(System::block_number());
System::block_number()
}
Loading
Loading