referrerpolicy=no-referrer-when-downgrade

cumulus_pov_validator/
main.rs

1// This file is part of Cumulus.
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
19use clap::Parser;
20use codec::{Decode, Encode};
21use polkadot_node_primitives::{BlockData, PoV, POV_BOMB_LIMIT};
22use polkadot_parachain_primitives::primitives::ValidationParams;
23use polkadot_primitives::PersistedValidationData;
24use sc_executor::WasmExecutor;
25use sp_core::traits::{CallContext, CodeExecutor, RuntimeCode, WrappedRuntimeCode};
26use std::{fs, path::PathBuf, time::Instant};
27use tracing::level_filters::LevelFilter;
28
29// This is now determined by the chain, call `validation_code_bomb_limit` API.
30// max_code_size * 10 = 30MB currently. Update constant if needed.
31const VALIDATION_CODE_BOMB_LIMIT: usize = 30 * 1024 * 1024;
32
33/// Tool for validating a `PoV` locally.
34#[derive(Parser)]
35struct Cli {
36	/// The path to the validation code that should be used to validate the `PoV`.
37	///
38	/// The validation code can either be downloaded from the relay chain that the parachain is
39	/// connected to or by building the runtime manually to obtain the WASM binary.
40	#[arg(long)]
41	validation_code: PathBuf,
42
43	/// The path to the `PoV` to validate.
44	///
45	/// The `PoV`'s can be obtained by running `polkadot-parachains --collator --chain YOUR_CHAIN
46	/// --export-pov-to-path PATH_TO_EXPORT` and then choose one of the exported `PoV`'s.
47	#[arg(long)]
48	pov: PathBuf,
49}
50
51fn main() -> anyhow::Result<()> {
52	let _ = tracing_subscriber::fmt()
53		.with_env_filter(
54			tracing_subscriber::EnvFilter::from_default_env()
55				.add_directive(LevelFilter::INFO.into()),
56		)
57		.with_writer(std::io::stderr)
58		.try_init();
59
60	let cli = Cli::parse();
61
62	let validation_code = fs::read(&cli.validation_code).map_err(|error| {
63		tracing::error!(%error, path = %cli.validation_code.display(), "Failed to read validation code");
64		anyhow::anyhow!("Failed to read validation code")
65	})?;
66
67	let validation_code =
68		sp_maybe_compressed_blob::decompress(&validation_code, VALIDATION_CODE_BOMB_LIMIT)
69			.map_err(|error| {
70				tracing::error!(%error, "Failed to decompress validation code");
71				anyhow::anyhow!("Failed to decompress validation code")
72			})?;
73
74	let pov_file = fs::read(&cli.pov).map_err(|error| {
75		tracing::error!(%error, path = %cli.pov.display(), "Failed to read PoV");
76		anyhow::anyhow!("Failed to read PoV")
77	})?;
78
79	let executor = WasmExecutor::<sp_io::SubstrateHostFunctions>::builder()
80		.with_allow_missing_host_functions(true)
81		.build();
82
83	let runtime_code = RuntimeCode {
84		code_fetcher: &WrappedRuntimeCode(validation_code.into()),
85		heap_pages: None,
86		// The hash is used for caching, which we need here, but we only use one wasm file. So, the
87		// actual hash is not that important.
88		hash: vec![1, 2, 3],
89	};
90
91	// We are calling `Core_version` to get the wasm file compiled. We don't care about the result.
92	let _ = executor
93		.call(
94			&mut sp_io::TestExternalities::default().ext(),
95			&runtime_code,
96			"Core_version",
97			&[],
98			CallContext::Offchain,
99		)
100		.0;
101
102	let pov_file_ptr = &mut &pov_file[..];
103	let pov = PoV::decode(pov_file_ptr).map_err(|error| {
104		tracing::error!(%error, "Failed to decode `PoV`");
105		anyhow::anyhow!("Failed to decode `PoV`")
106	})?;
107
108	let pvd = PersistedValidationData::decode(pov_file_ptr).map_err(|error| {
109		tracing::error!(%error, "Failed to `PersistedValidationData`");
110		anyhow::anyhow!("Failed to decode `PersistedValidationData`")
111	})?;
112
113	let pov = sp_maybe_compressed_blob::decompress(&pov.block_data.0, POV_BOMB_LIMIT).map_err(
114		|error| {
115			tracing::error!(%error, "Failed to decompress `PoV`");
116			anyhow::anyhow!("Failed to decompress `PoV`")
117		},
118	)?;
119
120	let validation_params = ValidationParams {
121		relay_parent_number: pvd.relay_parent_number,
122		relay_parent_storage_root: pvd.relay_parent_storage_root,
123		parent_head: pvd.parent_head,
124		block_data: BlockData(pov.into()),
125	};
126
127	tracing::info!("Starting validation");
128
129	let start = Instant::now();
130
131	let res = executor
132		.call(
133			&mut sp_io::TestExternalities::default().ext(),
134			&runtime_code,
135			"validate_block",
136			&validation_params.encode(),
137			CallContext::Offchain,
138		)
139		.0;
140
141	let duration = start.elapsed();
142
143	match res {
144		Ok(_) => tracing::info!("Validation was successful"),
145		Err(error) => tracing::error!(%error, "Validation failed"),
146	}
147
148	tracing::info!("Validation took {}ms", duration.as_millis());
149
150	Ok(())
151}