mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 07:41:08 +00:00
Squashed commit of slava-bridge-runtime:
commit ae4139bbb3cd4681ff4ad3f335b57e17f49ee895 Author: Hernando Castano <castano.ha@gmail.com> Date: Wed Jan 29 22:18:42 2020 -0500 Move slava-bridge-runtime into modules/ethereum/ commit feb522fff241b36bd74638ab5de04ee1068efd0b Merge: 53a08fe 00fe0de Author: Hernando Castano <castano.ha@gmail.com> Date: Wed Jan 29 22:17:26 2020 -0500 Merge branch 'bridge_runtime' of slava-bridge-runtime commit 00fe0dee5f50dd648e7c0f4af7fcb07f422a30af Author: Hernando Castano <castano.ha@gmail.com> Date: Wed Jan 29 22:14:03 2020 -0500 Move all files into one folder commit 46d9bdcc35b022bd802e3f99d3f7477110813e2d Author: Hernando Castano <castano.ha@gmail.com> Date: Wed Jan 29 22:02:37 2020 -0500 Move files I'm keeping into folder commit 6d10776610283439764de7897f25e921d08e99dc Author: Hernando Castano <castano.ha@gmail.com> Date: Sun Jan 12 20:42:03 2020 -0500 Make the bridge-eth-poa module compile again commit 95283a8672d692ed34501e4e7c380a3038099bf8 Author: Hernando Castano <castano.ha@gmail.com> Date: Sun Jan 12 20:39:23 2020 -0500 Add `sp-api` dependency back commit 31c476ac25391fb8f896b718998e9e80976a4cfd Author: Hernando Castano <castano.ha@gmail.com> Date: Sun Jan 12 20:28:26 2020 -0500 Bump dependencies Uses new frame-* and pallet-* versioned dependencies but a few other packages came along for the bump ride commit e22f41d97758712cd4094b98dc3a884098059bb8 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Tue Dec 10 14:52:04 2019 +0300 reward + penalize for Eth headers commit 34d35e7731c79b9e9ff21252063f939400ecfdb4 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Mon Dec 9 12:56:31 2019 +0300 added couple of TODOs commit 1a80caf9b643be4b823c9559a132285606641811 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Mon Dec 9 12:11:35 2019 +0300 removed debug print commit c590908bfb7e9d2075d1fd77556418eda7f330d3 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Mon Dec 9 11:55:09 2019 +0300 fixed bridge tests commit ab164f87019037bbc5cd2a69364ee4623e44ebbc Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Mon Dec 9 11:15:46 2019 +0300 lost deps commit 649e90068040a12a1aa78788dd834394201753fa Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Mon Dec 9 09:57:09 2019 +0300 fix compilation again commit cece62c2c2ddf772f01151ce3bf6dec28f4d0cca Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Mon Dec 9 09:52:30 2019 +0300 fix receipts root calculation commit 92f4026896efbe3e315a909161af06dc1e2a1ff4 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Thu Dec 5 16:09:23 2019 +0300 check transactions receipts root commit 2e6a5af5e1bcb843262c5ba475f950cf989eb39b Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Thu Dec 5 15:39:30 2019 +0300 provide keccak_256_ordered_root to runtime commit 2970aae4648beb8c531ec7c2ff706b65ea06ba63 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Thu Dec 5 15:15:03 2019 +0300 use existing keccak builtin from bridge runtime commit 75c498b0fe600be32b35a3e0d0da3b52ec5fd3b6 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Thu Dec 5 10:32:57 2019 +0300 submit multiple headers at once commit cdfdafc21efb4a667f2407c7139921d4b948fd7e Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Wed Dec 4 13:48:42 2019 +0300 expose && initialize bridge configuration commit 383b93be54981b13c564218652a85af78c88c4f7 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Wed Dec 4 11:28:22 2019 +0300 export bridge Call commit dacc2939db51859a3e1b87250b289b269a80793b Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Wed Dec 4 11:13:56 2019 +0300 expose BridgeEthPoaCall commit a5281c9387e622e28cbc89a62d268b359cb2f724 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Wed Dec 4 10:42:41 2019 +0300 EthereumHeadersApi::is_known_block commit c5658e1563be9b688355d03b20a62345418e3b78 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Wed Dec 4 10:14:42 2019 +0300 pub use parity_bytes::Bytes; commit 94cd24e5535101e871436ca35dcfda27f7955590 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Wed Dec 4 10:01:25 2019 +0300 exposed EthereumHeadersApi commit 607ec1760d146e8046122a1d2f868d70e15490e6 Author: Svyatoslav Nikolsky <svyatonik@gmail.com> Date: Tue Dec 3 14:51:40 2019 +0300 EthPoA bridge: runtime
This commit is contained in:
committed by
Bastian Köcher
parent
479c5bd99a
commit
c06777a42a
@@ -0,0 +1,128 @@
|
||||
[package]
|
||||
name = "node-cli"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Substrate node implementation in Rust."
|
||||
build = "build.rs"
|
||||
edition = "2018"
|
||||
default-run = "substrate"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "paritytech/substrate", branch = "master" }
|
||||
maintenance = { status = "actively-developed" }
|
||||
is-it-maintained-issue-resolution = { repository = "paritytech/substrate" }
|
||||
is-it-maintained-open-issues = { repository = "paritytech/substrate" }
|
||||
|
||||
[[bin]]
|
||||
name = "substrate"
|
||||
path = "bin/main.rs"
|
||||
required-features = ["cli"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
# third-party dependencies
|
||||
codec = { package = "parity-scale-codec", version = "1.0.6" }
|
||||
serde = { version = "1.0.102", features = ["derive"] }
|
||||
futures01 = { package = "futures", version = "0.1.29" }
|
||||
futures = { version = "0.3.1", features = ["compat"] }
|
||||
hex-literal = "0.2.1"
|
||||
jsonrpc-core = "14.0.3"
|
||||
log = "0.4.8"
|
||||
rand = "0.7.2"
|
||||
structopt = "=0.3.7"
|
||||
|
||||
# primitives
|
||||
sp-authority-discovery = { version = "2.0.0", path = "../../../primitives/authority-discovery" }
|
||||
sp-consensus-babe = { version = "0.8", path = "../../../primitives/consensus/babe" }
|
||||
grandpa-primitives = { version = "2.0.0", package = "sp-finality-grandpa", path = "../../../primitives/finality-grandpa" }
|
||||
sp-core = { version = "2.0.0", path = "../../../primitives/core" }
|
||||
sp-runtime = { version = "2.0.0", path = "../../../primitives/runtime" }
|
||||
sp-timestamp = { version = "2.0.0", default-features = false, path = "../../../primitives/timestamp" }
|
||||
sp-finality-tracker = { version = "2.0.0", default-features = false, path = "../../../primitives/finality-tracker" }
|
||||
sp-inherents = { version = "2.0.0", path = "../../../primitives/inherents" }
|
||||
sp-keyring = { version = "2.0.0", path = "../../../primitives/keyring" }
|
||||
sp-io = { version = "2.0.0", path = "../../../primitives/io" }
|
||||
sp-consensus = { version = "0.8", path = "../../../primitives/consensus/common" }
|
||||
sp-bridge-eth-poa = { package = "sp-bridge-eth-poa", path = "../../../primitives/bridge-eth-poa" }
|
||||
|
||||
# client dependencies
|
||||
sc-client-api = { version = "2.0.0", path = "../../../client/api" }
|
||||
sc-client = { version = "2.0.0", path = "../../../client/" }
|
||||
sc-chain-spec = { version = "2.0.0", path = "../../../client/chain-spec" }
|
||||
sc-transaction-pool = { version = "2.0.0", path = "../../../client/transaction-pool" }
|
||||
sp-transaction-pool = { version = "2.0.0", path = "../../../primitives/transaction-pool" }
|
||||
sc-network = { version = "0.8", path = "../../../client/network" }
|
||||
sc-consensus-babe = { version = "0.8", path = "../../../client/consensus/babe" }
|
||||
grandpa = { version = "2.0.0", package = "sc-finality-grandpa", path = "../../../client/finality-grandpa" }
|
||||
sc-client-db = { version = "2.0.0", default-features = false, path = "../../../client/db" }
|
||||
sc-offchain = { version = "2.0.0", path = "../../../client/offchain" }
|
||||
sc-rpc = { version = "2.0.0", path = "../../../client/rpc" }
|
||||
sc-basic-authority = { version = "2.0.0", path = "../../../client/basic-authorship" }
|
||||
sc-service = { version = "2.0.0", default-features = false, path = "../../../client/service" }
|
||||
sc-telemetry = { version = "2.0.0", path = "../../../client/telemetry" }
|
||||
sc-authority-discovery = { version = "2.0.0", path = "../../../client/authority-discovery" }
|
||||
|
||||
# frame dependencies
|
||||
pallet-indices = { version = "2.0.0", path = "../../../frame/indices" }
|
||||
pallet-timestamp = { version = "2.0.0", default-features = false, path = "../../../frame/timestamp" }
|
||||
pallet-contracts = { version = "2.0.0", path = "../../../frame/contracts" }
|
||||
frame-system = { version = "2.0.0", path = "../../../frame/system" }
|
||||
pallet-balances = { version = "2.0.0", path = "../../../frame/balances" }
|
||||
pallet-transaction-payment = { version = "2.0.0", path = "../../../frame/transaction-payment" }
|
||||
frame-support = { version = "2.0.0", default-features = false, path = "../../../frame/support" }
|
||||
pallet-im-online = { version = "2.0.0", default-features = false, path = "../../../frame/im-online" }
|
||||
pallet-authority-discovery = { version = "2.0.0", path = "../../../frame/authority-discovery" }
|
||||
|
||||
# node-specific dependencies
|
||||
node-runtime = { version = "2.0.0", path = "../runtime" }
|
||||
node-rpc = { version = "2.0.0", path = "../rpc" }
|
||||
node-primitives = { version = "2.0.0", path = "../primitives" }
|
||||
node-executor = { version = "2.0.0", path = "../executor" }
|
||||
|
||||
# CLI-specific dependencies
|
||||
tokio = { version = "0.1.22", optional = true }
|
||||
sc-cli = { version = "2.0.0", optional = true, path = "../../../client/cli" }
|
||||
ctrlc = { version = "3.1.3", features = ["termination"], optional = true }
|
||||
node-transaction-factory = { version = "2.0.0", optional = true, path = "../transaction-factory" }
|
||||
|
||||
# WASM-specific dependencies
|
||||
wasm-bindgen = { version = "0.2.57", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.7", optional = true }
|
||||
browser-utils = { path = "../../../utils/browser", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sc-keystore = { version = "2.0.0", path = "../../../client/keystore" }
|
||||
sc-consensus-babe = { version = "0.8", features = ["test-helpers"], path = "../../../client/consensus/babe" }
|
||||
sc-service-test = { version = "2.0.0", path = "../../../client/service/test" }
|
||||
futures = "0.3.1"
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
sc-cli = { version = "2.0.0", package = "sc-cli", path = "../../../client/cli" }
|
||||
build-script-utils = { version = "2.0.0", package = "substrate-build-script-utils", path = "../../../utils/build-script-utils" }
|
||||
structopt = "=0.3.7"
|
||||
vergen = "3.0.4"
|
||||
|
||||
[features]
|
||||
default = ["cli", "wasmtime"]
|
||||
browser = [
|
||||
"browser-utils",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
cli = [
|
||||
"sc-cli",
|
||||
"node-transaction-factory",
|
||||
"tokio",
|
||||
"ctrlc",
|
||||
"sc-service/rocksdb",
|
||||
"node-executor/wasmi-errno",
|
||||
]
|
||||
wasmtime = [
|
||||
"cli",
|
||||
"node-executor/wasmtime",
|
||||
"sc-cli/wasmtime",
|
||||
"sc-service/wasmtime",
|
||||
]
|
||||
@@ -0,0 +1,449 @@
|
||||
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate chain configurations.
|
||||
|
||||
use sc_chain_spec::ChainSpecExtension;
|
||||
use sp_core::{Pair, Public, crypto::UncheckedInto, sr25519};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use node_runtime::{
|
||||
AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, ContractsConfig, CouncilConfig, DemocracyConfig,
|
||||
GrandpaConfig, ImOnlineConfig, IndicesConfig, SessionConfig, SessionKeys, StakerStatus, StakingConfig, SudoConfig,
|
||||
SystemConfig, TechnicalCommitteeConfig, WASM_BINARY, BridgeEthPoaConfig,
|
||||
};
|
||||
use node_runtime::Block;
|
||||
use node_runtime::constants::currency::*;
|
||||
use sc_service;
|
||||
use hex_literal::hex;
|
||||
use sc_telemetry::TelemetryEndpoints;
|
||||
use grandpa_primitives::{AuthorityId as GrandpaId};
|
||||
use sp_consensus_babe::{AuthorityId as BabeId};
|
||||
use pallet_im_online::sr25519::{AuthorityId as ImOnlineId};
|
||||
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||
use sp_runtime::{Perbill, traits::{Verify, IdentifyAccount}};
|
||||
|
||||
pub use node_primitives::{AccountId, Balance, Signature};
|
||||
pub use node_runtime::GenesisConfig;
|
||||
|
||||
type AccountPublic = <Signature as Verify>::Signer;
|
||||
|
||||
const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
|
||||
|
||||
/// Node `ChainSpec` extensions.
|
||||
///
|
||||
/// Additional parameters for some Substrate core modules,
|
||||
/// customizable from the chain spec.
|
||||
#[derive(Default, Clone, Serialize, Deserialize, ChainSpecExtension)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Extensions {
|
||||
/// Block numbers with known hashes.
|
||||
pub fork_blocks: sc_client::ForkBlocks<Block>,
|
||||
/// Known bad block hashes.
|
||||
pub bad_blocks: sc_client::BadBlocks<Block>,
|
||||
}
|
||||
|
||||
/// Specialized `ChainSpec`.
|
||||
pub type ChainSpec = sc_service::ChainSpec<
|
||||
GenesisConfig,
|
||||
Extensions,
|
||||
>;
|
||||
/// Flaming Fir testnet generator
|
||||
pub fn flaming_fir_config() -> Result<ChainSpec, String> {
|
||||
ChainSpec::from_json_bytes(&include_bytes!("../res/flaming-fir.json")[..])
|
||||
}
|
||||
|
||||
fn session_keys(
|
||||
grandpa: GrandpaId,
|
||||
babe: BabeId,
|
||||
im_online: ImOnlineId,
|
||||
authority_discovery: AuthorityDiscoveryId,
|
||||
) -> SessionKeys {
|
||||
SessionKeys { grandpa, babe, im_online, authority_discovery }
|
||||
}
|
||||
|
||||
fn staging_testnet_config_genesis() -> GenesisConfig {
|
||||
// stash, controller, session-key
|
||||
// generated with secret:
|
||||
// for i in 1 2 3 4 ; do for j in stash controller; do subkey inspect "$secret"/fir/$j/$i; done; done
|
||||
// and
|
||||
// for i in 1 2 3 4 ; do for j in session; do subkey --ed25519 inspect "$secret"//fir//$j//$i; done; done
|
||||
|
||||
let initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId)> = vec![(
|
||||
// 5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy
|
||||
hex!["9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d12"].into(),
|
||||
// 5EnCiV7wSHeNhjW3FSUwiJNkcc2SBkPLn5Nj93FmbLtBjQUq
|
||||
hex!["781ead1e2fa9ccb74b44c19d29cb2a7a4b5be3972927ae98cd3877523976a276"].into(),
|
||||
// 5Fb9ayurnxnaXj56CjmyQLBiadfRCqUbL2VWNbbe1nZU6wiC
|
||||
hex!["9becad03e6dcac03cee07edebca5475314861492cdfc96a2144a67bbe9699332"].unchecked_into(),
|
||||
// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
|
||||
hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(),
|
||||
// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
|
||||
hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(),
|
||||
// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
|
||||
hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(),
|
||||
),(
|
||||
// 5ERawXCzCWkjVq3xz1W5KGNtVx2VdefvZ62Bw1FEuZW4Vny2
|
||||
hex!["68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde78"].into(),
|
||||
// 5Gc4vr42hH1uDZc93Nayk5G7i687bAQdHHc9unLuyeawHipF
|
||||
hex!["c8dc79e36b29395413399edaec3e20fcca7205fb19776ed8ddb25d6f427ec40e"].into(),
|
||||
// 5EockCXN6YkiNCDjpqqnbcqd4ad35nU4RmA1ikM4YeRN4WcE
|
||||
hex!["7932cff431e748892fa48e10c63c17d30f80ca42e4de3921e641249cd7fa3c2f"].unchecked_into(),
|
||||
// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
|
||||
hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(),
|
||||
// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
|
||||
hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(),
|
||||
// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
|
||||
hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(),
|
||||
),(
|
||||
// 5DyVtKWPidondEu8iHZgi6Ffv9yrJJ1NDNLom3X9cTDi98qp
|
||||
hex!["547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d65"].into(),
|
||||
// 5FeD54vGVNpFX3PndHPXJ2MDakc462vBCD5mgtWRnWYCpZU9
|
||||
hex!["9e42241d7cd91d001773b0b616d523dd80e13c6c2cab860b1234ef1b9ffc1526"].into(),
|
||||
// 5E1jLYfLdUQKrFrtqoKgFrRvxM3oQPMbf6DfcsrugZZ5Bn8d
|
||||
hex!["5633b70b80a6c8bb16270f82cca6d56b27ed7b76c8fd5af2986a25a4788ce440"].unchecked_into(),
|
||||
// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
|
||||
hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(),
|
||||
// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
|
||||
hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(),
|
||||
// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
|
||||
hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(),
|
||||
),(
|
||||
// 5HYZnKWe5FVZQ33ZRJK1rG3WaLMztxWrrNDb1JRwaHHVWyP9
|
||||
hex!["f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c2663"].into(),
|
||||
// 5EPQdAQ39WQNLCRjWsCk5jErsCitHiY5ZmjfWzzbXDoAoYbn
|
||||
hex!["66bc1e5d275da50b72b15de072a2468a5ad414919ca9054d2695767cf650012f"].into(),
|
||||
// 5DMa31Hd5u1dwoRKgC4uvqyrdK45RHv3CpwvpUC1EzuwDit4
|
||||
hex!["3919132b851ef0fd2dae42a7e734fe547af5a6b809006100f48944d7fae8e8ef"].unchecked_into(),
|
||||
// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
|
||||
hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"].unchecked_into(),
|
||||
// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
|
||||
hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"].unchecked_into(),
|
||||
// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
|
||||
hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"].unchecked_into(),
|
||||
)];
|
||||
|
||||
// generated with secret: subkey inspect "$secret"/fir
|
||||
let root_key: AccountId = hex![
|
||||
// 5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo
|
||||
"9ee5e5bdc0ec239eb164f865ecc345ce4c88e76ee002e0f7e318097347471809"
|
||||
].into();
|
||||
|
||||
let endowed_accounts: Vec<AccountId> = vec![root_key.clone()];
|
||||
|
||||
testnet_genesis(
|
||||
initial_authorities,
|
||||
root_key,
|
||||
Some(endowed_accounts),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Staging testnet config.
|
||||
pub fn staging_testnet_config() -> ChainSpec {
|
||||
let boot_nodes = vec![];
|
||||
ChainSpec::from_genesis(
|
||||
"Staging Testnet",
|
||||
"staging_testnet",
|
||||
staging_testnet_config_genesis,
|
||||
boot_nodes,
|
||||
Some(TelemetryEndpoints::new(vec![(STAGING_TELEMETRY_URL.to_string(), 0)])),
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper function to generate a crypto pair from seed
|
||||
pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
|
||||
TPublic::Pair::from_string(&format!("//{}", seed), None)
|
||||
.expect("static values are valid; qed")
|
||||
.public()
|
||||
}
|
||||
|
||||
/// Helper function to generate an account ID from seed
|
||||
pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId where
|
||||
AccountPublic: From<<TPublic::Pair as Pair>::Public>
|
||||
{
|
||||
AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
|
||||
}
|
||||
|
||||
/// Helper function to generate stash, controller and session key from seed
|
||||
pub fn get_authority_keys_from_seed(seed: &str) -> (
|
||||
AccountId,
|
||||
AccountId,
|
||||
GrandpaId,
|
||||
BabeId,
|
||||
ImOnlineId,
|
||||
AuthorityDiscoveryId,
|
||||
) {
|
||||
(
|
||||
get_account_id_from_seed::<sr25519::Public>(&format!("{}//stash", seed)),
|
||||
get_account_id_from_seed::<sr25519::Public>(seed),
|
||||
get_from_seed::<GrandpaId>(seed),
|
||||
get_from_seed::<BabeId>(seed),
|
||||
get_from_seed::<ImOnlineId>(seed),
|
||||
get_from_seed::<AuthorityDiscoveryId>(seed),
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper function to create GenesisConfig for testing
|
||||
pub fn testnet_genesis(
|
||||
initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId)>,
|
||||
root_key: AccountId,
|
||||
endowed_accounts: Option<Vec<AccountId>>,
|
||||
enable_println: bool,
|
||||
) -> GenesisConfig {
|
||||
let endowed_accounts: Vec<AccountId> = endowed_accounts.unwrap_or_else(|| {
|
||||
vec![
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Bob"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Charlie"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Dave"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Eve"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Ferdie"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Dave//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Eve//stash"),
|
||||
get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),
|
||||
]
|
||||
});
|
||||
let num_endowed_accounts = endowed_accounts.len();
|
||||
|
||||
const ENDOWMENT: Balance = 10_000_000 * DOLLARS;
|
||||
const STASH: Balance = 100 * DOLLARS;
|
||||
|
||||
GenesisConfig {
|
||||
frame_system: Some(SystemConfig {
|
||||
code: WASM_BINARY.to_vec(),
|
||||
changes_trie_config: Default::default(),
|
||||
}),
|
||||
pallet_balances: Some(BalancesConfig {
|
||||
balances: endowed_accounts.iter().cloned()
|
||||
.map(|k| (k, ENDOWMENT))
|
||||
.chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH)))
|
||||
.collect(),
|
||||
vesting: vec![],
|
||||
}),
|
||||
pallet_indices: Some(IndicesConfig {
|
||||
ids: endowed_accounts.iter().cloned()
|
||||
.chain(initial_authorities.iter().map(|x| x.0.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
}),
|
||||
pallet_session: Some(SessionConfig {
|
||||
keys: initial_authorities.iter().map(|x| {
|
||||
(x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone()))
|
||||
}).collect::<Vec<_>>(),
|
||||
}),
|
||||
pallet_staking: Some(StakingConfig {
|
||||
current_era: 0,
|
||||
validator_count: initial_authorities.len() as u32 * 2,
|
||||
minimum_validator_count: initial_authorities.len() as u32,
|
||||
stakers: initial_authorities.iter().map(|x| {
|
||||
(x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator)
|
||||
}).collect(),
|
||||
invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(),
|
||||
slash_reward_fraction: Perbill::from_percent(10),
|
||||
.. Default::default()
|
||||
}),
|
||||
pallet_democracy: Some(DemocracyConfig::default()),
|
||||
pallet_collective_Instance1: Some(CouncilConfig {
|
||||
members: endowed_accounts.iter().cloned()
|
||||
.collect::<Vec<_>>()[..(num_endowed_accounts + 1) / 2].to_vec(),
|
||||
phantom: Default::default(),
|
||||
}),
|
||||
pallet_collective_Instance2: Some(TechnicalCommitteeConfig {
|
||||
members: endowed_accounts.iter().cloned()
|
||||
.collect::<Vec<_>>()[..(num_endowed_accounts + 1) / 2].to_vec(),
|
||||
phantom: Default::default(),
|
||||
}),
|
||||
pallet_contracts: Some(ContractsConfig {
|
||||
current_schedule: pallet_contracts::Schedule {
|
||||
enable_println, // this should only be enabled on development chains
|
||||
..Default::default()
|
||||
},
|
||||
gas_price: 1 * MILLICENTS,
|
||||
}),
|
||||
pallet_sudo: Some(SudoConfig {
|
||||
key: root_key,
|
||||
}),
|
||||
pallet_babe: Some(BabeConfig {
|
||||
authorities: vec![],
|
||||
}),
|
||||
pallet_im_online: Some(ImOnlineConfig {
|
||||
keys: vec![],
|
||||
}),
|
||||
pallet_authority_discovery: Some(AuthorityDiscoveryConfig {
|
||||
keys: vec![],
|
||||
}),
|
||||
pallet_grandpa: Some(GrandpaConfig {
|
||||
authorities: vec![],
|
||||
}),
|
||||
pallet_membership_Instance1: Some(Default::default()),
|
||||
pallet_treasury: Some(Default::default()),
|
||||
// here comes configuration for Kovan chain
|
||||
bridge_eth_poa: Some(BridgeEthPoaConfig {
|
||||
initial_header: sp_bridge_eth_poa::Header {
|
||||
parent_hash: Default::default(),
|
||||
timestamp: 0,
|
||||
number: 0,
|
||||
author: Default::default(),
|
||||
transactions_root: "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".parse().unwrap(),
|
||||
uncles_hash: "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".parse().unwrap(),
|
||||
extra_data: vec![],
|
||||
state_root: "2480155b48a1cea17d67dbfdfaafe821c1d19cdd478c5358e8ec56dec24502b2".parse().unwrap(),
|
||||
receipts_root: "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".parse().unwrap(),
|
||||
log_bloom: Default::default(),
|
||||
gas_used: Default::default(),
|
||||
gas_limit: 6000000.into(),
|
||||
difficulty: 131072.into(),
|
||||
seal: vec![
|
||||
vec![128].into(),
|
||||
vec![184, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].into(),
|
||||
],
|
||||
},
|
||||
initial_difficulty: 0.into(),
|
||||
initial_validators: vec![
|
||||
[0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58,
|
||||
0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, 0x5B, 0xAd, 0xc0, 0xED].into(),
|
||||
[0x00, 0x42, 0x7f, 0xea, 0xe2, 0x41, 0x9c, 0x15, 0xb8, 0x9d,
|
||||
0x1c, 0x21, 0xaf, 0x10, 0xd1, 0xb6, 0x65, 0x0a, 0x4d, 0x3d].into(),
|
||||
[0x4E, 0xd9, 0xB0, 0x8e, 0x63, 0x54, 0xC7, 0x0f, 0xE6, 0xF8,
|
||||
0xCB, 0x04, 0x11, 0xb0, 0xd3, 0x24, 0x6b, 0x42, 0x4d, 0x6c].into(),
|
||||
[0x00, 0x20, 0xee, 0x4B, 0xe0, 0xe2, 0x02, 0x7d, 0x76, 0x60,
|
||||
0x3c, 0xB7, 0x51, 0xeE, 0x06, 0x95, 0x19, 0xbA, 0x81, 0xA1].into(),
|
||||
[0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5,
|
||||
0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, 0x3a, 0xfd, 0x03, 0x2d].into(),
|
||||
[0x00, 0x77, 0x33, 0xa1, 0xFE, 0x69, 0xCF, 0x3f, 0x2C, 0xF9,
|
||||
0x89, 0xF8, 0x1C, 0x7b, 0x4c, 0xAc, 0x16, 0x93, 0x38, 0x7A].into(),
|
||||
[0x00, 0xE6, 0xd2, 0xb9, 0x31, 0xF5, 0x5a, 0x3f, 0x17, 0x01,
|
||||
0xc7, 0x38, 0x9d, 0x59, 0x2a, 0x77, 0x78, 0x89, 0x78, 0x79].into(),
|
||||
[0x00, 0xe4, 0xa1, 0x06, 0x50, 0xe5, 0xa6, 0xD6, 0x00, 0x1C,
|
||||
0x38, 0xff, 0x8E, 0x64, 0xF9, 0x70, 0x16, 0xa1, 0x64, 0x5c].into(),
|
||||
[0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c,
|
||||
0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, 0x12, 0x31, 0x94, 0xde].into(),
|
||||
],
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn development_config_genesis() -> GenesisConfig {
|
||||
testnet_genesis(
|
||||
vec![
|
||||
get_authority_keys_from_seed("Alice"),
|
||||
],
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice"),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
/// Development config (single validator Alice)
|
||||
pub fn development_config() -> ChainSpec {
|
||||
ChainSpec::from_genesis(
|
||||
"Development",
|
||||
"dev",
|
||||
development_config_genesis,
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn local_testnet_genesis() -> GenesisConfig {
|
||||
testnet_genesis(
|
||||
vec![
|
||||
get_authority_keys_from_seed("Alice"),
|
||||
get_authority_keys_from_seed("Bob"),
|
||||
],
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice"),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Local testnet config (multivalidator Alice + Bob)
|
||||
pub fn local_testnet_config() -> ChainSpec {
|
||||
ChainSpec::from_genesis(
|
||||
"Local Testnet",
|
||||
"local_testnet",
|
||||
local_testnet_genesis,
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::service::{new_full, new_light};
|
||||
use sc_service_test;
|
||||
|
||||
fn local_testnet_genesis_instant_single() -> GenesisConfig {
|
||||
testnet_genesis(
|
||||
vec![
|
||||
get_authority_keys_from_seed("Alice"),
|
||||
],
|
||||
get_account_id_from_seed::<sr25519::Public>("Alice"),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Local testnet config (single validator - Alice)
|
||||
pub fn integration_test_config_with_single_authority() -> ChainSpec {
|
||||
ChainSpec::from_genesis(
|
||||
"Integration Test",
|
||||
"test",
|
||||
local_testnet_genesis_instant_single,
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Local testnet config (multivalidator Alice + Bob)
|
||||
pub fn integration_test_config_with_two_authorities() -> ChainSpec {
|
||||
ChainSpec::from_genesis(
|
||||
"Integration Test",
|
||||
"test",
|
||||
local_testnet_genesis,
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_connectivity() {
|
||||
sc_service_test::connectivity(
|
||||
integration_test_config_with_two_authorities(),
|
||||
|config| new_full(config),
|
||||
|config| new_light(config),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
[package]
|
||||
name = "node-runtime"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
# third-party dependencies
|
||||
codec = { package = "parity-scale-codec", version = "1.0.6", default-features = false, features = ["derive"] }
|
||||
integer-sqrt = { version = "0.1.2" }
|
||||
safe-mix = { version = "1.0", default-features = false }
|
||||
rustc-hex = { version = "2.0", optional = true }
|
||||
serde = { version = "1.0.102", optional = true }
|
||||
|
||||
# primitives
|
||||
sp-authority-discovery = { version = "2.0.0", default-features = false, path = "../../../primitives/authority-discovery" }
|
||||
sp-consensus-babe = { version = "0.8", default-features = false, path = "../../../primitives/consensus/babe" }
|
||||
sp-block-builder = { path = "../../../primitives/block-builder", default-features = false}
|
||||
sp-inherents = { version = "2.0.0", default-features = false, path = "../../../primitives/inherents" }
|
||||
node-primitives = { version = "2.0.0", default-features = false, path = "../primitives" }
|
||||
sp-offchain = { version = "2.0.0", default-features = false, path = "../../../primitives/offchain" }
|
||||
sp-core = { version = "2.0.0", default-features = false, path = "../../../primitives/core" }
|
||||
sp-std = { version = "2.0.0", default-features = false, path = "../../../primitives/std" }
|
||||
sp-api = { version = "2.0.0", default-features = false, path = "../../../primitives/api" }
|
||||
sp-runtime = { version = "2.0.0", default-features = false, path = "../../../primitives/runtime" }
|
||||
sp-staking = { version = "2.0.0", default-features = false, path = "../../../primitives/staking" }
|
||||
sp-keyring = { version = "2.0.0", optional = true, path = "../../../primitives/keyring" }
|
||||
sp-session = { version = "2.0.0", default-features = false, path = "../../../primitives/session" }
|
||||
sp-transaction-pool = { version = "2.0.0", default-features = false, path = "../../../primitives/transaction-pool" }
|
||||
sp-version = { version = "2.0.0", default-features = false, path = "../../../primitives/version" }
|
||||
sp-bridge-eth-poa = { package = "sp-bridge-eth-poa", path = "../../../primitives/bridge-eth-poa", default-features = false }
|
||||
|
||||
# frame dependencies
|
||||
frame-executive = { version = "2.0.0", default-features = false, path = "../../../frame/executive" }
|
||||
frame-support = { version = "2.0.0", default-features = false, path = "../../../frame/support" }
|
||||
frame-system = { version = "2.0.0", default-features = false, path = "../../../frame/system" }
|
||||
frame-system-rpc-runtime-api = { version = "2.0.0", default-features = false, path = "../../../frame/system/rpc/runtime-api/" }
|
||||
pallet-authority-discovery = { version = "2.0.0", default-features = false, path = "../../../frame/authority-discovery" }
|
||||
pallet-authorship = { version = "2.0.0", default-features = false, path = "../../../frame/authorship" }
|
||||
pallet-babe = { version = "2.0.0", default-features = false, path = "../../../frame/babe" }
|
||||
pallet-balances = { version = "2.0.0", default-features = false, path = "../../../frame/balances" }
|
||||
pallet-collective = { version = "2.0.0", default-features = false, path = "../../../frame/collective" }
|
||||
pallet-contracts = { version = "2.0.0", default-features = false, path = "../../../frame/contracts" }
|
||||
pallet-contracts-rpc-runtime-api = { version = "2.0.0", default-features = false, path = "../../../frame/contracts/rpc/runtime-api/" }
|
||||
pallet-democracy = { version = "2.0.0", default-features = false, path = "../../../frame/democracy" }
|
||||
pallet-elections-phragmen = { version = "2.0.0", default-features = false, path = "../../../frame/elections-phragmen" }
|
||||
pallet-finality-tracker = { version = "2.0.0", default-features = false, path = "../../../frame/finality-tracker" }
|
||||
pallet-grandpa = { version = "2.0.0", default-features = false, path = "../../../frame/grandpa" }
|
||||
pallet-im-online = { version = "2.0.0", default-features = false, path = "../../../frame/im-online" }
|
||||
pallet-indices = { version = "2.0.0", default-features = false, path = "../../../frame/indices" }
|
||||
pallet-membership = { version = "2.0.0", default-features = false, path = "../../../frame/membership" }
|
||||
pallet-nicks = { version = "2.0.0", default-features = false, path = "../../../frame/nicks" }
|
||||
pallet-offences = { version = "2.0.0", default-features = false, path = "../../../frame/offences" }
|
||||
pallet-randomness-collective-flip = { version = "2.0.0", default-features = false, path = "../../../frame/randomness-collective-flip" }
|
||||
pallet-session = { version = "2.0.0", features = ["historical"], path = "../../../frame/session", default-features = false }
|
||||
pallet-staking = { version = "2.0.0", features = ["migrate"], path = "../../../frame/staking", default-features = false }
|
||||
pallet-staking-reward-curve = { version = "2.0.0", path = "../../../frame/staking/reward-curve" }
|
||||
pallet-sudo = { version = "2.0.0", default-features = false, path = "../../../frame/sudo" }
|
||||
pallet-timestamp = { version = "2.0.0", default-features = false, path = "../../../frame/timestamp" }
|
||||
pallet-treasury = { version = "2.0.0", default-features = false, path = "../../../frame/treasury" }
|
||||
pallet-utility = { version = "2.0.0", default-features = false, path = "../../../frame/utility" }
|
||||
pallet-transaction-payment = { version = "2.0.0", default-features = false, path = "../../../frame/transaction-payment" }
|
||||
pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0", default-features = false, path = "../../../frame/transaction-payment/rpc/runtime-api/" }
|
||||
# TODO: Update this
|
||||
bridge-eth-poa = { package = "pallet-bridge-eth-poa", path = "../../../frame/bridge-eth-poa", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
wasm-builder-runner = { version = "1.0.4", package = "substrate-wasm-builder-runner", path = "../../../utils/wasm-builder-runner" }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-io = { version = "2.0.0", path = "../../../primitives/io" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"sp-authority-discovery/std",
|
||||
"pallet-authority-discovery/std",
|
||||
"pallet-authorship/std",
|
||||
"sp-consensus-babe/std",
|
||||
"pallet-babe/std",
|
||||
"pallet-balances/std",
|
||||
"sp-block-builder/std",
|
||||
"sp-bridge-eth-poa/std",
|
||||
"bridge-eth-poa/std", # TODO: Update this
|
||||
"codec/std",
|
||||
"pallet-collective/std",
|
||||
"pallet-contracts-rpc-runtime-api/std",
|
||||
"pallet-contracts/std",
|
||||
"pallet-democracy/std",
|
||||
"pallet-elections-phragmen/std",
|
||||
"frame-executive/std",
|
||||
"pallet-finality-tracker/std",
|
||||
"pallet-grandpa/std",
|
||||
"pallet-im-online/std",
|
||||
"pallet-indices/std",
|
||||
"sp-inherents/std",
|
||||
"pallet-membership/std",
|
||||
"pallet-nicks/std",
|
||||
"node-primitives/std",
|
||||
"sp-offchain/std",
|
||||
"pallet-offences/std",
|
||||
"sp-core/std",
|
||||
"pallet-randomness-collective-flip/std",
|
||||
"sp-std/std",
|
||||
"rustc-hex",
|
||||
"safe-mix/std",
|
||||
"serde",
|
||||
"pallet-session/std",
|
||||
"sp-api/std",
|
||||
"sp-runtime/std",
|
||||
"sp-staking/std",
|
||||
"pallet-staking/std",
|
||||
"sp-keyring",
|
||||
"sp-session/std",
|
||||
"pallet-sudo/std",
|
||||
"frame-support/std",
|
||||
"frame-system-rpc-runtime-api/std",
|
||||
"frame-system/std",
|
||||
"pallet-timestamp/std",
|
||||
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||
"pallet-transaction-payment/std",
|
||||
"pallet-treasury/std",
|
||||
"sp-transaction-pool/std",
|
||||
"pallet-utility/std",
|
||||
"sp-version/std",
|
||||
]
|
||||
@@ -0,0 +1,836 @@
|
||||
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The Substrate runtime. This can be compiled with ``#[no_std]`, ready for Wasm.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
|
||||
#![recursion_limit="256"]
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use frame_support::{
|
||||
construct_runtime, parameter_types, debug,
|
||||
weights::Weight,
|
||||
traits::{SplitTwoWays, Currency, Randomness},
|
||||
};
|
||||
use sp_core::u32_trait::{_1, _2, _3, _4};
|
||||
use node_primitives::{AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, Moment, Signature};
|
||||
use sp_api::impl_runtime_apis;
|
||||
use sp_runtime::{
|
||||
Permill, Perbill, Percent, ApplyExtrinsicResult, impl_opaque_keys, generic, create_runtime_str
|
||||
};
|
||||
use sp_runtime::curve::PiecewiseLinear;
|
||||
use sp_runtime::transaction_validity::TransactionValidity;
|
||||
use sp_runtime::traits::{
|
||||
self, BlakeTwo256, Block as BlockT, StaticLookup, SaturatedConversion,
|
||||
OpaqueKeys,
|
||||
};
|
||||
use sp_version::RuntimeVersion;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use sp_version::NativeVersion;
|
||||
use sp_core::OpaqueMetadata;
|
||||
use pallet_grandpa::AuthorityList as GrandpaAuthorityList;
|
||||
use pallet_grandpa::fg_primitives;
|
||||
use pallet_im_online::sr25519::{AuthorityId as ImOnlineId};
|
||||
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||
use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
|
||||
use pallet_contracts_rpc_runtime_api::ContractExecResult;
|
||||
use frame_system::offchain::TransactionSubmitter;
|
||||
use sp_inherents::{InherentData, CheckInherentsResult};
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
pub use sp_runtime::BuildStorage;
|
||||
pub use pallet_timestamp::Call as TimestampCall;
|
||||
pub use pallet_balances::Call as BalancesCall;
|
||||
pub use pallet_contracts::Gas;
|
||||
pub use frame_support::StorageValue;
|
||||
pub use pallet_staking::StakerStatus;
|
||||
pub use bridge_eth_poa::Call as BridgeEthPoaCall; // TODO: Update name
|
||||
|
||||
/// Implementations of some helper traits passed into runtime modules as associated types.
|
||||
pub mod impls;
|
||||
use impls::{CurrencyToVoteHandler, Author, LinearWeightToFee, TargetedFeeAdjustment};
|
||||
|
||||
/// Constant values used within the runtime.
|
||||
pub mod constants;
|
||||
use constants::{time::*, currency::*};
|
||||
|
||||
// Make the WASM binary available.
|
||||
#[cfg(feature = "std")]
|
||||
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
||||
|
||||
/// Runtime version.
|
||||
pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: create_runtime_str!("node"),
|
||||
impl_name: create_runtime_str!("substrate-node"),
|
||||
authoring_version: 10,
|
||||
// Per convention: if the runtime behavior changes, increment spec_version
|
||||
// and set impl_version to equal spec_version. If only runtime
|
||||
// implementation changes and behavior does not, then leave spec_version as
|
||||
// is and increment impl_version.
|
||||
spec_version: 202,
|
||||
impl_version: 202,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
/// Native version.
|
||||
#[cfg(any(feature = "std", test))]
|
||||
pub fn native_version() -> NativeVersion {
|
||||
NativeVersion {
|
||||
runtime_version: VERSION,
|
||||
can_author_with: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
type NegativeImbalance = <Balances as Currency<AccountId>>::NegativeImbalance;
|
||||
|
||||
pub type DealWithFees = SplitTwoWays<
|
||||
Balance,
|
||||
NegativeImbalance,
|
||||
_4, Treasury, // 4 parts (80%) goes to the treasury.
|
||||
_1, Author, // 1 part (20%) goes to the block author.
|
||||
>;
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: BlockNumber = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1_000_000_000;
|
||||
pub const MaximumBlockLength: u32 = 5 * 1024 * 1024;
|
||||
pub const Version: RuntimeVersion = VERSION;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
|
||||
}
|
||||
|
||||
impl frame_system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
type Index = Index;
|
||||
type BlockNumber = BlockNumber;
|
||||
type Hash = Hash;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = Indices;
|
||||
type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
type Version = Version;
|
||||
type ModuleToIndex = ModuleToIndex;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
// One storage item; value is size 4+4+16+32 bytes = 56 bytes.
|
||||
pub const MultisigDepositBase: Balance = 30 * CENTS;
|
||||
// Additional storage item size of 32 bytes.
|
||||
pub const MultisigDepositFactor: Balance = 5 * CENTS;
|
||||
pub const MaxSignatories: u16 = 100;
|
||||
}
|
||||
|
||||
impl pallet_utility::Trait for Runtime {
|
||||
type Event = Event;
|
||||
type Call = Call;
|
||||
type Currency = Balances;
|
||||
type MultisigDepositBase = MultisigDepositBase;
|
||||
type MultisigDepositFactor = MultisigDepositFactor;
|
||||
type MaxSignatories = MaxSignatories;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const EpochDuration: u64 = EPOCH_DURATION_IN_SLOTS;
|
||||
pub const ExpectedBlockTime: Moment = MILLISECS_PER_BLOCK;
|
||||
}
|
||||
|
||||
impl pallet_babe::Trait for Runtime {
|
||||
type EpochDuration = EpochDuration;
|
||||
type ExpectedBlockTime = ExpectedBlockTime;
|
||||
type EpochChangeTrigger = pallet_babe::ExternalTrigger;
|
||||
}
|
||||
|
||||
impl pallet_indices::Trait for Runtime {
|
||||
type AccountIndex = AccountIndex;
|
||||
type IsDeadAccount = Balances;
|
||||
type ResolveHint = pallet_indices::SimpleResolveHint<Self::AccountId, Self::AccountIndex>;
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: Balance = 1 * DOLLARS;
|
||||
pub const TransferFee: Balance = 1 * CENTS;
|
||||
pub const CreationFee: Balance = 1 * CENTS;
|
||||
}
|
||||
|
||||
impl pallet_balances::Trait for Runtime {
|
||||
type Balance = Balance;
|
||||
type OnFreeBalanceZero = ((Staking, Contracts), Session);
|
||||
type OnReapAccount = System;
|
||||
type OnNewAccount = Indices;
|
||||
type Event = Event;
|
||||
type DustRemoval = ();
|
||||
type TransferPayment = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type TransferFee = TransferFee;
|
||||
type CreationFee = CreationFee;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const TransactionBaseFee: Balance = 1 * CENTS;
|
||||
pub const TransactionByteFee: Balance = 10 * MILLICENTS;
|
||||
// setting this to zero will disable the weight fee.
|
||||
pub const WeightFeeCoefficient: Balance = 1_000;
|
||||
// for a sane configuration, this should always be less than `AvailableBlockRatio`.
|
||||
pub const TargetBlockFullness: Perbill = Perbill::from_percent(25);
|
||||
}
|
||||
|
||||
impl pallet_transaction_payment::Trait for Runtime {
|
||||
type Currency = Balances;
|
||||
type OnTransactionPayment = DealWithFees;
|
||||
type TransactionBaseFee = TransactionBaseFee;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
type WeightToFee = LinearWeightToFee<WeightFeeCoefficient>;
|
||||
type FeeMultiplierUpdate = TargetedFeeAdjustment<TargetBlockFullness>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MinimumPeriod: Moment = SLOT_DURATION / 2;
|
||||
}
|
||||
impl pallet_timestamp::Trait for Runtime {
|
||||
type Moment = Moment;
|
||||
type OnTimestampSet = Babe;
|
||||
type MinimumPeriod = MinimumPeriod;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const UncleGenerations: BlockNumber = 5;
|
||||
}
|
||||
|
||||
impl pallet_authorship::Trait for Runtime {
|
||||
type FindAuthor = pallet_session::FindAccountFromAuthorIndex<Self, Babe>;
|
||||
type UncleGenerations = UncleGenerations;
|
||||
type FilterUncle = ();
|
||||
type EventHandler = (Staking, ImOnline);
|
||||
}
|
||||
|
||||
impl_opaque_keys! {
|
||||
pub struct SessionKeys {
|
||||
pub grandpa: Grandpa,
|
||||
pub babe: Babe,
|
||||
pub im_online: ImOnline,
|
||||
pub authority_discovery: AuthorityDiscovery,
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17);
|
||||
}
|
||||
|
||||
impl pallet_session::Trait for Runtime {
|
||||
type OnSessionEnding = Staking;
|
||||
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type ShouldEndSession = Babe;
|
||||
type Event = Event;
|
||||
type Keys = SessionKeys;
|
||||
type ValidatorId = <Self as frame_system::Trait>::AccountId;
|
||||
type ValidatorIdOf = pallet_staking::StashOf<Self>;
|
||||
type SelectInitialValidators = Staking;
|
||||
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Trait for Runtime {
|
||||
type FullIdentification = pallet_staking::Exposure<AccountId, Balance>;
|
||||
type FullIdentificationOf = pallet_staking::ExposureOf<Runtime>;
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
const REWARD_CURVE: PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_025_000,
|
||||
max_inflation: 0_100_000,
|
||||
ideal_stake: 0_500_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 40,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const SessionsPerEra: sp_staking::SessionIndex = 6;
|
||||
pub const BondingDuration: pallet_staking::EraIndex = 24 * 28;
|
||||
pub const SlashDeferDuration: pallet_staking::EraIndex = 24 * 7; // 1/4 the bonding duration.
|
||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||
}
|
||||
|
||||
impl pallet_staking::Trait for Runtime {
|
||||
type Currency = Balances;
|
||||
type Time = Timestamp;
|
||||
type CurrencyToVote = CurrencyToVoteHandler;
|
||||
type RewardRemainder = Treasury;
|
||||
type Event = Event;
|
||||
type Slash = Treasury; // send the slashed funds to the treasury.
|
||||
type Reward = (); // rewards are minted from the void
|
||||
type SessionsPerEra = SessionsPerEra;
|
||||
type BondingDuration = BondingDuration;
|
||||
type SlashDeferDuration = SlashDeferDuration;
|
||||
/// A super-majority of the council can cancel the slash.
|
||||
type SlashCancelOrigin = pallet_collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>;
|
||||
type SessionInterface = Self;
|
||||
type RewardCurve = RewardCurve;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES;
|
||||
pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES;
|
||||
pub const EmergencyVotingPeriod: BlockNumber = 3 * 24 * 60 * MINUTES;
|
||||
pub const MinimumDeposit: Balance = 100 * DOLLARS;
|
||||
pub const EnactmentPeriod: BlockNumber = 30 * 24 * 60 * MINUTES;
|
||||
pub const CooloffPeriod: BlockNumber = 28 * 24 * 60 * MINUTES;
|
||||
// One cent: $10,000 / MB
|
||||
pub const PreimageByteDeposit: Balance = 1 * CENTS;
|
||||
}
|
||||
|
||||
impl pallet_democracy::Trait for Runtime {
|
||||
type Proposal = Call;
|
||||
type Event = Event;
|
||||
type Currency = Balances;
|
||||
type EnactmentPeriod = EnactmentPeriod;
|
||||
type LaunchPeriod = LaunchPeriod;
|
||||
type VotingPeriod = VotingPeriod;
|
||||
type EmergencyVotingPeriod = EmergencyVotingPeriod;
|
||||
type MinimumDeposit = MinimumDeposit;
|
||||
/// A straight majority of the council can decide what their next motion is.
|
||||
type ExternalOrigin = pallet_collective::EnsureProportionAtLeast<_1, _2, AccountId, CouncilCollective>;
|
||||
/// A super-majority can have the next scheduled referendum be a straight majority-carries vote.
|
||||
type ExternalMajorityOrigin = pallet_collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>;
|
||||
/// A unanimous council can have the next scheduled referendum be a straight default-carries
|
||||
/// (NTB) vote.
|
||||
type ExternalDefaultOrigin = pallet_collective::EnsureProportionAtLeast<_1, _1, AccountId, CouncilCollective>;
|
||||
/// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote
|
||||
/// be tabled immediately and with a shorter voting/enactment period.
|
||||
type FastTrackOrigin = pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, TechnicalCollective>;
|
||||
// To cancel a proposal which has been passed, 2/3 of the council must agree to it.
|
||||
type CancellationOrigin = pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, CouncilCollective>;
|
||||
// Any single technical committee member may veto a coming council proposal, however they can
|
||||
// only do it once and it lasts only for the cooloff period.
|
||||
type VetoOrigin = pallet_collective::EnsureMember<AccountId, TechnicalCollective>;
|
||||
type CooloffPeriod = CooloffPeriod;
|
||||
type PreimageByteDeposit = PreimageByteDeposit;
|
||||
type Slash = Treasury;
|
||||
}
|
||||
|
||||
type CouncilCollective = pallet_collective::Instance1;
|
||||
impl pallet_collective::Trait<CouncilCollective> for Runtime {
|
||||
type Origin = Origin;
|
||||
type Proposal = Call;
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const CandidacyBond: Balance = 10 * DOLLARS;
|
||||
pub const VotingBond: Balance = 1 * DOLLARS;
|
||||
pub const TermDuration: BlockNumber = 7 * DAYS;
|
||||
pub const DesiredMembers: u32 = 13;
|
||||
pub const DesiredRunnersUp: u32 = 7;
|
||||
}
|
||||
|
||||
impl pallet_elections_phragmen::Trait for Runtime {
|
||||
type Event = Event;
|
||||
type Currency = Balances;
|
||||
type CurrencyToVote = CurrencyToVoteHandler;
|
||||
type CandidacyBond = CandidacyBond;
|
||||
type VotingBond = VotingBond;
|
||||
type TermDuration = TermDuration;
|
||||
type DesiredMembers = DesiredMembers;
|
||||
type DesiredRunnersUp = DesiredRunnersUp;
|
||||
type LoserCandidate = ();
|
||||
type BadReport = ();
|
||||
type KickedMember = ();
|
||||
type ChangeMembers = Council;
|
||||
}
|
||||
|
||||
type TechnicalCollective = pallet_collective::Instance2;
|
||||
impl pallet_collective::Trait<TechnicalCollective> for Runtime {
|
||||
type Origin = Origin;
|
||||
type Proposal = Call;
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl pallet_membership::Trait<pallet_membership::Instance1> for Runtime {
|
||||
type Event = Event;
|
||||
type AddOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>;
|
||||
type RemoveOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>;
|
||||
type SwapOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>;
|
||||
type ResetOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>;
|
||||
type MembershipInitialized = TechnicalCommittee;
|
||||
type MembershipChanged = TechnicalCommittee;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ProposalBond: Permill = Permill::from_percent(5);
|
||||
pub const ProposalBondMinimum: Balance = 1 * DOLLARS;
|
||||
pub const SpendPeriod: BlockNumber = 1 * DAYS;
|
||||
pub const Burn: Permill = Permill::from_percent(50);
|
||||
pub const TipCountdown: BlockNumber = 1 * DAYS;
|
||||
pub const TipFindersFee: Percent = Percent::from_percent(20);
|
||||
pub const TipReportDepositBase: Balance = 1 * DOLLARS;
|
||||
pub const TipReportDepositPerByte: Balance = 1 * CENTS;
|
||||
}
|
||||
|
||||
impl pallet_treasury::Trait for Runtime {
|
||||
type Currency = Balances;
|
||||
type ApproveOrigin = pallet_collective::EnsureMembers<_4, AccountId, CouncilCollective>;
|
||||
type RejectOrigin = pallet_collective::EnsureMembers<_2, AccountId, CouncilCollective>;
|
||||
type Event = Event;
|
||||
type ProposalRejection = ();
|
||||
type ProposalBond = ProposalBond;
|
||||
type ProposalBondMinimum = ProposalBondMinimum;
|
||||
type SpendPeriod = SpendPeriod;
|
||||
type Burn = Burn;
|
||||
type Tippers = Elections;
|
||||
type TipCountdown = TipCountdown;
|
||||
type TipFindersFee = TipFindersFee;
|
||||
type TipReportDepositBase = TipReportDepositBase;
|
||||
type TipReportDepositPerByte = TipReportDepositPerByte;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ContractTransferFee: Balance = 1 * CENTS;
|
||||
pub const ContractCreationFee: Balance = 1 * CENTS;
|
||||
pub const ContractTransactionBaseFee: Balance = 1 * CENTS;
|
||||
pub const ContractTransactionByteFee: Balance = 10 * MILLICENTS;
|
||||
pub const ContractFee: Balance = 1 * CENTS;
|
||||
pub const TombstoneDeposit: Balance = 1 * DOLLARS;
|
||||
pub const RentByteFee: Balance = 1 * DOLLARS;
|
||||
pub const RentDepositOffset: Balance = 1000 * DOLLARS;
|
||||
pub const SurchargeReward: Balance = 150 * DOLLARS;
|
||||
}
|
||||
|
||||
impl pallet_contracts::Trait for Runtime {
|
||||
type Currency = Balances;
|
||||
type Time = Timestamp;
|
||||
type Randomness = RandomnessCollectiveFlip;
|
||||
type Call = Call;
|
||||
type Event = Event;
|
||||
type DetermineContractAddress = pallet_contracts::SimpleAddressDeterminator<Runtime>;
|
||||
type ComputeDispatchFee = pallet_contracts::DefaultDispatchFeeComputor<Runtime>;
|
||||
type TrieIdGenerator = pallet_contracts::TrieIdFromParentCounter<Runtime>;
|
||||
type GasPayment = ();
|
||||
type RentPayment = ();
|
||||
type SignedClaimHandicap = pallet_contracts::DefaultSignedClaimHandicap;
|
||||
type TombstoneDeposit = TombstoneDeposit;
|
||||
type StorageSizeOffset = pallet_contracts::DefaultStorageSizeOffset;
|
||||
type RentByteFee = RentByteFee;
|
||||
type RentDepositOffset = RentDepositOffset;
|
||||
type SurchargeReward = SurchargeReward;
|
||||
type TransferFee = ContractTransferFee;
|
||||
type CreationFee = ContractCreationFee;
|
||||
type TransactionBaseFee = ContractTransactionBaseFee;
|
||||
type TransactionByteFee = ContractTransactionByteFee;
|
||||
type ContractFee = ContractFee;
|
||||
type CallBaseFee = pallet_contracts::DefaultCallBaseFee;
|
||||
type InstantiateBaseFee = pallet_contracts::DefaultInstantiateBaseFee;
|
||||
type MaxDepth = pallet_contracts::DefaultMaxDepth;
|
||||
type MaxValueSize = pallet_contracts::DefaultMaxValueSize;
|
||||
type BlockGasLimit = pallet_contracts::DefaultBlockGasLimit;
|
||||
}
|
||||
|
||||
impl pallet_sudo::Trait for Runtime {
|
||||
type Event = Event;
|
||||
type Proposal = Call;
|
||||
}
|
||||
|
||||
/// A runtime transaction submitter.
|
||||
pub type SubmitTransaction = TransactionSubmitter<ImOnlineId, Runtime, UncheckedExtrinsic>;
|
||||
|
||||
parameter_types! {
|
||||
pub const SessionDuration: BlockNumber = EPOCH_DURATION_IN_SLOTS as _;
|
||||
}
|
||||
|
||||
impl pallet_im_online::Trait for Runtime {
|
||||
type AuthorityId = ImOnlineId;
|
||||
type Call = Call;
|
||||
type Event = Event;
|
||||
type SubmitTransaction = SubmitTransaction;
|
||||
type ReportUnresponsiveness = Offences;
|
||||
type SessionDuration = SessionDuration;
|
||||
}
|
||||
|
||||
impl pallet_offences::Trait for Runtime {
|
||||
type Event = Event;
|
||||
type IdentificationTuple = pallet_session::historical::IdentificationTuple<Self>;
|
||||
type OnOffenceHandler = Staking;
|
||||
}
|
||||
|
||||
impl pallet_authority_discovery::Trait for Runtime {}
|
||||
|
||||
impl pallet_grandpa::Trait for Runtime {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const WindowSize: BlockNumber = 101;
|
||||
pub const ReportLatency: BlockNumber = 1000;
|
||||
}
|
||||
|
||||
impl pallet_finality_tracker::Trait for Runtime {
|
||||
type OnFinalizationStalled = Grandpa;
|
||||
type WindowSize = WindowSize;
|
||||
type ReportLatency = ReportLatency;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ReservationFee: Balance = 1 * DOLLARS;
|
||||
pub const MinLength: usize = 3;
|
||||
pub const MaxLength: usize = 16;
|
||||
}
|
||||
|
||||
impl pallet_nicks::Trait for Runtime {
|
||||
type Event = Event;
|
||||
type Currency = Balances;
|
||||
type ReservationFee = ReservationFee;
|
||||
type Slashed = Treasury;
|
||||
type ForceOrigin = pallet_collective::EnsureMember<AccountId, CouncilCollective>;
|
||||
type MinLength = MinLength;
|
||||
type MaxLength = MaxLength;
|
||||
}
|
||||
|
||||
impl frame_system::offchain::CreateTransaction<Runtime, UncheckedExtrinsic> for Runtime {
|
||||
type Public = <Signature as traits::Verify>::Signer;
|
||||
type Signature = Signature;
|
||||
|
||||
fn create_transaction<TSigner: frame_system::offchain::Signer<Self::Public, Self::Signature>>(
|
||||
call: Call,
|
||||
public: Self::Public,
|
||||
account: AccountId,
|
||||
index: Index,
|
||||
) -> Option<(Call, <UncheckedExtrinsic as traits::Extrinsic>::SignaturePayload)> {
|
||||
// take the biggest period possible.
|
||||
let period = BlockHashCount::get()
|
||||
.checked_next_power_of_two()
|
||||
.map(|c| c / 2)
|
||||
.unwrap_or(2) as u64;
|
||||
let current_block = System::block_number()
|
||||
.saturated_into::<u64>()
|
||||
// The `System::block_number` is initialized with `n+1`,
|
||||
// so the actual block number is `n`.
|
||||
.saturating_sub(1);
|
||||
let tip = 0;
|
||||
let extra: SignedExtra = (
|
||||
frame_system::CheckVersion::<Runtime>::new(),
|
||||
frame_system::CheckGenesis::<Runtime>::new(),
|
||||
frame_system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)),
|
||||
frame_system::CheckNonce::<Runtime>::from(index),
|
||||
frame_system::CheckWeight::<Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
|
||||
Default::default(),
|
||||
);
|
||||
let raw_payload = SignedPayload::new(call, extra).map_err(|e| {
|
||||
debug::warn!("Unable to create signed payload: {:?}", e);
|
||||
}).ok()?;
|
||||
let signature = TSigner::sign(public, &raw_payload)?;
|
||||
let address = Indices::unlookup(account);
|
||||
let (call, extra, _) = raw_payload.deconstruct();
|
||||
Some((call, (address, signature, extra)))
|
||||
}
|
||||
}
|
||||
|
||||
impl bridge_eth_poa::Trait for Runtime {
|
||||
type OnHeadersSubmitted = ();
|
||||
}
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Runtime where
|
||||
Block = Block,
|
||||
NodeBlock = node_primitives::Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic
|
||||
{
|
||||
System: frame_system::{Module, Call, Storage, Config, Event},
|
||||
Utility: pallet_utility::{Module, Call, Storage, Event<T>},
|
||||
Babe: pallet_babe::{Module, Call, Storage, Config, Inherent(Timestamp)},
|
||||
Timestamp: pallet_timestamp::{Module, Call, Storage, Inherent},
|
||||
Authorship: pallet_authorship::{Module, Call, Storage, Inherent},
|
||||
Indices: pallet_indices,
|
||||
Balances: pallet_balances,
|
||||
TransactionPayment: pallet_transaction_payment::{Module, Storage},
|
||||
Staking: pallet_staking,
|
||||
Session: pallet_session::{Module, Call, Storage, Event, Config<T>},
|
||||
Democracy: pallet_democracy::{Module, Call, Storage, Config, Event<T>},
|
||||
Council: pallet_collective::<Instance1>::{Module, Call, Storage, Origin<T>, Event<T>, Config<T>},
|
||||
TechnicalCommittee: pallet_collective::<Instance2>::{Module, Call, Storage, Origin<T>, Event<T>, Config<T>},
|
||||
Elections: pallet_elections_phragmen::{Module, Call, Storage, Event<T>},
|
||||
TechnicalMembership: pallet_membership::<Instance1>::{Module, Call, Storage, Event<T>, Config<T>},
|
||||
FinalityTracker: pallet_finality_tracker::{Module, Call, Inherent},
|
||||
Grandpa: pallet_grandpa::{Module, Call, Storage, Config, Event},
|
||||
Treasury: pallet_treasury::{Module, Call, Storage, Config, Event<T>},
|
||||
Contracts: pallet_contracts,
|
||||
Sudo: pallet_sudo,
|
||||
ImOnline: pallet_im_online::{Module, Call, Storage, Event<T>, ValidateUnsigned, Config<T>},
|
||||
AuthorityDiscovery: pallet_authority_discovery::{Module, Call, Config},
|
||||
Offences: pallet_offences::{Module, Call, Storage, Event},
|
||||
RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage},
|
||||
Nicks: pallet_nicks::{Module, Call, Storage, Event<T>},
|
||||
BridgeEthPoa: bridge_eth_poa::{Module, Call, Config}, // TODO: Update this
|
||||
}
|
||||
);
|
||||
|
||||
/// The address format for describing accounts.
|
||||
pub type Address = <Indices as StaticLookup>::Source;
|
||||
/// Block header type as expected by this runtime.
|
||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
/// Block type as expected by this runtime.
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
/// A Block signed with a Justification
|
||||
pub type SignedBlock = generic::SignedBlock<Block>;
|
||||
/// BlockId type as expected by this runtime.
|
||||
pub type BlockId = generic::BlockId<Block>;
|
||||
/// The SignedExtension to the basic transaction logic.
|
||||
pub type SignedExtra = (
|
||||
frame_system::CheckVersion<Runtime>,
|
||||
frame_system::CheckGenesis<Runtime>,
|
||||
frame_system::CheckEra<Runtime>,
|
||||
frame_system::CheckNonce<Runtime>,
|
||||
frame_system::CheckWeight<Runtime>,
|
||||
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
|
||||
pallet_contracts::CheckBlockGasLimit<Runtime>,
|
||||
);
|
||||
/// Unchecked extrinsic type as expected by this runtime.
|
||||
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
|
||||
/// The payload being signed in transactions.
|
||||
pub type SignedPayload = generic::SignedPayload<Call, SignedExtra>;
|
||||
/// Extrinsic type that has already been checked.
|
||||
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Call, SignedExtra>;
|
||||
/// Executive: handles dispatch to the various modules.
|
||||
pub type Executive = frame_executive::Executive<Runtime, Block, frame_system::ChainContext<Runtime>, Runtime, AllModules>;
|
||||
|
||||
impl_runtime_apis! {
|
||||
impl sp_api::Core<Block> for Runtime {
|
||||
fn version() -> RuntimeVersion {
|
||||
VERSION
|
||||
}
|
||||
|
||||
fn execute_block(block: Block) {
|
||||
Executive::execute_block(block)
|
||||
}
|
||||
|
||||
fn initialize_block(header: &<Block as BlockT>::Header) {
|
||||
Executive::initialize_block(header)
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_api::Metadata<Block> for Runtime {
|
||||
fn metadata() -> OpaqueMetadata {
|
||||
Runtime::metadata().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_block_builder::BlockBuilder<Block> for Runtime {
|
||||
fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
|
||||
Executive::apply_extrinsic(extrinsic)
|
||||
}
|
||||
|
||||
fn finalize_block() -> <Block as BlockT>::Header {
|
||||
Executive::finalize_block()
|
||||
}
|
||||
|
||||
fn inherent_extrinsics(data: InherentData) -> Vec<<Block as BlockT>::Extrinsic> {
|
||||
data.create_extrinsics()
|
||||
}
|
||||
|
||||
fn check_inherents(block: Block, data: InherentData) -> CheckInherentsResult {
|
||||
data.check_extrinsics(&block)
|
||||
}
|
||||
|
||||
fn random_seed() -> <Block as BlockT>::Hash {
|
||||
RandomnessCollectiveFlip::random_seed()
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
|
||||
fn validate_transaction(tx: <Block as BlockT>::Extrinsic) -> TransactionValidity {
|
||||
Executive::validate_transaction(tx)
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_offchain::OffchainWorkerApi<Block> for Runtime {
|
||||
fn offchain_worker(header: &<Block as BlockT>::Header) {
|
||||
Executive::offchain_worker(header)
|
||||
}
|
||||
}
|
||||
|
||||
impl fg_primitives::GrandpaApi<Block> for Runtime {
|
||||
fn grandpa_authorities() -> GrandpaAuthorityList {
|
||||
Grandpa::grandpa_authorities()
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_consensus_babe::BabeApi<Block> for Runtime {
|
||||
fn configuration() -> sp_consensus_babe::BabeConfiguration {
|
||||
// The choice of `c` parameter (where `1 - c` represents the
|
||||
// probability of a slot being empty), is done in accordance to the
|
||||
// slot duration and expected target block time, for safely
|
||||
// resisting network delays of maximum two seconds.
|
||||
// <https://research.web3.foundation/en/latest/polkadot/BABE/Babe/#6-practical-results>
|
||||
sp_consensus_babe::BabeConfiguration {
|
||||
slot_duration: Babe::slot_duration(),
|
||||
epoch_length: EpochDuration::get(),
|
||||
c: PRIMARY_PROBABILITY,
|
||||
genesis_authorities: Babe::authorities(),
|
||||
randomness: Babe::randomness(),
|
||||
secondary_slots: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_authority_discovery::AuthorityDiscoveryApi<Block> for Runtime {
|
||||
fn authorities() -> Vec<AuthorityDiscoveryId> {
|
||||
AuthorityDiscovery::authorities()
|
||||
}
|
||||
}
|
||||
|
||||
impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Index> for Runtime {
|
||||
fn account_nonce(account: AccountId) -> Index {
|
||||
System::account_nonce(account)
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance> for Runtime {
|
||||
fn call(
|
||||
origin: AccountId,
|
||||
dest: AccountId,
|
||||
value: Balance,
|
||||
gas_limit: u64,
|
||||
input_data: Vec<u8>,
|
||||
) -> ContractExecResult {
|
||||
let exec_result = Contracts::bare_call(
|
||||
origin,
|
||||
dest.into(),
|
||||
value,
|
||||
gas_limit,
|
||||
input_data,
|
||||
);
|
||||
match exec_result {
|
||||
Ok(v) => ContractExecResult::Success {
|
||||
status: v.status,
|
||||
data: v.data,
|
||||
},
|
||||
Err(_) => ContractExecResult::Error,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_storage(
|
||||
address: AccountId,
|
||||
key: [u8; 32],
|
||||
) -> pallet_contracts_rpc_runtime_api::GetStorageResult {
|
||||
Contracts::get_storage(address, key).map_err(|rpc_err| {
|
||||
use pallet_contracts::GetStorageError;
|
||||
use pallet_contracts_rpc_runtime_api::{GetStorageError as RpcGetStorageError};
|
||||
/// Map the contract error into the RPC layer error.
|
||||
match rpc_err {
|
||||
GetStorageError::ContractDoesntExist => RpcGetStorageError::ContractDoesntExist,
|
||||
GetStorageError::IsTombstone => RpcGetStorageError::IsTombstone,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<
|
||||
Block,
|
||||
Balance,
|
||||
UncheckedExtrinsic,
|
||||
> for Runtime {
|
||||
fn query_info(uxt: UncheckedExtrinsic, len: u32) -> RuntimeDispatchInfo<Balance> {
|
||||
TransactionPayment::query_info(uxt, len)
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_session::SessionKeys<Block> for Runtime {
|
||||
fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
|
||||
SessionKeys::generate(seed)
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_bridge_eth_poa::EthereumHeadersApi<Block> for Runtime {
|
||||
fn best_block() -> (u64, sp_bridge_eth_poa::H256) {
|
||||
BridgeEthPoa::best_block()
|
||||
}
|
||||
|
||||
fn is_import_requires_receipts(header: sp_bridge_eth_poa::Header) -> bool {
|
||||
BridgeEthPoa::is_import_requires_receipts(header)
|
||||
}
|
||||
|
||||
fn is_known_block(hash: sp_bridge_eth_poa::H256) -> bool {
|
||||
BridgeEthPoa::is_known_block(hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frame_system::offchain::{SignAndSubmitTransaction, SubmitSignedTransaction};
|
||||
|
||||
#[test]
|
||||
fn validate_transaction_submitter_bounds() {
|
||||
fn is_submit_signed_transaction<T>() where
|
||||
T: SubmitSignedTransaction<
|
||||
Runtime,
|
||||
Call,
|
||||
>,
|
||||
{}
|
||||
|
||||
fn is_sign_and_submit_transaction<T>() where
|
||||
T: SignAndSubmitTransaction<
|
||||
Runtime,
|
||||
Call,
|
||||
Extrinsic=UncheckedExtrinsic,
|
||||
CreateTransaction=Runtime,
|
||||
Signer=ImOnlineId,
|
||||
>,
|
||||
{}
|
||||
|
||||
is_submit_signed_transaction::<SubmitTransaction>();
|
||||
is_sign_and_submit_transaction::<SubmitTransaction>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_hooks_weight_should_not_exceed_limits() {
|
||||
use frame_support::weights::WeighBlock;
|
||||
let check_for_block = |b| {
|
||||
let block_hooks_weight =
|
||||
<AllModules as WeighBlock<BlockNumber>>::on_initialize(b) +
|
||||
<AllModules as WeighBlock<BlockNumber>>::on_finalize(b);
|
||||
|
||||
assert_eq!(
|
||||
block_hooks_weight,
|
||||
0,
|
||||
"This test might fail simply because the value being compared to has increased to a \
|
||||
module declaring a new weight for a hook or call. In this case update the test and \
|
||||
happily move on.",
|
||||
);
|
||||
|
||||
// Invariant. Always must be like this to have a sane chain.
|
||||
assert!(block_hooks_weight < MaximumBlockWeight::get());
|
||||
|
||||
// Warning.
|
||||
if block_hooks_weight > MaximumBlockWeight::get() / 2 {
|
||||
println!(
|
||||
"block hooks weight is consuming more than a block's capacity. You probably want \
|
||||
to re-think this. This test will fail now."
|
||||
);
|
||||
assert!(false);
|
||||
}
|
||||
};
|
||||
|
||||
let _ = (0..100_000).for_each(check_for_block);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "node-testing"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Test utilities for Substrate node."
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
pallet-balances = { version = "2.0.0", path = "../../../frame/balances" }
|
||||
sc-client = { version = "2.0.0", path = "../../../client/" }
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0" }
|
||||
pallet-contracts = { version = "2.0.0", path = "../../../frame/contracts" }
|
||||
pallet-grandpa = { version = "2.0.0", path = "../../../frame/grandpa" }
|
||||
pallet-indices = { version = "2.0.0", path = "../../../frame/indices" }
|
||||
sp-keyring = { version = "2.0.0", path = "../../../primitives/keyring" }
|
||||
node-executor = { version = "2.0.0", path = "../executor" }
|
||||
node-primitives = { version = "2.0.0", path = "../primitives" }
|
||||
node-runtime = { version = "2.0.0", path = "../runtime" }
|
||||
sp-core = { version = "2.0.0", path = "../../../primitives/core" }
|
||||
sp-io = { version = "2.0.0", path = "../../../primitives/io" }
|
||||
frame-support = { version = "2.0.0", path = "../../../frame/support" }
|
||||
pallet-session = { version = "2.0.0", path = "../../../frame/session" }
|
||||
sp-runtime = { version = "2.0.0", path = "../../../primitives/runtime" }
|
||||
pallet-staking = { version = "2.0.0", path = "../../../frame/staking" }
|
||||
sc-executor = { version = "2.0.0", path = "../../../client/executor" }
|
||||
frame-system = { version = "2.0.0", path = "../../../frame/system" }
|
||||
substrate-test-client = { version = "2.0.0", path = "../../../test-utils/client" }
|
||||
pallet-timestamp = { version = "2.0.0", path = "../../../frame/timestamp" }
|
||||
pallet-transaction-payment = { version = "2.0.0", path = "../../../frame/transaction-payment" }
|
||||
pallet-treasury = { version = "2.0.0", path = "../../../frame/treasury" }
|
||||
wabt = "0.9.2"
|
||||
sp-bridge-eth-poa = { package = "sp-bridge-eth-poa", path = "../../../primitives/bridge-eth-poa" }
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Genesis Configuration.
|
||||
|
||||
use crate::keyring::*;
|
||||
use sp_keyring::{Ed25519Keyring, Sr25519Keyring};
|
||||
use node_runtime::{
|
||||
GenesisConfig, BalancesConfig, SessionConfig, StakingConfig, SystemConfig,
|
||||
GrandpaConfig, IndicesConfig, ContractsConfig, WASM_BINARY, BridgeEthPoaConfig,
|
||||
};
|
||||
use node_runtime::constants::currency::*;
|
||||
use sp_core::ChangesTrieConfiguration;
|
||||
use sp_runtime::Perbill;
|
||||
|
||||
|
||||
/// Create genesis runtime configuration for tests.
|
||||
pub fn config(support_changes_trie: bool, code: Option<&[u8]>) -> GenesisConfig {
|
||||
GenesisConfig {
|
||||
frame_system: Some(SystemConfig {
|
||||
changes_trie_config: if support_changes_trie { Some(ChangesTrieConfiguration {
|
||||
digest_interval: 2,
|
||||
digest_levels: 2,
|
||||
}) } else { None },
|
||||
code: code.map(|x| x.to_vec()).unwrap_or_else(|| WASM_BINARY.to_vec()),
|
||||
}),
|
||||
pallet_indices: Some(IndicesConfig {
|
||||
ids: vec![alice(), bob(), charlie(), dave(), eve(), ferdie()],
|
||||
}),
|
||||
pallet_balances: Some(BalancesConfig {
|
||||
balances: vec![
|
||||
(alice(), 111 * DOLLARS),
|
||||
(bob(), 100 * DOLLARS),
|
||||
(charlie(), 100_000_000 * DOLLARS),
|
||||
(dave(), 111 * DOLLARS),
|
||||
(eve(), 101 * DOLLARS),
|
||||
(ferdie(), 100 * DOLLARS),
|
||||
],
|
||||
vesting: vec![],
|
||||
}),
|
||||
pallet_session: Some(SessionConfig {
|
||||
keys: vec![
|
||||
(alice(), to_session_keys(
|
||||
&Ed25519Keyring::Alice,
|
||||
&Sr25519Keyring::Alice,
|
||||
)),
|
||||
(bob(), to_session_keys(
|
||||
&Ed25519Keyring::Bob,
|
||||
&Sr25519Keyring::Bob,
|
||||
)),
|
||||
(charlie(), to_session_keys(
|
||||
&Ed25519Keyring::Charlie,
|
||||
&Sr25519Keyring::Charlie,
|
||||
)),
|
||||
]
|
||||
}),
|
||||
pallet_staking: Some(StakingConfig {
|
||||
current_era: 0,
|
||||
stakers: vec![
|
||||
(dave(), alice(), 111 * DOLLARS, pallet_staking::StakerStatus::Validator),
|
||||
(eve(), bob(), 100 * DOLLARS, pallet_staking::StakerStatus::Validator),
|
||||
(ferdie(), charlie(), 100 * DOLLARS, pallet_staking::StakerStatus::Validator)
|
||||
],
|
||||
validator_count: 3,
|
||||
minimum_validator_count: 0,
|
||||
slash_reward_fraction: Perbill::from_percent(10),
|
||||
invulnerables: vec![alice(), bob(), charlie()],
|
||||
.. Default::default()
|
||||
}),
|
||||
pallet_contracts: Some(ContractsConfig {
|
||||
current_schedule: Default::default(),
|
||||
gas_price: 1 * MILLICENTS,
|
||||
}),
|
||||
pallet_babe: Some(Default::default()),
|
||||
pallet_grandpa: Some(GrandpaConfig {
|
||||
authorities: vec![],
|
||||
}),
|
||||
pallet_im_online: Some(Default::default()),
|
||||
pallet_authority_discovery: Some(Default::default()),
|
||||
pallet_democracy: Some(Default::default()),
|
||||
pallet_collective_Instance1: Some(Default::default()),
|
||||
pallet_collective_Instance2: Some(Default::default()),
|
||||
pallet_membership_Instance1: Some(Default::default()),
|
||||
pallet_sudo: Some(Default::default()),
|
||||
pallet_treasury: Some(Default::default()),
|
||||
// here comes configuration for Kovan chain
|
||||
// TODO: Update Name
|
||||
bridge_eth_poa: Some(BridgeEthPoaConfig {
|
||||
initial_header: sp_bridge_eth_poa::Header {
|
||||
parent_hash: Default::default(),
|
||||
timestamp: 0,
|
||||
number: 0,
|
||||
author: Default::default(),
|
||||
transactions_root: "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".parse().unwrap(),
|
||||
uncles_hash: "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".parse().unwrap(),
|
||||
extra_data: vec![],
|
||||
state_root: "2480155b48a1cea17d67dbfdfaafe821c1d19cdd478c5358e8ec56dec24502b2".parse().unwrap(),
|
||||
receipts_root: "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".parse().unwrap(),
|
||||
log_bloom: Default::default(),
|
||||
gas_used: Default::default(),
|
||||
gas_limit: 6000000.into(),
|
||||
difficulty: 131072.into(),
|
||||
seal: vec![
|
||||
vec![128].into(),
|
||||
vec![184, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].into(),
|
||||
],
|
||||
},
|
||||
initial_difficulty: 0.into(),
|
||||
initial_validators: vec![
|
||||
[0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58,
|
||||
0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, 0x5B, 0xAd, 0xc0, 0xED].into(),
|
||||
[0x00, 0x42, 0x7f, 0xea, 0xe2, 0x41, 0x9c, 0x15, 0xb8, 0x9d,
|
||||
0x1c, 0x21, 0xaf, 0x10, 0xd1, 0xb6, 0x65, 0x0a, 0x4d, 0x3d].into(),
|
||||
[0x4E, 0xd9, 0xB0, 0x8e, 0x63, 0x54, 0xC7, 0x0f, 0xE6, 0xF8,
|
||||
0xCB, 0x04, 0x11, 0xb0, 0xd3, 0x24, 0x6b, 0x42, 0x4d, 0x6c].into(),
|
||||
[0x00, 0x20, 0xee, 0x4B, 0xe0, 0xe2, 0x02, 0x7d, 0x76, 0x60,
|
||||
0x3c, 0xB7, 0x51, 0xeE, 0x06, 0x95, 0x19, 0xbA, 0x81, 0xA1].into(),
|
||||
[0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5,
|
||||
0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, 0x3a, 0xfd, 0x03, 0x2d].into(),
|
||||
[0x00, 0x77, 0x33, 0xa1, 0xFE, 0x69, 0xCF, 0x3f, 0x2C, 0xF9,
|
||||
0x89, 0xF8, 0x1C, 0x7b, 0x4c, 0xAc, 0x16, 0x93, 0x38, 0x7A].into(),
|
||||
[0x00, 0xE6, 0xd2, 0xb9, 0x31, 0xF5, 0x5a, 0x3f, 0x17, 0x01,
|
||||
0xc7, 0x38, 0x9d, 0x59, 0x2a, 0x77, 0x78, 0x89, 0x78, 0x79].into(),
|
||||
[0x00, 0xe4, 0xa1, 0x06, 0x50, 0xe5, 0xa6, 0xD6, 0x00, 0x1C,
|
||||
0x38, 0xff, 0x8E, 0x64, 0xF9, 0x70, 0x16, 0xa1, 0x64, 0x5c].into(),
|
||||
[0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c,
|
||||
0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, 0x12, 0x31, 0x94, 0xde].into(),
|
||||
],
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "pallet-bridge-eth-poa"
|
||||
description = "A Substrate Runtime module that is able to verify PoA headers and their finality."
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", optional = true }
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
|
||||
sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" }
|
||||
frame-support = { version = "2.0.0", default-features = false, path = "../support" } # TODO: Rename
|
||||
sp-runtime = { version = "2.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
frame-system = { version = "2.0.0", default-features = false, path = "../system" } # TODO: Rename
|
||||
sp-io = { version = "2.0.0", default-features = false, path = "../../primitives/io" }
|
||||
primitives = { package = "sp-bridge-eth-poa", path = "../../primitives/bridge-eth-poa", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
primitives = { package = "sp-bridge-eth-poa", path = "../../primitives/bridge-eth-poa", features = ["std", "test-helpers"] }
|
||||
parity-crypto = { version = "0.4", features = ["publickey"] }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"sp-std/std",
|
||||
"frame-support/std",
|
||||
"sp-runtime/std",
|
||||
"frame-system/std",
|
||||
"sp-io/std",
|
||||
"primitives/std",
|
||||
]
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity-Bridge.
|
||||
|
||||
// Parity-Bridge 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.
|
||||
|
||||
// Parity-Bridge 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 Parity-Bridge. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
/// Header import error.
|
||||
#[derive(RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||
pub enum Error {
|
||||
/// The header is beyound last finalized and can not be imported.
|
||||
AncientHeader,
|
||||
/// The header is already imported.
|
||||
KnownHeader,
|
||||
/// Seal has an incorrect format.
|
||||
InvalidSealArity,
|
||||
/// Block number isn't sensible.
|
||||
RidiculousNumber,
|
||||
/// Block has too much gas used.
|
||||
TooMuchGasUsed,
|
||||
/// Gas limit header field is invalid.
|
||||
InvalidGasLimit,
|
||||
/// Extra data is of an invalid length.
|
||||
ExtraDataOutOfBounds,
|
||||
/// Timestamp header overflowed.
|
||||
TimestampOverflow,
|
||||
/// The parent header is missing from the blockchain.
|
||||
MissingParentBlock,
|
||||
/// The header step is missing from the header.
|
||||
MissingStep,
|
||||
/// The header signature is missing from the header.
|
||||
MissingSignature,
|
||||
/// Empty steps are missing from the header.
|
||||
MissingEmptySteps,
|
||||
/// The same author issued different votes at the same step.
|
||||
DoubleVote,
|
||||
/// Validation proof insufficient.
|
||||
InsufficientProof,
|
||||
/// Difficulty header field is invalid.
|
||||
InvalidDifficulty,
|
||||
/// The received block is from an incorrect proposer.
|
||||
NotValidator,
|
||||
/// Missing transaction receipts for the operation.
|
||||
MissingTransactionsReceipts,
|
||||
/// Provided transactions receipts are not matching the header.
|
||||
TransactionsReceiptsMismatch,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn msg(&self) -> &'static str {
|
||||
match *self {
|
||||
Error::AncientHeader => "Header is beyound last finalized and can not be imported",
|
||||
Error::KnownHeader => "Header is already imported",
|
||||
Error::InvalidSealArity => "Header has an incorrect seal",
|
||||
Error::RidiculousNumber => "Header has too large number",
|
||||
Error::TooMuchGasUsed => "Header has too much gas used",
|
||||
Error::InvalidGasLimit => "Header has invalid gas limit",
|
||||
Error::ExtraDataOutOfBounds => "Header has too large extra data",
|
||||
Error::TimestampOverflow => "Header has too large timestamp",
|
||||
Error::MissingParentBlock => "Header has unknown parent hash",
|
||||
Error::MissingStep => "Header is missing step seal",
|
||||
Error::MissingSignature => "Header is missing signature seal",
|
||||
Error::MissingEmptySteps => "Header is missing empty steps seal",
|
||||
Error::DoubleVote => "Header has invalid step in seal",
|
||||
Error::InsufficientProof => "Header has insufficient proof",
|
||||
Error::InvalidDifficulty => "Header has invalid difficulty",
|
||||
Error::NotValidator => "Header is sealed by unexpected validator",
|
||||
Error::MissingTransactionsReceipts => "The import operation requires transactions receipts",
|
||||
Error::TransactionsReceiptsMismatch => "Invalid transactions receipts provided",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity-Bridge.
|
||||
|
||||
// Parity-Bridge 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.
|
||||
|
||||
// Parity-Bridge 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 Parity-Bridge. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::collections::{
|
||||
btree_map::{BTreeMap, Entry},
|
||||
btree_set::BTreeSet,
|
||||
vec_deque::VecDeque,
|
||||
};
|
||||
use sp_io::crypto::secp256k1_ecdsa_recover;
|
||||
use primitives::{Address, H256, Header, SealedEmptyStep, public_to_address};
|
||||
use crate::{Storage, ancestry};
|
||||
use crate::error::Error;
|
||||
|
||||
/// Tries to finalize blocks when given block is imported.
|
||||
///
|
||||
/// Returns numbers and hashes of finalized blocks in ascending order.
|
||||
pub fn finalize_blocks<S: Storage>(
|
||||
storage: &S,
|
||||
best_finalized_hash: &H256,
|
||||
header_validators: (&H256, &[Address]),
|
||||
hash: &H256,
|
||||
header: &Header,
|
||||
two_thirds_majority_transition: u64,
|
||||
) -> Result<Vec<(u64, H256)>, Error> {
|
||||
// compute count of voters for every unfinalized block in ancestry
|
||||
let validators = header_validators.1.iter().collect();
|
||||
let (mut votes, mut headers) = prepare_votes(
|
||||
storage,
|
||||
best_finalized_hash,
|
||||
&header_validators.0,
|
||||
&validators,
|
||||
hash,
|
||||
header,
|
||||
two_thirds_majority_transition,
|
||||
)?;
|
||||
|
||||
// now let's iterate in reverse order && find just finalized blocks
|
||||
let mut newly_finalized = Vec::new();
|
||||
while let Some((oldest_hash, oldest_number, signers)) = headers.pop_front() {
|
||||
if !is_finalized(&validators, &votes, oldest_number >= two_thirds_majority_transition) {
|
||||
break;
|
||||
}
|
||||
|
||||
remove_signers_votes(&signers, &mut votes);
|
||||
newly_finalized.push((oldest_number, oldest_hash));
|
||||
}
|
||||
|
||||
Ok(newly_finalized)
|
||||
}
|
||||
|
||||
/// Returns true if there are enough votes to treat this header as finalized.
|
||||
fn is_finalized(
|
||||
validators: &BTreeSet<&Address>,
|
||||
votes: &BTreeMap<Address, u64>,
|
||||
requires_two_thirds_majority: bool,
|
||||
) -> bool {
|
||||
(!requires_two_thirds_majority && votes.len() * 2 > validators.len()) ||
|
||||
(requires_two_thirds_majority && votes.len() * 3 > validators.len() * 2)
|
||||
}
|
||||
|
||||
/// Prepare 'votes' of header and its ancestors' signers.
|
||||
fn prepare_votes<S: Storage>(
|
||||
storage: &S,
|
||||
best_finalized_hash: &H256,
|
||||
validators_begin: &H256,
|
||||
validators: &BTreeSet<&Address>,
|
||||
hash: &H256,
|
||||
header: &Header,
|
||||
two_thirds_majority_transition: u64,
|
||||
) -> Result<(BTreeMap<Address, u64>, VecDeque<(H256, u64, BTreeSet<Address>)>), Error> {
|
||||
// this fn can only work with single validators set
|
||||
if !validators.contains(&header.author) {
|
||||
return Err(Error::NotValidator);
|
||||
}
|
||||
|
||||
// prepare iterator of signers of all ancestors of the header
|
||||
// we only take ancestors that are not yet pruned and those signed by
|
||||
// the same set of validators
|
||||
let mut parent_empty_step_signers = empty_steps_signers(header);
|
||||
let ancestry = ancestry(storage, header)
|
||||
.map(|(hash, header)| {
|
||||
let mut signers = BTreeSet::new();
|
||||
sp_std::mem::swap(&mut signers, &mut parent_empty_step_signers);
|
||||
signers.insert(header.author);
|
||||
|
||||
let empty_step_signers = empty_steps_signers(&header);
|
||||
let res = (hash, header.number, signers);
|
||||
parent_empty_step_signers = empty_step_signers;
|
||||
res
|
||||
})
|
||||
.take_while(|&(hash, _, _)| hash != *validators_begin && hash != *best_finalized_hash);
|
||||
|
||||
// now let's iterate built iterator and compute number of validators
|
||||
// 'voted' for each header
|
||||
// we stop when finalized block is met (because we only interested in
|
||||
// just finalized blocks)
|
||||
let mut votes = BTreeMap::new();
|
||||
let mut headers = VecDeque::new();
|
||||
for (hash, number, signers) in ancestry {
|
||||
add_signers_votes(validators, &signers, &mut votes)?;
|
||||
if is_finalized(validators, &votes, number >= two_thirds_majority_transition) {
|
||||
remove_signers_votes(&signers, &mut votes);
|
||||
break;
|
||||
}
|
||||
|
||||
headers.push_front((hash, number, signers));
|
||||
}
|
||||
|
||||
// update votes with last header vote
|
||||
let mut header_signers = BTreeSet::new();
|
||||
header_signers.insert(header.author);
|
||||
*votes.entry(header.author).or_insert(0) += 1;
|
||||
headers.push_back((*hash, header.number, header_signers));
|
||||
|
||||
Ok((votes, headers))
|
||||
}
|
||||
|
||||
/// Increase count of 'votes' for every passed signer.
|
||||
/// Fails if at least one of signers is not in the `validators` set.
|
||||
fn add_signers_votes(
|
||||
validators: &BTreeSet<&Address>,
|
||||
signers_to_add: &BTreeSet<Address>,
|
||||
votes: &mut BTreeMap<Address, u64>,
|
||||
) -> Result<(), Error> {
|
||||
for signer in signers_to_add {
|
||||
if !validators.contains(signer) {
|
||||
return Err(Error::NotValidator);
|
||||
}
|
||||
|
||||
*votes.entry(*signer).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrease 'votes' count for every passed signer.
|
||||
fn remove_signers_votes(signers_to_remove: &BTreeSet<Address>, votes: &mut BTreeMap<Address, u64>) {
|
||||
for signer in signers_to_remove {
|
||||
match votes.entry(*signer) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
if *entry.get() <= 1 {
|
||||
entry.remove();
|
||||
} else {
|
||||
*entry.get_mut() -= 1;
|
||||
}
|
||||
},
|
||||
Entry::Vacant(_) => unreachable!("we only remove signers that have been added; qed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns unique set of empty steps signers.
|
||||
fn empty_steps_signers(header: &Header) -> BTreeSet<Address> {
|
||||
header.empty_steps()
|
||||
.into_iter()
|
||||
.flat_map(|steps| steps)
|
||||
.filter_map(|step| empty_step_signer(&step, &header.parent_hash))
|
||||
.collect::<BTreeSet<_>>()
|
||||
}
|
||||
|
||||
/// Returns author of empty step signature.
|
||||
fn empty_step_signer(empty_step: &SealedEmptyStep, parent_hash: &H256) -> Option<Address> {
|
||||
let message = empty_step.message(parent_hash);
|
||||
secp256k1_ecdsa_recover(empty_step.signature.as_fixed_bytes(), message.as_fixed_bytes())
|
||||
.ok()
|
||||
.map(|public| public_to_address(&public))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::HeaderToImport;
|
||||
use crate::tests::{InMemoryStorage, genesis, validator, validators_addresses};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn verifies_header_author() {
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&InMemoryStorage::new(genesis(), validators_addresses(5)),
|
||||
&Default::default(),
|
||||
(&Default::default(), &[]),
|
||||
&Default::default(),
|
||||
&Header::default(),
|
||||
0,
|
||||
),
|
||||
Err(Error::NotValidator),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepares_votes() {
|
||||
// let's say we have 5 validators (we need 'votes' from 3 validators to achieve
|
||||
// finality)
|
||||
let mut storage = InMemoryStorage::new(genesis(), validators_addresses(5));
|
||||
|
||||
// when header#1 is inserted, nothing is finalized (1 vote)
|
||||
let header1 = Header {
|
||||
author: validator(0).address().as_fixed_bytes().into(),
|
||||
parent_hash: genesis().hash(),
|
||||
number: 1,
|
||||
..Default::default()
|
||||
};
|
||||
let hash1 = header1.hash();
|
||||
let mut header_to_import = HeaderToImport {
|
||||
context: storage.import_context(&genesis().hash()).unwrap(),
|
||||
is_best: true,
|
||||
hash: hash1,
|
||||
header: header1,
|
||||
total_difficulty: 0.into(),
|
||||
enacted_change: None,
|
||||
scheduled_change: None,
|
||||
};
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&storage,
|
||||
&Default::default(),
|
||||
(&Default::default(), &validators_addresses(5)),
|
||||
&hash1,
|
||||
&header_to_import.header,
|
||||
u64::max_value(),
|
||||
),
|
||||
Ok(Vec::new()),
|
||||
);
|
||||
storage.insert_header(header_to_import.clone());
|
||||
|
||||
// when header#2 is inserted, nothing is finalized (2 votes)
|
||||
header_to_import.header = Header {
|
||||
author: validator(1).address().as_fixed_bytes().into(),
|
||||
parent_hash: hash1,
|
||||
number: 2,
|
||||
..Default::default()
|
||||
};
|
||||
header_to_import.hash = header_to_import.header.hash();
|
||||
let hash2 = header_to_import.header.hash();
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&storage,
|
||||
&Default::default(),
|
||||
(&Default::default(), &validators_addresses(5)),
|
||||
&hash2,
|
||||
&header_to_import.header,
|
||||
u64::max_value(),
|
||||
),
|
||||
Ok(Vec::new()),
|
||||
);
|
||||
storage.insert_header(header_to_import.clone());
|
||||
|
||||
// when header#3 is inserted, header#1 is finalized (3 votes)
|
||||
header_to_import.header = Header {
|
||||
author: validator(2).address().as_fixed_bytes().into(),
|
||||
parent_hash: hash2,
|
||||
number: 3,
|
||||
..Default::default()
|
||||
};
|
||||
header_to_import.hash = header_to_import.header.hash();
|
||||
let hash3 = header_to_import.header.hash();
|
||||
assert_eq!(
|
||||
finalize_blocks(
|
||||
&storage,
|
||||
&Default::default(),
|
||||
(&Default::default(), &validators_addresses(5)),
|
||||
&hash3,
|
||||
&header_to_import.header,
|
||||
u64::max_value(),
|
||||
),
|
||||
Ok(vec![(1, hash1)]),
|
||||
);
|
||||
storage.insert_header(header_to_import);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity-Bridge.
|
||||
|
||||
// Parity-Bridge 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.
|
||||
|
||||
// Parity-Bridge 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 Parity-Bridge. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use primitives::{H256, Header, Receipt};
|
||||
use crate::{AuraConfiguration, Storage};
|
||||
use crate::error::Error;
|
||||
use crate::finality::finalize_blocks;
|
||||
use crate::validators::{Validators, ValidatorsConfiguration};
|
||||
use crate::verification::verify_aura_header;
|
||||
|
||||
/// Maximal number of headers behind best blocks that we are aiming to store. When there
|
||||
/// are too many unfinalized headers, it slows down finalization tracking significantly.
|
||||
/// That's why we won't consider imports/reorganizations to blocks of PRUNE_DEPTH age.
|
||||
/// If there's more headers than that, we prune the oldest. The only exception is
|
||||
/// when unfinalized header schedules validators set change. We can't compute finality
|
||||
/// for pruned headers => we won't know when to enact validators set change. That's
|
||||
/// why we never prune headers with scheduled changes.
|
||||
pub(crate) const PRUNE_DEPTH: u64 = 4096;
|
||||
|
||||
/// Imports bunch of headers and updates blocks finality.
|
||||
///
|
||||
/// Transactions receipts must be provided if `header_import_requires_receipts()`
|
||||
/// has returned true.
|
||||
/// If successful, returns tuple where first element is the number of useful headers
|
||||
/// we have imported and the second element is the number of useless headers (duplicate)
|
||||
/// we have NOT imported.
|
||||
/// Returns error if fatal error has occured during import. Some valid headers may be
|
||||
/// imported in this case.
|
||||
pub fn import_headers<S: Storage>(
|
||||
storage: &mut S,
|
||||
aura_config: &AuraConfiguration,
|
||||
validators_config: &ValidatorsConfiguration,
|
||||
prune_depth: u64,
|
||||
headers: Vec<(Header, Option<Vec<Receipt>>)>,
|
||||
) -> Result<(u64, u64), Error> {
|
||||
let mut useful = 0;
|
||||
let mut useless = 0;
|
||||
for (header, receipts) in headers {
|
||||
let import_result = import_header(
|
||||
storage,
|
||||
aura_config,
|
||||
validators_config,
|
||||
prune_depth,
|
||||
header,
|
||||
receipts,
|
||||
);
|
||||
|
||||
match import_result {
|
||||
Ok(_) => useful += 1,
|
||||
Err(Error::AncientHeader) | Err(Error::KnownHeader) => useless += 1,
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((useful, useless))
|
||||
}
|
||||
|
||||
/// Imports given header and updates blocks finality (if required).
|
||||
///
|
||||
/// Transactions receipts must be provided if `header_import_requires_receipts()`
|
||||
/// has returned true.
|
||||
pub fn import_header<S: Storage>(
|
||||
storage: &mut S,
|
||||
aura_config: &AuraConfiguration,
|
||||
validators_config: &ValidatorsConfiguration,
|
||||
prune_depth: u64,
|
||||
header: Header,
|
||||
receipts: Option<Vec<Receipt>>,
|
||||
) -> Result<H256, Error> {
|
||||
// first check that we are able to import this header at all
|
||||
let (hash, prev_finalized_hash) = is_importable_header(storage, &header)?;
|
||||
|
||||
// verify header
|
||||
let import_context = verify_aura_header(
|
||||
storage,
|
||||
aura_config,
|
||||
&header,
|
||||
)?;
|
||||
|
||||
// check if block schedules new validators
|
||||
let validators = Validators::new(validators_config);
|
||||
let (scheduled_change, enacted_change) =
|
||||
validators.extract_validators_change(&header, receipts)?;
|
||||
|
||||
// check if block finalizes some other blocks and corresponding scheduled validators
|
||||
let finalized_blocks = finalize_blocks(
|
||||
storage,
|
||||
&prev_finalized_hash,
|
||||
(import_context.validators_start(), import_context.validators()),
|
||||
&hash,
|
||||
&header,
|
||||
aura_config.two_thirds_majority_transition,
|
||||
)?;
|
||||
let enacted_change = enacted_change
|
||||
.or_else(|| validators.finalize_validators_change(storage, &finalized_blocks));
|
||||
|
||||
// NOTE: we can't return Err() from anywhere below this line
|
||||
// (because otherwise we'll have inconsistent storage if transaction will fail)
|
||||
|
||||
// and finally insert the block
|
||||
let (_, _, best_total_difficulty) = storage.best_block();
|
||||
let total_difficulty = import_context.total_difficulty() + header.difficulty;
|
||||
let is_best = total_difficulty > best_total_difficulty;
|
||||
let header_number = header.number;
|
||||
storage.insert_header(import_context.into_import_header(
|
||||
is_best,
|
||||
hash,
|
||||
header,
|
||||
total_difficulty,
|
||||
enacted_change,
|
||||
scheduled_change,
|
||||
));
|
||||
|
||||
// now mark finalized headers && prune old headers
|
||||
storage.finalize_headers(
|
||||
finalized_blocks.last().cloned(),
|
||||
match is_best {
|
||||
true => header_number.checked_sub(prune_depth),
|
||||
false => None,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
/// Returns true if transactions receipts are required to import given header.
|
||||
pub fn header_import_requires_receipts<S: Storage>(
|
||||
storage: &S,
|
||||
validators_config: &ValidatorsConfiguration,
|
||||
header: &Header,
|
||||
) -> bool {
|
||||
is_importable_header(storage, header)
|
||||
.map(|_| Validators::new(validators_config))
|
||||
.map(|validators| validators.maybe_signals_validators_change(header))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Checks that we are able to ***try to** import this header.
|
||||
/// Returns error if we should not try to import this block.
|
||||
/// Returns hash of the header and number of the last finalized block.
|
||||
fn is_importable_header<S: Storage>(storage: &S, header: &Header) -> Result<(H256, H256), Error> {
|
||||
// we never import any header that competes with finalized header
|
||||
let (finalized_block_number, finalized_block_hash) = storage.finalized_block();
|
||||
if header.number <= finalized_block_number {
|
||||
return Err(Error::AncientHeader);
|
||||
}
|
||||
// we never import any header with known hash
|
||||
let hash = header.hash();
|
||||
if storage.header(&hash).is_some() {
|
||||
return Err(Error::KnownHeader);
|
||||
}
|
||||
|
||||
Ok((hash, finalized_block_hash))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{kovan_aura_config, kovan_validators_config};
|
||||
use crate::tests::{
|
||||
InMemoryStorage,
|
||||
block_i, custom_block_i, signed_header, genesis,
|
||||
validator, validators_addresses,
|
||||
};
|
||||
use crate::validators::ValidatorsSource;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rejects_finalized_block_competitors() {
|
||||
let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3));
|
||||
storage.finalize_headers(Some((100, Default::default())), None);
|
||||
assert_eq!(
|
||||
import_header(
|
||||
&mut storage,
|
||||
&kovan_aura_config(),
|
||||
&kovan_validators_config(),
|
||||
PRUNE_DEPTH,
|
||||
Default::default(),
|
||||
None,
|
||||
),
|
||||
Err(Error::AncientHeader),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_known_header() {
|
||||
let validators = (0..3).map(|i| validator(i as u8)).collect::<Vec<_>>();
|
||||
let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3));
|
||||
let block = block_i(&storage, 1, &validators);
|
||||
assert_eq!(
|
||||
import_header(
|
||||
&mut storage,
|
||||
&kovan_aura_config(),
|
||||
&kovan_validators_config(),
|
||||
PRUNE_DEPTH,
|
||||
block.clone(),
|
||||
None,
|
||||
).map(|_| ()),
|
||||
Ok(()),
|
||||
);
|
||||
assert_eq!(
|
||||
import_header(
|
||||
&mut storage,
|
||||
&kovan_aura_config(),
|
||||
&kovan_validators_config(),
|
||||
PRUNE_DEPTH,
|
||||
block,
|
||||
None,
|
||||
).map(|_| ()),
|
||||
Err(Error::KnownHeader),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_header_works() {
|
||||
let validators_config = ValidatorsConfiguration::Multi(vec![
|
||||
(0, ValidatorsSource::List(validators_addresses(3))),
|
||||
(1, ValidatorsSource::List(validators_addresses(2))),
|
||||
]);
|
||||
let validators = (0..3).map(|i| validator(i as u8)).collect::<Vec<_>>();
|
||||
let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3));
|
||||
let header = block_i(&storage, 1, &validators);
|
||||
let hash = header.hash();
|
||||
assert_eq!(
|
||||
import_header(&mut storage, &kovan_aura_config(), &validators_config, PRUNE_DEPTH, header, None)
|
||||
.map(|_| ()),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
// check that new validators will be used for next header
|
||||
let imported_header = storage.stored_header(&hash).unwrap();
|
||||
assert_eq!(
|
||||
imported_header.next_validators_set_id,
|
||||
1, // new set is enacted from config
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_are_pruned() {
|
||||
let validators_config = ValidatorsConfiguration::Single(
|
||||
ValidatorsSource::Contract([3; 20].into(), validators_addresses(3)),
|
||||
);
|
||||
let validators = vec![validator(0), validator(1), validator(2)];
|
||||
let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3));
|
||||
|
||||
// header [0..11] are finalizing blocks [0; 9]
|
||||
// => since we want to keep 10 finalized blocks, we aren't pruning anything
|
||||
let mut last_block_hash = Default::default();
|
||||
for i in 1..11 {
|
||||
let header = block_i(&storage, i, &validators);
|
||||
last_block_hash = import_header(
|
||||
&mut storage,
|
||||
&kovan_aura_config(),
|
||||
&validators_config,
|
||||
10,
|
||||
header,
|
||||
None,
|
||||
).unwrap();
|
||||
}
|
||||
assert!(storage.header(&genesis().hash()).is_some());
|
||||
|
||||
// header 11 finalizes headers [10] AND schedules change
|
||||
// => we prune header#0
|
||||
let header = custom_block_i(&storage, 11, &validators, |header| {
|
||||
header.log_bloom = (&[0xff; 256]).into();
|
||||
header.receipts_root = "2e60346495092587026484e868a5b3063749032b2ea3843844509a6320d7f951".parse().unwrap();
|
||||
});
|
||||
last_block_hash = import_header(
|
||||
&mut storage,
|
||||
&kovan_aura_config(),
|
||||
&validators_config,
|
||||
10,
|
||||
header,
|
||||
Some(vec![crate::validators::tests::validators_change_recept(last_block_hash)]),
|
||||
).unwrap();
|
||||
assert!(storage.header(&genesis().hash()).is_none());
|
||||
|
||||
// and now let's say validators 1 && 2 went offline
|
||||
// => in the range 12-25 no blocks are finalized, but we still continue to prune old headers
|
||||
// until header#11 is met. we can't prune #11, because it schedules change
|
||||
let mut step = 56;
|
||||
for i in 12..25 {
|
||||
let header = Header {
|
||||
number: i as _,
|
||||
parent_hash: last_block_hash,
|
||||
gas_limit: 0x2000.into(),
|
||||
author: validator(2).address().to_fixed_bytes().into(),
|
||||
seal: vec![
|
||||
vec![step].into(),
|
||||
vec![].into(),
|
||||
],
|
||||
difficulty: i.into(),
|
||||
..Default::default()
|
||||
};
|
||||
let header = signed_header(&validators, header, step as _);
|
||||
last_block_hash = import_header(
|
||||
&mut storage,
|
||||
&kovan_aura_config(),
|
||||
&validators_config,
|
||||
10,
|
||||
header,
|
||||
None,
|
||||
).unwrap();
|
||||
step += 3;
|
||||
}
|
||||
assert_eq!(storage.oldest_unpruned_block(), 11);
|
||||
|
||||
// now let's insert block signed by validator 1
|
||||
// => blocks 11..24 are finalized and blocks 11..14 are pruned
|
||||
step -= 2;
|
||||
let header = Header {
|
||||
number: 25,
|
||||
parent_hash: last_block_hash,
|
||||
gas_limit: 0x2000.into(),
|
||||
author: validator(0).address().to_fixed_bytes().into(),
|
||||
seal: vec![
|
||||
vec![step].into(),
|
||||
vec![].into(),
|
||||
],
|
||||
difficulty: 25.into(),
|
||||
..Default::default()
|
||||
};
|
||||
let header = signed_header(&validators, header, step as _);
|
||||
import_header(
|
||||
&mut storage,
|
||||
&kovan_aura_config(),
|
||||
&validators_config,
|
||||
10,
|
||||
header,
|
||||
None,
|
||||
).unwrap();
|
||||
assert_eq!(storage.oldest_unpruned_block(), 15);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,740 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity-Bridge.
|
||||
|
||||
// Parity-Bridge 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.
|
||||
|
||||
// Parity-Bridge 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 Parity-Bridge. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_std::{prelude::*, iter::from_fn};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{decl_module, decl_storage};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use primitives::{Address, U256, H256, Header, Receipt};
|
||||
use validators::{ValidatorsSource, ValidatorsConfiguration};
|
||||
|
||||
pub use import::{import_header, header_import_requires_receipts};
|
||||
|
||||
mod error;
|
||||
mod finality;
|
||||
mod import;
|
||||
mod validators;
|
||||
mod verification;
|
||||
|
||||
/// Authority round engine configuration parameters.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)]
|
||||
pub struct AuraConfiguration {
|
||||
/// Empty step messages transition block.
|
||||
pub empty_steps_transition: u64,
|
||||
/// Transition block to strict empty steps validation.
|
||||
pub strict_empty_steps_transition: u64,
|
||||
/// Monotonic step validation transition block.
|
||||
pub validate_step_transition: u64,
|
||||
/// Chain score validation transition block.
|
||||
pub validate_score_transition: u64,
|
||||
/// First block for which a 2/3 quorum (instead of 1/2) is required.
|
||||
pub two_thirds_majority_transition: u64,
|
||||
/// Minimum gas limit.
|
||||
pub min_gas_limit: U256,
|
||||
/// Maximum gas limit.
|
||||
pub max_gas_limit: U256,
|
||||
/// Maximum size of extra data.
|
||||
pub maximum_extra_data_size: u64,
|
||||
}
|
||||
|
||||
/// Block header as it is stored in the runtime storage.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)]
|
||||
pub struct StoredHeader {
|
||||
/// The block header itself.
|
||||
pub header: Header,
|
||||
/// Total difficulty of the chain.
|
||||
pub total_difficulty: U256,
|
||||
/// The ID of set of validators that is expected to produce direct descendants of
|
||||
/// this block. If header enacts new set, this would be the new set. Otherwise
|
||||
/// this is the set that has produced the block itself.
|
||||
/// The hash is the hash of block where validators set has been enacted.
|
||||
pub next_validators_set_id: u64,
|
||||
}
|
||||
|
||||
/// Header that we're importing.
|
||||
#[derive(RuntimeDebug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||
pub struct HeaderToImport {
|
||||
/// Header import context,
|
||||
pub context: ImportContext,
|
||||
/// Should we consider this header as best?
|
||||
pub is_best: bool,
|
||||
/// The hash of the header.
|
||||
pub hash: H256,
|
||||
/// The header itself.
|
||||
pub header: Header,
|
||||
/// Total chain difficulty at the header.
|
||||
pub total_difficulty: U256,
|
||||
/// Validators set enacted change, if happened at the header.
|
||||
pub enacted_change: Option<Vec<Address>>,
|
||||
/// Validators set scheduled change, if happened at the header.
|
||||
pub scheduled_change: Option<Vec<Address>>,
|
||||
}
|
||||
|
||||
/// Header import context.
|
||||
#[derive(RuntimeDebug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||
pub struct ImportContext {
|
||||
parent_header: Header,
|
||||
parent_total_difficulty: U256,
|
||||
next_validators_set_id: u64,
|
||||
next_validators_set: (H256, Vec<Address>),
|
||||
}
|
||||
|
||||
impl ImportContext {
|
||||
/// Create import context using passing parameters;
|
||||
pub fn new(
|
||||
parent_header: Header,
|
||||
parent_total_difficulty: U256,
|
||||
next_validators_set_id: u64,
|
||||
next_validators_set: (H256, Vec<Address>),
|
||||
) -> Self {
|
||||
ImportContext {
|
||||
parent_header,
|
||||
parent_total_difficulty,
|
||||
next_validators_set_id,
|
||||
next_validators_set,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns reference to parent header.
|
||||
pub fn parent_header(&self) -> &Header {
|
||||
&self.parent_header
|
||||
}
|
||||
|
||||
/// Returns total chain difficulty at parent block.
|
||||
pub fn total_difficulty(&self) -> &U256 {
|
||||
&self.parent_total_difficulty
|
||||
}
|
||||
|
||||
/// Returns id of the set of validators.
|
||||
pub fn validators_set_id(&self) -> u64 {
|
||||
self.next_validators_set_id
|
||||
}
|
||||
|
||||
/// Returns block whenre validators set has been enacted.
|
||||
pub fn validators_start(&self) -> &H256 {
|
||||
&self.next_validators_set.0
|
||||
}
|
||||
|
||||
/// Returns reference to the set of validators of the block we're going to import.
|
||||
pub fn validators(&self) -> &[Address] {
|
||||
&self.next_validators_set.1
|
||||
}
|
||||
|
||||
/// Converts import context into header we're going to import.
|
||||
pub fn into_import_header(
|
||||
self,
|
||||
is_best: bool,
|
||||
hash: H256,
|
||||
header: Header,
|
||||
total_difficulty: U256,
|
||||
enacted_change: Option<Vec<Address>>,
|
||||
scheduled_change: Option<Vec<Address>>,
|
||||
) -> HeaderToImport {
|
||||
HeaderToImport {
|
||||
context: self,
|
||||
is_best,
|
||||
hash,
|
||||
header,
|
||||
total_difficulty,
|
||||
enacted_change,
|
||||
scheduled_change,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The storage that is used by the client.
|
||||
///
|
||||
/// Storage modification must be discarded if block import has failed.
|
||||
pub trait Storage {
|
||||
/// Get best known block.
|
||||
fn best_block(&self) -> (u64, H256, U256);
|
||||
/// Get last finalized block.
|
||||
fn finalized_block(&self) -> (u64, H256);
|
||||
/// Get imported header by its hash.
|
||||
fn header(&self, hash: &H256) -> Option<Header>;
|
||||
/// Get header import context by parent header hash.
|
||||
fn import_context(&self, parent_hash: &H256) -> Option<ImportContext>;
|
||||
/// Get new validators that are scheduled by given header.
|
||||
fn scheduled_change(&self, hash: &H256) -> Option<Vec<Address>>;
|
||||
/// Insert imported header.
|
||||
fn insert_header(&mut self, header: HeaderToImport);
|
||||
/// Finalize given block and prune all headers with number < prune_end.
|
||||
/// The headers in the pruning range could be either finalized, or not.
|
||||
/// It is the storage duty to ensure that unfinalized headers that have
|
||||
/// scheduled changes won't be pruned until they or their competitors
|
||||
/// are finalized.
|
||||
fn finalize_headers(
|
||||
&mut self,
|
||||
finalized: Option<(u64, H256)>,
|
||||
prune_end: Option<u64>,
|
||||
);
|
||||
}
|
||||
|
||||
/// Decides whether the session should be ended.
|
||||
pub trait OnHeadersSubmitted<AccountId> {
|
||||
/// Called when valid headers have been submitted.
|
||||
fn on_valid_headers_submitted(submitter: AccountId, useful: u64, useless: u64);
|
||||
/// Called when invalid headers have been submitted.
|
||||
fn on_invalid_headers_submitted(submitter: AccountId);
|
||||
}
|
||||
|
||||
impl<AccountId> OnHeadersSubmitted<AccountId> for () {
|
||||
fn on_valid_headers_submitted(_submitter: AccountId, _useful: u64, _useless: u64) {}
|
||||
fn on_invalid_headers_submitted(_submitter: AccountId) {}
|
||||
}
|
||||
|
||||
/// The module configuration trait
|
||||
pub trait Trait: frame_system::Trait {
|
||||
/// Handler for headers submission result.
|
||||
type OnHeadersSubmitted: OnHeadersSubmitted<Self::AccountId>;
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||
/// Import Aura chain headers. Ignores non-fatal errors (like when known
|
||||
/// header is provided), rewards for successful headers import and penalizes
|
||||
/// for fatal errors.
|
||||
///
|
||||
/// This should be used with caution - passing too many headers could lead to
|
||||
/// enormous block production/import time.
|
||||
pub fn import_headers(origin, headers_with_receipts: Vec<(Header, Option<Vec<Receipt>>)>) {
|
||||
let submitter = frame_system::ensure_signed(origin)?;
|
||||
let import_result = import::import_headers(
|
||||
&mut BridgeStorage,
|
||||
&kovan_aura_config(),
|
||||
&kovan_validators_config(),
|
||||
crate::import::PRUNE_DEPTH,
|
||||
headers_with_receipts,
|
||||
);
|
||||
|
||||
match import_result {
|
||||
Ok((useful, useless)) =>
|
||||
T::OnHeadersSubmitted::on_valid_headers_submitted(submitter, useful, useless),
|
||||
Err(error) => {
|
||||
// even though we may have accept some headers, we do not want to reward someone
|
||||
// who provides invalid headers
|
||||
T::OnHeadersSubmitted::on_invalid_headers_submitted(submitter);
|
||||
return Err(error.msg().into());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Bridge {
|
||||
/// Best known block.
|
||||
BestBlock: (u64, H256, U256);
|
||||
/// Best finalized block.
|
||||
FinalizedBlock: (u64, H256);
|
||||
/// Oldest unpruned block(s) number.
|
||||
OldestUnprunedBlock: u64;
|
||||
/// Map of imported headers by hash.
|
||||
Headers: map H256 => Option<StoredHeader>;
|
||||
/// Map of imported header hashes by number.
|
||||
HeadersByNumber: map u64 => Option<Vec<H256>>;
|
||||
/// The ID of next validator set.
|
||||
NextValidatorsSetId: u64;
|
||||
/// Map of validators sets by their id.
|
||||
ValidatorsSets: map u64 => Option<(H256, Vec<Address>)>;
|
||||
/// Validators sets reference count. Each header that is authored by this set increases
|
||||
/// the reference count. When we prune this header, we decrease the reference count.
|
||||
/// When it reaches zero, we are free to prune validator set as well.
|
||||
ValidatorsSetsRc: map u64 => Option<u64>;
|
||||
/// Map of validators set changes scheduled by given header.
|
||||
ScheduledChanges: map H256 => Option<Vec<Address>>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(initial_header): Header;
|
||||
config(initial_difficulty): U256;
|
||||
config(initial_validators): Vec<Address>;
|
||||
build(|config| {
|
||||
// the initial blocks should be selected so that:
|
||||
// 1) it doesn't signal validators changes;
|
||||
// 2) there are no scheduled validators changes from previous blocks;
|
||||
// 3) (implied) all direct children of initial block are authred by the same validators set.
|
||||
|
||||
assert!(
|
||||
!config.initial_validators.is_empty(),
|
||||
"Initial validators set can't be empty",
|
||||
);
|
||||
|
||||
let initial_hash = config.initial_header.hash();
|
||||
BestBlock::put((config.initial_header.number, initial_hash, config.initial_difficulty));
|
||||
FinalizedBlock::put((config.initial_header.number, initial_hash));
|
||||
OldestUnprunedBlock::put(config.initial_header.number);
|
||||
HeadersByNumber::insert(config.initial_header.number, vec![initial_hash]);
|
||||
Headers::insert(initial_hash, StoredHeader {
|
||||
header: config.initial_header.clone(),
|
||||
total_difficulty: config.initial_difficulty,
|
||||
next_validators_set_id: 0,
|
||||
});
|
||||
NextValidatorsSetId::put(1);
|
||||
ValidatorsSets::insert(0, (initial_hash, config.initial_validators.clone()));
|
||||
ValidatorsSetsRc::insert(0, 1);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Returns number and hash of the best block known to the bridge module.
|
||||
/// The caller should only submit `import_header` transaction that makes
|
||||
/// (or leads to making) other header the best one.
|
||||
pub fn best_block() -> (u64, H256) {
|
||||
let (number, hash, _) = BridgeStorage.best_block();
|
||||
(number, hash)
|
||||
}
|
||||
|
||||
/// Returns true if the import of given block requires transactions receipts.
|
||||
pub fn is_import_requires_receipts(header: Header) -> bool {
|
||||
import::header_import_requires_receipts(
|
||||
&BridgeStorage,
|
||||
&kovan_validators_config(),
|
||||
&header,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if header is known to the runtime.
|
||||
pub fn is_known_block(hash: H256) -> bool {
|
||||
BridgeStorage.header(&hash).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime bridge storage.
|
||||
struct BridgeStorage;
|
||||
|
||||
impl Storage for BridgeStorage {
|
||||
fn best_block(&self) -> (u64, H256, U256) {
|
||||
BestBlock::get()
|
||||
}
|
||||
|
||||
fn finalized_block(&self) -> (u64, H256) {
|
||||
FinalizedBlock::get()
|
||||
}
|
||||
|
||||
fn header(&self, hash: &H256) -> Option<Header> {
|
||||
Headers::get(hash).map(|header| header.header)
|
||||
}
|
||||
|
||||
fn import_context(&self, parent_hash: &H256) -> Option<ImportContext> {
|
||||
Headers::get(parent_hash)
|
||||
.map(|parent_header| {
|
||||
let (next_validators_set_start, next_validators) =
|
||||
ValidatorsSets::get(parent_header.next_validators_set_id)
|
||||
.expect("validators set is only pruned when last ref is pruned; there is a ref; qed");
|
||||
ImportContext {
|
||||
parent_header: parent_header.header,
|
||||
parent_total_difficulty: parent_header.total_difficulty,
|
||||
next_validators_set_id: parent_header.next_validators_set_id,
|
||||
next_validators_set: (next_validators_set_start, next_validators),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn scheduled_change(&self, hash: &H256) -> Option<Vec<Address>> {
|
||||
ScheduledChanges::get(hash)
|
||||
}
|
||||
|
||||
fn insert_header(&mut self, header: HeaderToImport) {
|
||||
if header.is_best {
|
||||
BestBlock::put((header.header.number, header.hash, header.total_difficulty));
|
||||
}
|
||||
if let Some(scheduled_change) = header.scheduled_change {
|
||||
ScheduledChanges::insert(&header.hash, scheduled_change);
|
||||
}
|
||||
let next_validators_set_id = match header.enacted_change {
|
||||
Some(enacted_change) => {
|
||||
let next_validators_set_id = NextValidatorsSetId::mutate(|set_id| {
|
||||
let next_set_id = *set_id;
|
||||
*set_id += 1;
|
||||
next_set_id
|
||||
});
|
||||
ValidatorsSets::insert(next_validators_set_id, (header.hash, enacted_change));
|
||||
ValidatorsSetsRc::insert(next_validators_set_id, 1);
|
||||
next_validators_set_id
|
||||
},
|
||||
None => {
|
||||
ValidatorsSetsRc::mutate(
|
||||
header.context.next_validators_set_id,
|
||||
|rc| {
|
||||
*rc = Some(rc.map(|rc| rc + 1).unwrap_or(1));
|
||||
*rc
|
||||
},
|
||||
);
|
||||
header.context.next_validators_set_id
|
||||
},
|
||||
};
|
||||
|
||||
HeadersByNumber::append_or_insert(header.header.number, vec![header.hash]);
|
||||
Headers::insert(&header.hash, StoredHeader {
|
||||
header: header.header,
|
||||
total_difficulty: header.total_difficulty,
|
||||
next_validators_set_id,
|
||||
});
|
||||
}
|
||||
|
||||
fn finalize_headers(
|
||||
&mut self,
|
||||
finalized: Option<(u64, H256)>,
|
||||
prune_end: Option<u64>,
|
||||
) {
|
||||
// remember just finalized block
|
||||
let finalized_number = finalized.as_ref().map(|f| f.0).unwrap_or_else(|| FinalizedBlock::get().0);
|
||||
if let Some(finalized) = finalized {
|
||||
FinalizedBlock::put(finalized);
|
||||
}
|
||||
|
||||
if let Some(prune_end) = prune_end {
|
||||
let prune_begin = OldestUnprunedBlock::get();
|
||||
|
||||
for number in prune_begin..prune_end {
|
||||
let blocks_at_number = HeadersByNumber::take(number);
|
||||
|
||||
// ensure that unfinalized headers we want to prune do not have scheduled changes
|
||||
if number > finalized_number {
|
||||
if let Some(ref blocks_at_number) = blocks_at_number {
|
||||
if blocks_at_number.iter().any(|block| ScheduledChanges::exists(block)) {
|
||||
HeadersByNumber::insert(number, blocks_at_number);
|
||||
OldestUnprunedBlock::put(number);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// physically remove headers and (probably) obsolete validators sets
|
||||
for hash in blocks_at_number.into_iter().flat_map(|x| x) {
|
||||
let header = Headers::take(&hash);
|
||||
ScheduledChanges::remove(hash);
|
||||
if let Some(header) = header {
|
||||
ValidatorsSetsRc::mutate(
|
||||
header.next_validators_set_id,
|
||||
|rc| match *rc {
|
||||
Some(rc) if rc > 1 => Some(rc - 1),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OldestUnprunedBlock::put(prune_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Aura engine configuration for Kovan chain.
|
||||
pub fn kovan_aura_config() -> AuraConfiguration {
|
||||
AuraConfiguration {
|
||||
empty_steps_transition: u64::max_value(),
|
||||
strict_empty_steps_transition: 0,
|
||||
validate_step_transition: 0x16e360,
|
||||
validate_score_transition: 0x41a3c4,
|
||||
two_thirds_majority_transition: u64::max_value(),
|
||||
min_gas_limit: 0x1388.into(),
|
||||
max_gas_limit: U256::max_value(),
|
||||
maximum_extra_data_size: 0x20,
|
||||
}
|
||||
}
|
||||
|
||||
/// Validators configuration for Kovan chain.
|
||||
pub fn kovan_validators_config() -> ValidatorsConfiguration {
|
||||
ValidatorsConfiguration::Multi(vec![
|
||||
(0, ValidatorsSource::List(vec![
|
||||
[0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58,
|
||||
0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, 0x5B, 0xAd, 0xc0, 0xED].into(),
|
||||
[0x00, 0x42, 0x7f, 0xea, 0xe2, 0x41, 0x9c, 0x15, 0xb8, 0x9d,
|
||||
0x1c, 0x21, 0xaf, 0x10, 0xd1, 0xb6, 0x65, 0x0a, 0x4d, 0x3d].into(),
|
||||
[0x4E, 0xd9, 0xB0, 0x8e, 0x63, 0x54, 0xC7, 0x0f, 0xE6, 0xF8,
|
||||
0xCB, 0x04, 0x11, 0xb0, 0xd3, 0x24, 0x6b, 0x42, 0x4d, 0x6c].into(),
|
||||
[0x00, 0x20, 0xee, 0x4B, 0xe0, 0xe2, 0x02, 0x7d, 0x76, 0x60,
|
||||
0x3c, 0xB7, 0x51, 0xeE, 0x06, 0x95, 0x19, 0xbA, 0x81, 0xA1].into(),
|
||||
[0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5,
|
||||
0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, 0x3a, 0xfd, 0x03, 0x2d].into(),
|
||||
[0x00, 0x77, 0x33, 0xa1, 0xFE, 0x69, 0xCF, 0x3f, 0x2C, 0xF9,
|
||||
0x89, 0xF8, 0x1C, 0x7b, 0x4c, 0xAc, 0x16, 0x93, 0x38, 0x7A].into(),
|
||||
[0x00, 0xE6, 0xd2, 0xb9, 0x31, 0xF5, 0x5a, 0x3f, 0x17, 0x01,
|
||||
0xc7, 0x38, 0x9d, 0x59, 0x2a, 0x77, 0x78, 0x89, 0x78, 0x79].into(),
|
||||
[0x00, 0xe4, 0xa1, 0x06, 0x50, 0xe5, 0xa6, 0xD6, 0x00, 0x1C,
|
||||
0x38, 0xff, 0x8E, 0x64, 0xF9, 0x70, 0x16, 0xa1, 0x64, 0x5c].into(),
|
||||
[0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c,
|
||||
0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, 0x12, 0x31, 0x94, 0xde].into(),
|
||||
])),
|
||||
(10960440, ValidatorsSource::List(vec![
|
||||
[0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58,
|
||||
0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, 0x5B, 0xAd, 0xc0, 0xED].into(),
|
||||
[0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5,
|
||||
0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, 0x3a, 0xfd, 0x03, 0x2d].into(),
|
||||
[0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c,
|
||||
0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, 0x12, 0x31, 0x94, 0xde].into(),
|
||||
])),
|
||||
(10960500, ValidatorsSource::Contract(
|
||||
[0xaE, 0x71, 0x80, 0x7C, 0x1B, 0x0a, 0x09, 0x3c, 0xB1, 0x54,
|
||||
0x7b, 0x68, 0x2D, 0xC7, 0x83, 0x16, 0xD9, 0x45, 0xc9, 0xB8].into(),
|
||||
vec![
|
||||
[0xd0, 0x5f, 0x74, 0x78, 0xc6, 0xaa, 0x10, 0x78, 0x12, 0x58,
|
||||
0xc5, 0xcc, 0x8b, 0x4f, 0x38, 0x5f, 0xc8, 0xfa, 0x98, 0x9c].into(),
|
||||
[0x03, 0x80, 0x1e, 0xfb, 0x0e, 0xfe, 0x2a, 0x25, 0xed, 0xe5,
|
||||
0xdd, 0x3a, 0x00, 0x3a, 0xe8, 0x80, 0xc0, 0x29, 0x2e, 0x4d].into(),
|
||||
[0xa4, 0xdf, 0x25, 0x5e, 0xcf, 0x08, 0xbb, 0xf2, 0xc2, 0x80,
|
||||
0x55, 0xc6, 0x52, 0x25, 0xc9, 0xa9, 0x84, 0x7a, 0xbd, 0x94].into(),
|
||||
[0x59, 0x6e, 0x82, 0x21, 0xa3, 0x0b, 0xfe, 0x6e, 0x7e, 0xff,
|
||||
0x67, 0xfe, 0xe6, 0x64, 0xa0, 0x1c, 0x73, 0xba, 0x3c, 0x56].into(),
|
||||
[0xfa, 0xad, 0xfa, 0xce, 0x3f, 0xbd, 0x81, 0xce, 0x37, 0xb0,
|
||||
0xe1, 0x9c, 0x0b, 0x65, 0xff, 0x42, 0x34, 0x14, 0x81, 0x32].into(),
|
||||
],
|
||||
)),
|
||||
])
|
||||
}
|
||||
|
||||
/// Return iterator of given header ancestors.
|
||||
pub(crate) fn ancestry<'a, S: Storage>(storage: &'a S, header: &Header) -> impl Iterator<Item = (H256, Header)> + 'a {
|
||||
let mut parent_hash = header.parent_hash.clone();
|
||||
from_fn(move || {
|
||||
let header = storage.header(&parent_hash);
|
||||
match header {
|
||||
Some(header) => {
|
||||
if header.number == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hash = parent_hash.clone();
|
||||
parent_hash = header.parent_hash.clone();
|
||||
Some((hash, header))
|
||||
},
|
||||
None => None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use std::collections::{HashMap, hash_map::Entry};
|
||||
use parity_crypto::publickey::{KeyPair, Secret, sign};
|
||||
use primitives::{H520, rlp_encode};
|
||||
use super::*;
|
||||
|
||||
pub fn genesis() -> Header {
|
||||
Header {
|
||||
seal: vec![
|
||||
vec![42].into(),
|
||||
vec![].into(),
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_i(storage: &InMemoryStorage, number: u64, validators: &[KeyPair]) -> Header {
|
||||
custom_block_i(storage, number, validators, |_| {})
|
||||
}
|
||||
|
||||
pub fn custom_block_i(
|
||||
storage: &InMemoryStorage,
|
||||
number: u64,
|
||||
validators: &[KeyPair],
|
||||
customize: impl FnOnce(&mut Header),
|
||||
) -> Header {
|
||||
let validator_index: u8 = (number % (validators.len() as u64)) as _;
|
||||
let mut header = Header {
|
||||
number,
|
||||
parent_hash: storage.headers_by_number[&(number - 1)][0].clone(),
|
||||
gas_limit: 0x2000.into(),
|
||||
author: validator(validator_index).address().to_fixed_bytes().into(),
|
||||
seal: vec![
|
||||
vec![number as u8 + 42].into(),
|
||||
vec![].into(),
|
||||
],
|
||||
difficulty: number.into(),
|
||||
..Default::default()
|
||||
};
|
||||
customize(&mut header);
|
||||
signed_header(validators, header, number + 42)
|
||||
}
|
||||
|
||||
pub fn signed_header(validators: &[KeyPair], mut header: Header, step: u64) -> Header {
|
||||
let message = header.seal_hash(false).unwrap();
|
||||
let validator_index = (step % validators.len() as u64) as usize;
|
||||
let signature = sign(validators[validator_index].secret(), &message.as_fixed_bytes().into()).unwrap();
|
||||
let signature: [u8; 65] = signature.into();
|
||||
let signature = H520::from(signature);
|
||||
header.seal[1] = rlp_encode(&signature);
|
||||
header
|
||||
}
|
||||
|
||||
pub fn validator(index: u8) -> KeyPair {
|
||||
KeyPair::from_secret(Secret::from([index + 1; 32])).unwrap()
|
||||
}
|
||||
|
||||
pub fn validators_addresses(count: u8) -> Vec<Address> {
|
||||
(0..count as usize).map(|i| validator(i as u8).address().as_fixed_bytes().into()).collect()
|
||||
}
|
||||
|
||||
pub struct InMemoryStorage {
|
||||
best_block: (u64, H256, U256),
|
||||
finalized_block: (u64, H256),
|
||||
oldest_unpruned_block: u64,
|
||||
headers: HashMap<H256, StoredHeader>,
|
||||
headers_by_number: HashMap<u64, Vec<H256>>,
|
||||
next_validators_set_id: u64,
|
||||
validators_sets: HashMap<u64, (H256, Vec<Address>)>,
|
||||
validators_sets_rc: HashMap<u64, u64>,
|
||||
scheduled_changes: HashMap<H256, Vec<Address>>,
|
||||
}
|
||||
|
||||
impl InMemoryStorage {
|
||||
pub fn new(initial_header: Header, initial_validators: Vec<Address>) -> Self {
|
||||
let hash = initial_header.hash();
|
||||
InMemoryStorage {
|
||||
best_block: (initial_header.number, hash, 0.into()),
|
||||
finalized_block: (initial_header.number, hash),
|
||||
oldest_unpruned_block: initial_header.number,
|
||||
headers_by_number: vec![(initial_header.number, vec![hash])].into_iter().collect(),
|
||||
headers: vec![(
|
||||
hash,
|
||||
StoredHeader {
|
||||
header: initial_header,
|
||||
total_difficulty: 0.into(),
|
||||
next_validators_set_id: 0,
|
||||
},
|
||||
)].into_iter().collect(),
|
||||
next_validators_set_id: 1,
|
||||
validators_sets: vec![(0, (hash, initial_validators))].into_iter().collect(),
|
||||
validators_sets_rc: vec![(0, 1)].into_iter().collect(),
|
||||
scheduled_changes: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn oldest_unpruned_block(&self) -> u64 {
|
||||
self.oldest_unpruned_block
|
||||
}
|
||||
|
||||
pub(crate) fn stored_header(&self, hash: &H256) -> Option<&StoredHeader> {
|
||||
self.headers.get(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl Storage for InMemoryStorage {
|
||||
fn best_block(&self) -> (u64, H256, U256) {
|
||||
self.best_block.clone()
|
||||
}
|
||||
|
||||
fn finalized_block(&self) -> (u64, H256) {
|
||||
self.finalized_block.clone()
|
||||
}
|
||||
|
||||
fn header(&self, hash: &H256) -> Option<Header> {
|
||||
self.headers.get(hash).map(|header| header.header.clone())
|
||||
}
|
||||
|
||||
fn import_context(&self, parent_hash: &H256) -> Option<ImportContext> {
|
||||
self.headers.get(parent_hash)
|
||||
.map(|parent_header| {
|
||||
let (next_validators_set_start, next_validators) =
|
||||
self.validators_sets.get(&parent_header.next_validators_set_id).unwrap();
|
||||
ImportContext {
|
||||
parent_header: parent_header.header.clone(),
|
||||
parent_total_difficulty: parent_header.total_difficulty,
|
||||
next_validators_set_id: parent_header.next_validators_set_id,
|
||||
next_validators_set: (*next_validators_set_start, next_validators.clone()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn scheduled_change(&self, hash: &H256) -> Option<Vec<Address>> {
|
||||
self.scheduled_changes.get(hash).cloned()
|
||||
}
|
||||
|
||||
fn insert_header(&mut self, header: HeaderToImport) {
|
||||
if header.is_best {
|
||||
self.best_block = (header.header.number, header.hash, header.total_difficulty);
|
||||
}
|
||||
if let Some(scheduled_change) = header.scheduled_change {
|
||||
self.scheduled_changes.insert(header.hash, scheduled_change);
|
||||
}
|
||||
let next_validators_set_id = match header.enacted_change {
|
||||
Some(enacted_change) => {
|
||||
let next_validators_set_id = self.next_validators_set_id;
|
||||
self.next_validators_set_id += 1;
|
||||
self.validators_sets.insert(next_validators_set_id, (header.hash, enacted_change));
|
||||
self.validators_sets_rc.insert(next_validators_set_id, 1);
|
||||
next_validators_set_id
|
||||
},
|
||||
None => {
|
||||
*self.validators_sets_rc.entry(header.context.next_validators_set_id).or_default() += 1;
|
||||
header.context.next_validators_set_id
|
||||
},
|
||||
};
|
||||
|
||||
self.headers_by_number.entry(header.header.number).or_default().push(header.hash);
|
||||
self.headers.insert(header.hash, StoredHeader {
|
||||
header: header.header,
|
||||
total_difficulty: header.total_difficulty,
|
||||
next_validators_set_id,
|
||||
});
|
||||
}
|
||||
|
||||
fn finalize_headers(
|
||||
&mut self,
|
||||
finalized: Option<(u64, H256)>,
|
||||
prune_end: Option<u64>,
|
||||
) {
|
||||
let finalized_number = finalized.as_ref().map(|f| f.0).unwrap_or_else(|| self.finalized_block.0);
|
||||
if let Some(finalized) = finalized {
|
||||
self.finalized_block = finalized;
|
||||
}
|
||||
|
||||
if let Some(prune_end) = prune_end {
|
||||
let prune_begin = self.oldest_unpruned_block;
|
||||
|
||||
for number in prune_begin..prune_end {
|
||||
let blocks_at_number = self.headers_by_number.remove(&number);
|
||||
|
||||
// ensure that unfinalized headers we want to prune do not have scheduled changes
|
||||
if number > finalized_number {
|
||||
if let Some(ref blocks_at_number) = blocks_at_number {
|
||||
if blocks_at_number.iter().any(|block| self.scheduled_changes.contains_key(block)) {
|
||||
self.headers_by_number.insert(number, blocks_at_number.clone());
|
||||
self.oldest_unpruned_block = number;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// physically remove headers and (probably) obsolete validators sets
|
||||
for hash in blocks_at_number.into_iter().flat_map(|x| x) {
|
||||
let header = self.headers.remove(&hash);
|
||||
self.scheduled_changes.remove(&hash);
|
||||
if let Some(header) = header {
|
||||
match self.validators_sets_rc.entry(header.next_validators_set_id) {
|
||||
Entry::Occupied(mut entry) => if *entry.get() == 1 {
|
||||
entry.remove();
|
||||
} else {
|
||||
*entry.get_mut() -= 1;
|
||||
},
|
||||
Entry::Vacant(_) => unreachable!("there's entry for each header")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.oldest_unpruned_block = prune_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity-Bridge.
|
||||
|
||||
// Parity-Bridge 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.
|
||||
|
||||
// Parity-Bridge 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 Parity-Bridge. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use primitives::{Address, H256, Header, LogEntry, Receipt, U256};
|
||||
use crate::Storage;
|
||||
use crate::error::Error;
|
||||
|
||||
/// The hash of InitiateChange event of the validators set contract.
|
||||
const CHANGE_EVENT_HASH: &'static [u8; 32] = &[0x55, 0x25, 0x2f, 0xa6, 0xee, 0xe4, 0x74, 0x1b,
|
||||
0x4e, 0x24, 0xa7, 0x4a, 0x70, 0xe9, 0xc1, 0x1f, 0xd2, 0xc2, 0x28, 0x1d, 0xf8, 0xd6, 0xea,
|
||||
0x13, 0x12, 0x6f, 0xf8, 0x45, 0xf7, 0x82, 0x5c, 0x89];
|
||||
|
||||
/// Where source of validators addresses come from. This covers the chain lifetime.
|
||||
pub enum ValidatorsConfiguration {
|
||||
/// There's a single source for the whole chain lifetime.
|
||||
Single(ValidatorsSource),
|
||||
/// Validators source changes at given blocks. The blocks are ordered
|
||||
/// by the block number.
|
||||
Multi(Vec<(u64, ValidatorsSource)>),
|
||||
}
|
||||
|
||||
/// Where validators addresses come from.
|
||||
///
|
||||
/// This source is valid within some blocks range. The blocks range could
|
||||
/// cover multiple epochs - i.e. the validators that are authoring blocks
|
||||
/// within this range could change, but the source itself can not.
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub enum ValidatorsSource {
|
||||
/// The validators addresses are hardcoded and never change.
|
||||
List(Vec<Address>),
|
||||
/// The validators addresses are determined by the validators set contract
|
||||
/// deployed at given address. The contract must implement the `ValidatorSet`
|
||||
/// interface. Additionally, the initial validators set must be provided.
|
||||
Contract(Address, Vec<Address>),
|
||||
}
|
||||
|
||||
/// Validators manager.
|
||||
pub struct Validators<'a> {
|
||||
config: &'a ValidatorsConfiguration,
|
||||
}
|
||||
|
||||
impl<'a> Validators<'a> {
|
||||
/// Creates new validators manager using given configuration.
|
||||
pub fn new(config: &'a ValidatorsConfiguration) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
/// Returns true if header (probabilistically) signals validators change and
|
||||
/// the caller needs to provide transactions receipts to import the header.
|
||||
pub fn maybe_signals_validators_change(&self, header: &Header) -> bool {
|
||||
let (_, _, source) = self.source_at(header.number);
|
||||
|
||||
// if we are taking validators set from the fixed list, there's always
|
||||
// single epoch
|
||||
// => we never require transactions receipts
|
||||
let contract_address = match source {
|
||||
ValidatorsSource::List(_) => return false,
|
||||
ValidatorsSource::Contract(contract_address, _) => contract_address,
|
||||
};
|
||||
|
||||
// else we need to check logs bloom and if it has required bits set, it means
|
||||
// that the contract has (probably) emitted epoch change event
|
||||
let expected_bloom = LogEntry {
|
||||
address: *contract_address,
|
||||
topics: vec![
|
||||
CHANGE_EVENT_HASH.into(),
|
||||
header.parent_hash,
|
||||
],
|
||||
data: Vec::new(), // irrelevant for bloom.
|
||||
}.bloom();
|
||||
|
||||
header.log_bloom.contains(&expected_bloom)
|
||||
}
|
||||
|
||||
/// Extracts validators change signal from the header.
|
||||
///
|
||||
/// Returns tuple where first element is the change scheduled by this header
|
||||
/// (i.e. this change is only applied starting from the block that has finalized
|
||||
/// current block). The second element is the immediately applied change.
|
||||
pub fn extract_validators_change(
|
||||
&self,
|
||||
header: &Header,
|
||||
receipts: Option<Vec<Receipt>>,
|
||||
) -> Result<(Option<Vec<Address>>, Option<Vec<Address>>), Error> {
|
||||
// let's first check if new source is starting from this header
|
||||
let (source_index, _, source) = self.source_at(header.number);
|
||||
let (next_starts_at, next_source) = self.source_at_next_header(source_index, header.number);
|
||||
if next_starts_at == header.number {
|
||||
match *next_source {
|
||||
ValidatorsSource::List(ref new_list) => return Ok((None, Some(new_list.clone()))),
|
||||
ValidatorsSource::Contract(_, ref new_list) => return Ok((Some(new_list.clone()), None)),
|
||||
}
|
||||
}
|
||||
|
||||
// else deal with previous source
|
||||
//
|
||||
// if we are taking validators set from the fixed list, there's always
|
||||
// single epoch
|
||||
// => we never require transactions receipts
|
||||
let contract_address = match source {
|
||||
ValidatorsSource::List(_) => return Ok((None, None)),
|
||||
ValidatorsSource::Contract(contract_address, _) => contract_address,
|
||||
};
|
||||
|
||||
// else we need to check logs bloom and if it has required bits set, it means
|
||||
// that the contract has (probably) emitted epoch change event
|
||||
let expected_bloom = LogEntry {
|
||||
address: *contract_address,
|
||||
topics: vec![
|
||||
CHANGE_EVENT_HASH.into(),
|
||||
header.parent_hash,
|
||||
],
|
||||
data: Vec::new(), // irrelevant for bloom.
|
||||
}.bloom();
|
||||
|
||||
if !header.log_bloom.contains(&expected_bloom) {
|
||||
return Ok((None, None));
|
||||
}
|
||||
|
||||
let receipts = receipts.ok_or(Error::MissingTransactionsReceipts)?;
|
||||
if !header.check_transactions_receipts(&receipts) {
|
||||
return Err(Error::TransactionsReceiptsMismatch);
|
||||
}
|
||||
|
||||
// iterate in reverse because only the _last_ change in a given
|
||||
// block actually has any effect
|
||||
Ok((receipts.iter()
|
||||
.rev()
|
||||
.filter(|r| r.log_bloom.contains(&expected_bloom))
|
||||
.flat_map(|r| r.logs.iter())
|
||||
.filter(|l| l.address == *contract_address &&
|
||||
l.topics.len() == 2 &&
|
||||
l.topics[0].as_fixed_bytes() == CHANGE_EVENT_HASH &&
|
||||
l.topics[1] == header.parent_hash
|
||||
)
|
||||
.filter_map(|l| {
|
||||
let data_len = l.data.len();
|
||||
if data_len < 64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_validators_len_u256 = U256::from_big_endian(&l.data[32..64]);
|
||||
let new_validators_len = new_validators_len_u256.low_u64();
|
||||
if new_validators_len_u256 != new_validators_len.into() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if (data_len - 64) as u64 != new_validators_len.saturating_mul(32) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(l.data[64..]
|
||||
.chunks(32)
|
||||
.map(|chunk| {
|
||||
let mut new_validator = Address::default();
|
||||
new_validator.as_mut().copy_from_slice(&chunk[12..32]);
|
||||
new_validator
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
.next(), None))
|
||||
}
|
||||
|
||||
/// Finalize changes when blocks are finalized.
|
||||
pub fn finalize_validators_change<S: Storage>(
|
||||
&self,
|
||||
storage: &mut S,
|
||||
finalized_blocks: &[(u64, H256)],
|
||||
) -> Option<Vec<Address>> {
|
||||
for (_, finalized_hash) in finalized_blocks.iter().rev() {
|
||||
if let Some(changes) = storage.scheduled_change(finalized_hash) {
|
||||
return Some(changes);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns source of validators that should author the header.
|
||||
fn source_at<'b>(&'b self, header_number: u64) -> (usize, u64, &'b ValidatorsSource) {
|
||||
match self.config {
|
||||
ValidatorsConfiguration::Single(ref source) => (0, 0, source),
|
||||
ValidatorsConfiguration::Multi(ref sources) => sources.iter().rev()
|
||||
.enumerate()
|
||||
.find(|(_, &(begin, _))| begin < header_number)
|
||||
.map(|(i, (begin, source))| (sources.len() - 1 - i, *begin, source))
|
||||
.expect("there's always entry for the initial block;\
|
||||
we do not touch any headers with number < initial block number; qed"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns source of validators that should author the next header.
|
||||
fn source_at_next_header<'b>(
|
||||
&'b self,
|
||||
header_source_index: usize,
|
||||
header_number: u64,
|
||||
) -> (u64, &'b ValidatorsSource) {
|
||||
match self.config {
|
||||
ValidatorsConfiguration::Single(ref source) => (0, source),
|
||||
ValidatorsConfiguration::Multi(ref sources) => {
|
||||
let next_source_index = header_source_index + 1;
|
||||
if next_source_index < sources.len() {
|
||||
let next_source = &sources[next_source_index];
|
||||
if next_source.0 < header_number + 1 {
|
||||
return (next_source.0, &next_source.1);
|
||||
}
|
||||
}
|
||||
|
||||
let source = &sources[header_source_index];
|
||||
(source.0, &source.1)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidatorsSource {
|
||||
/// Returns initial validators set.
|
||||
pub fn initial_epoch_validators(&self) -> Vec<Address> {
|
||||
match self {
|
||||
ValidatorsSource::List(ref list) => list.clone(),
|
||||
ValidatorsSource::Contract(_, ref list) => list.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get validator that should author the block at given step.
|
||||
pub fn step_validator(header_validators: &[Address], header_step: u64) -> Address {
|
||||
header_validators[(header_step % header_validators.len() as u64) as usize]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use primitives::TransactionOutcome;
|
||||
use crate::kovan_validators_config;
|
||||
use super::*;
|
||||
|
||||
pub(crate) fn validators_change_recept(parent_hash: H256) -> Receipt {
|
||||
Receipt {
|
||||
gas_used: 0.into(),
|
||||
log_bloom: (&[0xff; 256]).into(),
|
||||
outcome: TransactionOutcome::Unknown,
|
||||
logs: vec![
|
||||
LogEntry {
|
||||
address: [3; 20].into(),
|
||||
topics: vec![
|
||||
CHANGE_EVENT_HASH.into(),
|
||||
parent_hash,
|
||||
],
|
||||
data: vec![
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_at_works() {
|
||||
let config = ValidatorsConfiguration::Multi(vec![
|
||||
(0, ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
(100, ValidatorsSource::List(vec![[2; 20].into()])),
|
||||
(200, ValidatorsSource::Contract([3; 20].into(), vec![[3; 20].into()])),
|
||||
]);
|
||||
let validators = Validators::new(&config);
|
||||
|
||||
assert_eq!(
|
||||
validators.source_at(99),
|
||||
(0, 0, &ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
);
|
||||
assert_eq!(
|
||||
validators.source_at_next_header(0, 99),
|
||||
(0, &ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
validators.source_at(100),
|
||||
(0, 0, &ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
);
|
||||
assert_eq!(
|
||||
validators.source_at_next_header(0, 100),
|
||||
(100, &ValidatorsSource::List(vec![[2; 20].into()])),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
validators.source_at(200),
|
||||
(1, 100, &ValidatorsSource::List(vec![[2; 20].into()])),
|
||||
);
|
||||
assert_eq!(
|
||||
validators.source_at_next_header(1, 200),
|
||||
(200, &ValidatorsSource::Contract([3; 20].into(), vec![[3; 20].into()])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_signals_validators_change_works() {
|
||||
// when contract is active, but bloom has no required bits set
|
||||
let config = kovan_validators_config();
|
||||
let validators = Validators::new(&config);
|
||||
let mut header = Header::default();
|
||||
header.number = u64::max_value();
|
||||
assert!(!validators.maybe_signals_validators_change(&header));
|
||||
|
||||
// when contract is active and bloom has required bits set
|
||||
header.log_bloom = (&[0xff; 256]).into();
|
||||
assert!(validators.maybe_signals_validators_change(&header));
|
||||
|
||||
// when list is active and bloom has required bits set
|
||||
let config = ValidatorsConfiguration::Single(ValidatorsSource::List(vec![[42; 20].into()]));
|
||||
let validators = Validators::new(&config);
|
||||
assert!(!validators.maybe_signals_validators_change(&header));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_validators_change_works() {
|
||||
let config = ValidatorsConfiguration::Multi(vec![
|
||||
(0, ValidatorsSource::List(vec![[1; 20].into()])),
|
||||
(100, ValidatorsSource::List(vec![[2; 20].into()])),
|
||||
(200, ValidatorsSource::Contract([3; 20].into(), vec![[3; 20].into()])),
|
||||
]);
|
||||
let validators = Validators::new(&config);
|
||||
let mut header = Header::default();
|
||||
|
||||
// when we're at the block that switches to list source
|
||||
header.number = 100;
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, None),
|
||||
Ok((None, Some(vec![[2; 20].into()]))),
|
||||
);
|
||||
|
||||
// when we're inside list range
|
||||
header.number = 150;
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, None),
|
||||
Ok((None, None)),
|
||||
);
|
||||
|
||||
// when we're at the block that switches to contract source
|
||||
header.number = 200;
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, None),
|
||||
Ok((Some(vec![[3; 20].into()]), None)),
|
||||
);
|
||||
|
||||
// when we're inside contract range and logs bloom signals change
|
||||
// but we have no receipts
|
||||
header.number = 250;
|
||||
header.log_bloom = (&[0xff; 256]).into();
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, None),
|
||||
Err(Error::MissingTransactionsReceipts),
|
||||
);
|
||||
|
||||
// when we're inside contract range and logs bloom signals change
|
||||
// but there's no change in receipts
|
||||
header.receipts_root = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".parse().unwrap();
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, Some(Vec::new())),
|
||||
Ok((None, None)),
|
||||
);
|
||||
|
||||
// when we're inside contract range and logs bloom signals change
|
||||
// and there's change in receipts
|
||||
let receipts = vec![validators_change_recept(Default::default())];
|
||||
header.receipts_root = "81ce88dc524403b796222046bf3daf543978329b87ffd50228f1d3987031dc45".parse().unwrap();
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, Some(receipts)),
|
||||
Ok((Some(vec![[7; 20].into()]), None)),
|
||||
);
|
||||
|
||||
// when incorrect receipts root passed
|
||||
assert_eq!(
|
||||
validators.extract_validators_change(&header, Some(Vec::new())),
|
||||
Err(Error::TransactionsReceiptsMismatch),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity-Bridge.
|
||||
|
||||
// Parity-Bridge 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.
|
||||
|
||||
// Parity-Bridge 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 Parity-Bridge. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use sp_io::crypto::secp256k1_ecdsa_recover;
|
||||
use primitives::{Address, Header, H256, H520, SealedEmptyStep, U128, U256, public_to_address};
|
||||
use crate::{AuraConfiguration, ImportContext, Storage};
|
||||
use crate::error::Error;
|
||||
use crate::validators::step_validator;
|
||||
|
||||
/// Verify header by Aura rules.
|
||||
pub fn verify_aura_header<S: Storage>(
|
||||
storage: &S,
|
||||
params: &AuraConfiguration,
|
||||
header: &Header,
|
||||
) -> Result<ImportContext, Error> {
|
||||
// let's do the lightest check first
|
||||
contextless_checks(params, header)?;
|
||||
|
||||
// the rest of checks requires parent
|
||||
let context = storage.import_context(&header.parent_hash).ok_or(Error::MissingParentBlock)?;
|
||||
let validators = context.validators();
|
||||
let header_step = header.step().ok_or(Error::MissingStep)?;
|
||||
let parent_step = context.parent_header().step().ok_or(Error::MissingStep)?;
|
||||
|
||||
// Ensure header is from the step after context.
|
||||
if header_step == parent_step
|
||||
|| (header.number >= params.validate_step_transition && header_step <= parent_step) {
|
||||
return Err(Error::DoubleVote);
|
||||
}
|
||||
|
||||
// If empty step messages are enabled we will validate the messages in the seal, missing messages are not
|
||||
// reported as there's no way to tell whether the empty step message was never sent or simply not included.
|
||||
let empty_steps_len = match header.number >= params.empty_steps_transition {
|
||||
true => {
|
||||
let strict_empty_steps = header.number >= params.strict_empty_steps_transition;
|
||||
let empty_steps = header.empty_steps().ok_or(Error::MissingEmptySteps)?;
|
||||
let empty_steps_len = empty_steps.len();
|
||||
let mut prev_empty_step = 0;
|
||||
|
||||
for empty_step in empty_steps {
|
||||
if empty_step.step <= parent_step || empty_step.step >= header_step {
|
||||
return Err(Error::InsufficientProof);
|
||||
}
|
||||
|
||||
if !verify_empty_step(&header.parent_hash, &empty_step, validators) {
|
||||
return Err(Error::InsufficientProof);
|
||||
}
|
||||
|
||||
if strict_empty_steps {
|
||||
if empty_step.step <= prev_empty_step {
|
||||
return Err(Error::InsufficientProof);
|
||||
}
|
||||
|
||||
prev_empty_step = empty_step.step;
|
||||
}
|
||||
}
|
||||
|
||||
empty_steps_len
|
||||
},
|
||||
false => 0,
|
||||
};
|
||||
|
||||
// Validate chain score.
|
||||
if header.number >= params.validate_score_transition {
|
||||
let expected_difficulty = calculate_score(parent_step, header_step, empty_steps_len as _);
|
||||
if header.difficulty != expected_difficulty {
|
||||
return Err(Error::InvalidDifficulty);
|
||||
}
|
||||
}
|
||||
|
||||
let expected_validator = step_validator(validators, header_step);
|
||||
if header.author != expected_validator {
|
||||
return Err(Error::NotValidator);
|
||||
}
|
||||
|
||||
let validator_signature = header.signature().ok_or(Error::MissingSignature)?;
|
||||
let header_seal_hash = header
|
||||
.seal_hash(header.number >= params.empty_steps_transition)
|
||||
.ok_or(Error::MissingEmptySteps)?;
|
||||
let is_invalid_proposer = !verify_signature(&expected_validator, &validator_signature, &header_seal_hash);
|
||||
if is_invalid_proposer {
|
||||
return Err(Error::NotValidator);
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
/// Perform basic checks that only require header iteself.
|
||||
fn contextless_checks(config: &AuraConfiguration, header: &Header) -> Result<(), Error> {
|
||||
let expected_seal_fields = expected_header_seal_fields(config, header);
|
||||
if header.seal.len() != expected_seal_fields {
|
||||
return Err(Error::InvalidSealArity);
|
||||
}
|
||||
if header.number >= u64::max_value() {
|
||||
return Err(Error::RidiculousNumber);
|
||||
}
|
||||
if header.gas_used > header.gas_limit {
|
||||
return Err(Error::TooMuchGasUsed);
|
||||
}
|
||||
if header.gas_limit < config.min_gas_limit {
|
||||
return Err(Error::InvalidGasLimit);
|
||||
}
|
||||
if header.gas_limit > config.max_gas_limit {
|
||||
return Err(Error::InvalidGasLimit);
|
||||
}
|
||||
if header.number != 0 && header.extra_data.len() as u64 > config.maximum_extra_data_size {
|
||||
return Err(Error::ExtraDataOutOfBounds);
|
||||
}
|
||||
|
||||
// we can't detect if block is from future in runtime
|
||||
// => let's only do an overflow check
|
||||
if header.timestamp > i32::max_value() as u64 {
|
||||
return Err(Error::TimestampOverflow);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns expected number of seal fields in the header.
|
||||
fn expected_header_seal_fields(config: &AuraConfiguration, header: &Header) -> usize {
|
||||
if header.number >= config.empty_steps_transition {
|
||||
3
|
||||
} else {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify single sealed empty step.
|
||||
fn verify_empty_step(parent_hash: &H256, step: &SealedEmptyStep, validators: &[Address]) -> bool {
|
||||
let expected_validator = step_validator(validators, step.step);
|
||||
let message = step.message(parent_hash);
|
||||
verify_signature(&expected_validator, &step.signature, &message)
|
||||
}
|
||||
|
||||
/// Chain scoring: total weight is sqrt(U256::max_value())*height - step
|
||||
fn calculate_score(parent_step: u64, current_step: u64, current_empty_steps: usize) -> U256 {
|
||||
U256::from(U128::max_value()) + U256::from(parent_step) - U256::from(current_step) + U256::from(current_empty_steps)
|
||||
}
|
||||
|
||||
/// Verify that the signature over message has been produced by given validator.
|
||||
fn verify_signature(expected_validator: &Address, signature: &H520, message: &H256) -> bool {
|
||||
secp256k1_ecdsa_recover(signature.as_fixed_bytes(), message.as_fixed_bytes())
|
||||
.map(|public| public_to_address(&public))
|
||||
.map(|address| *expected_validator == address)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use parity_crypto::publickey::{KeyPair, sign};
|
||||
use primitives::{H520, rlp_encode};
|
||||
use crate::kovan_aura_config;
|
||||
use crate::tests::{InMemoryStorage, genesis, signed_header, validator, validators_addresses};
|
||||
use super::*;
|
||||
|
||||
fn sealed_empty_step(validators: &[KeyPair], parent_hash: &H256, step: u64) -> SealedEmptyStep {
|
||||
let mut empty_step = SealedEmptyStep { step, signature: Default::default() };
|
||||
let message = empty_step.message(parent_hash);
|
||||
let validator_index = (step % validators.len() as u64) as usize;
|
||||
let signature: [u8; 65] = sign(
|
||||
validators[validator_index].secret(),
|
||||
&message.as_fixed_bytes().into(),
|
||||
).unwrap().into();
|
||||
empty_step.signature = signature.into();
|
||||
empty_step
|
||||
}
|
||||
|
||||
fn verify_with_config(config: &AuraConfiguration, header: &Header) -> Result<ImportContext, Error> {
|
||||
let storage = InMemoryStorage::new(genesis(), validators_addresses(3));
|
||||
verify_aura_header(&storage, &config, header)
|
||||
}
|
||||
|
||||
fn default_verify(header: &Header) -> Result<ImportContext, Error> {
|
||||
verify_with_config(&kovan_aura_config(), header)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_seal_count() {
|
||||
// when there are no seals at all
|
||||
let mut header = Header::default();
|
||||
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
|
||||
|
||||
// when there's single seal (we expect 2 or 3 seals)
|
||||
header.seal = vec![vec![].into()];
|
||||
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
|
||||
|
||||
// when there's 3 seals (we expect 2 on Kovan)
|
||||
header.seal = vec![vec![].into(), vec![].into(), vec![].into()];
|
||||
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
|
||||
|
||||
// when there's 2 seals
|
||||
header.seal = vec![vec![].into(), vec![].into()];
|
||||
assert_ne!(default_verify(&header), Err(Error::InvalidSealArity));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_header_number() {
|
||||
// when number is u64::max_value()
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into(), vec![].into()],
|
||||
number: u64::max_value(),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(default_verify(&header), Err(Error::RidiculousNumber));
|
||||
|
||||
// when header is < u64::max_value()
|
||||
header.seal = vec![vec![].into(), vec![].into()];
|
||||
header.number -= 1;
|
||||
assert_ne!(default_verify(&header), Err(Error::RidiculousNumber));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_gas_used() {
|
||||
// when gas used is larger than gas limit
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_used: 1.into(),
|
||||
gas_limit: 0.into(),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(default_verify(&header), Err(Error::TooMuchGasUsed));
|
||||
|
||||
// when gas used is less than gas limit
|
||||
header.gas_limit = 1.into();
|
||||
assert_ne!(default_verify(&header), Err(Error::TooMuchGasUsed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_gas_limit() {
|
||||
let mut config = kovan_aura_config();
|
||||
config.min_gas_limit = 100.into();
|
||||
config.max_gas_limit = 200.into();
|
||||
|
||||
// when limit is lower than expected
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_limit: 50.into(),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
||||
|
||||
// when limit is larger than expected
|
||||
header.gas_limit = 250.into();
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
||||
|
||||
// when limit is within expected range
|
||||
header.gas_limit = 150.into();
|
||||
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_extra_data_len() {
|
||||
// when extra data is too large
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_limit: kovan_aura_config().min_gas_limit,
|
||||
extra_data: std::iter::repeat(42).take(1000).collect::<Vec<_>>().into(),
|
||||
number: 1,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
|
||||
|
||||
// when extra data size is OK
|
||||
header.extra_data = std::iter::repeat(42).take(10).collect::<Vec<_>>().into();
|
||||
assert_ne!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_timestamp() {
|
||||
// when timestamp overflows i32
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_limit: kovan_aura_config().min_gas_limit,
|
||||
timestamp: i32::max_value() as u64 + 1,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(default_verify(&header), Err(Error::TimestampOverflow));
|
||||
|
||||
// when timestamp doesn't overflow i32
|
||||
header.timestamp -= 1;
|
||||
assert_ne!(default_verify(&header), Err(Error::TimestampOverflow));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_parent_existence() {
|
||||
// when there's no parent in the storage
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_limit: kovan_aura_config().min_gas_limit,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(default_verify(&header), Err(Error::MissingParentBlock));
|
||||
|
||||
// when parent is in the storage
|
||||
header.parent_hash = genesis().hash();
|
||||
assert_ne!(default_verify(&header), Err(Error::MissingParentBlock));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_step() {
|
||||
// when step is missing from seals
|
||||
let mut header = Header {
|
||||
seal: vec![vec![].into(), vec![].into()],
|
||||
gas_limit: kovan_aura_config().min_gas_limit,
|
||||
parent_hash: genesis().hash(),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(default_verify(&header), Err(Error::MissingStep));
|
||||
|
||||
// when step is the same as for the parent block
|
||||
header.seal = vec![
|
||||
vec![42].into(),
|
||||
vec![].into(),
|
||||
];
|
||||
assert_eq!(default_verify(&header), Err(Error::DoubleVote));
|
||||
|
||||
// when step is OK
|
||||
header.seal = vec![
|
||||
vec![43].into(),
|
||||
vec![].into(),
|
||||
];
|
||||
assert_ne!(default_verify(&header), Err(Error::DoubleVote));
|
||||
|
||||
// now check with validate_step check enabled
|
||||
let mut config = kovan_aura_config();
|
||||
config.validate_step_transition = 0;
|
||||
|
||||
// when step is lesser that for the parent block
|
||||
header.seal = vec![
|
||||
vec![40].into(),
|
||||
vec![].into(),
|
||||
];
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::DoubleVote));
|
||||
|
||||
// when step is OK
|
||||
header.seal = vec![
|
||||
vec![44].into(),
|
||||
vec![].into(),
|
||||
];
|
||||
assert_ne!(verify_with_config(&config, &header), Err(Error::DoubleVote));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_empty_step() {
|
||||
let validators = vec![validator(0), validator(1), validator(2)];
|
||||
let mut config = kovan_aura_config();
|
||||
config.empty_steps_transition = 0;
|
||||
|
||||
// when empty step duplicates parent step
|
||||
let mut header = Header {
|
||||
seal: vec![
|
||||
vec![45].into(),
|
||||
vec![142].into(),
|
||||
SealedEmptyStep::rlp_of(&[
|
||||
sealed_empty_step(&validators, &genesis().hash(), 42),
|
||||
]),
|
||||
],
|
||||
gas_limit: kovan_aura_config().min_gas_limit,
|
||||
parent_hash: genesis().hash(),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||
|
||||
// when empty step signature check fails
|
||||
let mut wrong_sealed_empty_step = sealed_empty_step(&validators, &genesis().hash(), 43);
|
||||
wrong_sealed_empty_step.signature = Default::default();
|
||||
header.seal[2] = SealedEmptyStep::rlp_of(&[wrong_sealed_empty_step]);
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||
|
||||
// when we are accepting strict empty steps and they come not in order
|
||||
config.strict_empty_steps_transition = 0;
|
||||
header.seal[2] = SealedEmptyStep::rlp_of(&[
|
||||
sealed_empty_step(&validators, &genesis().hash(), 44),
|
||||
sealed_empty_step(&validators, &genesis().hash(), 43),
|
||||
]);
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||
|
||||
// when empty steps are OK
|
||||
header.seal[2] = SealedEmptyStep::rlp_of(&[
|
||||
sealed_empty_step(&validators, &genesis().hash(), 43),
|
||||
sealed_empty_step(&validators, &genesis().hash(), 44),
|
||||
]);
|
||||
assert_ne!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_chain_score() {
|
||||
let mut config = kovan_aura_config();
|
||||
config.validate_score_transition = 0;
|
||||
|
||||
// when chain score is invalid
|
||||
let mut header = Header {
|
||||
seal: vec![
|
||||
vec![43].into(),
|
||||
vec![].into(),
|
||||
],
|
||||
gas_limit: kovan_aura_config().min_gas_limit,
|
||||
parent_hash: genesis().hash(),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
|
||||
|
||||
// when chain score is accepted
|
||||
header.difficulty = calculate_score(42, 43, 0);
|
||||
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_validator() {
|
||||
let validators = vec![validator(0), validator(1), validator(2)];
|
||||
let good_header = signed_header(&validators, Header {
|
||||
author: validators[1].address().as_fixed_bytes().into(),
|
||||
seal: vec![
|
||||
vec![43].into(),
|
||||
vec![].into(),
|
||||
],
|
||||
gas_limit: kovan_aura_config().min_gas_limit,
|
||||
parent_hash: genesis().hash(),
|
||||
..Default::default()
|
||||
}, 43);
|
||||
|
||||
// when header author is invalid
|
||||
let mut header = good_header.clone();
|
||||
header.author = Default::default();
|
||||
assert_eq!(default_verify(&header), Err(Error::NotValidator));
|
||||
|
||||
// when header signature is invalid
|
||||
let mut header = good_header.clone();
|
||||
header.seal[1] = rlp_encode(&H520::default());
|
||||
assert_eq!(default_verify(&header), Err(Error::NotValidator));
|
||||
|
||||
// when everything is OK
|
||||
assert_eq!(default_verify(&good_header).map(|_| ()), Ok(()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
[package]
|
||||
name = "sp-bridge-eth-poa"
|
||||
description = "Primitives of Ethereum PoA Bridge module."
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", optional = true }
|
||||
serde-big-array = { version = "0.2", optional = true }
|
||||
ethbloom = { version = "0.8", default-features = false }
|
||||
parity-bytes = { version = "0.1", default-features = false }
|
||||
primitive-types = { version = "0.6", default-features = false, features = ["codec", "rlp"] }
|
||||
fixed-hash = { version = "0.5", default-features = false }
|
||||
impl-rlp = { version = "0.2", default-features = false }
|
||||
impl-serde = { version = "0.2.3", optional = true }
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
|
||||
rlp = { version = "0.4", default-features = false }
|
||||
sp-std = { version = "2.0.0", default-features = false, path = "../std" }
|
||||
sp-runtime = { version = "2.0.0", default-features = false, path = "../runtime" }
|
||||
sp-api = { version = "2.0.0", path = "../api", default-features = false }
|
||||
sp-io = { version = "2.0.0", default-features = false, path = "../io" }
|
||||
hash-db = { version = "0.15.2", default-features = false }
|
||||
triehash = { version = "0.8.2", default-features = false }
|
||||
plain_hasher = { version = "0.2.2", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
test-helpers = []
|
||||
std = [
|
||||
"serde/std",
|
||||
"serde-big-array",
|
||||
"ethbloom/std",
|
||||
"parity-bytes/std",
|
||||
"primitive-types/std",
|
||||
"primitive-types/serde",
|
||||
"fixed-hash/std",
|
||||
"impl-rlp/std",
|
||||
"impl-serde",
|
||||
"codec/std",
|
||||
"rlp/std",
|
||||
"sp-std/std",
|
||||
"sp-runtime/std",
|
||||
"sp-api/std",
|
||||
"sp-io/std",
|
||||
"hash-db/std",
|
||||
"triehash/std",
|
||||
"plain_hasher/std",
|
||||
]
|
||||
@@ -0,0 +1,361 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity-Bridge.
|
||||
|
||||
// Parity-Bridge 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.
|
||||
|
||||
// Parity-Bridge 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 Parity-Bridge. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use parity_bytes::Bytes;
|
||||
pub use primitive_types::{H160, H256, H512, U128, U256};
|
||||
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub use rlp::encode as rlp_encode;
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_io::hashing::keccak_256;
|
||||
use codec::{Decode, Encode};
|
||||
use ethbloom::{Bloom as EthBloom, Input as BloomInput};
|
||||
use rlp::{Decodable, DecoderError, Rlp, RlpStream};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use fixed_hash::construct_fixed_hash;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
#[cfg(feature = "std")]
|
||||
use serde_big_array::big_array;
|
||||
use impl_rlp::impl_fixed_hash_rlp;
|
||||
#[cfg(feature = "std")]
|
||||
use impl_serde::impl_fixed_hash_serde;
|
||||
|
||||
construct_fixed_hash! { pub struct H520(65); }
|
||||
impl_fixed_hash_rlp!(H520, 65);
|
||||
#[cfg(feature = "std")]
|
||||
impl_fixed_hash_serde!(H520, 65);
|
||||
|
||||
/// An ethereum address.
|
||||
pub type Address = H160;
|
||||
|
||||
/// An Aura header.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Default, Serialize, Deserialize))]
|
||||
pub struct Header {
|
||||
/// Parent block hash.
|
||||
pub parent_hash: H256,
|
||||
/// Block timestamp.
|
||||
pub timestamp: u64,
|
||||
/// Block number.
|
||||
pub number: u64,
|
||||
/// Block author.
|
||||
pub author: Address,
|
||||
|
||||
/// Transactions root.
|
||||
pub transactions_root: H256,
|
||||
/// Block uncles hash.
|
||||
pub uncles_hash: H256,
|
||||
/// Block extra data.
|
||||
pub extra_data: Bytes,
|
||||
|
||||
/// State root.
|
||||
pub state_root: H256,
|
||||
/// Block receipts root.
|
||||
pub receipts_root: H256,
|
||||
/// Block bloom.
|
||||
pub log_bloom: Bloom,
|
||||
/// Gas used for contracts execution.
|
||||
pub gas_used: U256,
|
||||
/// Block gas limit.
|
||||
pub gas_limit: U256,
|
||||
|
||||
/// Block difficulty.
|
||||
pub difficulty: U256,
|
||||
/// Vector of post-RLP-encoded fields.
|
||||
pub seal: Vec<Bytes>,
|
||||
}
|
||||
|
||||
/// Information describing execution of a transaction.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)]
|
||||
pub struct Receipt {
|
||||
/// The total gas used in the block following execution of the transaction.
|
||||
pub gas_used: U256,
|
||||
/// The OR-wide combination of all logs' blooms for this transaction.
|
||||
pub log_bloom: Bloom,
|
||||
/// The logs stemming from this transaction.
|
||||
pub logs: Vec<LogEntry>,
|
||||
/// Transaction outcome.
|
||||
pub outcome: TransactionOutcome,
|
||||
}
|
||||
|
||||
/// Transaction outcome store in the receipt.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)]
|
||||
pub enum TransactionOutcome {
|
||||
/// Status and state root are unknown under EIP-98 rules.
|
||||
Unknown,
|
||||
/// State root is known. Pre EIP-98 and EIP-658 rules.
|
||||
StateRoot(H256),
|
||||
/// Status code is known. EIP-658 rules.
|
||||
StatusCode(u8),
|
||||
}
|
||||
|
||||
/// A record of execution for a `LOG` operation.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)]
|
||||
pub struct LogEntry {
|
||||
/// The address of the contract executing at the point of the `LOG` operation.
|
||||
pub address: Address,
|
||||
/// The topics associated with the `LOG` operation.
|
||||
pub topics: Vec<H256>,
|
||||
/// The data associated with the `LOG` operation.
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
/// Logs bloom.
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct Bloom(
|
||||
#[cfg_attr(feature = "std", serde(with = "BigArray"))]
|
||||
[u8; 256]
|
||||
);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
big_array! { BigArray; }
|
||||
|
||||
/// An empty step message that is included in a seal, the only difference is that it doesn't include
|
||||
/// the `parent_hash` in order to save space. The included signature is of the original empty step
|
||||
/// message, which can be reconstructed by using the parent hash of the block in which this sealed
|
||||
/// empty message is included.
|
||||
pub struct SealedEmptyStep {
|
||||
/// Signature of the original message author.
|
||||
pub signature: H520,
|
||||
/// The step this message is generated for.
|
||||
pub step: u64,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
/// Get the hash of this header (keccak of the RLP with seal).
|
||||
pub fn hash(&self) -> H256 {
|
||||
keccak_256(&self.rlp(true)).into()
|
||||
}
|
||||
|
||||
/// Check if passed transactions receipts are matching this header.
|
||||
pub fn check_transactions_receipts(&self, receipts: &Vec<Receipt>) -> bool {
|
||||
struct Keccak256Hasher;
|
||||
|
||||
impl hash_db::Hasher for Keccak256Hasher {
|
||||
type Out = H256;
|
||||
type StdHasher = plain_hasher::PlainHasher;
|
||||
const LENGTH: usize = 32;
|
||||
fn hash(x: &[u8]) -> Self::Out {
|
||||
keccak_256(x).into()
|
||||
}
|
||||
}
|
||||
|
||||
let receipts = receipts.iter().map(|r| r.rlp());
|
||||
let actual_root = triehash::ordered_trie_root::<Keccak256Hasher, _>(receipts);
|
||||
let expected_root = self.receipts_root;
|
||||
actual_root == expected_root
|
||||
}
|
||||
|
||||
/// Gets the seal hash of this header.
|
||||
pub fn seal_hash(&self, include_empty_steps: bool) -> Option<H256> {
|
||||
Some(match include_empty_steps {
|
||||
true => {
|
||||
let mut message = self.hash().as_bytes().to_vec();
|
||||
message.extend_from_slice(self.seal.get(2)?);
|
||||
keccak_256(&message).into()
|
||||
},
|
||||
false => keccak_256(&self.rlp(false)).into(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get step this header is generated for.
|
||||
pub fn step(&self) -> Option<u64> {
|
||||
self.seal.get(0).map(|x| Rlp::new(&x)).and_then(|x| x.as_val().ok())
|
||||
}
|
||||
|
||||
/// Get header author' signature.
|
||||
pub fn signature(&self) -> Option<H520> {
|
||||
self.seal.get(1).and_then(|x| Rlp::new(x).as_val().ok())
|
||||
}
|
||||
|
||||
/// Extracts the empty steps from the header seal.
|
||||
pub fn empty_steps(&self) -> Option<Vec<SealedEmptyStep>> {
|
||||
self.seal.get(2).and_then(|x| Rlp::new(x).as_list::<SealedEmptyStep>().ok())
|
||||
}
|
||||
|
||||
/// Returns header RLP with or without seals.
|
||||
fn rlp(&self, with_seal: bool) -> Bytes {
|
||||
let mut s = RlpStream::new();
|
||||
if with_seal {
|
||||
s.begin_list(13 + self.seal.len());
|
||||
} else {
|
||||
s.begin_list(13);
|
||||
}
|
||||
|
||||
s.append(&self.parent_hash);
|
||||
s.append(&self.uncles_hash);
|
||||
s.append(&self.author);
|
||||
s.append(&self.state_root);
|
||||
s.append(&self.transactions_root);
|
||||
s.append(&self.receipts_root);
|
||||
s.append(&EthBloom::from(self.log_bloom.0));
|
||||
s.append(&self.difficulty);
|
||||
s.append(&self.number);
|
||||
s.append(&self.gas_limit);
|
||||
s.append(&self.gas_used);
|
||||
s.append(&self.timestamp);
|
||||
s.append(&self.extra_data);
|
||||
|
||||
if with_seal {
|
||||
for b in &self.seal {
|
||||
s.append_raw(b, 1);
|
||||
}
|
||||
}
|
||||
|
||||
s.out()
|
||||
}
|
||||
}
|
||||
|
||||
impl Receipt {
|
||||
/// Returns receipt RLP.
|
||||
fn rlp(&self) -> Bytes {
|
||||
let mut s = RlpStream::new();
|
||||
match self.outcome {
|
||||
TransactionOutcome::Unknown => {
|
||||
s.begin_list(3);
|
||||
},
|
||||
TransactionOutcome::StateRoot(ref root) => {
|
||||
s.begin_list(4);
|
||||
s.append(root);
|
||||
},
|
||||
TransactionOutcome::StatusCode(ref status_code) => {
|
||||
s.begin_list(4);
|
||||
s.append(status_code);
|
||||
},
|
||||
}
|
||||
s.append(&self.gas_used);
|
||||
s.append(&EthBloom::from(self.log_bloom.0));
|
||||
|
||||
s.begin_list(self.logs.len());
|
||||
for log in &self.logs {
|
||||
s.begin_list(3);
|
||||
s.append(&log.address);
|
||||
s.begin_list(log.topics.len());
|
||||
for topic in &log.topics {
|
||||
s.append(topic);
|
||||
}
|
||||
s.append(&log.data);
|
||||
}
|
||||
|
||||
s.out()
|
||||
}
|
||||
}
|
||||
|
||||
impl SealedEmptyStep {
|
||||
/// Returns message that has to be signed by the validator.
|
||||
pub fn message(&self, parent_hash: &H256) -> H256 {
|
||||
let mut message = RlpStream::new_list(2);
|
||||
message.append(&self.step);
|
||||
message.append(parent_hash);
|
||||
keccak_256(&message.out()).into()
|
||||
}
|
||||
|
||||
/// Returns rlp for the vector of empty steps (we only do encoding in tests).
|
||||
#[cfg(feature = "test-helpers")]
|
||||
pub fn rlp_of(empty_steps: &[SealedEmptyStep]) -> Bytes {
|
||||
let mut s = RlpStream::new();
|
||||
s.begin_list(empty_steps.len());
|
||||
for empty_step in empty_steps {
|
||||
s.begin_list(2)
|
||||
.append(&empty_step.signature)
|
||||
.append(&empty_step.step);
|
||||
}
|
||||
s.out()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for SealedEmptyStep {
|
||||
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
|
||||
let signature: H520 = rlp.val_at(0)?;
|
||||
let step = rlp.val_at(1)?;
|
||||
|
||||
Ok(SealedEmptyStep { signature, step })
|
||||
}
|
||||
}
|
||||
|
||||
impl LogEntry {
|
||||
/// Calculates the bloom of this log entry.
|
||||
pub fn bloom(&self) -> Bloom {
|
||||
let eth_bloom = self.topics.iter().fold(EthBloom::from(BloomInput::Raw(self.address.as_bytes())), |mut b, t| {
|
||||
b.accrue(BloomInput::Raw(t.as_bytes()));
|
||||
b
|
||||
});
|
||||
Bloom(*eth_bloom.data())
|
||||
}
|
||||
}
|
||||
|
||||
impl Bloom {
|
||||
/// Returns true if this bloom has all bits from the other set.
|
||||
pub fn contains(&self, other: &Bloom) -> bool {
|
||||
self.0.iter().zip(other.0.iter()).all(|(l, r)| (l & r) == *r)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8; 256]> for Bloom {
|
||||
fn from(buffer: &'a [u8; 256]) -> Bloom {
|
||||
Bloom(*buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Bloom> for Bloom {
|
||||
fn eq(&self, other: &Bloom) -> bool {
|
||||
self.0.iter().zip(other.0.iter()).all(|(l, r)| l == r)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Default for Bloom {
|
||||
fn default() -> Self {
|
||||
Bloom([0; 256])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::fmt::Debug for Bloom {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.debug_struct("Bloom").finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert public key into corresponding ethereum address.
|
||||
pub fn public_to_address(public: &[u8; 64]) -> Address {
|
||||
let hash = keccak_256(public);
|
||||
let mut result = Address::zero();
|
||||
result.as_bytes_mut().copy_from_slice(&hash[12..]);
|
||||
result
|
||||
}
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
/// API for headers submitters.
|
||||
pub trait EthereumHeadersApi {
|
||||
/// Returns number and hash of the best block known to the bridge module.
|
||||
/// The caller should only submit `import_header` transaction that makes
|
||||
/// (or leads to making) other header the best one.
|
||||
fn best_block() -> (u64, H256);
|
||||
|
||||
/// Returns true if the import of given block requires transactions receipts.
|
||||
fn is_import_requires_receipts(header: Header) -> bool;
|
||||
|
||||
/// Returns true if header is known to the runtime.
|
||||
fn is_known_block(hash: H256) -> bool;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate Blake2b Hasher implementation
|
||||
|
||||
use hash_db::Hasher;
|
||||
use hash256_std_hasher::Hash256StdHasher;
|
||||
use crate::hash::H256;
|
||||
|
||||
pub mod blake2 {
|
||||
use super::{Hasher, Hash256StdHasher, H256};
|
||||
#[cfg(feature = "std")]
|
||||
use crate::hashing::blake2_256;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
extern "C" {
|
||||
fn ext_blake2_256(data: *const u8, len: u32, out: *mut u8);
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn blake2_256(data: &[u8]) -> [u8; 32] {
|
||||
let mut result: [u8; 32] = Default::default();
|
||||
unsafe {
|
||||
ext_blake2_256(data.as_ptr(), data.len() as u32, result.as_mut_ptr());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Concrete implementation of Hasher using Blake2b 256-bit hashes
|
||||
#[derive(Debug)]
|
||||
pub struct Blake2Hasher;
|
||||
|
||||
impl Hasher for Blake2Hasher {
|
||||
type Out = H256;
|
||||
type StdHasher = Hash256StdHasher;
|
||||
const LENGTH: usize = 32;
|
||||
fn hash(x: &[u8]) -> Self::Out {
|
||||
blake2_256(x).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod keccak256 {
|
||||
use super::{Hasher, Hash256StdHasher, H256};
|
||||
#[cfg(feature = "std")]
|
||||
use crate::hashing::keccak_256;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
extern "C" {
|
||||
fn ext_keccak_256(data: *const u8, len: u32, out: *mut u8);
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn keccak_256(data: &[u8]) -> [u8; 32] {
|
||||
let mut result: [u8; 32] = Default::default();
|
||||
unsafe {
|
||||
ext_keccak_256(data.as_ptr(), data.len() as u32, result.as_mut_ptr());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Concrete implementation of Hasher using Keccak 256-bit hashes
|
||||
#[derive(Debug)]
|
||||
pub struct Keccak256Hasher;
|
||||
|
||||
impl Hasher for Keccak256Hasher {
|
||||
type Out = H256;
|
||||
type StdHasher = Hash256StdHasher;
|
||||
const LENGTH: usize = 32;
|
||||
fn hash(x: &[u8]) -> Self::Out {
|
||||
keccak_256(x).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
// Copyright 2017-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Shareable Substrate types.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
/// Initialize a key-value collection from array.
|
||||
///
|
||||
/// Creates a vector of given pairs and calls `collect` on the iterator from it.
|
||||
/// Can be used to create a `HashMap`.
|
||||
#[macro_export]
|
||||
macro_rules! map {
|
||||
($( $name:expr => $value:expr ),* $(,)? ) => (
|
||||
vec![ $( ( $name, $value ) ),* ].into_iter().collect()
|
||||
);
|
||||
}
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::ops::Deref;
|
||||
#[cfg(feature = "std")]
|
||||
use std::borrow::Cow;
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
#[cfg(feature = "std")]
|
||||
pub use serde;
|
||||
#[doc(hidden)]
|
||||
pub use codec::{Encode, Decode};
|
||||
|
||||
pub use sp_debug_derive::RuntimeDebug;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use impl_serde::serialize as bytes;
|
||||
|
||||
#[cfg(feature = "full_crypto")]
|
||||
pub mod hashing;
|
||||
#[cfg(feature = "full_crypto")]
|
||||
pub use hashing::{blake2_128, blake2_256, twox_64, twox_128, twox_256, keccak_256};
|
||||
#[cfg(feature = "std")]
|
||||
pub mod hexdisplay;
|
||||
pub mod crypto;
|
||||
|
||||
pub mod u32_trait;
|
||||
|
||||
pub mod ed25519;
|
||||
pub mod sr25519;
|
||||
pub mod ecdsa;
|
||||
pub mod hash;
|
||||
mod hasher;
|
||||
pub mod offchain;
|
||||
pub mod sandbox;
|
||||
pub mod uint;
|
||||
mod changes_trie;
|
||||
#[cfg(feature = "std")]
|
||||
pub mod traits;
|
||||
pub mod testing;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::hash::{H160, H256, H512, convert_hash};
|
||||
pub use self::uint::U256;
|
||||
pub use changes_trie::ChangesTrieConfiguration;
|
||||
#[cfg(feature = "full_crypto")]
|
||||
pub use crypto::{DeriveJunction, Pair, Public};
|
||||
|
||||
pub use hash_db::Hasher;
|
||||
// Switch back to Blake after PoC-3 is out
|
||||
// pub use self::hasher::blake::BlakeHasher;
|
||||
pub use self::hasher::blake2::Blake2Hasher;
|
||||
pub use self::hasher::keccak256::Keccak256Hasher;
|
||||
|
||||
pub use sp_storage as storage;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use sp_std;
|
||||
|
||||
/// Context for executing a call into the runtime.
|
||||
pub enum ExecutionContext {
|
||||
/// Context for general importing (including own blocks).
|
||||
Importing,
|
||||
/// Context used when syncing the blockchain.
|
||||
Syncing,
|
||||
/// Context used for block construction.
|
||||
BlockConstruction,
|
||||
/// Context used for offchain calls.
|
||||
///
|
||||
/// This allows passing offchain extension and customizing available capabilities.
|
||||
OffchainCall(Option<(Box<dyn offchain::Externalities>, offchain::Capabilities)>),
|
||||
}
|
||||
|
||||
impl ExecutionContext {
|
||||
/// Returns the capabilities of particular context.
|
||||
pub fn capabilities(&self) -> offchain::Capabilities {
|
||||
use ExecutionContext::*;
|
||||
|
||||
match self {
|
||||
Importing | Syncing | BlockConstruction =>
|
||||
offchain::Capabilities::none(),
|
||||
// Enable keystore by default for offchain calls. CC @bkchr
|
||||
OffchainCall(None) => [offchain::Capability::Keystore][..].into(),
|
||||
OffchainCall(Some((_, capabilities))) => *capabilities,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hex-serialized shim for `Vec<u8>`.
|
||||
#[derive(PartialEq, Eq, Clone, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash, PartialOrd, Ord))]
|
||||
pub struct Bytes(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
|
||||
impl From<Vec<u8>> for Bytes {
|
||||
fn from(s: Vec<u8>) -> Self { Bytes(s) }
|
||||
}
|
||||
|
||||
impl From<OpaqueMetadata> for Bytes {
|
||||
fn from(s: OpaqueMetadata) -> Self { Bytes(s.0) }
|
||||
}
|
||||
|
||||
impl Deref for Bytes {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] { &self.0[..] }
|
||||
}
|
||||
|
||||
/// Stores the encoded `RuntimeMetadata` for the native side as opaque type.
|
||||
#[derive(Encode, Decode, PartialEq)]
|
||||
pub struct OpaqueMetadata(Vec<u8>);
|
||||
|
||||
impl OpaqueMetadata {
|
||||
/// Creates a new instance with the given metadata blob.
|
||||
pub fn new(metadata: Vec<u8>) -> Self {
|
||||
OpaqueMetadata(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_std::ops::Deref for OpaqueMetadata {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that is either a native or an encoded value.
|
||||
#[cfg(feature = "std")]
|
||||
pub enum NativeOrEncoded<R> {
|
||||
/// The native representation.
|
||||
Native(R),
|
||||
/// The encoded representation.
|
||||
Encoded(Vec<u8>)
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<R: codec::Encode> sp_std::fmt::Debug for NativeOrEncoded<R> {
|
||||
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
||||
hexdisplay::HexDisplay::from(&self.as_encoded().as_ref()).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<R: codec::Encode> NativeOrEncoded<R> {
|
||||
/// Return the value as the encoded format.
|
||||
pub fn as_encoded(&self) -> Cow<'_, [u8]> {
|
||||
match self {
|
||||
NativeOrEncoded::Encoded(e) => Cow::Borrowed(e.as_slice()),
|
||||
NativeOrEncoded::Native(n) => Cow::Owned(n.encode()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the value as the encoded format.
|
||||
pub fn into_encoded(self) -> Vec<u8> {
|
||||
match self {
|
||||
NativeOrEncoded::Encoded(e) => e,
|
||||
NativeOrEncoded::Native(n) => n.encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<R: PartialEq + codec::Decode> PartialEq for NativeOrEncoded<R> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(NativeOrEncoded::Native(l), NativeOrEncoded::Native(r)) => l == r,
|
||||
(NativeOrEncoded::Native(n), NativeOrEncoded::Encoded(e)) |
|
||||
(NativeOrEncoded::Encoded(e), NativeOrEncoded::Native(n)) =>
|
||||
Some(n) == codec::Decode::decode(&mut &e[..]).ok().as_ref(),
|
||||
(NativeOrEncoded::Encoded(l), NativeOrEncoded::Encoded(r)) => l == r,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that is never in a native representation.
|
||||
/// This is type is useful in conjuction with `NativeOrEncoded`.
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(PartialEq)]
|
||||
pub enum NeverNativeValue {}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl codec::Encode for NeverNativeValue {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
// The enum is not constructable, so this function should never be callable!
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl codec::EncodeLike for NeverNativeValue {}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl codec::Decode for NeverNativeValue {
|
||||
fn decode<I: codec::Input>(_: &mut I) -> Result<Self, codec::Error> {
|
||||
Err("`NeverNativeValue` should never be decoded".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide a simple 4 byte identifier for a type.
|
||||
pub trait TypeId {
|
||||
/// Simple 4 byte identifier.
|
||||
const TYPE_ID: [u8; 4];
|
||||
}
|
||||
|
||||
/// A log level matching the one from `log` crate.
|
||||
///
|
||||
/// Used internally by `sp_io::log` method.
|
||||
#[derive(Encode, Decode, sp_runtime_interface::pass_by::PassByEnum, Copy, Clone)]
|
||||
pub enum LogLevel {
|
||||
/// `Error` log level.
|
||||
Error = 1,
|
||||
/// `Warn` log level.
|
||||
Warn = 2,
|
||||
/// `Info` log level.
|
||||
Info = 3,
|
||||
/// `Debug` log level.
|
||||
Debug = 4,
|
||||
/// `Trace` log level.
|
||||
Trace = 5,
|
||||
}
|
||||
|
||||
impl From<u32> for LogLevel {
|
||||
fn from(val: u32) -> Self {
|
||||
match val {
|
||||
x if x == LogLevel::Warn as u32 => LogLevel::Warn,
|
||||
x if x == LogLevel::Info as u32 => LogLevel::Info,
|
||||
x if x == LogLevel::Debug as u32 => LogLevel::Debug,
|
||||
x if x == LogLevel::Trace as u32 => LogLevel::Trace,
|
||||
_ => LogLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<log::Level> for LogLevel {
|
||||
fn from(l: log::Level) -> Self {
|
||||
use log::Level::*;
|
||||
match l {
|
||||
Error => Self::Error,
|
||||
Warn => Self::Warn,
|
||||
Info => Self::Info,
|
||||
Debug => Self::Debug,
|
||||
Trace => Self::Trace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LogLevel> for log::Level {
|
||||
fn from(l: LogLevel) -> Self {
|
||||
use self::LogLevel::*;
|
||||
match l {
|
||||
Error => Self::Error,
|
||||
Warn => Self::Warn,
|
||||
Info => Self::Info,
|
||||
Debug => Self::Debug,
|
||||
Trace => Self::Trace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the given value into a buffer and returns the pointer and the length as a single `u64`.
|
||||
///
|
||||
/// When Substrate calls into Wasm it expects a fixed signature for functions exported
|
||||
/// from the Wasm blob. The return value of this signature is always a `u64`.
|
||||
/// This `u64` stores the pointer to the encoded return value and the length of this encoded value.
|
||||
/// The low `32bits` are reserved for the pointer, followed by `32bit` for the length.
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub fn to_substrate_wasm_fn_return_value(value: &impl Encode) -> u64 {
|
||||
let encoded = value.encode();
|
||||
|
||||
let ptr = encoded.as_ptr() as u64;
|
||||
let length = encoded.len() as u64;
|
||||
let res = ptr | (length << 32);
|
||||
|
||||
// Leak the output vector to avoid it being freed.
|
||||
// This is fine in a WASM context since the heap
|
||||
// will be discarded after the call.
|
||||
sp_std::mem::forget(encoded);
|
||||
|
||||
res
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "sp-io"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "1.0.6", default-features = false }
|
||||
hash-db = { version = "0.15.2", default-features = false }
|
||||
sp-core = { version = "2.0.0", default-features = false, path = "../core" }
|
||||
sp-std = { version = "2.0.0", default-features = false, path = "../std" }
|
||||
libsecp256k1 = { version = "0.3.4", optional = true }
|
||||
sp-state-machine = { version = "2.0.0", optional = true, path = "../../primitives/state-machine" }
|
||||
sp-runtime-interface = { version = "2.0.0", default-features = false, path = "../runtime-interface" }
|
||||
sp-trie = { version = "2.0.0", optional = true, path = "../../primitives/trie" }
|
||||
sp-externalities = { version = "2.0.0", optional = true, path = "../externalities" }
|
||||
log = { version = "0.4.8", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"sp-core/std",
|
||||
"codec/std",
|
||||
"sp-std/std",
|
||||
"hash-db/std",
|
||||
"sp-trie",
|
||||
"sp-state-machine",
|
||||
"libsecp256k1",
|
||||
"sp-runtime-interface/std",
|
||||
"sp-externalities",
|
||||
"log",
|
||||
]
|
||||
|
||||
# These two features are used for `no_std` builds for the environments which already provides
|
||||
# `#[panic_handler]`, `#[alloc_error_handler]` and `#[global_allocator]`.
|
||||
#
|
||||
# For the regular wasm runtime builds those are not used.
|
||||
disable_panic_handler = []
|
||||
disable_oom = []
|
||||
disable_allocator = []
|
||||
@@ -0,0 +1,974 @@
|
||||
// Copyright 2017-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This is part of the Substrate runtime.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg_attr(not(feature = "std"), feature(alloc_error_handler))]
|
||||
#![cfg_attr(not(feature = "std"), feature(core_intrinsics))]
|
||||
|
||||
#![cfg_attr(feature = "std",
|
||||
doc = "Substrate runtime standard library as compiled when linked with Rust's standard library.")]
|
||||
#![cfg_attr(not(feature = "std"),
|
||||
doc = "Substrate's runtime standard library as compiled without Rust's standard library.")]
|
||||
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use sp_std::ops::Deref;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use sp_core::{
|
||||
crypto::Pair,
|
||||
traits::KeystoreExt,
|
||||
offchain::{OffchainExt, TransactionPoolExt},
|
||||
hexdisplay::HexDisplay,
|
||||
storage::{ChildStorageKey, ChildInfo},
|
||||
};
|
||||
|
||||
use sp_core::{
|
||||
crypto::KeyTypeId, ed25519, sr25519, H256, LogLevel,
|
||||
offchain::{
|
||||
Timestamp, HttpRequestId, HttpRequestStatus, HttpError, StorageKind, OpaqueNetworkState,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use ::sp_trie::{TrieConfiguration, trie_types::Layout};
|
||||
|
||||
use sp_runtime_interface::{runtime_interface, Pointer};
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use sp_externalities::{ExternalitiesExt, Externalities};
|
||||
|
||||
/// Error verifying ECDSA signature
|
||||
#[derive(Encode, Decode)]
|
||||
pub enum EcdsaVerifyError {
|
||||
/// Incorrect value of R or S
|
||||
BadRS,
|
||||
/// Incorrect value of V
|
||||
BadV,
|
||||
/// Invalid signature
|
||||
BadSignature,
|
||||
}
|
||||
|
||||
/// Returns a `ChildStorageKey` if the given `storage_key` slice is a valid storage
|
||||
/// key or panics otherwise.
|
||||
///
|
||||
/// Panicking here is aligned with what the `without_std` environment would do
|
||||
/// in the case of an invalid child storage key.
|
||||
#[cfg(feature = "std")]
|
||||
fn child_storage_key_or_panic(storage_key: &[u8]) -> ChildStorageKey {
|
||||
match ChildStorageKey::from_slice(storage_key) {
|
||||
Some(storage_key) => storage_key,
|
||||
None => panic!("child storage key is invalid"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface for accessing the storage from within the runtime.
|
||||
#[runtime_interface]
|
||||
pub trait Storage {
|
||||
/// Returns the data for `key` in the storage or `None` if the key can not be found.
|
||||
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.storage(key).map(|s| s.to_vec())
|
||||
}
|
||||
|
||||
/// All Child api uses :
|
||||
/// - A `child_storage_key` to define the anchor point for the child proof
|
||||
/// (commonly the location where the child root is stored in its parent trie).
|
||||
/// - A `child_storage_types` to identify the kind of the child type and how its
|
||||
/// `child definition` parameter is encoded.
|
||||
/// - A `child_definition_parameter` which is the additional information required
|
||||
/// to use the child trie. For instance defaults child tries requires this to
|
||||
/// contain a collision free unique id.
|
||||
///
|
||||
/// This function specifically returns the data for `key` in the child storage or `None`
|
||||
/// if the key can not be found.
|
||||
fn child_get(
|
||||
&self,
|
||||
child_storage_key: &[u8],
|
||||
child_definition: &[u8],
|
||||
child_type: u32,
|
||||
key: &[u8],
|
||||
) -> Option<Vec<u8>> {
|
||||
let storage_key = child_storage_key_or_panic(child_storage_key);
|
||||
let child_info = ChildInfo::resolve_child_info(child_type, child_definition)
|
||||
.expect("Invalid child definition");
|
||||
self.child_storage(storage_key, child_info, key).map(|s| s.to_vec())
|
||||
}
|
||||
|
||||
/// Get `key` from storage, placing the value into `value_out` and return the number of
|
||||
/// bytes that the entry in storage has beyond the offset or `None` if the storage entry
|
||||
/// doesn't exist at all.
|
||||
/// If `value_out` length is smaller than the returned length, only `value_out` length bytes
|
||||
/// are copied into `value_out`.
|
||||
fn read(&self, key: &[u8], value_out: &mut [u8], value_offset: u32) -> Option<u32> {
|
||||
self.storage(key).map(|value| {
|
||||
let value_offset = value_offset as usize;
|
||||
let data = &value[value_offset.min(value.len())..];
|
||||
let written = std::cmp::min(data.len(), value_out.len());
|
||||
value_out[..written].copy_from_slice(&data[..written]);
|
||||
value.len() as u32
|
||||
})
|
||||
}
|
||||
|
||||
/// Get `key` from child storage, placing the value into `value_out` and return the number
|
||||
/// of bytes that the entry in storage has beyond the offset or `None` if the storage entry
|
||||
/// doesn't exist at all.
|
||||
/// If `value_out` length is smaller than the returned length, only `value_out` length bytes
|
||||
/// are copied into `value_out`.
|
||||
///
|
||||
/// See `child_get` for common child api parameters.
|
||||
fn child_read(
|
||||
&self,
|
||||
child_storage_key: &[u8],
|
||||
child_definition: &[u8],
|
||||
child_type: u32,
|
||||
key: &[u8],
|
||||
value_out: &mut [u8],
|
||||
value_offset: u32,
|
||||
) -> Option<u32> {
|
||||
let storage_key = child_storage_key_or_panic(child_storage_key);
|
||||
let child_info = ChildInfo::resolve_child_info(child_type, child_definition)
|
||||
.expect("Invalid child definition");
|
||||
self.child_storage(storage_key, child_info, key)
|
||||
.map(|value| {
|
||||
let value_offset = value_offset as usize;
|
||||
let data = &value[value_offset.min(value.len())..];
|
||||
let written = std::cmp::min(data.len(), value_out.len());
|
||||
value_out[..written].copy_from_slice(&data[..written]);
|
||||
value.len() as u32
|
||||
})
|
||||
}
|
||||
|
||||
/// Set `key` to `value` in the storage.
|
||||
fn set(&mut self, key: &[u8], value: &[u8]) {
|
||||
self.set_storage(key.to_vec(), value.to_vec());
|
||||
}
|
||||
|
||||
/// Set `key` to `value` in the child storage denoted by `child_storage_key`.
|
||||
///
|
||||
/// See `child_get` for common child api parameters.
|
||||
fn child_set(
|
||||
&mut self,
|
||||
child_storage_key: &[u8],
|
||||
child_definition: &[u8],
|
||||
child_type: u32,
|
||||
key: &[u8],
|
||||
value: &[u8],
|
||||
) {
|
||||
let storage_key = child_storage_key_or_panic(child_storage_key);
|
||||
let child_info = ChildInfo::resolve_child_info(child_type, child_definition)
|
||||
.expect("Invalid child definition");
|
||||
self.set_child_storage(storage_key, child_info, key.to_vec(), value.to_vec());
|
||||
}
|
||||
|
||||
/// Clear the storage of the given `key` and its value.
|
||||
fn clear(&mut self, key: &[u8]) {
|
||||
self.clear_storage(key)
|
||||
}
|
||||
|
||||
/// Clear the given child storage of the given `key` and its value.
|
||||
///
|
||||
/// See `child_get` for common child api parameters.
|
||||
fn child_clear(
|
||||
&mut self,
|
||||
child_storage_key: &[u8],
|
||||
child_definition: &[u8],
|
||||
child_type: u32,
|
||||
key: &[u8],
|
||||
) {
|
||||
let storage_key = child_storage_key_or_panic(child_storage_key);
|
||||
let child_info = ChildInfo::resolve_child_info(child_type, child_definition)
|
||||
.expect("Invalid child definition");
|
||||
self.clear_child_storage(storage_key, child_info, key);
|
||||
}
|
||||
|
||||
/// Clear an entire child storage.
|
||||
///
|
||||
/// See `child_get` for common child api parameters.
|
||||
fn child_storage_kill(
|
||||
&mut self,
|
||||
child_storage_key: &[u8],
|
||||
child_definition: &[u8],
|
||||
child_type: u32,
|
||||
) {
|
||||
let storage_key = child_storage_key_or_panic(child_storage_key);
|
||||
let child_info = ChildInfo::resolve_child_info(child_type, child_definition)
|
||||
.expect("Invalid child definition");
|
||||
self.kill_child_storage(storage_key, child_info);
|
||||
}
|
||||
|
||||
/// Check whether the given `key` exists in storage.
|
||||
fn exists(&self, key: &[u8]) -> bool {
|
||||
self.exists_storage(key)
|
||||
}
|
||||
|
||||
/// Check whether the given `key` exists in storage.
|
||||
///
|
||||
/// See `child_get` for common child api parameters.
|
||||
fn child_exists(
|
||||
&self,
|
||||
child_storage_key: &[u8],
|
||||
child_definition: &[u8],
|
||||
child_type: u32,
|
||||
key: &[u8],
|
||||
) -> bool {
|
||||
let storage_key = child_storage_key_or_panic(child_storage_key);
|
||||
let child_info = ChildInfo::resolve_child_info(child_type, child_definition)
|
||||
.expect("Invalid child definition");
|
||||
self.exists_child_storage(storage_key, child_info, key)
|
||||
}
|
||||
|
||||
/// Clear the storage of each key-value pair where the key starts with the given `prefix`.
|
||||
fn clear_prefix(&mut self, prefix: &[u8]) {
|
||||
Externalities::clear_prefix(*self, prefix)
|
||||
}
|
||||
|
||||
/// Clear the child storage of each key-value pair where the key starts with the given `prefix`.
|
||||
///
|
||||
/// See `child_get` for common child api parameters.
|
||||
fn child_clear_prefix(
|
||||
&mut self,
|
||||
child_storage_key: &[u8],
|
||||
child_definition: &[u8],
|
||||
child_type: u32,
|
||||
prefix: &[u8],
|
||||
) {
|
||||
let storage_key = child_storage_key_or_panic(child_storage_key);
|
||||
let child_info = ChildInfo::resolve_child_info(child_type, child_definition)
|
||||
.expect("Invalid child definition");
|
||||
self.clear_child_prefix(storage_key, child_info, prefix);
|
||||
}
|
||||
|
||||
/// "Commit" all existing operations and compute the resulting storage root.
|
||||
///
|
||||
/// The hashing algorithm is defined by the `Block`.
|
||||
///
|
||||
/// Returns the SCALE encoded hash.
|
||||
fn root(&mut self) -> Vec<u8> {
|
||||
self.storage_root()
|
||||
}
|
||||
|
||||
/// "Commit" all existing operations and compute the resulting child storage root.
|
||||
///
|
||||
/// The hashing algorithm is defined by the `Block`.
|
||||
///
|
||||
/// Returns the SCALE encoded hash.
|
||||
///
|
||||
/// See `child_get` for common child api parameters.
|
||||
fn child_root(
|
||||
&mut self,
|
||||
child_storage_key: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let storage_key = child_storage_key_or_panic(child_storage_key);
|
||||
self.child_storage_root(storage_key)
|
||||
}
|
||||
|
||||
/// "Commit" all existing operations and get the resulting storage change root.
|
||||
/// `parent_hash` is a SCALE encoded hash.
|
||||
///
|
||||
/// The hashing algorithm is defined by the `Block`.
|
||||
///
|
||||
/// Returns an `Option` that holds the SCALE encoded hash.
|
||||
fn changes_root(&mut self, parent_hash: &[u8]) -> Option<Vec<u8>> {
|
||||
self.storage_changes_root(parent_hash)
|
||||
.expect("Invalid `parent_hash` given to `changes_root`.")
|
||||
}
|
||||
|
||||
/// Get the next key in storage after the given one in lexicographic order.
|
||||
fn next_key(&mut self, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.next_storage_key(&key)
|
||||
}
|
||||
|
||||
/// Get the next key in storage after the given one in lexicographic order in child storage.
|
||||
fn child_next_key(
|
||||
&mut self,
|
||||
child_storage_key: &[u8],
|
||||
child_definition: &[u8],
|
||||
child_type: u32,
|
||||
key: &[u8],
|
||||
) -> Option<Vec<u8>> {
|
||||
let storage_key = child_storage_key_or_panic(child_storage_key);
|
||||
let child_info = ChildInfo::resolve_child_info(child_type, child_definition)
|
||||
.expect("Invalid child definition");
|
||||
self.next_child_storage_key(storage_key, child_info, key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface that provides trie related functionality.
|
||||
#[runtime_interface]
|
||||
pub trait Trie {
|
||||
/// A trie root formed from the iterated items.
|
||||
fn blake2_256_root(input: Vec<(Vec<u8>, Vec<u8>)>) -> H256 {
|
||||
Layout::<sp_core::Blake2Hasher>::trie_root(input)
|
||||
}
|
||||
|
||||
/// A trie root formed from the enumerated items.
|
||||
fn blake2_256_ordered_root(input: Vec<Vec<u8>>) -> H256 {
|
||||
Layout::<sp_core::Blake2Hasher>::ordered_trie_root(input)
|
||||
}
|
||||
|
||||
/// A trie root formed from the enumerated items.
|
||||
fn keccak_256_ordered_root(input: Vec<Vec<u8>>) -> H256 {
|
||||
Layout::<sp_core::Keccak256Hasher>::ordered_trie_root(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface that provides miscellaneous functions for communicating between the runtime and the node.
|
||||
#[runtime_interface]
|
||||
pub trait Misc {
|
||||
/// The current relay chain identifier.
|
||||
fn chain_id(&self) -> u64 {
|
||||
sp_externalities::Externalities::chain_id(*self)
|
||||
}
|
||||
|
||||
/// Print a number.
|
||||
fn print_num(val: u64) {
|
||||
log::debug!(target: "runtime", "{}", val);
|
||||
}
|
||||
|
||||
/// Print any valid `utf8` buffer.
|
||||
fn print_utf8(utf8: &[u8]) {
|
||||
if let Ok(data) = std::str::from_utf8(utf8) {
|
||||
log::debug!(target: "runtime", "{}", data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Print any `u8` slice as hex.
|
||||
fn print_hex(data: &[u8]) {
|
||||
log::debug!(target: "runtime", "{}", HexDisplay::from(&data));
|
||||
}
|
||||
}
|
||||
|
||||
/// Interfaces for working with crypto related types from within the runtime.
|
||||
#[runtime_interface]
|
||||
pub trait Crypto {
|
||||
/// Returns all `ed25519` public keys for the given key id from the keystore.
|
||||
fn ed25519_public_keys(&mut self, id: KeyTypeId) -> Vec<ed25519::Public> {
|
||||
self.extension::<KeystoreExt>()
|
||||
.expect("No `keystore` associated for the current context!")
|
||||
.read()
|
||||
.ed25519_public_keys(id)
|
||||
}
|
||||
|
||||
/// Generate an `ed22519` key for the given key type using an optional `seed` and
|
||||
/// store it in the keystore.
|
||||
///
|
||||
/// The `seed` needs to be a valid utf8.
|
||||
///
|
||||
/// Returns the public key.
|
||||
fn ed25519_generate(&mut self, id: KeyTypeId, seed: Option<Vec<u8>>) -> ed25519::Public {
|
||||
let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!"));
|
||||
self.extension::<KeystoreExt>()
|
||||
.expect("No `keystore` associated for the current context!")
|
||||
.write()
|
||||
.ed25519_generate_new(id, seed)
|
||||
.expect("`ed25519_generate` failed")
|
||||
}
|
||||
|
||||
/// Sign the given `msg` with the `ed25519` key that corresponds to the given public key and
|
||||
/// key type in the keystore.
|
||||
///
|
||||
/// Returns the signature.
|
||||
fn ed25519_sign(
|
||||
&mut self,
|
||||
id: KeyTypeId,
|
||||
pub_key: &ed25519::Public,
|
||||
msg: &[u8],
|
||||
) -> Option<ed25519::Signature> {
|
||||
self.extension::<KeystoreExt>()
|
||||
.expect("No `keystore` associated for the current context!")
|
||||
.read()
|
||||
.ed25519_key_pair(id, &pub_key)
|
||||
.map(|k| k.sign(msg))
|
||||
}
|
||||
|
||||
/// Verify an `ed25519` signature.
|
||||
///
|
||||
/// Returns `true` when the verification in successful.
|
||||
fn ed25519_verify(
|
||||
&self,
|
||||
sig: &ed25519::Signature,
|
||||
msg: &[u8],
|
||||
pub_key: &ed25519::Public,
|
||||
) -> bool {
|
||||
ed25519::Pair::verify(sig, msg, pub_key)
|
||||
}
|
||||
|
||||
/// Returns all `sr25519` public keys for the given key id from the keystore.
|
||||
fn sr25519_public_keys(&mut self, id: KeyTypeId) -> Vec<sr25519::Public> {
|
||||
self.extension::<KeystoreExt>()
|
||||
.expect("No `keystore` associated for the current context!")
|
||||
.read()
|
||||
.sr25519_public_keys(id)
|
||||
}
|
||||
|
||||
/// Generate an `sr22519` key for the given key type using an optional seed and
|
||||
/// store it in the keystore.
|
||||
///
|
||||
/// The `seed` needs to be a valid utf8.
|
||||
///
|
||||
/// Returns the public key.
|
||||
fn sr25519_generate(&mut self, id: KeyTypeId, seed: Option<Vec<u8>>) -> sr25519::Public {
|
||||
let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!"));
|
||||
self.extension::<KeystoreExt>()
|
||||
.expect("No `keystore` associated for the current context!")
|
||||
.write()
|
||||
.sr25519_generate_new(id, seed)
|
||||
.expect("`sr25519_generate` failed")
|
||||
}
|
||||
|
||||
/// Sign the given `msg` with the `sr25519` key that corresponds to the given public key and
|
||||
/// key type in the keystore.
|
||||
///
|
||||
/// Returns the signature.
|
||||
fn sr25519_sign(
|
||||
&mut self,
|
||||
id: KeyTypeId,
|
||||
pub_key: &sr25519::Public,
|
||||
msg: &[u8],
|
||||
) -> Option<sr25519::Signature> {
|
||||
self.extension::<KeystoreExt>()
|
||||
.expect("No `keystore` associated for the current context!")
|
||||
.read()
|
||||
.sr25519_key_pair(id, &pub_key)
|
||||
.map(|k| k.sign(msg))
|
||||
}
|
||||
|
||||
/// Verify an `sr25519` signature.
|
||||
///
|
||||
/// Returns `true` when the verification in successful.
|
||||
fn sr25519_verify(sig: &sr25519::Signature, msg: &[u8], pubkey: &sr25519::Public) -> bool {
|
||||
sr25519::Pair::verify(sig, msg, pubkey)
|
||||
}
|
||||
|
||||
/// Verify and recover a SECP256k1 ECDSA signature.
|
||||
/// - `sig` is passed in RSV format. V should be either 0/1 or 27/28.
|
||||
/// Returns `Err` if the signature is bad, otherwise the 64-byte pubkey
|
||||
/// (doesn't include the 0x04 prefix).
|
||||
fn secp256k1_ecdsa_recover(
|
||||
sig: &[u8; 65],
|
||||
msg: &[u8; 32],
|
||||
) -> Result<[u8; 64], EcdsaVerifyError> {
|
||||
let rs = secp256k1::Signature::parse_slice(&sig[0..64])
|
||||
.map_err(|_| EcdsaVerifyError::BadRS)?;
|
||||
let v = secp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8)
|
||||
.map_err(|_| EcdsaVerifyError::BadV)?;
|
||||
let pubkey = secp256k1::recover(&secp256k1::Message::parse(msg), &rs, &v)
|
||||
.map_err(|_| EcdsaVerifyError::BadSignature)?;
|
||||
let mut res = [0u8; 64];
|
||||
res.copy_from_slice(&pubkey.serialize()[1..65]);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Verify and recover a SECP256k1 ECDSA signature.
|
||||
/// - `sig` is passed in RSV format. V should be either 0/1 or 27/28.
|
||||
/// - returns `Err` if the signature is bad, otherwise the 33-byte compressed pubkey.
|
||||
fn secp256k1_ecdsa_recover_compressed(
|
||||
sig: &[u8; 65],
|
||||
msg: &[u8; 32],
|
||||
) -> Result<[u8; 33], EcdsaVerifyError> {
|
||||
let rs = secp256k1::Signature::parse_slice(&sig[0..64])
|
||||
.map_err(|_| EcdsaVerifyError::BadRS)?;
|
||||
let v = secp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8)
|
||||
.map_err(|_| EcdsaVerifyError::BadV)?;
|
||||
let pubkey = secp256k1::recover(&secp256k1::Message::parse(msg), &rs, &v)
|
||||
.map_err(|_| EcdsaVerifyError::BadSignature)?;
|
||||
Ok(pubkey.serialize_compressed())
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface that provides functions for hashing with different algorithms.
|
||||
#[runtime_interface]
|
||||
pub trait Hashing {
|
||||
/// Conduct a 256-bit Keccak hash.
|
||||
fn keccak_256(data: &[u8]) -> [u8; 32] {
|
||||
sp_core::hashing::keccak_256(data)
|
||||
}
|
||||
|
||||
/// Conduct a 256-bit Sha2 hash.
|
||||
fn sha2_256(data: &[u8]) -> [u8; 32] {
|
||||
sp_core::hashing::sha2_256(data)
|
||||
}
|
||||
|
||||
/// Conduct a 128-bit Blake2 hash.
|
||||
fn blake2_128(data: &[u8]) -> [u8; 16] {
|
||||
sp_core::hashing::blake2_128(data)
|
||||
}
|
||||
|
||||
/// Conduct a 256-bit Blake2 hash.
|
||||
fn blake2_256(data: &[u8]) -> [u8; 32] {
|
||||
sp_core::hashing::blake2_256(data)
|
||||
}
|
||||
|
||||
/// Conduct four XX hashes to give a 256-bit result.
|
||||
fn twox_256(data: &[u8]) -> [u8; 32] {
|
||||
sp_core::hashing::twox_256(data)
|
||||
}
|
||||
|
||||
/// Conduct two XX hashes to give a 128-bit result.
|
||||
fn twox_128(data: &[u8]) -> [u8; 16] {
|
||||
sp_core::hashing::twox_128(data)
|
||||
}
|
||||
|
||||
/// Conduct two XX hashes to give a 64-bit result.
|
||||
fn twox_64(data: &[u8]) -> [u8; 8] {
|
||||
sp_core::hashing::twox_64(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface that provides functions to access the offchain functionality.
|
||||
#[runtime_interface]
|
||||
pub trait Offchain {
|
||||
/// Returns if the local node is a potential validator.
|
||||
///
|
||||
/// Even if this function returns `true`, it does not mean that any keys are configured
|
||||
/// and that the validator is registered in the chain.
|
||||
fn is_validator(&mut self) -> bool {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("is_validator can be called only in the offchain worker context")
|
||||
.is_validator()
|
||||
}
|
||||
|
||||
/// Submit an encoded transaction to the pool.
|
||||
///
|
||||
/// The transaction will end up in the pool.
|
||||
fn submit_transaction(&mut self, data: Vec<u8>) -> Result<(), ()> {
|
||||
self.extension::<TransactionPoolExt>()
|
||||
.expect("submit_transaction can be called only in the offchain call context with
|
||||
TransactionPool capabilities enabled")
|
||||
.submit_transaction(data)
|
||||
}
|
||||
|
||||
/// Returns information about the local node's network state.
|
||||
fn network_state(&mut self) -> Result<OpaqueNetworkState, ()> {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("network_state can be called only in the offchain worker context")
|
||||
.network_state()
|
||||
}
|
||||
|
||||
/// Returns current UNIX timestamp (in millis)
|
||||
fn timestamp(&mut self) -> Timestamp {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("timestamp can be called only in the offchain worker context")
|
||||
.timestamp()
|
||||
}
|
||||
|
||||
/// Pause the execution until `deadline` is reached.
|
||||
fn sleep_until(&mut self, deadline: Timestamp) {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("sleep_until can be called only in the offchain worker context")
|
||||
.sleep_until(deadline)
|
||||
}
|
||||
|
||||
/// Returns a random seed.
|
||||
///
|
||||
/// This is a trully random non deterministic seed generated by host environment.
|
||||
/// Obviously fine in the off-chain worker context.
|
||||
fn random_seed(&mut self) -> [u8; 32] {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("random_seed can be called only in the offchain worker context")
|
||||
.random_seed()
|
||||
}
|
||||
|
||||
/// Sets a value in the local storage.
|
||||
///
|
||||
/// Note this storage is not part of the consensus, it's only accessible by
|
||||
/// offchain worker tasks running on the same machine. It IS persisted between runs.
|
||||
fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("local_storage_set can be called only in the offchain worker context")
|
||||
.local_storage_set(kind, key, value)
|
||||
}
|
||||
|
||||
/// Sets a value in the local storage if it matches current value.
|
||||
///
|
||||
/// Since multiple offchain workers may be running concurrently, to prevent
|
||||
/// data races use CAS to coordinate between them.
|
||||
///
|
||||
/// Returns `true` if the value has been set, `false` otherwise.
|
||||
///
|
||||
/// Note this storage is not part of the consensus, it's only accessible by
|
||||
/// offchain worker tasks running on the same machine. It IS persisted between runs.
|
||||
fn local_storage_compare_and_set(
|
||||
&mut self,
|
||||
kind: StorageKind,
|
||||
key: &[u8],
|
||||
old_value: Option<Vec<u8>>,
|
||||
new_value: &[u8],
|
||||
) -> bool {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("local_storage_compare_and_set can be called only in the offchain worker context")
|
||||
.local_storage_compare_and_set(kind, key, old_value.as_ref().map(|v| v.deref()), new_value)
|
||||
}
|
||||
|
||||
/// Gets a value from the local storage.
|
||||
///
|
||||
/// If the value does not exist in the storage `None` will be returned.
|
||||
/// Note this storage is not part of the consensus, it's only accessible by
|
||||
/// offchain worker tasks running on the same machine. It IS persisted between runs.
|
||||
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>> {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("local_storage_get can be called only in the offchain worker context")
|
||||
.local_storage_get(kind, key)
|
||||
}
|
||||
|
||||
/// Initiates a http request given HTTP verb and the URL.
|
||||
///
|
||||
/// Meta is a future-reserved field containing additional, parity-scale-codec encoded parameters.
|
||||
/// Returns the id of newly started request.
|
||||
fn http_request_start(
|
||||
&mut self,
|
||||
method: &str,
|
||||
uri: &str,
|
||||
meta: &[u8],
|
||||
) -> Result<HttpRequestId, ()> {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("http_request_start can be called only in the offchain worker context")
|
||||
.http_request_start(method, uri, meta)
|
||||
}
|
||||
|
||||
/// Append header to the request.
|
||||
fn http_request_add_header(
|
||||
&mut self,
|
||||
request_id: HttpRequestId,
|
||||
name: &str,
|
||||
value: &str,
|
||||
) -> Result<(), ()> {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("http_request_add_header can be called only in the offchain worker context")
|
||||
.http_request_add_header(request_id, name, value)
|
||||
}
|
||||
|
||||
/// Write a chunk of request body.
|
||||
///
|
||||
/// Writing an empty chunks finalizes the request.
|
||||
/// Passing `None` as deadline blocks forever.
|
||||
///
|
||||
/// Returns an error in case deadline is reached or the chunk couldn't be written.
|
||||
fn http_request_write_body(
|
||||
&mut self,
|
||||
request_id: HttpRequestId,
|
||||
chunk: &[u8],
|
||||
deadline: Option<Timestamp>,
|
||||
) -> Result<(), HttpError> {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("http_request_write_body can be called only in the offchain worker context")
|
||||
.http_request_write_body(request_id, chunk, deadline)
|
||||
}
|
||||
|
||||
/// Block and wait for the responses for given requests.
|
||||
///
|
||||
/// Returns a vector of request statuses (the len is the same as ids).
|
||||
/// Note that if deadline is not provided the method will block indefinitely,
|
||||
/// otherwise unready responses will produce `DeadlineReached` status.
|
||||
///
|
||||
/// Passing `None` as deadline blocks forever.
|
||||
fn http_response_wait(
|
||||
&mut self,
|
||||
ids: &[HttpRequestId],
|
||||
deadline: Option<Timestamp>,
|
||||
) -> Vec<HttpRequestStatus> {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("http_response_wait can be called only in the offchain worker context")
|
||||
.http_response_wait(ids, deadline)
|
||||
}
|
||||
|
||||
/// Read all response headers.
|
||||
///
|
||||
/// Returns a vector of pairs `(HeaderKey, HeaderValue)`.
|
||||
/// NOTE response headers have to be read before response body.
|
||||
fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("http_response_headers can be called only in the offchain worker context")
|
||||
.http_response_headers(request_id)
|
||||
}
|
||||
|
||||
/// Read a chunk of body response to given buffer.
|
||||
///
|
||||
/// Returns the number of bytes written or an error in case a deadline
|
||||
/// is reached or server closed the connection.
|
||||
/// If `0` is returned it means that the response has been fully consumed
|
||||
/// and the `request_id` is now invalid.
|
||||
/// NOTE this implies that response headers must be read before draining the body.
|
||||
/// Passing `None` as a deadline blocks forever.
|
||||
fn http_response_read_body(
|
||||
&mut self,
|
||||
request_id: HttpRequestId,
|
||||
buffer: &mut [u8],
|
||||
deadline: Option<Timestamp>,
|
||||
) -> Result<u32, HttpError> {
|
||||
self.extension::<OffchainExt>()
|
||||
.expect("http_response_read_body can be called only in the offchain worker context")
|
||||
.http_response_read_body(request_id, buffer, deadline)
|
||||
.map(|r| r as u32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wasm only interface that provides functions for calling into the allocator.
|
||||
#[runtime_interface(wasm_only)]
|
||||
trait Allocator {
|
||||
/// Malloc the given number of bytes and return the pointer to the allocated memory location.
|
||||
fn malloc(&mut self, size: u32) -> Pointer<u8> {
|
||||
self.allocate_memory(size).expect("Failed to allocate memory")
|
||||
}
|
||||
|
||||
/// Free the given pointer.
|
||||
fn free(&mut self, ptr: Pointer<u8>) {
|
||||
self.deallocate_memory(ptr).expect("Failed to deallocate memory")
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface that provides functions for logging from within the runtime.
|
||||
#[runtime_interface]
|
||||
pub trait Logging {
|
||||
/// Request to print a log message on the host.
|
||||
///
|
||||
/// Note that this will be only displayed if the host is enabled to display log messages with
|
||||
/// given level and target.
|
||||
///
|
||||
/// Instead of using directly, prefer setting up `RuntimeLogger` and using `log` macros.
|
||||
fn log(level: LogLevel, target: &str, message: &[u8]) {
|
||||
if let Ok(message) = std::str::from_utf8(message) {
|
||||
log::log!(
|
||||
target: target,
|
||||
log::Level::from(level),
|
||||
"{}",
|
||||
message,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wasm-only interface that provides functions for interacting with the sandbox.
|
||||
#[runtime_interface(wasm_only)]
|
||||
pub trait Sandbox {
|
||||
/// Instantiate a new sandbox instance with the given `wasm_code`.
|
||||
fn instantiate(
|
||||
&mut self,
|
||||
dispatch_thunk: u32,
|
||||
wasm_code: &[u8],
|
||||
env_def: &[u8],
|
||||
state_ptr: Pointer<u8>,
|
||||
) -> u32 {
|
||||
self.sandbox()
|
||||
.instance_new(dispatch_thunk, wasm_code, env_def, state_ptr.into())
|
||||
.expect("Failed to instantiate a new sandbox")
|
||||
}
|
||||
|
||||
/// Invoke `function` in the sandbox with `sandbox_idx`.
|
||||
fn invoke(
|
||||
&mut self,
|
||||
instance_idx: u32,
|
||||
function: &str,
|
||||
args: &[u8],
|
||||
return_val_ptr: Pointer<u8>,
|
||||
return_val_len: u32,
|
||||
state_ptr: Pointer<u8>,
|
||||
) -> u32 {
|
||||
self.sandbox().invoke(
|
||||
instance_idx,
|
||||
&function,
|
||||
&args,
|
||||
return_val_ptr,
|
||||
return_val_len,
|
||||
state_ptr.into(),
|
||||
).expect("Failed to invoke function with sandbox")
|
||||
}
|
||||
|
||||
/// Create a new memory instance with the given `initial` and `maximum` size.
|
||||
fn memory_new(&mut self, initial: u32, maximum: u32) -> u32 {
|
||||
self.sandbox()
|
||||
.memory_new(initial, maximum)
|
||||
.expect("Failed to create new memory with sandbox")
|
||||
}
|
||||
|
||||
/// Get the memory starting at `offset` from the instance with `memory_idx` into the buffer.
|
||||
fn memory_get(
|
||||
&mut self,
|
||||
memory_idx: u32,
|
||||
offset: u32,
|
||||
buf_ptr: Pointer<u8>,
|
||||
buf_len: u32,
|
||||
) -> u32 {
|
||||
self.sandbox()
|
||||
.memory_get(memory_idx, offset, buf_ptr, buf_len)
|
||||
.expect("Failed to get memory with sandbox")
|
||||
}
|
||||
|
||||
/// Set the memory in the given `memory_idx` to the given value at `offset`.
|
||||
fn memory_set(
|
||||
&mut self,
|
||||
memory_idx: u32,
|
||||
offset: u32,
|
||||
val_ptr: Pointer<u8>,
|
||||
val_len: u32,
|
||||
) -> u32 {
|
||||
self.sandbox()
|
||||
.memory_set(memory_idx, offset, val_ptr, val_len)
|
||||
.expect("Failed to set memory with sandbox")
|
||||
}
|
||||
|
||||
/// Teardown the memory instance with the given `memory_idx`.
|
||||
fn memory_teardown(&mut self, memory_idx: u32) {
|
||||
self.sandbox().memory_teardown(memory_idx).expect("Failed to teardown memory with sandbox")
|
||||
}
|
||||
|
||||
/// Teardown the sandbox instance with the given `instance_idx`.
|
||||
fn instance_teardown(&mut self, instance_idx: u32) {
|
||||
self.sandbox().instance_teardown(instance_idx).expect("Failed to teardown sandbox instance")
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocator used by Substrate when executing the Wasm runtime.
|
||||
#[cfg(not(feature = "std"))]
|
||||
struct WasmAllocator;
|
||||
|
||||
#[cfg(all(not(feature = "disable_allocator"), not(feature = "std")))]
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: WasmAllocator = WasmAllocator;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod allocator_impl {
|
||||
use super::*;
|
||||
use core::alloc::{GlobalAlloc, Layout};
|
||||
|
||||
unsafe impl GlobalAlloc for WasmAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
allocator::malloc(layout.size() as u32)
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {
|
||||
allocator::free(ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A default panic handler for WASM environment.
|
||||
#[cfg(all(not(feature = "disable_panic_handler"), not(feature = "std")))]
|
||||
#[panic_handler]
|
||||
#[no_mangle]
|
||||
pub fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
unsafe {
|
||||
let message = sp_std::alloc::format!("{}", info);
|
||||
logging::log(LogLevel::Error, "runtime", message.as_bytes());
|
||||
core::intrinsics::abort()
|
||||
}
|
||||
}
|
||||
|
||||
/// A default OOM handler for WASM environment.
|
||||
#[cfg(all(not(feature = "disable_oom"), not(feature = "std")))]
|
||||
#[alloc_error_handler]
|
||||
pub fn oom(_: core::alloc::Layout) -> ! {
|
||||
unsafe {
|
||||
logging::log(LogLevel::Error, "runtime", b"Runtime memory exhausted. Aborting");
|
||||
core::intrinsics::abort();
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for Externalities implementation used in tests.
|
||||
#[cfg(feature = "std")]
|
||||
pub type TestExternalities = sp_state_machine::TestExternalities<sp_core::Blake2Hasher, u64>;
|
||||
|
||||
/// The host functions Substrate provides for the Wasm runtime environment.
|
||||
///
|
||||
/// All these host functions will be callable from inside the Wasm environment.
|
||||
#[cfg(feature = "std")]
|
||||
pub type SubstrateHostFunctions = (
|
||||
storage::HostFunctions,
|
||||
misc::HostFunctions,
|
||||
offchain::HostFunctions,
|
||||
crypto::HostFunctions,
|
||||
hashing::HostFunctions,
|
||||
allocator::HostFunctions,
|
||||
logging::HostFunctions,
|
||||
sandbox::HostFunctions,
|
||||
crate::trie::HostFunctions,
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_core::map;
|
||||
use sp_state_machine::BasicExternalities;
|
||||
use sp_core::storage::Storage;
|
||||
|
||||
#[test]
|
||||
fn storage_works() {
|
||||
let mut t = BasicExternalities::default();
|
||||
t.execute_with(|| {
|
||||
assert_eq!(storage::get(b"hello"), None);
|
||||
storage::set(b"hello", b"world");
|
||||
assert_eq!(storage::get(b"hello"), Some(b"world".to_vec()));
|
||||
assert_eq!(storage::get(b"foo"), None);
|
||||
storage::set(b"foo", &[1, 2, 3][..]);
|
||||
});
|
||||
|
||||
t = BasicExternalities::new(Storage {
|
||||
top: map![b"foo".to_vec() => b"bar".to_vec()],
|
||||
children: map![],
|
||||
});
|
||||
|
||||
t.execute_with(|| {
|
||||
assert_eq!(storage::get(b"hello"), None);
|
||||
assert_eq!(storage::get(b"foo"), Some(b"bar".to_vec()));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_storage_works() {
|
||||
let mut t = BasicExternalities::new(Storage {
|
||||
top: map![b":test".to_vec() => b"\x0b\0\0\0Hello world".to_vec()],
|
||||
children: map![],
|
||||
});
|
||||
|
||||
t.execute_with(|| {
|
||||
let mut v = [0u8; 4];
|
||||
assert!(storage::read(b":test", &mut v[..], 0).unwrap() >= 4);
|
||||
assert_eq!(v, [11u8, 0, 0, 0]);
|
||||
let mut w = [0u8; 11];
|
||||
assert!(storage::read(b":test", &mut w[..], 4).unwrap() >= 11);
|
||||
assert_eq!(&w, b"Hello world");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_prefix_works() {
|
||||
let mut t = BasicExternalities::new(Storage {
|
||||
top: map![
|
||||
b":a".to_vec() => b"\x0b\0\0\0Hello world".to_vec(),
|
||||
b":abcd".to_vec() => b"\x0b\0\0\0Hello world".to_vec(),
|
||||
b":abc".to_vec() => b"\x0b\0\0\0Hello world".to_vec(),
|
||||
b":abdd".to_vec() => b"\x0b\0\0\0Hello world".to_vec()
|
||||
],
|
||||
children: map![],
|
||||
});
|
||||
|
||||
t.execute_with(|| {
|
||||
storage::clear_prefix(b":abc");
|
||||
|
||||
assert!(storage::get(b":a").is_some());
|
||||
assert!(storage::get(b":abdd").is_some());
|
||||
assert!(storage::get(b":abcd").is_none());
|
||||
assert!(storage::get(b":abc").is_none());
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user