Skip to content

Commit 367a79b

Browse files
davxyskunertkoutemichalkucharczykandresilva
authored
Sassafras Consensus Pallet (paritytech#1577)
This PR introduces the pallet for Sassafras consensus. ## Non Goals The pallet delivers only the bare-bones and doesn't deliver support for auxiliary functionalities such as equivocation report and support for epoch change via session pallet. These functionalities were drafted in the [main PR](paritytech#1336), but IMO is better to introduce this auxiliary stuff in a follow up PR and after client code. ## Potential follow ups paritytech#2364 --------- Co-authored-by: Sebastian Kunert <[email protected]> Co-authored-by: Koute <[email protected]> Co-authored-by: Michal Kucharczyk <[email protected]> Co-authored-by: André Silva <[email protected]> Co-authored-by: Bastian Köcher <[email protected]>
1 parent 28a47ef commit 367a79b

File tree

19 files changed

+3723
-116
lines changed

19 files changed

+3723
-116
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[package]
2+
name = "pallet-sassafras"
3+
version = "0.3.5-dev"
4+
authors = ["Parity Technologies <[email protected]>"]
5+
edition = "2021"
6+
license = "Apache-2.0"
7+
homepage = "https://substrate.io"
8+
repository = "https://github.com/paritytech/substrate/"
9+
description = "Consensus extension module for Sassafras consensus."
10+
readme = "README.md"
11+
publish = false
12+
13+
[package.metadata.docs.rs]
14+
targets = ["x86_64-unknown-linux-gnu"]
15+
16+
[dependencies]
17+
scale-codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] }
18+
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
19+
frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true }
20+
frame-support = { path = "../support", default-features = false }
21+
frame-system = { path = "../system", default-features = false }
22+
log = { version = "0.4.17", default-features = false }
23+
sp-consensus-sassafras = { path = "../../primitives/consensus/sassafras", default-features = false, features = ["serde"] }
24+
sp-io = { path = "../../primitives/io", default-features = false }
25+
sp-runtime = { path = "../../primitives/runtime", default-features = false }
26+
sp-std = { path = "../../primitives/std", default-features = false }
27+
28+
[dev-dependencies]
29+
array-bytes = "6.1"
30+
sp-core = { path = "../../primitives/core" }
31+
32+
[features]
33+
default = [ "std" ]
34+
std = [
35+
"frame-benchmarking?/std",
36+
"frame-support/std",
37+
"frame-system/std",
38+
"log/std",
39+
"scale-codec/std",
40+
"scale-info/std",
41+
"sp-consensus-sassafras/std",
42+
"sp-io/std",
43+
"sp-runtime/std",
44+
"sp-std/std",
45+
]
46+
runtime-benchmarks = [
47+
"frame-benchmarking/runtime-benchmarks",
48+
"frame-support/runtime-benchmarks",
49+
"frame-system/runtime-benchmarks",
50+
"sp-runtime/runtime-benchmarks",
51+
]
52+
try-runtime = [
53+
"frame-support/try-runtime",
54+
"frame-system/try-runtime",
55+
"sp-runtime/try-runtime",
56+
]
57+
# Construct dummy ring context on genesis.
58+
# Mostly used for testing and development.
59+
construct-dummy-ring-context = []
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Runtime module for SASSAFRAS consensus.
2+
3+
- Tracking issue: https://github.com/paritytech/polkadot-sdk/issues/41
4+
- Protocol RFC proposal: https://github.com/polkadot-fellows/RFCs/pull/26
5+
6+
# ⚠️ WARNING ⚠️
7+
8+
The crate interfaces and structures are experimental and may be subject to changes.
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
//! Benchmarks for the Sassafras pallet.
19+
20+
use crate::*;
21+
use sp_consensus_sassafras::{vrf::VrfSignature, EphemeralPublic, EpochConfiguration};
22+
23+
use frame_benchmarking::v2::*;
24+
use frame_support::traits::Hooks;
25+
use frame_system::RawOrigin;
26+
27+
const LOG_TARGET: &str = "sassafras::benchmark";
28+
29+
const TICKETS_DATA: &[u8] = include_bytes!("data/25_tickets_100_auths.bin");
30+
31+
fn make_dummy_vrf_signature() -> VrfSignature {
32+
// This leverages our knowledge about serialized vrf signature structure.
33+
// Mostly to avoid to import all the bandersnatch primitive just for this test.
34+
let buf = [
35+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
36+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
37+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
38+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
39+
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xb5, 0x5f, 0x8e, 0xc7, 0x68, 0xf5, 0x05, 0x3f, 0xa9,
40+
0x18, 0xca, 0x07, 0x13, 0xc7, 0x4b, 0xa3, 0x9a, 0x97, 0xd3, 0x76, 0x8f, 0x0c, 0xbf, 0x2e,
41+
0xd4, 0xf9, 0x3a, 0xae, 0xc1, 0x96, 0x2a, 0x64, 0x80,
42+
];
43+
VrfSignature::decode(&mut &buf[..]).unwrap()
44+
}
45+
46+
#[benchmarks]
47+
mod benchmarks {
48+
use super::*;
49+
50+
// For first block (#1) we do some extra operation.
51+
// But is a one shot operation, so we don't account for it here.
52+
// We use 0, as it will be the path used by all the blocks with n != 1
53+
#[benchmark]
54+
fn on_initialize() {
55+
let block_num = BlockNumberFor::<T>::from(0u32);
56+
57+
let slot_claim = SlotClaim {
58+
authority_idx: 0,
59+
slot: Default::default(),
60+
vrf_signature: make_dummy_vrf_signature(),
61+
ticket_claim: None,
62+
};
63+
frame_system::Pallet::<T>::deposit_log((&slot_claim).into());
64+
65+
// We currently don't account for the potential weight added by the `on_finalize`
66+
// incremental sorting of the tickets.
67+
68+
#[block]
69+
{
70+
// According to `Hooks` trait docs, `on_finalize` `Weight` should be bundled
71+
// together with `on_initialize` `Weight`.
72+
Pallet::<T>::on_initialize(block_num);
73+
Pallet::<T>::on_finalize(block_num)
74+
}
75+
}
76+
77+
// Weight for the default internal epoch change trigger.
78+
//
79+
// Parameters:
80+
// - `x`: number of authorities (1:100).
81+
// - `y`: epoch length in slots (1000:5000)
82+
//
83+
// This accounts for the worst case which includes:
84+
// - load the full ring context.
85+
// - recompute the ring verifier.
86+
// - sorting the epoch tickets in one shot
87+
// (here we account for the very unluky scenario where we haven't done any sort work yet)
88+
// - pending epoch change config.
89+
//
90+
// For this bench we assume a redundancy factor of 2 (suggested value to be used in prod).
91+
#[benchmark]
92+
fn enact_epoch_change(x: Linear<1, 100>, y: Linear<1000, 5000>) {
93+
let authorities_count = x as usize;
94+
let epoch_length = y as u32;
95+
let redundancy_factor = 2;
96+
97+
let unsorted_tickets_count = epoch_length * redundancy_factor;
98+
99+
let mut meta = TicketsMetadata { unsorted_tickets_count, tickets_count: [0, 0] };
100+
let config = EpochConfiguration { redundancy_factor, attempts_number: 32 };
101+
102+
// Triggers ring verifier computation for `x` authorities
103+
let mut raw_data = TICKETS_DATA;
104+
let (authorities, _): (Vec<AuthorityId>, Vec<TicketEnvelope>) =
105+
Decode::decode(&mut raw_data).expect("Failed to decode tickets buffer");
106+
let next_authorities: Vec<_> = authorities[..authorities_count].to_vec();
107+
let next_authorities = WeakBoundedVec::force_from(next_authorities, None);
108+
NextAuthorities::<T>::set(next_authorities);
109+
110+
// Triggers JIT sorting tickets
111+
(0..meta.unsorted_tickets_count)
112+
.collect::<Vec<_>>()
113+
.chunks(SEGMENT_MAX_SIZE as usize)
114+
.enumerate()
115+
.for_each(|(segment_id, chunk)| {
116+
let segment = chunk
117+
.iter()
118+
.map(|i| {
119+
let id_bytes = crate::hashing::blake2_128(&i.to_le_bytes());
120+
TicketId::from_le_bytes(id_bytes)
121+
})
122+
.collect::<Vec<_>>();
123+
UnsortedSegments::<T>::insert(
124+
segment_id as u32,
125+
BoundedVec::truncate_from(segment),
126+
);
127+
});
128+
129+
// Triggers some code related to config change (dummy values)
130+
NextEpochConfig::<T>::set(Some(config));
131+
PendingEpochConfigChange::<T>::set(Some(config));
132+
133+
// Triggers the cleanup of the "just elapsed" epoch tickets (i.e. the current one)
134+
let epoch_tag = EpochIndex::<T>::get() & 1;
135+
meta.tickets_count[epoch_tag as usize] = epoch_length;
136+
(0..epoch_length).for_each(|i| {
137+
let id_bytes = crate::hashing::blake2_128(&i.to_le_bytes());
138+
let id = TicketId::from_le_bytes(id_bytes);
139+
TicketsIds::<T>::insert((epoch_tag as u8, i), id);
140+
let body = TicketBody {
141+
attempt_idx: i,
142+
erased_public: EphemeralPublic([i as u8; 32]),
143+
revealed_public: EphemeralPublic([i as u8; 32]),
144+
};
145+
TicketsData::<T>::set(id, Some(body));
146+
});
147+
148+
TicketsMeta::<T>::set(meta);
149+
150+
#[block]
151+
{
152+
Pallet::<T>::should_end_epoch(BlockNumberFor::<T>::from(3u32));
153+
let next_authorities = Pallet::<T>::next_authorities();
154+
// Using a different set of authorities triggers the recomputation of ring verifier.
155+
Pallet::<T>::enact_epoch_change(Default::default(), next_authorities);
156+
}
157+
}
158+
159+
#[benchmark]
160+
fn submit_tickets(x: Linear<1, 25>) {
161+
let tickets_count = x as usize;
162+
163+
let mut raw_data = TICKETS_DATA;
164+
let (authorities, tickets): (Vec<AuthorityId>, Vec<TicketEnvelope>) =
165+
Decode::decode(&mut raw_data).expect("Failed to decode tickets buffer");
166+
167+
log::debug!(target: LOG_TARGET, "PreBuiltTickets: {} tickets, {} authorities", tickets.len(), authorities.len());
168+
169+
// Set `NextRandomness` to the same value used for pre-built tickets
170+
// (see `make_tickets_data` test).
171+
NextRandomness::<T>::set([0; 32]);
172+
173+
Pallet::<T>::update_ring_verifier(&authorities);
174+
175+
// Set next epoch config to accept all the tickets
176+
let next_config = EpochConfiguration { attempts_number: 1, redundancy_factor: u32::MAX };
177+
NextEpochConfig::<T>::set(Some(next_config));
178+
179+
// Use the authorities in the pre-build tickets
180+
let authorities = WeakBoundedVec::force_from(authorities, None);
181+
NextAuthorities::<T>::set(authorities);
182+
183+
let tickets = tickets[..tickets_count].to_vec();
184+
let tickets = BoundedVec::truncate_from(tickets);
185+
186+
log::debug!(target: LOG_TARGET, "Submitting {} tickets", tickets_count);
187+
188+
#[extrinsic_call]
189+
submit_tickets(RawOrigin::None, tickets);
190+
}
191+
192+
#[benchmark]
193+
fn plan_config_change() {
194+
let config = EpochConfiguration { redundancy_factor: 1, attempts_number: 10 };
195+
196+
#[extrinsic_call]
197+
plan_config_change(RawOrigin::Root, config);
198+
}
199+
200+
// Construction of ring verifier
201+
#[benchmark]
202+
fn update_ring_verifier(x: Linear<1, 100>) {
203+
let authorities_count = x as usize;
204+
205+
let mut raw_data = TICKETS_DATA;
206+
let (authorities, _): (Vec<AuthorityId>, Vec<TicketEnvelope>) =
207+
Decode::decode(&mut raw_data).expect("Failed to decode tickets buffer");
208+
let authorities: Vec<_> = authorities[..authorities_count].to_vec();
209+
210+
#[block]
211+
{
212+
Pallet::<T>::update_ring_verifier(&authorities);
213+
}
214+
}
215+
216+
// Bare loading of ring context.
217+
//
218+
// It is interesting to see how this compares to 'update_ring_verifier', which
219+
// also recomputes and stores the new verifier.
220+
#[benchmark]
221+
fn load_ring_context() {
222+
#[block]
223+
{
224+
let _ring_ctx = RingContext::<T>::get().unwrap();
225+
}
226+
}
227+
228+
// Tickets segments sorting function benchmark.
229+
#[benchmark]
230+
fn sort_segments(x: Linear<1, 100>) {
231+
let segments_count = x as u32;
232+
let tickets_count = segments_count * SEGMENT_MAX_SIZE;
233+
234+
// Construct a bunch of dummy tickets
235+
let tickets: Vec<_> = (0..tickets_count)
236+
.map(|i| {
237+
let body = TicketBody {
238+
attempt_idx: i,
239+
erased_public: EphemeralPublic([i as u8; 32]),
240+
revealed_public: EphemeralPublic([i as u8; 32]),
241+
};
242+
let id_bytes = crate::hashing::blake2_128(&i.to_le_bytes());
243+
let id = TicketId::from_le_bytes(id_bytes);
244+
(id, body)
245+
})
246+
.collect();
247+
248+
for (chunk_id, chunk) in tickets.chunks(SEGMENT_MAX_SIZE as usize).enumerate() {
249+
let segment: Vec<TicketId> = chunk
250+
.iter()
251+
.map(|(id, body)| {
252+
TicketsData::<T>::set(id, Some(body.clone()));
253+
*id
254+
})
255+
.collect();
256+
let segment = BoundedVec::truncate_from(segment);
257+
UnsortedSegments::<T>::insert(chunk_id as u32, segment);
258+
}
259+
260+
// Update metadata
261+
let mut meta = TicketsMeta::<T>::get();
262+
meta.unsorted_tickets_count = tickets_count;
263+
TicketsMeta::<T>::set(meta.clone());
264+
265+
log::debug!(target: LOG_TARGET, "Before sort: {:?}", meta);
266+
#[block]
267+
{
268+
Pallet::<T>::sort_segments(u32::MAX, 0, &mut meta);
269+
}
270+
log::debug!(target: LOG_TARGET, "After sort: {:?}", meta);
271+
}
272+
}
24.1 KB
Binary file not shown.

0 commit comments

Comments
 (0)