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:
Hernando Castano
2020-01-29 22:34:08 -05:00
committed by Bastian Köcher
parent 479c5bd99a
commit c06777a42a
19 changed files with 5867 additions and 0 deletions
@@ -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());
});
}
}