moonbeam_service/lazy_loading/
state_overrides.rs

1// Copyright 2024 Moonbeam foundation
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::chain_spec::generate_accounts;
18use moonbeam_core_primitives::{AccountId, Balance};
19use pallet_parachain_staking::{Bond, CandidateMetadata, CollatorSnapshot, Delegations};
20use parity_scale_codec::Encode;
21use serde::Deserialize;
22use sp_core::{blake2_128, twox_64};
23use std::io::Read;
24use std::path::PathBuf;
25
26#[derive(Deserialize, Debug, Clone)]
27pub struct StateEntryConcrete {
28	pub(crate) pallet: String,
29	pub(crate) storage: String,
30	#[serde(
31		skip_serializing_if = "Option::is_none",
32		deserialize_with = "serde_hex::deserialize_as_option",
33		default
34	)]
35	pub(crate) key: Option<Vec<u8>>,
36	#[serde(deserialize_with = "serde_hex::deserialize")]
37	pub(crate) value: Vec<u8>,
38}
39
40#[derive(Deserialize, Debug, Clone)]
41pub struct StateEntryRaw {
42	#[serde(deserialize_with = "serde_hex::deserialize")]
43	pub(crate) key: Vec<u8>,
44	#[serde(deserialize_with = "serde_hex::deserialize")]
45	pub(crate) value: Vec<u8>,
46}
47
48#[derive(Deserialize, Debug, Clone)]
49#[serde(untagged)]
50pub enum StateEntry {
51	Concrete(StateEntryConcrete),
52	Raw(StateEntryRaw),
53}
54
55/// Mandatory state overrides that most exist when starting a node in lazy loading mode.
56pub fn base_state_overrides(runtime_code: Option<PathBuf>) -> Vec<StateEntry> {
57	use hex_literal::hex;
58	let alith_address = hex!("f24ff3a9cf04c71dbc94d0b566f7a27b94566cac");
59	let alith_pub = hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d");
60	let alith_staking_bond: Balance = 1_000_000_000_000_000_000;
61	let mut overrides = vec![
62		// Override `PendingValidationCode` since it conflicts
63		// with lazy loading
64		StateEntry::Concrete(StateEntryConcrete {
65			pallet: "ParachainSystem".to_string(),
66			storage: "PendingValidationCode".to_string(),
67			key: None,
68			value: Vec::new(),
69		}),
70		// Reset LastRelayChainBlockNumber
71		StateEntry::Concrete(StateEntryConcrete {
72			pallet: "ParachainSystem".to_string(),
73			storage: "LastRelayChainBlockNumber".to_string(),
74			key: None,
75			value: 0u32.encode(),
76		}),
77		StateEntry::Concrete(StateEntryConcrete {
78			pallet: "AuthorMapping".to_string(),
79			storage: "NimbusLookup".to_string(),
80			key: Some(
81				[
82					&blake2_128(alith_address.as_slice()),
83					alith_address.as_slice(),
84				]
85				.concat(),
86			),
87			value: alith_pub.to_vec(),
88		}),
89		StateEntry::Concrete(StateEntryConcrete {
90			pallet: "AuthorMapping".to_string(),
91			storage: "MappingWithDeposit".to_string(),
92			key: Some([&blake2_128(alith_pub.as_slice()), alith_pub.as_slice()].concat()),
93			value: (alith_address, alith_staking_bond, alith_pub).encode(),
94		}),
95		// We only use one collator in lazy-loading
96		StateEntry::Concrete(StateEntryConcrete {
97			pallet: "ParachainStaking".to_string(),
98			storage: "TotalSelected".to_string(),
99			key: None,
100			value: 1u32.encode(),
101		}),
102		// Set candidate pool
103		StateEntry::Concrete(StateEntryConcrete {
104			pallet: "ParachainStaking".to_string(),
105			storage: "CandidateInfo".to_string(),
106			key: Some(
107				[&twox_64(alith_address.as_slice()), alith_address.as_slice()]
108					.concat()
109					.to_vec(),
110			),
111			value: CandidateMetadata::new(alith_staking_bond).encode(),
112		}),
113		StateEntry::Concrete(StateEntryConcrete {
114			pallet: "ParachainStaking".to_string(),
115			storage: "TopDelegations".to_string(),
116			key: Some(
117				[&twox_64(alith_address.as_slice()), alith_address.as_slice()]
118					.concat()
119					.to_vec(),
120			),
121			value: Delegations::<AccountId, Balance> {
122				delegations: Default::default(),
123				total: Default::default(),
124			}
125			.encode(),
126		}),
127		StateEntry::Concrete(StateEntryConcrete {
128			pallet: "ParachainStaking".to_string(),
129			storage: "CandidatePool".to_string(),
130			key: None,
131			value: {
132				let bond = Bond::<AccountId, Balance> {
133					owner: AccountId::from(alith_address),
134					amount: alith_staking_bond,
135				};
136
137				vec![bond].encode()
138			},
139		}),
140		// Set Alith as selected candidate
141		StateEntry::Concrete(StateEntryConcrete {
142			pallet: "ParachainStaking".to_string(),
143			storage: "SelectedCandidates".to_string(),
144			key: None,
145			value: vec![alith_address].encode(),
146		}),
147		StateEntry::Concrete(StateEntryConcrete {
148			pallet: "ParachainStaking".to_string(),
149			storage: "AtStake".to_string(),
150			key: {
151				let round: u32 = 1;
152				Some(
153					[
154						&twox_64(&round.encode()),
155						round.encode().as_slice(),
156						&twox_64(alith_address.as_slice()),
157						alith_address.as_slice(),
158					]
159					.concat()
160					.to_vec(),
161				)
162			},
163			value: {
164				CollatorSnapshot::<AccountId, Balance> {
165					bond: alith_staking_bond.clone(),
166					delegations: Default::default(),
167					total: alith_staking_bond,
168				}
169				.encode()
170			},
171		}),
172		// Reset SlotInfo
173		StateEntry::Concrete(StateEntryConcrete {
174			pallet: "AsyncBacking".to_string(),
175			storage: "SlotInfo".to_string(),
176			key: None,
177			value: (1u64, 1u32).encode(),
178		}),
179	];
180
181	// Default mnemonic if none was provided
182	let test_mnemonic =
183		"bottom drive obey lake curtain smoke basket hold race lonely fit walk".to_string();
184	// Prefund the standard dev accounts
185	for address in generate_accounts(test_mnemonic, 6) {
186		overrides.push(StateEntry::Concrete(StateEntryConcrete {
187			pallet: "System".to_string(),
188			storage: "Account".to_string(),
189			key: Some(
190				[blake2_128(&address.0).as_slice(), address.0.as_slice()]
191					.concat()
192					.to_vec(),
193			),
194			value: frame_system::AccountInfo {
195				nonce: 0u32,
196				consumers: 0,
197				providers: 1,
198				sufficients: 0,
199				data: pallet_balances::AccountData::<Balance> {
200					free: Balance::MAX,
201					reserved: Default::default(),
202					frozen: Default::default(),
203					flags: Default::default(),
204				},
205			}
206			.encode(),
207		}))
208	}
209
210	if let Some(path) = runtime_code {
211		let mut reader = std::fs::File::open(path.clone())
212			.expect(format!("Could not open file {:?}", path).as_str());
213		let mut data = vec![];
214		reader
215			.read_to_end(&mut data)
216			.expect("Runtime code override invalid.");
217
218		overrides.push(StateEntry::Raw(StateEntryRaw {
219			key: sp_core::storage::well_known_keys::CODE.to_vec(),
220			value: data.to_vec(),
221		}));
222	}
223
224	overrides
225}
226
227pub fn read(path: PathBuf) -> Result<Vec<StateEntry>, String> {
228	let reader = std::fs::File::open(path).expect("Can open file");
229	let state = serde_json::from_reader(reader).expect("Can parse state overrides JSON");
230
231	Ok(state)
232}
233
234mod serde_hex {
235	use hex::FromHex;
236	use serde::{Deserialize, Deserializer};
237
238	fn sanitize(data: &str) -> &str {
239		if let Some(stripped_data) = data.strip_prefix("0x") {
240			stripped_data
241		} else {
242			data
243		}
244	}
245
246	pub fn deserialize_as_option<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
247	where
248		D: Deserializer<'de>,
249		T: FromHex,
250		<T as FromHex>::Error: std::fmt::Display + std::fmt::Debug,
251	{
252		Option::<String>::deserialize(deserializer).map(|value| {
253			value.map(|data| FromHex::from_hex(sanitize(data.as_str())).expect("Invalid option"))
254		})
255	}
256
257	pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
258	where
259		D: Deserializer<'de>,
260		T: FromHex,
261		<T as FromHex>::Error: std::fmt::Display + std::fmt::Debug,
262	{
263		String::deserialize(deserializer).map(|data| {
264			FromHex::from_hex(sanitize(data.as_str())).expect("Invalid hex encoded string")
265		})
266	}
267}