Merge pull request #5454 from paritytech/aura-contract-warp
Groundwork for generalized warp sync
This commit is contained in:
commit
35958a0965
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -339,8 +339,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "elastic-array"
|
name = "elastic-array"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
source = "git+https://github.com/paritytech/elastic-array#346f1ba5982576dab9d0b8fa178b50e1db0a21cd"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@ -670,7 +670,7 @@ version = "1.7.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"elastic-array 0.6.0 (git+https://github.com/paritytech/elastic-array)",
|
"elastic-array 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)",
|
"eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)",
|
||||||
"ethcore-bigint 0.1.2",
|
"ethcore-bigint 0.1.2",
|
||||||
@ -2030,7 +2030,7 @@ name = "rlp"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"elastic-array 0.6.0 (git+https://github.com/paritytech/elastic-array)",
|
"elastic-array 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ethcore-bigint 0.1.2",
|
"ethcore-bigint 0.1.2",
|
||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -2817,7 +2817,7 @@ dependencies = [
|
|||||||
"checksum docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab32ea6e284d87987066f21a9e809a73c14720571ef34516f0890b3d355ccfd8"
|
"checksum docopt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab32ea6e284d87987066f21a9e809a73c14720571ef34516f0890b3d355ccfd8"
|
||||||
"checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f"
|
"checksum dtoa 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f"
|
||||||
"checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91"
|
"checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91"
|
||||||
"checksum elastic-array 0.6.0 (git+https://github.com/paritytech/elastic-array)" = "<none>"
|
"checksum elastic-array 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71a64decd4b8cd06654a4e643c45cb558ad554abbffd82a7e16e34f45f51b605"
|
||||||
"checksum env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e3856f1697098606fc6cb97a93de88ca3f3bc35bb878c725920e6e82ecf05e83"
|
"checksum env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e3856f1697098606fc6cb97a93de88ca3f3bc35bb878c725920e6e82ecf05e83"
|
||||||
"checksum eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)" = "<none>"
|
"checksum eth-secp256k1 0.5.6 (git+https://github.com/paritytech/rust-secp256k1)" = "<none>"
|
||||||
"checksum ethabi 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63df67d0af5e3cb906b667ca1a6e00baffbed87d0d8f5f78468a1f5eb3a66345"
|
"checksum ethabi 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63df67d0af5e3cb906b667ca1a6e00baffbed87d0d8f5f78468a1f5eb3a66345"
|
||||||
|
@ -258,7 +258,7 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
|
|||||||
}.fake_sign(req.from);
|
}.fake_sign(req.from);
|
||||||
|
|
||||||
self.prove_transaction(transaction, id)
|
self.prove_transaction(transaction, id)
|
||||||
.map(|proof| ::request::ExecutionResponse { items: proof })
|
.map(|(_, proof)| ::request::ExecutionResponse { items: proof })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||||
|
@ -22,9 +22,17 @@ use std::io::Write;
|
|||||||
|
|
||||||
// TODO: `include!` these from files where they're pretty-printed?
|
// TODO: `include!` these from files where they're pretty-printed?
|
||||||
const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#;
|
const REGISTRY_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"canReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"bytes32"}],"name":"setData","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"confirmReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[{"name":"success","type":"bool"}],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"drop","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"setFee","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_to","type":"address"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getData","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserved","outputs":[{"name":"reserved","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"proposeReverse","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"hasReverse","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"getReverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_data","type":"address"}],"name":"reverse","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"uint256"}],"name":"setUint","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_who","type":"address"}],"name":"confirmReverseAs","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"removeReverse","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_key","type":"string"},{"name":"_value","type":"address"}],"name":"setAddress","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"}]"#;
|
||||||
|
|
||||||
const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#;
|
const SERVICE_TRANSACTION_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"}]"#;
|
||||||
|
|
||||||
const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#;
|
const SECRETSTORE_ACL_STORAGE_ABI: &'static str = r#"[{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"document","type":"bytes32"}],"name":"checkPermissions","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"}]"#;
|
||||||
|
|
||||||
|
// be very careful changing these: ensure `ethcore/engines` validator sets have corresponding
|
||||||
|
// changes.
|
||||||
|
const VALIDATOR_SET_ABI: &'static str = r#"[{"constant":true,"inputs":[],"name":"transitionNonce","outputs":[{"name":"nonce","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"validators","type":"address[]"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":true,"name":"_nonce","type":"uint256"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"ValidatorsChanged","type":"event"}]"#;
|
||||||
|
|
||||||
|
const VALIDATOR_REPORT_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}]"#;
|
||||||
|
|
||||||
fn build_file(name: &str, abi: &str, filename: &str) {
|
fn build_file(name: &str, abi: &str, filename: &str) {
|
||||||
let code = ::native_contract_generator::generate_module(name, abi).unwrap();
|
let code = ::native_contract_generator::generate_module(name, abi).unwrap();
|
||||||
|
|
||||||
@ -39,4 +47,6 @@ fn main() {
|
|||||||
build_file("Registry", REGISTRY_ABI, "registry.rs");
|
build_file("Registry", REGISTRY_ABI, "registry.rs");
|
||||||
build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs");
|
build_file("ServiceTransactionChecker", SERVICE_TRANSACTION_ABI, "service_transaction.rs");
|
||||||
build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs");
|
build_file("SecretStoreAclStorage", SECRETSTORE_ACL_STORAGE_ABI, "secretstore_acl_storage.rs");
|
||||||
|
build_file("ValidatorSet", VALIDATOR_SET_ABI, "validator_set.rs");
|
||||||
|
build_file("ValidatorReport", VALIDATOR_REPORT_ABI, "validator_report.rs");
|
||||||
}
|
}
|
||||||
|
@ -46,8 +46,8 @@ pub fn generate_module(struct_name: &str, abi: &str) -> Result<String, Error> {
|
|||||||
|
|
||||||
Ok(format!(r##"
|
Ok(format!(r##"
|
||||||
use byteorder::{{BigEndian, ByteOrder}};
|
use byteorder::{{BigEndian, ByteOrder}};
|
||||||
use futures::{{future, Future, BoxFuture}};
|
use futures::{{future, Future, IntoFuture, BoxFuture}};
|
||||||
use ethabi::{{Contract, Interface, Token}};
|
use ethabi::{{Contract, Interface, Token, Event}};
|
||||||
use util::{{self, Uint}};
|
use util::{{self, Uint}};
|
||||||
|
|
||||||
pub struct {name} {{
|
pub struct {name} {{
|
||||||
@ -70,6 +70,11 @@ impl {name} {{
|
|||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
/// Access the underlying `ethabi` contract.
|
||||||
|
pub fn contract(this: &Self) -> &Contract {{
|
||||||
|
&this.contract
|
||||||
|
}}
|
||||||
|
|
||||||
{functions}
|
{functions}
|
||||||
}}
|
}}
|
||||||
"##,
|
"##,
|
||||||
@ -99,7 +104,10 @@ fn generate_functions(contract: &Contract) -> Result<String, Error> {
|
|||||||
/// Inputs: {abi_inputs:?}
|
/// Inputs: {abi_inputs:?}
|
||||||
/// Outputs: {abi_outputs:?}
|
/// Outputs: {abi_outputs:?}
|
||||||
pub fn {snake_name}<F, U>(&self, call: F, {params}) -> BoxFuture<{output_type}, String>
|
pub fn {snake_name}<F, U>(&self, call: F, {params}) -> BoxFuture<{output_type}, String>
|
||||||
where F: Fn(util::Address, Vec<u8>) -> U, U: Future<Item=Vec<u8>, Error=String> + Send + 'static
|
where
|
||||||
|
F: Fn(util::Address, Vec<u8>) -> U,
|
||||||
|
U: IntoFuture<Item=Vec<u8>, Error=String>,
|
||||||
|
U::Future: Send + 'static
|
||||||
{{
|
{{
|
||||||
let function = self.contract.function(r#"{abi_name}"#.to_string())
|
let function = self.contract.function(r#"{abi_name}"#.to_string())
|
||||||
.expect("function existence checked at compile-time; qed");
|
.expect("function existence checked at compile-time; qed");
|
||||||
@ -111,6 +119,7 @@ pub fn {snake_name}<F, U>(&self, call: F, {params}) -> BoxFuture<{output_type},
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
call_future
|
call_future
|
||||||
|
.into_future()
|
||||||
.and_then(move |out| function.decode_output(out).map_err(|e| format!("{{:?}}", e)))
|
.and_then(move |out| function.decode_output(out).map_err(|e| format!("{{:?}}", e)))
|
||||||
.map(::std::collections::VecDeque::from)
|
.map(::std::collections::VecDeque::from)
|
||||||
.and_then(|mut outputs| {decode_outputs})
|
.and_then(|mut outputs| {decode_outputs})
|
||||||
@ -299,10 +308,10 @@ fn detokenize(name: &str, output_type: ParamType) -> String {
|
|||||||
ParamType::Bool => format!("{}.to_bool()", name),
|
ParamType::Bool => format!("{}.to_bool()", name),
|
||||||
ParamType::String => format!("{}.to_string()", name),
|
ParamType::String => format!("{}.to_string()", name),
|
||||||
ParamType::Array(kind) => {
|
ParamType::Array(kind) => {
|
||||||
let read_array = format!("x.into_iter().map(|a| {{ {} }}).collect::<Option<Vec<_>>()",
|
let read_array = format!("x.into_iter().map(|a| {{ {} }}).collect::<Option<Vec<_>>>()",
|
||||||
detokenize("a", *kind));
|
detokenize("a", *kind));
|
||||||
|
|
||||||
format!("{}.to_array().and_then(|x| {})",
|
format!("{}.to_array().and_then(|x| {{ {} }})",
|
||||||
name, read_array)
|
name, read_array)
|
||||||
}
|
}
|
||||||
ParamType::FixedArray(_, _) => panic!("Fixed-length arrays not supported.")
|
ParamType::FixedArray(_, _) => panic!("Fixed-length arrays not supported.")
|
||||||
|
@ -26,7 +26,11 @@ extern crate ethcore_util as util;
|
|||||||
mod registry;
|
mod registry;
|
||||||
mod service_transaction;
|
mod service_transaction;
|
||||||
mod secretstore_acl_storage;
|
mod secretstore_acl_storage;
|
||||||
|
mod validator_set;
|
||||||
|
mod validator_report;
|
||||||
|
|
||||||
pub use self::registry::Registry;
|
pub use self::registry::Registry;
|
||||||
pub use self::service_transaction::ServiceTransactionChecker;
|
pub use self::service_transaction::ServiceTransactionChecker;
|
||||||
pub use self::secretstore_acl_storage::SecretStoreAclStorage;
|
pub use self::secretstore_acl_storage::SecretStoreAclStorage;
|
||||||
|
pub use self::validator_set::ValidatorSet;
|
||||||
|
pub use self::validator_report::ValidatorReport;
|
||||||
|
22
ethcore/native_contracts/src/validator_report.rs
Normal file
22
ethcore/native_contracts/src/validator_report.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#![allow(unused_mut, unused_variables, unused_imports)]
|
||||||
|
|
||||||
|
//! Validator reporting.
|
||||||
|
// TODO: testing.
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/validator_report.rs"));
|
22
ethcore/native_contracts/src/validator_set.rs
Normal file
22
ethcore/native_contracts/src/validator_set.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#![allow(unused_mut, unused_variables, unused_imports)]
|
||||||
|
|
||||||
|
//! Validator set contract.
|
||||||
|
// TODO: testing.
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/validator_set.rs"));
|
@ -484,7 +484,11 @@ impl LockedBlock {
|
|||||||
/// Provide a valid seal in order to turn this into a `SealedBlock`.
|
/// Provide a valid seal in order to turn this into a `SealedBlock`.
|
||||||
/// This does check the validity of `seal` with the engine.
|
/// This does check the validity of `seal` with the engine.
|
||||||
/// Returns the `ClosedBlock` back again if the seal is no good.
|
/// Returns the `ClosedBlock` back again if the seal is no good.
|
||||||
pub fn try_seal(self, engine: &Engine, seal: Vec<Bytes>) -> Result<SealedBlock, (Error, LockedBlock)> {
|
pub fn try_seal(
|
||||||
|
self,
|
||||||
|
engine: &Engine,
|
||||||
|
seal: Vec<Bytes>,
|
||||||
|
) -> Result<SealedBlock, (Error, LockedBlock)> {
|
||||||
let mut s = self;
|
let mut s = self;
|
||||||
s.block.header.set_seal(seal);
|
s.block.header.set_seal(seal);
|
||||||
match engine.verify_block_seal(&s.block.header) {
|
match engine.verify_block_seal(&s.block.header) {
|
||||||
|
@ -419,6 +419,45 @@ impl<'a> Iterator for AncestryIter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An iterator which walks all epoch transitions.
|
||||||
|
/// Returns epoch transitions.
|
||||||
|
pub struct EpochTransitionIter<'a> {
|
||||||
|
chain: &'a BlockChain,
|
||||||
|
prefix_iter: Box<Iterator<Item=(Box<[u8]>, Box<[u8]>)> + 'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for EpochTransitionIter<'a> {
|
||||||
|
type Item = (u64, EpochTransition);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
match self.prefix_iter.next() {
|
||||||
|
Some((key, val)) => {
|
||||||
|
// iterator may continue beyond values beginning with this
|
||||||
|
// prefix.
|
||||||
|
if !key.starts_with(&EPOCH_KEY_PREFIX[..]) { return None }
|
||||||
|
|
||||||
|
let transitions: EpochTransitions = ::rlp::decode(&val[..]);
|
||||||
|
|
||||||
|
// if there are multiple candidates, at most one will be on the
|
||||||
|
// canon chain.
|
||||||
|
for transition in transitions.candidates.into_iter() {
|
||||||
|
let is_in_canon_chain = self.chain.block_hash(transition.block_number)
|
||||||
|
.map_or(false, |hash| hash == transition.block_hash);
|
||||||
|
|
||||||
|
if is_in_canon_chain {
|
||||||
|
return Some((transitions.number, transition))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// some epochs never occurred on the main chain.
|
||||||
|
}
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BlockChain {
|
impl BlockChain {
|
||||||
/// Create new instance of blockchain from given Genesis.
|
/// Create new instance of blockchain from given Genesis.
|
||||||
pub fn new(config: Config, genesis: &[u8], db: Arc<KeyValueDB>) -> BlockChain {
|
pub fn new(config: Config, genesis: &[u8], db: Arc<KeyValueDB>) -> BlockChain {
|
||||||
@ -804,6 +843,35 @@ impl BlockChain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert an epoch transition. Provide an epoch number being transitioned to
|
||||||
|
/// and epoch transition object.
|
||||||
|
///
|
||||||
|
/// The block the transition occurred at should have already been inserted into the chain.
|
||||||
|
pub fn insert_epoch_transition(&self, batch: &mut DBTransaction, epoch_num: u64, transition: EpochTransition) {
|
||||||
|
let mut transitions = match self.db.read(db::COL_EXTRA, &epoch_num) {
|
||||||
|
Some(existing) => existing,
|
||||||
|
None => EpochTransitions {
|
||||||
|
number: epoch_num,
|
||||||
|
candidates: Vec::with_capacity(1),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ensure we don't write any duplicates.
|
||||||
|
if transitions.candidates.iter().find(|c| c.block_hash == transition.block_hash).is_none() {
|
||||||
|
transitions.candidates.push(transition);
|
||||||
|
batch.write(db::COL_EXTRA, &epoch_num, &transitions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all epoch transitions.
|
||||||
|
pub fn epoch_transitions(&self) -> EpochTransitionIter {
|
||||||
|
let iter = self.db.iter_from_prefix(db::COL_EXTRA, &EPOCH_KEY_PREFIX[..]);
|
||||||
|
EpochTransitionIter {
|
||||||
|
chain: self,
|
||||||
|
prefix_iter: iter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a child to a given block. Assumes that the block hash is in
|
/// Add a child to a given block. Assumes that the block hash is in
|
||||||
/// the chain and the child's parent is this block.
|
/// the chain and the child's parent is this block.
|
||||||
///
|
///
|
||||||
@ -2114,4 +2182,58 @@ mod tests {
|
|||||||
assert_eq!(bc.rewind(), Some(genesis_hash.clone()));
|
assert_eq!(bc.rewind(), Some(genesis_hash.clone()));
|
||||||
assert_eq!(bc.rewind(), None);
|
assert_eq!(bc.rewind(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epoch_transitions_iter() {
|
||||||
|
use blockchain::extras::EpochTransition;
|
||||||
|
|
||||||
|
let mut canon_chain = ChainGenerator::default();
|
||||||
|
let mut finalizer = BlockFinalizer::default();
|
||||||
|
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
|
|
||||||
|
let db = new_db();
|
||||||
|
{
|
||||||
|
let bc = new_chain(&genesis, db.clone());
|
||||||
|
let uncle = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap();
|
||||||
|
|
||||||
|
let mut batch = db.transaction();
|
||||||
|
// create a longer fork
|
||||||
|
for i in 0..5 {
|
||||||
|
let canon_block = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
|
let hash = BlockView::new(&canon_block).header_view().sha3();
|
||||||
|
|
||||||
|
bc.insert_block(&mut batch, &canon_block, vec![]);
|
||||||
|
bc.insert_epoch_transition(&mut batch, i, EpochTransition {
|
||||||
|
block_hash: hash,
|
||||||
|
block_number: i + 1,
|
||||||
|
proof: vec![],
|
||||||
|
state_proof: vec![],
|
||||||
|
});
|
||||||
|
bc.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(bc.best_block_number(), 5);
|
||||||
|
|
||||||
|
let hash = BlockView::new(&uncle).header_view().sha3();
|
||||||
|
bc.insert_block(&mut batch, &uncle, vec![]);
|
||||||
|
bc.insert_epoch_transition(&mut batch, 999, EpochTransition {
|
||||||
|
block_hash: hash,
|
||||||
|
block_number: 1,
|
||||||
|
proof: vec![],
|
||||||
|
state_proof: vec![]
|
||||||
|
});
|
||||||
|
|
||||||
|
db.write(batch).unwrap();
|
||||||
|
bc.commit();
|
||||||
|
|
||||||
|
// epoch 999 not in canonical chain.
|
||||||
|
assert_eq!(bc.epoch_transitions().map(|(i, _)| i).collect::<Vec<_>>(), vec![0, 1, 2, 3, 4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-loading the blockchain should load the correct best block.
|
||||||
|
let bc = new_chain(&genesis, db);
|
||||||
|
|
||||||
|
assert_eq!(bc.best_block_number(), 5);
|
||||||
|
assert_eq!(bc.epoch_transitions().map(|(i, _)| i).collect::<Vec<_>>(), vec![0, 1, 2, 3, 4]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
use bloomchain;
|
use bloomchain;
|
||||||
use util::*;
|
use util::*;
|
||||||
|
use util::kvdb::PREFIX_LEN as DB_PREFIX_LEN;
|
||||||
use rlp::*;
|
use rlp::*;
|
||||||
use header::BlockNumber;
|
use header::BlockNumber;
|
||||||
use receipt::Receipt;
|
use receipt::Receipt;
|
||||||
@ -37,6 +38,8 @@ pub enum ExtrasIndex {
|
|||||||
BlocksBlooms = 3,
|
BlocksBlooms = 3,
|
||||||
/// Block receipts index
|
/// Block receipts index
|
||||||
BlockReceipts = 4,
|
BlockReceipts = 4,
|
||||||
|
/// Epoch transition data index.
|
||||||
|
EpochTransitions = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_index(hash: &H256, i: ExtrasIndex) -> H264 {
|
fn with_index(hash: &H256, i: ExtrasIndex) -> H264 {
|
||||||
@ -134,6 +137,36 @@ impl Key<BlockReceipts> for H256 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// length of epoch keys.
|
||||||
|
pub const EPOCH_KEY_LEN: usize = DB_PREFIX_LEN + 16;
|
||||||
|
|
||||||
|
/// epoch key prefix.
|
||||||
|
/// used to iterate over all epoch transitions in order from genesis.
|
||||||
|
pub const EPOCH_KEY_PREFIX: &'static [u8; DB_PREFIX_LEN] = &[
|
||||||
|
ExtrasIndex::EpochTransitions as u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub struct EpochTransitionsKey([u8; EPOCH_KEY_LEN]);
|
||||||
|
impl Deref for EpochTransitionsKey {
|
||||||
|
type Target = [u8];
|
||||||
|
|
||||||
|
fn deref(&self) -> &[u8] { &self.0[..] }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Key<EpochTransitions> for u64 {
|
||||||
|
type Target = EpochTransitionsKey;
|
||||||
|
|
||||||
|
fn key(&self) -> Self::Target {
|
||||||
|
let mut arr = [0u8; EPOCH_KEY_LEN];
|
||||||
|
arr[..DB_PREFIX_LEN].copy_from_slice(&EPOCH_KEY_PREFIX[..]);
|
||||||
|
|
||||||
|
write!(&mut arr[DB_PREFIX_LEN..], "{:016x}", self)
|
||||||
|
.expect("format arg is valid; no more than 16 chars will be written; qed");
|
||||||
|
|
||||||
|
EpochTransitionsKey(arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Familial details concerning a block
|
/// Familial details concerning a block
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BlockDetails {
|
pub struct BlockDetails {
|
||||||
@ -144,7 +177,7 @@ pub struct BlockDetails {
|
|||||||
/// Parent block hash
|
/// Parent block hash
|
||||||
pub parent: H256,
|
pub parent: H256,
|
||||||
/// List of children block hashes
|
/// List of children block hashes
|
||||||
pub children: Vec<H256>
|
pub children: Vec<H256>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeapSizeOf for BlockDetails {
|
impl HeapSizeOf for BlockDetails {
|
||||||
@ -241,6 +274,63 @@ impl HeapSizeOf for BlockReceipts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Candidate transitions to an epoch with specific number.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EpochTransitions {
|
||||||
|
pub number: u64,
|
||||||
|
pub candidates: Vec<EpochTransition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encodable for EpochTransitions {
|
||||||
|
fn rlp_append(&self, s: &mut RlpStream) {
|
||||||
|
s.begin_list(2).append(&self.number).append_list(&self.candidates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decodable for EpochTransitions {
|
||||||
|
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
|
||||||
|
Ok(EpochTransitions {
|
||||||
|
number: rlp.val_at(0)?,
|
||||||
|
candidates: rlp.list_at(1)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct EpochTransition {
|
||||||
|
pub block_hash: H256, // block hash at which the transition occurred.
|
||||||
|
pub block_number: BlockNumber, // block number at which the tranition occurred.
|
||||||
|
pub proof: Vec<u8>, // "transition/epoch" proof from the engine.
|
||||||
|
pub state_proof: Vec<DBValue>, // state items necessary to regenerate proof.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encodable for EpochTransition {
|
||||||
|
fn rlp_append(&self, s: &mut RlpStream) {
|
||||||
|
s.begin_list(4)
|
||||||
|
.append(&self.block_hash)
|
||||||
|
.append(&self.block_number)
|
||||||
|
.append(&self.proof)
|
||||||
|
.begin_list(self.state_proof.len());
|
||||||
|
|
||||||
|
for item in &self.state_proof {
|
||||||
|
s.append(&&**item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decodable for EpochTransition {
|
||||||
|
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
|
||||||
|
Ok(EpochTransition {
|
||||||
|
block_hash: rlp.val_at(0)?,
|
||||||
|
block_number: rlp.val_at(1)?,
|
||||||
|
proof: rlp.val_at(2)?,
|
||||||
|
state_proof: rlp.at(3)?.iter().map(|x| {
|
||||||
|
Ok(DBValue::from_slice(x.data()?))
|
||||||
|
}).collect::<Result<Vec<_>, _>>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use rlp::*;
|
use rlp::*;
|
||||||
|
@ -31,5 +31,6 @@ pub mod generator;
|
|||||||
pub use self::blockchain::{BlockProvider, BlockChain};
|
pub use self::blockchain::{BlockProvider, BlockChain};
|
||||||
pub use self::cache::CacheSize;
|
pub use self::cache::CacheSize;
|
||||||
pub use self::config::Config;
|
pub use self::config::Config;
|
||||||
|
pub use self::extras::EpochTransition;
|
||||||
pub use types::tree_route::TreeRoute;
|
pub use types::tree_route::TreeRoute;
|
||||||
pub use self::import_route::ImportRoute;
|
pub use self::import_route::ImportRoute;
|
||||||
|
@ -32,13 +32,13 @@ use util::kvdb::*;
|
|||||||
// other
|
// other
|
||||||
use basic_types::Seal;
|
use basic_types::Seal;
|
||||||
use block::*;
|
use block::*;
|
||||||
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
|
use blockchain::{BlockChain, BlockProvider, EpochTransition, TreeRoute, ImportRoute};
|
||||||
use blockchain::extras::TransactionAddress;
|
use blockchain::extras::TransactionAddress;
|
||||||
use client::Error as ClientError;
|
use client::Error as ClientError;
|
||||||
use client::{
|
use client::{
|
||||||
BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient,
|
BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient,
|
||||||
MiningBlockChainClient, EngineClient, TraceFilter, CallAnalytics, BlockImportError, Mode,
|
MiningBlockChainClient, EngineClient, TraceFilter, CallAnalytics, BlockImportError, Mode,
|
||||||
ChainNotify, PruningInfo,
|
ChainNotify, PruningInfo, ProvingBlockChainClient,
|
||||||
};
|
};
|
||||||
use encoded;
|
use encoded;
|
||||||
use engines::Engine;
|
use engines::Engine;
|
||||||
@ -49,7 +49,7 @@ use evm::{Factory as EvmFactory, Schedule};
|
|||||||
use executive::{Executive, Executed, TransactOptions, contract_address};
|
use executive::{Executive, Executed, TransactOptions, contract_address};
|
||||||
use factory::Factories;
|
use factory::Factories;
|
||||||
use futures::{future, Future};
|
use futures::{future, Future};
|
||||||
use header::BlockNumber;
|
use header::{BlockNumber, Header};
|
||||||
use io::*;
|
use io::*;
|
||||||
use log_entry::LocalizedLogEntry;
|
use log_entry::LocalizedLogEntry;
|
||||||
use miner::{Miner, MinerService, TransactionImportResult};
|
use miner::{Miner, MinerService, TransactionImportResult};
|
||||||
@ -247,17 +247,27 @@ impl Client {
|
|||||||
exit_handler: Mutex::new(None),
|
exit_handler: Mutex::new(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// prune old states.
|
||||||
{
|
{
|
||||||
let state_db = client.state_db.lock().boxed_clone();
|
let state_db = client.state_db.lock().boxed_clone();
|
||||||
let chain = client.chain.read();
|
let chain = client.chain.read();
|
||||||
client.prune_ancient(state_db, &chain)?;
|
client.prune_ancient(state_db, &chain)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure genesis epoch proof in the DB.
|
||||||
|
{
|
||||||
|
let chain = client.chain.read();
|
||||||
|
client.generate_epoch_proof(&spec.genesis_header(), 0, &*chain);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) {
|
if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) {
|
||||||
trace!(target: "client", "Found registrar at {}", reg_addr);
|
trace!(target: "client", "Found registrar at {}", reg_addr);
|
||||||
let registrar = Registry::new(reg_addr);
|
let registrar = Registry::new(reg_addr);
|
||||||
*client.registrar.lock() = Some(registrar);
|
*client.registrar.lock() = Some(registrar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure buffered changes are flushed.
|
||||||
|
client.db.read().flush().map_err(ClientError::Database)?;
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,6 +390,12 @@ impl Client {
|
|||||||
return Err(());
|
return Err(());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let verify_external_result = self.verifier.verify_block_external(header, &block.bytes, engine);
|
||||||
|
if let Err(e) = verify_external_result {
|
||||||
|
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
|
||||||
|
return Err(());
|
||||||
|
};
|
||||||
|
|
||||||
// Check if Parent is in chain
|
// Check if Parent is in chain
|
||||||
let chain_has_parent = chain.block_header(header.parent_hash());
|
let chain_has_parent = chain.block_header(header.parent_hash());
|
||||||
if let Some(parent) = chain_has_parent {
|
if let Some(parent) = chain_has_parent {
|
||||||
@ -398,7 +414,7 @@ impl Client {
|
|||||||
|
|
||||||
// Final Verification
|
// Final Verification
|
||||||
if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) {
|
if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) {
|
||||||
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
|
warn!(target: "client", "Stage 5 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,6 +585,22 @@ impl Client {
|
|||||||
//let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new));
|
//let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new));
|
||||||
|
|
||||||
let mut batch = DBTransaction::new();
|
let mut batch = DBTransaction::new();
|
||||||
|
|
||||||
|
// generate validation proof if the engine requires them.
|
||||||
|
// TODO: make conditional?
|
||||||
|
let entering_new_epoch = {
|
||||||
|
use engines::EpochChange;
|
||||||
|
match self.engine.is_epoch_end(block.header(), Some(block_data), Some(&receipts)) {
|
||||||
|
EpochChange::Yes(e, _) => Some((block.header().clone(), e)),
|
||||||
|
EpochChange::No => None,
|
||||||
|
EpochChange::Unsure(_) => {
|
||||||
|
warn!(target: "client", "Detected invalid engine implementation.");
|
||||||
|
warn!(target: "client", "Engine claims to require more block data, but everything provided.");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// CHECK! I *think* this is fine, even if the state_root is equal to another
|
// CHECK! I *think* this is fine, even if the state_root is equal to another
|
||||||
// already-imported block of the same number.
|
// already-imported block of the same number.
|
||||||
// TODO: Prove it with a test.
|
// TODO: Prove it with a test.
|
||||||
@ -576,6 +608,7 @@ impl Client {
|
|||||||
|
|
||||||
state.journal_under(&mut batch, number, hash).expect("DB commit failed");
|
state.journal_under(&mut batch, number, hash).expect("DB commit failed");
|
||||||
let route = chain.insert_block(&mut batch, block_data, receipts);
|
let route = chain.insert_block(&mut batch, block_data, receipts);
|
||||||
|
|
||||||
self.tracedb.read().import(&mut batch, TraceImportRequest {
|
self.tracedb.read().import(&mut batch, TraceImportRequest {
|
||||||
traces: traces.into(),
|
traces: traces.into(),
|
||||||
block_hash: hash.clone(),
|
block_hash: hash.clone(),
|
||||||
@ -595,9 +628,58 @@ impl Client {
|
|||||||
warn!("Failed to prune ancient state data: {}", e);
|
warn!("Failed to prune ancient state data: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some((header, epoch)) = entering_new_epoch {
|
||||||
|
self.generate_epoch_proof(&header, epoch, &chain);
|
||||||
|
}
|
||||||
|
|
||||||
route
|
route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate an epoch transition proof at the given block, and write it into the given blockchain.
|
||||||
|
fn generate_epoch_proof(&self, header: &Header, epoch_number: u64, chain: &BlockChain) {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
let mut batch = DBTransaction::new();
|
||||||
|
let hash = header.hash();
|
||||||
|
debug!(target: "client", "Generating validation proof for block {}", hash);
|
||||||
|
|
||||||
|
// proof is two-part. state items read in lexicographical order,
|
||||||
|
// and the secondary "proof" part.
|
||||||
|
let read_values = RefCell::new(BTreeSet::new());
|
||||||
|
let block_id = BlockId::Hash(hash.clone());
|
||||||
|
let proof = {
|
||||||
|
let call = |a, d| {
|
||||||
|
let tx = self.contract_call_tx(block_id, a, d);
|
||||||
|
let (result, items) = self.prove_transaction(tx, block_id)
|
||||||
|
.ok_or_else(|| format!("Unable to make call to generate epoch proof."))?;
|
||||||
|
|
||||||
|
read_values.borrow_mut().extend(items);
|
||||||
|
Ok(result)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.engine.epoch_proof(&header, &call)
|
||||||
|
};
|
||||||
|
|
||||||
|
// insert into database, using the generated proof.
|
||||||
|
match proof {
|
||||||
|
Ok(proof) => {
|
||||||
|
chain.insert_epoch_transition(&mut batch, epoch_number, EpochTransition {
|
||||||
|
block_hash: hash.clone(),
|
||||||
|
block_number: header.number(),
|
||||||
|
proof: proof,
|
||||||
|
state_proof: read_values.into_inner().into_iter().collect(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.db.read().write_buffered(batch);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(target: "client", "Error generating epoch change proof for block {}: {}", hash, e);
|
||||||
|
warn!(target: "client", "Snapshots generated by this node will be incomplete.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// prune ancient states until below the memory limit or only the minimum amount remain.
|
// prune ancient states until below the memory limit or only the minimum amount remain.
|
||||||
fn prune_ancient(&self, mut state_db: StateDB, chain: &BlockChain) -> Result<(), ClientError> {
|
fn prune_ancient(&self, mut state_db: StateDB, chain: &BlockChain) -> Result<(), ClientError> {
|
||||||
let number = match state_db.journal_db().latest_era() {
|
let number = match state_db.journal_db().latest_era() {
|
||||||
@ -814,7 +896,7 @@ impl Client {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
snapshot::take_snapshot(&self.chain.read(), start_hash, db.as_hashdb(), writer, p)?;
|
snapshot::take_snapshot(&*self.engine, &self.chain.read(), start_hash, db.as_hashdb(), writer, p)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -865,6 +947,20 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transaction for calling contracts from services like engine.
|
||||||
|
// from the null sender, with 50M gas.
|
||||||
|
fn contract_call_tx(&self, block_id: BlockId, address: Address, data: Bytes) -> SignedTransaction {
|
||||||
|
let from = Address::default();
|
||||||
|
Transaction {
|
||||||
|
nonce: self.nonce(&from, block_id).unwrap_or_else(|| self.engine.account_start_nonce()),
|
||||||
|
action: Action::Call(address),
|
||||||
|
gas: U256::from(50_000_000),
|
||||||
|
gas_price: U256::default(),
|
||||||
|
value: U256::default(),
|
||||||
|
data: data,
|
||||||
|
}.fake_sign(from)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl snapshot::DatabaseRestore for Client {
|
impl snapshot::DatabaseRestore for Client {
|
||||||
@ -1456,15 +1552,7 @@ impl BlockChainClient for Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String> {
|
fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String> {
|
||||||
let from = Address::default();
|
let transaction = self.contract_call_tx(block_id, address, data);
|
||||||
let transaction = Transaction {
|
|
||||||
nonce: self.latest_nonce(&from),
|
|
||||||
action: Action::Call(address),
|
|
||||||
gas: U256::from(50_000_000),
|
|
||||||
gas_price: U256::default(),
|
|
||||||
value: U256::default(),
|
|
||||||
data: data,
|
|
||||||
}.fake_sign(from);
|
|
||||||
|
|
||||||
self.call(&transaction, block_id, Default::default())
|
self.call(&transaction, block_id, Default::default())
|
||||||
.map_err(|e| format!("{:?}", e))
|
.map_err(|e| format!("{:?}", e))
|
||||||
@ -1620,7 +1708,7 @@ impl MayPanic for Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::client::ProvingBlockChainClient for Client {
|
impl ProvingBlockChainClient for Client {
|
||||||
fn prove_storage(&self, key1: H256, key2: H256, id: BlockId) -> Option<(Vec<Bytes>, H256)> {
|
fn prove_storage(&self, key1: H256, key2: H256, id: BlockId) -> Option<(Vec<Bytes>, H256)> {
|
||||||
self.state_at(id)
|
self.state_at(id)
|
||||||
.and_then(move |state| state.prove_storage(key1, key2).ok())
|
.and_then(move |state| state.prove_storage(key1, key2).ok())
|
||||||
@ -1631,7 +1719,7 @@ impl ::client::ProvingBlockChainClient for Client {
|
|||||||
.and_then(move |state| state.prove_account(key1).ok())
|
.and_then(move |state| state.prove_account(key1).ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<Vec<DBValue>> {
|
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<(Bytes, Vec<DBValue>)> {
|
||||||
let (state, mut env_info) = match (self.state_at(id), self.env_info(id)) {
|
let (state, mut env_info) = match (self.state_at(id), self.env_info(id)) {
|
||||||
(Some(s), Some(e)) => (s, e),
|
(Some(s), Some(e)) => (s, e),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
@ -1646,8 +1734,9 @@ impl ::client::ProvingBlockChainClient for Client {
|
|||||||
let res = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(&transaction, options);
|
let res = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(&transaction, options);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(ExecutionError::Internal(_)) => return None,
|
Err(ExecutionError::Internal(_)) => None,
|
||||||
_ => return Some(state.drop().1.extract_proof()),
|
Err(_) => Some((Vec::new(), state.drop().1.extract_proof())),
|
||||||
|
Ok(res) => Some((res.output, state.drop().1.extract_proof())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -769,7 +769,7 @@ impl ProvingBlockChainClient for TestBlockChainClient {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prove_transaction(&self, _: SignedTransaction, _: BlockId) -> Option<Vec<DBValue>> {
|
fn prove_transaction(&self, _: SignedTransaction, _: BlockId) -> Option<(Bytes, Vec<DBValue>)> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,5 +327,7 @@ pub trait ProvingBlockChainClient: BlockChainClient {
|
|||||||
fn prove_account(&self, key1: H256, id: BlockId) -> Option<(Vec<Bytes>, BasicAccount)>;
|
fn prove_account(&self, key1: H256, id: BlockId) -> Option<(Vec<Bytes>, BasicAccount)>;
|
||||||
|
|
||||||
/// Prove execution of a transaction at the given block.
|
/// Prove execution of a transaction at the given block.
|
||||||
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<Vec<DBValue>>;
|
/// Returns the output of the call and a vector of database items necessary
|
||||||
|
/// to reproduce it.
|
||||||
|
fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option<(Bytes, Vec<DBValue>)>;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ use rlp::{UntrustedRlp, encode};
|
|||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use block::*;
|
use block::*;
|
||||||
use spec::CommonParams;
|
use spec::CommonParams;
|
||||||
use engines::{Engine, Seal, EngineError};
|
use engines::{Call, Engine, Seal, EngineError};
|
||||||
use header::{Header, BlockNumber};
|
use header::{Header, BlockNumber};
|
||||||
use error::{Error, TransactionError, BlockError};
|
use error::{Error, TransactionError, BlockError};
|
||||||
use evm::Schedule;
|
use evm::Schedule;
|
||||||
@ -36,7 +36,7 @@ use transaction::UnverifiedTransaction;
|
|||||||
use client::{Client, EngineClient};
|
use client::{Client, EngineClient};
|
||||||
use state::CleanupMode;
|
use state::CleanupMode;
|
||||||
use super::signer::EngineSigner;
|
use super::signer::EngineSigner;
|
||||||
use super::validator_set::{ValidatorSet, new_validator_set};
|
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
||||||
|
|
||||||
/// `AuthorityRound` params.
|
/// `AuthorityRound` params.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -74,27 +74,78 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Engine using `AuthorityRound` proof-of-work consensus algorithm, suitable for Ethereum
|
// Helper for managing the step.
|
||||||
/// mainnet chains in the Olympic, Frontier and Homestead eras.
|
#[derive(Debug)]
|
||||||
|
struct Step {
|
||||||
|
calibrate: bool, // whether calibration is enabled.
|
||||||
|
inner: AtomicUsize,
|
||||||
|
duration: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Step {
|
||||||
|
fn load(&self) -> usize { self.inner.load(AtomicOrdering::SeqCst) }
|
||||||
|
fn duration_remaining(&self) -> Duration {
|
||||||
|
let now = unix_now();
|
||||||
|
let step_end = self.duration * (self.load() as u32 + 1);
|
||||||
|
if step_end > now {
|
||||||
|
step_end - now
|
||||||
|
} else {
|
||||||
|
Duration::from_secs(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn increment(&self) {
|
||||||
|
self.inner.fetch_add(1, AtomicOrdering::SeqCst);
|
||||||
|
}
|
||||||
|
fn calibrate(&self) {
|
||||||
|
if self.calibrate {
|
||||||
|
let new_step = unix_now().as_secs() / self.duration.as_secs();
|
||||||
|
self.inner.store(new_step as usize, AtomicOrdering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_future(&self, given: usize) -> bool {
|
||||||
|
if given > self.load() + 1 {
|
||||||
|
// Make absolutely sure that the given step is correct.
|
||||||
|
self.calibrate();
|
||||||
|
given > self.load() + 1
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Engine using `AuthorityRound` proof-of-authority BFT consensus.
|
||||||
pub struct AuthorityRound {
|
pub struct AuthorityRound {
|
||||||
params: CommonParams,
|
params: CommonParams,
|
||||||
gas_limit_bound_divisor: U256,
|
gas_limit_bound_divisor: U256,
|
||||||
block_reward: U256,
|
block_reward: U256,
|
||||||
registrar: Address,
|
registrar: Address,
|
||||||
step_duration: Duration,
|
|
||||||
builtins: BTreeMap<Address, Builtin>,
|
builtins: BTreeMap<Address, Builtin>,
|
||||||
transition_service: IoService<()>,
|
transition_service: IoService<()>,
|
||||||
step: AtomicUsize,
|
step: Arc<Step>,
|
||||||
proposed: AtomicBool,
|
proposed: AtomicBool,
|
||||||
client: RwLock<Option<Weak<EngineClient>>>,
|
client: RwLock<Option<Weak<EngineClient>>>,
|
||||||
signer: EngineSigner,
|
signer: EngineSigner,
|
||||||
validators: Box<ValidatorSet>,
|
validators: Box<ValidatorSet>,
|
||||||
/// Is this Engine just for testing (prevents step calibration).
|
|
||||||
calibrate_step: bool,
|
|
||||||
validate_score_transition: u64,
|
validate_score_transition: u64,
|
||||||
eip155_transition: u64,
|
eip155_transition: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// header-chain validator.
|
||||||
|
struct EpochVerifier {
|
||||||
|
epoch_number: u64,
|
||||||
|
step: Arc<Step>,
|
||||||
|
subchain_validators: SimpleList,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::EpochVerifier for EpochVerifier {
|
||||||
|
fn epoch_number(&self) -> u64 { self.epoch_number.clone() }
|
||||||
|
fn verify_light(&self, header: &Header) -> Result<(), Error> {
|
||||||
|
// always check the seal since it's fast.
|
||||||
|
// nothing heavier to do.
|
||||||
|
verify_external(header, &self.subchain_validators, &*self.step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
|
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
|
||||||
UntrustedRlp::new(&header.seal().get(0).expect("was either checked with verify_block_basic or is genesis; has 2 fields; qed (Make sure the spec file has a correct genesis seal)")).as_val()
|
UntrustedRlp::new(&header.seal().get(0).expect("was either checked with verify_block_basic or is genesis; has 2 fields; qed (Make sure the spec file has a correct genesis seal)")).as_val()
|
||||||
}
|
}
|
||||||
@ -103,6 +154,26 @@ fn header_signature(header: &Header) -> Result<Signature, ::rlp::DecoderError> {
|
|||||||
UntrustedRlp::new(&header.seal().get(1).expect("was checked with verify_block_basic; has 2 fields; qed")).as_val::<H520>().map(Into::into)
|
UntrustedRlp::new(&header.seal().get(1).expect("was checked with verify_block_basic; has 2 fields; qed")).as_val::<H520>().map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_external(header: &Header, validators: &ValidatorSet, step: &Step) -> Result<(), Error> {
|
||||||
|
let header_step = header_step(header)?;
|
||||||
|
|
||||||
|
// Give one step slack if step is lagging, double vote is still not possible.
|
||||||
|
if step.is_future(header_step) {
|
||||||
|
trace!(target: "engine", "verify_block_unordered: block from the future");
|
||||||
|
validators.report_benign(header.author());
|
||||||
|
Err(BlockError::InvalidSeal)?
|
||||||
|
} else {
|
||||||
|
let proposer_signature = header_signature(header)?;
|
||||||
|
let correct_proposer = validators.get(header.parent_hash(), header_step);
|
||||||
|
if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
|
||||||
|
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step);
|
||||||
|
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait AsMillis {
|
trait AsMillis {
|
||||||
fn as_millis(&self) -> u64;
|
fn as_millis(&self) -> u64;
|
||||||
}
|
}
|
||||||
@ -124,15 +195,17 @@ impl AuthorityRound {
|
|||||||
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
|
gas_limit_bound_divisor: our_params.gas_limit_bound_divisor,
|
||||||
block_reward: our_params.block_reward,
|
block_reward: our_params.block_reward,
|
||||||
registrar: our_params.registrar,
|
registrar: our_params.registrar,
|
||||||
step_duration: our_params.step_duration,
|
|
||||||
builtins: builtins,
|
builtins: builtins,
|
||||||
transition_service: IoService::<()>::start()?,
|
transition_service: IoService::<()>::start()?,
|
||||||
step: AtomicUsize::new(initial_step),
|
step: Arc::new(Step {
|
||||||
|
inner: AtomicUsize::new(initial_step),
|
||||||
|
calibrate: our_params.start_step.is_none(),
|
||||||
|
duration: our_params.step_duration,
|
||||||
|
}),
|
||||||
proposed: AtomicBool::new(false),
|
proposed: AtomicBool::new(false),
|
||||||
client: RwLock::new(None),
|
client: RwLock::new(None),
|
||||||
signer: Default::default(),
|
signer: Default::default(),
|
||||||
validators: new_validator_set(our_params.validators),
|
validators: new_validator_set(our_params.validators),
|
||||||
calibrate_step: our_params.start_step.is_none(),
|
|
||||||
validate_score_transition: our_params.validate_score_transition,
|
validate_score_transition: our_params.validate_score_transition,
|
||||||
eip155_transition: our_params.eip155_transition,
|
eip155_transition: our_params.eip155_transition,
|
||||||
});
|
});
|
||||||
@ -144,22 +217,6 @@ impl AuthorityRound {
|
|||||||
Ok(engine)
|
Ok(engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calibrate_step(&self) {
|
|
||||||
if self.calibrate_step {
|
|
||||||
self.step.store((unix_now().as_secs() / self.step_duration.as_secs()) as usize, AtomicOrdering::SeqCst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remaining_step_duration(&self) -> Duration {
|
|
||||||
let now = unix_now();
|
|
||||||
let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
|
|
||||||
if step_end > now {
|
|
||||||
step_end - now
|
|
||||||
} else {
|
|
||||||
Duration::from_secs(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn step_proposer(&self, bh: &H256, step: usize) -> Address {
|
fn step_proposer(&self, bh: &H256, step: usize) -> Address {
|
||||||
self.validators.get(bh, step)
|
self.validators.get(bh, step)
|
||||||
}
|
}
|
||||||
@ -167,16 +224,6 @@ impl AuthorityRound {
|
|||||||
fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool {
|
fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool {
|
||||||
self.step_proposer(bh, step) == *address
|
self.step_proposer(bh, step) == *address
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_future_step(&self, step: usize) -> bool {
|
|
||||||
if step > self.step.load(AtomicOrdering::SeqCst) + 1 {
|
|
||||||
// Make absolutely sure that the step is correct.
|
|
||||||
self.calibrate_step();
|
|
||||||
step > self.step.load(AtomicOrdering::SeqCst) + 1
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unix_now() -> Duration {
|
fn unix_now() -> Duration {
|
||||||
@ -192,7 +239,8 @@ const ENGINE_TIMEOUT_TOKEN: TimerToken = 23;
|
|||||||
impl IoHandler<()> for TransitionHandler {
|
impl IoHandler<()> for TransitionHandler {
|
||||||
fn initialize(&self, io: &IoContext<()>) {
|
fn initialize(&self, io: &IoContext<()>) {
|
||||||
if let Some(engine) = self.engine.upgrade() {
|
if let Some(engine) = self.engine.upgrade() {
|
||||||
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis())
|
let remaining = engine.step.duration_remaining();
|
||||||
|
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, remaining.as_millis())
|
||||||
.unwrap_or_else(|e| warn!(target: "engine", "Failed to start consensus step timer: {}.", e))
|
.unwrap_or_else(|e| warn!(target: "engine", "Failed to start consensus step timer: {}.", e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +249,8 @@ impl IoHandler<()> for TransitionHandler {
|
|||||||
if timer == ENGINE_TIMEOUT_TOKEN {
|
if timer == ENGINE_TIMEOUT_TOKEN {
|
||||||
if let Some(engine) = self.engine.upgrade() {
|
if let Some(engine) = self.engine.upgrade() {
|
||||||
engine.step();
|
engine.step();
|
||||||
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis())
|
let remaining = engine.step.duration_remaining();
|
||||||
|
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, remaining.as_millis())
|
||||||
.unwrap_or_else(|e| warn!(target: "engine", "Failed to restart consensus step timer: {}.", e))
|
.unwrap_or_else(|e| warn!(target: "engine", "Failed to restart consensus step timer: {}.", e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,7 +272,7 @@ impl Engine for AuthorityRound {
|
|||||||
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins }
|
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins }
|
||||||
|
|
||||||
fn step(&self) {
|
fn step(&self) {
|
||||||
self.step.fetch_add(1, AtomicOrdering::SeqCst);
|
self.step.increment();
|
||||||
self.proposed.store(false, AtomicOrdering::SeqCst);
|
self.proposed.store(false, AtomicOrdering::SeqCst);
|
||||||
if let Some(ref weak) = *self.client.read() {
|
if let Some(ref weak) = *self.client.read() {
|
||||||
if let Some(c) = weak.upgrade() {
|
if let Some(c) = weak.upgrade() {
|
||||||
@ -247,7 +296,7 @@ impl Engine for AuthorityRound {
|
|||||||
|
|
||||||
fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) {
|
fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) {
|
||||||
// Chain scoring: total weight is sqrt(U256::max_value())*height - step
|
// Chain scoring: total weight is sqrt(U256::max_value())*height - step
|
||||||
let new_difficulty = U256::from(U128::max_value()) + header_step(parent).expect("Header has been verified; qed").into() - self.step.load(AtomicOrdering::SeqCst).into();
|
let new_difficulty = U256::from(U128::max_value()) + header_step(parent).expect("Header has been verified; qed").into() - self.step.load().into();
|
||||||
header.set_difficulty(new_difficulty);
|
header.set_difficulty(new_difficulty);
|
||||||
header.set_gas_limit({
|
header.set_gas_limit({
|
||||||
let gas_limit = parent.gas_limit().clone();
|
let gas_limit = parent.gas_limit().clone();
|
||||||
@ -271,7 +320,7 @@ impl Engine for AuthorityRound {
|
|||||||
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
|
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
|
||||||
if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; }
|
if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; }
|
||||||
let header = block.header();
|
let header = block.header();
|
||||||
let step = self.step.load(AtomicOrdering::SeqCst);
|
let step = self.step.load();
|
||||||
if self.is_step_proposer(header.parent_hash(), step, header.author()) {
|
if self.is_step_proposer(header.parent_hash(), step, header.author()) {
|
||||||
if let Ok(signature) = self.signer.sign(header.bare_hash()) {
|
if let Ok(signature) = self.signer.sign(header.bare_hash()) {
|
||||||
trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step);
|
trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step);
|
||||||
@ -319,32 +368,19 @@ impl Engine for AuthorityRound {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do the validator and gas limit validation.
|
/// Do the step and gas limit validation.
|
||||||
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
let step = header_step(header)?;
|
let step = header_step(header)?;
|
||||||
// Give one step slack if step is lagging, double vote is still not possible.
|
|
||||||
if self.is_future_step(step) {
|
|
||||||
trace!(target: "engine", "verify_block_unordered: block from the future");
|
|
||||||
self.validators.report_benign(header.author());
|
|
||||||
Err(BlockError::InvalidSeal)?
|
|
||||||
} else {
|
|
||||||
let proposer_signature = header_signature(header)?;
|
|
||||||
let correct_proposer = self.step_proposer(header.parent_hash(), step);
|
|
||||||
if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
|
|
||||||
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", step);
|
|
||||||
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not calculate difficulty for genesis blocks.
|
// Do not calculate difficulty for genesis blocks.
|
||||||
if header.number() == 0 {
|
if header.number() == 0 {
|
||||||
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
|
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if parent is from a previous step.
|
// Ensure header is from the step after parent.
|
||||||
let parent_step = header_step(parent)?;
|
let parent_step = header_step(parent)?;
|
||||||
if step == parent_step {
|
if step <= parent_step {
|
||||||
trace!(target: "engine", "Multiple blocks proposed for step {}.", step);
|
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
|
||||||
self.validators.report_malicious(header.author());
|
self.validators.report_malicious(header.author());
|
||||||
Err(EngineError::DoubleVote(header.author().clone()))?;
|
Err(EngineError::DoubleVote(header.author().clone()))?;
|
||||||
}
|
}
|
||||||
@ -358,6 +394,34 @@ impl Engine for AuthorityRound {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check the validators.
|
||||||
|
fn verify_block_external(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
|
verify_external(header, &*self.validators, &*self.step)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the proofs we need just allow us to get the full validator set.
|
||||||
|
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Bytes, Error> {
|
||||||
|
self.validators.epoch_proof(header, caller)
|
||||||
|
.map_err(|e| EngineError::InsufficientProof(e).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
|
||||||
|
-> super::EpochChange
|
||||||
|
{
|
||||||
|
self.validators.is_epoch_end(header, block, receipts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn epoch_verifier(&self, header: &Header, proof: &[u8]) -> Result<Box<super::EpochVerifier>, Error> {
|
||||||
|
// extract a simple list from the proof.
|
||||||
|
let (num, simple_list) = self.validators.epoch_set(header, proof)?;
|
||||||
|
|
||||||
|
Ok(Box::new(EpochVerifier {
|
||||||
|
epoch_number: num,
|
||||||
|
step: self.step.clone(),
|
||||||
|
subchain_validators: simple_list,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> result::Result<(), Error> {
|
fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> result::Result<(), Error> {
|
||||||
t.check_low_s()?;
|
t.check_low_s()?;
|
||||||
|
|
||||||
@ -432,7 +496,7 @@ mod tests {
|
|||||||
let mut header: Header = Header::default();
|
let mut header: Header = Header::default();
|
||||||
header.set_seal(vec![encode(&H520::default()).to_vec()]);
|
header.set_seal(vec![encode(&H520::default()).to_vec()]);
|
||||||
|
|
||||||
let verify_result = engine.verify_block_family(&header, &Default::default(), None);
|
let verify_result = engine.verify_block_external(&header, None);
|
||||||
assert!(verify_result.is_err());
|
assert!(verify_result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,9 +550,11 @@ mod tests {
|
|||||||
// Two validators.
|
// Two validators.
|
||||||
// Spec starts with step 2.
|
// Spec starts with step 2.
|
||||||
header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
|
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
||||||
|
assert!(engine.verify_block_external(&header, None).is_err());
|
||||||
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
||||||
|
assert!(engine.verify_block_external(&header, None).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -511,7 +577,33 @@ mod tests {
|
|||||||
// Spec starts with step 2.
|
// Spec starts with step 2.
|
||||||
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
||||||
|
assert!(engine.verify_block_external(&header, None).is_ok());
|
||||||
header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||||
|
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
||||||
|
assert!(engine.verify_block_external(&header, None).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_step_backwards() {
|
||||||
|
let tap = AccountProvider::transient_provider();
|
||||||
|
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||||
|
|
||||||
|
let mut parent_header: Header = Header::default();
|
||||||
|
parent_header.set_seal(vec![encode(&4usize).to_vec()]);
|
||||||
|
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||||
|
let mut header: Header = Header::default();
|
||||||
|
header.set_number(1);
|
||||||
|
header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||||
|
header.set_author(addr);
|
||||||
|
|
||||||
|
let engine = Spec::new_test_round().engine;
|
||||||
|
|
||||||
|
let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap();
|
||||||
|
// Two validators.
|
||||||
|
// Spec starts with step 2.
|
||||||
|
header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||||
|
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
||||||
|
header.set_seal(vec![encode(&3usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
|
assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,14 @@ use account_provider::AccountProvider;
|
|||||||
use block::*;
|
use block::*;
|
||||||
use builtin::Builtin;
|
use builtin::Builtin;
|
||||||
use spec::CommonParams;
|
use spec::CommonParams;
|
||||||
use engines::{Engine, Seal};
|
use engines::{Engine, EngineError, Seal, Call, EpochChange};
|
||||||
use error::{BlockError, Error};
|
use error::{BlockError, Error};
|
||||||
use evm::Schedule;
|
use evm::Schedule;
|
||||||
use ethjson;
|
use ethjson;
|
||||||
use header::{Header, BlockNumber};
|
use header::{Header, BlockNumber};
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use super::signer::EngineSigner;
|
use super::signer::EngineSigner;
|
||||||
use super::validator_set::{ValidatorSet, new_validator_set};
|
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
||||||
|
|
||||||
/// `BasicAuthority` params.
|
/// `BasicAuthority` params.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -50,8 +50,32 @@ impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Engine using `BasicAuthority` proof-of-work consensus algorithm, suitable for Ethereum
|
struct EpochVerifier {
|
||||||
/// mainnet chains in the Olympic, Frontier and Homestead eras.
|
epoch_number: u64,
|
||||||
|
list: SimpleList,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::EpochVerifier for EpochVerifier {
|
||||||
|
fn epoch_number(&self) -> u64 { self.epoch_number.clone() }
|
||||||
|
fn verify_light(&self, header: &Header) -> Result<(), Error> {
|
||||||
|
verify_external(header, &self.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_external(header: &Header, validators: &ValidatorSet) -> Result<(), Error> {
|
||||||
|
use rlp::UntrustedRlp;
|
||||||
|
|
||||||
|
// Check if the signature belongs to a validator, can depend on parent state.
|
||||||
|
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
|
||||||
|
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
|
||||||
|
|
||||||
|
match validators.contains(header.parent_hash(), &signer) {
|
||||||
|
false => Err(BlockError::InvalidSeal.into()),
|
||||||
|
true => Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Engine using `BasicAuthority`, trivial proof-of-authority consensus.
|
||||||
pub struct BasicAuthority {
|
pub struct BasicAuthority {
|
||||||
params: CommonParams,
|
params: CommonParams,
|
||||||
gas_limit_bound_divisor: U256,
|
gas_limit_bound_divisor: U256,
|
||||||
@ -137,14 +161,6 @@ impl Engine for BasicAuthority {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
|
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
|
||||||
use rlp::UntrustedRlp;
|
|
||||||
// Check if the signature belongs to a validator, can depend on parent state.
|
|
||||||
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
|
|
||||||
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
|
|
||||||
if !self.validators.contains(header.parent_hash(), &signer) {
|
|
||||||
return Err(BlockError::InvalidSeal)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not calculate difficulty for genesis blocks.
|
// Do not calculate difficulty for genesis blocks.
|
||||||
if header.number() == 0 {
|
if header.number() == 0 {
|
||||||
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
|
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
|
||||||
@ -163,6 +179,32 @@ impl Engine for BasicAuthority {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_block_external(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
|
verify_external(header, &*self.validators)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the proofs we need just allow us to get the full validator set.
|
||||||
|
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Bytes, Error> {
|
||||||
|
self.validators.epoch_proof(header, caller)
|
||||||
|
.map_err(|e| EngineError::InsufficientProof(e).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
|
||||||
|
-> EpochChange
|
||||||
|
{
|
||||||
|
self.validators.is_epoch_end(header, block, receipts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn epoch_verifier(&self, header: &Header, proof: &[u8]) -> Result<Box<super::EpochVerifier>, Error> {
|
||||||
|
// extract a simple list from the proof.
|
||||||
|
let (num, simple_list) = self.validators.epoch_set(header, proof)?;
|
||||||
|
|
||||||
|
Ok(Box::new(EpochVerifier {
|
||||||
|
epoch_number: num,
|
||||||
|
list: simple_list,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn register_client(&self, client: Weak<Client>) {
|
fn register_client(&self, client: Weak<Client>) {
|
||||||
self.validators.register_contract(client);
|
self.validators.register_contract(client);
|
||||||
}
|
}
|
||||||
|
45
ethcore/src/engines/epoch_verifier.rs
Normal file
45
ethcore/src/engines/epoch_verifier.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Epoch verifiers.
|
||||||
|
|
||||||
|
use error::Error;
|
||||||
|
use header::Header;
|
||||||
|
|
||||||
|
/// Verifier for all blocks within an epoch with self-contained state.
|
||||||
|
///
|
||||||
|
/// See docs on `Engine` relating to proving functions for more details.
|
||||||
|
pub trait EpochVerifier: Sync {
|
||||||
|
/// Get the epoch number.
|
||||||
|
fn epoch_number(&self) -> u64;
|
||||||
|
|
||||||
|
/// Lightly verify the next block header.
|
||||||
|
/// This may not be a header belonging to a different epoch.
|
||||||
|
fn verify_light(&self, header: &Header) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Perform potentially heavier checks on the next block header.
|
||||||
|
fn verify_heavy(&self, header: &Header) -> Result<(), Error> {
|
||||||
|
self.verify_light(header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Special "no-op" verifier for stateless, epoch-less engines.
|
||||||
|
pub struct NoOp;
|
||||||
|
|
||||||
|
impl EpochVerifier for NoOp {
|
||||||
|
fn epoch_number(&self) -> u64 { 0 }
|
||||||
|
fn verify_light(&self, _header: &Header) -> Result<(), Error> { Ok(()) }
|
||||||
|
}
|
@ -16,35 +16,41 @@
|
|||||||
|
|
||||||
//! Consensus engine specification and basic implementations.
|
//! Consensus engine specification and basic implementations.
|
||||||
|
|
||||||
mod transition;
|
|
||||||
mod vote_collector;
|
|
||||||
mod null_engine;
|
|
||||||
mod instant_seal;
|
|
||||||
mod basic_authority;
|
|
||||||
mod authority_round;
|
mod authority_round;
|
||||||
mod tendermint;
|
mod basic_authority;
|
||||||
mod validator_set;
|
mod epoch_verifier;
|
||||||
|
mod instant_seal;
|
||||||
|
mod null_engine;
|
||||||
mod signer;
|
mod signer;
|
||||||
|
mod tendermint;
|
||||||
|
mod transition;
|
||||||
|
mod validator_set;
|
||||||
|
mod vote_collector;
|
||||||
|
|
||||||
pub use self::null_engine::NullEngine;
|
|
||||||
pub use self::instant_seal::InstantSeal;
|
|
||||||
pub use self::basic_authority::BasicAuthority;
|
|
||||||
pub use self::authority_round::AuthorityRound;
|
pub use self::authority_round::AuthorityRound;
|
||||||
|
pub use self::basic_authority::BasicAuthority;
|
||||||
|
pub use self::epoch_verifier::EpochVerifier;
|
||||||
|
pub use self::instant_seal::InstantSeal;
|
||||||
|
pub use self::null_engine::NullEngine;
|
||||||
pub use self::tendermint::Tendermint;
|
pub use self::tendermint::Tendermint;
|
||||||
|
|
||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
use util::*;
|
|
||||||
use ethkey::Signature;
|
|
||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use block::ExecutedBlock;
|
use block::ExecutedBlock;
|
||||||
use builtin::Builtin;
|
use builtin::Builtin;
|
||||||
|
use client::Client;
|
||||||
use env_info::EnvInfo;
|
use env_info::EnvInfo;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use spec::CommonParams;
|
|
||||||
use evm::Schedule;
|
use evm::Schedule;
|
||||||
use header::{Header, BlockNumber};
|
use header::{Header, BlockNumber};
|
||||||
|
use receipt::Receipt;
|
||||||
|
use snapshot::SnapshotComponents;
|
||||||
|
use spec::CommonParams;
|
||||||
use transaction::{UnverifiedTransaction, SignedTransaction};
|
use transaction::{UnverifiedTransaction, SignedTransaction};
|
||||||
use client::Client;
|
|
||||||
|
use ethkey::Signature;
|
||||||
|
use util::*;
|
||||||
|
|
||||||
/// Voting errors.
|
/// Voting errors.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -59,6 +65,8 @@ pub enum EngineError {
|
|||||||
UnexpectedMessage,
|
UnexpectedMessage,
|
||||||
/// Seal field has an unexpected size.
|
/// Seal field has an unexpected size.
|
||||||
BadSealFieldSize(OutOfBounds<usize>),
|
BadSealFieldSize(OutOfBounds<usize>),
|
||||||
|
/// Validation proof insufficient.
|
||||||
|
InsufficientProof(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for EngineError {
|
impl fmt::Display for EngineError {
|
||||||
@ -70,6 +78,7 @@ impl fmt::Display for EngineError {
|
|||||||
NotAuthorized(ref address) => format!("Signer {} is not authorized.", address),
|
NotAuthorized(ref address) => format!("Signer {} is not authorized.", address),
|
||||||
UnexpectedMessage => "This Engine should not be fed messages.".into(),
|
UnexpectedMessage => "This Engine should not be fed messages.".into(),
|
||||||
BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob),
|
BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob),
|
||||||
|
InsufficientProof(ref msg) => format!("Insufficient validation proof: {}", msg),
|
||||||
};
|
};
|
||||||
|
|
||||||
f.write_fmt(format_args!("Engine error ({})", msg))
|
f.write_fmt(format_args!("Engine error ({})", msg))
|
||||||
@ -87,6 +96,31 @@ pub enum Seal {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type alias for a function we can make calls through synchronously.
|
||||||
|
pub type Call<'a> = Fn(Address, Bytes) -> Result<Bytes, String> + 'a;
|
||||||
|
|
||||||
|
/// Results of a query of whether an epoch change occurred at the given block.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum EpochChange {
|
||||||
|
/// Cannot determine until more data is passed.
|
||||||
|
Unsure(Unsure),
|
||||||
|
/// No epoch change.
|
||||||
|
No,
|
||||||
|
/// Validation proof required, and the new epoch number and expected proof.
|
||||||
|
Yes(u64, Bytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// More data required to determine if an epoch change occurred at a given block.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum Unsure {
|
||||||
|
/// Needs the body.
|
||||||
|
NeedsBody,
|
||||||
|
/// Needs the receipts.
|
||||||
|
NeedsReceipts,
|
||||||
|
/// Needs both body and receipts.
|
||||||
|
NeedsBoth,
|
||||||
|
}
|
||||||
|
|
||||||
/// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based.
|
/// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based.
|
||||||
/// Provides hooks into each of the major parts of block import.
|
/// Provides hooks into each of the major parts of block import.
|
||||||
pub trait Engine : Sync + Send {
|
pub trait Engine : Sync + Send {
|
||||||
@ -152,6 +186,9 @@ pub trait Engine : Sync + Send {
|
|||||||
/// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import.
|
/// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import.
|
||||||
fn verify_block_family(&self, _header: &Header, _parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) }
|
fn verify_block_family(&self, _header: &Header, _parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) }
|
||||||
|
|
||||||
|
/// Phase 4 verification. Verify block header against potentially external data.
|
||||||
|
fn verify_block_external(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) }
|
||||||
|
|
||||||
/// Additional verification for transactions in blocks.
|
/// Additional verification for transactions in blocks.
|
||||||
// TODO: Add flags for which bits of the transaction to check.
|
// TODO: Add flags for which bits of the transaction to check.
|
||||||
// TODO: consider including State in the params.
|
// TODO: consider including State in the params.
|
||||||
@ -177,6 +214,40 @@ pub trait Engine : Sync + Send {
|
|||||||
self.verify_block_basic(header, None).and_then(|_| self.verify_block_unordered(header, None))
|
self.verify_block_basic(header, None).and_then(|_| self.verify_block_unordered(header, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate epoch change proof.
|
||||||
|
///
|
||||||
|
/// This will be used to generate proofs of epoch change as well as verify them.
|
||||||
|
/// Must be called on blocks that have already passed basic verification.
|
||||||
|
///
|
||||||
|
/// Return the "epoch proof" generated.
|
||||||
|
/// This must be usable to generate a `EpochVerifier` for verifying all blocks
|
||||||
|
/// from the supplied header up to the next one where proof is required.
|
||||||
|
///
|
||||||
|
/// For example, for PoA chains the proof will be a validator set,
|
||||||
|
/// and the corresponding `EpochVerifier` can be used to correctly validate
|
||||||
|
/// all blocks produced under that `ValidatorSet`
|
||||||
|
fn epoch_proof(&self, _header: &Header, _caller: &Call)
|
||||||
|
-> Result<Vec<u8>, Error>
|
||||||
|
{
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether an epoch change occurred at the given header.
|
||||||
|
/// Should not interact with state.
|
||||||
|
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[Receipt]>)
|
||||||
|
-> EpochChange
|
||||||
|
{
|
||||||
|
EpochChange::No
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an epoch verifier from validation proof.
|
||||||
|
///
|
||||||
|
/// The proof should be one generated by `epoch_proof`.
|
||||||
|
/// See docs of `epoch_proof` for description.
|
||||||
|
fn epoch_verifier(&self, _header: &Header, _proof: &[u8]) -> Result<Box<EpochVerifier>, Error> {
|
||||||
|
Ok(Box::new(self::epoch_verifier::NoOp))
|
||||||
|
}
|
||||||
|
|
||||||
/// Populate a header's fields based on its parent's header.
|
/// Populate a header's fields based on its parent's header.
|
||||||
/// Usually implements the chain scoring rule based on weight.
|
/// Usually implements the chain scoring rule based on weight.
|
||||||
/// The gas floor target must not be lower than the engine's minimum gas limit.
|
/// The gas floor target must not be lower than the engine's minimum gas limit.
|
||||||
@ -217,4 +288,10 @@ pub trait Engine : Sync + Send {
|
|||||||
|
|
||||||
/// Stops any services that the may hold the Engine and makes it safe to drop.
|
/// Stops any services that the may hold the Engine and makes it safe to drop.
|
||||||
fn stop(&self) {}
|
fn stop(&self) {}
|
||||||
|
|
||||||
|
/// Create a factory for building snapshot chunks and restoring from them.
|
||||||
|
/// Returning `None` indicates that this engine doesn't support snapshot creation.
|
||||||
|
fn snapshot_components(&self) -> Option<Box<SnapshotComponents>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,4 +60,8 @@ impl Engine for NullEngine {
|
|||||||
fn schedule(&self, _block_number: BlockNumber) -> Schedule {
|
fn schedule(&self, _block_number: BlockNumber) -> Schedule {
|
||||||
Schedule::new_homestead()
|
Schedule::new_homestead()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
|
||||||
|
Some(Box::new(::snapshot::PowSnapshot(10000)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,119 +19,96 @@
|
|||||||
|
|
||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
use util::*;
|
use util::*;
|
||||||
|
|
||||||
|
use futures::Future;
|
||||||
|
use native_contracts::ValidatorReport as Provider;
|
||||||
|
|
||||||
use client::{Client, BlockChainClient};
|
use client::{Client, BlockChainClient};
|
||||||
|
use engines::Call;
|
||||||
|
use header::Header;
|
||||||
|
|
||||||
use super::ValidatorSet;
|
use super::ValidatorSet;
|
||||||
use super::safe_contract::ValidatorSafeContract;
|
use super::safe_contract::ValidatorSafeContract;
|
||||||
|
|
||||||
/// The validator contract should have the following interface:
|
/// A validator contract with reporting.
|
||||||
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
|
|
||||||
pub struct ValidatorContract {
|
pub struct ValidatorContract {
|
||||||
validators: ValidatorSafeContract,
|
validators: ValidatorSafeContract,
|
||||||
provider: RwLock<Option<provider::Contract>>,
|
provider: Provider,
|
||||||
|
client: RwLock<Option<Weak<Client>>>, // TODO [keorn]: remove
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorContract {
|
impl ValidatorContract {
|
||||||
pub fn new(contract_address: Address) -> Self {
|
pub fn new(contract_address: Address) -> Self {
|
||||||
ValidatorContract {
|
ValidatorContract {
|
||||||
validators: ValidatorSafeContract::new(contract_address),
|
validators: ValidatorSafeContract::new(contract_address),
|
||||||
provider: RwLock::new(None),
|
provider: Provider::new(contract_address),
|
||||||
|
client: RwLock::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ValidatorContract {
|
||||||
|
// could be `impl Trait`.
|
||||||
|
// note: dispatches transactions to network as well as execute.
|
||||||
|
// TODO [keorn]: Make more general.
|
||||||
|
fn transact(&self) -> Box<Call> {
|
||||||
|
let client = self.client.read().clone();
|
||||||
|
Box::new(move |a, d| client.as_ref()
|
||||||
|
.and_then(Weak::upgrade)
|
||||||
|
.ok_or("No client!".into())
|
||||||
|
.and_then(|c| c.transact_contract(a, d).map_err(|e| format!("Transaction import error: {}", e)))
|
||||||
|
.map(|_| Default::default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ValidatorSet for ValidatorContract {
|
impl ValidatorSet for ValidatorContract {
|
||||||
fn contains(&self, bh: &H256, address: &Address) -> bool {
|
fn default_caller(&self, id: ::ids::BlockId) -> Box<Call> {
|
||||||
self.validators.contains(bh, address)
|
self.validators.default_caller(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, bh: &H256, nonce: usize) -> Address {
|
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
|
||||||
self.validators.get(bh, nonce)
|
-> ::engines::EpochChange
|
||||||
|
{
|
||||||
|
self.validators.is_epoch_end(header, block, receipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count(&self, bh: &H256) -> usize {
|
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
|
||||||
self.validators.count(bh)
|
self.validators.epoch_proof(header, caller)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, super::SimpleList), ::error::Error> {
|
||||||
|
self.validators.epoch_set(header, proof)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_with_caller(&self, bh: &H256, address: &Address, caller: &Call) -> bool {
|
||||||
|
self.validators.contains_with_caller(bh, address, caller)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_with_caller(&self, bh: &H256, nonce: usize, caller: &Call) -> Address {
|
||||||
|
self.validators.get_with_caller(bh, nonce, caller)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_with_caller(&self, bh: &H256, caller: &Call) -> usize {
|
||||||
|
self.validators.count_with_caller(bh, caller)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_malicious(&self, address: &Address) {
|
fn report_malicious(&self, address: &Address) {
|
||||||
if let Some(ref provider) = *self.provider.read() {
|
match self.provider.report_malicious(&*self.transact(), *address).wait() {
|
||||||
match provider.report_malicious(address) {
|
|
||||||
Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address),
|
Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address),
|
||||||
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),
|
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
warn!(target: "engine", "Malicious behaviour could not be reported: no provider contract.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_benign(&self, address: &Address) {
|
fn report_benign(&self, address: &Address) {
|
||||||
if let Some(ref provider) = *self.provider.read() {
|
match self.provider.report_benign(&*self.transact(), *address).wait() {
|
||||||
match provider.report_benign(address) {
|
|
||||||
Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address),
|
Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address),
|
||||||
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),
|
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
warn!(target: "engine", "Benign misbehaviour could not be reported: no provider contract.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_contract(&self, client: Weak<Client>) {
|
fn register_contract(&self, client: Weak<Client>) {
|
||||||
self.validators.register_contract(client.clone());
|
self.validators.register_contract(client.clone());
|
||||||
let transact = move |a, d| client
|
*self.client.write() = Some(client);
|
||||||
.upgrade()
|
|
||||||
.ok_or("No client!".into())
|
|
||||||
.and_then(|c| c.transact_contract(a, d).map_err(|e| format!("Transaction import error: {}", e)))
|
|
||||||
.map(|_| Default::default());
|
|
||||||
*self.provider.write() = Some(provider::Contract::new(self.validators.address, transact));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod provider {
|
|
||||||
// Autogenerated from JSON contract definition using Rust contract convertor.
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
use std::string::String;
|
|
||||||
use std::result::Result;
|
|
||||||
use std::fmt;
|
|
||||||
use {util, ethabi};
|
|
||||||
use util::{Uint};
|
|
||||||
|
|
||||||
pub struct Contract {
|
|
||||||
contract: ethabi::Contract,
|
|
||||||
address: util::Address,
|
|
||||||
do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static>,
|
|
||||||
}
|
|
||||||
impl Contract {
|
|
||||||
pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static {
|
|
||||||
Contract {
|
|
||||||
contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":false,\"inputs\":[{\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"reportMalicious\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"reportBenign\",\"outputs\":[],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")),
|
|
||||||
address: address,
|
|
||||||
do_call: Box::new(do_call),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
|
|
||||||
|
|
||||||
/// Auto-generated from: `{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"}`
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn report_malicious(&self, validator: &util::Address) -> Result<(), String> {
|
|
||||||
let call = self.contract.function("reportMalicious".into()).map_err(Self::as_string)?;
|
|
||||||
let data = call.encode_call(
|
|
||||||
vec![ethabi::Token::Address(validator.clone().0)]
|
|
||||||
).map_err(Self::as_string)?;
|
|
||||||
call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Auto-generated from: `{"constant":false,"inputs":[{"name":"validator","type":"address"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}`
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn report_benign(&self, validator: &util::Address) -> Result<(), String> {
|
|
||||||
let call = self.contract.function("reportBenign".into()).map_err(Self::as_string)?;
|
|
||||||
let data = call.encode_call(
|
|
||||||
vec![ethabi::Token::Address(validator.clone().0)]
|
|
||||||
).map_err(Self::as_string)?;
|
|
||||||
call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +157,7 @@ mod tests {
|
|||||||
header.set_parent_hash(client.chain_info().best_block_hash);
|
header.set_parent_hash(client.chain_info().best_block_hash);
|
||||||
|
|
||||||
// `reportBenign` when the designated proposer releases block from the future (bad clock).
|
// `reportBenign` when the designated proposer releases block from the future (bad clock).
|
||||||
assert!(client.engine().verify_block_family(&header, &header, None).is_err());
|
assert!(client.engine().verify_block_external(&header, None).is_err());
|
||||||
// Seal a block.
|
// Seal a block.
|
||||||
client.engine().step();
|
client.engine().step();
|
||||||
assert_eq!(client.chain_info().best_block_number, 1);
|
assert_eq!(client.chain_info().best_block_number, 1);
|
||||||
|
@ -22,14 +22,19 @@ mod contract;
|
|||||||
mod multi;
|
mod multi;
|
||||||
|
|
||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
|
use ids::BlockId;
|
||||||
use util::{Address, H256};
|
use util::{Address, H256};
|
||||||
use ethjson::spec::ValidatorSet as ValidatorSpec;
|
use ethjson::spec::ValidatorSet as ValidatorSpec;
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use self::simple_list::SimpleList;
|
use header::Header;
|
||||||
|
|
||||||
|
pub use self::simple_list::SimpleList;
|
||||||
use self::contract::ValidatorContract;
|
use self::contract::ValidatorContract;
|
||||||
use self::safe_contract::ValidatorSafeContract;
|
use self::safe_contract::ValidatorSafeContract;
|
||||||
use self::multi::Multi;
|
use self::multi::Multi;
|
||||||
|
|
||||||
|
use super::Call;
|
||||||
|
|
||||||
/// Creates a validator set from spec.
|
/// Creates a validator set from spec.
|
||||||
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
|
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
|
||||||
match spec {
|
match spec {
|
||||||
@ -42,13 +47,69 @@ pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A validator set.
|
||||||
pub trait ValidatorSet: Send + Sync {
|
pub trait ValidatorSet: Send + Sync {
|
||||||
/// Checks if a given address is a validator.
|
/// Get the default "Call" helper, for use in general operation.
|
||||||
fn contains(&self, parent_block_hash: &H256, address: &Address) -> bool;
|
// TODO [keorn]: this is a hack intended to migrate off of
|
||||||
|
// a strict dependency on state always being available.
|
||||||
|
fn default_caller(&self, block_id: BlockId) -> Box<Call>;
|
||||||
|
|
||||||
|
/// Checks if a given address is a validator,
|
||||||
|
/// using underlying, default call mechanism.
|
||||||
|
fn contains(&self, parent: &H256, address: &Address) -> bool {
|
||||||
|
let default = self.default_caller(BlockId::Hash(*parent));
|
||||||
|
self.contains_with_caller(parent, address, &*default)
|
||||||
|
}
|
||||||
/// Draws an validator nonce modulo number of validators.
|
/// Draws an validator nonce modulo number of validators.
|
||||||
fn get(&self, parent_block_hash: &H256, nonce: usize) -> Address;
|
fn get(&self, parent: &H256, nonce: usize) -> Address {
|
||||||
|
let default = self.default_caller(BlockId::Hash(*parent));
|
||||||
|
self.get_with_caller(parent, nonce, &*default)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the current number of validators.
|
/// Returns the current number of validators.
|
||||||
fn count(&self, parent_block_hash: &H256) -> usize;
|
fn count(&self, parent: &H256) -> usize {
|
||||||
|
let default = self.default_caller(BlockId::Hash(*parent));
|
||||||
|
self.count_with_caller(parent, &*default)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this block is the last one in its epoch.
|
||||||
|
/// Usually indicates that the validator set changed at the given block.
|
||||||
|
///
|
||||||
|
/// Should not inspect state! This is used in situations where
|
||||||
|
/// state is not generally available.
|
||||||
|
///
|
||||||
|
/// Return `Yes` or `No` indicating whether it changed at the given header,
|
||||||
|
/// or `Unsure` indicating a need for more information.
|
||||||
|
///
|
||||||
|
/// If block or receipts are provided, do not return `Unsure` indicating
|
||||||
|
/// need for them.
|
||||||
|
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
|
||||||
|
-> super::EpochChange;
|
||||||
|
|
||||||
|
/// Generate epoch proof.
|
||||||
|
/// Must interact with state only through the given caller!
|
||||||
|
/// Otherwise, generated proofs may be wrong.
|
||||||
|
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String>;
|
||||||
|
|
||||||
|
/// Recover the validator set for all
|
||||||
|
///
|
||||||
|
/// May fail if the given header doesn't kick off an epoch or
|
||||||
|
/// the proof is invalid.
|
||||||
|
///
|
||||||
|
/// Returns the epoch number and proof.
|
||||||
|
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, SimpleList), ::error::Error>;
|
||||||
|
|
||||||
|
/// Checks if a given address is a validator, with the given function
|
||||||
|
/// for executing synchronous calls to contracts.
|
||||||
|
fn contains_with_caller(&self, parent_block_hash: &H256, address: &Address, caller: &Call) -> bool;
|
||||||
|
|
||||||
|
/// Draws an validator nonce modulo number of validators.
|
||||||
|
///
|
||||||
|
fn get_with_caller(&self, parent_block_hash: &H256, nonce: usize, caller: &Call) -> Address;
|
||||||
|
|
||||||
|
/// Returns the current number of validators.
|
||||||
|
fn count_with_caller(&self, parent_block_hash: &H256, caller: &Call) -> usize;
|
||||||
|
|
||||||
/// Notifies about malicious behaviour.
|
/// Notifies about malicious behaviour.
|
||||||
fn report_malicious(&self, _validator: &Address) {}
|
fn report_malicious(&self, _validator: &Address) {}
|
||||||
/// Notifies about benign misbehaviour.
|
/// Notifies about benign misbehaviour.
|
||||||
|
@ -18,13 +18,14 @@
|
|||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
|
use engines::{Call, EpochChange};
|
||||||
use util::{H256, Address, RwLock};
|
use util::{H256, Address, RwLock};
|
||||||
use ids::BlockId;
|
use ids::BlockId;
|
||||||
use header::BlockNumber;
|
use header::{BlockNumber, Header};
|
||||||
use client::{Client, BlockChainClient};
|
use client::{Client, BlockChainClient};
|
||||||
use super::ValidatorSet;
|
use super::ValidatorSet;
|
||||||
|
|
||||||
type BlockNumberLookup = Box<Fn(&H256) -> Result<BlockNumber, String> + Send + Sync + 'static>;
|
type BlockNumberLookup = Box<Fn(BlockId) -> Result<BlockNumber, String> + Send + Sync + 'static>;
|
||||||
|
|
||||||
pub struct Multi {
|
pub struct Multi {
|
||||||
sets: BTreeMap<BlockNumber, Box<ValidatorSet>>,
|
sets: BTreeMap<BlockNumber, Box<ValidatorSet>>,
|
||||||
@ -40,42 +41,73 @@ impl Multi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn correct_set(&self, bh: &H256) -> Option<&Box<ValidatorSet>> {
|
fn correct_set(&self, id: BlockId) -> Option<&ValidatorSet> {
|
||||||
match self
|
match self.block_number.read()(id).map(|parent_block| self.correct_set_by_number(parent_block)) {
|
||||||
.block_number
|
Ok((_, set)) => Some(set),
|
||||||
.read()(bh)
|
|
||||||
.map(|parent_block| self
|
|
||||||
.sets
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.find(|&(block, _)| *block <= parent_block + 1)
|
|
||||||
.expect("constructor validation ensures that there is at least one validator set for block 0;
|
|
||||||
block 0 is less than any uint;
|
|
||||||
qed")
|
|
||||||
) {
|
|
||||||
Ok((block, set)) => {
|
|
||||||
trace!(target: "engine", "Multi ValidatorSet retrieved for block {}.", block);
|
|
||||||
Some(set)
|
|
||||||
},
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!(target: "engine", "ValidatorSet could not be recovered: {}", e);
|
debug!(target: "engine", "ValidatorSet could not be recovered: {}", e);
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get correct set by block number, along with block number at which
|
||||||
|
// this set was activated.
|
||||||
|
fn correct_set_by_number(&self, parent_block: BlockNumber) -> (BlockNumber, &ValidatorSet) {
|
||||||
|
let (block, set) = self.sets.iter()
|
||||||
|
.rev()
|
||||||
|
.find(|&(block, _)| *block <= parent_block + 1)
|
||||||
|
.expect("constructor validation ensures that there is at least one validator set for block 0;
|
||||||
|
block 0 is less than any uint;
|
||||||
|
qed");
|
||||||
|
|
||||||
|
trace!(target: "engine", "Multi ValidatorSet retrieved for block {}.", block);
|
||||||
|
(*block, &**set)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorSet for Multi {
|
impl ValidatorSet for Multi {
|
||||||
fn contains(&self, bh: &H256, address: &Address) -> bool {
|
fn default_caller(&self, block_id: BlockId) -> Box<Call> {
|
||||||
self.correct_set(bh).map_or(false, |set| set.contains(bh, address))
|
self.correct_set(block_id).map(|set| set.default_caller(block_id))
|
||||||
|
.unwrap_or(Box::new(|_, _| Err("No validator set for given ID.".into())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, bh: &H256, nonce: usize) -> Address {
|
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
|
||||||
self.correct_set(bh).map_or_else(Default::default, |set| set.get(bh, nonce))
|
-> EpochChange
|
||||||
|
{
|
||||||
|
let (set_block, set) = self.correct_set_by_number(header.number());
|
||||||
|
|
||||||
|
match set.is_epoch_end(header, block, receipts) {
|
||||||
|
EpochChange::Yes(num, proof) => EpochChange::Yes(set_block + num, proof),
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count(&self, bh: &H256) -> usize {
|
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
|
||||||
self.correct_set(bh).map_or_else(usize::max_value, |set| set.count(bh))
|
self.correct_set_by_number(header.number()).1.epoch_proof(header, caller)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, super::SimpleList), ::error::Error> {
|
||||||
|
// "multi" epoch is the inner set's epoch plus the transition block to that set.
|
||||||
|
// ensures epoch increases monotonically.
|
||||||
|
let (set_block, set) = self.correct_set_by_number(header.number());
|
||||||
|
let (inner_epoch, list) = set.epoch_set(header, proof)?;
|
||||||
|
Ok((set_block + inner_epoch, list))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_with_caller(&self, bh: &H256, address: &Address, caller: &Call) -> bool {
|
||||||
|
self.correct_set(BlockId::Hash(*bh))
|
||||||
|
.map_or(false, |set| set.contains_with_caller(bh, address, caller))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_with_caller(&self, bh: &H256, nonce: usize, caller: &Call) -> Address {
|
||||||
|
self.correct_set(BlockId::Hash(*bh))
|
||||||
|
.map_or_else(Default::default, |set| set.get_with_caller(bh, nonce, caller))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_with_caller(&self, bh: &H256, caller: &Call) -> usize {
|
||||||
|
self.correct_set(BlockId::Hash(*bh))
|
||||||
|
.map_or_else(usize::max_value, |set| set.count_with_caller(bh, caller))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_malicious(&self, validator: &Address) {
|
fn report_malicious(&self, validator: &Address) {
|
||||||
@ -94,10 +126,10 @@ impl ValidatorSet for Multi {
|
|||||||
for set in self.sets.values() {
|
for set in self.sets.values() {
|
||||||
set.register_contract(client.clone());
|
set.register_contract(client.clone());
|
||||||
}
|
}
|
||||||
*self.block_number.write() = Box::new(move |hash| client
|
*self.block_number.write() = Box::new(move |id| client
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or("No client!".into())
|
.ok_or("No client!".into())
|
||||||
.and_then(|c| c.block_number(BlockId::Hash(*hash)).ok_or("Unknown block".into())));
|
.and_then(|c| c.block_number(id).ok_or("Unknown block".into())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,24 +17,45 @@
|
|||||||
/// Validator set maintained in a contract, updated using `getValidators` method.
|
/// Validator set maintained in a contract, updated using `getValidators` method.
|
||||||
|
|
||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
use ethabi;
|
use futures::Future;
|
||||||
|
use native_contracts::ValidatorSet as Provider;
|
||||||
|
|
||||||
use util::*;
|
use util::*;
|
||||||
use util::cache::MemoryLruCache;
|
use util::cache::MemoryLruCache;
|
||||||
use types::ids::BlockId;
|
|
||||||
|
use basic_types::LogBloom;
|
||||||
use client::{Client, BlockChainClient};
|
use client::{Client, BlockChainClient};
|
||||||
|
use engines::Call;
|
||||||
|
use header::Header;
|
||||||
|
use ids::BlockId;
|
||||||
|
use log_entry::LogEntry;
|
||||||
|
|
||||||
use super::ValidatorSet;
|
use super::ValidatorSet;
|
||||||
use super::simple_list::SimpleList;
|
use super::simple_list::SimpleList;
|
||||||
|
|
||||||
const MEMOIZE_CAPACITY: usize = 500;
|
const MEMOIZE_CAPACITY: usize = 500;
|
||||||
const CONTRACT_INTERFACE: &'static [u8] = b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]";
|
|
||||||
const GET_VALIDATORS: &'static str = "getValidators";
|
// TODO: ethabi should be able to generate this.
|
||||||
|
const EVENT_NAME: &'static [u8] = &*b"ValidatorsChanged(bytes32,uint256,address[])";
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref EVENT_NAME_HASH: H256 = EVENT_NAME.sha3();
|
||||||
|
}
|
||||||
|
|
||||||
/// The validator contract should have the following interface:
|
/// The validator contract should have the following interface:
|
||||||
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
|
|
||||||
pub struct ValidatorSafeContract {
|
pub struct ValidatorSafeContract {
|
||||||
pub address: Address,
|
pub address: Address,
|
||||||
validators: RwLock<MemoryLruCache<H256, SimpleList>>,
|
validators: RwLock<MemoryLruCache<H256, SimpleList>>,
|
||||||
provider: RwLock<Option<provider::Contract>>,
|
provider: Provider,
|
||||||
|
client: RwLock<Option<Weak<Client>>>, // TODO [keorn]: remove
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_proof(nonce: U256, validators: &[Address]) -> Bytes {
|
||||||
|
use rlp::RlpStream;
|
||||||
|
|
||||||
|
let mut stream = RlpStream::new_list(2);
|
||||||
|
stream.append(&nonce).append_list(validators);
|
||||||
|
stream.drain().to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorSafeContract {
|
impl ValidatorSafeContract {
|
||||||
@ -42,14 +63,14 @@ impl ValidatorSafeContract {
|
|||||||
ValidatorSafeContract {
|
ValidatorSafeContract {
|
||||||
address: contract_address,
|
address: contract_address,
|
||||||
validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)),
|
validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)),
|
||||||
provider: RwLock::new(None),
|
provider: Provider::new(contract_address),
|
||||||
|
client: RwLock::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries the state and gets the set of validators.
|
/// Queries the state and gets the set of validators.
|
||||||
fn get_list(&self, block_hash: H256) -> Option<SimpleList> {
|
fn get_list(&self, caller: &Call) -> Option<SimpleList> {
|
||||||
if let Some(ref provider) = *self.provider.read() {
|
match self.provider.get_validators(caller).wait() {
|
||||||
match provider.get_validators(BlockId::Hash(block_hash)) {
|
|
||||||
Ok(new) => {
|
Ok(new) => {
|
||||||
debug!(target: "engine", "Set of validators obtained: {:?}", new);
|
debug!(target: "engine", "Set of validators obtained: {:?}", new);
|
||||||
Some(SimpleList::new(new))
|
Some(SimpleList::new(new))
|
||||||
@ -59,22 +80,151 @@ impl ValidatorSafeContract {
|
|||||||
None
|
None
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
warn!(target: "engine", "Set of validators could not be updated: no provider contract.");
|
|
||||||
|
/// Queries for the current validator set transition nonce.
|
||||||
|
fn get_nonce(&self, caller: &Call) -> Option<::util::U256> {
|
||||||
|
match self.provider.transition_nonce(caller).wait() {
|
||||||
|
Ok(nonce) => Some(nonce),
|
||||||
|
Err(s) => {
|
||||||
|
debug!(target: "engine", "Unable to fetch transition nonce: {}", s);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether the header matches the expected bloom.
|
||||||
|
//
|
||||||
|
// The expected log should have 3 topics:
|
||||||
|
// 1. ETHABI-encoded log name.
|
||||||
|
// 2. the block's parent hash.
|
||||||
|
// 3. the "nonce": n for the nth transition in history.
|
||||||
|
//
|
||||||
|
// We can only search for the first 2, since we don't have the third
|
||||||
|
// just yet.
|
||||||
|
//
|
||||||
|
// The parent hash is included to prevent
|
||||||
|
// malicious actors from brute forcing other logs that would
|
||||||
|
// produce the same bloom.
|
||||||
|
//
|
||||||
|
// The log data is an array of all new validator addresses.
|
||||||
|
fn expected_bloom(&self, header: &Header) -> LogBloom {
|
||||||
|
LogEntry {
|
||||||
|
address: self.address,
|
||||||
|
topics: vec![*EVENT_NAME_HASH, *header.parent_hash()],
|
||||||
|
data: Vec::new(), // irrelevant for bloom.
|
||||||
|
}.bloom()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorSet for ValidatorSafeContract {
|
impl ValidatorSet for ValidatorSafeContract {
|
||||||
fn contains(&self, block_hash: &H256, address: &Address) -> bool {
|
fn default_caller(&self, id: BlockId) -> Box<Call> {
|
||||||
|
let client = self.client.read().clone();
|
||||||
|
Box::new(move |addr, data| client.as_ref()
|
||||||
|
.and_then(Weak::upgrade)
|
||||||
|
.ok_or("No client!".into())
|
||||||
|
.and_then(|c| c.call_contract(id, addr, data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_epoch_end(&self, header: &Header, _block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
|
||||||
|
-> ::engines::EpochChange
|
||||||
|
{
|
||||||
|
let bloom = self.expected_bloom(header);
|
||||||
|
let header_bloom = header.log_bloom();
|
||||||
|
|
||||||
|
if &bloom & header_bloom != bloom { return ::engines::EpochChange::No }
|
||||||
|
|
||||||
|
match receipts {
|
||||||
|
None => ::engines::EpochChange::Unsure(::engines::Unsure::NeedsReceipts),
|
||||||
|
Some(receipts) => {
|
||||||
|
let check_log = |log: &LogEntry| {
|
||||||
|
log.address == self.address &&
|
||||||
|
log.topics.len() == 3 &&
|
||||||
|
log.topics[0] == *EVENT_NAME_HASH &&
|
||||||
|
log.topics[1] == *header.parent_hash()
|
||||||
|
// don't have anything to compare nonce to yet.
|
||||||
|
};
|
||||||
|
|
||||||
|
let event = Provider::contract(&self.provider)
|
||||||
|
.event("ValidatorsChanged".into())
|
||||||
|
.expect("Contract known ahead of time to have `ValidatorsChanged` event; qed");
|
||||||
|
|
||||||
|
// iterate in reverse because only the _last_ change in a given
|
||||||
|
// block actually has any effect.
|
||||||
|
// the contract should only increment the nonce once.
|
||||||
|
let mut decoded_events = receipts.iter()
|
||||||
|
.rev()
|
||||||
|
.filter(|r| &bloom & &r.log_bloom == bloom)
|
||||||
|
.flat_map(|r| r.logs.iter())
|
||||||
|
.filter(move |l| check_log(l))
|
||||||
|
.filter_map(|log| {
|
||||||
|
let topics = log.topics.iter().map(|x| x.0.clone()).collect();
|
||||||
|
match event.decode_log(topics, log.data.clone()) {
|
||||||
|
Ok(decoded) => Some(decoded),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match decoded_events.next() {
|
||||||
|
None => ::engines::EpochChange::No,
|
||||||
|
Some(matched_event) => {
|
||||||
|
// decode log manually until the native contract generator is
|
||||||
|
// good enough to do it for us.
|
||||||
|
let &(_, _, ref nonce_token) = &matched_event.params[1];
|
||||||
|
let &(_, _, ref validators_token) = &matched_event.params[2];
|
||||||
|
|
||||||
|
let nonce: Option<U256> = nonce_token.clone().to_uint()
|
||||||
|
.map(H256).map(Into::into);
|
||||||
|
let validators = validators_token.clone().to_array()
|
||||||
|
.and_then(|a| a.into_iter()
|
||||||
|
.map(|x| x.to_address().map(H160))
|
||||||
|
.collect::<Option<Vec<_>>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
match (nonce, validators) {
|
||||||
|
(Some(nonce), Some(validators)) => {
|
||||||
|
let proof = encode_proof(nonce, &validators);
|
||||||
|
let new_epoch = nonce.low_u64();
|
||||||
|
::engines::EpochChange::Yes(new_epoch, proof)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!(target: "engine", "Successfully decoded log turned out to be bad.");
|
||||||
|
::engines::EpochChange::No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the proof we generate is an RLP list containing two parts.
|
||||||
|
// (nonce, validators)
|
||||||
|
fn epoch_proof(&self, _header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
|
||||||
|
match (self.get_nonce(caller), self.get_list(caller)) {
|
||||||
|
(Some(nonce), Some(list)) => Ok(encode_proof(nonce, &list.into_inner())),
|
||||||
|
_ => Err("Caller insufficient to generate validator proof.".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn epoch_set(&self, _header: &Header, proof: &[u8]) -> Result<(u64, SimpleList), ::error::Error> {
|
||||||
|
use rlp::UntrustedRlp;
|
||||||
|
|
||||||
|
let rlp = UntrustedRlp::new(proof);
|
||||||
|
let nonce: u64 = rlp.val_at(0)?;
|
||||||
|
let validators: Vec<Address> = rlp.list_at(1)?;
|
||||||
|
|
||||||
|
Ok((nonce, SimpleList::new(validators)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_with_caller(&self, block_hash: &H256, address: &Address, caller: &Call) -> bool {
|
||||||
let mut guard = self.validators.write();
|
let mut guard = self.validators.write();
|
||||||
let maybe_existing = guard
|
let maybe_existing = guard
|
||||||
.get_mut(block_hash)
|
.get_mut(block_hash)
|
||||||
.map(|list| list.contains(block_hash, address));
|
.map(|list| list.contains(block_hash, address));
|
||||||
maybe_existing
|
maybe_existing
|
||||||
.unwrap_or_else(|| self
|
.unwrap_or_else(|| self
|
||||||
.get_list(block_hash.clone())
|
.get_list(caller)
|
||||||
.map_or(false, |list| {
|
.map_or(false, |list| {
|
||||||
let contains = list.contains(block_hash, address);
|
let contains = list.contains(block_hash, address);
|
||||||
guard.insert(block_hash.clone(), list);
|
guard.insert(block_hash.clone(), list);
|
||||||
@ -82,14 +232,14 @@ impl ValidatorSet for ValidatorSafeContract {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, block_hash: &H256, nonce: usize) -> Address {
|
fn get_with_caller(&self, block_hash: &H256, nonce: usize, caller: &Call) -> Address {
|
||||||
let mut guard = self.validators.write();
|
let mut guard = self.validators.write();
|
||||||
let maybe_existing = guard
|
let maybe_existing = guard
|
||||||
.get_mut(block_hash)
|
.get_mut(block_hash)
|
||||||
.map(|list| list.get(block_hash, nonce));
|
.map(|list| list.get(block_hash, nonce));
|
||||||
maybe_existing
|
maybe_existing
|
||||||
.unwrap_or_else(|| self
|
.unwrap_or_else(|| self
|
||||||
.get_list(block_hash.clone())
|
.get_list(caller)
|
||||||
.map_or_else(Default::default, |list| {
|
.map_or_else(Default::default, |list| {
|
||||||
let address = list.get(block_hash, nonce);
|
let address = list.get(block_hash, nonce);
|
||||||
guard.insert(block_hash.clone(), list);
|
guard.insert(block_hash.clone(), list);
|
||||||
@ -97,14 +247,14 @@ impl ValidatorSet for ValidatorSafeContract {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count(&self, block_hash: &H256) -> usize {
|
fn count_with_caller(&self, block_hash: &H256, caller: &Call) -> usize {
|
||||||
let mut guard = self.validators.write();
|
let mut guard = self.validators.write();
|
||||||
let maybe_existing = guard
|
let maybe_existing = guard
|
||||||
.get_mut(block_hash)
|
.get_mut(block_hash)
|
||||||
.map(|list| list.count(block_hash));
|
.map(|list| list.count(block_hash));
|
||||||
maybe_existing
|
maybe_existing
|
||||||
.unwrap_or_else(|| self
|
.unwrap_or_else(|| self
|
||||||
.get_list(block_hash.clone())
|
.get_list(caller)
|
||||||
.map_or_else(usize::max_value, |list| {
|
.map_or_else(usize::max_value, |list| {
|
||||||
let address = list.count(block_hash);
|
let address = list.count(block_hash);
|
||||||
guard.insert(block_hash.clone(), list);
|
guard.insert(block_hash.clone(), list);
|
||||||
@ -114,55 +264,7 @@ impl ValidatorSet for ValidatorSafeContract {
|
|||||||
|
|
||||||
fn register_contract(&self, client: Weak<Client>) {
|
fn register_contract(&self, client: Weak<Client>) {
|
||||||
trace!(target: "engine", "Setting up contract caller.");
|
trace!(target: "engine", "Setting up contract caller.");
|
||||||
let contract = ethabi::Contract::new(ethabi::Interface::load(CONTRACT_INTERFACE).expect("JSON interface is valid; qed"));
|
*self.client.write() = Some(client);
|
||||||
let call = contract.function(GET_VALIDATORS.into()).expect("Method name is valid; qed");
|
|
||||||
let data = call.encode_call(vec![]).expect("get_validators does not take any arguments; qed");
|
|
||||||
let contract_address = self.address.clone();
|
|
||||||
let do_call = move |id| client
|
|
||||||
.upgrade()
|
|
||||||
.ok_or("No client!".into())
|
|
||||||
.and_then(|c| c.call_contract(id, contract_address.clone(), data.clone()))
|
|
||||||
.map(|raw_output| call.decode_output(raw_output).expect("ethabi is correct; qed"));
|
|
||||||
*self.provider.write() = Some(provider::Contract::new(do_call));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod provider {
|
|
||||||
use std::string::String;
|
|
||||||
use std::result::Result;
|
|
||||||
use {util, ethabi};
|
|
||||||
use types::ids::BlockId;
|
|
||||||
|
|
||||||
pub struct Contract {
|
|
||||||
do_call: Box<Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Contract {
|
|
||||||
pub fn new<F>(do_call: F) -> Self where F: Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static {
|
|
||||||
Contract {
|
|
||||||
do_call: Box::new(do_call),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets validators from contract with interface: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
|
|
||||||
pub fn get_validators(&self, id: BlockId) -> Result<Vec<util::Address>, String> {
|
|
||||||
Ok((self.do_call)(id)?
|
|
||||||
.into_iter()
|
|
||||||
.rev()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.pop()
|
|
||||||
.expect("get_validators returns one argument; qed")
|
|
||||||
.to_array()
|
|
||||||
.and_then(|v| v
|
|
||||||
.into_iter()
|
|
||||||
.map(|a| a.to_address())
|
|
||||||
.collect::<Option<Vec<[u8; 20]>>>())
|
|
||||||
.expect("get_validators returns a list of addresses; qed")
|
|
||||||
.into_iter()
|
|
||||||
.map(util::Address::from)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +280,7 @@ mod tests {
|
|||||||
use miner::MinerService;
|
use miner::MinerService;
|
||||||
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
|
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
|
||||||
use super::super::ValidatorSet;
|
use super::super::ValidatorSet;
|
||||||
use super::ValidatorSafeContract;
|
use super::{ValidatorSafeContract, EVENT_NAME_HASH};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fetches_validators() {
|
fn fetches_validators() {
|
||||||
@ -256,4 +358,35 @@ mod tests {
|
|||||||
sync_client.flush_queue();
|
sync_client.flush_queue();
|
||||||
assert_eq!(sync_client.chain_info().best_block_number, 3);
|
assert_eq!(sync_client.chain_info().best_block_number, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn detects_bloom() {
|
||||||
|
use header::Header;
|
||||||
|
use engines::{EpochChange, Unsure};
|
||||||
|
use log_entry::LogEntry;
|
||||||
|
|
||||||
|
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
|
||||||
|
let engine = client.engine().clone();
|
||||||
|
let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap();
|
||||||
|
|
||||||
|
let last_hash = client.best_block_header().hash();
|
||||||
|
let mut new_header = Header::default();
|
||||||
|
new_header.set_parent_hash(last_hash);
|
||||||
|
|
||||||
|
// first, try without the parent hash.
|
||||||
|
let mut event = LogEntry {
|
||||||
|
address: validator_contract,
|
||||||
|
topics: vec![*EVENT_NAME_HASH],
|
||||||
|
data: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
new_header.set_log_bloom(event.bloom());
|
||||||
|
assert_eq!(engine.is_epoch_end(&new_header, None, None), EpochChange::No);
|
||||||
|
|
||||||
|
// with the last hash, it should need the receipts.
|
||||||
|
event.topics.push(last_hash);
|
||||||
|
new_header.set_log_bloom(event.bloom());
|
||||||
|
assert_eq!(engine.is_epoch_end(&new_header, None, None),
|
||||||
|
EpochChange::Unsure(Unsure::NeedsReceipts));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,40 +17,67 @@
|
|||||||
/// Preconfigured validator list.
|
/// Preconfigured validator list.
|
||||||
|
|
||||||
use util::{H256, Address, HeapSizeOf};
|
use util::{H256, Address, HeapSizeOf};
|
||||||
|
|
||||||
|
use engines::Call;
|
||||||
|
use header::Header;
|
||||||
use super::ValidatorSet;
|
use super::ValidatorSet;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Default)]
|
/// Validator set containing a known set of addresses.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
||||||
pub struct SimpleList {
|
pub struct SimpleList {
|
||||||
validators: Vec<Address>,
|
validators: Vec<Address>,
|
||||||
validator_n: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimpleList {
|
impl SimpleList {
|
||||||
|
/// Create a new `SimpleList`.
|
||||||
pub fn new(validators: Vec<Address>) -> Self {
|
pub fn new(validators: Vec<Address>) -> Self {
|
||||||
SimpleList {
|
SimpleList {
|
||||||
validator_n: validators.len(),
|
|
||||||
validators: validators,
|
validators: validators,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert into inner representation.
|
||||||
|
pub fn into_inner(self) -> Vec<Address> {
|
||||||
|
self.validators
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeapSizeOf for SimpleList {
|
impl HeapSizeOf for SimpleList {
|
||||||
fn heap_size_of_children(&self) -> usize {
|
fn heap_size_of_children(&self) -> usize {
|
||||||
self.validators.heap_size_of_children() + self.validator_n.heap_size_of_children()
|
self.validators.heap_size_of_children()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidatorSet for SimpleList {
|
impl ValidatorSet for SimpleList {
|
||||||
fn contains(&self, _bh: &H256, address: &Address) -> bool {
|
fn default_caller(&self, _block_id: ::ids::BlockId) -> Box<Call> {
|
||||||
|
Box::new(|_, _| Err("Simple list doesn't require calls.".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[::receipt::Receipt]>)
|
||||||
|
-> ::engines::EpochChange
|
||||||
|
{
|
||||||
|
::engines::EpochChange::No
|
||||||
|
}
|
||||||
|
|
||||||
|
fn epoch_proof(&self, _header: &Header, _caller: &Call) -> Result<Vec<u8>, String> {
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn epoch_set(&self, _header: &Header, _: &[u8]) -> Result<(u64, SimpleList), ::error::Error> {
|
||||||
|
Ok((0, self.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_with_caller(&self, _bh: &H256, address: &Address, _: &Call) -> bool {
|
||||||
self.validators.contains(address)
|
self.validators.contains(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, _bh: &H256, nonce: usize) -> Address {
|
fn get_with_caller(&self, _bh: &H256, nonce: usize, _: &Call) -> Address {
|
||||||
self.validators.get(nonce % self.validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone()
|
let validator_n = self.validators.len();
|
||||||
|
self.validators.get(nonce % validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count(&self, _bh: &H256) -> usize {
|
fn count_with_caller(&self, _bh: &H256, _: &Call) -> usize {
|
||||||
self.validator_n
|
self.validators.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,10 @@ use rlp::{self, UntrustedRlp};
|
|||||||
/// Parity tries to round block.gas_limit to multiple of this constant
|
/// Parity tries to round block.gas_limit to multiple of this constant
|
||||||
pub const PARITY_GAS_LIMIT_DETERMINANT: U256 = U256([37, 0, 0, 0]);
|
pub const PARITY_GAS_LIMIT_DETERMINANT: U256 = U256([37, 0, 0, 0]);
|
||||||
|
|
||||||
|
/// Number of blocks in an ethash snapshot.
|
||||||
|
// make dependent on difficulty incrment divisor?
|
||||||
|
const SNAPSHOT_BLOCKS: u64 = 30000;
|
||||||
|
|
||||||
/// Ethash params.
|
/// Ethash params.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct EthashParams {
|
pub struct EthashParams {
|
||||||
@ -139,17 +143,33 @@ pub struct Ethash {
|
|||||||
|
|
||||||
impl Ethash {
|
impl Ethash {
|
||||||
/// Create a new instance of Ethash engine
|
/// Create a new instance of Ethash engine
|
||||||
pub fn new(params: CommonParams, ethash_params: EthashParams, builtins: BTreeMap<Address, Builtin>) -> Self {
|
pub fn new(params: CommonParams, ethash_params: EthashParams, builtins: BTreeMap<Address, Builtin>) -> Arc<Self> {
|
||||||
Ethash {
|
Arc::new(Ethash {
|
||||||
params: params,
|
params: params,
|
||||||
ethash_params: ethash_params,
|
ethash_params: ethash_params,
|
||||||
builtins: builtins,
|
builtins: builtins,
|
||||||
pow: EthashManager::new(),
|
pow: EthashManager::new(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine for Ethash {
|
// TODO [rphmeier]
|
||||||
|
//
|
||||||
|
// for now, this is different than Ethash's own epochs, and signal
|
||||||
|
// "consensus epochs".
|
||||||
|
// in this sense, `Ethash` is epochless: the same `EpochVerifier` can be used
|
||||||
|
// for any block in the chain.
|
||||||
|
// in the future, we might move the Ethash epoch
|
||||||
|
// caching onto this mechanism as well.
|
||||||
|
impl ::engines::EpochVerifier for Arc<Ethash> {
|
||||||
|
fn epoch_number(&self) -> u64 { 0 }
|
||||||
|
fn verify_light(&self, _header: &Header) -> Result<(), Error> { Ok(()) }
|
||||||
|
fn verify_heavy(&self, header: &Header) -> Result<(), Error> {
|
||||||
|
self.verify_block_unordered(header, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Engine for Arc<Ethash> {
|
||||||
fn name(&self) -> &str { "Ethash" }
|
fn name(&self) -> &str { "Ethash" }
|
||||||
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
|
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
|
||||||
// Two fields - mix
|
// Two fields - mix
|
||||||
@ -379,6 +399,14 @@ impl Engine for Ethash {
|
|||||||
t.verify_basic(check_low_s, network_id, false)?;
|
t.verify_basic(check_low_s, network_id, false)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn epoch_verifier(&self, _header: &Header, _proof: &[u8]) -> Result<Box<::engines::EpochVerifier>, Error> {
|
||||||
|
Ok(Box::new(self.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
|
||||||
|
Some(Box::new(::snapshot::PowSnapshot(SNAPSHOT_BLOCKS)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to round gas_limit a bit so that:
|
// Try to round gas_limit a bit so that:
|
||||||
|
@ -238,7 +238,7 @@ impl Migration for OverlayRecentV7 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
for (key, value) in source.iter(None) {
|
for (key, value) in source.iter(None).into_iter().flat_map(|inner| inner) {
|
||||||
count += 1;
|
count += 1;
|
||||||
if count == 100_000 {
|
if count == 100_000 {
|
||||||
count = 0;
|
count = 0;
|
||||||
|
@ -102,7 +102,7 @@ impl Migration for ToV10 {
|
|||||||
|
|
||||||
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
|
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
|
||||||
let mut batch = Batch::new(config, col);
|
let mut batch = Batch::new(config, col);
|
||||||
for (key, value) in source.iter(col) {
|
for (key, value) in source.iter(col).into_iter().flat_map(|inner| inner) {
|
||||||
self.progress.tick();
|
self.progress.tick();
|
||||||
batch.insert(key.to_vec(), value.to_vec(), dest)?;
|
batch.insert(key.to_vec(), value.to_vec(), dest)?;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ impl Migration for ToV9 {
|
|||||||
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
|
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
|
||||||
let mut batch = Batch::new(config, self.column);
|
let mut batch = Batch::new(config, self.column);
|
||||||
|
|
||||||
for (key, value) in source.iter(col) {
|
for (key, value) in source.iter(col).into_iter().flat_map(|inner| inner) {
|
||||||
self.progress.tick();
|
self.progress.tick();
|
||||||
match self.extract {
|
match self.extract {
|
||||||
Extract::Header => {
|
Extract::Header => {
|
||||||
|
352
ethcore/src/snapshot/consensus/mod.rs
Normal file
352
ethcore/src/snapshot/consensus/mod.rs
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Secondary chunk creation and restoration, implementations for different consensus
|
||||||
|
//! engines.
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::io;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use blockchain::{BlockChain, BlockProvider};
|
||||||
|
use engines::Engine;
|
||||||
|
use snapshot::{Error, ManifestData};
|
||||||
|
use snapshot::block::AbridgedBlock;
|
||||||
|
|
||||||
|
use util::{Bytes, H256};
|
||||||
|
use util::kvdb::KeyValueDB;
|
||||||
|
use rand::OsRng;
|
||||||
|
use rlp::{RlpStream, UntrustedRlp};
|
||||||
|
|
||||||
|
|
||||||
|
/// A sink for produced chunks.
|
||||||
|
pub type ChunkSink<'a> = FnMut(&[u8]) -> io::Result<()> + 'a;
|
||||||
|
|
||||||
|
/// Components necessary for snapshot creation and restoration.
|
||||||
|
pub trait SnapshotComponents: Send {
|
||||||
|
/// Create secondary snapshot chunks; these corroborate the state data
|
||||||
|
/// in the state chunks.
|
||||||
|
///
|
||||||
|
/// Chunks shouldn't exceed the given preferred size, and should be fed
|
||||||
|
/// uncompressed into the sink.
|
||||||
|
///
|
||||||
|
/// This will vary by consensus engine, so it's exposed as a trait.
|
||||||
|
fn chunk_all(
|
||||||
|
&mut self,
|
||||||
|
chain: &BlockChain,
|
||||||
|
block_at: H256,
|
||||||
|
chunk_sink: &mut ChunkSink,
|
||||||
|
preferred_size: usize,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Create a rebuilder, which will have chunks fed into it in aribtrary
|
||||||
|
/// order and then be finalized.
|
||||||
|
///
|
||||||
|
/// The manifest, a database, and fresh `BlockChain` are supplied.
|
||||||
|
// TODO: supply anything for state?
|
||||||
|
fn rebuilder(
|
||||||
|
&self,
|
||||||
|
chain: BlockChain,
|
||||||
|
db: Arc<KeyValueDB>,
|
||||||
|
manifest: &ManifestData,
|
||||||
|
) -> Result<Box<Rebuilder>, ::error::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Restore from secondary snapshot chunks.
|
||||||
|
pub trait Rebuilder: Send {
|
||||||
|
/// Feed a chunk, potentially out of order.
|
||||||
|
///
|
||||||
|
/// Check `abort_flag` periodically while doing heavy work. If set to `false`, should bail with
|
||||||
|
/// `Error::RestorationAborted`.
|
||||||
|
fn feed(
|
||||||
|
&mut self,
|
||||||
|
chunk: &[u8],
|
||||||
|
engine: &Engine,
|
||||||
|
abort_flag: &AtomicBool,
|
||||||
|
) -> Result<(), ::error::Error>;
|
||||||
|
|
||||||
|
/// Finalize the restoration. Will be done after all chunks have been
|
||||||
|
/// fed successfully.
|
||||||
|
/// This will apply the necessary "glue" between chunks.
|
||||||
|
fn finalize(&mut self) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snapshot creation and restoration for PoW chains.
|
||||||
|
/// This includes blocks from the head of the chain as a
|
||||||
|
/// loose assurance that the chain is valid.
|
||||||
|
///
|
||||||
|
/// The field is the number of blocks from the head of the chain
|
||||||
|
/// to include in the snapshot.
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub struct PowSnapshot(pub u64);
|
||||||
|
|
||||||
|
impl SnapshotComponents for PowSnapshot {
|
||||||
|
fn chunk_all(
|
||||||
|
&mut self,
|
||||||
|
chain: &BlockChain,
|
||||||
|
block_at: H256,
|
||||||
|
chunk_sink: &mut ChunkSink,
|
||||||
|
preferred_size: usize,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
PowWorker {
|
||||||
|
chain: chain,
|
||||||
|
rlps: VecDeque::new(),
|
||||||
|
current_hash: block_at,
|
||||||
|
writer: chunk_sink,
|
||||||
|
preferred_size: preferred_size,
|
||||||
|
}.chunk_all(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuilder(
|
||||||
|
&self,
|
||||||
|
chain: BlockChain,
|
||||||
|
db: Arc<KeyValueDB>,
|
||||||
|
manifest: &ManifestData,
|
||||||
|
) -> Result<Box<Rebuilder>, ::error::Error> {
|
||||||
|
PowRebuilder::new(chain, db, manifest, self.0).map(|r| Box::new(r) as Box<_>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to build block chunks.
|
||||||
|
struct PowWorker<'a> {
|
||||||
|
chain: &'a BlockChain,
|
||||||
|
// block, receipt rlp pairs.
|
||||||
|
rlps: VecDeque<Bytes>,
|
||||||
|
current_hash: H256,
|
||||||
|
writer: &'a mut ChunkSink<'a>,
|
||||||
|
preferred_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PowWorker<'a> {
|
||||||
|
// Repeatedly fill the buffers and writes out chunks, moving backwards from starting block hash.
|
||||||
|
// Loops until we reach the first desired block, and writes out the remainder.
|
||||||
|
fn chunk_all(&mut self, snapshot_blocks: u64) -> Result<(), Error> {
|
||||||
|
let mut loaded_size = 0;
|
||||||
|
let mut last = self.current_hash;
|
||||||
|
|
||||||
|
let genesis_hash = self.chain.genesis_hash();
|
||||||
|
|
||||||
|
for _ in 0..snapshot_blocks {
|
||||||
|
if self.current_hash == genesis_hash { break }
|
||||||
|
|
||||||
|
let (block, receipts) = self.chain.block(&self.current_hash)
|
||||||
|
.and_then(|b| self.chain.block_receipts(&self.current_hash).map(|r| (b, r)))
|
||||||
|
.ok_or(Error::BlockNotFound(self.current_hash))?;
|
||||||
|
|
||||||
|
let abridged_rlp = AbridgedBlock::from_block_view(&block.view()).into_inner();
|
||||||
|
|
||||||
|
let pair = {
|
||||||
|
let mut pair_stream = RlpStream::new_list(2);
|
||||||
|
pair_stream.append_raw(&abridged_rlp, 1).append(&receipts);
|
||||||
|
pair_stream.out()
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_loaded_size = loaded_size + pair.len();
|
||||||
|
|
||||||
|
// cut off the chunk if too large.
|
||||||
|
|
||||||
|
if new_loaded_size > self.preferred_size && !self.rlps.is_empty() {
|
||||||
|
self.write_chunk(last)?;
|
||||||
|
loaded_size = pair.len();
|
||||||
|
} else {
|
||||||
|
loaded_size = new_loaded_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rlps.push_front(pair);
|
||||||
|
|
||||||
|
last = self.current_hash;
|
||||||
|
self.current_hash = block.header_view().parent_hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
if loaded_size != 0 {
|
||||||
|
self.write_chunk(last)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out the data in the buffers to a chunk on disk
|
||||||
|
//
|
||||||
|
// we preface each chunk with the parent of the first block's details,
|
||||||
|
// obtained from the details of the last block written.
|
||||||
|
fn write_chunk(&mut self, last: H256) -> Result<(), Error> {
|
||||||
|
trace!(target: "snapshot", "prepared block chunk with {} blocks", self.rlps.len());
|
||||||
|
|
||||||
|
let (last_header, last_details) = self.chain.block_header(&last)
|
||||||
|
.and_then(|n| self.chain.block_details(&last).map(|d| (n, d)))
|
||||||
|
.ok_or(Error::BlockNotFound(last))?;
|
||||||
|
|
||||||
|
let parent_number = last_header.number() - 1;
|
||||||
|
let parent_hash = last_header.parent_hash();
|
||||||
|
let parent_total_difficulty = last_details.total_difficulty - *last_header.difficulty();
|
||||||
|
|
||||||
|
trace!(target: "snapshot", "parent last written block: {}", parent_hash);
|
||||||
|
|
||||||
|
let num_entries = self.rlps.len();
|
||||||
|
let mut rlp_stream = RlpStream::new_list(3 + num_entries);
|
||||||
|
rlp_stream.append(&parent_number).append(parent_hash).append(&parent_total_difficulty);
|
||||||
|
|
||||||
|
for pair in self.rlps.drain(..) {
|
||||||
|
rlp_stream.append_raw(&pair, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw_data = rlp_stream.out();
|
||||||
|
|
||||||
|
(self.writer)(&raw_data)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rebuilder for proof-of-work chains.
|
||||||
|
/// Does basic verification for all blocks, but `PoW` verification for some.
|
||||||
|
/// Blocks must be fed in-order.
|
||||||
|
///
|
||||||
|
/// The first block in every chunk is disconnected from the last block in the
|
||||||
|
/// chunk before it, as chunks may be submitted out-of-order.
|
||||||
|
///
|
||||||
|
/// After all chunks have been submitted, we "glue" the chunks together.
|
||||||
|
pub struct PowRebuilder {
|
||||||
|
chain: BlockChain,
|
||||||
|
db: Arc<KeyValueDB>,
|
||||||
|
rng: OsRng,
|
||||||
|
disconnected: Vec<(u64, H256)>,
|
||||||
|
best_number: u64,
|
||||||
|
best_hash: H256,
|
||||||
|
best_root: H256,
|
||||||
|
fed_blocks: u64,
|
||||||
|
snapshot_blocks: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowRebuilder {
|
||||||
|
/// Create a new PowRebuilder.
|
||||||
|
fn new(chain: BlockChain, db: Arc<KeyValueDB>, manifest: &ManifestData, snapshot_blocks: u64) -> Result<Self, ::error::Error> {
|
||||||
|
Ok(PowRebuilder {
|
||||||
|
chain: chain,
|
||||||
|
db: db,
|
||||||
|
rng: OsRng::new()?,
|
||||||
|
disconnected: Vec::new(),
|
||||||
|
best_number: manifest.block_number,
|
||||||
|
best_hash: manifest.block_hash,
|
||||||
|
best_root: manifest.state_root,
|
||||||
|
fed_blocks: 0,
|
||||||
|
snapshot_blocks: snapshot_blocks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rebuilder for PowRebuilder {
|
||||||
|
/// Feed the rebuilder an uncompressed block chunk.
|
||||||
|
/// Returns the number of blocks fed or any errors.
|
||||||
|
fn feed(&mut self, chunk: &[u8], engine: &Engine, abort_flag: &AtomicBool) -> Result<(), ::error::Error> {
|
||||||
|
use basic_types::Seal::With;
|
||||||
|
use views::BlockView;
|
||||||
|
use snapshot::verify_old_block;
|
||||||
|
use util::U256;
|
||||||
|
use util::triehash::ordered_trie_root;
|
||||||
|
|
||||||
|
let rlp = UntrustedRlp::new(chunk);
|
||||||
|
let item_count = rlp.item_count()?;
|
||||||
|
let num_blocks = (item_count - 3) as u64;
|
||||||
|
|
||||||
|
trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 3);
|
||||||
|
|
||||||
|
if self.fed_blocks + num_blocks > self.snapshot_blocks {
|
||||||
|
return Err(Error::TooManyBlocks(self.snapshot_blocks, self.fed_blocks).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: assert here that these values are consistent with chunks being in order.
|
||||||
|
let mut cur_number = rlp.val_at::<u64>(0)? + 1;
|
||||||
|
let mut parent_hash = rlp.val_at::<H256>(1)?;
|
||||||
|
let parent_total_difficulty = rlp.val_at::<U256>(2)?;
|
||||||
|
|
||||||
|
for idx in 3..item_count {
|
||||||
|
if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
|
||||||
|
|
||||||
|
let pair = rlp.at(idx)?;
|
||||||
|
let abridged_rlp = pair.at(0)?.as_raw().to_owned();
|
||||||
|
let abridged_block = AbridgedBlock::from_raw(abridged_rlp);
|
||||||
|
let receipts: Vec<::receipt::Receipt> = pair.list_at(1)?;
|
||||||
|
let receipts_root = ordered_trie_root(
|
||||||
|
pair.at(1)?.iter().map(|r| r.as_raw().to_owned())
|
||||||
|
);
|
||||||
|
|
||||||
|
let block = abridged_block.to_block(parent_hash, cur_number, receipts_root)?;
|
||||||
|
let block_bytes = block.rlp_bytes(With);
|
||||||
|
let is_best = cur_number == self.best_number;
|
||||||
|
|
||||||
|
if is_best {
|
||||||
|
if block.header.hash() != self.best_hash {
|
||||||
|
return Err(Error::WrongBlockHash(cur_number, self.best_hash, block.header.hash()).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.header.state_root() != &self.best_root {
|
||||||
|
return Err(Error::WrongStateRoot(self.best_root, *block.header.state_root()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_old_block(
|
||||||
|
&mut self.rng,
|
||||||
|
&block.header,
|
||||||
|
engine,
|
||||||
|
&self.chain,
|
||||||
|
Some(&block_bytes),
|
||||||
|
is_best
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut batch = self.db.transaction();
|
||||||
|
|
||||||
|
// special-case the first block in each chunk.
|
||||||
|
if idx == 3 {
|
||||||
|
if self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, Some(parent_total_difficulty), is_best, false) {
|
||||||
|
self.disconnected.push((cur_number, block.header.hash()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, is_best, false);
|
||||||
|
}
|
||||||
|
self.db.write_buffered(batch);
|
||||||
|
self.chain.commit();
|
||||||
|
|
||||||
|
parent_hash = BlockView::new(&block_bytes).hash();
|
||||||
|
cur_number += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fed_blocks += num_blocks;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Glue together any disconnected chunks and check that the chain is complete.
|
||||||
|
fn finalize(&mut self) -> Result<(), Error> {
|
||||||
|
let mut batch = self.db.transaction();
|
||||||
|
|
||||||
|
for (first_num, first_hash) in self.disconnected.drain(..) {
|
||||||
|
let parent_num = first_num - 1;
|
||||||
|
|
||||||
|
// check if the parent is even in the chain.
|
||||||
|
// since we don't restore every single block in the chain,
|
||||||
|
// the first block of the first chunks has nothing to connect to.
|
||||||
|
if let Some(parent_hash) = self.chain.block_hash(parent_num) {
|
||||||
|
// if so, add the child to it.
|
||||||
|
self.chain.add_child(&mut batch, parent_hash, first_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.db.write_buffered(batch);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,8 @@ pub enum Error {
|
|||||||
VersionNotSupported(u64),
|
VersionNotSupported(u64),
|
||||||
/// Max chunk size is to small to fit basic account data.
|
/// Max chunk size is to small to fit basic account data.
|
||||||
ChunkTooSmall,
|
ChunkTooSmall,
|
||||||
|
/// Snapshots not supported by the consensus engine.
|
||||||
|
SnapshotsUnsupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
@ -79,6 +81,7 @@ impl fmt::Display for Error {
|
|||||||
Error::Trie(ref err) => err.fmt(f),
|
Error::Trie(ref err) => err.fmt(f),
|
||||||
Error::VersionNotSupported(ref ver) => write!(f, "Snapshot version {} is not supprted.", ver),
|
Error::VersionNotSupported(ref ver) => write!(f, "Snapshot version {} is not supprted.", ver),
|
||||||
Error::ChunkTooSmall => write!(f, "Chunk size is too small."),
|
Error::ChunkTooSmall => write!(f, "Chunk size is too small."),
|
||||||
|
Error::SnapshotsUnsupported => write!(f, "Snapshots unsupported by consensus engine."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
//! Snapshot creation, restoration, and network service.
|
//! Snapshot creation, restoration, and network service.
|
||||||
//!
|
//!
|
||||||
//! Documentation of the format can be found at
|
//! Documentation of the format can be found at
|
||||||
//! https://github.com/paritytech/parity/wiki/%22PV64%22-Snapshot-Format
|
//! https://github.com/paritytech/parity/wiki/Warp-Sync-Snapshot-Format
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
|
|
||||||
@ -28,7 +28,6 @@ use blockchain::{BlockChain, BlockProvider};
|
|||||||
use engines::Engine;
|
use engines::Engine;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use ids::BlockId;
|
use ids::BlockId;
|
||||||
use views::BlockView;
|
|
||||||
|
|
||||||
use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint};
|
use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint};
|
||||||
use util::Mutex;
|
use util::Mutex;
|
||||||
@ -40,7 +39,6 @@ use util::sha3::SHA3_NULL_RLP;
|
|||||||
use rlp::{RlpStream, UntrustedRlp};
|
use rlp::{RlpStream, UntrustedRlp};
|
||||||
use bloom_journal::Bloom;
|
use bloom_journal::Bloom;
|
||||||
|
|
||||||
use self::block::AbridgedBlock;
|
|
||||||
use self::io::SnapshotWriter;
|
use self::io::SnapshotWriter;
|
||||||
|
|
||||||
use super::state_db::StateDB;
|
use super::state_db::StateDB;
|
||||||
@ -51,6 +49,7 @@ use rand::{Rng, OsRng};
|
|||||||
|
|
||||||
pub use self::error::Error;
|
pub use self::error::Error;
|
||||||
|
|
||||||
|
pub use self::consensus::*;
|
||||||
pub use self::service::{Service, DatabaseRestore};
|
pub use self::service::{Service, DatabaseRestore};
|
||||||
pub use self::traits::SnapshotService;
|
pub use self::traits::SnapshotService;
|
||||||
pub use self::watcher::Watcher;
|
pub use self::watcher::Watcher;
|
||||||
@ -63,6 +62,7 @@ pub mod service;
|
|||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
mod block;
|
mod block;
|
||||||
|
mod consensus;
|
||||||
mod error;
|
mod error;
|
||||||
mod watcher;
|
mod watcher;
|
||||||
|
|
||||||
@ -83,9 +83,6 @@ mod traits {
|
|||||||
// Try to have chunks be around 4MB (before compression)
|
// Try to have chunks be around 4MB (before compression)
|
||||||
const PREFERRED_CHUNK_SIZE: usize = 4 * 1024 * 1024;
|
const PREFERRED_CHUNK_SIZE: usize = 4 * 1024 * 1024;
|
||||||
|
|
||||||
// How many blocks to include in a snapshot, starting from the head of the chain.
|
|
||||||
const SNAPSHOT_BLOCKS: u64 = 30000;
|
|
||||||
|
|
||||||
/// A progress indicator for snapshots.
|
/// A progress indicator for snapshots.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Progress {
|
pub struct Progress {
|
||||||
@ -122,6 +119,7 @@ impl Progress {
|
|||||||
}
|
}
|
||||||
/// Take a snapshot using the given blockchain, starting block hash, and database, writing into the given writer.
|
/// Take a snapshot using the given blockchain, starting block hash, and database, writing into the given writer.
|
||||||
pub fn take_snapshot<W: SnapshotWriter + Send>(
|
pub fn take_snapshot<W: SnapshotWriter + Send>(
|
||||||
|
engine: &Engine,
|
||||||
chain: &BlockChain,
|
chain: &BlockChain,
|
||||||
block_at: H256,
|
block_at: H256,
|
||||||
state_db: &HashDB,
|
state_db: &HashDB,
|
||||||
@ -136,9 +134,11 @@ pub fn take_snapshot<W: SnapshotWriter + Send>(
|
|||||||
info!("Taking snapshot starting at block {}", number);
|
info!("Taking snapshot starting at block {}", number);
|
||||||
|
|
||||||
let writer = Mutex::new(writer);
|
let writer = Mutex::new(writer);
|
||||||
|
let chunker = engine.snapshot_components().ok_or(Error::SnapshotsUnsupported)?;
|
||||||
let (state_hashes, block_hashes) = scope(|scope| {
|
let (state_hashes, block_hashes) = scope(|scope| {
|
||||||
let block_guard = scope.spawn(|| chunk_blocks(chain, block_at, &writer, p));
|
let writer = &writer;
|
||||||
let state_res = chunk_state(state_db, state_root, &writer, p);
|
let block_guard = scope.spawn(move || chunk_secondary(chunker, chain, block_at, writer, p));
|
||||||
|
let state_res = chunk_state(state_db, state_root, writer, p);
|
||||||
|
|
||||||
state_res.and_then(|state_hashes| {
|
state_res.and_then(|state_hashes| {
|
||||||
block_guard.join().map(|block_hashes| (state_hashes, block_hashes))
|
block_guard.join().map(|block_hashes| (state_hashes, block_hashes))
|
||||||
@ -163,128 +163,41 @@ pub fn take_snapshot<W: SnapshotWriter + Send>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to build block chunks.
|
/// Create and write out all secondary chunks to disk, returning a vector of all
|
||||||
struct BlockChunker<'a> {
|
/// the hashes of secondary chunks created.
|
||||||
chain: &'a BlockChain,
|
|
||||||
// block, receipt rlp pairs.
|
|
||||||
rlps: VecDeque<Bytes>,
|
|
||||||
current_hash: H256,
|
|
||||||
hashes: Vec<H256>,
|
|
||||||
snappy_buffer: Vec<u8>,
|
|
||||||
writer: &'a Mutex<SnapshotWriter + 'a>,
|
|
||||||
progress: &'a Progress,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> BlockChunker<'a> {
|
|
||||||
// Repeatedly fill the buffers and writes out chunks, moving backwards from starting block hash.
|
|
||||||
// Loops until we reach the first desired block, and writes out the remainder.
|
|
||||||
fn chunk_all(&mut self) -> Result<(), Error> {
|
|
||||||
let mut loaded_size = 0;
|
|
||||||
let mut last = self.current_hash;
|
|
||||||
|
|
||||||
let genesis_hash = self.chain.genesis_hash();
|
|
||||||
|
|
||||||
for _ in 0..SNAPSHOT_BLOCKS {
|
|
||||||
if self.current_hash == genesis_hash { break }
|
|
||||||
|
|
||||||
let (block, receipts) = self.chain.block(&self.current_hash)
|
|
||||||
.and_then(|b| self.chain.block_receipts(&self.current_hash).map(|r| (b, r)))
|
|
||||||
.ok_or(Error::BlockNotFound(self.current_hash))?;
|
|
||||||
|
|
||||||
let abridged_rlp = AbridgedBlock::from_block_view(&block.view()).into_inner();
|
|
||||||
|
|
||||||
let pair = {
|
|
||||||
let mut pair_stream = RlpStream::new_list(2);
|
|
||||||
pair_stream.append_raw(&abridged_rlp, 1).append(&receipts);
|
|
||||||
pair_stream.out()
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_loaded_size = loaded_size + pair.len();
|
|
||||||
|
|
||||||
// cut off the chunk if too large.
|
|
||||||
|
|
||||||
if new_loaded_size > PREFERRED_CHUNK_SIZE && !self.rlps.is_empty() {
|
|
||||||
self.write_chunk(last)?;
|
|
||||||
loaded_size = pair.len();
|
|
||||||
} else {
|
|
||||||
loaded_size = new_loaded_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.rlps.push_front(pair);
|
|
||||||
|
|
||||||
last = self.current_hash;
|
|
||||||
self.current_hash = block.header_view().parent_hash();
|
|
||||||
}
|
|
||||||
|
|
||||||
if loaded_size != 0 {
|
|
||||||
self.write_chunk(last)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// write out the data in the buffers to a chunk on disk
|
|
||||||
//
|
|
||||||
// we preface each chunk with the parent of the first block's details,
|
|
||||||
// obtained from the details of the last block written.
|
|
||||||
fn write_chunk(&mut self, last: H256) -> Result<(), Error> {
|
|
||||||
trace!(target: "snapshot", "prepared block chunk with {} blocks", self.rlps.len());
|
|
||||||
|
|
||||||
let (last_header, last_details) = self.chain.block_header(&last)
|
|
||||||
.and_then(|n| self.chain.block_details(&last).map(|d| (n, d)))
|
|
||||||
.ok_or(Error::BlockNotFound(last))?;
|
|
||||||
|
|
||||||
let parent_number = last_header.number() - 1;
|
|
||||||
let parent_hash = last_header.parent_hash();
|
|
||||||
let parent_total_difficulty = last_details.total_difficulty - *last_header.difficulty();
|
|
||||||
|
|
||||||
trace!(target: "snapshot", "parent last written block: {}", parent_hash);
|
|
||||||
|
|
||||||
let num_entries = self.rlps.len();
|
|
||||||
let mut rlp_stream = RlpStream::new_list(3 + num_entries);
|
|
||||||
rlp_stream.append(&parent_number).append(parent_hash).append(&parent_total_difficulty);
|
|
||||||
|
|
||||||
for pair in self.rlps.drain(..) {
|
|
||||||
rlp_stream.append_raw(&pair, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let raw_data = rlp_stream.out();
|
|
||||||
|
|
||||||
let size = snappy::compress_into(&raw_data, &mut self.snappy_buffer);
|
|
||||||
let compressed = &self.snappy_buffer[..size];
|
|
||||||
let hash = compressed.sha3();
|
|
||||||
|
|
||||||
self.writer.lock().write_block_chunk(hash, compressed)?;
|
|
||||||
trace!(target: "snapshot", "wrote block chunk. hash: {}, size: {}, uncompressed size: {}", hash.hex(), size, raw_data.len());
|
|
||||||
|
|
||||||
self.progress.size.fetch_add(size, Ordering::SeqCst);
|
|
||||||
self.progress.blocks.fetch_add(num_entries, Ordering::SeqCst);
|
|
||||||
|
|
||||||
self.hashes.push(hash);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create and write out all block chunks to disk, returning a vector of all
|
|
||||||
/// the hashes of block chunks created.
|
|
||||||
///
|
///
|
||||||
/// The path parameter is the directory to store the block chunks in.
|
/// Secondary chunks are engine-specific, but they intend to corroborate the state data
|
||||||
/// This function assumes the directory exists already.
|
/// in the state chunks.
|
||||||
/// Returns a list of chunk hashes, with the first having the blocks furthest from the genesis.
|
/// Returns a list of chunk hashes, with the first having the blocks furthest from the genesis.
|
||||||
pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_hash: H256, writer: &Mutex<SnapshotWriter + 'a>, progress: &'a Progress) -> Result<Vec<H256>, Error> {
|
pub fn chunk_secondary<'a>(mut chunker: Box<SnapshotComponents>, chain: &'a BlockChain, start_hash: H256, writer: &Mutex<SnapshotWriter + 'a>, progress: &'a Progress) -> Result<Vec<H256>, Error> {
|
||||||
let mut chunker = BlockChunker {
|
let mut chunk_hashes = Vec::new();
|
||||||
chain: chain,
|
let mut snappy_buffer = vec![0; snappy::max_compressed_len(PREFERRED_CHUNK_SIZE)];
|
||||||
rlps: VecDeque::new(),
|
|
||||||
current_hash: start_hash,
|
{
|
||||||
hashes: Vec::new(),
|
let mut chunk_sink = |raw_data: &[u8]| {
|
||||||
snappy_buffer: vec![0; snappy::max_compressed_len(PREFERRED_CHUNK_SIZE)],
|
let compressed_size = snappy::compress_into(raw_data, &mut snappy_buffer);
|
||||||
writer: writer,
|
let compressed = &snappy_buffer[..compressed_size];
|
||||||
progress: progress,
|
let hash = compressed.sha3();
|
||||||
|
let size = compressed.len();
|
||||||
|
|
||||||
|
writer.lock().write_block_chunk(hash, compressed)?;
|
||||||
|
trace!(target: "snapshot", "wrote secondary chunk. hash: {}, size: {}, uncompressed size: {}",
|
||||||
|
hash.hex(), size, raw_data.len());
|
||||||
|
|
||||||
|
progress.size.fetch_add(size, Ordering::SeqCst);
|
||||||
|
chunk_hashes.push(hash);
|
||||||
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
chunker.chunk_all()?;
|
chunker.chunk_all(
|
||||||
|
chain,
|
||||||
|
start_hash,
|
||||||
|
&mut chunk_sink,
|
||||||
|
PREFERRED_CHUNK_SIZE,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(chunker.hashes)
|
Ok(chunk_hashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State trie chunker.
|
/// State trie chunker.
|
||||||
@ -564,158 +477,15 @@ const POW_VERIFY_RATE: f32 = 0.02;
|
|||||||
/// the fullest verification possible. If not, it will take a random sample to determine whether it will
|
/// the fullest verification possible. If not, it will take a random sample to determine whether it will
|
||||||
/// do heavy or light verification.
|
/// do heavy or light verification.
|
||||||
pub fn verify_old_block(rng: &mut OsRng, header: &Header, engine: &Engine, chain: &BlockChain, body: Option<&[u8]>, always: bool) -> Result<(), ::error::Error> {
|
pub fn verify_old_block(rng: &mut OsRng, header: &Header, engine: &Engine, chain: &BlockChain, body: Option<&[u8]>, always: bool) -> Result<(), ::error::Error> {
|
||||||
|
engine.verify_block_basic(header, body)?;
|
||||||
|
|
||||||
if always || rng.gen::<f32>() <= POW_VERIFY_RATE {
|
if always || rng.gen::<f32>() <= POW_VERIFY_RATE {
|
||||||
|
engine.verify_block_unordered(header, body)?;
|
||||||
match chain.block_header(header.parent_hash()) {
|
match chain.block_header(header.parent_hash()) {
|
||||||
Some(parent) => engine.verify_block_family(header, &parent, body),
|
Some(parent) => engine.verify_block_family(header, &parent, body),
|
||||||
None => engine.verify_block_seal(header),
|
None => Ok(()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
engine.verify_block_basic(header, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rebuilds the blockchain from chunks.
|
|
||||||
///
|
|
||||||
/// Does basic verification for all blocks, but `PoW` verification for some.
|
|
||||||
/// Blocks must be fed in-order.
|
|
||||||
///
|
|
||||||
/// The first block in every chunk is disconnected from the last block in the
|
|
||||||
/// chunk before it, as chunks may be submitted out-of-order.
|
|
||||||
///
|
|
||||||
/// After all chunks have been submitted, we "glue" the chunks together.
|
|
||||||
pub struct BlockRebuilder {
|
|
||||||
chain: BlockChain,
|
|
||||||
db: Arc<Database>,
|
|
||||||
rng: OsRng,
|
|
||||||
disconnected: Vec<(u64, H256)>,
|
|
||||||
best_number: u64,
|
|
||||||
best_hash: H256,
|
|
||||||
best_root: H256,
|
|
||||||
fed_blocks: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockRebuilder {
|
|
||||||
/// Create a new BlockRebuilder.
|
|
||||||
pub fn new(chain: BlockChain, db: Arc<Database>, manifest: &ManifestData) -> Result<Self, ::error::Error> {
|
|
||||||
Ok(BlockRebuilder {
|
|
||||||
chain: chain,
|
|
||||||
db: db,
|
|
||||||
rng: OsRng::new()?,
|
|
||||||
disconnected: Vec::new(),
|
|
||||||
best_number: manifest.block_number,
|
|
||||||
best_hash: manifest.block_hash,
|
|
||||||
best_root: manifest.state_root,
|
|
||||||
fed_blocks: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Feed the rebuilder an uncompressed block chunk.
|
|
||||||
/// Returns the number of blocks fed or any errors.
|
|
||||||
pub fn feed(&mut self, chunk: &[u8], engine: &Engine, abort_flag: &AtomicBool) -> Result<u64, ::error::Error> {
|
|
||||||
use basic_types::Seal::With;
|
|
||||||
use util::U256;
|
|
||||||
use util::triehash::ordered_trie_root;
|
|
||||||
|
|
||||||
let rlp = UntrustedRlp::new(chunk);
|
|
||||||
let item_count = rlp.item_count()?;
|
|
||||||
let num_blocks = (item_count - 3) as u64;
|
|
||||||
|
|
||||||
trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 3);
|
|
||||||
|
|
||||||
if self.fed_blocks + num_blocks > SNAPSHOT_BLOCKS {
|
|
||||||
return Err(Error::TooManyBlocks(SNAPSHOT_BLOCKS, self.fed_blocks).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: assert here that these values are consistent with chunks being in order.
|
|
||||||
let mut cur_number = rlp.val_at::<u64>(0)? + 1;
|
|
||||||
let mut parent_hash = rlp.val_at::<H256>(1)?;
|
|
||||||
let parent_total_difficulty = rlp.val_at::<U256>(2)?;
|
|
||||||
|
|
||||||
for idx in 3..item_count {
|
|
||||||
if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
|
|
||||||
|
|
||||||
let pair = rlp.at(idx)?;
|
|
||||||
let abridged_rlp = pair.at(0)?.as_raw().to_owned();
|
|
||||||
let abridged_block = AbridgedBlock::from_raw(abridged_rlp);
|
|
||||||
let receipts: Vec<::receipt::Receipt> = pair.list_at(1)?;
|
|
||||||
let receipts_root = ordered_trie_root(
|
|
||||||
pair.at(1)?.iter().map(|r| r.as_raw().to_owned())
|
|
||||||
);
|
|
||||||
|
|
||||||
let block = abridged_block.to_block(parent_hash, cur_number, receipts_root)?;
|
|
||||||
let block_bytes = block.rlp_bytes(With);
|
|
||||||
let is_best = cur_number == self.best_number;
|
|
||||||
|
|
||||||
if is_best {
|
|
||||||
if block.header.hash() != self.best_hash {
|
|
||||||
return Err(Error::WrongBlockHash(cur_number, self.best_hash, block.header.hash()).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
if block.header.state_root() != &self.best_root {
|
|
||||||
return Err(Error::WrongStateRoot(self.best_root, *block.header.state_root()).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
verify_old_block(
|
|
||||||
&mut self.rng,
|
|
||||||
&block.header,
|
|
||||||
engine,
|
|
||||||
&self.chain,
|
|
||||||
Some(&block_bytes),
|
|
||||||
is_best
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut batch = self.db.transaction();
|
|
||||||
|
|
||||||
// special-case the first block in each chunk.
|
|
||||||
if idx == 3 {
|
|
||||||
if self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, Some(parent_total_difficulty), is_best, false) {
|
|
||||||
self.disconnected.push((cur_number, block.header.hash()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, is_best, false);
|
|
||||||
}
|
|
||||||
self.db.write_buffered(batch);
|
|
||||||
self.chain.commit();
|
|
||||||
|
|
||||||
parent_hash = BlockView::new(&block_bytes).hash();
|
|
||||||
cur_number += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fed_blocks += num_blocks;
|
|
||||||
|
|
||||||
Ok(num_blocks)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Glue together any disconnected chunks and check that the chain is complete.
|
|
||||||
pub fn finalize(self, canonical: HashMap<u64, H256>) -> Result<(), Error> {
|
|
||||||
let mut batch = self.db.transaction();
|
|
||||||
|
|
||||||
for (first_num, first_hash) in self.disconnected {
|
|
||||||
let parent_num = first_num - 1;
|
|
||||||
|
|
||||||
// check if the parent is even in the chain.
|
|
||||||
// since we don't restore every single block in the chain,
|
|
||||||
// the first block of the first chunks has nothing to connect to.
|
|
||||||
if let Some(parent_hash) = self.chain.block_hash(parent_num) {
|
|
||||||
// if so, add the child to it.
|
|
||||||
self.chain.add_child(&mut batch, parent_hash, first_hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.db.write_buffered(batch);
|
|
||||||
|
|
||||||
let best_number = self.best_number;
|
|
||||||
for num in (0..self.fed_blocks).map(|x| best_number - x) {
|
|
||||||
|
|
||||||
let hash = self.chain.block_hash(num).ok_or(Error::IncompleteChain)?;
|
|
||||||
|
|
||||||
if let Some(canon_hash) = canonical.get(&num).cloned() {
|
|
||||||
if canon_hash != hash {
|
|
||||||
return Err(Error::WrongBlockHash(num, canon_hash, hash));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
|
|
||||||
//! Snapshot network service implementation.
|
//! Snapshot network service implementation.
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
|
|
||||||
use super::{ManifestData, StateRebuilder, BlockRebuilder, RestorationStatus, SnapshotService};
|
use super::{ManifestData, StateRebuilder, Rebuilder, RestorationStatus, SnapshotService};
|
||||||
use super::io::{SnapshotReader, LooseReader, SnapshotWriter, LooseWriter};
|
use super::io::{SnapshotReader, LooseReader, SnapshotWriter, LooseWriter};
|
||||||
|
|
||||||
use blockchain::BlockChain;
|
use blockchain::BlockChain;
|
||||||
@ -69,12 +69,11 @@ struct Restoration {
|
|||||||
state_chunks_left: HashSet<H256>,
|
state_chunks_left: HashSet<H256>,
|
||||||
block_chunks_left: HashSet<H256>,
|
block_chunks_left: HashSet<H256>,
|
||||||
state: StateRebuilder,
|
state: StateRebuilder,
|
||||||
blocks: BlockRebuilder,
|
secondary: Box<Rebuilder>,
|
||||||
writer: Option<LooseWriter>,
|
writer: Option<LooseWriter>,
|
||||||
snappy_buffer: Bytes,
|
snappy_buffer: Bytes,
|
||||||
final_state_root: H256,
|
final_state_root: H256,
|
||||||
guard: Guard,
|
guard: Guard,
|
||||||
canonical_hashes: HashMap<u64, H256>,
|
|
||||||
db: Arc<Database>,
|
db: Arc<Database>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +85,7 @@ struct RestorationParams<'a> {
|
|||||||
writer: Option<LooseWriter>, // writer for recovered snapshot.
|
writer: Option<LooseWriter>, // writer for recovered snapshot.
|
||||||
genesis: &'a [u8], // genesis block of the chain.
|
genesis: &'a [u8], // genesis block of the chain.
|
||||||
guard: Guard, // guard for the restoration directory.
|
guard: Guard, // guard for the restoration directory.
|
||||||
|
engine: &'a Engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Restoration {
|
impl Restoration {
|
||||||
@ -100,7 +100,10 @@ impl Restoration {
|
|||||||
.map_err(UtilError::SimpleString)?);
|
.map_err(UtilError::SimpleString)?);
|
||||||
|
|
||||||
let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone());
|
let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone());
|
||||||
let blocks = BlockRebuilder::new(chain, raw_db.clone(), &manifest)?;
|
let components = params.engine.snapshot_components()
|
||||||
|
.ok_or_else(|| ::snapshot::Error::SnapshotsUnsupported)?;
|
||||||
|
|
||||||
|
let secondary = components.rebuilder(chain, raw_db.clone(), &manifest)?;
|
||||||
|
|
||||||
let root = manifest.state_root.clone();
|
let root = manifest.state_root.clone();
|
||||||
Ok(Restoration {
|
Ok(Restoration {
|
||||||
@ -108,12 +111,11 @@ impl Restoration {
|
|||||||
state_chunks_left: state_chunks,
|
state_chunks_left: state_chunks,
|
||||||
block_chunks_left: block_chunks,
|
block_chunks_left: block_chunks,
|
||||||
state: StateRebuilder::new(raw_db.clone(), params.pruning),
|
state: StateRebuilder::new(raw_db.clone(), params.pruning),
|
||||||
blocks: blocks,
|
secondary: secondary,
|
||||||
writer: params.writer,
|
writer: params.writer,
|
||||||
snappy_buffer: Vec::new(),
|
snappy_buffer: Vec::new(),
|
||||||
final_state_root: root,
|
final_state_root: root,
|
||||||
guard: params.guard,
|
guard: params.guard,
|
||||||
canonical_hashes: HashMap::new(),
|
|
||||||
db: raw_db,
|
db: raw_db,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -138,7 +140,7 @@ impl Restoration {
|
|||||||
if self.block_chunks_left.remove(&hash) {
|
if self.block_chunks_left.remove(&hash) {
|
||||||
let len = snappy::decompress_into(chunk, &mut self.snappy_buffer)?;
|
let len = snappy::decompress_into(chunk, &mut self.snappy_buffer)?;
|
||||||
|
|
||||||
self.blocks.feed(&self.snappy_buffer[..len], engine, flag)?;
|
self.secondary.feed(&self.snappy_buffer[..len], engine, flag)?;
|
||||||
if let Some(ref mut writer) = self.writer.as_mut() {
|
if let Some(ref mut writer) = self.writer.as_mut() {
|
||||||
writer.write_block_chunk(hash, chunk)?;
|
writer.write_block_chunk(hash, chunk)?;
|
||||||
}
|
}
|
||||||
@ -147,13 +149,8 @@ impl Restoration {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// note canonical hashes.
|
|
||||||
fn note_canonical(&mut self, hashes: &[(u64, H256)]) {
|
|
||||||
self.canonical_hashes.extend(hashes.iter().cloned());
|
|
||||||
}
|
|
||||||
|
|
||||||
// finish up restoration.
|
// finish up restoration.
|
||||||
fn finalize(self) -> Result<(), Error> {
|
fn finalize(mut self) -> Result<(), Error> {
|
||||||
use util::trie::TrieError;
|
use util::trie::TrieError;
|
||||||
|
|
||||||
if !self.is_done() { return Ok(()) }
|
if !self.is_done() { return Ok(()) }
|
||||||
@ -169,7 +166,7 @@ impl Restoration {
|
|||||||
self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?;
|
self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?;
|
||||||
|
|
||||||
// connect out-of-order chunks and verify chain integrity.
|
// connect out-of-order chunks and verify chain integrity.
|
||||||
self.blocks.finalize(self.canonical_hashes)?;
|
self.secondary.finalize()?;
|
||||||
|
|
||||||
if let Some(writer) = self.writer {
|
if let Some(writer) = self.writer {
|
||||||
writer.finish(self.manifest)?;
|
writer.finish(self.manifest)?;
|
||||||
@ -425,6 +422,7 @@ impl Service {
|
|||||||
writer: writer,
|
writer: writer,
|
||||||
genesis: &self.genesis_block,
|
genesis: &self.genesis_block,
|
||||||
guard: Guard::new(rest_dir),
|
guard: Guard::new(rest_dir),
|
||||||
|
engine: &*self.engine,
|
||||||
};
|
};
|
||||||
|
|
||||||
let state_chunks = params.manifest.state_hashes.len();
|
let state_chunks = params.manifest.state_hashes.len();
|
||||||
@ -593,14 +591,6 @@ impl SnapshotService for Service {
|
|||||||
trace!("Error sending snapshot service message: {:?}", e);
|
trace!("Error sending snapshot service message: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_canon_hashes(&self, canonical: &[(u64, H256)]) {
|
|
||||||
let mut rest = self.restoration.lock();
|
|
||||||
|
|
||||||
if let Some(ref mut rest) = rest.as_mut() {
|
|
||||||
rest.note_canonical(canonical);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Service {
|
impl Drop for Service {
|
||||||
|
@ -48,10 +48,6 @@ pub trait SnapshotService : Sync + Send {
|
|||||||
/// Feed a raw block chunk to the service to be processed asynchronously.
|
/// Feed a raw block chunk to the service to be processed asynchronously.
|
||||||
/// no-op if currently restoring.
|
/// no-op if currently restoring.
|
||||||
fn restore_block_chunk(&self, hash: H256, chunk: Bytes);
|
fn restore_block_chunk(&self, hash: H256, chunk: Bytes);
|
||||||
|
|
||||||
/// Give the restoration in-progress some canonical block hashes for
|
|
||||||
/// extra verification (performed at the end)
|
|
||||||
fn provide_canon_hashes(&self, canonical: &[(u64, H256)]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IpcConfig for SnapshotService { }
|
impl IpcConfig for SnapshotService { }
|
||||||
|
@ -21,33 +21,32 @@ use error::Error;
|
|||||||
|
|
||||||
use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
|
use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
|
||||||
use blockchain::BlockChain;
|
use blockchain::BlockChain;
|
||||||
use snapshot::{chunk_blocks, BlockRebuilder, Error as SnapshotError, Progress};
|
use snapshot::{chunk_secondary, Error as SnapshotError, Progress, SnapshotComponents};
|
||||||
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
|
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
|
||||||
|
|
||||||
use util::{Mutex, snappy};
|
use util::{Mutex, snappy};
|
||||||
use util::kvdb::{Database, DatabaseConfig};
|
use util::kvdb::{self, KeyValueDB, DBTransaction};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
const SNAPSHOT_MODE: ::snapshot::PowSnapshot = ::snapshot::PowSnapshot(30000);
|
||||||
|
|
||||||
fn chunk_and_restore(amount: u64) {
|
fn chunk_and_restore(amount: u64) {
|
||||||
let mut canon_chain = ChainGenerator::default();
|
let mut canon_chain = ChainGenerator::default();
|
||||||
let mut finalizer = BlockFinalizer::default();
|
let mut finalizer = BlockFinalizer::default();
|
||||||
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
|
||||||
|
|
||||||
let engine = Arc::new(::engines::NullEngine::default());
|
let engine = Arc::new(::engines::NullEngine::default());
|
||||||
let orig_path = RandomTempPath::create_dir();
|
|
||||||
let new_path = RandomTempPath::create_dir();
|
let new_path = RandomTempPath::create_dir();
|
||||||
let mut snapshot_path = new_path.as_path().to_owned();
|
let mut snapshot_path = new_path.as_path().to_owned();
|
||||||
snapshot_path.push("SNAP");
|
snapshot_path.push("SNAP");
|
||||||
|
|
||||||
let old_db = Arc::new(Database::open(&db_cfg, orig_path.as_str()).unwrap());
|
let old_db = Arc::new(kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
|
||||||
let bc = BlockChain::new(Default::default(), &genesis, old_db.clone());
|
let bc = BlockChain::new(Default::default(), &genesis, old_db.clone());
|
||||||
|
|
||||||
// build the blockchain.
|
// build the blockchain.
|
||||||
let mut batch = old_db.transaction();
|
let mut batch = DBTransaction::new();
|
||||||
for _ in 0..amount {
|
for _ in 0..amount {
|
||||||
let block = canon_chain.generate(&mut finalizer).unwrap();
|
let block = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
bc.insert_block(&mut batch, &block, vec![]);
|
bc.insert_block(&mut batch, &block, vec![]);
|
||||||
@ -56,12 +55,18 @@ fn chunk_and_restore(amount: u64) {
|
|||||||
|
|
||||||
old_db.write(batch).unwrap();
|
old_db.write(batch).unwrap();
|
||||||
|
|
||||||
|
|
||||||
let best_hash = bc.best_block_hash();
|
let best_hash = bc.best_block_hash();
|
||||||
|
|
||||||
// snapshot it.
|
// snapshot it.
|
||||||
let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap());
|
let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap());
|
||||||
let block_hashes = chunk_blocks(&bc, best_hash, &writer, &Progress::default()).unwrap();
|
let block_hashes = chunk_secondary(
|
||||||
|
Box::new(SNAPSHOT_MODE),
|
||||||
|
&bc,
|
||||||
|
best_hash,
|
||||||
|
&writer,
|
||||||
|
&Progress::default()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
let manifest = ::snapshot::ManifestData {
|
let manifest = ::snapshot::ManifestData {
|
||||||
version: 2,
|
version: 2,
|
||||||
state_hashes: Vec::new(),
|
state_hashes: Vec::new(),
|
||||||
@ -74,9 +79,10 @@ fn chunk_and_restore(amount: u64) {
|
|||||||
writer.into_inner().finish(manifest.clone()).unwrap();
|
writer.into_inner().finish(manifest.clone()).unwrap();
|
||||||
|
|
||||||
// restore it.
|
// restore it.
|
||||||
let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap());
|
let new_db = Arc::new(kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
|
||||||
let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone());
|
let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone());
|
||||||
let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap();
|
let mut rebuilder = SNAPSHOT_MODE.rebuilder(new_chain, new_db.clone(), &manifest).unwrap();
|
||||||
|
|
||||||
let reader = PackedReader::new(&snapshot_path).unwrap().unwrap();
|
let reader = PackedReader::new(&snapshot_path).unwrap().unwrap();
|
||||||
let flag = AtomicBool::new(true);
|
let flag = AtomicBool::new(true);
|
||||||
for chunk_hash in &reader.manifest().block_hashes {
|
for chunk_hash in &reader.manifest().block_hashes {
|
||||||
@ -85,7 +91,8 @@ fn chunk_and_restore(amount: u64) {
|
|||||||
rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap();
|
rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuilder.finalize(HashMap::new()).unwrap();
|
rebuilder.finalize().unwrap();
|
||||||
|
drop(rebuilder);
|
||||||
|
|
||||||
// and test it.
|
// and test it.
|
||||||
let new_chain = BlockChain::new(Default::default(), &genesis, new_db);
|
let new_chain = BlockChain::new(Default::default(), &genesis, new_db);
|
||||||
@ -118,10 +125,8 @@ fn checks_flag() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let chunk = stream.out();
|
let chunk = stream.out();
|
||||||
let path = RandomTempPath::create_dir();
|
|
||||||
|
|
||||||
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db = Arc::new(kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
|
||||||
let db = Arc::new(Database::open(&db_cfg, path.as_str()).unwrap());
|
|
||||||
let engine = Arc::new(::engines::NullEngine::default());
|
let engine = Arc::new(::engines::NullEngine::default());
|
||||||
let chain = BlockChain::new(Default::default(), &genesis, db.clone());
|
let chain = BlockChain::new(Default::default(), &genesis, db.clone());
|
||||||
|
|
||||||
@ -134,7 +139,7 @@ fn checks_flag() {
|
|||||||
block_hash: H256::default(),
|
block_hash: H256::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut rebuilder = BlockRebuilder::new(chain, db.clone(), &manifest).unwrap();
|
let mut rebuilder = SNAPSHOT_MODE.rebuilder(chain, db.clone(), &manifest).unwrap();
|
||||||
|
|
||||||
match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) {
|
match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) {
|
||||||
Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {}
|
Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {}
|
||||||
|
@ -350,7 +350,7 @@ fn transaction_proof() {
|
|||||||
data: Vec::new(),
|
data: Vec::new(),
|
||||||
}.fake_sign(address);
|
}.fake_sign(address);
|
||||||
|
|
||||||
let proof = client.prove_transaction(transaction.clone(), BlockId::Latest).unwrap();
|
let proof = client.prove_transaction(transaction.clone(), BlockId::Latest).unwrap().1;
|
||||||
let backend = state::backend::ProofCheck::new(&proof);
|
let backend = state::backend::ProofCheck::new(&proof);
|
||||||
|
|
||||||
let mut factories = ::factory::Factories::default();
|
let mut factories = ::factory::Factories::default();
|
||||||
|
@ -34,4 +34,8 @@ impl Verifier for CanonVerifier {
|
|||||||
fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error> {
|
fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error> {
|
||||||
verification::verify_block_final(expected, got)
|
verification::verify_block_final(expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_block_external(&self, header: &Header, bytes: &[u8], engine: &Engine) -> Result<(), Error> {
|
||||||
|
engine.verify_block_external(header, Some(bytes))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,4 +34,8 @@ impl Verifier for NoopVerifier {
|
|||||||
fn verify_block_final(&self, _expected: &Header, _got: &Header) -> Result<(), Error> {
|
fn verify_block_final(&self, _expected: &Header, _got: &Header) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_block_external(&self, _header: &Header, _bytes: &[u8], _engine: &Engine) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,4 +27,6 @@ pub trait Verifier: Send + Sync {
|
|||||||
fn verify_block_family(&self, header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error>;
|
fn verify_block_family(&self, header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error>;
|
||||||
/// Do a final verification check for an enacted header vs its expected counterpart.
|
/// Do a final verification check for an enacted header vs its expected counterpart.
|
||||||
fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error>;
|
fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error>;
|
||||||
|
/// Verify a block, inspecing external state.
|
||||||
|
fn verify_block_external(&self, header: &Header, bytes: &[u8], engine: &Engine) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
@ -47,5 +47,4 @@ impl SnapshotService for TestSnapshotService {
|
|||||||
fn abort_restore(&self) { }
|
fn abort_restore(&self) { }
|
||||||
fn restore_state_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
fn restore_state_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
||||||
fn restore_block_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
fn restore_block_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
||||||
fn provide_canon_hashes(&self, _hashes: &[(u64, H256)]) { }
|
|
||||||
}
|
}
|
@ -24,7 +24,6 @@ use SyncConfig;
|
|||||||
pub struct TestSnapshotService {
|
pub struct TestSnapshotService {
|
||||||
manifest: Option<ManifestData>,
|
manifest: Option<ManifestData>,
|
||||||
chunks: HashMap<H256, Bytes>,
|
chunks: HashMap<H256, Bytes>,
|
||||||
canon_hashes: Mutex<HashMap<u64, H256>>,
|
|
||||||
|
|
||||||
restoration_manifest: Mutex<Option<ManifestData>>,
|
restoration_manifest: Mutex<Option<ManifestData>>,
|
||||||
state_restoration_chunks: Mutex<HashMap<H256, Bytes>>,
|
state_restoration_chunks: Mutex<HashMap<H256, Bytes>>,
|
||||||
@ -36,7 +35,6 @@ impl TestSnapshotService {
|
|||||||
TestSnapshotService {
|
TestSnapshotService {
|
||||||
manifest: None,
|
manifest: None,
|
||||||
chunks: HashMap::new(),
|
chunks: HashMap::new(),
|
||||||
canon_hashes: Mutex::new(HashMap::new()),
|
|
||||||
restoration_manifest: Mutex::new(None),
|
restoration_manifest: Mutex::new(None),
|
||||||
state_restoration_chunks: Mutex::new(HashMap::new()),
|
state_restoration_chunks: Mutex::new(HashMap::new()),
|
||||||
block_restoration_chunks: Mutex::new(HashMap::new()),
|
block_restoration_chunks: Mutex::new(HashMap::new()),
|
||||||
@ -61,7 +59,6 @@ impl TestSnapshotService {
|
|||||||
TestSnapshotService {
|
TestSnapshotService {
|
||||||
manifest: Some(manifest),
|
manifest: Some(manifest),
|
||||||
chunks: chunks,
|
chunks: chunks,
|
||||||
canon_hashes: Mutex::new(HashMap::new()),
|
|
||||||
restoration_manifest: Mutex::new(None),
|
restoration_manifest: Mutex::new(None),
|
||||||
state_restoration_chunks: Mutex::new(HashMap::new()),
|
state_restoration_chunks: Mutex::new(HashMap::new()),
|
||||||
block_restoration_chunks: Mutex::new(HashMap::new()),
|
block_restoration_chunks: Mutex::new(HashMap::new()),
|
||||||
@ -115,10 +112,6 @@ impl SnapshotService for TestSnapshotService {
|
|||||||
self.block_restoration_chunks.lock().insert(hash, chunk);
|
self.block_restoration_chunks.lock().insert(hash, chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_canon_hashes(&self, hashes: &[(u64, H256)]) {
|
|
||||||
self.canon_hashes.lock().extend(hashes.iter().cloned());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -16,7 +16,7 @@ time = "0.1.34"
|
|||||||
rocksdb = { git = "https://github.com/paritytech/rust-rocksdb" }
|
rocksdb = { git = "https://github.com/paritytech/rust-rocksdb" }
|
||||||
eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1" }
|
eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1" }
|
||||||
rust-crypto = "0.2.34"
|
rust-crypto = "0.2.34"
|
||||||
elastic-array = { git = "https://github.com/paritytech/elastic-array" }
|
elastic-array = "0.7.0"
|
||||||
rlp = { path = "rlp" }
|
rlp = { path = "rlp" }
|
||||||
heapsize = { version = "0.3", features = ["unstable"] }
|
heapsize = { version = "0.3", features = ["unstable"] }
|
||||||
itertools = "0.5"
|
itertools = "0.5"
|
||||||
|
@ -6,7 +6,7 @@ version = "0.1.0"
|
|||||||
authors = ["Parity Technologies <admin@parity.io>"]
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
elastic-array = { git = "https://github.com/paritytech/elastic-array" }
|
elastic-array = "0.7.0"
|
||||||
ethcore-bigint = { path = "../bigint" }
|
ethcore-bigint = { path = "../bigint" }
|
||||||
lazy_static = "0.2"
|
lazy_static = "0.2"
|
||||||
rustc-serialize = "0.3"
|
rustc-serialize = "0.3"
|
||||||
|
@ -125,8 +125,8 @@ pub fn new(backing: Arc<::kvdb::KeyValueDB>, algorithm: Algorithm, col: Option<u
|
|||||||
}
|
}
|
||||||
|
|
||||||
// all keys must be at least 12 bytes
|
// all keys must be at least 12 bytes
|
||||||
const DB_PREFIX_LEN : usize = 12;
|
const DB_PREFIX_LEN : usize = ::kvdb::PREFIX_LEN;
|
||||||
const LATEST_ERA_KEY : [u8; DB_PREFIX_LEN] = [ b'l', b'a', b's', b't', 0, 0, 0, 0, 0, 0, 0, 0 ];
|
const LATEST_ERA_KEY : [u8; ::kvdb::PREFIX_LEN] = [ b'l', b'a', b's', b't', 0, 0, 0, 0, 0, 0, 0, 0 ];
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -36,6 +36,9 @@ use std::fs::File;
|
|||||||
const DB_BACKGROUND_FLUSHES: i32 = 2;
|
const DB_BACKGROUND_FLUSHES: i32 = 2;
|
||||||
const DB_BACKGROUND_COMPACTIONS: i32 = 2;
|
const DB_BACKGROUND_COMPACTIONS: i32 = 2;
|
||||||
|
|
||||||
|
/// Required length of prefixes.
|
||||||
|
pub const PREFIX_LEN: usize = 12;
|
||||||
|
|
||||||
/// Write transaction. Batches a sequence of put/delete operations for efficiency.
|
/// Write transaction. Batches a sequence of put/delete operations for efficiency.
|
||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct DBTransaction {
|
pub struct DBTransaction {
|
||||||
@ -167,6 +170,10 @@ pub trait KeyValueDB: Sync + Send {
|
|||||||
/// Iterate over flushed data for a given column.
|
/// Iterate over flushed data for a given column.
|
||||||
fn iter<'a>(&'a self, col: Option<u32>) -> Box<Iterator<Item=(Box<[u8]>, Box<[u8]>)> + 'a>;
|
fn iter<'a>(&'a self, col: Option<u32>) -> Box<Iterator<Item=(Box<[u8]>, Box<[u8]>)> + 'a>;
|
||||||
|
|
||||||
|
/// Iterate over flushed data for a given column, starting from a given prefix.
|
||||||
|
fn iter_from_prefix<'a>(&'a self, col: Option<u32>, prefix: &'a [u8])
|
||||||
|
-> Box<Iterator<Item=(Box<[u8]>, Box<[u8]>)> + 'a>;
|
||||||
|
|
||||||
/// Attempt to replace this database with a new one located at the given path.
|
/// Attempt to replace this database with a new one located at the given path.
|
||||||
fn restore(&self, new_db: &str) -> Result<(), UtilError>;
|
fn restore(&self, new_db: &str) -> Result<(), UtilError>;
|
||||||
}
|
}
|
||||||
@ -247,7 +254,21 @@ impl KeyValueDB for InMemory {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (k.into_boxed_slice(), v.to_vec().into_boxed_slice()))
|
.map(|(k, v)| (k.into_boxed_slice(), v.to_vec().into_boxed_slice()))
|
||||||
),
|
),
|
||||||
None => Box::new(None.into_iter())
|
None => Box::new(None.into_iter()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_from_prefix<'a>(&'a self, col: Option<u32>, prefix: &'a [u8])
|
||||||
|
-> Box<Iterator<Item=(Box<[u8]>, Box<[u8]>)> + 'a>
|
||||||
|
{
|
||||||
|
match self.columns.read().get(&col) {
|
||||||
|
Some(map) => Box::new(
|
||||||
|
map.clone()
|
||||||
|
.into_iter()
|
||||||
|
.skip_while(move |&(ref k, _)| !k.starts_with(prefix))
|
||||||
|
.map(|(k, v)| (k.into_boxed_slice(), v.to_vec().into_boxed_slice()))
|
||||||
|
),
|
||||||
|
None => Box::new(None.into_iter()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,23 +712,17 @@ impl Database {
|
|||||||
/// Get value by partial key. Prefix size should match configured prefix size. Only searches flushed values.
|
/// Get value by partial key. Prefix size should match configured prefix size. Only searches flushed values.
|
||||||
// TODO: support prefix seek for unflushed data
|
// TODO: support prefix seek for unflushed data
|
||||||
pub fn get_by_prefix(&self, col: Option<u32>, prefix: &[u8]) -> Option<Box<[u8]>> {
|
pub fn get_by_prefix(&self, col: Option<u32>, prefix: &[u8]) -> Option<Box<[u8]>> {
|
||||||
match *self.db.read() {
|
self.iter_from_prefix(col, prefix).and_then(|mut iter| {
|
||||||
Some(DBAndColumns { ref db, ref cfs }) => {
|
|
||||||
let mut iter = col.map_or_else(|| db.iterator_opt(IteratorMode::From(prefix, Direction::Forward), &self.read_opts),
|
|
||||||
|c| db.iterator_cf_opt(cfs[c as usize], IteratorMode::From(prefix, Direction::Forward), &self.read_opts)
|
|
||||||
.expect("iterator params are valid; qed"));
|
|
||||||
match iter.next() {
|
match iter.next() {
|
||||||
// TODO: use prefix_same_as_start read option (not availabele in C API currently)
|
// TODO: use prefix_same_as_start read option (not availabele in C API currently)
|
||||||
Some((k, v)) => if k[0 .. prefix.len()] == prefix[..] { Some(v) } else { None },
|
Some((k, v)) => if k[0 .. prefix.len()] == prefix[..] { Some(v) } else { None },
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get database iterator for flushed data.
|
/// Get database iterator for flushed data.
|
||||||
pub fn iter(&self, col: Option<u32>) -> DatabaseIterator {
|
pub fn iter(&self, col: Option<u32>) -> Option<DatabaseIterator> {
|
||||||
//TODO: iterate over overlay
|
//TODO: iterate over overlay
|
||||||
match *self.db.read() {
|
match *self.db.read() {
|
||||||
Some(DBAndColumns { ref db, ref cfs }) => {
|
Some(DBAndColumns { ref db, ref cfs }) => {
|
||||||
@ -717,12 +732,28 @@ impl Database {
|
|||||||
.expect("iterator params are valid; qed")
|
.expect("iterator params are valid; qed")
|
||||||
);
|
);
|
||||||
|
|
||||||
DatabaseIterator {
|
Some(DatabaseIterator {
|
||||||
iter: iter,
|
iter: iter,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
None => panic!("Not supported yet") //TODO: return an empty iterator or change return type
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_from_prefix(&self, col: Option<u32>, prefix: &[u8]) -> Option<DatabaseIterator> {
|
||||||
|
match *self.db.read() {
|
||||||
|
Some(DBAndColumns { ref db, ref cfs }) => {
|
||||||
|
let iter = col.map_or_else(|| db.iterator_opt(IteratorMode::From(prefix, Direction::Forward), &self.read_opts),
|
||||||
|
|c| db.iterator_cf_opt(cfs[c as usize], IteratorMode::From(prefix, Direction::Forward), &self.read_opts)
|
||||||
|
.expect("iterator params are valid; qed"));
|
||||||
|
|
||||||
|
Some(DatabaseIterator {
|
||||||
|
iter: iter,
|
||||||
|
_marker: PhantomData,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -836,7 +867,14 @@ impl KeyValueDB for Database {
|
|||||||
|
|
||||||
fn iter<'a>(&'a self, col: Option<u32>) -> Box<Iterator<Item=(Box<[u8]>, Box<[u8]>)> + 'a> {
|
fn iter<'a>(&'a self, col: Option<u32>) -> Box<Iterator<Item=(Box<[u8]>, Box<[u8]>)> + 'a> {
|
||||||
let unboxed = Database::iter(self, col);
|
let unboxed = Database::iter(self, col);
|
||||||
Box::new(unboxed)
|
Box::new(unboxed.into_iter().flat_map(|inner| inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_from_prefix<'a>(&'a self, col: Option<u32>, prefix: &'a [u8])
|
||||||
|
-> Box<Iterator<Item=(Box<[u8]>, Box<[u8]>)> + 'a>
|
||||||
|
{
|
||||||
|
let unboxed = Database::iter_from_prefix(self, col, prefix);
|
||||||
|
Box::new(unboxed.into_iter().flat_map(|inner| inner))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore(&self, new_db: &str) -> Result<(), UtilError> {
|
fn restore(&self, new_db: &str) -> Result<(), UtilError> {
|
||||||
@ -872,7 +910,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(&*db.get(None, &key1).unwrap().unwrap(), b"cat");
|
assert_eq!(&*db.get(None, &key1).unwrap().unwrap(), b"cat");
|
||||||
|
|
||||||
let contents: Vec<_> = db.iter(None).collect();
|
let contents: Vec<_> = db.iter(None).into_iter().flat_map(|inner| inner).collect();
|
||||||
assert_eq!(contents.len(), 2);
|
assert_eq!(contents.len(), 2);
|
||||||
assert_eq!(&*contents[0].0, &*key1);
|
assert_eq!(&*contents[0].0, &*key1);
|
||||||
assert_eq!(&*contents[0].1, b"cat");
|
assert_eq!(&*contents[0].1, b"cat");
|
||||||
|
@ -157,7 +157,12 @@ impl<T: SimpleMigration> Migration for T {
|
|||||||
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
|
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
|
||||||
let mut batch = Batch::new(config, col);
|
let mut batch = Batch::new(config, col);
|
||||||
|
|
||||||
for (key, value) in source.iter(col) {
|
let iter = match source.iter(col) {
|
||||||
|
Some(iter) => iter,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (key, value) in iter {
|
||||||
if let Some((key, value)) = self.simple_migrate(key.to_vec(), value.to_vec()) {
|
if let Some((key, value)) = self.simple_migrate(key.to_vec(), value.to_vec()) {
|
||||||
batch.insert(key, value, dest)?;
|
batch.insert(key, value, dest)?;
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ impl Migration for AddsColumn {
|
|||||||
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
|
fn migrate(&mut self, source: Arc<Database>, config: &Config, dest: &mut Database, col: Option<u32>) -> Result<(), Error> {
|
||||||
let mut batch = Batch::new(config, col);
|
let mut batch = Batch::new(config, col);
|
||||||
|
|
||||||
for (key, value) in source.iter(col) {
|
for (key, value) in source.iter(col).into_iter().flat_map(|inner| inner) {
|
||||||
batch.insert(key.to_vec(), value.to_vec(), dest)?;
|
batch.insert(key.to_vec(), value.to_vec(), dest)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user