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
28 commits
Select commit Hold shift + click to select a range
82500c8
First draft of Sassafras components structure
davxy May 24, 2022
d7bef64
Implementation of Sassafras test node
davxy May 24, 2022
59e5b53
Start filling the placeholders
davxy May 24, 2022
e5a8239
Implemented epoch change using internal trigger
davxy May 31, 2022
7a456e2
Epoch randomness computation using blocks VRF accumulator
davxy Jun 1, 2022
11e009a
Verification on block import
davxy Jun 13, 2022
717a16f
Tickets generation procedure
davxy Jun 14, 2022
d86aef1
Publish tickets on-chain using unsigned extrinsic
davxy Jun 16, 2022
33c163d
Introduced first implementation of secondary slot claiming
davxy Jun 22, 2022
b82cccf
Use ticket to claim slot without proof check
davxy Jun 27, 2022
5e9c1f5
Enact epoch tickets on last epoch block
davxy Jun 28, 2022
9865938
Small refactory
davxy Jun 28, 2022
c3660f5
Unify primary and secondary pre-digest
davxy Jun 28, 2022
b50d9f9
Bump Sassafras components version to 0.1.0
davxy Jun 29, 2022
d78f6a2
Use BTreeSet instead of Vec to store next epoch tickets
davxy Jul 1, 2022
2508a5e
Trick to fetch the fist ticket on epoch change
davxy Jul 1, 2022
1e0e092
Fix epoch ticket get api
davxy Jul 2, 2022
8f4b18c
Blocks verification on import
davxy Jul 4, 2022
622b183
Merge branch 'master' into davxy-sassafras-consensus-prototype1
davxy Jul 19, 2022
cfd4c6a
Fix after master merge
davxy Jul 19, 2022
442d901
Restore good old Cargo.lock
davxy Jul 19, 2022
be27264
Fix unused fields warnings
davxy Jul 19, 2022
2a25d82
Try to make the compiler happy
davxy Jul 19, 2022
0e44362
Removed num-bigint dependency
davxy Jul 20, 2022
cd1ef1c
More robust tickets handling
davxy Jul 20, 2022
3d92632
TODOs priority classification
davxy Jul 20, 2022
3d8d2a8
Resolved TODOs with P1
davxy Jul 20, 2022
ccdfd37
Fix github link
davxy Jul 20, 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
Prev Previous commit
Next Next commit
Unify primary and secondary pre-digest
  • Loading branch information
davxy committed Jun 28, 2022
commit c3660f5297ab0738b181d3a5ca705ae8d5f8d43d
83 changes: 22 additions & 61 deletions client/consensus/sassafras/src/authorship.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ use crate::Epoch;
use scale_codec::Encode;
use sp_application_crypto::AppKey;
use sp_consensus_sassafras::{
digests::{PreDigest, PrimaryPreDigest, SecondaryPreDigest},
make_ticket_transcript, make_ticket_transcript_data, make_transcript_data, AuthorityId,
SassafrasAuthorityWeight, Slot, Ticket, TicketMetadata, SASSAFRAS_TICKET_VRF_PREFIX,
digests::PreDigest, make_ticket_transcript, make_ticket_transcript_data, make_transcript_data,
AuthorityId, SassafrasAuthorityWeight, Slot, Ticket, TicketMetadata,
SASSAFRAS_TICKET_VRF_PREFIX,
};
use sp_consensus_vrf::schnorrkel::{PublicKey, VRFInOut, VRFOutput, VRFProof};
use sp_core::{twox_64, ByteArray};
Expand Down Expand Up @@ -63,30 +63,22 @@ pub fn claim_slot_using_keys(
return None
}

// TODO-SASS: if we are not the owner of the primary, we can think of submitting the
// secondary for redundancy (as for BABE)...
if let Some(ticket) = ticket {
claim_primary_slot(slot, ticket, epoch, keystore, authorities)
} else {
claim_secondary_slot(slot, epoch, keystore, authorities)
}
}
// TODO-SASS: fix u32 vs u64 for auth index
let (idx, proof) = match ticket {
Some(ticket) => {
log::debug!(target: "sassafras", "🌳 [TRY PRIMARY]");
let ticket_info = epoch.tickets_info.get(&ticket)?;
let idx = ticket_info.authority_index as u64;
(idx, Some(ticket_info.proof.clone()))
},
None => {
log::debug!(target: "sassafras", "🌳 [TRY SECONDARY]");
let idx = u64::from_le_bytes((epoch.randomness, slot).using_encoded(twox_64)) %
authorities.len() as u64;
(idx, None)
},
};

/// Claim a primary slot if it is our turn given the ticket.
/// Returns `None` if it is not our turn.
fn claim_primary_slot(
slot: Slot,
ticket: Ticket,
epoch: &Epoch,
keystore: &SyncCryptoStorePtr,
authorities: &[(AuthorityId, usize)],
) -> Option<(PreDigest, AuthorityId)> {
log::debug!(target: "sassafras", "🌳 [TRY PRIMARY]");
let ticket_info = epoch.tickets_info.get(&ticket)?;

log::debug!(target: "sassafras", "🌳 ticket-info = [ attempt: {}, auth-idx: {} ]", ticket_info.attempt, ticket_info.authority_index);

let idx = ticket_info.authority_index;
let expected_author = authorities.get(idx as usize).map(|auth| &auth.0)?;

for (authority_id, authority_index) in authorities {
Expand All @@ -105,50 +97,18 @@ fn claim_primary_slot(
);

if let Ok(Some(signature)) = result {
let pre_digest = PreDigest::Primary(PrimaryPreDigest {
let pre_digest = PreDigest {
authority_index: *authority_index as u32,
slot,
block_vrf_output: VRFOutput(signature.output),
block_vrf_proof: VRFProof(signature.proof.clone()),
ticket_vrf_proof: ticket_info.proof.clone(),
});
ticket_vrf_proof: proof,
};
return Some((pre_digest, authority_id.clone()))
}
}
}
None
}

/// Claim a secondary slot if it is our turn to propose
/// Returns `None` if it is not our turn.
fn claim_secondary_slot(
slot: Slot,
epoch: &Epoch,
keystore: &SyncCryptoStorePtr,
authorities: &[(AuthorityId, usize)],
) -> Option<(PreDigest, AuthorityId)> {
log::debug!(target: "sassafras", "🌳 [TRY SECONDARY]");

let idx = u64::from_le_bytes((epoch.randomness, slot).using_encoded(twox_64)) %
authorities.len() as u64;
let expected_author = authorities
.get(idx as usize)
.map(|auth| &auth.0)
.expect("authorities not empty and index constrained to list length; qed");

for (authority_id, authority_index) in authorities {
if authority_id == expected_author &&
SyncCryptoStore::has_keys(
&**keystore,
&[(authority_id.to_raw_vec(), AuthorityId::ID)],
) {
let pre_digest = PreDigest::Secondary(SecondaryPreDigest {
authority_index: *authority_index as u32,
slot,
});
return Some((pre_digest, authority_id.clone()))
}
}
None
}

Expand All @@ -163,6 +123,7 @@ fn claim_secondary_slot(
/// - V: number of validator in epoch
///
/// The parameters should be chosen such that T <= 1.
// TODO-SASS: this shall be analyzed properly.
#[inline]
pub fn calculate_threshold(
redundancy_factor: usize,
Expand Down
47 changes: 24 additions & 23 deletions client/consensus/sassafras/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,8 @@ use sp_blockchain::{
};
use sp_consensus::{
BlockOrigin, CacheKeyId, CanAuthorWith, Environment, Error as ConsensusError, Proposer,
SelectChain,
SelectChain, SyncOracle,
};
use sp_consensus_sassafras::{inherents::SassafrasInherentData, TicketMetadata, VRFProof};
use sp_consensus_slots::{Slot, SlotDuration};
use sp_core::{crypto::ByteArray, traits::SpawnEssentialNamed, ExecutionContext};
use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider};
Expand All @@ -87,15 +86,13 @@ use sp_runtime::{
DigestItem,
};

pub use sp_consensus::SyncOracle;
// Re-export Sassafras primitives.
pub use sp_consensus_sassafras::{
digests::{
CompatibleDigestItem, ConsensusLog, NextEpochDescriptor, PreDigest, PrimaryPreDigest,
SecondaryPreDigest,
},
digests::{CompatibleDigestItem, ConsensusLog, NextEpochDescriptor, PreDigest},
inherents::SassafrasInherentData,
AuthorityId, AuthorityPair, AuthoritySignature, SassafrasApi, SassafrasAuthorityWeight,
SassafrasEpochConfiguration, SassafrasGenesisConfiguration, Ticket, SASSAFRAS_ENGINE_ID,
VRF_OUTPUT_LENGTH,
SassafrasEpochConfiguration, SassafrasGenesisConfiguration, Ticket, TicketMetadata, VRFOutput,
VRFProof, SASSAFRAS_ENGINE_ID, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH,
};

mod verification;
Expand Down Expand Up @@ -204,18 +201,12 @@ pub enum Error<B: BlockT> {
/// Slot author not found
#[error("Slot author not found")]
SlotAuthorNotFound,
/// Secondary slot assignments are disabled for the current epoch.
#[error("Secondary slot assignments are disabled for the current epoch.")]
SecondarySlotAssignmentsDisabled,
/// Bad signature
#[error("Bad signature on {0:?}")]
BadSignature(B::Hash),
/// Invalid author: Expected secondary author
#[error("Invalid author: Expected secondary author: {0:?}, got: {1:?}.")]
InvalidAuthor(AuthorityId, AuthorityId),
/// No secondary author expected.
#[error("No secondary author expected.")]
NoSecondaryAuthorExpected,
/// VRF verification of block by author failed
#[error("VRF verification of block by author {0:?} failed: threshold {1} exceeded")]
VRFVerificationOfBlockFailed(AuthorityId, u128),
Expand Down Expand Up @@ -799,7 +790,7 @@ where
}

fn proposing_remaining_duration(&self, slot_info: &SlotInfo<B>) -> Duration {
let parent_slot = find_pre_digest::<B>(&slot_info.chain_head).ok().map(|d| d.slot());
let parent_slot = find_pre_digest::<B>(&slot_info.chain_head).ok().map(|d| d.slot);

// TODO-SASS : clarify this field. In Sassafras this is part of 'self'
let block_proposal_slot_portion = sc_consensus_slots::SlotProportion::new(0.5);
Expand All @@ -821,7 +812,16 @@ pub fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<PreDigest, Error
// Genesis block doesn't contain a pre digest so let's generate a
// dummy one to not break any invariants in the rest of the code
if header.number().is_zero() {
return Ok(PreDigest::Secondary(SecondaryPreDigest { authority_index: 0, slot: 0.into() }))
const PROOF: &str = "zero sequence is a valid vrf output/proof; qed";
let block_vrf_output = VRFOutput::try_from([0; VRF_OUTPUT_LENGTH]).expect(PROOF);
let block_vrf_proof = VRFProof::try_from([0; VRF_PROOF_LENGTH]).expect(PROOF);
return Ok(PreDigest {
authority_index: 0,
slot: 0.into(),
block_vrf_output,
block_vrf_proof,
ticket_vrf_proof: None,
})
}

let mut pre_digest: Option<_> = None;
Expand Down Expand Up @@ -1034,7 +1034,7 @@ where
descendent_query(&*self.client),
&parent_hash,
parent_header_metadata.number,
pre_digest.slot(),
pre_digest.slot,
)
.map_err(|e| Error::<Block>::ForkTree(Box::new(e)))?
.ok_or(Error::<Block>::FetchEpoch(parent_hash))?;
Expand Down Expand Up @@ -1063,7 +1063,7 @@ where
.pre_digest
.as_sassafras_pre_digest()
.expect("check_header always returns a pre-digest digest item; qed");
let slot = sassafras_pre_digest.slot();
let slot = sassafras_pre_digest.slot;

// The header is valid but let's check if there was something else already
// proposed at the same slot by the given author. If there was, we will
Expand Down Expand Up @@ -1200,7 +1200,7 @@ where
let pre_digest = find_pre_digest::<Block>(&block.header).expect(
"valid sassafras headers must contain a predigest; header has been already verified; qed",
);
let slot = pre_digest.slot();
let slot = pre_digest.slot;

let parent_hash = *block.header.parent_hash();
let parent_header = self
Expand All @@ -1213,7 +1213,7 @@ where
)
})?;

let parent_slot = find_pre_digest::<Block>(&parent_header).map(|d| d.slot()).expect(
let parent_slot = find_pre_digest::<Block>(&parent_header).map(|d| d.slot).expect(
"parent is non-genesis; valid Sassafras headers contain a pre-digest; \
header has already been verified; qed",
);
Expand Down Expand Up @@ -1258,7 +1258,8 @@ where
let epoch_descriptor = intermediate.epoch_descriptor;
let first_in_epoch = parent_slot < epoch_descriptor.start_slot();

let total_weight = parent_weight + pre_digest.added_weight();
let added_weight = pre_digest.ticket_vrf_proof.is_some() as u32;
let total_weight = parent_weight + added_weight;

// Search for this all the time so we can reject unexpected announcements.
let next_epoch_digest = find_next_epoch_digest::<Block>(&block.header)
Expand Down Expand Up @@ -1438,7 +1439,7 @@ where

find_pre_digest::<B>(&finalized_header)
.expect("finalized header must be valid; valid blocks have a pre-digest; qed")
.slot()
.slot
};

epoch_changes
Expand Down
16 changes: 7 additions & 9 deletions client/consensus/sassafras/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use super::{find_pre_digest, sassafras_err, BlockT, Epoch, Error};
use sc_consensus_slots::CheckedHeader;
use sp_consensus_sassafras::{
digests::{CompatibleDigestItem, PreDigest, PrimaryPreDigest, SecondaryPreDigest},
digests::{CompatibleDigestItem, PreDigest},
make_transcript, AuthorityId, AuthorityPair, AuthoritySignature,
};
use sp_consensus_slots::Slot;
Expand Down Expand Up @@ -63,8 +63,8 @@ pub(super) fn check_header<B: BlockT + Sized>(

let pre_digest = pre_digest.map(Ok).unwrap_or_else(|| find_pre_digest::<B>(&header))?;

if pre_digest.slot() > slot_now {
return Ok(CheckedHeader::Deferred(header, pre_digest.slot()))
if pre_digest.slot > slot_now {
return Ok(CheckedHeader::Deferred(header, pre_digest.slot))
}

let seal = header
Expand All @@ -80,23 +80,21 @@ pub(super) fn check_header<B: BlockT + Sized>(
let _pre_hash = header.hash();

//let authorities = &epoch.authorities;
let author = match epoch.authorities.get(pre_digest.authority_index() as usize) {
let author = match epoch.authorities.get(pre_digest.authority_index as usize) {
Some(author) => author.0.clone(),
None => return Err(sassafras_err(Error::SlotAuthorNotFound)),
};

match &pre_digest {
PreDigest::Primary(primary) => {
match pre_digest.ticket_vrf_proof {
Some(_) => {
// TODO-SASS: check primary
// 1. the ticket proof is valid
// 2. the block vrf proof is valid
let _ = primary;
log::debug!(target: "sassafras", "🌳 checking primary (TODO)");
},
PreDigest::Secondary(secondary) => {
None => {
// TODO-SASS: check secondary
// 1. the expected author index
let _ = secondary;
log::debug!(target: "sassafras", "🌳 checking secondary (TODO)");
},
}
Expand Down
Loading