mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 07:41:08 +00:00
get everything building
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
## `Polkadot` projects is a **OPENISH Open Source Project**
|
||||
## `Substrate` projects is a **OPENISH Open Source Project**
|
||||
-----------------------------------------
|
||||
|
||||
## What?
|
||||
|
||||
Generated
-303
@@ -1,27 +1,3 @@
|
||||
[[package]]
|
||||
name = "adder"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"polkadot-parachain 0.1.0",
|
||||
"substrate-codec-derive 0.1.0",
|
||||
"tiny-keccak 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adder-collator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"adder 0.1.0",
|
||||
"ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)",
|
||||
"ed25519 0.1.0",
|
||||
"exit-future 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-collator 0.1.0",
|
||||
"polkadot-parachain 0.1.0",
|
||||
"polkadot-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.4"
|
||||
@@ -184,11 +160,6 @@ dependencies = [
|
||||
"crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.9.1"
|
||||
@@ -1807,269 +1778,6 @@ dependencies = [
|
||||
"ethereum-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"ctrlc 1.1.1 (git+https://github.com/paritytech/rust-ctrlc.git)",
|
||||
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-cli 0.3.0",
|
||||
"vergen 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-executor 0.1.0",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"polkadot-runtime 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-executor 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-runtime-executive 0.1.0",
|
||||
"substrate-runtime-io 0.1.0",
|
||||
"substrate-runtime-primitives 0.1.0",
|
||||
"substrate-state-machine 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-availability-store"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"kvdb 0.1.0 (git+https://github.com/paritytech/parity.git)",
|
||||
"kvdb-memorydb 0.1.0 (git+https://github.com/paritytech/parity.git)",
|
||||
"kvdb-rocksdb 0.1.0 (git+https://github.com/paritytech/parity.git)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-cli"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"exit-future 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"polkadot-runtime 0.1.0",
|
||||
"polkadot-service 0.3.0",
|
||||
"polkadot-transaction-pool 0.1.0",
|
||||
"slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-cli 0.3.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-extrinsic-pool 0.1.0",
|
||||
"substrate-network 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-rpc 0.1.0",
|
||||
"substrate-rpc-servers 0.1.0",
|
||||
"substrate-runtime-primitives 0.1.0",
|
||||
"substrate-service 0.3.0",
|
||||
"substrate-state-machine 0.1.0",
|
||||
"substrate-telemetry 0.3.0",
|
||||
"tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-collator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ed25519 0.1.0",
|
||||
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-api 0.1.0",
|
||||
"polkadot-cli 0.3.0",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"polkadot-runtime 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-consensus"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ed25519 0.1.0",
|
||||
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"exit-future 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-api 0.1.0",
|
||||
"polkadot-availability-store 0.1.0",
|
||||
"polkadot-parachain 0.1.0",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"polkadot-runtime 0.1.0",
|
||||
"polkadot-statement-table 0.1.0",
|
||||
"polkadot-transaction-pool 0.1.0",
|
||||
"rhododendron 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-bft 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-runtime-primitives 0.1.0",
|
||||
"substrate-runtime-support 0.1.0",
|
||||
"tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-executor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"polkadot-runtime 0.1.0",
|
||||
"substrate-executor 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-network"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ed25519 0.1.0",
|
||||
"futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-api 0.1.0",
|
||||
"polkadot-availability-store 0.1.0",
|
||||
"polkadot-consensus 0.1.0",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"rhododendron 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-bft 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-codec-derive 0.1.0",
|
||||
"substrate-network 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-parachain"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-codec-derive 0.1.0",
|
||||
"tiny-keccak 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasmi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-primitives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"pretty_assertions 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-codec-derive 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-runtime-primitives 0.1.0",
|
||||
"substrate-runtime-std 0.1.0",
|
||||
"substrate-serializer 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-runtime"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-runtime-consensus 0.1.0",
|
||||
"substrate-runtime-council 0.1.0",
|
||||
"substrate-runtime-democracy 0.1.0",
|
||||
"substrate-runtime-executive 0.1.0",
|
||||
"substrate-runtime-io 0.1.0",
|
||||
"substrate-runtime-primitives 0.1.0",
|
||||
"substrate-runtime-session 0.1.0",
|
||||
"substrate-runtime-staking 0.1.0",
|
||||
"substrate-runtime-std 0.1.0",
|
||||
"substrate-runtime-support 0.1.0",
|
||||
"substrate-runtime-system 0.1.0",
|
||||
"substrate-runtime-timestamp 0.1.0",
|
||||
"substrate-runtime-version 0.1.0",
|
||||
"substrate-serializer 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-service"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"ed25519 0.1.0",
|
||||
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-api 0.1.0",
|
||||
"polkadot-availability-store 0.1.0",
|
||||
"polkadot-consensus 0.1.0",
|
||||
"polkadot-executor 0.1.0",
|
||||
"polkadot-network 0.1.0",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"polkadot-runtime 0.1.0",
|
||||
"polkadot-transaction-pool 0.1.0",
|
||||
"slog 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-network 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-runtime-io 0.1.0",
|
||||
"substrate-service 0.3.0",
|
||||
"substrate-telemetry 0.3.0",
|
||||
"tokio 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-statement-table"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"polkadot-primitives 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-codec-derive 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polkadot-transaction-pool"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ed25519 0.1.0",
|
||||
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"polkadot-api 0.1.0",
|
||||
"polkadot-primitives 0.1.0",
|
||||
"polkadot-runtime 0.1.0",
|
||||
"substrate-client 0.1.0",
|
||||
"substrate-codec 0.1.0",
|
||||
"substrate-extrinsic-pool 0.1.0",
|
||||
"substrate-keyring 0.1.0",
|
||||
"substrate-primitives 0.1.0",
|
||||
"substrate-runtime-primitives 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "0.4.1"
|
||||
@@ -3812,15 +3520,6 @@ name = "vec_map"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.1.3"
|
||||
@@ -3992,7 +3691,6 @@ dependencies = [
|
||||
"checksum base64 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5032d51da2741729bfdaeb2664d9b8c6d9fd1e2b90715c660b6def36628499c2"
|
||||
"checksum base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9263aa6a38da271eec5c91a83ce1e800f093c8535788d403d626d8d5c3f8f007"
|
||||
"checksum bigint 4.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "da1dde4308822ffaa13665757273a1b787481212f3f9b1c470a864b179a01f1b"
|
||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
|
||||
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
|
||||
"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
|
||||
@@ -4275,7 +3973,6 @@ dependencies = [
|
||||
"checksum varint 0.1.0 (git+https://github.com/tomaka/libp2p-rs?branch=polkadot-2)" = "<none>"
|
||||
"checksum vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed0f6789c8a85ca41bbc1c9d175422116a9869bd1cf31bb08e1493ecce60380"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum vergen 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c3365f36c57e5df714a34be40902b27a992eeddb9996eca52d0584611cf885d"
|
||||
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum wabt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "182ae543249ccf2705f324d233891c1176fca142e137b55ba43d9dbfe93f18a2"
|
||||
|
||||
+3
-41
@@ -1,41 +1,5 @@
|
||||
[[bin]]
|
||||
name = "polkadot"
|
||||
path = "polkadot/src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "polkadot"
|
||||
version = "0.3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
error-chain = "0.12"
|
||||
polkadot-cli = { path = "polkadot/cli" }
|
||||
futures = "0.1"
|
||||
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = "0.1"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"polkadot/api",
|
||||
"polkadot/availability-store",
|
||||
"polkadot/cli",
|
||||
"polkadot/collator",
|
||||
"polkadot/consensus",
|
||||
"polkadot/executor",
|
||||
"polkadot/network",
|
||||
"polkadot/primitives",
|
||||
"polkadot/runtime",
|
||||
"polkadot/service",
|
||||
"polkadot/statement-table",
|
||||
"polkadot/transaction-pool",
|
||||
"polkadot/service",
|
||||
|
||||
"polkadot/test-parachains/adder",
|
||||
"polkadot/test-parachains/adder/collator",
|
||||
|
||||
"substrate/bft",
|
||||
"substrate/cli",
|
||||
"substrate/client",
|
||||
@@ -80,8 +44,6 @@ members = [
|
||||
"subkey",
|
||||
]
|
||||
exclude = [
|
||||
"polkadot/runtime/wasm",
|
||||
"polkadot/test-parachains/adder/wasm",
|
||||
"demo/runtime/wasm",
|
||||
"substrate/executor/wasm",
|
||||
"substrate/pwasm-alloc",
|
||||
@@ -90,10 +52,10 @@ exclude = [
|
||||
]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "paritytech/polkadot", branch = "master" }
|
||||
travis-ci = { repository = "paritytech/paritysubstrate", branch = "master" }
|
||||
maintenance = { status = "actively-developed" }
|
||||
is-it-maintained-issue-resolution = { repository = "paritytech/polkadot" }
|
||||
is-it-maintained-open-issues = { repository = "paritytech/polkadot" }
|
||||
is-it-maintained-issue-resolution = { repository = "paritytech/paritysubstrate" }
|
||||
is-it-maintained-open-issues = { repository = "paritytech/paritysubstrate" }
|
||||
|
||||
[profile.release]
|
||||
# Substrate runtime requires unwinding.
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
FROM phusion/baseimage:0.10.1 as builder
|
||||
LABEL maintainer "chevdor@gmail.com"
|
||||
LABEL description="This is the build stage for Polkadot. Here we create the binary."
|
||||
|
||||
ARG PROFILE=release
|
||||
WORKDIR /polkadot
|
||||
|
||||
COPY . /polkadot
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y && \
|
||||
apt-get install -y cmake pkg-config libssl-dev git
|
||||
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y && \
|
||||
export PATH=$PATH:$HOME/.cargo/bin && \
|
||||
cargo build --$PROFILE
|
||||
|
||||
# ===== SECOND STAGE ======
|
||||
|
||||
FROM phusion/baseimage:0.10.0
|
||||
LABEL maintainer "chevdor@gmail.com"
|
||||
LABEL description="This is the 2nd stage: a very small image where we copy the Polkadot binary."
|
||||
ARG PROFILE=release
|
||||
COPY --from=builder /polkadot/target/$PROFILE/polkadot /usr/local/bin
|
||||
|
||||
RUN mv /usr/share/ca* /tmp && \
|
||||
rm -rf /usr/share/* && \
|
||||
mv /tmp/ca-certificates /usr/share/ && \
|
||||
rm -rf /usr/lib/python* && \
|
||||
mkdir -p /root/.local/share/Polkadot && \
|
||||
ln -s /root/.local/share/Polkadot /data
|
||||
|
||||
RUN rm -rf /usr/bin /usr/sbin
|
||||
|
||||
EXPOSE 30333 9933 9944
|
||||
VOLUME ["/data"]
|
||||
|
||||
CMD ["/usr/local/bin/polkadot"]
|
||||
+3
-209
@@ -1,210 +1,4 @@
|
||||
= Polkadot
|
||||
:Author: Polkadot developers
|
||||
:Revision: 0.2.0
|
||||
:toc:
|
||||
:sectnums:
|
||||
# Substrate
|
||||
|
||||
Implementation of a https://polkadot.network node in Rust.
|
||||
|
||||
|
||||
== To play
|
||||
|
||||
If you'd like to play with Polkadot, you'll need to install a client like this
|
||||
one. First, get Rust (1.26.1 or later) and the support software if you don't already have it:
|
||||
|
||||
[source, shell]
|
||||
----
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
sudo apt install make clang pkg-config libssl-dev
|
||||
----
|
||||
|
||||
Then, install Polkadot PoC-2:
|
||||
|
||||
[source, shell]
|
||||
cargo install --git https://github.com/paritytech/polkadot.git --branch v0.2 polkadot
|
||||
|
||||
You'll now have a `polkadot` binary installed to your `PATH`. You can drop the
|
||||
`--branch v0.2` or run `cargo install --git https://github.com/paritytech/polkadot.git polkadot`
|
||||
to get the very latest version of Polkadot, but these instructions might not work in that case.
|
||||
|
||||
If you want a specific version of polkadot, say `0.2.2`, you may run `cargo install --git https://github.com/paritytech/polkadot.git --tag v0.2.2 polkadot`.
|
||||
|
||||
=== Krumme Lanke Testnet
|
||||
|
||||
You will connect to the global Krumme Lanke testnet by default. To do this, just use:
|
||||
|
||||
[source, shell]
|
||||
polkadot
|
||||
|
||||
If you want to do anything on it (not that there's much to do), then you'll need
|
||||
to get some Krumme Lanke DOTs. Ask in the Polkadot watercooler.
|
||||
|
||||
=== Development
|
||||
|
||||
You can run a simple single-node development "network" on your machine by
|
||||
running in a terminal:
|
||||
|
||||
[source, shell]
|
||||
polkadot --dev
|
||||
|
||||
You can muck around by cloning and building the http://github.com/paritytech/polka-ui and http://github.com/paritytech/polkadot-ui or just heading to https://polkadot.js.org/apps.
|
||||
|
||||
|
||||
== Local Two-node Testnet
|
||||
|
||||
If you want to see the multi-node consensus algorithm in action locally, then
|
||||
you can create a local testnet. You'll need two terminals open. In one, run:
|
||||
|
||||
[source, shell]
|
||||
polkadot --chain=local --validator --key Alice -d /tmp/alice
|
||||
|
||||
and in the other, run:
|
||||
|
||||
[source, shell]
|
||||
polkadot --chain=local --validator --key Bob -d /tmp/bob --port 30334 --bootnodes '/ip4/127.0.0.1/tcp/30333/p2p/ALICE_BOOTNODE_ID_HERE'
|
||||
|
||||
Ensure you replace `ALICE_BOOTNODE_ID_HERE` with the node ID from the output of
|
||||
the first terminal.
|
||||
|
||||
|
||||
== Hacking on Polkadot
|
||||
|
||||
If you'd actually like hack on Polkadot, you can just grab the source code and
|
||||
build it. Ensure you have Rust and the support software installed:
|
||||
|
||||
[source, shell]
|
||||
----
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
rustup update nightly
|
||||
rustup target add wasm32-unknown-unknown --toolchain nightly
|
||||
rustup update stable
|
||||
cargo install --git https://github.com/alexcrichton/wasm-gc
|
||||
sudo apt install cmake pkg-config libssl-dev git
|
||||
----
|
||||
|
||||
Then, grab the Polkadot source code:
|
||||
|
||||
[source, shell]
|
||||
----
|
||||
git clone https://github.com/paritytech/polkadot.git
|
||||
cd polkadot
|
||||
----
|
||||
|
||||
Then build the code:
|
||||
|
||||
[source, shell]
|
||||
----
|
||||
./scripts/build.sh # Builds the WebAssembly binaries
|
||||
cargo build # Builds all native code
|
||||
----
|
||||
|
||||
You can run the tests if you like:
|
||||
|
||||
[source, shell]
|
||||
cargo test --all
|
||||
|
||||
You can start a development chain with:
|
||||
|
||||
[source, shell]
|
||||
cargo run -- --dev
|
||||
|
||||
|
||||
== Using Docker
|
||||
|
||||
=== The easiest way
|
||||
|
||||
The easiest/faster option is to use the latest image.
|
||||
|
||||
|
||||
.First run
|
||||
Let´s first check the version we have. The first time you run this command, the polkadot docker image will be downloaded. This takes a bit of time and bandwidth, be patient:
|
||||
|
||||
[source, shell]
|
||||
docker run --rm -it chevdor/polkadot:latest polkadot --version
|
||||
|
||||
|
||||
.Polkadot arguments
|
||||
You can also pass any argument/flag that polkadot supports:
|
||||
|
||||
[source, shell]
|
||||
docker run --rm -it chevdor/polkadot:latest polkadot --name "PolkaDocker"
|
||||
|
||||
|
||||
.Run as deamon
|
||||
Once you are done experimenting and picking the best node name :) you can start polkadot as daemon, exposes the polkadot ports and mount a volume that will keep your blockchain data locally:
|
||||
|
||||
[source, shell]
|
||||
docker run -d -p 30333:30333 -p 9933:9933 -p 9944:9944 -v /my/local/folder:/data chevdor/polkadot:latest polkadot
|
||||
|
||||
.Docker image update
|
||||
If you have an image such as `latest` locally, docker will *not* bother downloading the very latest that may be available.
|
||||
To update:
|
||||
|
||||
- stop and delete your containers (`docker stop ...` `docker rm ...`)
|
||||
- delete your previous image (`docker rmi chevdor/polkadot:latest`)
|
||||
- run as daemon again, the very latest image will be downloaded again
|
||||
|
||||
=== Build your own image
|
||||
|
||||
To get up and running with the smallest footprint on your system, you may use the Polkadot Docker image.
|
||||
You can either build it yourself (it takes a while...):
|
||||
|
||||
[source, shell]
|
||||
----
|
||||
./docker/build.sh
|
||||
----
|
||||
|
||||
=== Reporting issues
|
||||
|
||||
If you run into issues with polkadot when using docker, please run the following command
|
||||
(replace the tag with the appropriate one if you do not use latest):
|
||||
|
||||
[source, shell]
|
||||
docker run --rm -it chevdor/polkadot:latest polkadot --version
|
||||
|
||||
This will show you the polkadot version as well as the git commit ref that was used to build your container.
|
||||
Just paste that in the issue you create.
|
||||
|
||||
|
||||
== Shell completion
|
||||
|
||||
The Polkadot 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/polkadot.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/polkadot.bash $COMPL_DIR/
|
||||
echo "source $COMPL_DIR/polkadot.bash" >> $HOME/.bash_profile
|
||||
source $HOME/.bash_profile
|
||||
----
|
||||
|
||||
=== Update
|
||||
|
||||
When you build a new version of Polkadot, 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/polkadot.bash $COMPL_DIR/
|
||||
source $HOME/.bash_profile
|
||||
----
|
||||
|
||||
include::doc/packages.adoc[]
|
||||
Framework for blockchain innovators.
|
||||
More to come here.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// 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.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-api"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
error-chain = "0.12"
|
||||
polkadot-executor = { path = "../executor" }
|
||||
polkadot-runtime = { path = "../runtime" }
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
substrate-codec = { path = "../../substrate/codec" }
|
||||
substrate-runtime-io = { path = "../../substrate/runtime-io" }
|
||||
substrate-runtime-executive = { path = "../../substrate/runtime/executive" }
|
||||
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
|
||||
substrate-client = { path = "../../substrate/client" }
|
||||
substrate-primitives = { path = "../../substrate/primitives" }
|
||||
substrate-executor = { path = "../../substrate/executor" }
|
||||
substrate-state-machine = { path = "../../substrate/state-machine" }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-keyring = { path = "../../substrate/keyring" }
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot API
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,262 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Strongly typed API for full Polkadot client.
|
||||
|
||||
use client::backend::LocalBackend;
|
||||
use client::block_builder::BlockBuilder as ClientBlockBuilder;
|
||||
use client::{Client, LocalCallExecutor};
|
||||
use polkadot_executor::Executor as LocalDispatch;
|
||||
use substrate_executor::NativeExecutor;
|
||||
use state_machine;
|
||||
|
||||
use runtime::Address;
|
||||
use runtime_primitives::traits::AuxLookup;
|
||||
use primitives::{
|
||||
AccountId, Block, Header, BlockId, Hash, Index, InherentData,
|
||||
SessionKey, Timestamp, UncheckedExtrinsic,
|
||||
};
|
||||
use primitives::parachain::{DutyRoster, Id as ParaId};
|
||||
|
||||
use {BlockBuilder, PolkadotApi, LocalPolkadotApi, ErrorKind, Error, Result};
|
||||
|
||||
// set up the necessary scaffolding to execute a set of calls to the runtime.
|
||||
// this creates a new block on top of the given ID and initialises it.
|
||||
macro_rules! with_runtime {
|
||||
($client: ident, $at: expr, $exec: expr) => {{
|
||||
let parent = $at;
|
||||
let header = Header {
|
||||
parent_hash: $client.block_hash_from_id(&parent)?
|
||||
.ok_or_else(|| ErrorKind::UnknownBlock(format!("{:?}", parent)))?,
|
||||
number: $client.block_number_from_id(&parent)?
|
||||
.ok_or_else(|| ErrorKind::UnknownBlock(format!("{:?}", parent)))? + 1,
|
||||
state_root: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
};
|
||||
|
||||
$client.state_at(&parent).map_err(Error::from).and_then(|state| {
|
||||
let mut changes = Default::default();
|
||||
let mut ext = state_machine::Ext::new(&mut changes, &state);
|
||||
|
||||
::substrate_executor::with_native_environment(&mut ext, || {
|
||||
::runtime::Executive::initialise_block(&header);
|
||||
($exec)()
|
||||
}).map_err(Into::into)
|
||||
})
|
||||
}}
|
||||
}
|
||||
|
||||
impl<B: LocalBackend<Block>> BlockBuilder for ClientBlockBuilder<B, LocalCallExecutor<B, NativeExecutor<LocalDispatch>>, Block> {
|
||||
fn push_extrinsic(&mut self, extrinsic: UncheckedExtrinsic) -> Result<()> {
|
||||
self.push(extrinsic).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Bake the block with provided extrinsics.
|
||||
fn bake(self) -> Result<Block> {
|
||||
ClientBlockBuilder::bake(self).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: LocalBackend<Block>> PolkadotApi for Client<B, LocalCallExecutor<B, NativeExecutor<LocalDispatch>>, Block> {
|
||||
type BlockBuilder = ClientBlockBuilder<B, LocalCallExecutor<B, NativeExecutor<LocalDispatch>>, Block>;
|
||||
|
||||
fn session_keys(&self, at: &BlockId) -> Result<Vec<SessionKey>> {
|
||||
with_runtime!(self, at, ::runtime::Consensus::authorities)
|
||||
}
|
||||
|
||||
fn validators(&self, at: &BlockId) -> Result<Vec<AccountId>> {
|
||||
with_runtime!(self, at, ::runtime::Session::validators)
|
||||
}
|
||||
|
||||
fn random_seed(&self, at: &BlockId) -> Result<Hash> {
|
||||
with_runtime!(self, at, ::runtime::System::random_seed)
|
||||
}
|
||||
|
||||
fn duty_roster(&self, at: &BlockId) -> Result<DutyRoster> {
|
||||
with_runtime!(self, at, ::runtime::Parachains::calculate_duty_roster)
|
||||
}
|
||||
|
||||
fn timestamp(&self, at: &BlockId) -> Result<Timestamp> {
|
||||
with_runtime!(self, at, ::runtime::Timestamp::get)
|
||||
}
|
||||
|
||||
fn evaluate_block(&self, at: &BlockId, block: Block) -> Result<bool> {
|
||||
use substrate_executor::error::ErrorKind as ExecErrorKind;
|
||||
use codec::{Decode, Encode};
|
||||
use runtime::Block as RuntimeBlock;
|
||||
|
||||
let encoded = block.encode();
|
||||
let runtime_block = match RuntimeBlock::decode(&mut &encoded[..]) {
|
||||
Some(x) => x,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
let res = with_runtime!(self, at, || ::runtime::Executive::execute_block(runtime_block));
|
||||
match res {
|
||||
Ok(()) => Ok(true),
|
||||
Err(err) => match err.kind() {
|
||||
&ErrorKind::Executor(ExecErrorKind::Runtime) => Ok(false),
|
||||
_ => Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn index(&self, at: &BlockId, account: AccountId) -> Result<Index> {
|
||||
with_runtime!(self, at, || ::runtime::System::account_nonce(account))
|
||||
}
|
||||
|
||||
fn lookup(&self, at: &BlockId, address: Address) -> Result<Option<AccountId>> {
|
||||
with_runtime!(self, at, || <::runtime::Staking as AuxLookup>::lookup(address).ok())
|
||||
}
|
||||
|
||||
fn active_parachains(&self, at: &BlockId) -> Result<Vec<ParaId>> {
|
||||
with_runtime!(self, at, ::runtime::Parachains::active_parachains)
|
||||
}
|
||||
|
||||
fn parachain_code(&self, at: &BlockId, parachain: ParaId) -> Result<Option<Vec<u8>>> {
|
||||
with_runtime!(self, at, || ::runtime::Parachains::parachain_code(parachain))
|
||||
}
|
||||
|
||||
fn parachain_head(&self, at: &BlockId, parachain: ParaId) -> Result<Option<Vec<u8>>> {
|
||||
with_runtime!(self, at, || ::runtime::Parachains::parachain_head(parachain))
|
||||
}
|
||||
|
||||
fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result<Self::BlockBuilder> {
|
||||
let mut block_builder = self.new_block_at(at)?;
|
||||
for inherent in self.inherent_extrinsics(at, inherent_data)? {
|
||||
block_builder.push(inherent)?;
|
||||
}
|
||||
|
||||
Ok(block_builder)
|
||||
}
|
||||
|
||||
fn inherent_extrinsics(&self, at: &BlockId, inherent_data: InherentData) -> Result<Vec<UncheckedExtrinsic>> {
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
with_runtime!(self, at, || {
|
||||
let extrinsics = ::runtime::inherent_extrinsics(inherent_data);
|
||||
extrinsics.into_iter()
|
||||
.map(|x| x.encode()) // get encoded representation
|
||||
.map(|x| Decode::decode(&mut &x[..])) // get byte-vec equivalent to extrinsic
|
||||
.map(|x| x.expect("UncheckedExtrinsic has encoded representation equivalent to Vec<u8>; qed"))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: LocalBackend<Block>> LocalPolkadotApi for Client<B, LocalCallExecutor<B, NativeExecutor<LocalDispatch>>, Block>
|
||||
{}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use keyring::Keyring;
|
||||
use client::LocalCallExecutor;
|
||||
use client::in_mem::Backend as InMemory;
|
||||
use substrate_executor::NativeExecutionDispatch;
|
||||
use runtime::{GenesisConfig, ConsensusConfig, SessionConfig};
|
||||
|
||||
fn validators() -> Vec<AccountId> {
|
||||
vec![
|
||||
Keyring::One.to_raw_public().into(),
|
||||
Keyring::Two.to_raw_public().into(),
|
||||
]
|
||||
}
|
||||
|
||||
fn session_keys() -> Vec<SessionKey> {
|
||||
vec![
|
||||
Keyring::One.to_raw_public().into(),
|
||||
Keyring::Two.to_raw_public().into(),
|
||||
]
|
||||
}
|
||||
|
||||
fn client() -> Client<InMemory<Block>, LocalCallExecutor<InMemory<Block>, NativeExecutor<LocalDispatch>>, Block> {
|
||||
let genesis_config = GenesisConfig {
|
||||
consensus: Some(ConsensusConfig {
|
||||
code: LocalDispatch::native_equivalent().to_vec(),
|
||||
authorities: session_keys(),
|
||||
}),
|
||||
system: None,
|
||||
session: Some(SessionConfig {
|
||||
validators: validators(),
|
||||
session_length: 100,
|
||||
broken_percent_late: 100,
|
||||
}),
|
||||
council: Some(Default::default()),
|
||||
democracy: Some(Default::default()),
|
||||
parachains: Some(Default::default()),
|
||||
staking: Some(Default::default()),
|
||||
timestamp: Some(Default::default()),
|
||||
};
|
||||
|
||||
::client::new_in_mem(LocalDispatch::with_heap_pages(8), genesis_config).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gets_session_and_validator_keys() {
|
||||
let client = client();
|
||||
let id = BlockId::number(0);
|
||||
assert_eq!(client.session_keys(&id).unwrap(), session_keys());
|
||||
assert_eq!(client.validators(&id).unwrap(), validators());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_block_implicit_succeeds() {
|
||||
let client = client();
|
||||
|
||||
let id = BlockId::number(0);
|
||||
let block_builder = client.build_block(&id, InherentData {
|
||||
timestamp: 1_000_000,
|
||||
parachain_heads: Vec::new(),
|
||||
offline_indices: Vec::new(),
|
||||
}).unwrap();
|
||||
let block = block_builder.bake().unwrap();
|
||||
|
||||
assert_eq!(block.header.number, 1);
|
||||
assert!(block.header.extrinsics_root != Default::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_block_with_inherent_succeeds() {
|
||||
let client = client();
|
||||
|
||||
let id = BlockId::number(0);
|
||||
let inherent = client.inherent_extrinsics(&id, InherentData {
|
||||
timestamp: 1_000_000,
|
||||
parachain_heads: Vec::new(),
|
||||
offline_indices: Vec::new(),
|
||||
}).unwrap();
|
||||
|
||||
let mut block_builder = client.new_block_at(&id).unwrap();
|
||||
for extrinsic in inherent {
|
||||
block_builder.push(extrinsic).unwrap();
|
||||
}
|
||||
|
||||
let block = block_builder.bake().unwrap();
|
||||
|
||||
assert_eq!(block.header.number, 1);
|
||||
assert!(block.header.extrinsics_root != Default::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gets_random_seed_with_genesis() {
|
||||
let client = client();
|
||||
|
||||
let id = BlockId::number(0);
|
||||
assert!(client.random_seed(&id).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Strongly typed API for Polkadot based around the locally-compiled native
|
||||
//! runtime.
|
||||
|
||||
extern crate polkadot_executor;
|
||||
extern crate polkadot_primitives as primitives;
|
||||
extern crate polkadot_runtime as runtime;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_runtime_io as runtime_io;
|
||||
extern crate substrate_client as client;
|
||||
extern crate substrate_executor as substrate_executor;
|
||||
extern crate substrate_runtime_executive;
|
||||
extern crate substrate_primitives;
|
||||
extern crate substrate_runtime_primitives as runtime_primitives;
|
||||
extern crate substrate_state_machine as state_machine;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate substrate_keyring as keyring;
|
||||
|
||||
pub mod full;
|
||||
pub mod light;
|
||||
|
||||
use primitives::{
|
||||
AccountId, Block, BlockId, Hash, Index, SessionKey, Timestamp,
|
||||
UncheckedExtrinsic, InherentData,
|
||||
};
|
||||
use runtime::Address;
|
||||
use primitives::parachain::{DutyRoster, Id as ParaId};
|
||||
|
||||
error_chain! {
|
||||
errors {
|
||||
/// Unknown runtime code.
|
||||
UnknownRuntime {
|
||||
description("Unknown runtime code")
|
||||
display("Unknown runtime code")
|
||||
}
|
||||
/// Unknown block ID.
|
||||
UnknownBlock(b: String) {
|
||||
description("Unknown block")
|
||||
display("Unknown block {}", b)
|
||||
}
|
||||
/// Some other error.
|
||||
// TODO: allow to be specified as associated type of PolkadotApi
|
||||
Other(e: Box<::std::error::Error + Send>) {
|
||||
description("Other error")
|
||||
display("Other error: {}", e.description())
|
||||
}
|
||||
}
|
||||
|
||||
links {
|
||||
Executor(substrate_executor::error::Error, substrate_executor::error::ErrorKind);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<client::error::Error> for Error {
|
||||
fn from(e: client::error::Error) -> Error {
|
||||
match e {
|
||||
client::error::Error(client::error::ErrorKind::UnknownBlock(b), _) => Error::from_kind(ErrorKind::UnknownBlock(b)),
|
||||
other => Error::from_kind(ErrorKind::Other(Box::new(other) as Box<_>)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build new blocks.
|
||||
pub trait BlockBuilder {
|
||||
/// Push an extrinsic onto the block. Fails if the extrinsic is invalid.
|
||||
fn push_extrinsic(&mut self, extrinsic: UncheckedExtrinsic) -> Result<()>;
|
||||
|
||||
/// Bake the block with provided extrinsics.
|
||||
fn bake(self) -> Result<Block>;
|
||||
}
|
||||
|
||||
/// Trait encapsulating the Polkadot API.
|
||||
///
|
||||
/// All calls should fail when the exact runtime is unknown.
|
||||
pub trait PolkadotApi {
|
||||
/// The block builder for this API type.
|
||||
type BlockBuilder: BlockBuilder;
|
||||
|
||||
/// Get session keys at a given block.
|
||||
fn session_keys(&self, at: &BlockId) -> Result<Vec<SessionKey>>;
|
||||
|
||||
/// Get validators at a given block.
|
||||
fn validators(&self, at: &BlockId) -> Result<Vec<AccountId>>;
|
||||
|
||||
/// Get the value of the randomness beacon at a given block.
|
||||
fn random_seed(&self, at: &BlockId) -> Result<Hash>;
|
||||
|
||||
/// Get the authority duty roster at a block.
|
||||
fn duty_roster(&self, at: &BlockId) -> Result<DutyRoster>;
|
||||
|
||||
/// Get the timestamp registered at a block.
|
||||
fn timestamp(&self, at: &BlockId) -> Result<Timestamp>;
|
||||
|
||||
/// Get the nonce (né index) of an account at a block.
|
||||
fn index(&self, at: &BlockId, account: AccountId) -> Result<Index>;
|
||||
|
||||
/// Get the account id of an address at a block.
|
||||
fn lookup(&self, at: &BlockId, address: Address) -> Result<Option<AccountId>>;
|
||||
|
||||
/// Get the active parachains at a block.
|
||||
fn active_parachains(&self, at: &BlockId) -> Result<Vec<ParaId>>;
|
||||
|
||||
/// Get the validation code of a parachain at a block. If the parachain is active, this will always return `Some`.
|
||||
fn parachain_code(&self, at: &BlockId, parachain: ParaId) -> Result<Option<Vec<u8>>>;
|
||||
|
||||
/// Get the chain head of a parachain. If the parachain is active, this will always return `Some`.
|
||||
fn parachain_head(&self, at: &BlockId, parachain: ParaId) -> Result<Option<Vec<u8>>>;
|
||||
|
||||
/// Evaluate a block. Returns true if the block is good, false if it is known to be bad,
|
||||
/// and an error if we can't evaluate for some reason.
|
||||
fn evaluate_block(&self, at: &BlockId, block: Block) -> Result<bool>;
|
||||
|
||||
/// Build a block on top of the given, with inherent extrinsics pre-pushed.
|
||||
fn build_block(&self, at: &BlockId, inherent_data: InherentData) -> Result<Self::BlockBuilder>;
|
||||
|
||||
/// Attempt to produce the (encoded) inherent extrinsics for a block being built upon the given.
|
||||
/// This may vary by runtime and will fail if a runtime doesn't follow the same API.
|
||||
fn inherent_extrinsics(&self, at: &BlockId, inherent_data: InherentData) -> Result<Vec<UncheckedExtrinsic>>;
|
||||
}
|
||||
|
||||
/// Mark for all Polkadot API implementations, that are making use of state data, stored locally.
|
||||
pub trait LocalPolkadotApi: PolkadotApi {}
|
||||
|
||||
/// Mark for all Polkadot API implementations, that are fetching required state data from remote nodes.
|
||||
pub trait RemotePolkadotApi: PolkadotApi {}
|
||||
@@ -1,107 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Strongly typed API for light Polkadot client.
|
||||
|
||||
use std::sync::Arc;
|
||||
use client::backend::{Backend, RemoteBackend};
|
||||
use client::{Client, CallExecutor};
|
||||
use codec::Decode;
|
||||
use primitives::{
|
||||
AccountId, Block, BlockId, Hash, Index, InherentData,
|
||||
SessionKey, Timestamp, UncheckedExtrinsic,
|
||||
};
|
||||
use runtime::Address;
|
||||
use primitives::parachain::{DutyRoster, Id as ParaId};
|
||||
use {PolkadotApi, BlockBuilder, RemotePolkadotApi, Result, ErrorKind};
|
||||
|
||||
/// Light block builder. TODO: make this work (efficiently)
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct LightBlockBuilder;
|
||||
|
||||
impl BlockBuilder for LightBlockBuilder {
|
||||
fn push_extrinsic(&mut self, _xt: UncheckedExtrinsic) -> Result<()> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn bake(self) -> Result<Block> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Remote polkadot API implementation.
|
||||
pub struct RemotePolkadotApiWrapper<B: Backend<Block>, E: CallExecutor<Block>>(pub Arc<Client<B, E, Block>>);
|
||||
|
||||
impl<B: Backend<Block>, E: CallExecutor<Block>> PolkadotApi for RemotePolkadotApiWrapper<B, E> {
|
||||
type BlockBuilder = LightBlockBuilder;
|
||||
|
||||
fn session_keys(&self, at: &BlockId) -> Result<Vec<SessionKey>> {
|
||||
self.0.executor().call(at, "authorities", &[])
|
||||
.and_then(|r| Vec::<SessionKey>::decode(&mut &r.return_data[..])
|
||||
.ok_or("error decoding session keys".into()))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn validators(&self, _at: &BlockId) -> Result<Vec<AccountId>> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn random_seed(&self, _at: &BlockId) -> Result<Hash> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn duty_roster(&self, _at: &BlockId) -> Result<DutyRoster> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn timestamp(&self, _at: &BlockId) -> Result<Timestamp> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn evaluate_block(&self, _at: &BlockId, _block: Block) -> Result<bool> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn index(&self, _at: &BlockId, _account: AccountId) -> Result<Index> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn lookup(&self, _at: &BlockId, _address: Address) -> Result<Option<AccountId>> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn active_parachains(&self, _at: &BlockId) -> Result<Vec<ParaId>> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn parachain_code(&self, _at: &BlockId, _parachain: ParaId) -> Result<Option<Vec<u8>>> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn parachain_head(&self, _at: &BlockId, _parachain: ParaId) -> Result<Option<Vec<u8>>> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn build_block(&self, _at: &BlockId, _inherent: InherentData) -> Result<Self::BlockBuilder> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
|
||||
fn inherent_extrinsics(&self, _at: &BlockId, _inherent: InherentData) -> Result<Vec<Vec<u8>>> {
|
||||
Err(ErrorKind::UnknownRuntime.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: RemoteBackend<Block>, E: CallExecutor<Block>> RemotePolkadotApi for RemotePolkadotApiWrapper<B, E> {}
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-availability-store"
|
||||
description = "Persistent database for parachain data"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
parking_lot = "0.4"
|
||||
log = "0.3"
|
||||
substrate-codec = { path = "../../substrate/codec" }
|
||||
substrate-primitives = { path = "../../substrate/primitives" }
|
||||
kvdb = { git = "https://github.com/paritytech/parity.git" }
|
||||
kvdb-rocksdb = { git = "https://github.com/paritytech/parity.git" }
|
||||
kvdb-memorydb = { git = "https://github.com/paritytech/parity.git" }
|
||||
@@ -1,258 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Persistent database for parachain data.
|
||||
|
||||
extern crate polkadot_primitives;
|
||||
extern crate parking_lot;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_primitives;
|
||||
extern crate kvdb;
|
||||
extern crate kvdb_rocksdb;
|
||||
extern crate kvdb_memorydb;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use kvdb::{KeyValueDB, DBTransaction};
|
||||
use kvdb_rocksdb::{Database, DatabaseConfig};
|
||||
use polkadot_primitives::Hash;
|
||||
use polkadot_primitives::parachain::{Id as ParaId, BlockData, Extrinsic};
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::io;
|
||||
|
||||
mod columns {
|
||||
pub const DATA: Option<u32> = Some(0);
|
||||
pub const META: Option<u32> = Some(1);
|
||||
pub const NUM_COLUMNS: u32 = 2;
|
||||
}
|
||||
|
||||
/// Configuration for the availability store.
|
||||
pub struct Config {
|
||||
/// Cache size in bytes. If `None` default is used.
|
||||
pub cache_size: Option<usize>,
|
||||
/// Path to the database.
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
/// Some data to keep available.
|
||||
pub struct Data {
|
||||
/// The relay chain parent hash this should be localized to.
|
||||
pub relay_parent: Hash,
|
||||
/// The parachain index for this candidate.
|
||||
pub parachain_id: ParaId,
|
||||
/// Unique candidate receipt hash.
|
||||
pub candidate_hash: Hash,
|
||||
/// Block data.
|
||||
pub block_data: BlockData,
|
||||
/// Extrinsic data.
|
||||
pub extrinsic: Option<Extrinsic>,
|
||||
}
|
||||
|
||||
fn extract_io_err(err: ::kvdb::Error) -> io::Error {
|
||||
match err {
|
||||
::kvdb::Error(::kvdb::ErrorKind::Io(io_err), _) => io_err,
|
||||
::kvdb::Error(::kvdb::ErrorKind::Msg(msg), _) => io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
msg,
|
||||
),
|
||||
x => io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Unexpected error variant: {:?}", x), // only necessary because of nonexaustive match.
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn block_data_key(relay_parent: &Hash, candidate_hash: &Hash) -> Vec<u8> {
|
||||
(relay_parent, candidate_hash, 0i8).encode()
|
||||
}
|
||||
|
||||
fn extrinsic_key(relay_parent: &Hash, candidate_hash: &Hash) -> Vec<u8> {
|
||||
(relay_parent, candidate_hash, 1i8).encode()
|
||||
}
|
||||
|
||||
/// Handle to the availability store.
|
||||
#[derive(Clone)]
|
||||
pub struct Store {
|
||||
inner: Arc<KeyValueDB>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
/// Create a new `Store` with given config on disk.
|
||||
pub fn new(config: Config) -> io::Result<Self> {
|
||||
let mut db_config = DatabaseConfig::with_columns(Some(columns::NUM_COLUMNS));
|
||||
db_config.memory_budget = config.cache_size;
|
||||
db_config.wal = true;
|
||||
|
||||
let path = config.path.to_str().ok_or_else(|| io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Bad database path: {:?}", config.path),
|
||||
))?;
|
||||
|
||||
let db = Database::open(&db_config, &path).map_err(extract_io_err)?;
|
||||
|
||||
Ok(Store {
|
||||
inner: Arc::new(db),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new `Store` in-memory. Useful for tests.
|
||||
pub fn new_in_memory() -> Self {
|
||||
Store {
|
||||
inner: Arc::new(::kvdb_memorydb::create(::columns::NUM_COLUMNS)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Make some data available provisionally.
|
||||
pub fn make_available(&self, data: Data) -> io::Result<()> {
|
||||
let mut tx = DBTransaction::new();
|
||||
|
||||
// note the meta key.
|
||||
let mut v = match self.inner.get(columns::META, &*data.relay_parent) {
|
||||
Ok(Some(raw)) => Vec::decode(&mut &raw[..]).expect("all stored data serialized correctly; qed"),
|
||||
Ok(None) => Vec::new(),
|
||||
Err(e) => {
|
||||
warn!(target: "availability", "Error reading from availability store: {:?}", e);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
v.push(data.candidate_hash);
|
||||
tx.put_vec(columns::META, &data.relay_parent[..], v.encode());
|
||||
|
||||
tx.put_vec(
|
||||
columns::DATA,
|
||||
block_data_key(&data.relay_parent, &data.candidate_hash).as_slice(),
|
||||
data.block_data.encode()
|
||||
);
|
||||
|
||||
if let Some(_extrinsic) = data.extrinsic {
|
||||
tx.put_vec(
|
||||
columns::DATA,
|
||||
extrinsic_key(&data.relay_parent, &data.candidate_hash).as_slice(),
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
|
||||
self.inner.write(tx).map_err(extract_io_err)
|
||||
}
|
||||
|
||||
/// Note that a set of candidates have been included in a finalized block with given hash and parent hash.
|
||||
pub fn candidates_finalized(&self, parent: Hash, finalized_candidates: HashSet<Hash>) -> io::Result<()> {
|
||||
let mut tx = DBTransaction::new();
|
||||
|
||||
let v = match self.inner.get(columns::META, &parent[..]) {
|
||||
Ok(Some(raw)) => Vec::decode(&mut &raw[..]).expect("all stored data serialized correctly; qed"),
|
||||
Ok(None) => Vec::new(),
|
||||
Err(e) => {
|
||||
warn!(target: "availability", "Error reading from availability store: {:?}", e);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
tx.delete(columns::META, &parent[..]);
|
||||
|
||||
for candidate_hash in v {
|
||||
if !finalized_candidates.contains(&candidate_hash) {
|
||||
tx.delete(columns::DATA, block_data_key(&parent, &candidate_hash).as_slice());
|
||||
tx.delete(columns::DATA, extrinsic_key(&parent, &candidate_hash).as_slice());
|
||||
}
|
||||
}
|
||||
|
||||
self.inner.write(tx).map_err(extract_io_err)
|
||||
}
|
||||
|
||||
/// Query block data.
|
||||
pub fn block_data(&self, relay_parent: Hash, candidate_hash: Hash) -> Option<BlockData> {
|
||||
let encoded_key = block_data_key(&relay_parent, &candidate_hash);
|
||||
match self.inner.get(columns::DATA, &encoded_key[..]) {
|
||||
Ok(Some(raw)) => Some(
|
||||
BlockData::decode(&mut &raw[..]).expect("all stored data serialized correctly; qed")
|
||||
),
|
||||
Ok(None) => None,
|
||||
Err(e) => {
|
||||
warn!(target: "availability", "Error reading from availability store: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Query extrinsic data.
|
||||
pub fn extrinsic(&self, relay_parent: Hash, candidate_hash: Hash) -> Option<Extrinsic> {
|
||||
let encoded_key = extrinsic_key(&relay_parent, &candidate_hash);
|
||||
match self.inner.get(columns::DATA, &encoded_key[..]) {
|
||||
Ok(Some(_raw)) => Some(Extrinsic),
|
||||
Ok(None) => None,
|
||||
Err(e) => {
|
||||
warn!(target: "availability", "Error reading from availability store: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn finalization_removes_unneeded() {
|
||||
let relay_parent = [1; 32].into();
|
||||
|
||||
let para_id_1 = 5.into();
|
||||
let para_id_2 = 6.into();
|
||||
|
||||
let candidate_1 = [2; 32].into();
|
||||
let candidate_2 = [3; 32].into();
|
||||
|
||||
let block_data_1 = BlockData(vec![1, 2, 3]);
|
||||
let block_data_2 = BlockData(vec![4, 5, 6]);
|
||||
|
||||
let store = Store::new_in_memory();
|
||||
store.make_available(Data {
|
||||
relay_parent,
|
||||
parachain_id: para_id_1,
|
||||
candidate_hash: candidate_1,
|
||||
block_data: block_data_1.clone(),
|
||||
extrinsic: Some(Extrinsic),
|
||||
}).unwrap();
|
||||
|
||||
store.make_available(Data {
|
||||
relay_parent,
|
||||
parachain_id: para_id_2,
|
||||
candidate_hash: candidate_2,
|
||||
block_data: block_data_2.clone(),
|
||||
extrinsic: Some(Extrinsic),
|
||||
}).unwrap();
|
||||
|
||||
assert_eq!(store.block_data(relay_parent, candidate_1).unwrap(), block_data_1);
|
||||
assert_eq!(store.block_data(relay_parent, candidate_2).unwrap(), block_data_2);
|
||||
|
||||
assert!(store.extrinsic(relay_parent, candidate_1).is_some());
|
||||
assert!(store.extrinsic(relay_parent, candidate_2).is_some());
|
||||
|
||||
store.candidates_finalized(relay_parent, [candidate_1].iter().cloned().collect()).unwrap();
|
||||
|
||||
assert_eq!(store.block_data(relay_parent, candidate_1).unwrap(), block_data_1);
|
||||
assert!(store.block_data(relay_parent, candidate_2).is_none());
|
||||
|
||||
assert!(store.extrinsic(relay_parent, candidate_1).is_some());
|
||||
assert!(store.extrinsic(relay_parent, candidate_2).is_none());
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-cli"
|
||||
version = "0.3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Polkadot node implementation in Rust."
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "~2.32", features = ["yaml"] }
|
||||
error-chain = "0.12"
|
||||
log = "0.3"
|
||||
slog = "^2"
|
||||
lazy_static = "1.0"
|
||||
tokio = "0.1.7"
|
||||
futures = "0.1.17"
|
||||
parking_lot = "0.4"
|
||||
exit-future = "0.1"
|
||||
substrate-cli = { path = "../../substrate/cli" }
|
||||
substrate-client = { path = "../../substrate/client" }
|
||||
substrate-codec = { path = "../../substrate/codec" }
|
||||
substrate-extrinsic-pool = { path = "../../substrate/extrinsic-pool" }
|
||||
substrate-network = { path = "../../substrate/network" }
|
||||
substrate-primitives = { path = "../../substrate/primitives" }
|
||||
substrate-rpc = { path = "../../substrate/rpc" }
|
||||
substrate-rpc-servers = { path = "../../substrate/rpc-servers" }
|
||||
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
|
||||
substrate-service = { path = "../../substrate/service" }
|
||||
substrate-state-machine = { path = "../../substrate/state-machine" }
|
||||
substrate-telemetry = { path = "../../substrate/telemetry" }
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
polkadot-runtime = { path = "../runtime" }
|
||||
polkadot-service = { path = "../service" }
|
||||
polkadot-transaction-pool = { path = "../transaction-pool" }
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Predefined chains.
|
||||
|
||||
use service;
|
||||
|
||||
/// The chain specification (this should eventually be replaced by a more general JSON-based chain
|
||||
/// specification).
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ChainSpec {
|
||||
/// Whatever the current runtime is, with just Alice as an auth.
|
||||
Development,
|
||||
/// Whatever the current runtime is, with simple Alice/Bob auths.
|
||||
LocalTestnet,
|
||||
/// The PoC-1 & PoC-2 era testnet.
|
||||
KrummeLanke,
|
||||
/// Whatever the current runtime is with the "global testnet" defaults.
|
||||
StagingTestnet,
|
||||
}
|
||||
|
||||
/// Get a chain config from a spec setting.
|
||||
impl ChainSpec {
|
||||
pub(crate) fn load(self) -> Result<service::ChainSpec, String> {
|
||||
Ok(match self {
|
||||
ChainSpec::KrummeLanke => service::chain_spec::poc_1_testnet_config()?,
|
||||
ChainSpec::Development => service::chain_spec::development_config(),
|
||||
ChainSpec::LocalTestnet => service::chain_spec::local_testnet_config(),
|
||||
ChainSpec::StagingTestnet => service::chain_spec::staging_testnet_config(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"dev" => Some(ChainSpec::Development),
|
||||
"local" => Some(ChainSpec::LocalTestnet),
|
||||
"poc-1" => Some(ChainSpec::KrummeLanke),
|
||||
"" | "krummelanke" => Some(ChainSpec::KrummeLanke),
|
||||
"staging" => Some(ChainSpec::StagingTestnet),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot CLI library.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(unused_extern_crates)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate tokio;
|
||||
|
||||
extern crate substrate_cli as cli;
|
||||
extern crate polkadot_service as service;
|
||||
extern crate exit_future;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod chain_spec;
|
||||
|
||||
pub use cli::error;
|
||||
|
||||
use chain_spec::ChainSpec;
|
||||
|
||||
use futures::Future;
|
||||
use tokio::runtime::Runtime;
|
||||
pub use service::{Components as ServiceComponents, Service, CustomConfiguration};
|
||||
pub use cli::{VersionInfo, IntoExit};
|
||||
|
||||
fn load_spec(id: &str) -> Result<Option<service::ChainSpec>, String> {
|
||||
Ok(match ChainSpec::from(id) {
|
||||
Some(spec) => Some(spec.load()?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Additional worker making use of the node, to run asynchronously before shutdown.
|
||||
///
|
||||
/// This will be invoked with the service and spawn a future that resolves
|
||||
/// when complete.
|
||||
pub trait Worker: IntoExit {
|
||||
/// A future that resolves when the work is done or the node should exit.
|
||||
/// This will be run on a tokio runtime.
|
||||
type Work: Future<Item=(),Error=()> + Send + 'static;
|
||||
|
||||
/// Return configuration for the polkadot node.
|
||||
// TODO: make this the full configuration, so embedded nodes don't need
|
||||
// string CLI args
|
||||
fn configuration(&self) -> service::CustomConfiguration { Default::default() }
|
||||
|
||||
/// Do work and schedule exit.
|
||||
fn work<C: service::Components>(self, service: &service::Service<C>) -> Self::Work;
|
||||
}
|
||||
|
||||
/// Parse command line arguments into 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 run<I, T, W>(args: I, worker: W, version: cli::VersionInfo) -> error::Result<()> where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<std::ffi::OsString> + Clone,
|
||||
W: Worker,
|
||||
{
|
||||
|
||||
match cli::prepare_execution::<service::Factory, _, _, _, _>(args, worker, version, load_spec, "parity-polkadot")? {
|
||||
cli::Action::ExecutedInternally => (),
|
||||
cli::Action::RunService((mut config, worker)) => {
|
||||
info!("Parity ·:· Polkadot");
|
||||
info!(" version {}", config.full_version());
|
||||
info!(" by Parity Technologies, 2017, 2018");
|
||||
info!("Chain specification: {}", config.chain_spec.name());
|
||||
info!("Node name: {}", config.name);
|
||||
info!("Roles: {:?}", config.roles);
|
||||
config.custom = worker.configuration();
|
||||
let mut runtime = Runtime::new()?;
|
||||
let executor = runtime.executor();
|
||||
match config.roles == service::Roles::LIGHT {
|
||||
true => run_until_exit(&mut runtime, service::new_light(config, executor)?, worker)?,
|
||||
false => run_until_exit(&mut runtime, service::new_full(config, executor)?, worker)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn run_until_exit<C, W>(
|
||||
runtime: &mut Runtime,
|
||||
service: service::Service<C>,
|
||||
worker: W,
|
||||
) -> error::Result<()>
|
||||
where
|
||||
C: service::Components,
|
||||
W: Worker,
|
||||
{
|
||||
let (exit_send, exit) = exit_future::signal();
|
||||
|
||||
let executor = runtime.executor();
|
||||
cli::informant::start(&service, exit.clone(), executor.clone());
|
||||
|
||||
let _ = runtime.block_on(worker.work(&service));
|
||||
exit_send.fire();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-collator"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Collator node implementation"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.17"
|
||||
substrate-client = { path = "../../substrate/client" }
|
||||
substrate-codec = { path = "../../substrate/codec", version = "0.1" }
|
||||
substrate-primitives = { path = "../../substrate/primitives", version = "0.1" }
|
||||
polkadot-api = { path = "../api" }
|
||||
polkadot-runtime = { path = "../runtime", version = "0.1" }
|
||||
polkadot-primitives = { path = "../primitives", version = "0.1" }
|
||||
polkadot-cli = { path = "../cli" }
|
||||
log = "0.4"
|
||||
ed25519 = { path = "../../substrate/ed25519" }
|
||||
tokio = "0.1.7"
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Collator
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,441 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Collation node logic.
|
||||
//!
|
||||
//! A collator node lives on a distinct parachain and submits a proposal for
|
||||
//! a state transition, along with a proof for its validity
|
||||
//! (what we might call a witness or block data).
|
||||
//!
|
||||
//! One of collators' other roles is to route messages between chains.
|
||||
//! Each parachain produces a list of "egress" posts of messages for each other
|
||||
//! parachain on each block, for a total of N^2 lists all together.
|
||||
//!
|
||||
//! We will refer to the egress list at relay chain block X of parachain A with
|
||||
//! destination B as egress(X)[A -> B]
|
||||
//!
|
||||
//! On every block, each parachain will be intended to route messages from some
|
||||
//! subset of all the other parachains. (NOTE: in practice this is not done until PoC-3)
|
||||
//!
|
||||
//! Since the egress information is unique to every block, when routing from a
|
||||
//! parachain a collator must gather all egress posts from that parachain
|
||||
//! up to the last point in history that messages were successfully routed
|
||||
//! from that parachain, accounting for relay chain blocks where no candidate
|
||||
//! from the collator's parachain was produced.
|
||||
//!
|
||||
//! In the case that all parachains route to each other and a candidate for the
|
||||
//! collator's parachain was included in the last relay chain block, the collator
|
||||
//! only has to gather egress posts from other parachains one block back in relay
|
||||
//! chain history.
|
||||
//!
|
||||
//! This crate defines traits which provide context necessary for collation logic
|
||||
//! to be performed, as the collation logic itself.
|
||||
|
||||
extern crate futures;
|
||||
extern crate substrate_client as client;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate ed25519;
|
||||
extern crate tokio;
|
||||
|
||||
extern crate polkadot_api;
|
||||
extern crate polkadot_cli;
|
||||
extern crate polkadot_runtime;
|
||||
extern crate polkadot_primitives;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use futures::{future, stream, Stream, Future, IntoFuture};
|
||||
use client::BlockchainEvents;
|
||||
use polkadot_api::PolkadotApi;
|
||||
use polkadot_primitives::{AccountId, BlockId, SessionKey};
|
||||
use polkadot_primitives::parachain::{self, BlockData, DutyRoster, HeadData, ConsolidatedIngress, Message, Id as ParaId};
|
||||
use polkadot_cli::{ServiceComponents, Service, CustomConfiguration};
|
||||
use polkadot_cli::{Worker, IntoExit};
|
||||
use tokio::timer::Deadline;
|
||||
|
||||
pub use polkadot_cli::VersionInfo;
|
||||
|
||||
const COLLATION_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
/// Error to return when the head data was invalid.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct InvalidHead;
|
||||
|
||||
/// Collation errors.
|
||||
#[derive(Debug)]
|
||||
pub enum Error<R> {
|
||||
/// Error on the relay-chain side of things.
|
||||
Polkadot(R),
|
||||
/// Error on the collator side of things.
|
||||
Collator(InvalidHead),
|
||||
}
|
||||
|
||||
impl<R: fmt::Display> fmt::Display for Error<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Polkadot(ref err) => write!(f, "Polkadot node error: {}", err),
|
||||
Error::Collator(_) => write!(f, "Collator node error: Invalid head data"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parachain context needed for collation.
|
||||
///
|
||||
/// This can be implemented through an externally attached service or a stub.
|
||||
/// This is expected to be a lightweight, shared type like an Arc.
|
||||
pub trait ParachainContext: Clone {
|
||||
/// Produce a candidate, given the latest ingress queue information and the last parachain head.
|
||||
fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>(
|
||||
&self,
|
||||
last_head: HeadData,
|
||||
ingress: I,
|
||||
) -> Result<(BlockData, HeadData), InvalidHead>;
|
||||
}
|
||||
|
||||
/// Relay chain context needed to collate.
|
||||
/// This encapsulates a network and local database which may store
|
||||
/// some of the input.
|
||||
pub trait RelayChainContext {
|
||||
type Error;
|
||||
|
||||
/// Future that resolves to the un-routed egress queues of a parachain.
|
||||
/// The first item is the oldest.
|
||||
type FutureEgress: IntoFuture<Item=Vec<Vec<Message>>, Error=Self::Error>;
|
||||
|
||||
/// Provide a set of all parachains meant to be routed to at a block.
|
||||
fn routing_parachains(&self) -> BTreeSet<ParaId>;
|
||||
|
||||
/// Get un-routed egress queues from a parachain to the local parachain.
|
||||
fn unrouted_egress(&self, id: ParaId) -> Self::FutureEgress;
|
||||
}
|
||||
|
||||
fn key_to_account_id(key: &ed25519::Pair) -> AccountId {
|
||||
let pubkey_bytes: [u8; 32] = key.public().into();
|
||||
pubkey_bytes.into()
|
||||
}
|
||||
|
||||
/// Collate the necessary ingress queue using the given context.
|
||||
pub fn collate_ingress<'a, R>(relay_context: R)
|
||||
-> impl Future<Item=ConsolidatedIngress, Error=R::Error> + 'a
|
||||
where
|
||||
R: RelayChainContext,
|
||||
R::Error: 'a,
|
||||
R::FutureEgress: 'a,
|
||||
{
|
||||
let mut egress_fetch = Vec::new();
|
||||
|
||||
for routing_parachain in relay_context.routing_parachains() {
|
||||
let fetch = relay_context
|
||||
.unrouted_egress(routing_parachain)
|
||||
.into_future()
|
||||
.map(move |egresses| (routing_parachain, egresses));
|
||||
|
||||
egress_fetch.push(fetch);
|
||||
}
|
||||
|
||||
// create a map ordered first by the depth of the egress queue
|
||||
// and then by the parachain ID.
|
||||
//
|
||||
// then transform that into the consolidated egress queue.
|
||||
stream::futures_unordered(egress_fetch)
|
||||
.fold(BTreeMap::new(), |mut map, (routing_id, egresses)| {
|
||||
for (depth, egress) in egresses.into_iter().rev().enumerate() {
|
||||
let depth = -(depth as i64);
|
||||
map.insert((depth, routing_id), egress);
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
})
|
||||
.map(|ordered| ordered.into_iter().map(|((_, id), egress)| (id, egress)))
|
||||
.map(|i| i.collect::<Vec<_>>())
|
||||
.map(ConsolidatedIngress)
|
||||
}
|
||||
|
||||
/// Produce a candidate for the parachain, with given contexts, parent head, and signing key.
|
||||
pub fn collate<'a, R, P>(
|
||||
local_id: ParaId,
|
||||
last_head: HeadData,
|
||||
relay_context: R,
|
||||
para_context: P,
|
||||
key: Arc<ed25519::Pair>,
|
||||
)
|
||||
-> impl Future<Item=parachain::Collation, Error=Error<R::Error>> + 'a
|
||||
where
|
||||
R: RelayChainContext + 'a,
|
||||
R::Error: 'a,
|
||||
R::FutureEgress: 'a,
|
||||
P: ParachainContext + 'a,
|
||||
{
|
||||
collate_ingress(relay_context).map_err(Error::Polkadot).and_then(move |ingress| {
|
||||
let (block_data, head_data) = para_context.produce_candidate(
|
||||
last_head,
|
||||
ingress.0.iter().flat_map(|&(id, ref msgs)| msgs.iter().cloned().map(move |msg| (id, msg)))
|
||||
).map_err(Error::Collator)?;
|
||||
|
||||
let block_data_hash = block_data.hash();
|
||||
let signature = key.sign(&block_data_hash.0[..]).into();
|
||||
|
||||
let receipt = parachain::CandidateReceipt {
|
||||
parachain_index: local_id,
|
||||
collator: key_to_account_id(&*key),
|
||||
signature,
|
||||
head_data,
|
||||
balance_uploads: Vec::new(),
|
||||
egress_queue_roots: Vec::new(),
|
||||
fees: 0,
|
||||
block_data_hash,
|
||||
};
|
||||
|
||||
Ok(parachain::Collation {
|
||||
receipt,
|
||||
block_data,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Polkadot-api context.
|
||||
struct ApiContext;
|
||||
|
||||
impl RelayChainContext for ApiContext {
|
||||
type Error = ::polkadot_api::Error;
|
||||
type FutureEgress = Result<Vec<Vec<Message>>, Self::Error>;
|
||||
|
||||
fn routing_parachains(&self) -> BTreeSet<ParaId> {
|
||||
BTreeSet::new()
|
||||
}
|
||||
|
||||
fn unrouted_egress(&self, _id: ParaId) -> Self::FutureEgress {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
struct CollationNode<P, E> {
|
||||
parachain_context: P,
|
||||
exit: E,
|
||||
para_id: ParaId,
|
||||
key: Arc<ed25519::Pair>,
|
||||
}
|
||||
|
||||
impl<P, E> IntoExit for CollationNode<P, E> where
|
||||
P: ParachainContext + Send + 'static,
|
||||
E: Future<Item=(),Error=()> + Send + 'static
|
||||
{
|
||||
type Exit = E;
|
||||
fn into_exit(self) -> Self::Exit {
|
||||
self.exit
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, E> Worker for CollationNode<P, E> where
|
||||
P: ParachainContext + Send + 'static,
|
||||
E: Future<Item=(),Error=()> + Send + 'static
|
||||
{
|
||||
type Work = Box<Future<Item=(),Error=()> + Send>;
|
||||
|
||||
fn configuration(&self) -> CustomConfiguration {
|
||||
let mut config = CustomConfiguration::default();
|
||||
config.collating_for = Some((
|
||||
key_to_account_id(&*self.key),
|
||||
self.para_id.clone(),
|
||||
));
|
||||
config
|
||||
}
|
||||
|
||||
fn work<C: ServiceComponents>(self, service: &Service<C>) -> Self::Work {
|
||||
let CollationNode { parachain_context, exit, para_id, key } = self;
|
||||
let client = service.client();
|
||||
let api = service.api();
|
||||
let network = service.network();
|
||||
|
||||
let work = client.import_notification_stream()
|
||||
.for_each(move |notification| {
|
||||
macro_rules! try_fr {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Ok(x) => x,
|
||||
Err(e) => return future::Either::A(future::err(Error::Polkadot(e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let relay_parent = notification.hash;
|
||||
let id = BlockId::hash(relay_parent);
|
||||
|
||||
let network = network.clone();
|
||||
let api = api.clone();
|
||||
let key = key.clone();
|
||||
let parachain_context = parachain_context.clone();
|
||||
|
||||
let work = future::lazy(move || {
|
||||
let last_head = match try_fr!(api.parachain_head(&id, para_id)) {
|
||||
Some(last_head) => last_head,
|
||||
None => return future::Either::A(future::ok(())),
|
||||
};
|
||||
|
||||
let targets = compute_targets(
|
||||
para_id,
|
||||
try_fr!(api.session_keys(&id)).as_slice(),
|
||||
try_fr!(api.duty_roster(&id)),
|
||||
);
|
||||
|
||||
let collation_work = collate(
|
||||
para_id,
|
||||
HeadData(last_head),
|
||||
ApiContext,
|
||||
parachain_context,
|
||||
key,
|
||||
).map(move |collation| {
|
||||
network.with_spec(|spec, ctx| spec.add_local_collation(
|
||||
ctx,
|
||||
relay_parent,
|
||||
targets,
|
||||
collation,
|
||||
));
|
||||
});
|
||||
|
||||
future::Either::B(collation_work)
|
||||
});
|
||||
let deadlined = Deadline::new(work, Instant::now() + COLLATION_TIMEOUT);
|
||||
let silenced = deadlined.then(|res| match res {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
warn!("Collation failure: {}", e);
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(silenced);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let work_and_exit = work.select(exit).then(|_| Ok(()));
|
||||
Box::new(work_and_exit) as Box<_>
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_targets(para_id: ParaId, session_keys: &[SessionKey], roster: DutyRoster) -> HashSet<SessionKey> {
|
||||
use polkadot_primitives::parachain::Chain;
|
||||
|
||||
roster.validator_duty.iter().enumerate()
|
||||
.filter(|&(_, c)| c == &Chain::Parachain(para_id))
|
||||
.filter_map(|(i, _)| session_keys.get(i))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Run a collator node with the given `RelayChainContext` and `ParachainContext` and
|
||||
/// arguments to the underlying polkadot node.
|
||||
///
|
||||
/// Provide a future which resolves when the node should exit.
|
||||
/// This function blocks until done.
|
||||
pub fn run_collator<P, E, I, ArgT>(
|
||||
parachain_context: P,
|
||||
para_id: ParaId,
|
||||
exit: E,
|
||||
key: Arc<ed25519::Pair>,
|
||||
args: I,
|
||||
version: VersionInfo,
|
||||
) -> polkadot_cli::error::Result<()> where
|
||||
P: ParachainContext + Send + 'static,
|
||||
E: IntoFuture<Item=(),Error=()>,
|
||||
E::Future: Send + Clone + 'static,
|
||||
I: IntoIterator<Item=ArgT>,
|
||||
ArgT: Into<std::ffi::OsString> + Clone,
|
||||
{
|
||||
let node_logic = CollationNode { parachain_context, exit: exit.into_future(), para_id, key };
|
||||
polkadot_cli::run(args, node_logic, version)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::collections::{HashMap, BTreeSet};
|
||||
|
||||
use futures::Future;
|
||||
use polkadot_primitives::parachain::{Message, Id as ParaId};
|
||||
|
||||
pub struct DummyRelayChainCtx {
|
||||
egresses: HashMap<ParaId, Vec<Vec<Message>>>,
|
||||
currently_routing: BTreeSet<ParaId>,
|
||||
}
|
||||
|
||||
impl RelayChainContext for DummyRelayChainCtx {
|
||||
type Error = ();
|
||||
type FutureEgress = Result<Vec<Vec<Message>>, ()>;
|
||||
|
||||
fn routing_parachains(&self) -> BTreeSet<ParaId> {
|
||||
self.currently_routing.clone()
|
||||
}
|
||||
|
||||
fn unrouted_egress(&self, id: ParaId) -> Result<Vec<Vec<Message>>, ()> {
|
||||
Ok(self.egresses.get(&id).cloned().unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collates_ingress() {
|
||||
let route_from = |x: &[ParaId]| {
|
||||
let mut set = BTreeSet::new();
|
||||
set.extend(x.iter().cloned());
|
||||
set
|
||||
};
|
||||
|
||||
let message = |x: Vec<u8>| vec![Message(x)];
|
||||
|
||||
let dummy_ctx = DummyRelayChainCtx {
|
||||
currently_routing: route_from(&[2.into(), 3.into()]),
|
||||
egresses: vec![
|
||||
// egresses for `2`: last routed successfully 5 blocks ago.
|
||||
(2.into(), vec![
|
||||
message(vec![1, 2, 3]),
|
||||
message(vec![4, 5, 6]),
|
||||
message(vec![7, 8]),
|
||||
message(vec![10]),
|
||||
message(vec![12]),
|
||||
]),
|
||||
|
||||
// egresses for `3`: last routed successfully 3 blocks ago.
|
||||
(3.into(), vec![
|
||||
message(vec![9]),
|
||||
message(vec![11]),
|
||||
message(vec![13]),
|
||||
]),
|
||||
].into_iter().collect(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
collate_ingress(dummy_ctx).wait().unwrap(),
|
||||
ConsolidatedIngress(vec![
|
||||
(2.into(), message(vec![1, 2, 3])),
|
||||
(2.into(), message(vec![4, 5, 6])),
|
||||
(2.into(), message(vec![7, 8])),
|
||||
(3.into(), message(vec![9])),
|
||||
(2.into(), message(vec![10])),
|
||||
(3.into(), message(vec![11])),
|
||||
(2.into(), message(vec![12])),
|
||||
(3.into(), message(vec![13])),
|
||||
]
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-consensus"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.17"
|
||||
parking_lot = "0.4"
|
||||
tokio = "0.1.7"
|
||||
ed25519 = { path = "../../substrate/ed25519" }
|
||||
error-chain = "0.12"
|
||||
log = "0.3"
|
||||
exit-future = "0.1"
|
||||
rhododendron = "0.3"
|
||||
polkadot-api = { path = "../api" }
|
||||
polkadot-availability-store = { path = "../availability-store" }
|
||||
polkadot-parachain = { path = "../parachain" }
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
polkadot-runtime = { path = "../runtime" }
|
||||
polkadot-statement-table = { path = "../statement-table" }
|
||||
polkadot-transaction-pool = { path = "../transaction-pool" }
|
||||
substrate-bft = { path = "../../substrate/bft" }
|
||||
substrate-codec = { path = "../../substrate/codec" }
|
||||
substrate-primitives = { path = "../../substrate/primitives" }
|
||||
substrate-runtime-support = { path = "../../substrate/runtime-support" }
|
||||
substrate-client = { path = "../../substrate/client" }
|
||||
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-keyring = { path = "../../substrate/keyring" }
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Consensus
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,169 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Validator-side view of collation.
|
||||
//!
|
||||
//! This module contains type definitions, a trait for a batch of collators, and a trait for
|
||||
//! attempting to fetch a collation repeatedly until a valid one is obtained.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use polkadot_api::PolkadotApi;
|
||||
use polkadot_primitives::{Hash, AccountId, BlockId};
|
||||
use polkadot_primitives::parachain::{Id as ParaId, Collation, Extrinsic};
|
||||
|
||||
use futures::prelude::*;
|
||||
|
||||
/// Encapsulates connections to collators and allows collation on any parachain.
|
||||
///
|
||||
/// This is expected to be a lightweight, shared type like an `Arc`.
|
||||
pub trait Collators: Clone {
|
||||
/// Errors when producing collations.
|
||||
type Error;
|
||||
/// A full collation.
|
||||
type Collation: IntoFuture<Item=Collation,Error=Self::Error>;
|
||||
|
||||
/// Collate on a specific parachain, building on a given relay chain parent hash.
|
||||
///
|
||||
/// The returned collation should be checked for basic validity in the signature
|
||||
/// and will be checked for state-transition validity by the consumer of this trait.
|
||||
///
|
||||
/// This does not have to guarantee local availability, as a valid collation
|
||||
/// will be passed to the `TableRouter` instance.
|
||||
fn collate(&self, parachain: ParaId, relay_parent: Hash) -> Self::Collation;
|
||||
|
||||
/// Note a bad collator. TODO: take proof
|
||||
fn note_bad_collator(&self, collator: AccountId);
|
||||
}
|
||||
|
||||
/// A future which resolves when a collation is available.
|
||||
///
|
||||
/// This future is fused.
|
||||
pub struct CollationFetch<C: Collators, P: PolkadotApi> {
|
||||
parachain: ParaId,
|
||||
relay_parent_hash: Hash,
|
||||
relay_parent: BlockId,
|
||||
collators: C,
|
||||
live_fetch: Option<<C::Collation as IntoFuture>::Future>,
|
||||
client: Arc<P>,
|
||||
}
|
||||
|
||||
impl<C: Collators, P: PolkadotApi> CollationFetch<C, P> {
|
||||
/// Create a new collation fetcher for the given chain.
|
||||
pub fn new(parachain: ParaId, relay_parent: BlockId, relay_parent_hash: Hash, collators: C, client: Arc<P>) -> Self {
|
||||
CollationFetch {
|
||||
relay_parent_hash,
|
||||
relay_parent,
|
||||
collators,
|
||||
client,
|
||||
parachain,
|
||||
live_fetch: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the underlying relay parent hash.
|
||||
pub fn relay_parent(&self) -> Hash {
|
||||
self.relay_parent_hash
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Collators, P: PolkadotApi> Future for CollationFetch<C, P> {
|
||||
type Item = (Collation, Extrinsic);
|
||||
type Error = C::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<(Collation, Extrinsic), C::Error> {
|
||||
loop {
|
||||
let x = {
|
||||
let parachain = self.parachain.clone();
|
||||
let (r, c) = (self.relay_parent_hash, &self.collators);
|
||||
let poll = self.live_fetch
|
||||
.get_or_insert_with(move || c.collate(parachain, r).into_future())
|
||||
.poll();
|
||||
|
||||
try_ready!(poll)
|
||||
};
|
||||
|
||||
match validate_collation(&*self.client, &self.relay_parent, &x) {
|
||||
Ok(()) => {
|
||||
// TODO: generate extrinsic while verifying.
|
||||
return Ok(Async::Ready((x, Extrinsic)));
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to validate parachain due to API error: {}", e);
|
||||
|
||||
// just continue if we got a bad collation or failed to validate
|
||||
self.live_fetch = None;
|
||||
self.collators.note_bad_collator(x.receipt.collator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Errors that can occur when validating a parachain.
|
||||
error_chain! {
|
||||
types { Error, ErrorKind, ResultExt; }
|
||||
|
||||
errors {
|
||||
InactiveParachain(id: ParaId) {
|
||||
description("Collated for inactive parachain"),
|
||||
display("Collated for inactive parachain: {:?}", id),
|
||||
}
|
||||
ValidationFailure {
|
||||
description("Parachain candidate failed validation."),
|
||||
display("Parachain candidate failed validation."),
|
||||
}
|
||||
WrongHeadData(expected: Vec<u8>, got: Vec<u8>) {
|
||||
description("Parachain validation produced wrong head data."),
|
||||
display("Parachain validation produced wrong head data (expected: {:?}, got {:?}", expected, got),
|
||||
}
|
||||
}
|
||||
|
||||
links {
|
||||
PolkadotApi(::polkadot_api::Error, ::polkadot_api::ErrorKind);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether a given collation is valid. Returns `Ok` on success, error otherwise.
|
||||
pub fn validate_collation<P: PolkadotApi>(client: &P, relay_parent: &BlockId, collation: &Collation) -> Result<(), Error> {
|
||||
use parachain::{self, ValidationParams};
|
||||
|
||||
let para_id = collation.receipt.parachain_index;
|
||||
let validation_code = client.parachain_code(relay_parent, para_id)?
|
||||
.ok_or_else(|| ErrorKind::InactiveParachain(para_id))?;
|
||||
|
||||
let chain_head = client.parachain_head(relay_parent, para_id)?
|
||||
.ok_or_else(|| ErrorKind::InactiveParachain(para_id))?;
|
||||
|
||||
let params = ValidationParams {
|
||||
parent_head: chain_head,
|
||||
block_data: collation.block_data.0.clone(),
|
||||
};
|
||||
|
||||
match parachain::wasm::validate_candidate(&validation_code, params) {
|
||||
Ok(result) => {
|
||||
if result.head_data == collation.receipt.head_data.0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorKind::WrongHeadData(
|
||||
collation.receipt.head_data.0.clone(),
|
||||
result.head_data
|
||||
).into())
|
||||
}
|
||||
}
|
||||
Err(_) => Err(ErrorKind::ValidationFailure.into())
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Dynamic inclusion threshold over time.
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn duration_to_micros(duration: &Duration) -> u64 {
|
||||
duration.as_secs() * 1_000_000 + (duration.subsec_nanos() / 1000) as u64
|
||||
}
|
||||
|
||||
/// Dynamic inclusion threshold over time.
|
||||
///
|
||||
/// The acceptable proportion of parachains which must have parachain candidates
|
||||
/// reduces over time (eventually going to zero).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DynamicInclusion {
|
||||
start: Instant,
|
||||
y: u64,
|
||||
m: u64,
|
||||
}
|
||||
|
||||
impl DynamicInclusion {
|
||||
/// Constructs a new dynamic inclusion threshold calculator based on the time now,
|
||||
/// how many parachain candidates are required at the beginning, and when an empty
|
||||
/// block will be allowed.
|
||||
pub fn new(initial: usize, start: Instant, allow_empty: Duration) -> Self {
|
||||
// linear function f(n_candidates) -> valid after microseconds
|
||||
// f(0) = allow_empty
|
||||
// f(initial) = 0
|
||||
// m is actually the negative slope to avoid using signed arithmetic.
|
||||
let (y, m) = if initial != 0 {
|
||||
let y = duration_to_micros(&allow_empty);
|
||||
|
||||
(y, y / initial as u64)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
DynamicInclusion {
|
||||
start,
|
||||
y,
|
||||
m,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the duration from `now` after which the amount of included parachain candidates
|
||||
/// would be enough, or `None` if it is sufficient now.
|
||||
///
|
||||
/// Panics if `now` is earlier than the `start`.
|
||||
pub fn acceptable_in(&self, now: Instant, included: usize) -> Option<Instant> {
|
||||
let elapsed = now.duration_since(self.start);
|
||||
let elapsed = duration_to_micros(&elapsed);
|
||||
|
||||
let valid_after = self.y.saturating_sub(self.m * included as u64);
|
||||
|
||||
if elapsed >= valid_after {
|
||||
None
|
||||
} else {
|
||||
let until = Duration::from_millis((valid_after - elapsed) as u64 / 1000);
|
||||
Some(now + until)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the start instant.
|
||||
pub fn started_at(&self) -> Instant { self.start }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn full_immediately_allowed() {
|
||||
let now = Instant::now();
|
||||
|
||||
let dynamic = DynamicInclusion::new(
|
||||
10,
|
||||
now,
|
||||
Duration::from_millis(4000),
|
||||
);
|
||||
|
||||
assert!(dynamic.acceptable_in(now, 10).is_none());
|
||||
assert!(dynamic.acceptable_in(now, 11).is_none());
|
||||
assert!(dynamic.acceptable_in(now + Duration::from_millis(2000), 10).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn half_allowed_halfway() {
|
||||
let now = Instant::now();
|
||||
|
||||
let dynamic = DynamicInclusion::new(
|
||||
10,
|
||||
now,
|
||||
Duration::from_millis(4000),
|
||||
);
|
||||
|
||||
assert_eq!(dynamic.acceptable_in(now, 5), Some(now + Duration::from_millis(2000)));
|
||||
assert!(dynamic.acceptable_in(now + Duration::from_millis(2000), 5).is_none());
|
||||
assert!(dynamic.acceptable_in(now + Duration::from_millis(3000), 5).is_none());
|
||||
assert!(dynamic.acceptable_in(now + Duration::from_millis(4000), 5).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_initial_is_flat() {
|
||||
let now = Instant::now();
|
||||
|
||||
let dynamic = DynamicInclusion::new(
|
||||
0,
|
||||
now,
|
||||
Duration::from_secs(10_000),
|
||||
);
|
||||
|
||||
for i in 0..10_001 {
|
||||
let now = now + Duration::from_secs(i);
|
||||
assert!(dynamic.acceptable_in(now, 0).is_none());
|
||||
assert!(dynamic.acceptable_in(now, 1).is_none());
|
||||
assert!(dynamic.acceptable_in(now, 10).is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Errors that can occur during the consensus process.
|
||||
|
||||
use primitives::AuthorityId;
|
||||
|
||||
error_chain! {
|
||||
links {
|
||||
PolkadotApi(::polkadot_api::Error, ::polkadot_api::ErrorKind);
|
||||
Bft(::bft::Error, ::bft::ErrorKind);
|
||||
}
|
||||
|
||||
errors {
|
||||
InvalidDutyRosterLength(expected: usize, got: usize) {
|
||||
description("Duty Roster had invalid length"),
|
||||
display("Invalid duty roster length: expected {}, got {}", expected, got),
|
||||
}
|
||||
NotValidator(id: AuthorityId) {
|
||||
description("Local account ID not a validator at this block."),
|
||||
display("Local account ID ({:?}) not a validator at this block.", id),
|
||||
}
|
||||
PrematureDestruction {
|
||||
description("Proposer destroyed before finishing proposing or evaluating"),
|
||||
display("Proposer destroyed before finishing proposing or evaluating"),
|
||||
}
|
||||
Timer(e: ::tokio::timer::Error) {
|
||||
description("Failed to register or resolve async timer."),
|
||||
display("Timer failed: {}", e),
|
||||
}
|
||||
Executor(e: ::futures::future::ExecuteErrorKind) {
|
||||
description("Unable to dispatch agreement future"),
|
||||
display("Unable to dispatch agreement future: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::bft::InputStreamConcluded> for Error {
|
||||
fn from(err: ::bft::InputStreamConcluded) -> Self {
|
||||
::bft::Error::from(err).into()
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot block evaluation and evaluation errors.
|
||||
|
||||
use super::MAX_TRANSACTIONS_SIZE;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use polkadot_runtime::{Block as PolkadotGenericBlock, CheckedBlock};
|
||||
use polkadot_primitives::{Block, Hash, BlockNumber, Timestamp};
|
||||
use polkadot_primitives::parachain::Id as ParaId;
|
||||
|
||||
error_chain! {
|
||||
links {
|
||||
PolkadotApi(::polkadot_api::Error, ::polkadot_api::ErrorKind);
|
||||
}
|
||||
|
||||
errors {
|
||||
ProposalNotForPolkadot {
|
||||
description("Proposal provided not a Polkadot block."),
|
||||
display("Proposal provided not a Polkadot block."),
|
||||
}
|
||||
TimestampInFuture {
|
||||
description("Proposal had timestamp too far in the future."),
|
||||
display("Proposal had timestamp too far in the future."),
|
||||
}
|
||||
TooManyCandidates(expected: usize, got: usize) {
|
||||
description("Proposal included more candidates than is possible."),
|
||||
display("Proposal included {} candidates for {} parachains", got, expected),
|
||||
}
|
||||
ParachainOutOfOrder {
|
||||
description("Proposal included parachains out of order."),
|
||||
display("Proposal included parachains out of order."),
|
||||
}
|
||||
UnknownParachain(id: ParaId) {
|
||||
description("Proposal included unregistered parachain."),
|
||||
display("Proposal included unregistered parachain {:?}", id),
|
||||
}
|
||||
WrongParentHash(expected: Hash, got: Hash) {
|
||||
description("Proposal had wrong parent hash."),
|
||||
display("Proposal had wrong parent hash. Expected {:?}, got {:?}", expected, got),
|
||||
}
|
||||
WrongNumber(expected: BlockNumber, got: BlockNumber) {
|
||||
description("Proposal had wrong number."),
|
||||
display("Proposal had wrong number. Expected {:?}, got {:?}", expected, got),
|
||||
}
|
||||
ProposalTooLarge(size: usize) {
|
||||
description("Proposal exceeded the maximum size."),
|
||||
display(
|
||||
"Proposal exceeded the maximum size of {} by {} bytes.",
|
||||
MAX_TRANSACTIONS_SIZE, MAX_TRANSACTIONS_SIZE.saturating_sub(*size)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to evaluate a substrate block as a polkadot block, returning error
|
||||
/// upon any initial validity checks failing.
|
||||
pub fn evaluate_initial(
|
||||
proposal: &Block,
|
||||
now: Timestamp,
|
||||
parent_hash: &Hash,
|
||||
parent_number: BlockNumber,
|
||||
active_parachains: &[ParaId],
|
||||
) -> Result<CheckedBlock> {
|
||||
const MAX_TIMESTAMP_DRIFT: Timestamp = 60;
|
||||
|
||||
let encoded = Encode::encode(proposal);
|
||||
let proposal = PolkadotGenericBlock::decode(&mut &encoded[..])
|
||||
.and_then(|b| CheckedBlock::new(b).ok())
|
||||
.ok_or_else(|| ErrorKind::ProposalNotForPolkadot)?;
|
||||
|
||||
let transactions_size = proposal.extrinsics.iter().fold(0, |a, tx| {
|
||||
a + Encode::encode(tx).len()
|
||||
});
|
||||
|
||||
if transactions_size > MAX_TRANSACTIONS_SIZE {
|
||||
bail!(ErrorKind::ProposalTooLarge(transactions_size))
|
||||
}
|
||||
|
||||
if proposal.header.parent_hash != *parent_hash {
|
||||
bail!(ErrorKind::WrongParentHash(*parent_hash, proposal.header.parent_hash));
|
||||
}
|
||||
|
||||
if proposal.header.number != parent_number + 1 {
|
||||
bail!(ErrorKind::WrongNumber(parent_number + 1, proposal.header.number));
|
||||
}
|
||||
|
||||
let block_timestamp = proposal.timestamp();
|
||||
|
||||
// lenient maximum -- small drifts will just be delayed using a timer.
|
||||
if block_timestamp > now + MAX_TIMESTAMP_DRIFT {
|
||||
bail!(ErrorKind::TimestampInFuture)
|
||||
}
|
||||
|
||||
{
|
||||
let n_parachains = active_parachains.len();
|
||||
if proposal.parachain_heads().len() > n_parachains {
|
||||
bail!(ErrorKind::TooManyCandidates(n_parachains, proposal.parachain_heads().len()));
|
||||
}
|
||||
|
||||
let mut last_id = None;
|
||||
let mut iter = active_parachains.iter();
|
||||
for head in proposal.parachain_heads() {
|
||||
// proposed heads must be ascending order by parachain ID without duplicate.
|
||||
if last_id.as_ref().map_or(false, |x| x >= &head.parachain_index) {
|
||||
bail!(ErrorKind::ParachainOutOfOrder);
|
||||
}
|
||||
|
||||
if !iter.any(|x| x == &head.parachain_index) {
|
||||
// must be unknown since active parachains are always sorted.
|
||||
bail!(ErrorKind::UnknownParachain(head.parachain_index))
|
||||
}
|
||||
|
||||
last_id = Some(head.parachain_index);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(proposal)
|
||||
}
|
||||
@@ -1,857 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Propagation and agreement of candidates.
|
||||
//!
|
||||
//! Authorities are split into groups by parachain, and each authority might come
|
||||
//! up its own candidate for their parachain. Within groups, authorities pass around
|
||||
//! their candidates and produce statements of validity.
|
||||
//!
|
||||
//! Any candidate that receives majority approval by the authorities in a group
|
||||
//! may be subject to inclusion, unless any authorities flag that candidate as invalid.
|
||||
//!
|
||||
//! Wrongly flagging as invalid should be strongly disincentivized, so that in the
|
||||
//! equilibrium state it is not expected to happen. Likewise with the submission
|
||||
//! of invalid blocks.
|
||||
//!
|
||||
//! Groups themselves may be compromised by malicious authorities.
|
||||
|
||||
extern crate ed25519;
|
||||
extern crate parking_lot;
|
||||
extern crate polkadot_api;
|
||||
extern crate polkadot_availability_store as extrinsic_store;
|
||||
extern crate polkadot_statement_table as table;
|
||||
extern crate polkadot_parachain as parachain;
|
||||
extern crate polkadot_transaction_pool as transaction_pool;
|
||||
extern crate polkadot_runtime;
|
||||
extern crate polkadot_primitives;
|
||||
|
||||
extern crate substrate_bft as bft;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate substrate_runtime_support as runtime_support;
|
||||
extern crate substrate_runtime_primitives as runtime_primitives;
|
||||
extern crate substrate_client as client;
|
||||
|
||||
extern crate exit_future;
|
||||
extern crate tokio;
|
||||
extern crate rhododendron;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate substrate_keyring;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use extrinsic_store::Store as ExtrinsicStore;
|
||||
use polkadot_api::PolkadotApi;
|
||||
use polkadot_primitives::{AccountId, Hash, Block, BlockId, BlockNumber, Header, Timestamp, SessionKey};
|
||||
use polkadot_primitives::parachain::{Id as ParaId, Chain, DutyRoster, BlockData, Extrinsic as ParachainExtrinsic, CandidateReceipt, CandidateSignature};
|
||||
use primitives::AuthorityId;
|
||||
use transaction_pool::TransactionPool;
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use tokio::timer::{Delay, Interval};
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::future;
|
||||
use collation::CollationFetch;
|
||||
use dynamic_inclusion::DynamicInclusion;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
pub use self::collation::{validate_collation, Collators};
|
||||
pub use self::error::{ErrorKind, Error};
|
||||
pub use self::offline_tracker::OfflineTracker;
|
||||
pub use self::shared_table::{SharedTable, StatementProducer, ProducedStatements, Statement, SignedStatement, GenericStatement};
|
||||
pub use service::Service;
|
||||
|
||||
mod dynamic_inclusion;
|
||||
mod evaluation;
|
||||
mod error;
|
||||
mod offline_tracker;
|
||||
mod service;
|
||||
mod shared_table;
|
||||
|
||||
pub mod collation;
|
||||
|
||||
/// Shared offline validator tracker.
|
||||
pub type SharedOfflineTracker = Arc<RwLock<OfflineTracker>>;
|
||||
|
||||
// block size limit.
|
||||
const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
/// A handle to a statement table router.
|
||||
///
|
||||
/// This is expected to be a lightweight, shared type like an `Arc`.
|
||||
pub trait TableRouter: Clone {
|
||||
/// Errors when fetching data from the network.
|
||||
type Error;
|
||||
/// Future that resolves when candidate data is fetched.
|
||||
type FetchCandidate: IntoFuture<Item=BlockData,Error=Self::Error>;
|
||||
/// Future that resolves when extrinsic candidate data is fetched.
|
||||
type FetchExtrinsic: IntoFuture<Item=ParachainExtrinsic,Error=Self::Error>;
|
||||
|
||||
/// Call with local candidate data. This will make the data available on the network,
|
||||
/// and sign, import, and broadcast a statement about the candidate.
|
||||
fn local_candidate(&self, candidate: CandidateReceipt, block_data: BlockData, extrinsic: ParachainExtrinsic);
|
||||
|
||||
/// Fetch block data for a specific candidate.
|
||||
fn fetch_block_data(&self, candidate: &CandidateReceipt) -> Self::FetchCandidate;
|
||||
|
||||
/// Fetch extrinsic data for a specific candidate.
|
||||
fn fetch_extrinsic_data(&self, candidate: &CandidateReceipt) -> Self::FetchExtrinsic;
|
||||
}
|
||||
|
||||
/// A long-lived network which can create parachain statement and BFT message routing processes on demand.
|
||||
pub trait Network {
|
||||
/// The table router type. This should handle importing of any statements,
|
||||
/// routing statements to peers, and driving completion of any `StatementProducers`.
|
||||
type TableRouter: TableRouter;
|
||||
/// The input stream of BFT messages. Should never logically conclude.
|
||||
type Input: Stream<Item=bft::Communication<Block>,Error=Error>;
|
||||
/// The output sink of BFT messages. Messages sent here should eventually pass to all
|
||||
/// current authorities.
|
||||
type Output: Sink<SinkItem=bft::Communication<Block>,SinkError=Error>;
|
||||
|
||||
/// Instantiate a table router using the given shared table and task executor.
|
||||
fn communication_for(&self, validators: &[SessionKey], table: Arc<SharedTable>, task_executor: TaskExecutor) -> (Self::TableRouter, Self::Input, Self::Output);
|
||||
}
|
||||
|
||||
/// Information about a specific group.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct GroupInfo {
|
||||
/// Authorities meant to check validity of candidates.
|
||||
pub validity_guarantors: HashSet<SessionKey>,
|
||||
/// Authorities meant to check availability of candidate data.
|
||||
pub availability_guarantors: HashSet<SessionKey>,
|
||||
/// Number of votes needed for validity.
|
||||
pub needed_validity: usize,
|
||||
/// Number of votes needed for availability.
|
||||
pub needed_availability: usize,
|
||||
}
|
||||
|
||||
/// Sign a table statement against a parent hash.
|
||||
/// The actual message signed is the encoded statement concatenated with the
|
||||
/// parent hash.
|
||||
pub fn sign_table_statement(statement: &Statement, key: &ed25519::Pair, parent_hash: &Hash) -> CandidateSignature {
|
||||
let mut encoded = statement.encode();
|
||||
encoded.extend(&parent_hash.0);
|
||||
|
||||
key.sign(&encoded).into()
|
||||
}
|
||||
|
||||
/// Check signature on table statement.
|
||||
pub fn check_statement(statement: &Statement, signature: &CandidateSignature, signer: SessionKey, parent_hash: &Hash) -> bool {
|
||||
use runtime_primitives::traits::Verify;
|
||||
|
||||
let mut encoded = statement.encode();
|
||||
encoded.extend(&parent_hash.0);
|
||||
|
||||
signature.verify(&encoded[..], &signer.into())
|
||||
}
|
||||
|
||||
fn make_group_info(roster: DutyRoster, authorities: &[AuthorityId], local_id: AuthorityId) -> Result<(HashMap<ParaId, GroupInfo>, LocalDuty), Error> {
|
||||
if roster.validator_duty.len() != authorities.len() {
|
||||
bail!(ErrorKind::InvalidDutyRosterLength(authorities.len(), roster.validator_duty.len()))
|
||||
}
|
||||
|
||||
if roster.guarantor_duty.len() != authorities.len() {
|
||||
bail!(ErrorKind::InvalidDutyRosterLength(authorities.len(), roster.guarantor_duty.len()))
|
||||
}
|
||||
|
||||
let mut local_validation = None;
|
||||
let mut map = HashMap::new();
|
||||
|
||||
let duty_iter = authorities.iter().zip(&roster.validator_duty).zip(&roster.guarantor_duty);
|
||||
for ((authority, v_duty), a_duty) in duty_iter {
|
||||
if authority == &local_id {
|
||||
local_validation = Some(v_duty.clone());
|
||||
}
|
||||
|
||||
match *v_duty {
|
||||
Chain::Relay => {}, // does nothing for now.
|
||||
Chain::Parachain(ref id) => {
|
||||
map.entry(id.clone()).or_insert_with(GroupInfo::default)
|
||||
.validity_guarantors
|
||||
.insert(authority.clone());
|
||||
}
|
||||
}
|
||||
|
||||
match *a_duty {
|
||||
Chain::Relay => {}, // does nothing for now.
|
||||
Chain::Parachain(ref id) => {
|
||||
map.entry(id.clone()).or_insert_with(GroupInfo::default)
|
||||
.availability_guarantors
|
||||
.insert(authority.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for live_group in map.values_mut() {
|
||||
let validity_len = live_group.validity_guarantors.len();
|
||||
let availability_len = live_group.availability_guarantors.len();
|
||||
|
||||
live_group.needed_validity = validity_len / 2 + validity_len % 2;
|
||||
live_group.needed_availability = availability_len / 2 + availability_len % 2;
|
||||
}
|
||||
|
||||
match local_validation {
|
||||
Some(local_validation) => {
|
||||
let local_duty = LocalDuty {
|
||||
validation: local_validation,
|
||||
};
|
||||
|
||||
Ok((map, local_duty))
|
||||
}
|
||||
None => bail!(ErrorKind::NotValidator(local_id)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Polkadot proposer factory.
|
||||
pub struct ProposerFactory<C, N, P> {
|
||||
/// The client instance.
|
||||
pub client: Arc<P>,
|
||||
/// The transaction pool.
|
||||
pub transaction_pool: Arc<TransactionPool<P>>,
|
||||
/// The backing network handle.
|
||||
pub network: N,
|
||||
/// Parachain collators.
|
||||
pub collators: C,
|
||||
/// handle to remote task executor
|
||||
pub handle: TaskExecutor,
|
||||
/// The duration after which parachain-empty blocks will be allowed.
|
||||
pub parachain_empty_duration: Duration,
|
||||
/// Store for extrinsic data.
|
||||
pub extrinsic_store: ExtrinsicStore,
|
||||
/// Offline-tracker.
|
||||
pub offline: SharedOfflineTracker,
|
||||
}
|
||||
|
||||
impl<C, N, P> bft::Environment<Block> for ProposerFactory<C, N, P>
|
||||
where
|
||||
C: Collators + Send + 'static,
|
||||
N: Network,
|
||||
P: PolkadotApi + Send + Sync + 'static,
|
||||
<C::Collation as IntoFuture>::Future: Send + 'static,
|
||||
N::TableRouter: Send + 'static,
|
||||
{
|
||||
type Proposer = Proposer<P>;
|
||||
type Input = N::Input;
|
||||
type Output = N::Output;
|
||||
type Error = Error;
|
||||
|
||||
fn init(
|
||||
&self,
|
||||
parent_header: &Header,
|
||||
authorities: &[AuthorityId],
|
||||
sign_with: Arc<ed25519::Pair>,
|
||||
) -> Result<(Self::Proposer, Self::Input, Self::Output), Error> {
|
||||
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
|
||||
|
||||
let parent_hash = parent_header.hash().into();
|
||||
|
||||
let id = BlockId::hash(parent_hash);
|
||||
let duty_roster = self.client.duty_roster(&id)?;
|
||||
let random_seed = self.client.random_seed(&id)?;
|
||||
let random_seed = BlakeTwo256::hash(&*random_seed);
|
||||
|
||||
let validators = self.client.validators(&id)?;
|
||||
self.offline.write().note_new_block(&validators[..]);
|
||||
|
||||
let (group_info, local_duty) = make_group_info(
|
||||
duty_roster,
|
||||
authorities,
|
||||
sign_with.public().into(),
|
||||
)?;
|
||||
|
||||
info!("Starting consensus session on top of parent {:?}. Local parachain duty is {:?}",
|
||||
parent_hash, local_duty.validation);
|
||||
|
||||
let active_parachains = self.client.active_parachains(&id)?;
|
||||
|
||||
debug!(target: "consensus", "Active parachains: {:?}", active_parachains);
|
||||
|
||||
let n_parachains = active_parachains.len();
|
||||
let table = Arc::new(SharedTable::new(group_info, sign_with.clone(), parent_hash, self.extrinsic_store.clone()));
|
||||
let (router, input, output) = self.network.communication_for(
|
||||
authorities,
|
||||
table.clone(),
|
||||
self.handle.clone()
|
||||
);
|
||||
|
||||
let now = Instant::now();
|
||||
let dynamic_inclusion = DynamicInclusion::new(
|
||||
n_parachains,
|
||||
now,
|
||||
self.parachain_empty_duration.clone(),
|
||||
);
|
||||
|
||||
let validation_para = match local_duty.validation {
|
||||
Chain::Relay => None,
|
||||
Chain::Parachain(id) => Some(id),
|
||||
};
|
||||
|
||||
let collation_work = validation_para.map(|para| CollationFetch::new(
|
||||
para,
|
||||
id.clone(),
|
||||
parent_hash.clone(),
|
||||
self.collators.clone(),
|
||||
self.client.clone(),
|
||||
));
|
||||
let drop_signal = dispatch_collation_work(
|
||||
router.clone(),
|
||||
&self.handle,
|
||||
collation_work,
|
||||
self.extrinsic_store.clone(),
|
||||
);
|
||||
|
||||
let proposer = Proposer {
|
||||
client: self.client.clone(),
|
||||
dynamic_inclusion,
|
||||
local_key: sign_with,
|
||||
parent_hash,
|
||||
parent_id: id,
|
||||
parent_number: parent_header.number,
|
||||
random_seed,
|
||||
table,
|
||||
transaction_pool: self.transaction_pool.clone(),
|
||||
offline: self.offline.clone(),
|
||||
validators,
|
||||
_drop_signal: drop_signal,
|
||||
};
|
||||
|
||||
Ok((proposer, input, output))
|
||||
}
|
||||
}
|
||||
|
||||
// dispatch collation work to be done in the background. returns a signal object
|
||||
// that should fire when the collation work is no longer necessary (e.g. when the proposer object is dropped)
|
||||
fn dispatch_collation_work<R, C, P>(
|
||||
router: R,
|
||||
handle: &TaskExecutor,
|
||||
work: Option<CollationFetch<C, P>>,
|
||||
extrinsic_store: ExtrinsicStore,
|
||||
) -> exit_future::Signal where
|
||||
C: Collators + Send + 'static,
|
||||
P: PolkadotApi + Send + Sync + 'static,
|
||||
<C::Collation as IntoFuture>::Future: Send + 'static,
|
||||
R: TableRouter + Send + 'static,
|
||||
{
|
||||
use extrinsic_store::Data;
|
||||
|
||||
let (signal, exit) = exit_future::signal();
|
||||
|
||||
let work = match work {
|
||||
Some(w) => w,
|
||||
None => return signal,
|
||||
};
|
||||
|
||||
let relay_parent = work.relay_parent();
|
||||
let handled_work = work.then(move |result| match result {
|
||||
Ok((collation, extrinsic)) => {
|
||||
let res = extrinsic_store.make_available(Data {
|
||||
relay_parent,
|
||||
parachain_id: collation.receipt.parachain_index,
|
||||
candidate_hash: collation.receipt.hash(),
|
||||
block_data: collation.block_data.clone(),
|
||||
extrinsic: Some(extrinsic.clone()),
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(()) =>
|
||||
router.local_candidate(collation.receipt, collation.block_data, extrinsic),
|
||||
Err(e) =>
|
||||
warn!(target: "consensus", "Failed to make collation data available: {:?}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(_e) => {
|
||||
warn!(target: "consensus", "Failed to collate candidate");
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
let cancellable_work = handled_work.select(exit).then(|_| Ok(()));
|
||||
|
||||
// spawn onto thread pool.
|
||||
handle.spawn(cancellable_work);
|
||||
signal
|
||||
}
|
||||
|
||||
struct LocalDuty {
|
||||
validation: Chain,
|
||||
}
|
||||
|
||||
/// The Polkadot proposer logic.
|
||||
pub struct Proposer<C: PolkadotApi> {
|
||||
client: Arc<C>,
|
||||
dynamic_inclusion: DynamicInclusion,
|
||||
local_key: Arc<ed25519::Pair>,
|
||||
parent_hash: Hash,
|
||||
parent_id: BlockId,
|
||||
parent_number: BlockNumber,
|
||||
random_seed: Hash,
|
||||
table: Arc<SharedTable>,
|
||||
transaction_pool: Arc<TransactionPool<C>>,
|
||||
offline: SharedOfflineTracker,
|
||||
validators: Vec<AccountId>,
|
||||
_drop_signal: exit_future::Signal,
|
||||
}
|
||||
|
||||
impl<C: PolkadotApi + Send + Sync> Proposer<C> {
|
||||
fn primary_index(&self, round_number: usize, len: usize) -> usize {
|
||||
use primitives::uint::U256;
|
||||
|
||||
let big_len = U256::from(len);
|
||||
let offset = U256::from_big_endian(&self.random_seed.0) % big_len;
|
||||
let offset = offset.low_u64() as usize + round_number;
|
||||
offset % len
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> bft::Proposer<Block> for Proposer<C>
|
||||
where
|
||||
C: PolkadotApi + Send + Sync,
|
||||
{
|
||||
type Error = Error;
|
||||
type Create = future::Either<
|
||||
CreateProposal<C>,
|
||||
future::FutureResult<Block, Error>,
|
||||
>;
|
||||
type Evaluate = Box<Future<Item=bool, Error=Error>>;
|
||||
|
||||
fn propose(&self) -> Self::Create {
|
||||
const ATTEMPT_PROPOSE_EVERY: Duration = Duration::from_millis(100);
|
||||
|
||||
let initial_included = self.table.includable_count();
|
||||
let now = Instant::now();
|
||||
let enough_candidates = self.dynamic_inclusion.acceptable_in(
|
||||
now,
|
||||
initial_included,
|
||||
).unwrap_or_else(|| now + Duration::from_millis(1));
|
||||
|
||||
let timing = ProposalTiming {
|
||||
attempt_propose: Interval::new(now + ATTEMPT_PROPOSE_EVERY, ATTEMPT_PROPOSE_EVERY),
|
||||
enough_candidates: Delay::new(enough_candidates),
|
||||
dynamic_inclusion: self.dynamic_inclusion.clone(),
|
||||
last_included: initial_included,
|
||||
};
|
||||
|
||||
future::Either::A(CreateProposal {
|
||||
parent_hash: self.parent_hash.clone(),
|
||||
parent_number: self.parent_number.clone(),
|
||||
parent_id: self.parent_id.clone(),
|
||||
client: self.client.clone(),
|
||||
transaction_pool: self.transaction_pool.clone(),
|
||||
table: self.table.clone(),
|
||||
offline: self.offline.clone(),
|
||||
validators: self.validators.clone(),
|
||||
timing,
|
||||
})
|
||||
}
|
||||
|
||||
fn evaluate(&self, unchecked_proposal: &Block) -> Self::Evaluate {
|
||||
debug!(target: "bft", "evaluating block on top of parent ({}, {:?})", self.parent_number, self.parent_hash);
|
||||
|
||||
let active_parachains = match self.client.active_parachains(&self.parent_id) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Box::new(future::err(e.into())) as Box<_>,
|
||||
};
|
||||
|
||||
let current_timestamp = current_timestamp();
|
||||
|
||||
// do initial serialization and structural integrity checks.
|
||||
let maybe_proposal = evaluation::evaluate_initial(
|
||||
unchecked_proposal,
|
||||
current_timestamp,
|
||||
&self.parent_hash,
|
||||
self.parent_number,
|
||||
&active_parachains,
|
||||
);
|
||||
|
||||
let proposal = match maybe_proposal {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
// TODO: these errors are easily re-checked in runtime.
|
||||
debug!(target: "bft", "Invalid proposal: {:?}", e);
|
||||
return Box::new(future::ok(false));
|
||||
}
|
||||
};
|
||||
|
||||
let vote_delays = {
|
||||
let now = Instant::now();
|
||||
|
||||
let included_candidate_hashes = proposal
|
||||
.parachain_heads()
|
||||
.iter()
|
||||
.map(|candidate| candidate.hash());
|
||||
|
||||
// delay casting vote until we have proof that all candidates are
|
||||
// includable.
|
||||
let includability_tracker = self.table.track_includability(included_candidate_hashes)
|
||||
.map_err(|_| ErrorKind::PrematureDestruction.into());
|
||||
|
||||
// the duration at which the given number of parachains is acceptable.
|
||||
let count_delay = self.dynamic_inclusion.acceptable_in(
|
||||
now,
|
||||
proposal.parachain_heads().len(),
|
||||
);
|
||||
|
||||
// the duration until the given timestamp is current
|
||||
let proposed_timestamp = proposal.timestamp();
|
||||
let timestamp_delay = if proposed_timestamp > current_timestamp {
|
||||
Some(now + Duration::from_secs(proposed_timestamp - current_timestamp))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// delay casting vote until able according to minimum block time,
|
||||
// timestamp delay, and count delay.
|
||||
// construct a future from the maximum of the two durations.
|
||||
let max_delay = ::std::cmp::max(timestamp_delay, count_delay);
|
||||
|
||||
let temporary_delay = match max_delay {
|
||||
Some(duration) => future::Either::A(
|
||||
Delay::new(duration).map_err(|e| Error::from(ErrorKind::Timer(e)))
|
||||
),
|
||||
None => future::Either::B(future::ok(())),
|
||||
};
|
||||
|
||||
includability_tracker.join(temporary_delay)
|
||||
};
|
||||
|
||||
// refuse to vote if this block says a validator is offline that we
|
||||
// think isn't.
|
||||
let offline = proposal.noted_offline();
|
||||
if !self.offline.read().check_consistency(&self.validators[..], offline) {
|
||||
return Box::new(futures::empty());
|
||||
}
|
||||
|
||||
// evaluate whether the block is actually valid.
|
||||
// TODO: is it better to delay this until the delays are finished?
|
||||
let evaluated = self.client
|
||||
.evaluate_block(&self.parent_id, unchecked_proposal.clone())
|
||||
.map_err(Into::into);
|
||||
|
||||
let future = future::result(evaluated).and_then(move |good| {
|
||||
let end_result = future::ok(good);
|
||||
if good {
|
||||
// delay a "good" vote.
|
||||
future::Either::A(vote_delays.and_then(|_| end_result))
|
||||
} else {
|
||||
// don't delay a "bad" evaluation.
|
||||
future::Either::B(end_result)
|
||||
}
|
||||
});
|
||||
|
||||
Box::new(future) as Box<_>
|
||||
}
|
||||
|
||||
fn round_proposer(&self, round_number: usize, authorities: &[AuthorityId]) -> AuthorityId {
|
||||
let offset = self.primary_index(round_number, authorities.len());
|
||||
let proposer = authorities[offset].clone();
|
||||
trace!(target: "bft", "proposer for round {} is {}", round_number, proposer);
|
||||
|
||||
proposer
|
||||
}
|
||||
|
||||
fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, bft::Misbehavior<Hash>)>) {
|
||||
use rhododendron::Misbehavior as GenericMisbehavior;
|
||||
use runtime_primitives::bft::{MisbehaviorKind, MisbehaviorReport};
|
||||
use runtime_primitives::MaybeUnsigned;
|
||||
use polkadot_runtime::{Call, Extrinsic, BareExtrinsic, UncheckedExtrinsic, ConsensusCall};
|
||||
|
||||
let local_id = self.local_key.public().0.into();
|
||||
let mut next_index = {
|
||||
let cur_index = self.transaction_pool.cull_and_get_pending(BlockId::hash(self.parent_hash), |pending| pending
|
||||
.filter(|tx| tx.sender().map(|s| s == local_id).unwrap_or(false))
|
||||
.last()
|
||||
.map(|tx| Ok(tx.index()))
|
||||
.unwrap_or_else(|| self.client.index(&self.parent_id, local_id))
|
||||
);
|
||||
|
||||
match cur_index {
|
||||
Ok(Ok(cur_index)) => cur_index + 1,
|
||||
Ok(Err(e)) => {
|
||||
warn!(target: "consensus", "Error computing next transaction index: {}", e);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(target: "consensus", "Error computing next transaction index: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (target, misbehavior) in misbehavior {
|
||||
let report = MisbehaviorReport {
|
||||
parent_hash: self.parent_hash,
|
||||
parent_number: self.parent_number,
|
||||
target,
|
||||
misbehavior: match misbehavior {
|
||||
GenericMisbehavior::ProposeOutOfTurn(_, _, _) => continue,
|
||||
GenericMisbehavior::DoublePropose(_, _, _) => continue,
|
||||
GenericMisbehavior::DoublePrepare(round, (h1, s1), (h2, s2))
|
||||
=> MisbehaviorKind::BftDoublePrepare(round as u32, (h1, s1.signature), (h2, s2.signature)),
|
||||
GenericMisbehavior::DoubleCommit(round, (h1, s1), (h2, s2))
|
||||
=> MisbehaviorKind::BftDoubleCommit(round as u32, (h1, s1.signature), (h2, s2.signature)),
|
||||
}
|
||||
};
|
||||
let extrinsic = BareExtrinsic {
|
||||
signed: local_id,
|
||||
index: next_index,
|
||||
function: Call::Consensus(ConsensusCall::report_misbehavior(report)),
|
||||
};
|
||||
|
||||
next_index += 1;
|
||||
|
||||
let signature = MaybeUnsigned(self.local_key.sign(&extrinsic.encode()).into());
|
||||
|
||||
let extrinsic = Extrinsic {
|
||||
signed: extrinsic.signed.into(),
|
||||
index: extrinsic.index,
|
||||
function: extrinsic.function,
|
||||
};
|
||||
let uxt = UncheckedExtrinsic::new(extrinsic, signature);
|
||||
|
||||
self.transaction_pool.import_unchecked_extrinsic(BlockId::hash(self.parent_hash), uxt)
|
||||
.expect("locally signed extrinsic is valid; qed");
|
||||
}
|
||||
}
|
||||
|
||||
fn on_round_end(&self, round_number: usize, was_proposed: bool) {
|
||||
let primary_validator = self.validators[
|
||||
self.primary_index(round_number, self.validators.len())
|
||||
];
|
||||
|
||||
|
||||
// alter the message based on whether we think the empty proposer was forced to skip the round.
|
||||
// this is determined by checking if our local validator would have been forced to skip the round.
|
||||
let consider_online = was_proposed || {
|
||||
let forced_delay = self.dynamic_inclusion.acceptable_in(Instant::now(), self.table.includable_count());
|
||||
let public = ::ed25519::Public::from_raw(primary_validator.0);
|
||||
match forced_delay {
|
||||
None => info!(
|
||||
"Potential Offline Validator: {} failed to propose during assigned slot: {}",
|
||||
public,
|
||||
round_number,
|
||||
),
|
||||
Some(_) => info!(
|
||||
"Potential Offline Validator {} potentially forced to skip assigned slot: {}",
|
||||
public,
|
||||
round_number,
|
||||
),
|
||||
}
|
||||
|
||||
forced_delay.is_some()
|
||||
};
|
||||
|
||||
self.offline.write().note_round_end(primary_validator, consider_online);
|
||||
}
|
||||
}
|
||||
|
||||
fn current_timestamp() -> Timestamp {
|
||||
use std::time;
|
||||
|
||||
time::SystemTime::now().duration_since(time::UNIX_EPOCH)
|
||||
.expect("now always later than unix epoch; qed")
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
struct ProposalTiming {
|
||||
attempt_propose: Interval,
|
||||
dynamic_inclusion: DynamicInclusion,
|
||||
enough_candidates: Delay,
|
||||
last_included: usize,
|
||||
}
|
||||
|
||||
impl ProposalTiming {
|
||||
// whether it's time to attempt a proposal.
|
||||
// shouldn't be called outside of the context of a task.
|
||||
fn poll(&mut self, included: usize) -> Poll<(), ErrorKind> {
|
||||
// first drain from the interval so when the minimum delay is up
|
||||
// we don't have any notifications built up.
|
||||
//
|
||||
// this interval is just meant to produce periodic task wakeups
|
||||
// that lead to the `dynamic_inclusion` getting updated as necessary.
|
||||
if let Async::Ready(x) = self.attempt_propose.poll().map_err(ErrorKind::Timer)? {
|
||||
x.expect("timer still alive; intervals never end; qed");
|
||||
}
|
||||
|
||||
if included == self.last_included {
|
||||
return self.enough_candidates.poll().map_err(ErrorKind::Timer);
|
||||
}
|
||||
|
||||
// the amount of includable candidates has changed. schedule a wakeup
|
||||
// if it's not sufficient anymore.
|
||||
match self.dynamic_inclusion.acceptable_in(Instant::now(), included) {
|
||||
Some(instant) => {
|
||||
self.last_included = included;
|
||||
self.enough_candidates.reset(instant);
|
||||
self.enough_candidates.poll().map_err(ErrorKind::Timer)
|
||||
}
|
||||
None => Ok(Async::Ready(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Future which resolves upon the creation of a proposal.
|
||||
pub struct CreateProposal<C: PolkadotApi> {
|
||||
parent_hash: Hash,
|
||||
parent_number: BlockNumber,
|
||||
parent_id: BlockId,
|
||||
client: Arc<C>,
|
||||
transaction_pool: Arc<TransactionPool<C>>,
|
||||
table: Arc<SharedTable>,
|
||||
timing: ProposalTiming,
|
||||
validators: Vec<AccountId>,
|
||||
offline: SharedOfflineTracker,
|
||||
}
|
||||
|
||||
impl<C> CreateProposal<C> where C: PolkadotApi {
|
||||
fn propose_with(&self, candidates: Vec<CandidateReceipt>) -> Result<Block, Error> {
|
||||
use polkadot_api::BlockBuilder;
|
||||
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
|
||||
use polkadot_primitives::InherentData;
|
||||
|
||||
const MAX_VOTE_OFFLINE_SECONDS: Duration = Duration::from_secs(60);
|
||||
|
||||
// TODO: handle case when current timestamp behind that in state.
|
||||
let timestamp = current_timestamp();
|
||||
|
||||
let elapsed_since_start = self.timing.dynamic_inclusion.started_at().elapsed();
|
||||
let offline_indices = if elapsed_since_start > MAX_VOTE_OFFLINE_SECONDS {
|
||||
Vec::new()
|
||||
} else {
|
||||
self.offline.read().reports(&self.validators[..])
|
||||
};
|
||||
|
||||
if !offline_indices.is_empty() {
|
||||
info!(
|
||||
"Submitting offline validators {:?} for slash-vote",
|
||||
offline_indices.iter().map(|&i| self.validators[i as usize]).collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
let inherent_data = InherentData {
|
||||
timestamp,
|
||||
parachain_heads: candidates,
|
||||
offline_indices,
|
||||
};
|
||||
|
||||
let mut block_builder = self.client.build_block(&self.parent_id, inherent_data)?;
|
||||
|
||||
{
|
||||
let mut unqueue_invalid = Vec::new();
|
||||
let result = self.transaction_pool.cull_and_get_pending(BlockId::hash(self.parent_hash), |pending_iterator| {
|
||||
let mut pending_size = 0;
|
||||
for pending in pending_iterator {
|
||||
if pending_size + pending.encoded_size() >= MAX_TRANSACTIONS_SIZE { break }
|
||||
|
||||
match block_builder.push_extrinsic(pending.primitive_extrinsic()) {
|
||||
Ok(()) => {
|
||||
pending_size += pending.encoded_size();
|
||||
}
|
||||
Err(e) => {
|
||||
trace!(target: "transaction-pool", "Invalid transaction: {}", e);
|
||||
unqueue_invalid.push(pending.hash().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if let Err(e) = result {
|
||||
warn!("Unable to get the pending set: {:?}", e);
|
||||
}
|
||||
|
||||
self.transaction_pool.remove(&unqueue_invalid, false);
|
||||
}
|
||||
|
||||
let polkadot_block = block_builder.bake()?;
|
||||
|
||||
info!("Proposing block [number: {}; hash: {}; parent_hash: {}; extrinsics: [{}]]",
|
||||
polkadot_block.header.number,
|
||||
Hash::from(polkadot_block.header.hash()),
|
||||
polkadot_block.header.parent_hash,
|
||||
polkadot_block.extrinsics.iter()
|
||||
.map(|xt| format!("{}", BlakeTwo256::hash_of(xt)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
let substrate_block = Decode::decode(&mut polkadot_block.encode().as_slice())
|
||||
.expect("polkadot blocks defined to serialize to substrate blocks correctly; qed");
|
||||
|
||||
// TODO: full re-evaluation
|
||||
let active_parachains = self.client.active_parachains(&self.parent_id)?;
|
||||
assert!(evaluation::evaluate_initial(
|
||||
&substrate_block,
|
||||
timestamp,
|
||||
&self.parent_hash,
|
||||
self.parent_number,
|
||||
&active_parachains,
|
||||
).is_ok());
|
||||
|
||||
Ok(substrate_block)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Future for CreateProposal<C> where C: PolkadotApi {
|
||||
type Item = Block;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Block, Error> {
|
||||
// 1. try to propose if we have enough includable candidates and other
|
||||
// delays have concluded.
|
||||
let included = self.table.includable_count();
|
||||
try_ready!(self.timing.poll(included));
|
||||
|
||||
// 2. propose
|
||||
let proposed_candidates = self.table.with_proposal(|proposed_set| {
|
||||
proposed_set.into_iter().cloned().collect()
|
||||
});
|
||||
|
||||
self.propose_with(proposed_candidates).map(Async::Ready)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use substrate_keyring::Keyring;
|
||||
|
||||
#[test]
|
||||
fn sign_and_check_statement() {
|
||||
let statement: Statement = GenericStatement::Valid([1; 32].into());
|
||||
let parent_hash = [2; 32].into();
|
||||
|
||||
let sig = sign_table_statement(&statement, &Keyring::Alice.pair(), &parent_hash);
|
||||
|
||||
assert!(check_statement(&statement, &sig, Keyring::Alice.to_raw_public().into(), &parent_hash));
|
||||
assert!(!check_statement(&statement, &sig, Keyring::Alice.to_raw_public().into(), &[0xff; 32].into()));
|
||||
assert!(!check_statement(&statement, &sig, Keyring::Bob.to_raw_public().into(), &parent_hash));
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tracks offline validators.
|
||||
|
||||
use polkadot_primitives::AccountId;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
// time before we report a validator.
|
||||
const REPORT_TIME: Duration = Duration::from_secs(60 * 5);
|
||||
|
||||
struct Observed {
|
||||
last_round_end: Instant,
|
||||
offline_since: Instant,
|
||||
}
|
||||
|
||||
impl Observed {
|
||||
fn new() -> Observed {
|
||||
let now = Instant::now();
|
||||
Observed {
|
||||
last_round_end: now,
|
||||
offline_since: now,
|
||||
}
|
||||
}
|
||||
|
||||
fn note_round_end(&mut self, was_online: bool) {
|
||||
let now = Instant::now();
|
||||
|
||||
self.last_round_end = now;
|
||||
if was_online {
|
||||
self.offline_since = now;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_active(&self) -> bool {
|
||||
// can happen if clocks are not monotonic
|
||||
if self.offline_since > self.last_round_end { return true }
|
||||
self.last_round_end.duration_since(self.offline_since) < REPORT_TIME
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks offline validators and can issue a report for those offline.
|
||||
pub struct OfflineTracker {
|
||||
observed: HashMap<AccountId, Observed>,
|
||||
}
|
||||
|
||||
impl OfflineTracker {
|
||||
/// Create a new tracker.
|
||||
pub fn new() -> Self {
|
||||
OfflineTracker { observed: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Note new consensus is starting with the given set of validators.
|
||||
pub fn note_new_block(&mut self, validators: &[AccountId]) {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let set: HashSet<_> = validators.iter().cloned().collect();
|
||||
self.observed.retain(|k, _| set.contains(k));
|
||||
}
|
||||
|
||||
/// Note that a round has ended.
|
||||
pub fn note_round_end(&mut self, validator: AccountId, was_online: bool) {
|
||||
self.observed.entry(validator)
|
||||
.or_insert_with(Observed::new)
|
||||
.note_round_end(was_online);
|
||||
}
|
||||
|
||||
/// Generate a vector of indices for offline account IDs.
|
||||
pub fn reports(&self, validators: &[AccountId]) -> Vec<u32> {
|
||||
validators.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, v)| if self.is_online(v) {
|
||||
None
|
||||
} else {
|
||||
Some(i as u32)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether reports on a validator set are consistent with our view of things.
|
||||
pub fn check_consistency(&self, validators: &[AccountId], reports: &[u32]) -> bool {
|
||||
reports.iter().cloned().all(|r| {
|
||||
let v = match validators.get(r as usize) {
|
||||
Some(v) => v,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// we must think all validators reported externally are offline.
|
||||
let thinks_online = self.is_online(v);
|
||||
!thinks_online
|
||||
})
|
||||
}
|
||||
|
||||
fn is_online(&self, v: &AccountId) -> bool {
|
||||
self.observed.get(v).map(Observed::is_active).unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn validator_offline() {
|
||||
let mut tracker = OfflineTracker::new();
|
||||
let v = [0; 32].into();
|
||||
let v2 = [1; 32].into();
|
||||
let v3 = [2; 32].into();
|
||||
tracker.note_round_end(v, true);
|
||||
tracker.note_round_end(v2, true);
|
||||
tracker.note_round_end(v3, true);
|
||||
|
||||
let slash_time = REPORT_TIME + Duration::from_secs(5);
|
||||
tracker.observed.get_mut(&v).unwrap().offline_since -= slash_time;
|
||||
tracker.observed.get_mut(&v2).unwrap().offline_since -= slash_time;
|
||||
|
||||
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0, 1]);
|
||||
|
||||
tracker.note_new_block(&[v, v3]);
|
||||
assert_eq!(tracker.reports(&[v, v2, v3]), vec![0]);
|
||||
}
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Consensus service.
|
||||
|
||||
/// Consensus service. A long running service that manages BFT agreement and parachain
|
||||
/// candidate agreement over the network.
|
||||
///
|
||||
/// This uses a handle to an underlying thread pool to dispatch heavy work
|
||||
/// such as candidate verification while performing event-driven work
|
||||
/// on a local event loop.
|
||||
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::sync::Arc;
|
||||
|
||||
use bft::{self, BftService};
|
||||
use client::{BlockchainEvents, ChainHead, BlockBody};
|
||||
use ed25519;
|
||||
use futures::prelude::*;
|
||||
use polkadot_api::LocalPolkadotApi;
|
||||
use polkadot_primitives::{Block, Header};
|
||||
use transaction_pool::TransactionPool;
|
||||
use extrinsic_store::Store as ExtrinsicStore;
|
||||
|
||||
use tokio::executor::current_thread::TaskExecutor as LocalThreadHandle;
|
||||
use tokio::runtime::TaskExecutor as ThreadPoolHandle;
|
||||
use tokio::runtime::current_thread::Runtime as LocalRuntime;
|
||||
use tokio::timer::{Delay, Interval};
|
||||
|
||||
use super::{Network, Collators, ProposerFactory};
|
||||
use error;
|
||||
|
||||
const TIMER_DELAY_MS: u64 = 5000;
|
||||
const TIMER_INTERVAL_MS: u64 = 500;
|
||||
|
||||
// spin up an instance of BFT agreement on the current thread's executor.
|
||||
// panics if there is no current thread executor.
|
||||
fn start_bft<F, C>(
|
||||
header: Header,
|
||||
bft_service: Arc<BftService<Block, F, C>>,
|
||||
) where
|
||||
F: bft::Environment<Block> + 'static,
|
||||
C: bft::BlockImport<Block> + bft::Authorities<Block> + 'static,
|
||||
F::Error: ::std::fmt::Debug,
|
||||
<F::Proposer as bft::Proposer<Block>>::Error: ::std::fmt::Display + Into<error::Error>,
|
||||
<F as bft::Environment<Block>>::Error: ::std::fmt::Display
|
||||
{
|
||||
const DELAY_UNTIL: Duration = Duration::from_millis(5000);
|
||||
|
||||
let mut handle = LocalThreadHandle::current();
|
||||
let work = Delay::new(Instant::now() + DELAY_UNTIL)
|
||||
.then(move |res| {
|
||||
if let Err(e) = res {
|
||||
warn!(target: "bft", "Failed to force delay of consensus: {:?}", e);
|
||||
}
|
||||
|
||||
match bft_service.build_upon(&header) {
|
||||
Ok(maybe_bft_work) => {
|
||||
if maybe_bft_work.is_some() {
|
||||
debug!(target: "bft", "Starting agreement. After forced delay for {:?}",
|
||||
DELAY_UNTIL);
|
||||
}
|
||||
|
||||
maybe_bft_work
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(target: "bft", "BFT agreement error: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(|_| ());
|
||||
|
||||
if let Err(e) = handle.spawn_local(Box::new(work)) {
|
||||
debug!(target: "bft", "Couldn't initialize BFT agreement: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// creates a task to prune redundant entries in availability store upon block finalization
|
||||
//
|
||||
// NOTE: this will need to be changed to finality notification rather than
|
||||
// block import notifications when the consensus switches to non-instant finality.
|
||||
fn prune_unneeded_availability<C>(client: Arc<C>, extrinsic_store: ExtrinsicStore)
|
||||
-> impl Future<Item=(),Error=()> + Send
|
||||
where C: Send + Sync + BlockchainEvents<Block> + BlockBody<Block> + 'static
|
||||
{
|
||||
use codec::{Encode, Decode};
|
||||
use polkadot_primitives::BlockId;
|
||||
use polkadot_runtime::CheckedBlock;
|
||||
|
||||
enum NotifyError {
|
||||
NoBody,
|
||||
BodyFetch(::client::error::Error),
|
||||
UnexpectedFormat,
|
||||
ExtrinsicsWrong,
|
||||
}
|
||||
|
||||
impl NotifyError {
|
||||
fn log(&self, hash: &::polkadot_primitives::Hash) {
|
||||
match *self {
|
||||
NotifyError::NoBody => warn!("No block body for imported block {:?}", hash),
|
||||
NotifyError::BodyFetch(ref err) => warn!("Failed to fetch block body for imported block {:?}: {:?}", hash, err),
|
||||
NotifyError::UnexpectedFormat => warn!("Consensus outdated: Block {:?} has unexpected body format", hash),
|
||||
NotifyError::ExtrinsicsWrong => warn!("Consensus outdated: Extrinsics cannot be decoded for {:?}", hash),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.import_notification_stream()
|
||||
.for_each(move |notification| {
|
||||
let hash = notification.hash;
|
||||
let parent_hash = notification.header.parent_hash;
|
||||
let checked_block = client.block_body(&BlockId::hash(hash))
|
||||
.map_err(NotifyError::BodyFetch)
|
||||
.and_then(|maybe_body| maybe_body.ok_or(NotifyError::NoBody))
|
||||
.map(|extrinsics| Block { header: notification.header, extrinsics })
|
||||
.map(|b: Block| ::polkadot_runtime::Block::decode(&mut b.encode().as_slice()))
|
||||
.and_then(|maybe_block| maybe_block.ok_or(NotifyError::UnexpectedFormat))
|
||||
.and_then(|block| CheckedBlock::new(block).map_err(|_| NotifyError::ExtrinsicsWrong));
|
||||
|
||||
match checked_block {
|
||||
Ok(block) => {
|
||||
let candidate_hashes = block.parachain_heads().iter().map(|c| c.hash()).collect();
|
||||
if let Err(e) = extrinsic_store.candidates_finalized(parent_hash, candidate_hashes) {
|
||||
warn!(target: "consensus", "Failed to prune unneeded available data: {:?}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => e.log(&hash)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Consensus service. Starts working when created.
|
||||
pub struct Service {
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
exit_signal: Option<::exit_future::Signal>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Create and start a new instance.
|
||||
pub fn new<A, C, N>(
|
||||
client: Arc<C>,
|
||||
api: Arc<A>,
|
||||
network: N,
|
||||
transaction_pool: Arc<TransactionPool<A>>,
|
||||
thread_pool: ThreadPoolHandle,
|
||||
parachain_empty_duration: Duration,
|
||||
key: ed25519::Pair,
|
||||
extrinsic_store: ExtrinsicStore,
|
||||
) -> Service
|
||||
where
|
||||
A: LocalPolkadotApi + Send + Sync + 'static,
|
||||
C: BlockchainEvents<Block> + ChainHead<Block> + BlockBody<Block>,
|
||||
C: bft::BlockImport<Block> + bft::Authorities<Block> + Send + Sync + 'static,
|
||||
N: Network + Collators + Send + 'static,
|
||||
N::TableRouter: Send + 'static,
|
||||
<N::Collation as IntoFuture>::Future: Send + 'static,
|
||||
{
|
||||
use parking_lot::RwLock;
|
||||
use super::OfflineTracker;
|
||||
|
||||
let (signal, exit) = ::exit_future::signal();
|
||||
let thread = thread::spawn(move || {
|
||||
let mut runtime = LocalRuntime::new().expect("Could not create local runtime");
|
||||
let key = Arc::new(key);
|
||||
|
||||
let factory = ProposerFactory {
|
||||
client: api.clone(),
|
||||
transaction_pool: transaction_pool.clone(),
|
||||
collators: network.clone(),
|
||||
network,
|
||||
parachain_empty_duration,
|
||||
handle: thread_pool.clone(),
|
||||
extrinsic_store: extrinsic_store.clone(),
|
||||
offline: Arc::new(RwLock::new(OfflineTracker::new())),
|
||||
};
|
||||
let bft_service = Arc::new(BftService::new(client.clone(), key, factory));
|
||||
|
||||
let notifications = {
|
||||
let client = client.clone();
|
||||
let bft_service = bft_service.clone();
|
||||
|
||||
client.import_notification_stream().for_each(move |notification| {
|
||||
if notification.is_new_best {
|
||||
start_bft(notification.header, bft_service.clone());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
|
||||
let interval = Interval::new(
|
||||
Instant::now() + Duration::from_millis(TIMER_DELAY_MS),
|
||||
Duration::from_millis(TIMER_INTERVAL_MS),
|
||||
);
|
||||
|
||||
let mut prev_best = match client.best_block_header() {
|
||||
Ok(header) => header.hash(),
|
||||
Err(e) => {
|
||||
warn!("Cant's start consensus service. Error reading best block header: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let timed = {
|
||||
let c = client.clone();
|
||||
let s = bft_service.clone();
|
||||
|
||||
interval.map_err(|e| debug!("Timer error: {:?}", e)).for_each(move |_| {
|
||||
if let Ok(best_block) = c.best_block_header() {
|
||||
let hash = best_block.hash();
|
||||
if hash == prev_best && s.live_agreement() != Some(hash) {
|
||||
debug!("Starting consensus round after a timeout");
|
||||
start_bft(best_block, s.clone());
|
||||
}
|
||||
prev_best = hash;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
|
||||
runtime.spawn(notifications);
|
||||
runtime.spawn(timed);
|
||||
|
||||
let prune_available = prune_unneeded_availability(client, extrinsic_store)
|
||||
.select(exit.clone())
|
||||
.then(|_| Ok(()));
|
||||
|
||||
// spawn this on the tokio executor since it's fine on a thread pool.
|
||||
thread_pool.spawn(prune_available);
|
||||
|
||||
if let Err(e) = runtime.block_on(exit) {
|
||||
debug!("BFT event loop error {:?}", e);
|
||||
}
|
||||
});
|
||||
Service {
|
||||
thread: Some(thread),
|
||||
exit_signal: Some(signal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Service {
|
||||
fn drop(&mut self) {
|
||||
if let Some(signal) = self.exit_signal.take() {
|
||||
signal.fire();
|
||||
}
|
||||
|
||||
if let Some(thread) = self.thread.take() {
|
||||
thread.join().expect("The service thread has panicked");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Implements a future which resolves when all of the candidates referenced are includable.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::sync::oneshot;
|
||||
|
||||
use polkadot_primitives::Hash;
|
||||
|
||||
/// Track includability of a set of candidates,
|
||||
pub(super) fn track<I: IntoIterator<Item=(Hash, bool)>>(candidates: I) -> (IncludabilitySender, Includable) {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let tracking: HashMap<_, _> = candidates.into_iter().collect();
|
||||
let includable_count = tracking.values().filter(|x| **x).count();
|
||||
|
||||
let mut sender = IncludabilitySender {
|
||||
tracking,
|
||||
includable_count,
|
||||
sender: Some(tx),
|
||||
};
|
||||
|
||||
sender.try_complete();
|
||||
|
||||
(
|
||||
sender,
|
||||
Includable(rx),
|
||||
)
|
||||
}
|
||||
|
||||
/// The sending end of the includability sender.
|
||||
pub(super) struct IncludabilitySender {
|
||||
tracking: HashMap<Hash, bool>,
|
||||
includable_count: usize,
|
||||
sender: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl IncludabilitySender {
|
||||
/// update the inner candidate. wakes up the task as necessary.
|
||||
/// returns `Err(Canceled)` if the other end has hung up.
|
||||
///
|
||||
/// returns `true` when this is completed and should be destroyed.
|
||||
pub fn update_candidate(&mut self, candidate: Hash, includable: bool) -> bool {
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
match self.tracking.entry(candidate) {
|
||||
Entry::Vacant(_) => {}
|
||||
Entry::Occupied(mut entry) => {
|
||||
let old = entry.insert(includable);
|
||||
if !old && includable {
|
||||
self.includable_count += 1;
|
||||
} else if old && !includable {
|
||||
self.includable_count -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.try_complete()
|
||||
}
|
||||
|
||||
/// whether the sender is completed.
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.sender.is_none()
|
||||
}
|
||||
|
||||
fn try_complete(&mut self) -> bool {
|
||||
if self.includable_count == self.tracking.len() {
|
||||
if let Some(sender) = self.sender.take() {
|
||||
let _ = sender.send(());
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Future that resolves when all the candidates within are includable.
|
||||
pub struct Includable(oneshot::Receiver<()>);
|
||||
|
||||
impl Future for Includable {
|
||||
type Item = ();
|
||||
type Error = oneshot::Canceled;
|
||||
|
||||
fn poll(&mut self) -> Poll<(), oneshot::Canceled> {
|
||||
self.0.poll()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let hash1 = [1; 32].into();
|
||||
let hash2 = [2; 32].into();
|
||||
let hash3 = [3; 32].into();
|
||||
|
||||
let (mut sender, recv) = track([
|
||||
(hash1, true),
|
||||
(hash2, true),
|
||||
(hash2, false), // overwrite should favor latter.
|
||||
(hash3, true),
|
||||
].iter().cloned());
|
||||
|
||||
assert!(!sender.is_complete());
|
||||
|
||||
// true -> false transition is possible and should be handled.
|
||||
sender.update_candidate(hash1, false);
|
||||
assert!(!sender.is_complete());
|
||||
|
||||
sender.update_candidate(hash2, true);
|
||||
assert!(!sender.is_complete());
|
||||
|
||||
sender.update_candidate(hash1, true);
|
||||
assert!(sender.is_complete());
|
||||
|
||||
recv.wait().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,702 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Parachain statement table meant to be shared with a message router
|
||||
//! and a consensus proposer.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use extrinsic_store::{Data, Store as ExtrinsicStore};
|
||||
use table::{self, Table, Context as TableContextTrait};
|
||||
use polkadot_primitives::{Hash, SessionKey};
|
||||
use polkadot_primitives::parachain::{Id as ParaId, BlockData, Collation, Extrinsic, CandidateReceipt};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use futures::{future, prelude::*};
|
||||
|
||||
use super::{GroupInfo, TableRouter};
|
||||
use self::includable::IncludabilitySender;
|
||||
|
||||
mod includable;
|
||||
|
||||
pub use self::includable::Includable;
|
||||
pub use table::{SignedStatement, Statement};
|
||||
pub use table::generic::Statement as GenericStatement;
|
||||
|
||||
struct TableContext {
|
||||
parent_hash: Hash,
|
||||
key: Arc<::ed25519::Pair>,
|
||||
groups: HashMap<ParaId, GroupInfo>,
|
||||
}
|
||||
|
||||
impl table::Context for TableContext {
|
||||
fn is_member_of(&self, authority: &SessionKey, group: &ParaId) -> bool {
|
||||
self.groups.get(group).map_or(false, |g| g.validity_guarantors.contains(authority))
|
||||
}
|
||||
|
||||
fn is_availability_guarantor_of(&self, authority: &SessionKey, group: &ParaId) -> bool {
|
||||
self.groups.get(group).map_or(false, |g| g.availability_guarantors.contains(authority))
|
||||
}
|
||||
|
||||
fn requisite_votes(&self, group: &ParaId) -> (usize, usize) {
|
||||
self.groups.get(group).map_or(
|
||||
(usize::max_value(), usize::max_value()),
|
||||
|g| (g.needed_validity, g.needed_availability),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TableContext {
|
||||
fn local_id(&self) -> SessionKey {
|
||||
self.key.public().into()
|
||||
}
|
||||
|
||||
fn sign_statement(&self, statement: table::Statement) -> table::SignedStatement {
|
||||
let signature = ::sign_table_statement(&statement, &self.key, &self.parent_hash).into();
|
||||
|
||||
table::SignedStatement {
|
||||
statement,
|
||||
signature,
|
||||
sender: self.local_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A shared table object.
|
||||
struct SharedTableInner {
|
||||
table: Table<TableContext>,
|
||||
proposed_digest: Option<Hash>,
|
||||
checked_validity: HashSet<Hash>,
|
||||
checked_availability: HashSet<Hash>,
|
||||
trackers: Vec<IncludabilitySender>,
|
||||
extrinsic_store: ExtrinsicStore,
|
||||
}
|
||||
|
||||
impl SharedTableInner {
|
||||
// Import a single statement. Provide a handle to a table router and a function
|
||||
// used to determine if a referenced candidate is valid.
|
||||
//
|
||||
// the statement producer, if any, will produce only statements concerning the same candidate
|
||||
// as the one just imported
|
||||
fn import_remote_statement<R: TableRouter>(
|
||||
&mut self,
|
||||
context: &TableContext,
|
||||
router: &R,
|
||||
statement: table::SignedStatement,
|
||||
) -> Option<StatementProducer<
|
||||
<R::FetchCandidate as IntoFuture>::Future,
|
||||
<R::FetchExtrinsic as IntoFuture>::Future,
|
||||
>> {
|
||||
let summary = match self.table.import_statement(context, statement) {
|
||||
Some(summary) => summary,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
self.update_trackers(&summary.candidate, context);
|
||||
|
||||
let local_id = context.local_id();
|
||||
|
||||
let is_validity_member = context.is_member_of(&local_id, &summary.group_id);
|
||||
let is_availability_member =
|
||||
context.is_availability_guarantor_of(&local_id, &summary.group_id);
|
||||
|
||||
let digest = &summary.candidate;
|
||||
|
||||
// TODO: consider a strategy based on the number of candidate votes as well.
|
||||
// only check validity if this wasn't locally proposed.
|
||||
let checking_validity = is_validity_member
|
||||
&& self.proposed_digest.as_ref().map_or(true, |d| d != digest)
|
||||
&& self.checked_validity.insert(digest.clone());
|
||||
|
||||
let checking_availability = is_availability_member
|
||||
&& self.checked_availability.insert(digest.clone());
|
||||
|
||||
let work = if checking_validity || checking_availability {
|
||||
match self.table.get_candidate(&digest) {
|
||||
None => None, // TODO: handle table inconsistency somehow?
|
||||
Some(candidate) => {
|
||||
let fetch_block_data =
|
||||
router.fetch_block_data(candidate).into_future().fuse();
|
||||
|
||||
let fetch_extrinsic = if checking_availability {
|
||||
Some(
|
||||
router.fetch_extrinsic_data(candidate).into_future().fuse()
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Some(Work {
|
||||
candidate_receipt: candidate.clone(),
|
||||
fetch_block_data,
|
||||
fetch_extrinsic,
|
||||
evaluate: checking_validity,
|
||||
ensure_available: checking_availability,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
work.map(|work| StatementProducer {
|
||||
produced_statements: Default::default(),
|
||||
extrinsic_store: self.extrinsic_store.clone(),
|
||||
relay_parent: context.parent_hash.clone(),
|
||||
work
|
||||
})
|
||||
}
|
||||
|
||||
fn update_trackers(&mut self, candidate: &Hash, context: &TableContext) {
|
||||
let includable = self.table.candidate_includable(candidate, context);
|
||||
for i in (0..self.trackers.len()).rev() {
|
||||
if self.trackers[i].update_candidate(candidate.clone(), includable) {
|
||||
self.trackers.swap_remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Produced statements about a specific candidate.
|
||||
/// Both may be `None`.
|
||||
#[derive(Default)]
|
||||
pub struct ProducedStatements {
|
||||
/// A statement about the validity of the candidate.
|
||||
pub validity: Option<table::Statement>,
|
||||
/// A statement about availability of data. If this is `Some`,
|
||||
/// then `block_data` and `extrinsic` should be `Some` as well.
|
||||
pub availability: Option<table::Statement>,
|
||||
/// Block data to ensure availability of.
|
||||
pub block_data: Option<BlockData>,
|
||||
/// Extrinsic data to ensure availability of.
|
||||
pub extrinsic: Option<Extrinsic>,
|
||||
}
|
||||
|
||||
/// Future that produces statements about a specific candidate.
|
||||
pub struct StatementProducer<D: Future, E: Future> {
|
||||
produced_statements: ProducedStatements,
|
||||
work: Work<D, E>,
|
||||
relay_parent: Hash,
|
||||
extrinsic_store: ExtrinsicStore,
|
||||
}
|
||||
|
||||
impl<D: Future, E: Future> StatementProducer<D, E> {
|
||||
/// Attach a function for verifying fetched collation to the statement producer.
|
||||
/// This will transform it into a future.
|
||||
///
|
||||
/// The collation-checking function should return `true` if known to be valid,
|
||||
/// `false` if known to be invalid, and `None` if unable to determine.
|
||||
pub fn prime<C: FnMut(Collation) -> Option<bool>>(self, check_candidate: C) -> PrimedStatementProducer<D, E, C> {
|
||||
PrimedStatementProducer {
|
||||
inner: self,
|
||||
check_candidate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Work<D: Future, E: Future> {
|
||||
candidate_receipt: CandidateReceipt,
|
||||
fetch_block_data: future::Fuse<D>,
|
||||
fetch_extrinsic: Option<future::Fuse<E>>,
|
||||
evaluate: bool,
|
||||
ensure_available: bool,
|
||||
}
|
||||
|
||||
/// Primed statement producer.
|
||||
pub struct PrimedStatementProducer<D: Future, E: Future, C> {
|
||||
inner: StatementProducer<D, E>,
|
||||
check_candidate: C,
|
||||
}
|
||||
|
||||
impl<D, E, C, Err> Future for PrimedStatementProducer<D, E, C>
|
||||
where
|
||||
D: Future<Item=BlockData,Error=Err>,
|
||||
E: Future<Item=Extrinsic,Error=Err>,
|
||||
C: FnMut(Collation) -> Option<bool>,
|
||||
Err: From<::std::io::Error>,
|
||||
{
|
||||
type Item = ProducedStatements;
|
||||
type Error = Err;
|
||||
|
||||
fn poll(&mut self) -> Poll<ProducedStatements, Err> {
|
||||
let work = &mut self.inner.work;
|
||||
let candidate = &work.candidate_receipt;
|
||||
let statements = &mut self.inner.produced_statements;
|
||||
|
||||
let mut candidate_hash = None;
|
||||
let mut candidate_hash = move ||
|
||||
candidate_hash.get_or_insert_with(|| candidate.hash()).clone();
|
||||
|
||||
if let Async::Ready(block_data) = work.fetch_block_data.poll()? {
|
||||
statements.block_data = Some(block_data.clone());
|
||||
if work.evaluate {
|
||||
let is_good = (self.check_candidate)(Collation {
|
||||
block_data,
|
||||
receipt: work.candidate_receipt.clone(),
|
||||
});
|
||||
|
||||
let hash = candidate_hash();
|
||||
|
||||
debug!(target: "consensus", "Making validity statement about candidate {}: is_good? {:?}", hash, is_good);
|
||||
statements.validity = match is_good {
|
||||
Some(true) => Some(GenericStatement::Valid(hash)),
|
||||
Some(false) => Some(GenericStatement::Invalid(hash)),
|
||||
None => None,
|
||||
};
|
||||
|
||||
work.evaluate = false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Async::Ready(Some(extrinsic)) = work.fetch_extrinsic.poll()? {
|
||||
if work.ensure_available {
|
||||
let hash = candidate_hash();
|
||||
debug!(target: "consensus", "Claiming candidate {} available.", hash);
|
||||
|
||||
statements.extrinsic = Some(extrinsic);
|
||||
statements.availability = Some(GenericStatement::Available(hash));
|
||||
|
||||
work.ensure_available = false;
|
||||
}
|
||||
}
|
||||
|
||||
let done = match (work.evaluate, work.ensure_available) {
|
||||
(false, false) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if done {
|
||||
// commit claimed-available data to disk before returning statements from the future.
|
||||
if let (&Some(ref block), extrinsic) = (&statements.block_data, &statements.extrinsic) {
|
||||
self.inner.extrinsic_store.make_available(Data {
|
||||
relay_parent: self.inner.relay_parent,
|
||||
parachain_id: work.candidate_receipt.parachain_index,
|
||||
candidate_hash: candidate_hash(),
|
||||
block_data: block.clone(),
|
||||
extrinsic: extrinsic.clone(),
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(Async::Ready(::std::mem::replace(statements, Default::default())))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A shared table object.
|
||||
pub struct SharedTable {
|
||||
context: Arc<TableContext>,
|
||||
inner: Arc<Mutex<SharedTableInner>>,
|
||||
}
|
||||
|
||||
impl Clone for SharedTable {
|
||||
fn clone(&self) -> Self {
|
||||
SharedTable {
|
||||
context: self.context.clone(),
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedTable {
|
||||
/// Create a new shared table.
|
||||
///
|
||||
/// Provide the key to sign with, and the parent hash of the relay chain
|
||||
/// block being built.
|
||||
pub fn new(
|
||||
groups: HashMap<ParaId, GroupInfo>,
|
||||
key: Arc<::ed25519::Pair>,
|
||||
parent_hash: Hash,
|
||||
extrinsic_store: ExtrinsicStore,
|
||||
) -> Self {
|
||||
SharedTable {
|
||||
context: Arc::new(TableContext { groups, key, parent_hash }),
|
||||
inner: Arc::new(Mutex::new(SharedTableInner {
|
||||
table: Table::default(),
|
||||
proposed_digest: None,
|
||||
checked_validity: HashSet::new(),
|
||||
checked_availability: HashSet::new(),
|
||||
trackers: Vec::new(),
|
||||
extrinsic_store,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the parent hash this table should hold statements localized to.
|
||||
pub fn consensus_parent_hash(&self) -> &Hash {
|
||||
&self.context.parent_hash
|
||||
}
|
||||
|
||||
/// Get the local validator session key.
|
||||
pub fn session_key(&self) -> SessionKey {
|
||||
self.context.local_id()
|
||||
}
|
||||
|
||||
/// Get group info.
|
||||
pub fn group_info(&self) -> &HashMap<ParaId, GroupInfo> {
|
||||
&self.context.groups
|
||||
}
|
||||
|
||||
/// Import a single statement with remote source, whose signature has already been checked.
|
||||
///
|
||||
/// The statement producer, if any, will produce only statements concerning the same candidate
|
||||
/// as the one just imported
|
||||
pub fn import_remote_statement<R: TableRouter>(
|
||||
&self,
|
||||
router: &R,
|
||||
statement: table::SignedStatement,
|
||||
) -> Option<StatementProducer<
|
||||
<R::FetchCandidate as IntoFuture>::Future,
|
||||
<R::FetchExtrinsic as IntoFuture>::Future,
|
||||
>> {
|
||||
self.inner.lock().import_remote_statement(&*self.context, router, statement)
|
||||
}
|
||||
|
||||
/// Import many statements at once.
|
||||
///
|
||||
/// Provide an iterator yielding remote, pre-checked statements.
|
||||
///
|
||||
/// The statement producer, if any, will produce only statements concerning the same candidate
|
||||
/// as the one just imported
|
||||
pub fn import_remote_statements<R, I, U>(&self, router: &R, iterable: I) -> U
|
||||
where
|
||||
R: TableRouter,
|
||||
I: IntoIterator<Item=table::SignedStatement>,
|
||||
U: ::std::iter::FromIterator<Option<StatementProducer<
|
||||
<R::FetchCandidate as IntoFuture>::Future,
|
||||
<R::FetchExtrinsic as IntoFuture>::Future,
|
||||
>>>,
|
||||
{
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
iterable.into_iter().map(move |statement| {
|
||||
inner.import_remote_statement(&*self.context, router, statement)
|
||||
}).collect()
|
||||
}
|
||||
|
||||
/// Sign and import a local statement.
|
||||
///
|
||||
/// For candidate statements, this may also produce a second signed statement
|
||||
/// concerning the availability of the candidate data.
|
||||
pub fn sign_and_import(&self, statement: table::Statement)
|
||||
-> (SignedStatement, Option<SignedStatement>)
|
||||
{
|
||||
let (proposed_digest, availability) = match statement {
|
||||
GenericStatement::Candidate(ref c) => {
|
||||
let mut availability = None;
|
||||
let hash = c.hash();
|
||||
|
||||
// TODO: actually store the data in an availability store of some kind.
|
||||
if self.context.is_availability_guarantor_of(&self.context.local_id(), &c.parachain_index) {
|
||||
availability = Some(self.context.sign_statement(GenericStatement::Available(hash)));
|
||||
}
|
||||
|
||||
(Some(hash), availability)
|
||||
}
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
let signed_statement = self.context.sign_statement(statement);
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
if proposed_digest.is_some() {
|
||||
inner.proposed_digest = proposed_digest;
|
||||
}
|
||||
|
||||
inner.table.import_statement(&*self.context, signed_statement.clone());
|
||||
|
||||
// ensure the availability statement is imported after the candidate.
|
||||
if let Some(a) = availability.clone() {
|
||||
inner.table.import_statement(&*self.context, a);
|
||||
}
|
||||
|
||||
(signed_statement, availability)
|
||||
}
|
||||
|
||||
/// Execute a closure using a specific candidate.
|
||||
///
|
||||
/// Deadlocks if called recursively.
|
||||
pub fn with_candidate<F, U>(&self, digest: &Hash, f: F) -> U
|
||||
where F: FnOnce(Option<&CandidateReceipt>) -> U
|
||||
{
|
||||
let inner = self.inner.lock();
|
||||
f(inner.table.get_candidate(digest))
|
||||
}
|
||||
|
||||
/// Execute a closure using the current proposed set.
|
||||
///
|
||||
/// Deadlocks if called recursively.
|
||||
pub fn with_proposal<F, U>(&self, f: F) -> U
|
||||
where F: FnOnce(Vec<&CandidateReceipt>) -> U
|
||||
{
|
||||
let inner = self.inner.lock();
|
||||
f(inner.table.proposed_candidates(&*self.context))
|
||||
}
|
||||
|
||||
/// Get the number of parachains which have available candidates.
|
||||
pub fn includable_count(&self) -> usize {
|
||||
self.inner.lock().table.includable_count()
|
||||
}
|
||||
|
||||
/// Get all witnessed misbehavior.
|
||||
pub fn get_misbehavior(&self) -> HashMap<SessionKey, table::Misbehavior> {
|
||||
self.inner.lock().table.get_misbehavior().clone()
|
||||
}
|
||||
|
||||
/// Track includability of a given set of candidate hashes.
|
||||
pub fn track_includability<I>(&self, iterable: I) -> Includable
|
||||
where I: IntoIterator<Item=Hash>
|
||||
{
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
let (tx, rx) = includable::track(iterable.into_iter().map(|x| {
|
||||
let includable = inner.table.candidate_includable(&x, &*self.context);
|
||||
(x, includable)
|
||||
}));
|
||||
|
||||
if !tx.is_complete() {
|
||||
inner.trackers.push(tx);
|
||||
}
|
||||
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use substrate_keyring::Keyring;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DummyRouter;
|
||||
impl TableRouter for DummyRouter {
|
||||
type Error = ::std::io::Error;
|
||||
type FetchCandidate = ::futures::future::Empty<BlockData,Self::Error>;
|
||||
type FetchExtrinsic = ::futures::future::Empty<Extrinsic,Self::Error>;
|
||||
|
||||
fn local_candidate(&self, _candidate: CandidateReceipt, _block_data: BlockData, _extrinsic: Extrinsic) {
|
||||
|
||||
}
|
||||
fn fetch_block_data(&self, _candidate: &CandidateReceipt) -> Self::FetchCandidate {
|
||||
::futures::future::empty()
|
||||
}
|
||||
fn fetch_extrinsic_data(&self, _candidate: &CandidateReceipt) -> Self::FetchExtrinsic {
|
||||
::futures::future::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statement_triggers_fetch_and_evaluate() {
|
||||
let mut groups = HashMap::new();
|
||||
|
||||
let para_id = ParaId::from(1);
|
||||
let local_id = Keyring::Alice.to_raw_public().into();
|
||||
let local_key = Arc::new(Keyring::Alice.pair());
|
||||
|
||||
let validity_other = Keyring::Bob.to_raw_public().into();
|
||||
let validity_other_key = Keyring::Bob.pair();
|
||||
let parent_hash = Default::default();
|
||||
|
||||
groups.insert(para_id, GroupInfo {
|
||||
validity_guarantors: [local_id, validity_other].iter().cloned().collect(),
|
||||
availability_guarantors: Default::default(),
|
||||
needed_validity: 2,
|
||||
needed_availability: 0,
|
||||
});
|
||||
|
||||
let shared_table = SharedTable::new(
|
||||
groups,
|
||||
local_key.clone(),
|
||||
parent_hash,
|
||||
ExtrinsicStore::new_in_memory(),
|
||||
);
|
||||
|
||||
let candidate = CandidateReceipt {
|
||||
parachain_index: para_id,
|
||||
collator: [1; 32].into(),
|
||||
signature: Default::default(),
|
||||
head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]),
|
||||
balance_uploads: Vec::new(),
|
||||
egress_queue_roots: Vec::new(),
|
||||
fees: 1_000_000,
|
||||
block_data_hash: [2; 32].into(),
|
||||
};
|
||||
|
||||
let candidate_statement = GenericStatement::Candidate(candidate);
|
||||
|
||||
let signature = ::sign_table_statement(&candidate_statement, &validity_other_key, &parent_hash);
|
||||
let signed_statement = ::table::generic::SignedStatement {
|
||||
statement: candidate_statement,
|
||||
signature: signature.into(),
|
||||
sender: validity_other,
|
||||
};
|
||||
|
||||
let producer = shared_table.import_remote_statement(
|
||||
&DummyRouter,
|
||||
signed_statement,
|
||||
).expect("candidate and local validity group are same");
|
||||
|
||||
assert!(producer.work.evaluate, "should evaluate validity");
|
||||
assert!(producer.work.fetch_extrinsic.is_none(), "should not fetch extrinsic");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statement_triggers_fetch_and_availability() {
|
||||
let mut groups = HashMap::new();
|
||||
|
||||
let para_id = ParaId::from(1);
|
||||
let local_id = Keyring::Alice.to_raw_public().into();
|
||||
let local_key = Arc::new(Keyring::Alice.pair());
|
||||
|
||||
let validity_other = Keyring::Bob.to_raw_public().into();
|
||||
let validity_other_key = Keyring::Bob.pair();
|
||||
let parent_hash = Default::default();
|
||||
|
||||
groups.insert(para_id, GroupInfo {
|
||||
validity_guarantors: [validity_other].iter().cloned().collect(),
|
||||
availability_guarantors: [local_id].iter().cloned().collect(),
|
||||
needed_validity: 1,
|
||||
needed_availability: 1,
|
||||
});
|
||||
|
||||
let shared_table = SharedTable::new(
|
||||
groups,
|
||||
local_key.clone(),
|
||||
parent_hash,
|
||||
ExtrinsicStore::new_in_memory(),
|
||||
);
|
||||
|
||||
let candidate = CandidateReceipt {
|
||||
parachain_index: para_id,
|
||||
collator: [1; 32].into(),
|
||||
signature: Default::default(),
|
||||
head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]),
|
||||
balance_uploads: Vec::new(),
|
||||
egress_queue_roots: Vec::new(),
|
||||
fees: 1_000_000,
|
||||
block_data_hash: [2; 32].into(),
|
||||
};
|
||||
|
||||
let candidate_statement = GenericStatement::Candidate(candidate);
|
||||
|
||||
let signature = ::sign_table_statement(&candidate_statement, &validity_other_key, &parent_hash);
|
||||
let signed_statement = ::table::generic::SignedStatement {
|
||||
statement: candidate_statement,
|
||||
signature: signature.into(),
|
||||
sender: validity_other,
|
||||
};
|
||||
|
||||
let producer = shared_table.import_remote_statement(
|
||||
&DummyRouter,
|
||||
signed_statement,
|
||||
).expect("should produce work");
|
||||
|
||||
assert!(producer.work.fetch_extrinsic.is_some(), "should fetch extrinsic when guaranteeing availability");
|
||||
assert!(!producer.work.evaluate, "should not evaluate validity");
|
||||
assert!(producer.work.ensure_available);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_makes_block_data_available() {
|
||||
let store = ExtrinsicStore::new_in_memory();
|
||||
let relay_parent = [0; 32].into();
|
||||
let para_id = 5.into();
|
||||
let block_data = BlockData(vec![1, 2, 3]);
|
||||
|
||||
let candidate = CandidateReceipt {
|
||||
parachain_index: para_id,
|
||||
collator: [1; 32].into(),
|
||||
signature: Default::default(),
|
||||
head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]),
|
||||
balance_uploads: Vec::new(),
|
||||
egress_queue_roots: Vec::new(),
|
||||
fees: 1_000_000,
|
||||
block_data_hash: [2; 32].into(),
|
||||
};
|
||||
|
||||
let hash = candidate.hash();
|
||||
|
||||
let block_data_res: ::std::io::Result<_> = Ok(block_data.clone());
|
||||
let producer: StatementProducer<_, future::Empty<_, _>> = StatementProducer {
|
||||
produced_statements: Default::default(),
|
||||
work: Work {
|
||||
candidate_receipt: candidate,
|
||||
fetch_block_data: block_data_res.into_future().fuse(),
|
||||
fetch_extrinsic: None,
|
||||
evaluate: true,
|
||||
ensure_available: false,
|
||||
},
|
||||
relay_parent,
|
||||
extrinsic_store: store.clone(),
|
||||
};
|
||||
|
||||
let produced = producer.prime(|_| Some(true)).wait().unwrap();
|
||||
|
||||
assert_eq!(produced.block_data.as_ref(), Some(&block_data));
|
||||
assert!(produced.validity.is_some());
|
||||
assert!(produced.availability.is_none());
|
||||
|
||||
assert_eq!(store.block_data(relay_parent, hash).unwrap(), block_data);
|
||||
assert!(store.extrinsic(relay_parent, hash).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_availability() {
|
||||
let store = ExtrinsicStore::new_in_memory();
|
||||
let relay_parent = [0; 32].into();
|
||||
let para_id = 5.into();
|
||||
let block_data = BlockData(vec![1, 2, 3]);
|
||||
|
||||
let candidate = CandidateReceipt {
|
||||
parachain_index: para_id,
|
||||
collator: [1; 32].into(),
|
||||
signature: Default::default(),
|
||||
head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]),
|
||||
balance_uploads: Vec::new(),
|
||||
egress_queue_roots: Vec::new(),
|
||||
fees: 1_000_000,
|
||||
block_data_hash: [2; 32].into(),
|
||||
};
|
||||
|
||||
let hash = candidate.hash();
|
||||
|
||||
let block_data_res: ::std::io::Result<_> = Ok(block_data.clone());
|
||||
let extrinsic_res: ::std::io::Result<_> = Ok(Extrinsic);
|
||||
let producer = StatementProducer {
|
||||
produced_statements: Default::default(),
|
||||
work: Work {
|
||||
candidate_receipt: candidate,
|
||||
fetch_block_data: block_data_res.into_future().fuse(),
|
||||
fetch_extrinsic: Some(extrinsic_res.into_future().fuse()),
|
||||
evaluate: false,
|
||||
ensure_available: true,
|
||||
},
|
||||
relay_parent,
|
||||
extrinsic_store: store.clone(),
|
||||
};
|
||||
|
||||
let produced = producer.prime(|_| Some(true)).wait().unwrap();
|
||||
|
||||
assert_eq!(produced.block_data.as_ref(), Some(&block_data));
|
||||
assert!(produced.validity.is_none());
|
||||
assert!(produced.availability.is_some());
|
||||
|
||||
assert_eq!(store.block_data(relay_parent, hash).unwrap(), block_data);
|
||||
assert!(store.extrinsic(relay_parent, hash).is_some());
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-executor"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Polkadot node implementation in Rust."
|
||||
|
||||
[dependencies]
|
||||
substrate-executor = { path = "../../substrate/executor" }
|
||||
polkadot-runtime = { path = "../runtime" }
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Executor
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,23 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
//! A `CodeExecutor` specialisation which uses natively compiled runtime when the wasm to be
|
||||
//! executed is equivalent to the natively compiled code.
|
||||
|
||||
extern crate polkadot_runtime;
|
||||
#[macro_use] extern crate substrate_executor;
|
||||
|
||||
native_executor_instance!(pub Executor, polkadot_runtime::api::dispatch, polkadot_runtime::VERSION, include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm"));
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-network"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Polkadot-specific networking protocol"
|
||||
|
||||
[dependencies]
|
||||
parking_lot = "0.4"
|
||||
polkadot-api = { path = "../api" }
|
||||
polkadot-availability-store = { path = "../availability-store" }
|
||||
polkadot-consensus = { path = "../consensus" }
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
substrate-bft = { path = "../../substrate/bft" }
|
||||
substrate-codec = { path = "../../substrate/codec" }
|
||||
substrate-codec-derive = { path = "../../substrate/codec/derive" }
|
||||
substrate-network = { path = "../../substrate/network" }
|
||||
substrate-primitives = { path = "../../substrate/primitives" }
|
||||
ed25519 = { path = "../../substrate/ed25519" }
|
||||
futures = "0.1"
|
||||
tokio = "0.1.7"
|
||||
log = "0.4"
|
||||
rhododendron = "0.3"
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Network
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,320 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Bridge between the network and consensus service for getting collations to it.
|
||||
|
||||
use polkadot_primitives::{AccountId, Hash};
|
||||
use polkadot_primitives::parachain::{Id as ParaId, Collation};
|
||||
|
||||
use futures::sync::oneshot;
|
||||
|
||||
use std::collections::hash_map::{HashMap, Entry};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const COLLATION_LIFETIME: Duration = Duration::from_secs(60 * 5);
|
||||
|
||||
/// The role of the collator. Whether they're the primary or backup for this parachain.
|
||||
#[derive(PartialEq, Debug, Clone, Copy, Encode, Decode)]
|
||||
pub enum Role {
|
||||
/// Primary collators should send collations whenever it's time.
|
||||
Primary = 0,
|
||||
/// Backup collators should not.
|
||||
Backup = 1,
|
||||
}
|
||||
|
||||
/// A maintenance action for the collator set.
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Action {
|
||||
/// Disconnect the given collator.
|
||||
Disconnect(AccountId),
|
||||
/// Give the collator a new role.
|
||||
NewRole(AccountId, Role),
|
||||
}
|
||||
|
||||
struct CollationSlot {
|
||||
live_at: Instant,
|
||||
entries: SlotEntries,
|
||||
}
|
||||
|
||||
impl CollationSlot {
|
||||
fn blank_now() -> Self {
|
||||
CollationSlot {
|
||||
live_at: Instant::now(),
|
||||
entries: SlotEntries::Blank,
|
||||
}
|
||||
}
|
||||
|
||||
fn stay_alive(&self, now: Instant) -> bool {
|
||||
self.live_at + COLLATION_LIFETIME > now
|
||||
}
|
||||
}
|
||||
|
||||
enum SlotEntries {
|
||||
Blank,
|
||||
// not queried yet
|
||||
Pending(Vec<Collation>),
|
||||
// waiting for next to arrive.
|
||||
Awaiting(Vec<oneshot::Sender<Collation>>),
|
||||
}
|
||||
|
||||
impl SlotEntries {
|
||||
fn received_collation(&mut self, collation: Collation) {
|
||||
*self = match ::std::mem::replace(self, SlotEntries::Blank) {
|
||||
SlotEntries::Blank => SlotEntries::Pending(vec![collation]),
|
||||
SlotEntries::Pending(mut cs) => {
|
||||
cs.push(collation);
|
||||
SlotEntries::Pending(cs)
|
||||
}
|
||||
SlotEntries::Awaiting(senders) => {
|
||||
for sender in senders {
|
||||
let _ = sender.send(collation.clone());
|
||||
}
|
||||
|
||||
SlotEntries::Blank
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn await_with(&mut self, sender: oneshot::Sender<Collation>) {
|
||||
*self = match ::std::mem::replace(self, SlotEntries::Blank) {
|
||||
SlotEntries::Blank => SlotEntries::Awaiting(vec![sender]),
|
||||
SlotEntries::Awaiting(mut senders) => {
|
||||
senders.push(sender);
|
||||
SlotEntries::Awaiting(senders)
|
||||
}
|
||||
SlotEntries::Pending(mut cs) => {
|
||||
let next_collation = cs.pop().expect("empty variant is always `Blank`; qed");
|
||||
let _ = sender.send(next_collation);
|
||||
|
||||
if cs.is_empty() {
|
||||
SlotEntries::Blank
|
||||
} else {
|
||||
SlotEntries::Pending(cs)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
struct ParachainCollators {
|
||||
primary: AccountId,
|
||||
backup: Vec<AccountId>,
|
||||
}
|
||||
|
||||
/// Manages connected collators and role assignments from the perspective of a validator.
|
||||
pub struct CollatorPool {
|
||||
collators: HashMap<AccountId, ParaId>,
|
||||
parachain_collators: HashMap<ParaId, ParachainCollators>,
|
||||
collations: HashMap<(Hash, ParaId), CollationSlot>,
|
||||
}
|
||||
|
||||
impl CollatorPool {
|
||||
/// Create a new `CollatorPool` object.
|
||||
pub fn new() -> Self {
|
||||
CollatorPool {
|
||||
collators: HashMap::new(),
|
||||
parachain_collators: HashMap::new(),
|
||||
collations: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Call when a new collator is authenticated. Returns the role.
|
||||
pub fn on_new_collator(&mut self, account_id: AccountId, para_id: ParaId) -> Role {
|
||||
self.collators.insert(account_id.clone(), para_id);
|
||||
match self.parachain_collators.entry(para_id) {
|
||||
Entry::Vacant(vacant) => {
|
||||
vacant.insert(ParachainCollators {
|
||||
primary: account_id,
|
||||
backup: Vec::new(),
|
||||
});
|
||||
|
||||
Role::Primary
|
||||
},
|
||||
Entry::Occupied(mut occupied) => {
|
||||
occupied.get_mut().backup.push(account_id);
|
||||
|
||||
Role::Backup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when a collator disconnects. If it was the primary, returns a new primary for that
|
||||
/// parachain.
|
||||
pub fn on_disconnect(&mut self, account_id: AccountId) -> Option<AccountId> {
|
||||
self.collators.remove(&account_id).and_then(|para_id| match self.parachain_collators.entry(para_id) {
|
||||
Entry::Vacant(_) => None,
|
||||
Entry::Occupied(mut occ) => {
|
||||
if occ.get().primary == account_id {
|
||||
if occ.get().backup.is_empty() {
|
||||
occ.remove();
|
||||
None
|
||||
} else {
|
||||
let mut collators = occ.get_mut();
|
||||
collators.primary = collators.backup.pop().expect("backup non-empty; qed");
|
||||
Some(collators.primary)
|
||||
}
|
||||
} else {
|
||||
let pos = occ.get().backup.iter().position(|a| a == &account_id)
|
||||
.expect("registered collator always present in backup if not primary; qed");
|
||||
|
||||
occ.get_mut().backup.remove(pos);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Called when a collation is received.
|
||||
/// The collator should be registered for the parachain of the collation as a precondition of this function.
|
||||
/// The collation should have been checked for integrity of signature before passing to this function.
|
||||
pub fn on_collation(&mut self, account_id: AccountId, relay_parent: Hash, collation: Collation) {
|
||||
if let Some(para_id) = self.collators.get(&account_id) {
|
||||
debug_assert_eq!(para_id, &collation.receipt.parachain_index);
|
||||
|
||||
// TODO: punish if not primary?
|
||||
|
||||
self.collations.entry((relay_parent, para_id.clone()))
|
||||
.or_insert_with(CollationSlot::blank_now)
|
||||
.entries
|
||||
.received_collation(collation);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for a collation from a parachain.
|
||||
pub fn await_collation(&mut self, relay_parent: Hash, para_id: ParaId, sender: oneshot::Sender<Collation>) {
|
||||
self.collations.entry((relay_parent, para_id))
|
||||
.or_insert_with(CollationSlot::blank_now)
|
||||
.entries
|
||||
.await_with(sender);
|
||||
}
|
||||
|
||||
/// Call periodically to perform collator set maintenance.
|
||||
/// Returns a set of actions to perform on the network level.
|
||||
pub fn maintain_peers(&mut self) -> Vec<Action> {
|
||||
// TODO: rearrange periodically to new primary, evaluate based on latency etc.
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// called when a block with given hash has been imported.
|
||||
pub fn collect_garbage(&mut self, chain_head: Option<&Hash>) {
|
||||
let now = Instant::now();
|
||||
self.collations.retain(|&(ref h, _), slot| chain_head != Some(h) && slot.stay_alive(now));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use polkadot_primitives::parachain::{CandidateReceipt, BlockData, HeadData};
|
||||
use substrate_primitives::H512;
|
||||
use futures::Future;
|
||||
|
||||
#[test]
|
||||
fn disconnect_primary_gives_new_primary() {
|
||||
let mut pool = CollatorPool::new();
|
||||
let para_id: ParaId = 5.into();
|
||||
let bad_primary = [0; 32].into();
|
||||
let good_backup = [1; 32].into();
|
||||
|
||||
assert_eq!(pool.on_new_collator(bad_primary, para_id.clone()), Role::Primary);
|
||||
assert_eq!(pool.on_new_collator(good_backup, para_id.clone()), Role::Backup);
|
||||
assert_eq!(pool.on_disconnect(bad_primary), Some(good_backup));
|
||||
assert_eq!(pool.on_disconnect(good_backup), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnect_backup_removes_from_pool() {
|
||||
let mut pool = CollatorPool::new();
|
||||
let para_id: ParaId = 5.into();
|
||||
let primary = [0; 32].into();
|
||||
let backup = [1; 32].into();
|
||||
|
||||
assert_eq!(pool.on_new_collator(primary, para_id.clone()), Role::Primary);
|
||||
assert_eq!(pool.on_new_collator(backup, para_id.clone()), Role::Backup);
|
||||
assert_eq!(pool.on_disconnect(backup), None);
|
||||
assert!(pool.parachain_collators.get(¶_id).unwrap().backup.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn await_before_collation() {
|
||||
let mut pool = CollatorPool::new();
|
||||
let para_id: ParaId = 5.into();
|
||||
let primary = [0; 32].into();
|
||||
let relay_parent = [1; 32].into();
|
||||
|
||||
assert_eq!(pool.on_new_collator(primary, para_id.clone()), Role::Primary);
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
pool.await_collation(relay_parent, para_id, tx1);
|
||||
pool.await_collation(relay_parent, para_id, tx2);
|
||||
pool.on_collation(primary, relay_parent, Collation {
|
||||
receipt: CandidateReceipt {
|
||||
parachain_index: para_id,
|
||||
collator: primary.into(),
|
||||
signature: H512::from([2; 64]).into(),
|
||||
head_data: HeadData(vec![1, 2, 3]),
|
||||
balance_uploads: vec![],
|
||||
egress_queue_roots: vec![],
|
||||
fees: 0,
|
||||
block_data_hash: [3; 32].into(),
|
||||
},
|
||||
block_data: BlockData(vec![4, 5, 6]),
|
||||
});
|
||||
|
||||
rx1.wait().unwrap();
|
||||
rx2.wait().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collate_before_await() {
|
||||
let mut pool = CollatorPool::new();
|
||||
let para_id: ParaId = 5.into();
|
||||
let primary = [0; 32].into();
|
||||
let relay_parent = [1; 32].into();
|
||||
|
||||
assert_eq!(pool.on_new_collator(primary, para_id.clone()), Role::Primary);
|
||||
|
||||
pool.on_collation(primary, relay_parent, Collation {
|
||||
receipt: CandidateReceipt {
|
||||
parachain_index: para_id,
|
||||
collator: primary.into(),
|
||||
signature: H512::from([2; 64]).into(),
|
||||
head_data: HeadData(vec![1, 2, 3]),
|
||||
balance_uploads: vec![],
|
||||
egress_queue_roots: vec![],
|
||||
fees: 0,
|
||||
block_data_hash: [3; 32].into(),
|
||||
},
|
||||
block_data: BlockData(vec![4, 5, 6]),
|
||||
});
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
pool.await_collation(relay_parent, para_id, tx);
|
||||
rx.wait().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slot_stay_alive() {
|
||||
let slot = CollationSlot::blank_now();
|
||||
let now = slot.live_at;
|
||||
|
||||
assert!(slot.stay_alive(now));
|
||||
assert!(slot.stay_alive(now + Duration::from_secs(10)));
|
||||
assert!(!slot.stay_alive(now + COLLATION_LIFETIME));
|
||||
assert!(!slot.stay_alive(now + COLLATION_LIFETIME + Duration::from_secs(10)));
|
||||
}
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The "consensus" networking code built on top of the base network service.
|
||||
//! This fulfills the `polkadot_consensus::Network` trait, providing a hook to be called
|
||||
//! each time consensus begins on a new chain head.
|
||||
|
||||
use bft;
|
||||
use ed25519;
|
||||
use substrate_network::{self as net, generic_message as msg};
|
||||
use substrate_network::consensus_gossip::ConsensusMessage;
|
||||
use polkadot_api::{PolkadotApi, LocalPolkadotApi};
|
||||
use polkadot_consensus::{Network, SharedTable, Collators};
|
||||
use polkadot_primitives::{AccountId, Block, Hash, SessionKey};
|
||||
use polkadot_primitives::parachain::{Id as ParaId, Collation};
|
||||
use codec::Decode;
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::{Message, NetworkService, Knowledge, CurrentConsensus};
|
||||
use router::Router;
|
||||
|
||||
/// Sink for output BFT messages.
|
||||
pub struct BftSink<E> {
|
||||
network: Arc<NetworkService>,
|
||||
parent_hash: Hash,
|
||||
_marker: ::std::marker::PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E> Sink for BftSink<E> {
|
||||
type SinkItem = bft::Communication<Block>;
|
||||
// TODO: replace this with the ! type when that's stabilized
|
||||
type SinkError = E;
|
||||
|
||||
fn start_send(&mut self, message: bft::Communication<Block>) -> ::futures::StartSend<bft::Communication<Block>, E> {
|
||||
let network_message = net::LocalizedBftMessage {
|
||||
message: match message {
|
||||
::rhododendron::Communication::Consensus(c) => msg::BftMessage::Consensus(match c {
|
||||
::rhododendron::LocalizedMessage::Propose(proposal) => msg::SignedConsensusMessage::Propose(msg::SignedConsensusProposal {
|
||||
round_number: proposal.round_number as u32,
|
||||
proposal: proposal.proposal,
|
||||
digest: proposal.digest,
|
||||
sender: proposal.sender,
|
||||
digest_signature: proposal.digest_signature.signature,
|
||||
full_signature: proposal.full_signature.signature,
|
||||
}),
|
||||
::rhododendron::LocalizedMessage::Vote(vote) => msg::SignedConsensusMessage::Vote(msg::SignedConsensusVote {
|
||||
sender: vote.sender,
|
||||
signature: vote.signature.signature,
|
||||
vote: match vote.vote {
|
||||
::rhododendron::Vote::Prepare(r, h) => msg::ConsensusVote::Prepare(r as u32, h),
|
||||
::rhododendron::Vote::Commit(r, h) => msg::ConsensusVote::Commit(r as u32, h),
|
||||
::rhododendron::Vote::AdvanceRound(r) => msg::ConsensusVote::AdvanceRound(r as u32),
|
||||
}
|
||||
}),
|
||||
}),
|
||||
::rhododendron::Communication::Auxiliary(justification) => {
|
||||
let unchecked: bft::UncheckedJustification<_> = justification.uncheck().into();
|
||||
msg::BftMessage::Auxiliary(unchecked.into())
|
||||
}
|
||||
},
|
||||
parent_hash: self.parent_hash,
|
||||
};
|
||||
self.network.with_spec(
|
||||
move |spec, ctx| spec.consensus_gossip.multicast_bft_message(ctx, network_message)
|
||||
);
|
||||
Ok(::futures::AsyncSink::Ready)
|
||||
}
|
||||
|
||||
fn poll_complete(&mut self) -> ::futures::Poll<(), E> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
}
|
||||
|
||||
// check signature and authority validity of message.
|
||||
fn process_bft_message(msg: msg::LocalizedBftMessage<Block, Hash>, local_id: &SessionKey, authorities: &[SessionKey]) -> Result<Option<bft::Communication<Block>>, bft::Error> {
|
||||
Ok(Some(match msg.message {
|
||||
msg::BftMessage::Consensus(c) => ::rhododendron::Communication::Consensus(match c {
|
||||
msg::SignedConsensusMessage::Propose(proposal) => ::rhododendron::LocalizedMessage::Propose({
|
||||
if &proposal.sender == local_id { return Ok(None) }
|
||||
let proposal = ::rhododendron::LocalizedProposal {
|
||||
round_number: proposal.round_number as usize,
|
||||
proposal: proposal.proposal,
|
||||
digest: proposal.digest,
|
||||
sender: proposal.sender,
|
||||
digest_signature: ed25519::LocalizedSignature {
|
||||
signature: proposal.digest_signature,
|
||||
signer: ed25519::Public(proposal.sender.into()),
|
||||
},
|
||||
full_signature: ed25519::LocalizedSignature {
|
||||
signature: proposal.full_signature,
|
||||
signer: ed25519::Public(proposal.sender.into()),
|
||||
}
|
||||
};
|
||||
bft::check_proposal(authorities, &msg.parent_hash, &proposal)?;
|
||||
|
||||
trace!(target: "bft", "importing proposal message for round {} from {}", proposal.round_number, Hash::from(proposal.sender.0));
|
||||
proposal
|
||||
}),
|
||||
msg::SignedConsensusMessage::Vote(vote) => ::rhododendron::LocalizedMessage::Vote({
|
||||
if &vote.sender == local_id { return Ok(None) }
|
||||
let vote = ::rhododendron::LocalizedVote {
|
||||
sender: vote.sender,
|
||||
signature: ed25519::LocalizedSignature {
|
||||
signature: vote.signature,
|
||||
signer: ed25519::Public(vote.sender.0),
|
||||
},
|
||||
vote: match vote.vote {
|
||||
msg::ConsensusVote::Prepare(r, h) => ::rhododendron::Vote::Prepare(r as usize, h),
|
||||
msg::ConsensusVote::Commit(r, h) => ::rhododendron::Vote::Commit(r as usize, h),
|
||||
msg::ConsensusVote::AdvanceRound(r) => ::rhododendron::Vote::AdvanceRound(r as usize),
|
||||
}
|
||||
};
|
||||
bft::check_vote::<Block>(authorities, &msg.parent_hash, &vote)?;
|
||||
|
||||
trace!(target: "bft", "importing vote {:?} from {}", vote.vote, Hash::from(vote.sender.0));
|
||||
vote
|
||||
}),
|
||||
}),
|
||||
msg::BftMessage::Auxiliary(a) => {
|
||||
let justification = bft::UncheckedJustification::from(a);
|
||||
// TODO: get proper error
|
||||
let justification: Result<_, bft::Error> = bft::check_prepare_justification::<Block>(authorities, msg.parent_hash, justification)
|
||||
.map_err(|_| bft::ErrorKind::InvalidJustification.into());
|
||||
::rhododendron::Communication::Auxiliary(justification?)
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
// task that processes all gossipped consensus messages,
|
||||
// checking signatures
|
||||
struct MessageProcessTask<P: PolkadotApi> {
|
||||
inner_stream: mpsc::UnboundedReceiver<ConsensusMessage<Block>>,
|
||||
bft_messages: mpsc::UnboundedSender<bft::Communication<Block>>,
|
||||
validators: Vec<SessionKey>,
|
||||
table_router: Router<P>,
|
||||
}
|
||||
|
||||
impl<P: LocalPolkadotApi + Send + Sync + 'static> MessageProcessTask<P> {
|
||||
fn process_message(&self, msg: ConsensusMessage<Block>) -> Option<Async<()>> {
|
||||
match msg {
|
||||
ConsensusMessage::Bft(msg) => {
|
||||
let local_id = self.table_router.session_key();
|
||||
match process_bft_message(msg, &local_id, &self.validators[..]) {
|
||||
Ok(Some(msg)) => {
|
||||
if let Err(_) = self.bft_messages.unbounded_send(msg) {
|
||||
// if the BFT receiving stream has ended then
|
||||
// we should just bail.
|
||||
trace!(target: "bft", "BFT message stream appears to have closed");
|
||||
return Some(Async::Ready(()));
|
||||
}
|
||||
}
|
||||
Ok(None) => {} // ignored local message
|
||||
Err(e) => {
|
||||
debug!("Message validation failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
ConsensusMessage::ChainSpecific(msg, _) => {
|
||||
debug!(target: "consensus", "Processing consensus statement for live consensus");
|
||||
if let Some(Message::Statement(parent_hash, statement)) = Decode::decode(&mut msg.as_slice()) {
|
||||
if ::polkadot_consensus::check_statement(&statement.statement, &statement.signature, statement.sender, &parent_hash) {
|
||||
self.table_router.import_statement(statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: LocalPolkadotApi + Send + Sync + 'static> Future for MessageProcessTask<P> {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
loop {
|
||||
match self.inner_stream.poll() {
|
||||
Ok(Async::Ready(Some(val))) => if let Some(async) = self.process_message(val) {
|
||||
return Ok(async);
|
||||
},
|
||||
Ok(Async::Ready(None)) => return Ok(Async::Ready(())),
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(e) => debug!(target: "p_net", "Error getting consensus message: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Input stream from the consensus network.
|
||||
pub struct InputAdapter {
|
||||
input: mpsc::UnboundedReceiver<bft::Communication<Block>>,
|
||||
}
|
||||
|
||||
impl Stream for InputAdapter {
|
||||
type Item = bft::Communication<Block>;
|
||||
type Error = ::polkadot_consensus::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
match self.input.poll() {
|
||||
Err(_) | Ok(Async::Ready(None)) => Err(bft::InputStreamConcluded.into()),
|
||||
Ok(x) => Ok(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around the network service
|
||||
pub struct ConsensusNetwork<P> {
|
||||
network: Arc<NetworkService>,
|
||||
api: Arc<P>,
|
||||
}
|
||||
|
||||
impl<P> ConsensusNetwork<P> {
|
||||
/// Create a new consensus networking object.
|
||||
pub fn new(network: Arc<NetworkService>, api: Arc<P>) -> Self {
|
||||
ConsensusNetwork { network, api }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Clone for ConsensusNetwork<P> {
|
||||
fn clone(&self) -> Self {
|
||||
ConsensusNetwork {
|
||||
network: self.network.clone(),
|
||||
api: self.api.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A long-lived network which can create parachain statement and BFT message routing processes on demand.
|
||||
impl<P: LocalPolkadotApi + Send + Sync + 'static> Network for ConsensusNetwork<P> {
|
||||
type TableRouter = Router<P>;
|
||||
/// The input stream of BFT messages. Should never logically conclude.
|
||||
type Input = InputAdapter;
|
||||
/// The output sink of BFT messages. Messages sent here should eventually pass to all
|
||||
/// current validators.
|
||||
type Output = BftSink<::polkadot_consensus::Error>;
|
||||
|
||||
/// Instantiate a table router using the given shared table.
|
||||
fn communication_for(&self, validators: &[SessionKey], table: Arc<SharedTable>, task_executor: TaskExecutor) -> (Self::TableRouter, Self::Input, Self::Output) {
|
||||
let parent_hash = table.consensus_parent_hash().clone();
|
||||
|
||||
let sink = BftSink {
|
||||
network: self.network.clone(),
|
||||
parent_hash,
|
||||
_marker: Default::default(),
|
||||
};
|
||||
|
||||
let (bft_send, bft_recv) = mpsc::unbounded();
|
||||
|
||||
let knowledge = Arc::new(Mutex::new(Knowledge::new()));
|
||||
|
||||
let local_session_key = table.session_key();
|
||||
let table_router = Router::new(
|
||||
table,
|
||||
self.network.clone(),
|
||||
self.api.clone(),
|
||||
task_executor.clone(),
|
||||
parent_hash,
|
||||
knowledge.clone(),
|
||||
);
|
||||
|
||||
// spin up a task in the background that processes all incoming statements
|
||||
// TODO: propagate statements on a timer?
|
||||
let process_task = self.network.with_spec(|spec, ctx| {
|
||||
spec.new_consensus(ctx, CurrentConsensus {
|
||||
knowledge,
|
||||
parent_hash,
|
||||
local_session_key,
|
||||
});
|
||||
|
||||
MessageProcessTask {
|
||||
inner_stream: spec.consensus_gossip.messages_for(parent_hash),
|
||||
bft_messages: bft_send,
|
||||
validators: validators.to_vec(),
|
||||
table_router: table_router.clone(),
|
||||
}
|
||||
});
|
||||
|
||||
match process_task {
|
||||
Some(task) => task_executor.spawn(task),
|
||||
None => warn!(target: "p_net", "Cannot process incoming messages: network appears to be down"),
|
||||
}
|
||||
|
||||
(table_router, InputAdapter { input: bft_recv }, sink)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error when the network appears to be down.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct NetworkDown;
|
||||
|
||||
/// A future that resolves when a collation is received.
|
||||
pub struct AwaitingCollation(Option<::futures::sync::oneshot::Receiver<Collation>>);
|
||||
|
||||
impl Future for AwaitingCollation {
|
||||
type Item = Collation;
|
||||
type Error = NetworkDown;
|
||||
|
||||
fn poll(&mut self) -> Poll<Collation, NetworkDown> {
|
||||
match self.0.poll().map_err(|_| NetworkDown)? {
|
||||
Async::Ready(None) => Err(NetworkDown),
|
||||
Async::Ready(Some(x)) => Ok(Async::Ready(x)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<P: LocalPolkadotApi + Send + Sync + 'static> Collators for ConsensusNetwork<P> {
|
||||
type Error = NetworkDown;
|
||||
type Collation = AwaitingCollation;
|
||||
|
||||
fn collate(&self, parachain: ParaId, relay_parent: Hash) -> Self::Collation {
|
||||
AwaitingCollation(
|
||||
self.network.with_spec(|spec, _| spec.await_collation(relay_parent, parachain))
|
||||
)
|
||||
}
|
||||
|
||||
fn note_bad_collator(&self, collator: AccountId) {
|
||||
self.network.with_spec(|spec, ctx| spec.disconnect_bad_collator(ctx, collator));
|
||||
}
|
||||
}
|
||||
@@ -1,664 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot-specific network implementation.
|
||||
//!
|
||||
//! This manages gossip of consensus messages for BFT and for parachain statements,
|
||||
//! parachain block and extrinsic data fetching, communication between collators and validators,
|
||||
//! and more.
|
||||
|
||||
extern crate substrate_bft as bft;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_network;
|
||||
extern crate substrate_primitives;
|
||||
|
||||
extern crate polkadot_api;
|
||||
extern crate polkadot_availability_store as av_store;
|
||||
extern crate polkadot_consensus;
|
||||
extern crate polkadot_primitives;
|
||||
|
||||
extern crate ed25519;
|
||||
extern crate futures;
|
||||
extern crate parking_lot;
|
||||
extern crate tokio;
|
||||
extern crate rhododendron;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate substrate_codec_derive;
|
||||
|
||||
mod collator_pool;
|
||||
mod local_collations;
|
||||
mod router;
|
||||
pub mod consensus;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use futures::sync::oneshot;
|
||||
use parking_lot::Mutex;
|
||||
use polkadot_consensus::{Statement, SignedStatement, GenericStatement};
|
||||
use polkadot_primitives::{AccountId, Block, SessionKey, Hash, Header};
|
||||
use polkadot_primitives::parachain::{Id as ParaId, BlockData, Extrinsic, CandidateReceipt, Collation};
|
||||
use substrate_network::{NodeIndex, RequestId, Context, Severity};
|
||||
use substrate_network::consensus_gossip::ConsensusGossip;
|
||||
use substrate_network::{message, generic_message};
|
||||
use substrate_network::specialization::Specialization;
|
||||
use substrate_network::StatusMessage as GenericFullStatus;
|
||||
use self::collator_pool::{CollatorPool, Role, Action};
|
||||
use self::local_collations::LocalCollations;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Polkadot protocol id.
|
||||
pub const DOT_PROTOCOL_ID: ::substrate_network::ProtocolId = *b"dot";
|
||||
|
||||
type FullStatus = GenericFullStatus<Block>;
|
||||
|
||||
/// Specialization of the network service for the polkadot protocol.
|
||||
pub type NetworkService = ::substrate_network::Service<Block, PolkadotProtocol>;
|
||||
|
||||
/// Status of a Polkadot node.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct Status {
|
||||
collating_for: Option<(AccountId, ParaId)>,
|
||||
}
|
||||
|
||||
struct BlockDataRequest {
|
||||
attempted_peers: HashSet<SessionKey>,
|
||||
consensus_parent: Hash,
|
||||
candidate_hash: Hash,
|
||||
block_data_hash: Hash,
|
||||
sender: oneshot::Sender<BlockData>,
|
||||
}
|
||||
|
||||
// ensures collator-protocol messages are sent in correct order.
|
||||
// session key must be sent before collator role.
|
||||
enum CollatorState {
|
||||
Fresh,
|
||||
RolePending(Role),
|
||||
Primed,
|
||||
}
|
||||
|
||||
impl CollatorState {
|
||||
fn send_key<F: FnMut(Message)>(&mut self, key: SessionKey, mut f: F) {
|
||||
f(Message::SessionKey(key));
|
||||
if let CollatorState::RolePending(role) = ::std::mem::replace(self, CollatorState::Primed) {
|
||||
f(Message::CollatorRole(role));
|
||||
}
|
||||
}
|
||||
|
||||
fn set_role<F: FnMut(Message)>(&mut self, role: Role, mut f: F) {
|
||||
if let CollatorState::Primed = *self {
|
||||
f(Message::CollatorRole(role));
|
||||
} else {
|
||||
*self = CollatorState::RolePending(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PeerInfo {
|
||||
collating_for: Option<(AccountId, ParaId)>,
|
||||
validator_key: Option<SessionKey>,
|
||||
claimed_validator: bool,
|
||||
collator_state: CollatorState,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct KnowledgeEntry {
|
||||
knows_block_data: Vec<SessionKey>,
|
||||
knows_extrinsic: Vec<SessionKey>,
|
||||
block_data: Option<BlockData>,
|
||||
extrinsic: Option<Extrinsic>,
|
||||
}
|
||||
|
||||
/// Tracks knowledge of peers.
|
||||
struct Knowledge {
|
||||
candidates: HashMap<Hash, KnowledgeEntry>,
|
||||
}
|
||||
|
||||
impl Knowledge {
|
||||
pub fn new() -> Self {
|
||||
Knowledge {
|
||||
candidates: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn note_statement(&mut self, from: SessionKey, statement: &Statement) {
|
||||
match *statement {
|
||||
GenericStatement::Candidate(ref c) => {
|
||||
let mut entry = self.candidates.entry(c.hash()).or_insert_with(Default::default);
|
||||
entry.knows_block_data.push(from);
|
||||
entry.knows_extrinsic.push(from);
|
||||
}
|
||||
GenericStatement::Available(ref hash) => {
|
||||
let mut entry = self.candidates.entry(*hash).or_insert_with(Default::default);
|
||||
entry.knows_block_data.push(from);
|
||||
entry.knows_extrinsic.push(from);
|
||||
}
|
||||
GenericStatement::Valid(ref hash) | GenericStatement::Invalid(ref hash) => self.candidates.entry(*hash)
|
||||
.or_insert_with(Default::default)
|
||||
.knows_block_data
|
||||
.push(from),
|
||||
}
|
||||
}
|
||||
|
||||
fn note_candidate(&mut self, hash: Hash, block_data: Option<BlockData>, extrinsic: Option<Extrinsic>) {
|
||||
let entry = self.candidates.entry(hash).or_insert_with(Default::default);
|
||||
entry.block_data = entry.block_data.take().or(block_data);
|
||||
entry.extrinsic = entry.extrinsic.take().or(extrinsic);
|
||||
}
|
||||
}
|
||||
|
||||
struct CurrentConsensus {
|
||||
knowledge: Arc<Mutex<Knowledge>>,
|
||||
parent_hash: Hash,
|
||||
local_session_key: SessionKey,
|
||||
}
|
||||
|
||||
impl CurrentConsensus {
|
||||
// get locally stored block data for a candidate.
|
||||
fn block_data(&self, relay_parent: &Hash, hash: &Hash) -> Option<BlockData> {
|
||||
if relay_parent != &self.parent_hash { return None }
|
||||
|
||||
self.knowledge.lock().candidates.get(hash)
|
||||
.and_then(|entry| entry.block_data.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Polkadot-specific messages.
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
pub enum Message {
|
||||
/// signed statement and localized parent hash.
|
||||
Statement(Hash, SignedStatement),
|
||||
/// As a validator, tell the peer your current session key.
|
||||
// TODO: do this with a cryptographic proof of some kind
|
||||
SessionKey(SessionKey),
|
||||
/// Requesting parachain block data by (relay_parent, candidate_hash).
|
||||
RequestBlockData(RequestId, Hash, Hash),
|
||||
/// Provide block data by candidate hash or nothing if unknown.
|
||||
BlockData(RequestId, Option<BlockData>),
|
||||
/// Tell a collator their role.
|
||||
CollatorRole(Role),
|
||||
/// A collation provided by a peer. Relay parent and collation.
|
||||
Collation(Hash, Collation),
|
||||
}
|
||||
|
||||
fn send_polkadot_message(ctx: &mut Context<Block>, to: NodeIndex, message: Message) {
|
||||
trace!(target: "p_net", "Sending polkadot message to {}: {:?}", to, message);
|
||||
let encoded = message.encode();
|
||||
ctx.send_message(to, generic_message::Message::ChainSpecific(encoded))
|
||||
}
|
||||
|
||||
/// Polkadot protocol attachment for substrate.
|
||||
pub struct PolkadotProtocol {
|
||||
peers: HashMap<NodeIndex, PeerInfo>,
|
||||
collating_for: Option<(AccountId, ParaId)>,
|
||||
consensus_gossip: ConsensusGossip<Block>,
|
||||
collators: CollatorPool,
|
||||
validators: HashMap<SessionKey, NodeIndex>,
|
||||
local_collations: LocalCollations<Collation>,
|
||||
live_consensus: Option<CurrentConsensus>,
|
||||
in_flight: HashMap<(RequestId, NodeIndex), BlockDataRequest>,
|
||||
pending: Vec<BlockDataRequest>,
|
||||
extrinsic_store: Option<::av_store::Store>,
|
||||
next_req_id: u64,
|
||||
}
|
||||
|
||||
impl PolkadotProtocol {
|
||||
/// Instantiate a polkadot protocol handler.
|
||||
pub fn new(collating_for: Option<(AccountId, ParaId)>) -> Self {
|
||||
PolkadotProtocol {
|
||||
peers: HashMap::new(),
|
||||
consensus_gossip: ConsensusGossip::new(),
|
||||
collators: CollatorPool::new(),
|
||||
collating_for,
|
||||
validators: HashMap::new(),
|
||||
local_collations: LocalCollations::new(),
|
||||
live_consensus: None,
|
||||
in_flight: HashMap::new(),
|
||||
pending: Vec::new(),
|
||||
extrinsic_store: None,
|
||||
next_req_id: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gossip a consensus statement.
|
||||
fn gossip_statement(&mut self, ctx: &mut Context<Block>, parent_hash: Hash, statement: SignedStatement) {
|
||||
// TODO: something more targeted than gossip.
|
||||
let raw = Message::Statement(parent_hash, statement).encode();
|
||||
self.consensus_gossip.multicast_chain_specific(ctx, raw, parent_hash);
|
||||
}
|
||||
|
||||
/// Fetch block data by candidate receipt.
|
||||
fn fetch_block_data(&mut self, ctx: &mut Context<Block>, candidate: &CandidateReceipt, relay_parent: Hash) -> oneshot::Receiver<BlockData> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
self.pending.push(BlockDataRequest {
|
||||
attempted_peers: Default::default(),
|
||||
consensus_parent: relay_parent,
|
||||
candidate_hash: candidate.hash(),
|
||||
block_data_hash: candidate.block_data_hash,
|
||||
sender: tx,
|
||||
});
|
||||
|
||||
self.dispatch_pending_requests(ctx);
|
||||
rx
|
||||
}
|
||||
|
||||
/// Note new consensus session.
|
||||
fn new_consensus(&mut self, ctx: &mut Context<Block>, consensus: CurrentConsensus) {
|
||||
let old_data = self.live_consensus.as_ref().map(|c| (c.parent_hash, c.local_session_key));
|
||||
|
||||
if Some(&consensus.local_session_key) != old_data.as_ref().map(|&(_, ref key)| key) {
|
||||
for (id, peer_data) in self.peers.iter_mut()
|
||||
.filter(|&(_, ref info)| info.claimed_validator || info.collating_for.is_some())
|
||||
{
|
||||
peer_data.collator_state.send_key(consensus.local_session_key, |msg| send_polkadot_message(
|
||||
ctx,
|
||||
*id,
|
||||
msg
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
self.live_consensus = Some(consensus);
|
||||
self.consensus_gossip.collect_garbage(old_data.as_ref().map(|&(ref hash, _)| hash));
|
||||
}
|
||||
|
||||
fn dispatch_pending_requests(&mut self, ctx: &mut Context<Block>) {
|
||||
let consensus = match self.live_consensus {
|
||||
Some(ref mut c) => c,
|
||||
None => {
|
||||
self.pending.clear();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let knowledge = consensus.knowledge.lock();
|
||||
let mut new_pending = Vec::new();
|
||||
for mut pending in ::std::mem::replace(&mut self.pending, Vec::new()) {
|
||||
if pending.consensus_parent != consensus.parent_hash { continue }
|
||||
|
||||
if let Some(entry) = knowledge.candidates.get(&pending.candidate_hash) {
|
||||
// answer locally
|
||||
if let Some(ref data) = entry.block_data {
|
||||
let _ = pending.sender.send(data.clone());
|
||||
continue;
|
||||
}
|
||||
|
||||
let validator_keys = &mut self.validators;
|
||||
let next_peer = entry.knows_block_data.iter()
|
||||
.filter_map(|x| validator_keys.get(x).map(|id| (*x, *id)))
|
||||
.find(|&(ref key, _)| pending.attempted_peers.insert(*key))
|
||||
.map(|(_, id)| id);
|
||||
|
||||
// dispatch to peer
|
||||
if let Some(who) = next_peer {
|
||||
let req_id = self.next_req_id;
|
||||
self.next_req_id += 1;
|
||||
|
||||
send_polkadot_message(
|
||||
ctx,
|
||||
who,
|
||||
Message::RequestBlockData(req_id, pending.consensus_parent, pending.candidate_hash)
|
||||
);
|
||||
|
||||
self.in_flight.insert((req_id, who), pending);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
new_pending.push(pending);
|
||||
}
|
||||
|
||||
self.pending = new_pending;
|
||||
}
|
||||
|
||||
fn on_polkadot_message(&mut self, ctx: &mut Context<Block>, who: NodeIndex, raw: Vec<u8>, msg: Message) {
|
||||
trace!(target: "p_net", "Polkadot message from {}: {:?}", who, msg);
|
||||
match msg {
|
||||
Message::Statement(parent_hash, _statement) =>
|
||||
self.consensus_gossip.on_chain_specific(ctx, who, raw, parent_hash),
|
||||
Message::SessionKey(key) => self.on_session_key(ctx, who, key),
|
||||
Message::RequestBlockData(req_id, relay_parent, candidate_hash) => {
|
||||
let block_data = self.live_consensus.as_ref()
|
||||
.and_then(|c| c.block_data(&relay_parent, &candidate_hash))
|
||||
.or_else(|| self.extrinsic_store.as_ref()
|
||||
.and_then(|s| s.block_data(relay_parent, candidate_hash))
|
||||
);
|
||||
|
||||
send_polkadot_message(ctx, who, Message::BlockData(req_id, block_data));
|
||||
}
|
||||
Message::BlockData(req_id, data) => self.on_block_data(ctx, who, req_id, data),
|
||||
Message::Collation(relay_parent, collation) => self.on_collation(ctx, who, relay_parent, collation),
|
||||
Message::CollatorRole(role) => self.on_new_role(ctx, who, role),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_session_key(&mut self, ctx: &mut Context<Block>, who: NodeIndex, key: SessionKey) {
|
||||
{
|
||||
let info = match self.peers.get_mut(&who) {
|
||||
Some(peer) => peer,
|
||||
None => {
|
||||
trace!(target: "p_net", "Network inconsistency: message received from unconnected peer {}", who);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
if !info.claimed_validator {
|
||||
ctx.report_peer(who, Severity::Bad("Session key broadcasted without setting authority role"));
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(old_key) = ::std::mem::replace(&mut info.validator_key, Some(key)) {
|
||||
self.validators.remove(&old_key);
|
||||
|
||||
for (relay_parent, collation) in self.local_collations.fresh_key(&old_key, &key) {
|
||||
send_polkadot_message(
|
||||
ctx,
|
||||
who,
|
||||
Message::Collation(relay_parent, collation),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
self.validators.insert(key, who);
|
||||
}
|
||||
|
||||
self.dispatch_pending_requests(ctx);
|
||||
}
|
||||
|
||||
fn on_block_data(&mut self, ctx: &mut Context<Block>, who: NodeIndex, req_id: RequestId, data: Option<BlockData>) {
|
||||
match self.in_flight.remove(&(req_id, who)) {
|
||||
Some(req) => {
|
||||
if let Some(data) = data {
|
||||
if data.hash() == req.block_data_hash {
|
||||
let _ = req.sender.send(data);
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.pending.push(req);
|
||||
self.dispatch_pending_requests(ctx);
|
||||
}
|
||||
None => ctx.report_peer(who, Severity::Bad("Unexpected block data response")),
|
||||
}
|
||||
}
|
||||
|
||||
// when a validator sends us (a collator) a new role.
|
||||
fn on_new_role(&mut self, ctx: &mut Context<Block>, who: NodeIndex, role: Role) {
|
||||
let info = match self.peers.get(&who) {
|
||||
Some(peer) => peer,
|
||||
None => {
|
||||
trace!(target: "p_net", "Network inconsistency: message received from unconnected peer {}", who);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
debug!(target: "p_net", "New collator role {:?} from {}", role, who);
|
||||
|
||||
match info.validator_key {
|
||||
None => ctx.report_peer(
|
||||
who,
|
||||
Severity::Bad("Sent collator role without registering first as validator"),
|
||||
),
|
||||
Some(key) => for (relay_parent, collation) in self.local_collations.note_validator_role(key, role) {
|
||||
debug!(target: "p_net", "Broadcasting collation on relay parent {:?}", relay_parent);
|
||||
send_polkadot_message(
|
||||
ctx,
|
||||
who,
|
||||
Message::Collation(relay_parent, collation),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Specialization<Block> for PolkadotProtocol {
|
||||
fn status(&self) -> Vec<u8> {
|
||||
Status { collating_for: self.collating_for.clone() }.encode()
|
||||
}
|
||||
|
||||
fn on_connect(&mut self, ctx: &mut Context<Block>, who: NodeIndex, status: FullStatus) {
|
||||
let local_status = match Status::decode(&mut &status.chain_status[..]) {
|
||||
Some(status) => status,
|
||||
None => {
|
||||
Status { collating_for: None }
|
||||
}
|
||||
};
|
||||
|
||||
let validator = status.roles.contains(substrate_network::Roles::AUTHORITY);
|
||||
let send_key = validator || local_status.collating_for.is_some();
|
||||
|
||||
let mut peer_info = PeerInfo {
|
||||
collating_for: local_status.collating_for,
|
||||
validator_key: None,
|
||||
claimed_validator: validator,
|
||||
collator_state: CollatorState::Fresh,
|
||||
};
|
||||
|
||||
if let Some((ref acc_id, ref para_id)) = local_status.collating_for {
|
||||
if self.collator_peer(acc_id.clone()).is_some() {
|
||||
ctx.report_peer(who, Severity::Useless("Unknown Polkadot-specific reason"));
|
||||
return
|
||||
}
|
||||
|
||||
let collator_role = self.collators.on_new_collator(acc_id.clone(), para_id.clone());
|
||||
|
||||
peer_info.collator_state.set_role(collator_role, |msg| send_polkadot_message(
|
||||
ctx,
|
||||
who,
|
||||
msg,
|
||||
));
|
||||
}
|
||||
|
||||
if let (true, &Some(ref consensus)) = (send_key, &self.live_consensus) {
|
||||
peer_info.collator_state.send_key(consensus.local_session_key, |msg| send_polkadot_message(
|
||||
ctx,
|
||||
who,
|
||||
msg,
|
||||
));
|
||||
}
|
||||
|
||||
self.peers.insert(who, peer_info);
|
||||
self.consensus_gossip.new_peer(ctx, who, status.roles);
|
||||
self.dispatch_pending_requests(ctx);
|
||||
}
|
||||
|
||||
fn on_disconnect(&mut self, ctx: &mut Context<Block>, who: NodeIndex) {
|
||||
if let Some(info) = self.peers.remove(&who) {
|
||||
if let Some((acc_id, _)) = info.collating_for {
|
||||
let new_primary = self.collators.on_disconnect(acc_id)
|
||||
.and_then(|new_primary| self.collator_peer(new_primary));
|
||||
|
||||
if let Some((new_primary, primary_info)) = new_primary {
|
||||
primary_info.collator_state.set_role(Role::Primary, |msg| send_polkadot_message(
|
||||
ctx,
|
||||
new_primary,
|
||||
msg,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(validator_key) = info.validator_key {
|
||||
self.validators.remove(&validator_key);
|
||||
self.local_collations.on_disconnect(&validator_key);
|
||||
}
|
||||
|
||||
{
|
||||
let pending = &mut self.pending;
|
||||
self.in_flight.retain(|&(_, ref peer), val| {
|
||||
let retain = peer != &who;
|
||||
if !retain {
|
||||
let (sender, _) = oneshot::channel();
|
||||
pending.push(::std::mem::replace(val, BlockDataRequest {
|
||||
attempted_peers: Default::default(),
|
||||
consensus_parent: Default::default(),
|
||||
candidate_hash: Default::default(),
|
||||
block_data_hash: Default::default(),
|
||||
sender,
|
||||
}));
|
||||
}
|
||||
|
||||
retain
|
||||
});
|
||||
}
|
||||
self.consensus_gossip.peer_disconnected(ctx, who);
|
||||
self.dispatch_pending_requests(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_message(&mut self, ctx: &mut Context<Block>, who: NodeIndex, message: message::Message<Block>) {
|
||||
match message {
|
||||
generic_message::Message::BftMessage(msg) => {
|
||||
trace!(target: "p_net", "Polkadot BFT message from {}: {:?}", who, msg);
|
||||
// TODO: check signature here? what if relevant block is unknown?
|
||||
self.consensus_gossip.on_bft_message(ctx, who, msg)
|
||||
}
|
||||
generic_message::Message::ChainSpecific(raw) => {
|
||||
match Message::decode(&mut raw.as_slice()) {
|
||||
Some(msg) => self.on_polkadot_message(ctx, who, raw, msg),
|
||||
None => {
|
||||
trace!(target: "p_net", "Bad message from {}", who);
|
||||
ctx.report_peer(who, Severity::Bad("Invalid polkadot protocol message format"));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_abort(&mut self) {
|
||||
self.consensus_gossip.abort();
|
||||
}
|
||||
|
||||
fn maintain_peers(&mut self, ctx: &mut Context<Block>) {
|
||||
self.consensus_gossip.collect_garbage(None);
|
||||
self.collators.collect_garbage(None);
|
||||
self.local_collations.collect_garbage(None);
|
||||
self.dispatch_pending_requests(ctx);
|
||||
|
||||
for collator_action in self.collators.maintain_peers() {
|
||||
match collator_action {
|
||||
Action::Disconnect(collator) => self.disconnect_bad_collator(ctx, collator),
|
||||
Action::NewRole(account_id, role) => if let Some((collator, info)) = self.collator_peer(account_id) {
|
||||
info.collator_state.set_role(role, |msg| send_polkadot_message(
|
||||
ctx,
|
||||
collator,
|
||||
msg,
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_block_imported(&mut self, _ctx: &mut Context<Block>, hash: Hash, header: &Header) {
|
||||
self.collators.collect_garbage(Some(&hash));
|
||||
self.local_collations.collect_garbage(Some(&header.parent_hash));
|
||||
}
|
||||
}
|
||||
|
||||
impl PolkadotProtocol {
|
||||
// we received a collation from a peer
|
||||
fn on_collation(&mut self, ctx: &mut Context<Block>, from: NodeIndex, relay_parent: Hash, collation: Collation) {
|
||||
let collation_para = collation.receipt.parachain_index;
|
||||
let collated_acc = collation.receipt.collator;
|
||||
|
||||
match self.peers.get(&from) {
|
||||
None => ctx.report_peer(from, Severity::Useless("Unknown Polkadot specific reason")),
|
||||
Some(peer_info) => match peer_info.collating_for {
|
||||
None => ctx.report_peer(from, Severity::Bad("Sent collation without registering collator intent")),
|
||||
Some((ref acc_id, ref para_id)) => {
|
||||
let structurally_valid = para_id == &collation_para && acc_id == &collated_acc;
|
||||
if structurally_valid && collation.receipt.check_signature().is_ok() {
|
||||
debug!(target: "p_net", "Received collation for parachain {:?} from peer {}", para_id, from);
|
||||
self.collators.on_collation(acc_id.clone(), relay_parent, collation)
|
||||
} else {
|
||||
ctx.report_peer(from, Severity::Bad("Sent malformed collation"))
|
||||
};
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn await_collation(&mut self, relay_parent: Hash, para_id: ParaId) -> oneshot::Receiver<Collation> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
debug!(target: "p_net", "Attempting to get collation for parachain {:?} on relay parent {:?}", para_id, relay_parent);
|
||||
self.collators.await_collation(relay_parent, para_id, tx);
|
||||
rx
|
||||
}
|
||||
|
||||
// get connected peer with given account ID for collation.
|
||||
fn collator_peer(&mut self, account_id: AccountId) -> Option<(NodeIndex, &mut PeerInfo)> {
|
||||
let check_info = |info: &PeerInfo| info
|
||||
.collating_for
|
||||
.as_ref()
|
||||
.map_or(false, |&(ref acc_id, _)| acc_id == &account_id);
|
||||
|
||||
self.peers
|
||||
.iter_mut()
|
||||
.filter(|&(_, ref info)| check_info(&**info))
|
||||
.map(|(who, info)| (*who, info))
|
||||
.next()
|
||||
}
|
||||
|
||||
// disconnect a collator by account-id.
|
||||
fn disconnect_bad_collator(&mut self, ctx: &mut Context<Block>, account_id: AccountId) {
|
||||
if let Some((who, _)) = self.collator_peer(account_id) {
|
||||
ctx.report_peer(who, Severity::Bad("Consensus layer determined the given collator misbehaved"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PolkadotProtocol {
|
||||
/// Add a local collation and broadcast it to the necessary peers.
|
||||
pub fn add_local_collation(
|
||||
&mut self,
|
||||
ctx: &mut Context<Block>,
|
||||
relay_parent: Hash,
|
||||
targets: HashSet<SessionKey>,
|
||||
collation: Collation,
|
||||
) {
|
||||
debug!(target: "p_net", "Importing local collation on relay parent {:?} and parachain {:?}",
|
||||
relay_parent, collation.receipt.parachain_index);
|
||||
|
||||
for (primary, cloned_collation) in self.local_collations.add_collation(relay_parent, targets, collation.clone()) {
|
||||
match self.validators.get(&primary) {
|
||||
Some(who) => {
|
||||
debug!(target: "p_net", "Sending local collation to {:?}", primary);
|
||||
send_polkadot_message(
|
||||
ctx,
|
||||
*who,
|
||||
Message::Collation(relay_parent, cloned_collation),
|
||||
)
|
||||
},
|
||||
None =>
|
||||
warn!(target: "polkadot_network", "Encountered tracked but disconnected validator {:?}", primary),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// register availability store.
|
||||
pub fn register_availability_store(&mut self, extrinsic_store: ::av_store::Store) {
|
||||
self.extrinsic_store = Some(extrinsic_store);
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Local collations to be circulated to validators.
|
||||
//!
|
||||
//! Collations are attempted to be repropagated when a new validator connects,
|
||||
//! a validator changes his session key, or when they are generated.
|
||||
|
||||
use polkadot_primitives::{Hash, SessionKey};
|
||||
|
||||
use collator_pool::Role;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const LIVE_FOR: Duration = Duration::from_secs(60 * 5);
|
||||
|
||||
struct LocalCollation<C> {
|
||||
targets: HashSet<SessionKey>,
|
||||
collation: C,
|
||||
live_since: Instant,
|
||||
}
|
||||
|
||||
/// Tracker for locally collated values and which validators to send them to.
|
||||
pub struct LocalCollations<C> {
|
||||
primary_for: HashSet<SessionKey>,
|
||||
local_collations: HashMap<Hash, LocalCollation<C>>,
|
||||
}
|
||||
|
||||
impl<C: Clone> LocalCollations<C> {
|
||||
/// Create a new `LocalCollations` tracker.
|
||||
pub fn new() -> Self {
|
||||
LocalCollations {
|
||||
primary_for: HashSet::new(),
|
||||
local_collations: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Validator gave us a new role. If the new role is "primary", this function might return
|
||||
/// a set of collations to send to that validator.
|
||||
pub fn note_validator_role(&mut self, key: SessionKey, role: Role) -> Vec<(Hash, C)> {
|
||||
match role {
|
||||
Role::Backup => {
|
||||
self.primary_for.remove(&key);
|
||||
Vec::new()
|
||||
}
|
||||
Role::Primary => {
|
||||
let new_primary = self.primary_for.insert(key);
|
||||
if new_primary {
|
||||
self.collations_targeting(&key)
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fresh session key from a validator. Returns a vector of collations to send
|
||||
/// to the validator.
|
||||
pub fn fresh_key(&mut self, old_key: &SessionKey, new_key: &SessionKey) -> Vec<(Hash, C)> {
|
||||
if self.primary_for.remove(old_key) {
|
||||
self.primary_for.insert(*new_key);
|
||||
|
||||
self.collations_targeting(new_key)
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Validator disconnected.
|
||||
pub fn on_disconnect(&mut self, key: &SessionKey) {
|
||||
self.primary_for.remove(key);
|
||||
}
|
||||
|
||||
/// Mark collations relevant to the given parent hash as obsolete.
|
||||
pub fn collect_garbage(&mut self, relay_parent: Option<&Hash>) {
|
||||
if let Some(relay_parent) = relay_parent {
|
||||
self.local_collations.remove(relay_parent);
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
self.local_collations.retain(|_, v| v.live_since + LIVE_FOR > now);
|
||||
}
|
||||
|
||||
/// Add a collation. Returns an iterator of session keys to send to and lazy copies of the collation.
|
||||
pub fn add_collation<'a>(
|
||||
&'a mut self,
|
||||
relay_parent: Hash,
|
||||
targets: HashSet<SessionKey>,
|
||||
collation: C
|
||||
)
|
||||
-> impl Iterator<Item=(SessionKey, C)> + 'a
|
||||
{
|
||||
self.local_collations.insert(relay_parent, LocalCollation {
|
||||
targets,
|
||||
collation,
|
||||
live_since: Instant::now(),
|
||||
});
|
||||
|
||||
let local = self.local_collations.get(&relay_parent)
|
||||
.expect("just inserted to this key; qed");
|
||||
|
||||
let borrowed_collation = &local.collation;
|
||||
local.targets
|
||||
.intersection(&self.primary_for)
|
||||
.map(move |k| (*k, borrowed_collation.clone()))
|
||||
}
|
||||
|
||||
fn collations_targeting(&self, key: &SessionKey) -> Vec<(Hash, C)> {
|
||||
self.local_collations.iter()
|
||||
.filter(|&(_, ref v)| v.targets.contains(key))
|
||||
.map(|(h, v)| (*h, v.collation.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_validator_with_ready_collation() {
|
||||
let key = [1; 32].into();
|
||||
let relay_parent = [2; 32].into();
|
||||
let targets = {
|
||||
let mut set = HashSet::new();
|
||||
set.insert(key);
|
||||
set
|
||||
};
|
||||
|
||||
let mut tracker = LocalCollations::new();
|
||||
assert!(tracker.add_collation(relay_parent, targets, 5).next().is_none());
|
||||
assert_eq!(tracker.note_validator_role(key, Role::Primary), vec![(relay_parent, 5)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rename_with_ready() {
|
||||
let orig_key = [1; 32].into();
|
||||
let new_key = [2; 32].into();
|
||||
let relay_parent = [255; 32].into();
|
||||
let targets = {
|
||||
let mut set = HashSet::new();
|
||||
set.insert(new_key);
|
||||
set
|
||||
};
|
||||
|
||||
let mut tracker: LocalCollations<u8> = LocalCollations::new();
|
||||
assert!(tracker.add_collation(relay_parent, targets, 5).next().is_none());
|
||||
assert!(tracker.note_validator_role(orig_key, Role::Primary).is_empty());
|
||||
assert_eq!(tracker.fresh_key(&orig_key, &new_key), vec![(relay_parent, 5u8)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collecting_garbage() {
|
||||
let relay_parent_a = [255; 32].into();
|
||||
let relay_parent_b = [222; 32].into();
|
||||
|
||||
let mut tracker: LocalCollations<u8> = LocalCollations::new();
|
||||
assert!(tracker.add_collation(relay_parent_a, HashSet::new(), 5).next().is_none());
|
||||
assert!(tracker.add_collation(relay_parent_b, HashSet::new(), 69).next().is_none());
|
||||
|
||||
let live_since = Instant::now() - LIVE_FOR - Duration::from_secs(10);
|
||||
tracker.local_collations.get_mut(&relay_parent_b).unwrap().live_since = live_since;
|
||||
|
||||
tracker.collect_garbage(Some(&relay_parent_a));
|
||||
|
||||
// first one pruned because of relay parent, other because of time.
|
||||
assert!(tracker.local_collations.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_collation_with_connected_target() {
|
||||
let key = [1; 32].into();
|
||||
let relay_parent = [2; 32].into();
|
||||
let targets = {
|
||||
let mut set = HashSet::new();
|
||||
set.insert(key);
|
||||
set
|
||||
};
|
||||
|
||||
let mut tracker = LocalCollations::new();
|
||||
assert!(tracker.note_validator_role(key, Role::Primary).is_empty());
|
||||
assert_eq!(tracker.add_collation(relay_parent, targets, 5).next(), Some((key, 5)));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Statement routing and consensus table router implementation.
|
||||
//!
|
||||
//! During the consensus process, validators exchange statements on validity and availability
|
||||
//! of parachain candidates.
|
||||
//! The `Router` in this file hooks into the underlying network to fulfill
|
||||
//! the `TableRouter` trait from `polkadot-consensus`, which is expected to call into a shared statement table
|
||||
//! and dispatch evaluation work as necessary when new statements come in.
|
||||
|
||||
use polkadot_api::{PolkadotApi, LocalPolkadotApi};
|
||||
use polkadot_consensus::{SharedTable, TableRouter, SignedStatement, GenericStatement, StatementProducer};
|
||||
use polkadot_primitives::{Hash, BlockId, SessionKey};
|
||||
use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt};
|
||||
|
||||
use futures::prelude::*;
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{NetworkService, Knowledge};
|
||||
|
||||
/// Table routing implementation.
|
||||
pub struct Router<P: PolkadotApi> {
|
||||
table: Arc<SharedTable>,
|
||||
network: Arc<NetworkService>,
|
||||
api: Arc<P>,
|
||||
task_executor: TaskExecutor,
|
||||
parent_hash: Hash,
|
||||
knowledge: Arc<Mutex<Knowledge>>,
|
||||
deferred_statements: Arc<Mutex<DeferredStatements>>,
|
||||
}
|
||||
|
||||
impl<P: PolkadotApi> Router<P> {
|
||||
pub(crate) fn new(
|
||||
table: Arc<SharedTable>,
|
||||
network: Arc<NetworkService>,
|
||||
api: Arc<P>,
|
||||
task_executor: TaskExecutor,
|
||||
parent_hash: Hash,
|
||||
knowledge: Arc<Mutex<Knowledge>>,
|
||||
) -> Self {
|
||||
Router {
|
||||
table,
|
||||
network,
|
||||
api,
|
||||
task_executor,
|
||||
parent_hash,
|
||||
knowledge,
|
||||
deferred_statements: Arc::new(Mutex::new(DeferredStatements::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn session_key(&self) -> SessionKey {
|
||||
self.table.session_key()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: PolkadotApi> Clone for Router<P> {
|
||||
fn clone(&self) -> Self {
|
||||
Router {
|
||||
table: self.table.clone(),
|
||||
network: self.network.clone(),
|
||||
api: self.api.clone(),
|
||||
task_executor: self.task_executor.clone(),
|
||||
parent_hash: self.parent_hash.clone(),
|
||||
deferred_statements: self.deferred_statements.clone(),
|
||||
knowledge: self.knowledge.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: LocalPolkadotApi + Send + Sync + 'static> Router<P> {
|
||||
/// Import a statement whose signature has been checked already.
|
||||
pub(crate) fn import_statement(&self, statement: SignedStatement) {
|
||||
trace!(target: "p_net", "importing consensus statement {:?}", statement.statement);
|
||||
|
||||
// defer any statements for which we haven't imported the candidate yet
|
||||
let c_hash = {
|
||||
let candidate_data = match statement.statement {
|
||||
GenericStatement::Candidate(ref c) => Some(c.hash()),
|
||||
GenericStatement::Valid(ref hash)
|
||||
| GenericStatement::Invalid(ref hash)
|
||||
| GenericStatement::Available(ref hash)
|
||||
=> self.table.with_candidate(hash, |c| c.map(|_| *hash)),
|
||||
};
|
||||
match candidate_data {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
self.deferred_statements.lock().push(statement);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// import all statements pending on this candidate
|
||||
let (mut statements, _traces) = if let GenericStatement::Candidate(_) = statement.statement {
|
||||
self.deferred_statements.lock().get_deferred(&c_hash)
|
||||
} else {
|
||||
(Vec::new(), Vec::new())
|
||||
};
|
||||
|
||||
// prepend the candidate statement.
|
||||
debug!(target: "consensus", "Importing statements about candidate {:?}", c_hash);
|
||||
statements.insert(0, statement);
|
||||
let producers: Vec<_> = self.table.import_remote_statements(
|
||||
self,
|
||||
statements.iter().cloned(),
|
||||
);
|
||||
// dispatch future work as necessary.
|
||||
for (producer, statement) in producers.into_iter().zip(statements) {
|
||||
self.knowledge.lock().note_statement(statement.sender, &statement.statement);
|
||||
|
||||
if let Some(producer) = producer {
|
||||
trace!(target: "consensus", "driving statement work to completion");
|
||||
self.dispatch_work(c_hash, producer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_work<D, E>(&self, candidate_hash: Hash, producer: StatementProducer<D, E>) where
|
||||
D: Future<Item=BlockData,Error=io::Error> + Send + 'static,
|
||||
E: Future<Item=Extrinsic,Error=io::Error> + Send + 'static,
|
||||
{
|
||||
let parent_hash = self.parent_hash.clone();
|
||||
|
||||
let api = self.api.clone();
|
||||
let validate = move |collation| -> Option<bool> {
|
||||
let id = BlockId::hash(parent_hash);
|
||||
match ::polkadot_consensus::validate_collation(&*api, &id, &collation) {
|
||||
Ok(()) => Some(true),
|
||||
Err(e) => {
|
||||
debug!(target: "p_net", "Encountered bad collation: {}", e);
|
||||
Some(false)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let table = self.table.clone();
|
||||
let network = self.network.clone();
|
||||
let knowledge = self.knowledge.clone();
|
||||
|
||||
let work = producer.prime(validate)
|
||||
.map(move |produced| {
|
||||
// store the data before broadcasting statements, so other peers can fetch.
|
||||
knowledge.lock().note_candidate(
|
||||
candidate_hash,
|
||||
produced.block_data,
|
||||
produced.extrinsic
|
||||
);
|
||||
|
||||
// propagate the statements
|
||||
if let Some(validity) = produced.validity {
|
||||
let signed = table.sign_and_import(validity.clone()).0;
|
||||
network.with_spec(|spec, ctx| spec.gossip_statement(ctx, parent_hash, signed));
|
||||
}
|
||||
|
||||
if let Some(availability) = produced.availability {
|
||||
let signed = table.sign_and_import(availability).0;
|
||||
network.with_spec(|spec, ctx| spec.gossip_statement(ctx, parent_hash, signed));
|
||||
}
|
||||
})
|
||||
.map_err(|e| debug!(target: "p_net", "Failed to produce statements: {:?}", e));
|
||||
|
||||
self.task_executor.spawn(work);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: LocalPolkadotApi + Send> TableRouter for Router<P> {
|
||||
type Error = io::Error;
|
||||
type FetchCandidate = BlockDataReceiver;
|
||||
type FetchExtrinsic = Result<Extrinsic, Self::Error>;
|
||||
|
||||
fn local_candidate(&self, receipt: CandidateReceipt, block_data: BlockData, extrinsic: Extrinsic) {
|
||||
// give to network to make available.
|
||||
let hash = receipt.hash();
|
||||
let (candidate, availability) = self.table.sign_and_import(GenericStatement::Candidate(receipt));
|
||||
|
||||
self.knowledge.lock().note_candidate(hash, Some(block_data), Some(extrinsic));
|
||||
self.network.with_spec(|spec, ctx| {
|
||||
spec.gossip_statement(ctx, self.parent_hash, candidate);
|
||||
if let Some(availability) = availability {
|
||||
spec.gossip_statement(ctx, self.parent_hash, availability);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn fetch_block_data(&self, candidate: &CandidateReceipt) -> BlockDataReceiver {
|
||||
let parent_hash = self.parent_hash;
|
||||
let rx = self.network.with_spec(|spec, ctx| { spec.fetch_block_data(ctx, candidate, parent_hash) });
|
||||
BlockDataReceiver { inner: rx }
|
||||
}
|
||||
|
||||
fn fetch_extrinsic_data(&self, _candidate: &CandidateReceipt) -> Self::FetchExtrinsic {
|
||||
Ok(Extrinsic)
|
||||
}
|
||||
}
|
||||
|
||||
/// Receiver for block data.
|
||||
pub struct BlockDataReceiver {
|
||||
inner: Option<::futures::sync::oneshot::Receiver<BlockData>>,
|
||||
}
|
||||
|
||||
impl Future for BlockDataReceiver {
|
||||
type Item = BlockData;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<BlockData, io::Error> {
|
||||
match self.inner {
|
||||
Some(ref mut inner) => inner.poll().map_err(|_| io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Sending end of channel hung up",
|
||||
)),
|
||||
None => return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Network service is unavailable",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A unique trace for valid statements issued by a validator.
|
||||
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
|
||||
enum StatementTrace {
|
||||
Valid(SessionKey, Hash),
|
||||
Invalid(SessionKey, Hash),
|
||||
Available(SessionKey, Hash),
|
||||
}
|
||||
|
||||
// helper for deferring statements whose associated candidate is unknown.
|
||||
struct DeferredStatements {
|
||||
deferred: HashMap<Hash, Vec<SignedStatement>>,
|
||||
known_traces: HashSet<StatementTrace>,
|
||||
}
|
||||
|
||||
impl DeferredStatements {
|
||||
fn new() -> Self {
|
||||
DeferredStatements {
|
||||
deferred: HashMap::new(),
|
||||
known_traces: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, statement: SignedStatement) {
|
||||
let (hash, trace) = match statement.statement {
|
||||
GenericStatement::Candidate(_) => return,
|
||||
GenericStatement::Valid(hash) => (hash, StatementTrace::Valid(statement.sender, hash)),
|
||||
GenericStatement::Invalid(hash) => (hash, StatementTrace::Invalid(statement.sender, hash)),
|
||||
GenericStatement::Available(hash) => (hash, StatementTrace::Available(statement.sender, hash)),
|
||||
};
|
||||
|
||||
if self.known_traces.insert(trace) {
|
||||
self.deferred.entry(hash).or_insert_with(Vec::new).push(statement);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_deferred(&mut self, hash: &Hash) -> (Vec<SignedStatement>, Vec<StatementTrace>) {
|
||||
match self.deferred.remove(hash) {
|
||||
None => (Vec::new(), Vec::new()),
|
||||
Some(deferred) => {
|
||||
let mut traces = Vec::new();
|
||||
for statement in deferred.iter() {
|
||||
let trace = match statement.statement {
|
||||
GenericStatement::Candidate(_) => continue,
|
||||
GenericStatement::Valid(hash) => StatementTrace::Valid(statement.sender, hash),
|
||||
GenericStatement::Invalid(hash) => StatementTrace::Invalid(statement.sender, hash),
|
||||
GenericStatement::Available(hash) => StatementTrace::Available(statement.sender, hash),
|
||||
};
|
||||
|
||||
self.known_traces.remove(&trace);
|
||||
traces.push(trace);
|
||||
}
|
||||
|
||||
(deferred, traces)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use substrate_primitives::H512;
|
||||
|
||||
#[test]
|
||||
fn deferred_statements_works() {
|
||||
let mut deferred = DeferredStatements::new();
|
||||
let hash = [1; 32].into();
|
||||
let sig = H512([2; 64]).into();
|
||||
let sender = [255; 32].into();
|
||||
|
||||
let statement = SignedStatement {
|
||||
statement: GenericStatement::Valid(hash),
|
||||
sender,
|
||||
signature: sig,
|
||||
};
|
||||
|
||||
// pre-push.
|
||||
{
|
||||
let (signed, traces) = deferred.get_deferred(&hash);
|
||||
assert!(signed.is_empty());
|
||||
assert!(traces.is_empty());
|
||||
}
|
||||
|
||||
deferred.push(statement.clone());
|
||||
deferred.push(statement.clone());
|
||||
|
||||
// draining: second push should have been ignored.
|
||||
{
|
||||
let (signed, traces) = deferred.get_deferred(&hash);
|
||||
assert_eq!(signed.len(), 1);
|
||||
|
||||
assert_eq!(traces.len(), 1);
|
||||
assert_eq!(signed[0].clone(), statement);
|
||||
assert_eq!(traces[0].clone(), StatementTrace::Valid(sender, hash));
|
||||
}
|
||||
|
||||
// after draining
|
||||
{
|
||||
let (signed, traces) = deferred.get_deferred(&hash);
|
||||
assert!(signed.is_empty());
|
||||
assert!(traces.is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for polkadot and consensus network.
|
||||
|
||||
use super::{PolkadotProtocol, Status, CurrentConsensus, Knowledge, Message, FullStatus};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use polkadot_consensus::GenericStatement;
|
||||
use polkadot_primitives::{Block, Hash, SessionKey};
|
||||
use polkadot_primitives::parachain::{CandidateReceipt, HeadData, BlockData};
|
||||
use substrate_primitives::H512;
|
||||
use codec::Encode;
|
||||
use substrate_network::{Severity, NodeIndex, PeerInfo, ClientHandle, Context, Roles, message::Message as SubstrateMessage, specialization::Specialization, generic_message::Message as GenericMessage};
|
||||
|
||||
use std::sync::Arc;
|
||||
use futures::Future;
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestContext {
|
||||
disabled: Vec<NodeIndex>,
|
||||
disconnected: Vec<NodeIndex>,
|
||||
messages: Vec<(NodeIndex, SubstrateMessage<Block>)>,
|
||||
}
|
||||
|
||||
impl Context<Block> for TestContext {
|
||||
fn client(&self) -> &ClientHandle<Block> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn report_peer(&mut self, peer: NodeIndex, reason: Severity) {
|
||||
match reason {
|
||||
Severity::Bad(_) => self.disabled.push(peer),
|
||||
_ => self.disconnected.push(peer),
|
||||
}
|
||||
}
|
||||
|
||||
fn peer_info(&self, _peer: NodeIndex) -> Option<PeerInfo<Block>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn send_message(&mut self, who: NodeIndex, data: SubstrateMessage<Block>) {
|
||||
self.messages.push((who, data))
|
||||
}
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
fn has_message(&self, to: NodeIndex, message: Message) -> bool {
|
||||
use substrate_network::generic_message::Message as GenericMessage;
|
||||
|
||||
let encoded = message.encode();
|
||||
self.messages.iter().any(|&(ref peer, ref msg)| match msg {
|
||||
GenericMessage::ChainSpecific(ref data) => peer == &to && data == &encoded,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn make_status(status: &Status, roles: Roles) -> FullStatus {
|
||||
FullStatus {
|
||||
version: 1,
|
||||
roles,
|
||||
best_number: 0,
|
||||
best_hash: Default::default(),
|
||||
genesis_hash: Default::default(),
|
||||
chain_status: status.encode(),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_consensus(parent_hash: Hash, local_key: SessionKey) -> (CurrentConsensus, Arc<Mutex<Knowledge>>) {
|
||||
let knowledge = Arc::new(Mutex::new(Knowledge::new()));
|
||||
let c = CurrentConsensus {
|
||||
knowledge: knowledge.clone(),
|
||||
parent_hash,
|
||||
local_session_key: local_key,
|
||||
};
|
||||
|
||||
(c, knowledge)
|
||||
}
|
||||
|
||||
fn on_message(protocol: &mut PolkadotProtocol, ctx: &mut TestContext, from: NodeIndex, message: Message) {
|
||||
let encoded = message.encode();
|
||||
protocol.on_message(ctx, from, GenericMessage::ChainSpecific(encoded));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sends_session_key() {
|
||||
let mut protocol = PolkadotProtocol::new(None);
|
||||
|
||||
let peer_a = 1;
|
||||
let peer_b = 2;
|
||||
let parent_hash = [0; 32].into();
|
||||
let local_key = [1; 32].into();
|
||||
|
||||
let validator_status = Status { collating_for: None };
|
||||
let collator_status = Status { collating_for: Some(([2; 32].into(), 5.into())) };
|
||||
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
protocol.on_connect(&mut ctx, peer_a, make_status(&validator_status, Roles::AUTHORITY));
|
||||
assert!(ctx.messages.is_empty());
|
||||
}
|
||||
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
let (consensus, _knowledge) = make_consensus(parent_hash, local_key);
|
||||
protocol.new_consensus(&mut ctx, consensus);
|
||||
assert!(ctx.has_message(peer_a, Message::SessionKey(local_key)));
|
||||
}
|
||||
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
protocol.on_connect(&mut ctx, peer_b, make_status(&collator_status, Roles::NONE));
|
||||
assert!(ctx.has_message(peer_b, Message::SessionKey(local_key)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetches_from_those_with_knowledge() {
|
||||
let mut protocol = PolkadotProtocol::new(None);
|
||||
|
||||
let peer_a = 1;
|
||||
let peer_b = 2;
|
||||
let parent_hash = [0; 32].into();
|
||||
let local_key = [1; 32].into();
|
||||
|
||||
let block_data = BlockData(vec![1, 2, 3, 4]);
|
||||
let block_data_hash = block_data.hash();
|
||||
let candidate_receipt = CandidateReceipt {
|
||||
parachain_index: 5.into(),
|
||||
collator: [255; 32].into(),
|
||||
head_data: HeadData(vec![9, 9, 9]),
|
||||
signature: H512::from([1; 64]).into(),
|
||||
balance_uploads: Vec::new(),
|
||||
egress_queue_roots: Vec::new(),
|
||||
fees: 1_000_000,
|
||||
block_data_hash,
|
||||
};
|
||||
|
||||
let candidate_hash = candidate_receipt.hash();
|
||||
let a_key = [3; 32].into();
|
||||
let b_key = [4; 32].into();
|
||||
|
||||
let status = Status { collating_for: None };
|
||||
|
||||
let (consensus, knowledge) = make_consensus(parent_hash, local_key);
|
||||
protocol.new_consensus(&mut TestContext::default(), consensus);
|
||||
|
||||
knowledge.lock().note_statement(a_key, &GenericStatement::Valid(candidate_hash));
|
||||
let recv = protocol.fetch_block_data(&mut TestContext::default(), &candidate_receipt, parent_hash);
|
||||
|
||||
// connect peer A
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
protocol.on_connect(&mut ctx, peer_a, make_status(&status, Roles::AUTHORITY));
|
||||
assert!(ctx.has_message(peer_a, Message::SessionKey(local_key)));
|
||||
}
|
||||
|
||||
// peer A gives session key and gets asked for data.
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
on_message(&mut protocol, &mut ctx, peer_a, Message::SessionKey(a_key));
|
||||
assert!(protocol.validators.contains_key(&a_key));
|
||||
assert!(ctx.has_message(peer_a, Message::RequestBlockData(1, parent_hash, candidate_hash)));
|
||||
}
|
||||
|
||||
knowledge.lock().note_statement(b_key, &GenericStatement::Valid(candidate_hash));
|
||||
|
||||
// peer B connects and sends session key. request already assigned to A
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
protocol.on_connect(&mut ctx, peer_b, make_status(&status, Roles::AUTHORITY));
|
||||
on_message(&mut protocol, &mut ctx, peer_b, Message::SessionKey(b_key));
|
||||
assert!(!ctx.has_message(peer_b, Message::RequestBlockData(2, parent_hash, candidate_hash)));
|
||||
|
||||
}
|
||||
|
||||
// peer A disconnects, triggering reassignment
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
protocol.on_disconnect(&mut ctx, peer_a);
|
||||
assert!(!protocol.validators.contains_key(&a_key));
|
||||
assert!(ctx.has_message(peer_b, Message::RequestBlockData(2, parent_hash, candidate_hash)));
|
||||
}
|
||||
|
||||
// peer B comes back with block data.
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
on_message(&mut protocol, &mut ctx, peer_b, Message::BlockData(2, Some(block_data.clone())));
|
||||
drop(protocol);
|
||||
assert_eq!(recv.wait().unwrap(), block_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetches_available_block_data() {
|
||||
let mut protocol = PolkadotProtocol::new(None);
|
||||
|
||||
let peer_a = 1;
|
||||
let parent_hash = [0; 32].into();
|
||||
|
||||
let block_data = BlockData(vec![1, 2, 3, 4]);
|
||||
let block_data_hash = block_data.hash();
|
||||
let para_id = 5.into();
|
||||
let candidate_receipt = CandidateReceipt {
|
||||
parachain_index: para_id,
|
||||
collator: [255; 32].into(),
|
||||
head_data: HeadData(vec![9, 9, 9]),
|
||||
signature: H512::from([1; 64]).into(),
|
||||
balance_uploads: Vec::new(),
|
||||
egress_queue_roots: Vec::new(),
|
||||
fees: 1_000_000,
|
||||
block_data_hash,
|
||||
};
|
||||
|
||||
let candidate_hash = candidate_receipt.hash();
|
||||
let av_store = ::av_store::Store::new_in_memory();
|
||||
|
||||
let status = Status { collating_for: None };
|
||||
|
||||
protocol.register_availability_store(av_store.clone());
|
||||
|
||||
av_store.make_available(::av_store::Data {
|
||||
relay_parent: parent_hash,
|
||||
parachain_id: para_id,
|
||||
candidate_hash,
|
||||
block_data: block_data.clone(),
|
||||
extrinsic: None,
|
||||
}).unwrap();
|
||||
|
||||
// connect peer A
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
protocol.on_connect(&mut ctx, peer_a, make_status(&status, Roles::FULL));
|
||||
}
|
||||
|
||||
// peer A asks for historic block data and gets response
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
on_message(&mut protocol, &mut ctx, peer_a, Message::RequestBlockData(1, parent_hash, candidate_hash));
|
||||
assert!(ctx.has_message(peer_a, Message::BlockData(1, Some(block_data))));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_bad_collator() {
|
||||
let mut protocol = PolkadotProtocol::new(None);
|
||||
|
||||
let who = 1;
|
||||
let account_id = [2; 32].into();
|
||||
|
||||
let status = Status { collating_for: Some((account_id, 5.into())) };
|
||||
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
protocol.on_connect(&mut ctx, who, make_status(&status, Roles::NONE));
|
||||
}
|
||||
|
||||
{
|
||||
let mut ctx = TestContext::default();
|
||||
protocol.disconnect_bad_collator(&mut ctx, account_id);
|
||||
assert!(ctx.disabled.contains(&who));
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-parachain"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Types and utilities for creating and working with parachains"
|
||||
|
||||
[dependencies]
|
||||
substrate-codec = { path = "../../substrate/codec", default-features = false }
|
||||
substrate-codec-derive = { path = "../../substrate/codec/derive", default-features = false }
|
||||
wasmi = { version = "0.4", optional = true }
|
||||
error-chain = { version = "0.12", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tiny-keccak = "1.4"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["substrate-codec/std", "wasmi", "error-chain"]
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Parachain
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,119 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines primitive types for creating or validating a parachain.
|
||||
//!
|
||||
//! When compiled with standard library support, this crate exports a `wasm`
|
||||
//! module that can be used to validate parachain WASM.
|
||||
//!
|
||||
//! ## Parachain WASM
|
||||
//!
|
||||
//! Polkadot parachain WASM is in the form of a module which imports a memory
|
||||
//! instance and exports a function `validate`.
|
||||
//!
|
||||
//! `validate` accepts as input two `i32` values, representing a pointer/length pair
|
||||
//! respectively, that encodes `ValidationParams`.
|
||||
//!
|
||||
//! `validate` returns an `i32` which is a pointer to a little-endian 32-bit integer denoting a length.
|
||||
//! Subtracting the length from the initial pointer will give a new pointer to the actual return data,
|
||||
//!
|
||||
//! ASCII-diagram demonstrating the return data format:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! [return data][len (LE-u32)]
|
||||
//! ^~~returned pointer
|
||||
//! ```
|
||||
//!
|
||||
//! The `load_params` and `write_result` functions provide utilities for setting up
|
||||
//! a parachain WASM module in Rust.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg_attr(not(feature = "std"), feature(alloc))]
|
||||
|
||||
/// Re-export of substrate-codec.
|
||||
pub extern crate substrate_codec as codec;
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_codec_derive;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate core;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate wasmi;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub mod wasm;
|
||||
|
||||
/// Validation parameters for evaluating the parachain validity function.
|
||||
// TODO: consolidated ingress and balance downloads
|
||||
#[derive(PartialEq, Eq, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct ValidationParams {
|
||||
/// The collation body.
|
||||
pub block_data: Vec<u8>,
|
||||
/// Previous head-data.
|
||||
pub parent_head: Vec<u8>,
|
||||
}
|
||||
|
||||
/// The result of parachain validation.
|
||||
// TODO: egress and balance uploads
|
||||
#[derive(PartialEq, Eq, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct ValidationResult {
|
||||
/// New head data that should be included in the relay chain state.
|
||||
pub head_data: Vec<u8>
|
||||
}
|
||||
|
||||
/// Load the validation params from memory when implementing a Rust parachain.
|
||||
///
|
||||
/// Offset and length must have been provided by the validation
|
||||
/// function's entry point.
|
||||
pub unsafe fn load_params(offset: usize, len: usize) -> ValidationParams {
|
||||
let mut slice = ::core::slice::from_raw_parts(offset as *const u8, len);
|
||||
|
||||
ValidationParams::decode(&mut slice).expect("Invalid input data")
|
||||
}
|
||||
|
||||
/// Allocate the validation result in memory, getting the return-pointer back.
|
||||
///
|
||||
/// As described in the crate docs, this is a pointer to the appended length
|
||||
/// of the vector.
|
||||
pub fn write_result(result: ValidationResult) -> usize {
|
||||
let mut encoded = result.encode();
|
||||
let len = encoded.len();
|
||||
|
||||
assert!(len <= u32::max_value() as usize, "Len too large for parachain-WASM abi");
|
||||
(len as u32).using_encoded(|s| encoded.extend(s));
|
||||
|
||||
// do not alter `encoded` beyond this point. may reallocate.
|
||||
let end_ptr = &encoded[len] as *const u8 as usize;
|
||||
|
||||
// leak so it doesn't get zeroed.
|
||||
::core::mem::forget(encoded);
|
||||
end_ptr
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! WASM re-execution of a parachain candidate.
|
||||
//! In the context of relay-chain candidate evaluation, there are some additional
|
||||
//! steps to ensure that the provided input parameters are correct.
|
||||
//! Assuming the parameters are correct, this module provides a wrapper around
|
||||
//! a WASM VM for re-execution of a parachain candidate.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
use wasmi::{self, Module, ModuleInstance, MemoryInstance, MemoryDescriptor, MemoryRef, ModuleImportResolver};
|
||||
use wasmi::{memory_units, RuntimeValue};
|
||||
use wasmi::Error as WasmError;
|
||||
use wasmi::memory_units::{Bytes, Pages, RoundUpTo};
|
||||
|
||||
use super::{ValidationParams, ValidationResult};
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
error_chain! {
|
||||
types { Error, ErrorKind, ResultExt; }
|
||||
foreign_links {
|
||||
Wasm(WasmError);
|
||||
}
|
||||
errors {
|
||||
/// Call data too big. WASM32 only has a 32-bit address space.
|
||||
ParamsTooLarge(len: usize) {
|
||||
description("Validation parameters took up too much space to execute in WASM"),
|
||||
display("Validation parameters took up {} bytes, max allowed by WASM is {}", len, i32::max_value()),
|
||||
}
|
||||
/// Bad return data or type.
|
||||
BadReturn {
|
||||
description("Validation function returned invalid data."),
|
||||
display("Validation function returned invalid data."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Resolver {
|
||||
max_memory: u32, // in pages.
|
||||
memory: RefCell<Option<MemoryRef>>,
|
||||
}
|
||||
|
||||
impl ModuleImportResolver for Resolver {
|
||||
fn resolve_memory(
|
||||
&self,
|
||||
field_name: &str,
|
||||
descriptor: &MemoryDescriptor,
|
||||
) -> Result<MemoryRef, WasmError> {
|
||||
if field_name == "memory" {
|
||||
let effective_max = descriptor.maximum().unwrap_or(self.max_memory);
|
||||
if descriptor.initial() > self.max_memory || effective_max > self.max_memory {
|
||||
Err(WasmError::Instantiation("Module requested too much memory".to_owned()))
|
||||
} else {
|
||||
let mem = MemoryInstance::alloc(
|
||||
memory_units::Pages(descriptor.initial() as usize),
|
||||
descriptor.maximum().map(|x| memory_units::Pages(x as usize)),
|
||||
)?;
|
||||
*self.memory.borrow_mut() = Some(mem.clone());
|
||||
Ok(mem)
|
||||
}
|
||||
} else {
|
||||
Err(WasmError::Instantiation("Memory imported under unknown name".to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate a candidate under the given validation code.
|
||||
///
|
||||
/// This will fail if the validation code is not a proper parachain validation module.
|
||||
pub fn validate_candidate(validation_code: &[u8], params: ValidationParams) -> Result<ValidationResult, Error> {
|
||||
use wasmi::LINEAR_MEMORY_PAGE_SIZE;
|
||||
|
||||
// maximum memory in bytes
|
||||
const MAX_MEM: u32 = 1024 * 1024 * 1024; // 1 GiB
|
||||
|
||||
// instantiate the module.
|
||||
let (module, memory) = {
|
||||
let module = Module::from_buffer(validation_code)?;
|
||||
|
||||
let module_resolver = Resolver {
|
||||
max_memory: MAX_MEM / LINEAR_MEMORY_PAGE_SIZE.0 as u32,
|
||||
memory: RefCell::new(None),
|
||||
};
|
||||
|
||||
let module = ModuleInstance::new(
|
||||
&module,
|
||||
&wasmi::ImportsBuilder::new().with_resolver("env", &module_resolver),
|
||||
)?.run_start(&mut wasmi::NopExternals).map_err(WasmError::Trap)?;
|
||||
|
||||
let memory = module_resolver.memory.borrow()
|
||||
.as_ref()
|
||||
.ok_or_else(|| WasmError::Instantiation("No imported memory instance".to_owned()))?
|
||||
.clone();
|
||||
|
||||
(module, memory)
|
||||
};
|
||||
|
||||
// allocate call data in memory.
|
||||
let (offset, len) = {
|
||||
let encoded_call_data = params.encode();
|
||||
|
||||
// hard limit from WASM.
|
||||
if encoded_call_data.len() > i32::max_value() as usize {
|
||||
bail!(ErrorKind::ParamsTooLarge(encoded_call_data.len()));
|
||||
}
|
||||
|
||||
// allocate sufficient amount of wasm pages to fit encoded call data.
|
||||
let call_data_pages: Pages = Bytes(encoded_call_data.len()).round_up_to();
|
||||
let allocated_mem_start: Bytes = memory.grow(call_data_pages)?.into();
|
||||
|
||||
memory.set(allocated_mem_start.0 as u32, &encoded_call_data)
|
||||
.expect(
|
||||
"enough memory allocated just before this; \
|
||||
copying never fails if memory is large enough; qed"
|
||||
);
|
||||
|
||||
(allocated_mem_start.0, encoded_call_data.len())
|
||||
};
|
||||
|
||||
let output = module.invoke_export(
|
||||
"validate",
|
||||
&[RuntimeValue::I32(offset as i32), RuntimeValue::I32(len as i32)],
|
||||
&mut wasmi::NopExternals,
|
||||
)?;
|
||||
|
||||
match output {
|
||||
Some(RuntimeValue::I32(len_offset)) => {
|
||||
let len_offset = len_offset as u32;
|
||||
|
||||
let mut len_bytes = [0u8; 4];
|
||||
memory.get_into(len_offset, &mut len_bytes)?;
|
||||
|
||||
let len = u32::decode(&mut &len_bytes[..])
|
||||
.ok_or_else(|| ErrorKind::BadReturn)?;
|
||||
|
||||
let return_offset = if len > len_offset {
|
||||
bail!(ErrorKind::BadReturn);
|
||||
} else {
|
||||
len_offset - len
|
||||
};
|
||||
|
||||
// TODO: optimize when `wasmi` lets you inspect memory with a closure.
|
||||
let raw_return = memory.get(return_offset, len as usize)?;
|
||||
ValidationResult::decode(&mut &raw_return[..])
|
||||
.ok_or_else(|| ErrorKind::BadReturn)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
_ => bail!(ErrorKind::BadReturn),
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Basic parachain that adds a number as part of its state.
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_codec_derive;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate polkadot_parachain as parachain;
|
||||
extern crate tiny_keccak;
|
||||
|
||||
use parachain::ValidationParams;
|
||||
use codec::{Decode, Encode};
|
||||
|
||||
/// Head data for this parachain.
|
||||
#[derive(Default, Clone, Encode, Decode)]
|
||||
struct HeadData {
|
||||
/// Block number
|
||||
number: u64,
|
||||
/// parent block keccak256
|
||||
parent_hash: [u8; 32],
|
||||
/// hash of post-execution state.
|
||||
post_state: [u8; 32],
|
||||
}
|
||||
|
||||
/// Block data for this parachain.
|
||||
#[derive(Default, Clone, Encode, Decode)]
|
||||
struct BlockData {
|
||||
/// State to begin from.
|
||||
state: u64,
|
||||
/// Amount to add (overflowing)
|
||||
add: u64,
|
||||
}
|
||||
|
||||
const TEST_CODE: &[u8] = include_bytes!("res/adder.wasm");
|
||||
|
||||
fn hash_state(state: u64) -> [u8; 32] {
|
||||
::tiny_keccak::keccak256(state.encode().as_slice())
|
||||
}
|
||||
|
||||
fn hash_head(head: &HeadData) -> [u8; 32] {
|
||||
::tiny_keccak::keccak256(head.encode().as_slice())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_good_on_parent() {
|
||||
let parent_head = HeadData {
|
||||
number: 0,
|
||||
parent_hash: [0; 32],
|
||||
post_state: hash_state(0),
|
||||
};
|
||||
|
||||
let block_data = BlockData {
|
||||
state: 0,
|
||||
add: 512,
|
||||
};
|
||||
|
||||
let ret = parachain::wasm::validate_candidate(TEST_CODE, ValidationParams {
|
||||
parent_head: parent_head.encode(),
|
||||
block_data: block_data.encode(),
|
||||
}).unwrap();
|
||||
|
||||
let new_head = HeadData::decode(&mut &ret.head_data[..]).unwrap();
|
||||
|
||||
assert_eq!(new_head.number, 1);
|
||||
assert_eq!(new_head.parent_hash, hash_head(&parent_head));
|
||||
assert_eq!(new_head.post_state, hash_state(512));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_good_chain_on_parent() {
|
||||
let mut number = 0;
|
||||
let mut parent_hash = [0; 32];
|
||||
let mut last_state = 0;
|
||||
|
||||
for add in 0..10 {
|
||||
let parent_head = HeadData {
|
||||
number,
|
||||
parent_hash,
|
||||
post_state: hash_state(last_state),
|
||||
};
|
||||
|
||||
let block_data = BlockData {
|
||||
state: last_state,
|
||||
add,
|
||||
};
|
||||
|
||||
let ret = parachain::wasm::validate_candidate(TEST_CODE, ValidationParams {
|
||||
parent_head: parent_head.encode(),
|
||||
block_data: block_data.encode(),
|
||||
}).unwrap();
|
||||
|
||||
let new_head = HeadData::decode(&mut &ret.head_data[..]).unwrap();
|
||||
|
||||
assert_eq!(new_head.number, number + 1);
|
||||
assert_eq!(new_head.parent_hash, hash_head(&parent_head));
|
||||
assert_eq!(new_head.post_state, hash_state(last_state + add));
|
||||
|
||||
number += 1;
|
||||
parent_hash = hash_head(&new_head);
|
||||
last_state += add;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_bad_on_parent() {
|
||||
let parent_head = HeadData {
|
||||
number: 0,
|
||||
parent_hash: [0; 32],
|
||||
post_state: hash_state(0),
|
||||
};
|
||||
|
||||
let block_data = BlockData {
|
||||
state: 256, // start state is wrong.
|
||||
add: 256,
|
||||
};
|
||||
|
||||
let _ret = parachain::wasm::validate_candidate(TEST_CODE, ValidationParams {
|
||||
parent_head: parent_head.encode(),
|
||||
block_data: block_data.encode(),
|
||||
}).unwrap_err();
|
||||
}
|
||||
Binary file not shown.
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-primitives"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", default_features = false }
|
||||
serde_derive = { version = "1.0", optional = true }
|
||||
substrate-codec = { path = "../../substrate/codec", default_features = false }
|
||||
substrate-codec-derive = { path = "../../substrate/codec/derive", default_features = false }
|
||||
substrate-primitives = { path = "../../substrate/primitives", default_features = false }
|
||||
substrate-runtime-std = { path = "../../substrate/runtime-std", default_features = false }
|
||||
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives", default_features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-serializer = { path = "../../substrate/serializer" }
|
||||
pretty_assertions = "0.4"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"substrate-codec/std",
|
||||
"substrate-primitives/std",
|
||||
"substrate-runtime-std/std",
|
||||
"substrate-runtime-primitives/std",
|
||||
"serde_derive",
|
||||
"serde/std",
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot primitives
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,123 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Shareable Polkadot types.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg_attr(not(feature = "std"), feature(alloc))]
|
||||
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate substrate_runtime_primitives as runtime_primitives;
|
||||
extern crate substrate_runtime_std as rstd;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate substrate_serializer;
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_codec_derive;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate serde;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use primitives::bytes;
|
||||
|
||||
use rstd::prelude::*;
|
||||
use runtime_primitives::traits::BlakeTwo256;
|
||||
use runtime_primitives::generic;
|
||||
|
||||
pub mod parachain;
|
||||
|
||||
/// Block header type as expected by this runtime.
|
||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256, Log>;
|
||||
|
||||
/// Opaque, encoded, unchecked extrinsic.
|
||||
pub type UncheckedExtrinsic = Vec<u8>;
|
||||
|
||||
/// A "future-proof" block type for Polkadot. This will be resilient to upgrades in transaction
|
||||
/// format, because it doesn't attempt to decode extrinsics.
|
||||
///
|
||||
/// Specialized code needs to link to (at least one version of) the runtime directly
|
||||
/// in order to handle the extrinsics within.
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
|
||||
/// An index to a block.
|
||||
/// 32-bits will allow for 136 years of blocks assuming 1 block per second.
|
||||
/// TODO: switch to u32
|
||||
pub type BlockNumber = u64;
|
||||
|
||||
/// Alias to Ed25519 pubkey that identifies an account on the relay chain.
|
||||
pub type AccountId = primitives::hash::H256;
|
||||
|
||||
/// The type for looking up accounts. We don't expect more than 4 billion of them, but you
|
||||
/// never know...
|
||||
pub type AccountIndex = u64;
|
||||
|
||||
/// The Ed25519 pub key of an session that belongs to an authority of the relay chain. This is
|
||||
/// exactly equivalent to what the substrate calls an "authority".
|
||||
pub type SessionKey = primitives::AuthorityId;
|
||||
|
||||
/// Indentifier for a chain. 32-bit should be plenty.
|
||||
pub type ChainId = u32;
|
||||
|
||||
/// A hash of some data used by the relay chain.
|
||||
pub type Hash = primitives::H256;
|
||||
|
||||
/// Index of a transaction in the relay chain. 32-bit should be plenty.
|
||||
pub type Index = u32;
|
||||
|
||||
/// Alias to 512-bit hash when used in the context of a signature on the relay chain.
|
||||
/// Equipped with logic for possibly "unsigned" messages.
|
||||
pub type Signature = runtime_primitives::MaybeUnsigned<runtime_primitives::Ed25519Signature>;
|
||||
|
||||
/// A timestamp: seconds since the unix epoch.
|
||||
pub type Timestamp = u64;
|
||||
|
||||
/// The balance of an account.
|
||||
/// 128-bits (or 38 significant decimal figures) will allow for 10m currency (10^7) at a resolution
|
||||
/// to all for one second's worth of an annualised 50% reward be paid to a unit holder (10^11 unit
|
||||
/// denomination), or 10^18 total atomic units, to grow at 50%/year for 51 years (10^9 multiplier)
|
||||
/// for an eventual total of 10^27 units (27 significant decimal figures).
|
||||
/// We round denomination to 10^12 (12 sdf), and leave the other redundancy at the upper end so
|
||||
/// that 32 bits may be multiplied with a balance in 128 bits without worrying about overflow.
|
||||
pub type Balance = u128;
|
||||
|
||||
/// "generic" block ID for the future-proof block type.
|
||||
// TODO: parameterize blockid only as necessary.
|
||||
pub type BlockId = generic::BlockId<Block>;
|
||||
|
||||
/// A log entry in the block.
|
||||
#[derive(PartialEq, Eq, Clone, Default, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct Log(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
|
||||
/// Inherent data to include in a block.
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct InherentData {
|
||||
/// Current timestamp.
|
||||
pub timestamp: Timestamp,
|
||||
/// Parachain heads update.
|
||||
pub parachain_heads: Vec<::parachain::CandidateReceipt>,
|
||||
/// Indices of offline validators.
|
||||
pub offline_indices: Vec<u32>,
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot parachain types.
|
||||
|
||||
use rstd::prelude::*;
|
||||
use rstd::cmp::Ordering;
|
||||
use super::Hash;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use primitives::bytes;
|
||||
|
||||
/// Signature on candidate's block data by a collator.
|
||||
pub type CandidateSignature = ::runtime_primitives::Ed25519Signature;
|
||||
|
||||
/// Unique identifier of a parachain.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct Id(u32);
|
||||
|
||||
impl From<Id> for u32 {
|
||||
fn from(x: Id) -> Self { x.0 }
|
||||
}
|
||||
|
||||
impl From<u32> for Id {
|
||||
fn from(x: u32) -> Self { Id(x) }
|
||||
}
|
||||
|
||||
impl Id {
|
||||
/// Convert this Id into its inner representation.
|
||||
pub fn into_inner(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier for a chain, either one of a number of parachains or the relay chain.
|
||||
#[derive(Copy, Clone, PartialEq, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub enum Chain {
|
||||
/// The relay chain.
|
||||
Relay,
|
||||
/// A parachain of the given index.
|
||||
Parachain(Id),
|
||||
}
|
||||
|
||||
/// The duty roster specifying what jobs each validator must do.
|
||||
#[derive(Clone, PartialEq, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Default, Debug))]
|
||||
pub struct DutyRoster {
|
||||
/// Lookup from validator index to chain on which that validator has a duty to validate.
|
||||
pub validator_duty: Vec<Chain>,
|
||||
/// Lookup from validator index to chain on which that validator has a duty to guarantee
|
||||
/// availability.
|
||||
pub guarantor_duty: Vec<Chain>,
|
||||
}
|
||||
|
||||
/// Extrinsic data for a parachain.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
|
||||
#[cfg_attr(feature = "std", serde(deny_unknown_fields))]
|
||||
pub struct Extrinsic;
|
||||
|
||||
/// Candidate receipt type.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
|
||||
#[cfg_attr(feature = "std", serde(deny_unknown_fields))]
|
||||
pub struct CandidateReceipt {
|
||||
/// The ID of the parachain this is a candidate for.
|
||||
pub parachain_index: Id,
|
||||
/// The collator's relay-chain account ID
|
||||
pub collator: super::AccountId,
|
||||
/// Signature on blake2-256 of the block data by collator.
|
||||
pub signature: CandidateSignature,
|
||||
/// The head-data
|
||||
pub head_data: HeadData,
|
||||
/// Balance uploads to the relay chain.
|
||||
pub balance_uploads: Vec<(super::AccountId, u64)>,
|
||||
/// Egress queue roots.
|
||||
pub egress_queue_roots: Vec<(Id, Hash)>,
|
||||
/// Fees paid from the chain to the relay chain validators
|
||||
pub fees: u64,
|
||||
/// blake2-256 Hash of block data.
|
||||
pub block_data_hash: Hash,
|
||||
}
|
||||
|
||||
impl CandidateReceipt {
|
||||
/// Get the blake2_256 hash
|
||||
#[cfg(feature = "std")]
|
||||
pub fn hash(&self) -> Hash {
|
||||
use runtime_primitives::traits::{BlakeTwo256, Hash};
|
||||
BlakeTwo256::hash_of(self)
|
||||
}
|
||||
|
||||
/// Check integrity vs. provided block data.
|
||||
pub fn check_signature(&self) -> Result<(), ()> {
|
||||
use runtime_primitives::traits::Verify;
|
||||
|
||||
if self.signature.verify(&self.block_data_hash.0[..], &self.collator) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for CandidateReceipt {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for CandidateReceipt {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
// TODO: compare signatures or something more sane
|
||||
self.parachain_index.cmp(&other.parachain_index)
|
||||
.then_with(|| self.head_data.cmp(&other.head_data))
|
||||
}
|
||||
}
|
||||
|
||||
/// A full collation.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
|
||||
#[cfg_attr(feature = "std", serde(deny_unknown_fields))]
|
||||
pub struct Collation {
|
||||
/// Block data.
|
||||
pub block_data: BlockData,
|
||||
/// Candidate receipt itself.
|
||||
pub receipt: CandidateReceipt,
|
||||
}
|
||||
|
||||
/// Parachain ingress queue message.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct Message(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
|
||||
/// Consolidated ingress queue data.
|
||||
///
|
||||
/// This is just an ordered vector of other parachains' egress queues,
|
||||
/// obtained according to the routing rules.
|
||||
#[derive(Default, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct ConsolidatedIngress(pub Vec<(Id, Vec<Message>)>);
|
||||
|
||||
/// Parachain block data.
|
||||
///
|
||||
/// contains everything required to validate para-block, may contain block and witness data
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct BlockData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
|
||||
impl BlockData {
|
||||
/// Compute hash of block data.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn hash(&self) -> Hash {
|
||||
use runtime_primitives::traits::{BlakeTwo256, Hash};
|
||||
BlakeTwo256::hash(&self.0[..])
|
||||
}
|
||||
}
|
||||
/// Parachain header raw bytes wrapper type.
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct Header(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
|
||||
/// Parachain head data included in the chain.
|
||||
#[derive(PartialEq, Eq, Clone, PartialOrd, Ord, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct HeadData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
|
||||
/// Parachain validation code.
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct ValidationCode(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
|
||||
/// Activitiy bit field
|
||||
#[derive(PartialEq, Eq, Clone, Default, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct Activity(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
|
||||
/// Statements which can be made about parachain candidates.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
|
||||
pub enum Statement {
|
||||
/// Proposal of a parachain candidate.
|
||||
Candidate(CandidateReceipt),
|
||||
/// State that a parachain candidate is valid.
|
||||
Valid(Hash),
|
||||
/// Vote to commit to a candidate.
|
||||
Invalid(Hash),
|
||||
/// Vote to advance round after inactive primary.
|
||||
Available(Hash),
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-runtime"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
rustc-hex = "1.0"
|
||||
log = { version = "0.3", optional = true }
|
||||
serde = { version = "1.0", default_features = false }
|
||||
serde_derive = { version = "1.0", optional = true }
|
||||
safe-mix = { version = "1.0", default_features = false}
|
||||
polkadot-primitives = { path = "../primitives", default_features = false }
|
||||
substrate-codec = { path = "../../substrate/codec" }
|
||||
substrate-serializer = { path = "../../substrate/serializer" }
|
||||
substrate-runtime-std = { path = "../../substrate/runtime-std" }
|
||||
substrate-runtime-io = { path = "../../substrate/runtime-io" }
|
||||
substrate-runtime-support = { path = "../../substrate/runtime-support" }
|
||||
substrate-primitives = { path = "../../substrate/primitives" }
|
||||
substrate-keyring = { path = "../../substrate/keyring" }
|
||||
substrate-runtime-consensus = { path = "../../substrate/runtime/consensus" }
|
||||
substrate-runtime-council = { path = "../../substrate/runtime/council" }
|
||||
substrate-runtime-democracy = { path = "../../substrate/runtime/democracy" }
|
||||
substrate-runtime-executive = { path = "../../substrate/runtime/executive" }
|
||||
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
|
||||
substrate-runtime-session = { path = "../../substrate/runtime/session" }
|
||||
substrate-runtime-staking = { path = "../../substrate/runtime/staking" }
|
||||
substrate-runtime-system = { path = "../../substrate/runtime/system" }
|
||||
substrate-runtime-timestamp = { path = "../../substrate/runtime/timestamp" }
|
||||
substrate-runtime-version = { path = "../../substrate/runtime/version" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.1.0"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"polkadot-primitives/std",
|
||||
"substrate-codec/std",
|
||||
"substrate-primitives/std",
|
||||
"substrate-runtime-std/std",
|
||||
"substrate-runtime-io/std",
|
||||
"substrate-runtime-support/std",
|
||||
"substrate-runtime-consensus/std",
|
||||
"substrate-runtime-council/std",
|
||||
"substrate-runtime-democracy/std",
|
||||
"substrate-runtime-executive/std",
|
||||
"substrate-runtime-primitives/std",
|
||||
"substrate-runtime-session/std",
|
||||
"substrate-runtime-staking/std",
|
||||
"substrate-runtime-system/std",
|
||||
"substrate-runtime-timestamp/std",
|
||||
"substrate-runtime-version/std",
|
||||
"serde_derive",
|
||||
"serde/std",
|
||||
"log",
|
||||
"safe-mix/std"
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Runtime
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,118 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Typesafe block interaction.
|
||||
|
||||
use super::{Call, Block, TIMESTAMP_SET_POSITION, PARACHAINS_SET_POSITION, NOTE_OFFLINE_POSITION};
|
||||
use timestamp::Call as TimestampCall;
|
||||
use parachains::Call as ParachainsCall;
|
||||
use session::Call as SessionCall;
|
||||
use primitives::parachain::CandidateReceipt;
|
||||
|
||||
/// Provides a type-safe wrapper around a structurally valid block.
|
||||
pub struct CheckedBlock {
|
||||
inner: Block,
|
||||
file_line: Option<(&'static str, u32)>,
|
||||
}
|
||||
|
||||
impl CheckedBlock {
|
||||
/// Create a new checked block. Fails if the block is not structurally valid.
|
||||
pub fn new(block: Block) -> Result<Self, Block> {
|
||||
let has_timestamp = block.extrinsics.get(TIMESTAMP_SET_POSITION as usize).map_or(false, |xt| {
|
||||
!xt.is_signed() && match xt.extrinsic.function {
|
||||
Call::Timestamp(TimestampCall::set(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
if !has_timestamp { return Err(block) }
|
||||
|
||||
let has_heads = block.extrinsics.get(PARACHAINS_SET_POSITION as usize).map_or(false, |xt| {
|
||||
!xt.is_signed() && match xt.extrinsic.function {
|
||||
Call::Parachains(ParachainsCall::set_heads(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
if !has_heads { return Err(block) }
|
||||
|
||||
Ok(CheckedBlock {
|
||||
inner: block,
|
||||
file_line: None,
|
||||
})
|
||||
}
|
||||
|
||||
// Creates a new checked block, asserting that it is valid.
|
||||
#[doc(hidden)]
|
||||
pub fn new_unchecked(block: Block, file: &'static str, line: u32) -> Self {
|
||||
CheckedBlock {
|
||||
inner: block,
|
||||
file_line: Some((file, line)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the timestamp from the block.
|
||||
pub fn timestamp(&self) -> ::primitives::Timestamp {
|
||||
let x = self.inner.extrinsics.get(TIMESTAMP_SET_POSITION as usize).and_then(|xt| match xt.extrinsic.function {
|
||||
Call::Timestamp(TimestampCall::set(x)) => Some(x),
|
||||
_ => None
|
||||
});
|
||||
|
||||
match x {
|
||||
Some(x) => x,
|
||||
None => panic!("Invalid polkadot block asserted at {:?}", self.file_line),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the parachain heads from the block.
|
||||
pub fn parachain_heads(&self) -> &[CandidateReceipt] {
|
||||
let x = self.inner.extrinsics.get(PARACHAINS_SET_POSITION as usize).and_then(|xt| match xt.extrinsic.function {
|
||||
Call::Parachains(ParachainsCall::set_heads(ref x)) => Some(&x[..]),
|
||||
_ => None
|
||||
});
|
||||
|
||||
match x {
|
||||
Some(x) => x,
|
||||
None => panic!("Invalid polkadot block asserted at {:?}", self.file_line),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the noted offline validator indices (if any) from the block.
|
||||
pub fn noted_offline(&self) -> &[u32] {
|
||||
self.inner.extrinsics.get(NOTE_OFFLINE_POSITION as usize).and_then(|xt| match xt.extrinsic.function {
|
||||
Call::Session(SessionCall::note_offline(ref x)) => Some(&x[..]),
|
||||
_ => None,
|
||||
}).unwrap_or(&[])
|
||||
}
|
||||
|
||||
/// Convert into inner block.
|
||||
pub fn into_inner(self) -> Block { self.inner }
|
||||
}
|
||||
|
||||
impl ::std::ops::Deref for CheckedBlock {
|
||||
type Target = Block;
|
||||
|
||||
fn deref(&self) -> &Block { &self.inner }
|
||||
}
|
||||
|
||||
/// Assert that a block is structurally valid. May lead to panic in the future
|
||||
/// in case it isn't.
|
||||
#[macro_export]
|
||||
macro_rules! assert_polkadot_block {
|
||||
($block: expr) => {
|
||||
$crate::CheckedBlock::new_unchecked($block, file!(), line!())
|
||||
}
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The Polkadot runtime. This can be compiled with ``#[no_std]`, ready for Wasm.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate serde;
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_runtime_io as runtime_io;
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_runtime_support;
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_runtime_primitives as runtime_primitives;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate hex_literal;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate substrate_serializer;
|
||||
|
||||
extern crate substrate_primitives;
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_runtime_std as rstd;
|
||||
|
||||
extern crate polkadot_primitives as primitives;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_runtime_consensus as consensus;
|
||||
extern crate substrate_runtime_council as council;
|
||||
extern crate substrate_runtime_democracy as democracy;
|
||||
extern crate substrate_runtime_executive as executive;
|
||||
extern crate substrate_runtime_session as session;
|
||||
extern crate substrate_runtime_staking as staking;
|
||||
extern crate substrate_runtime_system as system;
|
||||
extern crate substrate_runtime_timestamp as timestamp;
|
||||
#[macro_use]
|
||||
extern crate substrate_runtime_version as version;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod checked_block;
|
||||
mod parachains;
|
||||
mod utils;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use checked_block::CheckedBlock;
|
||||
pub use utils::{inherent_extrinsics, check_extrinsic};
|
||||
pub use staking::address::Address as RawAddress;
|
||||
|
||||
use primitives::{AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, Log, SessionKey, Signature};
|
||||
use runtime_primitives::{generic, traits::{HasPublicAux, BlakeTwo256, Convert}};
|
||||
use version::RuntimeVersion;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use runtime_primitives::BuildStorage;
|
||||
|
||||
pub use consensus::Call as ConsensusCall;
|
||||
pub use timestamp::Call as TimestampCall;
|
||||
pub use parachains::Call as ParachainsCall;
|
||||
pub use primitives::Header;
|
||||
|
||||
/// The position of the timestamp set extrinsic.
|
||||
pub const TIMESTAMP_SET_POSITION: u32 = 0;
|
||||
/// The position of the parachains set extrinsic.
|
||||
pub const PARACHAINS_SET_POSITION: u32 = 1;
|
||||
/// The position of the offline nodes noting extrinsic.
|
||||
pub const NOTE_OFFLINE_POSITION: u32 = 2;
|
||||
|
||||
/// The address format for describing accounts.
|
||||
pub type Address = staking::Address<Concrete>;
|
||||
/// Block Id type for this block.
|
||||
pub type BlockId = generic::BlockId<Block>;
|
||||
/// Unchecked extrinsic type as expected by this runtime.
|
||||
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Index, Call, Signature>;
|
||||
/// Extrinsic type as expected by this runtime. This is not the type that is signed.
|
||||
pub type Extrinsic = generic::Extrinsic<Address, Index, Call>;
|
||||
/// Extrinsic type that is signed.
|
||||
pub type BareExtrinsic = generic::Extrinsic<AccountId, Index, Call>;
|
||||
/// Block type as expected by this runtime.
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
|
||||
/// Concrete runtime type used to parameterize the various modules.
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
|
||||
pub struct Concrete;
|
||||
|
||||
/// Polkadot runtime version.
|
||||
pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: ver_str!("polkadot"),
|
||||
impl_name: ver_str!("parity-polkadot"),
|
||||
authoring_version: 1,
|
||||
spec_version: 101,
|
||||
impl_version: 0,
|
||||
};
|
||||
|
||||
impl version::Trait for Concrete {
|
||||
const VERSION: RuntimeVersion = VERSION;
|
||||
}
|
||||
|
||||
/// Version module for this concrete runtime.
|
||||
pub type Version = version::Module<Concrete>;
|
||||
|
||||
impl HasPublicAux for Concrete {
|
||||
type PublicAux = AccountId; // TODO: Option<AccountId>
|
||||
}
|
||||
|
||||
impl system::Trait for Concrete {
|
||||
type Index = Index;
|
||||
type BlockNumber = BlockNumber;
|
||||
type Hash = Hash;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = generic::Digest<Log>;
|
||||
type AccountId = AccountId;
|
||||
type Header = Header;
|
||||
}
|
||||
/// System module for this concrete runtime.
|
||||
pub type System = system::Module<Concrete>;
|
||||
|
||||
impl consensus::Trait for Concrete {
|
||||
type PublicAux = <Concrete as HasPublicAux>::PublicAux;
|
||||
type SessionKey = SessionKey;
|
||||
}
|
||||
/// Consensus module for this concrete runtime.
|
||||
pub type Consensus = consensus::Module<Concrete>;
|
||||
|
||||
impl timestamp::Trait for Concrete {
|
||||
const TIMESTAMP_SET_POSITION: u32 = TIMESTAMP_SET_POSITION;
|
||||
type Moment = u64;
|
||||
}
|
||||
/// Timestamp module for this concrete runtime.
|
||||
pub type Timestamp = timestamp::Module<Concrete>;
|
||||
|
||||
/// Session key conversion.
|
||||
pub struct SessionKeyConversion;
|
||||
impl Convert<AccountId, SessionKey> for SessionKeyConversion {
|
||||
fn convert(a: AccountId) -> SessionKey {
|
||||
a.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl session::Trait for Concrete {
|
||||
const NOTE_OFFLINE_POSITION: u32 = NOTE_OFFLINE_POSITION;
|
||||
type ConvertAccountIdToSessionKey = SessionKeyConversion;
|
||||
type OnSessionChange = Staking;
|
||||
}
|
||||
/// Session module for this concrete runtime.
|
||||
pub type Session = session::Module<Concrete>;
|
||||
|
||||
impl staking::Trait for Concrete {
|
||||
type Balance = Balance;
|
||||
type AccountIndex = AccountIndex;
|
||||
type OnAccountKill = ();
|
||||
}
|
||||
/// Staking module for this concrete runtime.
|
||||
pub type Staking = staking::Module<Concrete>;
|
||||
|
||||
impl democracy::Trait for Concrete {
|
||||
type Proposal = PrivCall;
|
||||
}
|
||||
/// Democracy module for this concrete runtime.
|
||||
pub type Democracy = democracy::Module<Concrete>;
|
||||
|
||||
impl council::Trait for Concrete {}
|
||||
/// Council module for this concrete runtime.
|
||||
pub type Council = council::Module<Concrete>;
|
||||
/// Council voting module for this concrete runtime.
|
||||
pub type CouncilVoting = council::voting::Module<Concrete>;
|
||||
|
||||
impl parachains::Trait for Concrete {
|
||||
const SET_POSITION: u32 = PARACHAINS_SET_POSITION;
|
||||
|
||||
type PublicAux = <Concrete as HasPublicAux>::PublicAux;
|
||||
}
|
||||
pub type Parachains = parachains::Module<Concrete>;
|
||||
|
||||
impl_outer_dispatch! {
|
||||
/// Call type for polkadot transactions.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
|
||||
pub enum Call where aux: <Concrete as HasPublicAux>::PublicAux {
|
||||
Consensus = 0,
|
||||
Session = 1,
|
||||
Staking = 2,
|
||||
Timestamp = 3,
|
||||
Democracy = 5,
|
||||
Council = 6,
|
||||
CouncilVoting = 7,
|
||||
Parachains = 8,
|
||||
}
|
||||
|
||||
/// Internal calls.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
|
||||
pub enum PrivCall {
|
||||
Consensus = 0,
|
||||
Session = 1,
|
||||
Staking = 2,
|
||||
Democracy = 5,
|
||||
Council = 6,
|
||||
CouncilVoting = 7,
|
||||
Parachains = 8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executive: handles dispatch to the various modules.
|
||||
pub type Executive = executive::Executive<Concrete, Block, Staking, Staking,
|
||||
(((((((), Parachains), Council), Democracy), Staking), Session), Timestamp)>;
|
||||
|
||||
impl_outer_config! {
|
||||
pub struct GenesisConfig for Concrete {
|
||||
ConsensusConfig => consensus,
|
||||
SystemConfig => system,
|
||||
SessionConfig => session,
|
||||
StakingConfig => staking,
|
||||
DemocracyConfig => democracy,
|
||||
CouncilConfig => council,
|
||||
TimestampConfig => timestamp,
|
||||
ParachainsConfig => parachains,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod api {
|
||||
impl_stubs!(
|
||||
version => |()| super::Version::version(),
|
||||
authorities => |()| super::Consensus::authorities(),
|
||||
initialise_block => |header| super::Executive::initialise_block(&header),
|
||||
apply_extrinsic => |extrinsic| super::Executive::apply_extrinsic(extrinsic),
|
||||
execute_block => |block| super::Executive::execute_block(block),
|
||||
finalise_block => |()| super::Executive::finalise_block(),
|
||||
inherent_extrinsics => |inherent| super::inherent_extrinsics(inherent),
|
||||
validator_count => |()| super::Session::validator_count(),
|
||||
validators => |()| super::Session::validators()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use substrate_primitives as primitives;
|
||||
use codec::{Encode, Decode};
|
||||
use substrate_primitives::hexdisplay::HexDisplay;
|
||||
use substrate_serializer as ser;
|
||||
use runtime_primitives::traits::{Digest as DigestT, Header as HeaderT};
|
||||
type Digest = generic::Digest<Log>;
|
||||
|
||||
#[test]
|
||||
fn test_header_serialization() {
|
||||
let header = Header {
|
||||
parent_hash: 5.into(),
|
||||
number: 67,
|
||||
state_root: 3.into(),
|
||||
extrinsics_root: 6.into(),
|
||||
digest: { let mut d = Digest::default(); d.push(Log(vec![1])); d },
|
||||
};
|
||||
|
||||
assert_eq!(ser::to_string_pretty(&header), r#"{
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000005",
|
||||
"number": 67,
|
||||
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000003",
|
||||
"extrinsicsRoot": "0x0000000000000000000000000000000000000000000000000000000000000006",
|
||||
"digest": {
|
||||
"logs": [
|
||||
"0x01"
|
||||
]
|
||||
}
|
||||
}"#);
|
||||
|
||||
let v = header.encode();
|
||||
assert_eq!(Header::decode(&mut &v[..]).unwrap(), header);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_encoding_round_trip() {
|
||||
let mut block = Block {
|
||||
header: Header::new(1, Default::default(), Default::default(), Default::default(), Default::default()),
|
||||
extrinsics: vec![
|
||||
UncheckedExtrinsic::new(
|
||||
generic::Extrinsic {
|
||||
function: Call::Timestamp(timestamp::Call::set(100_000_000)),
|
||||
signed: Default::default(),
|
||||
index: Default::default(),
|
||||
},
|
||||
Default::default(),
|
||||
)
|
||||
],
|
||||
};
|
||||
|
||||
let raw = block.encode();
|
||||
let decoded = Block::decode(&mut &raw[..]).unwrap();
|
||||
|
||||
assert_eq!(block, decoded);
|
||||
|
||||
block.extrinsics.push(UncheckedExtrinsic::new(
|
||||
generic::Extrinsic {
|
||||
function: Call::Staking(staking::Call::stake()),
|
||||
signed: Default::default(),
|
||||
index: 10101,
|
||||
},
|
||||
Default::default(),
|
||||
));
|
||||
|
||||
let raw = block.encode();
|
||||
let decoded = Block::decode(&mut &raw[..]).unwrap();
|
||||
|
||||
assert_eq!(block, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_encoding_substrate_round_trip() {
|
||||
let mut block = Block {
|
||||
header: Header::new(1, Default::default(), Default::default(), Default::default(), Default::default()),
|
||||
extrinsics: vec![
|
||||
UncheckedExtrinsic::new(
|
||||
generic::Extrinsic {
|
||||
function: Call::Timestamp(timestamp::Call::set(100_000_000)),
|
||||
signed: Default::default(),
|
||||
index: Default::default(),
|
||||
},
|
||||
Default::default(),
|
||||
)
|
||||
],
|
||||
};
|
||||
|
||||
block.extrinsics.push(UncheckedExtrinsic::new(
|
||||
generic::Extrinsic {
|
||||
function: Call::Staking(staking::Call::stake()),
|
||||
signed: Default::default(),
|
||||
index: 10101,
|
||||
},
|
||||
Default::default()
|
||||
));
|
||||
|
||||
let raw = block.encode();
|
||||
let decoded_primitive = ::primitives::Block::decode(&mut &raw[..]).unwrap();
|
||||
let encoded_primitive = decoded_primitive.encode();
|
||||
let decoded = Block::decode(&mut &encoded_primitive[..]).unwrap();
|
||||
|
||||
assert_eq!(block, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_unchecked() {
|
||||
let tx = UncheckedExtrinsic::new(
|
||||
Extrinsic {
|
||||
signed: AccountId::from([1; 32]).into(),
|
||||
index: 999,
|
||||
function: Call::Timestamp(TimestampCall::set(135135)),
|
||||
},
|
||||
runtime_primitives::Ed25519Signature(primitives::hash::H512([0; 64])).into()
|
||||
);
|
||||
|
||||
// 6f000000
|
||||
// ff0101010101010101010101010101010101010101010101010101010101010101
|
||||
// e7030000
|
||||
// 0300
|
||||
// df0f0200
|
||||
// 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
let v = Encode::encode(&tx);
|
||||
assert_eq!(&v[..], &hex!["6f000000ff0101010101010101010101010101010101010101010101010101010101010101e70300000300df0f02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"][..]);
|
||||
println!("{}", HexDisplay::from(&v));
|
||||
assert_eq!(UncheckedExtrinsic::decode(&mut &v[..]).unwrap(), tx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_checked() {
|
||||
let xt = Extrinsic {
|
||||
signed: AccountId::from(hex!["0d71d1a9cad6f2ab773435a7dec1bac019994d05d1dd5eb3108211dcf25c9d1e"]).into(),
|
||||
index: 0,
|
||||
function: Call::CouncilVoting(council::voting::Call::propose(Box::new(
|
||||
PrivCall::Consensus(consensus::PrivCall::set_code(
|
||||
vec![]
|
||||
))
|
||||
))),
|
||||
};
|
||||
let v = Encode::encode(&xt);
|
||||
assert_eq!(Extrinsic::decode(&mut &v[..]).unwrap(), xt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parachain_calls_are_privcall() {
|
||||
let _register = PrivCall::Parachains(parachains::PrivCall::register_parachain(0.into(), vec![1, 2, 3], vec![]));
|
||||
let _deregister = PrivCall::Parachains(parachains::PrivCall::deregister_parachain(0.into()));
|
||||
}
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Main parachains logic. For now this is just the determination of which validators do what.
|
||||
|
||||
use rstd::prelude::*;
|
||||
use codec::Decode;
|
||||
|
||||
use runtime_primitives::traits::{Hash, BlakeTwo256, Executable, RefInto, MaybeEmpty};
|
||||
use primitives::parachain::{Id, Chain, DutyRoster, CandidateReceipt};
|
||||
use {system, session};
|
||||
|
||||
use substrate_runtime_support::{StorageValue, StorageMap};
|
||||
use substrate_runtime_support::dispatch::Result;
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use rstd::marker::PhantomData;
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use {runtime_io, runtime_primitives};
|
||||
|
||||
pub trait Trait: system::Trait<Hash = ::primitives::Hash> + session::Trait {
|
||||
/// The position of the set_heads call in the block.
|
||||
const SET_POSITION: u32;
|
||||
|
||||
type PublicAux: RefInto<Self::AccountId> + MaybeEmpty;
|
||||
}
|
||||
|
||||
decl_module! {
|
||||
/// Parachains module.
|
||||
pub struct Module<T: Trait>;
|
||||
/// Call type for parachains.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub enum Call where aux: <T as Trait>::PublicAux {
|
||||
// provide candidate receipts for parachains, in ascending order by id.
|
||||
fn set_heads(aux, heads: Vec<CandidateReceipt>) -> Result = 0;
|
||||
}
|
||||
|
||||
/// Private calls for parachains.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub enum PrivCall {
|
||||
fn register_parachain(id: Id, code: Vec<u8>, initial_head_data: Vec<u8>) -> Result = 0;
|
||||
fn deregister_parachain(id: Id) -> Result = 1;
|
||||
}
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait>;
|
||||
// Vector of all parachain IDs.
|
||||
pub Parachains get(active_parachains): b"para:chains" => default Vec<Id>;
|
||||
// The parachains registered at present.
|
||||
pub Code get(parachain_code): b"para:code" => map [ Id => Vec<u8> ];
|
||||
// The heads of the parachains registered at present. these are kept sorted.
|
||||
pub Heads get(parachain_head): b"para:head" => map [ Id => Vec<u8> ];
|
||||
|
||||
// Did the parachain heads get updated in this block?
|
||||
DidUpdate: b"para:did" => default bool;
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Calculate the current block's duty roster using system's random seed.
|
||||
pub fn calculate_duty_roster() -> DutyRoster {
|
||||
let parachains = Self::active_parachains();
|
||||
let parachain_count = parachains.len();
|
||||
let validator_count = <session::Module<T>>::validator_count() as usize;
|
||||
let validators_per_parachain = if parachain_count != 0 { (validator_count - 1) / parachain_count } else { 0 };
|
||||
|
||||
let mut roles_val = (0..validator_count).map(|i| match i {
|
||||
i if i < parachain_count * validators_per_parachain => {
|
||||
let idx = i / validators_per_parachain;
|
||||
Chain::Parachain(parachains[idx].clone())
|
||||
}
|
||||
_ => Chain::Relay,
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
let mut roles_gua = roles_val.clone();
|
||||
|
||||
let mut random_seed = system::Module::<T>::random_seed().to_vec();
|
||||
random_seed.extend(b"validator_role_pairs");
|
||||
let mut seed = BlakeTwo256::hash(&random_seed);
|
||||
|
||||
// shuffle
|
||||
for i in 0..(validator_count - 1) {
|
||||
// 8 bytes of entropy used per cycle, 32 bytes entropy per hash
|
||||
let offset = (i * 8 % 32) as usize;
|
||||
|
||||
// number of roles remaining to select from.
|
||||
let remaining = (validator_count - i) as usize;
|
||||
|
||||
// 4 * 2 32-bit ints per 256-bit seed.
|
||||
let val_index = u32::decode(&mut &seed[offset..offset + 4]).expect("using 4 bytes for a 32-bit quantity") as usize % remaining;
|
||||
let gua_index = u32::decode(&mut &seed[offset + 4..offset + 8]).expect("using 4 bytes for a 32-bit quantity") as usize % remaining;
|
||||
|
||||
if offset == 24 {
|
||||
// into the last 8 bytes - rehash to gather new entropy
|
||||
seed = BlakeTwo256::hash(&seed);
|
||||
}
|
||||
|
||||
// exchange last item with randomly chosen first.
|
||||
roles_val.swap(remaining - 1, val_index);
|
||||
roles_gua.swap(remaining - 1, gua_index);
|
||||
}
|
||||
|
||||
DutyRoster {
|
||||
validator_duty: roles_val,
|
||||
guarantor_duty: roles_gua,
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a parachain with given code.
|
||||
/// Fails if given ID is already used.
|
||||
pub fn register_parachain(id: Id, code: Vec<u8>, initial_head_data: Vec<u8>) -> Result {
|
||||
let mut parachains = Self::active_parachains();
|
||||
match parachains.binary_search(&id) {
|
||||
Ok(_) => fail!("Parachain already exists"),
|
||||
Err(idx) => parachains.insert(idx, id),
|
||||
}
|
||||
|
||||
<Code<T>>::insert(id, code);
|
||||
<Parachains<T>>::put(parachains);
|
||||
<Heads<T>>::insert(id, initial_head_data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deregister a parachain with given id
|
||||
pub fn deregister_parachain(id: Id) -> Result {
|
||||
let mut parachains = Self::active_parachains();
|
||||
match parachains.binary_search(&id) {
|
||||
Ok(idx) => { parachains.remove(idx); }
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
<Code<T>>::remove(id);
|
||||
<Heads<T>>::remove(id);
|
||||
<Parachains<T>>::put(parachains);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_heads(aux: &<T as Trait>::PublicAux, heads: Vec<CandidateReceipt>) -> Result {
|
||||
ensure!(aux.is_empty(), "set_heads must not be signed");
|
||||
ensure!(!<DidUpdate<T>>::exists(), "Parachain heads must be updated only once in the block");
|
||||
ensure!(
|
||||
<system::Module<T>>::extrinsic_index() == T::SET_POSITION,
|
||||
"Parachain heads update extrinsic must be at position {} in the block"
|
||||
// , T::SET_POSITION
|
||||
);
|
||||
|
||||
let active_parachains = Self::active_parachains();
|
||||
let mut iter = active_parachains.iter();
|
||||
|
||||
// perform this check before writing to storage.
|
||||
for head in &heads {
|
||||
ensure!(
|
||||
iter.find(|&p| p == &head.parachain_index).is_some(),
|
||||
"Submitted candidate for unregistered or out-of-order parachain {}"
|
||||
// , head.parachain_index.into_inner()
|
||||
);
|
||||
}
|
||||
|
||||
for head in heads {
|
||||
let id = head.parachain_index.clone();
|
||||
<Heads<T>>::insert(id, head.head_data.0);
|
||||
}
|
||||
|
||||
<DidUpdate<T>>::put(true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> Executable for Module<T> {
|
||||
fn execute() {
|
||||
assert!(<Self as Store>::DidUpdate::take(), "Parachain heads must be updated once in the block");
|
||||
}
|
||||
}
|
||||
|
||||
/// Parachains module genesis configuration.
|
||||
#[cfg(any(feature = "std", test))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GenesisConfig<T: Trait> {
|
||||
/// The initial parachains, mapped to code and initial head data
|
||||
pub parachains: Vec<(Id, Vec<u8>, Vec<u8>)>,
|
||||
/// Phantom data.
|
||||
#[serde(skip)]
|
||||
pub phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
impl<T: Trait> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
GenesisConfig {
|
||||
parachains: Vec::new(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
impl<T: Trait> runtime_primitives::BuildStorage for GenesisConfig<T>
|
||||
{
|
||||
fn build_storage(mut self) -> ::std::result::Result<runtime_io::TestExternalities, String> {
|
||||
use std::collections::HashMap;
|
||||
use codec::Encode;
|
||||
|
||||
self.parachains.sort_unstable_by_key(|&(ref id, _, _)| id.clone());
|
||||
self.parachains.dedup_by_key(|&mut (ref id, _, _)| id.clone());
|
||||
|
||||
let only_ids: Vec<_> = self.parachains.iter().map(|&(ref id, _, _)| id).cloned().collect();
|
||||
|
||||
let mut map: HashMap<_, _> = map![
|
||||
Self::hash(<Parachains<T>>::key()).to_vec() => only_ids.encode()
|
||||
];
|
||||
|
||||
for (id, code, genesis) in self.parachains {
|
||||
let code_key = Self::hash(&<Code<T>>::key_for(&id)).to_vec();
|
||||
let head_key = Self::hash(&<Heads<T>>::key_for(&id)).to_vec();
|
||||
|
||||
map.insert(code_key, code.encode());
|
||||
map.insert(head_key, genesis.encode());
|
||||
}
|
||||
|
||||
Ok(map.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use runtime_io::with_externalities;
|
||||
use substrate_primitives::H256;
|
||||
use runtime_primitives::BuildStorage;
|
||||
use runtime_primitives::traits::{HasPublicAux, Identity, BlakeTwo256};
|
||||
use runtime_primitives::testing::{Digest, Header};
|
||||
use {consensus, timestamp};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct Test;
|
||||
impl HasPublicAux for Test {
|
||||
type PublicAux = u64;
|
||||
}
|
||||
impl consensus::Trait for Test {
|
||||
type PublicAux = <Self as HasPublicAux>::PublicAux;
|
||||
type SessionKey = u64;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type Digest = Digest;
|
||||
type AccountId = u64;
|
||||
type Header = Header;
|
||||
}
|
||||
impl session::Trait for Test {
|
||||
const NOTE_OFFLINE_POSITION: u32 = 1;
|
||||
type ConvertAccountIdToSessionKey = Identity;
|
||||
type OnSessionChange = ();
|
||||
}
|
||||
impl timestamp::Trait for Test {
|
||||
const TIMESTAMP_SET_POSITION: u32 = 0;
|
||||
type Moment = u64;
|
||||
}
|
||||
impl Trait for Test {
|
||||
const SET_POSITION: u32 = 0;
|
||||
|
||||
type PublicAux = <Self as HasPublicAux>::PublicAux;
|
||||
}
|
||||
|
||||
type Parachains = Module<Test>;
|
||||
|
||||
fn new_test_ext(parachains: Vec<(Id, Vec<u8>, Vec<u8>)>) -> runtime_io::TestExternalities {
|
||||
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
t.extend(consensus::GenesisConfig::<Test>{
|
||||
code: vec![],
|
||||
authorities: vec![1, 2, 3],
|
||||
}.build_storage().unwrap());
|
||||
t.extend(session::GenesisConfig::<Test>{
|
||||
session_length: 1000,
|
||||
validators: vec![1, 2, 3, 4, 5, 6, 7, 8],
|
||||
broken_percent_late: 100,
|
||||
}.build_storage().unwrap());
|
||||
t.extend(GenesisConfig::<Test>{
|
||||
parachains: parachains,
|
||||
phantom: PhantomData,
|
||||
}.build_storage().unwrap());
|
||||
t
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_parachains_should_work() {
|
||||
let parachains = vec![
|
||||
(5u32.into(), vec![1,2,3], vec![1]),
|
||||
(100u32.into(), vec![4,5,6], vec![2]),
|
||||
];
|
||||
|
||||
with_externalities(&mut new_test_ext(parachains), || {
|
||||
assert_eq!(Parachains::active_parachains(), vec![5u32.into(), 100u32.into()]);
|
||||
assert_eq!(Parachains::parachain_code(&5u32.into()), Some(vec![1,2,3]));
|
||||
assert_eq!(Parachains::parachain_code(&100u32.into()), Some(vec![4,5,6]));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_deregister() {
|
||||
let parachains = vec![
|
||||
(5u32.into(), vec![1,2,3], vec![1]),
|
||||
(100u32.into(), vec![4,5,6], vec![2,]),
|
||||
];
|
||||
|
||||
with_externalities(&mut new_test_ext(parachains), || {
|
||||
assert_eq!(Parachains::active_parachains(), vec![5u32.into(), 100u32.into()]);
|
||||
|
||||
assert_eq!(Parachains::parachain_code(&5u32.into()), Some(vec![1,2,3]));
|
||||
assert_eq!(Parachains::parachain_code(&100u32.into()), Some(vec![4,5,6]));
|
||||
|
||||
Parachains::register_parachain(99u32.into(), vec![7,8,9], vec![1, 1, 1]).unwrap();
|
||||
|
||||
assert_eq!(Parachains::active_parachains(), vec![5u32.into(), 99u32.into(), 100u32.into()]);
|
||||
assert_eq!(Parachains::parachain_code(&99u32.into()), Some(vec![7,8,9]));
|
||||
|
||||
Parachains::deregister_parachain(5u32.into()).unwrap();
|
||||
|
||||
assert_eq!(Parachains::active_parachains(), vec![99u32.into(), 100u32.into()]);
|
||||
assert_eq!(Parachains::parachain_code(&5u32.into()), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duty_roster_works() {
|
||||
let parachains = vec![
|
||||
(0u32.into(), vec![], vec![]),
|
||||
(1u32.into(), vec![], vec![]),
|
||||
];
|
||||
|
||||
with_externalities(&mut new_test_ext(parachains), || {
|
||||
let check_roster = |duty_roster: &DutyRoster| {
|
||||
assert_eq!(duty_roster.validator_duty.len(), 8);
|
||||
assert_eq!(duty_roster.guarantor_duty.len(), 8);
|
||||
for i in (0..2).map(Id::from) {
|
||||
assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Parachain(i)).count(), 3);
|
||||
assert_eq!(duty_roster.guarantor_duty.iter().filter(|&&j| j == Chain::Parachain(i)).count(), 3);
|
||||
}
|
||||
assert_eq!(duty_roster.validator_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2);
|
||||
assert_eq!(duty_roster.guarantor_duty.iter().filter(|&&j| j == Chain::Relay).count(), 2);
|
||||
};
|
||||
|
||||
system::Module::<Test>::set_random_seed([0u8; 32].into());
|
||||
let duty_roster_0 = Parachains::calculate_duty_roster();
|
||||
check_roster(&duty_roster_0);
|
||||
|
||||
system::Module::<Test>::set_random_seed([1u8; 32].into());
|
||||
let duty_roster_1 = Parachains::calculate_duty_roster();
|
||||
check_roster(&duty_roster_1);
|
||||
assert!(duty_roster_0 != duty_roster_1);
|
||||
|
||||
|
||||
system::Module::<Test>::set_random_seed([2u8; 32].into());
|
||||
let duty_roster_2 = Parachains::calculate_duty_roster();
|
||||
check_roster(&duty_roster_2);
|
||||
assert!(duty_roster_0 != duty_roster_2);
|
||||
assert!(duty_roster_1 != duty_roster_2);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utils for block interaction.
|
||||
|
||||
use rstd::prelude::*;
|
||||
use super::{Call, UncheckedExtrinsic, Extrinsic, Staking};
|
||||
use runtime_primitives::traits::{Checkable, AuxLookup};
|
||||
use timestamp::Call as TimestampCall;
|
||||
use parachains::Call as ParachainsCall;
|
||||
use session::Call as SessionCall;
|
||||
|
||||
/// Produces the list of inherent extrinsics.
|
||||
pub fn inherent_extrinsics(data: ::primitives::InherentData) -> Vec<UncheckedExtrinsic> {
|
||||
let make_inherent = |function| UncheckedExtrinsic::new(
|
||||
Extrinsic {
|
||||
signed: Default::default(),
|
||||
function,
|
||||
index: 0,
|
||||
},
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let mut inherent = vec![
|
||||
make_inherent(Call::Timestamp(TimestampCall::set(data.timestamp))),
|
||||
make_inherent(Call::Parachains(ParachainsCall::set_heads(data.parachain_heads))),
|
||||
];
|
||||
|
||||
if !data.offline_indices.is_empty() {
|
||||
inherent.push(make_inherent(
|
||||
Call::Session(SessionCall::note_offline(data.offline_indices))
|
||||
));
|
||||
}
|
||||
|
||||
inherent
|
||||
}
|
||||
|
||||
/// Checks an unchecked extrinsic for validity.
|
||||
pub fn check_extrinsic(xt: UncheckedExtrinsic) -> bool {
|
||||
xt.check_with(Staking::lookup).is_ok()
|
||||
}
|
||||
-1256
File diff suppressed because it is too large
Load Diff
@@ -1,56 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-runtime"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
integer-sqrt = { git = "https://github.com/paritytech/integer-sqrt-rs.git", branch = "master" }
|
||||
polkadot-primitives = { path = "../../primitives", default-features = false }
|
||||
safe-mix = { version = "1.0", default-features = false }
|
||||
substrate-codec = { path = "../../../substrate/codec", default-features = false }
|
||||
substrate-primitives = { path = "../../../substrate/primitives", default-features = false }
|
||||
substrate-runtime-std = { path = "../../../substrate/runtime-std", default-features = false }
|
||||
substrate-runtime-io = { path = "../../../substrate/runtime-io", default-features = false }
|
||||
substrate-runtime-support = { path = "../../../substrate/runtime-support", default-features = false }
|
||||
substrate-runtime-consensus = { path = "../../../substrate/runtime/consensus", default-features = false }
|
||||
substrate-runtime-council = { path = "../../../substrate/runtime/council", default-features = false }
|
||||
substrate-runtime-democracy = { path = "../../../substrate/runtime/democracy", default-features = false }
|
||||
substrate-runtime-executive = { path = "../../../substrate/runtime/executive", default-features = false }
|
||||
substrate-runtime-primitives = { path = "../../../substrate/runtime/primitives", default-features = false }
|
||||
substrate-runtime-session = { path = "../../../substrate/runtime/session", default-features = false }
|
||||
substrate-runtime-staking = { path = "../../../substrate/runtime/staking", default-features = false }
|
||||
substrate-runtime-system = { path = "../../../substrate/runtime/system", default-features = false }
|
||||
substrate-runtime-timestamp = { path = "../../../substrate/runtime/timestamp", default-features = false }
|
||||
substrate-runtime-version = { path = "../../../substrate/runtime/version", default-features = false }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
std = [
|
||||
"polkadot-primitives/std",
|
||||
"safe-mix/std",
|
||||
"substrate-codec/std",
|
||||
"substrate-primitives/std",
|
||||
"substrate-runtime-std/std",
|
||||
"substrate-runtime-io/std",
|
||||
"substrate-runtime-support/std",
|
||||
"substrate-runtime-consensus/std",
|
||||
"substrate-runtime-council/std",
|
||||
"substrate-runtime-democracy/std",
|
||||
"substrate-runtime-executive/std",
|
||||
"substrate-runtime-primitives/std",
|
||||
"substrate-runtime-session/std",
|
||||
"substrate-runtime-staking/std",
|
||||
"substrate-runtime-system/std",
|
||||
"substrate-runtime-timestamp/std",
|
||||
"substrate-runtime-version/std",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
|
||||
[workspace]
|
||||
members = []
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cargo +nightly build --target=wasm32-unknown-unknown --release
|
||||
for i in polkadot_runtime
|
||||
do
|
||||
wasm-gc target/wasm32-unknown-unknown/release/$i.wasm target/wasm32-unknown-unknown/release/$i.compact.wasm
|
||||
done
|
||||
@@ -1 +0,0 @@
|
||||
../src
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,29 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-service"
|
||||
version = "0.3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
parking_lot = "0.4"
|
||||
error-chain = "0.12"
|
||||
lazy_static = "1.0"
|
||||
log = "0.3"
|
||||
slog = "^2"
|
||||
tokio = "0.1.7"
|
||||
hex-literal = "0.1"
|
||||
ed25519 = { path = "../../substrate/ed25519" }
|
||||
polkadot-availability-store = { path = "../availability-store" }
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
polkadot-runtime = { path = "../runtime" }
|
||||
polkadot-consensus = { path = "../consensus" }
|
||||
polkadot-executor = { path = "../executor" }
|
||||
polkadot-api = { path = "../api" }
|
||||
polkadot-transaction-pool = { path = "../transaction-pool" }
|
||||
polkadot-network = { path = "../network" }
|
||||
substrate-runtime-io = { path = "../../substrate/runtime-io" }
|
||||
substrate-primitives = { path = "../../substrate/primitives" }
|
||||
substrate-network = { path = "../../substrate/network" }
|
||||
substrate-client = { path = "../../substrate/client" }
|
||||
substrate-codec = { path = "../../substrate/codec" }
|
||||
substrate-service = { path = "../../substrate/service" }
|
||||
substrate-telemetry = { path = "../../substrate/telemetry" }
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Service
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
File diff suppressed because one or more lines are too long
@@ -1,191 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot chain configurations.
|
||||
|
||||
use ed25519;
|
||||
use primitives::AuthorityId;
|
||||
use polkadot_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig,
|
||||
SessionConfig, StakingConfig, TimestampConfig};
|
||||
use service::ChainSpec;
|
||||
|
||||
const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
|
||||
|
||||
pub fn poc_1_testnet_config() -> Result<ChainSpec<GenesisConfig>, String> {
|
||||
ChainSpec::from_embedded(include_bytes!("../res/krummelanke.json"))
|
||||
}
|
||||
|
||||
fn staging_testnet_config_genesis() -> GenesisConfig {
|
||||
let initial_authorities = vec![
|
||||
hex!["82c39b31a2b79a90f8e66e7a77fdb85a4ed5517f2ae39f6a80565e8ecae85cf5"].into(),
|
||||
hex!["4de37a07567ebcbf8c64568428a835269a566723687058e017b6d69db00a77e7"].into(),
|
||||
hex!["063d7787ebca768b7445dfebe7d62cbb1625ff4dba288ea34488da266dd6dca5"].into(),
|
||||
hex!["8101764f45778d4980dadaceee6e8af2517d3ab91ac9bec9cd1714fa5994081c"].into(),
|
||||
];
|
||||
let endowed_accounts = vec![
|
||||
hex!["f295940fa750df68a686fcf4abd4111c8a9c5a5a5a83c4c8639c451a94a7adfd"].into(),
|
||||
];
|
||||
GenesisConfig {
|
||||
consensus: Some(ConsensusConfig {
|
||||
code: include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm").to_vec(), // TODO change
|
||||
authorities: initial_authorities.clone(),
|
||||
}),
|
||||
system: None,
|
||||
session: Some(SessionConfig {
|
||||
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
|
||||
session_length: 60, // that's 5 minutes per session.
|
||||
broken_percent_late: 50,
|
||||
}),
|
||||
staking: Some(StakingConfig {
|
||||
current_era: 0,
|
||||
intentions: initial_authorities.iter().cloned().map(Into::into).collect(),
|
||||
transaction_base_fee: 100,
|
||||
transaction_byte_fee: 1,
|
||||
existential_deposit: 500,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
reclaim_rebate: 0,
|
||||
early_era_slash: 10000,
|
||||
session_reward: 100,
|
||||
balances: endowed_accounts.iter().map(|&k|(k, 1u128 << 60)).collect(),
|
||||
validator_count: 12,
|
||||
sessions_per_era: 12, // 1 hour per era
|
||||
bonding_duration: 24, // 1 day per bond.
|
||||
}),
|
||||
democracy: Some(DemocracyConfig {
|
||||
launch_period: 12 * 60 * 24, // 1 day per public referendum
|
||||
voting_period: 12 * 60 * 24 * 3, // 3 days to discuss & vote on an active referendum
|
||||
minimum_deposit: 5000, // 12000 as the minimum deposit for a referendum
|
||||
}),
|
||||
council: Some(CouncilConfig {
|
||||
active_council: vec![],
|
||||
candidacy_bond: 5000, // 5000 to become a council candidate
|
||||
voter_bond: 1000, // 1000 down to vote for a candidate
|
||||
present_slash_per_voter: 1, // slash by 1 per voter for an invalid presentation.
|
||||
carry_count: 6, // carry over the 6 runners-up to the next council election
|
||||
presentation_duration: 12 * 60 * 24, // one day for presenting winners.
|
||||
approval_voting_period: 12 * 60 * 24 * 2, // two days period between possible council elections.
|
||||
term_duration: 12 * 60 * 24 * 24, // 24 day term duration for the council.
|
||||
desired_seats: 0, // start with no council: we'll raise this once the stake has been dispersed a bit.
|
||||
inactive_grace_period: 1, // one addition vote should go by before an inactive voter can be reaped.
|
||||
|
||||
cooloff_period: 12 * 60 * 24 * 4, // 4 day cooling off period if council member vetoes a proposal.
|
||||
voting_period: 12 * 60 * 24, // 1 day voting period for council members.
|
||||
}),
|
||||
parachains: Some(Default::default()),
|
||||
timestamp: Some(TimestampConfig {
|
||||
period: 5, // 5 second block time.
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Staging testnet config.
|
||||
pub fn staging_testnet_config() -> ChainSpec<GenesisConfig> {
|
||||
let boot_nodes = vec![];
|
||||
ChainSpec::from_genesis(
|
||||
"Staging Testnet",
|
||||
"staging_testnet",
|
||||
staging_testnet_config_genesis,
|
||||
boot_nodes,
|
||||
Some(STAGING_TELEMETRY_URL.into()),
|
||||
)
|
||||
}
|
||||
|
||||
fn testnet_genesis(initial_authorities: Vec<AuthorityId>) -> GenesisConfig {
|
||||
let endowed_accounts = vec![
|
||||
ed25519::Pair::from_seed(b"Alice ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Bob ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Charlie ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Dave ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Eve ").public().0.into(),
|
||||
ed25519::Pair::from_seed(b"Ferdie ").public().0.into(),
|
||||
];
|
||||
GenesisConfig {
|
||||
consensus: Some(ConsensusConfig {
|
||||
code: include_bytes!("../../runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm").to_vec(),
|
||||
authorities: initial_authorities.clone(),
|
||||
}),
|
||||
system: None,
|
||||
session: Some(SessionConfig {
|
||||
validators: initial_authorities.iter().cloned().map(Into::into).collect(),
|
||||
session_length: 10,
|
||||
broken_percent_late: 30,
|
||||
}),
|
||||
staking: Some(StakingConfig {
|
||||
current_era: 0,
|
||||
intentions: initial_authorities.iter().cloned().map(Into::into).collect(),
|
||||
transaction_base_fee: 1,
|
||||
transaction_byte_fee: 0,
|
||||
existential_deposit: 500,
|
||||
transfer_fee: 0,
|
||||
creation_fee: 0,
|
||||
reclaim_rebate: 0,
|
||||
balances: endowed_accounts.iter().map(|&k|(k, (1u128 << 60))).collect(),
|
||||
validator_count: 2,
|
||||
sessions_per_era: 5,
|
||||
bonding_duration: 2,
|
||||
early_era_slash: 0,
|
||||
session_reward: 0,
|
||||
}),
|
||||
democracy: Some(DemocracyConfig {
|
||||
launch_period: 9,
|
||||
voting_period: 18,
|
||||
minimum_deposit: 10,
|
||||
}),
|
||||
council: Some(CouncilConfig {
|
||||
active_council: endowed_accounts.iter().filter(|a| initial_authorities.iter().find(|&b| a.0 == b.0).is_none()).map(|a| (a.clone(), 1000000)).collect(),
|
||||
candidacy_bond: 10,
|
||||
voter_bond: 2,
|
||||
present_slash_per_voter: 1,
|
||||
carry_count: 4,
|
||||
presentation_duration: 10,
|
||||
approval_voting_period: 20,
|
||||
term_duration: 1000000,
|
||||
desired_seats: (endowed_accounts.len() - initial_authorities.len()) as u32,
|
||||
inactive_grace_period: 1,
|
||||
|
||||
cooloff_period: 75,
|
||||
voting_period: 20,
|
||||
}),
|
||||
parachains: Some(Default::default()),
|
||||
timestamp: Some(TimestampConfig {
|
||||
period: 5, // 5 second block time.
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn development_config_genesis() -> GenesisConfig {
|
||||
testnet_genesis(vec![
|
||||
ed25519::Pair::from_seed(b"Alice ").public().into(),
|
||||
])
|
||||
}
|
||||
|
||||
/// Development config (single validator Alice)
|
||||
pub fn development_config() -> ChainSpec<GenesisConfig> {
|
||||
ChainSpec::from_genesis("Development", "development", development_config_genesis, vec![], None)
|
||||
}
|
||||
|
||||
fn local_testnet_genesis() -> GenesisConfig {
|
||||
testnet_genesis(vec![
|
||||
ed25519::Pair::from_seed(b"Alice ").public().into(),
|
||||
ed25519::Pair::from_seed(b"Bob ").public().into(),
|
||||
])
|
||||
}
|
||||
|
||||
/// Local testnet config (multivalidator Alice + Bob)
|
||||
pub fn local_testnet_config() -> ChainSpec<GenesisConfig> {
|
||||
ChainSpec::from_genesis("Local Testnet", "local_testnet", local_testnet_genesis, vec![], None)
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![warn(unused_extern_crates)]
|
||||
|
||||
//! Polkadot service. Specialized wrapper over substrate service.
|
||||
|
||||
extern crate ed25519;
|
||||
extern crate polkadot_availability_store as av_store;
|
||||
extern crate polkadot_primitives;
|
||||
extern crate polkadot_runtime;
|
||||
extern crate polkadot_executor;
|
||||
extern crate polkadot_api;
|
||||
extern crate polkadot_consensus as consensus;
|
||||
extern crate polkadot_transaction_pool as transaction_pool;
|
||||
extern crate polkadot_network;
|
||||
extern crate substrate_primitives as primitives;
|
||||
extern crate substrate_network as network;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_client as client;
|
||||
extern crate substrate_service as service;
|
||||
extern crate tokio;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate hex_literal;
|
||||
|
||||
pub mod chain_spec;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codec::{Encode, Decode};
|
||||
use transaction_pool::TransactionPool;
|
||||
use polkadot_api::{PolkadotApi, light::RemotePolkadotApiWrapper};
|
||||
use polkadot_primitives::{parachain, AccountId, Block, BlockId, Hash};
|
||||
use polkadot_runtime::GenesisConfig;
|
||||
use client::Client;
|
||||
use polkadot_network::{PolkadotProtocol, consensus::ConsensusNetwork};
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use service::FactoryFullConfiguration;
|
||||
|
||||
pub use service::{Roles, PruningMode, ExtrinsicPoolOptions,
|
||||
ErrorKind, Error, ComponentBlock, LightComponents, FullComponents};
|
||||
pub use client::ExecutionStrategy;
|
||||
|
||||
/// Specialised polkadot `ChainSpec`.
|
||||
pub type ChainSpec = service::ChainSpec<GenesisConfig>;
|
||||
/// Polkadot client type for specialised `Components`.
|
||||
pub type ComponentClient<C> = Client<<C as Components>::Backend, <C as Components>::Executor, Block>;
|
||||
pub type NetworkService = network::Service<Block, <Factory as service::ServiceFactory>::NetworkProtocol>;
|
||||
|
||||
/// A collection of type to generalise Polkadot specific components over full / light client.
|
||||
pub trait Components: service::Components {
|
||||
/// Polkadot API.
|
||||
type Api: 'static + PolkadotApi + Send + Sync;
|
||||
/// Client backend.
|
||||
type Backend: 'static + client::backend::Backend<Block>;
|
||||
/// Client executor.
|
||||
type Executor: 'static + client::CallExecutor<Block> + Send + Sync;
|
||||
}
|
||||
|
||||
impl Components for service::LightComponents<Factory> {
|
||||
type Api = RemotePolkadotApiWrapper<
|
||||
<service::LightComponents<Factory> as service::Components>::Backend,
|
||||
<service::LightComponents<Factory> as service::Components>::Executor,
|
||||
>;
|
||||
type Executor = service::LightExecutor<Factory>;
|
||||
type Backend = service::LightBackend<Factory>;
|
||||
}
|
||||
|
||||
impl Components for service::FullComponents<Factory> {
|
||||
type Api = service::FullClient<Factory>;
|
||||
type Executor = service::FullExecutor<Factory>;
|
||||
type Backend = service::FullBackend<Factory>;
|
||||
}
|
||||
|
||||
/// All configuration for the polkadot node.
|
||||
pub type Configuration = FactoryFullConfiguration<Factory>;
|
||||
|
||||
/// Polkadot-specific configuration.
|
||||
#[derive(Default)]
|
||||
pub struct CustomConfiguration {
|
||||
/// Set to `Some` with a collator `AccountId` and desired parachain
|
||||
/// if the network protocol should be started in collator mode.
|
||||
pub collating_for: Option<(AccountId, parachain::Id)>,
|
||||
}
|
||||
|
||||
/// Polkadot config for the substrate service.
|
||||
pub struct Factory;
|
||||
|
||||
impl service::ServiceFactory for Factory {
|
||||
type Block = Block;
|
||||
type NetworkProtocol = PolkadotProtocol;
|
||||
type RuntimeDispatch = polkadot_executor::Executor;
|
||||
type FullExtrinsicPool = TransactionPoolAdapter<
|
||||
service::FullBackend<Self>,
|
||||
service::FullExecutor<Self>,
|
||||
service::FullClient<Self>
|
||||
>;
|
||||
type LightExtrinsicPool = TransactionPoolAdapter<
|
||||
service::LightBackend<Self>,
|
||||
service::LightExecutor<Self>,
|
||||
RemotePolkadotApiWrapper<service::LightBackend<Self>, service::LightExecutor<Self>>
|
||||
>;
|
||||
type Genesis = GenesisConfig;
|
||||
type Configuration = CustomConfiguration;
|
||||
|
||||
const NETWORK_PROTOCOL_ID: network::ProtocolId = ::polkadot_network::DOT_PROTOCOL_ID;
|
||||
|
||||
fn build_full_extrinsic_pool(config: ExtrinsicPoolOptions, client: Arc<service::FullClient<Self>>)
|
||||
-> Result<Self::FullExtrinsicPool, Error>
|
||||
{
|
||||
let api = client.clone();
|
||||
Ok(TransactionPoolAdapter {
|
||||
pool: Arc::new(TransactionPool::new(config, api)),
|
||||
client: client,
|
||||
imports_external_transactions: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_light_extrinsic_pool(config: ExtrinsicPoolOptions, client: Arc<service::LightClient<Self>>)
|
||||
-> Result<Self::LightExtrinsicPool, Error>
|
||||
{
|
||||
let api = Arc::new(RemotePolkadotApiWrapper(client.clone()));
|
||||
Ok(TransactionPoolAdapter {
|
||||
pool: Arc::new(TransactionPool::new(config, api)),
|
||||
client: client,
|
||||
imports_external_transactions: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_network_protocol(config: &Configuration)
|
||||
-> Result<PolkadotProtocol, Error>
|
||||
{
|
||||
if let Some((_, ref para_id)) = config.custom.collating_for {
|
||||
info!("Starting network in Collator mode for parachain {:?}", para_id);
|
||||
}
|
||||
Ok(PolkadotProtocol::new(config.custom.collating_for))
|
||||
}
|
||||
}
|
||||
|
||||
/// Polkadot service.
|
||||
pub struct Service<C: Components> {
|
||||
inner: service::Service<C>,
|
||||
client: Arc<ComponentClient<C>>,
|
||||
network: Arc<NetworkService>,
|
||||
api: Arc<<C as Components>::Api>,
|
||||
_consensus: Option<consensus::Service>,
|
||||
}
|
||||
|
||||
impl <C: Components> Service<C> {
|
||||
pub fn client(&self) -> Arc<ComponentClient<C>> {
|
||||
self.client.clone()
|
||||
}
|
||||
|
||||
pub fn network(&self) -> Arc<NetworkService> {
|
||||
self.network.clone()
|
||||
}
|
||||
|
||||
pub fn api(&self) -> Arc<<C as Components>::Api> {
|
||||
self.api.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates light client and register protocol with the network service
|
||||
pub fn new_light(config: Configuration, executor: TaskExecutor)
|
||||
-> Result<Service<LightComponents<Factory>>, Error>
|
||||
{
|
||||
let service = service::Service::<LightComponents<Factory>>::new(config, executor)?;
|
||||
let api = Arc::new(RemotePolkadotApiWrapper(service.client()));
|
||||
Ok(Service {
|
||||
client: service.client(),
|
||||
network: service.network(),
|
||||
api: api,
|
||||
inner: service,
|
||||
_consensus: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates full client and register protocol with the network service
|
||||
pub fn new_full(config: Configuration, executor: TaskExecutor)
|
||||
-> Result<Service<FullComponents<Factory>>, Error>
|
||||
{
|
||||
// open availability store.
|
||||
let av_store = {
|
||||
use std::path::PathBuf;
|
||||
|
||||
let mut path = PathBuf::from(config.database_path.clone());
|
||||
path.push("availability");
|
||||
|
||||
::av_store::Store::new(::av_store::Config {
|
||||
cache_size: None,
|
||||
path,
|
||||
})?
|
||||
};
|
||||
|
||||
let is_validator = (config.roles & Roles::AUTHORITY) == Roles::AUTHORITY;
|
||||
let service = service::Service::<FullComponents<Factory>>::new(config, executor.clone())?;
|
||||
|
||||
// Spin consensus service if configured
|
||||
let consensus = if is_validator {
|
||||
// Load the first available key
|
||||
let key = service.keystore().load(&service.keystore().contents()?[0], "")?;
|
||||
info!("Using authority key {}", key.public());
|
||||
|
||||
let client = service.client();
|
||||
|
||||
let consensus_net = ConsensusNetwork::new(service.network(), client.clone());
|
||||
Some(consensus::Service::new(
|
||||
client.clone(),
|
||||
client.clone(),
|
||||
consensus_net,
|
||||
service.extrinsic_pool(),
|
||||
executor,
|
||||
::std::time::Duration::from_secs(4), // TODO: dynamic
|
||||
key,
|
||||
av_store.clone(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
service.network().with_spec(|spec, _| spec.register_availability_store(av_store));
|
||||
|
||||
Ok(Service {
|
||||
client: service.client(),
|
||||
network: service.network(),
|
||||
api: service.client(),
|
||||
inner: service,
|
||||
_consensus: consensus,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates bare client without any networking.
|
||||
pub fn new_client(config: Configuration)
|
||||
-> Result<Arc<service::ComponentClient<FullComponents<Factory>>>, Error>
|
||||
{
|
||||
service::new_client::<Factory>(config)
|
||||
}
|
||||
|
||||
impl<C: Components> ::std::ops::Deref for Service<C> {
|
||||
type Target = service::Service<C>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
/// Transaction pool adapter.
|
||||
pub struct TransactionPoolAdapter<B, E, A> where A: Send + Sync, E: Send + Sync {
|
||||
imports_external_transactions: bool,
|
||||
pool: Arc<TransactionPool<A>>,
|
||||
client: Arc<Client<B, E, Block>>,
|
||||
}
|
||||
|
||||
impl<B, E, A> TransactionPoolAdapter<B, E, A>
|
||||
where
|
||||
A: Send + Sync,
|
||||
B: client::backend::Backend<Block> + Send + Sync,
|
||||
E: client::CallExecutor<Block> + Send + Sync,
|
||||
{
|
||||
fn best_block_id(&self) -> Option<BlockId> {
|
||||
self.client.info()
|
||||
.map(|info| BlockId::hash(info.chain.best_hash))
|
||||
.map_err(|e| {
|
||||
debug!("Error getting best block: {:?}", e);
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, A> network::TransactionPool<Block> for TransactionPoolAdapter<B, E, A>
|
||||
where
|
||||
B: client::backend::Backend<Block> + Send + Sync,
|
||||
E: client::CallExecutor<Block> + Send + Sync,
|
||||
A: polkadot_api::PolkadotApi + Send + Sync,
|
||||
{
|
||||
fn transactions(&self) -> Vec<(Hash, Vec<u8>)> {
|
||||
let best_block_id = match self.best_block_id() {
|
||||
Some(id) => id,
|
||||
None => return vec![],
|
||||
};
|
||||
self.pool.cull_and_get_pending(best_block_id, |pending| pending
|
||||
.map(|t| {
|
||||
let hash = t.hash().clone();
|
||||
(hash, t.primitive_extrinsic())
|
||||
})
|
||||
.collect()
|
||||
).unwrap_or_else(|e| {
|
||||
warn!("Error retrieving pending set: {}", e);
|
||||
vec![]
|
||||
})
|
||||
}
|
||||
|
||||
fn import(&self, transaction: &Vec<u8>) -> Option<Hash> {
|
||||
if !self.imports_external_transactions {
|
||||
return None;
|
||||
}
|
||||
|
||||
let encoded = transaction.encode();
|
||||
if let Some(uxt) = Decode::decode(&mut &encoded[..]) {
|
||||
let best_block_id = self.best_block_id()?;
|
||||
match self.pool.import_unchecked_extrinsic(best_block_id, uxt) {
|
||||
Ok(xt) => Some(*xt.hash()),
|
||||
Err(e) => match *e.kind() {
|
||||
transaction_pool::ErrorKind::AlreadyImported(hash) => Some(hash[..].into()),
|
||||
_ => {
|
||||
debug!(target: "txpool", "Error adding transaction to the pool: {:?}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!(target: "txpool", "Error decoding transaction");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn on_broadcasted(&self, propagations: HashMap<Hash, Vec<String>>) {
|
||||
self.pool.on_broadcasted(propagations)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, A> service::ExtrinsicPool<Block> for TransactionPoolAdapter<B, E, A>
|
||||
where
|
||||
B: client::backend::Backend<Block> + Send + Sync + 'static,
|
||||
E: client::CallExecutor<Block> + Send + Sync + 'static,
|
||||
A: polkadot_api::PolkadotApi + Send + Sync + 'static,
|
||||
{
|
||||
type Api = TransactionPool<A>;
|
||||
|
||||
fn prune_imported(&self, hash: &Hash) {
|
||||
let block = BlockId::hash(*hash);
|
||||
if let Err(e) = self.pool.cull(block) {
|
||||
warn!("Culling error: {:?}", e);
|
||||
}
|
||||
|
||||
if let Err(e) = self.pool.retry_verification(block) {
|
||||
warn!("Re-verifying error: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn api(&self) -> Arc<Self::Api> {
|
||||
self.pool.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Src
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,77 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Polkadot CLI
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
extern crate polkadot_cli as cli;
|
||||
extern crate ctrlc;
|
||||
extern crate futures;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
use cli::{ServiceComponents, Service, VersionInfo};
|
||||
use futures::sync::oneshot;
|
||||
use futures::{future, Future};
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
mod vergen {
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/version.rs"));
|
||||
}
|
||||
|
||||
// the regular polkadot worker simply does nothing until ctrl-c
|
||||
struct Worker;
|
||||
impl cli::IntoExit for Worker {
|
||||
type Exit = future::MapErr<oneshot::Receiver<()>, fn(oneshot::Canceled) -> ()>;
|
||||
fn into_exit(self) -> Self::Exit {
|
||||
// can't use signal directly here because CtrlC takes only `Fn`.
|
||||
let (exit_send, exit) = oneshot::channel();
|
||||
|
||||
let exit_send_cell = RefCell::new(Some(exit_send));
|
||||
ctrlc::CtrlC::set_handler(move || {
|
||||
if let Some(exit_send) = exit_send_cell.try_borrow_mut().expect("signal handler not reentrant; qed").take() {
|
||||
exit_send.send(()).expect("Error sending exit notification");
|
||||
}
|
||||
});
|
||||
|
||||
exit.map_err(drop)
|
||||
}
|
||||
}
|
||||
|
||||
impl cli::Worker for Worker {
|
||||
type Work = <Self as cli::IntoExit>::Exit;
|
||||
fn work<C: ServiceComponents>(self, _service: &Service<C>) -> Self::Work {
|
||||
use cli::IntoExit;
|
||||
self.into_exit()
|
||||
}
|
||||
}
|
||||
|
||||
quick_main!(run);
|
||||
|
||||
fn run() -> cli::error::Result<()> {
|
||||
let version = VersionInfo {
|
||||
commit: vergen::short_sha(),
|
||||
version: env!("CARGO_PKG_VERSION"),
|
||||
executable_name: "polkadot",
|
||||
author: "Parity Team <admin@parity.io>",
|
||||
description: "Polkadot Node Rust Implementation",
|
||||
};
|
||||
cli::run(::std::env::args(), Worker, version)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-statement-table"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
substrate-codec = { path = "../../substrate/codec" }
|
||||
substrate-codec-derive = { path = "../../substrate/codec/derive" }
|
||||
substrate-primitives = { path = "../../substrate/primitives" }
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Statement table
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,88 +0,0 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The statement table.
|
||||
//!
|
||||
//! This stores messages other authorities issue about candidates.
|
||||
//!
|
||||
//! These messages are used to create a proposal submitted to a BFT consensus process.
|
||||
//!
|
||||
//! Proposals are formed of sets of candidates which have the requisite number of
|
||||
//! validity and availability votes.
|
||||
//!
|
||||
//! Each parachain is associated with two sets of authorities: those which can
|
||||
//! propose and attest to validity of candidates, and those who can only attest
|
||||
//! to availability.
|
||||
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_primitives;
|
||||
extern crate polkadot_primitives as primitives;
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_codec_derive;
|
||||
|
||||
pub mod generic;
|
||||
|
||||
pub use generic::Table;
|
||||
|
||||
use primitives::parachain::{Id, CandidateReceipt, CandidateSignature as Signature};
|
||||
use primitives::{SessionKey, Hash};
|
||||
|
||||
/// Statements about candidates on the network.
|
||||
pub type Statement = generic::Statement<CandidateReceipt, Hash>;
|
||||
|
||||
/// Signed statements about candidates.
|
||||
pub type SignedStatement = generic::SignedStatement<CandidateReceipt, Hash, SessionKey, Signature>;
|
||||
|
||||
/// Kinds of misbehavior, along with proof.
|
||||
pub type Misbehavior = generic::Misbehavior<CandidateReceipt, Hash, SessionKey, Signature>;
|
||||
|
||||
/// A summary of import of a statement.
|
||||
pub type Summary = generic::Summary<Hash, Id>;
|
||||
|
||||
/// Context necessary to construct a table.
|
||||
pub trait Context {
|
||||
/// Whether a authority is a member of a group.
|
||||
/// Members are meant to submit candidates and vote on validity.
|
||||
fn is_member_of(&self, authority: &SessionKey, group: &Id) -> bool;
|
||||
|
||||
/// Whether a authority is an availability guarantor of a group.
|
||||
/// Guarantors are meant to vote on availability for candidates submitted
|
||||
/// in a group.
|
||||
fn is_availability_guarantor_of(
|
||||
&self,
|
||||
authority: &SessionKey,
|
||||
group: &Id,
|
||||
) -> bool;
|
||||
|
||||
// requisite number of votes for validity and availability respectively from a group.
|
||||
fn requisite_votes(&self, group: &Id) -> (usize, usize);
|
||||
}
|
||||
|
||||
impl<C: Context> generic::Context for C {
|
||||
type AuthorityId = SessionKey;
|
||||
type Digest = Hash;
|
||||
type GroupId = Id;
|
||||
type Signature = Signature;
|
||||
type Candidate = CandidateReceipt;
|
||||
|
||||
fn candidate_digest(candidate: &CandidateReceipt) -> Hash {
|
||||
candidate.hash()
|
||||
}
|
||||
|
||||
fn candidate_group(candidate: &CandidateReceipt) -> Id {
|
||||
candidate.parachain_index.clone()
|
||||
}
|
||||
|
||||
fn is_member_of(&self, authority: &SessionKey, group: &Id) -> bool {
|
||||
Context::is_member_of(self, authority, group)
|
||||
}
|
||||
|
||||
fn is_availability_guarantor_of(&self, authority: &SessionKey, group: &Id) -> bool {
|
||||
Context::is_availability_guarantor_of(self, authority, group)
|
||||
}
|
||||
|
||||
fn requisite_votes(&self, group: &Id) -> (usize, usize) {
|
||||
Context::requisite_votes(self, group)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
target/
|
||||
Cargo.lock
|
||||
@@ -1,5 +0,0 @@
|
||||
# Test Parachains
|
||||
|
||||
Each parachain consists of three parts: a `#![no_std]` library with the main execution logic, a WASM crate which wraps this logic, and a collator node.
|
||||
|
||||
Run `build.sh` in this directory to build all registered test parachains and copy the generated WASM to the `parachain/tests/res` folder.
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "adder"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "Test parachain which adds to a number as its state transition"
|
||||
|
||||
[dependencies]
|
||||
polkadot-parachain = { path = "../../parachain/", default-features = false }
|
||||
substrate-codec-derive = { path = "../../../substrate/codec/derive", default-features = false }
|
||||
tiny-keccak = "1.4"
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "adder-collator"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
adder = { path = ".." }
|
||||
polkadot-parachain = { path = "../../../parachain" }
|
||||
polkadot-collator = { path = "../../../collator" }
|
||||
polkadot-primitives = { path = "../../../primitives" }
|
||||
ed25519 = { path = "../../../../substrate/ed25519" }
|
||||
parking_lot = "0.4"
|
||||
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
||||
futures = "0.1"
|
||||
exit-future = "0.1.2"
|
||||
@@ -1,145 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Collator for polkadot
|
||||
|
||||
extern crate adder;
|
||||
extern crate polkadot_parachain as parachain;
|
||||
extern crate polkadot_primitives as primitives;
|
||||
extern crate polkadot_collator as collator;
|
||||
extern crate ed25519;
|
||||
extern crate parking_lot;
|
||||
extern crate ctrlc;
|
||||
extern crate futures;
|
||||
extern crate exit_future;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use adder::{HeadData as AdderHead, BlockData as AdderBody};
|
||||
use ed25519::Pair;
|
||||
use parachain::codec::{Encode, Decode};
|
||||
use primitives::parachain::{HeadData, BlockData, Id as ParaId, Message};
|
||||
use collator::{InvalidHead, ParachainContext, VersionInfo};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
const GENESIS: AdderHead = AdderHead {
|
||||
number: 0,
|
||||
parent_hash: [0; 32],
|
||||
post_state: [1, 27, 77, 3, 221, 140, 1, 241, 4, 145, 67, 207, 156, 76, 129, 126, 75, 22, 127, 29, 27, 131, 229, 198, 240, 241, 13, 137, 186, 30, 123, 206],
|
||||
};
|
||||
|
||||
const GENESIS_BODY: AdderBody = AdderBody {
|
||||
state: 0,
|
||||
add: 0,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AdderContext {
|
||||
db: Arc<Mutex<HashMap<AdderHead, AdderBody>>>,
|
||||
}
|
||||
|
||||
/// The parachain context.
|
||||
impl ParachainContext for AdderContext {
|
||||
fn produce_candidate<I: IntoIterator<Item=(ParaId, Message)>>(
|
||||
&self,
|
||||
last_head: HeadData,
|
||||
_ingress: I,
|
||||
) -> Result<(BlockData, HeadData), InvalidHead>
|
||||
{
|
||||
let adder_head = AdderHead::decode(&mut &last_head.0[..])
|
||||
.ok_or(InvalidHead)?;
|
||||
|
||||
let mut db = self.db.lock();
|
||||
|
||||
let last_body = if adder_head == GENESIS {
|
||||
GENESIS_BODY
|
||||
} else {
|
||||
db.get(&adder_head)
|
||||
.expect("All past bodies stored since this is the only collator")
|
||||
.clone()
|
||||
};
|
||||
|
||||
let next_body = AdderBody {
|
||||
state: last_body.state.overflowing_add(last_body.add).0,
|
||||
add: adder_head.number % 100,
|
||||
};
|
||||
|
||||
let next_head = ::adder::execute(adder_head.hash(), adder_head, &next_body)
|
||||
.expect("good execution params; qed");
|
||||
|
||||
let encoded_head = HeadData(next_head.encode());
|
||||
let encoded_body = BlockData(next_body.encode());
|
||||
|
||||
println!("Created collation for #{}, post-state={}",
|
||||
next_head.number, next_body.state.overflowing_add(next_body.add).0);
|
||||
|
||||
db.insert(next_head.clone(), next_body);
|
||||
Ok((encoded_body, encoded_head))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let key = Arc::new(Pair::from_seed(&[1; 32]));
|
||||
let id: ParaId = 100.into();
|
||||
|
||||
println!("Starting adder collator with genesis: ");
|
||||
|
||||
{
|
||||
let encoded = GENESIS.encode();
|
||||
println!("Dec: {:?}", encoded);
|
||||
print!("Hex: 0x");
|
||||
for byte in encoded {
|
||||
print!("{:02x}", byte);
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
// can't use signal directly here because CtrlC takes only `Fn`.
|
||||
let (exit_send, exit) = exit_future::signal();
|
||||
|
||||
let exit_send_cell = RefCell::new(Some(exit_send));
|
||||
ctrlc::CtrlC::set_handler(move || {
|
||||
if let Some(exit_send) = exit_send_cell.try_borrow_mut().expect("signal handler not reentrant; qed").take() {
|
||||
exit_send.fire();
|
||||
}
|
||||
});
|
||||
|
||||
let context = AdderContext {
|
||||
db: Arc::new(Mutex::new(HashMap::new())),
|
||||
};
|
||||
|
||||
let res = ::collator::run_collator(
|
||||
context,
|
||||
id,
|
||||
exit,
|
||||
key,
|
||||
::std::env::args(),
|
||||
VersionInfo {
|
||||
version: "<unknown>",
|
||||
commit: "<unknown>",
|
||||
executable_name: "adder-collator",
|
||||
description: "collator for adder parachain",
|
||||
author: "parity technologies",
|
||||
}
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
println!("{}", e);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Basic parachain that adds a number as part of its state.
|
||||
|
||||
#![no_std]
|
||||
|
||||
#[macro_use]
|
||||
extern crate substrate_codec_derive;
|
||||
|
||||
extern crate polkadot_parachain as parachain;
|
||||
extern crate tiny_keccak;
|
||||
|
||||
use parachain::codec::{self, Encode};
|
||||
|
||||
/// Head data for this parachain.
|
||||
#[derive(Default, Clone, Hash, Eq, PartialEq, Encode, Decode)]
|
||||
pub struct HeadData {
|
||||
/// Block number
|
||||
pub number: u64,
|
||||
/// parent block keccak256
|
||||
pub parent_hash: [u8; 32],
|
||||
/// hash of post-execution state.
|
||||
pub post_state: [u8; 32],
|
||||
}
|
||||
|
||||
impl HeadData {
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
::tiny_keccak::keccak256(&self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Block data for this parachain.
|
||||
#[derive(Default, Clone, Encode, Decode)]
|
||||
pub struct BlockData {
|
||||
/// State to begin from.
|
||||
pub state: u64,
|
||||
/// Amount to add (overflowing)
|
||||
pub add: u64,
|
||||
}
|
||||
|
||||
pub fn hash_state(state: u64) -> [u8; 32] {
|
||||
::tiny_keccak::keccak256(state.encode().as_slice())
|
||||
}
|
||||
|
||||
/// Start state mismatched with parent header's state hash.
|
||||
#[derive(Debug)]
|
||||
pub struct StateMismatch;
|
||||
|
||||
/// Execute a block body on top of given parent head, producing new parent head
|
||||
/// if valid.
|
||||
pub fn execute(parent_hash: [u8; 32], parent_head: HeadData, block_data: &BlockData) -> Result<HeadData, StateMismatch> {
|
||||
debug_assert_eq!(parent_hash, parent_head.hash());
|
||||
|
||||
if hash_state(block_data.state) != parent_head.post_state {
|
||||
return Err(StateMismatch);
|
||||
}
|
||||
|
||||
let new_state = block_data.state.overflowing_add(block_data.add).0;
|
||||
|
||||
Ok(HeadData {
|
||||
number: parent_head.number + 1,
|
||||
parent_hash,
|
||||
post_state: hash_state(new_state),
|
||||
})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
src
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "adder-wasm"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
adder = { path = ".." }
|
||||
polkadot-parachain = { path = "../../../parachain", default-features = false }
|
||||
wee_alloc = { version = "0.4.1" }
|
||||
pwasm-libc = { version = "0.2" }
|
||||
tiny-keccak = "1.4"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[target.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
|
||||
[workspace]
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! WASM validation for adder parachain.
|
||||
|
||||
#![no_std]
|
||||
|
||||
#![feature(
|
||||
alloc, core_intrinsics, lang_items, panic_implementation, core_panic_info,
|
||||
alloc_error_handler
|
||||
)]
|
||||
|
||||
extern crate alloc;
|
||||
extern crate wee_alloc;
|
||||
extern crate pwasm_libc;
|
||||
extern crate adder;
|
||||
extern crate polkadot_parachain as parachain;
|
||||
extern crate tiny_keccak;
|
||||
|
||||
// Define global allocator.
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
use core::{intrinsics, panic};
|
||||
use parachain::ValidationResult;
|
||||
use parachain::codec::{Encode, Decode};
|
||||
use adder::{HeadData, BlockData};
|
||||
|
||||
#[panic_implementation]
|
||||
#[no_mangle]
|
||||
pub fn panic(_info: &panic::PanicInfo) -> ! {
|
||||
unsafe {
|
||||
intrinsics::abort()
|
||||
}
|
||||
}
|
||||
|
||||
#[alloc_error_handler]
|
||||
#[no_mangle]
|
||||
pub fn oom(_: ::core::alloc::Layout) -> ! {
|
||||
unsafe {
|
||||
intrinsics::abort();
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn validate(offset: usize, len: usize) -> usize {
|
||||
let params = unsafe { ::parachain::load_params(offset, len) };
|
||||
let parent_head = HeadData::decode(&mut ¶ms.parent_head[..])
|
||||
.expect("invalid parent head format.");
|
||||
|
||||
let block_data = BlockData::decode(&mut ¶ms.block_data[..])
|
||||
.expect("invalid block data format.");
|
||||
|
||||
let parent_hash = ::tiny_keccak::keccak256(¶ms.parent_head[..]);
|
||||
|
||||
match ::adder::execute(parent_hash, parent_head, &block_data) {
|
||||
Ok(new_head) => parachain::write_result(ValidationResult { head_data: new_head.encode() }),
|
||||
Err(_) => panic!("execution failure"),
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Make LLD produce a binary that imports memory from the outside environment.
|
||||
export RUSTFLAGS="-C link-arg=--import-memory -C lto=fat -C panic=abort"
|
||||
|
||||
for i in adder
|
||||
do
|
||||
cd $i/wasm
|
||||
cargo +nightly build --target=wasm32-unknown-unknown --release --no-default-features --target-dir target
|
||||
wasm-gc target/wasm32-unknown-unknown/release/$i'_'wasm.wasm target/wasm32-unknown-unknown/release/$i.wasm
|
||||
cp target/wasm32-unknown-unknown/release/$i.wasm ../../../parachain/tests/res/
|
||||
rm -rf target
|
||||
cd ../..
|
||||
done
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "polkadot-transaction-pool"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.3.0"
|
||||
error-chain = "0.12"
|
||||
parking_lot = "0.4"
|
||||
polkadot-api = { path = "../api" }
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
polkadot-runtime = { path = "../runtime" }
|
||||
substrate-client = { path = "../../substrate/client" }
|
||||
substrate-codec = { path = "../../substrate/codec" }
|
||||
substrate-keyring = { path = "../../substrate/keyring" }
|
||||
substrate-extrinsic-pool = { path = "../../substrate/extrinsic-pool" }
|
||||
substrate-primitives = { path = "../../substrate/primitives" }
|
||||
substrate-runtime-primitives = { path = "../../substrate/runtime/primitives" }
|
||||
ed25519 = { path = "../../substrate/ed25519" }
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
= Polkadot Transactin pool
|
||||
|
||||
placeholder
|
||||
//TODO Write content :)
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use extrinsic_pool::{self, txpool};
|
||||
use polkadot_api;
|
||||
use primitives::Hash;
|
||||
use runtime::{Address, UncheckedExtrinsic};
|
||||
|
||||
error_chain! {
|
||||
links {
|
||||
Pool(txpool::Error, txpool::ErrorKind);
|
||||
Api(polkadot_api::Error, polkadot_api::ErrorKind);
|
||||
}
|
||||
errors {
|
||||
/// Unexpected extrinsic format submitted
|
||||
InvalidExtrinsicFormat {
|
||||
description("Invalid extrinsic format."),
|
||||
display("Invalid extrinsic format."),
|
||||
}
|
||||
/// Attempted to queue an inherent transaction.
|
||||
IsInherent(xt: UncheckedExtrinsic) {
|
||||
description("Inherent transactions cannot be queued."),
|
||||
display("Inehrent transactions cannot be queued."),
|
||||
}
|
||||
/// Attempted to queue a transaction with bad signature.
|
||||
BadSignature(e: &'static str) {
|
||||
description("Transaction had bad signature."),
|
||||
display("Transaction had bad signature: {}", e),
|
||||
}
|
||||
/// Attempted to queue a transaction that is already in the pool.
|
||||
AlreadyImported(hash: Hash) {
|
||||
description("Transaction is already in the pool."),
|
||||
display("Transaction {:?} is already in the pool.", hash),
|
||||
}
|
||||
/// Import error.
|
||||
Import(err: Box<::std::error::Error + Send>) {
|
||||
description("Error importing transaction"),
|
||||
display("Error importing transaction: {}", err.description()),
|
||||
}
|
||||
/// Runtime failure.
|
||||
UnrecognisedAddress(who: Address) {
|
||||
description("Unrecognised address in extrinsic"),
|
||||
display("Unrecognised address in extrinsic: {}", who),
|
||||
}
|
||||
/// Extrinsic too large
|
||||
TooLarge(got: usize, max: usize) {
|
||||
description("Extrinsic too large"),
|
||||
display("Extrinsic is too large ({} > {})", got, max),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl extrinsic_pool::api::Error for Error {
|
||||
fn into_pool_error(self) -> ::std::result::Result<txpool::Error, Self> {
|
||||
match self {
|
||||
Error(ErrorKind::Pool(e), c) => Ok(txpool::Error(e, c)),
|
||||
e => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,742 +0,0 @@
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot 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.
|
||||
|
||||
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate ed25519;
|
||||
extern crate substrate_client as client;
|
||||
extern crate substrate_codec as codec;
|
||||
extern crate substrate_extrinsic_pool as extrinsic_pool;
|
||||
extern crate substrate_primitives;
|
||||
extern crate substrate_runtime_primitives;
|
||||
extern crate polkadot_runtime as runtime;
|
||||
extern crate polkadot_primitives as primitives;
|
||||
extern crate polkadot_api;
|
||||
extern crate parking_lot;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate substrate_keyring;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod error;
|
||||
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
ops::Deref,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use extrinsic_pool::{
|
||||
api::{ExtrinsicPool, EventStream},
|
||||
txpool::{self, Readiness, scoring::{Change, Choice}},
|
||||
watcher::Watcher,
|
||||
Pool,
|
||||
Listener,
|
||||
};
|
||||
use polkadot_api::PolkadotApi;
|
||||
use primitives::{AccountId, BlockId, Hash, Index, UncheckedExtrinsic as FutureProofUncheckedExtrinsic};
|
||||
use runtime::{Address, UncheckedExtrinsic};
|
||||
use substrate_runtime_primitives::traits::{Bounded, Checkable, Hash as HashT, BlakeTwo256};
|
||||
|
||||
pub use extrinsic_pool::txpool::{Options, Status, LightStatus, VerifiedTransaction as VerifiedTransactionOps};
|
||||
pub use error::{Error, ErrorKind, Result};
|
||||
|
||||
/// Maximal size of a single encoded extrinsic.
|
||||
///
|
||||
/// See also polkadot-consensus::MAX_TRANSACTIONS_SIZE
|
||||
const MAX_TRANSACTION_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
/// Type alias for convenience.
|
||||
pub type CheckedExtrinsic = <UncheckedExtrinsic as Checkable<fn(Address) -> std::result::Result<AccountId, &'static str>>>::Checked;
|
||||
|
||||
/// A verified transaction which should be includable and non-inherent.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VerifiedTransaction {
|
||||
original: UncheckedExtrinsic,
|
||||
inner: Option<CheckedExtrinsic>,
|
||||
sender: Option<AccountId>,
|
||||
hash: Hash,
|
||||
encoded_size: usize,
|
||||
}
|
||||
|
||||
impl VerifiedTransaction {
|
||||
/// Access the underlying transaction.
|
||||
pub fn as_transaction(&self) -> &UncheckedExtrinsic {
|
||||
&self.original
|
||||
}
|
||||
|
||||
/// Convert to primitive unchecked extrinsic.
|
||||
pub fn primitive_extrinsic(&self) -> ::primitives::UncheckedExtrinsic {
|
||||
Decode::decode(&mut self.as_transaction().encode().as_slice())
|
||||
.expect("UncheckedExtrinsic shares repr with Vec<u8>; qed")
|
||||
}
|
||||
|
||||
/// Consume the verified transaction, yielding the checked counterpart.
|
||||
pub fn into_inner(self) -> Option<CheckedExtrinsic> {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Get the 256-bit hash of this transaction.
|
||||
pub fn hash(&self) -> &Hash {
|
||||
&self.hash
|
||||
}
|
||||
|
||||
/// Get the account ID of the sender of this transaction.
|
||||
pub fn sender(&self) -> Option<AccountId> {
|
||||
self.sender
|
||||
}
|
||||
|
||||
/// Get the account ID of the sender of this transaction.
|
||||
pub fn index(&self) -> Index {
|
||||
self.original.extrinsic.index
|
||||
}
|
||||
|
||||
/// Get encoded size of the transaction.
|
||||
pub fn encoded_size(&self) -> usize {
|
||||
self.encoded_size
|
||||
}
|
||||
|
||||
/// Returns `true` if the transaction is not yet fully verified.
|
||||
pub fn is_fully_verified(&self) -> bool {
|
||||
self.inner.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl txpool::VerifiedTransaction for VerifiedTransaction {
|
||||
type Hash = Hash;
|
||||
type Sender = Option<AccountId>;
|
||||
|
||||
fn hash(&self) -> &Self::Hash {
|
||||
&self.hash
|
||||
}
|
||||
|
||||
fn sender(&self) -> &Self::Sender {
|
||||
&self.sender
|
||||
}
|
||||
|
||||
fn mem_usage(&self) -> usize {
|
||||
self.encoded_size // TODO
|
||||
}
|
||||
}
|
||||
|
||||
/// Scoring implementation for polkadot transactions.
|
||||
#[derive(Debug)]
|
||||
pub struct Scoring;
|
||||
|
||||
impl txpool::Scoring<VerifiedTransaction> for Scoring {
|
||||
type Score = u64;
|
||||
type Event = ();
|
||||
|
||||
fn compare(&self, old: &VerifiedTransaction, other: &VerifiedTransaction) -> Ordering {
|
||||
old.index().cmp(&other.index())
|
||||
}
|
||||
|
||||
fn choose(&self, old: &VerifiedTransaction, new: &VerifiedTransaction) -> Choice {
|
||||
if old.is_fully_verified() {
|
||||
assert!(new.is_fully_verified(), "Scoring::choose called with transactions from different senders");
|
||||
if old.index() == new.index() {
|
||||
// TODO [ToDr] Do we allow replacement? If yes then it should be Choice::ReplaceOld
|
||||
return Choice::RejectNew;
|
||||
}
|
||||
}
|
||||
|
||||
// This will keep both transactions, even though they have the same indices.
|
||||
// It's fine for not fully verified transactions, we might also allow it for
|
||||
// verified transactions but it would mean that only one of the two is actually valid
|
||||
// (most likely the first to be included in the block).
|
||||
Choice::InsertNew
|
||||
}
|
||||
|
||||
fn update_scores(
|
||||
&self,
|
||||
xts: &[txpool::Transaction<VerifiedTransaction>],
|
||||
scores: &mut [Self::Score],
|
||||
_change: Change<()>
|
||||
) {
|
||||
for i in 0..xts.len() {
|
||||
if !xts[i].is_fully_verified() {
|
||||
scores[i] = 0;
|
||||
} else {
|
||||
// all the same score since there are no fees.
|
||||
// TODO: prioritize things like misbehavior or fishermen reports
|
||||
scores[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn should_replace(&self, old: &VerifiedTransaction, _new: &VerifiedTransaction) -> bool {
|
||||
// Always replace not fully verified transactions.
|
||||
!old.is_fully_verified()
|
||||
}
|
||||
}
|
||||
|
||||
/// Readiness evaluator for polkadot transactions.
|
||||
pub struct Ready<'a, A: 'a + PolkadotApi> {
|
||||
at_block: BlockId,
|
||||
api: &'a A,
|
||||
known_nonces: HashMap<AccountId, ::primitives::Index>,
|
||||
}
|
||||
|
||||
impl<'a, A: 'a + PolkadotApi> Ready<'a, A> {
|
||||
/// Create a new readiness evaluator at the given block. Requires that
|
||||
/// the ID has already been checked for local corresponding and available state.
|
||||
fn create(at: BlockId, api: &'a A) -> Self {
|
||||
Ready {
|
||||
at_block: at,
|
||||
api,
|
||||
known_nonces: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + PolkadotApi> Clone for Ready<'a, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Ready {
|
||||
at_block: self.at_block.clone(),
|
||||
api: self.api,
|
||||
known_nonces: self.known_nonces.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: 'a + PolkadotApi> txpool::Ready<VerifiedTransaction> for Ready<'a, A>
|
||||
{
|
||||
fn is_ready(&mut self, xt: &VerifiedTransaction) -> Readiness {
|
||||
let sender = match xt.sender() {
|
||||
Some(sender) => sender,
|
||||
None => return Readiness::Future
|
||||
};
|
||||
|
||||
trace!(target: "transaction-pool", "Checking readiness of {} (from {})", xt.hash, Hash::from(sender));
|
||||
|
||||
// TODO: find a way to handle index error properly -- will need changes to
|
||||
// transaction-pool trait.
|
||||
let (api, at_block) = (&self.api, &self.at_block);
|
||||
let next_index = self.known_nonces.entry(sender)
|
||||
.or_insert_with(|| api.index(at_block, sender).ok().unwrap_or_else(Bounded::max_value));
|
||||
|
||||
trace!(target: "transaction-pool", "Next index for sender is {}; xt index is {}", next_index, xt.original.extrinsic.index);
|
||||
|
||||
let result = match xt.original.extrinsic.index.cmp(&next_index) {
|
||||
// TODO: this won't work perfectly since accounts can now be killed, returning the nonce
|
||||
// to zero.
|
||||
// We should detect if the index was reset and mark all transactions as `Stale` for cull to work correctly.
|
||||
// Otherwise those transactions will keep occupying the queue.
|
||||
// Perhaps we could mark as stale if `index - state_index` > X?
|
||||
Ordering::Greater => Readiness::Future,
|
||||
Ordering::Equal => Readiness::Ready,
|
||||
// TODO [ToDr] Should mark transactions referrencing too old blockhash as `Stale` as well.
|
||||
Ordering::Less => Readiness::Stale,
|
||||
};
|
||||
|
||||
// remember to increment `next_index`
|
||||
*next_index = next_index.saturating_add(1);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Verifier<'a, A: 'a> {
|
||||
api: &'a A,
|
||||
at_block: BlockId,
|
||||
}
|
||||
|
||||
impl<'a, A> Verifier<'a, A> where
|
||||
A: 'a + PolkadotApi,
|
||||
{
|
||||
const NO_ACCOUNT: &'static str = "Account not found.";
|
||||
|
||||
fn lookup(&self, address: Address) -> ::std::result::Result<AccountId, &'static str> {
|
||||
// TODO [ToDr] Consider introducing a cache for this.
|
||||
match self.api.lookup(&self.at_block, address.clone()) {
|
||||
Ok(Some(address)) => Ok(address),
|
||||
Ok(None) => Err(Self::NO_ACCOUNT.into()),
|
||||
Err(e) => {
|
||||
error!("Error looking up address: {:?}: {:?}", address, e);
|
||||
Err("API error.")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A> txpool::Verifier<UncheckedExtrinsic> for Verifier<'a, A> where
|
||||
A: 'a + PolkadotApi,
|
||||
{
|
||||
type VerifiedTransaction = VerifiedTransaction;
|
||||
type Error = Error;
|
||||
|
||||
fn verify_transaction(&self, uxt: UncheckedExtrinsic) -> Result<Self::VerifiedTransaction> {
|
||||
if !uxt.is_signed() {
|
||||
bail!(ErrorKind::IsInherent(uxt))
|
||||
}
|
||||
|
||||
let encoded = uxt.encode();
|
||||
let encoded_size = encoded.len();
|
||||
|
||||
if encoded_size > MAX_TRANSACTION_SIZE {
|
||||
bail!(ErrorKind::TooLarge(encoded_size, MAX_TRANSACTION_SIZE));
|
||||
}
|
||||
|
||||
let hash = BlakeTwo256::hash(&encoded);
|
||||
debug!(target: "transaction-pool", "Transaction submitted: {}", ::substrate_primitives::hexdisplay::HexDisplay::from(&encoded));
|
||||
|
||||
let inner = match uxt.clone().check_with(|a| self.lookup(a)) {
|
||||
Ok(xt) => Some(xt),
|
||||
// keep the transaction around in the future pool and attempt to promote it later.
|
||||
Err(Self::NO_ACCOUNT) => None,
|
||||
Err(e) => bail!(e),
|
||||
};
|
||||
let sender = inner.as_ref().map(|x| x.signed.clone());
|
||||
|
||||
if encoded_size < 1024 {
|
||||
debug!(target: "transaction-pool", "Transaction verified: {} => {:?}", hash, uxt);
|
||||
} else {
|
||||
debug!(target: "transaction-pool", "Transaction verified: {} ({} bytes is too large to display)", hash, encoded_size);
|
||||
}
|
||||
|
||||
Ok(VerifiedTransaction {
|
||||
original: uxt,
|
||||
inner,
|
||||
sender,
|
||||
hash,
|
||||
encoded_size
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The polkadot transaction pool.
|
||||
///
|
||||
/// Wraps a `extrinsic_pool::Pool`.
|
||||
pub struct TransactionPool<A> {
|
||||
inner: Pool<Hash, VerifiedTransaction, Scoring, Error>,
|
||||
api: Arc<A>,
|
||||
}
|
||||
|
||||
impl<A> TransactionPool<A> where
|
||||
A: PolkadotApi,
|
||||
{
|
||||
/// Create a new transaction pool.
|
||||
pub fn new(options: Options, api: Arc<A>) -> Self {
|
||||
TransactionPool {
|
||||
inner: Pool::new(options, Scoring),
|
||||
api,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to directly import `UncheckedExtrinsic` without going through serialization.
|
||||
pub fn import_unchecked_extrinsic(&self, block: BlockId, uxt: UncheckedExtrinsic) -> Result<Arc<VerifiedTransaction>> {
|
||||
let verifier = Verifier {
|
||||
api: &*self.api,
|
||||
at_block: block,
|
||||
};
|
||||
self.inner.submit(verifier, vec![uxt]).map(|mut v| v.swap_remove(0))
|
||||
}
|
||||
|
||||
/// Retry to import all semi-verified transactions (unknown account indices)
|
||||
pub fn retry_verification(&self, block: BlockId) -> Result<()> {
|
||||
let to_reverify = self.inner.remove_sender(None);
|
||||
let verifier = Verifier {
|
||||
api: &*self.api,
|
||||
at_block: block,
|
||||
};
|
||||
|
||||
self.inner.submit(verifier, to_reverify.into_iter().map(|tx| tx.original.clone()))?;
|
||||
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, block: BlockId, hash: Hash) -> Result<Option<Arc<VerifiedTransaction>>> {
|
||||
let result = self.inner.remove(&[hash], false).pop().expect("One hash passed; one result received; qed");
|
||||
if let Some(tx) = result {
|
||||
self.import_unchecked_extrinsic(block, tx.original.clone()).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Cull old transactions from the queue.
|
||||
pub fn cull(&self, block: BlockId) -> Result<usize> {
|
||||
let ready = Ready::create(block, &*self.api);
|
||||
Ok(self.inner.cull(None, ready))
|
||||
}
|
||||
|
||||
/// Cull transactions from the queue and then compute the pending set.
|
||||
pub fn cull_and_get_pending<F, T>(&self, block: BlockId, f: F) -> Result<T> where
|
||||
F: FnOnce(txpool::PendingIterator<VerifiedTransaction, Ready<A>, Scoring, Listener<Hash>>) -> T,
|
||||
{
|
||||
let ready = Ready::create(block, &*self.api);
|
||||
self.inner.cull(None, ready.clone());
|
||||
Ok(self.inner.pending(ready, f))
|
||||
}
|
||||
|
||||
/// Remove a set of transactions idenitified by hashes.
|
||||
pub fn remove(&self, hashes: &[Hash], is_valid: bool) -> Vec<Option<Arc<VerifiedTransaction>>> {
|
||||
self.inner.remove(hashes, is_valid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> Deref for TransactionPool<A> {
|
||||
type Target = Pool<Hash, VerifiedTransaction, Scoring, Error>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: more general transaction pool, which can handle more kinds of vec-encoded transactions,
|
||||
// even when runtime is out of date.
|
||||
impl<A> ExtrinsicPool<FutureProofUncheckedExtrinsic, BlockId, Hash> for TransactionPool<A> where
|
||||
A: Send + Sync + 'static,
|
||||
A: PolkadotApi,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn submit(&self, block: BlockId, xts: Vec<FutureProofUncheckedExtrinsic>) -> Result<Vec<Hash>> {
|
||||
xts.into_iter()
|
||||
.map(|xt| xt.encode())
|
||||
.map(|encoded| {
|
||||
let decoded = UncheckedExtrinsic::decode(&mut &encoded[..]).ok_or(ErrorKind::InvalidExtrinsicFormat)?;
|
||||
let tx = self.import_unchecked_extrinsic(block, decoded)?;
|
||||
Ok(*tx.hash())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn submit_and_watch(&self, block: BlockId, xt: FutureProofUncheckedExtrinsic) -> Result<Watcher<Hash>> {
|
||||
let encoded = xt.encode();
|
||||
let decoded = UncheckedExtrinsic::decode(&mut &encoded[..]).ok_or(ErrorKind::InvalidExtrinsicFormat)?;
|
||||
|
||||
let verifier = Verifier {
|
||||
api: &*self.api,
|
||||
at_block: block,
|
||||
};
|
||||
|
||||
self.inner.submit_and_watch(verifier, decoded)
|
||||
}
|
||||
|
||||
fn light_status(&self) -> LightStatus {
|
||||
self.inner.light_status()
|
||||
}
|
||||
|
||||
fn import_notification_stream(&self) -> EventStream {
|
||||
self.inner.import_notification_stream()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{atomic::{self, AtomicBool}, Arc};
|
||||
use super::TransactionPool;
|
||||
use substrate_keyring::Keyring::{self, *};
|
||||
use codec::{Decode, Encode};
|
||||
use polkadot_api::{PolkadotApi, BlockBuilder, Result};
|
||||
use primitives::{AccountId, AccountIndex, Block, BlockId, Hash, Index, SessionKey,
|
||||
UncheckedExtrinsic as FutureProofUncheckedExtrinsic};
|
||||
use runtime::{RawAddress, Call, TimestampCall, BareExtrinsic, Extrinsic, UncheckedExtrinsic};
|
||||
use primitives::parachain::{DutyRoster, Id as ParaId};
|
||||
use substrate_runtime_primitives::{MaybeUnsigned, generic};
|
||||
|
||||
struct TestBlockBuilder;
|
||||
impl BlockBuilder for TestBlockBuilder {
|
||||
fn push_extrinsic(&mut self, _extrinsic: FutureProofUncheckedExtrinsic) -> Result<()> { unimplemented!() }
|
||||
fn bake(self) -> Result<Block> { unimplemented!() }
|
||||
}
|
||||
|
||||
fn number_of(at: &BlockId) -> u32 {
|
||||
match at {
|
||||
generic::BlockId::Number(n) => *n as u32,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct TestPolkadotApi {
|
||||
no_lookup: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl TestPolkadotApi {
|
||||
fn without_lookup() -> Self {
|
||||
TestPolkadotApi {
|
||||
no_lookup: Arc::new(AtomicBool::new(true)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_lookup(&self) {
|
||||
self.no_lookup.store(false, atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl PolkadotApi for TestPolkadotApi {
|
||||
type BlockBuilder = TestBlockBuilder;
|
||||
|
||||
fn session_keys(&self, _at: &BlockId) -> Result<Vec<SessionKey>> { unimplemented!() }
|
||||
fn validators(&self, _at: &BlockId) -> Result<Vec<AccountId>> { unimplemented!() }
|
||||
fn random_seed(&self, _at: &BlockId) -> Result<Hash> { unimplemented!() }
|
||||
fn duty_roster(&self, _at: &BlockId) -> Result<DutyRoster> { unimplemented!() }
|
||||
fn timestamp(&self, _at: &BlockId) -> Result<u64> { unimplemented!() }
|
||||
fn evaluate_block(&self, _at: &BlockId, _block: Block) -> Result<bool> { unimplemented!() }
|
||||
fn active_parachains(&self, _at: &BlockId) -> Result<Vec<ParaId>> { unimplemented!() }
|
||||
fn parachain_code(&self, _at: &BlockId, _parachain: ParaId) -> Result<Option<Vec<u8>>> { unimplemented!() }
|
||||
fn parachain_head(&self, _at: &BlockId, _parachain: ParaId) -> Result<Option<Vec<u8>>> { unimplemented!() }
|
||||
fn build_block(&self, _at: &BlockId, _inherent: ::primitives::InherentData) -> Result<Self::BlockBuilder> { unimplemented!() }
|
||||
fn inherent_extrinsics(&self, _at: &BlockId, _inherent: ::primitives::InherentData) -> Result<Vec<Vec<u8>>> { unimplemented!() }
|
||||
|
||||
fn index(&self, _at: &BlockId, _account: AccountId) -> Result<Index> {
|
||||
Ok((_account[0] as u32) + number_of(_at))
|
||||
}
|
||||
fn lookup(&self, _at: &BlockId, _address: RawAddress<AccountId, AccountIndex>) -> Result<Option<AccountId>> {
|
||||
match _address {
|
||||
RawAddress::Id(i) => Ok(Some(i)),
|
||||
RawAddress::Index(_) if self.no_lookup.load(atomic::Ordering::SeqCst) => Ok(None),
|
||||
RawAddress::Index(i) => Ok(match (i < 8, i + (number_of(_at) as u64) % 8) {
|
||||
(false, _) => None,
|
||||
(_, 0) => Some(Alice.to_raw_public().into()),
|
||||
(_, 1) => Some(Bob.to_raw_public().into()),
|
||||
(_, 2) => Some(Charlie.to_raw_public().into()),
|
||||
(_, 3) => Some(Dave.to_raw_public().into()),
|
||||
(_, 4) => Some(Eve.to_raw_public().into()),
|
||||
(_, 5) => Some(Ferdie.to_raw_public().into()),
|
||||
(_, 6) => Some(One.to_raw_public().into()),
|
||||
(_, 7) => Some(Two.to_raw_public().into()),
|
||||
_ => None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn uxt(who: Keyring, nonce: Index, use_id: bool) -> UncheckedExtrinsic {
|
||||
let sxt = BareExtrinsic {
|
||||
signed: who.to_raw_public().into(),
|
||||
index: nonce,
|
||||
function: Call::Timestamp(TimestampCall::set(0)),
|
||||
};
|
||||
let sig = sxt.using_encoded(|e| who.sign(e));
|
||||
UncheckedExtrinsic::new(Extrinsic {
|
||||
signed: if use_id { RawAddress::Id(sxt.signed) } else { RawAddress::Index(
|
||||
match who {
|
||||
Alice => 0,
|
||||
Bob => 1,
|
||||
Charlie => 2,
|
||||
Dave => 3,
|
||||
Eve => 4,
|
||||
Ferdie => 5,
|
||||
One => 6,
|
||||
Two => 7,
|
||||
}
|
||||
)},
|
||||
index: sxt.index,
|
||||
function: sxt.function,
|
||||
}, MaybeUnsigned(sig.into())).using_encoded(|e| UncheckedExtrinsic::decode(&mut &e[..])).unwrap()
|
||||
}
|
||||
|
||||
fn pool(api: &TestPolkadotApi) -> TransactionPool<TestPolkadotApi> {
|
||||
TransactionPool::new(Default::default(), Arc::new(api.clone()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_submission_should_work() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_submission_should_work() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_id_submission_should_work() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209), (Some(Alice.to_raw_public().into()), 210)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_index_submission_should_work() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, false)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209), (Some(Alice.to_raw_public().into()), 210)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_based_early_nonce_should_be_culled() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 208, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_based_early_nonce_should_be_culled() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 208, false)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_based_late_nonce_should_be_queued() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209), (Some(Alice.to_raw_public().into()), 210)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_based_late_nonce_should_be_queued() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, false)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209), (Some(Alice.to_raw_public().into()), 210)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_then_id_submission_should_make_progress() {
|
||||
let api = TestPolkadotApi::without_lookup();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
api.enable_lookup();
|
||||
pool.retry_verification(BlockId::number(0)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![
|
||||
(Some(Alice.to_raw_public().into()), 209),
|
||||
(Some(Alice.to_raw_public().into()), 210)
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retrying_verification_might_not_change_anything() {
|
||||
let api = TestPolkadotApi::without_lookup();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
pool.retry_verification(BlockId::number(1)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_then_index_submission_should_make_progress() {
|
||||
let api = TestPolkadotApi::without_lookup();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, false)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![
|
||||
(Some(Alice.to_raw_public().into()), 209)
|
||||
]);
|
||||
|
||||
// when
|
||||
api.enable_lookup();
|
||||
pool.retry_verification(BlockId::number(0)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![
|
||||
(Some(Alice.to_raw_public().into()), 209),
|
||||
(Some(Alice.to_raw_public().into()), 210)
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_change_should_result_in_second_tx_culled_or_future() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
let block = BlockId::number(0);
|
||||
pool.import_unchecked_extrinsic(block, uxt(Alice, 209, false)).unwrap();
|
||||
let hash = *pool.import_unchecked_extrinsic(block, uxt(Alice, 210, false)).unwrap().hash();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(block, |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![
|
||||
(Some(Alice.to_raw_public().into()), 209),
|
||||
(Some(Alice.to_raw_public().into()), 210)
|
||||
]);
|
||||
|
||||
// first xt is mined, but that has a side-effect of switching index 0 from Alice to Bob.
|
||||
// second xt now invalid signature, so it fails.
|
||||
|
||||
// there is no way of reporting this back to the queue right now (TODO). this should cause
|
||||
// the queue to flush all information regarding the sender index/account.
|
||||
|
||||
// after this, a re-evaluation of the second's readiness should result in it being thrown
|
||||
// out (or maybe placed in future queue).
|
||||
let err = pool.reverify_transaction(BlockId::number(1), hash).unwrap_err();
|
||||
match *err.kind() {
|
||||
::error::ErrorKind::Msg(ref m) if m == "bad signature in extrinsic" => {},
|
||||
ref e => assert!(false, "The transaction should be rejected with BadSignature error, got: {:?}", e),
|
||||
}
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(1), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user