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

Sassafras: align to the latest specs #4373

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
d26386b
Begin Sassafras overhaul to align to the latest specs
davxy May 3, 2024
f5ce73b
Rething epoch index management
davxy May 6, 2024
fe461ba
Configuration separated by current epoch data
davxy May 7, 2024
2d08b1a
More tests
davxy May 7, 2024
9aa0d12
Test tickets submission and correct order for committed tickets
davxy May 7, 2024
680d88d
Remove old implementation
davxy May 7, 2024
1d0b093
Fix warnings
davxy May 7, 2024
9e32ffb
Remove some leftovers
davxy May 7, 2024
5249537
Benchmarking
davxy May 8, 2024
edb1bdf
Weights update
davxy May 8, 2024
af3f20a
Fix test
davxy May 8, 2024
7f91e75
Submit tickets via Inherents
davxy May 8, 2024
457ee80
Update lock file
davxy May 8, 2024
7cee2b6
Merge branch 'master' into sassafras-overhaul
davxy May 8, 2024
136673a
Update lock
davxy May 8, 2024
a421a58
Add license to weights
davxy May 8, 2024
6d142e8
Pre-computed tickets data
davxy May 8, 2024
61a4692
Rename TicketsMetadata to TicketsCounter
davxy May 9, 2024
5baff7b
Update weights
davxy May 9, 2024
257432f
Nits
davxy May 9, 2024
6c60e88
Get rid of persistent epoch index
davxy May 9, 2024
7ab117a
Improve docs
davxy May 10, 2024
2464819
Fix sassafras vrf inputs
davxy May 10, 2024
dadc7d8
EpochTag trait
davxy May 10, 2024
0bea265
Check randomness vrf signature on debug
davxy May 10, 2024
62b544d
Deposit tickets test
davxy May 10, 2024
c487918
Nits
davxy May 10, 2024
6d8080d
Fix std feature propagation
davxy May 10, 2024
72b1a65
Fallback to AURA if unable to construct ring verifier
davxy May 13, 2024
dcb9042
Ticket body is the onchain struct
davxy Jun 10, 2024
683b7e5
Nit
davxy Jun 10, 2024
fe2f284
Update benchmarking code
davxy Jun 10, 2024
5d8b76b
Merge branch 'master' into sassafras-overhaul
davxy Jun 10, 2024
fda29a6
Typo
davxy Jun 10, 2024
7a61b1c
Clippy
davxy Jun 10, 2024
d56c1c2
Remove dead code
davxy Jul 22, 2024
4bf4273
Merge branch 'master' into sassafras-overhaul
davxy Jul 24, 2024
296380d
Fix after master merge
davxy Jul 24, 2024
a485f8e
Apply suggestions from code review
davxy Jul 24, 2024
d3c3174
Rename epoch length -> duration
davxy Jul 24, 2024
712f307
Update weights
davxy Jul 24, 2024
f4ab66c
Simplify logic
davxy Jul 24, 2024
476b188
Excercise all failure paths during tickets submission
davxy Jul 24, 2024
20f57fe
Update weights
davxy Jul 24, 2024
bcfdd09
Add note about epoch duration
davxy Jul 28, 2024
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
2 changes: 2 additions & 0 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion substrate/frame/sassafras/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ frame-support = { path = "../support", default-features = false }
frame-system = { path = "../system", default-features = false }
log = { workspace = true }
sp-consensus-sassafras = { path = "../../primitives/consensus/sassafras", default-features = false, features = ["serde"] }
sp-core = { path = "../../primitives/core", default-features = false }
sp-io = { path = "../../primitives/io", default-features = false }
sp-runtime = { path = "../../primitives/runtime", default-features = false }
sp-std = { path = "../../primitives/std", default-features = false }

[dev-dependencies]
env_logger = "0.11.3"
array-bytes = "6.2.2"
sp-core = { path = "../../primitives/core" }
sp-crypto-hashing = { path = "../../primitives/crypto/hashing" }

[features]
Expand All @@ -43,6 +44,7 @@ std = [
"scale-codec/std",
"scale-info/std",
"sp-consensus-sassafras/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
Expand Down
197 changes: 47 additions & 150 deletions substrate/frame/sassafras/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,21 @@
//! Benchmarks for the Sassafras pallet.

use crate::*;
use sp_consensus_sassafras::{vrf::VrfSignature, EphemeralPublic, EpochConfiguration};
use sp_consensus_sassafras::vrf::VrfSignature;

use frame_benchmarking::v2::*;
use frame_support::traits::Hooks;
use frame_system::RawOrigin;

const LOG_TARGET: &str = "sassafras::benchmark";

const TICKETS_DATA: &[u8] = include_bytes!("data/25_tickets_100_auths.bin");
// Pre-constructed tickets generated via the `generate_test_teckets` function
const TICKETS_DATA: &[u8] = include_bytes!("data/tickets.bin");

fn make_dummy_vrf_signature() -> VrfSignature {
fn dummy_vrf_signature() -> VrfSignature {
// This leverages our knowledge about serialized vrf signature structure.
// Mostly to avoid to import all the bandersnatch primitive just for this test.
let buf = [
const RAW_VRF_SIGNATURE: [u8; 99] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
Expand All @@ -40,31 +41,24 @@ fn make_dummy_vrf_signature() -> VrfSignature {
0x18, 0xca, 0x07, 0x13, 0xc7, 0x4b, 0xa3, 0x9a, 0x97, 0xd3, 0x76, 0x8f, 0x0c, 0xbf, 0x2e,
0xd4, 0xf9, 0x3a, 0xae, 0xc1, 0x96, 0x2a, 0x64, 0x80,
];
VrfSignature::decode(&mut &buf[..]).unwrap()
VrfSignature::decode(&mut &RAW_VRF_SIGNATURE[..]).unwrap()
}

#[benchmarks]
mod benchmarks {
use super::*;

// For first block (#1) we do some extra operation.
// But is a one shot operation, so we don't account for it here.
// We use 0, as it will be the path used by all the blocks with n != 1
#[benchmark]
fn on_initialize() {
let block_num = BlockNumberFor::<T>::from(0u32);

let slot_claim = SlotClaim {
authority_idx: 0,
slot: Default::default(),
vrf_signature: make_dummy_vrf_signature(),
ticket_claim: None,
vrf_signature: dummy_vrf_signature(),
};
frame_system::Pallet::<T>::deposit_log((&slot_claim).into());

// We currently don't account for the potential weight added by the `on_finalize`
// incremental sorting of the tickets.

#[block]
{
// According to `Hooks` trait docs, `on_finalize` `Weight` should be bundled
Expand All @@ -77,78 +71,44 @@ mod benchmarks {
// Weight for the default internal epoch change trigger.
//
// Parameters:
// - `x`: number of authorities (1:100).
// - `y`: epoch length in slots (1000:5000)
// - `x`: number of authorities [1:100].
// - `y`: number of tickets [100:1000];
//
// This accounts for the worst case which includes:
// - load the full ring context.
// - recompute the ring verifier.
// - sorting the epoch tickets in one shot
// (here we account for the very unlucky scenario where we haven't done any sort work yet)
// - pending epoch change config.
//
// For this bench we assume a redundancy factor of 2 (suggested value to be used in prod).
// - recomputing the ring verifier key from a new authorites set.
// - picking all the tickets from the accumulator in one shot.
#[benchmark]
fn enact_epoch_change(x: Linear<1, 100>, y: Linear<1000, 5000>) {
fn enact_epoch_change(x: Linear<1, 100>, y: Linear<100, 1000>) {
let authorities_count = x as usize;
let epoch_length = y as u32;
let redundancy_factor = 2;
let accumulated_tickets = y as u32;

let unsorted_tickets_count = epoch_length * redundancy_factor;
let config = Pallet::<T>::protocol_config();

let mut meta = TicketsMetadata { unsorted_tickets_count, tickets_count: [0, 0] };
let config = EpochConfiguration { redundancy_factor, attempts_number: 32 };
// Makes the epoch change legit
let post_init_cache = EphemeralData {
prev_slot: Slot::from(config.epoch_length as u64 - 1),
block_randomness: Randomness::default(),
};
TemporaryData::<T>::put(post_init_cache);
CurrentSlot::<T>::set(Slot::from(config.epoch_length as u64));

// Triggers ring verifier computation for `x` authorities
let mut raw_data = TICKETS_DATA;
let (authorities, _): (Vec<AuthorityId>, Vec<TicketEnvelope>) =
Decode::decode(&mut raw_data).expect("Failed to decode tickets buffer");
let next_authorities: Vec<_> = authorities[..authorities_count].to_vec();
// Force ring verifier key re-computation
let next_authorities: Vec<_> =
Authorities::<T>::get().into_iter().cycle().take(authorities_count).collect();
let next_authorities = WeakBoundedVec::force_from(next_authorities, None);
NextAuthorities::<T>::set(next_authorities);

// Triggers JIT sorting tickets
(0..meta.unsorted_tickets_count)
.collect::<Vec<_>>()
.chunks(SEGMENT_MAX_SIZE as usize)
.enumerate()
.for_each(|(segment_id, chunk)| {
let segment = chunk
.iter()
.map(|i| {
let id_bytes = crate::hashing::blake2_128(&i.to_le_bytes());
TicketId::from_le_bytes(id_bytes)
})
.collect::<Vec<_>>();
UnsortedSegments::<T>::insert(
segment_id as u32,
BoundedVec::truncate_from(segment),
);
});

// Triggers some code related to config change (dummy values)
NextEpochConfig::<T>::set(Some(config));
PendingEpochConfigChange::<T>::set(Some(config));

// Triggers the cleanup of the "just elapsed" epoch tickets (i.e. the current one)
let epoch_tag = EpochIndex::<T>::get() & 1;
meta.tickets_count[epoch_tag as usize] = epoch_length;
(0..epoch_length).for_each(|i| {
let id_bytes = crate::hashing::blake2_128(&i.to_le_bytes());
let id = TicketId::from_le_bytes(id_bytes);
TicketsIds::<T>::insert((epoch_tag as u8, i), id);
let body = TicketBody {
attempt_idx: i,
erased_public: EphemeralPublic::from([i as u8; 32]),
revealed_public: EphemeralPublic::from([i as u8; 32]),
};
TicketsData::<T>::set(id, Some(body));
// Add tickets to the accumulator
(0..accumulated_tickets).for_each(|i| {
let mut id = TicketId([0xff; 32]);
id.0[..4].copy_from_slice(&i.to_be_bytes()[..]);
let body = TicketBody { attempt_idx: 0, extra: Default::default() };
TicketsAccumulator::<T>::insert(TicketKey::from(id), &body);
});

TicketsMeta::<T>::set(meta);

#[block]
{
// Also account for the call typically done in case of epoch change
Pallet::<T>::should_end_epoch(BlockNumberFor::<T>::from(3u32));
let next_authorities = Pallet::<T>::next_authorities();
// Using a different set of authorities triggers the recomputation of ring verifier.
Expand All @@ -157,55 +117,37 @@ mod benchmarks {
}

#[benchmark]
fn submit_tickets(x: Linear<1, 25>) {
fn submit_tickets(x: Linear<1, 16>) {
let tickets_count = x as usize;

let mut raw_data = TICKETS_DATA;
let (authorities, tickets): (Vec<AuthorityId>, Vec<TicketEnvelope>) =
Decode::decode(&mut raw_data).expect("Failed to decode tickets buffer");

log::debug!(target: LOG_TARGET, "PreBuiltTickets: {} tickets, {} authorities", tickets.len(), authorities.len());

// Set `NextRandomness` to the same value used for pre-built tickets
// (see `make_tickets_data` test).
NextRandomness::<T>::set([0; 32]);

let (randomness, authorities, tickets): (
Randomness,
Vec<AuthorityId>,
Vec<TicketEnvelope>,
) = Decode::decode(&mut raw_data).expect("Failed to decode tickets buffer");
assert!(tickets.len() >= tickets_count);

// Use the same values used for the pre-built tickets
Pallet::<T>::update_ring_verifier(&authorities);

// Set next epoch config to accept all the tickets
let next_config = EpochConfiguration { attempts_number: 1, redundancy_factor: u32::MAX };
NextEpochConfig::<T>::set(Some(next_config));

// Use the authorities in the pre-build tickets
let authorities = WeakBoundedVec::force_from(authorities, None);
NextAuthorities::<T>::set(authorities);
NextAuthorities::<T>::set(WeakBoundedVec::force_from(authorities, None));
let mut randomness_buf = RandomnessBuf::<T>::get();
randomness_buf[2] = randomness;
RandomnessBuf::<T>::set(randomness_buf);

let tickets = tickets[..tickets_count].to_vec();
let tickets = BoundedVec::truncate_from(tickets);

log::debug!(target: LOG_TARGET, "Submitting {} tickets", tickets_count);

#[extrinsic_call]
submit_tickets(RawOrigin::None, tickets);
}

#[benchmark]
fn plan_config_change() {
let config = EpochConfiguration { redundancy_factor: 1, attempts_number: 10 };

#[extrinsic_call]
plan_config_change(RawOrigin::Root, config);
}

// Construction of ring verifier
#[benchmark]
fn update_ring_verifier(x: Linear<1, 100>) {
let authorities_count = x as usize;

let mut raw_data = TICKETS_DATA;
let (authorities, _): (Vec<AuthorityId>, Vec<TicketEnvelope>) =
Decode::decode(&mut raw_data).expect("Failed to decode tickets buffer");
let authorities: Vec<_> = authorities[..authorities_count].to_vec();
let authorities: Vec<_> =
Authorities::<T>::get().into_iter().cycle().take(authorities_count).collect();

#[block]
{
Expand All @@ -221,52 +163,7 @@ mod benchmarks {
fn load_ring_context() {
#[block]
{
let _ring_ctx = RingContext::<T>::get().unwrap();
}
}

// Tickets segments sorting function benchmark.
#[benchmark]
fn sort_segments(x: Linear<1, 100>) {
let segments_count = x as u32;
let tickets_count = segments_count * SEGMENT_MAX_SIZE;

// Construct a bunch of dummy tickets
let tickets: Vec<_> = (0..tickets_count)
.map(|i| {
let body = TicketBody {
attempt_idx: i,
erased_public: EphemeralPublic::from([i as u8; 32]),
revealed_public: EphemeralPublic::from([i as u8; 32]),
};
let id_bytes = crate::hashing::blake2_128(&i.to_le_bytes());
let id = TicketId::from_le_bytes(id_bytes);
(id, body)
})
.collect();

for (chunk_id, chunk) in tickets.chunks(SEGMENT_MAX_SIZE as usize).enumerate() {
let segment: Vec<TicketId> = chunk
.iter()
.map(|(id, body)| {
TicketsData::<T>::set(id, Some(body.clone()));
*id
})
.collect();
let segment = BoundedVec::truncate_from(segment);
UnsortedSegments::<T>::insert(chunk_id as u32, segment);
}

// Update metadata
let mut meta = TicketsMeta::<T>::get();
meta.unsorted_tickets_count = tickets_count;
TicketsMeta::<T>::set(meta);

log::debug!(target: LOG_TARGET, "Before sort: {:?}", meta);
#[block]
{
Pallet::<T>::sort_segments(u32::MAX, 0, &mut meta);
let _ = RingContext::<T>::get().unwrap();
}
log::debug!(target: LOG_TARGET, "After sort: {:?}", meta);
}
}
Binary file not shown.
Loading
Loading