move block commitment check to the on_initialize hook

Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
This commit is contained in:
Uncle Stinky 2026-02-21 17:57:49 +03:00
parent 03262539aa
commit 5dd0c73f7a
Signed by: st1nky
GPG Key ID: 016064BD97603B40
5 changed files with 165 additions and 120 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-slow-clap"
version = "0.4.15"
version = "0.4.16"
description = "Applause protocol for the EVM bridge"
license.workspace = true
authors.workspace = true

View File

@ -28,7 +28,7 @@ use sp_runtime::{
storage::StorageValueRef,
storage_lock::{StorageLock, Time},
},
traits::{BlockNumberProvider, Convert, Saturating},
traits::{BlockNumberProvider, Convert, Saturating, UniqueSaturatedInto},
Perbill, RuntimeAppPublic, RuntimeDebug,
};
use sp_staking::{
@ -80,6 +80,7 @@ const LOCK_BLOCK_EXPIRATION: u64 = 20;
const COMMITMENT_DELAY_MILLIS: u64 = 600_000;
const ONE_HOUR_MILLIS: u64 = 3_600_000;
const CHECK_BLOCK_INTERVAL: u64 = 300;
pub type AuthIndex = u32;
@ -347,6 +348,10 @@ pub mod pallet {
authority_id: AuthIndex,
network_id: NetworkIdOf<T>,
},
BlockCommitmentsCheck {
network_id: NetworkIdOf<T>,
block_number: BlockNumberFor<T>,
},
}
#[pallet::error]
@ -470,6 +475,68 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(current_block: BlockNumberFor<T>) -> Weight {
// TODO: what about start of the session???
let mut weight = T::DbWeight::get().reads(1);
let networks_count = T::NetworkDataHandler::count();
let current_block_number: u64 = current_block.unique_saturated_into();
//let cycle_start = (current_block_number / CHECK_BLOCK_INTERVAL) * CHECK_BLOCK_INTERVAL;
let cycle_offset = current_block_number % CHECK_BLOCK_INTERVAL;
if cycle_offset >= networks_count.into() {
return Default::default();
}
let converted_block: usize = current_block_number.unique_saturated_into();
if let Some((network_id, network_data)) =
T::NetworkDataHandler::network_for_block(converted_block)
{
weight = weight.saturating_add(T::DbWeight::get().reads_writes(3, 1));
let session_index = T::ValidatorSet::session_index();
let block_commitments = BlockCommitments::<T>::get(&network_id);
let validators = Validators::<T>::get(&session_index);
if validators.len() == 0 {
return weight;
}
let disabled_bitmap = DisabledAuthorityIndexes::<T>::get(&session_index)
.unwrap_or(BitMap::new(validators.len() as u32));
let max_block_deviation = ONE_HOUR_MILLIS
.saturating_mul(6) // TODO: make it constant or calculate
.saturating_div(network_data.avg_block_speed);
let offence_bitmap = Self::capture_deviation_in_commitments_and_remove(
&disabled_bitmap,
&block_commitments,
&validators,
max_block_deviation,
);
let offence_type = OffenceType::CommitmentOffence;
let extra_weight = Self::try_offend_validators(
&session_index,
&validators,
offence_bitmap,
disabled_bitmap,
offence_type,
);
Self::deposit_event(Event::<T>::BlockCommitmentsCheck {
block_number: current_block,
network_id,
});
weight = weight.saturating_add(extra_weight);
}
weight
}
fn offchain_worker(now: BlockNumberFor<T>) {
match Self::start_slow_clapping(now) {
Ok(_) => {
@ -695,10 +762,6 @@ impl<T: Config> Pallet<T> {
}
fn try_applause(clap: &Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>) -> DispatchResult {
if T::NetworkDataHandler::is_nullification_period() {
return Ok(());
}
let commission = T::NetworkDataHandler::get(&clap.network_id)
.map(|network_data| Perbill::from_parts(network_data.incoming_fee))
.unwrap_or_default()
@ -740,73 +803,35 @@ impl<T: Config> Pallet<T> {
Error::<T>::CommitInWrongSession,
);
let block_commitments = BlockCommitments::<T>::try_mutate(
&network_id,
|current_commitments| -> Result<BTreeMap<AuthIndex, CommitmentDetails>, DispatchError> {
let mut new_commitment_details = new_commitment.commitment;
BlockCommitments::<T>::try_mutate(&network_id, |current_commitments| -> DispatchResult {
let mut new_commitment_details = new_commitment.commitment;
let (current_commits, current_last_updated) = current_commitments
.get(&authority_index)
.map(|details| {
(
details.commits.saturating_add(1),
details.last_updated.saturating_add(COMMITMENT_DELAY_MILLIS),
)
})
.unwrap_or((1, 0));
let (current_commits, current_last_updated) = current_commitments
.get(&authority_index)
.map(|details| {
(
details.commits.saturating_add(1),
details.last_updated.saturating_add(COMMITMENT_DELAY_MILLIS),
)
})
.unwrap_or((1, 0));
ensure!(
new_commitment_details.last_updated > current_last_updated,
Error::<T>::TimeWentBackwards
);
ensure!(
new_commitment_details.last_updated > current_last_updated,
Error::<T>::TimeWentBackwards
);
new_commitment_details.commits = current_commits;
current_commitments.insert(authority_index, new_commitment_details);
new_commitment_details.commits = current_commits;
current_commitments.insert(authority_index, new_commitment_details);
Ok(current_commitments.clone())
},
)?;
let current_commits = block_commitments
.get(&authority_index)
.map(|details| details.commits)
.unwrap_or(1);
Ok(())
})?;
Self::deposit_event(Event::<T>::BlockCommited {
network_id,
authority_id: authority_index,
});
let validators = Validators::<T>::get(&session_index);
let disabled_bitmap = DisabledAuthorityIndexes::<T>::get(&session_index)
.unwrap_or(BitMap::new(validators.len() as u32));
let max_block_deviation = T::NetworkDataHandler::get(&network_id)
.map(|network| {
ONE_HOUR_MILLIS
.saturating_mul(6)
.saturating_div(network.avg_block_speed)
})
.unwrap_or_default();
if current_commits % 3 == 0 && validators.len() > 0 {
let offence_bitmap = Self::capture_deviation_in_commitments_and_remove(
&disabled_bitmap,
&block_commitments,
&validators,
max_block_deviation,
);
let offence_type = OffenceType::CommitmentOffence;
Self::try_offend_validators(
&session_index,
&validators,
offence_bitmap,
disabled_bitmap,
offence_type,
);
}
Ok(())
}
@ -1394,7 +1419,7 @@ impl<T: Config> Pallet<T> {
offence_bitmap: BitMap,
disabled_bitmap: BitMap,
offence_type: OffenceType,
) {
) -> Weight {
let validator_set_count = validators.len() as u32;
let offenders = validators
@ -1417,16 +1442,17 @@ impl<T: Config> Pallet<T> {
if not_enough_validators_left && offenders.len() > 0 {
Self::deposit_event(Event::<T>::BlackSwan);
return;
return T::DbWeight::get().writes(1);
}
if offenders.len() == 0 {
let offenders_len = offenders.len() as u32;
if offenders_len == 0 {
let equilibrium_event = match offence_type {
OffenceType::CommitmentOffence => Event::<T>::AuthoritiesCommitmentEquilibrium,
OffenceType::ThrottlingOffence(_) => Event::<T>::AuthoritiesApplauseEquilibrium,
};
Self::deposit_event(equilibrium_event);
return;
return T::DbWeight::get().writes(1);
}
let offence_event = match offence_type {
@ -1460,6 +1486,8 @@ impl<T: Config> Pallet<T> {
if let Err(e) = T::ReportUnresponsiveness::report_offence(reporters, offence) {
sp_runtime::print(e);
}
T::WeightInfo::try_offend_validators(offenders_len)
}
}

View File

@ -130,6 +130,7 @@ parameter_types! {
parameter_types! {
pub static MockAverageSessionLength: Option<u64> = None;
pub const MaxNetworks: u32 = 5;
}
impl ghost_networks::Config for Runtime {
@ -139,6 +140,7 @@ impl ghost_networks::Config for Runtime {
type RegisterOrigin = EnsureRoot<Self::AccountId>;
type UpdateOrigin = EnsureRoot<Self::AccountId>;
type RemoveOrigin = EnsureRoot<Self::AccountId>;
type MaxNetworks = MaxNetworks;
type WeightInfo = ();
}

View File

@ -1,6 +1,9 @@
#![cfg(test)]
use std::time::{SystemTime, UNIX_EPOCH};
use std::{
collections::HashMap,
time::{SystemTime, UNIX_EPOCH},
};
use super::*;
use crate::evm_types::Log;
@ -892,7 +895,6 @@ fn should_nullify_commission_on_finalize() {
assert_ok!(do_clap_from(session_index, network_id, 1, false));
assert_ok!(do_clap_from(session_index, network_id, 2, false));
assert_eq!(Networks::accumulated_commission(), amount);
assert_eq!(Networks::is_nullification_period(), false);
assert_applaused(&session_index, &unique_hash);
assert_eq!(
@ -903,63 +905,13 @@ fn should_nullify_commission_on_finalize() {
),
(amount, 0u64)
); // precomputed values
assert_eq!(Networks::is_nullification_period(), true);
Networks::on_finalize(System::block_number());
assert_eq!(Networks::is_nullification_period(), false);
assert_eq!(Networks::accumulated_commission(), 0);
assert_ok!(do_clap_from(session_index, network_id, 3, false));
assert_eq!(Networks::accumulated_commission(), 0);
});
}
#[test]
fn should_avoid_applause_during_nullification_period() {
let zero: u64 = 0u64;
let total_staked = 69_000_000;
let total_issuance = 100_000_000;
let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);
let (_, receiver, amount, _) = get_mocked_metadata();
new_test_ext().execute_with(|| {
let _ = prepare_evm_network(None, None);
let session_index = advance_session_and_get_index();
assert_eq!(Networks::is_nullification_period(), false);
assert_eq!(
BridgedInflationCurve::<RewardCurve, Runtime>::era_payout(
total_staked,
total_issuance,
zero
),
(zero, zero)
);
assert_eq!(Networks::is_nullification_period(), true);
assert_ok!(do_clap_from(session_index, network_id, 0, false));
assert_ok!(do_clap_from(session_index, network_id, 1, false));
assert_eq!(Balances::total_balance(&receiver), 0);
assert_clapped(&session_index, &unique_hash, 0, true);
assert_clapped(&session_index, &unique_hash, 1, true);
assert_clapped(&session_index, &unique_hash, 2, false);
assert_clapped(&session_index, &unique_hash, 3, false);
Networks::on_finalize(System::block_number());
assert_eq!(Networks::is_nullification_period(), false);
assert_ok!(do_clap_from(session_index, network_id, 2, false));
assert_ok!(do_clap_from(session_index, network_id, 3, false));
assert_eq!(Balances::total_balance(&receiver), amount);
assert_applaused(&session_index, &unique_hash);
assert_clapped(&session_index, &unique_hash, 0, true);
assert_clapped(&session_index, &unique_hash, 1, true);
assert_clapped(&session_index, &unique_hash, 2, true);
assert_clapped(&session_index, &unique_hash, 3, true);
});
}
#[test]
fn should_avoid_session_overlap_on_mended_session_index() {
let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);
@ -1194,6 +1146,7 @@ fn should_disable_on_commitment_inactivity() {
}
}
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::SomeAuthoritiesDelayed {
delayed: vec![(3, 3)],
@ -1259,6 +1212,7 @@ fn should_disable_on_commitment_block_deviation() {
));
}
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::SomeAuthoritiesDelayed {
delayed: vec![(3, 3)],
@ -1372,6 +1326,7 @@ fn should_not_offend_disabled_authorities() {
}
}
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::AuthoritiesCommitmentEquilibrium,
));
@ -1417,6 +1372,7 @@ fn should_not_slash_by_applause_if_disabled_by_commitment() {
}
}
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::AuthoritiesCommitmentEquilibrium,
));
@ -1501,6 +1457,7 @@ fn should_split_commit_slash_between_active_validators() {
}
}
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::SomeAuthoritiesDelayed {
delayed: vec![(3, 3)],
@ -1508,7 +1465,7 @@ fn should_split_commit_slash_between_active_validators() {
));
let offences = Offences::get();
assert_eq!(offences.len(), 3);
assert_eq!(offences.len(), 1);
for offence in offences {
assert_eq!(offence.0, vec![0, 1, 2]);
assert_eq!(offence.1.session_index, session_index);
@ -1520,6 +1477,55 @@ fn should_split_commit_slash_between_active_validators() {
});
}
#[test]
fn should_check_different_networks_during_on_initialize() {
let times = 69;
let networks_count = 3;
let mut check_commitment_events = HashMap::new();
new_test_ext().execute_with(|| {
let _ = advance_session_and_get_index();
for network_id in 0..networks_count {
prepare_evm_network(Some(network_id), None);
}
let check_interval = times * CHECK_BLOCK_INTERVAL;
for check_block in 0..check_interval {
SlowClap::on_initialize(check_block);
}
let binding = System::events();
let total_number_of_events = binding
.iter()
.filter(|x| {
x.event == RuntimeEvent::SlowClap(crate::Event::AuthoritiesCommitmentEquilibrium)
})
.count();
binding
.iter()
.map(|record| record.event.clone())
.for_each(|event| {
if let RuntimeEvent::SlowClap(crate::Event::BlockCommitmentsCheck {
network_id,
..
}) = event
{
*check_commitment_events.entry(network_id).or_insert(0) += 1;
}
});
let expected_events = networks_count * (times as u32);
assert_eq!(total_number_of_events, expected_events as usize);
assert_eq!(check_commitment_events.len() as u32, networks_count);
for network_id in 0..networks_count {
let network_id_count = check_commitment_events.get(&network_id).unwrap();
assert_eq!(*network_id_count, times);
}
});
}
#[test]
fn should_check_responses_correctly() {
new_test_ext().execute_with(|| {
@ -1884,6 +1890,7 @@ fn evm_block_response(state: &mut testing::OffchainState) {
headers: vec![
("Accept".to_string(), "application/json".to_string()),
("Content-Type".to_string(), "application/json".to_string()),
("User-Agent".to_string(), "curl/8.9.0".to_string()),
],
response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()),
body: expected_body.clone(),
@ -1897,6 +1904,7 @@ fn evm_block_response(state: &mut testing::OffchainState) {
headers: vec![
("Accept".to_string(), "application/json".to_string()),
("Content-Type".to_string(), "application/json".to_string()),
("User-Agent".to_string(), "curl/8.9.0".to_string()),
],
response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()),
body: expected_body,
@ -1951,6 +1959,7 @@ fn evm_logs_response(state: &mut testing::OffchainState) {
headers: vec![
("Accept".to_string(), "application/json".to_string()),
("Content-Type".to_string(), "application/json".to_string()),
("User-Agent".to_string(), "curl/8.9.0".to_string()),
],
response_headers: vec![
("Accept".to_string(), "application/json".to_string()),
@ -1968,6 +1977,7 @@ fn evm_logs_response(state: &mut testing::OffchainState) {
headers: vec![
("Accept".to_string(), "application/json".to_string()),
("Content-Type".to_string(), "application/json".to_string()),
("User-Agent".to_string(), "curl/8.9.0".to_string()),
],
response_headers: vec![
("Accept".to_string(), "application/json".to_string()),

View File

@ -49,6 +49,7 @@ use core::marker::PhantomData;
pub trait WeightInfo {
fn slow_clap() -> Weight;
fn commit_block()-> Weight;
fn try_offend_validators(offenders_len: u32) -> Weight;
}
impl WeightInfo for () {
@ -86,4 +87,8 @@ impl WeightInfo for () {
fn commit_block()-> Weight {
Default::default()
}
fn try_offend_validators(offenders_len: u32) -> Weight {
Default::default()
}
}