From c06777a42a0000e627ae57645cf410e0f69601bb Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Wed, 29 Jan 2020 22:34:08 -0500 Subject: [PATCH] Squashed commit of slava-bridge-runtime: commit ae4139bbb3cd4681ff4ad3f335b57e17f49ee895 Author: Hernando Castano Date: Wed Jan 29 22:18:42 2020 -0500 Move slava-bridge-runtime into modules/ethereum/ commit feb522fff241b36bd74638ab5de04ee1068efd0b Merge: 53a08fe 00fe0de Author: Hernando Castano Date: Wed Jan 29 22:17:26 2020 -0500 Merge branch 'bridge_runtime' of slava-bridge-runtime commit 00fe0dee5f50dd648e7c0f4af7fcb07f422a30af Author: Hernando Castano Date: Wed Jan 29 22:14:03 2020 -0500 Move all files into one folder commit 46d9bdcc35b022bd802e3f99d3f7477110813e2d Author: Hernando Castano Date: Wed Jan 29 22:02:37 2020 -0500 Move files I'm keeping into folder commit 6d10776610283439764de7897f25e921d08e99dc Author: Hernando Castano Date: Sun Jan 12 20:42:03 2020 -0500 Make the bridge-eth-poa module compile again commit 95283a8672d692ed34501e4e7c380a3038099bf8 Author: Hernando Castano Date: Sun Jan 12 20:39:23 2020 -0500 Add `sp-api` dependency back commit 31c476ac25391fb8f896b718998e9e80976a4cfd Author: Hernando Castano 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 Date: Tue Dec 10 14:52:04 2019 +0300 reward + penalize for Eth headers commit 34d35e7731c79b9e9ff21252063f939400ecfdb4 Author: Svyatoslav Nikolsky Date: Mon Dec 9 12:56:31 2019 +0300 added couple of TODOs commit 1a80caf9b643be4b823c9559a132285606641811 Author: Svyatoslav Nikolsky Date: Mon Dec 9 12:11:35 2019 +0300 removed debug print commit c590908bfb7e9d2075d1fd77556418eda7f330d3 Author: Svyatoslav Nikolsky Date: Mon Dec 9 11:55:09 2019 +0300 fixed bridge tests commit ab164f87019037bbc5cd2a69364ee4623e44ebbc Author: Svyatoslav Nikolsky Date: Mon Dec 9 11:15:46 2019 +0300 lost deps commit 649e90068040a12a1aa78788dd834394201753fa Author: Svyatoslav Nikolsky Date: Mon Dec 9 09:57:09 2019 +0300 fix compilation again commit cece62c2c2ddf772f01151ce3bf6dec28f4d0cca Author: Svyatoslav Nikolsky Date: Mon Dec 9 09:52:30 2019 +0300 fix receipts root calculation commit 92f4026896efbe3e315a909161af06dc1e2a1ff4 Author: Svyatoslav Nikolsky Date: Thu Dec 5 16:09:23 2019 +0300 check transactions receipts root commit 2e6a5af5e1bcb843262c5ba475f950cf989eb39b Author: Svyatoslav Nikolsky Date: Thu Dec 5 15:39:30 2019 +0300 provide keccak_256_ordered_root to runtime commit 2970aae4648beb8c531ec7c2ff706b65ea06ba63 Author: Svyatoslav Nikolsky Date: Thu Dec 5 15:15:03 2019 +0300 use existing keccak builtin from bridge runtime commit 75c498b0fe600be32b35a3e0d0da3b52ec5fd3b6 Author: Svyatoslav Nikolsky Date: Thu Dec 5 10:32:57 2019 +0300 submit multiple headers at once commit cdfdafc21efb4a667f2407c7139921d4b948fd7e Author: Svyatoslav Nikolsky Date: Wed Dec 4 13:48:42 2019 +0300 expose && initialize bridge configuration commit 383b93be54981b13c564218652a85af78c88c4f7 Author: Svyatoslav Nikolsky Date: Wed Dec 4 11:28:22 2019 +0300 export bridge Call commit dacc2939db51859a3e1b87250b289b269a80793b Author: Svyatoslav Nikolsky Date: Wed Dec 4 11:13:56 2019 +0300 expose BridgeEthPoaCall commit a5281c9387e622e28cbc89a62d268b359cb2f724 Author: Svyatoslav Nikolsky Date: Wed Dec 4 10:42:41 2019 +0300 EthereumHeadersApi::is_known_block commit c5658e1563be9b688355d03b20a62345418e3b78 Author: Svyatoslav Nikolsky Date: Wed Dec 4 10:14:42 2019 +0300 pub use parity_bytes::Bytes; commit 94cd24e5535101e871436ca35dcfda27f7955590 Author: Svyatoslav Nikolsky Date: Wed Dec 4 10:01:25 2019 +0300 exposed EthereumHeadersApi commit 607ec1760d146e8046122a1d2f868d70e15490e6 Author: Svyatoslav Nikolsky Date: Tue Dec 3 14:51:40 2019 +0300 EthPoA bridge: runtime --- .../bridge-runtime/bin/node/cli/Cargo.toml | 128 +++ .../bin/node/cli/src/chain_spec.rs | 449 ++++++++ .../bin/node/runtime/Cargo.toml | 127 +++ .../bin/node/runtime/src/lib.rs | 836 +++++++++++++++ .../bin/node/testing/Cargo.toml | 32 + .../bin/node/testing/src/genesis.rs | 144 +++ .../frame/bridge-eth-poa/Cargo.toml | 33 + .../frame/bridge-eth-poa/src/error.rs | 84 ++ .../frame/bridge-eth-poa/src/finality.rs | 284 +++++ .../frame/bridge-eth-poa/src/import.rs | 347 +++++++ .../frame/bridge-eth-poa/src/lib.rs | 740 +++++++++++++ .../frame/bridge-eth-poa/src/validators.rs | 394 +++++++ .../frame/bridge-eth-poa/src/verification.rs | 448 ++++++++ .../primitives/bridge-eth-poa/Cargo.toml | 49 + .../primitives/bridge-eth-poa/src/lib.rs | 361 +++++++ .../primitives/core/src/hasher.rs | 85 ++ .../bridge-runtime/primitives/core/src/lib.rs | 312 ++++++ .../bridge-runtime/primitives/io/Cargo.toml | 40 + .../bridge-runtime/primitives/io/src/lib.rs | 974 ++++++++++++++++++ 19 files changed, 5867 insertions(+) create mode 100644 bridges/modules/ethereum/bridge-runtime/bin/node/cli/Cargo.toml create mode 100644 bridges/modules/ethereum/bridge-runtime/bin/node/cli/src/chain_spec.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/bin/node/runtime/Cargo.toml create mode 100644 bridges/modules/ethereum/bridge-runtime/bin/node/runtime/src/lib.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/bin/node/testing/Cargo.toml create mode 100644 bridges/modules/ethereum/bridge-runtime/bin/node/testing/src/genesis.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/Cargo.toml create mode 100644 bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/error.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/finality.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/import.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/lib.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/validators.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/verification.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/primitives/bridge-eth-poa/Cargo.toml create mode 100644 bridges/modules/ethereum/bridge-runtime/primitives/bridge-eth-poa/src/lib.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/primitives/core/src/hasher.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/primitives/core/src/lib.rs create mode 100644 bridges/modules/ethereum/bridge-runtime/primitives/io/Cargo.toml create mode 100644 bridges/modules/ethereum/bridge-runtime/primitives/io/src/lib.rs diff --git a/bridges/modules/ethereum/bridge-runtime/bin/node/cli/Cargo.toml b/bridges/modules/ethereum/bridge-runtime/bin/node/cli/Cargo.toml new file mode 100644 index 0000000000..4df7401f23 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/bin/node/cli/Cargo.toml @@ -0,0 +1,128 @@ +[package] +name = "node-cli" +version = "2.0.0" +authors = ["Parity Technologies "] +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", +] diff --git a/bridges/modules/ethereum/bridge-runtime/bin/node/cli/src/chain_spec.rs b/bridges/modules/ethereum/bridge-runtime/bin/node/cli/src/chain_spec.rs new file mode 100644 index 0000000000..8a0af96157 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/bin/node/cli/src/chain_spec.rs @@ -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 . + +//! 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 = ::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, + /// Known bad block hashes. + pub bad_blocks: sc_client::BadBlocks, +} + +/// Specialized `ChainSpec`. +pub type ChainSpec = sc_service::ChainSpec< + GenesisConfig, + Extensions, +>; +/// Flaming Fir testnet generator +pub fn flaming_fir_config() -> Result { + 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 = 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(seed: &str) -> ::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(seed: &str) -> AccountId where + AccountPublic: From<::Public> +{ + AccountPublic::from(get_from_seed::(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::(&format!("{}//stash", seed)), + get_account_id_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(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>, + enable_println: bool, +) -> GenesisConfig { + let endowed_accounts: Vec = endowed_accounts.unwrap_or_else(|| { + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("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::>(), + }), + 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::>(), + }), + 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::>()[..(num_endowed_accounts + 1) / 2].to_vec(), + phantom: Default::default(), + }), + pallet_collective_Instance2: Some(TechnicalCommitteeConfig { + members: endowed_accounts.iter().cloned() + .collect::>()[..(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::("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::("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::("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), + ); + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/bin/node/runtime/Cargo.toml b/bridges/modules/ethereum/bridge-runtime/bin/node/runtime/Cargo.toml new file mode 100644 index 0000000000..7ca943621b --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/bin/node/runtime/Cargo.toml @@ -0,0 +1,127 @@ +[package] +name = "node-runtime" +version = "2.0.0" +authors = ["Parity Technologies "] +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", +] diff --git a/bridges/modules/ethereum/bridge-runtime/bin/node/runtime/src/lib.rs b/bridges/modules/ethereum/bridge-runtime/bin/node/runtime/src/lib.rs new file mode 100644 index 0000000000..21cfc6d8bc --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/bin/node/runtime/src/lib.rs @@ -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 . + +//! 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 = >::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; + 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; + 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; + type FeeMultiplierUpdate = TargetedFeeAdjustment; +} + +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; + 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 = ::KeyTypeIdProviders; + type ShouldEndSession = Babe; + type Event = Event; + type Keys = SessionKeys; + type ValidatorId = ::AccountId; + type ValidatorIdOf = pallet_staking::StashOf; + type SelectInitialValidators = Staking; + type DisabledValidatorsThreshold = DisabledValidatorsThreshold; +} + +impl pallet_session::historical::Trait for Runtime { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} + +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; + type CooloffPeriod = CooloffPeriod; + type PreimageByteDeposit = PreimageByteDeposit; + type Slash = Treasury; +} + +type CouncilCollective = pallet_collective::Instance1; +impl pallet_collective::Trait 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 for Runtime { + type Origin = Origin; + type Proposal = Call; + type Event = Event; +} + +impl pallet_membership::Trait 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; + type ComputeDispatchFee = pallet_contracts::DefaultDispatchFeeComputor; + type TrieIdGenerator = pallet_contracts::TrieIdFromParentCounter; + 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; + +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; + 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; + type MinLength = MinLength; + type MaxLength = MaxLength; +} + +impl frame_system::offchain::CreateTransaction for Runtime { + type Public = ::Signer; + type Signature = Signature; + + fn create_transaction>( + call: Call, + public: Self::Public, + account: AccountId, + index: Index, + ) -> Option<(Call, ::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::() + // 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::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::mortal(period, current_block)), + frame_system::CheckNonce::::from(index), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::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}, + 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}, + Democracy: pallet_democracy::{Module, Call, Storage, Config, Event}, + Council: pallet_collective::::{Module, Call, Storage, Origin, Event, Config}, + TechnicalCommittee: pallet_collective::::{Module, Call, Storage, Origin, Event, Config}, + Elections: pallet_elections_phragmen::{Module, Call, Storage, Event}, + TechnicalMembership: pallet_membership::::{Module, Call, Storage, Event, Config}, + FinalityTracker: pallet_finality_tracker::{Module, Call, Inherent}, + Grandpa: pallet_grandpa::{Module, Call, Storage, Config, Event}, + Treasury: pallet_treasury::{Module, Call, Storage, Config, Event}, + Contracts: pallet_contracts, + Sudo: pallet_sudo, + ImOnline: pallet_im_online::{Module, Call, Storage, Event, ValidateUnsigned, Config}, + 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}, + BridgeEthPoa: bridge_eth_poa::{Module, Call, Config}, // TODO: Update this + } +); + +/// The address format for describing accounts. +pub type Address = ::Source; +/// Block header type as expected by this runtime. +pub type Header = generic::Header; +/// Block type as expected by this runtime. +pub type Block = generic::Block; +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + pallet_contracts::CheckBlockGasLimit, +); +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +/// The payload being signed in transactions. +pub type SignedPayload = generic::SignedPayload; +/// Extrinsic type that has already been checked. +pub type CheckedExtrinsic = generic::CheckedExtrinsic; +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive, Runtime, AllModules>; + +impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + Runtime::metadata().into() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents(block: Block, data: InherentData) -> CheckInherentsResult { + data.check_extrinsics(&block) + } + + fn random_seed() -> ::Hash { + RandomnessCollectiveFlip::random_seed() + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction(tx: ::Extrinsic) -> TransactionValidity { + Executive::validate_transaction(tx) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl fg_primitives::GrandpaApi for Runtime { + fn grandpa_authorities() -> GrandpaAuthorityList { + Grandpa::grandpa_authorities() + } + } + + impl sp_consensus_babe::BabeApi 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. + // + 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 for Runtime { + fn authorities() -> Vec { + AuthorityDiscovery::authorities() + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Index { + System::account_nonce(account) + } + } + + impl pallet_contracts_rpc_runtime_api::ContractsApi for Runtime { + fn call( + origin: AccountId, + dest: AccountId, + value: Balance, + gas_limit: u64, + input_data: Vec, + ) -> 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 { + TransactionPayment::query_info(uxt, len) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + } + + impl sp_bridge_eth_poa::EthereumHeadersApi 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() where + T: SubmitSignedTransaction< + Runtime, + Call, + >, + {} + + fn is_sign_and_submit_transaction() where + T: SignAndSubmitTransaction< + Runtime, + Call, + Extrinsic=UncheckedExtrinsic, + CreateTransaction=Runtime, + Signer=ImOnlineId, + >, + {} + + is_submit_signed_transaction::(); + is_sign_and_submit_transaction::(); + } + + #[test] + fn block_hooks_weight_should_not_exceed_limits() { + use frame_support::weights::WeighBlock; + let check_for_block = |b| { + let block_hooks_weight = + >::on_initialize(b) + + >::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); + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/bin/node/testing/Cargo.toml b/bridges/modules/ethereum/bridge-runtime/bin/node/testing/Cargo.toml new file mode 100644 index 0000000000..c0707ad0ee --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/bin/node/testing/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "node-testing" +version = "2.0.0" +authors = ["Parity Technologies "] +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" } diff --git a/bridges/modules/ethereum/bridge-runtime/bin/node/testing/src/genesis.rs b/bridges/modules/ethereum/bridge-runtime/bin/node/testing/src/genesis.rs new file mode 100644 index 0000000000..85dddc808e --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/bin/node/testing/src/genesis.rs @@ -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 . + +//! 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(), + ], + }), + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/Cargo.toml b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/Cargo.toml new file mode 100644 index 0000000000..b8f195adba --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/Cargo.toml @@ -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 "] +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", +] diff --git a/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/error.rs b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/error.rs new file mode 100644 index 0000000000..f716a7e066 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/error.rs @@ -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 . + +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", + } + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/finality.rs b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/finality.rs new file mode 100644 index 0000000000..74c1660e8c --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/finality.rs @@ -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 . + +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( + storage: &S, + best_finalized_hash: &H256, + header_validators: (&H256, &[Address]), + hash: &H256, + header: &Header, + two_thirds_majority_transition: u64, +) -> Result, 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, + 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( + storage: &S, + best_finalized_hash: &H256, + validators_begin: &H256, + validators: &BTreeSet<&Address>, + hash: &H256, + header: &Header, + two_thirds_majority_transition: u64, +) -> Result<(BTreeMap, VecDeque<(H256, u64, BTreeSet
)>), 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
, + votes: &mut BTreeMap, +) -> 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
, votes: &mut BTreeMap) { + 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
{ + header.empty_steps() + .into_iter() + .flat_map(|steps| steps) + .filter_map(|step| empty_step_signer(&step, &header.parent_hash)) + .collect::>() +} + +/// Returns author of empty step signature. +fn empty_step_signer(empty_step: &SealedEmptyStep, parent_hash: &H256) -> Option
{ + 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); + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/import.rs b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/import.rs new file mode 100644 index 0000000000..318fea1bcc --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/import.rs @@ -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 . + +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( + storage: &mut S, + aura_config: &AuraConfiguration, + validators_config: &ValidatorsConfiguration, + prune_depth: u64, + headers: Vec<(Header, Option>)>, +) -> 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( + storage: &mut S, + aura_config: &AuraConfiguration, + validators_config: &ValidatorsConfiguration, + prune_depth: u64, + header: Header, + receipts: Option>, +) -> Result { + // 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( + 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(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::>(); + 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::>(); + 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); + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/lib.rs b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/lib.rs new file mode 100644 index 0000000000..430a950d3c --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/lib.rs @@ -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 . + +#![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>, + /// Validators set scheduled change, if happened at the header. + pub scheduled_change: Option>, +} + +/// 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
), +} + +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
), + ) -> 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>, + scheduled_change: Option>, + ) -> 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
; + /// Get header import context by parent header hash. + fn import_context(&self, parent_hash: &H256) -> Option; + /// Get new validators that are scheduled by given header. + fn scheduled_change(&self, hash: &H256) -> Option>; + /// 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, + ); +} + +/// Decides whether the session should be ended. +pub trait OnHeadersSubmitted { + /// 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 OnHeadersSubmitted 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; +} + +decl_module! { + pub struct Module 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>)>) { + 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 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; + /// Map of imported header hashes by number. + HeadersByNumber: map u64 => Option>; + /// The ID of next validator set. + NextValidatorsSetId: u64; + /// Map of validators sets by their id. + ValidatorsSets: map u64 => Option<(H256, Vec
)>; + /// 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; + /// Map of validators set changes scheduled by given header. + ScheduledChanges: map H256 => Option>; + } + add_extra_genesis { + config(initial_header): Header; + config(initial_difficulty): U256; + config(initial_validators): Vec
; + 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 Module { + /// 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
{ + Headers::get(hash).map(|header| header.header) + } + + fn import_context(&self, parent_hash: &H256) -> Option { + 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> { + 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, + ) { + // 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 + '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
{ + (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, + headers_by_number: HashMap>, + next_validators_set_id: u64, + validators_sets: HashMap)>, + validators_sets_rc: HashMap, + scheduled_changes: HashMap>, + } + + impl InMemoryStorage { + pub fn new(initial_header: Header, initial_validators: Vec
) -> 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
{ + self.headers.get(hash).map(|header| header.header.clone()) + } + + fn import_context(&self, parent_hash: &H256) -> Option { + 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> { + 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, + ) { + 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; + } + } + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/validators.rs b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/validators.rs new file mode 100644 index 0000000000..db1eacd3b3 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/validators.rs @@ -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 . + +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
), + /// 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
), +} + +/// 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>, + ) -> Result<(Option>, Option>), 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( + &self, + storage: &mut S, + finalized_blocks: &[(u64, H256)], + ) -> Option> { + 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
{ + 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), + ); + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/verification.rs b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/verification.rs new file mode 100644 index 0000000000..10574db327 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/frame/bridge-eth-poa/src/verification.rs @@ -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 . + +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( + storage: &S, + params: &AuraConfiguration, + header: &Header, +) -> Result { + // 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 { + let storage = InMemoryStorage::new(genesis(), validators_addresses(3)); + verify_aura_header(&storage, &config, header) + } + + fn default_verify(header: &Header) -> Result { + 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::>().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::>().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(())); + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/primitives/bridge-eth-poa/Cargo.toml b/bridges/modules/ethereum/bridge-runtime/primitives/bridge-eth-poa/Cargo.toml new file mode 100644 index 0000000000..11c4d46ee2 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/primitives/bridge-eth-poa/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "sp-bridge-eth-poa" +description = "Primitives of Ethereum PoA Bridge module." +version = "0.1.0" +authors = ["Parity Technologies "] +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", +] diff --git a/bridges/modules/ethereum/bridge-runtime/primitives/bridge-eth-poa/src/lib.rs b/bridges/modules/ethereum/bridge-runtime/primitives/bridge-eth-poa/src/lib.rs new file mode 100644 index 0000000000..f2a7ab7f14 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/primitives/bridge-eth-poa/src/lib.rs @@ -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 . + +#![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, +} + +/// 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, + /// 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, + /// 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) -> 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::(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 { + 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 { + 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 { + 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> { + self.seal.get(2).and_then(|x| Rlp::new(x).as_list::().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 { + 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 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; + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/primitives/core/src/hasher.rs b/bridges/modules/ethereum/bridge-runtime/primitives/core/src/hasher.rs new file mode 100644 index 0000000000..bb0bd665bc --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/primitives/core/src/hasher.rs @@ -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 . + +//! 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() + } + } +} diff --git a/bridges/modules/ethereum/bridge-runtime/primitives/core/src/lib.rs b/bridges/modules/ethereum/bridge-runtime/primitives/core/src/lib.rs new file mode 100644 index 0000000000..0092f892b7 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/primitives/core/src/lib.rs @@ -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 . + +//! 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, 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`. +#[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); + +impl From> for Bytes { + fn from(s: Vec) -> Self { Bytes(s) } +} + +impl From 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); + +impl OpaqueMetadata { + /// Creates a new instance with the given metadata blob. + pub fn new(metadata: Vec) -> Self { + OpaqueMetadata(metadata) + } +} + +impl sp_std::ops::Deref for OpaqueMetadata { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Something that is either a native or an encoded value. +#[cfg(feature = "std")] +pub enum NativeOrEncoded { + /// The native representation. + Native(R), + /// The encoded representation. + Encoded(Vec) +} + +#[cfg(feature = "std")] +impl sp_std::fmt::Debug for NativeOrEncoded { + 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 NativeOrEncoded { + /// 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 { + match self { + NativeOrEncoded::Encoded(e) => e, + NativeOrEncoded::Native(n) => n.encode(), + } + } +} + +#[cfg(feature = "std")] +impl PartialEq for NativeOrEncoded { + 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 { + // 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(_: &mut I) -> Result { + 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 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 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 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 +} diff --git a/bridges/modules/ethereum/bridge-runtime/primitives/io/Cargo.toml b/bridges/modules/ethereum/bridge-runtime/primitives/io/Cargo.toml new file mode 100644 index 0000000000..04350c9479 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/primitives/io/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "sp-io" +version = "2.0.0" +authors = ["Parity Technologies "] +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 = [] diff --git a/bridges/modules/ethereum/bridge-runtime/primitives/io/src/lib.rs b/bridges/modules/ethereum/bridge-runtime/primitives/io/src/lib.rs new file mode 100644 index 0000000000..8a87028da6 --- /dev/null +++ b/bridges/modules/ethereum/bridge-runtime/primitives/io/src/lib.rs @@ -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 . + +//! 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> { + 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> { + 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 { + 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 { + 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 { + 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 { + 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> { + 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> { + 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> { + 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, Vec)>) -> H256 { + Layout::::trie_root(input) + } + + /// A trie root formed from the enumerated items. + fn blake2_256_ordered_root(input: Vec>) -> H256 { + Layout::::ordered_trie_root(input) + } + + /// A trie root formed from the enumerated items. + fn keccak_256_ordered_root(input: Vec>) -> H256 { + Layout::::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 { + self.extension::() + .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>) -> ed25519::Public { + let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!")); + self.extension::() + .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 { + self.extension::() + .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 { + self.extension::() + .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>) -> sr25519::Public { + let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!")); + self.extension::() + .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 { + self.extension::() + .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::() + .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) -> Result<(), ()> { + self.extension::() + .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 { + self.extension::() + .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::() + .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::() + .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::() + .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::() + .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>, + new_value: &[u8], + ) -> bool { + self.extension::() + .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> { + self.extension::() + .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 { + self.extension::() + .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::() + .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, + ) -> Result<(), HttpError> { + self.extension::() + .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, + ) -> Vec { + self.extension::() + .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, Vec)> { + self.extension::() + .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, + ) -> Result { + self.extension::() + .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 { + self.allocate_memory(size).expect("Failed to allocate memory") + } + + /// Free the given pointer. + fn free(&mut self, ptr: Pointer) { + 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, + ) -> 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, + return_val_len: u32, + state_ptr: Pointer, + ) -> 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, + 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, + 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; + +/// 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()); + }); + } +}