mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 13:15:41 +00:00
Phase 1 of repo reorg (#719)
* Remove unneeded script * Rename Substrate Demo -> Substrate * Rename demo -> node * Build wasm from last rename. * Merge ed25519 into substrate-primitives * Minor tweak * Rename substrate -> core * Move substrate-runtime-support to core/runtime/support * Rename/move substrate-runtime-version * Move codec up a level * Rename substrate-codec -> parity-codec * Move environmental up a level * Move pwasm-* up to top, ready for removal * Remove requirement of s-r-support from s-r-primitives * Move core/runtime/primitives into core/runtime-primitives * Remove s-r-support dep from s-r-version * Remove dep of s-r-support from bft * Remove dep of s-r-support from node/consensus * Sever all other core deps from s-r-support * Forgot the no_std directive * Rename non-SRML modules to sr-* to avoid match clashes * Move runtime/* to srml/* * Rename substrate-runtime-* -> srml-* * Move srml to top-level
This commit is contained in:
committed by
Arkadiy Paronyan
parent
8fe5aa4c81
commit
1e01162505
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "substrate-bft"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.17"
|
||||
parity-codec = { path = "../../codec" }
|
||||
substrate-primitives = { path = "../primitives" }
|
||||
sr-primitives = { path = "../sr-primitives" }
|
||||
sr-version = { path = "../sr-version" }
|
||||
tokio = "0.1.7"
|
||||
parking_lot = "0.4"
|
||||
error-chain = "0.12"
|
||||
log = "0.3"
|
||||
rhododendron = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-keyring = { path = "../keyring" }
|
||||
substrate-executor = { path = "../executor" }
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Substrate BFT
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Error types in the BFT service.
|
||||
use runtime_version::RuntimeVersion;
|
||||
use primitives::ed25519;
|
||||
|
||||
error_chain! {
|
||||
errors {
|
||||
/// Missing state at block with given descriptor.
|
||||
StateUnavailable(b: String) {
|
||||
description("State missing at given block."),
|
||||
display("State unavailable at block {}", b),
|
||||
}
|
||||
|
||||
/// I/O terminated unexpectedly
|
||||
IoTerminated {
|
||||
description("I/O terminated unexpectedly."),
|
||||
display("I/O terminated unexpectedly."),
|
||||
}
|
||||
|
||||
/// Unable to schedule wakeup.
|
||||
FaultyTimer(e: ::tokio::timer::Error) {
|
||||
description("Timer error"),
|
||||
display("Timer error: {}", e),
|
||||
}
|
||||
|
||||
/// Unable to propose a block.
|
||||
CannotPropose {
|
||||
description("Unable to create block proposal."),
|
||||
display("Unable to create block proposal."),
|
||||
}
|
||||
|
||||
/// Error checking signature
|
||||
InvalidSignature(s: ed25519::Signature, a: ::primitives::AuthorityId) {
|
||||
description("Message signature is invalid"),
|
||||
display("Message signature {:?} by {:?} is invalid.", s, a),
|
||||
}
|
||||
|
||||
/// Account is not an authority.
|
||||
InvalidAuthority(a: ::primitives::AuthorityId) {
|
||||
description("Message sender is not a valid authority"),
|
||||
display("Message sender {:?} is not a valid authority.", a),
|
||||
}
|
||||
|
||||
/// Authoring interface does not match the runtime.
|
||||
IncompatibleAuthoringRuntime(native: RuntimeVersion, on_chain: RuntimeVersion) {
|
||||
description("Authoring for current runtime is not supported"),
|
||||
display("Authoring for current runtime is not supported. Native ({}) cannot author for on-chain ({}).", native, on_chain),
|
||||
}
|
||||
|
||||
/// Authoring interface does not match the runtime.
|
||||
RuntimeVersionMissing {
|
||||
description("Current runtime has no version"),
|
||||
display("Authoring for current runtime is not supported since it has no version."),
|
||||
}
|
||||
|
||||
/// Authoring interface does not match the runtime.
|
||||
NativeRuntimeMissing {
|
||||
description("This build has no native runtime"),
|
||||
display("Authoring in current build is not supported since it has no runtime."),
|
||||
}
|
||||
|
||||
/// Justification requirements not met.
|
||||
InvalidJustification {
|
||||
description("Invalid justification"),
|
||||
display("Invalid justification."),
|
||||
}
|
||||
|
||||
/// Some other error.
|
||||
Other(e: Box<::std::error::Error + Send>) {
|
||||
description("Other error")
|
||||
display("Other error: {}", e.description())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::rhododendron::InputStreamConcluded> for Error {
|
||||
fn from(_: ::rhododendron::InputStreamConcluded) -> Error {
|
||||
ErrorKind::IoTerminated.into()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "substrate-cli"
|
||||
version = "0.3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Substrate CLI interface."
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "~2.32", features = ["yaml"] }
|
||||
backtrace = "0.3"
|
||||
env_logger = "0.4"
|
||||
error-chain = "0.12"
|
||||
log = "0.3"
|
||||
atty = "0.2"
|
||||
regex = "1"
|
||||
time = "0.1"
|
||||
slog = "^2"
|
||||
ansi_term = "0.10"
|
||||
lazy_static = "1.0"
|
||||
app_dirs = "1.2"
|
||||
tokio = "0.1.7"
|
||||
futures = "0.1.17"
|
||||
fdlimit = "0.1"
|
||||
exit-future = "0.1"
|
||||
sysinfo = "0.5.7"
|
||||
substrate-client = { path = "../../core/client" }
|
||||
substrate-network = { path = "../../core/network" }
|
||||
substrate-network-libp2p = { path = "../../core/network-libp2p" }
|
||||
sr-primitives = { path = "../../core/sr-primitives" }
|
||||
substrate-service = { path = "../../core/service" }
|
||||
substrate-telemetry = { path = "../../core/telemetry" }
|
||||
names = "0.11.0"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "~2.32"
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Substrate CLI
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
Polkadot CLI library
|
||||
|
||||
include::doc/shell-completion.adoc[]
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
||||
use std::fs;
|
||||
use std::env;
|
||||
use clap::Shell;
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
build_shell_completion();
|
||||
}
|
||||
|
||||
/// Build shell completion scripts for all known shells
|
||||
/// Full list in https://github.com/kbknapp/clap-rs/blob/e9d0562a1dc5dfe731ed7c767e6cee0af08f0cf9/src/app/parser.rs#L123
|
||||
fn build_shell_completion() {
|
||||
let shells = [Shell::Bash, Shell::Fish, Shell::Zsh, Shell::Elvish, Shell::PowerShell];
|
||||
for shell in shells.iter() {
|
||||
build_completion(shell);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the shell auto-completion for a given Shell
|
||||
fn build_completion(shell: &Shell) {
|
||||
let yml = load_yaml!("src/cli.yml");
|
||||
|
||||
let outdir = match env::var_os("OUT_DIR") {
|
||||
None => return,
|
||||
Some(dir) => dir,
|
||||
};
|
||||
let path = Path::new(&outdir)
|
||||
.parent().unwrap()
|
||||
.parent().unwrap()
|
||||
.parent().unwrap()
|
||||
.join("completion-scripts");
|
||||
|
||||
fs::create_dir(&path).ok();
|
||||
|
||||
let mut app = clap::App::from_yaml(&yml);
|
||||
app.gen_completions(
|
||||
"polkadot",
|
||||
*shell,
|
||||
&path);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
== Shell completion
|
||||
|
||||
The Substrate cli command supports shell auto-completion. For this to work, you will need to run the completion script matching you build and system.
|
||||
|
||||
Assuming you built a release version using `cargo build --release` and use `bash` run the following:
|
||||
|
||||
[source, shell]
|
||||
source target/release/completion-scripts/substrate.bash
|
||||
|
||||
You can find completion scripts for:
|
||||
- bash
|
||||
- fish
|
||||
- zsh
|
||||
- elvish
|
||||
- powershell
|
||||
|
||||
To make this change persistent, you can proceed as follow:
|
||||
|
||||
.First install
|
||||
|
||||
[source, shell]
|
||||
----
|
||||
COMPL_DIR=$HOME/.completion
|
||||
mkdir -p $COMPL_DIR
|
||||
cp -f target/release/completion-scripts/substrate.bash $COMPL_DIR/
|
||||
echo "source $COMPL_DIR/substrate.bash" >> $HOME/.bash_profile
|
||||
source $HOME/.bash_profile
|
||||
----
|
||||
|
||||
.Update
|
||||
|
||||
When you build a new version of Substrate, the following will ensure you auto-completion script matches the current binary:
|
||||
|
||||
[source, shell]
|
||||
----
|
||||
COMPL_DIR=$HOME/.completion
|
||||
mkdir -p $COMPL_DIR
|
||||
cp -f target/release/completion-scripts/substrate.bash $COMPL_DIR/
|
||||
source $HOME/.bash_profile
|
||||
----
|
||||
@@ -0,0 +1,229 @@
|
||||
name: {name}
|
||||
author: {author}
|
||||
about: {description}
|
||||
args:
|
||||
- log:
|
||||
short: l
|
||||
long: log
|
||||
value_name: LOG_PATTERN
|
||||
help: Sets a custom logging filter
|
||||
takes_value: true
|
||||
- base-path:
|
||||
long: base-path
|
||||
short: d
|
||||
value_name: PATH
|
||||
help: Specify custom base path
|
||||
takes_value: true
|
||||
- keystore-path:
|
||||
long: keystore-path
|
||||
value_name: PATH
|
||||
help: Specify custom keystore path
|
||||
takes_value: true
|
||||
- key:
|
||||
long: key
|
||||
value_name: STRING
|
||||
help: Specify additional key seed
|
||||
takes_value: true
|
||||
- node-key:
|
||||
long: node-key
|
||||
value_name: KEY
|
||||
help: Specify node secret key (64-character hex string)
|
||||
takes_value: true
|
||||
- validator:
|
||||
long: validator
|
||||
help: Enable validator mode
|
||||
takes_value: false
|
||||
- light:
|
||||
long: light
|
||||
help: Run in light client mode
|
||||
takes_value: false
|
||||
- dev:
|
||||
long: dev
|
||||
help: Run in development mode; implies --chain=dev --validator --key Alice
|
||||
takes_value: false
|
||||
- listen-addr:
|
||||
long: listen-addr
|
||||
value_name: LISTEN_ADDR
|
||||
help: Listen on this multiaddress
|
||||
takes_value: true
|
||||
multiple: true
|
||||
- port:
|
||||
long: port
|
||||
value_name: PORT
|
||||
help: Specify p2p protocol TCP port. Only used if --listen-addr is not specified.
|
||||
takes_value: true
|
||||
- rpc-external:
|
||||
long: rpc-external
|
||||
help: Listen to all RPC interfaces (default is local)
|
||||
takes_value: false
|
||||
- ws-external:
|
||||
long: ws-external
|
||||
help: Listen to all Websocket interfaces (default is local)
|
||||
takes_value: false
|
||||
- rpc-port:
|
||||
long: rpc-port
|
||||
value_name: PORT
|
||||
help: Specify HTTP RPC server TCP port
|
||||
takes_value: true
|
||||
- ws-port:
|
||||
long: ws-port
|
||||
value_name: PORT
|
||||
help: Specify WebSockets RPC server TCP port
|
||||
takes_value: true
|
||||
- bootnodes:
|
||||
long: bootnodes
|
||||
value_name: URL
|
||||
help: Specify a list of bootnodes
|
||||
takes_value: true
|
||||
multiple: true
|
||||
- reserved-nodes:
|
||||
long: reserved-nodes
|
||||
value_name: URL
|
||||
help: Specify a list of reserved node addresses
|
||||
takes_value: true
|
||||
multiple: true
|
||||
- min-peers:
|
||||
long: min-peers
|
||||
value_name: MIN_PEERS
|
||||
help: Specify the minimum number of peers
|
||||
takes_value: true
|
||||
- max-peers:
|
||||
long: max-peers
|
||||
value_name: MAX_PEERS
|
||||
help: Specify the maximum number of peers
|
||||
takes_value: true
|
||||
- chain:
|
||||
long: chain
|
||||
value_name: CHAIN_SPEC
|
||||
help: Specify the chain specification (one of krummelanke, dev, local or staging)
|
||||
takes_value: true
|
||||
- pruning:
|
||||
long: pruning
|
||||
value_name: PRUNING_MODE
|
||||
help: Specify the pruning mode, a number of blocks to keep or "archive". Default is 256.
|
||||
takes_value: true
|
||||
- name:
|
||||
long: name
|
||||
value_name: NAME
|
||||
help: The human-readable name for this node, as reported to the telemetry server, if enabled
|
||||
takes_value: true
|
||||
- telemetry:
|
||||
short: t
|
||||
long: telemetry
|
||||
help: Should connect to the Polkadot telemetry server (telemetry is off by default on local chains)
|
||||
takes_value: false
|
||||
- no-telemetry:
|
||||
long: no-telemetry
|
||||
help: Should not connect to the Polkadot telemetry server (telemetry is on by default on global chains)
|
||||
takes_value: false
|
||||
- telemetry-url:
|
||||
long: telemetry-url
|
||||
value_name: TELEMETRY_URL
|
||||
help: The URL of the telemetry server. Implies --telemetry
|
||||
takes_value: true
|
||||
- execution:
|
||||
long: execution
|
||||
value_name: STRATEGY
|
||||
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
|
||||
subcommands:
|
||||
- build-spec:
|
||||
about: Build a spec.json file, outputing to stdout
|
||||
args:
|
||||
- raw:
|
||||
long: raw
|
||||
help: Force raw genesis storage output.
|
||||
takes_value: false
|
||||
- chain:
|
||||
long: chain
|
||||
value_name: CHAIN_SPEC
|
||||
help: Specify the chain specification (one of krummelanke, dev, local or staging)
|
||||
takes_value: true
|
||||
- export-blocks:
|
||||
about: Export blocks to a file
|
||||
args:
|
||||
- OUTPUT:
|
||||
index: 1
|
||||
help: Output file name or stdout if unspecified.
|
||||
required: false
|
||||
- chain:
|
||||
long: chain
|
||||
value_name: CHAIN_SPEC
|
||||
help: Specify the chain specification.
|
||||
takes_value: true
|
||||
- base-path:
|
||||
long: base-path
|
||||
short: d
|
||||
value_name: PATH
|
||||
help: Specify custom base path.
|
||||
takes_value: true
|
||||
- from:
|
||||
long: from
|
||||
value_name: BLOCK
|
||||
help: Specify starting block number. 1 by default.
|
||||
takes_value: true
|
||||
- to:
|
||||
long: to
|
||||
value_name: BLOCK
|
||||
help: Specify last block number. Best block by default.
|
||||
takes_value: true
|
||||
- json:
|
||||
long: json
|
||||
help: Use JSON output rather than binary.
|
||||
takes_value: false
|
||||
- import-blocks:
|
||||
about: Import blocks from file.
|
||||
args:
|
||||
- INPUT:
|
||||
index: 1
|
||||
help: Input file or stdin if unspecified.
|
||||
required: false
|
||||
- chain:
|
||||
long: chain
|
||||
value_name: CHAIN_SPEC
|
||||
help: Specify the chain specification.
|
||||
takes_value: true
|
||||
- base-path:
|
||||
long: base-path
|
||||
short: d
|
||||
value_name: PATH
|
||||
help: Specify custom base path.
|
||||
takes_value: true
|
||||
- execution:
|
||||
long: execution
|
||||
value_name: STRATEGY
|
||||
help: The means of execution used when calling into the runtime. Can be either wasm, native or both.
|
||||
- max-heap-pages:
|
||||
long: max-heap-pages
|
||||
value_name: COUNT
|
||||
help: The maximum number of 64KB pages to ever allocate for Wasm execution. Don't alter this unless you know what you're doing.
|
||||
- revert:
|
||||
about: Revert chain to the previous state
|
||||
args:
|
||||
- NUM:
|
||||
index: 1
|
||||
help: Number of blocks to revert. Default is 256.
|
||||
- chain:
|
||||
long: chain
|
||||
value_name: CHAIN_SPEC
|
||||
help: Specify the chain specification.
|
||||
takes_value: true
|
||||
- base-path:
|
||||
long: base-path
|
||||
short: d
|
||||
value_name: PATH
|
||||
help: Specify custom base path.
|
||||
takes_value: true
|
||||
- purge-chain:
|
||||
about: Remove the whole chain data.
|
||||
args:
|
||||
- chain:
|
||||
long: chain
|
||||
value_name: CHAIN_SPEC
|
||||
help: Specify the chain specification.
|
||||
takes_value: true
|
||||
- base-path:
|
||||
long: base-path
|
||||
short: d
|
||||
value_name: PATH
|
||||
help: Specify custom base path.
|
||||
takes_value: true
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Initialization errors.
|
||||
|
||||
use client;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
Io(::std::io::Error) #[doc="IO error"];
|
||||
Cli(::clap::Error) #[doc="CLI error"];
|
||||
Service(::service::Error) #[doc="Substrate service error"];
|
||||
}
|
||||
links {
|
||||
Client(client::error::Error, client::error::ErrorKind) #[doc="Client error"];
|
||||
}
|
||||
errors {
|
||||
/// Input error.
|
||||
Input(m: String) {
|
||||
description("Invalid input"),
|
||||
display("{}", m),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Console informant. Prints sync progress and block events. Runs on the calling thread.
|
||||
|
||||
use ansi_term::Colour;
|
||||
use std::time::{Duration, Instant};
|
||||
use futures::{Future, Stream};
|
||||
use service::{Service, Components};
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use tokio::timer::Interval;
|
||||
use sysinfo::{get_current_pid, ProcessExt, System, SystemExt};
|
||||
use network::{SyncState, SyncProvider};
|
||||
use client::BlockchainEvents;
|
||||
use runtime_primitives::traits::{Header, As};
|
||||
|
||||
const TIMER_INTERVAL_MS: u64 = 5000;
|
||||
|
||||
/// Spawn informant on the event loop
|
||||
pub fn start<C>(service: &Service<C>, exit: ::exit_future::Exit, handle: TaskExecutor)
|
||||
where
|
||||
C: Components,
|
||||
{
|
||||
let interval = Interval::new(Instant::now(), Duration::from_millis(TIMER_INTERVAL_MS));
|
||||
|
||||
let network = service.network();
|
||||
let client = service.client();
|
||||
let txpool = service.extrinsic_pool();
|
||||
let mut last_number = None;
|
||||
|
||||
let mut sys = System::new();
|
||||
let self_pid = get_current_pid();
|
||||
|
||||
let display_notifications = interval.map_err(|e| debug!("Timer error: {:?}", e)).for_each(move |_| {
|
||||
let sync_status = network.status();
|
||||
|
||||
if let Ok(best_block) = client.best_block_header() {
|
||||
let hash = best_block.hash();
|
||||
let num_peers = sync_status.num_peers;
|
||||
let best_number: u64 = best_block.number().as_();
|
||||
let speed = move || speed(best_number, last_number);
|
||||
let (status, target) = match (sync_status.sync.state, sync_status.sync.best_seen_block) {
|
||||
(SyncState::Idle, _) => ("Idle".into(), "".into()),
|
||||
(SyncState::Downloading, None) => (format!("Syncing{}", speed()), "".into()),
|
||||
(SyncState::Downloading, Some(n)) => (format!("Syncing{}", speed()), format!(", target=#{}", n)),
|
||||
};
|
||||
last_number = Some(best_number);
|
||||
let txpool_status = txpool.light_status();
|
||||
info!(
|
||||
target: "substrate",
|
||||
"{}{} ({} peers), best: #{} ({})",
|
||||
Colour::White.bold().paint(&status),
|
||||
target,
|
||||
Colour::White.bold().paint(format!("{}", sync_status.num_peers)),
|
||||
Colour::White.paint(format!("{}", best_number)),
|
||||
hash
|
||||
);
|
||||
|
||||
// get cpu usage and memory usage of this process
|
||||
let (cpu_usage, memory) = if sys.refresh_process(self_pid) {
|
||||
let proc = sys.get_process(self_pid).expect("Above refresh_process succeeds, this should be Some(), qed");
|
||||
(proc.cpu_usage(), proc.memory())
|
||||
} else { (0.0, 0) };
|
||||
|
||||
telemetry!(
|
||||
"system.interval";
|
||||
"status" => format!("{}{}", status, target),
|
||||
"peers" => num_peers,
|
||||
"height" => best_number,
|
||||
"best" => ?hash,
|
||||
"txcount" => txpool_status.transaction_count,
|
||||
"cpu" => cpu_usage,
|
||||
"memory" => memory
|
||||
);
|
||||
} else {
|
||||
warn!("Error getting best block information");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let client = service.client();
|
||||
let display_block_import = client.import_notification_stream().for_each(|n| {
|
||||
info!(target: "substrate", "Imported #{} ({})", n.header.number(), n.hash);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let txpool = service.extrinsic_pool();
|
||||
let display_txpool_import = txpool.import_notification_stream().for_each(move |_| {
|
||||
let status = txpool.light_status();
|
||||
telemetry!("txpool.import"; "mem_usage" => status.mem_usage, "count" => status.transaction_count, "sender" => status.senders);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let informant_work = display_notifications.join3(display_block_import, display_txpool_import);
|
||||
handle.spawn(exit.until(informant_work).map(|_| ()));
|
||||
}
|
||||
|
||||
fn speed(best_number: u64, last_number: Option<u64>) -> String {
|
||||
let speed = match last_number {
|
||||
Some(num) => (best_number.saturating_sub(num) * 10_000 / TIMER_INTERVAL_MS) as f64,
|
||||
None => 0.0
|
||||
};
|
||||
|
||||
if speed < 1.0 {
|
||||
"".into()
|
||||
} else {
|
||||
format!(" {:4.1} bps", speed / 10.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,571 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// tag::description[]
|
||||
//! Substrate CLI library.
|
||||
// end::description[]
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(unused_extern_crates)]
|
||||
|
||||
extern crate app_dirs;
|
||||
extern crate env_logger;
|
||||
extern crate atty;
|
||||
extern crate ansi_term;
|
||||
extern crate regex;
|
||||
extern crate time;
|
||||
extern crate fdlimit;
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
extern crate names;
|
||||
extern crate backtrace;
|
||||
extern crate sysinfo;
|
||||
|
||||
extern crate substrate_client as client;
|
||||
extern crate substrate_network as network;
|
||||
extern crate substrate_network_libp2p as network_libp2p;
|
||||
extern crate sr_primitives as runtime_primitives;
|
||||
extern crate substrate_service as service;
|
||||
#[macro_use]
|
||||
extern crate slog; // needed until we can reexport `slog_info` from `substrate_telemetry`
|
||||
#[macro_use]
|
||||
extern crate substrate_telemetry;
|
||||
extern crate exit_future;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate clap;
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod error;
|
||||
pub mod informant;
|
||||
mod panic_hook;
|
||||
|
||||
use network_libp2p::AddrComponent;
|
||||
use runtime_primitives::traits::As;
|
||||
use service::{
|
||||
ServiceFactory, FactoryFullConfiguration, RuntimeGenesis,
|
||||
FactoryGenesis, PruningMode, ChainSpec,
|
||||
};
|
||||
use network::NonReservedPeerMode;
|
||||
|
||||
use std::io::{Write, Read, stdin, stdout};
|
||||
use std::iter;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use names::{Generator, Name};
|
||||
use regex::Regex;
|
||||
|
||||
use futures::Future;
|
||||
|
||||
/// Executable version. Used to pass version information from the root crate.
|
||||
pub struct VersionInfo {
|
||||
/// Implementation version.
|
||||
pub version: &'static str,
|
||||
/// SCM Commit hash.
|
||||
pub commit: &'static str,
|
||||
/// Executable file name.
|
||||
pub executable_name: &'static str,
|
||||
/// Executable file description.
|
||||
pub description: &'static str,
|
||||
/// Executable file author.
|
||||
pub author: &'static str,
|
||||
}
|
||||
|
||||
/// CLI Action
|
||||
pub enum Action<F: ServiceFactory, E: IntoExit> {
|
||||
/// Substrate handled the command. No need to do anything.
|
||||
ExecutedInternally,
|
||||
/// Service mode requested. Caller should start the service.
|
||||
RunService((FactoryFullConfiguration<F>, E)),
|
||||
}
|
||||
|
||||
/// Something that can be converted into an exit signal.
|
||||
pub trait IntoExit {
|
||||
/// Exit signal type.
|
||||
type Exit: Future<Item=(),Error=()> + Send + 'static;
|
||||
/// Convert into exit signal.
|
||||
fn into_exit(self) -> Self::Exit;
|
||||
}
|
||||
|
||||
fn load_spec<F, G>(matches: &clap::ArgMatches, factory: F) -> Result<ChainSpec<G>, String>
|
||||
where G: RuntimeGenesis, F: FnOnce(&str) -> Result<Option<ChainSpec<G>>, String>,
|
||||
{
|
||||
let chain_key = matches.value_of("chain").unwrap_or_else(|| if matches.is_present("dev") { "dev" } else { "" });
|
||||
let spec = match factory(chain_key)? {
|
||||
Some(spec) => spec,
|
||||
None => ChainSpec::from_json_file(PathBuf::from(chain_key))?
|
||||
};
|
||||
Ok(spec)
|
||||
}
|
||||
|
||||
fn base_path(matches: &clap::ArgMatches) -> PathBuf {
|
||||
matches.value_of("base-path")
|
||||
.map(|x| Path::new(x).to_owned())
|
||||
.unwrap_or_else(default_base_path)
|
||||
}
|
||||
|
||||
/// Check whether a node name is considered as valid
|
||||
fn is_node_name_valid(_name: &str) -> Result<(), &str> {
|
||||
const MAX_NODE_NAME_LENGTH: usize = 32;
|
||||
let name = _name.to_string();
|
||||
if name.chars().count() >= MAX_NODE_NAME_LENGTH {
|
||||
return Err("Node name too long");
|
||||
}
|
||||
|
||||
let invalid_chars = r"[\\.@]";
|
||||
let re = Regex::new(invalid_chars).unwrap();
|
||||
if re.is_match(&name) {
|
||||
return Err("Node name should not contain invalid chars such as '.' and '@'");
|
||||
}
|
||||
|
||||
let invalid_patterns = r"(https?:\\/+)?(www)+";
|
||||
let re = Regex::new(invalid_patterns).unwrap();
|
||||
if re.is_match(&name) {
|
||||
return Err("Node name should not contain urls");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse command line arguments and execute commands or return service configuration.
|
||||
///
|
||||
/// IANA unassigned port ranges that we could use:
|
||||
/// 6717-6766 Unassigned
|
||||
/// 8504-8553 Unassigned
|
||||
/// 9556-9591 Unassigned
|
||||
/// 9803-9874 Unassigned
|
||||
/// 9926-9949 Unassigned
|
||||
pub fn prepare_execution<F, I, T, E, S>(
|
||||
args: I,
|
||||
exit: E,
|
||||
version: VersionInfo,
|
||||
spec_factory: S,
|
||||
impl_name: &'static str,
|
||||
) -> error::Result<Action<F, E>>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<std::ffi::OsString> + Clone,
|
||||
E: IntoExit,
|
||||
F: ServiceFactory,
|
||||
S: FnOnce(&str) -> Result<Option<ChainSpec<FactoryGenesis<F>>>, String>,
|
||||
{
|
||||
panic_hook::set();
|
||||
|
||||
let full_version = service::config::full_version_from_strs(
|
||||
version.version,
|
||||
version.commit
|
||||
);
|
||||
let yaml = format!(include_str!("./cli.yml"),
|
||||
name = version.executable_name,
|
||||
description = version.description,
|
||||
author = version.author,
|
||||
);
|
||||
let yaml = &clap::YamlLoader::load_from_str(&yaml).expect("Invalid yml file")[0];
|
||||
let matches = match clap::App::from_yaml(yaml)
|
||||
.version(&(full_version + "\n")[..])
|
||||
.get_matches_from_safe(args) {
|
||||
Ok(m) => m,
|
||||
Err(e) => e.exit(),
|
||||
};
|
||||
|
||||
// TODO [ToDr] Split parameters parsing from actual execution.
|
||||
let log_pattern = matches.value_of("log").unwrap_or("");
|
||||
init_logger(log_pattern);
|
||||
fdlimit::raise_fd_limit();
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("build-spec") {
|
||||
let spec = load_spec(&matches, spec_factory)?;
|
||||
build_spec::<F>(matches, spec)?;
|
||||
return Ok(Action::ExecutedInternally);
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("export-blocks") {
|
||||
let spec = load_spec(&matches, spec_factory)?;
|
||||
export_blocks::<F, _>(matches, spec, exit.into_exit())?;
|
||||
return Ok(Action::ExecutedInternally);
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("import-blocks") {
|
||||
let spec = load_spec(&matches, spec_factory)?;
|
||||
import_blocks::<F, _>(matches, spec, exit.into_exit())?;
|
||||
return Ok(Action::ExecutedInternally);
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("revert") {
|
||||
let spec = load_spec(&matches, spec_factory)?;
|
||||
revert_chain::<F>(matches, spec)?;
|
||||
return Ok(Action::ExecutedInternally);
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("purge-chain") {
|
||||
let spec = load_spec(&matches, spec_factory)?;
|
||||
purge_chain::<F>(matches, spec)?;
|
||||
return Ok(Action::ExecutedInternally);
|
||||
}
|
||||
|
||||
let spec = load_spec(&matches, spec_factory)?;
|
||||
let mut config = service::Configuration::default_with_spec(spec);
|
||||
|
||||
config.impl_name = impl_name;
|
||||
config.impl_commit = version.commit;
|
||||
config.impl_version = version.version;
|
||||
|
||||
config.name = match matches.value_of("name") {
|
||||
None => Generator::with_naming(Name::Numbered).next().unwrap(),
|
||||
Some(name) => name.into(),
|
||||
};
|
||||
match is_node_name_valid(&config.name) {
|
||||
Ok(_) => (),
|
||||
Err(msg) => return Err(error::ErrorKind::Input(
|
||||
format!("Invalid node name '{}'. Reason: {}. If unsure, use none.", config.name, msg)).into())
|
||||
}
|
||||
|
||||
let base_path = base_path(&matches);
|
||||
|
||||
config.keystore_path = matches.value_of("keystore")
|
||||
.map(|x| Path::new(x).to_owned())
|
||||
.unwrap_or_else(|| keystore_path(&base_path, config.chain_spec.id()))
|
||||
.to_string_lossy()
|
||||
.into();
|
||||
|
||||
config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into();
|
||||
|
||||
config.pruning = match matches.value_of("pruning") {
|
||||
Some("archive") => PruningMode::ArchiveAll,
|
||||
None => PruningMode::default(),
|
||||
Some(s) => PruningMode::keep_blocks(s.parse()
|
||||
.map_err(|_| error::ErrorKind::Input("Invalid pruning mode specified".to_owned()))?),
|
||||
};
|
||||
|
||||
let role =
|
||||
if matches.is_present("light") {
|
||||
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
|
||||
service::Roles::LIGHT
|
||||
} else if matches.is_present("validator") || matches.is_present("dev") {
|
||||
config.execution_strategy = service::ExecutionStrategy::Both;
|
||||
service::Roles::AUTHORITY
|
||||
} else {
|
||||
config.execution_strategy = service::ExecutionStrategy::NativeWhenPossible;
|
||||
service::Roles::FULL
|
||||
};
|
||||
|
||||
if let Some(s) = matches.value_of("execution") {
|
||||
config.execution_strategy = match s {
|
||||
"both" => service::ExecutionStrategy::Both,
|
||||
"native" => service::ExecutionStrategy::NativeWhenPossible,
|
||||
"wasm" => service::ExecutionStrategy::AlwaysWasm,
|
||||
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
|
||||
};
|
||||
}
|
||||
|
||||
config.roles = role;
|
||||
{
|
||||
config.network.boot_nodes.extend(matches
|
||||
.values_of("bootnodes")
|
||||
.map_or(Default::default(), |v| v.map(|n| n.to_owned()).collect::<Vec<_>>()));
|
||||
config.network.config_path = Some(network_path(&base_path, config.chain_spec.id()).to_string_lossy().into());
|
||||
config.network.net_config_path = config.network.config_path.clone();
|
||||
config.network.reserved_nodes.extend(matches
|
||||
.values_of("reserved-nodes")
|
||||
.map_or(Default::default(), |v| v.map(|n| n.to_owned()).collect::<Vec<_>>()));
|
||||
if !config.network.reserved_nodes.is_empty() {
|
||||
config.network.non_reserved_mode = NonReservedPeerMode::Deny;
|
||||
}
|
||||
|
||||
config.network.listen_addresses = Vec::new();
|
||||
for addr in matches.values_of("listen-addr").unwrap_or_default() {
|
||||
let addr = addr.parse().map_err(|_| "Invalid listen multiaddress")?;
|
||||
config.network.listen_addresses.push(addr);
|
||||
}
|
||||
if config.network.listen_addresses.is_empty() {
|
||||
let port = match matches.value_of("port") {
|
||||
Some(port) => port.parse().map_err(|_| "Invalid p2p port value specified.")?,
|
||||
None => 30333,
|
||||
};
|
||||
config.network.listen_addresses = vec![
|
||||
iter::once(AddrComponent::IP4(Ipv4Addr::new(0, 0, 0, 0)))
|
||||
.chain(iter::once(AddrComponent::TCP(port)))
|
||||
.collect()
|
||||
];
|
||||
}
|
||||
|
||||
config.network.public_addresses = Vec::new();
|
||||
|
||||
config.network.client_version = config.client_id();
|
||||
config.network.use_secret = match matches.value_of("node-key").map(|s| s.parse()) {
|
||||
Some(Ok(secret)) => Some(secret),
|
||||
Some(Err(err)) => return Err(format!("Error parsing node key: {}", err).into()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let min_peers = match matches.value_of("min-peers") {
|
||||
Some(min_peers) => min_peers.parse().map_err(|_| "Invalid min-peers value specified.")?,
|
||||
None => 25,
|
||||
};
|
||||
let max_peers = match matches.value_of("max-peers") {
|
||||
Some(max_peers) => max_peers.parse().map_err(|_| "Invalid max-peers value specified.")?,
|
||||
None => 50,
|
||||
};
|
||||
if min_peers > max_peers {
|
||||
return Err(error::ErrorKind::Input("Min-peers mustn't be larger than max-peers.".to_owned()).into());
|
||||
}
|
||||
config.network.min_peers = min_peers;
|
||||
config.network.max_peers = max_peers;
|
||||
}
|
||||
|
||||
config.keys = matches.values_of("key").unwrap_or_default().map(str::to_owned).collect();
|
||||
if matches.is_present("dev") {
|
||||
config.keys.push("Alice".into());
|
||||
}
|
||||
|
||||
let rpc_interface: &str = if matches.is_present("rpc-external") { "0.0.0.0" } else { "127.0.0.1" };
|
||||
let ws_interface: &str = if matches.is_present("ws-external") { "0.0.0.0" } else { "127.0.0.1" };
|
||||
|
||||
config.rpc_http = Some(parse_address(&format!("{}:{}", rpc_interface, 9933), "rpc-port", &matches)?);
|
||||
config.rpc_ws = Some(parse_address(&format!("{}:{}", ws_interface, 9944), "ws-port", &matches)?);
|
||||
|
||||
// Override telemetry
|
||||
if matches.is_present("no-telemetry") {
|
||||
config.telemetry_url = None;
|
||||
} else if let Some(url) = matches.value_of("telemetry-url") {
|
||||
config.telemetry_url = Some(url.to_owned());
|
||||
}
|
||||
|
||||
Ok(Action::RunService((config, exit)))
|
||||
}
|
||||
|
||||
fn build_spec<F>(matches: &clap::ArgMatches, spec: ChainSpec<FactoryGenesis<F>>) -> error::Result<()>
|
||||
where F: ServiceFactory,
|
||||
{
|
||||
info!("Building chain spec");
|
||||
let raw = matches.is_present("raw");
|
||||
let json = service::chain_ops::build_spec::<FactoryGenesis<F>>(spec, raw)?;
|
||||
print!("{}", json);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn export_blocks<F, E>(matches: &clap::ArgMatches, spec: ChainSpec<FactoryGenesis<F>>, exit: E) -> error::Result<()>
|
||||
where F: ServiceFactory, E: Future<Item=(),Error=()> + Send + 'static,
|
||||
{
|
||||
let base_path = base_path(matches);
|
||||
let mut config = service::Configuration::default_with_spec(spec);
|
||||
config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into();
|
||||
info!("DB path: {}", config.database_path);
|
||||
let from: u64 = match matches.value_of("from") {
|
||||
Some(v) => v.parse().map_err(|_| "Invalid --from argument")?,
|
||||
None => 1,
|
||||
};
|
||||
|
||||
let to: Option<u64> = match matches.value_of("to") {
|
||||
Some(v) => Some(v.parse().map_err(|_| "Invalid --to argument")?),
|
||||
None => None,
|
||||
};
|
||||
let json = matches.is_present("json");
|
||||
|
||||
let file: Box<Write> = match matches.value_of("OUTPUT") {
|
||||
Some(filename) => Box::new(File::create(filename)?),
|
||||
None => Box::new(stdout()),
|
||||
};
|
||||
|
||||
Ok(service::chain_ops::export_blocks::<F, _, _>(config, exit, file, As::sa(from), to.map(As::sa), json)?)
|
||||
}
|
||||
|
||||
fn import_blocks<F, E>(matches: &clap::ArgMatches, spec: ChainSpec<FactoryGenesis<F>>, exit: E) -> error::Result<()>
|
||||
where F: ServiceFactory, E: Future<Item=(),Error=()> + Send + 'static,
|
||||
{
|
||||
let base_path = base_path(matches);
|
||||
let mut config = service::Configuration::default_with_spec(spec);
|
||||
config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into();
|
||||
|
||||
if let Some(s) = matches.value_of("execution") {
|
||||
config.execution_strategy = match s {
|
||||
"both" => service::ExecutionStrategy::Both,
|
||||
"native" => service::ExecutionStrategy::NativeWhenPossible,
|
||||
"wasm" => service::ExecutionStrategy::AlwaysWasm,
|
||||
_ => return Err(error::ErrorKind::Input("Invalid execution mode specified".to_owned()).into()),
|
||||
};
|
||||
}
|
||||
|
||||
let file: Box<Read> = match matches.value_of("INPUT") {
|
||||
Some(filename) => Box::new(File::open(filename)?),
|
||||
None => Box::new(stdin()),
|
||||
};
|
||||
|
||||
Ok(service::chain_ops::import_blocks::<F, _, _>(config, exit, file)?)
|
||||
}
|
||||
|
||||
fn revert_chain<F>(matches: &clap::ArgMatches, spec: ChainSpec<FactoryGenesis<F>>) -> error::Result<()>
|
||||
where F: ServiceFactory,
|
||||
{
|
||||
let base_path = base_path(matches);
|
||||
let mut config = service::Configuration::default_with_spec(spec);
|
||||
config.database_path = db_path(&base_path, config.chain_spec.id()).to_string_lossy().into();
|
||||
|
||||
let blocks = match matches.value_of("NUM") {
|
||||
Some(v) => v.parse().map_err(|_| "Invalid block count specified")?,
|
||||
None => 256,
|
||||
};
|
||||
|
||||
Ok(service::chain_ops::revert_chain::<F>(config, As::sa(blocks))?)
|
||||
}
|
||||
|
||||
fn purge_chain<F>(matches: &clap::ArgMatches, spec: ChainSpec<FactoryGenesis<F>>) -> error::Result<()>
|
||||
where F: ServiceFactory,
|
||||
{
|
||||
let base_path = base_path(matches);
|
||||
let database_path = db_path(&base_path, spec.id());
|
||||
|
||||
print!("Are you sure to remove {:?}? (y/n)", &database_path);
|
||||
stdout().flush().expect("failed to flush stdout");
|
||||
|
||||
let mut input = String::new();
|
||||
stdin().read_line(&mut input)?;
|
||||
let input = input.trim();
|
||||
|
||||
match input.chars().nth(0) {
|
||||
Some('y') | Some('Y') => {
|
||||
fs::remove_dir_all(&database_path)?;
|
||||
println!("{:?} removed.", &database_path);
|
||||
},
|
||||
_ => println!("Aborted"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_address(default: &str, port_param: &str, matches: &clap::ArgMatches) -> Result<SocketAddr, String> {
|
||||
let mut address: SocketAddr = default.parse().ok().ok_or_else(|| format!("Invalid address specified for --{}.", port_param))?;
|
||||
if let Some(port) = matches.value_of(port_param) {
|
||||
let port: u16 = port.parse().ok().ok_or_else(|| format!("Invalid port for --{} specified.", port_param))?;
|
||||
address.set_port(port);
|
||||
}
|
||||
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
fn keystore_path(base_path: &Path, chain_id: &str) -> PathBuf {
|
||||
let mut path = base_path.to_owned();
|
||||
path.push("chains");
|
||||
path.push(chain_id);
|
||||
path.push("keystore");
|
||||
path
|
||||
}
|
||||
|
||||
fn db_path(base_path: &Path, chain_id: &str) -> PathBuf {
|
||||
let mut path = base_path.to_owned();
|
||||
path.push("chains");
|
||||
path.push(chain_id);
|
||||
path.push("db");
|
||||
path
|
||||
}
|
||||
|
||||
fn network_path(base_path: &Path, chain_id: &str) -> PathBuf {
|
||||
let mut path = base_path.to_owned();
|
||||
path.push("chains");
|
||||
path.push(chain_id);
|
||||
path.push("network");
|
||||
path
|
||||
}
|
||||
|
||||
fn default_base_path() -> PathBuf {
|
||||
use app_dirs::{AppInfo, AppDataType};
|
||||
|
||||
let app_info = AppInfo {
|
||||
name: "Polkadot",
|
||||
author: "Parity Technologies",
|
||||
};
|
||||
|
||||
app_dirs::get_app_root(
|
||||
AppDataType::UserData,
|
||||
&app_info,
|
||||
).expect("app directories exist on all supported platforms; qed")
|
||||
}
|
||||
|
||||
fn init_logger(pattern: &str) {
|
||||
use ansi_term::Colour;
|
||||
|
||||
let mut builder = env_logger::LogBuilder::new();
|
||||
// Disable info logging by default for some modules:
|
||||
builder.filter(Some("ws"), log::LogLevelFilter::Warn);
|
||||
builder.filter(Some("hyper"), log::LogLevelFilter::Warn);
|
||||
// Enable info for others.
|
||||
builder.filter(None, log::LogLevelFilter::Info);
|
||||
|
||||
if let Ok(lvl) = std::env::var("RUST_LOG") {
|
||||
builder.parse(&lvl);
|
||||
}
|
||||
|
||||
builder.parse(pattern);
|
||||
let isatty = atty::is(atty::Stream::Stderr);
|
||||
let enable_color = isatty;
|
||||
|
||||
let format = move |record: &log::LogRecord| {
|
||||
let timestamp = time::strftime("%Y-%m-%d %H:%M:%S", &time::now()).expect("Error formatting log timestamp");
|
||||
|
||||
let mut output = if log::max_log_level() <= log::LogLevelFilter::Info {
|
||||
format!("{} {}", Colour::Black.bold().paint(timestamp), record.args())
|
||||
} else {
|
||||
let name = ::std::thread::current().name().map_or_else(Default::default, |x| format!("{}", Colour::Blue.bold().paint(x)));
|
||||
format!("{} {} {} {} {}", Colour::Black.bold().paint(timestamp), name, record.level(), record.target(), record.args())
|
||||
};
|
||||
|
||||
if !enable_color {
|
||||
output = kill_color(output.as_ref());
|
||||
}
|
||||
|
||||
if !isatty && record.level() <= log::LogLevel::Info && atty::is(atty::Stream::Stdout) {
|
||||
// duplicate INFO/WARN output to console
|
||||
println!("{}", output);
|
||||
}
|
||||
output
|
||||
};
|
||||
builder.format(format);
|
||||
|
||||
builder.init().expect("Logger initialized only once.");
|
||||
}
|
||||
|
||||
fn kill_color(s: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").expect("Error initializing color regex");
|
||||
}
|
||||
RE.replace_all(s, "").to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tests_node_name_good() {
|
||||
assert!(is_node_name_valid("short name").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tests_node_name_bad() {
|
||||
assert!(is_node_name_valid("long names are not very cool for the ui").is_err());
|
||||
assert!(is_node_name_valid("Dots.not.Ok").is_err());
|
||||
assert!(is_node_name_valid("http://visit.me").is_err());
|
||||
assert!(is_node_name_valid("https://visit.me").is_err());
|
||||
assert!(is_node_name_valid("www.visit.me").is_err());
|
||||
assert!(is_node_name_valid("email@domain").is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Custom panic hook with bug report link
|
||||
|
||||
use backtrace::Backtrace;
|
||||
use std::io::{self, Write};
|
||||
use std::panic::{self, PanicInfo};
|
||||
use std::thread;
|
||||
|
||||
/// Set the panic hook
|
||||
pub fn set() {
|
||||
panic::set_hook(Box::new(panic_hook));
|
||||
}
|
||||
|
||||
static ABOUT_PANIC: &str = "
|
||||
This is a bug. Please report it at:
|
||||
|
||||
https://github.com/paritytech/polkadot/issues/new
|
||||
";
|
||||
|
||||
fn panic_hook(info: &PanicInfo) {
|
||||
let location = info.location();
|
||||
let file = location.as_ref().map(|l| l.file()).unwrap_or("<unknown>");
|
||||
let line = location.as_ref().map(|l| l.line()).unwrap_or(0);
|
||||
|
||||
let msg = match info.payload().downcast_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match info.payload().downcast_ref::<String>() {
|
||||
Some(s) => &s[..],
|
||||
None => "Box<Any>",
|
||||
}
|
||||
};
|
||||
|
||||
let thread = thread::current();
|
||||
let name = thread.name().unwrap_or("<unnamed>");
|
||||
|
||||
let backtrace = Backtrace::new();
|
||||
|
||||
let mut stderr = io::stderr();
|
||||
|
||||
let _ = writeln!(stderr, "");
|
||||
let _ = writeln!(stderr, "====================");
|
||||
let _ = writeln!(stderr, "");
|
||||
let _ = writeln!(stderr, "{:?}", backtrace);
|
||||
let _ = writeln!(stderr, "");
|
||||
let _ = writeln!(
|
||||
stderr,
|
||||
"Thread '{}' panicked at '{}', {}:{}",
|
||||
name, msg, file, line
|
||||
);
|
||||
|
||||
let _ = writeln!(stderr, "{}", ABOUT_PANIC);
|
||||
::std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "substrate-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
error-chain = "0.12"
|
||||
fnv = "1.0"
|
||||
log = "0.3"
|
||||
parking_lot = "0.4"
|
||||
triehash = "0.2"
|
||||
hex-literal = "0.1"
|
||||
futures = "0.1.17"
|
||||
slog = "^2"
|
||||
heapsize = "0.4"
|
||||
substrate-bft = { path = "../bft" }
|
||||
parity-codec = { path = "../../codec" }
|
||||
substrate-executor = { path = "../executor" }
|
||||
substrate-primitives = { path = "../primitives" }
|
||||
sr-io = { path = "../sr-io" }
|
||||
substrate-metadata = { path = "../metadata" }
|
||||
sr-primitives = { path = "../sr-primitives" }
|
||||
substrate-state-machine = { path = "../state-machine" }
|
||||
substrate-keyring = { path = "../../core/keyring" }
|
||||
substrate-telemetry = { path = "../telemetry" }
|
||||
hashdb = "0.2.1"
|
||||
patricia-trie = "0.2.1"
|
||||
rlp = "0.2.4"
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-test-client = { path = "../test-client" }
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Client
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "substrate-client-db"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
parking_lot = "0.4"
|
||||
log = "0.3"
|
||||
kvdb = "0.1"
|
||||
kvdb-rocksdb = "0.1.3"
|
||||
hashdb = "0.2.1"
|
||||
memorydb = "0.2.1"
|
||||
substrate-primitives = { path = "../../../core/primitives" }
|
||||
sr-primitives = { path = "../../../core/sr-primitives" }
|
||||
substrate-client = { path = "../../../core/client" }
|
||||
substrate-state-machine = { path = "../../../core/state-machine" }
|
||||
parity-codec = { path = "../../../codec" }
|
||||
parity-codec-derive = { path = "../../../codec/derive" }
|
||||
substrate-executor = { path = "../../../core/executor" }
|
||||
substrate-state-db = { path = "../../../core/state-db" }
|
||||
|
||||
[dev-dependencies]
|
||||
kvdb-memorydb = "0.1"
|
||||
@@ -0,0 +1,432 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! DB-backed cache of blockchain data.
|
||||
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use kvdb::{KeyValueDB, DBTransaction};
|
||||
|
||||
use client::blockchain::Cache as BlockchainCache;
|
||||
use client::error::Result as ClientResult;
|
||||
use codec::{Codec, Encode, Decode};
|
||||
use primitives::AuthorityId;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{Block as BlockT, As, NumberFor};
|
||||
use utils::{COLUMN_META, BlockKey, db_err, meta_keys, read_id, db_key_to_number, number_to_db_key};
|
||||
|
||||
/// Database-backed cache of blockchain data.
|
||||
pub struct DbCache<Block: BlockT> {
|
||||
db: Arc<KeyValueDB>,
|
||||
block_index_column: Option<u32>,
|
||||
authorities_at: DbCacheList<Block, Vec<AuthorityId>>,
|
||||
}
|
||||
|
||||
impl<Block> DbCache<Block>
|
||||
where
|
||||
Block: BlockT,
|
||||
NumberFor<Block>: As<u64>,
|
||||
{
|
||||
/// Create new cache.
|
||||
pub fn new(
|
||||
db: Arc<KeyValueDB>,
|
||||
block_index_column: Option<u32>,
|
||||
authorities_column: Option<u32>
|
||||
) -> ClientResult<Self> {
|
||||
Ok(DbCache {
|
||||
db: db.clone(),
|
||||
block_index_column,
|
||||
authorities_at: DbCacheList::new(db, meta_keys::BEST_AUTHORITIES, authorities_column)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get authorities_cache.
|
||||
pub fn authorities_at_cache(&self) -> &DbCacheList<Block, Vec<AuthorityId>> {
|
||||
&self.authorities_at
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block> BlockchainCache<Block> for DbCache<Block>
|
||||
where
|
||||
Block: BlockT,
|
||||
NumberFor<Block>: As<u64>,
|
||||
{
|
||||
fn authorities_at(&self, at: BlockId<Block>) -> Option<Vec<AuthorityId>> {
|
||||
let authorities_at = read_id(&*self.db, self.block_index_column, at).and_then(|at| match at {
|
||||
Some(at) => self.authorities_at.value_at_key(at),
|
||||
None => Ok(None),
|
||||
});
|
||||
|
||||
match authorities_at {
|
||||
Ok(authorities) => authorities,
|
||||
Err(error) => {
|
||||
warn!("Trying to read authorities from db cache has failed with: {}", error);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Database-backed blockchain cache which holds its entries as a list.
|
||||
/// The meta column holds the pointer to the best known cache entry and
|
||||
/// every entry points to the previous entry.
|
||||
/// New entry appears when the set of authorities changes in block, so the
|
||||
/// best entry here means the entry that is valid for the best block (and
|
||||
/// probably for its ascendants).
|
||||
pub struct DbCacheList<Block: BlockT, T: Clone> {
|
||||
db: Arc<KeyValueDB>,
|
||||
meta_key: &'static [u8],
|
||||
column: Option<u32>,
|
||||
/// Best entry at the moment. None means that cache has no entries at all.
|
||||
best_entry: RwLock<Option<Entry<NumberFor<Block>, T>>>,
|
||||
}
|
||||
|
||||
/// Single cache entry.
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct Entry<N, T: Clone> {
|
||||
/// first block, when this value became actual
|
||||
valid_from: N,
|
||||
/// None means that we do not know the value starting from `valid_from` block
|
||||
value: Option<T>,
|
||||
}
|
||||
|
||||
/// Internal representation of the single cache entry. The entry points to the
|
||||
/// previous entry in the cache, allowing us to traverse back in time in list-style.
|
||||
#[derive(Encode, Decode)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
struct StorageEntry<N, T> {
|
||||
/// None if valid from the beginning
|
||||
prev_valid_from: Option<N>,
|
||||
/// None means that we do not know the value starting from `valid_from` block
|
||||
value: Option<T>,
|
||||
}
|
||||
|
||||
impl<Block, T> DbCacheList<Block, T>
|
||||
where
|
||||
Block: BlockT,
|
||||
NumberFor<Block>: As<u64>,
|
||||
T: Clone + PartialEq + Codec,
|
||||
{
|
||||
/// Creates new cache list.
|
||||
fn new(db: Arc<KeyValueDB>, meta_key: &'static [u8], column: Option<u32>) -> ClientResult<Self> {
|
||||
let best_entry = RwLock::new(db.get(COLUMN_META, meta_key)
|
||||
.map_err(db_err)
|
||||
.and_then(|block| match block {
|
||||
Some(block) => {
|
||||
let valid_from = db_key_to_number(&block)?;
|
||||
read_storage_entry::<Block, T>(&*db, column, valid_from)
|
||||
.map(|entry| Some(Entry {
|
||||
valid_from,
|
||||
value: entry
|
||||
.expect("meta entry references the entry at the block; storage entry at block exists when referenced; qed")
|
||||
.value,
|
||||
}))
|
||||
},
|
||||
None => Ok(None),
|
||||
})?);
|
||||
|
||||
Ok(DbCacheList {
|
||||
db,
|
||||
column,
|
||||
meta_key,
|
||||
best_entry,
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the best known entry.
|
||||
pub fn best_entry(&self) -> Option<Entry<NumberFor<Block>, T>> {
|
||||
self.best_entry.read().clone()
|
||||
}
|
||||
|
||||
/// Commits the new best pending value to the database. Returns Some if best entry must
|
||||
/// be updated after transaction is committed.
|
||||
pub fn commit_best_entry(
|
||||
&self,
|
||||
transaction: &mut DBTransaction,
|
||||
valid_from: NumberFor<Block>,
|
||||
pending_value: Option<T>
|
||||
) -> Option<Entry<NumberFor<Block>, T>> {
|
||||
let best_entry = self.best_entry();
|
||||
let update_best_entry = match (
|
||||
best_entry.as_ref().and_then(|a| a.value.as_ref()),
|
||||
pending_value.as_ref()
|
||||
) {
|
||||
(Some(best_value), Some(pending_value)) => best_value != pending_value,
|
||||
(None, Some(_)) | (Some(_), None) => true,
|
||||
(None, None) => false,
|
||||
};
|
||||
if !update_best_entry {
|
||||
return None;
|
||||
}
|
||||
|
||||
let valid_from_key = number_to_db_key(valid_from);
|
||||
transaction.put(COLUMN_META, self.meta_key, &valid_from_key);
|
||||
transaction.put(self.column, &valid_from_key, &StorageEntry {
|
||||
prev_valid_from: best_entry.map(|b| b.valid_from),
|
||||
value: pending_value.clone(),
|
||||
}.encode());
|
||||
|
||||
Some(Entry {
|
||||
valid_from,
|
||||
value: pending_value,
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates the best in-memory cache entry. Must be called after transaction with changes
|
||||
/// from commit_best_entry has been committed.
|
||||
pub fn update_best_entry(&self, best_entry: Option<Entry<NumberFor<Block>, T>>) {
|
||||
*self.best_entry.write() = best_entry;
|
||||
}
|
||||
|
||||
/// Prune all entries from the beginning up to the block (including entry at the number). Returns
|
||||
/// the number of pruned entries. Pruning never deletes the latest entry in the cache.
|
||||
pub fn prune_entries(
|
||||
&self,
|
||||
transaction: &mut DBTransaction,
|
||||
last_to_prune: NumberFor<Block>
|
||||
) -> ClientResult<usize> {
|
||||
// find the last entry we want to keep
|
||||
let mut last_entry_to_keep = match self.best_entry() {
|
||||
Some(best_entry) => best_entry.valid_from,
|
||||
None => return Ok(0),
|
||||
};
|
||||
let mut first_entry_to_remove = last_entry_to_keep;
|
||||
while first_entry_to_remove > last_to_prune {
|
||||
last_entry_to_keep = first_entry_to_remove;
|
||||
|
||||
let entry = read_storage_entry::<Block, T>(&*self.db, self.column, first_entry_to_remove)?
|
||||
.expect("entry referenced from the next entry; entry exists when referenced; qed");
|
||||
// if we have reached the first list entry
|
||||
// AND all list entries are for blocks that are later than last_to_prune
|
||||
// => nothing to prune
|
||||
first_entry_to_remove = match entry.prev_valid_from {
|
||||
Some(prev_valid_from) => prev_valid_from,
|
||||
None => return Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
// remove all entries, starting from entry_to_remove
|
||||
let mut pruned = 0;
|
||||
let mut entry_to_remove = Some(first_entry_to_remove);
|
||||
while let Some(current_entry) = entry_to_remove {
|
||||
let entry = read_storage_entry::<Block, T>(&*self.db, self.column, current_entry)?
|
||||
.expect("referenced entry exists; entry_to_remove is a reference to the entry; qed");
|
||||
|
||||
if current_entry != last_entry_to_keep {
|
||||
transaction.delete(self.column, &number_to_db_key(current_entry));
|
||||
pruned += 1;
|
||||
}
|
||||
entry_to_remove = entry.prev_valid_from;
|
||||
}
|
||||
|
||||
let mut entry = read_storage_entry::<Block, T>(&*self.db, self.column, last_entry_to_keep)?
|
||||
.expect("last_entry_to_keep >= first_entry_to_remove; that means that we're leaving this entry in the db; qed");
|
||||
entry.prev_valid_from = None;
|
||||
transaction.put(self.column, &number_to_db_key(last_entry_to_keep), &entry.encode());
|
||||
|
||||
Ok(pruned)
|
||||
}
|
||||
|
||||
/// Reads the cached value, actual at given block. Returns None if the value was not cached
|
||||
/// or if it has been pruned.
|
||||
fn value_at_key(&self, key: BlockKey) -> ClientResult<Option<T>> {
|
||||
let at = db_key_to_number::<NumberFor<Block>>(&key)?;
|
||||
let best_valid_from = match self.best_entry() {
|
||||
// there are entries in cache
|
||||
Some(best_entry) => {
|
||||
// we're looking for the best value
|
||||
if at >= best_entry.valid_from {
|
||||
return Ok(best_entry.value);
|
||||
}
|
||||
|
||||
// we're looking for the value of older blocks
|
||||
best_entry.valid_from
|
||||
},
|
||||
// there are no entries in the cache
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let mut entry = read_storage_entry::<Block, T>(&*self.db, self.column, best_valid_from)?
|
||||
.expect("self.best_entry().is_some() if there's entry for best_valid_from; qed");
|
||||
loop {
|
||||
let prev_valid_from = match entry.prev_valid_from {
|
||||
Some(prev_valid_from) => prev_valid_from,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let prev_entry = read_storage_entry::<Block, T>(&*self.db, self.column, prev_valid_from)?
|
||||
.expect("entry referenced from the next entry; entry exists when referenced; qed");
|
||||
if at >= prev_valid_from {
|
||||
return Ok(prev_entry.value);
|
||||
}
|
||||
|
||||
entry = prev_entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the entry at the block with given number.
|
||||
fn read_storage_entry<Block, T>(
|
||||
db: &KeyValueDB,
|
||||
column: Option<u32>,
|
||||
number: NumberFor<Block>
|
||||
) -> ClientResult<Option<StorageEntry<NumberFor<Block>, T>>>
|
||||
where
|
||||
Block: BlockT,
|
||||
NumberFor<Block>: As<u64>,
|
||||
T: Codec,
|
||||
{
|
||||
db.get(column, &number_to_db_key(number))
|
||||
.and_then(|entry| match entry {
|
||||
Some(entry) => Ok(StorageEntry::<NumberFor<Block>, T>::decode(&mut &entry[..])),
|
||||
None => Ok(None),
|
||||
})
|
||||
.map_err(db_err)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use runtime_primitives::testing::Block as RawBlock;
|
||||
use light::{AUTHORITIES_ENTRIES_TO_KEEP, columns, LightStorage};
|
||||
use light::tests::insert_block;
|
||||
use super::*;
|
||||
|
||||
type Block = RawBlock<u64>;
|
||||
|
||||
#[test]
|
||||
fn authorities_storage_entry_serialized() {
|
||||
let test_cases: Vec<StorageEntry<u64, Vec<AuthorityId>>> = vec![
|
||||
StorageEntry { prev_valid_from: Some(42), value: Some(vec![[1u8; 32].into()]) },
|
||||
StorageEntry { prev_valid_from: None, value: Some(vec![[1u8; 32].into(), [2u8; 32].into()]) },
|
||||
StorageEntry { prev_valid_from: None, value: None },
|
||||
];
|
||||
|
||||
for expected in test_cases {
|
||||
let serialized = expected.encode();
|
||||
let deserialized = StorageEntry::decode(&mut &serialized[..]).unwrap();
|
||||
assert_eq!(expected, deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn best_authorities_are_updated() {
|
||||
let db = LightStorage::new_test();
|
||||
let authorities_at: Vec<(usize, Option<Entry<u64, Vec<AuthorityId>>>)> = vec![
|
||||
(0, None),
|
||||
(0, None),
|
||||
(1, Some(Entry { valid_from: 1, value: Some(vec![[2u8; 32].into()]) })),
|
||||
(1, Some(Entry { valid_from: 1, value: Some(vec![[2u8; 32].into()]) })),
|
||||
(2, Some(Entry { valid_from: 3, value: Some(vec![[4u8; 32].into()]) })),
|
||||
(2, Some(Entry { valid_from: 3, value: Some(vec![[4u8; 32].into()]) })),
|
||||
(3, Some(Entry { valid_from: 5, value: None })),
|
||||
(3, Some(Entry { valid_from: 5, value: None })),
|
||||
];
|
||||
|
||||
// before any block, there are no entries in cache
|
||||
assert!(db.cache().authorities_at_cache().best_entry().is_none());
|
||||
assert_eq!(db.db().iter(columns::AUTHORITIES).count(), 0);
|
||||
|
||||
// insert blocks and check that best_authorities() returns correct result
|
||||
let mut prev_hash = Default::default();
|
||||
for number in 0..authorities_at.len() {
|
||||
let authorities_at_number = authorities_at[number].1.clone().and_then(|e| e.value);
|
||||
prev_hash = insert_block(&db, &prev_hash, number as u64, authorities_at_number);
|
||||
assert_eq!(db.cache().authorities_at_cache().best_entry(), authorities_at[number].1);
|
||||
assert_eq!(db.db().iter(columns::AUTHORITIES).count(), authorities_at[number].0);
|
||||
}
|
||||
|
||||
// check that authorities_at() returns correct results for all retrospective blocks
|
||||
for number in 1..authorities_at.len() + 1 {
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(number as u64)),
|
||||
authorities_at.get(number + 1)
|
||||
.or_else(|| authorities_at.last())
|
||||
.unwrap().1.clone().and_then(|e| e.value));
|
||||
}
|
||||
|
||||
// now check that cache entries are pruned when new blocks are inserted
|
||||
let mut current_entries_count = authorities_at.last().unwrap().0;
|
||||
let pruning_starts_at = AUTHORITIES_ENTRIES_TO_KEEP as usize;
|
||||
for number in authorities_at.len()..authorities_at.len() + pruning_starts_at {
|
||||
prev_hash = insert_block(&db, &prev_hash, number as u64, None);
|
||||
if number > pruning_starts_at {
|
||||
let prev_entries_count = authorities_at[number - pruning_starts_at].0;
|
||||
let entries_count = authorities_at.get(number - pruning_starts_at + 1).map(|e| e.0)
|
||||
.unwrap_or_else(|| authorities_at.last().unwrap().0);
|
||||
current_entries_count -= entries_count - prev_entries_count;
|
||||
}
|
||||
|
||||
// there's always at least 1 entry in the cache (after first insertion)
|
||||
assert_eq!(db.db().iter(columns::AUTHORITIES).count(), ::std::cmp::max(current_entries_count, 1));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn best_authorities_are_pruned() {
|
||||
let db = LightStorage::<Block>::new_test();
|
||||
let mut transaction = DBTransaction::new();
|
||||
|
||||
// insert first entry at block#100
|
||||
db.cache().authorities_at_cache().update_best_entry(
|
||||
db.cache().authorities_at_cache().commit_best_entry(&mut transaction, 100, Some(vec![[1u8; 32].into()])));
|
||||
db.db().write(transaction).unwrap();
|
||||
|
||||
// no entries are pruned, since there's only one entry in the cache
|
||||
let mut transaction = DBTransaction::new();
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 50).unwrap(), 0);
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 100).unwrap(), 0);
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 150).unwrap(), 0);
|
||||
|
||||
// insert second entry at block#200
|
||||
let mut transaction = DBTransaction::new();
|
||||
db.cache().authorities_at_cache().update_best_entry(
|
||||
db.cache().authorities_at_cache().commit_best_entry(&mut transaction, 200, Some(vec![[2u8; 32].into()])));
|
||||
db.db().write(transaction).unwrap();
|
||||
|
||||
let mut transaction = DBTransaction::new();
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 50).unwrap(), 0);
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 100).unwrap(), 1);
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 150).unwrap(), 1);
|
||||
// still only 1 entry is removed since pruning never deletes the last entry
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 200).unwrap(), 1);
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 250).unwrap(), 1);
|
||||
|
||||
// physically remove entry for block#100 from db
|
||||
let mut transaction = DBTransaction::new();
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 150).unwrap(), 1);
|
||||
db.db().write(transaction).unwrap();
|
||||
|
||||
assert_eq!(db.cache().authorities_at_cache().best_entry().unwrap().value, Some(vec![[2u8; 32].into()]));
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(50)), None);
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(100)), None);
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(150)), None);
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(200)), Some(vec![[2u8; 32].into()]));
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(250)), Some(vec![[2u8; 32].into()]));
|
||||
|
||||
// try to delete last entry => failure (no entries are removed)
|
||||
let mut transaction = DBTransaction::new();
|
||||
assert_eq!(db.cache().authorities_at_cache().prune_entries(&mut transaction, 300).unwrap(), 0);
|
||||
db.db().write(transaction).unwrap();
|
||||
|
||||
assert_eq!(db.cache().authorities_at_cache().best_entry().unwrap().value, Some(vec![[2u8; 32].into()]));
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(50)), None);
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(100)), None);
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(150)), None);
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(200)), Some(vec![[2u8; 32].into()]));
|
||||
assert_eq!(db.cache().authorities_at(BlockId::Number(250)), Some(vec![[2u8; 32].into()]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,714 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// tag::description[]
|
||||
//! Client backend that uses RocksDB database as storage.
|
||||
// end::description[]
|
||||
|
||||
extern crate substrate_client as client;
|
||||
extern crate kvdb_rocksdb;
|
||||
extern crate kvdb;
|
||||
extern crate hashdb;
|
||||
extern crate memorydb;
|
||||
extern crate parking_lot;
|
||||
extern crate substrate_state_machine as state_machine;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate sr_primitives as runtime_primitives;
|
||||
extern crate parity_codec as codec;
|
||||
extern crate substrate_executor as executor;
|
||||
extern crate substrate_state_db as state_db;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[macro_use]
|
||||
extern crate parity_codec_derive;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate kvdb_memorydb;
|
||||
|
||||
pub mod light;
|
||||
|
||||
mod cache;
|
||||
mod utils;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::path::PathBuf;
|
||||
use std::io;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use hashdb::Hasher;
|
||||
use kvdb::{KeyValueDB, DBTransaction};
|
||||
use memorydb::MemoryDB;
|
||||
use parking_lot::RwLock;
|
||||
use primitives::{H256, AuthorityId, Blake2Hasher, RlpCodec};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::bft::Justification;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, Hash, HashFor, NumberFor, Zero};
|
||||
use runtime_primitives::BuildStorage;
|
||||
use state_machine::backend::Backend as StateBackend;
|
||||
use executor::RuntimeInfo;
|
||||
use state_machine::{CodeExecutor, DBValue, ExecutionStrategy};
|
||||
use utils::{Meta, db_err, meta_keys, number_to_db_key, db_key_to_number, open_database,
|
||||
read_db, read_id, read_meta};
|
||||
use state_db::StateDb;
|
||||
pub use state_db::PruningMode;
|
||||
|
||||
const FINALIZATION_WINDOW: u64 = 32;
|
||||
|
||||
/// DB-backed patricia trie state, transaction type is an overlay of changes to commit.
|
||||
pub type DbState = state_machine::TrieBackend<Blake2Hasher, RlpCodec>;
|
||||
|
||||
/// Database settings.
|
||||
pub struct DatabaseSettings {
|
||||
/// Cache size in bytes. If `None` default is used.
|
||||
pub cache_size: Option<usize>,
|
||||
/// Path to the database.
|
||||
pub path: PathBuf,
|
||||
/// Pruning mode.
|
||||
pub pruning: PruningMode,
|
||||
}
|
||||
|
||||
/// Create an instance of db-backed client.
|
||||
pub fn new_client<E, S, Block>(
|
||||
settings: DatabaseSettings,
|
||||
executor: E,
|
||||
genesis_storage: S,
|
||||
execution_strategy: ExecutionStrategy,
|
||||
) -> Result<client::Client<Backend<Block>, client::LocalCallExecutor<Backend<Block>, E>, Block>, client::error::Error>
|
||||
where
|
||||
Block: BlockT,
|
||||
E: CodeExecutor<Blake2Hasher> + RuntimeInfo,
|
||||
S: BuildStorage,
|
||||
{
|
||||
let backend = Arc::new(Backend::new(settings, FINALIZATION_WINDOW)?);
|
||||
let executor = client::LocalCallExecutor::new(backend.clone(), executor);
|
||||
Ok(client::Client::new(backend, executor, genesis_storage, execution_strategy)?)
|
||||
}
|
||||
|
||||
mod columns {
|
||||
pub const META: Option<u32> = Some(0);
|
||||
pub const STATE: Option<u32> = Some(1);
|
||||
pub const STATE_META: Option<u32> = Some(2);
|
||||
pub const BLOCK_INDEX: Option<u32> = Some(3);
|
||||
pub const HEADER: Option<u32> = Some(4);
|
||||
pub const BODY: Option<u32> = Some(5);
|
||||
pub const JUSTIFICATION: Option<u32> = Some(6);
|
||||
}
|
||||
|
||||
struct PendingBlock<Block: BlockT> {
|
||||
header: Block::Header,
|
||||
justification: Option<Justification<Block::Hash>>,
|
||||
body: Option<Vec<Block::Extrinsic>>,
|
||||
is_best: bool,
|
||||
}
|
||||
|
||||
// wrapper that implements trait required for state_db
|
||||
struct StateMetaDb<'a>(&'a KeyValueDB);
|
||||
|
||||
impl<'a> state_db::MetaDb for StateMetaDb<'a> {
|
||||
type Error = io::Error;
|
||||
|
||||
fn get_meta(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
|
||||
self.0.get(columns::STATE_META, key).map(|r| r.map(|v| v.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Block database
|
||||
pub struct BlockchainDb<Block: BlockT> {
|
||||
db: Arc<KeyValueDB>,
|
||||
meta: RwLock<Meta<<Block::Header as HeaderT>::Number, Block::Hash>>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> BlockchainDb<Block> {
|
||||
fn new(db: Arc<KeyValueDB>) -> Result<Self, client::error::Error> {
|
||||
let meta = read_meta::<Block>(&*db, columns::HEADER)?;
|
||||
Ok(BlockchainDb {
|
||||
db,
|
||||
meta: RwLock::new(meta)
|
||||
})
|
||||
}
|
||||
|
||||
fn update_meta(&self, hash: Block::Hash, number: <Block::Header as HeaderT>::Number, is_best: bool) {
|
||||
if is_best {
|
||||
let mut meta = self.meta.write();
|
||||
if number == Zero::zero() {
|
||||
meta.genesis_hash = hash;
|
||||
}
|
||||
meta.best_number = number;
|
||||
meta.best_hash = hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> client::blockchain::HeaderBackend<Block> for BlockchainDb<Block> {
|
||||
fn header(&self, id: BlockId<Block>) -> Result<Option<Block::Header>, client::error::Error> {
|
||||
match read_db(&*self.db, columns::BLOCK_INDEX, columns::HEADER, id)? {
|
||||
Some(header) => match Block::Header::decode(&mut &header[..]) {
|
||||
Some(header) => Ok(Some(header)),
|
||||
None => return Err(client::error::ErrorKind::Backend("Error decoding header".into()).into()),
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn info(&self) -> Result<client::blockchain::Info<Block>, client::error::Error> {
|
||||
let meta = self.meta.read();
|
||||
Ok(client::blockchain::Info {
|
||||
best_hash: meta.best_hash,
|
||||
best_number: meta.best_number,
|
||||
genesis_hash: meta.genesis_hash,
|
||||
})
|
||||
}
|
||||
|
||||
fn status(&self, id: BlockId<Block>) -> Result<client::blockchain::BlockStatus, client::error::Error> {
|
||||
let exists = match id {
|
||||
BlockId::Hash(_) => read_id(&*self.db, columns::BLOCK_INDEX, id)?.is_some(),
|
||||
BlockId::Number(n) => n <= self.meta.read().best_number,
|
||||
};
|
||||
match exists {
|
||||
true => Ok(client::blockchain::BlockStatus::InChain),
|
||||
false => Ok(client::blockchain::BlockStatus::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn number(&self, hash: Block::Hash) -> Result<Option<<Block::Header as HeaderT>::Number>, client::error::Error> {
|
||||
read_id::<Block>(&*self.db, columns::BLOCK_INDEX, BlockId::Hash(hash))
|
||||
.and_then(|key| match key {
|
||||
Some(key) => Ok(Some(db_key_to_number(&key)?)),
|
||||
None => Ok(None),
|
||||
})
|
||||
}
|
||||
|
||||
fn hash(&self, number: <Block::Header as HeaderT>::Number) -> Result<Option<Block::Hash>, client::error::Error> {
|
||||
read_db::<Block>(&*self.db, columns::BLOCK_INDEX, columns::HEADER, BlockId::Number(number)).map(|x|
|
||||
x.map(|raw| HashFor::<Block>::hash(&raw[..])).map(Into::into)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> client::blockchain::Backend<Block> for BlockchainDb<Block> {
|
||||
fn body(&self, id: BlockId<Block>) -> Result<Option<Vec<Block::Extrinsic>>, client::error::Error> {
|
||||
match read_db(&*self.db, columns::BLOCK_INDEX, columns::BODY, id)? {
|
||||
Some(body) => match Decode::decode(&mut &body[..]) {
|
||||
Some(body) => Ok(Some(body)),
|
||||
None => return Err(client::error::ErrorKind::Backend("Error decoding body".into()).into()),
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn justification(&self, id: BlockId<Block>) -> Result<Option<Justification<Block::Hash>>, client::error::Error> {
|
||||
match read_db(&*self.db, columns::BLOCK_INDEX, columns::JUSTIFICATION, id)? {
|
||||
Some(justification) => match Decode::decode(&mut &justification[..]) {
|
||||
Some(justification) => Ok(Some(justification)),
|
||||
None => return Err(client::error::ErrorKind::Backend("Error decoding justification".into()).into()),
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn cache(&self) -> Option<&client::blockchain::Cache<Block>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Database transaction
|
||||
pub struct BlockImportOperation<Block: BlockT, H: Hasher> {
|
||||
old_state: DbState,
|
||||
updates: MemoryDB<H>,
|
||||
pending_block: Option<PendingBlock<Block>>,
|
||||
}
|
||||
|
||||
impl<Block> client::backend::BlockImportOperation<Block, Blake2Hasher, RlpCodec>
|
||||
for BlockImportOperation<Block, Blake2Hasher>
|
||||
where Block: BlockT,
|
||||
{
|
||||
type State = DbState;
|
||||
|
||||
fn state(&self) -> Result<Option<&Self::State>, client::error::Error> {
|
||||
Ok(Some(&self.old_state))
|
||||
}
|
||||
|
||||
fn set_block_data(&mut self, header: Block::Header, body: Option<Vec<Block::Extrinsic>>, justification: Option<Justification<Block::Hash>>, is_best: bool) -> Result<(), client::error::Error> {
|
||||
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
|
||||
self.pending_block = Some(PendingBlock {
|
||||
header,
|
||||
body,
|
||||
justification,
|
||||
is_best,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_authorities(&mut self, _authorities: Vec<AuthorityId>) {
|
||||
// currently authorities are not cached on full nodes
|
||||
}
|
||||
|
||||
fn update_storage(&mut self, update: MemoryDB<Blake2Hasher>) -> Result<(), client::error::Error> {
|
||||
self.updates = update;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> Result<(), client::error::Error> {
|
||||
// TODO: wipe out existing trie.
|
||||
let (_, update) = self.old_state.storage_root(iter.into_iter().map(|(k, v)| (k, Some(v))));
|
||||
self.updates = update;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct StorageDb<Block: BlockT> {
|
||||
pub db: Arc<KeyValueDB>,
|
||||
pub state_db: StateDb<Block::Hash, H256>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> state_machine::Storage<Blake2Hasher> for StorageDb<Block> {
|
||||
fn get(&self, key: &H256) -> Result<Option<DBValue>, String> {
|
||||
self.state_db.get(&key.0.into(), self).map(|r| r.map(|v| DBValue::from_slice(&v)))
|
||||
.map_err(|e| format!("Database backend error: {:?}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> state_db::HashDb for StorageDb<Block> {
|
||||
type Error = io::Error;
|
||||
type Hash = H256;
|
||||
|
||||
fn get(&self, key: &H256) -> Result<Option<Vec<u8>>, Self::Error> {
|
||||
self.db.get(columns::STATE, &key[..]).map(|r| r.map(|v| v.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Disk backend. Keeps data in a key-value store. In archive mode, trie nodes are kept from all blocks.
|
||||
/// Otherwise, trie nodes are kept only from the most recent block.
|
||||
pub struct Backend<Block: BlockT> {
|
||||
storage: Arc<StorageDb<Block>>,
|
||||
blockchain: BlockchainDb<Block>,
|
||||
finalization_window: u64,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> Backend<Block> {
|
||||
/// Create a new instance of database backend.
|
||||
pub fn new(config: DatabaseSettings, finalization_window: u64) -> Result<Self, client::error::Error> {
|
||||
let db = open_database(&config, "full")?;
|
||||
|
||||
Backend::from_kvdb(db as Arc<_>, config.pruning, finalization_window)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn new_test(keep_blocks: u32) -> Self {
|
||||
use utils::NUM_COLUMNS;
|
||||
|
||||
let db = Arc::new(::kvdb_memorydb::create(NUM_COLUMNS));
|
||||
|
||||
Backend::from_kvdb(db as Arc<_>, PruningMode::keep_blocks(keep_blocks), 0).expect("failed to create test-db")
|
||||
}
|
||||
|
||||
fn from_kvdb(db: Arc<KeyValueDB>, pruning: PruningMode, finalization_window: u64) -> Result<Self, client::error::Error> {
|
||||
let blockchain = BlockchainDb::new(db.clone())?;
|
||||
let map_e = |e: state_db::Error<io::Error>| ::client::error::Error::from(format!("State database error: {:?}", e));
|
||||
let state_db: StateDb<Block::Hash, H256> = StateDb::new(pruning, &StateMetaDb(&*db)).map_err(map_e)?;
|
||||
let storage_db = StorageDb {
|
||||
db,
|
||||
state_db,
|
||||
};
|
||||
|
||||
Ok(Backend {
|
||||
storage: Arc::new(storage_db),
|
||||
blockchain,
|
||||
finalization_window,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_state_commit(transaction: &mut DBTransaction, commit: state_db::CommitSet<H256>) {
|
||||
for (key, val) in commit.data.inserted.into_iter() {
|
||||
transaction.put(columns::STATE, &key[..], &val);
|
||||
}
|
||||
for key in commit.data.deleted.into_iter() {
|
||||
transaction.delete(columns::STATE, &key[..]);
|
||||
}
|
||||
for (key, val) in commit.meta.inserted.into_iter() {
|
||||
transaction.put(columns::STATE_META, &key[..], &val);
|
||||
}
|
||||
for key in commit.meta.deleted.into_iter() {
|
||||
transaction.delete(columns::STATE_META, &key[..]);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block> client::backend::Backend<Block, Blake2Hasher, RlpCodec> for Backend<Block> where Block: BlockT {
|
||||
type BlockImportOperation = BlockImportOperation<Block, Blake2Hasher>;
|
||||
type Blockchain = BlockchainDb<Block>;
|
||||
type State = DbState;
|
||||
|
||||
fn begin_operation(&self, block: BlockId<Block>) -> Result<Self::BlockImportOperation, client::error::Error> {
|
||||
let state = self.state_at(block)?;
|
||||
Ok(BlockImportOperation {
|
||||
pending_block: None,
|
||||
old_state: state,
|
||||
updates: MemoryDB::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn commit_operation(&self, mut operation: Self::BlockImportOperation) -> Result<(), client::error::Error> {
|
||||
use client::blockchain::HeaderBackend;
|
||||
let mut transaction = DBTransaction::new();
|
||||
if let Some(pending_block) = operation.pending_block {
|
||||
let hash = pending_block.header.hash();
|
||||
let number = pending_block.header.number().clone();
|
||||
let key = number_to_db_key(number.clone());
|
||||
transaction.put(columns::HEADER, &key, &pending_block.header.encode());
|
||||
if let Some(body) = pending_block.body {
|
||||
transaction.put(columns::BODY, &key, &body.encode());
|
||||
}
|
||||
if let Some(justification) = pending_block.justification {
|
||||
transaction.put(columns::JUSTIFICATION, &key, &justification.encode());
|
||||
}
|
||||
transaction.put(columns::BLOCK_INDEX, hash.as_ref(), &key);
|
||||
if pending_block.is_best {
|
||||
transaction.put(columns::META, meta_keys::BEST_BLOCK, &key);
|
||||
}
|
||||
let mut changeset: state_db::ChangeSet<H256> = state_db::ChangeSet::default();
|
||||
for (key, (val, rc)) in operation.updates.drain() {
|
||||
if rc > 0 {
|
||||
changeset.inserted.push((key.0.into(), val.to_vec()));
|
||||
} else if rc < 0 {
|
||||
changeset.deleted.push(key.0.into());
|
||||
}
|
||||
}
|
||||
let number_u64 = number.as_().into();
|
||||
let commit = self.storage.state_db.insert_block(&hash, number_u64, &pending_block.header.parent_hash(), changeset);
|
||||
apply_state_commit(&mut transaction, commit);
|
||||
|
||||
//finalize an older block
|
||||
if number_u64 > self.finalization_window {
|
||||
let finalizing_hash = if self.finalization_window == 0 {
|
||||
Some(hash)
|
||||
} else {
|
||||
let finalizing = number_u64 - self.finalization_window;
|
||||
if finalizing > self.storage.state_db.best_finalized() {
|
||||
self.blockchain.hash(As::sa(finalizing))?
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(finalizing_hash) = finalizing_hash {
|
||||
trace!(target: "db", "Finalizing block #{} ({:?})", number_u64 - self.finalization_window, finalizing_hash);
|
||||
let commit = self.storage.state_db.finalize_block(&finalizing_hash);
|
||||
apply_state_commit(&mut transaction, commit);
|
||||
}
|
||||
}
|
||||
|
||||
debug!(target: "db", "DB Commit {:?} ({}), best = {}", hash, number, pending_block.is_best);
|
||||
self.storage.db.write(transaction).map_err(db_err)?;
|
||||
self.blockchain.update_meta(hash, number, pending_block.is_best);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn revert(&self, n: NumberFor<Block>) -> Result<NumberFor<Block>, client::error::Error> {
|
||||
use client::blockchain::HeaderBackend;
|
||||
let mut best = self.blockchain.info()?.best_number;
|
||||
for c in 0 .. n.as_() {
|
||||
if best == As::sa(0) {
|
||||
return Ok(As::sa(c))
|
||||
}
|
||||
let mut transaction = DBTransaction::new();
|
||||
match self.storage.state_db.revert_one() {
|
||||
Some(commit) => {
|
||||
apply_state_commit(&mut transaction, commit);
|
||||
let removed = self.blockchain.hash(best)?.ok_or_else(
|
||||
|| client::error::ErrorKind::UnknownBlock(
|
||||
format!("Error reverting to {}. Block hash not found.", best)))?;
|
||||
best -= As::sa(1);
|
||||
let key = number_to_db_key(best.clone());
|
||||
let hash = self.blockchain.hash(best)?.ok_or_else(
|
||||
|| client::error::ErrorKind::UnknownBlock(
|
||||
format!("Error reverting to {}. Block hash not found.", best)))?;
|
||||
transaction.put(columns::META, meta_keys::BEST_BLOCK, &key);
|
||||
transaction.delete(columns::BLOCK_INDEX, removed.as_ref());
|
||||
self.storage.db.write(transaction).map_err(db_err)?;
|
||||
self.blockchain.update_meta(hash, best, true);
|
||||
}
|
||||
None => return Ok(As::sa(c))
|
||||
}
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn blockchain(&self) -> &BlockchainDb<Block> {
|
||||
&self.blockchain
|
||||
}
|
||||
|
||||
fn state_at(&self, block: BlockId<Block>) -> Result<Self::State, client::error::Error> {
|
||||
use client::blockchain::HeaderBackend as BcHeaderBackend;
|
||||
|
||||
// special case for genesis initialization
|
||||
match block {
|
||||
BlockId::Hash(h) if h == Default::default() =>
|
||||
return Ok(DbState::with_storage_for_genesis(self.storage.clone())),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.blockchain.header(block) {
|
||||
Ok(Some(ref hdr)) if !self.storage.state_db.is_pruned(hdr.number().as_()) => {
|
||||
let root = H256::from_slice(hdr.state_root().as_ref());
|
||||
Ok(DbState::with_storage(self.storage.clone(), root))
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
_ => Err(client::error::ErrorKind::UnknownBlock(format!("{:?}", block)).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block> client::backend::LocalBackend<Block, Blake2Hasher, RlpCodec> for Backend<Block>
|
||||
where Block: BlockT {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hashdb::HashDB;
|
||||
use super::*;
|
||||
use client::backend::Backend as BTrait;
|
||||
use client::backend::BlockImportOperation as Op;
|
||||
use client::blockchain::HeaderBackend as BlockchainHeaderBackend;
|
||||
use runtime_primitives::testing::{Header, Block as RawBlock};
|
||||
|
||||
type Block = RawBlock<u64>;
|
||||
|
||||
#[test]
|
||||
fn block_hash_inserted_correctly() {
|
||||
let db = Backend::<Block>::new_test(1);
|
||||
for i in 0..10 {
|
||||
assert!(db.blockchain().hash(i).unwrap().is_none());
|
||||
|
||||
{
|
||||
let id = if i == 0 {
|
||||
BlockId::Hash(Default::default())
|
||||
} else {
|
||||
BlockId::Number(i - 1)
|
||||
};
|
||||
|
||||
let mut op = db.begin_operation(id).unwrap();
|
||||
let header = Header {
|
||||
number: i,
|
||||
parent_hash: if i == 0 {
|
||||
Default::default()
|
||||
} else {
|
||||
db.blockchain.hash(i - 1).unwrap().unwrap()
|
||||
},
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
};
|
||||
|
||||
op.set_block_data(
|
||||
header,
|
||||
Some(vec![]),
|
||||
None,
|
||||
true,
|
||||
).unwrap();
|
||||
db.commit_operation(op).unwrap();
|
||||
}
|
||||
|
||||
assert!(db.blockchain().hash(i).unwrap().is_some())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_state_data() {
|
||||
let db = Backend::<Block>::new_test(2);
|
||||
{
|
||||
let mut op = db.begin_operation(BlockId::Hash(Default::default())).unwrap();
|
||||
let mut header = Header {
|
||||
number: 0,
|
||||
parent_hash: Default::default(),
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
};
|
||||
|
||||
let storage = vec![
|
||||
(vec![1, 3, 5], vec![2, 4, 6]),
|
||||
(vec![1, 2, 3], vec![9, 9, 9]),
|
||||
];
|
||||
|
||||
header.state_root = op.old_state.storage_root(storage
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(x, y)| (x, Some(y)))
|
||||
).0.into();
|
||||
|
||||
op.reset_storage(storage.iter().cloned()).unwrap();
|
||||
op.set_block_data(
|
||||
header,
|
||||
Some(vec![]),
|
||||
None,
|
||||
true
|
||||
).unwrap();
|
||||
|
||||
db.commit_operation(op).unwrap();
|
||||
|
||||
let state = db.state_at(BlockId::Number(0)).unwrap();
|
||||
|
||||
assert_eq!(state.storage(&[1, 3, 5]).unwrap(), Some(vec![2, 4, 6]));
|
||||
assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9]));
|
||||
assert_eq!(state.storage(&[5, 5, 5]).unwrap(), None);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
let mut op = db.begin_operation(BlockId::Number(0)).unwrap();
|
||||
let mut header = Header {
|
||||
number: 1,
|
||||
parent_hash: Default::default(),
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
};
|
||||
|
||||
let storage = vec![
|
||||
(vec![1, 3, 5], None),
|
||||
(vec![5, 5, 5], Some(vec![4, 5, 6])),
|
||||
];
|
||||
|
||||
let (root, overlay) = op.old_state.storage_root(storage.iter().cloned());
|
||||
op.update_storage(overlay).unwrap();
|
||||
header.state_root = root.into();
|
||||
|
||||
op.set_block_data(
|
||||
header,
|
||||
Some(vec![]),
|
||||
None,
|
||||
true
|
||||
).unwrap();
|
||||
|
||||
db.commit_operation(op).unwrap();
|
||||
|
||||
let state = db.state_at(BlockId::Number(1)).unwrap();
|
||||
|
||||
assert_eq!(state.storage(&[1, 3, 5]).unwrap(), None);
|
||||
assert_eq!(state.storage(&[1, 2, 3]).unwrap(), Some(vec![9, 9, 9]));
|
||||
assert_eq!(state.storage(&[5, 5, 5]).unwrap(), Some(vec![4, 5, 6]));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_only_when_negative_rc() {
|
||||
let key;
|
||||
let backend = Backend::<Block>::new_test(0);
|
||||
|
||||
let hash = {
|
||||
let mut op = backend.begin_operation(BlockId::Hash(Default::default())).unwrap();
|
||||
let mut header = Header {
|
||||
number: 0,
|
||||
parent_hash: Default::default(),
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
};
|
||||
|
||||
let storage: Vec<(_, _)> = vec![];
|
||||
|
||||
header.state_root = op.old_state.storage_root(storage
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(x, y)| (x, Some(y)))
|
||||
).0.into();
|
||||
let hash = header.hash();
|
||||
|
||||
op.reset_storage(storage.iter().cloned()).unwrap();
|
||||
|
||||
key = op.updates.insert(b"hello");
|
||||
op.set_block_data(
|
||||
header,
|
||||
Some(vec![]),
|
||||
None,
|
||||
true
|
||||
).unwrap();
|
||||
|
||||
backend.commit_operation(op).unwrap();
|
||||
|
||||
assert_eq!(backend.storage.db.get(::columns::STATE, &key.0[..]).unwrap().unwrap(), &b"hello"[..]);
|
||||
hash
|
||||
};
|
||||
|
||||
let hash = {
|
||||
let mut op = backend.begin_operation(BlockId::Number(0)).unwrap();
|
||||
let mut header = Header {
|
||||
number: 1,
|
||||
parent_hash: hash,
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
};
|
||||
|
||||
let storage: Vec<(_, _)> = vec![];
|
||||
|
||||
header.state_root = op.old_state.storage_root(storage
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(x, y)| (x, Some(y)))
|
||||
).0.into();
|
||||
let hash = header.hash();
|
||||
|
||||
op.updates.insert(b"hello");
|
||||
op.updates.remove(&key);
|
||||
op.set_block_data(
|
||||
header,
|
||||
Some(vec![]),
|
||||
None,
|
||||
true
|
||||
).unwrap();
|
||||
|
||||
backend.commit_operation(op).unwrap();
|
||||
|
||||
assert_eq!(backend.storage.db.get(::columns::STATE, &key.0[..]).unwrap().unwrap(), &b"hello"[..]);
|
||||
hash
|
||||
};
|
||||
|
||||
{
|
||||
let mut op = backend.begin_operation(BlockId::Number(1)).unwrap();
|
||||
let mut header = Header {
|
||||
number: 2,
|
||||
parent_hash: hash,
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
};
|
||||
|
||||
let storage: Vec<(_, _)> = vec![];
|
||||
|
||||
header.state_root = op.old_state.storage_root(storage
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(x, y)| (x, Some(y)))
|
||||
).0.into();
|
||||
|
||||
op.updates.remove(&key);
|
||||
op.set_block_data(
|
||||
header,
|
||||
Some(vec![]),
|
||||
None,
|
||||
true
|
||||
).unwrap();
|
||||
|
||||
backend.commit_operation(op).unwrap();
|
||||
|
||||
assert!(backend.storage.db.get(::columns::STATE, &key.0[..]).unwrap().is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! RocksDB-based light client blockchain storage.
|
||||
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use kvdb::{KeyValueDB, DBTransaction};
|
||||
|
||||
use client::blockchain::{BlockStatus, Cache as BlockchainCache,
|
||||
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo};
|
||||
use client::cht;
|
||||
use client::error::{ErrorKind as ClientErrorKind, Result as ClientResult};
|
||||
use client::light::blockchain::Storage as LightBlockchainStorage;
|
||||
use codec::{Decode, Encode};
|
||||
use primitives::{AuthorityId, H256, Blake2Hasher};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash, HashFor,
|
||||
Zero, One, As, NumberFor};
|
||||
use cache::DbCache;
|
||||
use utils::{meta_keys, Meta, db_err, number_to_db_key, db_key_to_number, open_database,
|
||||
read_db, read_id, read_meta};
|
||||
use DatabaseSettings;
|
||||
|
||||
pub(crate) mod columns {
|
||||
pub const META: Option<u32> = ::utils::COLUMN_META;
|
||||
pub const BLOCK_INDEX: Option<u32> = Some(1);
|
||||
pub const HEADER: Option<u32> = Some(2);
|
||||
pub const AUTHORITIES: Option<u32> = Some(3);
|
||||
pub const CHT: Option<u32> = Some(4);
|
||||
}
|
||||
|
||||
/// Keep authorities for last 'AUTHORITIES_ENTRIES_TO_KEEP' blocks.
|
||||
pub(crate) const AUTHORITIES_ENTRIES_TO_KEEP: u64 = cht::SIZE;
|
||||
|
||||
/// Light blockchain storage. Stores most recent headers + CHTs for older headers.
|
||||
pub struct LightStorage<Block: BlockT> {
|
||||
db: Arc<KeyValueDB>,
|
||||
meta: RwLock<Meta<<<Block as BlockT>::Header as HeaderT>::Number, Block::Hash>>,
|
||||
cache: DbCache<Block>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
struct BestAuthorities<N> {
|
||||
/// first block, when this set became actual
|
||||
valid_from: N,
|
||||
/// None means that we do not know the set starting from `valid_from` block
|
||||
authorities: Option<Vec<AuthorityId>>,
|
||||
}
|
||||
|
||||
impl<Block> LightStorage<Block>
|
||||
where
|
||||
Block: BlockT,
|
||||
{
|
||||
/// Create new storage with given settings.
|
||||
pub fn new(config: DatabaseSettings) -> ClientResult<Self> {
|
||||
let db = open_database(&config, "light")?;
|
||||
|
||||
Self::from_kvdb(db as Arc<_>)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new_test() -> Self {
|
||||
use utils::NUM_COLUMNS;
|
||||
|
||||
let db = Arc::new(::kvdb_memorydb::create(NUM_COLUMNS));
|
||||
|
||||
Self::from_kvdb(db as Arc<_>).expect("failed to create test-db")
|
||||
}
|
||||
|
||||
fn from_kvdb(db: Arc<KeyValueDB>) -> ClientResult<Self> {
|
||||
let cache = DbCache::new(db.clone(), columns::BLOCK_INDEX, columns::AUTHORITIES)?;
|
||||
let meta = RwLock::new(read_meta::<Block>(&*db, columns::HEADER)?);
|
||||
|
||||
Ok(LightStorage {
|
||||
db,
|
||||
meta,
|
||||
cache,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn db(&self) -> &Arc<KeyValueDB> {
|
||||
&self.db
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn cache(&self) -> &DbCache<Block> {
|
||||
&self.cache
|
||||
}
|
||||
|
||||
fn update_meta(&self, hash: Block::Hash, number: <<Block as BlockT>::Header as HeaderT>::Number, is_best: bool) {
|
||||
if is_best {
|
||||
let mut meta = self.meta.write();
|
||||
if number == <<Block as BlockT>::Header as HeaderT>::Number::zero() {
|
||||
meta.genesis_hash = hash;
|
||||
}
|
||||
|
||||
meta.best_number = number;
|
||||
meta.best_hash = hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block> BlockchainHeaderBackend<Block> for LightStorage<Block>
|
||||
where
|
||||
Block: BlockT,
|
||||
{
|
||||
fn header(&self, id: BlockId<Block>) -> ClientResult<Option<Block::Header>> {
|
||||
match read_db(&*self.db, columns::BLOCK_INDEX, columns::HEADER, id)? {
|
||||
Some(header) => match Block::Header::decode(&mut &header[..]) {
|
||||
Some(header) => Ok(Some(header)),
|
||||
None => return Err(ClientErrorKind::Backend("Error decoding header".into()).into()),
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn info(&self) -> ClientResult<BlockchainInfo<Block>> {
|
||||
let meta = self.meta.read();
|
||||
Ok(BlockchainInfo {
|
||||
best_hash: meta.best_hash,
|
||||
best_number: meta.best_number,
|
||||
genesis_hash: meta.genesis_hash,
|
||||
})
|
||||
}
|
||||
|
||||
fn status(&self, id: BlockId<Block>) -> ClientResult<BlockStatus> {
|
||||
let exists = match id {
|
||||
BlockId::Hash(_) => read_id(&*self.db, columns::BLOCK_INDEX, id)?.is_some(),
|
||||
BlockId::Number(n) => n <= self.meta.read().best_number,
|
||||
};
|
||||
match exists {
|
||||
true => Ok(BlockStatus::InChain),
|
||||
false => Ok(BlockStatus::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn number(&self, hash: Block::Hash) -> ClientResult<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
|
||||
read_id::<Block>(&*self.db, columns::BLOCK_INDEX, BlockId::Hash(hash))
|
||||
.and_then(|key| match key {
|
||||
Some(key) => Ok(Some(db_key_to_number(&key)?)),
|
||||
None => Ok(None),
|
||||
})
|
||||
}
|
||||
|
||||
fn hash(&self, number: <<Block as BlockT>::Header as HeaderT>::Number) -> ClientResult<Option<Block::Hash>> {
|
||||
read_db::<Block>(&*self.db, columns::BLOCK_INDEX, columns::HEADER, BlockId::Number(number)).map(|x|
|
||||
x.map(|raw| HashFor::<Block>::hash(&raw[..])).map(Into::into)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
|
||||
where
|
||||
Block: BlockT,
|
||||
Block::Hash: From<H256>,
|
||||
{
|
||||
fn import_header(&self, is_new_best: bool, header: Block::Header, authorities: Option<Vec<AuthorityId>>) -> ClientResult<()> {
|
||||
let mut transaction = DBTransaction::new();
|
||||
|
||||
let hash = header.hash();
|
||||
let number = *header.number();
|
||||
let key = number_to_db_key(number);
|
||||
|
||||
transaction.put(columns::HEADER, &key, &header.encode());
|
||||
transaction.put(columns::BLOCK_INDEX, hash.as_ref(), &key);
|
||||
|
||||
let best_authorities = if is_new_best {
|
||||
transaction.put(columns::META, meta_keys::BEST_BLOCK, &key);
|
||||
|
||||
// cache authorities for previous block
|
||||
let number: u64 = number.as_();
|
||||
let previous_number = number.checked_sub(1);
|
||||
let best_authorities = previous_number
|
||||
.and_then(|previous_number| self.cache.authorities_at_cache()
|
||||
.commit_best_entry(&mut transaction, As::sa(previous_number), authorities));
|
||||
|
||||
// prune authorities from 'ancient' blocks
|
||||
if let Some(ancient_number) = number.checked_sub(AUTHORITIES_ENTRIES_TO_KEEP) {
|
||||
self.cache.authorities_at_cache().prune_entries(&mut transaction, As::sa(ancient_number))?;
|
||||
}
|
||||
|
||||
best_authorities
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// build new CHT if required
|
||||
if let Some(new_cht_number) = cht::is_build_required(cht::SIZE, *header.number()) {
|
||||
let new_cht_start: NumberFor<Block> = cht::start_number(cht::SIZE, new_cht_number);
|
||||
let new_cht_root: Option<Block::Hash> = cht::compute_root::<Block::Header, Blake2Hasher, _>(
|
||||
cht::SIZE, new_cht_number, (new_cht_start.as_()..)
|
||||
.map(|num| self.hash(As::sa(num)).unwrap_or_default()));
|
||||
|
||||
if let Some(new_cht_root) = new_cht_root {
|
||||
transaction.put(columns::CHT, &number_to_db_key(new_cht_start), new_cht_root.as_ref());
|
||||
|
||||
let mut prune_block = new_cht_start;
|
||||
let new_cht_end = cht::end_number(cht::SIZE, new_cht_number);
|
||||
trace!(target: "db", "Replacing blocks [{}..{}] with CHT#{}", new_cht_start, new_cht_end, new_cht_number);
|
||||
|
||||
while prune_block <= new_cht_end {
|
||||
transaction.delete(columns::HEADER, &number_to_db_key(prune_block));
|
||||
prune_block += <<Block as BlockT>::Header as HeaderT>::Number::one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Light DB Commit {:?} ({})", hash, number);
|
||||
self.db.write(transaction).map_err(db_err)?;
|
||||
self.update_meta(hash, number, is_new_best);
|
||||
if let Some(best_authorities) = best_authorities {
|
||||
self.cache.authorities_at_cache().update_best_entry(Some(best_authorities));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cht_root(&self, cht_size: u64, block: <<Block as BlockT>::Header as HeaderT>::Number) -> ClientResult<Block::Hash> {
|
||||
let no_cht_for_block = || ClientErrorKind::Backend(format!("CHT for block {} not exists", block)).into();
|
||||
|
||||
let cht_number = cht::block_to_cht_number(cht_size, block).ok_or_else(no_cht_for_block)?;
|
||||
let cht_start = cht::start_number(cht_size, cht_number);
|
||||
self.db.get(columns::CHT, &number_to_db_key(cht_start)).map_err(db_err)?
|
||||
.ok_or_else(no_cht_for_block)
|
||||
.and_then(|hash| Block::Hash::decode(&mut &*hash).ok_or_else(no_cht_for_block))
|
||||
}
|
||||
|
||||
fn cache(&self) -> Option<&BlockchainCache<Block>> {
|
||||
Some(&self.cache)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use client::cht;
|
||||
use runtime_primitives::testing::{H256 as Hash, Header, Block as RawBlock};
|
||||
use super::*;
|
||||
|
||||
type Block = RawBlock<u32>;
|
||||
|
||||
pub fn insert_block(
|
||||
db: &LightStorage<Block>,
|
||||
parent: &Hash,
|
||||
number: u64,
|
||||
authorities: Option<Vec<AuthorityId>>
|
||||
) -> Hash {
|
||||
let header = Header {
|
||||
number: number.into(),
|
||||
parent_hash: *parent,
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
};
|
||||
|
||||
let hash = header.hash();
|
||||
db.import_header(true, header, authorities).unwrap();
|
||||
hash
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_known_header() {
|
||||
let db = LightStorage::new_test();
|
||||
let known_hash = insert_block(&db, &Default::default(), 0, None);
|
||||
let header_by_hash = db.header(BlockId::Hash(known_hash)).unwrap().unwrap();
|
||||
let header_by_number = db.header(BlockId::Number(0)).unwrap().unwrap();
|
||||
assert_eq!(header_by_hash, header_by_number);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_return_unknown_header() {
|
||||
let db = LightStorage::<Block>::new_test();
|
||||
assert!(db.header(BlockId::Hash(1.into())).unwrap().is_none());
|
||||
assert!(db.header(BlockId::Number(0)).unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_info() {
|
||||
let db = LightStorage::new_test();
|
||||
let genesis_hash = insert_block(&db, &Default::default(), 0, None);
|
||||
let info = db.info().unwrap();
|
||||
assert_eq!(info.best_hash, genesis_hash);
|
||||
assert_eq!(info.best_number, 0);
|
||||
assert_eq!(info.genesis_hash, genesis_hash);
|
||||
let best_hash = insert_block(&db, &genesis_hash, 1, None);
|
||||
let info = db.info().unwrap();
|
||||
assert_eq!(info.best_hash, best_hash);
|
||||
assert_eq!(info.best_number, 1);
|
||||
assert_eq!(info.genesis_hash, genesis_hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_block_status() {
|
||||
let db = LightStorage::new_test();
|
||||
let genesis_hash = insert_block(&db, &Default::default(), 0, None);
|
||||
assert_eq!(db.status(BlockId::Hash(genesis_hash)).unwrap(), BlockStatus::InChain);
|
||||
assert_eq!(db.status(BlockId::Number(0)).unwrap(), BlockStatus::InChain);
|
||||
assert_eq!(db.status(BlockId::Hash(1.into())).unwrap(), BlockStatus::Unknown);
|
||||
assert_eq!(db.status(BlockId::Number(1)).unwrap(), BlockStatus::Unknown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_block_hash() {
|
||||
let db = LightStorage::new_test();
|
||||
let genesis_hash = insert_block(&db, &Default::default(), 0, None);
|
||||
assert_eq!(db.hash(0).unwrap(), Some(genesis_hash));
|
||||
assert_eq!(db.hash(1).unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_header_works() {
|
||||
let db = LightStorage::new_test();
|
||||
|
||||
let genesis_hash = insert_block(&db, &Default::default(), 0, None);
|
||||
assert_eq!(db.db.iter(columns::HEADER).count(), 1);
|
||||
assert_eq!(db.db.iter(columns::BLOCK_INDEX).count(), 1);
|
||||
|
||||
let _ = insert_block(&db, &genesis_hash, 1, None);
|
||||
assert_eq!(db.db.iter(columns::HEADER).count(), 2);
|
||||
assert_eq!(db.db.iter(columns::BLOCK_INDEX).count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ancient_headers_are_replaced_with_cht() {
|
||||
let db = LightStorage::new_test();
|
||||
|
||||
// insert genesis block header (never pruned)
|
||||
let mut prev_hash = insert_block(&db, &Default::default(), 0, None);
|
||||
|
||||
// insert SIZE blocks && ensure that nothing is pruned
|
||||
for number in 0..cht::SIZE {
|
||||
prev_hash = insert_block(&db, &prev_hash, 1 + number, None);
|
||||
}
|
||||
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE) as usize);
|
||||
assert_eq!(db.db.iter(columns::CHT).count(), 0);
|
||||
|
||||
// insert next SIZE blocks && ensure that nothing is pruned
|
||||
for number in 0..cht::SIZE {
|
||||
prev_hash = insert_block(&db, &prev_hash, 1 + cht::SIZE + number, None);
|
||||
}
|
||||
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + cht::SIZE) as usize);
|
||||
assert_eq!(db.db.iter(columns::CHT).count(), 0);
|
||||
|
||||
// insert block #{2 * cht::SIZE + 1} && check that new CHT is created + headers of this CHT are pruned
|
||||
insert_block(&db, &prev_hash, 1 + cht::SIZE + cht::SIZE, None);
|
||||
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + 1) as usize);
|
||||
assert_eq!(db.db.iter(columns::CHT).count(), 1);
|
||||
assert!((0..cht::SIZE).all(|i| db.db.get(columns::HEADER, &number_to_db_key(1 + i)).unwrap().is_none()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_cht_fails_for_genesis_block() {
|
||||
assert!(LightStorage::<Block>::new_test().cht_root(cht::SIZE, 0).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_cht_fails_for_non_existant_cht() {
|
||||
assert!(LightStorage::<Block>::new_test().cht_root(cht::SIZE, (cht::SIZE / 2) as u64).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_cht_works() {
|
||||
let db = LightStorage::new_test();
|
||||
|
||||
// insert 1 + SIZE + SIZE + 1 blocks so that CHT#0 is created
|
||||
let mut prev_hash = Default::default();
|
||||
for i in 0..1 + cht::SIZE + cht::SIZE + 1 {
|
||||
prev_hash = insert_block(&db, &prev_hash, i as u64, None);
|
||||
}
|
||||
|
||||
let cht_root_1 = db.cht_root(cht::SIZE, cht::start_number(cht::SIZE, 0)).unwrap();
|
||||
let cht_root_2 = db.cht_root(cht::SIZE, (cht::start_number(cht::SIZE, 0) + cht::SIZE / 2) as u64).unwrap();
|
||||
let cht_root_3 = db.cht_root(cht::SIZE, cht::end_number(cht::SIZE, 0)).unwrap();
|
||||
assert_eq!(cht_root_1, cht_root_2);
|
||||
assert_eq!(cht_root_2, cht_root_3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Db-based backend utility structures and functions, used by both
|
||||
//! full and light storages.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::io;
|
||||
|
||||
use kvdb::{KeyValueDB, DBTransaction};
|
||||
use kvdb_rocksdb::{Database, DatabaseConfig};
|
||||
|
||||
use client;
|
||||
use codec::Decode;
|
||||
use hashdb::DBValue;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, Hash, HashFor, Zero};
|
||||
use DatabaseSettings;
|
||||
|
||||
/// Number of columns in the db. Must be the same for both full && light dbs.
|
||||
/// Otherwise RocksDb will fail to open database && check its type.
|
||||
pub const NUM_COLUMNS: u32 = 7;
|
||||
/// Meta column. The set of keys in the column is shared by full && light storages.
|
||||
pub const COLUMN_META: Option<u32> = Some(0);
|
||||
|
||||
/// Keys of entries in COLUMN_META.
|
||||
pub mod meta_keys {
|
||||
/// Type of storage (full or light).
|
||||
pub const TYPE: &[u8; 4] = b"type";
|
||||
/// Best block key.
|
||||
pub const BEST_BLOCK: &[u8; 4] = b"best";
|
||||
/// Best authorities block key.
|
||||
pub const BEST_AUTHORITIES: &[u8; 4] = b"auth";
|
||||
}
|
||||
|
||||
/// Database metadata.
|
||||
pub struct Meta<N, H> {
|
||||
/// Hash of the best known block.
|
||||
pub best_hash: H,
|
||||
/// Number of the best known block.
|
||||
pub best_number: N,
|
||||
/// Hash of the genesis block.
|
||||
pub genesis_hash: H,
|
||||
}
|
||||
|
||||
/// Type of block key in the database (LE block number).
|
||||
pub type BlockKey = [u8; 4];
|
||||
|
||||
/// Convert block number into key (LE representation).
|
||||
pub fn number_to_db_key<N>(n: N) -> BlockKey where N: As<u64> {
|
||||
let n: u64 = n.as_();
|
||||
assert!(n & 0xffffffff00000000 == 0);
|
||||
|
||||
[
|
||||
(n >> 24) as u8,
|
||||
((n >> 16) & 0xff) as u8,
|
||||
((n >> 8) & 0xff) as u8,
|
||||
(n & 0xff) as u8
|
||||
]
|
||||
}
|
||||
|
||||
/// Convert block key into block number.
|
||||
pub fn db_key_to_number<N>(key: &[u8]) -> client::error::Result<N> where N: As<u64> {
|
||||
match key.len() {
|
||||
4 => Ok((key[0] as u64) << 24
|
||||
| (key[1] as u64) << 16
|
||||
| (key[2] as u64) << 8
|
||||
| (key[3] as u64)).map(As::sa),
|
||||
_ => Err(client::error::ErrorKind::Backend("Invalid block key".into()).into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps database error to client error
|
||||
pub fn db_err(err: io::Error) -> client::error::Error {
|
||||
use std::error::Error;
|
||||
client::error::ErrorKind::Backend(err.description().into()).into()
|
||||
}
|
||||
|
||||
/// Open RocksDB database.
|
||||
pub fn open_database(config: &DatabaseSettings, db_type: &str) -> client::error::Result<Arc<KeyValueDB>> {
|
||||
let mut db_config = DatabaseConfig::with_columns(Some(NUM_COLUMNS));
|
||||
db_config.memory_budget = config.cache_size;
|
||||
let path = config.path.to_str().ok_or_else(|| client::error::ErrorKind::Backend("Invalid database path".into()))?;
|
||||
let db = Database::open(&db_config, &path).map_err(db_err)?;
|
||||
|
||||
// check database type
|
||||
match db.get(COLUMN_META, meta_keys::TYPE).map_err(db_err)? {
|
||||
Some(stored_type) => {
|
||||
if db_type.as_bytes() != &*stored_type {
|
||||
return Err(client::error::ErrorKind::Backend(
|
||||
format!("Unexpected database type. Expected: {}", db_type)).into());
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let mut transaction = DBTransaction::new();
|
||||
transaction.put(COLUMN_META, meta_keys::TYPE, db_type.as_bytes());
|
||||
db.write(transaction).map_err(db_err)?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(Arc::new(db))
|
||||
}
|
||||
|
||||
/// Convert block id to block key, reading number from db if required.
|
||||
pub fn read_id<Block>(db: &KeyValueDB, col_index: Option<u32>, id: BlockId<Block>) -> Result<Option<BlockKey>, client::error::Error>
|
||||
where
|
||||
Block: BlockT,
|
||||
{
|
||||
match id {
|
||||
BlockId::Hash(h) => db.get(col_index, h.as_ref())
|
||||
.map(|v| v.map(|v| {
|
||||
let mut key: [u8; 4] = [0; 4];
|
||||
key.copy_from_slice(&v);
|
||||
key
|
||||
})).map_err(db_err),
|
||||
BlockId::Number(n) => Ok(Some(number_to_db_key(n))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read database column entry for the given block.
|
||||
pub fn read_db<Block>(db: &KeyValueDB, col_index: Option<u32>, col: Option<u32>, id: BlockId<Block>) -> client::error::Result<Option<DBValue>>
|
||||
where
|
||||
Block: BlockT,
|
||||
{
|
||||
read_id(db, col_index, id).and_then(|key| match key {
|
||||
Some(key) => db.get(col, &key).map_err(db_err),
|
||||
None => Ok(None),
|
||||
})
|
||||
}
|
||||
|
||||
/// Read meta from the database.
|
||||
pub fn read_meta<Block>(db: &KeyValueDB, col_header: Option<u32>) -> Result<Meta<<<Block as BlockT>::Header as HeaderT>::Number, Block::Hash>, client::error::Error>
|
||||
where
|
||||
Block: BlockT,
|
||||
{
|
||||
let genesis_number = <<Block as BlockT>::Header as HeaderT>::Number::zero();
|
||||
let (best_hash, best_number) = if let Some(Some(header)) = db.get(COLUMN_META, meta_keys::BEST_BLOCK).and_then(|id|
|
||||
match id {
|
||||
Some(id) => db.get(col_header, &id).map(|h| h.map(|b| Block::Header::decode(&mut &b[..]))),
|
||||
None => Ok(None),
|
||||
}).map_err(db_err)?
|
||||
{
|
||||
let hash = header.hash();
|
||||
debug!("DB Opened blockchain db, best {:?} ({})", hash, header.number());
|
||||
(hash, *header.number())
|
||||
} else {
|
||||
(Default::default(), genesis_number)
|
||||
};
|
||||
|
||||
let genesis_hash = db.get(col_header, &number_to_db_key(genesis_number))
|
||||
.map_err(db_err)?
|
||||
.map(|raw| HashFor::<Block>::hash(&raw[..]))
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
|
||||
Ok(Meta {
|
||||
best_hash,
|
||||
best_number,
|
||||
genesis_hash,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot Client data backend
|
||||
|
||||
use error;
|
||||
use primitives::AuthorityId;
|
||||
use runtime_primitives::bft::Justification;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{Block as BlockT, NumberFor};
|
||||
use state_machine::backend::Backend as StateBackend;
|
||||
use patricia_trie::NodeCodec;
|
||||
use hashdb::Hasher;
|
||||
|
||||
/// Block insertion operation. Keeps hold if the inserted block state and data.
|
||||
pub trait BlockImportOperation<Block, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
/// Associated state backend type.
|
||||
type State: StateBackend<H, C>;
|
||||
|
||||
/// Returns pending state. Returns None for backends with locally-unavailable state data.
|
||||
fn state(&self) -> error::Result<Option<&Self::State>>;
|
||||
/// Append block data to the transaction.
|
||||
fn set_block_data(
|
||||
&mut self,
|
||||
header: Block::Header,
|
||||
body: Option<Vec<Block::Extrinsic>>,
|
||||
justification: Option<Justification<Block::Hash>>,
|
||||
is_new_best: bool
|
||||
) -> error::Result<()>;
|
||||
|
||||
/// Append authorities set to the transaction. This is a set of parent block (set which
|
||||
/// has been used to check justification of this block).
|
||||
fn update_authorities(&mut self, authorities: Vec<AuthorityId>);
|
||||
/// Inject storage data into the database.
|
||||
fn update_storage(&mut self, update: <Self::State as StateBackend<H, C>>::Transaction) -> error::Result<()>;
|
||||
/// Inject storage data into the database replacing any existing data.
|
||||
fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()>;
|
||||
}
|
||||
|
||||
/// Client backend. Manages the data layer.
|
||||
///
|
||||
/// Note on state pruning: while an object from `state_at` is alive, the state
|
||||
/// should not be pruned. The backend should internally reference-count
|
||||
/// its state objects.
|
||||
///
|
||||
/// The same applies for live `BlockImportOperation`s: while an import operation building on a parent `P`
|
||||
/// is alive, the state for `P` should not be pruned.
|
||||
pub trait Backend<Block, H, C>: Send + Sync
|
||||
where
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
/// Associated block insertion operation type.
|
||||
type BlockImportOperation: BlockImportOperation<Block, H, C>;
|
||||
/// Associated blockchain backend type.
|
||||
type Blockchain: ::blockchain::Backend<Block>;
|
||||
/// Associated state backend type.
|
||||
type State: StateBackend<H, C>;
|
||||
|
||||
/// Begin a new block insertion transaction with given parent block id.
|
||||
/// When constructing the genesis, this is called with all-zero hash.
|
||||
fn begin_operation(&self, block: BlockId<Block>) -> error::Result<Self::BlockImportOperation>;
|
||||
/// Commit block insertion.
|
||||
fn commit_operation(&self, transaction: Self::BlockImportOperation) -> error::Result<()>;
|
||||
/// Returns reference to blockchain backend.
|
||||
fn blockchain(&self) -> &Self::Blockchain;
|
||||
/// Returns state backend with post-state of given block.
|
||||
fn state_at(&self, block: BlockId<Block>) -> error::Result<Self::State>;
|
||||
/// Attempts to revert the chain by `n` blocks. Returns the number of blocks that were
|
||||
/// successfully reverted.
|
||||
fn revert(&self, n: NumberFor<Block>) -> error::Result<NumberFor<Block>>;
|
||||
}
|
||||
|
||||
/// Mark for all Backend implementations, that are making use of state data, stored locally.
|
||||
pub trait LocalBackend<Block, H, C>: Backend<Block, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{}
|
||||
|
||||
/// Mark for all Backend implementations, that are fetching required state data from remote nodes.
|
||||
pub trait RemoteBackend<Block, H, C>: Backend<Block, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utility struct to build a block.
|
||||
|
||||
use std::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
use state_machine::{self, native_when_possible};
|
||||
use runtime_primitives::traits::{Header as HeaderT, Hash, Block as BlockT, One, HashFor};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use {backend, error, Client, CallExecutor};
|
||||
use runtime_primitives::{ApplyResult, ApplyOutcome};
|
||||
use patricia_trie::NodeCodec;
|
||||
use primitives::{Blake2Hasher, RlpCodec};
|
||||
use hashdb::Hasher;
|
||||
use rlp::Encodable;
|
||||
|
||||
/// Utility for building new (valid) blocks from a stream of extrinsics.
|
||||
pub struct BlockBuilder<B, E, Block, H, C>
|
||||
where
|
||||
B: backend::Backend<Block, H, C>,
|
||||
E: CallExecutor<Block, H, C> + Clone,
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
H::Out: Encodable + Ord,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
header: <Block as BlockT>::Header,
|
||||
extrinsics: Vec<<Block as BlockT>::Extrinsic>,
|
||||
executor: E,
|
||||
state: B::State,
|
||||
changes: state_machine::OverlayedChanges,
|
||||
}
|
||||
|
||||
impl<B, E, Block> BlockBuilder<B, E, Block, Blake2Hasher, RlpCodec>
|
||||
where
|
||||
B: backend::Backend<Block, Blake2Hasher, RlpCodec>,
|
||||
E: CallExecutor<Block, Blake2Hasher, RlpCodec> + Clone,
|
||||
Block: BlockT,
|
||||
{
|
||||
/// Create a new instance of builder from the given client, building on the latest block.
|
||||
pub fn new(client: &Client<B, E, Block>) -> error::Result<Self> {
|
||||
client.info().and_then(|i| Self::at_block(&BlockId::Hash(i.chain.best_hash), client))
|
||||
}
|
||||
|
||||
/// Create a new instance of builder from the given client using a particular block's ID to
|
||||
/// build upon.
|
||||
pub fn at_block(block_id: &BlockId<Block>, client: &Client<B, E, Block>) -> error::Result<Self> {
|
||||
let number = client.block_number_from_id(block_id)?
|
||||
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{}", block_id)))?
|
||||
+ One::one();
|
||||
|
||||
let parent_hash = client.block_hash_from_id(block_id)?
|
||||
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{}", block_id)))?;
|
||||
|
||||
let executor = client.executor().clone();
|
||||
let state = client.state_at(block_id)?;
|
||||
let mut changes = Default::default();
|
||||
let header = <<Block as BlockT>::Header as HeaderT>::new(
|
||||
number,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
parent_hash,
|
||||
Default::default()
|
||||
);
|
||||
|
||||
executor.call_at_state(&state, &mut changes, "initialise_block", &header.encode(), native_when_possible())?;
|
||||
changes.commit_prospective();
|
||||
|
||||
Ok(BlockBuilder {
|
||||
header,
|
||||
extrinsics: Vec::new(),
|
||||
executor,
|
||||
state,
|
||||
changes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Push onto the block's list of extrinsics. This will ensure the extrinsic
|
||||
/// can be validly executed (by executing it); if it is invalid, it'll be returned along with
|
||||
/// the error. Otherwise, it will return a mutable reference to self (in order to chain).
|
||||
pub fn push(&mut self, xt: <Block as BlockT>::Extrinsic) -> error::Result<()> {
|
||||
match self.executor.call_at_state(&self.state, &mut self.changes, "apply_extrinsic", &xt.encode(), native_when_possible()) {
|
||||
Ok((result, _)) => {
|
||||
match ApplyResult::decode(&mut result.as_slice()) {
|
||||
Some(Ok(ApplyOutcome::Success)) | Some(Ok(ApplyOutcome::Fail)) => {
|
||||
self.extrinsics.push(xt);
|
||||
self.changes.commit_prospective();
|
||||
Ok(())
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
self.changes.discard_prospective();
|
||||
Err(error::ErrorKind::ApplyExtinsicFailed(e).into())
|
||||
}
|
||||
None => {
|
||||
self.changes.discard_prospective();
|
||||
Err(error::ErrorKind::CallResultDecode("apply_extrinsic").into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
self.changes.discard_prospective();
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the builder to return a valid `Block` containing all pushed extrinsics.
|
||||
pub fn bake(mut self) -> error::Result<Block> {
|
||||
let (output, _) = self.executor.call_at_state(
|
||||
&self.state,
|
||||
&mut self.changes,
|
||||
"finalise_block",
|
||||
&[],
|
||||
native_when_possible(),
|
||||
)?;
|
||||
self.header = <<Block as BlockT>::Header as Decode>::decode(&mut &output[..])
|
||||
.expect("Header came straight out of runtime so must be valid");
|
||||
|
||||
debug_assert_eq!(
|
||||
self.header.extrinsics_root().clone(),
|
||||
HashFor::<Block>::ordered_trie_root(self.extrinsics.iter().map(Encode::encode)),
|
||||
);
|
||||
|
||||
Ok(<Block as BlockT>::new(self.header, self.extrinsics))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot blockchain trait
|
||||
|
||||
use primitives::AuthorityId;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::bft::Justification;
|
||||
|
||||
use error::{ErrorKind, Result};
|
||||
|
||||
/// Blockchain database header backend. Does not perform any validation.
|
||||
pub trait HeaderBackend<Block: BlockT>: Send + Sync {
|
||||
/// Get block header. Returns `None` if block is not found.
|
||||
fn header(&self, id: BlockId<Block>) -> Result<Option<Block::Header>>;
|
||||
/// Get blockchain info.
|
||||
fn info(&self) -> Result<Info<Block>>;
|
||||
/// Get block status.
|
||||
fn status(&self, id: BlockId<Block>) -> Result<BlockStatus>;
|
||||
/// Get block number by hash. Returns `None` if the header is not in the chain.
|
||||
fn number(&self, hash: Block::Hash) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>>;
|
||||
/// Get block hash by number. Returns `None` if the header is not in the chain.
|
||||
fn hash(&self, number: NumberFor<Block>) -> Result<Option<Block::Hash>>;
|
||||
|
||||
/// Get block header. Returns `UnknownBlock` error if block is not found.
|
||||
fn expect_header(&self, id: BlockId<Block>) -> Result<Block::Header> {
|
||||
self.header(id)?.ok_or_else(|| ErrorKind::UnknownBlock(format!("{}", id)).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Blockchain database backend. Does not perform any validation.
|
||||
pub trait Backend<Block: BlockT>: HeaderBackend<Block> {
|
||||
/// Get block body. Returns `None` if block is not found.
|
||||
fn body(&self, id: BlockId<Block>) -> Result<Option<Vec<<Block as BlockT>::Extrinsic>>>;
|
||||
/// Get block justification. Returns `None` if justification does not exist.
|
||||
fn justification(&self, id: BlockId<Block>) -> Result<Option<Justification<Block::Hash>>>;
|
||||
|
||||
/// Returns data cache reference, if it is enabled on this backend.
|
||||
fn cache(&self) -> Option<&Cache<Block>>;
|
||||
}
|
||||
|
||||
/// Blockchain optional data cache.
|
||||
pub trait Cache<Block: BlockT>: Send + Sync {
|
||||
/// Returns the set of authorities, that was active at given block or None if there's no entry in the cache.
|
||||
fn authorities_at(&self, block: BlockId<Block>) -> Option<Vec<AuthorityId>>;
|
||||
}
|
||||
|
||||
/// Block import outcome
|
||||
pub enum ImportResult<E> {
|
||||
/// Imported successfully.
|
||||
Imported,
|
||||
/// Block already exists, skippped.
|
||||
AlreadyInChain,
|
||||
/// Unknown parent.
|
||||
UnknownParent,
|
||||
/// Other errror.
|
||||
Err(E),
|
||||
}
|
||||
|
||||
/// Blockchain info
|
||||
#[derive(Debug)]
|
||||
pub struct Info<Block: BlockT> {
|
||||
/// Best block hash.
|
||||
pub best_hash: <<Block as BlockT>::Header as HeaderT>::Hash,
|
||||
/// Best block number.
|
||||
pub best_number: <<Block as BlockT>::Header as HeaderT>::Number,
|
||||
/// Genesis block hash.
|
||||
pub genesis_hash: <<Block as BlockT>::Header as HeaderT>::Hash,
|
||||
}
|
||||
|
||||
/// Block status.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BlockStatus {
|
||||
/// Already in the blockchain.
|
||||
InChain,
|
||||
/// Not in the queue or the blockchain.
|
||||
Unknown,
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::cmp::Ord;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::Block as BlockT;
|
||||
use state_machine::{self, OverlayedChanges, Ext,
|
||||
CodeExecutor, ExecutionManager, native_when_possible};
|
||||
use executor::{RuntimeVersion, RuntimeInfo};
|
||||
use patricia_trie::NodeCodec;
|
||||
use hashdb::Hasher;
|
||||
use rlp::Encodable;
|
||||
use codec::Decode;
|
||||
use primitives::{Blake2Hasher, RlpCodec};
|
||||
|
||||
use backend;
|
||||
use error;
|
||||
|
||||
/// Information regarding the result of a call.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CallResult {
|
||||
/// The data that was returned from the call.
|
||||
pub return_data: Vec<u8>,
|
||||
/// The changes made to the state by the call.
|
||||
pub changes: OverlayedChanges,
|
||||
}
|
||||
|
||||
/// Method call executor.
|
||||
pub trait CallExecutor<B, H, C>
|
||||
where
|
||||
B: BlockT,
|
||||
H: Hasher,
|
||||
H::Out: Ord + Encodable,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
/// Externalities error type.
|
||||
type Error: state_machine::Error;
|
||||
|
||||
/// Execute a call to a contract on top of state in a block of given hash.
|
||||
///
|
||||
/// No changes are made.
|
||||
fn call(&self,
|
||||
id: &BlockId<B>,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
) -> Result<CallResult, error::Error>;
|
||||
|
||||
/// Extract RuntimeVersion of given block
|
||||
///
|
||||
/// No changes are made.
|
||||
fn runtime_version(&self, id: &BlockId<B>) -> Result<RuntimeVersion, error::Error>;
|
||||
|
||||
/// Execute a call to a contract on top of given state.
|
||||
///
|
||||
/// No changes are made.
|
||||
fn call_at_state<
|
||||
S: state_machine::Backend<H, C>,
|
||||
F: FnOnce(Result<Vec<u8>, Self::Error>, Result<Vec<u8>, Self::Error>) -> Result<Vec<u8>, Self::Error>,
|
||||
>(&self,
|
||||
state: &S,
|
||||
overlay: &mut OverlayedChanges,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
manager: ExecutionManager<F>
|
||||
) -> Result<(Vec<u8>, S::Transaction), error::Error>;
|
||||
|
||||
/// Execute a call to a contract on top of given state, gathering execution proof.
|
||||
///
|
||||
/// No changes are made.
|
||||
fn prove_at_state<S: state_machine::Backend<H, C>>(&self,
|
||||
state: S,
|
||||
overlay: &mut OverlayedChanges,
|
||||
method: &str,
|
||||
call_data: &[u8]
|
||||
) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error>;
|
||||
|
||||
/// Get runtime version if supported.
|
||||
fn native_runtime_version(&self) -> Option<RuntimeVersion>;
|
||||
}
|
||||
|
||||
/// Call executor that executes methods locally, querying all required
|
||||
/// data from local backend.
|
||||
pub struct LocalCallExecutor<B, E> {
|
||||
backend: Arc<B>,
|
||||
executor: E,
|
||||
}
|
||||
|
||||
impl<B, E> LocalCallExecutor<B, E> {
|
||||
/// Creates new instance of local call executor.
|
||||
pub fn new(backend: Arc<B>, executor: E) -> Self {
|
||||
LocalCallExecutor { backend, executor }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> Clone for LocalCallExecutor<B, E> where E: Clone {
|
||||
fn clone(&self) -> Self {
|
||||
LocalCallExecutor {
|
||||
backend: self.backend.clone(),
|
||||
executor: self.executor.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block> CallExecutor<Block, Blake2Hasher, RlpCodec> for LocalCallExecutor<B, E>
|
||||
where
|
||||
B: backend::LocalBackend<Block, Blake2Hasher, RlpCodec>,
|
||||
E: CodeExecutor<Blake2Hasher> + RuntimeInfo,
|
||||
Block: BlockT,
|
||||
{
|
||||
type Error = E::Error;
|
||||
|
||||
fn call(&self,
|
||||
id: &BlockId<Block>,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
) -> error::Result<CallResult> {
|
||||
let mut changes = OverlayedChanges::default();
|
||||
let (return_data, _) = self.call_at_state(
|
||||
&self.backend.state_at(*id)?,
|
||||
&mut changes,
|
||||
method,
|
||||
call_data,
|
||||
native_when_possible(),
|
||||
)?;
|
||||
Ok(CallResult { return_data, changes })
|
||||
}
|
||||
|
||||
fn runtime_version(&self, id: &BlockId<Block>) -> error::Result<RuntimeVersion> {
|
||||
let mut overlay = OverlayedChanges::default();
|
||||
let state = self.backend.state_at(*id)?;
|
||||
use state_machine::Backend;
|
||||
let code = state.storage(b":code")
|
||||
.map_err(|e| error::ErrorKind::Execution(Box::new(e)))?
|
||||
.ok_or(error::ErrorKind::VersionInvalid)?
|
||||
.to_vec();
|
||||
let heap_pages = state.storage(b":heappages")
|
||||
.map_err(|e| error::ErrorKind::Execution(Box::new(e)))?
|
||||
.and_then(|v| u64::decode(&mut &v[..]))
|
||||
.unwrap_or(8) as usize;
|
||||
|
||||
self.executor.runtime_version(&mut Ext::new(&mut overlay, &state), heap_pages, &code)
|
||||
.ok_or(error::ErrorKind::VersionInvalid.into())
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
S: state_machine::Backend<Blake2Hasher, RlpCodec>,
|
||||
F: FnOnce(Result<Vec<u8>, Self::Error>, Result<Vec<u8>, Self::Error>) -> Result<Vec<u8>, Self::Error>,
|
||||
>(&self,
|
||||
state: &S,
|
||||
changes: &mut OverlayedChanges,
|
||||
method: &str,
|
||||
call_data: &[u8],
|
||||
manager: ExecutionManager<F>,
|
||||
) -> error::Result<(Vec<u8>, S::Transaction)> {
|
||||
state_machine::execute_using_consensus_failure_handler(
|
||||
state,
|
||||
changes,
|
||||
&self.executor,
|
||||
method,
|
||||
call_data,
|
||||
manager,
|
||||
).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn prove_at_state<S: state_machine::Backend<Blake2Hasher, RlpCodec>>(&self,
|
||||
state: S,
|
||||
changes: &mut OverlayedChanges,
|
||||
method: &str,
|
||||
call_data: &[u8]
|
||||
) -> Result<(Vec<u8>, Vec<Vec<u8>>), error::Error> {
|
||||
state_machine::prove_execution(
|
||||
state,
|
||||
changes,
|
||||
&self.executor,
|
||||
method,
|
||||
call_data,
|
||||
)
|
||||
.map(|(result, proof, _)| (result, proof))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn native_runtime_version(&self) -> Option<RuntimeVersion> {
|
||||
<E as RuntimeInfo>::NATIVE_VERSION
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Canonical hash trie definitions and helper functions.
|
||||
//!
|
||||
//! Each CHT is a trie mapping block numbers to canonical hash.
|
||||
//! One is generated for every `SIZE` blocks, allowing us to discard those blocks in
|
||||
//! favor of the trie root. When the "ancient" blocks need to be accessed, we simply
|
||||
//! request an inclusion proof of a specific block number against the trie with the
|
||||
//! root has. A correct proof implies that the claimed block is identical to the one
|
||||
//! we discarded.
|
||||
|
||||
use hashdb;
|
||||
use heapsize::HeapSizeOf;
|
||||
use patricia_trie::NodeCodec;
|
||||
use rlp::Encodable;
|
||||
use triehash;
|
||||
|
||||
use primitives::H256;
|
||||
use runtime_primitives::traits::{As, Header as HeaderT, SimpleArithmetic, One};
|
||||
use state_machine::backend::InMemory as InMemoryState;
|
||||
use state_machine::{prove_read, read_proof_check};
|
||||
|
||||
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
|
||||
|
||||
/// The size of each CHT. This value is passed to every CHT-related function from
|
||||
/// production code. Other values are passed from tests.
|
||||
pub const SIZE: u64 = 2048;
|
||||
|
||||
/// Returns Some(cht_number) if CHT is need to be built when the block with given number is canonized.
|
||||
pub fn is_build_required<N>(cht_size: u64, block_num: N) -> Option<N>
|
||||
where
|
||||
N: Clone + SimpleArithmetic,
|
||||
{
|
||||
let block_cht_num = block_to_cht_number(cht_size, block_num.clone())?;
|
||||
let two = N::one() + N::one();
|
||||
if block_cht_num < two {
|
||||
return None;
|
||||
}
|
||||
let cht_start = start_number(cht_size, block_cht_num.clone());
|
||||
if cht_start != block_num {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(block_cht_num - two)
|
||||
}
|
||||
|
||||
/// Compute a CHT root from an iterator of block hashes. Fails if shorter than
|
||||
/// SIZE items. The items are assumed to proceed sequentially from `start_number(cht_num)`.
|
||||
/// Discards the trie's nodes.
|
||||
pub fn compute_root<Header, Hasher, I>(
|
||||
cht_size: u64,
|
||||
cht_num: Header::Number,
|
||||
hashes: I,
|
||||
) -> Option<Header::Hash>
|
||||
where
|
||||
Header: HeaderT,
|
||||
Header::Hash: From<Hasher::Out>,
|
||||
Hasher: hashdb::Hasher,
|
||||
Hasher::Out: Ord + Encodable,
|
||||
I: IntoIterator<Item=Option<Header::Hash>>,
|
||||
{
|
||||
build_pairs::<Header, I>(cht_size, cht_num, hashes)
|
||||
.map(|pairs| triehash::trie_root::<Hasher, _, _, _>(pairs).into())
|
||||
}
|
||||
|
||||
/// Build CHT-based header proof.
|
||||
pub fn build_proof<Header, Hasher, Codec, I>(
|
||||
cht_size: u64,
|
||||
cht_num: Header::Number,
|
||||
block_num: Header::Number,
|
||||
hashes: I
|
||||
) -> Option<Vec<Vec<u8>>>
|
||||
where
|
||||
Header: HeaderT,
|
||||
Hasher: hashdb::Hasher,
|
||||
Hasher::Out: Ord + Encodable + HeapSizeOf,
|
||||
Codec: NodeCodec<Hasher>,
|
||||
I: IntoIterator<Item=Option<Header::Hash>>,
|
||||
{
|
||||
let transaction = build_pairs::<Header, I>(cht_size, cht_num, hashes)?
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, Some(v)))
|
||||
.collect::<Vec<_>>();
|
||||
let storage = InMemoryState::<Hasher, Codec>::default().update(transaction);
|
||||
let (value, proof) = prove_read(storage, &encode_cht_key(block_num)).ok()?;
|
||||
if value.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(proof)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check CHT-based header proof.
|
||||
pub fn check_proof<Header, Hasher, Codec>(
|
||||
local_root: Header::Hash,
|
||||
local_number: Header::Number,
|
||||
remote_hash: Header::Hash,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<()>
|
||||
where
|
||||
Header: HeaderT,
|
||||
Header::Hash: From<H256>,
|
||||
Hasher: hashdb::Hasher,
|
||||
Hasher::Out: Ord + Encodable + HeapSizeOf + From<Header::Hash>,
|
||||
Codec: NodeCodec<Hasher>,
|
||||
{
|
||||
let local_cht_key = encode_cht_key(local_number);
|
||||
let local_cht_value = read_proof_check::<Hasher, Codec>(local_root.into(), remote_proof,
|
||||
&local_cht_key).map_err(|e| ClientError::from(e))?;
|
||||
let local_cht_value = local_cht_value.ok_or_else(|| ClientErrorKind::InvalidHeaderProof)?;
|
||||
let local_hash: Header::Hash = decode_cht_value(&local_cht_value).ok_or_else(|| ClientErrorKind::InvalidHeaderProof)?;
|
||||
match local_hash == remote_hash {
|
||||
true => Ok(()),
|
||||
false => Err(ClientErrorKind::InvalidHeaderProof.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build pairs for computing CHT.
|
||||
fn build_pairs<Header, I>(
|
||||
cht_size: u64,
|
||||
cht_num: Header::Number,
|
||||
hashes: I
|
||||
) -> Option<Vec<(Vec<u8>, Vec<u8>)>>
|
||||
where
|
||||
Header: HeaderT,
|
||||
I: IntoIterator<Item=Option<Header::Hash>>,
|
||||
{
|
||||
let start_num = start_number(cht_size, cht_num);
|
||||
let mut pairs = Vec::new();
|
||||
let mut hash_number = start_num;
|
||||
for hash in hashes.into_iter().take(cht_size as usize) {
|
||||
pairs.push(hash.map(|hash| (
|
||||
encode_cht_key(hash_number).to_vec(),
|
||||
encode_cht_value(hash)
|
||||
))?);
|
||||
hash_number += Header::Number::one();
|
||||
}
|
||||
|
||||
if pairs.len() as u64 == cht_size {
|
||||
Some(pairs)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the starting block of a given CHT.
|
||||
/// CHT 0 includes block 1...SIZE,
|
||||
/// CHT 1 includes block SIZE + 1 ... 2*SIZE
|
||||
/// More generally: CHT N includes block (1 + N*SIZE)...((N+1)*SIZE).
|
||||
/// This is because the genesis hash is assumed to be known
|
||||
/// and including it would be redundant.
|
||||
pub fn start_number<N: SimpleArithmetic>(cht_size: u64, cht_num: N) -> N {
|
||||
(cht_num * As::sa(cht_size)) + N::one()
|
||||
}
|
||||
|
||||
/// Get the ending block of a given CHT.
|
||||
pub fn end_number<N: SimpleArithmetic>(cht_size: u64, cht_num: N) -> N {
|
||||
(cht_num + N::one()) * As::sa(cht_size)
|
||||
}
|
||||
|
||||
/// Convert a block number to a CHT number.
|
||||
/// Returns `None` for `block_num` == 0, `Some` otherwise.
|
||||
pub fn block_to_cht_number<N: SimpleArithmetic>(cht_size: u64, block_num: N) -> Option<N> {
|
||||
if block_num == N::zero() {
|
||||
None
|
||||
} else {
|
||||
Some((block_num - N::one()) / As::sa(cht_size))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert header number into CHT key.
|
||||
pub fn encode_cht_key<N: As<u64>>(number: N) -> Vec<u8> {
|
||||
let number: u64 = number.as_();
|
||||
vec![
|
||||
(number >> 56) as u8,
|
||||
((number >> 48) & 0xff) as u8,
|
||||
((number >> 40) & 0xff) as u8,
|
||||
((number >> 32) & 0xff) as u8,
|
||||
((number >> 24) & 0xff) as u8,
|
||||
((number >> 16) & 0xff) as u8,
|
||||
((number >> 8) & 0xff) as u8,
|
||||
(number & 0xff) as u8
|
||||
]
|
||||
}
|
||||
|
||||
/// Convert header hash into CHT value.
|
||||
fn encode_cht_value<Hash: AsRef<[u8]>>(hash: Hash) -> Vec<u8> {
|
||||
hash.as_ref().to_vec()
|
||||
}
|
||||
|
||||
/// Convert CHT value into block header hash.
|
||||
pub fn decode_cht_value<Hash: From<H256>>(value: &[u8]) -> Option<Hash> {
|
||||
match value.len() {
|
||||
32 => Some(H256::from_slice(&value[0..32]).into()),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use primitives::{Blake2Hasher, RlpCodec};
|
||||
use test_client::runtime::Header;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_build_required_works() {
|
||||
assert_eq!(is_build_required(SIZE, 0), None);
|
||||
assert_eq!(is_build_required(SIZE, 1), None);
|
||||
assert_eq!(is_build_required(SIZE, SIZE), None);
|
||||
assert_eq!(is_build_required(SIZE, SIZE + 1), None);
|
||||
assert_eq!(is_build_required(SIZE, 2 * SIZE), None);
|
||||
assert_eq!(is_build_required(SIZE, 2 * SIZE + 1), Some(0));
|
||||
assert_eq!(is_build_required(SIZE, 3 * SIZE), None);
|
||||
assert_eq!(is_build_required(SIZE, 3 * SIZE + 1), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_number_works() {
|
||||
assert_eq!(start_number(SIZE, 0), 1);
|
||||
assert_eq!(start_number(SIZE, 1), SIZE + 1);
|
||||
assert_eq!(start_number(SIZE, 2), SIZE + SIZE + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn end_number_works() {
|
||||
assert_eq!(end_number(SIZE, 0), SIZE);
|
||||
assert_eq!(end_number(SIZE, 1), SIZE + SIZE);
|
||||
assert_eq!(end_number(SIZE, 2), SIZE + SIZE + SIZE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_pairs_fails_when_no_enough_blocks() {
|
||||
assert!(build_pairs::<Header, _>(SIZE, 0, vec![Some(1.into()); SIZE as usize / 2]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_pairs_fails_when_missing_block() {
|
||||
assert!(build_pairs::<Header, _>(SIZE, 0, ::std::iter::repeat(Some(1.into())).take(SIZE as usize / 2)
|
||||
.chain(::std::iter::once(None))
|
||||
.chain(::std::iter::repeat(Some(2.into())).take(SIZE as usize / 2 - 1))).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_root_works() {
|
||||
assert!(compute_root::<Header, Blake2Hasher, _>(SIZE, 42, vec![Some(1.into()); SIZE as usize]).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_proof_fails_when_querying_wrong_block() {
|
||||
assert!(build_proof::<Header, Blake2Hasher, RlpCodec, _>(
|
||||
SIZE, 0, (SIZE * 1000) as u64, vec![Some(1.into()); SIZE as usize]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_proof_works() {
|
||||
assert!(build_proof::<Header, Blake2Hasher, RlpCodec, _>(
|
||||
SIZE, 0, (SIZE / 2) as u64, vec![Some(1.into()); SIZE as usize]).is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,798 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate Client
|
||||
|
||||
use std::sync::Arc;
|
||||
use futures::sync::mpsc;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use primitives::AuthorityId;
|
||||
use runtime_primitives::{bft::Justification, generic::{BlockId, SignedBlock, Block as RuntimeBlock}};
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, One, As, NumberFor};
|
||||
use runtime_primitives::BuildStorage;
|
||||
use substrate_metadata::JsonMetadataDecodable;
|
||||
use primitives::{Blake2Hasher, RlpCodec};
|
||||
use primitives::storage::{StorageKey, StorageData};
|
||||
use codec::{Encode, Decode};
|
||||
use state_machine::{
|
||||
Backend as StateBackend, CodeExecutor,
|
||||
ExecutionStrategy, ExecutionManager, prove_read
|
||||
};
|
||||
|
||||
use backend::{self, BlockImportOperation};
|
||||
use blockchain::{self, Info as ChainInfo, Backend as ChainBackend, HeaderBackend as ChainHeaderBackend};
|
||||
use call_executor::{CallExecutor, LocalCallExecutor};
|
||||
use executor::{RuntimeVersion, RuntimeInfo};
|
||||
use notifications::{StorageNotifications, StorageEventStream};
|
||||
use {cht, error, in_mem, block_builder, bft, genesis};
|
||||
|
||||
/// Type that implements `futures::Stream` of block import events.
|
||||
pub type BlockchainEventStream<Block> = mpsc::UnboundedReceiver<BlockImportNotification<Block>>;
|
||||
|
||||
/// Substrate Client
|
||||
pub struct Client<B, E, Block> where Block: BlockT {
|
||||
backend: Arc<B>,
|
||||
executor: E,
|
||||
storage_notifications: Mutex<StorageNotifications<Block>>,
|
||||
import_notification_sinks: Mutex<Vec<mpsc::UnboundedSender<BlockImportNotification<Block>>>>,
|
||||
import_lock: Mutex<()>,
|
||||
importing_block: RwLock<Option<Block::Hash>>, // holds the block hash currently being imported. TODO: replace this with block queue
|
||||
execution_strategy: ExecutionStrategy,
|
||||
}
|
||||
|
||||
/// A source of blockchain evenets.
|
||||
pub trait BlockchainEvents<Block: BlockT> {
|
||||
/// Get block import event stream.
|
||||
fn import_notification_stream(&self) -> BlockchainEventStream<Block>;
|
||||
|
||||
/// Get storage changes event stream.
|
||||
///
|
||||
/// Passing `None` as `filter_keys` subscribes to all storage changes.
|
||||
fn storage_changes_notification_stream(&self, filter_keys: Option<&[StorageKey]>) -> error::Result<StorageEventStream<Block::Hash>>;
|
||||
}
|
||||
|
||||
/// Chain head information.
|
||||
pub trait ChainHead<Block: BlockT> {
|
||||
/// Get best block header.
|
||||
fn best_block_header(&self) -> Result<<Block as BlockT>::Header, error::Error>;
|
||||
}
|
||||
|
||||
/// Fetch block body by ID.
|
||||
pub trait BlockBody<Block: BlockT> {
|
||||
/// Get block body by ID. Returns `None` if the body is not stored.
|
||||
fn block_body(&self, id: &BlockId<Block>) -> error::Result<Option<Vec<<Block as BlockT>::Extrinsic>>>;
|
||||
}
|
||||
|
||||
/// Client info
|
||||
// TODO: split queue info from chain info and amalgamate into single struct.
|
||||
#[derive(Debug)]
|
||||
pub struct ClientInfo<Block: BlockT> {
|
||||
/// Best block hash.
|
||||
pub chain: ChainInfo<Block>,
|
||||
/// Best block number in the queue.
|
||||
pub best_queued_number: Option<<<Block as BlockT>::Header as HeaderT>::Number>,
|
||||
/// Best queued block hash.
|
||||
pub best_queued_hash: Option<Block::Hash>,
|
||||
}
|
||||
|
||||
/// Block import result.
|
||||
#[derive(Debug)]
|
||||
pub enum ImportResult {
|
||||
/// Added to the import queue.
|
||||
Queued,
|
||||
/// Already in the import queue.
|
||||
AlreadyQueued,
|
||||
/// Already in the blockchain.
|
||||
AlreadyInChain,
|
||||
/// Block or parent is known to be bad.
|
||||
KnownBad,
|
||||
/// Block parent is not in the chain.
|
||||
UnknownParent,
|
||||
}
|
||||
|
||||
/// Block status.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BlockStatus {
|
||||
/// Added to the import queue.
|
||||
Queued,
|
||||
/// Already in the blockchain.
|
||||
InChain,
|
||||
/// Block or parent is known to be bad.
|
||||
KnownBad,
|
||||
/// Not in the queue or the blockchain.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Block data origin.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum BlockOrigin {
|
||||
/// Genesis block built into the client.
|
||||
Genesis,
|
||||
/// Block is part of the initial sync with the network.
|
||||
NetworkInitialSync,
|
||||
/// Block was broadcasted on the network.
|
||||
NetworkBroadcast,
|
||||
/// Block that was received from the network and validated in the consensus process.
|
||||
ConsensusBroadcast,
|
||||
/// Block that was collated by this node.
|
||||
Own,
|
||||
/// Block was imported from a file.
|
||||
File,
|
||||
}
|
||||
|
||||
/// Summary of an imported block
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BlockImportNotification<Block: BlockT> {
|
||||
/// Imported block header hash.
|
||||
pub hash: Block::Hash,
|
||||
/// Imported block origin.
|
||||
pub origin: BlockOrigin,
|
||||
/// Imported block header.
|
||||
pub header: Block::Header,
|
||||
/// Is this the new best block.
|
||||
pub is_new_best: bool,
|
||||
}
|
||||
|
||||
/// A header paired with a justification which has already been checked.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct JustifiedHeader<Block: BlockT> {
|
||||
header: <Block as BlockT>::Header,
|
||||
justification: ::bft::Justification<Block::Hash>,
|
||||
authorities: Vec<AuthorityId>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> JustifiedHeader<Block> {
|
||||
/// Deconstruct the justified header into parts.
|
||||
pub fn into_inner(self) -> (<Block as BlockT>::Header, ::bft::Justification<Block::Hash>, Vec<AuthorityId>) {
|
||||
(self.header, self.justification, self.authorities)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an instance of in-memory client.
|
||||
pub fn new_in_mem<E, Block, S>(
|
||||
executor: E,
|
||||
genesis_storage: S,
|
||||
) -> error::Result<Client<in_mem::Backend<Block, Blake2Hasher, RlpCodec>, LocalCallExecutor<in_mem::Backend<Block, Blake2Hasher, RlpCodec>, E>, Block>>
|
||||
where
|
||||
E: CodeExecutor<Blake2Hasher> + RuntimeInfo,
|
||||
S: BuildStorage,
|
||||
Block: BlockT,
|
||||
{
|
||||
let backend = Arc::new(in_mem::Backend::new());
|
||||
let executor = LocalCallExecutor::new(backend.clone(), executor);
|
||||
Client::new(backend, executor, genesis_storage, ExecutionStrategy::NativeWhenPossible)
|
||||
}
|
||||
|
||||
impl<B, E, Block> Client<B, E, Block> where
|
||||
B: backend::Backend<Block, Blake2Hasher, RlpCodec>,
|
||||
E: CallExecutor<Block, Blake2Hasher, RlpCodec>,
|
||||
Block: BlockT,
|
||||
{
|
||||
/// Creates new Substrate Client with given blockchain and code executor.
|
||||
pub fn new<S: BuildStorage>(
|
||||
backend: Arc<B>,
|
||||
executor: E,
|
||||
build_genesis_storage: S,
|
||||
execution_strategy: ExecutionStrategy,
|
||||
) -> error::Result<Self> {
|
||||
if backend.blockchain().header(BlockId::Number(Zero::zero()))?.is_none() {
|
||||
let genesis_storage = build_genesis_storage.build_storage()?;
|
||||
let genesis_block = genesis::construct_genesis_block::<Block>(&genesis_storage);
|
||||
info!("Initialising Genesis block/state (state: {}, header-hash: {})", genesis_block.header().state_root(), genesis_block.header().hash());
|
||||
let mut op = backend.begin_operation(BlockId::Hash(Default::default()))?;
|
||||
op.reset_storage(genesis_storage.into_iter())?;
|
||||
op.set_block_data(genesis_block.deconstruct().0, Some(vec![]), None, true)?;
|
||||
backend.commit_operation(op)?;
|
||||
}
|
||||
Ok(Client {
|
||||
backend,
|
||||
executor,
|
||||
storage_notifications: Default::default(),
|
||||
import_notification_sinks: Default::default(),
|
||||
import_lock: Default::default(),
|
||||
importing_block: Default::default(),
|
||||
execution_strategy,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a reference to the state at a given block.
|
||||
pub fn state_at(&self, block: &BlockId<Block>) -> error::Result<B::State> {
|
||||
self.backend.state_at(*block)
|
||||
}
|
||||
|
||||
/// Expose backend reference. To be used in tests only
|
||||
pub fn backend(&self) -> &Arc<B> {
|
||||
&self.backend
|
||||
}
|
||||
|
||||
/// Return single storage entry of contract under given address in state in a block of given hash.
|
||||
pub fn storage(&self, id: &BlockId<Block>, key: &StorageKey) -> error::Result<Option<StorageData>> {
|
||||
Ok(self.state_at(id)?
|
||||
.storage(&key.0).map_err(|e| error::Error::from_state(Box::new(e)))?
|
||||
.map(StorageData))
|
||||
}
|
||||
|
||||
/// Get the code at a given block.
|
||||
pub fn code_at(&self, id: &BlockId<Block>) -> error::Result<Vec<u8>> {
|
||||
Ok(self.storage(id, &StorageKey(b":code".to_vec()))?
|
||||
.expect("None is returned if there's no value stored for the given key; ':code' key is always defined; qed").0)
|
||||
}
|
||||
|
||||
/// Get the set of authorities at a given block.
|
||||
pub fn authorities_at(&self, id: &BlockId<Block>) -> error::Result<Vec<AuthorityId>> {
|
||||
match self.backend.blockchain().cache().and_then(|cache| cache.authorities_at(*id)) {
|
||||
Some(cached_value) => Ok(cached_value),
|
||||
None => self.executor.call(id, "authorities",&[])
|
||||
.and_then(|r| Vec::<AuthorityId>::decode(&mut &r.return_data[..])
|
||||
.ok_or(error::ErrorKind::AuthLenInvalid.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the RuntimeVersion at a given block.
|
||||
pub fn runtime_version_at(&self, id: &BlockId<Block>) -> error::Result<RuntimeVersion> {
|
||||
// TODO: Post Poc-2 return an error if version is missing
|
||||
self.executor.runtime_version(id)
|
||||
}
|
||||
|
||||
/// Get call executor reference.
|
||||
pub fn executor(&self) -> &E {
|
||||
&self.executor
|
||||
}
|
||||
|
||||
/// Returns the runtime metadata as JSON.
|
||||
pub fn json_metadata(&self, id: &BlockId<Block>) -> error::Result<String> {
|
||||
self.executor.call(id, "json_metadata",&[])
|
||||
.and_then(|r| Vec::<JsonMetadataDecodable>::decode(&mut &r.return_data[..])
|
||||
.ok_or("JSON Metadata decoding failed".into()))
|
||||
.and_then(|metadata| {
|
||||
let mut json = metadata.into_iter().enumerate().fold(String::from("{"),
|
||||
|mut json, (i, m)| {
|
||||
if i > 0 {
|
||||
json.push_str(",");
|
||||
}
|
||||
let (mtype, val) = m.into_json_string();
|
||||
json.push_str(&format!(r#" "{}": {}"#, mtype, val));
|
||||
json
|
||||
}
|
||||
);
|
||||
json.push_str(" }");
|
||||
|
||||
Ok(json)
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads storage value at a given block + key, returning read proof.
|
||||
pub fn read_proof(&self, id: &BlockId<Block>, key: &[u8]) -> error::Result<Vec<Vec<u8>>> {
|
||||
self.state_at(id)
|
||||
.and_then(|state| prove_read(state, key)
|
||||
.map(|(_, proof)| proof)
|
||||
.map_err(Into::into))
|
||||
}
|
||||
|
||||
/// Execute a call to a contract on top of state in a block of given hash
|
||||
/// AND returning execution proof.
|
||||
///
|
||||
/// No changes are made.
|
||||
pub fn execution_proof(&self, id: &BlockId<Block>, method: &str, call_data: &[u8]) -> error::Result<(Vec<u8>, Vec<Vec<u8>>)> {
|
||||
self.state_at(id).and_then(|state| self.executor.prove_at_state(state, &mut Default::default(), method, call_data))
|
||||
}
|
||||
|
||||
/// Reads given header and generates CHT-based header proof.
|
||||
pub fn header_proof(&self, id: &BlockId<Block>) -> error::Result<(Block::Header, Vec<Vec<u8>>)> {
|
||||
self.header_proof_with_cht_size(id, cht::SIZE)
|
||||
}
|
||||
|
||||
/// Reads given header and generates CHT-based header proof for CHT of given size.
|
||||
pub fn header_proof_with_cht_size(&self, id: &BlockId<Block>, cht_size: u64) -> error::Result<(Block::Header, Vec<Vec<u8>>)> {
|
||||
let proof_error = || error::ErrorKind::Backend(format!("Failed to generate header proof for {:?}", id));
|
||||
let header = self.header(id)?.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", id)))?;
|
||||
let block_num = *header.number();
|
||||
let cht_num = cht::block_to_cht_number(cht_size, block_num).ok_or_else(proof_error)?;
|
||||
let cht_start = cht::start_number(cht_size, cht_num);
|
||||
let headers = (cht_start.as_()..).map(|num| self.block_hash(As::sa(num)).unwrap_or_default());
|
||||
let proof = cht::build_proof::<Block::Header, Blake2Hasher, RlpCodec, _>(cht_size, cht_num, block_num, headers)
|
||||
.ok_or_else(proof_error)?;
|
||||
Ok((header, proof))
|
||||
}
|
||||
|
||||
/// Create a new block, built on the head of the chain.
|
||||
pub fn new_block(&self) -> error::Result<block_builder::BlockBuilder<B, E, Block, Blake2Hasher, RlpCodec>>
|
||||
where E: Clone
|
||||
{
|
||||
block_builder::BlockBuilder::new(self)
|
||||
}
|
||||
|
||||
/// Create a new block, built on top of `parent`.
|
||||
pub fn new_block_at(&self, parent: &BlockId<Block>) -> error::Result<block_builder::BlockBuilder<B, E, Block, Blake2Hasher, RlpCodec>>
|
||||
where E: Clone
|
||||
{
|
||||
block_builder::BlockBuilder::at_block(parent, &self)
|
||||
}
|
||||
|
||||
/// Set up the native execution environment to call into a native runtime code.
|
||||
pub fn call_api<A, R>(&self, function: &'static str, args: &A) -> error::Result<R>
|
||||
where A: Encode, R: Decode
|
||||
{
|
||||
self.call_api_at(&BlockId::Number(self.info()?.chain.best_number), function, args)
|
||||
}
|
||||
|
||||
/// Call a runtime function at given block.
|
||||
pub fn call_api_at<A, R>(&self, at: &BlockId<Block>, function: &'static str, args: &A) -> error::Result<R>
|
||||
where A: Encode, R: Decode
|
||||
{
|
||||
let parent = at;
|
||||
let header = <<Block as BlockT>::Header as HeaderT>::new(
|
||||
self.block_number_from_id(&parent)?
|
||||
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", parent)))? + As::sa(1),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
self.block_hash_from_id(&parent)?
|
||||
.ok_or_else(|| error::ErrorKind::UnknownBlock(format!("{:?}", parent)))?,
|
||||
Default::default()
|
||||
);
|
||||
self.state_at(&parent).and_then(|state| {
|
||||
let mut overlay = Default::default();
|
||||
let execution_manager = || ExecutionManager::Both(|wasm_result, native_result| {
|
||||
warn!("Consensus error between wasm and native runtime execution at block {:?}", at);
|
||||
warn!(" Function {:?}", function);
|
||||
warn!(" Native result {:?}", native_result);
|
||||
warn!(" Wasm result {:?}", wasm_result);
|
||||
wasm_result
|
||||
});
|
||||
self.executor().call_at_state(
|
||||
&state,
|
||||
&mut overlay,
|
||||
"initialise_block",
|
||||
&header.encode(),
|
||||
execution_manager()
|
||||
)?;
|
||||
let (r, _) = args.using_encoded(|input|
|
||||
self.executor().call_at_state(
|
||||
&state,
|
||||
&mut overlay,
|
||||
function,
|
||||
input,
|
||||
execution_manager()
|
||||
))?;
|
||||
Ok(R::decode(&mut &r[..])
|
||||
.ok_or_else(|| error::Error::from(error::ErrorKind::CallResultDecode(function)))?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check a header's justification.
|
||||
pub fn check_justification(
|
||||
&self,
|
||||
header: <Block as BlockT>::Header,
|
||||
justification: ::bft::UncheckedJustification<Block::Hash>,
|
||||
) -> error::Result<JustifiedHeader<Block>> {
|
||||
let parent_hash = header.parent_hash().clone();
|
||||
let authorities = self.authorities_at(&BlockId::Hash(parent_hash))?;
|
||||
let just = ::bft::check_justification::<Block>(&authorities[..], parent_hash, justification)
|
||||
.map_err(|_|
|
||||
error::ErrorKind::BadJustification(
|
||||
format!("{}", header.hash())
|
||||
)
|
||||
)?;
|
||||
Ok(JustifiedHeader {
|
||||
header,
|
||||
justification: just,
|
||||
authorities,
|
||||
})
|
||||
}
|
||||
|
||||
/// Queue a block for import.
|
||||
pub fn import_block(
|
||||
&self,
|
||||
origin: BlockOrigin,
|
||||
header: JustifiedHeader<Block>,
|
||||
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
|
||||
) -> error::Result<ImportResult> {
|
||||
let (header, justification, authorities) = header.into_inner();
|
||||
let parent_hash = header.parent_hash().clone();
|
||||
match self.backend.blockchain().status(BlockId::Hash(parent_hash))? {
|
||||
blockchain::BlockStatus::InChain => {},
|
||||
blockchain::BlockStatus::Unknown => return Ok(ImportResult::UnknownParent),
|
||||
}
|
||||
let hash = header.hash();
|
||||
let _import_lock = self.import_lock.lock();
|
||||
let height: u64 = header.number().as_();
|
||||
*self.importing_block.write() = Some(hash);
|
||||
let result = self.execute_and_import_block(origin, hash, header, justification, body, authorities);
|
||||
*self.importing_block.write() = None;
|
||||
telemetry!("block.import";
|
||||
"height" => height,
|
||||
"best" => ?hash,
|
||||
"origin" => ?origin
|
||||
);
|
||||
result
|
||||
}
|
||||
|
||||
fn execute_and_import_block(
|
||||
&self,
|
||||
origin: BlockOrigin,
|
||||
hash: Block::Hash,
|
||||
header: Block::Header,
|
||||
justification: bft::Justification<Block::Hash>,
|
||||
body: Option<Vec<Block::Extrinsic>>,
|
||||
authorities: Vec<AuthorityId>,
|
||||
) -> error::Result<ImportResult> {
|
||||
let parent_hash = header.parent_hash().clone();
|
||||
match self.backend.blockchain().status(BlockId::Hash(hash))? {
|
||||
blockchain::BlockStatus::InChain => return Ok(ImportResult::AlreadyInChain),
|
||||
blockchain::BlockStatus::Unknown => {},
|
||||
}
|
||||
|
||||
let mut transaction = self.backend.begin_operation(BlockId::Hash(parent_hash))?;
|
||||
let (storage_update, storage_changes) = match transaction.state()? {
|
||||
Some(transaction_state) => {
|
||||
let mut overlay = Default::default();
|
||||
let mut r = self.executor.call_at_state(
|
||||
transaction_state,
|
||||
&mut overlay,
|
||||
"execute_block",
|
||||
&<Block as BlockT>::new(header.clone(), body.clone().unwrap_or_default()).encode(),
|
||||
match (origin, self.execution_strategy) {
|
||||
(BlockOrigin::NetworkInitialSync, _) | (_, ExecutionStrategy::NativeWhenPossible) =>
|
||||
ExecutionManager::NativeWhenPossible,
|
||||
(_, ExecutionStrategy::AlwaysWasm) => ExecutionManager::AlwaysWasm,
|
||||
_ => ExecutionManager::Both(|wasm_result, native_result| {
|
||||
warn!("Consensus error between wasm and native block execution at block {}", hash);
|
||||
warn!(" Header {:?}", header);
|
||||
warn!(" Native result {:?}", native_result);
|
||||
warn!(" Wasm result {:?}", wasm_result);
|
||||
telemetry!("block.execute.consensus_failure";
|
||||
"hash" => ?hash,
|
||||
"origin" => ?origin,
|
||||
"header" => ?header
|
||||
);
|
||||
wasm_result
|
||||
}),
|
||||
},
|
||||
);
|
||||
let (_, storage_update) = r?;
|
||||
overlay.commit_prospective();
|
||||
(Some(storage_update), Some(overlay.into_committed()))
|
||||
},
|
||||
None => (None, None)
|
||||
};
|
||||
|
||||
let is_new_best = header.number() == &(self.backend.blockchain().info()?.best_number + One::one());
|
||||
trace!("Imported {}, (#{}), best={}, origin={:?}", hash, header.number(), is_new_best, origin);
|
||||
let unchecked: bft::UncheckedJustification<_> = justification.uncheck().into();
|
||||
transaction.set_block_data(header.clone(), body, Some(unchecked.into()), is_new_best)?;
|
||||
transaction.update_authorities(authorities);
|
||||
if let Some(storage_update) = storage_update {
|
||||
transaction.update_storage(storage_update)?;
|
||||
}
|
||||
self.backend.commit_operation(transaction)?;
|
||||
|
||||
if origin == BlockOrigin::NetworkBroadcast || origin == BlockOrigin::Own || origin == BlockOrigin::ConsensusBroadcast {
|
||||
|
||||
if let Some(storage_changes) = storage_changes {
|
||||
// TODO [ToDr] How to handle re-orgs? Should we re-emit all storage changes?
|
||||
self.storage_notifications.lock()
|
||||
.trigger(&hash, storage_changes);
|
||||
}
|
||||
|
||||
let notification = BlockImportNotification::<Block> {
|
||||
hash: hash,
|
||||
origin: origin,
|
||||
header: header,
|
||||
is_new_best: is_new_best,
|
||||
};
|
||||
self.import_notification_sinks.lock()
|
||||
.retain(|sink| sink.unbounded_send(notification.clone()).is_ok());
|
||||
}
|
||||
Ok(ImportResult::Queued)
|
||||
}
|
||||
|
||||
/// Attempts to revert the chain by `n` blocks. Returns the number of blocks that were
|
||||
/// successfully reverted.
|
||||
pub fn revert(&self, n: NumberFor<Block>) -> error::Result<NumberFor<Block>> {
|
||||
Ok(self.backend.revert(n)?)
|
||||
}
|
||||
|
||||
/// Get blockchain info.
|
||||
pub fn info(&self) -> error::Result<ClientInfo<Block>> {
|
||||
let info = self.backend.blockchain().info().map_err(|e| error::Error::from_blockchain(Box::new(e)))?;
|
||||
Ok(ClientInfo {
|
||||
chain: info,
|
||||
best_queued_hash: None,
|
||||
best_queued_number: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get block status.
|
||||
pub fn block_status(&self, id: &BlockId<Block>) -> error::Result<BlockStatus> {
|
||||
// TODO: more efficient implementation
|
||||
if let BlockId::Hash(ref h) = id {
|
||||
if self.importing_block.read().as_ref().map_or(false, |importing| h == importing) {
|
||||
return Ok(BlockStatus::Queued);
|
||||
}
|
||||
}
|
||||
match self.backend.blockchain().header(*id).map_err(|e| error::Error::from_blockchain(Box::new(e)))?.is_some() {
|
||||
true => Ok(BlockStatus::InChain),
|
||||
false => Ok(BlockStatus::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get block hash by number.
|
||||
pub fn block_hash(&self, block_number: <<Block as BlockT>::Header as HeaderT>::Number) -> error::Result<Option<Block::Hash>> {
|
||||
self.backend.blockchain().hash(block_number)
|
||||
}
|
||||
|
||||
/// Convert an arbitrary block ID into a block hash.
|
||||
pub fn block_hash_from_id(&self, id: &BlockId<Block>) -> error::Result<Option<Block::Hash>> {
|
||||
match *id {
|
||||
BlockId::Hash(h) => Ok(Some(h)),
|
||||
BlockId::Number(n) => self.block_hash(n),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an arbitrary block ID into a block hash.
|
||||
pub fn block_number_from_id(&self, id: &BlockId<Block>) -> error::Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
|
||||
match *id {
|
||||
BlockId::Hash(_) => Ok(self.header(id)?.map(|h| h.number().clone())),
|
||||
BlockId::Number(n) => Ok(Some(n)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get block header by id.
|
||||
pub fn header(&self, id: &BlockId<Block>) -> error::Result<Option<<Block as BlockT>::Header>> {
|
||||
self.backend.blockchain().header(*id)
|
||||
}
|
||||
|
||||
/// Get block body by id.
|
||||
pub fn body(&self, id: &BlockId<Block>) -> error::Result<Option<Vec<<Block as BlockT>::Extrinsic>>> {
|
||||
self.backend.blockchain().body(*id)
|
||||
}
|
||||
|
||||
/// Get block justification set by id.
|
||||
pub fn justification(&self, id: &BlockId<Block>) -> error::Result<Option<Justification<Block::Hash>>> {
|
||||
self.backend.blockchain().justification(*id)
|
||||
}
|
||||
|
||||
/// Get full block by id.
|
||||
pub fn block(&self, id: &BlockId<Block>) -> error::Result<Option<SignedBlock<Block::Header, Block::Extrinsic, Block::Hash>>> {
|
||||
Ok(match (self.header(id)?, self.body(id)?, self.justification(id)?) {
|
||||
(Some(header), Some(extrinsics), Some(justification)) =>
|
||||
Some(SignedBlock { block: RuntimeBlock { header, extrinsics }, justification }),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get best block header.
|
||||
pub fn best_block_header(&self) -> error::Result<<Block as BlockT>::Header> {
|
||||
let info = self.backend.blockchain().info().map_err(|e| error::Error::from_blockchain(Box::new(e)))?;
|
||||
Ok(self.header(&BlockId::Hash(info.best_hash))?.expect("Best block header must always exist"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block> bft::BlockImport<Block> for Client<B, E, Block>
|
||||
where
|
||||
B: backend::Backend<Block, Blake2Hasher, RlpCodec>,
|
||||
E: CallExecutor<Block, Blake2Hasher, RlpCodec>,
|
||||
Block: BlockT,
|
||||
{
|
||||
fn import_block(
|
||||
&self,
|
||||
block: Block,
|
||||
justification: ::bft::Justification<Block::Hash>,
|
||||
authorities: &[AuthorityId]
|
||||
) -> bool {
|
||||
let (header, extrinsics) = block.deconstruct();
|
||||
let justified_header = JustifiedHeader {
|
||||
header: header,
|
||||
justification,
|
||||
authorities: authorities.to_vec(),
|
||||
};
|
||||
|
||||
self.import_block(BlockOrigin::ConsensusBroadcast, justified_header, Some(extrinsics)).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block> bft::Authorities<Block> for Client<B, E, Block>
|
||||
where
|
||||
B: backend::Backend<Block, Blake2Hasher, RlpCodec>,
|
||||
E: CallExecutor<Block, Blake2Hasher, RlpCodec>,
|
||||
Block: BlockT,
|
||||
{
|
||||
fn authorities(&self, at: &BlockId<Block>) -> Result<Vec<AuthorityId>, bft::Error> {
|
||||
let on_chain_version: Result<_, bft::Error> = self.runtime_version_at(at)
|
||||
.map_err(|e| { trace!("Error getting runtime version {:?}", e); bft::ErrorKind::RuntimeVersionMissing.into() });
|
||||
let on_chain_version = on_chain_version?;
|
||||
let native_version: Result<_, bft::Error> = self.executor.native_runtime_version()
|
||||
.ok_or_else(|| bft::ErrorKind::NativeRuntimeMissing.into());
|
||||
let native_version = native_version?;
|
||||
if !on_chain_version.can_author_with(&native_version) {
|
||||
return Err(bft::ErrorKind::IncompatibleAuthoringRuntime(on_chain_version, native_version).into())
|
||||
}
|
||||
self.authorities_at(at).map_err(|_| {
|
||||
let descriptor = format!("{:?}", at);
|
||||
bft::ErrorKind::StateUnavailable(descriptor).into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block> BlockchainEvents<Block> for Client<B, E, Block>
|
||||
where
|
||||
E: CallExecutor<Block, Blake2Hasher, RlpCodec>,
|
||||
Block: BlockT,
|
||||
{
|
||||
/// Get block import event stream.
|
||||
fn import_notification_stream(&self) -> BlockchainEventStream<Block> {
|
||||
let (sink, stream) = mpsc::unbounded();
|
||||
self.import_notification_sinks.lock().push(sink);
|
||||
stream
|
||||
}
|
||||
|
||||
/// Get storage changes event stream.
|
||||
fn storage_changes_notification_stream(&self, filter_keys: Option<&[StorageKey]>) -> error::Result<StorageEventStream<Block::Hash>> {
|
||||
Ok(self.storage_notifications.lock().listen(filter_keys))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block> ChainHead<Block> for Client<B, E, Block>
|
||||
where
|
||||
B: backend::Backend<Block, Blake2Hasher, RlpCodec>,
|
||||
E: CallExecutor<Block, Blake2Hasher, RlpCodec>,
|
||||
Block: BlockT,
|
||||
{
|
||||
fn best_block_header(&self) -> error::Result<<Block as BlockT>::Header> {
|
||||
Client::best_block_header(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block> BlockBody<Block> for Client<B, E, Block>
|
||||
where
|
||||
B: backend::Backend<Block, Blake2Hasher, RlpCodec>,
|
||||
E: CallExecutor<Block, Blake2Hasher, RlpCodec>,
|
||||
Block: BlockT,
|
||||
{
|
||||
fn block_body(&self, id: &BlockId<Block>) -> error::Result<Option<Vec<<Block as BlockT>::Extrinsic>>> {
|
||||
self.body(id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use keyring::Keyring;
|
||||
use test_client::{self, TestClient};
|
||||
use test_client::client::BlockOrigin;
|
||||
use test_client::client::backend::Backend as TestBackend;
|
||||
use test_client::BlockBuilderExt;
|
||||
use test_client::runtime::Transfer;
|
||||
|
||||
#[test]
|
||||
fn client_initialises_from_genesis_ok() {
|
||||
let client = test_client::new();
|
||||
|
||||
assert_eq!(client.call_api::<_, u64>("balance_of", &Keyring::Alice.to_raw_public()).unwrap(), 1000);
|
||||
assert_eq!(client.call_api::<_, u64>("balance_of", &Keyring::Ferdie.to_raw_public()).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorities_call_works() {
|
||||
let client = test_client::new();
|
||||
|
||||
assert_eq!(client.info().unwrap().chain.best_number, 0);
|
||||
assert_eq!(client.authorities_at(&BlockId::Number(0)).unwrap(), vec![
|
||||
Keyring::Alice.to_raw_public().into(),
|
||||
Keyring::Bob.to_raw_public().into(),
|
||||
Keyring::Charlie.to_raw_public().into()
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_builder_works_with_no_transactions() {
|
||||
let client = test_client::new();
|
||||
|
||||
let builder = client.new_block().unwrap();
|
||||
|
||||
client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
|
||||
|
||||
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_builder_works_with_transactions() {
|
||||
let client = test_client::new();
|
||||
|
||||
let mut builder = client.new_block().unwrap();
|
||||
|
||||
builder.push_transfer(Transfer {
|
||||
from: Keyring::Alice.to_raw_public().into(),
|
||||
to: Keyring::Ferdie.to_raw_public().into(),
|
||||
amount: 42,
|
||||
nonce: 0,
|
||||
}).unwrap();
|
||||
|
||||
client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
|
||||
|
||||
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
||||
assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap());
|
||||
assert_eq!(client.call_api::<_, u64>("balance_of", &Keyring::Alice.to_raw_public()).unwrap(), 958);
|
||||
assert_eq!(client.call_api::<_, u64>("balance_of", &Keyring::Ferdie.to_raw_public()).unwrap(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_uses_authorities_from_blockchain_cache() {
|
||||
let client = test_client::new();
|
||||
test_client::client::in_mem::cache_authorities_at(
|
||||
client.backend().blockchain(),
|
||||
Default::default(),
|
||||
Some(vec![[1u8; 32].into()]));
|
||||
assert_eq!(client.authorities_at(
|
||||
&BlockId::Hash(Default::default())).unwrap(),
|
||||
vec![[1u8; 32].into()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_builder_does_not_include_invalid() {
|
||||
let client = test_client::new();
|
||||
|
||||
let mut builder = client.new_block().unwrap();
|
||||
|
||||
builder.push_transfer(Transfer {
|
||||
from: Keyring::Alice.to_raw_public().into(),
|
||||
to: Keyring::Ferdie.to_raw_public().into(),
|
||||
amount: 42,
|
||||
nonce: 0,
|
||||
}).unwrap();
|
||||
|
||||
assert!(builder.push_transfer(Transfer {
|
||||
from: Keyring::Eve.to_raw_public().into(),
|
||||
to: Keyring::Alice.to_raw_public().into(),
|
||||
amount: 42,
|
||||
nonce: 0,
|
||||
}).is_err());
|
||||
|
||||
client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
|
||||
|
||||
assert_eq!(client.info().unwrap().chain.best_number, 1);
|
||||
assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap());
|
||||
assert_eq!(client.body(&BlockId::Number(1)).unwrap().unwrap().len(), 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_metadata() {
|
||||
let client = test_client::new();
|
||||
|
||||
let mut builder = client.new_block().unwrap();
|
||||
|
||||
builder.push_transfer(Transfer {
|
||||
from: Keyring::Alice.to_raw_public().into(),
|
||||
to: Keyring::Ferdie.to_raw_public().into(),
|
||||
amount: 42,
|
||||
nonce: 0,
|
||||
}).unwrap();
|
||||
|
||||
assert!(builder.push_transfer(Transfer {
|
||||
from: Keyring::Eve.to_raw_public().into(),
|
||||
to: Keyring::Alice.to_raw_public().into(),
|
||||
amount: 42,
|
||||
nonce: 0,
|
||||
}).is_err());
|
||||
|
||||
client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
client.json_metadata(&BlockId::Number(1)).unwrap(),
|
||||
r#"{ "events": { "name": "Test", "events": { "event": hallo } } }"#
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot client possible errors.
|
||||
|
||||
use std;
|
||||
use state_machine;
|
||||
use runtime_primitives::ApplyError;
|
||||
|
||||
error_chain! {
|
||||
errors {
|
||||
/// Backend error.
|
||||
Backend(s: String) {
|
||||
description("Unrecoverable backend error"),
|
||||
display("Backend error: {}", s),
|
||||
}
|
||||
|
||||
/// Unknown block.
|
||||
UnknownBlock(h: String) {
|
||||
description("unknown block"),
|
||||
display("UnknownBlock: {}", &*h),
|
||||
}
|
||||
|
||||
/// Applying extrinsic error.
|
||||
ApplyExtinsicFailed(e: ApplyError) {
|
||||
description("Extrinsic error"),
|
||||
display("Extrinsic error: {:?}", e),
|
||||
}
|
||||
|
||||
/// Execution error.
|
||||
Execution(e: Box<state_machine::Error>) {
|
||||
description("execution error"),
|
||||
display("Execution: {}", e),
|
||||
}
|
||||
|
||||
/// Blockchain error.
|
||||
Blockchain(e: Box<std::error::Error + Send>) {
|
||||
description("Blockchain error"),
|
||||
display("Blockchain: {}", e),
|
||||
}
|
||||
|
||||
/// Invalid state data.
|
||||
AuthLenEmpty {
|
||||
description("authority count state error"),
|
||||
display("Current state of blockchain has no authority count value"),
|
||||
}
|
||||
|
||||
/// Invalid state data.
|
||||
AuthEmpty(i: u32) {
|
||||
description("authority value state error"),
|
||||
display("Current state of blockchain has no authority value for index {}", i),
|
||||
}
|
||||
|
||||
/// Invalid state data.
|
||||
AuthLenInvalid {
|
||||
description("authority count state error"),
|
||||
display("Current state of blockchain has invalid authority count value"),
|
||||
}
|
||||
|
||||
/// Cound not get runtime version.
|
||||
VersionInvalid {
|
||||
description("Runtime version error"),
|
||||
display("On-chain runtime does not specify version"),
|
||||
}
|
||||
|
||||
/// Invalid state data.
|
||||
AuthInvalid(i: u32) {
|
||||
description("authority value state error"),
|
||||
display("Current state of blockchain has invalid authority value for index {}", i),
|
||||
}
|
||||
|
||||
/// Bad justification for header.
|
||||
BadJustification(h: String) {
|
||||
description("bad justification for header"),
|
||||
display("bad justification for header: {}", &*h),
|
||||
}
|
||||
|
||||
/// Not available on light client.
|
||||
NotAvailableOnLightClient {
|
||||
description("not available on light client"),
|
||||
display("This method is not currently available when running in light client mode"),
|
||||
}
|
||||
|
||||
/// Invalid remote header proof.
|
||||
InvalidHeaderProof {
|
||||
description("invalid header proof"),
|
||||
display("Remote node has responded with invalid header proof"),
|
||||
}
|
||||
|
||||
/// Invalid remote execution proof.
|
||||
InvalidExecutionProof {
|
||||
description("invalid execution proof"),
|
||||
display("Remote node has responded with invalid execution proof"),
|
||||
}
|
||||
|
||||
/// Remote fetch has been cancelled.
|
||||
RemoteFetchCancelled {
|
||||
description("remote fetch cancelled"),
|
||||
display("Remote data fetch has been cancelled"),
|
||||
}
|
||||
|
||||
/// Remote fetch has been failed.
|
||||
RemoteFetchFailed {
|
||||
description("remote fetch failed"),
|
||||
display("Remote data fetch has been failed"),
|
||||
}
|
||||
|
||||
/// Error decoding call result.
|
||||
CallResultDecode(method: &'static str) {
|
||||
description("Error decoding call result")
|
||||
display("Error decoding call result of {}", method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO [ToDr] Temporary, state_machine::Error should be a regular error not Box.
|
||||
impl From<Box<state_machine::Error>> for Error {
|
||||
fn from(e: Box<state_machine::Error>) -> Self {
|
||||
ErrorKind::Execution(e).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<state_machine::backend::Void> for Error {
|
||||
fn from(e: state_machine::backend::Void) -> Self {
|
||||
match e {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Chain a blockchain error.
|
||||
pub fn from_blockchain(e: Box<std::error::Error + Send>) -> Self {
|
||||
ErrorKind::Blockchain(e).into()
|
||||
}
|
||||
|
||||
/// Chain a state error.
|
||||
pub fn from_state(e: Box<state_machine::Error + Send>) -> Self {
|
||||
ErrorKind::Execution(e).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl state_machine::Error for Error {}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tool for creating the genesis block.
|
||||
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash as HashT, Zero};
|
||||
use runtime_primitives::StorageMap;
|
||||
|
||||
/// Create a genesis block, given the initial storage.
|
||||
pub fn construct_genesis_block<
|
||||
Block: BlockT
|
||||
> (
|
||||
storage: &StorageMap
|
||||
) -> Block {
|
||||
let state_root = <<<Block as BlockT>::Header as HeaderT>::Hashing as HashT>::trie_root(storage.clone().into_iter());
|
||||
let extrinsics_root = <<<Block as BlockT>::Header as HeaderT>::Hashing as HashT>::trie_root(::std::iter::empty::<(&[u8], &[u8])>());
|
||||
Block::new(
|
||||
<<Block as BlockT>::Header as HeaderT>::new(
|
||||
Zero::zero(),
|
||||
extrinsics_root,
|
||||
state_root,
|
||||
Default::default(),
|
||||
Default::default()
|
||||
),
|
||||
Default::default()
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codec::{Encode, Decode, Joiner};
|
||||
use keyring::Keyring;
|
||||
use executor::NativeExecutionDispatch;
|
||||
use state_machine::{execute, OverlayedChanges, ExecutionStrategy};
|
||||
use state_machine::backend::InMemory;
|
||||
use test_client;
|
||||
use test_client::runtime::genesismap::{GenesisConfig, additional_storage_with_genesis};
|
||||
use test_client::runtime::{Hash, Transfer, Block, BlockNumber, Header, Digest, Extrinsic};
|
||||
use primitives::{Blake2Hasher, RlpCodec, ed25519::{Public, Pair}};
|
||||
|
||||
native_executor_instance!(Executor, test_client::runtime::api::dispatch, test_client::runtime::VERSION, include_bytes!("../../test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm"));
|
||||
|
||||
fn executor() -> ::executor::NativeExecutor<Executor> {
|
||||
NativeExecutionDispatch::new()
|
||||
}
|
||||
|
||||
fn construct_block(backend: &InMemory<Blake2Hasher, RlpCodec>, number: BlockNumber, parent_hash: Hash, state_root: Hash, txs: Vec<Transfer>) -> (Vec<u8>, Hash) {
|
||||
use triehash::ordered_trie_root;
|
||||
|
||||
let transactions = txs.into_iter().map(|tx| {
|
||||
let signature = Pair::from(Keyring::from_public(Public::from_raw(tx.from.0)).unwrap())
|
||||
.sign(&tx.encode()).into();
|
||||
|
||||
Extrinsic { transfer: tx, signature }
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
let extrinsics_root = ordered_trie_root::<Blake2Hasher, _, _>(transactions.iter().map(Encode::encode)).into();
|
||||
|
||||
println!("root before: {:?}", extrinsics_root);
|
||||
let mut header = Header {
|
||||
parent_hash,
|
||||
number,
|
||||
state_root,
|
||||
extrinsics_root,
|
||||
digest: Digest { logs: vec![], },
|
||||
};
|
||||
let hash = header.hash();
|
||||
let mut overlay = OverlayedChanges::default();
|
||||
|
||||
execute(
|
||||
backend,
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"initialise_block",
|
||||
&header.encode(),
|
||||
ExecutionStrategy::NativeWhenPossible,
|
||||
).unwrap();
|
||||
|
||||
for tx in transactions.iter() {
|
||||
execute(
|
||||
backend,
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"apply_extrinsic",
|
||||
&tx.encode(),
|
||||
ExecutionStrategy::NativeWhenPossible,
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
let (ret_data, _) = execute(
|
||||
backend,
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"finalise_block",
|
||||
&[],
|
||||
ExecutionStrategy::NativeWhenPossible,
|
||||
).unwrap();
|
||||
header = Header::decode(&mut &ret_data[..]).unwrap();
|
||||
println!("root after: {:?}", header.extrinsics_root);
|
||||
|
||||
(vec![].and(&Block { header, extrinsics: transactions }), hash)
|
||||
}
|
||||
|
||||
fn block1(genesis_hash: Hash, backend: &InMemory<Blake2Hasher, RlpCodec>) -> (Vec<u8>, Hash) {
|
||||
construct_block(
|
||||
backend,
|
||||
1,
|
||||
genesis_hash,
|
||||
hex!("25e5b37074063ab75c889326246640729b40d0c86932edc527bc80db0e04fe5c").into(),
|
||||
vec![Transfer {
|
||||
from: Keyring::One.to_raw_public().into(),
|
||||
to: Keyring::Two.to_raw_public().into(),
|
||||
amount: 69,
|
||||
nonce: 0,
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_genesis_should_work_with_native() {
|
||||
let mut storage = GenesisConfig::new_simple(
|
||||
vec![Keyring::One.to_raw_public().into(), Keyring::Two.to_raw_public().into()], 1000
|
||||
).genesis_map();
|
||||
let block = construct_genesis_block::<Block>(&storage);
|
||||
let genesis_hash = block.header.hash();
|
||||
storage.extend(additional_storage_with_genesis(&block).into_iter());
|
||||
|
||||
let backend = InMemory::from(storage);
|
||||
let (b1data, _b1hash) = block1(genesis_hash, &backend);
|
||||
|
||||
let mut overlay = OverlayedChanges::default();
|
||||
let _ = execute(
|
||||
&backend,
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"execute_block",
|
||||
&b1data,
|
||||
ExecutionStrategy::NativeWhenPossible,
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_genesis_should_work_with_wasm() {
|
||||
let mut storage = GenesisConfig::new_simple(
|
||||
vec![Keyring::One.to_raw_public().into(), Keyring::Two.to_raw_public().into()], 1000
|
||||
).genesis_map();
|
||||
let block = construct_genesis_block::<Block>(&storage);
|
||||
let genesis_hash = block.header.hash();
|
||||
storage.extend(additional_storage_with_genesis(&block).into_iter());
|
||||
|
||||
let backend = InMemory::from(storage);
|
||||
let (b1data, _b1hash) = block1(genesis_hash, &backend);
|
||||
|
||||
let mut overlay = OverlayedChanges::default();
|
||||
let _ = execute(
|
||||
&backend,
|
||||
&mut overlay,
|
||||
&executor(),
|
||||
"execute_block",
|
||||
&b1data,
|
||||
ExecutionStrategy::AlwaysWasm,
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn construct_genesis_with_bad_transaction_should_panic() {
|
||||
let mut storage = GenesisConfig::new_simple(
|
||||
vec![Keyring::One.to_raw_public().into(), Keyring::Two.to_raw_public().into()], 68
|
||||
).genesis_map();
|
||||
let block = construct_genesis_block::<Block>(&storage);
|
||||
let genesis_hash = block.header.hash();
|
||||
storage.extend(additional_storage_with_genesis(&block).into_iter());
|
||||
|
||||
let backend = InMemory::from(storage);
|
||||
let (b1data, _b1hash) = block1(genesis_hash, &backend);
|
||||
|
||||
let mut overlay = OverlayedChanges::default();
|
||||
let _ = execute(
|
||||
&backend,
|
||||
&mut overlay,
|
||||
&Executor::new(),
|
||||
"execute_block",
|
||||
&b1data,
|
||||
ExecutionStrategy::NativeWhenPossible,
|
||||
).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! In memory client backend
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
use error;
|
||||
use backend;
|
||||
use light;
|
||||
use primitives::AuthorityId;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, NumberFor, As};
|
||||
use runtime_primitives::bft::Justification;
|
||||
use blockchain::{self, BlockStatus};
|
||||
use state_machine::backend::{Backend as StateBackend, InMemory};
|
||||
use patricia_trie::NodeCodec;
|
||||
use hashdb::Hasher;
|
||||
use heapsize::HeapSizeOf;
|
||||
|
||||
struct PendingBlock<B: BlockT> {
|
||||
block: StoredBlock<B>,
|
||||
is_best: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
enum StoredBlock<B: BlockT> {
|
||||
Header(B::Header, Option<Justification<B::Hash>>),
|
||||
Full(B, Option<Justification<B::Hash>>),
|
||||
}
|
||||
|
||||
impl<B: BlockT> StoredBlock<B> {
|
||||
fn new(header: B::Header, body: Option<Vec<B::Extrinsic>>, just: Option<Justification<B::Hash>>) -> Self {
|
||||
match body {
|
||||
Some(body) => StoredBlock::Full(B::new(header, body), just),
|
||||
None => StoredBlock::Header(header, just),
|
||||
}
|
||||
}
|
||||
|
||||
fn header(&self) -> &B::Header {
|
||||
match *self {
|
||||
StoredBlock::Header(ref h, _) => h,
|
||||
StoredBlock::Full(ref b, _) => b.header(),
|
||||
}
|
||||
}
|
||||
|
||||
fn justification(&self) -> Option<&Justification<B::Hash>> {
|
||||
match *self {
|
||||
StoredBlock::Header(_, ref j) | StoredBlock::Full(_, ref j) => j.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
fn extrinsics(&self) -> Option<&[B::Extrinsic]> {
|
||||
match *self {
|
||||
StoredBlock::Header(_, _) => None,
|
||||
StoredBlock::Full(ref b, _) => Some(b.extrinsics())
|
||||
}
|
||||
}
|
||||
|
||||
fn into_inner(self) -> (B::Header, Option<Vec<B::Extrinsic>>, Option<Justification<B::Hash>>) {
|
||||
match self {
|
||||
StoredBlock::Header(header, just) => (header, None, just),
|
||||
StoredBlock::Full(block, just) => {
|
||||
let (header, body) = block.deconstruct();
|
||||
(header, Some(body), just)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct BlockchainStorage<Block: BlockT> {
|
||||
blocks: HashMap<Block::Hash, StoredBlock<Block>>,
|
||||
hashes: HashMap<<<Block as BlockT>::Header as HeaderT>::Number, Block::Hash>,
|
||||
best_hash: Block::Hash,
|
||||
best_number: <<Block as BlockT>::Header as HeaderT>::Number,
|
||||
genesis_hash: Block::Hash,
|
||||
cht_roots: HashMap<NumberFor<Block>, Block::Hash>,
|
||||
}
|
||||
|
||||
/// In-memory blockchain. Supports concurrent reads.
|
||||
pub struct Blockchain<Block: BlockT> {
|
||||
storage: Arc<RwLock<BlockchainStorage<Block>>>,
|
||||
cache: Cache<Block>,
|
||||
}
|
||||
|
||||
struct Cache<Block: BlockT> {
|
||||
storage: Arc<RwLock<BlockchainStorage<Block>>>,
|
||||
authorities_at: RwLock<HashMap<Block::Hash, Option<Vec<AuthorityId>>>>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT + Clone> Clone for Blockchain<Block> {
|
||||
fn clone(&self) -> Self {
|
||||
let storage = Arc::new(RwLock::new(self.storage.read().clone()));
|
||||
Blockchain {
|
||||
storage: storage.clone(),
|
||||
cache: Cache {
|
||||
storage,
|
||||
authorities_at: RwLock::new(self.cache.authorities_at.read().clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> Blockchain<Block> {
|
||||
/// Get header hash of given block.
|
||||
pub fn id(&self, id: BlockId<Block>) -> Option<Block::Hash> {
|
||||
match id {
|
||||
BlockId::Hash(h) => Some(h),
|
||||
BlockId::Number(n) => self.storage.read().hashes.get(&n).cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new in-memory blockchain storage.
|
||||
pub fn new() -> Blockchain<Block> {
|
||||
let storage = Arc::new(RwLock::new(
|
||||
BlockchainStorage {
|
||||
blocks: HashMap::new(),
|
||||
hashes: HashMap::new(),
|
||||
best_hash: Default::default(),
|
||||
best_number: Zero::zero(),
|
||||
genesis_hash: Default::default(),
|
||||
cht_roots: HashMap::new(),
|
||||
}));
|
||||
Blockchain {
|
||||
storage: storage.clone(),
|
||||
cache: Cache {
|
||||
storage: storage,
|
||||
authorities_at: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a block header and associated data.
|
||||
pub fn insert(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
header: <Block as BlockT>::Header,
|
||||
justification: Option<Justification<Block::Hash>>,
|
||||
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
|
||||
is_new_best: bool
|
||||
) {
|
||||
let number = header.number().clone();
|
||||
let mut storage = self.storage.write();
|
||||
storage.blocks.insert(hash.clone(), StoredBlock::new(header, body, justification));
|
||||
storage.hashes.insert(number, hash.clone());
|
||||
if is_new_best {
|
||||
storage.best_hash = hash.clone();
|
||||
storage.best_number = number.clone();
|
||||
}
|
||||
if number == Zero::zero() {
|
||||
storage.genesis_hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare this blockchain with another in-mem blockchain
|
||||
pub fn equals_to(&self, other: &Self) -> bool {
|
||||
self.canon_equals_to(other) && self.storage.read().blocks == other.storage.read().blocks
|
||||
}
|
||||
|
||||
/// Compare canonical chain to other canonical chain.
|
||||
pub fn canon_equals_to(&self, other: &Self) -> bool {
|
||||
let this = self.storage.read();
|
||||
let other = other.storage.read();
|
||||
this.hashes == other.hashes
|
||||
&& this.best_hash == other.best_hash
|
||||
&& this.best_number == other.best_number
|
||||
&& this.genesis_hash == other.genesis_hash
|
||||
}
|
||||
|
||||
/// Insert CHT root.
|
||||
pub fn insert_cht_root(&self, block: NumberFor<Block>, cht_root: Block::Hash) {
|
||||
self.storage.write().cht_roots.insert(block, cht_root);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> blockchain::HeaderBackend<Block> for Blockchain<Block> {
|
||||
fn header(&self, id: BlockId<Block>) -> error::Result<Option<<Block as BlockT>::Header>> {
|
||||
Ok(self.id(id).and_then(|hash| {
|
||||
self.storage.read().blocks.get(&hash).map(|b| b.header().clone())
|
||||
}))
|
||||
}
|
||||
|
||||
fn info(&self) -> error::Result<blockchain::Info<Block>> {
|
||||
let storage = self.storage.read();
|
||||
Ok(blockchain::Info {
|
||||
best_hash: storage.best_hash,
|
||||
best_number: storage.best_number,
|
||||
genesis_hash: storage.genesis_hash,
|
||||
})
|
||||
}
|
||||
|
||||
fn status(&self, id: BlockId<Block>) -> error::Result<BlockStatus> {
|
||||
match self.id(id).map_or(false, |hash| self.storage.read().blocks.contains_key(&hash)) {
|
||||
true => Ok(BlockStatus::InChain),
|
||||
false => Ok(BlockStatus::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn number(&self, hash: Block::Hash) -> error::Result<Option<NumberFor<Block>>> {
|
||||
Ok(self.storage.read().blocks.get(&hash).map(|b| *b.header().number()))
|
||||
}
|
||||
|
||||
fn hash(&self, number: <<Block as BlockT>::Header as HeaderT>::Number) -> error::Result<Option<Block::Hash>> {
|
||||
Ok(self.id(BlockId::Number(number)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<Block: BlockT> blockchain::Backend<Block> for Blockchain<Block> {
|
||||
fn body(&self, id: BlockId<Block>) -> error::Result<Option<Vec<<Block as BlockT>::Extrinsic>>> {
|
||||
Ok(self.id(id).and_then(|hash| {
|
||||
self.storage.read().blocks.get(&hash)
|
||||
.and_then(|b| b.extrinsics().map(|x| x.to_vec()))
|
||||
}))
|
||||
}
|
||||
|
||||
fn justification(&self, id: BlockId<Block>) -> error::Result<Option<Justification<Block::Hash>>> {
|
||||
Ok(self.id(id).and_then(|hash| self.storage.read().blocks.get(&hash).and_then(|b|
|
||||
b.justification().map(|x| x.clone()))
|
||||
))
|
||||
}
|
||||
|
||||
fn cache(&self) -> Option<&blockchain::Cache<Block>> {
|
||||
Some(&self.cache)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> light::blockchain::Storage<Block> for Blockchain<Block>
|
||||
where
|
||||
Block::Hash: From<[u8; 32]>,
|
||||
{
|
||||
fn import_header(
|
||||
&self,
|
||||
is_new_best: bool,
|
||||
header: Block::Header,
|
||||
authorities: Option<Vec<AuthorityId>>
|
||||
) -> error::Result<()> {
|
||||
let hash = header.hash();
|
||||
let parent_hash = *header.parent_hash();
|
||||
self.insert(hash, header, None, None, is_new_best);
|
||||
if is_new_best {
|
||||
self.cache.insert(parent_hash, authorities);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cht_root(&self, _cht_size: u64, block: NumberFor<Block>) -> error::Result<Block::Hash> {
|
||||
self.storage.read().cht_roots.get(&block).cloned()
|
||||
.ok_or_else(|| error::ErrorKind::Backend(format!("CHT for block {} not exists", block)).into())
|
||||
}
|
||||
|
||||
fn cache(&self) -> Option<&blockchain::Cache<Block>> {
|
||||
Some(&self.cache)
|
||||
}
|
||||
}
|
||||
|
||||
/// In-memory operation.
|
||||
pub struct BlockImportOperation<Block: BlockT, H: Hasher, C: NodeCodec<H>> {
|
||||
pending_block: Option<PendingBlock<Block>>,
|
||||
pending_authorities: Option<Vec<AuthorityId>>,
|
||||
old_state: InMemory<H, C>,
|
||||
new_state: Option<InMemory<H, C>>,
|
||||
}
|
||||
|
||||
impl<Block, H, C> backend::BlockImportOperation<Block, H, C> for BlockImportOperation<Block, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
H::Out: HeapSizeOf,
|
||||
{
|
||||
type State = InMemory<H, C>;
|
||||
|
||||
fn state(&self) -> error::Result<Option<&Self::State>> {
|
||||
Ok(Some(&self.old_state))
|
||||
}
|
||||
|
||||
fn set_block_data(
|
||||
&mut self,
|
||||
header: <Block as BlockT>::Header,
|
||||
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
|
||||
justification: Option<Justification<Block::Hash>>,
|
||||
is_new_best: bool
|
||||
) -> error::Result<()> {
|
||||
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
|
||||
self.pending_block = Some(PendingBlock {
|
||||
block: StoredBlock::new(header, body, justification),
|
||||
is_best: is_new_best,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_authorities(&mut self, authorities: Vec<AuthorityId>) {
|
||||
self.pending_authorities = Some(authorities);
|
||||
}
|
||||
|
||||
fn update_storage(&mut self, update: <InMemory<H, C> as StateBackend<H, C>>::Transaction) -> error::Result<()> {
|
||||
self.new_state = Some(self.old_state.update(update));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, iter: I) -> error::Result<()> {
|
||||
self.new_state = Some(InMemory::from(iter.collect::<HashMap<_, _>>()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// In-memory backend. Keeps all states and blocks in memory. Useful for testing.
|
||||
pub struct Backend<Block, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>
|
||||
{
|
||||
states: RwLock<HashMap<Block::Hash, InMemory<H, C>>>,
|
||||
blockchain: Blockchain<Block>,
|
||||
}
|
||||
|
||||
impl<Block, H, C> Backend<Block, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>
|
||||
{
|
||||
/// Create a new instance of in-mem backend.
|
||||
pub fn new() -> Backend<Block, H, C> {
|
||||
Backend {
|
||||
states: RwLock::new(HashMap::new()),
|
||||
blockchain: Blockchain::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, H, C> backend::Backend<Block, H, C> for Backend<Block, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
H::Out: HeapSizeOf,
|
||||
C: NodeCodec<H> + Send + Sync,
|
||||
{
|
||||
type BlockImportOperation = BlockImportOperation<Block, H, C>;
|
||||
type Blockchain = Blockchain<Block>;
|
||||
type State = InMemory<H, C>;
|
||||
|
||||
fn begin_operation(&self, block: BlockId<Block>) -> error::Result<Self::BlockImportOperation> {
|
||||
let state = match block {
|
||||
BlockId::Hash(ref h) if h.clone() == Default::default() => Self::State::default(),
|
||||
_ => self.state_at(block)?,
|
||||
};
|
||||
|
||||
Ok(BlockImportOperation {
|
||||
pending_block: None,
|
||||
pending_authorities: None,
|
||||
old_state: state,
|
||||
new_state: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn commit_operation(&self, operation: Self::BlockImportOperation) -> error::Result<()> {
|
||||
if let Some(pending_block) = operation.pending_block {
|
||||
let old_state = &operation.old_state;
|
||||
let (header, body, justification) = pending_block.block.into_inner();
|
||||
let hash = header.hash();
|
||||
let parent_hash = *header.parent_hash();
|
||||
|
||||
self.states.write().insert(hash, operation.new_state.unwrap_or_else(|| old_state.clone()));
|
||||
self.blockchain.insert(hash, header, justification, body, pending_block.is_best);
|
||||
// dumb implementation - store value for each block
|
||||
if pending_block.is_best {
|
||||
self.blockchain.cache.insert(parent_hash, operation.pending_authorities);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn blockchain(&self) -> &Self::Blockchain {
|
||||
&self.blockchain
|
||||
}
|
||||
|
||||
fn state_at(&self, block: BlockId<Block>) -> error::Result<Self::State> {
|
||||
match self.blockchain.id(block).and_then(|id| self.states.read().get(&id).cloned()) {
|
||||
Some(state) => Ok(state),
|
||||
None => Err(error::ErrorKind::UnknownBlock(format!("{}", block)).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn revert(&self, _n: NumberFor<Block>) -> error::Result<NumberFor<Block>> {
|
||||
Ok(As::sa(0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, H, C> backend::LocalBackend<Block, H, C> for Backend<Block, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
H: Hasher,
|
||||
H::Out: HeapSizeOf,
|
||||
C: NodeCodec<H> + Send + Sync,
|
||||
{}
|
||||
|
||||
impl<Block: BlockT> Cache<Block> {
|
||||
fn insert(&self, at: Block::Hash, authorities: Option<Vec<AuthorityId>>) {
|
||||
self.authorities_at.write().insert(at, authorities);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> blockchain::Cache<Block> for Cache<Block> {
|
||||
fn authorities_at(&self, block: BlockId<Block>) -> Option<Vec<AuthorityId>> {
|
||||
let hash = match block {
|
||||
BlockId::Hash(hash) => hash,
|
||||
BlockId::Number(number) => self.storage.read().hashes.get(&number).cloned()?,
|
||||
};
|
||||
|
||||
self.authorities_at.read().get(&hash).cloned().unwrap_or(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert authorities entry into in-memory blockchain cache. Extracted as a separate function to use it in tests.
|
||||
pub fn cache_authorities_at<Block: BlockT>(
|
||||
blockchain: &Blockchain<Block>,
|
||||
at: Block::Hash,
|
||||
authorities: Option<Vec<AuthorityId>>
|
||||
) {
|
||||
blockchain.cache.insert(at, authorities);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate Client and associated logic.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![recursion_limit="128"]
|
||||
|
||||
extern crate substrate_bft as bft;
|
||||
extern crate parity_codec as codec;
|
||||
extern crate substrate_metadata;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate sr_io as runtime_io;
|
||||
extern crate sr_primitives as runtime_primitives;
|
||||
extern crate substrate_state_machine as state_machine;
|
||||
#[cfg(test)] extern crate substrate_keyring as keyring;
|
||||
#[cfg(test)] extern crate substrate_test_client as test_client;
|
||||
#[macro_use] extern crate substrate_telemetry;
|
||||
#[macro_use] extern crate slog; // needed until we can reexport `slog_info` from `substrate_telemetry`
|
||||
|
||||
extern crate fnv;
|
||||
extern crate futures;
|
||||
extern crate parking_lot;
|
||||
extern crate triehash;
|
||||
extern crate patricia_trie;
|
||||
extern crate hashdb;
|
||||
extern crate rlp;
|
||||
extern crate heapsize;
|
||||
|
||||
#[macro_use] extern crate error_chain;
|
||||
#[macro_use] extern crate log;
|
||||
#[cfg_attr(test, macro_use)] extern crate substrate_executor as executor;
|
||||
#[cfg(test)] #[macro_use] extern crate hex_literal;
|
||||
|
||||
pub mod error;
|
||||
pub mod blockchain;
|
||||
pub mod backend;
|
||||
pub mod cht;
|
||||
pub mod in_mem;
|
||||
pub mod genesis;
|
||||
pub mod block_builder;
|
||||
pub mod light;
|
||||
mod call_executor;
|
||||
mod client;
|
||||
mod notifications;
|
||||
|
||||
pub use blockchain::Info as ChainInfo;
|
||||
pub use call_executor::{CallResult, CallExecutor, LocalCallExecutor};
|
||||
pub use client::{
|
||||
new_in_mem,
|
||||
BlockBody, BlockStatus, BlockOrigin, BlockchainEventStream, BlockchainEvents,
|
||||
Client, ClientInfo, ChainHead,
|
||||
ImportResult, JustifiedHeader,
|
||||
};
|
||||
pub use notifications::{StorageEventStream, StorageChangeSet};
|
||||
pub use state_machine::ExecutionStrategy;
|
||||
@@ -0,0 +1,229 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client backend. Only stores headers and justifications of blocks.
|
||||
//! Everything else is requested from full nodes on demand.
|
||||
|
||||
use std::sync::{Arc, Weak};
|
||||
use futures::{Future, IntoFuture};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use primitives::AuthorityId;
|
||||
use runtime_primitives::{bft::Justification, generic::BlockId};
|
||||
use runtime_primitives::traits::{Block as BlockT, NumberFor};
|
||||
use state_machine::{
|
||||
Backend as StateBackend,
|
||||
TrieBackend as StateTrieBackend,
|
||||
TryIntoTrieBackend as TryIntoStateTrieBackend
|
||||
};
|
||||
|
||||
use backend::{Backend as ClientBackend, BlockImportOperation, RemoteBackend};
|
||||
use blockchain::HeaderBackend as BlockchainHeaderBackend;
|
||||
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
|
||||
use light::blockchain::{Blockchain, Storage as BlockchainStorage};
|
||||
use light::fetcher::{Fetcher, RemoteReadRequest};
|
||||
use patricia_trie::NodeCodec;
|
||||
use hashdb::Hasher;
|
||||
|
||||
/// Light client backend.
|
||||
pub struct Backend<S, F> {
|
||||
blockchain: Arc<Blockchain<S, F>>,
|
||||
}
|
||||
|
||||
/// Light block (header and justification) import operation.
|
||||
pub struct ImportOperation<Block: BlockT, S, F> {
|
||||
is_new_best: bool,
|
||||
header: Option<Block::Header>,
|
||||
authorities: Option<Vec<AuthorityId>>,
|
||||
_phantom: ::std::marker::PhantomData<(S, F)>,
|
||||
}
|
||||
|
||||
/// On-demand state.
|
||||
pub struct OnDemandState<Block: BlockT, S, F> {
|
||||
fetcher: Weak<F>,
|
||||
blockchain: Weak<Blockchain<S, F>>,
|
||||
block: Block::Hash,
|
||||
cached_header: RwLock<Option<Block::Header>>,
|
||||
}
|
||||
|
||||
impl<S, F> Backend<S, F> {
|
||||
/// Create new light backend.
|
||||
pub fn new(blockchain: Arc<Blockchain<S, F>>) -> Self {
|
||||
Self { blockchain }
|
||||
}
|
||||
|
||||
/// Get shared blockchain reference.
|
||||
pub fn blockchain(&self) -> &Arc<Blockchain<S, F>> {
|
||||
&self.blockchain
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block, H, C> ClientBackend<Block, H, C> for Backend<S, F> where
|
||||
Block: BlockT,
|
||||
S: BlockchainStorage<Block>,
|
||||
F: Fetcher<Block>,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
type BlockImportOperation = ImportOperation<Block, S, F>;
|
||||
type Blockchain = Blockchain<S, F>;
|
||||
type State = OnDemandState<Block, S, F>;
|
||||
|
||||
fn begin_operation(&self, _block: BlockId<Block>) -> ClientResult<Self::BlockImportOperation> {
|
||||
Ok(ImportOperation {
|
||||
is_new_best: false,
|
||||
header: None,
|
||||
authorities: None,
|
||||
_phantom: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn commit_operation(&self, operation: Self::BlockImportOperation) -> ClientResult<()> {
|
||||
let header = operation.header.expect("commit is called after set_block_data; set_block_data sets header; qed");
|
||||
self.blockchain.storage().import_header(operation.is_new_best, header, operation.authorities)
|
||||
}
|
||||
|
||||
fn blockchain(&self) -> &Blockchain<S, F> {
|
||||
&self.blockchain
|
||||
}
|
||||
|
||||
fn state_at(&self, block: BlockId<Block>) -> ClientResult<Self::State> {
|
||||
let block_hash = match block {
|
||||
BlockId::Hash(h) => Some(h),
|
||||
BlockId::Number(n) => self.blockchain.hash(n).unwrap_or_default(),
|
||||
};
|
||||
|
||||
Ok(OnDemandState {
|
||||
fetcher: self.blockchain.fetcher(),
|
||||
blockchain: Arc::downgrade(&self.blockchain),
|
||||
block: block_hash.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", block)))?,
|
||||
cached_header: RwLock::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
fn revert(&self, _n: NumberFor<Block>) -> ClientResult<NumberFor<Block>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block, H, C> RemoteBackend<Block, H, C> for Backend<S, F>
|
||||
where
|
||||
Block: BlockT,
|
||||
S: BlockchainStorage<Block>,
|
||||
F: Fetcher<Block>,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{}
|
||||
|
||||
impl<S, F, Block, H, C> BlockImportOperation<Block, H, C> for ImportOperation<Block, S, F>
|
||||
where
|
||||
Block: BlockT,
|
||||
F: Fetcher<Block>,
|
||||
S: BlockchainStorage<Block>,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
type State = OnDemandState<Block, S, F>;
|
||||
|
||||
fn state(&self) -> ClientResult<Option<&Self::State>> {
|
||||
// None means 'locally-stateless' backend
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn set_block_data(
|
||||
&mut self,
|
||||
header: Block::Header,
|
||||
_body: Option<Vec<Block::Extrinsic>>,
|
||||
_justification: Option<Justification<Block::Hash>>,
|
||||
is_new_best: bool
|
||||
) -> ClientResult<()> {
|
||||
self.is_new_best = is_new_best;
|
||||
self.header = Some(header);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_authorities(&mut self, authorities: Vec<AuthorityId>) {
|
||||
self.authorities = Some(authorities);
|
||||
}
|
||||
|
||||
fn update_storage(&mut self, _update: <Self::State as StateBackend<H, C>>::Transaction) -> ClientResult<()> {
|
||||
// we're not storing anything locally => ignore changes
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_storage<I: Iterator<Item=(Vec<u8>, Vec<u8>)>>(&mut self, _iter: I) -> ClientResult<()> {
|
||||
// we're not storing anything locally => ignore changes
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, S, F, H, C> StateBackend<H, C> for OnDemandState<Block, S, F>
|
||||
where
|
||||
Block: BlockT,
|
||||
S: BlockchainStorage<Block>,
|
||||
F: Fetcher<Block>,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
type Error = ClientError;
|
||||
type Transaction = ();
|
||||
|
||||
fn storage(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
|
||||
let mut header = self.cached_header.read().clone();
|
||||
if header.is_none() {
|
||||
let cached_header = self.blockchain.upgrade()
|
||||
.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", self.block)).into())
|
||||
.and_then(|blockchain| blockchain.expect_header(BlockId::Hash(self.block)))?;
|
||||
header = Some(cached_header.clone());
|
||||
*self.cached_header.write() = Some(cached_header);
|
||||
}
|
||||
|
||||
self.fetcher.upgrade().ok_or(ClientErrorKind::NotAvailableOnLightClient)?
|
||||
.remote_read(RemoteReadRequest {
|
||||
block: self.block,
|
||||
header: header.expect("if block above guarantees that header is_some(); qed"),
|
||||
key: key.to_vec(),
|
||||
retry_count: None,
|
||||
})
|
||||
.into_future().wait()
|
||||
}
|
||||
|
||||
fn for_keys_with_prefix<A: FnMut(&[u8])>(&self, _prefix: &[u8], _action: A) {
|
||||
// whole state is not available on light node
|
||||
}
|
||||
|
||||
fn storage_root<I>(&self, _delta: I) -> (H::Out, Self::Transaction)
|
||||
where I: IntoIterator<Item=(Vec<u8>, Option<Vec<u8>>)> {
|
||||
(H::Out::default(), ())
|
||||
}
|
||||
|
||||
fn pairs(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
// whole state is not available on light node
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, S, F, H, C> TryIntoStateTrieBackend<H, C> for OnDemandState<Block, S, F>
|
||||
where
|
||||
Block: BlockT,
|
||||
F: Fetcher<Block>,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
fn try_into_trie_backend(self) -> Option<StateTrieBackend<H, C>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client blockchin backend. Only stores headers and justifications of recent
|
||||
//! blocks. CHT roots are stored for headers of ancient blocks.
|
||||
|
||||
use std::sync::Weak;
|
||||
use futures::{Future, IntoFuture};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use primitives::AuthorityId;
|
||||
use runtime_primitives::{bft::Justification, generic::BlockId};
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
|
||||
|
||||
use blockchain::{Backend as BlockchainBackend, BlockStatus, Cache as BlockchainCache,
|
||||
HeaderBackend as BlockchainHeaderBackend, Info as BlockchainInfo};
|
||||
use cht;
|
||||
use error::{ErrorKind as ClientErrorKind, Result as ClientResult};
|
||||
use light::fetcher::{Fetcher, RemoteHeaderRequest};
|
||||
|
||||
/// Light client blockchain storage.
|
||||
pub trait Storage<Block: BlockT>: BlockchainHeaderBackend<Block> {
|
||||
/// Store new header.
|
||||
fn import_header(
|
||||
&self,
|
||||
is_new_best: bool,
|
||||
header: Block::Header,
|
||||
authorities: Option<Vec<AuthorityId>>
|
||||
) -> ClientResult<()>;
|
||||
|
||||
/// Get CHT root for given block. Fails if the block is not pruned (not a part of any CHT).
|
||||
fn cht_root(&self, cht_size: u64, block: NumberFor<Block>) -> ClientResult<Block::Hash>;
|
||||
|
||||
/// Get storage cache.
|
||||
fn cache(&self) -> Option<&BlockchainCache<Block>>;
|
||||
}
|
||||
|
||||
/// Light client blockchain.
|
||||
pub struct Blockchain<S, F> {
|
||||
fetcher: Mutex<Weak<F>>,
|
||||
storage: S,
|
||||
}
|
||||
|
||||
impl<S, F> Blockchain<S, F> {
|
||||
/// Create new light blockchain backed with given storage.
|
||||
pub fn new(storage: S) -> Self {
|
||||
Self {
|
||||
fetcher: Mutex::new(Default::default()),
|
||||
storage,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets fetcher reference.
|
||||
pub fn set_fetcher(&self, fetcher: Weak<F>) {
|
||||
*self.fetcher.lock() = fetcher;
|
||||
}
|
||||
|
||||
/// Get fetcher weak reference.
|
||||
pub fn fetcher(&self) -> Weak<F> {
|
||||
self.fetcher.lock().clone()
|
||||
}
|
||||
|
||||
/// Get storage reference.
|
||||
pub fn storage(&self) -> &S {
|
||||
&self.storage
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block> BlockchainHeaderBackend<Block> for Blockchain<S, F> where Block: BlockT, S: Storage<Block>, F: Fetcher<Block> {
|
||||
fn header(&self, id: BlockId<Block>) -> ClientResult<Option<Block::Header>> {
|
||||
match self.storage.header(id)? {
|
||||
Some(header) => Ok(Some(header)),
|
||||
None => {
|
||||
let number = match id {
|
||||
BlockId::Hash(hash) => match self.storage.number(hash)? {
|
||||
Some(number) => number,
|
||||
None => return Ok(None),
|
||||
},
|
||||
BlockId::Number(number) => number,
|
||||
};
|
||||
|
||||
// if the header is from future or genesis (we never prune genesis) => return
|
||||
if number.is_zero() || self.storage.status(BlockId::Number(number))? != BlockStatus::InChain {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
self.fetcher().upgrade().ok_or(ClientErrorKind::NotAvailableOnLightClient)?
|
||||
.remote_header(RemoteHeaderRequest {
|
||||
cht_root: self.storage.cht_root(cht::SIZE, number)?,
|
||||
block: number,
|
||||
retry_count: None,
|
||||
})
|
||||
.into_future().wait()
|
||||
.map(Some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn info(&self) -> ClientResult<BlockchainInfo<Block>> {
|
||||
self.storage.info()
|
||||
}
|
||||
|
||||
fn status(&self, id: BlockId<Block>) -> ClientResult<BlockStatus> {
|
||||
self.storage.status(id)
|
||||
}
|
||||
|
||||
fn number(&self, hash: Block::Hash) -> ClientResult<Option<NumberFor<Block>>> {
|
||||
self.storage.number(hash)
|
||||
}
|
||||
|
||||
fn hash(&self, number: <<Block as BlockT>::Header as HeaderT>::Number) -> ClientResult<Option<Block::Hash>> {
|
||||
self.storage.hash(number)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, Block> BlockchainBackend<Block> for Blockchain<S, F> where Block: BlockT, S: Storage<Block>, F: Fetcher<Block> {
|
||||
fn body(&self, _id: BlockId<Block>) -> ClientResult<Option<Vec<Block::Extrinsic>>> {
|
||||
// TODO [light]: fetch from remote node
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn justification(&self, _id: BlockId<Block>) -> ClientResult<Option<Justification<Block::Hash>>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn cache(&self) -> Option<&BlockchainCache<Block>> {
|
||||
self.storage.cache()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client call exector. Executes methods on remote full nodes, fetching
|
||||
//! execution proof and checking it locally.
|
||||
|
||||
use std::sync::Arc;
|
||||
use futures::{IntoFuture, Future};
|
||||
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use state_machine::{Backend as StateBackend, CodeExecutor, OverlayedChanges,
|
||||
execution_proof_check, ExecutionManager};
|
||||
use primitives::H256;
|
||||
use patricia_trie::NodeCodec;
|
||||
use hashdb::Hasher;
|
||||
use rlp::Encodable;
|
||||
|
||||
use blockchain::Backend as ChainBackend;
|
||||
use call_executor::{CallExecutor, CallResult};
|
||||
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
|
||||
use light::fetcher::{Fetcher, RemoteCallRequest};
|
||||
use executor::RuntimeVersion;
|
||||
use codec::Decode;
|
||||
use heapsize::HeapSizeOf;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Call executor that executes methods on remote node, querying execution proof
|
||||
/// and checking proof by re-executing locally.
|
||||
pub struct RemoteCallExecutor<B, F, H, C> {
|
||||
blockchain: Arc<B>,
|
||||
fetcher: Arc<F>,
|
||||
_hasher: PhantomData<H>,
|
||||
_codec: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<B, F, H, C> Clone for RemoteCallExecutor<B, F, H, C> {
|
||||
fn clone(&self) -> Self {
|
||||
RemoteCallExecutor {
|
||||
blockchain: self.blockchain.clone(),
|
||||
fetcher: self.fetcher.clone(),
|
||||
_hasher: Default::default(),
|
||||
_codec: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F, H, C> RemoteCallExecutor<B, F, H, C> {
|
||||
/// Creates new instance of remote call executor.
|
||||
pub fn new(blockchain: Arc<B>, fetcher: Arc<F>) -> Self {
|
||||
RemoteCallExecutor { blockchain, fetcher, _hasher: PhantomData, _codec: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F, Block, H, C> CallExecutor<Block, H, C> for RemoteCallExecutor<B, F, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
B: ChainBackend<Block>,
|
||||
F: Fetcher<Block>,
|
||||
H: Hasher,
|
||||
H::Out: Ord + Encodable,
|
||||
C: NodeCodec<H>
|
||||
{
|
||||
type Error = ClientError;
|
||||
|
||||
fn call(&self, id: &BlockId<Block>, method: &str, call_data: &[u8]) -> ClientResult<CallResult> {
|
||||
let block_hash = match *id {
|
||||
BlockId::Hash(hash) => hash,
|
||||
BlockId::Number(number) => self.blockchain.hash(number)?
|
||||
.ok_or_else(|| ClientErrorKind::UnknownBlock(format!("{}", number)))?,
|
||||
};
|
||||
let block_header = self.blockchain.expect_header(id.clone())?;
|
||||
|
||||
self.fetcher.remote_call(RemoteCallRequest {
|
||||
block: block_hash,
|
||||
header: block_header,
|
||||
method: method.into(),
|
||||
call_data: call_data.to_vec(),
|
||||
retry_count: None,
|
||||
}).into_future().wait()
|
||||
}
|
||||
|
||||
fn runtime_version(&self, id: &BlockId<Block>) -> ClientResult<RuntimeVersion> {
|
||||
let call_result = self.call(id, "version", &[])?;
|
||||
RuntimeVersion::decode(&mut call_result.return_data.as_slice())
|
||||
.ok_or_else(|| ClientErrorKind::VersionInvalid.into())
|
||||
}
|
||||
|
||||
fn call_at_state<
|
||||
S: StateBackend<H, C>,
|
||||
FF: FnOnce(Result<Vec<u8>, Self::Error>, Result<Vec<u8>, Self::Error>) -> Result<Vec<u8>, Self::Error>
|
||||
>(&self,
|
||||
_state: &S,
|
||||
_changes: &mut OverlayedChanges,
|
||||
_method: &str,
|
||||
_call_data: &[u8],
|
||||
_m: ExecutionManager<FF>
|
||||
) -> ClientResult<(Vec<u8>, S::Transaction)> {
|
||||
Err(ClientErrorKind::NotAvailableOnLightClient.into())
|
||||
}
|
||||
|
||||
fn prove_at_state<S: StateBackend<H, C>>(
|
||||
&self,
|
||||
_state: S,
|
||||
_changes: &mut OverlayedChanges,
|
||||
_method: &str,
|
||||
_call_data: &[u8]
|
||||
) -> ClientResult<(Vec<u8>, Vec<Vec<u8>>)> {
|
||||
Err(ClientErrorKind::NotAvailableOnLightClient.into())
|
||||
}
|
||||
|
||||
fn native_runtime_version(&self) -> Option<RuntimeVersion> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Check remote execution proof using given backend.
|
||||
pub fn check_execution_proof<Header, E, H, C>(
|
||||
executor: &E,
|
||||
request: &RemoteCallRequest<Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<CallResult>
|
||||
where
|
||||
Header: HeaderT,
|
||||
E: CodeExecutor<H>,
|
||||
H: Hasher,
|
||||
H::Out: Ord + Encodable + HeapSizeOf + From<H256>,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
let local_state_root = request.header.state_root();
|
||||
|
||||
let mut changes = OverlayedChanges::default();
|
||||
let (local_result, _) = execution_proof_check::<H, C, _>(
|
||||
H256::from_slice(local_state_root.as_ref()).into(),
|
||||
remote_proof,
|
||||
&mut changes,
|
||||
executor,
|
||||
&request.method,
|
||||
&request.call_data)?;
|
||||
|
||||
Ok(CallResult { return_data: local_result, changes })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_client;
|
||||
use executor::NativeExecutionDispatch;
|
||||
use super::*;
|
||||
use primitives::RlpCodec;
|
||||
|
||||
#[test]
|
||||
fn execution_proof_is_generated_and_checked() {
|
||||
// prepare remote client
|
||||
let remote_client = test_client::new();
|
||||
let remote_block_id = BlockId::Number(0);
|
||||
let remote_block_storage_root = remote_client.state_at(&remote_block_id)
|
||||
.unwrap().storage_root(::std::iter::empty()).0;
|
||||
|
||||
// 'fetch' execution proof from remote node
|
||||
let remote_execution_proof = remote_client.execution_proof(&remote_block_id, "authorities", &[]).unwrap().1;
|
||||
|
||||
// check remote execution proof locally
|
||||
let local_executor = test_client::LocalExecutor::new();
|
||||
check_execution_proof::<_, _, _, RlpCodec>(&local_executor, &RemoteCallRequest {
|
||||
block: test_client::runtime::Hash::default(),
|
||||
header: test_client::runtime::Header {
|
||||
state_root: remote_block_storage_root.into(),
|
||||
parent_hash: Default::default(),
|
||||
number: 0,
|
||||
extrinsics_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
},
|
||||
method: "authorities".into(),
|
||||
call_data: vec![],
|
||||
retry_count: None,
|
||||
}, remote_execution_proof).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client data fetcher. Fetches requested data from remote full nodes.
|
||||
|
||||
use futures::IntoFuture;
|
||||
|
||||
use primitives::H256;
|
||||
use hashdb::Hasher;
|
||||
use patricia_trie::NodeCodec;
|
||||
use rlp::Encodable;
|
||||
use heapsize::HeapSizeOf;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use state_machine::{CodeExecutor, read_proof_check};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use call_executor::CallResult;
|
||||
use cht;
|
||||
use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult};
|
||||
use light::call_executor::check_execution_proof;
|
||||
|
||||
/// Remote call request.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct RemoteCallRequest<Header: HeaderT> {
|
||||
/// Call at state of given block.
|
||||
pub block: Header::Hash,
|
||||
/// Head of block at which call is perormed.
|
||||
pub header: Header,
|
||||
/// Method to call.
|
||||
pub method: String,
|
||||
/// Call data.
|
||||
pub call_data: Vec<u8>,
|
||||
/// Number of times to retry request. None means that default RETRY_COUNT is used.
|
||||
pub retry_count: Option<usize>,
|
||||
}
|
||||
|
||||
/// Remote canonical header request.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct RemoteHeaderRequest<Header: HeaderT> {
|
||||
/// The root of CHT this block is included in.
|
||||
pub cht_root: Header::Hash,
|
||||
/// Number of the header to query.
|
||||
pub block: Header::Number,
|
||||
/// Number of times to retry request. None means that default RETRY_COUNT is used.
|
||||
pub retry_count: Option<usize>,
|
||||
}
|
||||
|
||||
/// Remote storage read request.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct RemoteReadRequest<Header: HeaderT> {
|
||||
/// Read at state of given block.
|
||||
pub block: Header::Hash,
|
||||
/// Head of block at which read is perormed.
|
||||
pub header: Header,
|
||||
/// Storage key to read.
|
||||
pub key: Vec<u8>,
|
||||
/// Number of times to retry request. None means that default RETRY_COUNT is used.
|
||||
pub retry_count: Option<usize>,
|
||||
}
|
||||
|
||||
/// Light client data fetcher. Implementations of this trait must check if remote data
|
||||
/// is correct (see FetchedDataChecker) and return already checked data.
|
||||
pub trait Fetcher<Block: BlockT>: Send + Sync {
|
||||
/// Remote header future.
|
||||
type RemoteHeaderResult: IntoFuture<Item=Block::Header, Error=ClientError>;
|
||||
/// Remote storage read future.
|
||||
type RemoteReadResult: IntoFuture<Item=Option<Vec<u8>>, Error=ClientError>;
|
||||
/// Remote call result future.
|
||||
type RemoteCallResult: IntoFuture<Item=CallResult, Error=ClientError>;
|
||||
|
||||
/// Fetch remote header.
|
||||
fn remote_header(&self, request: RemoteHeaderRequest<Block::Header>) -> Self::RemoteHeaderResult;
|
||||
/// Fetch remote storage value.
|
||||
fn remote_read(&self, request: RemoteReadRequest<Block::Header>) -> Self::RemoteReadResult;
|
||||
/// Fetch remote call result.
|
||||
fn remote_call(&self, request: RemoteCallRequest<Block::Header>) -> Self::RemoteCallResult;
|
||||
}
|
||||
|
||||
/// Light client remote data checker.
|
||||
///
|
||||
/// Implementations of this trait should not use any blockchain data except that is
|
||||
/// passed to its methods.
|
||||
pub trait FetchChecker<Block: BlockT>: Send + Sync {
|
||||
/// Check remote header proof.
|
||||
fn check_header_proof(
|
||||
&self,
|
||||
request: &RemoteHeaderRequest<Block::Header>,
|
||||
header: Option<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<Block::Header>;
|
||||
/// Check remote storage read proof.
|
||||
fn check_read_proof(
|
||||
&self,
|
||||
request: &RemoteReadRequest<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<Option<Vec<u8>>>;
|
||||
/// Check remote method execution proof.
|
||||
fn check_execution_proof(
|
||||
&self,
|
||||
request: &RemoteCallRequest<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<CallResult>;
|
||||
}
|
||||
|
||||
/// Remote data checker.
|
||||
pub struct LightDataChecker<E, H, C> {
|
||||
executor: E,
|
||||
_hasher: PhantomData<H>,
|
||||
_codec: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<E, H, C> LightDataChecker<E, H, C> {
|
||||
/// Create new light data checker.
|
||||
pub fn new(executor: E) -> Self {
|
||||
Self {
|
||||
executor, _hasher: PhantomData, _codec: PhantomData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, Block, H, C> FetchChecker<Block> for LightDataChecker<E, H, C>
|
||||
where
|
||||
Block: BlockT,
|
||||
Block::Hash: Into<H::Out> + From<H256>,
|
||||
E: CodeExecutor<H>,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H> + Sync + Send,
|
||||
H::Out: Ord + Encodable + HeapSizeOf + From<Block::Hash> + From<H256>,
|
||||
{
|
||||
fn check_header_proof(
|
||||
&self,
|
||||
request: &RemoteHeaderRequest<Block::Header>,
|
||||
remote_header: Option<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<Block::Header> {
|
||||
let remote_header = remote_header.ok_or_else(||
|
||||
ClientError::from(ClientErrorKind::InvalidHeaderProof))?;
|
||||
let remote_header_hash = remote_header.hash();
|
||||
cht::check_proof::<Block::Header, H, C>(
|
||||
request.cht_root,
|
||||
request.block,
|
||||
remote_header_hash,
|
||||
remote_proof)
|
||||
.map(|_| remote_header)
|
||||
}
|
||||
|
||||
fn check_read_proof(
|
||||
&self,
|
||||
request: &RemoteReadRequest<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<Option<Vec<u8>>> {
|
||||
let local_state_root = request.header.state_root().clone();
|
||||
read_proof_check::<H, C>(local_state_root.into(), remote_proof, &request.key).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn check_execution_proof(
|
||||
&self,
|
||||
request: &RemoteCallRequest<Block::Header>,
|
||||
remote_proof: Vec<Vec<u8>>
|
||||
) -> ClientResult<CallResult> {
|
||||
check_execution_proof::<_, _, H, C>(&self.executor, request, remote_proof)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use futures::future::{ok, err, FutureResult};
|
||||
use parking_lot::Mutex;
|
||||
use call_executor::CallResult;
|
||||
use executor::{self, NativeExecutionDispatch};
|
||||
use error::Error as ClientError;
|
||||
use test_client::{self, TestClient, runtime::{Hash, Block, Header}};
|
||||
use test_client::client::BlockOrigin;
|
||||
use in_mem::{Blockchain as InMemoryBlockchain};
|
||||
use light::fetcher::{Fetcher, FetchChecker, LightDataChecker,
|
||||
RemoteCallRequest, RemoteHeaderRequest};
|
||||
use primitives::{Blake2Hasher, RlpCodec};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use state_machine::Backend;
|
||||
use super::*;
|
||||
|
||||
pub type OkCallFetcher = Mutex<CallResult>;
|
||||
|
||||
impl Fetcher<Block> for OkCallFetcher {
|
||||
type RemoteHeaderResult = FutureResult<Header, ClientError>;
|
||||
type RemoteReadResult = FutureResult<Option<Vec<u8>>, ClientError>;
|
||||
type RemoteCallResult = FutureResult<CallResult, ClientError>;
|
||||
|
||||
fn remote_header(&self, _request: RemoteHeaderRequest<Header>) -> Self::RemoteHeaderResult {
|
||||
err("Not implemented on test node".into())
|
||||
}
|
||||
|
||||
fn remote_read(&self, _request: RemoteReadRequest<Header>) -> Self::RemoteReadResult {
|
||||
err("Not implemented on test node".into())
|
||||
}
|
||||
|
||||
fn remote_call(&self, _request: RemoteCallRequest<Header>) -> Self::RemoteCallResult {
|
||||
ok((*self.lock()).clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_for_read_proof_check() -> (
|
||||
LightDataChecker<executor::NativeExecutor<test_client::LocalExecutor>, Blake2Hasher, RlpCodec>,
|
||||
Header, Vec<Vec<u8>>, usize)
|
||||
{
|
||||
// prepare remote client
|
||||
let remote_client = test_client::new();
|
||||
let remote_block_id = BlockId::Number(0);
|
||||
let remote_block_hash = remote_client.block_hash(0).unwrap().unwrap();
|
||||
let mut remote_block_header = remote_client.header(&remote_block_id).unwrap().unwrap();
|
||||
remote_block_header.state_root = remote_client.state_at(&remote_block_id).unwrap().storage_root(::std::iter::empty()).0.into();
|
||||
|
||||
// 'fetch' read proof from remote node
|
||||
let authorities_len = remote_client.authorities_at(&remote_block_id).unwrap().len();
|
||||
let remote_read_proof = remote_client.read_proof(&remote_block_id, b":auth:len").unwrap();
|
||||
|
||||
// check remote read proof locally
|
||||
let local_storage = InMemoryBlockchain::<Block>::new();
|
||||
local_storage.insert(remote_block_hash, remote_block_header.clone(), None, None, true);
|
||||
let local_executor = test_client::LocalExecutor::new();
|
||||
let local_checker = LightDataChecker::new(local_executor);
|
||||
(local_checker, remote_block_header, remote_read_proof, authorities_len)
|
||||
}
|
||||
|
||||
fn prepare_for_header_proof_check(insert_cht: bool) -> (
|
||||
LightDataChecker<executor::NativeExecutor<test_client::LocalExecutor>, Blake2Hasher, RlpCodec>,
|
||||
Hash, Header, Vec<Vec<u8>>)
|
||||
{
|
||||
// prepare remote client
|
||||
let remote_client = test_client::new();
|
||||
let mut local_headers_hashes = Vec::new();
|
||||
for i in 0..4 {
|
||||
let builder = remote_client.new_block().unwrap();
|
||||
remote_client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap();
|
||||
local_headers_hashes.push(remote_client.block_hash(i + 1).unwrap());
|
||||
}
|
||||
|
||||
// 'fetch' header proof from remote node
|
||||
let remote_block_id = BlockId::Number(1);
|
||||
let (remote_block_header, remote_header_proof) = remote_client.header_proof_with_cht_size(&remote_block_id, 4).unwrap();
|
||||
|
||||
// check remote read proof locally
|
||||
let local_storage = InMemoryBlockchain::<Block>::new();
|
||||
let local_cht_root = cht::compute_root::<Header, Blake2Hasher, _>(4, 0, local_headers_hashes.into_iter()).unwrap();
|
||||
if insert_cht {
|
||||
local_storage.insert_cht_root(1, local_cht_root);
|
||||
}
|
||||
let local_executor = test_client::LocalExecutor::new();
|
||||
let local_checker = LightDataChecker::new(local_executor);
|
||||
(local_checker, local_cht_root, remote_block_header, remote_header_proof)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_read_proof_is_generated_and_checked() {
|
||||
let (local_checker, remote_block_header, remote_read_proof, authorities_len) = prepare_for_read_proof_check();
|
||||
assert_eq!((&local_checker as &FetchChecker<Block>).check_read_proof(&RemoteReadRequest::<Header> {
|
||||
block: remote_block_header.hash(),
|
||||
header: remote_block_header,
|
||||
key: b":auth:len".to_vec(),
|
||||
retry_count: None,
|
||||
}, remote_read_proof).unwrap().unwrap()[0], authorities_len as u8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_proof_is_generated_and_checked() {
|
||||
let (local_checker, local_cht_root, remote_block_header, remote_header_proof) = prepare_for_header_proof_check(true);
|
||||
assert_eq!((&local_checker as &FetchChecker<Block>).check_header_proof(&RemoteHeaderRequest::<Header> {
|
||||
cht_root: local_cht_root,
|
||||
block: 1,
|
||||
retry_count: None,
|
||||
}, Some(remote_block_header.clone()), remote_header_proof).unwrap(), remote_block_header);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_header_proof_fails_if_cht_root_is_invalid() {
|
||||
let (local_checker, _, mut remote_block_header, remote_header_proof) = prepare_for_header_proof_check(true);
|
||||
remote_block_header.number = 100;
|
||||
assert!((&local_checker as &FetchChecker<Block>).check_header_proof(&RemoteHeaderRequest::<Header> {
|
||||
cht_root: Default::default(),
|
||||
block: 1,
|
||||
retry_count: None,
|
||||
}, Some(remote_block_header.clone()), remote_header_proof).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_header_proof_fails_if_invalid_header_provided() {
|
||||
let (local_checker, local_cht_root, mut remote_block_header, remote_header_proof) = prepare_for_header_proof_check(true);
|
||||
remote_block_header.number = 100;
|
||||
assert!((&local_checker as &FetchChecker<Block>).check_header_proof(&RemoteHeaderRequest::<Header> {
|
||||
cht_root: local_cht_root,
|
||||
block: 1,
|
||||
retry_count: None,
|
||||
}, Some(remote_block_header.clone()), remote_header_proof).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Light client components.
|
||||
|
||||
pub mod backend;
|
||||
pub mod blockchain;
|
||||
pub mod call_executor;
|
||||
pub mod fetcher;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use primitives::{Blake2Hasher, RlpCodec};
|
||||
use runtime_primitives::BuildStorage;
|
||||
use runtime_primitives::traits::Block as BlockT;
|
||||
use state_machine::{CodeExecutor, ExecutionStrategy};
|
||||
|
||||
use client::Client;
|
||||
use error::Result as ClientResult;
|
||||
use light::backend::Backend;
|
||||
use light::blockchain::{Blockchain, Storage as BlockchainStorage};
|
||||
use light::call_executor::RemoteCallExecutor;
|
||||
use light::fetcher::{Fetcher, LightDataChecker};
|
||||
use hashdb::Hasher;
|
||||
use patricia_trie::NodeCodec;
|
||||
|
||||
/// Create an instance of light client blockchain backend.
|
||||
pub fn new_light_blockchain<B: BlockT, S: BlockchainStorage<B>, F>(storage: S) -> Arc<Blockchain<S, F>> {
|
||||
Arc::new(Blockchain::new(storage))
|
||||
}
|
||||
|
||||
/// Create an instance of light client backend.
|
||||
pub fn new_light_backend<B: BlockT, S: BlockchainStorage<B>, F: Fetcher<B>>(blockchain: Arc<Blockchain<S, F>>, fetcher: Arc<F>) -> Arc<Backend<S, F>> {
|
||||
blockchain.set_fetcher(Arc::downgrade(&fetcher));
|
||||
Arc::new(Backend::new(blockchain))
|
||||
}
|
||||
|
||||
/// Create an instance of light client.
|
||||
pub fn new_light<B, S, F, GS>(
|
||||
backend: Arc<Backend<S, F>>,
|
||||
fetcher: Arc<F>,
|
||||
genesis_storage: GS,
|
||||
) -> ClientResult<Client<Backend<S, F>, RemoteCallExecutor<Blockchain<S, F>, F, Blake2Hasher, RlpCodec>, B>>
|
||||
where
|
||||
B: BlockT,
|
||||
S: BlockchainStorage<B>,
|
||||
F: Fetcher<B>,
|
||||
GS: BuildStorage,
|
||||
{
|
||||
let executor = RemoteCallExecutor::new(backend.blockchain().clone(), fetcher);
|
||||
Client::new(backend, executor, genesis_storage, ExecutionStrategy::NativeWhenPossible)
|
||||
}
|
||||
|
||||
/// Create an instance of fetch data checker.
|
||||
pub fn new_fetch_checker<E, H, C>(
|
||||
executor: E,
|
||||
) -> LightDataChecker<E, H, C>
|
||||
where
|
||||
E: CodeExecutor<H>,
|
||||
H: Hasher,
|
||||
C: NodeCodec<H>,
|
||||
{
|
||||
LightDataChecker::new(executor)
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Storage notifications
|
||||
|
||||
use std::{
|
||||
collections::{HashSet, HashMap},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use fnv::{FnvHashSet, FnvHashMap};
|
||||
use futures::sync::mpsc;
|
||||
use primitives::storage::{StorageKey, StorageData};
|
||||
use runtime_primitives::traits::Block as BlockT;
|
||||
|
||||
/// Storage change set
|
||||
#[derive(Debug)]
|
||||
pub struct StorageChangeSet {
|
||||
changes: Arc<Vec<(StorageKey, Option<StorageData>)>>,
|
||||
filter: Option<HashSet<StorageKey>>,
|
||||
}
|
||||
|
||||
impl StorageChangeSet {
|
||||
/// Convert the change set into iterator over storage items.
|
||||
pub fn iter<'a>(&'a self) -> impl Iterator<Item=&'a (StorageKey, Option<StorageData>)> + 'a {
|
||||
self.changes
|
||||
.iter()
|
||||
.filter(move |&(key, _)| match self.filter {
|
||||
Some(ref filter) => filter.contains(key),
|
||||
None => true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Type that implements `futures::Stream` of storage change events.
|
||||
pub type StorageEventStream<H> = mpsc::UnboundedReceiver<(H, StorageChangeSet)>;
|
||||
|
||||
type SubscriberId = u64;
|
||||
|
||||
/// Manages storage listeners.
|
||||
#[derive(Debug)]
|
||||
pub struct StorageNotifications<Block: BlockT> {
|
||||
next_id: SubscriberId,
|
||||
wildcard_listeners: FnvHashSet<SubscriberId>,
|
||||
listeners: HashMap<StorageKey, FnvHashSet<SubscriberId>>,
|
||||
sinks: FnvHashMap<SubscriberId, (
|
||||
mpsc::UnboundedSender<(Block::Hash, StorageChangeSet)>,
|
||||
Option<HashSet<StorageKey>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> Default for StorageNotifications<Block> {
|
||||
fn default() -> Self {
|
||||
StorageNotifications {
|
||||
next_id: Default::default(),
|
||||
wildcard_listeners: Default::default(),
|
||||
listeners: Default::default(),
|
||||
sinks: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> StorageNotifications<Block> {
|
||||
/// Trigger notification to all listeners.
|
||||
///
|
||||
/// Note the changes are going to be filtered by listener's filter key.
|
||||
/// In fact no event might be sent if clients are not interested in the changes.
|
||||
pub fn trigger(&mut self, hash: &Block::Hash, changeset: impl Iterator<Item=(Vec<u8>, Option<Vec<u8>>)>) {
|
||||
let has_wildcard = !self.wildcard_listeners.is_empty();
|
||||
|
||||
// early exit if no listeners
|
||||
if !has_wildcard && self.listeners.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut subscribers = self.wildcard_listeners.clone();
|
||||
let mut changes = Vec::new();
|
||||
|
||||
// Collect subscribers and changes
|
||||
for (k, v) in changeset {
|
||||
let k = StorageKey(k);
|
||||
let listeners = self.listeners.get(&k);
|
||||
|
||||
if let Some(ref listeners) = listeners {
|
||||
subscribers.extend(listeners.iter());
|
||||
}
|
||||
|
||||
if has_wildcard || listeners.is_some() {
|
||||
changes.push((k, v.map(StorageData)));
|
||||
}
|
||||
}
|
||||
|
||||
// Don't send empty notifications
|
||||
if changes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let changes = Arc::new(changes);
|
||||
// Trigger the events
|
||||
for subscriber in subscribers {
|
||||
let should_remove = {
|
||||
let &(ref sink, ref filter) = self.sinks.get(&subscriber)
|
||||
.expect("subscribers returned from self.listeners are always in self.sinks; qed");
|
||||
sink.unbounded_send((hash.clone(), StorageChangeSet {
|
||||
changes: changes.clone(),
|
||||
filter: filter.clone(),
|
||||
})).is_err()
|
||||
};
|
||||
|
||||
if should_remove {
|
||||
self.remove_subscriber(subscriber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_subscriber(&mut self, subscriber: SubscriberId) {
|
||||
if let Some((_, filters)) = self.sinks.remove(&subscriber) {
|
||||
match filters {
|
||||
None => {
|
||||
self.wildcard_listeners.remove(&subscriber);
|
||||
},
|
||||
Some(filters) => {
|
||||
for key in filters {
|
||||
let remove_key = match self.listeners.get_mut(&key) {
|
||||
Some(ref mut set) => {
|
||||
set.remove(&subscriber);
|
||||
set.is_empty()
|
||||
},
|
||||
None => false,
|
||||
};
|
||||
|
||||
if remove_key {
|
||||
self.listeners.remove(&key);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start listening for particular storage keys.
|
||||
pub fn listen(&mut self, filter_keys: Option<&[StorageKey]>) -> StorageEventStream<Block::Hash> {
|
||||
self.next_id += 1;
|
||||
|
||||
// add subscriber for every key
|
||||
let keys = match filter_keys {
|
||||
None => {
|
||||
self.wildcard_listeners.insert(self.next_id);
|
||||
None
|
||||
},
|
||||
Some(keys) => Some(keys.iter().map(|key| {
|
||||
self.listeners
|
||||
.entry(key.clone())
|
||||
.or_insert_with(Default::default)
|
||||
.insert(self.next_id);
|
||||
key.clone()
|
||||
}).collect())
|
||||
};
|
||||
|
||||
// insert sink
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
self.sinks.insert(self.next_id, (tx, keys));
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use runtime_primitives::testing::{H256 as Hash, Block as RawBlock};
|
||||
use super::*;
|
||||
use futures::Stream;
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
impl From<Vec<(StorageKey, Option<StorageData>)>> for StorageChangeSet {
|
||||
fn from(changes: Vec<(StorageKey, Option<StorageData>)>) -> Self {
|
||||
StorageChangeSet {
|
||||
changes: Arc::new(changes),
|
||||
filter: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl PartialEq for StorageChangeSet {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.iter().eq(other.iter())
|
||||
}
|
||||
}
|
||||
|
||||
type Block = RawBlock<Hash>;
|
||||
|
||||
#[test]
|
||||
fn triggering_change_should_notify_wildcard_listeners() {
|
||||
// given
|
||||
let mut notifications = StorageNotifications::<Block>::default();
|
||||
let mut recv = notifications.listen(None).wait();
|
||||
|
||||
// when
|
||||
let changeset = vec![
|
||||
(vec![2], Some(vec![3])),
|
||||
(vec![3], None),
|
||||
];
|
||||
notifications.trigger(&1.into(), changeset.into_iter());
|
||||
|
||||
// then
|
||||
assert_eq!(recv.next().unwrap(), Ok((1.into(), vec![
|
||||
(StorageKey(vec![2]), Some(StorageData(vec![3]))),
|
||||
(StorageKey(vec![3]), None),
|
||||
].into())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_only_notify_interested_listeners() {
|
||||
// given
|
||||
let mut notifications = StorageNotifications::<Block>::default();
|
||||
let mut recv1 = notifications.listen(Some(&[StorageKey(vec![1])])).wait();
|
||||
let mut recv2 = notifications.listen(Some(&[StorageKey(vec![2])])).wait();
|
||||
|
||||
// when
|
||||
let changeset = vec![
|
||||
(vec![2], Some(vec![3])),
|
||||
(vec![1], None),
|
||||
];
|
||||
notifications.trigger(&1.into(), changeset.into_iter());
|
||||
|
||||
// then
|
||||
assert_eq!(recv1.next().unwrap(), Ok((1.into(), vec![
|
||||
(StorageKey(vec![1]), None),
|
||||
].into())));
|
||||
assert_eq!(recv2.next().unwrap(), Ok((1.into(), vec![
|
||||
(StorageKey(vec![2]), Some(StorageData(vec![3]))),
|
||||
].into())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_cleanup_subscribers_if_dropped() {
|
||||
// given
|
||||
let mut notifications = StorageNotifications::<Block>::default();
|
||||
{
|
||||
let _recv1 = notifications.listen(Some(&[StorageKey(vec![1])])).wait();
|
||||
let _recv2 = notifications.listen(Some(&[StorageKey(vec![2])])).wait();
|
||||
let _recv3 = notifications.listen(None).wait();
|
||||
assert_eq!(notifications.listeners.len(), 2);
|
||||
assert_eq!(notifications.wildcard_listeners.len(), 1);
|
||||
}
|
||||
|
||||
// when
|
||||
let changeset = vec![
|
||||
(vec![2], Some(vec![3])),
|
||||
(vec![1], None),
|
||||
];
|
||||
notifications.trigger(&1.into(), changeset.into_iter());
|
||||
|
||||
// then
|
||||
assert_eq!(notifications.listeners.len(), 0);
|
||||
assert_eq!(notifications.wildcard_listeners.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_send_empty_notifications() {
|
||||
// given
|
||||
let mut recv = {
|
||||
let mut notifications = StorageNotifications::<Block>::default();
|
||||
let recv = notifications.listen(None).wait();
|
||||
|
||||
// when
|
||||
let changeset = vec![];
|
||||
notifications.trigger(&1.into(), changeset.into_iter());
|
||||
recv
|
||||
};
|
||||
|
||||
// then
|
||||
assert_eq!(recv.next(), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "substrate-executor"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
error-chain = "0.12"
|
||||
parity-codec = { path = "../../codec" }
|
||||
sr-io = { path = "../sr-io" }
|
||||
substrate-primitives = { path = "../primitives" }
|
||||
substrate-serializer = { path = "../serializer" }
|
||||
substrate-state-machine = { path = "../state-machine" }
|
||||
sr-version = { path = "../sr-version" }
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
wasmi = "0.4"
|
||||
byteorder = "1.1"
|
||||
triehash = "0.2"
|
||||
twox-hash = "1.1.0"
|
||||
lazy_static = "1.0"
|
||||
parking_lot = "*"
|
||||
log = "0.3"
|
||||
hashdb = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.1"
|
||||
wabt = "0.4"
|
||||
hex-literal = "0.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
wasm-extern-trace = []
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Executor
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Rust executor possible errors.
|
||||
|
||||
use state_machine;
|
||||
use serializer;
|
||||
use wasmi;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
InvalidData(serializer::Error) #[doc = "Unserializable Data"];
|
||||
Trap(wasmi::Trap) #[doc = "Trap occured during execution"];
|
||||
Wasmi(wasmi::Error) #[doc = "Wasmi loading/instantiating error"];
|
||||
}
|
||||
|
||||
errors {
|
||||
/// Method is not found
|
||||
MethodNotFound(t: String) {
|
||||
description("method not found"),
|
||||
display("Method not found: '{}'", t),
|
||||
}
|
||||
|
||||
/// Code is invalid (expected single byte)
|
||||
InvalidCode(c: Vec<u8>) {
|
||||
description("invalid code"),
|
||||
display("Invalid Code: {:?}", c),
|
||||
}
|
||||
|
||||
/// Could not get runtime version.
|
||||
VersionInvalid {
|
||||
description("Runtime version error"),
|
||||
display("On-chain runtime does not specify version"),
|
||||
}
|
||||
|
||||
/// Externalities have failed.
|
||||
Externalities {
|
||||
description("externalities failure"),
|
||||
display("Externalities error"),
|
||||
}
|
||||
|
||||
/// Invalid index.
|
||||
InvalidIndex {
|
||||
description("index given was not in range"),
|
||||
display("Invalid index provided"),
|
||||
}
|
||||
|
||||
/// Invalid return type.
|
||||
InvalidReturn {
|
||||
description("u64 was not returned"),
|
||||
display("Invalid type returned (should be u64)"),
|
||||
}
|
||||
|
||||
/// Runtime failed.
|
||||
Runtime {
|
||||
description("runtime failure"),
|
||||
display("Runtime error"),
|
||||
}
|
||||
|
||||
/// Runtime failed.
|
||||
InvalidMemoryReference {
|
||||
description("invalid memory reference"),
|
||||
display("Invalid memory reference"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl state_machine::Error for Error {}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// tag::description[]
|
||||
//! Temporary crate for contracts implementations.
|
||||
//!
|
||||
//! This will be replaced with WASM contracts stored on-chain.
|
||||
//! ** NOTE ***
|
||||
//! This is entirely deprecated with the idea of a single-module Wasm module for state transition.
|
||||
//! The dispatch table should be replaced with the specific functions needed:
|
||||
//! - execute_block(bytes)
|
||||
//! - init_block(PrevBlock?) -> InProgressBlock
|
||||
//! - add_transaction(InProgressBlock) -> InProgressBlock
|
||||
//! I leave it as is for now as it might be removed before this is ever done.
|
||||
// end::description[]
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![recursion_limit="128"]
|
||||
|
||||
extern crate parity_codec as codec;
|
||||
extern crate sr_io as runtime_io;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate substrate_serializer as serializer;
|
||||
extern crate substrate_state_machine as state_machine;
|
||||
extern crate sr_version as runtime_version;
|
||||
|
||||
extern crate serde;
|
||||
extern crate wasmi;
|
||||
extern crate byteorder;
|
||||
extern crate triehash;
|
||||
extern crate parking_lot;
|
||||
extern crate twox_hash;
|
||||
extern crate hashdb;
|
||||
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate assert_matches;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate wabt;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate hex_literal;
|
||||
|
||||
#[macro_use]
|
||||
mod wasm_utils;
|
||||
mod wasm_executor;
|
||||
#[macro_use]
|
||||
mod native_executor;
|
||||
mod sandbox;
|
||||
|
||||
pub mod error;
|
||||
pub use wasm_executor::WasmExecutor;
|
||||
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
|
||||
pub use state_machine::Externalities;
|
||||
pub use runtime_version::RuntimeVersion;
|
||||
pub use codec::Codec;
|
||||
use primitives::Blake2Hasher;
|
||||
|
||||
/// Provides runtime information.
|
||||
pub trait RuntimeInfo {
|
||||
/// Native runtime information if any.
|
||||
const NATIVE_VERSION: Option<RuntimeVersion>;
|
||||
|
||||
/// Extract RuntimeVersion of given :code block
|
||||
fn runtime_version<E: Externalities<Blake2Hasher>> (
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8]
|
||||
) -> Option<RuntimeVersion>;
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use error::{Error, ErrorKind, Result};
|
||||
use state_machine::{CodeExecutor, Externalities};
|
||||
use wasm_executor::WasmExecutor;
|
||||
use wasmi::Module as WasmModule;
|
||||
use runtime_version::RuntimeVersion;
|
||||
use std::collections::HashMap;
|
||||
use codec::Decode;
|
||||
use primitives::hashing::blake2_256;
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
use RuntimeInfo;
|
||||
use primitives::Blake2Hasher;
|
||||
|
||||
// For the internal Runtime Cache:
|
||||
// Is it compatible enough to run this natively or do we need to fall back on the WasmModule
|
||||
|
||||
enum RuntimePreproc {
|
||||
InvalidCode,
|
||||
ValidCode(WasmModule, Option<RuntimeVersion>),
|
||||
}
|
||||
|
||||
type CacheType = HashMap<[u8; 32], RuntimePreproc>;
|
||||
|
||||
lazy_static! {
|
||||
static ref RUNTIMES_CACHE: Mutex<CacheType> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
// helper function to generate low-over-head caching_keys
|
||||
// it is asserted that part of the audit process that any potential on-chain code change
|
||||
// will have done is to ensure that the two-x hash is different to that of any other
|
||||
// :code value from the same chain
|
||||
fn gen_cache_key(code: &[u8]) -> [u8; 32] {
|
||||
blake2_256(code)
|
||||
}
|
||||
|
||||
/// fetch a runtime version from the cache or if there is no cached version yet, create
|
||||
/// the runtime version entry for `code`, determines whether `Compatibility::IsCompatible`
|
||||
/// can be used by comparing returned RuntimeVersion to `ref_version`
|
||||
fn fetch_cached_runtime_version<'a, E: Externalities<Blake2Hasher>>(
|
||||
wasm_executor: &WasmExecutor,
|
||||
cache: &'a mut MutexGuard<CacheType>,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8]
|
||||
) -> Result<(&'a WasmModule, &'a Option<RuntimeVersion>)> {
|
||||
let maybe_runtime_preproc = cache.entry(gen_cache_key(code))
|
||||
.or_insert_with(|| match WasmModule::from_buffer(code) {
|
||||
Ok(module) => {
|
||||
let version = wasm_executor.call_in_wasm_module(ext, heap_pages, &module, "version", &[])
|
||||
.ok()
|
||||
.and_then(|v| RuntimeVersion::decode(&mut v.as_slice()));
|
||||
RuntimePreproc::ValidCode(module, version)
|
||||
}
|
||||
Err(e) => {
|
||||
trace!(target: "executor", "Invalid code presented to executor ({:?})", e);
|
||||
RuntimePreproc::InvalidCode
|
||||
}
|
||||
});
|
||||
match maybe_runtime_preproc {
|
||||
RuntimePreproc::InvalidCode => Err(ErrorKind::InvalidCode(code.into()).into()),
|
||||
RuntimePreproc::ValidCode(m, v) => Ok((m, v)),
|
||||
}
|
||||
}
|
||||
|
||||
fn safe_call<F, U>(f: F) -> Result<U>
|
||||
where F: ::std::panic::UnwindSafe + FnOnce() -> U
|
||||
{
|
||||
// Substrate uses custom panic hook that terminates process on panic. Disable it for the native call.
|
||||
let hook = ::std::panic::take_hook();
|
||||
let result = ::std::panic::catch_unwind(f).map_err(|_| ErrorKind::Runtime.into());
|
||||
::std::panic::set_hook(hook);
|
||||
result
|
||||
}
|
||||
|
||||
/// Set up the externalities and safe calling environment to execute calls to a native runtime.
|
||||
///
|
||||
/// If the inner closure panics, it will be caught and return an error.
|
||||
pub fn with_native_environment<F, U>(ext: &mut Externalities<Blake2Hasher>, f: F) -> Result<U>
|
||||
where F: ::std::panic::UnwindSafe + FnOnce() -> U
|
||||
{
|
||||
::runtime_io::with_externalities(ext, move || safe_call(f))
|
||||
}
|
||||
|
||||
/// Delegate for dispatching a CodeExecutor call to native code.
|
||||
pub trait NativeExecutionDispatch: Send + Sync {
|
||||
/// Get the wasm code that the native dispatch will be equivalent to.
|
||||
fn native_equivalent() -> &'static [u8];
|
||||
|
||||
/// Dispatch a method and input data to be executed natively. Returns `Some` result or `None`
|
||||
/// if the `method` is unknown. Panics if there's an unrecoverable error.
|
||||
// fn dispatch<H: hashdb::Hasher>(ext: &mut Externalities<H>, method: &str, data: &[u8]) -> Result<Vec<u8>>;
|
||||
fn dispatch(ext: &mut Externalities<Blake2Hasher>, method: &str, data: &[u8]) -> Result<Vec<u8>>;
|
||||
|
||||
/// Get native runtime version.
|
||||
const VERSION: RuntimeVersion;
|
||||
|
||||
/// Construct corresponding `NativeExecutor`
|
||||
fn new() -> NativeExecutor<Self> where Self: Sized {
|
||||
NativeExecutor::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
|
||||
/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
|
||||
#[derive(Debug)]
|
||||
pub struct NativeExecutor<D: NativeExecutionDispatch> {
|
||||
/// Dummy field to avoid the compiler complaining about us not using `D`.
|
||||
_dummy: ::std::marker::PhantomData<D>,
|
||||
/// The fallback executor in case native isn't available.
|
||||
fallback: WasmExecutor,
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> NativeExecutor<D> {
|
||||
/// Create new instance.
|
||||
pub fn new() -> Self {
|
||||
NativeExecutor {
|
||||
_dummy: Default::default(),
|
||||
fallback: WasmExecutor::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> Clone for NativeExecutor<D> {
|
||||
fn clone(&self) -> Self {
|
||||
NativeExecutor {
|
||||
_dummy: Default::default(),
|
||||
fallback: self.fallback.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> RuntimeInfo for NativeExecutor<D> {
|
||||
const NATIVE_VERSION: Option<RuntimeVersion> = Some(D::VERSION);
|
||||
|
||||
fn runtime_version<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8],
|
||||
) -> Option<RuntimeVersion> {
|
||||
fetch_cached_runtime_version(&self.fallback, &mut RUNTIMES_CACHE.lock(), ext, heap_pages, code).ok()?.1.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: NativeExecutionDispatch> CodeExecutor<Blake2Hasher> for NativeExecutor<D> {
|
||||
type Error = Error;
|
||||
|
||||
fn call<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8],
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
use_native: bool,
|
||||
) -> (Result<Vec<u8>>, bool) {
|
||||
let mut c = RUNTIMES_CACHE.lock();
|
||||
let (module, onchain_version) = match fetch_cached_runtime_version(&self.fallback, &mut c, ext, heap_pages, code) {
|
||||
Ok((module, onchain_version)) => (module, onchain_version),
|
||||
Err(_) => return (Err(ErrorKind::InvalidCode(code.into()).into()), false),
|
||||
};
|
||||
match (use_native, onchain_version.as_ref().map_or(false, |v| v.can_call_with(&D::VERSION))) {
|
||||
(_, false) => {
|
||||
trace!(target: "executor", "Request for native execution failed (native: {}, chain: {})", D::VERSION, onchain_version.as_ref().map_or_else(||"<None>".into(), |v| format!("{}", v)));
|
||||
(self.fallback.call_in_wasm_module(ext, heap_pages, module, method, data), false)
|
||||
}
|
||||
(false, _) => {
|
||||
(self.fallback.call_in_wasm_module(ext, heap_pages, module, method, data), false)
|
||||
}
|
||||
_ => {
|
||||
trace!(target: "executor", "Request for native execution succeeded (native: {}, chain: {})", D::VERSION, onchain_version.as_ref().map_or_else(||"<None>".into(), |v| format!("{}", v)));
|
||||
(D::dispatch(ext, method, data), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! native_executor_instance {
|
||||
(pub $name:ident, $dispatcher:path, $version:path, $code:expr) => {
|
||||
pub struct $name;
|
||||
native_executor_instance!(IMPL $name, $dispatcher, $version, $code);
|
||||
};
|
||||
($name:ident, $dispatcher:path, $version:path, $code:expr) => {
|
||||
/// A unit struct which implements `NativeExecutionDispatch` feeding in the hard-coded runtime.
|
||||
struct $name;
|
||||
native_executor_instance!(IMPL $name, $dispatcher, $version, $code);
|
||||
};
|
||||
(IMPL $name:ident, $dispatcher:path, $version:path, $code:expr) => {
|
||||
// TODO: this is not so great – I think I should go back to have dispatch take a type param and modify this macro to accept a type param and then pass it in from the test-client instead
|
||||
use primitives::Blake2Hasher as _Blake2Hasher;
|
||||
impl $crate::NativeExecutionDispatch for $name {
|
||||
const VERSION: $crate::RuntimeVersion = $version;
|
||||
fn native_equivalent() -> &'static [u8] {
|
||||
// WARNING!!! This assumes that the runtime was built *before* the main project. Until we
|
||||
// get a proper build script, this must be strictly adhered to or things will go wrong.
|
||||
$code
|
||||
}
|
||||
fn dispatch(ext: &mut $crate::Externalities<_Blake2Hasher>, method: &str, data: &[u8]) -> $crate::error::Result<Vec<u8>> {
|
||||
$crate::with_native_environment(ext, move || $dispatcher(method, data))?
|
||||
.ok_or_else(|| $crate::error::ErrorKind::MethodNotFound(method.to_owned()).into())
|
||||
}
|
||||
|
||||
fn new() -> $crate::NativeExecutor<$name> {
|
||||
$crate::NativeExecutor::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! This module implements sandboxing support in the runtime.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use codec::{Decode, Encode};
|
||||
use primitives::sandbox as sandbox_primitives;
|
||||
use wasm_utils::UserError;
|
||||
use wasmi;
|
||||
use wasmi::memory_units::Pages;
|
||||
use wasmi::{
|
||||
Externals, FuncRef, ImportResolver, MemoryInstance, MemoryRef, Module, ModuleInstance,
|
||||
ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind
|
||||
};
|
||||
|
||||
/// Index of a function inside the supervisor.
|
||||
///
|
||||
/// This is a typically an index in the default table of the supervisor, however
|
||||
/// the exact meaning of this index is depends on the implementation of dispatch function.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
struct SupervisorFuncIndex(usize);
|
||||
|
||||
/// Index of a function within guest index space.
|
||||
///
|
||||
/// This index is supposed to be used with as index for `Externals`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
struct GuestFuncIndex(usize);
|
||||
|
||||
/// This struct holds a mapping from guest index space to supervisor.
|
||||
struct GuestToSupervisorFunctionMapping {
|
||||
funcs: Vec<SupervisorFuncIndex>,
|
||||
}
|
||||
|
||||
impl GuestToSupervisorFunctionMapping {
|
||||
fn new() -> GuestToSupervisorFunctionMapping {
|
||||
GuestToSupervisorFunctionMapping { funcs: Vec::new() }
|
||||
}
|
||||
|
||||
fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex {
|
||||
let idx = self.funcs.len();
|
||||
self.funcs.push(supervisor_func);
|
||||
GuestFuncIndex(idx)
|
||||
}
|
||||
|
||||
fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option<SupervisorFuncIndex> {
|
||||
self.funcs.get(guest_func_idx.0).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
struct Imports {
|
||||
func_map: HashMap<(Vec<u8>, Vec<u8>), GuestFuncIndex>,
|
||||
memories_map: HashMap<(Vec<u8>, Vec<u8>), MemoryRef>,
|
||||
}
|
||||
|
||||
impl ImportResolver for Imports {
|
||||
fn resolve_func(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
signature: &::wasmi::Signature,
|
||||
) -> Result<FuncRef, ::wasmi::Error> {
|
||||
let key = (
|
||||
module_name.as_bytes().to_owned(),
|
||||
field_name.as_bytes().to_owned(),
|
||||
);
|
||||
let idx = *self.func_map.get(&key).ok_or_else(|| {
|
||||
::wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
))
|
||||
})?;
|
||||
Ok(::wasmi::FuncInstance::alloc_host(signature.clone(), idx.0))
|
||||
}
|
||||
|
||||
fn resolve_memory(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_memory_type: &::wasmi::MemoryDescriptor,
|
||||
) -> Result<MemoryRef, ::wasmi::Error> {
|
||||
let key = (
|
||||
module_name.as_bytes().to_vec(),
|
||||
field_name.as_bytes().to_vec(),
|
||||
);
|
||||
let mem = self.memories_map
|
||||
.get(&key)
|
||||
.ok_or_else(|| {
|
||||
::wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
))
|
||||
})?
|
||||
.clone();
|
||||
Ok(mem)
|
||||
}
|
||||
|
||||
fn resolve_global(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_global_type: &::wasmi::GlobalDescriptor,
|
||||
) -> Result<::wasmi::GlobalRef, ::wasmi::Error> {
|
||||
Err(::wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
)))
|
||||
}
|
||||
|
||||
fn resolve_table(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_table_type: &::wasmi::TableDescriptor,
|
||||
) -> Result<::wasmi::TableRef, ::wasmi::Error> {
|
||||
Err(::wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait encapsulates sandboxing capabilities.
|
||||
///
|
||||
/// Note that this functions are only called in the `supervisor` context.
|
||||
pub trait SandboxCapabilities {
|
||||
/// Returns a reference to an associated sandbox `Store`.
|
||||
fn store(&self) -> &Store;
|
||||
|
||||
/// Returns a mutable reference to an associated sandbox `Store`.
|
||||
fn store_mut(&mut self) -> &mut Store;
|
||||
|
||||
/// Allocate space of the specified length in the supervisor memory.
|
||||
///
|
||||
/// Returns pointer to the allocated block.
|
||||
fn allocate(&mut self, len: u32) -> u32;
|
||||
|
||||
/// Deallocate space specified by the pointer that was previously returned by [`allocate`].
|
||||
///
|
||||
/// [`allocate`]: #tymethod.allocate
|
||||
fn deallocate(&mut self, ptr: u32);
|
||||
|
||||
/// Write `data` into the supervisor memory at offset specified by `ptr`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `ptr + data.len()` is out of bounds.
|
||||
fn write_memory(&mut self, ptr: u32, data: &[u8]) -> Result<(), UserError>;
|
||||
|
||||
/// Read `len` bytes from the supervisor memory.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `ptr + len` is out of bounds.
|
||||
fn read_memory(&self, ptr: u32, len: u32) -> Result<Vec<u8>, UserError>;
|
||||
}
|
||||
|
||||
/// Implementation of [`Externals`] that allows execution of guest module with
|
||||
/// [externals][`Externals`] that might refer functions defined by supervisor.
|
||||
///
|
||||
/// [`Externals`]: ../../wasmi/trait.Externals.html
|
||||
pub struct GuestExternals<'a, FE: SandboxCapabilities + Externals + 'a> {
|
||||
supervisor_externals: &'a mut FE,
|
||||
sandbox_instance: &'a SandboxInstance,
|
||||
state: u32,
|
||||
}
|
||||
|
||||
fn trap() -> Trap {
|
||||
TrapKind::Host(Box::new(UserError("Sandbox error"))).into()
|
||||
}
|
||||
|
||||
fn deserialize_result(serialized_result: &[u8]) -> Result<Option<RuntimeValue>, Trap> {
|
||||
use self::sandbox_primitives::{HostError, ReturnValue};
|
||||
let result_val = Result::<ReturnValue, HostError>::decode(&mut &serialized_result[..])
|
||||
.ok_or_else(|| trap())?;
|
||||
|
||||
match result_val {
|
||||
Ok(return_value) => Ok(match return_value {
|
||||
ReturnValue::Unit => None,
|
||||
ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)),
|
||||
}),
|
||||
Err(HostError) => Err(trap()),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, FE: SandboxCapabilities + Externals + 'a> Externals for GuestExternals<'a, FE> {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: usize,
|
||||
args: RuntimeArgs,
|
||||
) -> Result<Option<RuntimeValue>, Trap> {
|
||||
// Make `index` typesafe again.
|
||||
let index = GuestFuncIndex(index);
|
||||
|
||||
let dispatch_thunk = self.sandbox_instance.dispatch_thunk.clone();
|
||||
let func_idx = self.sandbox_instance
|
||||
.guest_to_supervisor_mapping
|
||||
.func_by_guest_index(index)
|
||||
.expect(
|
||||
"`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`;
|
||||
`FuncInstance::alloc_host` is called with indexes that was obtained from `guest_to_supervisor_mapping`;
|
||||
`func_by_guest_index` called with `index` can't return `None`;
|
||||
qed"
|
||||
);
|
||||
|
||||
// Serialize arguments into a byte vector.
|
||||
let invoke_args_data: Vec<u8> = args.as_ref()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(sandbox_primitives::TypedValue::from)
|
||||
.collect::<Vec<_>>()
|
||||
.encode();
|
||||
|
||||
let state = self.state;
|
||||
|
||||
// Move serialized arguments inside the memory and invoke dispatch thunk and
|
||||
// then free allocated memory.
|
||||
let invoke_args_ptr = self.supervisor_externals
|
||||
.allocate(invoke_args_data.len() as u32);
|
||||
self.supervisor_externals
|
||||
.write_memory(invoke_args_ptr, &invoke_args_data)?;
|
||||
let result = ::wasmi::FuncInstance::invoke(
|
||||
&dispatch_thunk,
|
||||
&[
|
||||
RuntimeValue::I32(invoke_args_ptr as i32),
|
||||
RuntimeValue::I32(invoke_args_data.len() as i32),
|
||||
RuntimeValue::I32(state as i32),
|
||||
RuntimeValue::I32(func_idx.0 as i32),
|
||||
],
|
||||
self.supervisor_externals,
|
||||
);
|
||||
self.supervisor_externals.deallocate(invoke_args_ptr);
|
||||
|
||||
// dispatch_thunk returns pointer to serialized arguments.
|
||||
let (serialized_result_val_ptr, serialized_result_val_len) = match result {
|
||||
// Unpack pointer and len of the serialized result data.
|
||||
Ok(Some(RuntimeValue::I64(v))) => {
|
||||
// Cast to u64 to use zero-extension.
|
||||
let v = v as u64;
|
||||
let ptr = (v as u64 >> 32) as u32;
|
||||
let len = (v & 0xFFFFFFFF) as u32;
|
||||
(ptr, len)
|
||||
}
|
||||
_ => return Err(trap()),
|
||||
};
|
||||
|
||||
let serialized_result_val = self.supervisor_externals
|
||||
.read_memory(serialized_result_val_ptr, serialized_result_val_len)?;
|
||||
self.supervisor_externals
|
||||
.deallocate(serialized_result_val_ptr);
|
||||
|
||||
// We do not have to check the signature here, because it's automatically
|
||||
// checked by wasmi.
|
||||
|
||||
deserialize_result(&serialized_result_val)
|
||||
}
|
||||
}
|
||||
|
||||
fn with_guest_externals<FE, R, F>(
|
||||
supervisor_externals: &mut FE,
|
||||
sandbox_instance: &SandboxInstance,
|
||||
state: u32,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
FE: SandboxCapabilities + Externals,
|
||||
F: FnOnce(&mut GuestExternals<FE>) -> R,
|
||||
{
|
||||
let mut guest_externals = GuestExternals {
|
||||
supervisor_externals,
|
||||
sandbox_instance,
|
||||
state,
|
||||
};
|
||||
f(&mut guest_externals)
|
||||
}
|
||||
|
||||
/// Sandboxed instance of a wasm module.
|
||||
///
|
||||
/// It's primary purpose is to [`invoke`] exported functions on it.
|
||||
///
|
||||
/// All imports of this instance are specified at the creation time and
|
||||
/// imports are implemented by the supervisor.
|
||||
///
|
||||
/// Hence, in order to invoke an exported function on a sandboxed module instance,
|
||||
/// it's required to provide supervisor externals: it will be used to execute
|
||||
/// code in the supervisor context.
|
||||
///
|
||||
/// [`invoke`]: #method.invoke
|
||||
pub struct SandboxInstance {
|
||||
instance: ModuleRef,
|
||||
dispatch_thunk: FuncRef,
|
||||
guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping,
|
||||
}
|
||||
|
||||
impl SandboxInstance {
|
||||
/// Invoke an exported function by a name.
|
||||
///
|
||||
/// `supervisor_externals` is required to execute the implementations
|
||||
/// of the syscalls that published to a sandboxed module instance.
|
||||
///
|
||||
/// The `state` parameter can be used to provide custom data for
|
||||
/// these syscall implementations.
|
||||
pub fn invoke<FE: SandboxCapabilities + Externals>(
|
||||
&self,
|
||||
export_name: &str,
|
||||
args: &[RuntimeValue],
|
||||
supervisor_externals: &mut FE,
|
||||
state: u32,
|
||||
) -> Result<Option<wasmi::RuntimeValue>, wasmi::Error> {
|
||||
with_guest_externals(
|
||||
supervisor_externals,
|
||||
self,
|
||||
state,
|
||||
|guest_externals| {
|
||||
self.instance
|
||||
.invoke_export(export_name, args, guest_externals)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_environment_definition(
|
||||
raw_env_def: &[u8],
|
||||
memories: &[Option<MemoryRef>],
|
||||
) -> Result<(Imports, GuestToSupervisorFunctionMapping), UserError> {
|
||||
let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut &raw_env_def[..]).ok_or_else(|| UserError("Sandbox error"))?;
|
||||
|
||||
let mut func_map = HashMap::new();
|
||||
let mut memories_map = HashMap::new();
|
||||
let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new();
|
||||
|
||||
for entry in &env_def.entries {
|
||||
let module = entry.module_name.clone();
|
||||
let field = entry.field_name.clone();
|
||||
|
||||
match entry.entity {
|
||||
sandbox_primitives::ExternEntity::Function(func_idx) => {
|
||||
let externals_idx =
|
||||
guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize));
|
||||
func_map.insert((module, field), externals_idx);
|
||||
}
|
||||
sandbox_primitives::ExternEntity::Memory(memory_idx) => {
|
||||
let memory_ref = memories
|
||||
.get(memory_idx as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.ok_or_else(|| UserError("Sandbox error"))?;
|
||||
memories_map.insert((module, field), memory_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
Imports {
|
||||
func_map,
|
||||
memories_map,
|
||||
},
|
||||
guest_to_supervisor_mapping,
|
||||
))
|
||||
}
|
||||
|
||||
/// Instantiate a guest module and return it's index in the store.
|
||||
///
|
||||
/// The guest module's code is specified in `wasm`. Environment that will be available to
|
||||
/// guest module is specified in `raw_env_def` (serialized version of [`EnvironmentDefinition`]).
|
||||
/// `dispatch_thunk` is used as function that handle calls from guests.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if any of the following conditions happens:
|
||||
///
|
||||
/// - `raw_env_def` can't be deserialized as a [`EnvironmentDefinition`].
|
||||
/// - Module in `wasm` is invalid or couldn't be instantiated.
|
||||
///
|
||||
/// [`EnvironmentDefinition`]: ../../sandbox/struct.EnvironmentDefinition.html
|
||||
pub fn instantiate<FE: SandboxCapabilities + Externals>(
|
||||
supervisor_externals: &mut FE,
|
||||
dispatch_thunk: FuncRef,
|
||||
wasm: &[u8],
|
||||
raw_env_def: &[u8],
|
||||
state: u32,
|
||||
) -> Result<u32, UserError> {
|
||||
let (imports, guest_to_supervisor_mapping) =
|
||||
decode_environment_definition(raw_env_def, &supervisor_externals.store().memories)?;
|
||||
|
||||
let module = Module::from_buffer(wasm).map_err(|_| UserError("Sandbox error"))?;
|
||||
let instance = ModuleInstance::new(&module, &imports).map_err(|_| UserError("Sandbox error"))?;
|
||||
|
||||
let sandbox_instance = Rc::new(SandboxInstance {
|
||||
// In general, it's not a very good idea to use `.not_started_instance()` for anything
|
||||
// but for extracting memory and tables. But in this particular case, we are extracting
|
||||
// for the purpose of running `start` function which should be ok.
|
||||
instance: instance.not_started_instance().clone(),
|
||||
dispatch_thunk,
|
||||
guest_to_supervisor_mapping,
|
||||
});
|
||||
|
||||
with_guest_externals(
|
||||
supervisor_externals,
|
||||
&sandbox_instance,
|
||||
state,
|
||||
|guest_externals| {
|
||||
instance
|
||||
.run_start(guest_externals)
|
||||
.map_err(|_| UserError("Sandbox error"))
|
||||
},
|
||||
)?;
|
||||
|
||||
let instance_idx = supervisor_externals
|
||||
.store_mut()
|
||||
.register_sandbox_instance(sandbox_instance);
|
||||
|
||||
Ok(instance_idx)
|
||||
}
|
||||
|
||||
/// This struct keeps track of all sandboxed components.
|
||||
pub struct Store {
|
||||
// Memories and instances are `Some` untill torndown.
|
||||
instances: Vec<Option<Rc<SandboxInstance>>>,
|
||||
memories: Vec<Option<MemoryRef>>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
/// Create a new empty sandbox store.
|
||||
pub fn new() -> Store {
|
||||
Store {
|
||||
instances: Vec::new(),
|
||||
memories: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new memory instance and return it's index.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if the memory couldn't be created.
|
||||
/// Typically happens if `initial` is more than `maximum`.
|
||||
pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result<u32, UserError> {
|
||||
let maximum = match maximum {
|
||||
sandbox_primitives::MEM_UNLIMITED => None,
|
||||
specified_limit => Some(Pages(specified_limit as usize)),
|
||||
};
|
||||
|
||||
let mem =
|
||||
MemoryInstance::alloc(Pages(initial as usize), maximum).map_err(|_| UserError("Sandbox error"))?;
|
||||
let mem_idx = self.memories.len();
|
||||
self.memories.push(Some(mem));
|
||||
Ok(mem_idx as u32)
|
||||
}
|
||||
|
||||
/// Returns `SandboxInstance` by `instance_idx`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` If `instance_idx` isn't a valid index of an instance or
|
||||
/// instance is already torndown.
|
||||
pub fn instance(&self, instance_idx: u32) -> Result<Rc<SandboxInstance>, UserError> {
|
||||
self.instances
|
||||
.get(instance_idx as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.ok_or_else(|| UserError("Sandbox error"))
|
||||
}
|
||||
|
||||
/// Returns reference to a memory instance by `memory_idx`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` If `memory_idx` isn't a valid index of an memory or
|
||||
/// memory is already torndown.
|
||||
pub fn memory(&self, memory_idx: u32) -> Result<MemoryRef, UserError> {
|
||||
self.memories
|
||||
.get(memory_idx as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.ok_or_else(|| UserError("Sandbox error"))
|
||||
}
|
||||
|
||||
/// Teardown the memory at the specified index.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if `memory_idx` isn't a valid index of an memory.
|
||||
pub fn memory_teardown(&mut self, memory_idx: u32) -> Result<(), UserError> {
|
||||
if memory_idx as usize >= self.memories.len() {
|
||||
return Err(UserError("Sandbox error"));
|
||||
}
|
||||
self.memories[memory_idx as usize] = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Teardown the instance at the specified index.
|
||||
pub fn instance_teardown(&mut self, instance_idx: u32) -> Result<(), UserError> {
|
||||
if instance_idx as usize >= self.instances.len() {
|
||||
return Err(UserError("Sandbox error"));
|
||||
}
|
||||
self.instances[instance_idx as usize] = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_sandbox_instance(&mut self, sandbox_instance: Rc<SandboxInstance>) -> u32 {
|
||||
let instance_idx = self.instances.len();
|
||||
self.instances.push(Some(sandbox_instance));
|
||||
instance_idx as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use wasm_executor::WasmExecutor;
|
||||
use state_machine::TestExternalities;
|
||||
use wabt;
|
||||
|
||||
#[test]
|
||||
fn sandbox_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
let code = wabt::wat2wasm(r#"
|
||||
(module
|
||||
(import "env" "assert" (func $assert (param i32)))
|
||||
(import "env" "inc_counter" (func $inc_counter (param i32) (result i32)))
|
||||
(func (export "call")
|
||||
(drop
|
||||
(call $inc_counter (i32.const 5))
|
||||
)
|
||||
|
||||
(call $inc_counter (i32.const 3))
|
||||
;; current counter value is on the stack
|
||||
|
||||
;; check whether current == 8
|
||||
i32.const 8
|
||||
i32.eq
|
||||
|
||||
call $assert
|
||||
)
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_trap() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
let code = wabt::wat2wasm(r#"
|
||||
(module
|
||||
(import "env" "assert" (func $assert (param i32)))
|
||||
(func (export "call")
|
||||
i32.const 0
|
||||
call $assert
|
||||
)
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
vec![0],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_called() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
let code = wabt::wat2wasm(r#"
|
||||
(module
|
||||
(import "env" "assert" (func $assert (param i32)))
|
||||
(import "env" "inc_counter" (func $inc_counter (param i32) (result i32)))
|
||||
|
||||
;; Start function
|
||||
(start $start)
|
||||
(func $start
|
||||
;; Increment counter by 1
|
||||
(drop
|
||||
(call $inc_counter (i32.const 1))
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "call")
|
||||
;; Increment counter by 1. The current value is placed on the stack.
|
||||
(call $inc_counter (i32.const 1))
|
||||
|
||||
;; Counter is incremented twice by 1, once there and once in `start` func.
|
||||
;; So check the returned value is equal to 2.
|
||||
i32.const 2
|
||||
i32.eq
|
||||
call $assert
|
||||
)
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invoke_args() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
let code = wabt::wat2wasm(r#"
|
||||
(module
|
||||
(import "env" "assert" (func $assert (param i32)))
|
||||
|
||||
(func (export "call") (param $x i32) (param $y i64)
|
||||
;; assert that $x = 0x12345678
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(get_local $x)
|
||||
(i32.const 0x12345678)
|
||||
)
|
||||
)
|
||||
|
||||
(call $assert
|
||||
(i64.eq
|
||||
(get_local $y)
|
||||
(i64.const 0x1234567887654321)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_args", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_val() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
let code = wabt::wat2wasm(r#"
|
||||
(module
|
||||
(func (export "call") (param $x i32) (result i32)
|
||||
(i32.add
|
||||
(get_local $x)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_return_val", &code).unwrap(),
|
||||
vec![1],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Rust implementation of Substrate contracts.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use wasmi::{
|
||||
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder
|
||||
};
|
||||
use wasmi::RuntimeValue::{I32, I64};
|
||||
use wasmi::memory_units::{Pages, Bytes};
|
||||
use state_machine::Externalities;
|
||||
use error::{Error, ErrorKind, Result};
|
||||
use wasm_utils::UserError;
|
||||
use primitives::{blake2_256, twox_128, twox_256, ed25519};
|
||||
use primitives::hexdisplay::HexDisplay;
|
||||
use primitives::sandbox as sandbox_primitives;
|
||||
use primitives::Blake2Hasher;
|
||||
use triehash::ordered_trie_root;
|
||||
use sandbox;
|
||||
|
||||
|
||||
struct Heap {
|
||||
end: u32,
|
||||
}
|
||||
|
||||
impl Heap {
|
||||
/// Construct new `Heap` struct with a given number of pages.
|
||||
///
|
||||
/// Returns `Err` if the heap couldn't allocate required
|
||||
/// number of pages.
|
||||
///
|
||||
/// This could mean that wasm binary specifies memory
|
||||
/// limit and we are trying to allocate beyond that limit.
|
||||
fn new(memory: &MemoryRef, pages: usize) -> Result<Self> {
|
||||
let prev_page_count = memory.initial();
|
||||
memory.grow(Pages(pages)).map_err(|_| Error::from(ErrorKind::Runtime))?;
|
||||
Ok(Heap {
|
||||
end: Bytes::from(prev_page_count).0 as u32,
|
||||
})
|
||||
}
|
||||
|
||||
fn allocate(&mut self, size: u32) -> u32 {
|
||||
let r = self.end;
|
||||
self.end += size;
|
||||
r
|
||||
}
|
||||
|
||||
fn deallocate(&mut self, _offset: u32) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="wasm-extern-trace")]
|
||||
macro_rules! debug_trace {
|
||||
( $( $x:tt )* ) => ( trace!( $( $x )* ) )
|
||||
}
|
||||
#[cfg(not(feature="wasm-extern-trace"))]
|
||||
macro_rules! debug_trace {
|
||||
( $( $x:tt )* ) => ()
|
||||
}
|
||||
|
||||
struct FunctionExecutor<'e, E: Externalities<Blake2Hasher> + 'e> {
|
||||
sandbox_store: sandbox::Store,
|
||||
heap: Heap,
|
||||
memory: MemoryRef,
|
||||
table: Option<TableRef>,
|
||||
ext: &'e mut E,
|
||||
hash_lookup: HashMap<Vec<u8>, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<'e, E: Externalities<Blake2Hasher>> FunctionExecutor<'e, E> {
|
||||
fn new(m: MemoryRef, heap_pages: usize, t: Option<TableRef>, e: &'e mut E) -> Result<Self> {
|
||||
Ok(FunctionExecutor {
|
||||
sandbox_store: sandbox::Store::new(),
|
||||
heap: Heap::new(&m, heap_pages)?,
|
||||
memory: m,
|
||||
table: t,
|
||||
ext: e,
|
||||
hash_lookup: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'e, E: Externalities<Blake2Hasher>> sandbox::SandboxCapabilities for FunctionExecutor<'e, E> {
|
||||
fn store(&self) -> &sandbox::Store {
|
||||
&self.sandbox_store
|
||||
}
|
||||
fn store_mut(&mut self) -> &mut sandbox::Store {
|
||||
&mut self.sandbox_store
|
||||
}
|
||||
fn allocate(&mut self, len: u32) -> u32 {
|
||||
self.heap.allocate(len)
|
||||
}
|
||||
fn deallocate(&mut self, ptr: u32) {
|
||||
self.heap.deallocate(ptr)
|
||||
}
|
||||
fn write_memory(&mut self, ptr: u32, data: &[u8]) -> ::std::result::Result<(), UserError> {
|
||||
self.memory.set(ptr, data).map_err(|_| UserError("Invalid attempt to write_memory"))
|
||||
}
|
||||
fn read_memory(&self, ptr: u32, len: u32) -> ::std::result::Result<Vec<u8>, UserError> {
|
||||
self.memory.get(ptr, len as usize).map_err(|_| UserError("Invalid attempt to write_memory"))
|
||||
}
|
||||
}
|
||||
|
||||
trait WritePrimitive<T: Sized> {
|
||||
fn write_primitive(&self, offset: u32, t: T) -> ::std::result::Result<(), UserError>;
|
||||
}
|
||||
|
||||
impl WritePrimitive<u32> for MemoryInstance {
|
||||
fn write_primitive(&self, offset: u32, t: u32) -> ::std::result::Result<(), UserError> {
|
||||
use byteorder::{LittleEndian, ByteOrder};
|
||||
let mut r = [0u8; 4];
|
||||
LittleEndian::write_u32(&mut r, t);
|
||||
self.set(offset, &r).map_err(|_| UserError("Invalid attempt to write_primitive"))
|
||||
}
|
||||
}
|
||||
|
||||
trait ReadPrimitive<T: Sized> {
|
||||
fn read_primitive(&self, offset: u32) -> ::std::result::Result<T, UserError>;
|
||||
}
|
||||
|
||||
impl ReadPrimitive<u32> for MemoryInstance {
|
||||
fn read_primitive(&self, offset: u32) -> ::std::result::Result<u32, UserError> {
|
||||
use byteorder::{LittleEndian, ByteOrder};
|
||||
Ok(LittleEndian::read_u32(&self.get(offset, 4).map_err(|_| UserError("Invalid attempt to read_primitive"))?))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this macro does not support `where` clauses and that seems somewhat tricky to add
|
||||
impl_function_executor!(this: FunctionExecutor<'e, E>,
|
||||
ext_print_utf8(utf8_data: *const u8, utf8_len: u32) => {
|
||||
if let Ok(utf8) = this.memory.get(utf8_data, utf8_len as usize) {
|
||||
if let Ok(message) = String::from_utf8(utf8) {
|
||||
println!("{}", message);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
ext_print_hex(data: *const u8, len: u32) => {
|
||||
if let Ok(hex) = this.memory.get(data, len as usize) {
|
||||
println!("{}", HexDisplay::from(&hex));
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
ext_print_num(number: u64) => {
|
||||
println!("{}", number);
|
||||
Ok(())
|
||||
},
|
||||
ext_memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 => {
|
||||
let sl1 = this.memory.get(s1, n as usize).map_err(|_| UserError("Invalid attempt to read from memory in first arg of ext_memcmp"))?;
|
||||
let sl2 = this.memory.get(s2, n as usize).map_err(|_| UserError("Invalid attempt to read from memory in second arg of ext_memcmp"))?;
|
||||
Ok(match sl1.cmp(&sl2) {
|
||||
Ordering::Greater => 1,
|
||||
Ordering::Less => -1,
|
||||
Ordering::Equal => 0,
|
||||
})
|
||||
},
|
||||
ext_memcpy(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 => {
|
||||
this.memory.copy_nonoverlapping(src as usize, dest as usize, count as usize)
|
||||
.map_err(|_| UserError("Invalid attempt to copy_nonoverlapping in ext_memcpy"))?;
|
||||
debug_trace!(target: "sr-io", "memcpy {} from {}, {} bytes", dest, src, count);
|
||||
Ok(dest)
|
||||
},
|
||||
ext_memmove(dest: *mut u8, src: *const u8, count: usize) -> *mut u8 => {
|
||||
this.memory.copy(src as usize, dest as usize, count as usize)
|
||||
.map_err(|_| UserError("Invalid attempt to copy in ext_memmove"))?;
|
||||
debug_trace!(target: "sr-io", "memmove {} from {}, {} bytes", dest, src, count);
|
||||
Ok(dest)
|
||||
},
|
||||
ext_memset(dest: *mut u8, val: u32, count: usize) -> *mut u8 => {
|
||||
debug_trace!(target: "sr-io", "memset {} with {}, {} bytes", dest, val, count);
|
||||
this.memory.clear(dest as usize, val as u8, count as usize)
|
||||
.map_err(|_| UserError("Invalid attempt to clear in ext_memset"))?;
|
||||
Ok(dest)
|
||||
},
|
||||
ext_malloc(size: usize) -> *mut u8 => {
|
||||
let r = this.heap.allocate(size);
|
||||
debug_trace!(target: "sr-io", "malloc {} bytes at {}", size, r);
|
||||
Ok(r)
|
||||
},
|
||||
ext_free(addr: *mut u8) => {
|
||||
this.heap.deallocate(addr);
|
||||
debug_trace!(target: "sr-io", "free {}", addr);
|
||||
Ok(())
|
||||
},
|
||||
ext_set_storage(key_data: *const u8, key_len: u32, value_data: *const u8, value_len: u32) => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_set_storage"))?;
|
||||
let value = this.memory.get(value_data, value_len as usize).map_err(|_| UserError("Invalid attempt to determine value in ext_set_storage"))?;
|
||||
if let Some(_preimage) = this.hash_lookup.get(&key) {
|
||||
debug_trace!(target: "wasm-trace", "*** Setting storage: %{} -> {} [k={}]", ::primitives::hexdisplay::ascii_format(&_preimage), HexDisplay::from(&value), HexDisplay::from(&key));
|
||||
} else {
|
||||
debug_trace!(target: "wasm-trace", "*** Setting storage: {} -> {} [k={}]", ::primitives::hexdisplay::ascii_format(&key), HexDisplay::from(&value), HexDisplay::from(&key));
|
||||
}
|
||||
this.ext.set_storage(key, value);
|
||||
Ok(())
|
||||
},
|
||||
ext_clear_storage(key_data: *const u8, key_len: u32) => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_clear_storage"))?;
|
||||
debug_trace!(target: "wasm-trace", "*** Clearing storage: {} [k={}]",
|
||||
if let Some(_preimage) = this.hash_lookup.get(&key) {
|
||||
format!("%{}", ::primitives::hexdisplay::ascii_format(&_preimage))
|
||||
} else {
|
||||
format!(" {}", ::primitives::hexdisplay::ascii_format(&key))
|
||||
}, HexDisplay::from(&key));
|
||||
this.ext.clear_storage(&key);
|
||||
Ok(())
|
||||
},
|
||||
ext_exists_storage(key_data: *const u8, key_len: u32) -> u32 => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_exists_storage"))?;
|
||||
Ok(if this.ext.exists_storage(&key) { 1 } else { 0 })
|
||||
},
|
||||
ext_clear_prefix(prefix_data: *const u8, prefix_len: u32) => {
|
||||
let prefix = this.memory.get(prefix_data, prefix_len as usize).map_err(|_| UserError("Invalid attempt to determine prefix in ext_clear_prefix"))?;
|
||||
this.ext.clear_prefix(&prefix);
|
||||
Ok(())
|
||||
},
|
||||
// return 0 and place u32::max_value() into written_out if no value exists for the key.
|
||||
ext_get_allocated_storage(key_data: *const u8, key_len: u32, written_out: *mut u32) -> *mut u8 => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to determine key in ext_get_allocated_storage"))?;
|
||||
let maybe_value = this.ext.storage(&key);
|
||||
|
||||
debug_trace!(target: "wasm-trace", "*** Getting storage: {} == {} [k={}]",
|
||||
if let Some(_preimage) = this.hash_lookup.get(&key) {
|
||||
format!("%{}", ::primitives::hexdisplay::ascii_format(&_preimage))
|
||||
} else {
|
||||
format!(" {}", ::primitives::hexdisplay::ascii_format(&key))
|
||||
},
|
||||
if let Some(ref b) = maybe_value {
|
||||
format!("{}", HexDisplay::from(b))
|
||||
} else {
|
||||
"<empty>".to_owned()
|
||||
},
|
||||
HexDisplay::from(&key)
|
||||
);
|
||||
|
||||
if let Some(value) = maybe_value {
|
||||
let offset = this.heap.allocate(value.len() as u32) as u32;
|
||||
this.memory.set(offset, &value).map_err(|_| UserError("Invalid attempt to set memory in ext_get_allocated_storage"))?;
|
||||
this.memory.write_primitive(written_out, value.len() as u32)
|
||||
.map_err(|_| UserError("Invalid attempt to write written_out in ext_get_allocated_storage"))?;
|
||||
Ok(offset)
|
||||
} else {
|
||||
this.memory.write_primitive(written_out, u32::max_value())
|
||||
.map_err(|_| UserError("Invalid attempt to write failed written_out in ext_get_allocated_storage"))?;
|
||||
Ok(0)
|
||||
}
|
||||
},
|
||||
// return u32::max_value() if no value exists for the key.
|
||||
ext_get_storage_into(key_data: *const u8, key_len: u32, value_data: *mut u8, value_len: u32, value_offset: u32) -> u32 => {
|
||||
let key = this.memory.get(key_data, key_len as usize).map_err(|_| UserError("Invalid attempt to get key in ext_get_storage_into"))?;
|
||||
let maybe_value = this.ext.storage(&key);
|
||||
debug_trace!(target: "wasm-trace", "*** Getting storage: {} == {} [k={}]",
|
||||
if let Some(_preimage) = this.hash_lookup.get(&key) {
|
||||
format!("%{}", ::primitives::hexdisplay::ascii_format(&_preimage))
|
||||
} else {
|
||||
format!(" {}", ::primitives::hexdisplay::ascii_format(&key))
|
||||
},
|
||||
if let Some(ref b) = maybe_value {
|
||||
format!("{}", HexDisplay::from(b))
|
||||
} else {
|
||||
"<empty>".to_owned()
|
||||
},
|
||||
HexDisplay::from(&key)
|
||||
);
|
||||
|
||||
if let Some(value) = maybe_value {
|
||||
let value = &value[value_offset as usize..];
|
||||
let written = ::std::cmp::min(value_len as usize, value.len());
|
||||
this.memory.set(value_data, &value[..written]).map_err(|_| UserError("Invalid attempt to set value in ext_get_storage_into"))?;
|
||||
Ok(written as u32)
|
||||
} else {
|
||||
Ok(u32::max_value())
|
||||
}
|
||||
},
|
||||
ext_storage_root(result: *mut u8) => {
|
||||
let r = this.ext.storage_root();
|
||||
this.memory.set(result, r.as_ref()).map_err(|_| UserError("Invalid attempt to set memory in ext_storage_root"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_blake2_256_enumerated_trie_root(values_data: *const u8, lens_data: *const u32, lens_len: u32, result: *mut u8) => {
|
||||
let values = (0..lens_len)
|
||||
.map(|i| this.memory.read_primitive(lens_data + i * 4))
|
||||
.collect::<::std::result::Result<Vec<u32>, UserError>>()?
|
||||
.into_iter()
|
||||
.scan(0u32, |acc, v| { let o = *acc; *acc += v; Some((o, v)) })
|
||||
.map(|(offset, len)|
|
||||
this.memory.get(values_data + offset, len as usize)
|
||||
.map_err(|_| UserError("Invalid attempt to get memory in ext_blake2_256_enumerated_trie_root"))
|
||||
)
|
||||
.collect::<::std::result::Result<Vec<_>, UserError>>()?;
|
||||
let r = ordered_trie_root::<Blake2Hasher, _, _>(values.into_iter());
|
||||
this.memory.set(result, &r[..]).map_err(|_| UserError("Invalid attempt to set memory in ext_blake2_256_enumerated_trie_root"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_chain_id() -> u64 => {
|
||||
Ok(this.ext.chain_id())
|
||||
},
|
||||
ext_twox_128(data: *const u8, len: u32, out: *mut u8) => {
|
||||
let result = if len == 0 {
|
||||
let hashed = twox_128(&[0u8; 0]);
|
||||
debug_trace!(target: "xxhash", "XXhash: '' -> {}", HexDisplay::from(&hashed));
|
||||
this.hash_lookup.insert(hashed.to_vec(), vec![]);
|
||||
hashed
|
||||
} else {
|
||||
let key = this.memory.get(data, len as usize).map_err(|_| UserError("Invalid attempt to get key in ext_twox_128"))?;
|
||||
let hashed_key = twox_128(&key);
|
||||
debug_trace!(target: "xxhash", "XXhash: {} -> {}",
|
||||
if let Ok(_skey) = ::std::str::from_utf8(&key) {
|
||||
_skey.to_owned()
|
||||
} else {
|
||||
format!("{}", HexDisplay::from(&key))
|
||||
},
|
||||
HexDisplay::from(&hashed_key)
|
||||
);
|
||||
this.hash_lookup.insert(hashed_key.to_vec(), key);
|
||||
hashed_key
|
||||
};
|
||||
|
||||
this.memory.set(out, &result).map_err(|_| UserError("Invalid attempt to set result in ext_twox_128"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_twox_256(data: *const u8, len: u32, out: *mut u8) => {
|
||||
let result = if len == 0 {
|
||||
twox_256(&[0u8; 0])
|
||||
} else {
|
||||
twox_256(&this.memory.get(data, len as usize).map_err(|_| UserError("Invalid attempt to get data in ext_twox_256"))?)
|
||||
};
|
||||
this.memory.set(out, &result).map_err(|_| UserError("Invalid attempt to set result in ext_twox_256"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_blake2_256(data: *const u8, len: u32, out: *mut u8) => {
|
||||
let result = if len == 0 {
|
||||
blake2_256(&[0u8; 0])
|
||||
} else {
|
||||
blake2_256(&this.memory.get(data, len as usize).map_err(|_| UserError("Invalid attempt to get data in ext_blake2_256"))?)
|
||||
};
|
||||
this.memory.set(out, &result).map_err(|_| UserError("Invalid attempt to set result in ext_blake2_256"))?;
|
||||
Ok(())
|
||||
},
|
||||
ext_ed25519_verify(msg_data: *const u8, msg_len: u32, sig_data: *const u8, pubkey_data: *const u8) -> u32 => {
|
||||
let mut sig = [0u8; 64];
|
||||
this.memory.get_into(sig_data, &mut sig[..]).map_err(|_| UserError("Invalid attempt to get signature in ext_ed25519_verify"))?;
|
||||
let mut pubkey = [0u8; 32];
|
||||
this.memory.get_into(pubkey_data, &mut pubkey[..]).map_err(|_| UserError("Invalid attempt to get pubkey in ext_ed25519_verify"))?;
|
||||
let msg = this.memory.get(msg_data, msg_len as usize).map_err(|_| UserError("Invalid attempt to get message in ext_ed25519_verify"))?;
|
||||
|
||||
Ok(if ed25519::verify(&sig, &msg, &pubkey) {
|
||||
0
|
||||
} else {
|
||||
5
|
||||
})
|
||||
},
|
||||
ext_sandbox_instantiate(dispatch_thunk_idx: usize, wasm_ptr: *const u8, wasm_len: usize, imports_ptr: *const u8, imports_len: usize, state: usize) -> u32 => {
|
||||
let wasm = this.memory.get(wasm_ptr, wasm_len as usize).map_err(|_| UserError("Sandbox error"))?;
|
||||
let raw_env_def = this.memory.get(imports_ptr, imports_len as usize).map_err(|_| UserError("Sandbox error"))?;
|
||||
|
||||
// Extract a dispatch thunk from instance's table by the specified index.
|
||||
let dispatch_thunk = {
|
||||
let table = this.table.as_ref().ok_or_else(|| UserError("Sandbox error"))?;
|
||||
table.get(dispatch_thunk_idx)
|
||||
.map_err(|_| UserError("Sandbox error"))?
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.clone()
|
||||
};
|
||||
|
||||
let instance_idx = sandbox::instantiate(this, dispatch_thunk, &wasm, &raw_env_def, state)?;
|
||||
|
||||
Ok(instance_idx as u32)
|
||||
},
|
||||
ext_sandbox_instance_teardown(instance_idx: u32) => {
|
||||
this.sandbox_store.instance_teardown(instance_idx)?;
|
||||
Ok(())
|
||||
},
|
||||
ext_sandbox_invoke(instance_idx: u32, export_ptr: *const u8, export_len: usize, args_ptr: *const u8, args_len: usize, return_val_ptr: *const u8, return_val_len: usize, state: usize) -> u32 => {
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
trace!(target: "sr-sandbox", "invoke, instance_idx={}", instance_idx);
|
||||
let export = this.memory.get(export_ptr, export_len as usize)
|
||||
.map_err(|_| UserError("Sandbox error"))
|
||||
.and_then(|b|
|
||||
String::from_utf8(b)
|
||||
.map_err(|_| UserError("Sandbox error"))
|
||||
)?;
|
||||
|
||||
// Deserialize arguments and convert them into wasmi types.
|
||||
let serialized_args = this.memory.get(args_ptr, args_len as usize)
|
||||
.map_err(|_| UserError("Sandbox error"))?;
|
||||
let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &serialized_args[..])
|
||||
.ok_or_else(|| UserError("Sandbox error"))?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = this.sandbox_store.instance(instance_idx)?;
|
||||
let result = instance.invoke(&export, &args, this, state);
|
||||
|
||||
match result {
|
||||
Ok(None) => Ok(sandbox_primitives::ERR_OK),
|
||||
Ok(Some(val)) => {
|
||||
// Serialize return value and write it back into the memory.
|
||||
sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| {
|
||||
if val.len() > return_val_len as usize {
|
||||
Err(UserError("Sandbox error"))?;
|
||||
}
|
||||
this.memory
|
||||
.set(return_val_ptr, val)
|
||||
.map_err(|_| UserError("Sandbox error"))?;
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
}
|
||||
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
|
||||
}
|
||||
},
|
||||
ext_sandbox_memory_new(initial: u32, maximum: u32) -> u32 => {
|
||||
let mem_idx = this.sandbox_store.new_memory(initial, maximum)?;
|
||||
Ok(mem_idx)
|
||||
},
|
||||
ext_sandbox_memory_get(memory_idx: u32, offset: u32, buf_ptr: *mut u8, buf_len: usize) -> u32 => {
|
||||
let dst_memory = this.sandbox_store.memory(memory_idx)?;
|
||||
|
||||
let data: Vec<u8> = match dst_memory.get(offset, buf_len as usize) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
match this.memory.set(buf_ptr, &data) {
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
},
|
||||
ext_sandbox_memory_set(memory_idx: u32, offset: u32, val_ptr: *const u8, val_len: usize) -> u32 => {
|
||||
let dst_memory = this.sandbox_store.memory(memory_idx)?;
|
||||
|
||||
let data = match this.memory.get(offset, val_len as usize) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
match dst_memory.set(val_ptr, &data) {
|
||||
Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
},
|
||||
ext_sandbox_memory_teardown(memory_idx: u32) => {
|
||||
this.sandbox_store.memory_teardown(memory_idx)?;
|
||||
Ok(())
|
||||
},
|
||||
=> <'e, E: Externalities<Blake2Hasher> + 'e>
|
||||
);
|
||||
|
||||
/// Wasm rust executor for contracts.
|
||||
///
|
||||
/// Executes the provided code in a sandboxed wasm runtime.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WasmExecutor {
|
||||
}
|
||||
|
||||
impl WasmExecutor {
|
||||
|
||||
/// Create a new instance.
|
||||
pub fn new() -> Self {
|
||||
WasmExecutor{}
|
||||
}
|
||||
|
||||
/// Call a given method in the given code.
|
||||
/// This should be used for tests only.
|
||||
pub fn call<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
code: &[u8],
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let module = ::wasmi::Module::from_buffer(code).expect("all modules compiled with rustc are valid wasm code; qed");
|
||||
self.call_in_wasm_module(ext, heap_pages, &module, method, data)
|
||||
}
|
||||
|
||||
/// Call a given method in the given wasm-module runtime.
|
||||
pub fn call_in_wasm_module<E: Externalities<Blake2Hasher>>(
|
||||
&self,
|
||||
ext: &mut E,
|
||||
heap_pages: usize,
|
||||
module: &Module,
|
||||
method: &str,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
// start module instantiation. Don't run 'start' function yet.
|
||||
let intermediate_instance = ModuleInstance::new(
|
||||
module,
|
||||
&ImportsBuilder::new()
|
||||
.with_resolver("env", FunctionExecutor::<E>::resolver())
|
||||
)?;
|
||||
|
||||
// extract a reference to a linear memory, optional reference to a table
|
||||
// and then initialize FunctionExecutor.
|
||||
let memory = intermediate_instance
|
||||
.not_started_instance()
|
||||
.export_by_name("memory")
|
||||
// TODO: with code coming from the blockchain it isn't strictly been compiled with rustc anymore.
|
||||
// these assumptions are probably not true anymore
|
||||
.expect("all modules compiled with rustc should have an export named 'memory'; qed")
|
||||
.as_memory()
|
||||
.expect("in module generated by rustc export named 'memory' should be a memory; qed")
|
||||
.clone();
|
||||
let table: Option<TableRef> = intermediate_instance
|
||||
.not_started_instance()
|
||||
.export_by_name("__indirect_function_table")
|
||||
.and_then(|e| e.as_table().cloned());
|
||||
|
||||
let mut fec = FunctionExecutor::new(memory.clone(), heap_pages, table, ext)?;
|
||||
|
||||
// finish instantiation by running 'start' function (if any).
|
||||
let instance = intermediate_instance.run_start(&mut fec)?;
|
||||
|
||||
let size = data.len() as u32;
|
||||
let offset = fec.heap.allocate(size);
|
||||
memory.set(offset, &data)?;
|
||||
|
||||
let result = instance.invoke_export(
|
||||
method,
|
||||
&[
|
||||
I32(offset as i32),
|
||||
I32(size as i32)
|
||||
],
|
||||
&mut fec
|
||||
);
|
||||
|
||||
let returned = match result {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
trace!(target: "wasm-executor", "Failed to execute code with {} pages", heap_pages);
|
||||
return Err(e.into())
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(I64(r)) = returned {
|
||||
let offset = r as u32;
|
||||
let length = (r >> 32) as u32 as usize;
|
||||
memory.get(offset, length)
|
||||
.map_err(|_| ErrorKind::Runtime.into())
|
||||
} else {
|
||||
Err(ErrorKind::InvalidReturn.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codec::Encode;
|
||||
use state_machine::TestExternalities;
|
||||
|
||||
// TODO: move into own crate.
|
||||
macro_rules! map {
|
||||
($( $name:expr => $value:expr ),*) => (
|
||||
vec![ $( ( $name, $value ) ),* ].into_iter().collect()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returning_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_empty_return", &[]).unwrap();
|
||||
assert_eq!(output, vec![0u8; 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panicking_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_panic", &[]);
|
||||
assert!(output.is_err());
|
||||
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_conditional_panic", &[2]);
|
||||
assert!(output.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
ext.set_storage(b"foo".to_vec(), b"bar".to_vec());
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_data_in", b"Hello world").unwrap();
|
||||
|
||||
assert_eq!(output, b"all ok!".to_vec());
|
||||
|
||||
let expected : TestExternalities<_> = map![
|
||||
b"input".to_vec() => b"Hello world".to_vec(),
|
||||
b"foo".to_vec() => b"bar".to_vec(),
|
||||
b"baz".to_vec() => b"bar".to_vec()
|
||||
];
|
||||
assert_eq!(expected, ext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_prefix_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
ext.set_storage(b"aaa".to_vec(), b"1".to_vec());
|
||||
ext.set_storage(b"aab".to_vec(), b"2".to_vec());
|
||||
ext.set_storage(b"aba".to_vec(), b"3".to_vec());
|
||||
ext.set_storage(b"abb".to_vec(), b"4".to_vec());
|
||||
ext.set_storage(b"bbb".to_vec(), b"5".to_vec());
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
|
||||
// This will clear all entries which prefix is "ab".
|
||||
let output = WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_clear_prefix", b"ab").unwrap();
|
||||
|
||||
assert_eq!(output, b"all ok!".to_vec());
|
||||
|
||||
let expected: TestExternalities<_> = map![
|
||||
b"aaa".to_vec() => b"1".to_vec(),
|
||||
b"aab".to_vec() => b"2".to_vec(),
|
||||
b"bbb".to_vec() => b"5".to_vec()
|
||||
];
|
||||
assert_eq!(expected, ext);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blake2_256_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_256", &[]).unwrap(),
|
||||
blake2_256(&b""[..]).encode()
|
||||
);
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_blake2_256", b"Hello world!").unwrap(),
|
||||
blake2_256(&b"Hello world!"[..]).encode()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twox_256_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_256", &[]).unwrap(),
|
||||
hex!("99e9d85137db46ef4bbea33613baafd56f963c64b1f3685a4eb4abd67ff6203a")
|
||||
);
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_256", b"Hello world!").unwrap(),
|
||||
hex!("b27dfd7f223f177f2a13647b533599af0c07f68bda23d96d059da2b451a35a74")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twox_128_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_128", &[]).unwrap(),
|
||||
hex!("99e9d85137db46ef4bbea33613baafd5")
|
||||
);
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_twox_128", b"Hello world!").unwrap(),
|
||||
hex!("b27dfd7f223f177f2a13647b533599af")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ed25519_verify_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
let key = ed25519::Pair::from_seed(&blake2_256(b"test"));
|
||||
let sig = key.sign(b"all ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
||||
vec![1]
|
||||
);
|
||||
|
||||
let other_sig = key.sign(b"all is not ok!");
|
||||
let mut calldata = vec![];
|
||||
calldata.extend_from_slice(key.public().as_ref());
|
||||
calldata.extend_from_slice(other_sig.as_ref());
|
||||
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_ed25519_verify", &calldata).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enumerated_trie_root_should_work() {
|
||||
let mut ext = TestExternalities::default();
|
||||
let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm");
|
||||
assert_eq!(
|
||||
WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_enumerated_trie_root", &[]).unwrap(),
|
||||
ordered_trie_root::<Blake2Hasher, _, _>(vec![b"zero".to_vec(), b"one".to_vec(), b"two".to_vec()]).0.encode()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Rust implementation of Substrate contracts.
|
||||
|
||||
use wasmi::{ValueType, RuntimeValue, HostError};
|
||||
use wasmi::nan_preserving_float::{F32, F64};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserError(pub &'static str);
|
||||
impl fmt::Display for UserError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "UserError: {}", self.0)
|
||||
}
|
||||
}
|
||||
impl HostError for UserError {
|
||||
}
|
||||
|
||||
pub trait ConvertibleToWasm { const VALUE_TYPE: ValueType; type NativeType; fn to_runtime_value(self) -> RuntimeValue; }
|
||||
impl ConvertibleToWasm for i32 { type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self) } }
|
||||
impl ConvertibleToWasm for u32 { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) } }
|
||||
impl ConvertibleToWasm for i64 { type NativeType = i64; const VALUE_TYPE: ValueType = ValueType::I64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self) } }
|
||||
impl ConvertibleToWasm for u64 { type NativeType = u64; const VALUE_TYPE: ValueType = ValueType::I64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self as i64) } }
|
||||
impl ConvertibleToWasm for F32 { type NativeType = F32; const VALUE_TYPE: ValueType = ValueType::F32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::F32(self) } }
|
||||
impl ConvertibleToWasm for F64 { type NativeType = F64; const VALUE_TYPE: ValueType = ValueType::F64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::F64(self) } }
|
||||
impl ConvertibleToWasm for isize { type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) } }
|
||||
impl ConvertibleToWasm for usize { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as u32 as i32) } }
|
||||
impl<T> ConvertibleToWasm for *const T { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) } }
|
||||
impl<T> ConvertibleToWasm for *mut T { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) } }
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! convert_args {
|
||||
() => ([]);
|
||||
( $( $t:ty ),* ) => ( [ $( { use $crate::wasm_utils::ConvertibleToWasm; <$t>::VALUE_TYPE }, )* ] );
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! gen_signature {
|
||||
( ( $( $params: ty ),* ) ) => (
|
||||
{
|
||||
$crate::wasmi::Signature::new(&convert_args!($($params),*)[..], None)
|
||||
}
|
||||
);
|
||||
|
||||
( ( $( $params: ty ),* ) -> $returns: ty ) => (
|
||||
{
|
||||
$crate::wasmi::Signature::new(&convert_args!($($params),*)[..], Some({
|
||||
use $crate::wasm_utils::ConvertibleToWasm; <$returns>::VALUE_TYPE
|
||||
}))
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! resolve_fn {
|
||||
(@iter $index:expr, $sig_var:ident, $name_var:ident) => ();
|
||||
(@iter $index:expr, $sig_var:ident, $name_var:ident $name:ident ( $( $params:ty ),* ) $( -> $returns:ty )* => $($tail:tt)* ) => (
|
||||
if $name_var == stringify!($name) {
|
||||
let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* );
|
||||
if $sig_var != &signature {
|
||||
return Err($crate::wasmi::Error::Instantiation(
|
||||
format!("Export {} has different signature {:?}", $name_var, $sig_var),
|
||||
));
|
||||
}
|
||||
return Ok($crate::wasmi::FuncInstance::alloc_host(signature, $index));
|
||||
}
|
||||
resolve_fn!(@iter $index + 1, $sig_var, $name_var $($tail)*)
|
||||
);
|
||||
|
||||
($sig_var:ident, $name_var:ident, $($tail:tt)* ) => (
|
||||
resolve_fn!(@iter 0, $sig_var, $name_var $($tail)*);
|
||||
);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! unmarshall_args {
|
||||
( $body:tt, $objectname:ident, $args_iter:ident, $( $names:ident : $params:ty ),*) => ({
|
||||
$(
|
||||
let $names : <$params as $crate::wasm_utils::ConvertibleToWasm>::NativeType =
|
||||
$args_iter.next()
|
||||
.and_then(|rt_val| rt_val.try_into())
|
||||
.expect(
|
||||
"`$args_iter` comes from an argument of Externals::invoke_index;
|
||||
args to an external call always matches the signature of the external;
|
||||
external signatures are built with count and types and in order defined by `$params`;
|
||||
here, we iterating on `$params`;
|
||||
qed;
|
||||
"
|
||||
);
|
||||
)*
|
||||
$body
|
||||
})
|
||||
}
|
||||
|
||||
/// Since we can't specify the type of closure directly at binding site:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let f: FnOnce() -> Result<<u32 as ConvertibleToWasm>::NativeType, _> = || { /* ... */ };
|
||||
/// ```
|
||||
///
|
||||
/// we use this function to constrain the type of the closure.
|
||||
#[inline(always)]
|
||||
pub fn constrain_closure<R, F>(f: F) -> F
|
||||
where
|
||||
F: FnOnce() -> Result<R, ::wasmi::Trap>
|
||||
{
|
||||
f
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! marshall {
|
||||
( $args_iter:ident, $objectname:ident, ( $( $names:ident : $params:ty ),* ) -> $returns:ty => $body:tt ) => ({
|
||||
let body = $crate::wasm_utils::constrain_closure::<
|
||||
<$returns as $crate::wasm_utils::ConvertibleToWasm>::NativeType, _
|
||||
>(|| {
|
||||
unmarshall_args!($body, $objectname, $args_iter, $( $names : $params ),*)
|
||||
});
|
||||
let r = body()?;
|
||||
return Ok(Some({ use $crate::wasm_utils::ConvertibleToWasm; r.to_runtime_value() }))
|
||||
});
|
||||
( $args_iter:ident, $objectname:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
|
||||
let body = $crate::wasm_utils::constrain_closure::<(), _>(|| {
|
||||
unmarshall_args!($body, $objectname, $args_iter, $( $names : $params ),*)
|
||||
});
|
||||
body()?;
|
||||
return Ok(None)
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! dispatch_fn {
|
||||
( @iter $index:expr, $index_ident:ident, $objectname:ident, $args_iter:ident) => {
|
||||
// `$index` comes from an argument of Externals::invoke_index;
|
||||
// externals are always invoked with index given by resolve_fn! at resolve time;
|
||||
// For each next function resolve_fn! gives new index, starting from 0;
|
||||
// Both dispatch_fn! and resolve_fn! are called with the same list of functions;
|
||||
// qed;
|
||||
panic!("fn with index {} is undefined", $index);
|
||||
};
|
||||
|
||||
( @iter $index:expr, $index_ident:ident, $objectname:ident, $args_iter:ident, $name:ident ( $( $names:ident : $params:ty ),* ) $( -> $returns:ty )* => $body:tt $($tail:tt)*) => (
|
||||
if $index_ident == $index {
|
||||
{ marshall!($args_iter, $objectname, ( $( $names : $params ),* ) $( -> $returns )* => $body) }
|
||||
}
|
||||
dispatch_fn!( @iter $index + 1, $index_ident, $objectname, $args_iter $($tail)*)
|
||||
);
|
||||
|
||||
( $index_ident:ident, $objectname:ident, $args_iter:ident, $($tail:tt)* ) => (
|
||||
dispatch_fn!( @iter 0, $index_ident, $objectname, $args_iter, $($tail)*);
|
||||
);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_function_executor {
|
||||
( $objectname:ident : $structname:ty,
|
||||
$( $name:ident ( $( $names:ident : $params:ty ),* ) $( -> $returns:ty )* => $body:tt , )*
|
||||
=> $($pre:tt)+ ) => (
|
||||
impl $( $pre ) + $structname {
|
||||
#[allow(unused)]
|
||||
fn resolver() -> &'static $crate::wasmi::ModuleImportResolver {
|
||||
struct Resolver;
|
||||
impl $crate::wasmi::ModuleImportResolver for Resolver {
|
||||
fn resolve_func(&self, name: &str, signature: &$crate::wasmi::Signature) -> ::std::result::Result<$crate::wasmi::FuncRef, $crate::wasmi::Error> {
|
||||
resolve_fn!(signature, name, $( $name( $( $params ),* ) $( -> $returns )* => )*);
|
||||
|
||||
Err($crate::wasmi::Error::Instantiation(
|
||||
format!("Export {} not found", name),
|
||||
))
|
||||
}
|
||||
}
|
||||
&Resolver
|
||||
}
|
||||
}
|
||||
|
||||
impl $( $pre ) + $crate::wasmi::Externals for $structname {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: usize,
|
||||
args: $crate::wasmi::RuntimeArgs,
|
||||
) -> ::std::result::Result<Option<$crate::wasmi::RuntimeValue>, $crate::wasmi::Trap> {
|
||||
let $objectname = self;
|
||||
let mut args = args.as_ref().iter();
|
||||
dispatch_fn!(index, $objectname, args, $( $name( $( $names : $params ),* ) $( -> $returns )* => $body ),*);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Generated
+216
@@ -0,0 +1,216 @@
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "fixed-hash"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "hashdb"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "parity-codec"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parity-codec-derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.14.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plain_hasher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pwasm-alloc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"pwasm-libc 0.1.0",
|
||||
"rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pwasm-libc"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "runtime-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sr-io 0.1.0",
|
||||
"sr-sandbox 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hex"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "sr-io"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hashdb 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec 0.1.0",
|
||||
"rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-std 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sr-sandbox"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"parity-codec 0.1.0",
|
||||
"rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-io 0.1.0",
|
||||
"sr-std 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sr-std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"pwasm-alloc 0.1.0",
|
||||
"pwasm-libc 0.1.0",
|
||||
"rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "substrate-primitives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fixed-hash 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hashdb 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec 0.1.0",
|
||||
"parity-codec-derive 0.1.0",
|
||||
"plain_hasher 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-hex 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-std 0.1.0",
|
||||
"uint 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uint"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-hex 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"
|
||||
"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
|
||||
"checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda"
|
||||
"checksum fixed-hash 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d5ec8112f00ea8a483e04748a85522184418fd1cf02890b626d8fc28683f7de"
|
||||
"checksum hashdb 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f1c71fc577cde89b3345d5f2880fecaf462a32e96c619f431279bdaf1ba5ddb1"
|
||||
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
|
||||
"checksum plain_hasher 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "95fa6386b1d34aaf0adb9b7dd2885dbe7c34190e6263785e5a7ec2b19044a90f"
|
||||
"checksum proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "cccdc7557a98fe98453030f077df7f3a042052fae465bb61d2c2c41435cfd9b6"
|
||||
"checksum quote 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3372dc35766b36a99ce2352bd1b6ea0137c38d215cc0c8780bf6de6df7842ba9"
|
||||
"checksum rustc-hex 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b03280c2813907a030785570c577fb27d3deec8da4c18566751ade94de0ace"
|
||||
"checksum rustc_version 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9743a7670d88d5d52950408ecdb7c71d8986251ab604d4689dd2ca25c9bca69"
|
||||
"checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537"
|
||||
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526"
|
||||
"checksum syn 0.14.7 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e13df71f29f9440b50261a5882c86eac334f1badb3134ec26f0de2f1418e44"
|
||||
"checksum uint 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "754ba11732b9161b94c41798e5197e5e75388d012f760c42adb5000353e98646"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "runtime-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
sr-io = { path = "../../sr-io", version = "0.1", default_features = false }
|
||||
sr-sandbox = { path = "../../sr-sandbox", version = "0.1", default_features = false }
|
||||
substrate-primitives = { path = "../../primitives", default_features = false }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
|
||||
[workspace]
|
||||
members = []
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cargo +nightly build --target=wasm32-unknown-unknown --release
|
||||
for i in test
|
||||
do
|
||||
wasm-gc target/wasm32-unknown-unknown/release/runtime_$i.wasm target/wasm32-unknown-unknown/release/runtime_$i.compact.wasm
|
||||
done
|
||||
@@ -0,0 +1,122 @@
|
||||
#![no_std]
|
||||
#![feature(panic_handler)]
|
||||
#![cfg_attr(feature = "strict", deny(warnings))]
|
||||
|
||||
#![feature(alloc)]
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[macro_use]
|
||||
extern crate sr_io as runtime_io;
|
||||
extern crate sr_sandbox as sandbox;
|
||||
extern crate substrate_primitives;
|
||||
|
||||
use runtime_io::{
|
||||
set_storage, storage, clear_prefix, print, blake2_256,
|
||||
twox_128, twox_256, ed25519_verify, enumerated_trie_root
|
||||
};
|
||||
|
||||
impl_stubs!(
|
||||
test_data_in NO_DECODE => |input| {
|
||||
print("set_storage");
|
||||
set_storage(b"input", input);
|
||||
|
||||
print("storage");
|
||||
let foo = storage(b"foo").unwrap();
|
||||
|
||||
print("set_storage");
|
||||
set_storage(b"baz", &foo);
|
||||
|
||||
print("finished!");
|
||||
b"all ok!".to_vec()
|
||||
},
|
||||
test_clear_prefix NO_DECODE => |input| {
|
||||
clear_prefix(input);
|
||||
b"all ok!".to_vec()
|
||||
},
|
||||
test_empty_return NO_DECODE => |_| Vec::new(),
|
||||
test_panic NO_DECODE => |_| panic!("test panic"),
|
||||
test_conditional_panic NO_DECODE => |input: &[u8]| {
|
||||
if input.len() > 0 {
|
||||
panic!("test panic")
|
||||
}
|
||||
input.to_vec()
|
||||
},
|
||||
test_blake2_256 NO_DECODE => |input| blake2_256(input).to_vec(),
|
||||
test_twox_256 NO_DECODE => |input| twox_256(input).to_vec(),
|
||||
test_twox_128 NO_DECODE => |input| twox_128(input).to_vec(),
|
||||
test_ed25519_verify NO_DECODE => |input: &[u8]| {
|
||||
let mut pubkey = [0; 32];
|
||||
let mut sig = [0; 64];
|
||||
|
||||
pubkey.copy_from_slice(&input[0..32]);
|
||||
sig.copy_from_slice(&input[32..96]);
|
||||
|
||||
let msg = b"all ok!";
|
||||
[ed25519_verify(&sig, &msg[..], &pubkey) as u8].to_vec()
|
||||
},
|
||||
test_enumerated_trie_root NO_DECODE => |_| {
|
||||
enumerated_trie_root::<substrate_primitives::Blake2Hasher>(&[&b"zero"[..], &b"one"[..], &b"two"[..]]).to_vec()
|
||||
},
|
||||
test_sandbox NO_DECODE => |code: &[u8]| {
|
||||
let ok = execute_sandboxed(code, &[]).is_ok();
|
||||
[ok as u8].to_vec()
|
||||
},
|
||||
test_sandbox_args NO_DECODE => |code: &[u8]| {
|
||||
let ok = execute_sandboxed(
|
||||
code,
|
||||
&[
|
||||
sandbox::TypedValue::I32(0x12345678),
|
||||
sandbox::TypedValue::I64(0x1234567887654321),
|
||||
]
|
||||
).is_ok();
|
||||
[ok as u8].to_vec()
|
||||
},
|
||||
test_sandbox_return_val NO_DECODE => |code: &[u8]| {
|
||||
let result = execute_sandboxed(
|
||||
code,
|
||||
&[
|
||||
sandbox::TypedValue::I32(0x1336),
|
||||
]
|
||||
);
|
||||
let ok = if let Ok(sandbox::ReturnValue::Value(sandbox::TypedValue::I32(0x1337))) = result { true } else { false };
|
||||
[ok as u8].to_vec()
|
||||
}
|
||||
);
|
||||
|
||||
fn execute_sandboxed(code: &[u8], args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
|
||||
struct State {
|
||||
counter: u32,
|
||||
}
|
||||
|
||||
fn env_assert(_e: &mut State, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
|
||||
if args.len() != 1 {
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
let condition = args[0].as_i32().ok_or_else(|| sandbox::HostError)?;
|
||||
if condition != 0 {
|
||||
Ok(sandbox::ReturnValue::Unit)
|
||||
} else {
|
||||
Err(sandbox::HostError)
|
||||
}
|
||||
}
|
||||
fn env_inc_counter(e: &mut State, args: &[sandbox::TypedValue]) -> Result<sandbox::ReturnValue, sandbox::HostError> {
|
||||
if args.len() != 1 {
|
||||
return Err(sandbox::HostError);
|
||||
}
|
||||
let inc_by = args[0].as_i32().ok_or_else(|| sandbox::HostError)?;
|
||||
e.counter += inc_by as u32;
|
||||
Ok(sandbox::ReturnValue::Value(sandbox::TypedValue::I32(e.counter as i32)))
|
||||
}
|
||||
|
||||
let mut state = State { counter: 0 };
|
||||
|
||||
let mut env_builder = sandbox::EnvironmentDefinitionBuilder::new();
|
||||
env_builder.add_host_func("env", "assert", env_assert);
|
||||
env_builder.add_host_func("env", "inc_counter", env_inc_counter);
|
||||
|
||||
let mut instance = sandbox::Instance::new(code, &env_builder, &mut state)?;
|
||||
let result = instance.invoke(b"call", args, &mut state);
|
||||
|
||||
result.map_err(|_| sandbox::HostError)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "substrate-extrinsic-pool"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
error-chain = "0.12"
|
||||
futures = "0.1"
|
||||
log = "0.3"
|
||||
parking_lot = "0.4"
|
||||
transaction-pool = "1.13.2"
|
||||
sr-primitives = { path = "../../core/sr-primitives" }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-test-client = { path = "../../core/test-client" }
|
||||
substrate-keyring = { path = "../../core/keyring" }
|
||||
parity-codec = { path = "../../codec" }
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= extrinsic-pool
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! External Error trait for extrinsic pool.
|
||||
|
||||
use txpool;
|
||||
|
||||
/// Extrinsic pool error.
|
||||
pub trait IntoPoolError: ::std::error::Error + Send + Sized {
|
||||
/// Try to extract original `txpool::Error`
|
||||
///
|
||||
/// This implementation is optional and used only to
|
||||
/// provide more descriptive error messages for end users
|
||||
/// of RPC API.
|
||||
fn into_pool_error(self) -> Result<txpool::Error, Self> { Err(self) }
|
||||
}
|
||||
|
||||
impl IntoPoolError for txpool::Error {
|
||||
fn into_pool_error(self) -> Result<txpool::Error, Self> { Ok(self) }
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// tag::description[]
|
||||
//! Generic extrinsic pool.
|
||||
// end::description[]
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(unused_extern_crates)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate parking_lot;
|
||||
extern crate sr_primitives as runtime_primitives;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate transaction_pool as txpool;
|
||||
#[cfg(test)] extern crate substrate_test_client as test_client;
|
||||
#[cfg(test)] extern crate substrate_keyring as keyring;
|
||||
#[cfg(test)] extern crate parity_codec as codec;
|
||||
|
||||
pub mod watcher;
|
||||
mod error;
|
||||
mod listener;
|
||||
mod pool;
|
||||
mod rotator;
|
||||
|
||||
pub use listener::Listener;
|
||||
pub use pool::{Pool, ChainApi, EventStream, Verified, VerifiedFor, ExtrinsicFor, ExHash, AllExtrinsics};
|
||||
pub use txpool::scoring;
|
||||
pub use txpool::{Error, ErrorKind};
|
||||
pub use error::IntoPoolError;
|
||||
pub use txpool::{Options, Status, LightStatus, VerifiedTransaction, Readiness, Transaction};
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{
|
||||
sync::Arc,
|
||||
fmt,
|
||||
collections::HashMap,
|
||||
};
|
||||
use txpool;
|
||||
|
||||
use watcher;
|
||||
|
||||
/// Extrinsic pool default listener.
|
||||
#[derive(Default)]
|
||||
pub struct Listener<H: ::std::hash::Hash + Eq> {
|
||||
watchers: HashMap<H, watcher::Sender<H>>
|
||||
}
|
||||
|
||||
impl<H: ::std::hash::Hash + Eq + Copy + fmt::Debug + fmt::LowerHex + Default> Listener<H> {
|
||||
/// Creates a new watcher for given verified extrinsic.
|
||||
///
|
||||
/// The watcher can be used to subscribe to lifecycle events of that extrinsic.
|
||||
pub fn create_watcher<T: txpool::VerifiedTransaction<Hash=H>>(&mut self, xt: Arc<T>) -> watcher::Watcher<H> {
|
||||
let sender = self.watchers.entry(*xt.hash()).or_insert_with(watcher::Sender::default);
|
||||
sender.new_watcher()
|
||||
}
|
||||
|
||||
/// Notify the listeners about extrinsic broadcast.
|
||||
pub fn broadcasted(&mut self, hash: &H, peers: Vec<String>) {
|
||||
self.fire(hash, |watcher| watcher.broadcast(peers));
|
||||
}
|
||||
|
||||
fn fire<F>(&mut self, hash: &H, fun: F) where F: FnOnce(&mut watcher::Sender<H>) {
|
||||
let clean = if let Some(h) = self.watchers.get_mut(hash) {
|
||||
fun(h);
|
||||
h.is_done()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if clean {
|
||||
self.watchers.remove(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T> txpool::Listener<T> for Listener<H> where
|
||||
H: ::std::hash::Hash + Eq + Copy + fmt::Debug + fmt::LowerHex + Default,
|
||||
T: txpool::VerifiedTransaction<Hash=H>,
|
||||
{
|
||||
fn added(&mut self, tx: &Arc<T>, old: Option<&Arc<T>>) {
|
||||
if let Some(old) = old {
|
||||
let hash = tx.hash();
|
||||
self.fire(old.hash(), |watcher| watcher.usurped(*hash));
|
||||
}
|
||||
}
|
||||
|
||||
fn dropped(&mut self, tx: &Arc<T>, by: Option<&T>) {
|
||||
self.fire(tx.hash(), |watcher| match by {
|
||||
Some(t) => watcher.usurped(*t.hash()),
|
||||
None => watcher.dropped(),
|
||||
})
|
||||
}
|
||||
|
||||
fn rejected(&mut self, tx: &Arc<T>, reason: &txpool::ErrorKind) {
|
||||
warn!(target: "extrinsic-pool", "Extrinsic rejected ({}): {:?}", reason, tx);
|
||||
}
|
||||
|
||||
fn invalid(&mut self, tx: &Arc<T>) {
|
||||
warn!(target: "extrinsic-pool", "Extrinsic invalid: {:?}", tx);
|
||||
}
|
||||
|
||||
fn canceled(&mut self, tx: &Arc<T>) {
|
||||
debug!(target: "extrinsic-pool", "Extrinsic canceled: {:?}", tx);
|
||||
}
|
||||
|
||||
fn culled(&mut self, tx: &Arc<T>) {
|
||||
// TODO [ToDr] latest block number?
|
||||
let header_hash = Default::default();
|
||||
self.fire(tx.hash(), |watcher| watcher.finalised(header_hash))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt,
|
||||
sync::Arc,
|
||||
time,
|
||||
};
|
||||
use futures::sync::mpsc;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use txpool::{self, Scoring, Readiness};
|
||||
|
||||
use error::IntoPoolError;
|
||||
use listener::Listener;
|
||||
use rotator::PoolRotator;
|
||||
use watcher::Watcher;
|
||||
|
||||
use runtime_primitives::{generic::BlockId, traits::Block as BlockT};
|
||||
|
||||
/// Modification notification event stream type;
|
||||
pub type EventStream = mpsc::UnboundedReceiver<()>;
|
||||
|
||||
/// Extrinsic hash type for a pool.
|
||||
pub type ExHash<A> = <A as ChainApi>::Hash;
|
||||
/// Extrinsic type for a pool.
|
||||
pub type ExtrinsicFor<A> = <<A as ChainApi>::Block as BlockT>::Extrinsic;
|
||||
/// Verified extrinsic data for `ChainApi`.
|
||||
pub type VerifiedFor<A> = Verified<ExtrinsicFor<A>, <A as ChainApi>::VEx>;
|
||||
/// A collection of all extrinsics.
|
||||
pub type AllExtrinsics<A> = BTreeMap<<<A as ChainApi>::VEx as txpool::VerifiedTransaction>::Sender, Vec<ExtrinsicFor<A>>>;
|
||||
|
||||
/// Verified extrinsic struct. Wraps original extrinsic and verification info.
|
||||
#[derive(Debug)]
|
||||
pub struct Verified<Ex, VEx> {
|
||||
/// Original extrinsic.
|
||||
pub original: Ex,
|
||||
/// Verification data.
|
||||
pub verified: VEx,
|
||||
/// Pool deadline, after it's reached we remove the extrinsic from the pool.
|
||||
pub valid_till: time::Instant,
|
||||
}
|
||||
|
||||
impl<Ex, VEx> txpool::VerifiedTransaction for Verified<Ex, VEx>
|
||||
where
|
||||
Ex: fmt::Debug,
|
||||
VEx: txpool::VerifiedTransaction,
|
||||
{
|
||||
type Hash = <VEx as txpool::VerifiedTransaction>::Hash;
|
||||
type Sender = <VEx as txpool::VerifiedTransaction>::Sender;
|
||||
|
||||
fn hash(&self) -> &Self::Hash {
|
||||
self.verified.hash()
|
||||
}
|
||||
|
||||
fn sender(&self) -> &Self::Sender {
|
||||
self.verified.sender()
|
||||
}
|
||||
|
||||
fn mem_usage(&self) -> usize {
|
||||
// TODO: add `original` mem usage.
|
||||
self.verified.mem_usage()
|
||||
}
|
||||
}
|
||||
|
||||
/// Concrete extrinsic validation and query logic.
|
||||
pub trait ChainApi: Send + Sync {
|
||||
/// Block type.
|
||||
type Block: BlockT;
|
||||
/// Extrinsic hash type.
|
||||
type Hash: ::std::hash::Hash + Eq + Copy + fmt::Debug + fmt::LowerHex + Serialize + DeserializeOwned + ::std::str::FromStr + Send + Sync + Default + 'static;
|
||||
/// Extrinsic sender type.
|
||||
type Sender: ::std::hash::Hash + fmt::Debug + Serialize + DeserializeOwned + Eq + Clone + Send + Sync + Ord + Default;
|
||||
/// Unchecked extrinsic type.
|
||||
/// Verified extrinsic type.
|
||||
type VEx: txpool::VerifiedTransaction<Hash=Self::Hash, Sender=Self::Sender> + Send + Sync + Clone;
|
||||
/// Readiness evaluator
|
||||
type Ready;
|
||||
/// Error type.
|
||||
type Error: From<txpool::Error> + IntoPoolError;
|
||||
/// Score type.
|
||||
type Score: ::std::cmp::Ord + Clone + Default + fmt::Debug + Send + Send + Sync + fmt::LowerHex;
|
||||
/// Custom scoring update event type.
|
||||
type Event: ::std::fmt::Debug;
|
||||
/// Verify extrinsic at given block.
|
||||
fn verify_transaction(&self, at: &BlockId<Self::Block>, uxt: &ExtrinsicFor<Self>) -> Result<Self::VEx, Self::Error>;
|
||||
|
||||
/// Create new readiness evaluator.
|
||||
fn ready(&self) -> Self::Ready;
|
||||
|
||||
/// Check readiness for verified extrinsic at given block.
|
||||
fn is_ready(&self, at: &BlockId<Self::Block>, context: &mut Self::Ready, xt: &VerifiedFor<Self>) -> Readiness;
|
||||
|
||||
/// Decides on ordering of `T`s from a particular sender.
|
||||
fn compare(old: &VerifiedFor<Self>, other: &VerifiedFor<Self>) -> ::std::cmp::Ordering;
|
||||
|
||||
/// Decides how to deal with two transactions from a sender that seem to occupy the same slot in the queue.
|
||||
fn choose(old: &VerifiedFor<Self>, new: &VerifiedFor<Self>) -> txpool::scoring::Choice;
|
||||
|
||||
/// Updates the transaction scores given a list of transactions and a change to previous scoring.
|
||||
/// NOTE: you can safely assume that both slices have the same length.
|
||||
/// (i.e. score at index `i` represents transaction at the same index)
|
||||
fn update_scores(xts: &[txpool::Transaction<VerifiedFor<Self>>], scores: &mut [Self::Score], change: txpool::scoring::Change<Self::Event>);
|
||||
|
||||
/// Decides if `new` should push out `old` transaction from the pool.
|
||||
///
|
||||
/// NOTE returning `InsertNew` here can lead to some transactions being accepted above pool limits.
|
||||
fn should_replace(old: &VerifiedFor<Self>, new: &VerifiedFor<Self>) -> txpool::scoring::Choice;
|
||||
}
|
||||
|
||||
pub struct Ready<'a, 'b, B: 'a + ChainApi> {
|
||||
api: &'a B,
|
||||
at: &'b BlockId<B::Block>,
|
||||
context: B::Ready,
|
||||
rotator: &'a PoolRotator<B::Hash>,
|
||||
now: time::Instant,
|
||||
}
|
||||
|
||||
impl<'a, 'b, B: ChainApi> txpool::Ready<VerifiedFor<B>> for Ready<'a, 'b, B> {
|
||||
fn is_ready(&mut self, xt: &VerifiedFor<B>) -> Readiness {
|
||||
if self.rotator.ban_if_stale(&self.now, xt) {
|
||||
debug!(target: "extrinsic-pool", "[{:?}] Banning as stale.", txpool::VerifiedTransaction::hash(xt));
|
||||
return Readiness::Stale;
|
||||
}
|
||||
|
||||
self.api.is_ready(self.at, &mut self.context, xt)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScoringAdapter<T>(::std::marker::PhantomData<T>);
|
||||
|
||||
impl<T> ::std::fmt::Debug for ScoringAdapter<T> {
|
||||
fn fmt(&self, _f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ChainApi> Scoring<VerifiedFor<T>> for ScoringAdapter<T> {
|
||||
type Score = <T as ChainApi>::Score;
|
||||
type Event = <T as ChainApi>::Event;
|
||||
|
||||
fn compare(&self, old: &VerifiedFor<T>, other: &VerifiedFor<T>) -> ::std::cmp::Ordering {
|
||||
T::compare(old, other)
|
||||
}
|
||||
|
||||
fn choose(&self, old: &VerifiedFor<T>, new: &VerifiedFor<T>) -> txpool::scoring::Choice {
|
||||
T::choose(old, new)
|
||||
}
|
||||
|
||||
fn update_scores(&self, xts: &[txpool::Transaction<VerifiedFor<T>>], scores: &mut [Self::Score], change: txpool::scoring::Change<Self::Event>) {
|
||||
T::update_scores(xts, scores, change)
|
||||
}
|
||||
|
||||
fn should_replace(&self, old: &VerifiedFor<T>, new: &VerifiedFor<T>) -> txpool::scoring::Choice {
|
||||
T::should_replace(old, new)
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum time the transaction will be kept in the pool.
|
||||
///
|
||||
/// Transactions that don't get included within the limit are removed from the pool.
|
||||
const POOL_TIME: time::Duration = time::Duration::from_secs(60 * 5);
|
||||
|
||||
/// Extrinsics pool.
|
||||
pub struct Pool<B: ChainApi> {
|
||||
api: B,
|
||||
pool: RwLock<txpool::Pool<
|
||||
VerifiedFor<B>,
|
||||
ScoringAdapter<B>,
|
||||
Listener<B::Hash>,
|
||||
>>,
|
||||
import_notification_sinks: Mutex<Vec<mpsc::UnboundedSender<()>>>,
|
||||
rotator: PoolRotator<B::Hash>,
|
||||
}
|
||||
|
||||
impl<B: ChainApi> Pool<B> {
|
||||
/// Create a new transaction pool.
|
||||
pub fn new(options: txpool::Options, api: B) -> Self {
|
||||
Pool {
|
||||
pool: RwLock::new(txpool::Pool::new(Listener::default(), ScoringAdapter::<B>(Default::default()), options)),
|
||||
import_notification_sinks: Default::default(),
|
||||
api,
|
||||
rotator: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Imports a pre-verified extrinsic to the pool.
|
||||
pub fn import(&self, xt: VerifiedFor<B>) -> Result<Arc<VerifiedFor<B>>, B::Error> {
|
||||
let result = self.pool.write().import(xt)?;
|
||||
|
||||
self.import_notification_sinks.lock()
|
||||
.retain(|sink| sink.unbounded_send(()).is_ok());
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Return an event stream of transactions imported to the pool.
|
||||
pub fn import_notification_stream(&self) -> EventStream {
|
||||
let (sink, stream) = mpsc::unbounded();
|
||||
self.import_notification_sinks.lock().push(sink);
|
||||
stream
|
||||
}
|
||||
|
||||
/// Invoked when extrinsics are broadcasted.
|
||||
pub fn on_broadcasted(&self, propagated: HashMap<B::Hash, Vec<String>>) {
|
||||
for (hash, peers) in propagated.into_iter() {
|
||||
self.pool.write().listener_mut().broadcasted(&hash, peers);
|
||||
}
|
||||
}
|
||||
|
||||
/// Imports a bunch of unverified extrinsics to the pool
|
||||
pub fn submit_at<T>(&self, at: &BlockId<B::Block>, xts: T) -> Result<Vec<Arc<VerifiedFor<B>>>, B::Error> where
|
||||
T: IntoIterator<Item=ExtrinsicFor<B>>
|
||||
{
|
||||
xts
|
||||
.into_iter()
|
||||
.map(|xt| {
|
||||
match self.api.verify_transaction(at, &xt) {
|
||||
Ok(ref verified) if self.rotator.is_banned(txpool::VerifiedTransaction::hash(verified)) => {
|
||||
return (Err(txpool::Error::from("Temporarily Banned".to_owned()).into()), xt)
|
||||
},
|
||||
result => (result, xt),
|
||||
}
|
||||
})
|
||||
.map(|(v, xt)| {
|
||||
let xt = Verified {
|
||||
original: xt,
|
||||
verified: v?,
|
||||
valid_till: time::Instant::now() + POOL_TIME,
|
||||
};
|
||||
Ok(self.pool.write().import(xt)?)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Imports one unverified extrinsic to the pool
|
||||
pub fn submit_one(&self, at: &BlockId<B::Block>, xt: ExtrinsicFor<B>) -> Result<Arc<VerifiedFor<B>>, B::Error> {
|
||||
Ok(self.submit_at(at, ::std::iter::once(xt))?.pop().expect("One extrinsic passed; one result returned; qed"))
|
||||
}
|
||||
|
||||
/// Import a single extrinsic and starts to watch their progress in the pool.
|
||||
pub fn submit_and_watch(&self, at: &BlockId<B::Block>, xt: ExtrinsicFor<B>) -> Result<Watcher<B::Hash>, B::Error> {
|
||||
let xt = self.submit_at(at, Some(xt))?.pop().expect("One extrinsic passed; one result returned; qed");
|
||||
Ok(self.pool.write().listener_mut().create_watcher(xt))
|
||||
}
|
||||
|
||||
/// Remove from the pool.
|
||||
pub fn remove(&self, hashes: &[B::Hash], is_valid: bool) -> Vec<Option<Arc<VerifiedFor<B>>>> {
|
||||
let mut pool = self.pool.write();
|
||||
let mut results = Vec::with_capacity(hashes.len());
|
||||
|
||||
// temporarily ban invalid transactions
|
||||
if !is_valid {
|
||||
debug!(target: "transaction-pool", "Banning invalid transactions: {:?}", hashes);
|
||||
self.rotator.ban(&time::Instant::now(), hashes);
|
||||
}
|
||||
|
||||
for hash in hashes {
|
||||
results.push(pool.remove(hash, is_valid));
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Cull transactions from the queue.
|
||||
pub fn cull_from(
|
||||
&self,
|
||||
at: &BlockId<B::Block>,
|
||||
senders: Option<&[<B::VEx as txpool::VerifiedTransaction>::Sender]>,
|
||||
) -> usize
|
||||
{
|
||||
self.rotator.clear_timeouts(&time::Instant::now());
|
||||
let ready = self.ready(at);
|
||||
self.pool.write().cull(senders, ready)
|
||||
}
|
||||
|
||||
/// Cull old transactions from the queue.
|
||||
pub fn cull(&self, at: &BlockId<B::Block>) -> Result<usize, B::Error> {
|
||||
Ok(self.cull_from(at, None))
|
||||
}
|
||||
|
||||
/// Cull transactions from the queue and then compute the pending set.
|
||||
pub fn cull_and_get_pending<F, T>(&self, at: &BlockId<B::Block>, f: F) -> Result<T, B::Error> where
|
||||
F: FnOnce(txpool::PendingIterator<VerifiedFor<B>, Ready<B>, ScoringAdapter<B>, Listener<B::Hash>>) -> T,
|
||||
{
|
||||
self.cull_from(at, None);
|
||||
Ok(self.pending(at, f))
|
||||
}
|
||||
|
||||
/// Get the full status of the queue (including readiness)
|
||||
pub fn status<R: txpool::Ready<VerifiedFor<B>>>(&self, ready: R) -> txpool::Status {
|
||||
self.pool.read().status(ready)
|
||||
}
|
||||
|
||||
/// Returns light status of the pool.
|
||||
pub fn light_status(&self) -> txpool::LightStatus {
|
||||
self.pool.read().light_status()
|
||||
}
|
||||
|
||||
/// Removes all transactions from given sender
|
||||
pub fn remove_sender(&self, sender: <B::VEx as txpool::VerifiedTransaction>::Sender) -> Vec<Arc<VerifiedFor<B>>> {
|
||||
let mut pool = self.pool.write();
|
||||
let pending = pool.pending_from_sender(|_: &VerifiedFor<B>| txpool::Readiness::Ready, &sender).collect();
|
||||
// remove all transactions from this sender
|
||||
pool.cull(Some(&[sender]), |_: &VerifiedFor<B>| txpool::Readiness::Stale);
|
||||
pending
|
||||
}
|
||||
|
||||
/// Retrieve the pending set. Be careful to not leak the pool `ReadGuard` to prevent deadlocks.
|
||||
pub fn pending<F, T>(&self, at: &BlockId<B::Block>, f: F) -> T where
|
||||
F: FnOnce(txpool::PendingIterator<VerifiedFor<B>, Ready<B>, ScoringAdapter<B>, Listener<B::Hash>>) -> T,
|
||||
{
|
||||
let ready = self.ready(at);
|
||||
f(self.pool.read().pending(ready))
|
||||
}
|
||||
|
||||
/// Retry to import all verified transactions from given sender.
|
||||
pub fn retry_verification(&self, at: &BlockId<B::Block>, sender: <B::VEx as txpool::VerifiedTransaction>::Sender) -> Result<(), B::Error> {
|
||||
let to_reverify = self.remove_sender(sender);
|
||||
self.submit_at(at, to_reverify.into_iter().map(|ex| Arc::try_unwrap(ex).expect("Removed items have no references").original))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reverify transaction that has been reported incorrect.
|
||||
///
|
||||
/// Returns `Ok(None)` in case the hash is missing, `Err(e)` in case of verification error and new transaction
|
||||
/// reference otherwise.
|
||||
///
|
||||
/// TODO [ToDr] That method is currently unused, should be used together with BlockBuilder
|
||||
/// when we detect that particular transaction has failed.
|
||||
/// In such case we will attempt to remove or re-verify it.
|
||||
pub fn reverify_transaction(&self, at: &BlockId<B::Block>, hash: B::Hash) -> Result<Option<Arc<VerifiedFor<B>>>, B::Error> {
|
||||
let result = self.remove(&[hash], false).pop().expect("One hash passed; one result received; qed");
|
||||
if let Some(ex) = result {
|
||||
self.submit_one(at, Arc::try_unwrap(ex).expect("Removed items have no references").original).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve all transactions in the pool grouped by sender.
|
||||
pub fn all(&self) -> AllExtrinsics<B> {
|
||||
use txpool::VerifiedTransaction;
|
||||
let pool = self.pool.read();
|
||||
let all = pool.unordered_pending(AlwaysReady);
|
||||
all.fold(Default::default(), |mut map: AllExtrinsics<B>, tx| {
|
||||
// Map with `null` key is not serializable, so we fallback to default accountId.
|
||||
map.entry(tx.verified.sender().clone())
|
||||
.or_insert_with(Vec::new)
|
||||
// use bytes type to make it serialize nicer.
|
||||
.push(tx.original.clone());
|
||||
map
|
||||
})
|
||||
}
|
||||
|
||||
fn ready<'a, 'b>(&'a self, at: &'b BlockId<B::Block>) -> Ready<'a, 'b, B> {
|
||||
Ready {
|
||||
api: &self.api,
|
||||
rotator: &self.rotator,
|
||||
context: self.api.ready(),
|
||||
at,
|
||||
now: time::Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Readiness implementation that returns `Ready` for all transactions.
|
||||
pub struct AlwaysReady;
|
||||
impl<VEx> txpool::Ready<VEx> for AlwaysReady {
|
||||
fn is_ready(&mut self, _tx: &VEx) -> txpool::Readiness {
|
||||
txpool::Readiness::Ready
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use txpool;
|
||||
use super::{VerifiedFor, ExtrinsicFor};
|
||||
use std::collections::HashMap;
|
||||
use std::cmp::Ordering;
|
||||
use {Pool, ChainApi, scoring, Readiness};
|
||||
use keyring::Keyring::{self, *};
|
||||
use codec::Encode;
|
||||
use test_client::runtime::{AccountId, Block, Hash, Index, Extrinsic, Transfer};
|
||||
use runtime_primitives::{generic, traits::{Hash as HashT, BlindCheckable, BlakeTwo256}};
|
||||
use VerifiedTransaction as VerifiedExtrinsic;
|
||||
|
||||
type BlockId = generic::BlockId<Block>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VerifiedTransaction {
|
||||
pub hash: Hash,
|
||||
pub sender: AccountId,
|
||||
pub nonce: u64,
|
||||
}
|
||||
|
||||
impl txpool::VerifiedTransaction for VerifiedTransaction {
|
||||
type Hash = Hash;
|
||||
type Sender = AccountId;
|
||||
|
||||
fn hash(&self) -> &Self::Hash {
|
||||
&self.hash
|
||||
}
|
||||
|
||||
fn sender(&self) -> &Self::Sender {
|
||||
&self.sender
|
||||
}
|
||||
|
||||
fn mem_usage(&self) -> usize {
|
||||
256
|
||||
}
|
||||
}
|
||||
|
||||
struct TestApi;
|
||||
|
||||
impl TestApi {
|
||||
fn default() -> Self {
|
||||
TestApi
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainApi for TestApi {
|
||||
type Block = Block;
|
||||
type Hash = Hash;
|
||||
type Sender = AccountId;
|
||||
type Error = txpool::Error;
|
||||
type VEx = VerifiedTransaction;
|
||||
type Ready = HashMap<AccountId, u64>;
|
||||
type Score = u64;
|
||||
type Event = ();
|
||||
|
||||
fn verify_transaction(&self, _at: &BlockId, uxt: &ExtrinsicFor<Self>) -> Result<Self::VEx, Self::Error> {
|
||||
let hash = BlakeTwo256::hash(&uxt.encode());
|
||||
let xt = uxt.clone().check()?;
|
||||
Ok(VerifiedTransaction {
|
||||
hash,
|
||||
sender: xt.transfer.from,
|
||||
nonce: xt.transfer.nonce,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_ready(&self, at: &BlockId, nonce_cache: &mut Self::Ready, xt: &VerifiedFor<Self>) -> Readiness {
|
||||
let sender = xt.verified.sender;
|
||||
let next_index = nonce_cache.entry(sender)
|
||||
.or_insert_with(|| index(at, sender));
|
||||
|
||||
let result = match xt.original.transfer.nonce.cmp(&next_index) {
|
||||
Ordering::Greater => Readiness::Future,
|
||||
Ordering::Equal => Readiness::Ready,
|
||||
Ordering::Less => Readiness::Stale,
|
||||
};
|
||||
|
||||
// remember to increment `next_index`
|
||||
*next_index = next_index.saturating_add(1);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn ready(&self) -> Self::Ready {
|
||||
HashMap::default()
|
||||
}
|
||||
|
||||
fn compare(old: &VerifiedFor<Self>, other: &VerifiedFor<Self>) -> Ordering {
|
||||
old.original.transfer.nonce.cmp(&other.original.transfer.nonce)
|
||||
}
|
||||
|
||||
fn choose(old: &VerifiedFor<Self>, new: &VerifiedFor<Self>) -> scoring::Choice {
|
||||
assert!(new.verified.sender == old.verified.sender, "Scoring::choose called with transactions from different senders");
|
||||
if old.original.transfer.nonce == new.original.transfer.nonce {
|
||||
return scoring::Choice::RejectNew;
|
||||
}
|
||||
scoring::Choice::InsertNew
|
||||
}
|
||||
|
||||
fn update_scores(
|
||||
xts: &[txpool::Transaction<VerifiedFor<Self>>],
|
||||
scores: &mut [Self::Score],
|
||||
_change: scoring::Change<()>
|
||||
) {
|
||||
for i in 0..xts.len() {
|
||||
scores[i] = xts[i].original.transfer.amount;
|
||||
}
|
||||
}
|
||||
|
||||
fn should_replace(_old: &VerifiedFor<Self>, _new: &VerifiedFor<Self>) -> scoring::Choice {
|
||||
scoring::Choice::InsertNew
|
||||
}
|
||||
}
|
||||
|
||||
fn index(at: &BlockId, _account: AccountId) -> u64 {
|
||||
(_account[0] as u64) + number_of(at)
|
||||
}
|
||||
|
||||
fn number_of(at: &BlockId) -> u64 {
|
||||
match at {
|
||||
generic::BlockId::Number(n) => *n as u64,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn uxt(who: Keyring, nonce: Index) -> Extrinsic {
|
||||
let transfer = Transfer {
|
||||
from: who.to_raw_public().into(),
|
||||
to: AccountId::default(),
|
||||
nonce,
|
||||
amount: 1,
|
||||
};
|
||||
let signature = transfer.using_encoded(|e| who.sign(e));
|
||||
Extrinsic {
|
||||
transfer,
|
||||
signature: signature.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn pool() -> Pool<TestApi> {
|
||||
Pool::new(Default::default(), TestApi::default())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submission_should_work() {
|
||||
let pool = pool();
|
||||
assert_eq!(209, index(&BlockId::number(0), Alice.to_raw_public().into()));
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (*a.sender(), a.original.transfer.nonce)).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Alice.to_raw_public().into(), 209)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_submission_should_work() {
|
||||
let pool = pool();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (*a.sender(), a.original.transfer.nonce)).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Alice.to_raw_public().into(), 209), (Alice.to_raw_public().into(), 210)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn early_nonce_should_be_culled() {
|
||||
let pool = pool();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 208)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (*a.sender(), a.original.transfer.nonce)).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn late_nonce_should_be_queued() {
|
||||
let pool = pool();
|
||||
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (*a.sender(), a.original.transfer.nonce)).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (*a.sender(), a.original.transfer.nonce)).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Alice.to_raw_public().into(), 209), (Alice.to_raw_public().into(), 210)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrying_verification_might_not_change_anything() {
|
||||
let pool = pool();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (*a.sender(), a.original.transfer.nonce)).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Alice.to_raw_public().into(), 209), (Alice.to_raw_public().into(), 210)]);
|
||||
|
||||
pool.retry_verification(&BlockId::number(1), Alice.to_raw_public().into()).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (*a.sender(), a.original.transfer.nonce)).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Alice.to_raw_public().into(), 209), (Alice.to_raw_public().into(), 210)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_ban_invalid_transactions() {
|
||||
let pool = pool();
|
||||
let uxt = uxt(Alice, 209);
|
||||
let hash = *pool.submit_one(&BlockId::number(0), uxt.clone()).unwrap().hash();
|
||||
pool.remove(&[hash], true);
|
||||
pool.submit_one(&BlockId::number(0), uxt.clone()).unwrap();
|
||||
|
||||
// when
|
||||
pool.remove(&[hash], false);
|
||||
let pending: Vec<AccountId> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| *a.sender()).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
// then
|
||||
pool.submit_one(&BlockId::number(0), uxt.clone()).unwrap_err();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Rotate extrinsic inside the pool.
|
||||
//!
|
||||
//! Keeps only recent extrinsic and discard the ones kept for a significant amount of time.
|
||||
//! Discarded extrinsics are banned so that they don't get re-imported again.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
hash,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use txpool::VerifiedTransaction;
|
||||
use Verified;
|
||||
|
||||
/// Expected size of the banned extrinsics cache.
|
||||
const EXPECTED_SIZE: usize = 2048;
|
||||
|
||||
/// Pool rotator is responsible to only keep fresh extrinsics in the pool.
|
||||
///
|
||||
/// Extrinsics that occupy the pool for too long are culled and temporarily banned from entering
|
||||
/// the pool again.
|
||||
pub struct PoolRotator<Hash> {
|
||||
/// How long the extrinsic is banned for.
|
||||
ban_time: Duration,
|
||||
/// Currently banned extrinsics.
|
||||
banned_until: RwLock<HashMap<Hash, Instant>>,
|
||||
}
|
||||
|
||||
impl<Hash: hash::Hash + Eq> Default for PoolRotator<Hash> {
|
||||
fn default() -> Self {
|
||||
PoolRotator {
|
||||
ban_time: Duration::from_secs(60 * 30),
|
||||
banned_until: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Hash: hash::Hash + Eq + Clone> PoolRotator<Hash> {
|
||||
/// Returns `true` if extrinsic hash is currently banned.
|
||||
pub fn is_banned(&self, hash: &Hash) -> bool {
|
||||
self.banned_until.read().contains_key(hash)
|
||||
}
|
||||
|
||||
/// Bans given set of hashes.
|
||||
pub fn ban(&self, now: &Instant, hashes: &[Hash]) {
|
||||
let mut banned = self.banned_until.write();
|
||||
|
||||
for hash in hashes {
|
||||
banned.insert(hash.clone(), *now + self.ban_time);
|
||||
}
|
||||
|
||||
if banned.len() > 2 * EXPECTED_SIZE {
|
||||
while banned.len() > EXPECTED_SIZE {
|
||||
if let Some(key) = banned.keys().next().cloned() {
|
||||
banned.remove(&key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bans extrinsic if it's stale.
|
||||
///
|
||||
/// Returns `true` if extrinsic is stale and got banned.
|
||||
pub fn ban_if_stale<Ex, VEx>(&self, now: &Instant, xt: &Verified<Ex, VEx>) -> bool where
|
||||
VEx: VerifiedTransaction<Hash=Hash>,
|
||||
Hash: fmt::Debug + fmt::LowerHex,
|
||||
{
|
||||
if &xt.valid_till > now {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.ban(now, &[xt.verified.hash().clone()]);
|
||||
true
|
||||
}
|
||||
|
||||
/// Removes timed bans.
|
||||
pub fn clear_timeouts(&self, now: &Instant) {
|
||||
let mut banned = self.banned_until.write();
|
||||
|
||||
banned.retain(|_, &mut v| v >= *now);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pool::tests::VerifiedTransaction;
|
||||
use test_client::runtime::Hash;
|
||||
|
||||
fn rotator() -> PoolRotator<Hash> {
|
||||
PoolRotator {
|
||||
ban_time: Duration::from_millis(10),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn tx() -> (Hash, Verified<u64, VerifiedTransaction>) {
|
||||
let hash = 5.into();
|
||||
let tx = Verified {
|
||||
original: 5,
|
||||
verified: VerifiedTransaction {
|
||||
hash,
|
||||
sender: Default::default(),
|
||||
nonce: Default::default(),
|
||||
},
|
||||
valid_till: Instant::now(),
|
||||
};
|
||||
|
||||
(hash, tx)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_ban_if_not_stale() {
|
||||
// given
|
||||
let (hash, tx) = tx();
|
||||
let rotator = rotator();
|
||||
assert!(!rotator.is_banned(&hash));
|
||||
let past = Instant::now() - Duration::from_millis(1000);
|
||||
|
||||
// when
|
||||
assert!(!rotator.ban_if_stale(&past, &tx));
|
||||
|
||||
// then
|
||||
assert!(!rotator.is_banned(&hash));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_ban_stale_extrinsic() {
|
||||
// given
|
||||
let (hash, tx) = tx();
|
||||
let rotator = rotator();
|
||||
assert!(!rotator.is_banned(&hash));
|
||||
|
||||
// when
|
||||
assert!(rotator.ban_if_stale(&Instant::now(), &tx));
|
||||
|
||||
// then
|
||||
assert!(rotator.is_banned(&hash));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_clear_banned() {
|
||||
// given
|
||||
let (hash, tx) = tx();
|
||||
let rotator = rotator();
|
||||
assert!(rotator.ban_if_stale(&Instant::now(), &tx));
|
||||
assert!(rotator.is_banned(&hash));
|
||||
|
||||
// when
|
||||
let future = Instant::now() + rotator.ban_time + rotator.ban_time;
|
||||
rotator.clear_timeouts(&future);
|
||||
|
||||
// then
|
||||
assert!(!rotator.is_banned(&hash));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_garbage_collect() {
|
||||
// given
|
||||
fn tx_with(i: u64, time: Instant) -> Verified<u64, VerifiedTransaction> {
|
||||
let hash = i.into();
|
||||
Verified {
|
||||
original: i,
|
||||
verified: VerifiedTransaction {
|
||||
hash,
|
||||
sender: Default::default(),
|
||||
nonce: Default::default(),
|
||||
},
|
||||
valid_till: time,
|
||||
}
|
||||
}
|
||||
|
||||
let rotator = rotator();
|
||||
|
||||
let now = Instant::now();
|
||||
let past = now - Duration::from_secs(1);
|
||||
|
||||
// when
|
||||
for i in 0..2*EXPECTED_SIZE {
|
||||
let tx = tx_with(i as u64, past);
|
||||
assert!(rotator.ban_if_stale(&now, &tx));
|
||||
}
|
||||
assert_eq!(rotator.banned_until.read().len(), 2*EXPECTED_SIZE);
|
||||
|
||||
// then
|
||||
let tx = tx_with(2*EXPECTED_SIZE as u64, past);
|
||||
// trigger a garbage collection
|
||||
assert!(rotator.ban_if_stale(&now, &tx));
|
||||
assert_eq!(rotator.banned_until.read().len(), EXPECTED_SIZE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Extrinsics status updates.
|
||||
|
||||
use futures::{
|
||||
Stream,
|
||||
sync::mpsc,
|
||||
};
|
||||
|
||||
/// Possible extrinsic status events
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Status<H> {
|
||||
/// Extrinsic has been finalised in block with given hash.
|
||||
Finalised(H),
|
||||
/// Some state change (perhaps another extrinsic was included) rendered this extrinsic invalid.
|
||||
Usurped(H),
|
||||
/// The extrinsic has been broadcast to the given peers.
|
||||
Broadcast(Vec<String>),
|
||||
/// Extrinsic has been dropped from the pool because of the limit.
|
||||
Dropped,
|
||||
}
|
||||
|
||||
/// Extrinsic watcher.
|
||||
///
|
||||
/// Represents a stream of status updates for particular extrinsic.
|
||||
#[derive(Debug)]
|
||||
pub struct Watcher<H> {
|
||||
receiver: mpsc::UnboundedReceiver<Status<H>>,
|
||||
}
|
||||
|
||||
impl<H> Watcher<H> {
|
||||
/// Pipe the notifications to given sink.
|
||||
///
|
||||
/// Make sure to drive the future to completion.
|
||||
pub fn into_stream(self) -> impl Stream<Item=Status<H>, Error=()> {
|
||||
// we can safely ignore the error here, `UnboundedReceiver` never fails.
|
||||
self.receiver.map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sender part of the watcher. Exposed only for testing purposes.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Sender<H> {
|
||||
receivers: Vec<mpsc::UnboundedSender<Status<H>>>,
|
||||
finalised: bool,
|
||||
}
|
||||
|
||||
impl<H: Clone> Sender<H> {
|
||||
/// Add a new watcher to this sender object.
|
||||
pub fn new_watcher(&mut self) -> Watcher<H> {
|
||||
let (tx, receiver) = mpsc::unbounded();
|
||||
self.receivers.push(tx);
|
||||
Watcher {
|
||||
receiver,
|
||||
}
|
||||
}
|
||||
|
||||
/// Some state change (perhaps another extrinsic was included) rendered this extrinsic invalid.
|
||||
pub fn usurped(&mut self, hash: H) {
|
||||
self.send(Status::Usurped(hash))
|
||||
}
|
||||
|
||||
/// Extrinsic has been finalised in block with given hash.
|
||||
pub fn finalised(&mut self, hash: H) {
|
||||
self.send(Status::Finalised(hash));
|
||||
self.finalised = true;
|
||||
}
|
||||
|
||||
/// Transaction has been dropped from the pool because of the limit.
|
||||
pub fn dropped(&mut self) {
|
||||
self.send(Status::Dropped);
|
||||
}
|
||||
|
||||
/// The extrinsic has been broadcast to the given peers.
|
||||
pub fn broadcast(&mut self, peers: Vec<String>) {
|
||||
self.send(Status::Broadcast(peers))
|
||||
}
|
||||
|
||||
|
||||
/// Returns true if the are no more listeners for this extrinsic or it was finalised.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.finalised || self.receivers.is_empty()
|
||||
}
|
||||
|
||||
fn send(&mut self, status: Status<H>) {
|
||||
self.receivers.retain(|sender| sender.unbounded_send(status.clone()).is_ok())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "substrate-keyring"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
substrate-primitives = { path = "../primitives" }
|
||||
hex-literal = { version = "0.1.0" }
|
||||
lazy_static = { version = "1.0" }
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Keyring
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// tag::description[]
|
||||
//! Support code for the runtime.
|
||||
// end::description[]
|
||||
|
||||
#[macro_use] extern crate hex_literal;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
extern crate substrate_primitives;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use substrate_primitives::ed25519::{Pair, Public, Signature};
|
||||
pub use substrate_primitives::ed25519;
|
||||
|
||||
/// Set of test accounts.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Keyring {
|
||||
Alice,
|
||||
Bob,
|
||||
Charlie,
|
||||
Dave,
|
||||
Eve,
|
||||
Ferdie,
|
||||
One,
|
||||
Two,
|
||||
}
|
||||
|
||||
impl Keyring {
|
||||
pub fn from_public(who: Public) -> Option<Keyring> {
|
||||
[
|
||||
Keyring::Alice,
|
||||
Keyring::Bob,
|
||||
Keyring::Charlie,
|
||||
Keyring::Dave,
|
||||
Keyring::Eve,
|
||||
Keyring::Ferdie,
|
||||
Keyring::One,
|
||||
Keyring::Two,
|
||||
].iter()
|
||||
.map(|i| *i)
|
||||
.find(|&k| Public::from(k) == who)
|
||||
}
|
||||
|
||||
pub fn from_raw_public(who: [u8; 32]) -> Option<Keyring> {
|
||||
Self::from_public(Public::from_raw(who))
|
||||
}
|
||||
|
||||
pub fn to_raw_public(self) -> [u8; 32] {
|
||||
*Public::from(self).as_array_ref()
|
||||
}
|
||||
|
||||
pub fn to_raw_public_vec(self) -> Vec<u8> {
|
||||
Public::from(self).to_raw_vec()
|
||||
}
|
||||
|
||||
pub fn sign(self, msg: &[u8]) -> Signature {
|
||||
Pair::from(self).sign(msg)
|
||||
}
|
||||
|
||||
pub fn pair(self) -> Pair {
|
||||
match self {
|
||||
Keyring::Alice => Pair::from_seed(b"Alice "),
|
||||
Keyring::Bob => Pair::from_seed(b"Bob "),
|
||||
Keyring::Charlie => Pair::from_seed(b"Charlie "),
|
||||
Keyring::Dave => Pair::from_seed(b"Dave "),
|
||||
Keyring::Eve => Pair::from_seed(b"Eve "),
|
||||
Keyring::Ferdie => Pair::from_seed(b"Ferdie "),
|
||||
Keyring::One => Pair::from_seed(b"12345678901234567890123456789012"),
|
||||
Keyring::Two => Pair::from_seed(&hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keyring> for &'static str {
|
||||
fn from(k: Keyring) -> Self {
|
||||
match k {
|
||||
Keyring::Alice => "Alice",
|
||||
Keyring::Bob => "Bob",
|
||||
Keyring::Charlie => "Charlie",
|
||||
Keyring::Dave => "Dave",
|
||||
Keyring::Eve => "Eve",
|
||||
Keyring::Ferdie => "Ferdie",
|
||||
Keyring::One => "one",
|
||||
Keyring::Two => "two",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref PRIVATE_KEYS: HashMap<Keyring, Pair> = {
|
||||
[
|
||||
Keyring::Alice,
|
||||
Keyring::Bob,
|
||||
Keyring::Charlie,
|
||||
Keyring::Dave,
|
||||
Keyring::Eve,
|
||||
Keyring::Ferdie,
|
||||
Keyring::One,
|
||||
Keyring::Two,
|
||||
].iter().map(|&i| (i, i.pair())).collect()
|
||||
};
|
||||
|
||||
static ref PUBLIC_KEYS: HashMap<Keyring, Public> = {
|
||||
PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect()
|
||||
};
|
||||
}
|
||||
|
||||
impl From<Keyring> for Public {
|
||||
fn from(k: Keyring) -> Self {
|
||||
(*PUBLIC_KEYS).get(&k).unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keyring> for Pair {
|
||||
fn from(k: Keyring) -> Self {
|
||||
k.pair()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keyring> for [u8; 32] {
|
||||
fn from(k: Keyring) -> Self {
|
||||
*(*PUBLIC_KEYS).get(&k).unwrap().as_array_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keyring> for &'static [u8; 32] {
|
||||
fn from(k: Keyring) -> Self {
|
||||
(*PUBLIC_KEYS).get(&k).unwrap().as_array_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8; 32]> for Keyring {
|
||||
fn as_ref(&self) -> &[u8; 32] {
|
||||
(*PUBLIC_KEYS).get(self).unwrap().as_array_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Public> for Keyring {
|
||||
fn as_ref(&self) -> &Public {
|
||||
(*PUBLIC_KEYS).get(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Keyring {
|
||||
type Target = [u8; 32];
|
||||
fn deref(&self) -> &[u8; 32] {
|
||||
(*PUBLIC_KEYS).get(self).unwrap().as_array_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ed25519::Verifiable;
|
||||
|
||||
#[test]
|
||||
fn should_work() {
|
||||
assert!(Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", Keyring::Alice));
|
||||
assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Bob!", Keyring::Alice));
|
||||
assert!(!Keyring::Alice.sign(b"I am Alice!").verify(b"I am Alice!", Keyring::Bob));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "substrate-keystore"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
substrate-primitives = { path = "../primitives" }
|
||||
parity-crypto = { version = "0.1", default_features = false }
|
||||
error-chain = "0.12"
|
||||
hex = "0.3"
|
||||
rand = "0.4"
|
||||
serde_json = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
subtle = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3"
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Keystore
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,293 @@
|
||||
// Copyright 2017-2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// tag::description[]
|
||||
//! Keystore (and session key management) for ed25519 based chains like Polkadot.
|
||||
// end::description[]
|
||||
|
||||
extern crate substrate_primitives;
|
||||
extern crate parity_crypto as crypto;
|
||||
extern crate subtle;
|
||||
extern crate rand;
|
||||
extern crate serde_json;
|
||||
extern crate serde;
|
||||
extern crate hex;
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate tempdir;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Write};
|
||||
|
||||
use substrate_primitives::{hashing::blake2_256, ed25519::{Pair, Public, PKCS_LEN}};
|
||||
|
||||
pub use crypto::KEY_ITERATIONS;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
Io(io::Error);
|
||||
Json(serde_json::Error);
|
||||
}
|
||||
|
||||
errors {
|
||||
InvalidPassword {
|
||||
description("Invalid password"),
|
||||
display("Invalid password"),
|
||||
}
|
||||
InvalidPKCS8 {
|
||||
description("Invalid PKCS#8 data"),
|
||||
display("Invalid PKCS#8 data"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct InvalidPassword;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct EncryptedKey {
|
||||
mac: [u8; 32],
|
||||
salt: [u8; 32],
|
||||
ciphertext: Vec<u8>, // TODO: switch to fixed-size when serde supports
|
||||
iv: [u8; 16],
|
||||
iterations: u32,
|
||||
}
|
||||
|
||||
impl EncryptedKey {
|
||||
fn encrypt(plain: &[u8; PKCS_LEN], password: &str, iterations: u32) -> Self {
|
||||
use rand::{Rng, OsRng};
|
||||
|
||||
let mut rng = OsRng::new().expect("OS Randomness available on all supported platforms; qed");
|
||||
|
||||
let salt: [u8; 32] = rng.gen();
|
||||
let iv: [u8; 16] = rng.gen();
|
||||
|
||||
// two parts of derived key
|
||||
// DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits]
|
||||
let (derived_left_bits, derived_right_bits) = crypto::derive_key_iterations(password.as_bytes(), &salt, iterations);
|
||||
|
||||
// preallocated (on-stack in case of `Secret`) buffer to hold cipher
|
||||
// length = length(plain) as we are using CTR-approach
|
||||
let mut ciphertext = vec![0; PKCS_LEN];
|
||||
|
||||
// aes-128-ctr with initial vector of iv
|
||||
crypto::aes::encrypt_128_ctr(&derived_left_bits, &iv, plain, &mut *ciphertext)
|
||||
.expect("input lengths of key and iv are both 16; qed");
|
||||
|
||||
// Blake2_256(DK[16..31] ++ <ciphertext>), where DK[16..31] - derived_right_bits
|
||||
let mac = blake2_256(&crypto::derive_mac(&derived_right_bits, &*ciphertext));
|
||||
|
||||
EncryptedKey {
|
||||
salt,
|
||||
iv,
|
||||
mac,
|
||||
iterations,
|
||||
ciphertext,
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt(&self, password: &str) -> Result<[u8; PKCS_LEN]> {
|
||||
let (derived_left_bits, derived_right_bits) =
|
||||
crypto::derive_key_iterations(password.as_bytes(), &self.salt, self.iterations);
|
||||
|
||||
let mac = blake2_256(&crypto::derive_mac(&derived_right_bits, &self.ciphertext));
|
||||
|
||||
if subtle::slices_equal(&mac[..], &self.mac[..]) != 1 {
|
||||
return Err(ErrorKind::InvalidPassword.into());
|
||||
}
|
||||
|
||||
let mut plain = [0; PKCS_LEN];
|
||||
crypto::aes::decrypt_128_ctr(&derived_left_bits, &self.iv, &self.ciphertext, &mut plain[..])
|
||||
.expect("input lengths of key and iv are both 16; qed");
|
||||
Ok(plain)
|
||||
}
|
||||
}
|
||||
|
||||
type Seed = [u8; 32];
|
||||
|
||||
/// Key store.
|
||||
pub struct Store {
|
||||
path: PathBuf,
|
||||
additional: HashMap<Public, Seed>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
/// Create a new store at the given path.
|
||||
pub fn open(path: PathBuf) -> Result<Self> {
|
||||
fs::create_dir_all(&path)?;
|
||||
Ok(Store { path, additional: HashMap::new() })
|
||||
}
|
||||
|
||||
/// Generate a new key, placing it into the store.
|
||||
pub fn generate(&self, password: &str) -> Result<Pair> {
|
||||
let (pair, pkcs_bytes) = Pair::generate_with_pkcs8();
|
||||
let key_file = EncryptedKey::encrypt(&pkcs_bytes, password, KEY_ITERATIONS as u32);
|
||||
|
||||
let mut file = File::create(self.key_file_path(&pair.public()))?;
|
||||
::serde_json::to_writer(&file, &key_file)?;
|
||||
|
||||
file.flush()?;
|
||||
|
||||
Ok(pair)
|
||||
}
|
||||
|
||||
/// Create a new key from seed. Do not place it into the store.
|
||||
/// Only the first 32 bytes of the sead are used. This is meant to be used for testing only.
|
||||
// TODO: Remove this
|
||||
pub fn generate_from_seed(&mut self, seed: &str) -> Result<Pair> {
|
||||
let mut s: [u8; 32] = [' ' as u8; 32];
|
||||
|
||||
let was_hex = if seed.len() == 66 && &seed[0..2] == "0x" {
|
||||
if let Ok(d) = hex::decode(&seed[2..]) {
|
||||
s.copy_from_slice(&d);
|
||||
true
|
||||
} else { false }
|
||||
} else { false };
|
||||
|
||||
if !was_hex {
|
||||
let len = ::std::cmp::min(32, seed.len());
|
||||
&mut s[..len].copy_from_slice(&seed.as_bytes()[..len]);
|
||||
}
|
||||
|
||||
let pair = Pair::from_seed(&s);
|
||||
self.additional.insert(pair.public(), s);
|
||||
Ok(pair)
|
||||
}
|
||||
|
||||
/// Load a key file with given public key.
|
||||
pub fn load(&self, public: &Public, password: &str) -> Result<Pair> {
|
||||
if let Some(ref seed) = self.additional.get(public) {
|
||||
let pair = Pair::from_seed(seed);
|
||||
return Ok(pair);
|
||||
}
|
||||
let path = self.key_file_path(public);
|
||||
let file = File::open(path)?;
|
||||
|
||||
let encrypted_key: EncryptedKey = ::serde_json::from_reader(&file)?;
|
||||
let pkcs_bytes = encrypted_key.decrypt(password)?;
|
||||
|
||||
Pair::from_pkcs8(&pkcs_bytes[..]).map_err(|_| ErrorKind::InvalidPKCS8.into())
|
||||
}
|
||||
|
||||
/// Get public keys of all stored keys.
|
||||
pub fn contents(&self) -> Result<Vec<Public>> {
|
||||
let mut public_keys: Vec<Public> = self.additional.keys().cloned().collect();
|
||||
for entry in fs::read_dir(&self.path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
// skip directories and non-unicode file names (hex is unicode)
|
||||
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if name.len() != 64 { continue }
|
||||
|
||||
match hex::decode(name) {
|
||||
Ok(ref hex) if hex.len() == 32 => {
|
||||
let mut buf = [0; 32];
|
||||
buf.copy_from_slice(&hex[..]);
|
||||
|
||||
public_keys.push(Public(buf));
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(public_keys)
|
||||
}
|
||||
|
||||
fn key_file_path(&self, public: &Public) -> PathBuf {
|
||||
let mut buf = self.path.clone();
|
||||
buf.push(hex::encode(public.as_slice()));
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[test]
|
||||
fn encrypt_and_decrypt() {
|
||||
let plain = [1; PKCS_LEN];
|
||||
let encrypted_key = EncryptedKey::encrypt(&plain, "thepassword", KEY_ITERATIONS as u32);
|
||||
|
||||
let decrypted_key = encrypted_key.decrypt("thepassword").unwrap();
|
||||
|
||||
assert_eq!(&plain[..], &decrypted_key[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decrypt_wrong_password_fails() {
|
||||
let plain = [1; PKCS_LEN];
|
||||
let encrypted_key = EncryptedKey::encrypt(&plain, "thepassword", KEY_ITERATIONS as u32);
|
||||
|
||||
assert!(encrypted_key.decrypt("thepassword2").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decrypt_wrong_iterations_fails() {
|
||||
let plain = [1; PKCS_LEN];
|
||||
let mut encrypted_key = EncryptedKey::encrypt(&plain, "thepassword", KEY_ITERATIONS as u32);
|
||||
|
||||
encrypted_key.iterations -= 64;
|
||||
|
||||
assert!(encrypted_key.decrypt("thepassword").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_store() {
|
||||
let temp_dir = TempDir::new("keystore").unwrap();
|
||||
let store = Store::open(temp_dir.path().to_owned()).unwrap();
|
||||
|
||||
assert!(store.contents().unwrap().is_empty());
|
||||
|
||||
let key = store.generate("thepassword").unwrap();
|
||||
let key2 = store.load(&key.public(), "thepassword").unwrap();
|
||||
|
||||
assert!(store.load(&key.public(), "notthepassword").is_err());
|
||||
|
||||
assert_eq!(key.public(), key2.public());
|
||||
|
||||
assert_eq!(store.contents().unwrap()[0], key.public());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_from_seed() {
|
||||
let temp_dir = TempDir::new("keystore").unwrap();
|
||||
let mut store = Store::open(temp_dir.path().to_owned()).unwrap();
|
||||
|
||||
let pair = store.generate_from_seed("0x1").unwrap();
|
||||
assert_eq!("5GqhgbUd2S9uc5Tm7hWhw29Tw2jBnuHshmTV1fDF4V1w3G2z", pair.public().to_ss58check());
|
||||
|
||||
let pair = store.generate_from_seed("0x3d97c819d68f9bafa7d6e79cb991eebcd77d966c5334c0b94d9e1fa7ad0869dc").unwrap();
|
||||
assert_eq!("5DKUrgFqCPV8iAXx9sjy1nyBygQCeiUYRFWurZGhnrn3HBL8", pair.public().to_ss58check());
|
||||
|
||||
let pair = store.generate_from_seed("12345678901234567890123456789022").unwrap();
|
||||
assert_eq!("5DscZvfjnM5im7oKRXXP9xtCG1SEwfMb8J5eGLmw5EHhoHR3", pair.public().to_ss58check());
|
||||
|
||||
let pair = store.generate_from_seed("1").unwrap();
|
||||
assert_eq!("5DYnksEZFc7kgtfyNM1xK2eBtW142gZ3Ho3NQubrF2S6B2fq", pair.public().to_ss58check());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "substrate-metadata"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
parity-codec = { path = "../../codec", default_features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"parity-codec/std"
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Substrate BFT
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Decodable variant of the JsonMetadata.
|
||||
//!
|
||||
//! This really doesn't belong here, but is necessary for the moment. In the future
|
||||
//! it should be removed entirely to an external module for shimming on to the
|
||||
//! codec-encoded metadata.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate parity_codec as codec;
|
||||
|
||||
use codec::{Encode, Output};
|
||||
#[cfg(feature = "std")]
|
||||
use codec::{Decode, Input};
|
||||
|
||||
/// The metadata of a runtime encoded as JSON.
|
||||
#[derive(Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub enum JsonMetadata {
|
||||
Events { name: &'static str, events: &'static [(&'static str, fn() -> &'static str)] },
|
||||
Module { module: &'static str, prefix: &'static str },
|
||||
ModuleWithStorage { module: &'static str, prefix: &'static str, storage: &'static str }
|
||||
}
|
||||
|
||||
impl Encode for JsonMetadata {
|
||||
fn encode_to<W: Output>(&self, dest: &mut W) {
|
||||
match self {
|
||||
JsonMetadata::Events { name, events } => {
|
||||
0i8.encode_to(dest);
|
||||
name.encode_to(dest);
|
||||
events.iter().fold(0u32, |count, _| count + 1).encode_to(dest);
|
||||
events
|
||||
.iter()
|
||||
.map(|(module, data)| (module, data()))
|
||||
.for_each(|val| val.encode_to(dest));
|
||||
},
|
||||
JsonMetadata::Module { module, prefix } => {
|
||||
1i8.encode_to(dest);
|
||||
prefix.encode_to(dest);
|
||||
module.encode_to(dest);
|
||||
},
|
||||
JsonMetadata::ModuleWithStorage { module, prefix, storage } => {
|
||||
2i8.encode_to(dest);
|
||||
prefix.encode_to(dest);
|
||||
module.encode_to(dest);
|
||||
storage.encode_to(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<JsonMetadata> for JsonMetadata {
|
||||
fn eq(&self, other: &JsonMetadata) -> bool {
|
||||
match (self, other) {
|
||||
(
|
||||
JsonMetadata::Events { name: lname, events: left },
|
||||
JsonMetadata::Events { name: rname, events: right }
|
||||
) => {
|
||||
lname == rname && left.iter().zip(right.iter()).fold(true, |res, (l, r)| {
|
||||
res && l.0 == r.0 && l.1() == r.1()
|
||||
})
|
||||
},
|
||||
(
|
||||
JsonMetadata::Module { prefix: lpre, module: lmod },
|
||||
JsonMetadata::Module { prefix: rpre, module: rmod }
|
||||
) => {
|
||||
lpre == rpre && lmod == rmod
|
||||
},
|
||||
(
|
||||
JsonMetadata::ModuleWithStorage { prefix: lpre, module: lmod, storage: lstore },
|
||||
JsonMetadata::ModuleWithStorage { prefix: rpre, module: rmod, storage: rstore }
|
||||
) => {
|
||||
lpre == rpre && lmod == rmod && lstore == rstore
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility struct for making `JsonMetadata` decodeable.
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
#[cfg(feature = "std")]
|
||||
pub enum JsonMetadataDecodable {
|
||||
Events { name: String, events: Vec<(String, String)> },
|
||||
Module { module: String, prefix: String },
|
||||
ModuleWithStorage { module: String, prefix: String, storage: String }
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl JsonMetadataDecodable {
|
||||
/// Returns the instance as JSON string.
|
||||
/// The first value of the tuple is the name of the metadata type and the second in the JSON string.
|
||||
pub fn into_json_string(self) -> (&'static str, String) {
|
||||
match self {
|
||||
JsonMetadataDecodable::Events { name, events } => {
|
||||
(
|
||||
"events",
|
||||
format!(
|
||||
r#"{{ "name": "{}", "events": {{ {} }} }}"#, name,
|
||||
events.iter().enumerate()
|
||||
.fold(String::from(""), |mut json, (i, (name, data))| {
|
||||
if i > 0 {
|
||||
json.push_str(", ");
|
||||
}
|
||||
json.push_str(&format!(r#""{}": {}"#, name, data));
|
||||
json
|
||||
})
|
||||
)
|
||||
)
|
||||
},
|
||||
JsonMetadataDecodable::Module { prefix, module } => {
|
||||
("module", format!(r#"{{ "prefix": "{}", "module": {} }}"#, prefix, module))
|
||||
},
|
||||
JsonMetadataDecodable::ModuleWithStorage { prefix, module, storage } => {
|
||||
(
|
||||
"moduleWithStorage",
|
||||
format!(
|
||||
r#"{{ "prefix": "{}", "module": {}, "storage": {} }}"#,
|
||||
prefix, module, storage
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Decode for JsonMetadataDecodable {
|
||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||
i8::decode(input).and_then(|variant| {
|
||||
match variant {
|
||||
0 => String::decode(input)
|
||||
.and_then(|name| Vec::<(String, String)>::decode(input).map(|events| (name, events)))
|
||||
.and_then(|(name, events)| Some(JsonMetadataDecodable::Events { name, events })),
|
||||
1 => String::decode(input)
|
||||
.and_then(|prefix| String::decode(input).map(|v| (prefix, v)))
|
||||
.and_then(|(prefix, module)| Some(JsonMetadataDecodable::Module { prefix, module })),
|
||||
2 => String::decode(input)
|
||||
.and_then(|prefix| String::decode(input).map(|v| (prefix, v)))
|
||||
.and_then(|(prefix, module)| String::decode(input).map(|v| (prefix, module, v)))
|
||||
.and_then(|(prefix, module, storage)| Some(JsonMetadataDecodable::ModuleWithStorage { prefix, module, storage })),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl PartialEq<JsonMetadata> for JsonMetadataDecodable {
|
||||
fn eq(&self, other: &JsonMetadata) -> bool {
|
||||
match (self, other) {
|
||||
(
|
||||
JsonMetadataDecodable::Events { name: lname, events: left },
|
||||
JsonMetadata::Events { name: rname, events: right }
|
||||
) => {
|
||||
lname == rname && left.iter().zip(right.iter()).fold(true, |res, (l, r)| {
|
||||
res && l.0 == r.0 && l.1 == r.1()
|
||||
})
|
||||
},
|
||||
(
|
||||
JsonMetadataDecodable::Module { prefix: lpre, module: lmod },
|
||||
JsonMetadata::Module { prefix: rpre, module: rmod }
|
||||
) => {
|
||||
lpre == rpre && lmod == rmod
|
||||
},
|
||||
(
|
||||
JsonMetadataDecodable::ModuleWithStorage { prefix: lpre, module: lmod, storage: lstore },
|
||||
JsonMetadata::ModuleWithStorage { prefix: rpre, module: rmod, storage: rstore }
|
||||
) => {
|
||||
lpre == rpre && lmod == rmod && lstore == rstore
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "substrate-misbehavior-check"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
parity-codec = { path = "../../codec", default-features = false }
|
||||
substrate-primitives = { path = "../primitives", default-features = false }
|
||||
sr-primitives = { path = "../sr-primitives", default-features = false }
|
||||
sr-io = { path = "../sr-io", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-bft = { path = "../bft" }
|
||||
rhododendron = "0.3"
|
||||
substrate-keyring = { path = "../keyring" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["parity-codec/std", "substrate-primitives/std", "sr-primitives/std", "sr-io/std"]
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Misbehavior-check
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// tag::description[]
|
||||
//! Utility for substrate-based runtimes that want to check misbehavior reports.
|
||||
// end::description[]
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate parity_codec as codec;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate sr_io as runtime_io;
|
||||
extern crate sr_primitives as runtime_primitives;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate substrate_bft;
|
||||
#[cfg(test)]
|
||||
extern crate substrate_keyring as keyring;
|
||||
#[cfg(test)]
|
||||
extern crate rhododendron;
|
||||
|
||||
use codec::{Codec, Encode};
|
||||
use primitives::{AuthorityId, Signature};
|
||||
|
||||
use runtime_primitives::bft::{Action, Message, MisbehaviorKind};
|
||||
|
||||
// check a message signature. returns true if signed by that authority.
|
||||
fn check_message_sig<B: Codec, H: Codec>(
|
||||
message: Message<B, H>,
|
||||
signature: &Signature,
|
||||
from: &AuthorityId
|
||||
) -> bool {
|
||||
let msg: Vec<u8> = message.encode();
|
||||
runtime_io::ed25519_verify(&signature.0, &msg, from)
|
||||
}
|
||||
|
||||
fn prepare<B, H>(parent: H, round_number: u32, hash: H) -> Message<B, H> {
|
||||
Message {
|
||||
parent,
|
||||
action: Action::Prepare(round_number, hash),
|
||||
}
|
||||
}
|
||||
|
||||
fn commit<B, H>(parent: H, round_number: u32, hash: H) -> Message<B, H> {
|
||||
Message {
|
||||
parent,
|
||||
action: Action::Commit(round_number, hash),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate misbehavior.
|
||||
///
|
||||
/// Doesn't check that the header hash in question is
|
||||
/// valid or whether the misbehaving authority was part of
|
||||
/// the set at that block.
|
||||
pub fn evaluate_misbehavior<B: Codec, H: Codec + Copy>(
|
||||
misbehaved: &AuthorityId,
|
||||
parent_hash: H,
|
||||
kind: &MisbehaviorKind<H>,
|
||||
) -> bool {
|
||||
match *kind {
|
||||
MisbehaviorKind::BftDoublePrepare(round, (h_1, ref s_1), (h_2, ref s_2)) => {
|
||||
s_1 != s_2 &&
|
||||
check_message_sig::<B, H>(prepare::<B, H>(parent_hash, round, h_1), s_1, misbehaved) &&
|
||||
check_message_sig::<B, H>(prepare::<B, H>(parent_hash, round, h_2), s_2, misbehaved)
|
||||
}
|
||||
MisbehaviorKind::BftDoubleCommit(round, (h_1, ref s_1), (h_2, ref s_2)) => {
|
||||
s_1 != s_2 &&
|
||||
check_message_sig::<B, H>(commit::<B, H>(parent_hash, round, h_1), s_1, misbehaved) &&
|
||||
check_message_sig::<B, H>(commit::<B, H>(parent_hash, round, h_2), s_2, misbehaved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use keyring::ed25519;
|
||||
use keyring::Keyring;
|
||||
|
||||
use runtime_primitives::testing::{H256, Block as RawBlock};
|
||||
|
||||
type Block = RawBlock<u64>;
|
||||
|
||||
fn sign_prepare(key: &ed25519::Pair, round: u32, hash: H256, parent_hash: H256) -> (H256, Signature) {
|
||||
let msg = substrate_bft::sign_message::<Block>(
|
||||
rhododendron::Message::Vote(rhododendron::Vote::Prepare(round as _, hash)),
|
||||
key,
|
||||
parent_hash
|
||||
);
|
||||
|
||||
match msg {
|
||||
rhododendron::LocalizedMessage::Vote(vote) => (hash, vote.signature.signature),
|
||||
_ => panic!("signing vote leads to signed vote"),
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_commit(key: &ed25519::Pair, round: u32, hash: H256, parent_hash: H256) -> (H256, Signature) {
|
||||
let msg = substrate_bft::sign_message::<Block>(
|
||||
rhododendron::Message::Vote(rhododendron::Vote::Commit(round as _, hash)),
|
||||
key,
|
||||
parent_hash
|
||||
);
|
||||
|
||||
match msg {
|
||||
rhododendron::LocalizedMessage::Vote(vote) => (hash, vote.signature.signature),
|
||||
_ => panic!("signing vote leads to signed vote"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluates_double_prepare() {
|
||||
let key: ed25519::Pair = Keyring::One.into();
|
||||
let parent_hash = [0xff; 32].into();
|
||||
let hash_1 = [0; 32].into();
|
||||
let hash_2 = [1; 32].into();
|
||||
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&key.public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoublePrepare(
|
||||
1,
|
||||
sign_prepare(&key, 1, hash_1, parent_hash),
|
||||
sign_prepare(&key, 1, hash_2, parent_hash)
|
||||
)
|
||||
));
|
||||
|
||||
// same signature twice is not misbehavior.
|
||||
let signed = sign_prepare(&key, 1, hash_1, parent_hash);
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&key.public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoublePrepare(
|
||||
1,
|
||||
signed,
|
||||
signed,
|
||||
)
|
||||
) == false);
|
||||
|
||||
// misbehavior has wrong target.
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&Keyring::Two.to_raw_public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoublePrepare(
|
||||
1,
|
||||
sign_prepare(&key, 1, hash_1, parent_hash),
|
||||
sign_prepare(&key, 1, hash_2, parent_hash),
|
||||
)
|
||||
) == false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluates_double_commit() {
|
||||
let key: ed25519::Pair = Keyring::One.into();
|
||||
let parent_hash = [0xff; 32].into();
|
||||
let hash_1 = [0; 32].into();
|
||||
let hash_2 = [1; 32].into();
|
||||
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&key.public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoubleCommit(
|
||||
1,
|
||||
sign_commit(&key, 1, hash_1, parent_hash),
|
||||
sign_commit(&key, 1, hash_2, parent_hash)
|
||||
)
|
||||
));
|
||||
|
||||
// same signature twice is not misbehavior.
|
||||
let signed = sign_commit(&key, 1, hash_1, parent_hash);
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&key.public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoubleCommit(
|
||||
1,
|
||||
signed,
|
||||
signed,
|
||||
)
|
||||
) == false);
|
||||
|
||||
// misbehavior has wrong target.
|
||||
assert!(evaluate_misbehavior::<Block, H256>(
|
||||
&Keyring::Two.to_raw_public().into(),
|
||||
parent_hash,
|
||||
&MisbehaviorKind::BftDoubleCommit(
|
||||
1,
|
||||
sign_commit(&key, 1, hash_1, parent_hash),
|
||||
sign_commit(&key, 1, hash_2, parent_hash),
|
||||
)
|
||||
) == false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
description = "libp2p implementation of the ethcore network library"
|
||||
homepage = "http://parity.io"
|
||||
license = "GPL-3.0"
|
||||
name = "substrate-network-libp2p"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
bytes = "0.4"
|
||||
error-chain = { version = "0.12", default-features = false }
|
||||
fnv = "1.0"
|
||||
futures = "0.1"
|
||||
libp2p = { git = "https://github.com/libp2p/rust-libp2p", rev = "304e9c72c88bc97824f2734dc19d1b5f4556d346", default-features = false, features = ["libp2p-secio", "libp2p-secio-secp256k1"] }
|
||||
ethcore-io = { git = "https://github.com/paritytech/parity.git" }
|
||||
ethkey = { git = "https://github.com/paritytech/parity.git" }
|
||||
ethereum-types = "0.3"
|
||||
parking_lot = "0.5"
|
||||
libc = "0.2"
|
||||
log = "0.3"
|
||||
rand = "0.5.0"
|
||||
serde = "1.0.70"
|
||||
serde_derive = "1.0.70"
|
||||
serde_json = "1.0.24"
|
||||
tokio = "0.1"
|
||||
tokio-io = "0.1"
|
||||
tokio-timer = "0.2"
|
||||
unsigned-varint = { version = "0.2.1", features = ["codec"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.2"
|
||||
parity-bytes = "0.1"
|
||||
ethcore-io = { git = "https://github.com/paritytech/parity.git" }
|
||||
ethcore-logger = { git = "https://github.com/paritytech/parity.git" }
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Network libp2p
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Connection filter trait.
|
||||
|
||||
use super::NodeId;
|
||||
|
||||
/// Filtered connection direction.
|
||||
pub enum ConnectionDirection {
|
||||
Inbound,
|
||||
Outbound,
|
||||
}
|
||||
|
||||
/// Connection filter. Each connection is checked against `connection_allowed`.
|
||||
pub trait ConnectionFilter : Send + Sync {
|
||||
/// Filter a connection. Returns `true` if connection should be allowed. `false` if rejected.
|
||||
fn connection_allowed(&self, own_id: &NodeId, connecting_id: &NodeId, direction: ConnectionDirection) -> bool;
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use ProtocolId;
|
||||
use libp2p::core::{Multiaddr, ConnectionUpgrade, Endpoint};
|
||||
use PacketId;
|
||||
use std::io::Error as IoError;
|
||||
use std::vec::IntoIter as VecIntoIter;
|
||||
use futures::{future, Future, stream, Stream, Sink};
|
||||
use futures::sync::mpsc;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use unsigned_varint::codec::UviBytes;
|
||||
|
||||
/// Connection upgrade for a single protocol.
|
||||
///
|
||||
/// Note that "a single protocol" here refers to `par` for example. However
|
||||
/// each protocol can have multiple different versions for networking purposes.
|
||||
#[derive(Clone)]
|
||||
pub struct RegisteredProtocol<T> {
|
||||
/// Id of the protocol for API purposes.
|
||||
id: ProtocolId,
|
||||
/// Base name of the protocol as advertised on the network.
|
||||
/// Ends with `/` so that we can append a version number behind.
|
||||
base_name: Bytes,
|
||||
/// List of protocol versions that we support, plus their packet count.
|
||||
/// Ordered in descending order so that the best comes first.
|
||||
/// The packet count is used to filter out invalid messages.
|
||||
supported_versions: Vec<(u8, u8)>,
|
||||
/// Custom data.
|
||||
custom_data: T,
|
||||
}
|
||||
|
||||
/// Output of a `RegisteredProtocol` upgrade.
|
||||
pub struct RegisteredProtocolOutput<T> {
|
||||
/// Data passed to `RegisteredProtocol::new`.
|
||||
pub custom_data: T,
|
||||
|
||||
/// Id of the protocol.
|
||||
pub protocol_id: ProtocolId,
|
||||
|
||||
/// Endpoint of the connection.
|
||||
pub endpoint: Endpoint,
|
||||
|
||||
/// Version of the protocol that was negotiated.
|
||||
pub protocol_version: u8,
|
||||
|
||||
/// Channel to sender outgoing messages to.
|
||||
// TODO: consider assembling packet_id here
|
||||
pub outgoing: mpsc::UnboundedSender<Bytes>,
|
||||
|
||||
/// Stream where incoming messages are received. The stream ends whenever
|
||||
/// either side is closed.
|
||||
pub incoming: Box<Stream<Item = (PacketId, Bytes), Error = IoError> + Send>,
|
||||
}
|
||||
|
||||
impl<T> RegisteredProtocol<T> {
|
||||
/// Creates a new `RegisteredProtocol`. The `custom_data` parameter will be
|
||||
/// passed inside the `RegisteredProtocolOutput`.
|
||||
pub fn new(custom_data: T, protocol: ProtocolId, versions: &[(u8, u8)])
|
||||
-> Self {
|
||||
let mut proto_name = Bytes::from_static(b"/core/");
|
||||
proto_name.extend_from_slice(&protocol);
|
||||
proto_name.extend_from_slice(b"/");
|
||||
|
||||
RegisteredProtocol {
|
||||
base_name: proto_name,
|
||||
id: protocol,
|
||||
supported_versions: {
|
||||
let mut tmp: Vec<_> = versions.iter().rev().cloned().collect();
|
||||
tmp.sort_unstable_by(|a, b| b.1.cmp(&a.1));
|
||||
tmp
|
||||
},
|
||||
custom_data: custom_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the ID of the protocol.
|
||||
pub fn id(&self) -> ProtocolId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Returns the custom data that was passed to `new`.
|
||||
pub fn custom_data(&self) -> &T {
|
||||
&self.custom_data
|
||||
}
|
||||
}
|
||||
|
||||
// `Maf` is short for `MultiaddressFuture`
|
||||
impl<T, C, Maf> ConnectionUpgrade<C, Maf> for RegisteredProtocol<T>
|
||||
where C: AsyncRead + AsyncWrite + Send + 'static, // TODO: 'static :-/
|
||||
Maf: Future<Item = Multiaddr, Error = IoError> + Send + 'static, // TODO: 'static :(
|
||||
{
|
||||
type NamesIter = VecIntoIter<(Bytes, Self::UpgradeIdentifier)>;
|
||||
type UpgradeIdentifier = u8; // Protocol version
|
||||
|
||||
#[inline]
|
||||
fn protocol_names(&self) -> Self::NamesIter {
|
||||
// Report each version as an individual protocol.
|
||||
self.supported_versions.iter().map(|&(ver, _)| {
|
||||
let num = ver.to_string();
|
||||
let mut name = self.base_name.clone();
|
||||
name.extend_from_slice(num.as_bytes());
|
||||
(name, ver)
|
||||
}).collect::<Vec<_>>().into_iter()
|
||||
}
|
||||
|
||||
type Output = RegisteredProtocolOutput<T>;
|
||||
type MultiaddrFuture = Maf;
|
||||
type Future = future::FutureResult<(Self::Output, Self::MultiaddrFuture), IoError>;
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn upgrade(
|
||||
self,
|
||||
socket: C,
|
||||
protocol_version: Self::UpgradeIdentifier,
|
||||
endpoint: Endpoint,
|
||||
remote_addr: Maf
|
||||
) -> Self::Future {
|
||||
let packet_count = self.supported_versions
|
||||
.iter()
|
||||
.find(|&(v, _)| *v == protocol_version)
|
||||
.expect("negotiated protocol version that wasn't advertised ; \
|
||||
programmer error")
|
||||
.1;
|
||||
|
||||
// This function is called whenever we successfully negotiated a
|
||||
// protocol with a remote (both if initiated by us or by the remote)
|
||||
|
||||
// This channel is used to send outgoing packets to the custom_data
|
||||
// for this open substream.
|
||||
let (msg_tx, msg_rx) = mpsc::unbounded();
|
||||
|
||||
// Build the sink for outgoing network bytes, and the stream for
|
||||
// incoming instructions. `stream` implements `Stream<Item = Message>`.
|
||||
enum Message {
|
||||
/// Received data from the network.
|
||||
RecvSocket(BytesMut),
|
||||
/// Data to send to the network.
|
||||
/// The packet_id must already be inside the `Bytes`.
|
||||
SendReq(Bytes),
|
||||
/// The socket has been closed.
|
||||
Finished,
|
||||
}
|
||||
|
||||
let (sink, stream) = {
|
||||
let framed = AsyncRead::framed(socket, UviBytes::default());
|
||||
let msg_rx = msg_rx.map(Message::SendReq)
|
||||
.map_err(|()| unreachable!("mpsc::UnboundedReceiver never errors"));
|
||||
let (sink, stream) = framed.split();
|
||||
let stream = stream.map(Message::RecvSocket)
|
||||
.chain(stream::once(Ok(Message::Finished)));
|
||||
(sink, msg_rx.select(stream))
|
||||
};
|
||||
|
||||
let incoming = stream::unfold((sink, stream, false), move |(sink, stream, finished)| {
|
||||
if finished {
|
||||
return None
|
||||
}
|
||||
|
||||
Some(stream
|
||||
.into_future()
|
||||
.map_err(|(err, _)| err)
|
||||
.and_then(move |(message, stream)|
|
||||
match message {
|
||||
Some(Message::RecvSocket(mut data)) => {
|
||||
// The `data` should be prefixed by the packet ID,
|
||||
// therefore an empty packet is invalid.
|
||||
if data.is_empty() {
|
||||
debug!(target: "sub-libp2p", "ignoring incoming \
|
||||
packet because it was empty");
|
||||
let f = future::ok((None, (sink, stream, false)));
|
||||
return future::Either::A(f)
|
||||
}
|
||||
|
||||
let packet_id = data[0];
|
||||
let data = data.split_off(1);
|
||||
|
||||
if packet_id >= packet_count {
|
||||
debug!(target: "sub-libp2p", "ignoring incoming packet \
|
||||
because packet_id {} is too large", packet_id);
|
||||
let f = future::ok((None, (sink, stream, false)));
|
||||
future::Either::A(f)
|
||||
} else {
|
||||
let out = Some((packet_id, data.freeze()));
|
||||
let f = future::ok((out, (sink, stream, false)));
|
||||
future::Either::A(f)
|
||||
}
|
||||
},
|
||||
|
||||
Some(Message::SendReq(data)) => {
|
||||
let fut = sink.send(data)
|
||||
.map(move |sink| (None, (sink, stream, false)));
|
||||
future::Either::B(fut)
|
||||
},
|
||||
|
||||
Some(Message::Finished) | None => {
|
||||
let f = future::ok((None, (sink, stream, true)));
|
||||
future::Either::A(f)
|
||||
},
|
||||
}
|
||||
))
|
||||
}).filter_map(|v| v);
|
||||
|
||||
let out = RegisteredProtocolOutput {
|
||||
custom_data: self.custom_data,
|
||||
protocol_id: self.id,
|
||||
endpoint,
|
||||
protocol_version: protocol_version,
|
||||
outgoing: msg_tx,
|
||||
incoming: Box::new(incoming),
|
||||
};
|
||||
|
||||
future::ok((out, remote_addr))
|
||||
}
|
||||
}
|
||||
|
||||
// Connection upgrade for all the protocols contained in it.
|
||||
#[derive(Clone)]
|
||||
pub struct RegisteredProtocols<T>(pub Vec<RegisteredProtocol<T>>);
|
||||
|
||||
impl<T> RegisteredProtocols<T> {
|
||||
/// Finds a protocol in the list by its id.
|
||||
pub fn find_protocol(&self, protocol: ProtocolId)
|
||||
-> Option<&RegisteredProtocol<T>> {
|
||||
self.0.iter().find(|p| p.id == protocol)
|
||||
}
|
||||
|
||||
/// Returns true if the given protocol is in the list.
|
||||
pub fn has_protocol(&self, protocol: ProtocolId) -> bool {
|
||||
self.0.iter().any(|p| p.id == protocol)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for RegisteredProtocols<T> {
|
||||
fn default() -> Self {
|
||||
RegisteredProtocols(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C, Maf> ConnectionUpgrade<C, Maf> for RegisteredProtocols<T>
|
||||
where C: AsyncRead + AsyncWrite + Send + 'static, // TODO: 'static :-/
|
||||
Maf: Future<Item = Multiaddr, Error = IoError> + Send + 'static, // TODO: 'static :(
|
||||
{
|
||||
type NamesIter = VecIntoIter<(Bytes, Self::UpgradeIdentifier)>;
|
||||
type UpgradeIdentifier = (usize,
|
||||
<RegisteredProtocol<T> as ConnectionUpgrade<C, Maf>>::UpgradeIdentifier);
|
||||
|
||||
fn protocol_names(&self) -> Self::NamesIter {
|
||||
// We concat the lists of `RegisteredProtocol::protocol_names` for
|
||||
// each protocol.
|
||||
self.0.iter().enumerate().flat_map(|(n, proto)|
|
||||
ConnectionUpgrade::<C, Maf>::protocol_names(proto)
|
||||
.map(move |(name, id)| (name, (n, id)))
|
||||
).collect::<Vec<_>>().into_iter()
|
||||
}
|
||||
|
||||
type Output = <RegisteredProtocol<T> as ConnectionUpgrade<C, Maf>>::Output;
|
||||
type MultiaddrFuture = <RegisteredProtocol<T> as
|
||||
ConnectionUpgrade<C, Maf>>::MultiaddrFuture;
|
||||
type Future = <RegisteredProtocol<T> as ConnectionUpgrade<C, Maf>>::Future;
|
||||
|
||||
#[inline]
|
||||
fn upgrade(
|
||||
self,
|
||||
socket: C,
|
||||
upgrade_identifier: Self::UpgradeIdentifier,
|
||||
endpoint: Endpoint,
|
||||
remote_addr: Maf
|
||||
) -> Self::Future {
|
||||
let (protocol_index, inner_proto_id) = upgrade_identifier;
|
||||
self.0.into_iter()
|
||||
.nth(protocol_index)
|
||||
.expect("invalid protocol index ; programmer logic error")
|
||||
.upgrade(socket, inner_proto_id, endpoint, remote_addr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{io, net, fmt};
|
||||
use libc::{ENFILE, EMFILE};
|
||||
use io::IoError;
|
||||
use ethkey;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum DisconnectReason
|
||||
{
|
||||
DisconnectRequested,
|
||||
TCPError,
|
||||
BadProtocol,
|
||||
UselessPeer,
|
||||
TooManyPeers,
|
||||
DuplicatePeer,
|
||||
IncompatibleProtocol,
|
||||
NullIdentity,
|
||||
ClientQuit,
|
||||
UnexpectedIdentity,
|
||||
LocalIdentity,
|
||||
PingTimeout,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl DisconnectReason {
|
||||
pub fn from_u8(n: u8) -> DisconnectReason {
|
||||
match n {
|
||||
0 => DisconnectReason::DisconnectRequested,
|
||||
1 => DisconnectReason::TCPError,
|
||||
2 => DisconnectReason::BadProtocol,
|
||||
3 => DisconnectReason::UselessPeer,
|
||||
4 => DisconnectReason::TooManyPeers,
|
||||
5 => DisconnectReason::DuplicatePeer,
|
||||
6 => DisconnectReason::IncompatibleProtocol,
|
||||
7 => DisconnectReason::NullIdentity,
|
||||
8 => DisconnectReason::ClientQuit,
|
||||
9 => DisconnectReason::UnexpectedIdentity,
|
||||
10 => DisconnectReason::LocalIdentity,
|
||||
11 => DisconnectReason::PingTimeout,
|
||||
_ => DisconnectReason::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DisconnectReason {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::DisconnectReason::*;
|
||||
|
||||
let msg = match *self {
|
||||
DisconnectRequested => "disconnect requested",
|
||||
TCPError => "TCP error",
|
||||
BadProtocol => "bad protocol",
|
||||
UselessPeer => "useless peer",
|
||||
TooManyPeers => "too many peers",
|
||||
DuplicatePeer => "duplicate peer",
|
||||
IncompatibleProtocol => "incompatible protocol",
|
||||
NullIdentity => "null identity",
|
||||
ClientQuit => "client quit",
|
||||
UnexpectedIdentity => "unexpected identity",
|
||||
LocalIdentity => "local identity",
|
||||
PingTimeout => "ping timeout",
|
||||
Unknown => "unknown",
|
||||
};
|
||||
|
||||
f.write_str(msg)
|
||||
}
|
||||
}
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
SocketIo(IoError) #[doc = "Socket IO error."];
|
||||
}
|
||||
|
||||
errors {
|
||||
#[doc = "Error concerning the network address parsing subsystem."]
|
||||
AddressParse {
|
||||
description("Failed to parse network address"),
|
||||
display("Failed to parse network address"),
|
||||
}
|
||||
|
||||
#[doc = "Error concerning the network address resolution subsystem."]
|
||||
AddressResolve(err: Option<io::Error>) {
|
||||
description("Failed to resolve network address"),
|
||||
display("Failed to resolve network address {}", err.as_ref().map_or("".to_string(), |e| e.to_string())),
|
||||
}
|
||||
|
||||
#[doc = "Authentication failure"]
|
||||
Auth {
|
||||
description("Authentication failure"),
|
||||
display("Authentication failure"),
|
||||
}
|
||||
|
||||
#[doc = "Unrecognised protocol"]
|
||||
BadProtocol {
|
||||
description("Bad protocol"),
|
||||
display("Bad protocol"),
|
||||
}
|
||||
|
||||
#[doc = "Expired message"]
|
||||
Expired {
|
||||
description("Expired message"),
|
||||
display("Expired message"),
|
||||
}
|
||||
|
||||
#[doc = "Peer not found"]
|
||||
PeerNotFound {
|
||||
description("Peer not found"),
|
||||
display("Peer not found"),
|
||||
}
|
||||
|
||||
#[doc = "Peer is disconnected"]
|
||||
Disconnect(reason: DisconnectReason) {
|
||||
description("Peer disconnected"),
|
||||
display("Peer disconnected: {}", reason),
|
||||
}
|
||||
|
||||
#[doc = "Invalid node id"]
|
||||
InvalidNodeId {
|
||||
description("Invalid node id"),
|
||||
display("Invalid node id"),
|
||||
}
|
||||
|
||||
#[doc = "Packet size is over the protocol limit"]
|
||||
OversizedPacket {
|
||||
description("Packet is too large"),
|
||||
display("Packet is too large"),
|
||||
}
|
||||
|
||||
#[doc = "Reached system resource limits for this process"]
|
||||
ProcessTooManyFiles {
|
||||
description("Too many open files in process."),
|
||||
display("Too many open files in this process. Check your resource limits and restart parity"),
|
||||
}
|
||||
|
||||
#[doc = "Reached system wide resource limits"]
|
||||
SystemTooManyFiles {
|
||||
description("Too many open files on system."),
|
||||
display("Too many open files on system. Consider closing some processes/release some file handlers or increas the system-wide resource limits and restart parity."),
|
||||
}
|
||||
|
||||
#[doc = "An unknown IO error occurred."]
|
||||
Io(err: io::Error) {
|
||||
description("IO Error"),
|
||||
display("Unexpected IO error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self {
|
||||
match err.raw_os_error() {
|
||||
Some(ENFILE) => ErrorKind::ProcessTooManyFiles.into(),
|
||||
Some(EMFILE) => ErrorKind::SystemTooManyFiles.into(),
|
||||
_ => Error::from_kind(ErrorKind::Io(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ethkey::Error> for Error {
|
||||
fn from(_err: ethkey::Error) -> Self {
|
||||
ErrorKind::Auth.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ethkey::crypto::Error> for Error {
|
||||
fn from(_err: ethkey::crypto::Error) -> Self {
|
||||
ErrorKind::Auth.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<net::AddrParseError> for Error {
|
||||
fn from(_err: net::AddrParseError) -> Self { ErrorKind::AddressParse.into() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_errors() {
|
||||
assert_eq!(DisconnectReason::ClientQuit, DisconnectReason::from_u8(8));
|
||||
let mut r = DisconnectReason::DisconnectRequested;
|
||||
for i in 0 .. 20 {
|
||||
r = DisconnectReason::from_u8(i);
|
||||
}
|
||||
assert_eq!(DisconnectReason::Unknown, r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_errors() {
|
||||
use libc::{EMFILE, ENFILE};
|
||||
|
||||
assert_matches!(
|
||||
<Error as From<io::Error>>::from(
|
||||
io::Error::from_raw_os_error(ENFILE)
|
||||
).kind(),
|
||||
ErrorKind::ProcessTooManyFiles);
|
||||
|
||||
assert_matches!(
|
||||
<Error as From<io::Error>>::from(
|
||||
io::Error::from_raw_os_error(EMFILE)
|
||||
).kind(),
|
||||
ErrorKind::SystemTooManyFiles);
|
||||
|
||||
assert_matches!(
|
||||
<Error as From<io::Error>>::from(
|
||||
io::Error::from_raw_os_error(0)
|
||||
).kind(),
|
||||
ErrorKind::Io(_));
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// tag::description[]
|
||||
//! TODO: Missing doc
|
||||
// end::description[]
|
||||
|
||||
#![recursion_limit="128"]
|
||||
#![type_length_limit = "268435456"]
|
||||
|
||||
extern crate parking_lot;
|
||||
extern crate fnv;
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_timer;
|
||||
extern crate ethkey;
|
||||
extern crate libc;
|
||||
extern crate libp2p;
|
||||
extern crate rand;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate bytes;
|
||||
extern crate unsigned_varint;
|
||||
|
||||
extern crate ethcore_io as io;
|
||||
extern crate ethereum_types;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(test)] #[macro_use]
|
||||
extern crate assert_matches;
|
||||
|
||||
pub use connection_filter::{ConnectionFilter, ConnectionDirection};
|
||||
pub use io::TimerToken;
|
||||
pub use error::{Error, ErrorKind, DisconnectReason};
|
||||
pub use libp2p::{Multiaddr, multiaddr::AddrComponent};
|
||||
pub use traits::*;
|
||||
|
||||
mod connection_filter;
|
||||
mod custom_proto;
|
||||
mod error;
|
||||
mod network_state;
|
||||
mod service;
|
||||
mod timeouts;
|
||||
mod topology;
|
||||
mod traits;
|
||||
mod transport;
|
||||
|
||||
pub use service::NetworkService;
|
||||
|
||||
/// Check if node url is valid
|
||||
pub fn validate_node_url(url: &str) -> Result<(), Error> {
|
||||
match url.parse::<libp2p::multiaddr::Multiaddr>() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(ErrorKind::InvalidNodeId.into()),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,953 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use bytes::Bytes;
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use futures::sync::mpsc;
|
||||
use libp2p::core::{multiaddr::ToMultiaddr, Multiaddr, AddrComponent, Endpoint, UniqueConnec};
|
||||
use libp2p::core::{UniqueConnecState, PeerId, PublicKey};
|
||||
use libp2p::kad::KadConnecController;
|
||||
use libp2p::ping::Pinger;
|
||||
use libp2p::secio;
|
||||
use {Error, ErrorKind, NetworkConfiguration, NonReservedPeerMode};
|
||||
use {NodeIndex, ProtocolId, SessionInfo};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use rand::{self, Rng};
|
||||
use topology::{DisconnectReason, NetTopology};
|
||||
use std::fs;
|
||||
use std::io::{Error as IoError, ErrorKind as IoErrorKind, Read, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::atomic;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// File where the peers are stored.
|
||||
const NODES_FILE: &str = "nodes.json";
|
||||
// File where the private key is stored.
|
||||
const SECRET_FILE: &str = "secret";
|
||||
// Duration during which a peer is disabled.
|
||||
const PEER_DISABLE_DURATION: Duration = Duration::from_secs(5 * 60);
|
||||
|
||||
// Common struct shared throughout all the components of the service.
|
||||
pub struct NetworkState {
|
||||
/// Contains the information about the network.
|
||||
topology: RwLock<NetTopology>,
|
||||
|
||||
/// Active connections.
|
||||
connections: RwLock<Connections>,
|
||||
|
||||
/// Maximum incoming peers.
|
||||
max_incoming_peers: u32,
|
||||
/// Maximum outgoing peers.
|
||||
max_outgoing_peers: u32,
|
||||
|
||||
/// If true, only reserved peers can connect.
|
||||
reserved_only: atomic::AtomicBool,
|
||||
/// List of the IDs of the reserved peers.
|
||||
reserved_peers: RwLock<FnvHashSet<PeerId>>,
|
||||
|
||||
/// Each node we discover gets assigned a new unique ID. This ID increases linearly.
|
||||
next_node_index: atomic::AtomicUsize,
|
||||
|
||||
/// List of the IDs of the disabled peers. These peers will see their
|
||||
/// connections refused. Includes the time when the disabling expires.
|
||||
disabled_nodes: Mutex<FnvHashMap<PeerId, Instant>>,
|
||||
|
||||
/// Local private key.
|
||||
local_private_key: secio::SecioKeyPair,
|
||||
/// Local public key.
|
||||
local_public_key: PublicKey,
|
||||
}
|
||||
|
||||
struct Connections {
|
||||
/// For each libp2p peer ID, the ID of the peer in the API we expose.
|
||||
/// Also corresponds to the index in `info_by_peer`.
|
||||
peer_by_nodeid: FnvHashMap<PeerId, usize>,
|
||||
|
||||
/// For each peer ID, information about our connection to this peer.
|
||||
info_by_peer: FnvHashMap<NodeIndex, PeerConnectionInfo>,
|
||||
}
|
||||
|
||||
struct PeerConnectionInfo {
|
||||
/// A list of protocols, and the potential corresponding connection.
|
||||
/// The `UniqueConnec` contains a sender and the protocol version.
|
||||
/// The sender can be used to transmit data for the remote. Note that the
|
||||
/// packet_id has to be inside the `Bytes`.
|
||||
protocols: Vec<(ProtocolId, UniqueConnec<(mpsc::UnboundedSender<Bytes>, u8)>)>,
|
||||
|
||||
/// The Kademlia connection to this node.
|
||||
kad_connec: UniqueConnec<KadConnecController>,
|
||||
|
||||
/// The ping connection to this node.
|
||||
ping_connec: UniqueConnec<Pinger>,
|
||||
|
||||
/// Id of the peer.
|
||||
id: PeerId,
|
||||
|
||||
/// True if this connection was initiated by us. `None` if we're not connected.
|
||||
/// Note that it is theoretically possible that we dial the remote at the
|
||||
/// same time they dial us, in which case the protocols may be dispatched
|
||||
/// between both connections, and in which case the value here will be racy.
|
||||
originated: Option<bool>,
|
||||
|
||||
/// Latest known ping duration.
|
||||
ping: Option<Duration>,
|
||||
|
||||
/// The client version of the remote, or `None` if not known.
|
||||
client_version: Option<String>,
|
||||
|
||||
/// The multiaddresses of the remote, or `None` if not known.
|
||||
remote_addresses: Vec<Multiaddr>,
|
||||
|
||||
/// The local multiaddress used to communicate with the remote, or `None`
|
||||
/// if not known.
|
||||
// TODO: never filled ; also shouldn't be an `Option`
|
||||
local_address: Option<Multiaddr>,
|
||||
}
|
||||
|
||||
/// Simplified, POD version of PeerConnectionInfo.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PeerInfo {
|
||||
/// Id of the peer.
|
||||
pub id: PeerId,
|
||||
|
||||
/// True if this connection was initiated by us.
|
||||
/// Note that it is theoretically possible that we dial the remote at the
|
||||
/// same time they dial us, in which case the protocols may be dispatched
|
||||
/// between both connections, and in which case the value here will be racy.
|
||||
pub originated: bool,
|
||||
|
||||
/// Latest known ping duration.
|
||||
pub ping: Option<Duration>,
|
||||
|
||||
/// The client version of the remote, or `None` if not known.
|
||||
pub client_version: Option<String>,
|
||||
|
||||
/// The multiaddress of the remote.
|
||||
pub remote_address: Option<Multiaddr>,
|
||||
|
||||
/// The local multiaddress used to communicate with the remote, or `None`
|
||||
/// if not known.
|
||||
pub local_address: Option<Multiaddr>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a PeerConnectionInfo> for PeerInfo {
|
||||
fn from(i: &'a PeerConnectionInfo) -> PeerInfo {
|
||||
PeerInfo {
|
||||
id: i.id.clone(),
|
||||
originated: i.originated.unwrap_or(true),
|
||||
ping: i.ping,
|
||||
client_version: i.client_version.clone(),
|
||||
remote_address: i.remote_addresses.get(0).map(|a| a.clone()),
|
||||
local_address: i.local_address.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkState {
|
||||
pub fn new(config: &NetworkConfiguration) -> Result<NetworkState, Error> {
|
||||
// Private and public keys configuration.
|
||||
let local_private_key = obtain_private_key(&config)?;
|
||||
let local_public_key = local_private_key.to_public_key();
|
||||
|
||||
// Build the storage for peers, including the bootstrap nodes.
|
||||
let mut topology = if let Some(ref path) = config.net_config_path {
|
||||
let path = Path::new(path).join(NODES_FILE);
|
||||
debug!(target: "sub-libp2p", "Initializing peer store for JSON file {:?}", path);
|
||||
NetTopology::from_file(path)
|
||||
} else {
|
||||
debug!(target: "sub-libp2p", "No peers file configured ; peers won't be saved");
|
||||
NetTopology::memory()
|
||||
};
|
||||
|
||||
let reserved_peers = {
|
||||
let mut reserved_peers = FnvHashSet::with_capacity_and_hasher(
|
||||
config.reserved_nodes.len(),
|
||||
Default::default()
|
||||
);
|
||||
for peer in config.reserved_nodes.iter() {
|
||||
let (id, _) = parse_and_add_to_topology(peer, &mut topology)?;
|
||||
reserved_peers.insert(id);
|
||||
}
|
||||
RwLock::new(reserved_peers)
|
||||
};
|
||||
|
||||
let expected_max_peers = config.max_peers as usize + config.reserved_nodes.len();
|
||||
|
||||
Ok(NetworkState {
|
||||
topology: RwLock::new(topology),
|
||||
max_outgoing_peers: config.min_peers,
|
||||
max_incoming_peers: config.max_peers.saturating_sub(config.min_peers),
|
||||
connections: RwLock::new(Connections {
|
||||
peer_by_nodeid: FnvHashMap::with_capacity_and_hasher(expected_max_peers, Default::default()),
|
||||
info_by_peer: FnvHashMap::with_capacity_and_hasher(expected_max_peers, Default::default()),
|
||||
}),
|
||||
reserved_only: atomic::AtomicBool::new(config.non_reserved_mode == NonReservedPeerMode::Deny),
|
||||
reserved_peers,
|
||||
next_node_index: atomic::AtomicUsize::new(0),
|
||||
disabled_nodes: Mutex::new(Default::default()),
|
||||
local_private_key,
|
||||
local_public_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the private key of the local node.
|
||||
pub fn local_private_key(&self) -> &secio::SecioKeyPair {
|
||||
&self.local_private_key
|
||||
}
|
||||
|
||||
/// Returns the public key of the local node.
|
||||
pub fn local_public_key(&self) -> &PublicKey {
|
||||
&self.local_public_key
|
||||
}
|
||||
|
||||
/// Returns a list of peers and addresses which we should try connect to.
|
||||
///
|
||||
/// Because of expiration and back-off mechanisms, this list can change
|
||||
/// by itself over time. The `Instant` that is returned corresponds to
|
||||
/// the earlier known time when a new entry will be added automatically to
|
||||
/// the list.
|
||||
pub fn outgoing_connections_to_attempt(&self) -> (Vec<(PeerId, Multiaddr)>, Instant) {
|
||||
// TODO: handle better
|
||||
let connections = self.connections.read();
|
||||
|
||||
let num_to_attempt = if self.reserved_only.load(atomic::Ordering::Relaxed) {
|
||||
0
|
||||
} else {
|
||||
let num_open_custom_connections = num_open_custom_connections(&connections, &self.reserved_peers.read());
|
||||
self.max_outgoing_peers.saturating_sub(num_open_custom_connections.unreserved_outgoing)
|
||||
};
|
||||
|
||||
let topology = self.topology.read();
|
||||
let (list, change) = topology.addrs_to_attempt();
|
||||
let list = list
|
||||
.filter(|&(peer, _)| {
|
||||
// Filter out peers which we are already connected to.
|
||||
let cur = match connections.peer_by_nodeid.get(peer) {
|
||||
Some(e) => e,
|
||||
None => return true
|
||||
};
|
||||
|
||||
let infos = match connections.info_by_peer.get(&cur) {
|
||||
Some(i) => i,
|
||||
None => return true
|
||||
};
|
||||
|
||||
!infos.protocols.iter().any(|(_, conn)| conn.is_alive())
|
||||
})
|
||||
.take(num_to_attempt as usize)
|
||||
.map(|(addr, peer)| (addr.clone(), peer.clone()))
|
||||
.collect();
|
||||
(list, change)
|
||||
}
|
||||
|
||||
/// Returns true if we are connected to any peer at all.
|
||||
pub fn has_connected_peer(&self) -> bool {
|
||||
!self.connections.read().peer_by_nodeid.is_empty()
|
||||
}
|
||||
|
||||
/// Get a list of all connected peers by id.
|
||||
pub fn connected_peers(&self) -> Vec<NodeIndex> {
|
||||
self.connections.read().peer_by_nodeid.values().cloned().collect()
|
||||
}
|
||||
|
||||
/// Returns true if the given `NodeIndex` is valid.
|
||||
///
|
||||
/// `NodeIndex`s are never reused, so once this function returns `false` it
|
||||
/// will never return `true` again for the same `NodeIndex`.
|
||||
pub fn is_peer_connected(&self, peer: NodeIndex) -> bool {
|
||||
self.connections.read().info_by_peer.contains_key(&peer)
|
||||
}
|
||||
|
||||
/// Reports the ping of the peer. Returned later by `session_info()`.
|
||||
/// No-op if the `who` is not valid/expired.
|
||||
pub fn report_ping_duration(&self, who: NodeIndex, ping: Duration) {
|
||||
let mut connections = self.connections.write();
|
||||
let info = match connections.info_by_peer.get_mut(&who) {
|
||||
Some(info) => info,
|
||||
None => return,
|
||||
};
|
||||
info.ping = Some(ping);
|
||||
}
|
||||
|
||||
/// If we're connected to a peer with the given protocol, returns
|
||||
/// information about the connection. Otherwise, returns `None`.
|
||||
pub fn session_info(&self, peer: NodeIndex, protocol: ProtocolId) -> Option<SessionInfo> {
|
||||
let connections = self.connections.read();
|
||||
let info = match connections.info_by_peer.get(&peer) {
|
||||
Some(info) => info,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let protocol_version = match info.protocols.iter().find(|&(ref p, _)| p == &protocol) {
|
||||
Some(&(_, ref unique_connec)) =>
|
||||
if let Some(val) = unique_connec.poll() {
|
||||
val.1 as u32
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
None => return None,
|
||||
};
|
||||
|
||||
Some(SessionInfo {
|
||||
id: None, // TODO: ???? what to do??? wrong format!
|
||||
client_version: info.client_version.clone().take().unwrap_or(String::new()),
|
||||
protocol_version,
|
||||
capabilities: Vec::new(), // TODO: list of supported protocols ; hard
|
||||
peer_capabilities: Vec::new(), // TODO: difference with `peer_capabilities`?
|
||||
ping: info.ping,
|
||||
originated: info.originated.unwrap_or(true),
|
||||
remote_address: info.remote_addresses.get(0).map(|a| a.to_string()).unwrap_or_default(),
|
||||
local_address: info.local_address.as_ref().map(|a| a.to_string())
|
||||
.unwrap_or(String::new()),
|
||||
})
|
||||
}
|
||||
|
||||
/// If we're connected to a peer with the given protocol, returns the
|
||||
/// protocol version. Otherwise, returns `None`.
|
||||
pub fn protocol_version(&self, peer: NodeIndex, protocol: ProtocolId) -> Option<u8> {
|
||||
let connections = self.connections.read();
|
||||
let peer = match connections.info_by_peer.get(&peer) {
|
||||
Some(peer) => peer,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
peer.protocols.iter()
|
||||
.find(|p| p.0 == protocol)
|
||||
.and_then(|p| p.1.poll())
|
||||
.map(|(_, version)| version)
|
||||
}
|
||||
|
||||
/// Equivalent to `session_info(peer).map(|info| info.client_version)`.
|
||||
pub fn peer_client_version(&self, peer: NodeIndex, protocol: ProtocolId) -> Option<String> {
|
||||
// TODO: implement more directly, without going through `session_info`
|
||||
self.session_info(peer, protocol)
|
||||
.map(|info| info.client_version)
|
||||
}
|
||||
|
||||
/// Adds an address discovered by Kademlia.
|
||||
/// Note that we don't have to be connected to a peer to add an address.
|
||||
/// If `connectable` is `true`, that means we have a hint from a remote that this node can be
|
||||
/// connected to.
|
||||
pub fn add_kad_discovered_addr(&self, node_id: &PeerId, addr: Multiaddr, connectable: bool) {
|
||||
self.topology.write().add_kademlia_discovered_addr(node_id, addr, connectable)
|
||||
}
|
||||
|
||||
/// Returns the known multiaddresses of a peer.
|
||||
///
|
||||
/// The boolean associated to each address indicates whether we're connected to it.
|
||||
pub fn addrs_of_peer(&self, node_id: &PeerId) -> Vec<(Multiaddr, bool)> {
|
||||
let topology = self.topology.read();
|
||||
// Note: I have no idea why, but fusing the two lines below fails the
|
||||
// borrow check
|
||||
let out: Vec<_> = topology
|
||||
.addrs_of_peer(node_id).map(|(a, c)| (a.clone(), c)).collect();
|
||||
out
|
||||
}
|
||||
|
||||
/// Sets information about a peer.
|
||||
///
|
||||
/// No-op if the node index is invalid.
|
||||
pub fn set_node_info(
|
||||
&self,
|
||||
node_index: NodeIndex,
|
||||
client_version: String
|
||||
) {
|
||||
let mut connections = self.connections.write();
|
||||
let infos = match connections.info_by_peer.get_mut(&node_index) {
|
||||
Some(i) => i,
|
||||
None => return
|
||||
};
|
||||
|
||||
infos.client_version = Some(client_version);
|
||||
}
|
||||
|
||||
/// Adds a peer to the internal peer store.
|
||||
/// Returns an error if the peer address is invalid.
|
||||
pub fn add_bootstrap_peer(&self, peer: &str) -> Result<(PeerId, Multiaddr), Error> {
|
||||
parse_and_add_to_topology(peer, &mut self.topology.write())
|
||||
}
|
||||
|
||||
/// Adds a reserved peer to the list of reserved peers.
|
||||
/// Returns an error if the peer address is invalid.
|
||||
pub fn add_reserved_peer(&self, peer: &str) -> Result<(), Error> {
|
||||
let (id, _) = parse_and_add_to_topology(peer, &mut self.topology.write())?;
|
||||
self.reserved_peers.write().insert(id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes the peer from the list of reserved peers. If we're in reserved mode, drops any
|
||||
/// active connection to this peer.
|
||||
/// Returns an error if the peer address is invalid.
|
||||
pub fn remove_reserved_peer(&self, peer: &str) -> Result<(), Error> {
|
||||
let (id, _) = parse_and_add_to_topology(peer, &mut self.topology.write())?;
|
||||
self.reserved_peers.write().remove(&id);
|
||||
|
||||
// Dropping the peer if we're in reserved mode.
|
||||
if self.reserved_only.load(atomic::Ordering::SeqCst) {
|
||||
let mut connections = self.connections.write();
|
||||
if let Some(who) = connections.peer_by_nodeid.remove(&id) {
|
||||
connections.info_by_peer.remove(&who);
|
||||
// TODO: use drop_peer instead
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the non-reserved peer mode.
|
||||
pub fn set_non_reserved_mode(&self, mode: NonReservedPeerMode) {
|
||||
match mode {
|
||||
NonReservedPeerMode::Accept =>
|
||||
self.reserved_only.store(false, atomic::Ordering::SeqCst),
|
||||
NonReservedPeerMode::Deny =>
|
||||
// TODO: drop existing peers?
|
||||
self.reserved_only.store(true, atomic::Ordering::SeqCst),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports that we tried to connect to the given address but failed.
|
||||
///
|
||||
/// This decreases the chance this address will be tried again in the future.
|
||||
#[inline]
|
||||
pub fn report_failed_to_connect(&self, addr: &Multiaddr) {
|
||||
trace!(target: "sub-libp2p", "Failed to connect to {:?}", addr);
|
||||
self.topology.write().report_failed_to_connect(addr);
|
||||
}
|
||||
|
||||
/// Returns the `NodeIndex` corresponding to a node id, or assigns a `NodeIndex` if none
|
||||
/// exists.
|
||||
///
|
||||
/// Returns an error if this node is on the list of disabled/banned nodes..
|
||||
pub fn assign_node_index(
|
||||
&self,
|
||||
node_id: &PeerId
|
||||
) -> Result<NodeIndex, IoError> {
|
||||
// Check whether node is disabled.
|
||||
// TODO: figure out the locking strategy here to avoid possible deadlocks
|
||||
// TODO: put disabled_nodes in connections?
|
||||
let mut disabled_nodes = self.disabled_nodes.lock();
|
||||
if let Some(timeout) = disabled_nodes.get(node_id).cloned() {
|
||||
if timeout > Instant::now() {
|
||||
debug!(target: "sub-libp2p", "Refusing peer {:?} because it is disabled", node_id);
|
||||
return Err(IoError::new(IoErrorKind::ConnectionRefused, "peer is disabled"));
|
||||
} else {
|
||||
disabled_nodes.remove(node_id);
|
||||
}
|
||||
}
|
||||
drop(disabled_nodes);
|
||||
|
||||
let mut connections = self.connections.write();
|
||||
let connections = &mut *connections;
|
||||
let peer_by_nodeid = &mut connections.peer_by_nodeid;
|
||||
let info_by_peer = &mut connections.info_by_peer;
|
||||
|
||||
let who = *peer_by_nodeid.entry(node_id.clone()).or_insert_with(|| {
|
||||
let new_id = self.next_node_index.fetch_add(1, atomic::Ordering::Relaxed);
|
||||
trace!(target: "sub-libp2p", "Creating new peer #{:?} for {:?}", new_id, node_id);
|
||||
|
||||
info_by_peer.insert(new_id, PeerConnectionInfo {
|
||||
protocols: Vec::new(), // TODO: Vec::with_capacity(num_registered_protocols),
|
||||
kad_connec: UniqueConnec::empty(),
|
||||
ping_connec: UniqueConnec::empty(),
|
||||
id: node_id.clone(),
|
||||
originated: None,
|
||||
ping: None,
|
||||
client_version: None,
|
||||
local_address: None,
|
||||
remote_addresses: Vec::with_capacity(1),
|
||||
});
|
||||
|
||||
new_id
|
||||
});
|
||||
|
||||
Ok(who)
|
||||
}
|
||||
|
||||
/// Notifies that we're connected to a node through an address.
|
||||
///
|
||||
/// Returns an error if we refuse the connection.
|
||||
///
|
||||
/// Note that is it legal to connection multiple times to the same node id through different
|
||||
/// addresses and endpoints.
|
||||
pub fn report_connected(
|
||||
&self,
|
||||
node_index: NodeIndex,
|
||||
addr: &Multiaddr,
|
||||
endpoint: Endpoint
|
||||
) -> Result<(), IoError> {
|
||||
let mut connections = self.connections.write();
|
||||
|
||||
// TODO: double locking in this function ; although this has been reviewed to not deadlock
|
||||
// as of the writing of this code, it is possible that a later change that isn't carefully
|
||||
// reviewed triggers one
|
||||
|
||||
if endpoint == Endpoint::Listener {
|
||||
let stats = num_open_custom_connections(&connections, &self.reserved_peers.read());
|
||||
if stats.unreserved_incoming >= self.max_incoming_peers {
|
||||
debug!(target: "sub-libp2p", "Refusing incoming connection from {} because we \
|
||||
reached max incoming peers", addr);
|
||||
return Err(IoError::new(IoErrorKind::ConnectionRefused,
|
||||
"maximum incoming peers reached"));
|
||||
}
|
||||
}
|
||||
|
||||
let infos = match connections.info_by_peer.get_mut(&node_index) {
|
||||
Some(i) => i,
|
||||
None => return Ok(())
|
||||
};
|
||||
|
||||
if !infos.remote_addresses.iter().any(|a| a == addr) {
|
||||
infos.remote_addresses.push(addr.clone());
|
||||
}
|
||||
|
||||
if infos.originated.is_none() {
|
||||
infos.originated = Some(endpoint == Endpoint::Dialer);
|
||||
}
|
||||
|
||||
self.topology.write().report_connected(addr, &infos.id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the node id from a node index.
|
||||
///
|
||||
/// Returns `None` if the node index is invalid.
|
||||
pub fn node_id_from_index(
|
||||
&self,
|
||||
node_index: NodeIndex
|
||||
) -> Option<PeerId> {
|
||||
let mut connections = self.connections.write();
|
||||
let infos = match connections.info_by_peer.get_mut(&node_index) {
|
||||
Some(i) => i,
|
||||
None => return None
|
||||
};
|
||||
Some(infos.id.clone())
|
||||
}
|
||||
|
||||
/// Obtains the `UniqueConnec` corresponding to the Kademlia connection to a peer.
|
||||
///
|
||||
/// Returns `None` if the node index is invalid.
|
||||
pub fn kad_connection(
|
||||
&self,
|
||||
node_index: NodeIndex
|
||||
) -> Option<UniqueConnec<KadConnecController>> {
|
||||
let mut connections = self.connections.write();
|
||||
let infos = match connections.info_by_peer.get_mut(&node_index) {
|
||||
Some(i) => i,
|
||||
None => return None
|
||||
};
|
||||
Some(infos.kad_connec.clone())
|
||||
}
|
||||
|
||||
/// Obtains the `UniqueConnec` corresponding to the Ping connection to a peer.
|
||||
///
|
||||
/// Returns `None` if the node index is invalid.
|
||||
pub fn ping_connection(
|
||||
&self,
|
||||
node_index: NodeIndex
|
||||
) -> Option<UniqueConnec<Pinger>> {
|
||||
let mut connections = self.connections.write();
|
||||
let infos = match connections.info_by_peer.get_mut(&node_index) {
|
||||
Some(i) => i,
|
||||
None => return None
|
||||
};
|
||||
Some(infos.ping_connec.clone())
|
||||
}
|
||||
|
||||
/// Cleans up inactive connections and returns a list of
|
||||
/// connections to ping and identify.
|
||||
pub fn cleanup_and_prepare_updates(
|
||||
&self
|
||||
) -> Vec<PeriodicUpdate> {
|
||||
self.topology.write().cleanup();
|
||||
|
||||
let mut connections = self.connections.write();
|
||||
let connections = &mut *connections;
|
||||
let peer_by_nodeid = &mut connections.peer_by_nodeid;
|
||||
let info_by_peer = &mut connections.info_by_peer;
|
||||
|
||||
let mut ret = Vec::with_capacity(info_by_peer.len());
|
||||
info_by_peer.retain(|&who, infos| {
|
||||
// Remove the peer if neither Kad nor any protocol is alive.
|
||||
if !infos.kad_connec.is_alive() &&
|
||||
!infos.protocols.iter().any(|(_, conn)| conn.is_alive())
|
||||
{
|
||||
peer_by_nodeid.remove(&infos.id);
|
||||
trace!(target: "sub-libp2p", "Cleaning up expired peer \
|
||||
#{:?} ({:?})", who, infos.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(addr) = infos.remote_addresses.get(0) {
|
||||
ret.push(PeriodicUpdate {
|
||||
node_index: who,
|
||||
peer_id: infos.id.clone(),
|
||||
address: addr.clone(),
|
||||
pinger: infos.ping_connec.clone(),
|
||||
identify: infos.client_version.is_none(),
|
||||
});
|
||||
}
|
||||
true
|
||||
});
|
||||
ret
|
||||
}
|
||||
|
||||
/// Obtains the `UniqueConnec` corresponding to a custom protocol connection to a peer.
|
||||
///
|
||||
/// Returns `None` if the node index is invalid.
|
||||
pub fn custom_proto(
|
||||
&self,
|
||||
node_index: NodeIndex,
|
||||
protocol_id: ProtocolId,
|
||||
) -> Option<UniqueConnec<(mpsc::UnboundedSender<Bytes>, u8)>> {
|
||||
let mut connections = self.connections.write();
|
||||
let infos = match connections.info_by_peer.get_mut(&node_index) {
|
||||
Some(i) => i,
|
||||
None => return None
|
||||
};
|
||||
|
||||
if let Some((_, ref uconn)) = infos.protocols.iter().find(|&(prot, _)| prot == &protocol_id) {
|
||||
return Some(uconn.clone())
|
||||
}
|
||||
|
||||
let unique_connec = UniqueConnec::empty();
|
||||
infos.protocols.push((protocol_id.clone(), unique_connec.clone()));
|
||||
Some(unique_connec)
|
||||
}
|
||||
|
||||
/// Sends some data to the given peer, using the sender that was passed
|
||||
/// to the `UniqueConnec` of `custom_proto`.
|
||||
pub fn send(&self, who: NodeIndex, protocol: ProtocolId, message: Bytes) -> Result<(), Error> {
|
||||
if let Some(peer) = self.connections.read().info_by_peer.get(&who) {
|
||||
let sender = peer.protocols.iter().find(|elem| elem.0 == protocol)
|
||||
.and_then(|e| e.1.poll())
|
||||
.map(|e| e.0);
|
||||
if let Some(sender) = sender {
|
||||
sender.unbounded_send(message)
|
||||
.map_err(|err| ErrorKind::Io(IoError::new(IoErrorKind::Other, err)))?;
|
||||
Ok(())
|
||||
} else {
|
||||
// We are connected to this peer, but not with the current
|
||||
// protocol.
|
||||
debug!(target: "sub-libp2p",
|
||||
"Tried to send message to peer {} for which we aren't connected with the requested protocol",
|
||||
who
|
||||
);
|
||||
return Err(ErrorKind::PeerNotFound.into())
|
||||
}
|
||||
} else {
|
||||
debug!(target: "sub-libp2p", "Tried to send message to invalid peer ID {}", who);
|
||||
return Err(ErrorKind::PeerNotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the info on a peer, if there's an active connection.
|
||||
pub fn peer_info(&self, who: NodeIndex) -> Option<PeerInfo> {
|
||||
self.connections.read().info_by_peer.get(&who).map(Into::into)
|
||||
}
|
||||
|
||||
/// Reports that an attempt to make a low-level ping of the peer failed.
|
||||
pub fn report_ping_failed(&self, who: NodeIndex) {
|
||||
self.drop_peer(who);
|
||||
}
|
||||
|
||||
/// Disconnects a peer, if a connection exists (ie. drops the Kademlia
|
||||
/// controller, and the senders that were stored in the `UniqueConnec` of
|
||||
/// `custom_proto`).
|
||||
pub fn drop_peer(&self, who: NodeIndex) {
|
||||
let mut connections = self.connections.write();
|
||||
if let Some(peer_info) = connections.info_by_peer.remove(&who) {
|
||||
trace!(target: "sub-libp2p", "Destroying peer #{} {:?} ; kademlia = {:?} ; num_protos = {:?}",
|
||||
who,
|
||||
peer_info.id,
|
||||
peer_info.kad_connec.is_alive(),
|
||||
peer_info.protocols.iter().filter(|c| c.1.is_alive()).count());
|
||||
let old = connections.peer_by_nodeid.remove(&peer_info.id);
|
||||
debug_assert_eq!(old, Some(who));
|
||||
for addr in &peer_info.remote_addresses {
|
||||
self.topology.write().report_disconnected(addr,
|
||||
DisconnectReason::ClosedGracefully); // TODO: wrong reason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnects all the peers.
|
||||
/// This destroys all the Kademlia controllers and the senders that were
|
||||
/// stored in the `UniqueConnec` of `custom_proto`.
|
||||
pub fn disconnect_all(&self) {
|
||||
let mut connec = self.connections.write();
|
||||
*connec = Connections {
|
||||
info_by_peer: FnvHashMap::with_capacity_and_hasher(
|
||||
connec.peer_by_nodeid.capacity(), Default::default()),
|
||||
peer_by_nodeid: FnvHashMap::with_capacity_and_hasher(
|
||||
connec.peer_by_nodeid.capacity(), Default::default()),
|
||||
};
|
||||
}
|
||||
|
||||
/// Disables a peer for `PEER_DISABLE_DURATION`. This adds the peer to the
|
||||
/// list of disabled peers, and drops any existing connections if
|
||||
/// necessary (ie. drops the sender that was stored in the `UniqueConnec`
|
||||
/// of `custom_proto`).
|
||||
pub fn ban_peer(&self, who: NodeIndex, reason: &str) {
|
||||
// TODO: what do we do if the peer is reserved?
|
||||
// TODO: same logging as in drop_peer
|
||||
let mut connections = self.connections.write();
|
||||
let peer_info = if let Some(peer_info) = connections.info_by_peer.remove(&who) {
|
||||
if let &Some(ref client_version) = &peer_info.client_version {
|
||||
info!(target: "network", "Peer {} (version: {}, addresses: {:?}) disabled. {}", who, client_version, peer_info.remote_addresses, reason);
|
||||
} else {
|
||||
info!(target: "network", "Peer {} (addresses: {:?}) disabled. {}", who, peer_info.remote_addresses, reason);
|
||||
}
|
||||
let old = connections.peer_by_nodeid.remove(&peer_info.id);
|
||||
debug_assert_eq!(old, Some(who));
|
||||
peer_info
|
||||
} else {
|
||||
return
|
||||
};
|
||||
|
||||
drop(connections);
|
||||
let timeout = Instant::now() + PEER_DISABLE_DURATION;
|
||||
self.disabled_nodes.lock().insert(peer_info.id.clone(), timeout);
|
||||
}
|
||||
|
||||
/// Flushes the caches to the disk.
|
||||
///
|
||||
/// This is done in an atomical way, so that an error doesn't corrupt
|
||||
/// anything.
|
||||
pub fn flush_caches_to_disk(&self) -> Result<(), IoError> {
|
||||
match self.topology.read().flush_to_disk() {
|
||||
Ok(()) => {
|
||||
debug!(target: "sub-libp2p", "Flushed JSON peer store to disk");
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(target: "sub-libp2p", "Failed to flush changes to JSON peer store: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NetworkState {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.flush_caches_to_disk();
|
||||
}
|
||||
}
|
||||
|
||||
/// Periodic update that should be performed by the user of the network state.
|
||||
pub struct PeriodicUpdate {
|
||||
/// Index of the node in the network state.
|
||||
pub node_index: NodeIndex,
|
||||
/// Id of the peer.
|
||||
pub peer_id: PeerId,
|
||||
/// Address of the node to ping.
|
||||
pub address: Multiaddr,
|
||||
/// Object that allows pinging the node.
|
||||
pub pinger: UniqueConnec<Pinger>,
|
||||
/// The node should be identified as well.
|
||||
pub identify: bool,
|
||||
}
|
||||
|
||||
struct OpenCustomConnectionsNumbers {
|
||||
/// Total number of open and pending connections.
|
||||
pub total: u32,
|
||||
/// Unreserved incoming number of open and pending connections.
|
||||
pub unreserved_incoming: u32,
|
||||
/// Unreserved outgoing number of open and pending connections.
|
||||
pub unreserved_outgoing: u32,
|
||||
}
|
||||
|
||||
/// Returns the number of open and pending connections with
|
||||
/// custom protocols.
|
||||
fn num_open_custom_connections(connections: &Connections, reserved_peers: &FnvHashSet<PeerId>) -> OpenCustomConnectionsNumbers {
|
||||
let filtered = connections
|
||||
.info_by_peer
|
||||
.values()
|
||||
.filter(|info|
|
||||
info.protocols.iter().any(|&(_, ref connec)|
|
||||
match connec.state() {
|
||||
UniqueConnecState::Pending | UniqueConnecState::Full => true,
|
||||
_ => false
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
let mut total: u32 = 0;
|
||||
let mut unreserved_incoming: u32 = 0;
|
||||
let mut unreserved_outgoing: u32 = 0;
|
||||
|
||||
for info in filtered {
|
||||
total += 1;
|
||||
let node_is_reserved = reserved_peers.contains(&info.id);
|
||||
if !node_is_reserved {
|
||||
if !info.originated.unwrap_or(true) {
|
||||
unreserved_incoming += 1;
|
||||
} else {
|
||||
unreserved_outgoing += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OpenCustomConnectionsNumbers {
|
||||
total,
|
||||
unreserved_incoming,
|
||||
unreserved_outgoing,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an address of the form `/ip4/x.x.x.x/tcp/x/p2p/xxxxxx`, and adds it
|
||||
/// to the given topology. Returns the corresponding peer ID and multiaddr.
|
||||
fn parse_and_add_to_topology(
|
||||
addr_str: &str,
|
||||
topology: &mut NetTopology
|
||||
) -> Result<(PeerId, Multiaddr), Error> {
|
||||
|
||||
let mut addr = addr_str.to_multiaddr().map_err(|_| ErrorKind::AddressParse)?;
|
||||
let who = match addr.pop() {
|
||||
Some(AddrComponent::P2P(key)) =>
|
||||
PeerId::from_multihash(key).map_err(|_| ErrorKind::AddressParse)?,
|
||||
_ => return Err(ErrorKind::AddressParse.into()),
|
||||
};
|
||||
|
||||
topology.add_bootstrap_addr(&who, addr.clone());
|
||||
Ok((who, addr))
|
||||
}
|
||||
|
||||
/// Obtains or generates the local private key using the configuration.
|
||||
fn obtain_private_key(config: &NetworkConfiguration)
|
||||
-> Result<secio::SecioKeyPair, IoError> {
|
||||
if let Some(ref secret) = config.use_secret {
|
||||
// Key was specified in the configuration.
|
||||
secio::SecioKeyPair::secp256k1_raw_key(&secret[..])
|
||||
.map_err(|err| IoError::new(IoErrorKind::InvalidData, err))
|
||||
|
||||
} else {
|
||||
if let Some(ref path) = config.net_config_path {
|
||||
fs::create_dir_all(Path::new(path))?;
|
||||
|
||||
// Try fetch the key from a the file containing th esecret.
|
||||
let secret_path = Path::new(path).join(SECRET_FILE);
|
||||
match load_private_key_from_file(&secret_path) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(err) => {
|
||||
// Failed to fetch existing file ; generate a new key
|
||||
trace!(target: "sub-libp2p",
|
||||
"Failed to load existing secret key file {:?}, generating new key ; err = {:?}",
|
||||
secret_path,
|
||||
err
|
||||
);
|
||||
Ok(gen_key_and_try_write_to_file(&secret_path))
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// No path in the configuration, nothing we can do except generate
|
||||
// a new key.
|
||||
let mut key: [u8; 32] = [0; 32];
|
||||
rand::rngs::EntropyRng::new().fill(&mut key);
|
||||
Ok(secio::SecioKeyPair::secp256k1_raw_key(&key)
|
||||
.expect("randomly-generated key with correct len should always be valid"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to load a private key from a file located at the given path.
|
||||
fn load_private_key_from_file<P>(path: P)
|
||||
-> Result<secio::SecioKeyPair, IoError>
|
||||
where P: AsRef<Path>
|
||||
{
|
||||
fs::File::open(path)
|
||||
.and_then(|mut file| {
|
||||
// We are in 2018 and there is still no method on `std::io::Read`
|
||||
// that directly returns a `Vec`.
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).map(|_| buf)
|
||||
})
|
||||
.and_then(|content|
|
||||
secio::SecioKeyPair::secp256k1_raw_key(&content)
|
||||
.map_err(|err| IoError::new(IoErrorKind::InvalidData, err))
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates a new secret key and tries to write it to the given file.
|
||||
/// Doesn't error if we couldn't open or write to the file.
|
||||
fn gen_key_and_try_write_to_file<P>(path: P) -> secio::SecioKeyPair
|
||||
where P: AsRef<Path> {
|
||||
let raw_key: [u8; 32] = rand::rngs::EntropyRng::new().gen();
|
||||
let secio_key = secio::SecioKeyPair::secp256k1_raw_key(&raw_key)
|
||||
.expect("randomly-generated key with correct len should always be valid");
|
||||
|
||||
// And store the newly-generated key in the file if possible.
|
||||
// Errors that happen while doing so are ignored.
|
||||
match open_priv_key_file(&path) {
|
||||
Ok(mut file) =>
|
||||
match file.write_all(&raw_key) {
|
||||
Ok(()) => (),
|
||||
Err(err) => warn!(target: "sub-libp2p",
|
||||
"Failed to write secret key in file {:?} ; err = {:?}",
|
||||
path.as_ref(),
|
||||
err
|
||||
),
|
||||
},
|
||||
Err(err) => warn!(target: "sub-libp2p",
|
||||
"Failed to store secret key in file {:?} ; err = {:?}",
|
||||
path.as_ref(),
|
||||
err
|
||||
),
|
||||
}
|
||||
|
||||
secio_key
|
||||
}
|
||||
|
||||
/// Opens a file containing a private key in write mode.
|
||||
#[cfg(unix)]
|
||||
fn open_priv_key_file<P>(path: P) -> Result<fs::File, IoError>
|
||||
where P: AsRef<Path>
|
||||
{
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.mode(256 | 128) // 0o600 in decimal
|
||||
.open(path)
|
||||
}
|
||||
/// Opens a file containing a private key in write mode.
|
||||
#[cfg(not(unix))]
|
||||
fn open_priv_key_file<P>(path: P) -> Result<fs::File, IoError>
|
||||
where P: AsRef<Path>
|
||||
{
|
||||
fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use libp2p::core::PublicKey;
|
||||
use network_state::NetworkState;
|
||||
|
||||
#[test]
|
||||
fn refuse_disabled_peer() {
|
||||
let state = NetworkState::new(&Default::default()).unwrap();
|
||||
let example_peer = PublicKey::Rsa(vec![1, 2, 3, 4]).into_peer_id();
|
||||
|
||||
let who = state.assign_node_index(&example_peer).unwrap();
|
||||
state.ban_peer(who, "Just a test");
|
||||
|
||||
assert!(state.assign_node_index(&example_peer).is_err());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use futures::{Async, future, Future, Poll, stream, Stream, sync::mpsc};
|
||||
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
|
||||
use std::marker::PhantomData;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio_timer::{self, Delay};
|
||||
|
||||
/// Builds the timeouts system.
|
||||
///
|
||||
/// The `timeouts_rx` should be a stream receiving newly-created timeout
|
||||
/// requests. Returns a stream that produces items as their timeout elapses.
|
||||
/// `T` can be anything you want, as it is transparently passed from the input
|
||||
/// to the output. Timeouts continue to fire forever, as there is no way to
|
||||
/// unregister them.
|
||||
pub fn build_timeouts_stream<'a, T>(
|
||||
timeouts_rx: mpsc::UnboundedReceiver<(Duration, T)>
|
||||
) -> Box<Stream<Item = T, Error = IoError> + 'a>
|
||||
where T: Clone + 'a {
|
||||
let next_timeout = next_in_timeouts_stream(timeouts_rx);
|
||||
|
||||
// The `unfold` function is essentially a loop turned into a stream. The
|
||||
// first parameter is the initial state, and the closure returns the new
|
||||
// state and an item.
|
||||
let stream = stream::unfold(vec![future::Either::A(next_timeout)], move |timeouts| {
|
||||
// `timeouts` is a `Vec` of futures that produce an `Out`.
|
||||
|
||||
// `select_ok` panics if `timeouts` is empty anyway.
|
||||
if timeouts.is_empty() {
|
||||
return None
|
||||
}
|
||||
|
||||
Some(future::select_ok(timeouts.into_iter())
|
||||
.and_then(move |(item, mut timeouts)|
|
||||
match item {
|
||||
Out::NewTimeout((Some((duration, item)), next_timeouts)) => {
|
||||
// Received a new timeout request on the channel.
|
||||
let next_timeout = next_in_timeouts_stream(next_timeouts);
|
||||
let timeout = Delay::new(Instant::now() + duration);
|
||||
let timeout = TimeoutWrapper(timeout, duration, Some(item), PhantomData);
|
||||
timeouts.push(future::Either::B(timeout));
|
||||
timeouts.push(future::Either::A(next_timeout));
|
||||
Ok((None, timeouts))
|
||||
},
|
||||
Out::NewTimeout((None, _)) =>
|
||||
// The channel has been closed.
|
||||
Ok((None, timeouts)),
|
||||
Out::Timeout(duration, item) => {
|
||||
// A timeout has happened.
|
||||
let returned = item.clone();
|
||||
let timeout = Delay::new(Instant::now() + duration);
|
||||
let timeout = TimeoutWrapper(timeout, duration, Some(item), PhantomData);
|
||||
timeouts.push(future::Either::B(timeout));
|
||||
Ok((Some(returned), timeouts))
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
}).filter_map(|item| item);
|
||||
|
||||
// Note that we use a `Box` in order to speed up compilation time.
|
||||
Box::new(stream) as Box<Stream<Item = _, Error = _>>
|
||||
}
|
||||
|
||||
/// Local enum representing the output of the selection.
|
||||
enum Out<A, B> {
|
||||
NewTimeout(A),
|
||||
Timeout(Duration, B),
|
||||
}
|
||||
|
||||
/// Convenience function that calls `.into_future()` on the timeouts stream,
|
||||
/// and applies some modifiers.
|
||||
/// This function is necessary. Otherwise if we copy-paste its content we run
|
||||
/// into errors because the type of the copy-pasted closures differs.
|
||||
fn next_in_timeouts_stream<T, B>(
|
||||
stream: mpsc::UnboundedReceiver<T>
|
||||
) -> impl Future<Item = Out<(Option<T>, mpsc::UnboundedReceiver<T>), B>, Error = IoError> {
|
||||
stream
|
||||
.into_future()
|
||||
.map(Out::NewTimeout)
|
||||
.map_err(|_| unreachable!("an UnboundedReceiver can never error"))
|
||||
}
|
||||
|
||||
/// Does the equivalent to `future.map(move |()| (duration, item)).map_err(|err| to_io_err(err))`.
|
||||
struct TimeoutWrapper<A, F, T>(F, Duration, Option<T>, PhantomData<A>);
|
||||
impl<A, F, T> Future for TimeoutWrapper<A, F, T>
|
||||
where F: Future<Item = (), Error = tokio_timer::Error> {
|
||||
type Item = Out<A, T>;
|
||||
type Error = IoError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.0.poll() {
|
||||
Ok(Async::Ready(())) => (),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => return Err(IoError::new(IoErrorKind::Other, err.to_string())),
|
||||
}
|
||||
|
||||
let out = Out::Timeout(self.1, self.2.take().expect("poll() called again after success"));
|
||||
Ok(Async::Ready(out))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,655 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.?
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
use parking_lot::Mutex;
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use serde_json;
|
||||
use std::{cmp, fs};
|
||||
use std::io::{Read, Cursor, Error as IoError, ErrorKind as IoErrorKind, Write, BufReader, BufWriter};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
/// For each address we're connected to, a period of this duration increases the score by 1.
|
||||
const CONNEC_DURATION_PER_SCORE: Duration = Duration::from_secs(10);
|
||||
/// Maximum value for the score.
|
||||
const MAX_SCORE: u32 = 100;
|
||||
/// When we successfully connect to a node, raises its score to the given minimum value.
|
||||
const CONNECTED_MINIMUM_SCORE: u32 = 20;
|
||||
/// Initial score that a node discovered through Kademlia receives, where we have a hint that the
|
||||
/// node is reachable.
|
||||
const KADEMLIA_DISCOVERY_INITIAL_SCORE_CONNECTABLE: u32 = 15;
|
||||
/// Initial score that a node discovered through Kademlia receives, without any hint.
|
||||
const KADEMLIA_DISCOVERY_INITIAL_SCORE: u32 = 10;
|
||||
/// Score adjustement when we fail to connect to an address.
|
||||
const SCORE_DIFF_ON_FAILED_TO_CONNECT: i32 = -1;
|
||||
/// Default time-to-live for addresses discovered through Kademlia.
|
||||
/// After this time has elapsed and no connection has succeeded, the address will be removed.
|
||||
const KADEMLIA_DISCOVERY_EXPIRATION: Duration = Duration::from_secs(2 * 3600);
|
||||
/// After a successful connection, the TTL is set to a minimum at this amount.
|
||||
const EXPIRATION_PUSH_BACK_CONNEC: Duration = Duration::from_secs(2 * 3600);
|
||||
/// Initial score that a bootstrap node receives when registered.
|
||||
const BOOTSTRAP_NODE_SCORE: u32 = 100;
|
||||
/// Time to live of a boostrap node. This only applies if you start the node later *without*
|
||||
/// that bootstrap node configured anymore.
|
||||
const BOOTSTRAP_NODE_EXPIRATION: Duration = Duration::from_secs(24 * 3600);
|
||||
/// The first time we fail to connect to an address, wait this duration before trying again.
|
||||
const FIRST_CONNECT_FAIL_BACKOFF: Duration = Duration::from_secs(2);
|
||||
/// Every time we fail to connect to an address, multiply the backoff by this constant.
|
||||
const FAIL_BACKOFF_MULTIPLIER: u32 = 2;
|
||||
/// We need a maximum value for the backoff, overwise we risk an overflow.
|
||||
const MAX_BACKOFF: Duration = Duration::from_secs(30 * 60);
|
||||
|
||||
// TODO: should be merged with the Kademlia k-buckets
|
||||
|
||||
/// Stores information about the topology of the network.
|
||||
#[derive(Debug)]
|
||||
pub struct NetTopology {
|
||||
store: FnvHashMap<PeerId, PeerInfo>,
|
||||
cache_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for NetTopology {
|
||||
#[inline]
|
||||
fn default() -> NetTopology {
|
||||
NetTopology::memory()
|
||||
}
|
||||
}
|
||||
|
||||
impl NetTopology {
|
||||
/// Initializes a new `NetTopology` that isn't tied to any file.
|
||||
///
|
||||
/// `flush_to_disk()` will be a no-op.
|
||||
#[inline]
|
||||
pub fn memory() -> NetTopology {
|
||||
NetTopology {
|
||||
store: Default::default(),
|
||||
cache_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a `NetTopology` that will use `path` as a cache.
|
||||
///
|
||||
/// This function tries to load a known topology from the file. If the file doesn't exist
|
||||
/// or contains garbage data, the execution still continues.
|
||||
///
|
||||
/// Calling `flush_to_disk()` in the future writes to the given path.
|
||||
pub fn from_file<P: AsRef<Path>>(path: P) -> NetTopology {
|
||||
let path = path.as_ref();
|
||||
debug!(target: "sub-libp2p", "Initializing peer store for JSON file {:?}", path);
|
||||
NetTopology {
|
||||
store: try_load(path),
|
||||
cache_path: Some(path.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the topology into the path passed to `from_file`.
|
||||
///
|
||||
/// No-op if the object was created with `memory()`.
|
||||
pub fn flush_to_disk(&self) -> Result<(), IoError> {
|
||||
let path = match self.cache_path {
|
||||
Some(ref p) => p,
|
||||
None => return Ok(())
|
||||
};
|
||||
|
||||
let file = fs::File::create(path)?;
|
||||
// TODO: the capacity of the BufWriter is kind of arbitrary ; decide better
|
||||
serialize(BufWriter::with_capacity(1024 * 1024, file), &self.store)
|
||||
}
|
||||
|
||||
/// Perform a cleanup pass, removing all obsolete addresses and peers.
|
||||
///
|
||||
/// This should be done from time to time.
|
||||
pub fn cleanup(&mut self) {
|
||||
let now_systime = SystemTime::now();
|
||||
self.store.retain(|_, peer| {
|
||||
peer.addrs.retain(|a| {
|
||||
a.expires > now_systime || a.is_connected()
|
||||
});
|
||||
!peer.addrs.is_empty()
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the known potential addresses of a peer, ordered by score.
|
||||
///
|
||||
/// The boolean associated to each address indicates whether we're connected to it.
|
||||
// TODO: filter out backed off ones?
|
||||
pub fn addrs_of_peer(&self, peer: &PeerId) -> impl Iterator<Item = (&Multiaddr, bool)> {
|
||||
let peer = if let Some(peer) = self.store.get(peer) {
|
||||
peer
|
||||
} else {
|
||||
// TODO: use an EitherIterator or something
|
||||
return Vec::new().into_iter();
|
||||
};
|
||||
|
||||
let now = SystemTime::now();
|
||||
let mut list = peer.addrs.iter().filter_map(move |addr| {
|
||||
let (score, connected) = addr.score_and_is_connected();
|
||||
if (addr.expires >= now && score > 0) || connected {
|
||||
Some((score, connected, &addr.addr))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
list.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
// TODO: meh, optimize
|
||||
let l = list.into_iter().map(|(_, connec, addr)| (addr, connec)).collect::<Vec<_>>();
|
||||
l.into_iter()
|
||||
}
|
||||
|
||||
/// Returns a list of all the known addresses of peers, ordered by the
|
||||
/// order in which we should attempt to connect to them.
|
||||
///
|
||||
/// Because of expiration and back-off mechanisms, this list can grow
|
||||
/// by itself over time. The `Instant` that is returned corresponds to
|
||||
/// the earlier known time when a new entry will be added automatically to
|
||||
/// the list.
|
||||
pub fn addrs_to_attempt(&self) -> (impl Iterator<Item = (&PeerId, &Multiaddr)>, Instant) {
|
||||
// TODO: optimize
|
||||
let now = Instant::now();
|
||||
let now_systime = SystemTime::now();
|
||||
let mut instant = now + Duration::from_secs(3600);
|
||||
let mut addrs_out = Vec::new();
|
||||
|
||||
for (peer, info) in &self.store {
|
||||
for addr in &info.addrs {
|
||||
let (score, is_connected) = addr.score_and_is_connected();
|
||||
if score == 0 || addr.expires < now_systime {
|
||||
continue;
|
||||
}
|
||||
if !is_connected && addr.back_off_until > now {
|
||||
instant = cmp::min(instant, addr.back_off_until);
|
||||
continue;
|
||||
}
|
||||
|
||||
addrs_out.push(((peer, &addr.addr), score));
|
||||
}
|
||||
}
|
||||
|
||||
addrs_out.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
(addrs_out.into_iter().map(|a| a.0), instant)
|
||||
}
|
||||
|
||||
/// Adds an address corresponding to a boostrap node.
|
||||
///
|
||||
/// We assume that the address is valid, so its score starts very high.
|
||||
pub fn add_bootstrap_addr(&mut self, peer: &PeerId, addr: Multiaddr) {
|
||||
let now_systime = SystemTime::now();
|
||||
let now = Instant::now();
|
||||
|
||||
let peer = peer_access(&mut self.store, peer);
|
||||
|
||||
let mut found = false;
|
||||
peer.addrs.retain(|a| {
|
||||
if a.expires < now_systime && !a.is_connected() {
|
||||
return false;
|
||||
}
|
||||
if a.addr == addr {
|
||||
found = true;
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
if !found {
|
||||
peer.addrs.push(Addr {
|
||||
addr,
|
||||
expires: now_systime + BOOTSTRAP_NODE_EXPIRATION,
|
||||
back_off_until: now,
|
||||
next_back_off: FIRST_CONNECT_FAIL_BACKOFF,
|
||||
score: Mutex::new(AddrScore {
|
||||
connected_since: None,
|
||||
score: BOOTSTRAP_NODE_SCORE,
|
||||
latest_score_update: now,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an address discovered through the Kademlia DHT.
|
||||
///
|
||||
/// This address is not necessarily valid and should expire after a TTL.
|
||||
///
|
||||
/// If `connectable` is true, that means we have some sort of hint that this node can
|
||||
/// be reached.
|
||||
pub fn add_kademlia_discovered_addr(
|
||||
&mut self,
|
||||
peer_id: &PeerId,
|
||||
addr: Multiaddr,
|
||||
connectable: bool
|
||||
) {
|
||||
let now_systime = SystemTime::now();
|
||||
let now = Instant::now();
|
||||
|
||||
let peer = peer_access(&mut self.store, peer_id);
|
||||
|
||||
let mut found = false;
|
||||
peer.addrs.retain(|a| {
|
||||
if a.expires < now_systime && !a.is_connected() {
|
||||
return false;
|
||||
}
|
||||
if a.addr == addr {
|
||||
found = true;
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
if !found {
|
||||
trace!(
|
||||
target: "sub-libp2p",
|
||||
"Peer store: adding address {} for {:?} (connectable hint: {:?})",
|
||||
addr,
|
||||
peer_id,
|
||||
connectable
|
||||
);
|
||||
|
||||
let initial_score = if connectable {
|
||||
KADEMLIA_DISCOVERY_INITIAL_SCORE_CONNECTABLE
|
||||
} else {
|
||||
KADEMLIA_DISCOVERY_INITIAL_SCORE
|
||||
};
|
||||
|
||||
peer.addrs.push(Addr {
|
||||
addr,
|
||||
expires: now_systime + KADEMLIA_DISCOVERY_EXPIRATION,
|
||||
back_off_until: now,
|
||||
next_back_off: FIRST_CONNECT_FAIL_BACKOFF,
|
||||
score: Mutex::new(AddrScore {
|
||||
connected_since: None,
|
||||
score: initial_score,
|
||||
latest_score_update: now,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates the peer store that we're connected to this given address.
|
||||
///
|
||||
/// This increases the score of the address that we connected to. Since we assume that only
|
||||
/// one peer can be reached with any specific address, we also remove all addresses from other
|
||||
/// peers that match the one we connected to.
|
||||
pub fn report_connected(&mut self, addr: &Multiaddr, peer: &PeerId) {
|
||||
let now = Instant::now();
|
||||
|
||||
// Just making sure that we have an entry for this peer in `store`, but don't use it.
|
||||
let _ = peer_access(&mut self.store, peer);
|
||||
|
||||
for (peer_in_store, info_in_store) in self.store.iter_mut() {
|
||||
if peer == peer_in_store {
|
||||
if let Some(addr) = info_in_store.addrs.iter_mut().find(|a| &a.addr == addr) {
|
||||
addr.connected_now(CONNECTED_MINIMUM_SCORE);
|
||||
addr.back_off_until = now;
|
||||
addr.next_back_off = FIRST_CONNECT_FAIL_BACKOFF;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: a else block would be better, but we get borrowck errors
|
||||
info_in_store.addrs.push(Addr {
|
||||
addr: addr.clone(),
|
||||
expires: SystemTime::now() + EXPIRATION_PUSH_BACK_CONNEC,
|
||||
back_off_until: now,
|
||||
next_back_off: FIRST_CONNECT_FAIL_BACKOFF,
|
||||
score: Mutex::new(AddrScore {
|
||||
connected_since: Some(now),
|
||||
latest_score_update: now,
|
||||
score: CONNECTED_MINIMUM_SCORE,
|
||||
}),
|
||||
});
|
||||
|
||||
} else {
|
||||
// Set the score to 0 for any address that matches the one we connected to.
|
||||
for addr_in_store in &mut info_in_store.addrs {
|
||||
if &addr_in_store.addr == addr {
|
||||
addr_in_store.adjust_score(-(MAX_SCORE as i32));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates the peer store that we're disconnected from an address.
|
||||
///
|
||||
/// There's no need to indicate a peer ID, as each address can only have one peer ID.
|
||||
/// If we were indeed connected to this addr, then we can find out which peer ID it is.
|
||||
pub fn report_disconnected(&mut self, addr: &Multiaddr, reason: DisconnectReason) {
|
||||
let score_diff = match reason {
|
||||
DisconnectReason::ClosedGracefully => -1,
|
||||
};
|
||||
|
||||
for info in self.store.values_mut() {
|
||||
for a in info.addrs.iter_mut() {
|
||||
if &a.addr == addr {
|
||||
a.disconnected_now(score_diff);
|
||||
a.back_off_until = Instant::now() + a.next_back_off;
|
||||
a.next_back_off = cmp::min(a.next_back_off * FAIL_BACKOFF_MULTIPLIER, MAX_BACKOFF);
|
||||
let expires_push_back = SystemTime::now() + EXPIRATION_PUSH_BACK_CONNEC;
|
||||
if a.expires < expires_push_back {
|
||||
a.expires = expires_push_back;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates the peer store that we failed to connect to an address.
|
||||
///
|
||||
/// We don't care about which peer is supposed to be behind that address. If we failed to dial
|
||||
/// it for a specific peer, we would also fail to dial it for all peers that have this
|
||||
/// address.
|
||||
pub fn report_failed_to_connect(&mut self, addr: &Multiaddr) {
|
||||
for info in self.store.values_mut() {
|
||||
for a in info.addrs.iter_mut() {
|
||||
if &a.addr == addr {
|
||||
a.adjust_score(SCORE_DIFF_ON_FAILED_TO_CONNECT);
|
||||
a.back_off_until = Instant::now() + a.next_back_off;
|
||||
a.next_back_off = cmp::min(a.next_back_off * FAIL_BACKOFF_MULTIPLIER, MAX_BACKOFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reason why we disconnected from a peer.
|
||||
pub enum DisconnectReason {
|
||||
/// The disconnection was graceful.
|
||||
ClosedGracefully,
|
||||
}
|
||||
|
||||
fn peer_access<'a>(store: &'a mut FnvHashMap<PeerId, PeerInfo>, peer: &PeerId) -> &'a mut PeerInfo {
|
||||
// TODO: should be optimizable if HashMap gets a better API
|
||||
store.entry(peer.clone()).or_insert_with(Default::default)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct PeerInfo {
|
||||
/// Addresses of that peer.
|
||||
addrs: Vec<Addr>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Addr {
|
||||
/// The multiaddress.
|
||||
addr: Multiaddr,
|
||||
/// When the address expires.
|
||||
expires: SystemTime,
|
||||
next_back_off: Duration,
|
||||
/// Don't try to connect to this node until `Instant`.
|
||||
back_off_until: Instant,
|
||||
score: Mutex<AddrScore>,
|
||||
}
|
||||
|
||||
impl Clone for Addr {
|
||||
fn clone(&self) -> Addr {
|
||||
Addr {
|
||||
addr: self.addr.clone(),
|
||||
expires: self.expires.clone(),
|
||||
next_back_off: self.next_back_off.clone(),
|
||||
back_off_until: self.back_off_until.clone(),
|
||||
score: Mutex::new(self.score.lock().clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct AddrScore {
|
||||
/// If connected, contains the moment when we connected. `None` if we're not connected.
|
||||
connected_since: Option<Instant>,
|
||||
/// Score of this address. Potentially needs to be updated based on `latest_score_update`.
|
||||
score: u32,
|
||||
/// When we last updated the score.
|
||||
latest_score_update: Instant,
|
||||
}
|
||||
|
||||
impl Addr {
|
||||
/// Sets the addr to connected. If the score is lower than the given value, raises it to this
|
||||
/// value.
|
||||
fn connected_now(&self, raise_to_min: u32) {
|
||||
let mut score = self.score.lock();
|
||||
let now = Instant::now();
|
||||
Addr::flush(&mut score, now);
|
||||
score.connected_since = Some(now);
|
||||
if score.score < raise_to_min {
|
||||
score.score = raise_to_min;
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a modification to the score.
|
||||
fn adjust_score(&self, score_diff: i32) {
|
||||
let mut score = self.score.lock();
|
||||
Addr::flush(&mut score, Instant::now());
|
||||
if score_diff >= 0 {
|
||||
score.score = cmp::min(MAX_SCORE, score.score + score_diff as u32);
|
||||
} else {
|
||||
score.score = score.score.saturating_sub(-score_diff as u32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the addr to disconnected and applies a modification to the score.
|
||||
fn disconnected_now(&self, score_diff: i32) {
|
||||
let mut score = self.score.lock();
|
||||
Addr::flush(&mut score, Instant::now());
|
||||
score.connected_since = None;
|
||||
if score_diff >= 0 {
|
||||
score.score = cmp::min(MAX_SCORE, score.score + score_diff as u32);
|
||||
} else {
|
||||
score.score = score.score.saturating_sub(-score_diff as u32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if we are connected to this addr.
|
||||
fn is_connected(&self) -> bool {
|
||||
let score = self.score.lock();
|
||||
score.connected_since.is_some()
|
||||
}
|
||||
|
||||
/// Returns the score, and true if we are connected to this addr.
|
||||
fn score_and_is_connected(&self) -> (u32, bool) {
|
||||
let mut score = self.score.lock();
|
||||
Addr::flush(&mut score, Instant::now());
|
||||
let is_connected = score.connected_since.is_some();
|
||||
(score.score, is_connected)
|
||||
}
|
||||
|
||||
/// Updates `score` and `latest_score_update`, and returns the score.
|
||||
fn score(&self) -> u32 {
|
||||
let mut score = self.score.lock();
|
||||
Addr::flush(&mut score, Instant::now());
|
||||
score.score
|
||||
}
|
||||
|
||||
fn flush(score: &mut AddrScore, now: Instant) {
|
||||
if let Some(connected_since) = score.connected_since {
|
||||
let potential_score: u32 = div_dur_with_dur(now - connected_since, CONNEC_DURATION_PER_SCORE);
|
||||
// We flush when we connect to an address.
|
||||
debug_assert!(score.latest_score_update >= connected_since);
|
||||
let effective_score: u32 =
|
||||
div_dur_with_dur(score.latest_score_update - connected_since, CONNEC_DURATION_PER_SCORE);
|
||||
let to_add = potential_score.saturating_sub(effective_score);
|
||||
score.score = cmp::min(MAX_SCORE, score.score + to_add);
|
||||
}
|
||||
|
||||
score.latest_score_update = now;
|
||||
}
|
||||
}
|
||||
|
||||
/// Divides a `Duration` with a `Duration`. This exists in the stdlib but isn't stable yet.
|
||||
// TODO: remove this function once stable
|
||||
fn div_dur_with_dur(a: Duration, b: Duration) -> u32 {
|
||||
let a_ms = a.as_secs() * 1_000_000 + (a.subsec_nanos() / 1_000) as u64;
|
||||
let b_ms = b.as_secs() * 1_000_000 + (b.subsec_nanos() / 1_000) as u64;
|
||||
(a_ms / b_ms) as u32
|
||||
}
|
||||
|
||||
/// Serialized version of a `PeerInfo`. Suitable for storage in the cache file.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct SerializedPeerInfo {
|
||||
addrs: Vec<SerializedAddr>,
|
||||
}
|
||||
|
||||
/// Serialized version of an `Addr`. Suitable for storage in the cache file.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct SerializedAddr {
|
||||
addr: String,
|
||||
expires: SystemTime,
|
||||
score: u32,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Addr> for SerializedAddr {
|
||||
fn from(addr: &'a Addr) -> SerializedAddr {
|
||||
SerializedAddr {
|
||||
addr: addr.addr.to_string(),
|
||||
expires: addr.expires,
|
||||
score: addr.score(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to load storage from a file.
|
||||
/// Deletes the file and returns an empty map if the file doesn't exist, cannot be opened
|
||||
/// or is corrupted.
|
||||
fn try_load(path: impl AsRef<Path>) -> FnvHashMap<PeerId, PeerInfo> {
|
||||
let path = path.as_ref();
|
||||
if !path.exists() {
|
||||
debug!(target: "sub-libp2p", "Peer storage file {:?} doesn't exist", path);
|
||||
return Default::default()
|
||||
}
|
||||
|
||||
let mut file = match fs::File::open(path) {
|
||||
// TODO: the capacity of the BufReader is kind of arbitrary ; decide better
|
||||
Ok(f) => BufReader::with_capacity(1024 * 1024, f),
|
||||
Err(err) => {
|
||||
warn!(target: "sub-libp2p", "Failed to open peer storage file: {:?}", err);
|
||||
info!(target: "sub-libp2p", "Deleting peer storage file {:?}", path);
|
||||
let _ = fs::remove_file(path);
|
||||
return Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
// We want to support empty files (and treat them as an empty recordset). Unfortunately
|
||||
// `serde_json` will always produce an error if we do this ("unexpected EOF at line 0
|
||||
// column 0"). Therefore we start by reading one byte from the file in order to check
|
||||
// for EOF.
|
||||
|
||||
let mut first_byte = [0];
|
||||
let num_read = match file.read(&mut first_byte) {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
// TODO: DRY
|
||||
warn!(target: "sub-libp2p", "Failed to read peer storage file: {:?}", err);
|
||||
info!(target: "sub-libp2p", "Deleting peer storage file {:?}", path);
|
||||
let _ = fs::remove_file(path);
|
||||
return Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
if num_read == 0 {
|
||||
// File is empty.
|
||||
debug!(target: "sub-libp2p", "Peer storage file {:?} is empty", path);
|
||||
Default::default()
|
||||
|
||||
} else {
|
||||
let data = Cursor::new(first_byte).chain(file);
|
||||
match serde_json::from_reader::<_, serde_json::Value>(data) {
|
||||
Ok(serde_json::Value::Null) => Default::default(),
|
||||
Ok(serde_json::Value::Object(map)) => deserialize_tolerant(map.into_iter()),
|
||||
Ok(_) | Err(_) => {
|
||||
// The `Ok(_)` case means that the file doesn't contain a map.
|
||||
let _ = fs::remove_file(path);
|
||||
Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to turn a deserialized version of the storage into the final version.
|
||||
///
|
||||
/// Skips entries that are invalid.
|
||||
fn deserialize_tolerant(
|
||||
iter: impl Iterator<Item = (String, serde_json::Value)>
|
||||
) -> FnvHashMap<PeerId, PeerInfo> {
|
||||
let now = Instant::now();
|
||||
let now_systime = SystemTime::now();
|
||||
|
||||
let mut out = FnvHashMap::default();
|
||||
for (peer, info) in iter {
|
||||
let peer: PeerId = match peer.parse() {
|
||||
Ok(p) => p,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let info: SerializedPeerInfo = match serde_json::from_value(info) {
|
||||
Ok(i) => i,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let mut addrs = Vec::with_capacity(info.addrs.len());
|
||||
for addr in info.addrs {
|
||||
let multiaddr = match addr.addr.parse() {
|
||||
Ok(a) => a,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if addr.expires < now_systime {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs.push(Addr {
|
||||
addr: multiaddr,
|
||||
expires: addr.expires,
|
||||
next_back_off: FIRST_CONNECT_FAIL_BACKOFF,
|
||||
back_off_until: now,
|
||||
score: Mutex::new(AddrScore {
|
||||
connected_since: None,
|
||||
score: addr.score,
|
||||
latest_score_update: now,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if addrs.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
out.insert(peer, PeerInfo { addrs });
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// Attempts to turn a deserialized version of the storage into the final version.
|
||||
///
|
||||
/// Skips entries that are invalid or expired.
|
||||
fn serialize<W: Write>(out: W, map: &FnvHashMap<PeerId, PeerInfo>) -> Result<(), IoError> {
|
||||
let now = SystemTime::now();
|
||||
let array: FnvHashMap<_, _> = map.iter().filter_map(|(peer, info)| {
|
||||
if info.addrs.is_empty() {
|
||||
return None
|
||||
}
|
||||
|
||||
let peer = peer.to_base58();
|
||||
let info = SerializedPeerInfo {
|
||||
addrs: info.addrs.iter()
|
||||
.filter(|a| a.expires > now || a.is_connected())
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Some((peer, info))
|
||||
}).collect();
|
||||
|
||||
serde_json::to_writer_pretty(out, &array)
|
||||
.map_err(|err| IoError::new(IoErrorKind::Other, err))
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt;
|
||||
use std::cmp::Ordering;
|
||||
use std::iter;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str;
|
||||
use std::time::Duration;
|
||||
use io::TimerToken;
|
||||
use libp2p::{multiaddr::AddrComponent, Multiaddr};
|
||||
use error::Error;
|
||||
use ethkey::Secret;
|
||||
use ethereum_types::H512;
|
||||
|
||||
/// Protocol handler level packet id
|
||||
pub type PacketId = u8;
|
||||
/// Protocol / handler id
|
||||
pub type ProtocolId = [u8; 3];
|
||||
|
||||
/// Node public key
|
||||
pub type NodeId = H512;
|
||||
|
||||
/// Local (temporary) peer session ID.
|
||||
pub type NodeIndex = usize;
|
||||
|
||||
/// Shared session information
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionInfo {
|
||||
/// Peer public key
|
||||
pub id: Option<NodeId>,
|
||||
/// Peer client ID
|
||||
pub client_version: String,
|
||||
/// Peer RLPx protocol version
|
||||
pub protocol_version: u32,
|
||||
/// Session protocol capabilities
|
||||
pub capabilities: Vec<SessionCapabilityInfo>,
|
||||
/// Peer protocol capabilities
|
||||
pub peer_capabilities: Vec<PeerCapabilityInfo>,
|
||||
/// Peer ping delay
|
||||
pub ping: Option<Duration>,
|
||||
/// True if this session was originated by us.
|
||||
pub originated: bool,
|
||||
/// Remote endpoint address of the session
|
||||
pub remote_address: String,
|
||||
/// Local endpoint address of the session
|
||||
pub local_address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PeerCapabilityInfo {
|
||||
pub protocol: ProtocolId,
|
||||
pub version: u8,
|
||||
}
|
||||
|
||||
impl ToString for PeerCapabilityInfo {
|
||||
fn to_string(&self) -> String {
|
||||
format!("{}/{}", str::from_utf8(&self.protocol[..]).unwrap_or("???"), self.version)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SessionCapabilityInfo {
|
||||
pub protocol: [u8; 3],
|
||||
pub version: u8,
|
||||
pub packet_count: u8,
|
||||
pub id_offset: u8,
|
||||
}
|
||||
|
||||
impl PartialOrd for SessionCapabilityInfo {
|
||||
fn partial_cmp(&self, other: &SessionCapabilityInfo) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for SessionCapabilityInfo {
|
||||
fn cmp(&self, b: &SessionCapabilityInfo) -> Ordering {
|
||||
// By protocol id first
|
||||
if self.protocol != b.protocol {
|
||||
return self.protocol.cmp(&b.protocol);
|
||||
}
|
||||
// By version
|
||||
self.version.cmp(&b.version)
|
||||
}
|
||||
}
|
||||
|
||||
/// Network service configuration
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct NetworkConfiguration {
|
||||
/// Directory path to store general network configuration. None means nothing will be saved
|
||||
pub config_path: Option<String>,
|
||||
/// Directory path to store network-specific configuration. None means nothing will be saved
|
||||
pub net_config_path: Option<String>,
|
||||
/// Multiaddresses to listen for incoming connections.
|
||||
pub listen_addresses: Vec<Multiaddr>,
|
||||
/// Multiaddresses to advertise. Detected automatically if empty.
|
||||
pub public_addresses: Vec<Multiaddr>,
|
||||
/// List of initial node addresses
|
||||
pub boot_nodes: Vec<String>,
|
||||
/// Use provided node key instead of default
|
||||
pub use_secret: Option<Secret>,
|
||||
/// Minimum number of connected peers to maintain
|
||||
pub min_peers: u32,
|
||||
/// Maximum allowed number of peers
|
||||
pub max_peers: u32,
|
||||
/// List of reserved node addresses.
|
||||
pub reserved_nodes: Vec<String>,
|
||||
/// The non-reserved peer mode.
|
||||
pub non_reserved_mode: NonReservedPeerMode,
|
||||
/// Client identifier
|
||||
pub client_version: String,
|
||||
}
|
||||
|
||||
impl Default for NetworkConfiguration {
|
||||
fn default() -> Self {
|
||||
NetworkConfiguration::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkConfiguration {
|
||||
/// Create a new instance of default settings.
|
||||
pub fn new() -> Self {
|
||||
NetworkConfiguration {
|
||||
config_path: None,
|
||||
net_config_path: None,
|
||||
listen_addresses: vec![
|
||||
iter::once(AddrComponent::IP4(Ipv4Addr::new(0, 0, 0, 0)))
|
||||
.chain(iter::once(AddrComponent::TCP(30333)))
|
||||
.collect()
|
||||
],
|
||||
public_addresses: Vec::new(),
|
||||
boot_nodes: Vec::new(),
|
||||
use_secret: None,
|
||||
min_peers: 25,
|
||||
max_peers: 50,
|
||||
reserved_nodes: Vec::new(),
|
||||
non_reserved_mode: NonReservedPeerMode::Accept,
|
||||
client_version: "Parity-network".into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new default configuration for localhost-only connection with random port (useful for testing)
|
||||
pub fn new_local() -> NetworkConfiguration {
|
||||
let mut config = NetworkConfiguration::new();
|
||||
config.listen_addresses = vec![
|
||||
iter::once(AddrComponent::IP4(Ipv4Addr::new(127, 0, 0, 1)))
|
||||
.chain(iter::once(AddrComponent::TCP(0)))
|
||||
.collect()
|
||||
];
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
/// The severity of misbehaviour of a peer that is reported.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Severity<'a> {
|
||||
/// Peer is timing out. Could be bad connectivity of overload of work on either of our sides.
|
||||
Timeout,
|
||||
/// Peer has been notably useless. E.g. unable to answer a request that we might reasonably consider
|
||||
/// it could answer.
|
||||
Useless(&'a str),
|
||||
/// Peer has behaved in an invalid manner. This doesn't necessarily need to be Byzantine, but peer
|
||||
/// must have taken concrete action in order to behave in such a way which is wantanly invalid.
|
||||
Bad(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Severity<'a> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Severity::Timeout => write!(fmt, "Timeout"),
|
||||
Severity::Useless(r) => write!(fmt, "Useless ({})", r),
|
||||
Severity::Bad(r) => write!(fmt, "Bad ({})", r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// IO access point. This is passed to all IO handlers and provides an interface to the IO subsystem.
|
||||
pub trait NetworkContext {
|
||||
/// Send a packet over the network to another peer.
|
||||
fn send(&self, peer: NodeIndex, packet_id: PacketId, data: Vec<u8>);
|
||||
|
||||
/// Send a packet over the network to another peer using specified protocol.
|
||||
fn send_protocol(&self, protocol: ProtocolId, peer: NodeIndex, packet_id: PacketId, data: Vec<u8>);
|
||||
|
||||
/// Respond to a current network message. Panics if no there is no packet in the context. If the session is expired returns nothing.
|
||||
fn respond(&self, packet_id: PacketId, data: Vec<u8>);
|
||||
|
||||
/// Report peer. Depending on the report, peer may be disconnected and possibly banned.
|
||||
fn report_peer(&self, peer: NodeIndex, reason: Severity);
|
||||
|
||||
/// Check if the session is still active.
|
||||
fn is_expired(&self) -> bool;
|
||||
|
||||
/// Register a new IO timer. 'IoHandler::timeout' will be called with the token.
|
||||
fn register_timer(&self, token: TimerToken, delay: Duration) -> Result<(), Error>;
|
||||
|
||||
/// Returns peer identification string
|
||||
fn peer_client_version(&self, peer: NodeIndex) -> String;
|
||||
|
||||
/// Returns information on p2p session
|
||||
fn session_info(&self, peer: NodeIndex) -> Option<SessionInfo>;
|
||||
|
||||
/// Returns max version for a given protocol.
|
||||
fn protocol_version(&self, protocol: ProtocolId, peer: NodeIndex) -> Option<u8>;
|
||||
|
||||
/// Returns this object's subprotocol name.
|
||||
fn subprotocol_name(&self) -> ProtocolId;
|
||||
}
|
||||
|
||||
impl<'a, T> NetworkContext for &'a T where T: ?Sized + NetworkContext {
|
||||
fn send(&self, peer: NodeIndex, packet_id: PacketId, data: Vec<u8>) {
|
||||
(**self).send(peer, packet_id, data)
|
||||
}
|
||||
|
||||
fn send_protocol(&self, protocol: ProtocolId, peer: NodeIndex, packet_id: PacketId, data: Vec<u8>) {
|
||||
(**self).send_protocol(protocol, peer, packet_id, data)
|
||||
}
|
||||
|
||||
fn respond(&self, packet_id: PacketId, data: Vec<u8>) {
|
||||
(**self).respond(packet_id, data)
|
||||
}
|
||||
|
||||
fn report_peer(&self, peer: NodeIndex, reason: Severity) {
|
||||
(**self).report_peer(peer, reason)
|
||||
}
|
||||
|
||||
fn is_expired(&self) -> bool {
|
||||
(**self).is_expired()
|
||||
}
|
||||
|
||||
fn register_timer(&self, token: TimerToken, delay: Duration) -> Result<(), Error> {
|
||||
(**self).register_timer(token, delay)
|
||||
}
|
||||
|
||||
fn peer_client_version(&self, peer: NodeIndex) -> String {
|
||||
(**self).peer_client_version(peer)
|
||||
}
|
||||
|
||||
fn session_info(&self, peer: NodeIndex) -> Option<SessionInfo> {
|
||||
(**self).session_info(peer)
|
||||
}
|
||||
|
||||
fn protocol_version(&self, protocol: ProtocolId, peer: NodeIndex) -> Option<u8> {
|
||||
(**self).protocol_version(protocol, peer)
|
||||
}
|
||||
|
||||
fn subprotocol_name(&self) -> ProtocolId {
|
||||
(**self).subprotocol_name()
|
||||
}
|
||||
}
|
||||
|
||||
/// Network IO protocol handler. This needs to be implemented for each new subprotocol.
|
||||
/// All the handler function are called from within IO event loop.
|
||||
/// `Message` is the type for message data.
|
||||
pub trait NetworkProtocolHandler: Sync + Send {
|
||||
/// Initialize the handler
|
||||
fn initialize(&self, _io: &NetworkContext) {}
|
||||
/// Called when new network packet received.
|
||||
fn read(&self, io: &NetworkContext, peer: &NodeIndex, packet_id: u8, data: &[u8]);
|
||||
/// Called when new peer is connected. Only called when peer supports the same protocol.
|
||||
fn connected(&self, io: &NetworkContext, peer: &NodeIndex);
|
||||
/// Called when a previously connected peer disconnects.
|
||||
fn disconnected(&self, io: &NetworkContext, peer: &NodeIndex);
|
||||
/// Timer function called after a timeout created with `NetworkContext::timeout`.
|
||||
fn timeout(&self, _io: &NetworkContext, _timer: TimerToken) {}
|
||||
}
|
||||
|
||||
/// Non-reserved peer modes.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum NonReservedPeerMode {
|
||||
/// Accept them. This is the default.
|
||||
Accept,
|
||||
/// Deny them.
|
||||
Deny,
|
||||
}
|
||||
|
||||
impl NonReservedPeerMode {
|
||||
/// Attempt to parse the peer mode from a string.
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"accept" => Some(NonReservedPeerMode::Accept),
|
||||
"deny" => Some(NonReservedPeerMode::Deny),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use libp2p::{self, PeerId, Transport, mplex, secio, yamux};
|
||||
use libp2p::core::{either, upgrade, transport::BoxedMuxed};
|
||||
use libp2p::transport_timeout::TransportTimeout;
|
||||
use std::time::Duration;
|
||||
use std::usize;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
/// Builds the transport that serves as a common ground for all connections.
|
||||
pub fn build_transport(
|
||||
local_private_key: secio::SecioKeyPair
|
||||
) -> BoxedMuxed<(PeerId, impl AsyncRead + AsyncWrite)> {
|
||||
let mut mplex_config = mplex::MplexConfig::new();
|
||||
mplex_config.max_buffer_len_behaviour(mplex::MaxBufferBehaviour::Block);
|
||||
mplex_config.max_buffer_len(usize::MAX);
|
||||
|
||||
let base = libp2p::CommonTransport::new()
|
||||
.with_upgrade(secio::SecioConfig {
|
||||
key: local_private_key,
|
||||
})
|
||||
.and_then(move |out, endpoint, client_addr| {
|
||||
let upgrade = upgrade::or(
|
||||
upgrade::map(mplex_config, either::EitherOutput::First),
|
||||
upgrade::map(yamux::Config::default(), either::EitherOutput::Second),
|
||||
);
|
||||
let key = out.remote_key;
|
||||
let upgrade = upgrade::map(upgrade, move |muxer| (key, muxer));
|
||||
upgrade::apply(out.stream, upgrade, endpoint, client_addr)
|
||||
})
|
||||
.into_connection_reuse()
|
||||
.map(|(key, substream), _| (key.into_peer_id(), substream));
|
||||
|
||||
TransportTimeout::new(base, Duration::from_secs(20))
|
||||
.boxed_muxed()
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity 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 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. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate parking_lot;
|
||||
extern crate parity_bytes;
|
||||
extern crate ethcore_io as io;
|
||||
extern crate ethcore_logger;
|
||||
extern crate substrate_network_libp2p;
|
||||
extern crate ethkey;
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::*;
|
||||
use parking_lot::Mutex;
|
||||
use parity_bytes::Bytes;
|
||||
use substrate_network_libp2p::*;
|
||||
use ethkey::{Random, Generator};
|
||||
use io::TimerToken;
|
||||
|
||||
pub struct TestProtocol {
|
||||
drop_session: bool,
|
||||
pub packet: Mutex<Bytes>,
|
||||
pub got_timeout: AtomicBool,
|
||||
pub got_disconnect: AtomicBool,
|
||||
}
|
||||
|
||||
impl TestProtocol {
|
||||
pub fn new(drop_session: bool) -> Self {
|
||||
TestProtocol {
|
||||
packet: Mutex::new(Vec::new()),
|
||||
got_timeout: AtomicBool::new(false),
|
||||
got_disconnect: AtomicBool::new(false),
|
||||
drop_session: drop_session,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn got_packet(&self) -> bool {
|
||||
self.packet.lock()[..] == b"hello"[..]
|
||||
}
|
||||
|
||||
pub fn got_timeout(&self) -> bool {
|
||||
self.got_timeout.load(AtomicOrdering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn got_disconnect(&self) -> bool {
|
||||
self.got_disconnect.load(AtomicOrdering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkProtocolHandler for TestProtocol {
|
||||
fn initialize(&self, io: &NetworkContext) {
|
||||
io.register_timer(0, Duration::from_millis(10)).unwrap();
|
||||
}
|
||||
|
||||
fn read(&self, _io: &NetworkContext, _peer: &NodeIndex, packet_id: u8, data: &[u8]) {
|
||||
assert_eq!(packet_id, 33);
|
||||
self.packet.lock().extend(data);
|
||||
}
|
||||
|
||||
fn connected(&self, io: &NetworkContext, peer: &NodeIndex) {
|
||||
if self.drop_session {
|
||||
io.report_peer(*peer, Severity::Bad("We are evil and just want to drop"))
|
||||
} else {
|
||||
io.respond(33, "hello".to_owned().into_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnected(&self, _io: &NetworkContext, _peer: &NodeIndex) {
|
||||
self.got_disconnect.store(true, AtomicOrdering::Relaxed);
|
||||
}
|
||||
|
||||
/// Timer function called after a timeout created with `NetworkContext::timeout`.
|
||||
fn timeout(&self, _io: &NetworkContext, timer: TimerToken) {
|
||||
assert_eq!(timer, 0);
|
||||
self.got_timeout.store(true, AtomicOrdering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn net_service() {
|
||||
let _service = NetworkService::new(
|
||||
NetworkConfiguration::new_local(),
|
||||
vec![(Arc::new(TestProtocol::new(false)), *b"myp", &[(1u8, 1)])]
|
||||
).expect("Error creating network service");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // TODO: how is this test even supposed to work?
|
||||
fn net_disconnect() {
|
||||
let key1 = Random.generate().unwrap();
|
||||
let mut config1 = NetworkConfiguration::new_local();
|
||||
config1.use_secret = Some(key1.secret().clone());
|
||||
config1.boot_nodes = vec![ ];
|
||||
let handler1 = Arc::new(TestProtocol::new(false));
|
||||
let service1 = NetworkService::new(config1, vec![(handler1.clone(), *b"tst", &[(42u8, 1), (43u8, 1)])]).unwrap();
|
||||
let mut config2 = NetworkConfiguration::new_local();
|
||||
config2.boot_nodes = vec![ service1.external_url().unwrap() ];
|
||||
let handler2 = Arc::new(TestProtocol::new(true));
|
||||
let _service2 = NetworkService::new(config2, vec![(handler2.clone(), *b"tst", &[(42u8, 1), (43u8, 1)])]).unwrap();
|
||||
while !(handler1.got_disconnect() && handler2.got_disconnect()) {
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
assert!(handler1.got_disconnect());
|
||||
assert!(handler2.got_disconnect());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn net_timeout() {
|
||||
let config = NetworkConfiguration::new_local();
|
||||
let handler = Arc::new(TestProtocol::new(false));
|
||||
let _service = NetworkService::new(config, vec![(handler.clone(), *b"tst", &[(42u8, 1), (43u8, 1)])]).unwrap();
|
||||
while !handler.got_timeout() {
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
description = "Polkadot network protocol"
|
||||
name = "substrate-network"
|
||||
version = "0.1.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[lib]
|
||||
|
||||
[dependencies]
|
||||
log = "0.3"
|
||||
parking_lot = "0.4"
|
||||
error-chain = "0.12"
|
||||
bitflags = "1.0"
|
||||
futures = "0.1.17"
|
||||
linked-hash-map = "0.5"
|
||||
rustc-hex = "1.0"
|
||||
ethcore-io = { git = "https://github.com/paritytech/parity.git" }
|
||||
substrate-primitives = { path = "../../core/primitives" }
|
||||
substrate-client = { path = "../../core/client" }
|
||||
sr-primitives = { path = "../../core/sr-primitives" }
|
||||
parity-codec = { path = "../../codec" }
|
||||
parity-codec-derive = { path = "../../codec/derive" }
|
||||
substrate-network-libp2p = { path = "../../core/network-libp2p" }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.4"
|
||||
substrate-keyring = { path = "../../core/keyring" }
|
||||
substrate-test-client = { path = "../../core/test-client" }
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
= Network
|
||||
|
||||
.Summary
|
||||
[source, toml]
|
||||
----
|
||||
include::Cargo.toml[lines=2..5]
|
||||
----
|
||||
|
||||
.Description
|
||||
----
|
||||
include::src/lib.rs[tag=description]
|
||||
----
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::mem;
|
||||
use std::cmp;
|
||||
use std::ops::Range;
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::collections::hash_map::Entry;
|
||||
use network_libp2p::NodeIndex;
|
||||
use runtime_primitives::traits::{Block as BlockT, NumberFor, As};
|
||||
use message;
|
||||
|
||||
const MAX_PARALLEL_DOWNLOADS: u32 = 1;
|
||||
|
||||
/// Block data with origin.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BlockData<B: BlockT> {
|
||||
pub block: message::BlockData<B>,
|
||||
pub origin: NodeIndex,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum BlockRangeState<B: BlockT> {
|
||||
Downloading {
|
||||
len: NumberFor<B>,
|
||||
downloading: u32,
|
||||
},
|
||||
Complete(Vec<BlockData<B>>),
|
||||
}
|
||||
|
||||
impl<B: BlockT> BlockRangeState<B> {
|
||||
pub fn len(&self) -> NumberFor<B> {
|
||||
match *self {
|
||||
BlockRangeState::Downloading { len, .. } => len,
|
||||
BlockRangeState::Complete(ref blocks) => As::sa(blocks.len() as u64),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of blocks being downloaded.
|
||||
#[derive(Default)]
|
||||
pub struct BlockCollection<B: BlockT> {
|
||||
/// Downloaded blocks.
|
||||
blocks: BTreeMap<NumberFor<B>, BlockRangeState<B>>,
|
||||
peer_requests: HashMap<NodeIndex, NumberFor<B>>,
|
||||
}
|
||||
|
||||
impl<B: BlockT> BlockCollection<B> {
|
||||
/// Create a new instance.
|
||||
pub fn new() -> Self {
|
||||
BlockCollection {
|
||||
blocks: BTreeMap::new(),
|
||||
peer_requests: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear everything.
|
||||
pub fn clear(&mut self) {
|
||||
self.blocks.clear();
|
||||
self.peer_requests.clear();
|
||||
}
|
||||
|
||||
/// Insert a set of blocks into collection.
|
||||
pub fn insert(&mut self, start: NumberFor<B>, blocks: Vec<message::BlockData<B>>, who: NodeIndex) {
|
||||
if blocks.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.blocks.get(&start) {
|
||||
Some(&BlockRangeState::Downloading { .. }) => {
|
||||
trace!(target: "sync", "Ignored block data still marked as being downloaded: {}", start);
|
||||
debug_assert!(false);
|
||||
return;
|
||||
},
|
||||
Some(&BlockRangeState::Complete(ref existing)) if existing.len() >= blocks.len() => {
|
||||
trace!(target: "sync", "Ignored block data already downloaded: {}", start);
|
||||
return;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.blocks.insert(start, BlockRangeState::Complete(blocks.into_iter().map(|b| BlockData { origin: who, block: b }).collect()));
|
||||
}
|
||||
|
||||
/// Returns a set of block hashes that require a header download. The returned set is marked as being downloaded.
|
||||
pub fn needed_blocks(&mut self, who: NodeIndex, count: usize, peer_best: NumberFor<B>, common: NumberFor<B>) -> Option<Range<NumberFor<B>>> {
|
||||
// First block number that we need to download
|
||||
let first_different = common + As::sa(1);
|
||||
let count = As::sa(count as u64);
|
||||
let (mut range, downloading) = {
|
||||
let mut downloading_iter = self.blocks.iter().peekable();
|
||||
let mut prev: Option<(&NumberFor<B>, &BlockRangeState<B>)> = None;
|
||||
loop {
|
||||
let next = downloading_iter.next();
|
||||
break match &(prev, next) {
|
||||
&(Some((start, &BlockRangeState::Downloading { ref len, downloading })), _) if downloading < MAX_PARALLEL_DOWNLOADS =>
|
||||
(*start .. *start + *len, downloading),
|
||||
&(Some((start, r)), Some((next_start, _))) if *start + r.len() < *next_start =>
|
||||
(*start + r.len() .. cmp::min(*next_start, *start + r.len() + count), 0), // gap
|
||||
&(Some((start, r)), None) =>
|
||||
(*start + r.len() .. *start + r.len() + count, 0), // last range
|
||||
&(None, None) =>
|
||||
(first_different .. first_different + count, 0), // empty
|
||||
&(None, Some((start, _))) if *start > first_different =>
|
||||
(first_different .. cmp::min(first_different + count, *start), 0), // gap at the start
|
||||
_ => {
|
||||
prev = next;
|
||||
continue
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
// crop to peers best
|
||||
if range.start > peer_best {
|
||||
trace!(target: "sync", "Out of range for peer {} ({} vs {})", who, range.start, peer_best);
|
||||
return None;
|
||||
}
|
||||
range.end = cmp::min(peer_best + As::sa(1), range.end);
|
||||
self.peer_requests.insert(who, range.start);
|
||||
self.blocks.insert(range.start, BlockRangeState::Downloading { len: range.end - range.start, downloading: downloading + 1 });
|
||||
if range.end <= range.start {
|
||||
panic!("Empty range {:?}, count={}, peer_best={}, common={}, blocks={:?}", range, count, peer_best, common, self.blocks);
|
||||
}
|
||||
Some(range)
|
||||
}
|
||||
|
||||
/// Get a valid chain of blocks ordered in descending order and ready for importing into blockchain.
|
||||
pub fn drain(&mut self, from: NumberFor<B>) -> Vec<BlockData<B>> {
|
||||
let mut drained = Vec::new();
|
||||
let mut ranges = Vec::new();
|
||||
{
|
||||
let mut prev = from;
|
||||
for (start, range_data) in &mut self.blocks {
|
||||
match range_data {
|
||||
&mut BlockRangeState::Complete(ref mut blocks) if *start <= prev => {
|
||||
prev = *start + As::sa(blocks.len() as u64);
|
||||
let mut blocks = mem::replace(blocks, Vec::new());
|
||||
drained.append(&mut blocks);
|
||||
ranges.push(*start);
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
for r in ranges {
|
||||
self.blocks.remove(&r);
|
||||
}
|
||||
trace!(target: "sync", "Drained {} blocks", drained.len());
|
||||
drained
|
||||
}
|
||||
|
||||
pub fn clear_peer_download(&mut self, who: NodeIndex) {
|
||||
match self.peer_requests.entry(who) {
|
||||
Entry::Occupied(entry) => {
|
||||
let start = entry.remove();
|
||||
let remove = match self.blocks.get_mut(&start) {
|
||||
Some(&mut BlockRangeState::Downloading { ref mut downloading, .. }) if *downloading > 1 => {
|
||||
*downloading = *downloading - 1;
|
||||
false
|
||||
},
|
||||
Some(&mut BlockRangeState::Downloading { .. }) => {
|
||||
true
|
||||
},
|
||||
_ => {
|
||||
debug_assert!(false);
|
||||
false
|
||||
}
|
||||
};
|
||||
if remove {
|
||||
self.blocks.remove(&start);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{BlockCollection, BlockData, BlockRangeState};
|
||||
use message;
|
||||
use runtime_primitives::testing::Block as RawBlock;
|
||||
use primitives::H256;
|
||||
|
||||
type Block = RawBlock<u64>;
|
||||
|
||||
fn is_empty(bc: &BlockCollection<Block>) -> bool {
|
||||
bc.blocks.is_empty() &&
|
||||
bc.peer_requests.is_empty()
|
||||
}
|
||||
|
||||
fn generate_blocks(n: usize) -> Vec<message::BlockData<Block>> {
|
||||
(0 .. n).map(|_| message::generic::BlockData {
|
||||
hash: H256::random(),
|
||||
header: None,
|
||||
body: None,
|
||||
message_queue: None,
|
||||
receipt: None,
|
||||
justification: None,
|
||||
}).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_clear() {
|
||||
let mut bc = BlockCollection::new();
|
||||
assert!(is_empty(&bc));
|
||||
bc.insert(1, generate_blocks(100), 0);
|
||||
assert!(!is_empty(&bc));
|
||||
bc.clear();
|
||||
assert!(is_empty(&bc));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_blocks() {
|
||||
let mut bc = BlockCollection::new();
|
||||
assert!(is_empty(&bc));
|
||||
let peer0 = 0;
|
||||
let peer1 = 1;
|
||||
let peer2 = 2;
|
||||
|
||||
let blocks = generate_blocks(150);
|
||||
assert_eq!(bc.needed_blocks(peer0, 40, 150, 0), Some(1 .. 41));
|
||||
assert_eq!(bc.needed_blocks(peer1, 40, 150, 0), Some(41 .. 81));
|
||||
assert_eq!(bc.needed_blocks(peer2, 40, 150, 0), Some(81 .. 121));
|
||||
|
||||
bc.clear_peer_download(peer1);
|
||||
bc.insert(41, blocks[41..81].to_vec(), peer1);
|
||||
assert_eq!(bc.drain(1), vec![]);
|
||||
assert_eq!(bc.needed_blocks(peer1, 40, 150, 0), Some(121 .. 151));
|
||||
bc.clear_peer_download(peer0);
|
||||
bc.insert(1, blocks[1..11].to_vec(), peer0);
|
||||
|
||||
assert_eq!(bc.needed_blocks(peer0, 40, 150, 0), Some(11 .. 41));
|
||||
assert_eq!(bc.drain(1), blocks[1..11].iter().map(|b| BlockData { block: b.clone(), origin: 0 }).collect::<Vec<_>>());
|
||||
|
||||
bc.clear_peer_download(peer0);
|
||||
bc.insert(11, blocks[11..41].to_vec(), peer0);
|
||||
|
||||
let drained = bc.drain(12);
|
||||
assert_eq!(drained[..30], blocks[11..41].iter().map(|b| BlockData { block: b.clone(), origin: 0 }).collect::<Vec<_>>()[..]);
|
||||
assert_eq!(drained[30..], blocks[41..81].iter().map(|b| BlockData { block: b.clone(), origin: 1 }).collect::<Vec<_>>()[..]);
|
||||
|
||||
bc.clear_peer_download(peer2);
|
||||
assert_eq!(bc.needed_blocks(peer2, 40, 150, 80), Some(81 .. 121));
|
||||
bc.clear_peer_download(peer2);
|
||||
bc.insert(81, blocks[81..121].to_vec(), peer2);
|
||||
bc.clear_peer_download(peer1);
|
||||
bc.insert(121, blocks[121..150].to_vec(), peer1);
|
||||
|
||||
assert_eq!(bc.drain(80), vec![]);
|
||||
let drained = bc.drain(81);
|
||||
assert_eq!(drained[..40], blocks[81..121].iter().map(|b| BlockData { block: b.clone(), origin: 2 }).collect::<Vec<_>>()[..]);
|
||||
assert_eq!(drained[40..], blocks[121..150].iter().map(|b| BlockData { block: b.clone(), origin: 1 }).collect::<Vec<_>>()[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_gap() {
|
||||
let mut bc: BlockCollection<Block> = BlockCollection::new();
|
||||
bc.blocks.insert(100, BlockRangeState::Downloading {
|
||||
len: 128,
|
||||
downloading: 1,
|
||||
});
|
||||
let blocks = generate_blocks(10).into_iter().map(|b| BlockData { block: b, origin: 0 }).collect();
|
||||
bc.blocks.insert(114305, BlockRangeState::Complete(blocks));
|
||||
|
||||
assert_eq!(bc.needed_blocks(0, 128, 10000, 000), Some(1 .. 100));
|
||||
assert_eq!(bc.needed_blocks(0, 128, 10000, 600), Some(100 + 128 .. 100 + 128 + 128));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Blockchain access trait
|
||||
|
||||
use client::{self, Client as SubstrateClient, ImportResult, ClientInfo, BlockStatus, BlockOrigin, CallExecutor};
|
||||
use client::error::Error;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use runtime_primitives::bft::Justification;
|
||||
use primitives::{Blake2Hasher, RlpCodec};
|
||||
|
||||
/// Local client abstraction for the network.
|
||||
pub trait Client<Block: BlockT>: Send + Sync {
|
||||
/// Import a new block. Parent is supposed to be existing in the blockchain.
|
||||
fn import(&self, origin: BlockOrigin, header: Block::Header, justification: Justification<Block::Hash>, body: Option<Vec<Block::Extrinsic>>) -> Result<ImportResult, Error>;
|
||||
|
||||
/// Get blockchain info.
|
||||
fn info(&self) -> Result<ClientInfo<Block>, Error>;
|
||||
|
||||
/// Get block status.
|
||||
fn block_status(&self, id: &BlockId<Block>) -> Result<BlockStatus, Error>;
|
||||
|
||||
/// Get block hash by number.
|
||||
fn block_hash(&self, block_number: <Block::Header as HeaderT>::Number) -> Result<Option<Block::Hash>, Error>;
|
||||
|
||||
/// Get block header.
|
||||
fn header(&self, id: &BlockId<Block>) -> Result<Option<Block::Header>, Error>;
|
||||
|
||||
/// Get block body.
|
||||
fn body(&self, id: &BlockId<Block>) -> Result<Option<Vec<Block::Extrinsic>>, Error>;
|
||||
|
||||
/// Get block justification.
|
||||
fn justification(&self, id: &BlockId<Block>) -> Result<Option<Justification<Block::Hash>>, Error>;
|
||||
|
||||
/// Get block header proof.
|
||||
fn header_proof(&self, block_number: <Block::Header as HeaderT>::Number) -> Result<(Block::Header, Vec<Vec<u8>>), Error>;
|
||||
|
||||
/// Get storage read execution proof.
|
||||
fn read_proof(&self, block: &Block::Hash, key: &[u8]) -> Result<Vec<Vec<u8>>, Error>;
|
||||
|
||||
/// Get method execution proof.
|
||||
fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error>;
|
||||
}
|
||||
|
||||
impl<B, E, Block> Client<Block> for SubstrateClient<B, E, Block> where
|
||||
B: client::backend::Backend<Block, Blake2Hasher, RlpCodec> + Send + Sync + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher, RlpCodec> + Send + Sync + 'static,
|
||||
Block: BlockT,
|
||||
{
|
||||
fn import(&self, origin: BlockOrigin, header: Block::Header, justification: Justification<Block::Hash>, body: Option<Vec<Block::Extrinsic>>) -> Result<ImportResult, Error> {
|
||||
// TODO: defer justification check.
|
||||
let justified_header = self.check_justification(header, justification.into())?;
|
||||
(self as &SubstrateClient<B, E, Block>).import_block(origin, justified_header, body)
|
||||
}
|
||||
|
||||
fn info(&self) -> Result<ClientInfo<Block>, Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).info()
|
||||
}
|
||||
|
||||
fn block_status(&self, id: &BlockId<Block>) -> Result<BlockStatus, Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).block_status(id)
|
||||
}
|
||||
|
||||
fn block_hash(&self, block_number: <Block::Header as HeaderT>::Number) -> Result<Option<Block::Hash>, Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).block_hash(block_number)
|
||||
}
|
||||
|
||||
fn header(&self, id: &BlockId<Block>) -> Result<Option<Block::Header>, Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).header(id)
|
||||
}
|
||||
|
||||
fn body(&self, id: &BlockId<Block>) -> Result<Option<Vec<Block::Extrinsic>>, Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).body(id)
|
||||
}
|
||||
|
||||
fn justification(&self, id: &BlockId<Block>) -> Result<Option<Justification<Block::Hash>>, Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).justification(id)
|
||||
}
|
||||
|
||||
fn header_proof(&self, block_number: <Block::Header as HeaderT>::Number) -> Result<(Block::Header, Vec<Vec<u8>>), Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).header_proof(&BlockId::Number(block_number))
|
||||
}
|
||||
|
||||
fn read_proof(&self, block: &Block::Hash, key: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).read_proof(&BlockId::Hash(block.clone()), key)
|
||||
}
|
||||
|
||||
fn execution_proof(&self, block: &Block::Hash, method: &str, data: &[u8]) -> Result<(Vec<u8>, Vec<Vec<u8>>), Error> {
|
||||
(self as &SubstrateClient<B, E, Block>).execution_proof(&BlockId::Hash(block.clone()), method, data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
pub use service::Roles;
|
||||
|
||||
/// Protocol configuration
|
||||
#[derive(Clone)]
|
||||
pub struct ProtocolConfig {
|
||||
/// Assigned roles.
|
||||
pub roles: Roles,
|
||||
}
|
||||
|
||||
impl Default for ProtocolConfig {
|
||||
fn default() -> ProtocolConfig {
|
||||
ProtocolConfig {
|
||||
roles: Roles::FULL,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utility for gossip of network messages between authorities.
|
||||
//! Handles chain-specific and standard BFT messages.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use futures::sync::mpsc;
|
||||
use std::time::{Instant, Duration};
|
||||
use network_libp2p::NodeIndex;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use message::{self, generic::Message as GenericMessage};
|
||||
use protocol::Context;
|
||||
use service::Roles;
|
||||
|
||||
// TODO: Add additional spam/DoS attack protection.
|
||||
const MESSAGE_LIFETIME: Duration = Duration::from_secs(600);
|
||||
|
||||
struct PeerConsensus<H> {
|
||||
known_messages: HashSet<H>,
|
||||
}
|
||||
|
||||
/// Consensus messages.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ConsensusMessage<B: BlockT> {
|
||||
/// A message concerning BFT agreement
|
||||
Bft(message::LocalizedBftMessage<B>),
|
||||
/// A message concerning some chain-specific aspect of consensus
|
||||
ChainSpecific(Vec<u8>, B::Hash),
|
||||
}
|
||||
|
||||
struct MessageEntry<B: BlockT> {
|
||||
hash: B::Hash,
|
||||
message: ConsensusMessage<B>,
|
||||
instant: Instant,
|
||||
}
|
||||
|
||||
/// Consensus network protocol handler. Manages statements and candidate requests.
|
||||
pub struct ConsensusGossip<B: BlockT> {
|
||||
peers: HashMap<NodeIndex, PeerConsensus<B::Hash>>,
|
||||
message_sink: Option<(mpsc::UnboundedSender<ConsensusMessage<B>>, B::Hash)>,
|
||||
messages: Vec<MessageEntry<B>>,
|
||||
message_hashes: HashSet<B::Hash>,
|
||||
}
|
||||
|
||||
impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
/// Create a new instance.
|
||||
pub fn new() -> Self {
|
||||
ConsensusGossip {
|
||||
peers: HashMap::new(),
|
||||
message_sink: None,
|
||||
messages: Default::default(),
|
||||
message_hashes: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes all notification streams.
|
||||
pub fn abort(&mut self) {
|
||||
self.message_sink = None;
|
||||
}
|
||||
|
||||
/// Handle new connected peer.
|
||||
pub fn new_peer(&mut self, protocol: &mut Context<B>, who: NodeIndex, roles: Roles) {
|
||||
if roles.intersects(Roles::AUTHORITY | Roles::FULL) {
|
||||
trace!(target:"gossip", "Registering {:?} {}", roles, who);
|
||||
// Send out all known messages.
|
||||
// TODO: limit by size
|
||||
let mut known_messages = HashSet::new();
|
||||
for entry in self.messages.iter() {
|
||||
known_messages.insert(entry.hash);
|
||||
let message = match entry.message {
|
||||
ConsensusMessage::Bft(ref bft) => GenericMessage::BftMessage(bft.clone()),
|
||||
ConsensusMessage::ChainSpecific(ref msg, _) => GenericMessage::ChainSpecific(msg.clone()),
|
||||
};
|
||||
|
||||
protocol.send_message(who, message);
|
||||
}
|
||||
self.peers.insert(who, PeerConsensus {
|
||||
known_messages,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate(&mut self, protocol: &mut Context<B>, message: message::Message<B>, hash: B::Hash) {
|
||||
for (id, ref mut peer) in self.peers.iter_mut() {
|
||||
if peer.known_messages.insert(hash.clone()) {
|
||||
trace!(target:"gossip", "Propagating to {}: {:?}", id, message);
|
||||
protocol.send_message(*id, message.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register_message(&mut self, hash: B::Hash, message: ConsensusMessage<B>) {
|
||||
if self.message_hashes.insert(hash) {
|
||||
self.messages.push(MessageEntry {
|
||||
hash,
|
||||
instant: Instant::now(),
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles incoming BFT message, passing to stream and repropagating.
|
||||
pub fn on_bft_message(&mut self, protocol: &mut Context<B>, who: NodeIndex, message: message::LocalizedBftMessage<B>) {
|
||||
if let Some((hash, message)) = self.handle_incoming(protocol, who, ConsensusMessage::Bft(message)) {
|
||||
// propagate to other peers.
|
||||
self.multicast(protocol, message, Some(hash));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles incoming chain-specific message and repropagates
|
||||
pub fn on_chain_specific(&mut self, protocol: &mut Context<B>, who: NodeIndex, message: Vec<u8>, parent_hash: B::Hash) {
|
||||
debug!(target: "gossip", "received chain-specific gossip message");
|
||||
if let Some((hash, message)) = self.handle_incoming(protocol, who, ConsensusMessage::ChainSpecific(message, parent_hash)) {
|
||||
debug!(target: "gossip", "handled incoming chain-specific message");
|
||||
// propagate to other peers.
|
||||
self.multicast(protocol, message, Some(hash));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a stream of messages relevant to consensus on top of a given parent hash.
|
||||
pub fn messages_for(&mut self, parent_hash: B::Hash) -> mpsc::UnboundedReceiver<ConsensusMessage<B>> {
|
||||
let (sink, stream) = mpsc::unbounded();
|
||||
|
||||
for entry in self.messages.iter() {
|
||||
let message_matches = match entry.message {
|
||||
ConsensusMessage::Bft(ref msg) => msg.parent_hash == parent_hash,
|
||||
ConsensusMessage::ChainSpecific(_, ref h) => h == &parent_hash,
|
||||
};
|
||||
|
||||
if message_matches {
|
||||
sink.unbounded_send(entry.message.clone()).expect("receiving end known to be open; qed");
|
||||
}
|
||||
}
|
||||
|
||||
self.message_sink = Some((sink, parent_hash));
|
||||
stream
|
||||
}
|
||||
|
||||
/// Multicast a chain-specific message to other authorities.
|
||||
pub fn multicast_chain_specific(&mut self, protocol: &mut Context<B>, message: Vec<u8>, parent_hash: B::Hash) {
|
||||
trace!(target:"gossip", "sending chain-specific message");
|
||||
self.multicast(protocol, ConsensusMessage::ChainSpecific(message, parent_hash), None);
|
||||
}
|
||||
|
||||
/// Multicast a BFT message to other authorities
|
||||
pub fn multicast_bft_message(&mut self, protocol: &mut Context<B>, message: message::LocalizedBftMessage<B>) {
|
||||
// Broadcast message to all authorities.
|
||||
trace!(target:"gossip", "Broadcasting BFT message {:?}", message);
|
||||
self.multicast(protocol, ConsensusMessage::Bft(message), None);
|
||||
}
|
||||
|
||||
/// Call when a peer has been disconnected to stop tracking gossip status.
|
||||
pub fn peer_disconnected(&mut self, _protocol: &mut Context<B>, who: NodeIndex) {
|
||||
self.peers.remove(&who);
|
||||
}
|
||||
|
||||
/// Prune old or no longer relevant consensus messages.
|
||||
/// Supply an optional block hash where consensus is known to have concluded.
|
||||
pub fn collect_garbage(&mut self, best_hash: Option<&B::Hash>) {
|
||||
let hashes = &mut self.message_hashes;
|
||||
let before = self.messages.len();
|
||||
let now = Instant::now();
|
||||
self.messages.retain(|entry| {
|
||||
if entry.instant + MESSAGE_LIFETIME >= now &&
|
||||
best_hash.map_or(true, |parent_hash|
|
||||
match entry.message {
|
||||
ConsensusMessage::Bft(ref msg) => &msg.parent_hash != parent_hash,
|
||||
ConsensusMessage::ChainSpecific(_, ref h) => h != parent_hash,
|
||||
})
|
||||
{
|
||||
true
|
||||
} else {
|
||||
hashes.remove(&entry.hash);
|
||||
false
|
||||
}
|
||||
});
|
||||
if self.messages.len() != before {
|
||||
trace!(target:"gossip", "Cleaned up {} stale messages", before - self.messages.len());
|
||||
}
|
||||
for (_, ref mut peer) in self.peers.iter_mut() {
|
||||
peer.known_messages.retain(|h| hashes.contains(h));
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_incoming(&mut self, protocol: &mut Context<B>, who: NodeIndex, message: ConsensusMessage<B>) -> Option<(B::Hash, ConsensusMessage<B>)> {
|
||||
let (hash, parent, message) = match message {
|
||||
ConsensusMessage::Bft(msg) => {
|
||||
let parent = msg.parent_hash;
|
||||
let generic = GenericMessage::BftMessage(msg);
|
||||
(
|
||||
::protocol::hash_message(&generic),
|
||||
parent,
|
||||
match generic {
|
||||
GenericMessage::BftMessage(msg) => ConsensusMessage::Bft(msg),
|
||||
_ => panic!("`generic` is known to be the `BftMessage` variant; qed"),
|
||||
}
|
||||
)
|
||||
}
|
||||
ConsensusMessage::ChainSpecific(msg, parent) => {
|
||||
let generic = GenericMessage::ChainSpecific(msg);
|
||||
(
|
||||
::protocol::hash_message::<B>(&generic),
|
||||
parent,
|
||||
match generic {
|
||||
GenericMessage::ChainSpecific(msg) => ConsensusMessage::ChainSpecific(msg, parent),
|
||||
_ => panic!("`generic` is known to be the `ChainSpecific` variant; qed"),
|
||||
}
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if self.message_hashes.contains(&hash) {
|
||||
trace!(target:"gossip", "Ignored already known message from {}", who);
|
||||
return None;
|
||||
}
|
||||
|
||||
match (protocol.client().info(), protocol.client().header(&BlockId::Hash(parent))) {
|
||||
(_, Err(e)) | (Err(e), _) => {
|
||||
debug!(target:"gossip", "Error reading blockchain: {:?}", e);
|
||||
return None;
|
||||
},
|
||||
(Ok(info), Ok(Some(header))) => {
|
||||
if header.number() < &info.chain.best_number {
|
||||
trace!(target:"gossip", "Ignored ancient message from {}, hash={}", who, parent);
|
||||
return None;
|
||||
}
|
||||
},
|
||||
(Ok(_), Ok(None)) => {},
|
||||
}
|
||||
|
||||
if let Some(ref mut peer) = self.peers.get_mut(&who) {
|
||||
peer.known_messages.insert(hash);
|
||||
if let Some((sink, parent_hash)) = self.message_sink.take() {
|
||||
if parent == parent_hash {
|
||||
debug!(target: "gossip", "Pushing relevant consensus message to sink.");
|
||||
if let Err(e) = sink.unbounded_send(message.clone()) {
|
||||
trace!(target:"gossip", "Error broadcasting message notification: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
self.message_sink = Some((sink, parent_hash));
|
||||
}
|
||||
} else {
|
||||
trace!(target:"gossip", "Ignored statement from unregistered peer {}", who);
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((hash, message))
|
||||
}
|
||||
|
||||
fn multicast(&mut self, protocol: &mut Context<B>, message: ConsensusMessage<B>, hash: Option<B::Hash>) {
|
||||
let generic = match message {
|
||||
ConsensusMessage::Bft(ref message) => GenericMessage::BftMessage(message.clone()),
|
||||
ConsensusMessage::ChainSpecific(ref message, _) => GenericMessage::ChainSpecific(message.clone()),
|
||||
};
|
||||
|
||||
let hash = hash.unwrap_or_else(|| ::protocol::hash_message(&generic));
|
||||
self.register_message(hash, message);
|
||||
self.propagate(protocol, generic, hash);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use runtime_primitives::bft::Justification;
|
||||
use runtime_primitives::testing::{H256, Header, Block as RawBlock};
|
||||
use std::time::Instant;
|
||||
use message::{self, generic};
|
||||
use super::*;
|
||||
|
||||
type Block = RawBlock<u64>;
|
||||
|
||||
#[test]
|
||||
fn collects_garbage() {
|
||||
let prev_hash = H256::random();
|
||||
let best_hash = H256::random();
|
||||
let mut consensus = ConsensusGossip::<Block>::new();
|
||||
let now = Instant::now();
|
||||
let m1_hash = H256::random();
|
||||
let m2_hash = H256::random();
|
||||
let m1 = ConsensusMessage::Bft(message::LocalizedBftMessage {
|
||||
parent_hash: prev_hash,
|
||||
message: message::generic::BftMessage::Auxiliary(Justification {
|
||||
round_number: 0,
|
||||
hash: Default::default(),
|
||||
signatures: Default::default(),
|
||||
}),
|
||||
});
|
||||
let m2 = ConsensusMessage::ChainSpecific(vec![1, 2, 3], best_hash);
|
||||
|
||||
macro_rules! push_msg {
|
||||
($hash:expr, $now: expr, $m:expr) => {
|
||||
consensus.messages.push(MessageEntry {
|
||||
hash: $hash,
|
||||
instant: $now,
|
||||
message: $m,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
push_msg!(m1_hash, now, m1);
|
||||
push_msg!(m2_hash, now, m2.clone());
|
||||
consensus.message_hashes.insert(m1_hash);
|
||||
consensus.message_hashes.insert(m2_hash);
|
||||
|
||||
// nothing to collect
|
||||
consensus.collect_garbage(None);
|
||||
assert_eq!(consensus.messages.len(), 2);
|
||||
assert_eq!(consensus.message_hashes.len(), 2);
|
||||
|
||||
// random header, nothing should be cleared
|
||||
let mut header = Header {
|
||||
parent_hash: H256::default(),
|
||||
number: 0,
|
||||
state_root: H256::default(),
|
||||
extrinsics_root: H256::default(),
|
||||
digest: Default::default(),
|
||||
};
|
||||
|
||||
consensus.collect_garbage(Some(&H256::default()));
|
||||
assert_eq!(consensus.messages.len(), 2);
|
||||
assert_eq!(consensus.message_hashes.len(), 2);
|
||||
|
||||
// header that matches one of the messages
|
||||
header.parent_hash = prev_hash;
|
||||
consensus.collect_garbage(Some(&prev_hash));
|
||||
assert_eq!(consensus.messages.len(), 1);
|
||||
assert_eq!(consensus.message_hashes.len(), 1);
|
||||
assert!(consensus.message_hashes.contains(&m2_hash));
|
||||
|
||||
// make timestamp expired
|
||||
consensus.messages.clear();
|
||||
push_msg!(m2_hash, now - MESSAGE_LIFETIME, m2);
|
||||
consensus.collect_garbage(None);
|
||||
assert!(consensus.messages.is_empty());
|
||||
assert!(consensus.message_hashes.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_stream_include_those_sent_before_asking_for_stream() {
|
||||
use futures::Stream;
|
||||
|
||||
let mut consensus = ConsensusGossip::new();
|
||||
|
||||
let bft_message = generic::BftMessage::Consensus(generic::SignedConsensusMessage::Vote(generic::SignedConsensusVote {
|
||||
vote: generic::ConsensusVote::AdvanceRound(0),
|
||||
sender: [0; 32].into(),
|
||||
signature: Default::default(),
|
||||
}));
|
||||
|
||||
let parent_hash = [1; 32].into();
|
||||
|
||||
let localized = ::message::LocalizedBftMessage::<Block> {
|
||||
message: bft_message,
|
||||
parent_hash: parent_hash,
|
||||
};
|
||||
|
||||
let message = generic::Message::BftMessage(localized.clone());
|
||||
let message_hash = ::protocol::hash_message::<Block>(&message);
|
||||
|
||||
let message = ConsensusMessage::Bft(localized);
|
||||
consensus.register_message(message_hash, message.clone());
|
||||
let stream = consensus.messages_for(parent_hash);
|
||||
|
||||
assert_eq!(stream.wait().next(), Some(Ok(message)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot service possible errors.
|
||||
|
||||
use std::io::Error as IoError;
|
||||
use network_libp2p::Error as NetworkError;
|
||||
use client;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
Network(NetworkError) #[doc = "Devp2p error."];
|
||||
Io(IoError) #[doc = "IO error."];
|
||||
}
|
||||
|
||||
links {
|
||||
Client(client::error::Error, client::error::ErrorKind) #[doc="Client error"];
|
||||
}
|
||||
|
||||
errors {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Blocks import queue.
|
||||
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use parking_lot::{Condvar, Mutex, RwLock};
|
||||
|
||||
use client::{BlockOrigin, ImportResult};
|
||||
use network_libp2p::{NodeIndex, Severity};
|
||||
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
|
||||
|
||||
use blocks::BlockData;
|
||||
use chain::Client;
|
||||
use error::{ErrorKind, Error};
|
||||
use protocol::Context;
|
||||
use service::ExecuteInContext;
|
||||
use sync::ChainSync;
|
||||
|
||||
/// Blocks import queue API.
|
||||
pub trait ImportQueue<B: BlockT>: Send + Sync {
|
||||
/// Clear the queue when sync is restarting.
|
||||
fn clear(&self);
|
||||
/// Clears the import queue and stops importing.
|
||||
fn stop(&self);
|
||||
/// Get queue status.
|
||||
fn status(&self) -> ImportQueueStatus<B>;
|
||||
/// Is block with given hash currently in the queue.
|
||||
fn is_importing(&self, hash: &B::Hash) -> bool;
|
||||
/// Import bunch of blocks.
|
||||
fn import_blocks(&self, sync: &mut ChainSync<B>, protocol: &mut Context<B>, blocks: (BlockOrigin, Vec<BlockData<B>>));
|
||||
}
|
||||
|
||||
/// Import queue status. It isn't completely accurate.
|
||||
pub struct ImportQueueStatus<B: BlockT> {
|
||||
/// Number of blocks that are currently in the queue.
|
||||
pub importing_count: usize,
|
||||
/// The number of the best block that was ever in the queue since start/last failure.
|
||||
pub best_importing_number: <<B as BlockT>::Header as HeaderT>::Number,
|
||||
}
|
||||
|
||||
/// Blocks import queue that is importing blocks in the separate thread.
|
||||
pub struct AsyncImportQueue<B: BlockT> {
|
||||
handle: Mutex<Option<::std::thread::JoinHandle<()>>>,
|
||||
data: Arc<AsyncImportQueueData<B>>,
|
||||
}
|
||||
|
||||
/// Locks order: queue, queue_blocks, best_importing_number
|
||||
struct AsyncImportQueueData<B: BlockT> {
|
||||
signal: Condvar,
|
||||
queue: Mutex<VecDeque<(BlockOrigin, Vec<BlockData<B>>)>>,
|
||||
queue_blocks: RwLock<HashSet<B::Hash>>,
|
||||
best_importing_number: RwLock<<<B as BlockT>::Header as HeaderT>::Number>,
|
||||
is_stopping: AtomicBool,
|
||||
}
|
||||
|
||||
impl<B: BlockT> AsyncImportQueue<B> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
handle: Mutex::new(None),
|
||||
data: Arc::new(AsyncImportQueueData::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start<E: 'static + ExecuteInContext<B>>(&self, sync: Weak<RwLock<ChainSync<B>>>, service: Weak<E>, chain: Weak<Client<B>>) -> Result<(), Error> {
|
||||
debug_assert!(self.handle.lock().is_none());
|
||||
|
||||
let qdata = self.data.clone();
|
||||
*self.handle.lock() = Some(::std::thread::Builder::new().name("ImportQueue".into()).spawn(move || {
|
||||
import_thread(sync, service, chain, qdata)
|
||||
}).map_err(|err| Error::from(ErrorKind::Io(err)))?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT> AsyncImportQueueData<B> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
signal: Default::default(),
|
||||
queue: Mutex::new(VecDeque::new()),
|
||||
queue_blocks: RwLock::new(HashSet::new()),
|
||||
best_importing_number: RwLock::new(Zero::zero()),
|
||||
is_stopping: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT> ImportQueue<B> for AsyncImportQueue<B> {
|
||||
fn clear(&self) {
|
||||
let mut queue = self.data.queue.lock();
|
||||
let mut queue_blocks = self.data.queue_blocks.write();
|
||||
let mut best_importing_number = self.data.best_importing_number.write();
|
||||
queue_blocks.clear();
|
||||
queue.clear();
|
||||
*best_importing_number = Zero::zero();
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
self.clear();
|
||||
if let Some(handle) = self.handle.lock().take() {
|
||||
self.data.is_stopping.store(true, Ordering::SeqCst);
|
||||
self.data.signal.notify_one();
|
||||
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
|
||||
fn status(&self) -> ImportQueueStatus<B> {
|
||||
ImportQueueStatus {
|
||||
importing_count: self.data.queue_blocks.read().len(),
|
||||
best_importing_number: *self.data.best_importing_number.read(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_importing(&self, hash: &B::Hash) -> bool {
|
||||
self.data.queue_blocks.read().contains(hash)
|
||||
}
|
||||
|
||||
fn import_blocks(&self, _sync: &mut ChainSync<B>, _protocol: &mut Context<B>, blocks: (BlockOrigin, Vec<BlockData<B>>)) {
|
||||
if blocks.1.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
trace!(target:"sync", "Scheduling {} blocks for import", blocks.1.len());
|
||||
|
||||
let mut queue = self.data.queue.lock();
|
||||
let mut queue_blocks = self.data.queue_blocks.write();
|
||||
let mut best_importing_number = self.data.best_importing_number.write();
|
||||
let new_best_importing_number = blocks.1.last().and_then(|b| b.block.header.as_ref().map(|h| h.number().clone())).unwrap_or_else(|| Zero::zero());
|
||||
queue_blocks.extend(blocks.1.iter().map(|b| b.block.hash.clone()));
|
||||
if new_best_importing_number > *best_importing_number {
|
||||
*best_importing_number = new_best_importing_number;
|
||||
}
|
||||
queue.push_back(blocks);
|
||||
self.data.signal.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT> Drop for AsyncImportQueue<B> {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocks import thread.
|
||||
fn import_thread<B: BlockT, E: ExecuteInContext<B>>(sync: Weak<RwLock<ChainSync<B>>>, service: Weak<E>, chain: Weak<Client<B>>, qdata: Arc<AsyncImportQueueData<B>>) {
|
||||
trace!(target: "sync", "Starting import thread");
|
||||
loop {
|
||||
if qdata.is_stopping.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
|
||||
let new_blocks = {
|
||||
let mut queue_lock = qdata.queue.lock();
|
||||
if queue_lock.is_empty() {
|
||||
qdata.signal.wait(&mut queue_lock);
|
||||
}
|
||||
|
||||
match queue_lock.pop_front() {
|
||||
Some(new_blocks) => new_blocks,
|
||||
None => break,
|
||||
}
|
||||
};
|
||||
|
||||
match (sync.upgrade(), service.upgrade(), chain.upgrade()) {
|
||||
(Some(sync), Some(service), Some(chain)) => {
|
||||
let blocks_hashes: Vec<B::Hash> = new_blocks.1.iter().map(|b| b.block.hash.clone()).collect();
|
||||
if !import_many_blocks(&mut SyncLink::Indirect(&sync, &*chain, &*service), Some(&*qdata), new_blocks) {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut queue_blocks = qdata.queue_blocks.write();
|
||||
for blocks_hash in blocks_hashes {
|
||||
queue_blocks.remove(&blocks_hash);
|
||||
}
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
trace!(target: "sync", "Stopping import thread");
|
||||
}
|
||||
|
||||
/// ChainSync link trait.
|
||||
trait SyncLinkApi<B: BlockT> {
|
||||
/// Get chain reference.
|
||||
fn chain(&self) -> &Client<B>;
|
||||
/// Block imported.
|
||||
fn block_imported(&mut self, hash: &B::Hash, number: NumberFor<B>);
|
||||
/// Maintain sync.
|
||||
fn maintain_sync(&mut self);
|
||||
/// Disconnect from peer.
|
||||
fn useless_peer(&mut self, who: NodeIndex, reason: &str);
|
||||
/// Disconnect from peer and restart sync.
|
||||
fn note_useless_and_restart_sync(&mut self, who: NodeIndex, reason: &str);
|
||||
/// Restart sync.
|
||||
fn restart(&mut self);
|
||||
}
|
||||
|
||||
/// Link with the ChainSync service.
|
||||
enum SyncLink<'a, B: 'a + BlockT, E: 'a + ExecuteInContext<B>> {
|
||||
/// Indirect link (through service).
|
||||
Indirect(&'a RwLock<ChainSync<B>>, &'a Client<B>, &'a E),
|
||||
/// Direct references are given.
|
||||
#[cfg(test)]
|
||||
Direct(&'a mut ChainSync<B>, &'a mut Context<B>),
|
||||
}
|
||||
|
||||
/// Block import successful result.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum BlockImportResult<H: ::std::fmt::Debug + PartialEq, N: ::std::fmt::Debug + PartialEq> {
|
||||
/// Imported known block.
|
||||
ImportedKnown(H, N),
|
||||
/// Imported unknown block.
|
||||
ImportedUnknown(H, N),
|
||||
}
|
||||
|
||||
/// Block import error.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum BlockImportError {
|
||||
/// Disconnect from peer and continue import of next bunch of blocks.
|
||||
Disconnect(NodeIndex),
|
||||
/// Disconnect from peer and restart sync.
|
||||
DisconnectAndRestart(NodeIndex),
|
||||
/// Restart sync.
|
||||
Restart,
|
||||
}
|
||||
|
||||
/// Import a bunch of blocks.
|
||||
fn import_many_blocks<'a, B: BlockT>(
|
||||
link: &mut SyncLinkApi<B>,
|
||||
qdata: Option<&AsyncImportQueueData<B>>,
|
||||
blocks: (BlockOrigin, Vec<BlockData<B>>)
|
||||
) -> bool
|
||||
{
|
||||
let (blocks_origin, blocks) = blocks;
|
||||
let count = blocks.len();
|
||||
let mut imported = 0;
|
||||
|
||||
let blocks_range = match (
|
||||
blocks.first().and_then(|b| b.block.header.as_ref().map(|h| h.number())),
|
||||
blocks.last().and_then(|b| b.block.header.as_ref().map(|h| h.number())),
|
||||
) {
|
||||
(Some(first), Some(last)) if first != last => format!(" ({}..{})", first, last),
|
||||
(Some(first), Some(_)) => format!(" ({})", first),
|
||||
_ => Default::default(),
|
||||
};
|
||||
trace!(target:"sync", "Starting import of {} blocks{}", count, blocks_range);
|
||||
|
||||
// Blocks in the response/drain should be in ascending order.
|
||||
for block in blocks {
|
||||
let import_result = import_single_block(link.chain(), blocks_origin.clone(), block);
|
||||
let is_import_failed = import_result.is_err();
|
||||
imported += process_import_result(link, import_result);
|
||||
if is_import_failed {
|
||||
qdata.map(|qdata| *qdata.best_importing_number.write() = Zero::zero());
|
||||
return true;
|
||||
}
|
||||
|
||||
if qdata.map(|qdata| qdata.is_stopping.load(Ordering::SeqCst)).unwrap_or_default() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
trace!(target: "sync", "Imported {} of {}", imported, count);
|
||||
link.maintain_sync();
|
||||
true
|
||||
}
|
||||
|
||||
/// Single block import function.
|
||||
fn import_single_block<B: BlockT>(
|
||||
chain: &Client<B>,
|
||||
block_origin: BlockOrigin,
|
||||
block: BlockData<B>
|
||||
) -> Result<BlockImportResult<B::Hash, <<B as BlockT>::Header as HeaderT>::Number>, BlockImportError>
|
||||
{
|
||||
let origin = block.origin;
|
||||
let block = block.block;
|
||||
match (block.header, block.justification) {
|
||||
(Some(header), Some(justification)) => {
|
||||
let number = header.number().clone();
|
||||
let hash = header.hash();
|
||||
let parent = header.parent_hash().clone();
|
||||
|
||||
let result = chain.import(
|
||||
block_origin,
|
||||
header,
|
||||
justification,
|
||||
block.body,
|
||||
);
|
||||
match result {
|
||||
Ok(ImportResult::AlreadyInChain) => {
|
||||
trace!(target: "sync", "Block already in chain {}: {:?}", number, hash);
|
||||
Ok(BlockImportResult::ImportedKnown(hash, number))
|
||||
},
|
||||
Ok(ImportResult::AlreadyQueued) => {
|
||||
trace!(target: "sync", "Block already queued {}: {:?}", number, hash);
|
||||
Ok(BlockImportResult::ImportedKnown(hash, number))
|
||||
},
|
||||
Ok(ImportResult::Queued) => {
|
||||
trace!(target: "sync", "Block queued {}: {:?}", number, hash);
|
||||
Ok(BlockImportResult::ImportedUnknown(hash, number))
|
||||
},
|
||||
Ok(ImportResult::UnknownParent) => {
|
||||
debug!(target: "sync", "Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent);
|
||||
Err(BlockImportError::Restart)
|
||||
},
|
||||
Ok(ImportResult::KnownBad) => {
|
||||
debug!(target: "sync", "Peer gave us a bad block {}: {:?}", number, hash);
|
||||
Err(BlockImportError::DisconnectAndRestart(origin)) //TODO: use persistent ID
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(target: "sync", "Error importing block {}: {:?}: {:?}", number, hash, e);
|
||||
Err(BlockImportError::Restart)
|
||||
}
|
||||
}
|
||||
},
|
||||
(None, _) => {
|
||||
debug!(target: "sync", "Header {} was not provided by {} ", block.hash, origin);
|
||||
Err(BlockImportError::Disconnect(origin)) //TODO: use persistent ID
|
||||
},
|
||||
(_, None) => {
|
||||
debug!(target: "sync", "Justification set for block {} was not provided by {} ", block.hash, origin);
|
||||
Err(BlockImportError::Disconnect(origin)) //TODO: use persistent ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Process single block import result.
|
||||
fn process_import_result<'a, B: BlockT>(
|
||||
link: &mut SyncLinkApi<B>,
|
||||
result: Result<BlockImportResult<B::Hash, <<B as BlockT>::Header as HeaderT>::Number>, BlockImportError>
|
||||
) -> usize
|
||||
{
|
||||
match result {
|
||||
Ok(BlockImportResult::ImportedKnown(hash, number)) => {
|
||||
link.block_imported(&hash, number);
|
||||
1
|
||||
},
|
||||
Ok(BlockImportResult::ImportedUnknown(hash, number)) => {
|
||||
link.block_imported(&hash, number);
|
||||
1
|
||||
},
|
||||
Err(BlockImportError::Disconnect(who)) => {
|
||||
// TODO: FIXME: @arkpar BlockImport shouldn't be trying to manage the peer set.
|
||||
// This should contain an actual reason.
|
||||
link.useless_peer(who, "Import result was stated Disconnect");
|
||||
0
|
||||
},
|
||||
Err(BlockImportError::DisconnectAndRestart(who)) => {
|
||||
// TODO: FIXME: @arkpar BlockImport shouldn't be trying to manage the peer set.
|
||||
// This should contain an actual reason.
|
||||
link.note_useless_and_restart_sync(who, "Import result was stated DisconnectAndRestart");
|
||||
0
|
||||
},
|
||||
Err(BlockImportError::Restart) => {
|
||||
link.restart();
|
||||
0
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B: 'static + BlockT, E: 'a + ExecuteInContext<B>> SyncLink<'a, B, E> {
|
||||
/// Execute closure with locked ChainSync.
|
||||
fn with_sync<F: Fn(&mut ChainSync<B>, &mut Context<B>)>(&mut self, closure: F) {
|
||||
match *self {
|
||||
#[cfg(test)]
|
||||
SyncLink::Direct(ref mut sync, ref mut protocol) =>
|
||||
closure(*sync, *protocol),
|
||||
SyncLink::Indirect(ref sync, _, ref service) =>
|
||||
service.execute_in_context(move |protocol| {
|
||||
let mut sync = sync.write();
|
||||
closure(&mut *sync, protocol)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B: 'static + BlockT, E: ExecuteInContext<B>> SyncLinkApi<B> for SyncLink<'a, B, E> {
|
||||
fn chain(&self) -> &Client<B> {
|
||||
match *self {
|
||||
#[cfg(test)]
|
||||
SyncLink::Direct(_, ref protocol) => protocol.client(),
|
||||
SyncLink::Indirect(_, ref chain, _) => *chain,
|
||||
}
|
||||
}
|
||||
|
||||
fn block_imported(&mut self, hash: &B::Hash, number: NumberFor<B>) {
|
||||
self.with_sync(|sync, _| sync.block_imported(&hash, number))
|
||||
}
|
||||
|
||||
fn maintain_sync(&mut self) {
|
||||
self.with_sync(|sync, protocol| sync.maintain_sync(protocol))
|
||||
}
|
||||
|
||||
fn useless_peer(&mut self, who: NodeIndex, reason: &str) {
|
||||
self.with_sync(|_, protocol| protocol.report_peer(who, Severity::Useless(reason)))
|
||||
}
|
||||
|
||||
fn note_useless_and_restart_sync(&mut self, who: NodeIndex, reason: &str) {
|
||||
self.with_sync(|sync, protocol| {
|
||||
protocol.report_peer(who, Severity::Useless(reason)); // is this actually malign or just useless?
|
||||
sync.restart(protocol);
|
||||
})
|
||||
}
|
||||
|
||||
fn restart(&mut self) {
|
||||
self.with_sync(|sync, protocol| sync.restart(protocol))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use client;
|
||||
use message;
|
||||
use test_client::{self, TestClient};
|
||||
use test_client::runtime::{Block, Hash};
|
||||
use on_demand::tests::DummyExecutor;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use super::*;
|
||||
|
||||
/// Blocks import queue that is importing blocks in the same thread.
|
||||
pub struct SyncImportQueue;
|
||||
struct DummyExecuteInContext;
|
||||
|
||||
impl<B: 'static + BlockT> ExecuteInContext<B> for DummyExecuteInContext {
|
||||
fn execute_in_context<F: Fn(&mut Context<B>)>(&self, _closure: F) { }
|
||||
}
|
||||
|
||||
impl<B: 'static + BlockT> ImportQueue<B> for SyncImportQueue {
|
||||
fn clear(&self) { }
|
||||
|
||||
fn stop(&self) { }
|
||||
|
||||
fn status(&self) -> ImportQueueStatus<B> {
|
||||
ImportQueueStatus {
|
||||
importing_count: 0,
|
||||
best_importing_number: Zero::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_importing(&self, _hash: &B::Hash) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn import_blocks(&self, sync: &mut ChainSync<B>, protocol: &mut Context<B>, blocks: (BlockOrigin, Vec<BlockData<B>>)) {
|
||||
import_many_blocks(&mut SyncLink::Direct::<_, DummyExecuteInContext>(sync, protocol), None, blocks);
|
||||
}
|
||||
}
|
||||
|
||||
struct TestLink {
|
||||
chain: Arc<Client<Block>>,
|
||||
imported: usize,
|
||||
maintains: usize,
|
||||
disconnects: usize,
|
||||
restarts: usize,
|
||||
}
|
||||
|
||||
impl TestLink {
|
||||
fn new() -> TestLink {
|
||||
TestLink {
|
||||
chain: Arc::new(test_client::new()),
|
||||
imported: 0,
|
||||
maintains: 0,
|
||||
disconnects: 0,
|
||||
restarts: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn total(&self) -> usize {
|
||||
self.imported + self.maintains + self.disconnects + self.restarts
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncLinkApi<Block> for TestLink {
|
||||
fn chain(&self) -> &Client<Block> { &*self.chain }
|
||||
fn block_imported(&mut self, _hash: &Hash, _number: NumberFor<Block>) { self.imported += 1; }
|
||||
fn maintain_sync(&mut self) { self.maintains += 1; }
|
||||
fn useless_peer(&mut self, _: NodeIndex, _: &str) { self.disconnects += 1; }
|
||||
fn note_useless_and_restart_sync(&mut self, _: NodeIndex, _: &str) { self.disconnects += 1; self.restarts += 1; }
|
||||
fn restart(&mut self) { self.restarts += 1; }
|
||||
}
|
||||
|
||||
fn prepare_good_block() -> (client::Client<test_client::Backend, test_client::Executor, Block>, Hash, u64, BlockData<Block>) {
|
||||
let client = test_client::new();
|
||||
let block = client.new_block().unwrap().bake().unwrap();
|
||||
client.justify_and_import(BlockOrigin::File, block).unwrap();
|
||||
|
||||
let (hash, number) = (client.block_hash(1).unwrap().unwrap(), 1);
|
||||
let block = message::BlockData::<Block> {
|
||||
hash: client.block_hash(1).unwrap().unwrap(),
|
||||
header: client.header(&BlockId::Number(1)).unwrap(),
|
||||
body: None,
|
||||
receipt: None,
|
||||
message_queue: None,
|
||||
justification: client.justification(&BlockId::Number(1)).unwrap(),
|
||||
};
|
||||
|
||||
(client, hash, number, BlockData { block, origin: 0 })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_single_good_block_works() {
|
||||
let (_, hash, number, block) = prepare_good_block();
|
||||
assert_eq!(import_single_block(&test_client::new(), BlockOrigin::File, block), Ok(BlockImportResult::ImportedUnknown(hash, number)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_single_good_known_block_is_ignored() {
|
||||
let (client, hash, number, block) = prepare_good_block();
|
||||
assert_eq!(import_single_block(&client, BlockOrigin::File, block), Ok(BlockImportResult::ImportedKnown(hash, number)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_single_good_block_without_header_fails() {
|
||||
let (_, _, _, mut block) = prepare_good_block();
|
||||
block.block.header = None;
|
||||
assert_eq!(import_single_block(&test_client::new(), BlockOrigin::File, block), Err(BlockImportError::Disconnect(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_single_good_block_without_justification_fails() {
|
||||
let (_, _, _, mut block) = prepare_good_block();
|
||||
block.block.justification = None;
|
||||
assert_eq!(import_single_block(&test_client::new(), BlockOrigin::File, block), Err(BlockImportError::Disconnect(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_import_result_works() {
|
||||
let mut link = TestLink::new();
|
||||
assert_eq!(process_import_result::<Block>(&mut link, Ok(BlockImportResult::ImportedKnown(Default::default(), 0))), 1);
|
||||
assert_eq!(link.total(), 1);
|
||||
|
||||
let mut link = TestLink::new();
|
||||
assert_eq!(process_import_result::<Block>(&mut link, Ok(BlockImportResult::ImportedKnown(Default::default(), 0))), 1);
|
||||
assert_eq!(link.total(), 1);
|
||||
assert_eq!(link.imported, 1);
|
||||
|
||||
let mut link = TestLink::new();
|
||||
assert_eq!(process_import_result::<Block>(&mut link, Ok(BlockImportResult::ImportedUnknown(Default::default(), 0))), 1);
|
||||
assert_eq!(link.total(), 1);
|
||||
assert_eq!(link.imported, 1);
|
||||
|
||||
let mut link = TestLink::new();
|
||||
assert_eq!(process_import_result::<Block>(&mut link, Err(BlockImportError::Disconnect(0))), 0);
|
||||
assert_eq!(link.total(), 1);
|
||||
assert_eq!(link.disconnects, 1);
|
||||
|
||||
let mut link = TestLink::new();
|
||||
assert_eq!(process_import_result::<Block>(&mut link, Err(BlockImportError::DisconnectAndRestart(0))), 0);
|
||||
assert_eq!(link.total(), 2);
|
||||
assert_eq!(link.disconnects, 1);
|
||||
assert_eq!(link.restarts, 1);
|
||||
|
||||
let mut link = TestLink::new();
|
||||
assert_eq!(process_import_result::<Block>(&mut link, Err(BlockImportError::Restart)), 0);
|
||||
assert_eq!(link.total(), 1);
|
||||
assert_eq!(link.restarts, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_many_blocks_stops_when_stopping() {
|
||||
let (_, _, _, block) = prepare_good_block();
|
||||
let qdata = AsyncImportQueueData::new();
|
||||
qdata.is_stopping.store(true, Ordering::SeqCst);
|
||||
assert!(!import_many_blocks(&mut TestLink::new(), Some(&qdata), (BlockOrigin::File, vec![block.clone(), block])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_import_queue_drops() {
|
||||
let queue = AsyncImportQueue::new();
|
||||
let service = Arc::new(DummyExecutor);
|
||||
let chain = Arc::new(test_client::new());
|
||||
queue.start(Weak::new(), Arc::downgrade(&service), Arc::downgrade(&chain) as Weak<Client<Block>>).unwrap();
|
||||
drop(queue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use network_libp2p::{NetworkContext, Severity, NodeIndex, SessionInfo};
|
||||
|
||||
/// IO interface for the syncing handler.
|
||||
/// Provides peer connection management and an interface to the blockchain client.
|
||||
pub trait SyncIo {
|
||||
/// Report a peer for misbehaviour.
|
||||
fn report_peer(&mut self, who: NodeIndex, reason: Severity);
|
||||
/// Send a packet to a peer.
|
||||
fn send(&mut self, who: NodeIndex, data: Vec<u8>);
|
||||
/// Returns peer identifier string
|
||||
fn peer_info(&self, who: NodeIndex) -> String {
|
||||
who.to_string()
|
||||
}
|
||||
/// Returns information on p2p session
|
||||
fn peer_session_info(&self, who: NodeIndex) -> Option<SessionInfo>;
|
||||
/// Check if the session is expired
|
||||
fn is_expired(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Wraps `NetworkContext` and the blockchain client
|
||||
pub struct NetSyncIo<'s> {
|
||||
network: &'s NetworkContext,
|
||||
}
|
||||
|
||||
impl<'s> NetSyncIo<'s> {
|
||||
/// Creates a new instance from the `NetworkContext` and the blockchain client reference.
|
||||
pub fn new(network: &'s NetworkContext) -> NetSyncIo<'s> {
|
||||
NetSyncIo {
|
||||
network: network,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> SyncIo for NetSyncIo<'s> {
|
||||
fn report_peer(&mut self, who: NodeIndex, reason: Severity) {
|
||||
self.network.report_peer(who, reason);
|
||||
}
|
||||
|
||||
fn send(&mut self, who: NodeIndex, data: Vec<u8>) {
|
||||
self.network.send(who, 0, data)
|
||||
}
|
||||
|
||||
fn peer_session_info(&self, who: NodeIndex) -> Option<SessionInfo> {
|
||||
self.network.session_info(who)
|
||||
}
|
||||
|
||||
fn is_expired(&self) -> bool {
|
||||
self.network.is_expired()
|
||||
}
|
||||
|
||||
fn peer_info(&self, who: NodeIndex) -> String {
|
||||
self.network.peer_client_version(who)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![warn(unused_extern_crates)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
// tag::description[]
|
||||
//! Substrate-specific P2P networking: synchronizing blocks, propagating BFT messages.
|
||||
//! Allows attachment of an optional subprotocol for chain-specific requests.
|
||||
// end::description[]
|
||||
|
||||
extern crate ethcore_io as core_io;
|
||||
extern crate linked_hash_map;
|
||||
extern crate parking_lot;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate substrate_client as client;
|
||||
extern crate sr_primitives as runtime_primitives;
|
||||
extern crate substrate_network_libp2p as network_libp2p;
|
||||
extern crate parity_codec as codec;
|
||||
extern crate futures;
|
||||
extern crate rustc_hex;
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate bitflags;
|
||||
#[macro_use] extern crate error_chain;
|
||||
#[macro_use] extern crate parity_codec_derive;
|
||||
|
||||
#[cfg(test)] extern crate env_logger;
|
||||
#[cfg(test)] extern crate substrate_keyring as keyring;
|
||||
#[cfg(test)] extern crate substrate_test_client as test_client;
|
||||
|
||||
mod service;
|
||||
mod sync;
|
||||
mod protocol;
|
||||
mod io;
|
||||
mod config;
|
||||
mod chain;
|
||||
mod blocks;
|
||||
mod on_demand;
|
||||
mod import_queue;
|
||||
pub mod consensus_gossip;
|
||||
pub mod error;
|
||||
pub mod message;
|
||||
pub mod specialization;
|
||||
|
||||
#[cfg(test)] mod test;
|
||||
|
||||
pub use chain::Client as ClientHandle;
|
||||
pub use service::{Service, FetchFuture, ConsensusService, BftMessageStream,
|
||||
TransactionPool, Params, ManageNetwork, SyncProvider};
|
||||
pub use protocol::{ProtocolStatus, PeerInfo, Context};
|
||||
pub use sync::{Status as SyncStatus, SyncState};
|
||||
pub use network_libp2p::{NonReservedPeerMode, NetworkConfiguration, NodeIndex, ProtocolId, ConnectionFilter, ConnectionDirection, Severity};
|
||||
pub use message::{generic as generic_message, RequestId, BftMessage, LocalizedBftMessage, ConsensusVote, SignedConsensusVote, SignedConsensusMessage, SignedConsensusProposal, Status as StatusMessage};
|
||||
pub use error::Error;
|
||||
pub use config::{Roles, ProtocolConfig};
|
||||
pub use on_demand::{OnDemand, OnDemandService, RemoteResponse};
|
||||
@@ -0,0 +1,375 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Network packet message types. These get serialized and put into the lower level protocol payload.
|
||||
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use codec::{Encode, Decode, Input, Output};
|
||||
pub use self::generic::{
|
||||
BlockAnnounce, RemoteCallRequest, RemoteReadRequest,
|
||||
RemoteHeaderRequest, RemoteHeaderResponse, ConsensusVote,
|
||||
SignedConsensusVote, FromBlock
|
||||
};
|
||||
|
||||
/// A unique ID of a request.
|
||||
pub type RequestId = u64;
|
||||
|
||||
/// Type alias for using the message type using block type parameters.
|
||||
pub type Message<B> = generic::Message<
|
||||
B,
|
||||
<B as BlockT>::Header,
|
||||
<B as BlockT>::Hash,
|
||||
<<B as BlockT>::Header as HeaderT>::Number,
|
||||
<B as BlockT>::Extrinsic,
|
||||
>;
|
||||
|
||||
/// Type alias for using the status type using block type parameters.
|
||||
pub type Status<B> = generic::Status<
|
||||
<B as BlockT>::Hash,
|
||||
<<B as BlockT>::Header as HeaderT>::Number,
|
||||
>;
|
||||
|
||||
/// Type alias for using the block request type using block type parameters.
|
||||
pub type BlockRequest<B> = generic::BlockRequest<
|
||||
<B as BlockT>::Hash,
|
||||
<<B as BlockT>::Header as HeaderT>::Number,
|
||||
>;
|
||||
|
||||
/// Type alias for using the localized bft message type using block type parameters.
|
||||
pub type LocalizedBftMessage<B> = generic::LocalizedBftMessage<
|
||||
B,
|
||||
<B as BlockT>::Hash,
|
||||
>;
|
||||
|
||||
/// Type alias for using the BlockData type using block type parameters.
|
||||
pub type BlockData<B> = generic::BlockData<
|
||||
<B as BlockT>::Header,
|
||||
<B as BlockT>::Hash,
|
||||
<B as BlockT>::Extrinsic,
|
||||
>;
|
||||
|
||||
/// Type alias for using the BlockResponse type using block type parameters.
|
||||
pub type BlockResponse<B> = generic::BlockResponse<
|
||||
<B as BlockT>::Header,
|
||||
<B as BlockT>::Hash,
|
||||
<B as BlockT>::Extrinsic,
|
||||
>;
|
||||
|
||||
/// Type alias for using the BftMessage type using block type parameters.
|
||||
pub type BftMessage<B> = generic::BftMessage<
|
||||
B,
|
||||
<B as BlockT>::Hash,
|
||||
>;
|
||||
|
||||
/// Type alias for using the SignedConsensusProposal type using block type parameters.
|
||||
pub type SignedConsensusProposal<B> = generic::SignedConsensusProposal<
|
||||
B,
|
||||
<B as BlockT>::Hash,
|
||||
>;
|
||||
|
||||
/// Type alias for using the SignedConsensusProposal type using block type parameters.
|
||||
pub type SignedConsensusMessage<B> = generic::SignedConsensusProposal<
|
||||
B,
|
||||
<B as BlockT>::Hash,
|
||||
>;
|
||||
|
||||
/// A set of transactions.
|
||||
pub type Transactions<E> = Vec<E>;
|
||||
|
||||
/// Bits of block data and associated artefacts to request.
|
||||
bitflags! {
|
||||
/// Node roles bitmask.
|
||||
pub struct BlockAttributes: u8 {
|
||||
/// Include block header.
|
||||
const HEADER = 0b00000001;
|
||||
/// Include block body.
|
||||
const BODY = 0b00000010;
|
||||
/// Include block receipt.
|
||||
const RECEIPT = 0b00000100;
|
||||
/// Include block message queue.
|
||||
const MESSAGE_QUEUE = 0b00001000;
|
||||
/// Include a justification for the block.
|
||||
const JUSTIFICATION = 0b00010000;
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for BlockAttributes {
|
||||
fn encode_to<T: Output>(&self, dest: &mut T) {
|
||||
dest.push_byte(self.bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for BlockAttributes {
|
||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||
Self::from_bits(input.read_byte()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode)]
|
||||
/// Block enumeration direction.
|
||||
pub enum Direction {
|
||||
/// Enumerate in ascending order (from child to parent).
|
||||
Ascending = 0,
|
||||
/// Enumerate in descendfing order (from parent to canonical child).
|
||||
Descending = 1,
|
||||
}
|
||||
|
||||
/// Remote call response.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct RemoteCallResponse {
|
||||
/// Id of a request this response was made for.
|
||||
pub id: RequestId,
|
||||
/// Execution proof.
|
||||
pub proof: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
/// Remote read response.
|
||||
pub struct RemoteReadResponse {
|
||||
/// Id of a request this response was made for.
|
||||
pub id: RequestId,
|
||||
/// Read proof.
|
||||
pub proof: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// Generic types.
|
||||
pub mod generic {
|
||||
use primitives::{AuthorityId, ed25519};
|
||||
use runtime_primitives::bft::Justification;
|
||||
use service::Roles;
|
||||
use super::{
|
||||
BlockAttributes, RemoteCallResponse, RemoteReadResponse,
|
||||
RequestId, Transactions, Direction
|
||||
};
|
||||
|
||||
/// Block data sent in the response.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct BlockData<Header, Hash, Extrinsic> {
|
||||
/// Block header hash.
|
||||
pub hash: Hash,
|
||||
/// Block header if requested.
|
||||
pub header: Option<Header>,
|
||||
/// Block body if requested.
|
||||
pub body: Option<Vec<Extrinsic>>,
|
||||
/// Block receipt if requested.
|
||||
pub receipt: Option<Vec<u8>>,
|
||||
/// Block message queue if requested.
|
||||
pub message_queue: Option<Vec<u8>>,
|
||||
/// Justification if requested.
|
||||
pub justification: Option<Justification<Hash>>,
|
||||
}
|
||||
|
||||
/// Identifies starting point of a block sequence.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub enum FromBlock<Hash, Number> {
|
||||
/// Start with given hash.
|
||||
Hash(Hash),
|
||||
/// Start with given block number.
|
||||
Number(Number),
|
||||
}
|
||||
|
||||
/// Communication that can occur between participants in consensus.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub enum BftMessage<Block, Hash> {
|
||||
/// A consensus message (proposal or vote)
|
||||
Consensus(SignedConsensusMessage<Block, Hash>),
|
||||
/// Auxiliary communication (just proof-of-lock for now).
|
||||
Auxiliary(Justification<Hash>),
|
||||
}
|
||||
|
||||
/// BFT Consensus message with parent header hash attached to it.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct LocalizedBftMessage<Block, Hash> {
|
||||
/// Consensus message.
|
||||
pub message: BftMessage<Block, Hash>,
|
||||
/// Parent header hash.
|
||||
pub parent_hash: Hash,
|
||||
}
|
||||
|
||||
/// A localized proposal message. Contains two signed pieces of data.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct SignedConsensusProposal<Block, Hash> {
|
||||
/// The round number.
|
||||
pub round_number: u32,
|
||||
/// The proposal sent.
|
||||
pub proposal: Block,
|
||||
/// The digest of the proposal.
|
||||
pub digest: Hash,
|
||||
/// The sender of the proposal
|
||||
pub sender: AuthorityId,
|
||||
/// The signature on the message (propose, round number, digest)
|
||||
pub digest_signature: ed25519::Signature,
|
||||
/// The signature on the message (propose, round number, proposal)
|
||||
pub full_signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
/// A localized vote message, including the sender.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct SignedConsensusVote<H> {
|
||||
/// The message sent.
|
||||
pub vote: ConsensusVote<H>,
|
||||
/// The sender of the message
|
||||
pub sender: AuthorityId,
|
||||
/// The signature of the message.
|
||||
pub signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
/// Votes during a consensus round.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub enum ConsensusVote<H> {
|
||||
/// Prepare to vote for proposal with digest D.
|
||||
Prepare(u32, H),
|
||||
/// Commit to proposal with digest D..
|
||||
Commit(u32, H),
|
||||
/// Propose advancement to a new round.
|
||||
AdvanceRound(u32),
|
||||
}
|
||||
|
||||
/// A localized message.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub enum SignedConsensusMessage<Block, Hash> {
|
||||
/// A proposal.
|
||||
Propose(SignedConsensusProposal<Block, Hash>),
|
||||
/// A vote.
|
||||
Vote(SignedConsensusVote<Hash>),
|
||||
}
|
||||
|
||||
/// A network message.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub enum Message<Block, Header, Hash, Number, Extrinsic> {
|
||||
/// Status packet.
|
||||
Status(Status<Hash, Number>),
|
||||
/// Block request.
|
||||
BlockRequest(BlockRequest<Hash, Number>),
|
||||
/// Block response.
|
||||
BlockResponse(BlockResponse<Header, Hash, Extrinsic>),
|
||||
/// Block announce.
|
||||
BlockAnnounce(BlockAnnounce<Header>),
|
||||
/// Transactions.
|
||||
Transactions(Transactions<Extrinsic>),
|
||||
/// BFT Consensus statement.
|
||||
BftMessage(LocalizedBftMessage<Block, Hash>),
|
||||
/// Remote method call request.
|
||||
RemoteCallRequest(RemoteCallRequest<Hash>),
|
||||
/// Remote method call response.
|
||||
RemoteCallResponse(RemoteCallResponse),
|
||||
/// Remote storage read request.
|
||||
RemoteReadRequest(RemoteReadRequest<Hash>),
|
||||
/// Remote storage read response.
|
||||
RemoteReadResponse(RemoteReadResponse),
|
||||
/// Remote header request.
|
||||
RemoteHeaderRequest(RemoteHeaderRequest<Number>),
|
||||
/// Remote header response.
|
||||
RemoteHeaderResponse(RemoteHeaderResponse<Header>),
|
||||
/// Chain-specific message
|
||||
#[codec(index = "255")]
|
||||
ChainSpecific(Vec<u8>),
|
||||
}
|
||||
|
||||
/// Status sent on connection.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct Status<Hash, Number> {
|
||||
/// Protocol version.
|
||||
pub version: u32,
|
||||
/// Supported roles.
|
||||
pub roles: Roles,
|
||||
/// Best block number.
|
||||
pub best_number: Number,
|
||||
/// Best block hash.
|
||||
pub best_hash: Hash,
|
||||
/// Genesis block hash.
|
||||
pub genesis_hash: Hash,
|
||||
/// Chain-specific status.
|
||||
pub chain_status: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Request block data from a peer.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct BlockRequest<Hash, Number> {
|
||||
/// Unique request id.
|
||||
pub id: RequestId,
|
||||
/// Bits of block data to request.
|
||||
pub fields: BlockAttributes,
|
||||
/// Start from this block.
|
||||
pub from: FromBlock<Hash, Number>,
|
||||
/// End at this block. An implementation defined maximum is used when unspecified.
|
||||
pub to: Option<Hash>,
|
||||
/// Sequence direction.
|
||||
pub direction: Direction,
|
||||
/// Maximum number of blocks to return. An implementation defined maximum is used when unspecified.
|
||||
pub max: Option<u32>,
|
||||
}
|
||||
|
||||
/// Response to `BlockRequest`
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct BlockResponse<Header, Hash, Extrinsic> {
|
||||
/// Id of a request this response was made for.
|
||||
pub id: RequestId,
|
||||
/// Block data for the requested sequence.
|
||||
pub blocks: Vec<BlockData<Header, Hash, Extrinsic>>,
|
||||
}
|
||||
|
||||
/// Announce a new complete relay chain block on the network.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct BlockAnnounce<H> {
|
||||
/// New block header.
|
||||
pub header: H,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
/// Remote call request.
|
||||
pub struct RemoteCallRequest<H> {
|
||||
/// Unique request id.
|
||||
pub id: RequestId,
|
||||
/// Block at which to perform call.
|
||||
pub block: H,
|
||||
/// Method name.
|
||||
pub method: String,
|
||||
/// Call data.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
/// Remote storage read request.
|
||||
pub struct RemoteReadRequest<H> {
|
||||
/// Unique request id.
|
||||
pub id: RequestId,
|
||||
/// Block at which to perform call.
|
||||
pub block: H,
|
||||
/// Storage key.
|
||||
pub key: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
/// Remote header request.
|
||||
pub struct RemoteHeaderRequest<N> {
|
||||
/// Unique request id.
|
||||
pub id: RequestId,
|
||||
/// Block number to request header for.
|
||||
pub block: N,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
/// Remote header response.
|
||||
pub struct RemoteHeaderResponse<Header> {
|
||||
/// Id of a request this response was made for.
|
||||
pub id: RequestId,
|
||||
/// Header. None if proof generation has failed (e.g. header is unknown).
|
||||
pub header: Option<Header>,
|
||||
/// Header proof.
|
||||
pub proof: Vec<Vec<u8>>,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,736 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! On-demand requests service.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::{Instant, Duration};
|
||||
use futures::{Async, Future, Poll};
|
||||
use futures::sync::oneshot::{channel, Receiver, Sender};
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use linked_hash_map::Entry;
|
||||
use parking_lot::Mutex;
|
||||
use client;
|
||||
use client::light::fetcher::{Fetcher, FetchChecker, RemoteHeaderRequest,
|
||||
RemoteCallRequest, RemoteReadRequest};
|
||||
use io::SyncIo;
|
||||
use message;
|
||||
use network_libp2p::{Severity, NodeIndex};
|
||||
use service;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
|
||||
/// Remote request timeout.
|
||||
const REQUEST_TIMEOUT: Duration = Duration::from_secs(15);
|
||||
/// Default request retry count.
|
||||
const RETRY_COUNT: usize = 1;
|
||||
|
||||
/// On-demand service API.
|
||||
pub trait OnDemandService<Block: BlockT>: Send + Sync {
|
||||
/// When new node is connected.
|
||||
fn on_connect(&self, peer: NodeIndex, role: service::Roles);
|
||||
|
||||
/// When node is disconnected.
|
||||
fn on_disconnect(&self, peer: NodeIndex);
|
||||
|
||||
/// Maintain peers requests.
|
||||
fn maintain_peers(&self, io: &mut SyncIo);
|
||||
|
||||
/// When header response is received from remote node.
|
||||
fn on_remote_header_response(
|
||||
&self,
|
||||
io: &mut SyncIo,
|
||||
peer: NodeIndex,
|
||||
response: message::RemoteHeaderResponse<Block::Header>
|
||||
);
|
||||
|
||||
/// When read response is received from remote node.
|
||||
fn on_remote_read_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteReadResponse);
|
||||
|
||||
/// When call response is received from remote node.
|
||||
fn on_remote_call_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteCallResponse);
|
||||
}
|
||||
|
||||
/// On-demand requests service. Dispatches requests to appropriate peers.
|
||||
pub struct OnDemand<B: BlockT, E: service::ExecuteInContext<B>> {
|
||||
core: Mutex<OnDemandCore<B, E>>,
|
||||
checker: Arc<FetchChecker<B>>,
|
||||
}
|
||||
|
||||
/// On-demand remote call response.
|
||||
pub struct RemoteResponse<T> {
|
||||
receiver: Receiver<Result<T, client::error::Error>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct OnDemandCore<B: BlockT, E: service::ExecuteInContext<B>> {
|
||||
service: Weak<E>,
|
||||
next_request_id: u64,
|
||||
pending_requests: VecDeque<Request<B>>,
|
||||
active_peers: LinkedHashMap<NodeIndex, Request<B>>,
|
||||
idle_peers: VecDeque<NodeIndex>,
|
||||
}
|
||||
|
||||
struct Request<Block: BlockT> {
|
||||
id: u64,
|
||||
timestamp: Instant,
|
||||
retry_count: usize,
|
||||
data: RequestData<Block>,
|
||||
}
|
||||
|
||||
enum RequestData<Block: BlockT> {
|
||||
RemoteHeader(RemoteHeaderRequest<Block::Header>, Sender<Result<Block::Header, client::error::Error>>),
|
||||
RemoteRead(RemoteReadRequest<Block::Header>, Sender<Result<Option<Vec<u8>>, client::error::Error>>),
|
||||
RemoteCall(RemoteCallRequest<Block::Header>, Sender<Result<client::CallResult, client::error::Error>>),
|
||||
}
|
||||
|
||||
enum Accept<Block: BlockT> {
|
||||
Ok,
|
||||
CheckFailed(client::error::Error, RequestData<Block>),
|
||||
Unexpected(RequestData<Block>),
|
||||
}
|
||||
|
||||
impl<T> Future for RemoteResponse<T> {
|
||||
type Item = T;
|
||||
type Error = client::error::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
self.receiver.poll()
|
||||
.map_err(|_| client::error::ErrorKind::RemoteFetchCancelled.into())
|
||||
.and_then(|r| match r {
|
||||
Async::Ready(Ok(ready)) => Ok(Async::Ready(ready)),
|
||||
Async::Ready(Err(error)) => Err(error),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT, E> OnDemand<B, E> where
|
||||
E: service::ExecuteInContext<B>,
|
||||
B::Header: HeaderT,
|
||||
{
|
||||
/// Creates new on-demand service.
|
||||
pub fn new(checker: Arc<FetchChecker<B>>) -> Self {
|
||||
OnDemand {
|
||||
checker,
|
||||
core: Mutex::new(OnDemandCore {
|
||||
service: Weak::new(),
|
||||
next_request_id: 0,
|
||||
pending_requests: VecDeque::new(),
|
||||
active_peers: LinkedHashMap::new(),
|
||||
idle_peers: VecDeque::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets weak reference to network service.
|
||||
pub fn set_service_link(&self, service: Weak<E>) {
|
||||
self.core.lock().service = service;
|
||||
}
|
||||
|
||||
/// Schedule && dispatch all scheduled requests.
|
||||
fn schedule_request<R>(&self, retry_count: Option<usize>, data: RequestData<B>, result: R) -> R {
|
||||
let mut core = self.core.lock();
|
||||
core.insert(retry_count.unwrap_or(RETRY_COUNT), data);
|
||||
core.dispatch();
|
||||
result
|
||||
}
|
||||
|
||||
/// Try to accept response from given peer.
|
||||
fn accept_response<F: FnOnce(Request<B>) -> Accept<B>>(&self, rtype: &str, io: &mut SyncIo, peer: NodeIndex, request_id: u64, try_accept: F) {
|
||||
let mut core = self.core.lock();
|
||||
let request = match core.remove(peer, request_id) {
|
||||
Some(request) => request,
|
||||
None => {
|
||||
io.report_peer(peer, Severity::Bad(&format!("Invalid remote {} response from peer", rtype)));
|
||||
core.remove_peer(peer);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let retry_count = request.retry_count;
|
||||
let (retry_count, retry_request_data) = match try_accept(request) {
|
||||
Accept::Ok => (retry_count, None),
|
||||
Accept::CheckFailed(error, retry_request_data) => {
|
||||
io.report_peer(peer, Severity::Bad(&format!("Failed to check remote {} response from peer: {}", rtype, error)));
|
||||
core.remove_peer(peer);
|
||||
|
||||
if retry_count > 0 {
|
||||
(retry_count - 1, Some(retry_request_data))
|
||||
} else {
|
||||
trace!(target: "sync", "Failed to get remote {} response for given number of retries", rtype);
|
||||
retry_request_data.fail(client::error::ErrorKind::RemoteFetchFailed.into());
|
||||
(0, None)
|
||||
}
|
||||
},
|
||||
Accept::Unexpected(retry_request_data) => {
|
||||
io.report_peer(peer, Severity::Bad(&format!("Unexpected response to remote {} from peer", rtype)));
|
||||
core.remove_peer(peer);
|
||||
|
||||
(retry_count, Some(retry_request_data))
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(request_data) = retry_request_data {
|
||||
core.insert(retry_count, request_data);
|
||||
}
|
||||
|
||||
core.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> OnDemandService<B> for OnDemand<B, E> where
|
||||
B: BlockT,
|
||||
E: service::ExecuteInContext<B>,
|
||||
B::Header: HeaderT,
|
||||
{
|
||||
fn on_connect(&self, peer: NodeIndex, role: service::Roles) {
|
||||
if !role.intersects(service::Roles::FULL | service::Roles::AUTHORITY) { // TODO: correct?
|
||||
return;
|
||||
}
|
||||
|
||||
let mut core = self.core.lock();
|
||||
core.add_peer(peer);
|
||||
core.dispatch();
|
||||
}
|
||||
|
||||
fn on_disconnect(&self, peer: NodeIndex) {
|
||||
let mut core = self.core.lock();
|
||||
core.remove_peer(peer);
|
||||
core.dispatch();
|
||||
}
|
||||
|
||||
fn maintain_peers(&self, io: &mut SyncIo) {
|
||||
let mut core = self.core.lock();
|
||||
for bad_peer in core.maintain_peers() {
|
||||
io.report_peer(bad_peer, Severity::Timeout);
|
||||
}
|
||||
core.dispatch();
|
||||
}
|
||||
|
||||
fn on_remote_header_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteHeaderResponse<B::Header>) {
|
||||
self.accept_response("header", io, peer, response.id, |request| match request.data {
|
||||
RequestData::RemoteHeader(request, sender) => match self.checker.check_header_proof(&request, response.header, response.proof) {
|
||||
Ok(response) => {
|
||||
// we do not bother if receiver has been dropped already
|
||||
let _ = sender.send(Ok(response));
|
||||
Accept::Ok
|
||||
},
|
||||
Err(error) => Accept::CheckFailed(error, RequestData::RemoteHeader(request, sender)),
|
||||
},
|
||||
data @ _ => Accept::Unexpected(data),
|
||||
})
|
||||
}
|
||||
|
||||
fn on_remote_read_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteReadResponse) {
|
||||
self.accept_response("read", io, peer, response.id, |request| match request.data {
|
||||
RequestData::RemoteRead(request, sender) => match self.checker.check_read_proof(&request, response.proof) {
|
||||
Ok(response) => {
|
||||
// we do not bother if receiver has been dropped already
|
||||
let _ = sender.send(Ok(response));
|
||||
Accept::Ok
|
||||
},
|
||||
Err(error) => Accept::CheckFailed(error, RequestData::RemoteRead(request, sender)),
|
||||
},
|
||||
data @ _ => Accept::Unexpected(data),
|
||||
})
|
||||
}
|
||||
|
||||
fn on_remote_call_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteCallResponse) {
|
||||
self.accept_response("call", io, peer, response.id, |request| match request.data {
|
||||
RequestData::RemoteCall(request, sender) => match self.checker.check_execution_proof(&request, response.proof) {
|
||||
Ok(response) => {
|
||||
// we do not bother if receiver has been dropped already
|
||||
let _ = sender.send(Ok(response));
|
||||
Accept::Ok
|
||||
},
|
||||
Err(error) => Accept::CheckFailed(error, RequestData::RemoteCall(request, sender)),
|
||||
},
|
||||
data @ _ => Accept::Unexpected(data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> Fetcher<B> for OnDemand<B, E> where
|
||||
B: BlockT,
|
||||
E: service::ExecuteInContext<B>,
|
||||
B::Header: HeaderT,
|
||||
{
|
||||
type RemoteHeaderResult = RemoteResponse<B::Header>;
|
||||
type RemoteReadResult = RemoteResponse<Option<Vec<u8>>>;
|
||||
type RemoteCallResult = RemoteResponse<client::CallResult>;
|
||||
|
||||
fn remote_header(&self, request: RemoteHeaderRequest<B::Header>) -> Self::RemoteHeaderResult {
|
||||
let (sender, receiver) = channel();
|
||||
self.schedule_request(request.retry_count.clone(), RequestData::RemoteHeader(request, sender),
|
||||
RemoteResponse { receiver })
|
||||
}
|
||||
|
||||
fn remote_read(&self, request: RemoteReadRequest<B::Header>) -> Self::RemoteReadResult {
|
||||
let (sender, receiver) = channel();
|
||||
self.schedule_request(request.retry_count.clone(), RequestData::RemoteRead(request, sender),
|
||||
RemoteResponse { receiver })
|
||||
}
|
||||
|
||||
fn remote_call(&self, request: RemoteCallRequest<B::Header>) -> Self::RemoteCallResult {
|
||||
let (sender, receiver) = channel();
|
||||
self.schedule_request(request.retry_count.clone(), RequestData::RemoteCall(request, sender),
|
||||
RemoteResponse { receiver })
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> OnDemandCore<B, E> where
|
||||
B: BlockT,
|
||||
E: service::ExecuteInContext<B>,
|
||||
B::Header: HeaderT,
|
||||
{
|
||||
pub fn add_peer(&mut self, peer: NodeIndex) {
|
||||
self.idle_peers.push_back(peer);
|
||||
}
|
||||
|
||||
pub fn remove_peer(&mut self, peer: NodeIndex) {
|
||||
if let Some(request) = self.active_peers.remove(&peer) {
|
||||
self.pending_requests.push_front(request);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(idle_index) = self.idle_peers.iter().position(|i| *i == peer) {
|
||||
self.idle_peers.swap_remove_back(idle_index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maintain_peers(&mut self) -> Vec<NodeIndex> {
|
||||
let now = Instant::now();
|
||||
let mut bad_peers = Vec::new();
|
||||
loop {
|
||||
match self.active_peers.front() {
|
||||
Some((_, request)) if now - request.timestamp >= REQUEST_TIMEOUT => (),
|
||||
_ => return bad_peers,
|
||||
}
|
||||
|
||||
let (bad_peer, request) = self.active_peers.pop_front().expect("front() is Some as checked above");
|
||||
self.pending_requests.push_front(request);
|
||||
bad_peers.push(bad_peer);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, retry_count: usize, data: RequestData<B>) {
|
||||
let request_id = self.next_request_id;
|
||||
self.next_request_id += 1;
|
||||
|
||||
self.pending_requests.push_back(Request {
|
||||
id: request_id,
|
||||
timestamp: Instant::now(),
|
||||
retry_count,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, peer: NodeIndex, id: u64) -> Option<Request<B>> {
|
||||
match self.active_peers.entry(peer) {
|
||||
Entry::Occupied(entry) => match entry.get().id == id {
|
||||
true => {
|
||||
self.idle_peers.push_back(peer);
|
||||
Some(entry.remove())
|
||||
},
|
||||
false => None,
|
||||
},
|
||||
Entry::Vacant(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch(&mut self) {
|
||||
let service = match self.service.upgrade() {
|
||||
Some(service) => service,
|
||||
None => return,
|
||||
};
|
||||
|
||||
while !self.pending_requests.is_empty() {
|
||||
let peer = match self.idle_peers.pop_front() {
|
||||
Some(peer) => peer,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut request = self.pending_requests.pop_front().expect("checked in loop condition; qed");
|
||||
request.timestamp = Instant::now();
|
||||
trace!(target: "sync", "Dispatching remote request {} to peer {}", request.id, peer);
|
||||
|
||||
service.execute_in_context(|ctx| ctx.send_message(peer, request.message()));
|
||||
self.active_peers.insert(peer, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> Request<Block> {
|
||||
pub fn message(&self) -> message::Message<Block> {
|
||||
match self.data {
|
||||
RequestData::RemoteHeader(ref data, _) => message::generic::Message::RemoteHeaderRequest(
|
||||
message::RemoteHeaderRequest {
|
||||
id: self.id,
|
||||
block: data.block,
|
||||
}),
|
||||
RequestData::RemoteRead(ref data, _) => message::generic::Message::RemoteReadRequest(
|
||||
message::RemoteReadRequest {
|
||||
id: self.id,
|
||||
block: data.block,
|
||||
key: data.key.clone(),
|
||||
}),
|
||||
RequestData::RemoteCall(ref data, _) => message::generic::Message::RemoteCallRequest(
|
||||
message::RemoteCallRequest {
|
||||
id: self.id,
|
||||
block: data.block,
|
||||
method: data.method.clone(),
|
||||
data: data.call_data.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> RequestData<Block> {
|
||||
pub fn fail(self, error: client::error::Error) {
|
||||
// don't care if anyone is listening
|
||||
match self {
|
||||
RequestData::RemoteHeader(_, sender) => { let _ = sender.send(Err(error)); },
|
||||
RequestData::RemoteCall(_, sender) => { let _ = sender.send(Err(error)); },
|
||||
RequestData::RemoteRead(_, sender) => { let _ = sender.send(Err(error)); },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use futures::Future;
|
||||
use parking_lot::RwLock;
|
||||
use client;
|
||||
use client::light::fetcher::{Fetcher, FetchChecker, RemoteHeaderRequest,
|
||||
RemoteCallRequest, RemoteReadRequest};
|
||||
use message;
|
||||
use network_libp2p::NodeIndex;
|
||||
use service::{Roles, ExecuteInContext};
|
||||
use test::TestIo;
|
||||
use super::{REQUEST_TIMEOUT, OnDemand, OnDemandService};
|
||||
use test_client::runtime::{Block, Header};
|
||||
|
||||
pub struct DummyExecutor;
|
||||
struct DummyFetchChecker { ok: bool }
|
||||
|
||||
impl ExecuteInContext<Block> for DummyExecutor {
|
||||
fn execute_in_context<F: Fn(&mut ::protocol::Context<Block>)>(&self, _closure: F) {}
|
||||
}
|
||||
|
||||
impl FetchChecker<Block> for DummyFetchChecker {
|
||||
fn check_header_proof(
|
||||
&self,
|
||||
_request: &RemoteHeaderRequest<Header>,
|
||||
header: Option<Header>,
|
||||
_remote_proof: Vec<Vec<u8>>
|
||||
) -> client::error::Result<Header> {
|
||||
match self.ok {
|
||||
true if header.is_some() => Ok(header.unwrap()),
|
||||
_ => Err(client::error::ErrorKind::Backend("Test error".into()).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_read_proof(&self, _request: &RemoteReadRequest<Header>, _remote_proof: Vec<Vec<u8>>) -> client::error::Result<Option<Vec<u8>>> {
|
||||
match self.ok {
|
||||
true => Ok(Some(vec![42])),
|
||||
false => Err(client::error::ErrorKind::Backend("Test error".into()).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_execution_proof(&self, _request: &RemoteCallRequest<Header>, _remote_proof: Vec<Vec<u8>>) -> client::error::Result<client::CallResult> {
|
||||
match self.ok {
|
||||
true => Ok(client::CallResult {
|
||||
return_data: vec![42],
|
||||
changes: Default::default(),
|
||||
}),
|
||||
false => Err(client::error::ErrorKind::Backend("Test error".into()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dummy(ok: bool) -> (Arc<DummyExecutor>, Arc<OnDemand<Block, DummyExecutor>>) {
|
||||
let executor = Arc::new(DummyExecutor);
|
||||
let service = Arc::new(OnDemand::new(Arc::new(DummyFetchChecker { ok })));
|
||||
service.set_service_link(Arc::downgrade(&executor));
|
||||
(executor, service)
|
||||
}
|
||||
|
||||
fn total_peers(on_demand: &OnDemand<Block, DummyExecutor>) -> usize {
|
||||
let core = on_demand.core.lock();
|
||||
core.idle_peers.len() + core.active_peers.len()
|
||||
}
|
||||
|
||||
fn receive_call_response(on_demand: &OnDemand<Block, DummyExecutor>, network: &mut TestIo, peer: NodeIndex, id: message::RequestId) {
|
||||
on_demand.on_remote_call_response(network, peer, message::RemoteCallResponse {
|
||||
id: id,
|
||||
proof: vec![vec![2]],
|
||||
});
|
||||
}
|
||||
|
||||
fn dummy_header() -> Header {
|
||||
Header {
|
||||
parent_hash: Default::default(),
|
||||
number: 0,
|
||||
state_root: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn knows_about_peers_roles() {
|
||||
let (_, on_demand) = dummy(true);
|
||||
on_demand.on_connect(0, Roles::LIGHT);
|
||||
on_demand.on_connect(1, Roles::FULL);
|
||||
on_demand.on_connect(2, Roles::AUTHORITY);
|
||||
assert_eq!(vec![1, 2], on_demand.core.lock().idle_peers.iter().cloned().collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnects_from_idle_peer() {
|
||||
let (_, on_demand) = dummy(true);
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
assert_eq!(1, total_peers(&*on_demand));
|
||||
on_demand.on_disconnect(0);
|
||||
assert_eq!(0, total_peers(&*on_demand));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnects_from_timeouted_peer() {
|
||||
let (_x, on_demand) = dummy(true);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
on_demand.on_connect(1, Roles::FULL);
|
||||
assert_eq!(vec![0, 1], on_demand.core.lock().idle_peers.iter().cloned().collect::<Vec<_>>());
|
||||
assert!(on_demand.core.lock().active_peers.is_empty());
|
||||
|
||||
on_demand.remote_call(RemoteCallRequest {
|
||||
block: Default::default(),
|
||||
header: dummy_header(),
|
||||
method: "test".into(),
|
||||
call_data: vec![],
|
||||
retry_count: None,
|
||||
});
|
||||
assert_eq!(vec![1], on_demand.core.lock().idle_peers.iter().cloned().collect::<Vec<_>>());
|
||||
assert_eq!(vec![0], on_demand.core.lock().active_peers.keys().cloned().collect::<Vec<_>>());
|
||||
|
||||
on_demand.core.lock().active_peers[&0].timestamp = Instant::now() - REQUEST_TIMEOUT - REQUEST_TIMEOUT;
|
||||
on_demand.maintain_peers(&mut network);
|
||||
assert!(on_demand.core.lock().idle_peers.is_empty());
|
||||
assert_eq!(vec![1], on_demand.core.lock().active_peers.keys().cloned().collect::<Vec<_>>());
|
||||
assert!(network.to_disconnect.contains(&0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnects_from_peer_on_response_with_wrong_id() {
|
||||
let (_x, on_demand) = dummy(true);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
|
||||
on_demand.remote_call(RemoteCallRequest {
|
||||
block: Default::default(),
|
||||
header: dummy_header(),
|
||||
method: "test".into(),
|
||||
call_data: vec![],
|
||||
retry_count: None,
|
||||
});
|
||||
receive_call_response(&*on_demand, &mut network, 0, 1);
|
||||
assert!(network.to_disconnect.contains(&0));
|
||||
assert_eq!(on_demand.core.lock().pending_requests.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnects_from_peer_on_incorrect_response() {
|
||||
let (_x, on_demand) = dummy(false);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
on_demand.remote_call(RemoteCallRequest {
|
||||
block: Default::default(),
|
||||
header: dummy_header(),
|
||||
method: "test".into(),
|
||||
call_data: vec![],
|
||||
retry_count: Some(1),
|
||||
});
|
||||
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
receive_call_response(&*on_demand, &mut network, 0, 0);
|
||||
assert!(network.to_disconnect.contains(&0));
|
||||
assert_eq!(on_demand.core.lock().pending_requests.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnects_from_peer_on_unexpected_response() {
|
||||
let (_x, on_demand) = dummy(true);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
|
||||
receive_call_response(&*on_demand, &mut network, 0, 0);
|
||||
assert!(network.to_disconnect.contains(&0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnects_from_peer_on_wrong_response_type() {
|
||||
let (_x, on_demand) = dummy(false);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
|
||||
on_demand.remote_call(RemoteCallRequest {
|
||||
block: Default::default(),
|
||||
header: dummy_header(),
|
||||
method: "test".into(),
|
||||
call_data: vec![],
|
||||
retry_count: Some(1),
|
||||
});
|
||||
|
||||
on_demand.on_remote_read_response(&mut network, 0, message::RemoteReadResponse {
|
||||
id: 0,
|
||||
proof: vec![vec![2]],
|
||||
});
|
||||
assert!(network.to_disconnect.contains(&0));
|
||||
assert_eq!(on_demand.core.lock().pending_requests.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receives_remote_failure_after_retry_count_failures() {
|
||||
use parking_lot::{Condvar, Mutex};
|
||||
|
||||
let retry_count = 2;
|
||||
let (_x, on_demand) = dummy(false);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
for i in 0..retry_count+1 {
|
||||
on_demand.on_connect(i, Roles::FULL);
|
||||
}
|
||||
|
||||
let sync = Arc::new((Mutex::new(0), Mutex::new(0), Condvar::new()));
|
||||
let thread_sync = sync.clone();
|
||||
|
||||
let response = on_demand.remote_call(RemoteCallRequest {
|
||||
block: Default::default(),
|
||||
header: dummy_header(),
|
||||
method: "test".into(),
|
||||
call_data: vec![],
|
||||
retry_count: Some(retry_count)
|
||||
});
|
||||
let thread = ::std::thread::spawn(move || {
|
||||
let &(ref current, ref finished_at, ref finished) = &*thread_sync;
|
||||
let _ = response.wait().unwrap_err();
|
||||
*finished_at.lock() = *current.lock();
|
||||
finished.notify_one();
|
||||
});
|
||||
|
||||
let &(ref current, ref finished_at, ref finished) = &*sync;
|
||||
for i in 0..retry_count+1 {
|
||||
let mut current = current.lock();
|
||||
*current = *current + 1;
|
||||
receive_call_response(&*on_demand, &mut network, i, i as u64);
|
||||
}
|
||||
|
||||
let mut finished_at = finished_at.lock();
|
||||
assert!(!finished.wait_for(&mut finished_at, ::std::time::Duration::from_millis(1000)).timed_out());
|
||||
assert_eq!(*finished_at, retry_count + 1);
|
||||
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receives_remote_call_response() {
|
||||
let (_x, on_demand) = dummy(true);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
|
||||
let response = on_demand.remote_call(RemoteCallRequest {
|
||||
block: Default::default(),
|
||||
header: dummy_header(),
|
||||
method: "test".into(),
|
||||
call_data: vec![],
|
||||
retry_count: None,
|
||||
});
|
||||
let thread = ::std::thread::spawn(move || {
|
||||
let result = response.wait().unwrap();
|
||||
assert_eq!(result.return_data, vec![42]);
|
||||
});
|
||||
|
||||
receive_call_response(&*on_demand, &mut network, 0, 0);
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receives_remote_read_response() {
|
||||
let (_x, on_demand) = dummy(true);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
|
||||
let response = on_demand.remote_read(RemoteReadRequest {
|
||||
header: dummy_header(),
|
||||
block: Default::default(),
|
||||
key: b":key".to_vec(),
|
||||
retry_count: None,
|
||||
});
|
||||
let thread = ::std::thread::spawn(move || {
|
||||
let result = response.wait().unwrap();
|
||||
assert_eq!(result, Some(vec![42]));
|
||||
});
|
||||
|
||||
on_demand.on_remote_read_response(&mut network, 0, message::RemoteReadResponse {
|
||||
id: 0,
|
||||
proof: vec![vec![2]],
|
||||
});
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receives_remote_header_response() {
|
||||
let (_x, on_demand) = dummy(true);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut network = TestIo::new(&queue, None);
|
||||
on_demand.on_connect(0, Roles::FULL);
|
||||
|
||||
let response = on_demand.remote_header(RemoteHeaderRequest {
|
||||
cht_root: Default::default(),
|
||||
block: 1,
|
||||
retry_count: None,
|
||||
});
|
||||
let thread = ::std::thread::spawn(move || {
|
||||
let result = response.wait().unwrap();
|
||||
assert_eq!(result.hash(), "80729accb7bb10ff9c637a10e8bb59f21c52571aa7b46544c5885ca89ed190f4".into());
|
||||
});
|
||||
|
||||
on_demand.on_remote_header_response(&mut network, 0, message::RemoteHeaderResponse {
|
||||
id: 0,
|
||||
header: Some(Header {
|
||||
parent_hash: Default::default(),
|
||||
number: 1,
|
||||
state_root: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
}),
|
||||
proof: vec![vec![2]],
|
||||
});
|
||||
thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::{mem, cmp};
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use parking_lot::RwLock;
|
||||
use rustc_hex::ToHex;
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash, HashFor, NumberFor, As};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use network_libp2p::{NodeIndex, Severity};
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
use message::{self, Message};
|
||||
use message::generic::Message as GenericMessage;
|
||||
use specialization::Specialization;
|
||||
use sync::{ChainSync, Status as SyncStatus, SyncState};
|
||||
use service::{Roles, TransactionPool, ExHashT};
|
||||
use import_queue::ImportQueue;
|
||||
use config::ProtocolConfig;
|
||||
use chain::Client;
|
||||
use on_demand::OnDemandService;
|
||||
use io::SyncIo;
|
||||
use error;
|
||||
|
||||
const REQUEST_TIMEOUT_SEC: u64 = 40;
|
||||
|
||||
/// Current protocol version.
|
||||
pub (crate) const CURRENT_VERSION: u32 = 1;
|
||||
/// Current packet count.
|
||||
pub (crate) const CURRENT_PACKET_COUNT: u8 = 1;
|
||||
|
||||
// Maximum allowed entries in `BlockResponse`
|
||||
const MAX_BLOCK_DATA_RESPONSE: u32 = 128;
|
||||
|
||||
// Lock must always be taken in order declared here.
|
||||
pub struct Protocol<B: BlockT, S: Specialization<B>, H: ExHashT> {
|
||||
config: ProtocolConfig,
|
||||
on_demand: Option<Arc<OnDemandService<B>>>,
|
||||
genesis_hash: B::Hash,
|
||||
sync: Arc<RwLock<ChainSync<B>>>,
|
||||
specialization: RwLock<S>,
|
||||
context_data: ContextData<B, H>,
|
||||
// Connected peers pending Status message.
|
||||
handshaking_peers: RwLock<HashMap<NodeIndex, time::Instant>>,
|
||||
transaction_pool: Arc<TransactionPool<H, B>>,
|
||||
}
|
||||
/// Syncing status and statistics
|
||||
#[derive(Clone)]
|
||||
pub struct ProtocolStatus<B: BlockT> {
|
||||
/// Sync status.
|
||||
pub sync: SyncStatus<B>,
|
||||
/// Total number of connected peers
|
||||
pub num_peers: usize,
|
||||
/// Total number of active peers.
|
||||
pub num_active_peers: usize,
|
||||
}
|
||||
|
||||
/// Peer information
|
||||
struct Peer<B: BlockT, H: ExHashT> {
|
||||
/// Protocol version
|
||||
protocol_version: u32,
|
||||
/// Roles
|
||||
roles: Roles,
|
||||
/// Peer best block hash
|
||||
best_hash: B::Hash,
|
||||
/// Peer best block number
|
||||
best_number: <B::Header as HeaderT>::Number,
|
||||
/// Pending block request if any
|
||||
block_request: Option<message::BlockRequest<B>>,
|
||||
/// Request timestamp
|
||||
request_timestamp: Option<time::Instant>,
|
||||
/// Holds a set of transactions known to this peer.
|
||||
known_extrinsics: HashSet<H>,
|
||||
/// Holds a set of blocks known to this peer.
|
||||
known_blocks: HashSet<B::Hash>,
|
||||
/// Request counter,
|
||||
next_request_id: message::RequestId,
|
||||
}
|
||||
|
||||
/// Info about a peer's known state.
|
||||
#[derive(Debug)]
|
||||
pub struct PeerInfo<B: BlockT> {
|
||||
/// Roles
|
||||
pub roles: Roles,
|
||||
/// Protocol version
|
||||
pub protocol_version: u32,
|
||||
/// Peer best block hash
|
||||
pub best_hash: B::Hash,
|
||||
/// Peer best block number
|
||||
pub best_number: <B::Header as HeaderT>::Number,
|
||||
}
|
||||
|
||||
/// Context for a network-specific handler.
|
||||
pub trait Context<B: BlockT> {
|
||||
/// Get a reference to the client.
|
||||
fn client(&self) -> &::chain::Client<B>;
|
||||
|
||||
/// Point out that a peer has been malign or irresponsible or appeared lazy.
|
||||
fn report_peer(&mut self, who: NodeIndex, reason: Severity);
|
||||
|
||||
/// Get peer info.
|
||||
fn peer_info(&self, peer: NodeIndex) -> Option<PeerInfo<B>>;
|
||||
|
||||
/// Send a message to a peer.
|
||||
fn send_message(&mut self, who: NodeIndex, data: ::message::Message<B>);
|
||||
}
|
||||
|
||||
/// Protocol context.
|
||||
pub(crate) struct ProtocolContext<'a, B: 'a + BlockT, H: 'a + ExHashT> {
|
||||
io: &'a mut SyncIo,
|
||||
context_data: &'a ContextData<B, H>,
|
||||
}
|
||||
|
||||
impl<'a, B: BlockT + 'a, H: 'a + ExHashT> ProtocolContext<'a, B, H> {
|
||||
pub(crate) fn new(context_data: &'a ContextData<B, H>, io: &'a mut SyncIo) -> Self {
|
||||
ProtocolContext {
|
||||
io,
|
||||
context_data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a message to a peer.
|
||||
pub fn send_message(&mut self, who: NodeIndex, message: Message<B>) {
|
||||
send_message(&self.context_data.peers, self.io, who, message)
|
||||
}
|
||||
|
||||
/// Point out that a peer has been malign or irresponsible or appeared lazy.
|
||||
pub fn report_peer(&mut self, who: NodeIndex, reason: Severity) {
|
||||
self.io.report_peer(who, reason);
|
||||
}
|
||||
|
||||
/// Get peer info.
|
||||
pub fn peer_info(&self, peer: NodeIndex) -> Option<PeerInfo<B>> {
|
||||
self.context_data.peers.read().get(&peer).map(|p| {
|
||||
PeerInfo {
|
||||
roles: p.roles,
|
||||
protocol_version: p.protocol_version,
|
||||
best_hash: p.best_hash,
|
||||
best_number: p.best_number,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B: BlockT + 'a, H: ExHashT + 'a> Context<B> for ProtocolContext<'a, B, H> {
|
||||
fn send_message(&mut self, who: NodeIndex, message: Message<B>) {
|
||||
ProtocolContext::send_message(self, who, message);
|
||||
}
|
||||
|
||||
fn report_peer(&mut self, who: NodeIndex, reason: Severity) {
|
||||
ProtocolContext::report_peer(self, who, reason);
|
||||
}
|
||||
|
||||
fn peer_info(&self, who: NodeIndex) -> Option<PeerInfo<B>> {
|
||||
ProtocolContext::peer_info(self, who)
|
||||
}
|
||||
|
||||
fn client(&self) -> &Client<B> {
|
||||
&*self.context_data.chain
|
||||
}
|
||||
}
|
||||
|
||||
/// Data necessary to create a context.
|
||||
pub(crate) struct ContextData<B: BlockT, H: ExHashT> {
|
||||
// All connected peers
|
||||
peers: RwLock<HashMap<NodeIndex, Peer<B, H>>>,
|
||||
chain: Arc<Client<B>>,
|
||||
}
|
||||
|
||||
impl<B: BlockT, S: Specialization<B>, H: ExHashT> Protocol<B, S, H> {
|
||||
/// Create a new instance.
|
||||
pub fn new(
|
||||
config: ProtocolConfig,
|
||||
chain: Arc<Client<B>>,
|
||||
import_queue: Arc<ImportQueue<B>>,
|
||||
on_demand: Option<Arc<OnDemandService<B>>>,
|
||||
transaction_pool: Arc<TransactionPool<H, B>>,
|
||||
specialization: S,
|
||||
) -> error::Result<Self> {
|
||||
let info = chain.info()?;
|
||||
let sync = ChainSync::new(config.roles, &info, import_queue);
|
||||
let protocol = Protocol {
|
||||
config: config,
|
||||
context_data: ContextData {
|
||||
peers: RwLock::new(HashMap::new()),
|
||||
chain,
|
||||
},
|
||||
on_demand,
|
||||
genesis_hash: info.chain.genesis_hash,
|
||||
sync: Arc::new(RwLock::new(sync)),
|
||||
specialization: RwLock::new(specialization),
|
||||
handshaking_peers: RwLock::new(HashMap::new()),
|
||||
transaction_pool: transaction_pool,
|
||||
};
|
||||
Ok(protocol)
|
||||
}
|
||||
|
||||
pub(crate) fn context_data(&self) -> &ContextData<B, H> {
|
||||
&self.context_data
|
||||
}
|
||||
|
||||
pub(crate) fn sync(&self) -> &Arc<RwLock<ChainSync<B>>> {
|
||||
&self.sync
|
||||
}
|
||||
|
||||
/// Returns protocol status
|
||||
pub fn status(&self) -> ProtocolStatus<B> {
|
||||
let sync = self.sync.read();
|
||||
let peers = self.context_data.peers.read();
|
||||
ProtocolStatus {
|
||||
sync: sync.status(),
|
||||
num_peers: peers.values().count(),
|
||||
num_active_peers: peers.values().filter(|p| p.block_request.is_some()).count(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_packet(&self, io: &mut SyncIo, who: NodeIndex, mut data: &[u8]) {
|
||||
let message: Message<B> = match Decode::decode(&mut data) {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
trace!(target: "sync", "Invalid packet from {}", who);
|
||||
io.report_peer(who, Severity::Bad("Peer sent us a packet with invalid format"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match message {
|
||||
GenericMessage::Status(s) => self.on_status_message(io, who, s),
|
||||
GenericMessage::BlockRequest(r) => self.on_block_request(io, who, r),
|
||||
GenericMessage::BlockResponse(r) => {
|
||||
let request = {
|
||||
let mut peers = self.context_data.peers.write();
|
||||
if let Some(ref mut peer) = peers.get_mut(&who) {
|
||||
peer.request_timestamp = None;
|
||||
match mem::replace(&mut peer.block_request, None) {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
io.report_peer(who, Severity::Bad("Unexpected response packet received from peer"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
io.report_peer(who, Severity::Bad("Unexpected packet received from peer"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
if request.id != r.id {
|
||||
trace!(target: "sync", "Ignoring mismatched response packet from {} (expected {} got {})", who, request.id, r.id);
|
||||
return;
|
||||
}
|
||||
self.on_block_response(io, who, request, r);
|
||||
},
|
||||
GenericMessage::BlockAnnounce(announce) => self.on_block_announce(io, who, announce),
|
||||
GenericMessage::Transactions(m) => self.on_extrinsics(io, who, m),
|
||||
GenericMessage::RemoteCallRequest(request) => self.on_remote_call_request(io, who, request),
|
||||
GenericMessage::RemoteCallResponse(response) => self.on_remote_call_response(io, who, response),
|
||||
GenericMessage::RemoteReadRequest(request) => self.on_remote_read_request(io, who, request),
|
||||
GenericMessage::RemoteReadResponse(response) => self.on_remote_read_response(io, who, response),
|
||||
GenericMessage::RemoteHeaderRequest(request) => self.on_remote_header_request(io, who, request),
|
||||
GenericMessage::RemoteHeaderResponse(response) => self.on_remote_header_response(io, who, response),
|
||||
other => self.specialization.write().on_message(&mut ProtocolContext::new(&self.context_data, io), who, other),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_message(&self, io: &mut SyncIo, who: NodeIndex, message: Message<B>) {
|
||||
send_message::<B, H>(&self.context_data.peers, io, who, message)
|
||||
}
|
||||
|
||||
/// Called when a new peer is connected
|
||||
pub fn on_peer_connected(&self, io: &mut SyncIo, who: NodeIndex) {
|
||||
trace!(target: "sync", "Connected {}: {}", who, io.peer_info(who));
|
||||
self.handshaking_peers.write().insert(who, time::Instant::now());
|
||||
self.send_status(io, who);
|
||||
}
|
||||
|
||||
/// Called by peer when it is disconnecting
|
||||
pub fn on_peer_disconnected(&self, io: &mut SyncIo, peer: NodeIndex) {
|
||||
trace!(target: "sync", "Disconnecting {}: {}", peer, io.peer_info(peer));
|
||||
|
||||
// lock all the the peer lists so that add/remove peer events are in order
|
||||
let mut sync = self.sync.write();
|
||||
let mut spec = self.specialization.write();
|
||||
|
||||
let removed = {
|
||||
let mut peers = self.context_data.peers.write();
|
||||
let mut handshaking_peers = self.handshaking_peers.write();
|
||||
handshaking_peers.remove(&peer);
|
||||
peers.remove(&peer).is_some()
|
||||
};
|
||||
if removed {
|
||||
let mut context = ProtocolContext::new(&self.context_data, io);
|
||||
sync.peer_disconnected(&mut context, peer);
|
||||
spec.on_disconnect(&mut context, peer);
|
||||
self.on_demand.as_ref().map(|s| s.on_disconnect(peer));
|
||||
}
|
||||
}
|
||||
|
||||
fn on_block_request(&self, io: &mut SyncIo, peer: NodeIndex, request: message::BlockRequest<B>) {
|
||||
trace!(target: "sync", "BlockRequest {} from {}: from {:?} to {:?} max {:?}", request.id, peer, request.from, request.to, request.max);
|
||||
let mut blocks = Vec::new();
|
||||
let mut id = match request.from {
|
||||
message::FromBlock::Hash(h) => BlockId::Hash(h),
|
||||
message::FromBlock::Number(n) => BlockId::Number(n),
|
||||
};
|
||||
let max = cmp::min(request.max.unwrap_or(u32::max_value()), MAX_BLOCK_DATA_RESPONSE) as usize;
|
||||
// TODO: receipts, etc.
|
||||
let get_header = request.fields.contains(message::BlockAttributes::HEADER);
|
||||
let get_body = request.fields.contains(message::BlockAttributes::BODY);
|
||||
let get_justification = request.fields.contains(message::BlockAttributes::JUSTIFICATION);
|
||||
while let Some(header) = self.context_data.chain.header(&id).unwrap_or(None) {
|
||||
if blocks.len() >= max {
|
||||
break;
|
||||
}
|
||||
let number = header.number().clone();
|
||||
let hash = header.hash();
|
||||
let justification = if get_justification { self.context_data.chain.justification(&BlockId::Hash(hash)).unwrap_or(None) } else { None };
|
||||
let block_data = message::generic::BlockData {
|
||||
hash: hash,
|
||||
header: if get_header { Some(header) } else { None },
|
||||
body: if get_body { self.context_data.chain.body(&BlockId::Hash(hash)).unwrap_or(None) } else { None },
|
||||
receipt: None,
|
||||
message_queue: None,
|
||||
justification,
|
||||
};
|
||||
blocks.push(block_data);
|
||||
match request.direction {
|
||||
message::Direction::Ascending => id = BlockId::Number(number + As::sa(1)),
|
||||
message::Direction::Descending => {
|
||||
if number == As::sa(0) {
|
||||
break;
|
||||
}
|
||||
id = BlockId::Number(number - As::sa(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
let response = message::generic::BlockResponse {
|
||||
id: request.id,
|
||||
blocks: blocks,
|
||||
};
|
||||
trace!(target: "sync", "Sending BlockResponse with {} blocks", response.blocks.len());
|
||||
self.send_message(io, peer, GenericMessage::BlockResponse(response))
|
||||
}
|
||||
|
||||
fn on_block_response(&self, io: &mut SyncIo, peer: NodeIndex, request: message::BlockRequest<B>, response: message::BlockResponse<B>) {
|
||||
// TODO: validate response
|
||||
let blocks_range = match (
|
||||
response.blocks.first().and_then(|b| b.header.as_ref().map(|h| h.number())),
|
||||
response.blocks.last().and_then(|b| b.header.as_ref().map(|h| h.number())),
|
||||
) {
|
||||
(Some(first), Some(last)) if first != last => format!(" ({}..{})", first, last),
|
||||
(Some(first), Some(_)) => format!(" ({})", first),
|
||||
_ => Default::default(),
|
||||
};
|
||||
trace!(target: "sync", "BlockResponse {} from {} with {} blocks{}",
|
||||
response.id, peer, response.blocks.len(), blocks_range);
|
||||
|
||||
self.sync.write().on_block_data(&mut ProtocolContext::new(&self.context_data, io), peer, request, response);
|
||||
}
|
||||
|
||||
/// Perform time based maintenance.
|
||||
pub fn tick(&self, io: &mut SyncIo) {
|
||||
self.maintain_peers(io);
|
||||
self.on_demand.as_ref().map(|s| s.maintain_peers(io));
|
||||
}
|
||||
|
||||
fn maintain_peers(&self, io: &mut SyncIo) {
|
||||
let tick = time::Instant::now();
|
||||
let mut aborting = Vec::new();
|
||||
{
|
||||
let peers = self.context_data.peers.read();
|
||||
let handshaking_peers = self.handshaking_peers.read();
|
||||
for (who, timestamp) in peers.iter()
|
||||
.filter_map(|(id, peer)| peer.request_timestamp.as_ref().map(|r| (id, r)))
|
||||
.chain(handshaking_peers.iter()) {
|
||||
if (tick - *timestamp).as_secs() > REQUEST_TIMEOUT_SEC {
|
||||
trace!(target: "sync", "Timeout {}", who);
|
||||
aborting.push(*who);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.specialization.write().maintain_peers(&mut ProtocolContext::new(&self.context_data, io));
|
||||
for p in aborting {
|
||||
io.report_peer(p, Severity::Timeout);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peer_info(&self, peer: NodeIndex) -> Option<PeerInfo<B>> {
|
||||
self.context_data.peers.read().get(&peer).map(|p| {
|
||||
PeerInfo {
|
||||
roles: p.roles,
|
||||
protocol_version: p.protocol_version,
|
||||
best_hash: p.best_hash,
|
||||
best_number: p.best_number,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Called by peer to report status
|
||||
fn on_status_message(&self, io: &mut SyncIo, who: NodeIndex, status: message::Status<B>) {
|
||||
trace!(target: "sync", "New peer {} {:?}", who, status);
|
||||
if io.is_expired() {
|
||||
trace!(target: "sync", "Status packet from expired session {}:{}", who, io.peer_info(who));
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let mut peers = self.context_data.peers.write();
|
||||
let mut handshaking_peers = self.handshaking_peers.write();
|
||||
if peers.contains_key(&who) {
|
||||
debug!(target: "sync", "Unexpected status packet from {}:{}", who, io.peer_info(who));
|
||||
return;
|
||||
}
|
||||
if status.genesis_hash != self.genesis_hash {
|
||||
io.report_peer(who, Severity::Bad(&format!("Peer is on different chain (our genesis: {} theirs: {})", self.genesis_hash, status.genesis_hash)));
|
||||
return;
|
||||
}
|
||||
if status.version != CURRENT_VERSION {
|
||||
io.report_peer(who, Severity::Bad(&format!("Peer using unsupported protocol version {}", status.version)));
|
||||
return;
|
||||
}
|
||||
|
||||
let peer = Peer {
|
||||
protocol_version: status.version,
|
||||
roles: status.roles,
|
||||
best_hash: status.best_hash,
|
||||
best_number: status.best_number,
|
||||
block_request: None,
|
||||
request_timestamp: None,
|
||||
known_extrinsics: HashSet::new(),
|
||||
known_blocks: HashSet::new(),
|
||||
next_request_id: 0,
|
||||
};
|
||||
peers.insert(who.clone(), peer);
|
||||
handshaking_peers.remove(&who);
|
||||
debug!(target: "sync", "Connected {} {}", who, io.peer_info(who));
|
||||
}
|
||||
|
||||
let mut context = ProtocolContext::new(&self.context_data, io);
|
||||
self.sync.write().new_peer(&mut context, who);
|
||||
self.specialization.write().on_connect(&mut context, who, status.clone());
|
||||
self.on_demand.as_ref().map(|s| s.on_connect(who, status.roles));
|
||||
}
|
||||
|
||||
/// Called when peer sends us new extrinsics
|
||||
fn on_extrinsics(&self, _io: &mut SyncIo, who: NodeIndex, extrinsics: message::Transactions<B::Extrinsic>) {
|
||||
// Accept extrinsics only when fully synced
|
||||
if self.sync.read().status().state != SyncState::Idle {
|
||||
trace!(target: "sync", "{} Ignoring extrinsics while syncing", who);
|
||||
return;
|
||||
}
|
||||
trace!(target: "sync", "Received {} extrinsics from {}", extrinsics.len(), who);
|
||||
let mut peers = self.context_data.peers.write();
|
||||
if let Some(ref mut peer) = peers.get_mut(&who) {
|
||||
for t in extrinsics {
|
||||
if let Some(hash) = self.transaction_pool.import(&t) {
|
||||
peer.known_extrinsics.insert(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when we propagate ready extrinsics to peers.
|
||||
pub fn propagate_extrinsics(&self, io: &mut SyncIo) {
|
||||
debug!(target: "sync", "Propagating extrinsics");
|
||||
|
||||
// Accept transactions only when fully synced
|
||||
if self.sync.read().status().state != SyncState::Idle {
|
||||
return;
|
||||
}
|
||||
|
||||
let extrinsics = self.transaction_pool.transactions();
|
||||
|
||||
let mut propagated_to = HashMap::new();
|
||||
let mut peers = self.context_data.peers.write();
|
||||
for (who, ref mut peer) in peers.iter_mut() {
|
||||
let (hashes, to_send): (Vec<_>, Vec<_>) = extrinsics
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|&(ref hash, _)| peer.known_extrinsics.insert(hash.clone()))
|
||||
.unzip();
|
||||
|
||||
if !to_send.is_empty() {
|
||||
let node_id = io.peer_session_info(*who).map(|info| match info.id {
|
||||
Some(id) => format!("{}@{:x}", info.remote_address, id),
|
||||
None => info.remote_address.clone(),
|
||||
});
|
||||
|
||||
if let Some(id) = node_id {
|
||||
for hash in hashes {
|
||||
propagated_to.entry(hash).or_insert_with(Vec::new).push(id.clone());
|
||||
}
|
||||
}
|
||||
trace!(target: "sync", "Sending {} transactions to {}", to_send.len(), who);
|
||||
self.send_message(io, *who, GenericMessage::Transactions(to_send));
|
||||
}
|
||||
}
|
||||
self.transaction_pool.on_broadcasted(propagated_to);
|
||||
}
|
||||
|
||||
/// Send Status message
|
||||
fn send_status(&self, io: &mut SyncIo, who: NodeIndex) {
|
||||
if let Ok(info) = self.context_data.chain.info() {
|
||||
let status = message::generic::Status {
|
||||
version: CURRENT_VERSION,
|
||||
genesis_hash: info.chain.genesis_hash,
|
||||
roles: self.config.roles.into(),
|
||||
best_number: info.chain.best_number,
|
||||
best_hash: info.chain.best_hash,
|
||||
chain_status: self.specialization.read().status(),
|
||||
};
|
||||
self.send_message(io, who, GenericMessage::Status(status))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn abort(&self) {
|
||||
let mut sync = self.sync.write();
|
||||
let mut spec = self.specialization.write();
|
||||
let mut peers = self.context_data.peers.write();
|
||||
let mut handshaking_peers = self.handshaking_peers.write();
|
||||
sync.clear();
|
||||
spec.on_abort();
|
||||
peers.clear();
|
||||
handshaking_peers.clear();
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
// stop processing import requests first (without holding a sync lock)
|
||||
let import_queue = self.sync.read().import_queue();
|
||||
import_queue.stop();
|
||||
|
||||
// and then clear all the sync data
|
||||
self.abort();
|
||||
}
|
||||
|
||||
pub fn on_block_announce(&self, io: &mut SyncIo, who: NodeIndex, announce: message::BlockAnnounce<B::Header>) {
|
||||
let header = announce.header;
|
||||
let hash = header.hash();
|
||||
{
|
||||
let mut peers = self.context_data.peers.write();
|
||||
if let Some(ref mut peer) = peers.get_mut(&who) {
|
||||
peer.known_blocks.insert(hash.clone());
|
||||
}
|
||||
}
|
||||
self.sync.write().on_block_announce(&mut ProtocolContext::new(&self.context_data, io), who, hash, &header);
|
||||
}
|
||||
|
||||
pub fn on_block_imported(&self, io: &mut SyncIo, hash: B::Hash, header: &B::Header) {
|
||||
self.sync.write().update_chain_info(&header);
|
||||
self.specialization.write().on_block_imported(
|
||||
&mut ProtocolContext::new(&self.context_data, io),
|
||||
hash.clone(),
|
||||
header
|
||||
);
|
||||
|
||||
// blocks are not announced by light clients
|
||||
if self.config.roles & Roles::LIGHT == Roles::LIGHT {
|
||||
return;
|
||||
}
|
||||
|
||||
// send out block announcements
|
||||
let mut peers = self.context_data.peers.write();
|
||||
|
||||
for (who, ref mut peer) in peers.iter_mut() {
|
||||
if peer.known_blocks.insert(hash.clone()) {
|
||||
trace!(target: "sync", "Announcing block {:?} to {}", hash, who);
|
||||
self.send_message(io, *who, GenericMessage::BlockAnnounce(message::BlockAnnounce {
|
||||
header: header.clone()
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remote_call_request(&self, io: &mut SyncIo, who: NodeIndex, request: message::RemoteCallRequest<B::Hash>) {
|
||||
trace!(target: "sync", "Remote call request {} from {} ({} at {})", request.id, who, request.method, request.block);
|
||||
let proof = match self.context_data.chain.execution_proof(&request.block, &request.method, &request.data) {
|
||||
Ok((_, proof)) => proof,
|
||||
Err(error) => {
|
||||
trace!(target: "sync", "Remote call request {} from {} ({} at {}) failed with: {}",
|
||||
request.id, who, request.method, request.block, error);
|
||||
Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
self.send_message(io, who, GenericMessage::RemoteCallResponse(message::RemoteCallResponse {
|
||||
id: request.id, proof,
|
||||
}));
|
||||
}
|
||||
|
||||
fn on_remote_call_response(&self, io: &mut SyncIo, who: NodeIndex, response: message::RemoteCallResponse) {
|
||||
trace!(target: "sync", "Remote call response {} from {}", response.id, who);
|
||||
self.on_demand.as_ref().map(|s| s.on_remote_call_response(io, who, response));
|
||||
}
|
||||
|
||||
fn on_remote_read_request(&self, io: &mut SyncIo, who: NodeIndex, request: message::RemoteReadRequest<B::Hash>) {
|
||||
trace!(target: "sync", "Remote read request {} from {} ({} at {})",
|
||||
request.id, who, request.key.to_hex(), request.block);
|
||||
let proof = match self.context_data.chain.read_proof(&request.block, &request.key) {
|
||||
Ok(proof) => proof,
|
||||
Err(error) => {
|
||||
trace!(target: "sync", "Remote read request {} from {} ({} at {}) failed with: {}",
|
||||
request.id, who, request.key.to_hex(), request.block, error);
|
||||
Default::default()
|
||||
},
|
||||
};
|
||||
self.send_message(io, who, GenericMessage::RemoteReadResponse(message::RemoteReadResponse {
|
||||
id: request.id, proof,
|
||||
}));
|
||||
}
|
||||
fn on_remote_read_response(&self, io: &mut SyncIo, who: NodeIndex, response: message::RemoteReadResponse) {
|
||||
trace!(target: "sync", "Remote read response {} from {}", response.id, who);
|
||||
self.on_demand.as_ref().map(|s| s.on_remote_read_response(io, who, response));
|
||||
}
|
||||
|
||||
fn on_remote_header_request(&self, io: &mut SyncIo, who: NodeIndex, request: message::RemoteHeaderRequest<NumberFor<B>>) {
|
||||
trace!(target: "sync", "Remote header proof request {} from {} ({})",
|
||||
request.id, who, request.block);
|
||||
let (header, proof) = match self.context_data.chain.header_proof(request.block) {
|
||||
Ok((header, proof)) => (Some(header), proof),
|
||||
Err(error) => {
|
||||
trace!(target: "sync", "Remote header proof request {} from {} ({}) failed with: {}",
|
||||
request.id, who, request.block, error);
|
||||
(Default::default(), Default::default())
|
||||
},
|
||||
};
|
||||
self.send_message(io, who, GenericMessage::RemoteHeaderResponse(message::RemoteHeaderResponse {
|
||||
id: request.id, header, proof,
|
||||
}));
|
||||
}
|
||||
|
||||
fn on_remote_header_response(&self, io: &mut SyncIo, who: NodeIndex, response: message::RemoteHeaderResponse<B::Header>) {
|
||||
trace!(target: "sync", "Remote header proof response {} from {}", response.id, who);
|
||||
self.on_demand.as_ref().map(|s| s.on_remote_header_response(io, who, response));
|
||||
}
|
||||
|
||||
/// Execute a closure with access to a network context and specialization.
|
||||
pub fn with_spec<F, U>(&self, io: &mut SyncIo, f: F) -> U
|
||||
where F: FnOnce(&mut S, &mut Context<B>) -> U
|
||||
{
|
||||
f(&mut* self.specialization.write(), &mut ProtocolContext::new(&self.context_data, io))
|
||||
}
|
||||
}
|
||||
|
||||
fn send_message<B: BlockT, H: ExHashT>(peers: &RwLock<HashMap<NodeIndex, Peer<B, H>>>, io: &mut SyncIo, who: NodeIndex, mut message: Message<B>) {
|
||||
match &mut message {
|
||||
&mut GenericMessage::BlockRequest(ref mut r) => {
|
||||
let mut peers = peers.write();
|
||||
if let Some(ref mut peer) = peers.get_mut(&who) {
|
||||
r.id = peer.next_request_id;
|
||||
peer.next_request_id = peer.next_request_id + 1;
|
||||
peer.block_request = Some(r.clone());
|
||||
peer.request_timestamp = Some(time::Instant::now());
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
io.send(who, message.encode());
|
||||
}
|
||||
|
||||
/// Hash a message.
|
||||
pub(crate) fn hash_message<B: BlockT>(message: &Message<B>) -> B::Hash {
|
||||
let data = message.encode();
|
||||
HashFor::<B>::hash(&data)
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
use futures::sync::{oneshot, mpsc};
|
||||
use network_libp2p::{NetworkProtocolHandler, NetworkContext, NodeIndex, ProtocolId,
|
||||
NetworkConfiguration , NonReservedPeerMode, ErrorKind};
|
||||
use network_libp2p::{NetworkService};
|
||||
use core_io::{TimerToken};
|
||||
use io::NetSyncIo;
|
||||
use protocol::{Protocol, ProtocolContext, Context, ProtocolStatus, PeerInfo as ProtocolPeerInfo};
|
||||
use config::{ProtocolConfig};
|
||||
use error::Error;
|
||||
use chain::Client;
|
||||
use message::LocalizedBftMessage;
|
||||
use specialization::Specialization;
|
||||
use on_demand::OnDemandService;
|
||||
use import_queue::AsyncImportQueue;
|
||||
use runtime_primitives::traits::{Block as BlockT};
|
||||
|
||||
/// Type that represents fetch completion future.
|
||||
pub type FetchFuture = oneshot::Receiver<Vec<u8>>;
|
||||
/// Type that represents bft messages stream.
|
||||
pub type BftMessageStream<B> = mpsc::UnboundedReceiver<LocalizedBftMessage<B>>;
|
||||
|
||||
const TICK_TOKEN: TimerToken = 0;
|
||||
const TICK_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
|
||||
const PROPAGATE_TOKEN: TimerToken = 1;
|
||||
const PROPAGATE_TIMEOUT: Duration = Duration::from_millis(5000);
|
||||
|
||||
bitflags! {
|
||||
/// Node roles bitmask.
|
||||
pub struct Roles: u8 {
|
||||
/// No network.
|
||||
const NONE = 0b00000000;
|
||||
/// Full node, does not participate in consensus.
|
||||
const FULL = 0b00000001;
|
||||
/// Light client node.
|
||||
const LIGHT = 0b00000010;
|
||||
/// Act as an authority
|
||||
const AUTHORITY = 0b00000100;
|
||||
}
|
||||
}
|
||||
|
||||
impl ::codec::Encode for Roles {
|
||||
fn encode_to<T: ::codec::Output>(&self, dest: &mut T) {
|
||||
dest.push_byte(self.bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl ::codec::Decode for Roles {
|
||||
fn decode<I: ::codec::Input>(input: &mut I) -> Option<Self> {
|
||||
Self::from_bits(input.read_byte()?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync status
|
||||
pub trait SyncProvider<B: BlockT>: Send + Sync {
|
||||
/// Get sync status
|
||||
fn status(&self) -> ProtocolStatus<B>;
|
||||
/// Get peers information
|
||||
fn peers(&self) -> Vec<PeerInfo<B>>;
|
||||
/// Get this node id if available.
|
||||
fn node_id(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
pub trait ExHashT: ::std::hash::Hash + Eq + ::std::fmt::Debug + Clone + Send + Sync + 'static {}
|
||||
impl<T> ExHashT for T where T: ::std::hash::Hash + Eq + ::std::fmt::Debug + Clone + Send + Sync + 'static {}
|
||||
|
||||
/// Transaction pool interface
|
||||
pub trait TransactionPool<H: ExHashT, B: BlockT>: Send + Sync {
|
||||
/// Get transactions from the pool that are ready to be propagated.
|
||||
fn transactions(&self) -> Vec<(H, B::Extrinsic)>;
|
||||
/// Import a transaction into the pool.
|
||||
fn import(&self, transaction: &B::Extrinsic) -> Option<H>;
|
||||
/// Notify the pool about transactions broadcast.
|
||||
fn on_broadcasted(&self, propagations: HashMap<H, Vec<String>>);
|
||||
}
|
||||
|
||||
/// ConsensusService
|
||||
pub trait ConsensusService<B: BlockT>: Send + Sync {
|
||||
/// Maintain connectivity to given addresses.
|
||||
fn connect_to_authorities(&self, addresses: &[String]);
|
||||
|
||||
/// Get BFT message stream for messages corresponding to consensus on given
|
||||
/// parent hash.
|
||||
fn bft_messages(&self, parent_hash: B::Hash) -> BftMessageStream<B>;
|
||||
/// Send out a BFT message.
|
||||
fn send_bft_message(&self, message: LocalizedBftMessage<B>);
|
||||
}
|
||||
|
||||
/// Service able to execute closure in the network context.
|
||||
pub trait ExecuteInContext<B: BlockT>: Send + Sync {
|
||||
/// Execute closure in network context.
|
||||
fn execute_in_context<F: Fn(&mut Context<B>)>(&self, closure: F);
|
||||
}
|
||||
|
||||
/// Network protocol handler
|
||||
struct ProtocolHandler<B: BlockT, S: Specialization<B>, H: ExHashT> {
|
||||
protocol: Protocol<B, S, H>,
|
||||
}
|
||||
|
||||
/// Peer connection information
|
||||
#[derive(Debug)]
|
||||
pub struct PeerInfo<B: BlockT> {
|
||||
/// Public node id
|
||||
pub id: Option<String>,
|
||||
/// Node client ID
|
||||
pub client_version: String,
|
||||
/// Capabilities
|
||||
pub capabilities: Vec<String>,
|
||||
/// Remote endpoint address
|
||||
pub remote_address: String,
|
||||
/// Local endpoint address
|
||||
pub local_address: String,
|
||||
/// Dot protocol info.
|
||||
pub dot_info: Option<ProtocolPeerInfo<B>>,
|
||||
}
|
||||
|
||||
/// Service initialization parameters.
|
||||
pub struct Params<B: BlockT, S, H: ExHashT> {
|
||||
/// Configuration.
|
||||
pub config: ProtocolConfig,
|
||||
/// Network layer configuration.
|
||||
pub network_config: NetworkConfiguration,
|
||||
/// Polkadot relay chain access point.
|
||||
pub chain: Arc<Client<B>>,
|
||||
/// On-demand service reference.
|
||||
pub on_demand: Option<Arc<OnDemandService<B>>>,
|
||||
/// Transaction pool.
|
||||
pub transaction_pool: Arc<TransactionPool<H, B>>,
|
||||
/// Protocol specialization.
|
||||
pub specialization: S,
|
||||
}
|
||||
|
||||
/// Polkadot network service. Handles network IO and manages connectivity.
|
||||
pub struct Service<B: BlockT + 'static, S: Specialization<B>, H: ExHashT> {
|
||||
/// Network service
|
||||
network: NetworkService,
|
||||
/// Devp2p protocol handler
|
||||
handler: Arc<ProtocolHandler<B, S, H>>,
|
||||
/// Devp2p protocol ID.
|
||||
protocol_id: ProtocolId,
|
||||
}
|
||||
|
||||
impl<B: BlockT + 'static, S: Specialization<B>, H: ExHashT> Service<B, S, H> {
|
||||
/// Creates and register protocol with the network service
|
||||
pub fn new(params: Params<B, S, H>, protocol_id: ProtocolId) -> Result<Arc<Service<B, S, H>>, Error> {
|
||||
let chain = params.chain.clone();
|
||||
let import_queue = Arc::new(AsyncImportQueue::new());
|
||||
let handler = Arc::new(ProtocolHandler {
|
||||
protocol: Protocol::new(
|
||||
params.config,
|
||||
params.chain,
|
||||
import_queue.clone(),
|
||||
params.on_demand,
|
||||
params.transaction_pool,
|
||||
params.specialization,
|
||||
)?,
|
||||
});
|
||||
let versions = [(::protocol::CURRENT_VERSION as u8, ::protocol::CURRENT_PACKET_COUNT)];
|
||||
let protocols = vec![(handler.clone() as Arc<_>, protocol_id, &versions[..])];
|
||||
let service = match NetworkService::new(params.network_config.clone(), protocols) {
|
||||
Ok(service) => service,
|
||||
Err(err) => {
|
||||
match err.kind() {
|
||||
ErrorKind::Io(ref e) if e.kind() == io::ErrorKind::AddrInUse =>
|
||||
warn!("Network port is already in use, make sure that another instance of Polkadot client is not running or change the port using the --port option."),
|
||||
_ => warn!("Error starting network: {}", err),
|
||||
};
|
||||
return Err(err.into())
|
||||
},
|
||||
};
|
||||
let sync = Arc::new(Service {
|
||||
network: service,
|
||||
protocol_id,
|
||||
handler,
|
||||
});
|
||||
|
||||
import_queue.start(
|
||||
Arc::downgrade(sync.handler.protocol.sync()),
|
||||
Arc::downgrade(&sync),
|
||||
Arc::downgrade(&chain)
|
||||
)?;
|
||||
|
||||
Ok(sync)
|
||||
}
|
||||
|
||||
/// Called when a new block is imported by the client.
|
||||
pub fn on_block_imported(&self, hash: B::Hash, header: &B::Header) {
|
||||
self.network.with_context(self.protocol_id, |context| {
|
||||
self.handler.protocol.on_block_imported(&mut NetSyncIo::new(context), hash, header)
|
||||
});
|
||||
}
|
||||
|
||||
/// Called when new transactons are imported by the client.
|
||||
pub fn trigger_repropagate(&self) {
|
||||
self.network.with_context(self.protocol_id, |context| {
|
||||
self.handler.protocol.propagate_extrinsics(&mut NetSyncIo::new(context));
|
||||
});
|
||||
}
|
||||
|
||||
/// Execute a closure with the chain-specific network specialization.
|
||||
/// If the network is unavailable, this will return `None`.
|
||||
pub fn with_spec<F, U>(&self, f: F) -> Option<U>
|
||||
where F: FnOnce(&mut S, &mut Context<B>) -> U
|
||||
{
|
||||
let mut res = None;
|
||||
self.network.with_context(self.protocol_id, |context| {
|
||||
res = Some(self.handler.protocol.with_spec(&mut NetSyncIo::new(context), f))
|
||||
});
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT + 'static, S: Specialization<B>, H:ExHashT> Drop for Service<B, S, H> {
|
||||
fn drop(&mut self) {
|
||||
self.handler.protocol.stop();
|
||||
}
|
||||
}
|
||||
impl<B: BlockT + 'static, S: Specialization<B>, H: ExHashT> ExecuteInContext<B> for Service<B, S, H> {
|
||||
fn execute_in_context<F: Fn(&mut ::protocol::Context<B>)>(&self, closure: F) {
|
||||
self.network.with_context(self.protocol_id, |context| {
|
||||
closure(&mut ProtocolContext::new(self.handler.protocol.context_data(), &mut NetSyncIo::new(context)))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT + 'static, S: Specialization<B>, H: ExHashT> SyncProvider<B> for Service<B, S, H> {
|
||||
/// Get sync status
|
||||
fn status(&self) -> ProtocolStatus<B> {
|
||||
self.handler.protocol.status()
|
||||
}
|
||||
|
||||
/// Get sync peers
|
||||
fn peers(&self) -> Vec<PeerInfo<B>> {
|
||||
self.network.with_context_eval(self.protocol_id, |ctx| {
|
||||
let peer_ids = self.network.connected_peers();
|
||||
|
||||
peer_ids.into_iter().filter_map(|who| {
|
||||
let session_info = match ctx.session_info(who) {
|
||||
None => return None,
|
||||
Some(info) => info,
|
||||
};
|
||||
|
||||
Some(PeerInfo {
|
||||
id: session_info.id.map(|id| format!("{:x}", id)),
|
||||
client_version: session_info.client_version,
|
||||
capabilities: session_info.peer_capabilities.into_iter().map(|c| c.to_string()).collect(),
|
||||
remote_address: session_info.remote_address,
|
||||
local_address: session_info.local_address,
|
||||
dot_info: self.handler.protocol.peer_info(who),
|
||||
})
|
||||
}).collect()
|
||||
}).unwrap_or_else(Vec::new)
|
||||
}
|
||||
|
||||
fn node_id(&self) -> Option<String> {
|
||||
self.network.external_url()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockT + 'static, S: Specialization<B>, H: ExHashT> NetworkProtocolHandler for ProtocolHandler<B, S, H> {
|
||||
fn initialize(&self, io: &NetworkContext) {
|
||||
io.register_timer(TICK_TOKEN, TICK_TIMEOUT)
|
||||
.expect("Error registering sync timer");
|
||||
|
||||
io.register_timer(PROPAGATE_TOKEN, PROPAGATE_TIMEOUT)
|
||||
.expect("Error registering transaction propagation timer");
|
||||
}
|
||||
|
||||
fn read(&self, io: &NetworkContext, peer: &NodeIndex, _packet_id: u8, data: &[u8]) {
|
||||
self.protocol.handle_packet(&mut NetSyncIo::new(io), *peer, data);
|
||||
}
|
||||
|
||||
fn connected(&self, io: &NetworkContext, peer: &NodeIndex) {
|
||||
self.protocol.on_peer_connected(&mut NetSyncIo::new(io), *peer);
|
||||
}
|
||||
|
||||
fn disconnected(&self, io: &NetworkContext, peer: &NodeIndex) {
|
||||
self.protocol.on_peer_disconnected(&mut NetSyncIo::new(io), *peer);
|
||||
}
|
||||
|
||||
fn timeout(&self, io: &NetworkContext, timer: TimerToken) {
|
||||
match timer {
|
||||
TICK_TOKEN => self.protocol.tick(&mut NetSyncIo::new(io)),
|
||||
PROPAGATE_TOKEN => self.protocol.propagate_extrinsics(&mut NetSyncIo::new(io)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for managing network
|
||||
pub trait ManageNetwork: Send + Sync {
|
||||
/// Set to allow unreserved peers to connect
|
||||
fn accept_unreserved_peers(&self);
|
||||
/// Set to deny unreserved peers to connect
|
||||
fn deny_unreserved_peers(&self);
|
||||
/// Remove reservation for the peer
|
||||
fn remove_reserved_peer(&self, peer: String) -> Result<(), String>;
|
||||
/// Add reserved peer
|
||||
fn add_reserved_peer(&self, peer: String) -> Result<(), String>;
|
||||
}
|
||||
|
||||
|
||||
impl<B: BlockT + 'static, S: Specialization<B>, H: ExHashT> ManageNetwork for Service<B, S, H> {
|
||||
fn accept_unreserved_peers(&self) {
|
||||
self.network.set_non_reserved_mode(NonReservedPeerMode::Accept);
|
||||
}
|
||||
|
||||
fn deny_unreserved_peers(&self) {
|
||||
self.network.set_non_reserved_mode(NonReservedPeerMode::Deny);
|
||||
}
|
||||
|
||||
fn remove_reserved_peer(&self, peer: String) -> Result<(), String> {
|
||||
self.network.remove_reserved_peer(&peer).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn add_reserved_peer(&self, peer: String) -> Result<(), String> {
|
||||
self.network.add_reserved_peer(&peer).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Specializations of the substrate network protocol to allow more complex forms of communication.
|
||||
|
||||
use ::NodeIndex;
|
||||
use runtime_primitives::traits::Block as BlockT;
|
||||
use protocol::Context;
|
||||
|
||||
/// A specialization of the substrate network protocol. Handles events and sends messages.
|
||||
pub trait Specialization<B: BlockT>: Send + Sync + 'static {
|
||||
/// Get the current specialization-status.
|
||||
fn status(&self) -> Vec<u8>;
|
||||
|
||||
/// Called on start-up.
|
||||
fn on_start(&mut self) { }
|
||||
|
||||
/// Called when a peer successfully handshakes.
|
||||
fn on_connect(&mut self, ctx: &mut Context<B>, who: NodeIndex, status: ::message::Status<B>);
|
||||
|
||||
/// Called when a peer is disconnected. If the peer ID is unknown, it should be ignored.
|
||||
fn on_disconnect(&mut self, ctx: &mut Context<B>, who: NodeIndex);
|
||||
|
||||
/// Called when a network-specific message arrives.
|
||||
fn on_message(&mut self, ctx: &mut Context<B>, who: NodeIndex, message: ::message::Message<B>);
|
||||
|
||||
/// Called on abort.
|
||||
fn on_abort(&mut self) { }
|
||||
|
||||
/// Called periodically to maintain peers and handle timeouts.
|
||||
fn maintain_peers(&mut self, _ctx: &mut Context<B>) { }
|
||||
|
||||
/// Called when a block is _imported_ at the head of the chain (not during major sync).
|
||||
fn on_block_imported(&mut self, _ctx: &mut Context<B>, _hash: B::Hash, _header: &B::Header) { }
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use protocol::Context;
|
||||
use network_libp2p::{Severity, NodeIndex};
|
||||
use client::{BlockStatus, BlockOrigin, ClientInfo};
|
||||
use client::error::Error as ClientError;
|
||||
use blocks::{self, BlockCollection};
|
||||
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, NumberFor};
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use message::{self, generic::Message as GenericMessage};
|
||||
use service::Roles;
|
||||
use import_queue::ImportQueue;
|
||||
|
||||
// Maximum blocks to request in a single packet.
|
||||
const MAX_BLOCKS_TO_REQUEST: usize = 128;
|
||||
// Maximum blocks to store in the import queue.
|
||||
const MAX_IMPORTING_BLOCKS: usize = 2048;
|
||||
|
||||
struct PeerSync<B: BlockT> {
|
||||
pub common_hash: B::Hash,
|
||||
pub common_number: NumberFor<B>,
|
||||
pub best_hash: B::Hash,
|
||||
pub best_number: NumberFor<B>,
|
||||
pub state: PeerSyncState<B>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
enum PeerSyncState<B: BlockT> {
|
||||
AncestorSearch(NumberFor<B>),
|
||||
Available,
|
||||
DownloadingNew(NumberFor<B>),
|
||||
DownloadingStale(B::Hash),
|
||||
}
|
||||
|
||||
/// Relay chain sync strategy.
|
||||
pub struct ChainSync<B: BlockT> {
|
||||
genesis_hash: B::Hash,
|
||||
peers: HashMap<NodeIndex, PeerSync<B>>,
|
||||
blocks: BlockCollection<B>,
|
||||
best_queued_number: NumberFor<B>,
|
||||
best_queued_hash: B::Hash,
|
||||
required_block_attributes: message::BlockAttributes,
|
||||
import_queue: Arc<ImportQueue<B>>,
|
||||
}
|
||||
|
||||
/// Reported sync state.
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub enum SyncState {
|
||||
/// Initial sync is complete, keep-up sync is active.
|
||||
Idle,
|
||||
/// Actively catching up with the chain.
|
||||
Downloading
|
||||
}
|
||||
|
||||
/// Syncing status and statistics
|
||||
#[derive(Clone)]
|
||||
pub struct Status<B: BlockT> {
|
||||
/// Current global sync state.
|
||||
pub state: SyncState,
|
||||
/// Target sync block number.
|
||||
pub best_seen_block: Option<NumberFor<B>>,
|
||||
}
|
||||
|
||||
impl<B: BlockT> ChainSync<B> {
|
||||
/// Create a new instance.
|
||||
pub(crate) fn new(role: Roles, info: &ClientInfo<B>, import_queue: Arc<ImportQueue<B>>) -> Self {
|
||||
let mut required_block_attributes = message::BlockAttributes::HEADER | message::BlockAttributes::JUSTIFICATION;
|
||||
if role.intersects(Roles::FULL | Roles::AUTHORITY) {
|
||||
required_block_attributes |= message::BlockAttributes::BODY;
|
||||
}
|
||||
|
||||
ChainSync {
|
||||
genesis_hash: info.chain.genesis_hash,
|
||||
peers: HashMap::new(),
|
||||
blocks: BlockCollection::new(),
|
||||
best_queued_hash: info.best_queued_hash.unwrap_or(info.chain.best_hash),
|
||||
best_queued_number: info.best_queued_number.unwrap_or(info.chain.best_number),
|
||||
required_block_attributes,
|
||||
import_queue,
|
||||
}
|
||||
}
|
||||
|
||||
fn best_seen_block(&self) -> Option<NumberFor<B>> {
|
||||
self.peers.values().max_by_key(|p| p.best_number).map(|p| p.best_number)
|
||||
}
|
||||
|
||||
/// Returns import queue reference.
|
||||
pub(crate) fn import_queue(&self) -> Arc<ImportQueue<B>> {
|
||||
self.import_queue.clone()
|
||||
}
|
||||
|
||||
/// Returns sync status.
|
||||
pub(crate) fn status(&self) -> Status<B> {
|
||||
let best_seen = self.best_seen_block();
|
||||
let state = match &best_seen {
|
||||
&Some(n) if n > self.best_queued_number && n - self.best_queued_number > As::sa(5) => SyncState::Downloading,
|
||||
_ => SyncState::Idle,
|
||||
};
|
||||
Status {
|
||||
state: state,
|
||||
best_seen_block: best_seen,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle new connected peer.
|
||||
pub(crate) fn new_peer(&mut self, protocol: &mut Context<B>, who: NodeIndex) {
|
||||
if let Some(info) = protocol.peer_info(who) {
|
||||
match (block_status(&*protocol.client(), &*self.import_queue, info.best_hash), info.best_number) {
|
||||
(Err(e), _) => {
|
||||
debug!(target:"sync", "Error reading blockchain: {:?}", e);
|
||||
protocol.report_peer(who, Severity::Useless(&format!("Error legimimately reading blockchain status: {:?}", e)));
|
||||
},
|
||||
(Ok(BlockStatus::KnownBad), _) => {
|
||||
protocol.report_peer(who, Severity::Bad(&format!("New peer with known bad best block {} ({}).", info.best_hash, info.best_number)));
|
||||
},
|
||||
(Ok(BlockStatus::Unknown), b) if b == As::sa(0) => {
|
||||
protocol.report_peer(who, Severity::Bad(&format!("New peer with unknown genesis hash {} ({}).", info.best_hash, info.best_number)));
|
||||
},
|
||||
(Ok(BlockStatus::Unknown), _) => {
|
||||
let our_best = self.best_queued_number;
|
||||
if our_best > As::sa(0) {
|
||||
debug!(target:"sync", "New peer with unknown best hash {} ({}), searching for common ancestor.", info.best_hash, info.best_number);
|
||||
self.peers.insert(who, PeerSync {
|
||||
common_hash: self.genesis_hash,
|
||||
common_number: As::sa(0),
|
||||
best_hash: info.best_hash,
|
||||
best_number: info.best_number,
|
||||
state: PeerSyncState::AncestorSearch(our_best),
|
||||
});
|
||||
Self::request_ancestry(protocol, who, our_best)
|
||||
} else {
|
||||
// We are at genesis, just start downloading
|
||||
debug!(target:"sync", "New peer with best hash {} ({}).", info.best_hash, info.best_number);
|
||||
self.peers.insert(who, PeerSync {
|
||||
common_hash: self.genesis_hash,
|
||||
common_number: As::sa(0),
|
||||
best_hash: info.best_hash,
|
||||
best_number: info.best_number,
|
||||
state: PeerSyncState::Available,
|
||||
});
|
||||
self.download_new(protocol, who)
|
||||
}
|
||||
},
|
||||
(Ok(BlockStatus::Queued), _) | (Ok(BlockStatus::InChain), _) => {
|
||||
debug!(target:"sync", "New peer with known best hash {} ({}).", info.best_hash, info.best_number);
|
||||
self.peers.insert(who, PeerSync {
|
||||
common_hash: info.best_hash,
|
||||
common_number: info.best_number,
|
||||
best_hash: info.best_hash,
|
||||
best_number: info.best_number,
|
||||
state: PeerSyncState::Available,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_block_data(&mut self, protocol: &mut Context<B>, who: NodeIndex, _request: message::BlockRequest<B>, response: message::BlockResponse<B>) {
|
||||
let new_blocks = if let Some(ref mut peer) = self.peers.get_mut(&who) {
|
||||
match peer.state {
|
||||
PeerSyncState::DownloadingNew(start_block) => {
|
||||
self.blocks.clear_peer_download(who);
|
||||
peer.state = PeerSyncState::Available;
|
||||
|
||||
self.blocks.insert(start_block, response.blocks, who);
|
||||
self.blocks.drain(self.best_queued_number + As::sa(1))
|
||||
},
|
||||
PeerSyncState::DownloadingStale(_) => {
|
||||
peer.state = PeerSyncState::Available;
|
||||
response.blocks.into_iter().map(|b| blocks::BlockData {
|
||||
origin: who,
|
||||
block: b
|
||||
}).collect()
|
||||
},
|
||||
PeerSyncState::AncestorSearch(n) => {
|
||||
match response.blocks.get(0) {
|
||||
Some(ref block) => {
|
||||
trace!(target: "sync", "Got ancestry block #{} ({}) from peer {}", n, block.hash, who);
|
||||
match protocol.client().block_hash(n) {
|
||||
Ok(Some(block_hash)) if block_hash == block.hash => {
|
||||
if peer.common_number < n {
|
||||
peer.common_hash = block.hash;
|
||||
peer.common_number = n;
|
||||
}
|
||||
peer.state = PeerSyncState::Available;
|
||||
trace!(target:"sync", "Found common ancestor for peer {}: {} ({})", who, block.hash, n);
|
||||
vec![]
|
||||
},
|
||||
Ok(our_best) if n > As::sa(0) => {
|
||||
trace!(target:"sync", "Ancestry block mismatch for peer {}: theirs: {} ({}), ours: {:?}", who, block.hash, n, our_best);
|
||||
let n = n - As::sa(1);
|
||||
peer.state = PeerSyncState::AncestorSearch(n);
|
||||
Self::request_ancestry(protocol, who, n);
|
||||
return;
|
||||
},
|
||||
Ok(_) => { // genesis mismatch
|
||||
trace!(target:"sync", "Ancestry search: genesis mismatch for peer {}", who);
|
||||
protocol.report_peer(who, Severity::Bad("Ancestry search: genesis mismatch for peer"));
|
||||
return;
|
||||
},
|
||||
Err(e) => {
|
||||
protocol.report_peer(who, Severity::Useless(&format!("Error answering legitimate blockchain query: {:?}", e)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
trace!(target:"sync", "Invalid response when searching for ancestor from {}", who);
|
||||
protocol.report_peer(who, Severity::Bad("Invalid response when searching for ancestor"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
PeerSyncState::Available => Vec::new(),
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let best_seen = self.best_seen_block();
|
||||
let is_best = new_blocks.first().and_then(|b| b.block.header.as_ref()).map(|h| best_seen.as_ref().map_or(false, |n| h.number() >= n));
|
||||
let origin = if is_best.unwrap_or_default() { BlockOrigin::NetworkBroadcast } else { BlockOrigin::NetworkInitialSync };
|
||||
let import_queue = self.import_queue.clone();
|
||||
if let Some((hash, number)) = new_blocks.last()
|
||||
.and_then(|b| b.block.header.as_ref().map(|h|(b.block.hash.clone(), *h.number())))
|
||||
{
|
||||
if number > self.best_queued_number {
|
||||
self.best_queued_number = number;
|
||||
self.best_queued_hash = hash;
|
||||
}
|
||||
}
|
||||
import_queue.import_blocks(self, protocol, (origin, new_blocks));
|
||||
self.maintain_sync(protocol);
|
||||
}
|
||||
|
||||
pub fn maintain_sync(&mut self, protocol: &mut Context<B>) {
|
||||
let peers: Vec<NodeIndex> = self.peers.keys().map(|p| *p).collect();
|
||||
for peer in peers {
|
||||
self.download_new(protocol, peer);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_imported(&mut self, hash: &B::Hash, number: NumberFor<B>) {
|
||||
if number > self.best_queued_number {
|
||||
self.best_queued_number = number;
|
||||
self.best_queued_hash = *hash;
|
||||
}
|
||||
// Update common blocks
|
||||
for (_, peer) in self.peers.iter_mut() {
|
||||
trace!("Updating peer info ours={}, theirs={}", number, peer.best_number);
|
||||
if peer.best_number >= number {
|
||||
peer.common_number = number;
|
||||
peer.common_hash = *hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_chain_info(&mut self, best_header: &B::Header) {
|
||||
let hash = best_header.hash();
|
||||
self.block_imported(&hash, best_header.number().clone())
|
||||
}
|
||||
|
||||
pub(crate) fn on_block_announce(&mut self, protocol: &mut Context<B>, who: NodeIndex, hash: B::Hash, header: &B::Header) {
|
||||
let number = *header.number();
|
||||
if let Some(ref mut peer) = self.peers.get_mut(&who) {
|
||||
if number > peer.best_number {
|
||||
peer.best_number = number;
|
||||
peer.best_hash = hash;
|
||||
}
|
||||
if number <= self.best_queued_number && number > peer.common_number {
|
||||
peer.common_number = number
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.is_known_or_already_downloading(protocol, &hash) {
|
||||
let stale = number <= self.best_queued_number;
|
||||
if stale {
|
||||
if !self.is_known_or_already_downloading(protocol, header.parent_hash()) {
|
||||
trace!(target: "sync", "Ignoring unknown stale block announce from {}: {} {:?}", who, hash, header);
|
||||
} else {
|
||||
trace!(target: "sync", "Downloading new stale block announced from {}: {} {:?}", who, hash, header);
|
||||
self.download_stale(protocol, who, &hash);
|
||||
}
|
||||
} else {
|
||||
trace!(target: "sync", "Downloading new block announced from {}: {} {:?}", who, hash, header);
|
||||
self.download_new(protocol, who);
|
||||
}
|
||||
} else {
|
||||
trace!(target: "sync", "Known block announce from {}: {}", who, hash);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_known_or_already_downloading(&self, protocol: &mut Context<B>, hash: &B::Hash) -> bool {
|
||||
self.peers.iter().any(|(_, p)| p.state == PeerSyncState::DownloadingStale(*hash))
|
||||
|| block_status(&*protocol.client(), &*self.import_queue, *hash).ok().map_or(false, |s| s != BlockStatus::Unknown)
|
||||
}
|
||||
|
||||
pub(crate) fn peer_disconnected(&mut self, protocol: &mut Context<B>, who: NodeIndex) {
|
||||
self.blocks.clear_peer_download(who);
|
||||
self.peers.remove(&who);
|
||||
self.maintain_sync(protocol);
|
||||
}
|
||||
|
||||
pub(crate) fn restart(&mut self, protocol: &mut Context<B>) {
|
||||
self.import_queue.clear();
|
||||
self.blocks.clear();
|
||||
let ids: Vec<NodeIndex> = self.peers.keys().map(|p| *p).collect();
|
||||
for id in ids {
|
||||
self.new_peer(protocol, id);
|
||||
}
|
||||
match protocol.client().info() {
|
||||
Ok(info) => {
|
||||
self.best_queued_hash = info.best_queued_hash.unwrap_or(info.chain.best_hash);
|
||||
self.best_queued_number = info.best_queued_number.unwrap_or(info.chain.best_number);
|
||||
},
|
||||
Err(e) => {
|
||||
debug!(target:"sync", "Error reading blockchain: {:?}", e);
|
||||
self.best_queued_hash = self.genesis_hash;
|
||||
self.best_queued_number = As::sa(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.blocks.clear();
|
||||
self.peers.clear();
|
||||
}
|
||||
|
||||
// Download old block.
|
||||
fn download_stale(&mut self, protocol: &mut Context<B>, who: NodeIndex, hash: &B::Hash) {
|
||||
if let Some(ref mut peer) = self.peers.get_mut(&who) {
|
||||
match peer.state {
|
||||
PeerSyncState::Available => {
|
||||
let request = message::generic::BlockRequest {
|
||||
id: 0,
|
||||
fields: self.required_block_attributes.clone(),
|
||||
from: message::FromBlock::Hash(*hash),
|
||||
to: None,
|
||||
direction: message::Direction::Ascending,
|
||||
max: Some(1),
|
||||
};
|
||||
peer.state = PeerSyncState::DownloadingStale(*hash);
|
||||
protocol.send_message(who, GenericMessage::BlockRequest(request));
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Issue a request for a peer to download new blocks, if any are available
|
||||
fn download_new(&mut self, protocol: &mut Context<B>, who: NodeIndex) {
|
||||
if let Some(ref mut peer) = self.peers.get_mut(&who) {
|
||||
let import_status = self.import_queue.status();
|
||||
// when there are too many blocks in the queue => do not try to download new blocks
|
||||
if import_status.importing_count > MAX_IMPORTING_BLOCKS {
|
||||
return;
|
||||
}
|
||||
// we should not download already queued blocks
|
||||
let common_number = ::std::cmp::max(peer.common_number, import_status.best_importing_number);
|
||||
|
||||
trace!(target: "sync", "Considering new block download from {}, common block is {}, best is {:?}", who, common_number, peer.best_number);
|
||||
match peer.state {
|
||||
PeerSyncState::Available => {
|
||||
if let Some(range) = self.blocks.needed_blocks(who, MAX_BLOCKS_TO_REQUEST, peer.best_number, common_number) {
|
||||
trace!(target: "sync", "Requesting blocks from {}, ({} to {})", who, range.start, range.end);
|
||||
let request = message::generic::BlockRequest {
|
||||
id: 0,
|
||||
fields: self.required_block_attributes.clone(),
|
||||
from: message::FromBlock::Number(range.start),
|
||||
to: None,
|
||||
direction: message::Direction::Ascending,
|
||||
max: Some((range.end - range.start).as_() as u32),
|
||||
};
|
||||
peer.state = PeerSyncState::DownloadingNew(range.start);
|
||||
protocol.send_message(who, GenericMessage::BlockRequest(request));
|
||||
} else {
|
||||
trace!(target: "sync", "Nothing to request");
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn request_ancestry(protocol: &mut Context<B>, who: NodeIndex, block: NumberFor<B>) {
|
||||
trace!(target: "sync", "Requesting ancestry block #{} from {}", block, who);
|
||||
let request = message::generic::BlockRequest {
|
||||
id: 0,
|
||||
fields: message::BlockAttributes::HEADER | message::BlockAttributes::JUSTIFICATION,
|
||||
from: message::FromBlock::Number(block),
|
||||
to: None,
|
||||
direction: message::Direction::Ascending,
|
||||
max: Some(1),
|
||||
};
|
||||
protocol.send_message(who, GenericMessage::BlockRequest(request));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get block status, taking into account import queue.
|
||||
fn block_status<B: BlockT>(
|
||||
chain: &::chain::Client<B>,
|
||||
queue: &ImportQueue<B>,
|
||||
hash: B::Hash) -> Result<BlockStatus, ClientError>
|
||||
{
|
||||
if queue.is_importing(&hash) {
|
||||
return Ok(BlockStatus::Queued);
|
||||
}
|
||||
|
||||
chain.block_status(&BlockId::Hash(hash))
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
mod sync;
|
||||
|
||||
use std::collections::{VecDeque, HashSet, HashMap};
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use client;
|
||||
use client::block_builder::BlockBuilder;
|
||||
use runtime_primitives::traits::Block as BlockT;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use io::SyncIo;
|
||||
use protocol::{Context, Protocol};
|
||||
use primitives::{Blake2Hasher, RlpCodec};
|
||||
use config::ProtocolConfig;
|
||||
use service::TransactionPool;
|
||||
use network_libp2p::{NodeIndex, SessionInfo, Severity};
|
||||
use keyring::Keyring;
|
||||
use codec::Encode;
|
||||
use import_queue::tests::SyncImportQueue;
|
||||
use test_client::{self, TestClient};
|
||||
use test_client::runtime::{Block, Hash, Transfer, Extrinsic};
|
||||
use specialization::Specialization;
|
||||
|
||||
pub struct DummySpecialization;
|
||||
|
||||
impl Specialization<Block> for DummySpecialization {
|
||||
fn status(&self) -> Vec<u8> { vec![] }
|
||||
|
||||
fn on_connect(&mut self, _ctx: &mut Context<Block>, _peer_id: NodeIndex, _status: ::message::Status<Block>) {
|
||||
|
||||
}
|
||||
|
||||
fn on_disconnect(&mut self, _ctx: &mut Context<Block>, _peer_id: NodeIndex) {
|
||||
|
||||
}
|
||||
|
||||
fn on_message(&mut self, _ctx: &mut Context<Block>, _peer_id: NodeIndex, _message: ::message::Message<Block>) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestIo<'p> {
|
||||
queue: &'p RwLock<VecDeque<TestPacket>>,
|
||||
pub to_disconnect: HashSet<NodeIndex>,
|
||||
packets: Vec<TestPacket>,
|
||||
peers_info: HashMap<NodeIndex, String>,
|
||||
_sender: Option<NodeIndex>,
|
||||
}
|
||||
|
||||
impl<'p> TestIo<'p> where {
|
||||
pub fn new(queue: &'p RwLock<VecDeque<TestPacket>>, sender: Option<NodeIndex>) -> TestIo<'p> {
|
||||
TestIo {
|
||||
queue: queue,
|
||||
_sender: sender,
|
||||
to_disconnect: HashSet::new(),
|
||||
packets: Vec::new(),
|
||||
peers_info: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'p> Drop for TestIo<'p> {
|
||||
fn drop(&mut self) {
|
||||
self.queue.write().extend(self.packets.drain(..));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'p> SyncIo for TestIo<'p> {
|
||||
fn report_peer(&mut self, who: NodeIndex, _reason: Severity) {
|
||||
self.to_disconnect.insert(who);
|
||||
}
|
||||
|
||||
fn is_expired(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn send(&mut self, who: NodeIndex, data: Vec<u8>) {
|
||||
self.packets.push(TestPacket {
|
||||
data: data,
|
||||
recipient: who,
|
||||
});
|
||||
}
|
||||
|
||||
fn peer_info(&self, who: NodeIndex) -> String {
|
||||
self.peers_info.get(&who)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| who.to_string())
|
||||
}
|
||||
|
||||
fn peer_session_info(&self, _peer_id: NodeIndex) -> Option<SessionInfo> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Mocked subprotocol packet
|
||||
pub struct TestPacket {
|
||||
data: Vec<u8>,
|
||||
recipient: NodeIndex,
|
||||
}
|
||||
|
||||
pub struct Peer {
|
||||
client: Arc<client::Client<test_client::Backend, test_client::Executor, Block>>,
|
||||
pub sync: Protocol<Block, DummySpecialization, Hash>,
|
||||
pub queue: RwLock<VecDeque<TestPacket>>,
|
||||
}
|
||||
|
||||
impl Peer {
|
||||
/// Called after blockchain has been populated to updated current state.
|
||||
fn start(&self) {
|
||||
// Update the sync state to the latest chain state.
|
||||
let info = self.client.info().expect("In-mem client does not fail");
|
||||
let header = self.client.header(&BlockId::Hash(info.chain.best_hash)).unwrap().unwrap();
|
||||
self.sync.on_block_imported(&mut TestIo::new(&self.queue, None), info.chain.best_hash, &header);
|
||||
}
|
||||
|
||||
/// Called on connection to other indicated peer.
|
||||
fn on_connect(&self, other: NodeIndex) {
|
||||
self.sync.on_peer_connected(&mut TestIo::new(&self.queue, Some(other)), other);
|
||||
}
|
||||
|
||||
/// Called on disconnect from other indicated peer.
|
||||
fn on_disconnect(&self, other: NodeIndex) {
|
||||
let mut io = TestIo::new(&self.queue, Some(other));
|
||||
self.sync.on_peer_disconnected(&mut io, other);
|
||||
}
|
||||
|
||||
/// Receive a message from another peer. Return a set of peers to disconnect.
|
||||
fn receive_message(&self, from: NodeIndex, msg: TestPacket) -> HashSet<NodeIndex> {
|
||||
let mut io = TestIo::new(&self.queue, Some(from));
|
||||
self.sync.handle_packet(&mut io, from, &msg.data);
|
||||
self.flush();
|
||||
io.to_disconnect.clone()
|
||||
}
|
||||
|
||||
/// Produce the next pending message to send to another peer.
|
||||
fn pending_message(&self) -> Option<TestPacket> {
|
||||
self.flush();
|
||||
self.queue.write().pop_front()
|
||||
}
|
||||
|
||||
/// Whether this peer is done syncing (has no messages to send).
|
||||
fn is_done(&self) -> bool {
|
||||
self.queue.read().is_empty()
|
||||
}
|
||||
|
||||
/// Execute a "sync step". This is called for each peer after it sends a packet.
|
||||
fn sync_step(&self) {
|
||||
self.flush();
|
||||
self.sync.tick(&mut TestIo::new(&self.queue, None));
|
||||
}
|
||||
|
||||
/// Restart sync for a peer.
|
||||
fn restart_sync(&self) {
|
||||
self.sync.abort();
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
}
|
||||
|
||||
fn generate_blocks<F>(&self, count: usize, mut edit_block: F)
|
||||
where F: FnMut(&mut BlockBuilder<test_client::Backend, test_client::Executor, Block, Blake2Hasher, RlpCodec>)
|
||||
{
|
||||
for _ in 0 .. count {
|
||||
let mut builder = self.client.new_block().unwrap();
|
||||
edit_block(&mut builder);
|
||||
let block = builder.bake().unwrap();
|
||||
trace!("Generating {}, (#{})", block.hash(), block.header.number);
|
||||
self.client.justify_and_import(client::BlockOrigin::File, block).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn push_blocks(&self, count: usize, with_tx: bool) {
|
||||
let mut nonce = 0;
|
||||
if with_tx {
|
||||
self.generate_blocks(count, |builder| {
|
||||
let transfer = Transfer {
|
||||
from: Keyring::Alice.to_raw_public().into(),
|
||||
to: Keyring::Alice.to_raw_public().into(),
|
||||
amount: 1,
|
||||
nonce,
|
||||
};
|
||||
let signature = Keyring::from_raw_public(transfer.from.0).unwrap().sign(&transfer.encode()).into();
|
||||
builder.push(Extrinsic { transfer, signature }).unwrap();
|
||||
nonce = nonce + 1;
|
||||
});
|
||||
} else {
|
||||
self.generate_blocks(count, |_| ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmptyTransactionPool;
|
||||
|
||||
impl TransactionPool<Hash, Block> for EmptyTransactionPool {
|
||||
fn transactions(&self) -> Vec<(Hash, Extrinsic)> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn import(&self, _transaction: &Extrinsic) -> Option<Hash> {
|
||||
None
|
||||
}
|
||||
|
||||
fn on_broadcasted(&self, _: HashMap<Hash, Vec<String>>) {}
|
||||
}
|
||||
|
||||
pub struct TestNet {
|
||||
peers: Vec<Arc<Peer>>,
|
||||
started: bool,
|
||||
disconnect_events: Vec<(NodeIndex, NodeIndex)>, //disconnected (initiated by, to)
|
||||
}
|
||||
|
||||
impl TestNet {
|
||||
fn new(n: usize) -> Self {
|
||||
Self::new_with_config(n, ProtocolConfig::default())
|
||||
}
|
||||
|
||||
fn new_with_config(n: usize, config: ProtocolConfig) -> Self {
|
||||
let mut net = TestNet {
|
||||
peers: Vec::new(),
|
||||
started: false,
|
||||
disconnect_events: Vec::new(),
|
||||
};
|
||||
|
||||
for _ in 0..n {
|
||||
net.add_peer(&config);
|
||||
}
|
||||
net
|
||||
}
|
||||
|
||||
pub fn add_peer(&mut self, config: &ProtocolConfig) {
|
||||
let client = Arc::new(test_client::new());
|
||||
let tx_pool = Arc::new(EmptyTransactionPool);
|
||||
let import_queue = Arc::new(SyncImportQueue);
|
||||
let sync = Protocol::new(config.clone(), client.clone(), import_queue, None, tx_pool, DummySpecialization).unwrap();
|
||||
self.peers.push(Arc::new(Peer {
|
||||
sync: sync,
|
||||
client: client,
|
||||
queue: RwLock::new(VecDeque::new()),
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn peer(&self, i: usize) -> &Peer {
|
||||
&self.peers[i]
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
if self.started {
|
||||
return;
|
||||
}
|
||||
for peer in 0..self.peers.len() {
|
||||
self.peers[peer].start();
|
||||
for client in 0..self.peers.len() {
|
||||
if peer != client {
|
||||
self.peers[peer].on_connect(client as NodeIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.started = true;
|
||||
}
|
||||
|
||||
fn sync_step(&mut self) {
|
||||
for peer in 0..self.peers.len() {
|
||||
let packet = self.peers[peer].pending_message();
|
||||
if let Some(packet) = packet {
|
||||
let disconnecting = {
|
||||
let recipient = packet.recipient;
|
||||
trace!("--- {} -> {} ---", peer, recipient);
|
||||
let to_disconnect = self.peers[recipient].receive_message(peer as NodeIndex, packet);
|
||||
for d in &to_disconnect {
|
||||
// notify this that disconnecting peers are disconnecting
|
||||
self.peers[recipient].on_disconnect(*d as NodeIndex);
|
||||
self.disconnect_events.push((peer, *d));
|
||||
}
|
||||
to_disconnect
|
||||
};
|
||||
for d in &disconnecting {
|
||||
// notify other peers that this peer is disconnecting
|
||||
self.peers[*d].on_disconnect(peer as NodeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
self.sync_step_peer(peer);
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_step_peer(&mut self, peer_num: usize) {
|
||||
self.peers[peer_num].sync_step();
|
||||
}
|
||||
|
||||
fn restart_peer(&mut self, i: usize) {
|
||||
self.peers[i].restart_sync();
|
||||
}
|
||||
|
||||
fn sync(&mut self) -> u32 {
|
||||
self.start();
|
||||
let mut total_steps = 0;
|
||||
while !self.done() {
|
||||
self.sync_step();
|
||||
total_steps += 1;
|
||||
}
|
||||
total_steps
|
||||
}
|
||||
|
||||
fn sync_steps(&mut self, count: usize) {
|
||||
self.start();
|
||||
for _ in 0..count {
|
||||
self.sync_step();
|
||||
}
|
||||
}
|
||||
|
||||
fn done(&self) -> bool {
|
||||
self.peers.iter().all(|p| p.is_done())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use client::backend::Backend;
|
||||
use client::blockchain::HeaderBackend as BlockchainHeaderBackend;
|
||||
use sync::SyncState;
|
||||
use Roles;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sync_from_two_peers_works() {
|
||||
::env_logger::init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
net.peer(1).push_blocks(100, false);
|
||||
net.peer(2).push_blocks(100, false);
|
||||
net.sync();
|
||||
assert!(net.peer(0).client.backend().blockchain().equals_to(net.peer(1).client.backend().blockchain()));
|
||||
let status = net.peer(0).sync.status();
|
||||
assert_eq!(status.sync.state, SyncState::Idle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_from_two_peers_with_ancestry_search_works() {
|
||||
::env_logger::init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
net.peer(0).push_blocks(10, true);
|
||||
net.peer(1).push_blocks(100, false);
|
||||
net.peer(2).push_blocks(100, false);
|
||||
net.restart_peer(0);
|
||||
net.sync();
|
||||
assert!(net.peer(0).client.backend().blockchain().canon_equals_to(net.peer(1).client.backend().blockchain()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_long_chain_works() {
|
||||
let mut net = TestNet::new(2);
|
||||
net.peer(1).push_blocks(500, false);
|
||||
net.sync_steps(3);
|
||||
assert_eq!(net.peer(0).sync.status().sync.state, SyncState::Downloading);
|
||||
net.sync();
|
||||
assert!(net.peer(0).client.backend().blockchain().equals_to(net.peer(1).client.backend().blockchain()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_no_common_longer_chain_fails() {
|
||||
::env_logger::init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
net.peer(0).push_blocks(20, true);
|
||||
net.peer(1).push_blocks(20, false);
|
||||
net.sync();
|
||||
assert!(!net.peer(0).client.backend().blockchain().canon_equals_to(net.peer(1).client.backend().blockchain()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_after_fork_works() {
|
||||
::env_logger::init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
net.peer(0).push_blocks(30, false);
|
||||
net.peer(1).push_blocks(30, false);
|
||||
net.peer(2).push_blocks(30, false);
|
||||
|
||||
net.peer(0).push_blocks(10, true);
|
||||
net.peer(1).push_blocks(20, false);
|
||||
net.peer(2).push_blocks(20, false);
|
||||
|
||||
net.peer(1).push_blocks(10, true);
|
||||
net.peer(2).push_blocks(1, false);
|
||||
|
||||
// peer 1 has the best chain
|
||||
let peer1_chain = net.peer(1).client.backend().blockchain().clone();
|
||||
net.sync();
|
||||
assert!(net.peer(0).client.backend().blockchain().canon_equals_to(&peer1_chain));
|
||||
assert!(net.peer(1).client.backend().blockchain().canon_equals_to(&peer1_chain));
|
||||
assert!(net.peer(2).client.backend().blockchain().canon_equals_to(&peer1_chain));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks_are_not_announced_by_light_nodes() {
|
||||
::env_logger::init().ok();
|
||||
let mut net = TestNet::new(0);
|
||||
|
||||
// full peer0 is connected to light peer
|
||||
// light peer1 is connected to full peer2
|
||||
let mut light_config = ProtocolConfig::default();
|
||||
light_config.roles = Roles::LIGHT;
|
||||
net.add_peer(&ProtocolConfig::default());
|
||||
net.add_peer(&light_config);
|
||||
net.add_peer(&ProtocolConfig::default());
|
||||
|
||||
net.peer(0).push_blocks(1, false);
|
||||
net.peer(0).start();
|
||||
net.peer(1).start();
|
||||
net.peer(2).start();
|
||||
net.peer(0).on_connect(1);
|
||||
net.peer(1).on_connect(2);
|
||||
|
||||
// generate block at peer0 && run sync
|
||||
while !net.done() {
|
||||
net.sync_step();
|
||||
}
|
||||
|
||||
// peer 0 has the best chain
|
||||
// peer 1 has the best chain
|
||||
// peer 2 has genesis-chain only
|
||||
assert_eq!(net.peer(0).client.backend().blockchain().info().unwrap().best_number, 1);
|
||||
assert_eq!(net.peer(1).client.backend().blockchain().info().unwrap().best_number, 1);
|
||||
assert_eq!(net.peer(2).client.backend().blockchain().info().unwrap().best_number, 0);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
[package]
|
||||
name = "substrate-primitives"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
crunchy = "0.1"
|
||||
sr-std = { path = "../sr-std", default_features = false }
|
||||
parity-codec = { path = "../../codec", default_features = false }
|
||||
parity-codec-derive = { path = "../../codec/derive", default_features = false }
|
||||
elastic-array = {version = "0.10", optional = true }
|
||||
fixed-hash = { version = "0.2.2", default_features = false }
|
||||
rustc-hex = { version = "2.0", default_features = false }
|
||||
serde = { version = "1.0", default_features = false }
|
||||
serde_derive = { version = "1.0", optional = true }
|
||||
uint = { version = "0.4.1", default_features = false }
|
||||
rlp = { version = "0.2.4", optional = true }
|
||||
twox-hash = { version = "1.1.0", optional = true }
|
||||
byteorder = { version = "1.1", default_features = false }
|
||||
wasmi = { version = "0.4", optional = true }
|
||||
hashdb = { version = "0.2.1", default_features = false }
|
||||
patricia-trie = { version = "0.2.1", optional = true }
|
||||
plain_hasher = { version = "0.2", default_features = false }
|
||||
ring = { version = "0.12", optional = true }
|
||||
untrusted = { version = "0.5", optional = true }
|
||||
hex-literal = { version = "0.1", optional = true }
|
||||
base58 = { version = "0.1", optional = true }
|
||||
blake2-rfc = { version = "0.2.18", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-serializer = { path = "../serializer" }
|
||||
pretty_assertions = "0.4"
|
||||
heapsize = "0.4"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"wasmi",
|
||||
"uint/std",
|
||||
"fixed-hash/std",
|
||||
"fixed-hash/heapsizeof",
|
||||
"fixed-hash/libc",
|
||||
"parity-codec/std",
|
||||
"sr-std/std",
|
||||
"serde/std",
|
||||
"rustc-hex/std",
|
||||
"twox-hash",
|
||||
"blake2-rfc",
|
||||
"ring",
|
||||
"untrusted",
|
||||
"hex-literal",
|
||||
"base58",
|
||||
"serde_derive",
|
||||
"byteorder/std",
|
||||
"patricia-trie",
|
||||
"rlp",
|
||||
"elastic-array",
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user