Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e4364e3
First experiments with Sassafras equivocations report
davxy Sep 12, 2022
b267418
Preparation for sassafras client tests
davxy Sep 20, 2022
1292e86
Cleanup and first working client test with multiple peers
davxy Sep 23, 2022
d0c8bc2
Merge branch 'davxy-sassafras-protocol' into davxy/sassafras-protocol…
davxy Sep 24, 2022
0a20351
Dummy commit
davxy Sep 24, 2022
326ac44
Conflicts resolution
davxy Sep 24, 2022
5b43f28
Test code refactory
davxy Sep 27, 2022
2dd6086
Better submit-tickets extrinsic tag
davxy Sep 27, 2022
829b90b
Refactory of client tests
davxy Oct 6, 2022
a2d66b6
Aux data revert implementation
davxy Oct 6, 2022
ca4b563
Handle skipped epochs on block-import
davxy Oct 7, 2022
e7be289
Skipped epoch test and fix
davxy Oct 11, 2022
7690a5c
Fix to epoch start slot computation
davxy Oct 14, 2022
6c20538
Minor tweaks
davxy Oct 17, 2022
2361064
Trivial comments refactory
davxy Oct 24, 2022
a26a31e
Do not alter original epoch changes node on epoch skip
davxy Oct 24, 2022
f3ebc2b
Insert tickets aux data after block import
davxy Oct 24, 2022
84bfdff
Tests environment refactory
davxy Oct 24, 2022
cfe639e
Use in-memory keystore for tests
davxy Oct 24, 2022
51e81a2
Push lock file
davxy Oct 24, 2022
04e92f6
Use test accounts keyring
davxy Oct 24, 2022
193134a
Test for secondary slots claims
davxy Oct 24, 2022
8bd8aed
Improved tests after epoch changes tree fix
davxy Oct 25, 2022
76c24bd
Tests for blocks verification
davxy Oct 29, 2022
950020b
Next epoch tickets incremental sort
davxy Oct 29, 2022
94e9ee8
Incremental sortition test
davxy Oct 29, 2022
d1a7edd
Set proper tickets tx longevity
davxy Oct 29, 2022
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
Next Next commit
First experiments with Sassafras equivocations report
  • Loading branch information
davxy committed Sep 20, 2022
commit e4364e34aef43fb3612129057271ea6d3b6916e5
6 changes: 3 additions & 3 deletions bin/node-sassafras/node/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>

let slot_duration = sassafras_link.genesis_config().slot_duration();

let sassafras_config = sc_consensus_sassafras::SassafrasParams {
let sassafras_params = sc_consensus_sassafras::SassafrasParams {
client: client.clone(),
keystore: keystore_container.sync_keystore(),
select_chain,
Expand All @@ -281,7 +281,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
sync_oracle: network.clone(),
justification_sync_link: network.clone(),
force_authoring,
create_inherent_data_providers: move |_, ()| async move {
create_inherent_data_providers: move |_, _| async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();

let slot =
Expand All @@ -294,7 +294,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
can_author_with,
};

let sassafras = sc_consensus_sassafras::start_sassafras(sassafras_config)?;
let sassafras = sc_consensus_sassafras::start_sassafras(sassafras_params)?;

// the Sassafras authoring task is considered essential, i.e. if it
// fails we take down the service with it.
Expand Down
19 changes: 16 additions & 3 deletions bin/node-sassafras/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,16 +453,29 @@ impl_runtime_apis! {
fn slot_ticket(slot: sp_consensus_sassafras::Slot) -> Option<sp_consensus_sassafras::Ticket> {
Sassafras::slot_ticket(slot)
}

fn generate_key_ownership_proof(
slot: sp_consensus_sassafras::Slot,
authority_id: sp_consensus_sassafras::AuthorityId,
) -> Option<sp_consensus_sassafras::OpaqueKeyOwnershipProof> {
None
}

fn submit_report_equivocation_unsigned_extrinsic(
equivocation_proof: sp_consensus_sassafras::EquivocationProof<<Block as BlockT>::Header>,
_key_owner_proof: sp_consensus_sassafras::OpaqueKeyOwnershipProof,
) -> bool {
//let key_owner_proof = key_owner_proof.decode()?;
Sassafras::submit_unsigned_equivocation_report(equivocation_proof)
}
}

impl sp_session::SessionKeys<Block> for Runtime {
fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
SessionKeys::generate(seed)
}

fn decode_session_keys(
encoded: Vec<u8>,
) -> Option<Vec<(Vec<u8>, KeyTypeId)>> {
fn decode_session_keys(encoded: Vec<u8>) -> Option<Vec<(Vec<u8>, KeyTypeId)>> {
SessionKeys::decode_into_raw_public_keys(&encoded)
}
}
Expand Down
4 changes: 1 addition & 3 deletions client/consensus/sassafras/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ use sc_consensus_epochs::{
descendent_query, Epoch as EpochT, EpochChangesFor, EpochIdentifier, EpochIdentifierPosition,
SharedEpochChanges, ViableEpochDescriptor,
};
use sc_consensus_slots::{
check_equivocation, CheckedHeader, InherentDataProviderExt, SlotInfo, StorageChanges,
};
use sc_consensus_slots::{CheckedHeader, InherentDataProviderExt, SlotInfo, StorageChanges};
use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE};
use sp_api::{ApiExt, ProvideRuntimeApi};
use sp_application_crypto::AppKey;
Expand Down
62 changes: 52 additions & 10 deletions client/consensus/sassafras/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,31 +224,73 @@ where
}

// Check if authorship of this header is an equivocation and return a proof if so.
let equivocation_proof =
match check_equivocation(&*self.client, slot_now, slot, header, author)
.map_err(Error::Client)?
{
Some(proof) => proof,
None => return Ok(()),
};
let equivocation_proof = match sc_consensus_slots::check_equivocation(
&*self.client,
slot_now,
slot,
header,
author,
)
.map_err(Error::Client)?
{
Some(proof) => proof,
None => return Ok(()),
};

info!(
"Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
target: "sassafras",
"🌳 Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
author,
slot,
equivocation_proof.first_header.hash(),
equivocation_proof.second_header.hash(),
);

// Get the best block on which we will build and send the equivocation report.
let _best_id: BlockId<Block> = self
let best_id = self
.select_chain
.best_chain()
.await
.map(|h| BlockId::Hash(h.hash()))
.map_err(|e| Error::Client(e.into()))?;

// TODO-SASS-P2
// Generate a key ownership proof. We start by trying to generate the key owernship proof
// at the parent of the equivocating header, this will make sure that proof generation is
// successful since it happens during the on-going session (i.e. session keys are available
// in the state to be able to generate the proof). This might fail if the equivocation
// happens on the first block of the session, in which case its parent would be on the
// previous session. If generation on the parent header fails we try with best block as
// well.
let generate_key_owner_proof = |block_id: &BlockId<Block>| {
self.client
.runtime_api()
.generate_key_ownership_proof(block_id, slot, equivocation_proof.offender.clone())
.map_err(Error::RuntimeApi)
};

let parent_id = BlockId::Hash(*header.parent_hash());
let key_owner_proof = match generate_key_owner_proof(&parent_id)? {
Some(proof) => proof,
None => match generate_key_owner_proof(&best_id)? {
Some(proof) => proof,
None => {
debug!(target: "babe", "Equivocation offender is not part of the authority set.");
return Ok(())
},
},
};

// submit equivocation report at best block.
self.client
.runtime_api()
.submit_report_equivocation_unsigned_extrinsic(
&best_id,
equivocation_proof,
key_owner_proof,
)
.map_err(Error::RuntimeApi)?;

info!(target: "sassafras", "🌳 Submitted equivocation report for author {:?}", author);

Ok(())
}
Expand Down
79 changes: 71 additions & 8 deletions frame/sassafras/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ use frame_support::{traits::Get, weights::Weight, BoundedVec, WeakBoundedVec};
use frame_system::offchain::{SendTransactionTypes, SubmitTransaction};
use sp_consensus_sassafras::{
digests::{ConsensusLog, NextEpochDescriptor, PreDigest},
AuthorityId, Randomness, SassafrasAuthorityWeight, SassafrasEpochConfiguration, Slot, Ticket,
SASSAFRAS_ENGINE_ID,
AuthorityId, EquivocationProof, Randomness, SassafrasAuthorityWeight,
SassafrasEpochConfiguration, Slot, Ticket, SASSAFRAS_ENGINE_ID,
};
use sp_runtime::{
generic::DigestItem,
Expand Down Expand Up @@ -302,6 +302,7 @@ pub mod pallet {
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Submit next epoch tickets.
///
/// TODO-SASS-P3: this is an unsigned extrinsic. Can we remov ethe weight?
#[pallet::weight(10_000)]
pub fn submit_tickets(
Expand All @@ -321,23 +322,55 @@ pub mod pallet {
Ok(())
}

/// Plan an epoch config change. The epoch config change is recorded and will be enacted on
/// the next call to `enact_session_change`. The config will be activated one epoch after.
/// Multiple calls to this method will replace any existing planned config change that had
/// not been enacted yet.
/// Plan an epoch config change.
///
/// The epoch config change is recorded and will be enacted on the next call to
/// `enact_session_change`.
///
/// The config will be activated one epoch after. Multiple calls to this method will
/// replace any existing planned config change that had not been enacted yet.
///
/// TODO: TODO-SASS-P4: proper weight
#[pallet::weight(10_000)]
pub fn plan_config_change(
origin: OriginFor<T>,
config: SassafrasEpochConfiguration,
) -> DispatchResult {
ensure_root(origin)?;

ensure!(
config.redundancy_factor != 0 && config.attempts_number != 0,
Error::<T>::InvalidConfiguration
);
PendingEpochConfigChange::<T>::put(config);
Ok(())
}

/// Report authority equivocation.
///
/// This method will verify the equivocation proof and validate the given key ownership
/// proof against the extracted offender. If both are valid, the offence will be reported.
///
/// This extrinsic must be called unsigned and it is expected that only block authors will
/// call it (validated in `ValidateUnsigned`), as such if the block author is defined it
/// will be defined as the equivocation reporter.
///
/// TODO: TODO-SASS-P4: proper weight
#[pallet::weight(10_000)]
pub fn report_equivocation_unsigned(
origin: OriginFor<T>,
_equivocation_proof: EquivocationProof<T::Header>,
//key_owner_proof: T::KeyOwnerProof,
) -> DispatchResult {
ensure_none(origin)?;

// Self::do_report_equivocation(
// T::HandleEquivocation::block_author(),
// *equivocation_proof,
// key_owner_proof,
// )
Ok(())
}
}

#[pallet::validate_unsigned]
Expand Down Expand Up @@ -728,17 +761,47 @@ impl<T: Config> Pallet<T> {
metadata.segments_count = segments_count;
}

/// Submit next epoch validator tickets via an unsigned extrinsic.
/// Submit next epoch validator tickets via an unsigned extrinsic constructed with a call to
/// `submit_unsigned_transaction`.
///
/// The submitted tickets are added to the `NextTickets` list as long as the extrinsic has
/// is called within the first half of the epoch. That is, tickets received within the
/// second half are dropped.
///
/// TODO-SASS-P3: we have to add the zk validity proofs
pub fn submit_tickets_unsigned_extrinsic(mut tickets: Vec<Ticket>) -> bool {
log::debug!(target: "sassafras", "🌳 @@@@@@@@@@ submitting {} tickets", tickets.len());
tickets.sort_unstable();
let tickets = BoundedVec::truncate_from(tickets);
let call = Call::submit_tickets { tickets };
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()).is_ok()
match SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) {
Ok(_) => true,
Err(e) => {
log::error!(target: "runtime::sassafras", "Error submitting tickets {:?}", e);
false
},
}
}

/// Submits an equivocation via an unsigned extrinsic.
///
/// Unsigned extrinsic is created with a call to `report_equivocation_unsigned`.
pub fn submit_unsigned_equivocation_report(
equivocation_proof: EquivocationProof<T::Header>,
//key_owner_proof: T::KeyOwnerProof,
) -> bool {
let call = Call::report_equivocation_unsigned {
equivocation_proof,
// key_owner_proof,
};

match SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) {
Ok(()) => true,
Err(e) => {
log::error!(target: "runtime::sassafras", "Error submitting equivocation report: {:?}", e);
false
},
}
}
}

Expand Down
1 change: 1 addition & 0 deletions primitives/consensus/babe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ where
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
#[derive(Decode, Encode, PartialEq)]
pub struct OpaqueKeyOwnershipProof(Vec<u8>);

impl OpaqueKeyOwnershipProof {
/// Create a new `OpaqueKeyOwnershipProof` using the given encoded
/// representation.
Expand Down
55 changes: 53 additions & 2 deletions primitives/consensus/sassafras/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ pub type SassafrasAuthorityWeight = u64;
/// Primary blocks have a weight of 1 whereas secondary blocks have a weight of 0.
pub type SassafrasBlockWeight = u32;

/// An equivocation proof for multiple block authorships on the same slot (i.e. double vote).
pub type EquivocationProof<H> = sp_consensus_slots::EquivocationProof<H, AuthorityId>;

/// Configuration data used by the Sassafras consensus engine.
#[derive(Clone, Encode, Decode, RuntimeDebug, PartialEq, Eq)]
pub struct SassafrasConfiguration {
Expand Down Expand Up @@ -130,7 +133,6 @@ pub struct TicketAux {
/// The parameters should be chosen such that T <= 1.
/// If `attempts * validators` is zero then we fallback to T = 0
// TODO-SASS-P3: this formula must be double-checked...
#[inline]
pub fn compute_threshold(redundancy: u32, slots: u32, attempts: u32, validators: u32) -> U256 {
let den = attempts as u64 * validators as u64;
let num = redundancy as u64 * slots as u64;
Expand All @@ -141,11 +143,31 @@ pub fn compute_threshold(redundancy: u32, slots: u32, attempts: u32, validators:
}

/// Returns true if the given VRF output is lower than the given threshold, false otherwise.
#[inline]
pub fn check_threshold(ticket: &Ticket, threshold: U256) -> bool {
U256::from(ticket.as_bytes()) < threshold
}

/// An opaque type used to represent the key ownership proof at the runtime API boundary.
/// The inner value is an encoded representation of the actual key ownership proof which will be
/// parameterized when defining the runtime. At the runtime API boundary this type is unknown and
/// as such we keep this opaque representation, implementors of the runtime API will have to make
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
#[derive(Decode, Encode, PartialEq)]
pub struct OpaqueKeyOwnershipProof(Vec<u8>);

impl OpaqueKeyOwnershipProof {
/// Create a new `OpaqueKeyOwnershipProof` using the given encoded representation.
pub fn new(inner: Vec<u8>) -> OpaqueKeyOwnershipProof {
OpaqueKeyOwnershipProof(inner)
}

/// Try to decode this `OpaqueKeyOwnershipProof` into the given concrete key
/// ownership proof type.
pub fn decode<T: Decode>(self) -> Option<T> {
Decode::decode(&mut &self.0[..]).ok()
}
}

// Runtime API.
sp_api::decl_runtime_apis! {
/// API necessary for block authorship with Sassafras.
Expand All @@ -159,5 +181,34 @@ sp_api::decl_runtime_apis! {

/// Get expected ticket for the given slot.
fn slot_ticket(slot: Slot) -> Option<Ticket>;

/// Generates a proof of key ownership for the given authority in the current epoch.
///
/// An example usage of this module is coupled with the session historical module to prove
/// that a given authority key is tied to a given staking identity during a specific
/// session. Proofs of key ownership are necessary for submitting equivocation reports.
///
/// NOTE: even though the API takes a `slot` as parameter the current implementations
/// ignores this parameter and instead relies on this method being called at the correct
/// block height, i.e. any point at which the epoch for the given slot is live on-chain.
/// Future implementations will instead use indexed data through an offchain worker, not
/// requiring older states to be available.
fn generate_key_ownership_proof(
slot: Slot,
authority_id: AuthorityId,
) -> Option<OpaqueKeyOwnershipProof>;

/// Submits an unsigned extrinsic to report an equivocation.
///
/// The caller must provide the equivocation proof and a key ownership proof (should be
/// obtained using `generate_key_ownership_proof`). The extrinsic will be unsigned and
/// should only be accepted for local authorship (not to be broadcast to the network). This
/// method returns `None` when creation of the extrinsic fails, e.g. if equivocation
/// reporting is disabled for the given runtime (i.e. this method is hardcoded to return
/// `None`). Only useful in an offchain context.
fn submit_report_equivocation_unsigned_extrinsic(
equivocation_proof: EquivocationProof<Block::Header>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> bool;
}
}