referrerpolicy=no-referrer-when-downgrade

pallet_sassafras/
benchmarking.rs

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
20use crate::*;
21use sp_consensus_sassafras::{vrf::VrfSignature, EphemeralPublic, EpochConfiguration};
22
23use frame_benchmarking::v2::*;
24use frame_support::traits::Hooks;
25use frame_system::RawOrigin;
26
27const LOG_TARGET: &str = "sassafras::benchmark";
28
29const TICKETS_DATA: &[u8] = include_bytes!("data/25_tickets_100_auths.bin");
30
31fn 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]
47mod 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 unlucky 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::from([i as u8; 32]),
143				revealed_public: EphemeralPublic::from([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::from([i as u8; 32]),
240					revealed_public: EphemeralPublic::from([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);
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}