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:
Gav Wood
2018-09-12 11:13:31 +02:00
committed by Arkadiy Paronyan
parent 8fe5aa4c81
commit 1e01162505
374 changed files with 2845 additions and 2902 deletions
+20
View File
@@ -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" }
+13
View File
@@ -0,0 +1,13 @@
= Substrate BFT
.Summary
[source, toml]
----
include::Cargo.toml[lines=2..5]
----
.Description
----
include::src/lib.rs[tag=description]
----
+95
View File
@@ -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
+35
View File
@@ -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"
+13
View File
@@ -0,0 +1,13 @@
= Substrate CLI
.Summary
[source, toml]
----
include::Cargo.toml[lines=2..5]
----
.Description
Polkadot CLI library
include::doc/shell-completion.adoc[]
+59
View File
@@ -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
----
+229
View File
@@ -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
+37
View File
@@ -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),
}
}
}
+124
View File
@@ -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)
}
}
+571
View File
@@ -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());
}
}
+69
View File
@@ -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);
}
+31
View File
@@ -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" }
+13
View File
@@ -0,0 +1,13 @@
= Client
.Summary
[source, toml]
----
include::Cargo.toml[lines=2..5]
----
.Description
----
include::src/lib.rs[tag=description]
----
+23
View File
@@ -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"
+432
View File
@@ -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()]));
}
}
+714
View File
@@ -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());
}
}
}
+392
View File
@@ -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);
}
}
+174
View File
@@ -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,
})
}
+107
View File
@@ -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>,
{}
+140
View File
@@ -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))
}
}
+92
View File
@@ -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,
}
+199
View File
@@ -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
}
}
+274
View File
@@ -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());
}
}
+798
View File
@@ -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 } } }"#
);
}
}
+154
View File
@@ -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 {}
+202
View File
@@ -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();
}
}
+439
View File
@@ -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);
}
+69
View File
@@ -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;
+229
View File
@@ -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();
}
}
+308
View File
@@ -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());
}
}
+77
View File
@@ -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)
}
+289
View File
@@ -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);
}
}
+32
View File
@@ -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 = []
+13
View File
@@ -0,0 +1,13 @@
= Executor
.Summary
[source, toml]
----
include::Cargo.toml[lines=2..5]
----
.Description
----
include::src/lib.rs[tag=description]
----
+81
View File
@@ -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 {}
+93
View File
@@ -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()
}
}
}
}
+676
View File
@@ -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()
);
}
}
+199
View File
@@ -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 ),*);
}
}
);
}
+216
View File
@@ -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"
+19
View File
@@ -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 = []
+8
View File
@@ -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
+122
View File
@@ -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)
}
+19
View File
@@ -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" }
+13
View File
@@ -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) }
}
+49
View File
@@ -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))
}
}
+606
View File
@@ -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())
}
}
+9
View File
@@ -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" }
+13
View File
@@ -0,0 +1,13 @@
= Keyring
.Summary
[source, toml]
----
include::Cargo.toml[lines=2..5]
----
.Description
----
include::src/lib.rs[tag=description]
----
+177
View File
@@ -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));
}
}
+18
View File
@@ -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"
+13
View File
@@ -0,0 +1,13 @@
= Keystore
.Summary
[source, toml]
----
include::Cargo.toml[lines=2..5]
----
.Description
----
include::src/lib.rs[tag=description]
----
+293
View File
@@ -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());
}
}
+13
View File
@@ -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"
]
+13
View File
@@ -0,0 +1,13 @@
= Substrate BFT
.Summary
[source, toml]
----
include::Cargo.toml[lines=2..5]
----
.Description
----
include::src/lib.rs[tag=description]
----
+190
View File
@@ -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]
----
+206
View File
@@ -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);
}
}
+34
View File
@@ -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" }
+13
View File
@@ -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)
}
}
+221
View File
@@ -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(_));
}
+75
View File
@@ -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))
}
+299
View File
@@ -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));
}
}
+29
View File
@@ -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" }
+13
View File
@@ -0,0 +1,13 @@
= Network
.Summary
[source, toml]
----
include::Cargo.toml[lines=2..5]
----
.Description
----
include::src/lib.rs[tag=description]
----
+282
View File
@@ -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));
}
}
+105
View File
@@ -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)
}
}
+32
View File
@@ -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)));
}
}
+35
View File
@@ -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 {
}
}
+593
View File
@@ -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);
}
}
+72
View File
@@ -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)
}
}
+69
View File
@@ -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};
+375
View File
@@ -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>>,
}
}
+736
View File
@@ -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();
}
}
+679
View File
@@ -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)
}
+340
View File
@@ -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) { }
}
+428
View File
@@ -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))
}
+330
View File
@@ -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())
}
}
+121
View File
@@ -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);
}
+58
View File
@@ -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