referrerpolicy=no-referrer-when-downgrade

sc_consensus_babe/
aux_schema.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Schema for BABE epoch changes in the aux-db.
20
21use codec::{Decode, Encode};
22use log::info;
23
24use crate::{migration::EpochV0, Epoch, LOG_TARGET};
25use sc_client_api::backend::AuxStore;
26use sc_consensus_epochs::{
27	migration::{EpochChangesV0For, EpochChangesV1For},
28	EpochChangesFor, SharedEpochChanges,
29};
30use sp_blockchain::{Error as ClientError, Result as ClientResult};
31use sp_consensus_babe::{BabeBlockWeight, BabeConfiguration};
32use sp_runtime::traits::Block as BlockT;
33
34const BABE_EPOCH_CHANGES_VERSION: &[u8] = b"babe_epoch_changes_version";
35const BABE_EPOCH_CHANGES_KEY: &[u8] = b"babe_epoch_changes";
36const BABE_EPOCH_CHANGES_CURRENT_VERSION: u32 = 3;
37
38/// The aux storage key used to store the block weight of the given block hash.
39pub fn block_weight_key<H: Encode>(block_hash: H) -> Vec<u8> {
40	(b"block_weight", block_hash).encode()
41}
42
43fn load_decode<B, T>(backend: &B, key: &[u8]) -> ClientResult<Option<T>>
44where
45	B: AuxStore,
46	T: Decode,
47{
48	let corrupt = |e: codec::Error| {
49		ClientError::Backend(format!("BABE DB is corrupted. Decode error: {}", e))
50	};
51	match backend.get_aux(key)? {
52		None => Ok(None),
53		Some(t) => T::decode(&mut &t[..]).map(Some).map_err(corrupt),
54	}
55}
56
57/// Load or initialize persistent epoch change data from backend.
58pub fn load_epoch_changes<Block: BlockT, B: AuxStore>(
59	backend: &B,
60	config: &BabeConfiguration,
61) -> ClientResult<SharedEpochChanges<Block, Epoch>> {
62	let version = load_decode::<_, u32>(backend, BABE_EPOCH_CHANGES_VERSION)?;
63
64	let maybe_epoch_changes = match version {
65		None => {
66			load_decode::<_, EpochChangesV0For<Block, EpochV0>>(backend, BABE_EPOCH_CHANGES_KEY)?
67				.map(|v0| v0.migrate().map(|_, _, epoch| epoch.migrate(config)))
68		},
69		Some(1) => {
70			load_decode::<_, EpochChangesV1For<Block, EpochV0>>(backend, BABE_EPOCH_CHANGES_KEY)?
71				.map(|v1| v1.migrate().map(|_, _, epoch| epoch.migrate(config)))
72		},
73		Some(2) => {
74			// v2 still uses `EpochChanges` v1 format but with a different `Epoch` type.
75			load_decode::<_, EpochChangesV1For<Block, Epoch>>(backend, BABE_EPOCH_CHANGES_KEY)?
76				.map(|v2| v2.migrate())
77		},
78		Some(BABE_EPOCH_CHANGES_CURRENT_VERSION) => {
79			load_decode::<_, EpochChangesFor<Block, Epoch>>(backend, BABE_EPOCH_CHANGES_KEY)?
80		},
81		Some(other) => {
82			return Err(ClientError::Backend(format!("Unsupported BABE DB version: {:?}", other)))
83		},
84	};
85
86	let epoch_changes =
87		SharedEpochChanges::<Block, Epoch>::new(maybe_epoch_changes.unwrap_or_else(|| {
88			info!(
89				target: LOG_TARGET,
90				"👶 Creating empty BABE epoch changes on what appears to be first startup.",
91			);
92			EpochChangesFor::<Block, Epoch>::default()
93		}));
94
95	// rebalance the tree after deserialization. this isn't strictly necessary
96	// since the tree is now rebalanced on every update operation. but since the
97	// tree wasn't rebalanced initially it's useful to temporarily leave it here
98	// to avoid having to wait until an import for rebalancing.
99	epoch_changes.shared_data().rebalance();
100
101	Ok(epoch_changes)
102}
103
104/// Update the epoch changes on disk after a change.
105pub(crate) fn write_epoch_changes<Block: BlockT, F, R>(
106	epoch_changes: &EpochChangesFor<Block, Epoch>,
107	write_aux: F,
108) -> R
109where
110	F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
111{
112	BABE_EPOCH_CHANGES_CURRENT_VERSION.using_encoded(|version| {
113		let encoded_epoch_changes = epoch_changes.encode();
114		write_aux(&[
115			(BABE_EPOCH_CHANGES_KEY, encoded_epoch_changes.as_slice()),
116			(BABE_EPOCH_CHANGES_VERSION, version),
117		])
118	})
119}
120
121/// Write the cumulative chain-weight of a block ot aux storage.
122pub(crate) fn write_block_weight<H: Encode, F, R>(
123	block_hash: H,
124	block_weight: BabeBlockWeight,
125	write_aux: F,
126) -> R
127where
128	F: FnOnce(&[(Vec<u8>, &[u8])]) -> R,
129{
130	let key = block_weight_key(block_hash);
131	block_weight.using_encoded(|s| write_aux(&[(key, s)]))
132}
133
134/// Load the cumulative chain-weight associated with a block.
135pub fn load_block_weight<H: Encode, B: AuxStore>(
136	backend: &B,
137	block_hash: H,
138) -> ClientResult<Option<BabeBlockWeight>> {
139	load_decode(backend, block_weight_key(block_hash).as_slice())
140}
141
142#[cfg(test)]
143mod test {
144	use super::*;
145	use crate::migration::EpochV0;
146	use fork_tree::ForkTree;
147	use sc_consensus_epochs::{EpochHeader, PersistedEpoch, PersistedEpochHeader};
148	use sc_network_test::Block as TestBlock;
149	use sp_consensus::Error as ConsensusError;
150	use sp_consensus_babe::AllowedSlots;
151	use sp_core::H256;
152	use sp_runtime::traits::NumberFor;
153	use substrate_test_runtime_client;
154
155	#[test]
156	fn load_decode_from_v0_epoch_changes() {
157		let epoch = EpochV0 {
158			start_slot: 0.into(),
159			authorities: vec![],
160			randomness: [0; 32],
161			epoch_index: 1,
162			duration: 100,
163		};
164		let client = substrate_test_runtime_client::new();
165		let mut v0_tree = ForkTree::<H256, NumberFor<TestBlock>, _>::new();
166		v0_tree
167			.import::<_, ConsensusError>(
168				Default::default(),
169				Default::default(),
170				PersistedEpoch::Regular(epoch),
171				&|_, _| Ok(false), // Test is single item only so this can be set to false.
172			)
173			.unwrap();
174
175		client
176			.insert_aux(
177				&[(
178					BABE_EPOCH_CHANGES_KEY,
179					&EpochChangesV0For::<TestBlock, EpochV0>::from_raw(v0_tree).encode()[..],
180				)],
181				&[],
182			)
183			.unwrap();
184
185		assert_eq!(load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(), None);
186
187		let epoch_changes = load_epoch_changes::<TestBlock, _>(
188			&client,
189			&BabeConfiguration {
190				slot_duration: 10,
191				epoch_length: 4,
192				c: (3, 10),
193				authorities: Vec::new(),
194				randomness: Default::default(),
195				allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots,
196			},
197		)
198		.unwrap();
199
200		assert!(
201			epoch_changes
202				.shared_data()
203				.tree()
204				.iter()
205				.map(|(_, _, epoch)| epoch.clone())
206				.collect::<Vec<_>>() ==
207				vec![PersistedEpochHeader::Regular(EpochHeader {
208					start_slot: 0.into(),
209					end_slot: 100.into(),
210				})],
211		); // PersistedEpochHeader does not implement Debug, so we use assert! directly.
212
213		write_epoch_changes::<TestBlock, _, _>(&epoch_changes.shared_data(), |values| {
214			client.insert_aux(values, &[]).unwrap();
215		});
216
217		assert_eq!(load_decode::<_, u32>(&client, BABE_EPOCH_CHANGES_VERSION).unwrap(), Some(3));
218	}
219}