Skip to content

Commit

Permalink
feat: committee selection beacon slashing
Browse files Browse the repository at this point in the history
  • Loading branch information
snormore committed Nov 14, 2024
1 parent 85e3842 commit c5a858e
Show file tree
Hide file tree
Showing 24 changed files with 1,462 additions and 148 deletions.
27 changes: 20 additions & 7 deletions core/application/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ impl ApplicationEnv {
let app = ApplicationState::executor(ctx);
let last_block_hash = app.get_block_hash();

let block_number = app.get_block_number() + 1;
let last_block_number = app.get_block_number();
let block_number = last_block_number + 1;

let committee_before_execution = app.get_current_committee();

// Create block response
let mut response = BlockExecutionResponse {
Expand Down Expand Up @@ -134,19 +137,28 @@ impl ApplicationEnv {
}

// Update node registry changes on the response if there were any for this block.
if let Some(committee) = app.get_current_committee() {
let committee = app.get_current_committee();
if let Some(committee) = &committee {
if let Some(changes) = committee.node_registry_changes.get(&block_number) {
response.node_registry_changes = changes.clone();
}
}

// If the committee changed, advance to the next epoch era.
let has_committee_members_changes =
committee_before_execution.map(|c| c.members) != committee.map(|c| c.members);
if has_committee_members_changes {
app.set_epoch_era(app.get_epoch_era() + 1);
}

// Set the last executed block hash and sub dag index
// if epoch changed a new committee starts and subdag starts back at 0
let (new_sub_dag_index, new_sub_dag_round) = if response.change_epoch {
(0, 0)
} else {
(block.sub_dag_index, block.sub_dag_round)
};
let (new_sub_dag_index, new_sub_dag_round) =
if response.change_epoch || has_committee_members_changes {
(0, 0)
} else {
(block.sub_dag_index, block.sub_dag_round)
};
app.set_last_block(response.block_hash, new_sub_dag_index, new_sub_dag_round);

// Set the new state root on the response.
Expand Down Expand Up @@ -457,6 +469,7 @@ impl ApplicationEnv {
}

metadata_table.insert(Metadata::Epoch, Value::Epoch(0));
metadata_table.insert(Metadata::EpochEra, Value::EpochEra(0));

tracing::info!("Genesis block loaded into application state.");
Ok(true)
Expand Down
39 changes: 33 additions & 6 deletions core/application/src/state/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ impl<B: Backend> StateExecutor<B> {

// Increase the nodes stake by the amount being staked
node.stake.staked += amount.clone();
self.node_info.set(index, node);
self.node_info.set(index, node.clone());
},
None => {
// If the node doesn't Exist, create it. But check if they provided all the required
Expand Down Expand Up @@ -651,10 +651,10 @@ impl<B: Backend> StateExecutor<B> {
return TransactionResponse::Revert(ExecutionError::InsufficientNodeDetails);
}
},
}
};

// decrement the owners balance
owner.flk_balance -= amount;
owner.flk_balance -= amount.clone();

// Commit changes to the owner
self.account_info.set(sender, owner);
Expand Down Expand Up @@ -758,7 +758,7 @@ impl<B: Backend> StateExecutor<B> {
// current epoch + lock time todo(dalton): we should be storing unstaked tokens in a
// list so we can have multiple locked stakes with dif lock times
node.stake.staked -= amount.clone();
node.stake.locked += amount;
node.stake.locked += amount.clone();
node.stake.locked_until = current_epoch + lock_time;

// Save the changed node state.
Expand All @@ -769,7 +769,7 @@ impl<B: Backend> StateExecutor<B> {
// set as Participation::False on epoch change.
if !self.has_sufficient_unlocked_stake(&node_index) && self.is_participating(&node_index) {
node.participation = Participation::OptedOut;
self.node_info.set(node_index, node);
self.node_info.set(node_index, node.clone());
}

// Return success.
Expand Down Expand Up @@ -864,6 +864,7 @@ impl<B: Backend> StateExecutor<B> {
Some(mut node_info) => {
node_info.participation = Participation::OptedIn;
self.node_info.set(index, node_info);

TransactionResponse::Success(ExecutionData::None)
},
None => TransactionResponse::Revert(ExecutionError::NodeDoesNotExist),
Expand All @@ -879,6 +880,7 @@ impl<B: Backend> StateExecutor<B> {
Some(mut node_info) => {
node_info.participation = Participation::OptedOut;
self.node_info.set(index, node_info);

TransactionResponse::Success(ExecutionData::None)
},
None => TransactionResponse::Revert(ExecutionError::NodeDoesNotExist),
Expand Down Expand Up @@ -1591,7 +1593,7 @@ impl<B: Backend> StateExecutor<B> {
}
}

fn get_epoch(&self) -> u64 {
pub fn get_epoch(&self) -> u64 {
if let Some(Value::Epoch(epoch)) = self.metadata.get(&Metadata::Epoch) {
epoch
} else {
Expand All @@ -1600,6 +1602,14 @@ impl<B: Backend> StateExecutor<B> {
}
}

pub fn get_committee(&self, epoch: Epoch) -> Committee {
self.committee_info.get(&epoch).unwrap_or_default()
}

pub fn get_node_public_key(&self, node_index: &NodeIndex) -> NodePublicKey {
self.node_info.get(node_index).unwrap().public_key
}

fn clear_content_registry(&self, node_index: &NodeIndex) -> Result<(), ExecutionError> {
let uris = self.node_to_uri.get(node_index).unwrap_or_default();

Expand Down Expand Up @@ -1634,6 +1644,7 @@ impl<B: Backend> StateExecutor<B> {
}
}
}

/// Records a node registry change for the current epoch and block number.
fn record_node_registry_change(
&self,
Expand All @@ -1655,4 +1666,20 @@ impl<B: Backend> StateExecutor<B> {
pub fn get_current_committee(&self) -> Option<Committee> {
self.committee_info.get(&self.get_epoch())
}

pub fn get_node_index(&self, node_public_key: &NodePublicKey) -> Option<NodeIndex> {
self.pub_key_to_index.get(node_public_key)
}

pub fn get_epoch_era(&self) -> u64 {
if let Some(Value::EpochEra(era)) = self.metadata.get(&Metadata::EpochEra) {
era
} else {
0
}
}

pub fn set_epoch_era(&self, era: u64) {
self.metadata.set(Metadata::EpochEra, Value::EpochEra(era));
}
}
117 changes: 114 additions & 3 deletions core/application/src/state/executor/epoch_change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use lightning_interfaces::types::{
Metadata,
NodeIndex,
NodeInfo,
NodeRegistryChange,
NodeRegistryChangeSlashReason,
Participation,
ProtocolParamKey,
ProtocolParamValue,
Expand Down Expand Up @@ -105,6 +107,9 @@ impl<B: Backend> StateExecutor<B> {
// Start the committee selection beacon round at 0.
self.set_committee_selection_beacon_round(0);

// Reset the epoch era.
self.metadata.set(Metadata::EpochEra, Value::EpochEra(0));

// Return success.
TransactionResponse::Success(ExecutionData::None)
} else {
Expand Down Expand Up @@ -318,6 +323,21 @@ impl<B: Backend> StateExecutor<B> {
return TransactionResponse::Success(ExecutionData::EpochChange);
}

let commit_count = beacons.len();
let reveal_count = beacons
.iter()
.filter(|(_, (_, reveal))| reveal.is_some())
.count();
let (phase_start, phase_end) = reveal_phase.unwrap();
tracing::debug!(
"waiting for remaining nodes to reveal (epoch: {}, start: {}, end: {}, committed: {}, revealed: {})",
epoch,
phase_start,
phase_end,
commit_count,
reveal_count,
);

// Return success.
TransactionResponse::Success(ExecutionData::None)
}
Expand Down Expand Up @@ -417,7 +437,12 @@ impl<B: Backend> StateExecutor<B> {
epoch,
round + 1,
commit_start,
commit_end
commit_end,
);
tracing::debug!(
"commit phase had insufficient participation (committed: {:?}, nodes: {:?})",
committee_beacons.keys().collect::<Vec<_>>(),
participating_nodes,
);
self.set_committee_selection_beacon_commit_phase(commit_start, commit_end);

Expand Down Expand Up @@ -506,13 +531,20 @@ impl<B: Backend> StateExecutor<B> {
let non_revealing_nodes = beacons
.iter()
.filter(|(_, (_, reveal))| reveal.is_none())
.map(|(node_index, _)| node_index)
.map(|(node_index, _)| *node_index)
.collect::<Vec<_>>();
for node_index in non_revealing_nodes {
for node_index in &non_revealing_nodes {
self.committee_selection_beacon_non_revealing_node
.set(*node_index, ());
}

// Slash non-revealing nodes and remove from the committee and active set of nodes if they
// no longer have sufficient stake.
let slash_amount = self.get_committee_selection_beacon_non_reveal_slash_amount();
for node_index in &non_revealing_nodes {
self.slash_node_after_non_reveal_and_maybe_kick(node_index, &slash_amount);
}

// Clear the beacon state.
for digest in self.committee_selection_beacon.keys() {
self.committee_selection_beacon.remove(&digest);
Expand Down Expand Up @@ -1016,4 +1048,83 @@ impl<B: Backend> StateExecutor<B> {
_ => unreachable!("invalid committee size in protocol parameters"),
}
}

/// Slash a node by removing the given amount from the node's staked balance.
///
/// If the node no longer has sufficient stake, it's removed from the committee and active node
/// set.
pub fn slash_node_after_non_reveal_and_maybe_kick(
&self,
node_index: &NodeIndex,
amount: &HpUfixed<18>,
) {
let node_index = *node_index;

// Remove the given slash amount from the node's staked balance.
let mut node_info = self.node_info.get(&node_index).unwrap();
let remaining_amount = if node_info.stake.staked >= amount.clone() {
node_info.stake.staked -= amount.clone();
HpUfixed::<18>::zero()
} else {
let remaining = amount.clone() - node_info.stake.staked;
node_info.stake.staked = HpUfixed::zero();
remaining
};
if node_info.stake.locked >= remaining_amount {
node_info.stake.locked -= remaining_amount;
} else {
node_info.stake.locked = HpUfixed::zero();
}
self.node_info.set(node_index, node_info.clone());
tracing::info!("slashing node {:?} by {:?}", node_index, amount);

// Record the node registry change.
self.record_node_registry_change(
node_info.public_key,
NodeRegistryChange::Slashed((
amount.clone(),
node_info.stake,
NodeRegistryChangeSlashReason::CommitteeBeaconNonReveal,
)),
);

// If the node no longer has sufficient stake, remove it from the committee members and
// active nodes.
if !self.has_sufficient_stake(&node_index) {
let epoch = self.get_epoch();
let mut committee = self.get_committee(epoch);

// Remove node from committee members and active node set.
committee.members.retain(|member| *member != node_index);
committee
.active_node_set
.retain(|member| *member != node_index);

// Save the updated committee info.
tracing::info!(
"removing node {:?} from committee and active node set after being slashed",
node_index
);
self.committee_info.set(epoch, committee);
}
}

/// Get the slash amount for non-revealing nodes in the committee selection beacon process.
///
/// Returns 0 if the slash amount is not set in the protocol parameters.
/// Panics if the slash amount is not the expected type.
fn get_committee_selection_beacon_non_reveal_slash_amount(&self) -> HpUfixed<18> {
match self
.parameters
.get(&ProtocolParamKey::CommitteeSelectionBeaconNonRevealSlashAmount)
{
Some(ProtocolParamValue::CommitteeSelectionBeaconNonRevealSlashAmount(amount)) => {
amount.into()
},
None => HpUfixed::<18>::zero(),
_ => unreachable!(
"invalid committee selection beacon non-reveal slash amount in protocol parameters"
),
}
}
}
Loading

0 comments on commit c5a858e

Please sign in to comment.