lib.rs 12.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

#![deny(unused_extern_crates, missing_docs)]

//! Utilities for End to end runtime tests

Shawn Tabrizi's avatar
Shawn Tabrizi committed
21
22
use codec::Encode;
use democracy::{AccountVote, Conviction, Vote};
23
use grandpa::GrandpaBlockImport;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
24
25
26
use polkadot_runtime::{
	CouncilCollective, Event, FastTrackVotingPeriod, Runtime, RuntimeApi, TechnicalCollective,
};
27
use polkadot_runtime_common::claims;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
28
use sc_consensus_babe::BabeBlockImport;
29
use sc_consensus_manual_seal::consensus::babe::SlotTimestampProvider;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
30
31
32
33
34
35
36
37
use sc_service::{TFullBackend, TFullClient};
use sp_runtime::{app_crypto::sp_core::H256, generic::Era, AccountId32};
use std::{error::Error, future::Future, str::FromStr};
use support::{weights::Weight, StorageValue};
use test_runner::{
	build_runtime, client_parts, task_executor, ChainInfo, ConfigOrChainSpec, Node,
	SignatureVerificationOverride,
};
38
39
40
41
42

type BlockImport<B, BE, C, SC> = BabeBlockImport<B, C, GrandpaBlockImport<BE, B, C, SC>>;
type Block = polkadot_primitives::v1::Block;
type SelectChain = sc_consensus::LongestChain<TFullBackend<Block>, Block>;

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/// Declare an instance of the native executor named `Executor`. Include the wasm binary as the
/// equivalent wasm code.
pub struct Executor;

impl sc_executor::NativeExecutionDispatch for Executor {
	type ExtendHostFunctions =
		(benchmarking::benchmarking::HostFunctions, SignatureVerificationOverride);

	fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
		polkadot_runtime::api::dispatch(method, data)
	}

	fn native_version() -> sc_executor::NativeVersion {
		polkadot_runtime::native_version()
	}
}
59

Denis_P's avatar
Denis_P committed
60
/// `ChainInfo` implementation.
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
pub struct PolkadotChainInfo;

impl ChainInfo for PolkadotChainInfo {
	type Block = Block;
	type Executor = Executor;
	type Runtime = Runtime;
	type RuntimeApi = RuntimeApi;
	type SelectChain = SelectChain;
	type BlockImport = BlockImport<
		Self::Block,
		TFullBackend<Self::Block>,
		TFullClient<Self::Block, RuntimeApi, Self::Executor>,
		Self::SelectChain,
	>;
	type SignedExtras = polkadot_runtime::SignedExtra;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
76
77
	type InherentDataProviders =
		(SlotTimestampProvider, sp_consensus_babe::inherents::InherentDataProvider);
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

	fn signed_extras(from: <Runtime as system::Config>::AccountId) -> Self::SignedExtras {
		(
			system::CheckSpecVersion::<Runtime>::new(),
			system::CheckTxVersion::<Runtime>::new(),
			system::CheckGenesis::<Runtime>::new(),
			system::CheckMortality::<Runtime>::from(Era::Immortal),
			system::CheckNonce::<Runtime>::from(system::Pallet::<Runtime>::account_nonce(from)),
			system::CheckWeight::<Runtime>::new(),
			transaction_payment::ChargeTransactionPayment::<Runtime>::from(0),
			claims::PrevalidateAttests::<Runtime>::new(),
		)
	}
}

/// Dispatch with root origin, via pallet-democracy
Shawn Tabrizi's avatar
Shawn Tabrizi committed
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
pub async fn dispatch_with_root<T>(
	call: impl Into<<T::Runtime as system::Config>::Call>,
	node: &Node<T>,
) -> Result<(), Box<dyn Error>>
where
	T: ChainInfo<
		Block = Block,
		Executor = Executor,
		Runtime = Runtime,
		RuntimeApi = RuntimeApi,
		SelectChain = SelectChain,
		BlockImport = BlockImport<
			Block,
			TFullBackend<Block>,
			TFullClient<Block, RuntimeApi, Executor>,
			SelectChain,
		>,
		SignedExtras = polkadot_runtime::SignedExtra,
	>,
113
114
{
	type DemocracyCall = democracy::Call<Runtime>;
Seun Lanlege's avatar
Seun Lanlege committed
115
	type CouncilCollectiveEvent = collective::Event<Runtime, CouncilCollective>;
116
117
	type CouncilCollectiveCall = collective::Call<Runtime, CouncilCollective>;
	type TechnicalCollectiveCall = collective::Call<Runtime, TechnicalCollective>;
Seun Lanlege's avatar
Seun Lanlege committed
118
	type TechnicalCollectiveEvent = collective::Event<Runtime, TechnicalCollective>;
119
120
121
122
123
124

	// here lies a black mirror esque copy of on chain whales.
	let whales = vec![
		"1rvXMZpAj9nKLQkPFCymyH7Fg3ZyKJhJbrc7UtHbTVhJm1A",
		"15j4dg5GzsL1bw2U2AWgeyAk6QTxq43V7ZPbXdAmbVLjvDCK",
	]
Shawn Tabrizi's avatar
Shawn Tabrizi committed
125
126
127
	.into_iter()
	.map(|account| AccountId32::from_str(account).unwrap())
	.collect::<Vec<_>>();
128
129

	// and these
Shawn Tabrizi's avatar
Shawn Tabrizi committed
130
131
132
133
134
135
	let (technical_collective, council_collective) = node.with_state(|| {
		(
			collective::Members::<Runtime, TechnicalCollective>::get(),
			collective::Members::<Runtime, CouncilCollective>::get(),
		)
	});
136
137
138
139

	// hash of the proposal in democracy
	let proposal_hash = {
		// note the call (pre-image?) of the call.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
140
141
		node.submit_extrinsic(
			DemocracyCall::note_preimage(call.into().encode()),
142
			Some(whales[0].clone()),
Shawn Tabrizi's avatar
Shawn Tabrizi committed
143
144
		)
		.await?;
145
146
147
		node.seal_blocks(1).await;

		// fetch proposal hash from event emitted by the runtime
Seun Lanlege's avatar
Seun Lanlege committed
148
		let events = node.events();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
149
150
		events
			.iter()
151
			.filter_map(|event| match event.event {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
152
153
154
				Event::Democracy(democracy::Event::PreimageNoted(ref proposal_hash, _, _)) =>
					Some(proposal_hash.clone()),
				_ => None,
155
156
			})
			.next()
Shawn Tabrizi's avatar
Shawn Tabrizi committed
157
158
159
			.ok_or_else(|| {
				format!("democracy::Event::PreimageNoted not found in events: {:#?}", events)
			})?
160
161
162
163
	};

	// submit external_propose call through council collective
	{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
164
165
		let external_propose =
			DemocracyCall::external_propose_majority(proposal_hash.clone().into());
166
167
168
169
170
171
172
173
		let length = external_propose.using_encoded(|x| x.len()) as u32 + 1;
		let weight = Weight::MAX / 100_000_000;
		let proposal = CouncilCollectiveCall::propose(
			council_collective.len() as u32,
			Box::new(external_propose.clone().into()),
			length,
		);

174
175
		node.submit_extrinsic(proposal.clone(), Some(council_collective[0].clone()))
			.await?;
176
177
178
		node.seal_blocks(1).await;

		// fetch proposal index from event emitted by the runtime
Seun Lanlege's avatar
Seun Lanlege committed
179
		let events = node.events();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
180
181
182
183
184
185
		let (index, hash): (u32, H256) = events
			.iter()
			.filter_map(|event| match event.event {
				Event::Council(CouncilCollectiveEvent::Proposed(_, index, ref hash, _)) =>
					Some((index, hash.clone())),
				_ => None,
186
187
			})
			.next()
Shawn Tabrizi's avatar
Shawn Tabrizi committed
188
189
190
			.ok_or_else(|| {
				format!("CouncilCollectiveEvent::Proposed not found in events: {:#?}", events)
			})?;
191
192
193
194

		// vote
		for member in &council_collective[1..] {
			let call = CouncilCollectiveCall::vote(hash.clone(), index, true);
195
			node.submit_extrinsic(call, Some(member.clone())).await?;
196
197
198
199
200
		}
		node.seal_blocks(1).await;

		// close vote
		let call = CouncilCollectiveCall::close(hash, index, weight, length);
201
		node.submit_extrinsic(call, Some(council_collective[0].clone())).await?;
202
203
204
		node.seal_blocks(1).await;

		// assert that proposal has been passed on chain
Shawn Tabrizi's avatar
Shawn Tabrizi committed
205
206
		let events = node
			.events()
207
			.into_iter()
Shawn Tabrizi's avatar
Shawn Tabrizi committed
208
209
210
211
212
213
214
215
			.filter(|event| match event.event {
				Event::Council(CouncilCollectiveEvent::Closed(_hash, _, _)) if hash == _hash =>
					true,
				Event::Council(CouncilCollectiveEvent::Approved(_hash)) if hash == _hash => true,
				Event::Council(CouncilCollectiveEvent::Executed(_hash, Ok(())))
					if hash == _hash =>
					true,
				_ => false,
216
217
218
219
			})
			.collect::<Vec<_>>();

		// make sure all 3 events are in state
Seun Lanlege's avatar
Seun Lanlege committed
220
		assert_eq!(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
221
222
			events.len(),
			3,
Seun Lanlege's avatar
Seun Lanlege committed
223
224
225
			"CouncilCollectiveEvent::{{Closed, Approved, Executed}} not found in events: {:#?}",
			node.events(),
		);
226
227
228
229
	}

	// next technical collective must fast track the proposal.
	{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
230
231
		let fast_track =
			DemocracyCall::fast_track(proposal_hash.into(), FastTrackVotingPeriod::get(), 0);
232
233
234
235
236
237
238
239
		let weight = Weight::MAX / 100_000_000;
		let length = fast_track.using_encoded(|x| x.len()) as u32 + 1;
		let proposal = TechnicalCollectiveCall::propose(
			technical_collective.len() as u32,
			Box::new(fast_track.into()),
			length,
		);

240
		node.submit_extrinsic(proposal, Some(technical_collective[0].clone())).await?;
241
242
		node.seal_blocks(1).await;

Seun Lanlege's avatar
Seun Lanlege committed
243
		let events = node.events();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
244
245
246
247
248
249
250
251
252
253
		let (index, hash) = events
			.iter()
			.filter_map(|event| match event.event {
				Event::TechnicalCommittee(TechnicalCollectiveEvent::Proposed(
					_,
					index,
					ref hash,
					_,
				)) => Some((index, hash.clone())),
				_ => None,
254
255
			})
			.next()
Shawn Tabrizi's avatar
Shawn Tabrizi committed
256
257
258
			.ok_or_else(|| {
				format!("TechnicalCollectiveEvent::Proposed not found in events: {:#?}", events)
			})?;
259
260
261
262

		// vote
		for member in &technical_collective[1..] {
			let call = TechnicalCollectiveCall::vote(hash.clone(), index, true);
263
			node.submit_extrinsic(call, Some(member.clone())).await?;
264
265
266
267
268
		}
		node.seal_blocks(1).await;

		// close vote
		let call = TechnicalCollectiveCall::close(hash, index, weight, length);
269
		node.submit_extrinsic(call, Some(technical_collective[0].clone())).await?;
270
271
272
		node.seal_blocks(1).await;

		// assert that fast-track proposal has been passed on chain
Shawn Tabrizi's avatar
Shawn Tabrizi committed
273
274
		let events = node
			.events()
275
			.into_iter()
Shawn Tabrizi's avatar
Shawn Tabrizi committed
276
277
278
279
280
281
282
283
284
285
286
			.filter(|event| match event.event {
				Event::TechnicalCommittee(TechnicalCollectiveEvent::Closed(_hash, _, _))
					if hash == _hash =>
					true,
				Event::TechnicalCommittee(TechnicalCollectiveEvent::Approved(_hash))
					if hash == _hash =>
					true,
				Event::TechnicalCommittee(TechnicalCollectiveEvent::Executed(_hash, Ok(())))
					if hash == _hash =>
					true,
				_ => false,
287
288
289
290
			})
			.collect::<Vec<_>>();

		// make sure all 3 events are in state
Seun Lanlege's avatar
Seun Lanlege committed
291
		assert_eq!(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
292
293
			events.len(),
			3,
Seun Lanlege's avatar
Seun Lanlege committed
294
295
296
			"TechnicalCollectiveEvent::{{Closed, Approved, Executed}} not found in events: {:#?}",
			node.events(),
		);
297
298
299
	}

	// now runtime upgrade proposal is a fast-tracked referendum we can vote for.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
300
301
	let ref_index = node
		.events()
302
303
		.into_iter()
		.filter_map(|event| match event.event {
Seun Lanlege's avatar
Seun Lanlege committed
304
			Event::Democracy(democracy::Event::Started(index, _)) => Some(index),
305
306
307
			_ => None,
		})
		.next()
Shawn Tabrizi's avatar
Shawn Tabrizi committed
308
309
310
		.ok_or_else(|| {
			format!("democracy::Event::Started not found in events: {:#?}", node.events())
		})?;
Seun Lanlege's avatar
Seun Lanlege committed
311

312
	let call = DemocracyCall::vote(
Seun Lanlege's avatar
Seun Lanlege committed
313
		ref_index,
314
315
316
317
318
319
320
		AccountVote::Standard {
			vote: Vote { aye: true, conviction: Conviction::Locked1x },
			// 10 DOTS
			balance: 10_000_000_000_000,
		},
	);
	for whale in whales {
321
		node.submit_extrinsic(call.clone(), Some(whale)).await?;
322
323
324
325
326
327
	}

	// wait for fast track period.
	node.seal_blocks(FastTrackVotingPeriod::get() as usize).await;

	// assert that the proposal is passed by looking at events
Shawn Tabrizi's avatar
Shawn Tabrizi committed
328
329
	let events = node
		.events()
330
		.into_iter()
Shawn Tabrizi's avatar
Shawn Tabrizi committed
331
332
333
334
335
336
337
338
		.filter(|event| match event.event {
			Event::Democracy(democracy::Event::Passed(_index)) if _index == ref_index => true,
			Event::Democracy(democracy::Event::PreimageUsed(_hash, _, _))
				if _hash == proposal_hash =>
				true,
			Event::Democracy(democracy::Event::Executed(_index, Ok(()))) if _index == ref_index =>
				true,
			_ => false,
339
340
341
342
		})
		.collect::<Vec<_>>();

	// make sure all events were emitted
Seun Lanlege's avatar
Seun Lanlege committed
343
	assert_eq!(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
344
345
		events.len(),
		3,
Seun Lanlege's avatar
Seun Lanlege committed
346
347
348
		"democracy::Event::{{Passed, PreimageUsed, Executed}} not found in events: {:#?}",
		node.events(),
	);
349
350
351
352
353
	Ok(())
}

/// Runs the test-runner as a binary.
pub fn run<F, Fut>(callback: F) -> Result<(), Box<dyn Error>>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
354
355
356
where
	F: FnOnce(Node<PolkadotChainInfo>) -> Fut,
	Fut: Future<Output = Result<(), Box<dyn Error>>>,
357
358
{
	use sc_cli::{CliConfiguration, SubstrateCli};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
359
	use structopt::StructOpt;
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374

	let mut tokio_runtime = build_runtime()?;
	let task_executor = task_executor(tokio_runtime.handle().clone());
	// parse cli args
	let cmd = <polkadot_cli::Cli as StructOpt>::from_args();
	// set up logging
	let filters = cmd.run.base.log_filters()?;
	let logger = sc_tracing::logging::LoggerBuilder::new(filters);
	logger.init()?;

	// set up the test-runner
	let config = cmd.create_configuration(&cmd.run.base, task_executor)?;
	sc_cli::print_node_infos::<polkadot_cli::Cli>(&config);
	let (rpc, task_manager, client, pool, command_sink, backend) =
		client_parts::<PolkadotChainInfo>(ConfigOrChainSpec::Config(config))?;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
375
376
	let node =
		Node::<PolkadotChainInfo>::new(rpc, task_manager, client, pool, command_sink, backend);
377
378
379
380
381
382
383
384
385
386
387

	// hand off node.
	tokio_runtime.block_on(callback(node))?;

	Ok(())
}

#[cfg(test)]
mod tests {
	use super::*;
	use polkadot_service::chain_spec::polkadot_development_config;
Shawn Tabrizi's avatar
Shawn Tabrizi committed
388
389
	use sp_keyring::sr25519::Keyring::Alice;
	use sp_runtime::{traits::IdentifyAccount, MultiSigner};
390
391
392
393
394
395

	#[test]
	fn test_runner() {
		let mut runtime = build_runtime().unwrap();
		let task_executor = task_executor(runtime.handle().clone());
		let (rpc, task_manager, client, pool, command_sink, backend) =
Shawn Tabrizi's avatar
Shawn Tabrizi committed
396
397
398
399
400
401
402
			client_parts::<PolkadotChainInfo>(ConfigOrChainSpec::ChainSpec(
				Box::new(polkadot_development_config().unwrap()),
				task_executor,
			))
			.unwrap();
		let node =
			Node::<PolkadotChainInfo>::new(rpc, task_manager, client, pool, command_sink, backend);
403
404
405
406
407
408

		runtime.block_on(async {
			// seals blocks
			node.seal_blocks(1).await;
			// submit extrinsics
			let alice = MultiSigner::from(Alice.public()).into_account();
409
			node.submit_extrinsic(system::Call::remark((b"hello world").to_vec()), Some(alice))
410
411
412
413
414
415
416
417
418
419
				.await
				.unwrap();

			// look ma, I can read state.
			let _events = node.with_state(|| system::Pallet::<Runtime>::events());
			// get access to the underlying client.
			let _client = node.client();
		});
	}
}