Merge branch 'master' into ui-2

This commit is contained in:
Jaco Greeff 2017-06-28 14:27:25 +02:00
commit 8798ed4499
103 changed files with 2950 additions and 1121 deletions

View File

@ -25,8 +25,8 @@ linux-stable:
- rustup default stable
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
- cargo build -j $(nproc) --release -p evmbin
- cargo build -j $(nproc) --release -p ethstore
- cargo build -j $(nproc) --release -p ethkey
- cargo build -j $(nproc) --release -p ethstore-cli
- cargo build -j $(nproc) --release -p ethkey-cli
- strip target/release/parity
- strip target/release/parity-evm
- strip target/release/ethstore
@ -105,8 +105,8 @@ linux-stable-debian:
script:
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
- cargo build -j $(nproc) --release -p evmbin
- cargo build -j $(nproc) --release -p ethstore
- cargo build -j $(nproc) --release -p ethkey
- cargo build -j $(nproc) --release -p ethstore-cli
- cargo build -j $(nproc) --release -p ethkey-cli
- strip target/release/parity
- strip target/release/parity-evm
- strip target/release/ethstore
@ -191,8 +191,17 @@ linux-centos:
- export CC="gcc"
- export PLATFORM=x86_64-unknown-centos-gnu
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
- cargo build -j $(nproc) --release -p evmbin
- cargo build -j $(nproc) --release -p ethstore-cli
- cargo build -j $(nproc) --release -p ethkey-cli
- strip target/release/parity
- strip target/release/parity-evm
- strip target/release/ethstore
- strip target/release/ethkey
- md5sum target/release/parity > parity.md5
- md5sum target/release/parity-evm > parity-evm.md5
- md5sum target/release/ethstore > ethstore.md5
- md5sum target/release/ethkey > ethkey.md5
- export SHA3=$(target/release/parity tools hash target/release/parity)
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
@ -200,6 +209,12 @@ linux-centos:
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity-evm --body target/release/parity-evm
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity-evm.md5 --body parity-evm.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethstore --body target/release/ethstore
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethstore.md5 --body ethstore.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethkey --body target/release/ethkey
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethkey.md5 --body ethkey.md5
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
@ -222,12 +237,22 @@ linux-i686:
- export HOST_CXX=g++
- export COMMIT=$(git rev-parse HEAD)
- export PLATFORM=i686-unknown-linux-gnu
- cargo build -j $(nproc) --target i686-unknown-linux-gnu --features final --release $CARGOFLAGS
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
- strip target/$PLATFORM/release/parity
- strip target/$PLATFORM/release/parity-evm
- strip target/$PLATFORM/release/ethstore
- strip target/$PLATFORM/release/ethkey
- strip target/$PLATFORM/release/parity
- md5sum target/$PLATFORM/release/parity > parity.md5
- export SHA3=$(target/$PLATFORM/release/parity tools hash target/$PLATFORM/release/parity)
- sh scripts/deb-build.sh i386
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_i386.deb"
- md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5"
@ -269,11 +294,23 @@ linux-armv7:
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
- md5sum target/$PLATFORM/release/parity > parity.md5
- export SHA3=$(target/$PLATFORM/release/parity tools hash target/$PLATFORM/release/parity)
- sh scripts/deb-build.sh i386
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity-evm
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethstore
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethkey
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- sh scripts/deb-build.sh armhf
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
@ -315,11 +352,20 @@ linux-arm:
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity-evm
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethstore
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethkey
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- sh scripts/deb-build.sh armhf
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
@ -341,45 +387,6 @@ linux-arm:
- target/arm-unknown-linux-gnueabihf/release/parity
name: "arm-unknown-linux-gnueabihf_parity"
allow_failure: true
linux-armv6:
stage: build
image: ethcore/rust-armv6:latest
only:
- beta
# - tags
# - stable
# - triggers
script:
- export CC=arm-linux-gnueabi-gcc
- export CXX=arm-linux-gnueabi-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- export PLATFORM=arm-unknown-linux-gnueabi
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.$PLATFORM]" >> .cargo/config
- echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- arm-linux-gnueabi-strip target/$PLATFORM/release/parity
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-arm
artifacts:
paths:
- target/arm-unknown-linux-gnueabi/release/parity
name: "arm-unknown-linux-gnueabi_parity"
allow_failure: true
linux-aarch64:
stage: build
image: parity/rust-arm64:gitlab-ci
@ -400,11 +407,20 @@ linux-aarch64:
- echo "linker= \"aarch64-linux-gnu-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
- aarch64-linux-gnu-strip target/$PLATFORM/release/parity
- aarch64-linux-gnu-strip target/$PLATFORM/release/parity-evm
- aarch64-linux-gnu-strip target/$PLATFORM/release/ethstore
- aarch64-linux-gnu-strip target/$PLATFORM/release/ethkey
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- sh scripts/deb-build.sh arm64
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_arm64.deb"
- md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5"
@ -436,8 +452,11 @@ darwin:
export COMMIT=$(git rev-parse HEAD)
export PLATFORM=x86_64-apple-darwin
rustup default stable
cargo clean
cargo build -j 8 --features final --release #$CARGOFLAGS
cargo build -j 8 --features final --release -p ethstore #$CARGOFLAGS
cargo build -j 8 --release -p ethstore-cli #$CARGOFLAGS
cargo build -j 8 --release -p ethkey-cli #$CARGOFLAGS
cargo build -j 8 --release -p evmbin #$CARGOFLAGS
rm -rf parity.md5
md5sum target/release/parity > parity.md5
export SHA3=$(target/release/parity tools hash target/release/parity)
@ -482,7 +501,11 @@ windows:
- set RUST_BACKTRACE=1
- set RUSTFLAGS=%RUSTFLAGS%
- rustup default stable-x86_64-pc-windows-msvc
- cargo clean
- cargo build --features final --release #%CARGOFLAGS%
- cargo build --release -p ethstore-cli #%CARGOFLAGS%
- cargo build --release -p ethkey-cli #%CARGOFLAGS%
- cargo build --release -p evmbin #%CARGOFLAGS%
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
- target\release\parity.exe tools hash target\release\parity.exe > parity.sha3
- set /P SHA3=<parity.sha3

2
Cargo.lock generated
View File

@ -1833,7 +1833,7 @@ dependencies = [
[[package]]
name = "parity-ui-precompiled"
version = "1.4.0"
source = "git+https://github.com/paritytech/js-precompiled.git#6bc749402a9dd05ce41f97a4cc4f1ee48512e6df"
source = "git+https://github.com/paritytech/js-precompiled.git#7dc30d69a4e15b1fc22d3d25939a177f0979df07"
dependencies = [
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@ -95,7 +95,7 @@ secretstore = ["ethcore-secretstore"]
path = "parity/main.rs"
name = "parity"
[profile.debug]
[profile.dev]
panic = "abort"
[profile.release]

View File

@ -89,6 +89,18 @@ cargo install --git https://github.com/paritytech/parity.git parity
----
## Install from the snap store
In any of the [supported Linux distros](https://snapcraft.io/docs/core/install):
```bash
sudo snap install parity --edge
```
(Note that this is an experimental and unstable release, at the moment)
----
## Build from source
```bash

View File

@ -38,15 +38,53 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
// Embedding header:
if let Some(embeddable_on) = embeddable_on {
headers.set_raw(
"X-Frame-Options",
vec![format!("ALLOW-FROM http://{}", address(&embeddable_on)).into_bytes()]
);
if let Some(ref embeddable_on) = embeddable_on {
headers.set_raw("X-Frame-Options", vec![
format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()
]);
} else {
// TODO [ToDr] Should we be more strict here (DENY?)?
headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
}
// Content Security Policy headers
headers.set_raw("Content-Security-Policy", vec![
// Allow connecting to WS servers and HTTP(S) servers.
// We could be more restrictive and allow only RPC server URL.
b"connect-src http: https: ws: wss:;".to_vec(),
// Allow framing any content from HTTP(S).
// Again we could only allow embedding from RPC server URL.
// (deprecated)
b"frame-src 'self' http: https:;".to_vec(),
// Allow framing and web workers from HTTP(S).
b"child-src 'self' http: https:;".to_vec(),
// We allow data: blob: and HTTP(s) images.
// We could get rid of wildcarding HTTP and only allow RPC server URL.
// (http require for local dapps icons)
b"img-src 'self' 'unsafe-inline' data: blob: http: https:;".to_vec(),
// Allow style from data: blob: and HTTPS.
b"style-src 'self' 'unsafe-inline' data: blob: https:;".to_vec(),
// Allow fonts from data: and HTTPS.
b"font-src 'self' data: https:;".to_vec(),
// Allow inline scripts and scripts eval (webpack/jsconsole)
b"script-src 'self' 'unsafe-inline' 'unsafe-eval';".to_vec(),
// Restrict everything else to the same origin.
b"default-src 'self';".to_vec(),
// Run in sandbox mode (although it's not fully safe since we allow same-origin and script)
b"sandbox allow-same-origin allow-forms allow-modals allow-popups allow-presentation allow-scripts;".to_vec(),
// Disallow subitting forms from any dapps
b"form-action 'none';".to_vec(),
// Never allow mixed content
b"block-all-mixed-content;".to_vec(),
// Specify if the site can be embedded.
match embeddable_on {
Some((ref host, ref port)) if host == "127.0.0.1" => {
format!("frame-ancestors {} {};", address(&(host.to_owned(), *port)), address(&("localhost".to_owned(), *port)))
},
Some(ref embed) => format!("frame-ancestors {};", address(embed)),
None => format!("frame-ancestors 'self';"),
}.into_bytes(),
]);
}

View File

@ -121,4 +121,8 @@ pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
headers.iter().find(|header| header.as_str() == "X-Content-Type-Options: nosniff").is_some(),
"X-Content-Type-Options missing: {:?}", headers
);
assert!(
headers.iter().find(|header| header.starts_with("Content-Security-Policy: ")).is_some(),
"Content-Security-Policy missing: {:?}", headers
)
}

View File

@ -28,7 +28,7 @@ const SECRETSTORE_ACL_STORAGE_ABI: &'static str = include_str!("res/secretstore_
const VALIDATOR_SET_ABI: &'static str = include_str!("res/validator_set.json");
const VALIDATOR_REPORT_ABI: &'static str = include_str!("res/validator_report.json");
const TEST_VALIDATOR_SET_ABI: &'static str = r#"[{"constant":true,"inputs":[],"name":"transitionNonce","outputs":[{"name":"n","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newValidators","type":"address[]"}],"name":"setValidators","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"vals","type":"address[]"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"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 TEST_VALIDATOR_SET_ABI: &'static str = include_str!("res/test_validator_set.json");
fn build_file(name: &str, abi: &str, filename: &str) {
let code = ::native_contract_generator::generate_module(name, abi).unwrap();

View File

@ -108,7 +108,7 @@ fn generate_functions(contract: &Contract) -> Result<String, Error> {
/// Outputs: {abi_outputs:?}
pub fn {snake_name}<F, U>(&self, call: F, {params}) -> BoxFuture<{output_type}, String>
where
F: Fn(util::Address, Vec<u8>) -> U,
F: FnOnce(util::Address, Vec<u8>) -> U,
U: IntoFuture<Item=Vec<u8>, Error=String>,
U::Future: Send + 'static
{{

View File

@ -0,0 +1,8 @@
[
{"constant":false,"inputs":[{"name":"_validators","type":"address[]"}],"name":"setValidators","outputs":[],"payable":false,"type":"function"},
{"constant":false,"inputs":[{"name":"","type":"address"},{"name":"","type":"bytes"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"},
{"constant":false,"inputs":[],"name":"finalizeChange","outputs":[],"payable":false,"type":"function"},
{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"_validators","type":"address[]"}],"payable":false,"type":"function"},
{"constant":false,"inputs":[{"name":"","type":"address"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"},
{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"InitiateChange","type":"event"}
]

View File

@ -1,5 +1,5 @@
[
{"constant":true,"inputs":[],"name":"transitionNonce","outputs":[{"name":"nonce","type":"uint256"}],"payable":false,"type":"function"},
{"constant":false,"inputs":[],"name":"finalizeChange","outputs":[],"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"}
{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"InitiateChange","type":"event"}
]

View File

@ -11,7 +11,8 @@
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e",
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"
]
}
},
"immediateTransitions": true
}
}
},

View File

@ -8,7 +8,8 @@
"startStep": 2,
"validators": {
"contract": "0x0000000000000000000000000000000000000005"
}
},
"immediateTransitions": true
}
}
},

View File

@ -260,8 +260,10 @@ impl<'x> OpenBlock<'x> {
author: Address,
gas_range_target: (U256, U256),
extra_data: Bytes,
is_epoch_begin: bool,
) -> Result<Self, Error> {
let state = State::from_existing(db, parent.state_root().clone(), engine.account_start_nonce(), factories)?;
let number = parent.number() + 1;
let state = State::from_existing(db, parent.state_root().clone(), engine.account_start_nonce(number), factories)?;
let mut r = OpenBlock {
block: ExecutedBlock::new(state, tracing),
engine: engine,
@ -269,7 +271,7 @@ impl<'x> OpenBlock<'x> {
};
r.block.header.set_parent_hash(parent.hash());
r.block.header.set_number(parent.number() + 1);
r.block.header.set_number(number);
r.block.header.set_author(author);
r.block.header.set_timestamp_now(parent.timestamp());
r.block.header.set_extra_data(extra_data);
@ -278,7 +280,8 @@ impl<'x> OpenBlock<'x> {
let gas_floor_target = cmp::max(gas_range_target.0, engine.params().min_gas_limit);
let gas_ceil_target = cmp::max(gas_range_target.1, gas_floor_target);
engine.populate_from_parent(&mut r.block.header, parent, gas_floor_target, gas_ceil_target);
engine.on_new_block(&mut r.block, last_hashes)?;
engine.on_new_block(&mut r.block, last_hashes, is_epoch_begin)?;
Ok(r)
}
@ -429,7 +432,7 @@ impl<'x> OpenBlock<'x> {
#[cfg(test)]
/// Return mutable block reference. To be used in tests only.
pub fn block_mut (&mut self) -> &mut ExecutedBlock { &mut self.block }
pub fn block_mut(&mut self) -> &mut ExecutedBlock { &mut self.block }
}
impl<'x> IsBlock for OpenBlock<'x> {
@ -553,16 +556,29 @@ pub fn enact(
parent: &Header,
last_hashes: Arc<LastHashes>,
factories: Factories,
is_epoch_begin: bool,
) -> Result<LockedBlock, Error> {
{
if ::log::max_log_level() >= ::log::LogLevel::Trace {
let s = State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(), factories.clone())?;
let s = State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(parent.number() + 1), factories.clone())?;
trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n",
header.number(), s.root(), header.author(), s.balance(&header.author())?);
}
}
let mut b = OpenBlock::new(engine, factories, tracing, db, parent, last_hashes, Address::new(), (3141562.into(), 31415620.into()), vec![])?;
let mut b = OpenBlock::new(
engine,
factories,
tracing,
db,
parent,
last_hashes,
Address::new(),
(3141562.into(), 31415620.into()),
vec![],
is_epoch_begin,
)?;
b.set_difficulty(*header.difficulty());
b.set_gas_limit(*header.gas_limit());
b.set_timestamp(header.timestamp());
@ -617,9 +633,22 @@ pub fn enact_verified(
parent: &Header,
last_hashes: Arc<LastHashes>,
factories: Factories,
is_epoch_begin: bool,
) -> Result<LockedBlock, Error> {
let view = BlockView::new(&block.bytes);
enact(&block.header, &block.transactions, &view.uncles(), engine, tracing, db, parent, last_hashes, factories)
enact(
&block.header,
&block.transactions,
&view.uncles(),
engine,
tracing,
db,
parent,
last_hashes,
factories,
is_epoch_begin,
)
}
#[cfg(test)]
@ -652,7 +681,7 @@ mod tests {
let header = block.header();
let transactions: Result<Vec<_>, Error> = block.transactions().into_iter().map(SignedTransaction::new).collect();
let transactions = transactions?;
enact(&header, &transactions, &block.uncles(), engine, tracing, db, parent, last_hashes, factories)
enact(&header, &transactions, &block.uncles(), engine, tracing, db, parent, last_hashes, factories, false)
}
/// Enact the block given by `block_bytes` using `engine` on the database `db` with given `parent` block header. Seal the block aferwards
@ -677,7 +706,7 @@ mod tests {
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(&*spec.engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = OpenBlock::new(&*spec.engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = b.close_and_lock();
let _ = b.seal(&*spec.engine, vec![]);
}
@ -691,7 +720,7 @@ mod tests {
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap()
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap()
.close_and_lock().seal(engine, vec![]).unwrap();
let orig_bytes = b.rlp_bytes();
let orig_db = b.drain();
@ -715,7 +744,7 @@ mod tests {
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let mut open_block = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
let mut open_block = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let mut uncle1_header = Header::new();
uncle1_header.set_extra_data(b"uncle1".to_vec());
let mut uncle2_header = Header::new();

View File

@ -35,6 +35,7 @@ use blockchain::{CacheSize, ImportRoute, Config};
use db::{self, Writable, Readable, CacheUpdatePolicy};
use cache_manager::CacheManager;
use encoded;
use engines::epoch::{Transition as EpochTransition, PendingTransition as PendingEpochTransition};
const LOG_BLOOMS_LEVELS: usize = 3;
const LOG_BLOOMS_ELEMENTS_PER_INDEX: usize = 16;
@ -145,6 +146,10 @@ pub trait BlockProvider {
where F: Fn(&LogEntry) -> bool, Self: Sized;
}
macro_rules! otry {
($e:expr) => { match $e { Some(x) => x, None => return None } }
}
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
enum CacheId {
BlockHeader(H256),
@ -508,7 +513,7 @@ impl BlockChain {
number: header.number(),
total_difficulty: header.difficulty(),
parent: header.parent_hash(),
children: vec![]
children: vec![],
};
let mut batch = DBTransaction::new();
@ -698,10 +703,6 @@ impl BlockChain {
/// If the tree route verges into pruned or unknown blocks,
/// `None` is returned.
pub fn tree_route(&self, from: H256, to: H256) -> Option<TreeRoute> {
macro_rules! otry {
($e:expr) => { match $e { Some(x) => x, None => return None } }
}
let mut from_branch = vec![];
let mut to_branch = vec![];
@ -878,16 +879,60 @@ impl BlockChain {
}
}
/// Get a specific epoch transition by epoch number and provided block hash.
pub fn epoch_transition(&self, epoch_num: u64, block_hash: H256) -> Option<EpochTransition> {
trace!(target: "blockchain", "Loading epoch {} transition at block {}",
epoch_num, block_hash);
/// Get a specific epoch transition by block number and provided block hash.
pub fn epoch_transition(&self, block_num: u64, block_hash: H256) -> Option<EpochTransition> {
trace!(target: "blockchain", "Loading epoch transition at block {}, {}",
block_num, block_hash);
self.db.read(db::COL_EXTRA, &epoch_num).and_then(|transitions: EpochTransitions| {
self.db.read(db::COL_EXTRA, &block_num).and_then(|transitions: EpochTransitions| {
transitions.candidates.into_iter().find(|c| c.block_hash == block_hash)
})
}
/// Get the transition to the epoch the given parent hash is part of
/// or transitions to.
/// This will give the epoch that any children of this parent belong to.
///
/// The block corresponding the the parent hash must be stored already.
pub fn epoch_transition_for(&self, parent_hash: H256) -> Option<EpochTransition> {
// slow path: loop back block by block
for hash in otry!(self.ancestry_iter(parent_hash)) {
let details = otry!(self.block_details(&hash));
// look for transition in database.
if let Some(transition) = self.epoch_transition(details.number, hash) {
return Some(transition)
}
// canonical hash -> fast breakout:
// get the last epoch transition up to this block.
//
// if `block_hash` is canonical it will only return transitions up to
// the parent.
if otry!(self.block_hash(details.number)) == hash {
return self.epoch_transitions()
.map(|(_, t)| t)
.take_while(|t| t.block_number <= details.number)
.last()
}
}
// should never happen as the loop will encounter genesis before concluding.
None
}
/// Write a pending epoch transition by block hash.
pub fn insert_pending_transition(&self, batch: &mut DBTransaction, hash: H256, t: PendingEpochTransition) {
batch.write(db::COL_EXTRA, &hash, &t);
}
/// Get a pending epoch transition by block hash.
// TODO: implement removal safely: this can only be done upon finality of a block
// that _uses_ the pending transition.
pub fn get_pending_transition(&self, hash: H256) -> Option<PendingEpochTransition> {
self.db.read(db::COL_EXTRA, &hash)
}
/// Add a child to a given block. Assumes that the block hash is in
/// the chain and the child's parent is this block.
///
@ -1165,12 +1210,12 @@ impl BlockChain {
let mut parent_details = self.block_details(&parent_hash).unwrap_or_else(|| panic!("Invalid parent hash: {:?}", parent_hash));
parent_details.children.push(info.hash.clone());
// create current block details
// create current block details.
let details = BlockDetails {
number: header.number(),
total_difficulty: info.total_difficulty,
parent: parent_hash.clone(),
children: vec![]
children: vec![],
};
// write to batch
@ -2201,7 +2246,7 @@ mod tests {
#[test]
fn epoch_transitions_iter() {
use blockchain::extras::EpochTransition;
use ::engines::EpochTransition;
let mut canon_chain = ChainGenerator::default();
let mut finalizer = BlockFinalizer::default();
@ -2223,7 +2268,6 @@ mod tests {
block_hash: hash,
block_number: i + 1,
proof: vec![],
state_proof: vec![],
});
bc.commit();
}
@ -2236,7 +2280,6 @@ mod tests {
block_hash: hash,
block_number: 1,
proof: vec![],
state_proof: vec![]
});
db.write(batch).unwrap();
@ -2252,4 +2295,85 @@ mod tests {
assert_eq!(bc.best_block_number(), 5);
assert_eq!(bc.epoch_transitions().map(|(i, _)| i).collect::<Vec<_>>(), vec![0, 1, 2, 3, 4]);
}
#[test]
fn epoch_transition_for() {
use ::engines::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 mut batch = db.transaction();
bc.insert_epoch_transition(&mut batch, 0, EpochTransition {
block_hash: bc.genesis_hash(),
block_number: 0,
proof: vec![],
});
db.write(batch).unwrap();
// set up a chain where we have a canonical chain of 10 blocks
// and a non-canonical fork of 8 from genesis.
let fork_hash = {
let mut fork_chain = canon_chain.fork(1);
let mut fork_finalizer = finalizer.fork();
for _ in 0..7 {
let mut batch = db.transaction();
let fork_block = fork_chain.generate(&mut fork_finalizer).unwrap();
bc.insert_block(&mut batch, &fork_block, vec![]);
bc.commit();
db.write(batch).unwrap();
}
assert_eq!(bc.best_block_number(), 7);
bc.chain_info().best_block_hash
};
for _ in 0..10 {
let mut batch = db.transaction();
let canon_block = canon_chain.generate(&mut finalizer).unwrap();
bc.insert_block(&mut batch, &canon_block, vec![]);
bc.commit();
db.write(batch).unwrap();
}
assert_eq!(bc.best_block_number(), 10);
let mut batch = db.transaction();
bc.insert_epoch_transition(&mut batch, 4, EpochTransition {
block_hash: bc.block_hash(4).unwrap(),
block_number: 4,
proof: vec![],
});
db.write(batch).unwrap();
// blocks where the parent is one of the first 4 will be part of genesis epoch.
for i in 0..4 {
let hash = bc.block_hash(i).unwrap();
assert_eq!(bc.epoch_transition_for(hash).unwrap().block_number, 0);
}
// blocks where the parent is the transition at 4 or after will be
// part of that epoch.
for i in 4..11 {
let hash = bc.block_hash(i).unwrap();
assert_eq!(bc.epoch_transition_for(hash).unwrap().block_number, 4);
}
let fork_hashes = bc.ancestry_iter(fork_hash).unwrap().collect::<Vec<_>>();
assert_eq!(fork_hashes.len(), 8);
// non-canonical fork blocks should all have genesis transition
for fork_hash in fork_hashes {
assert_eq!(bc.epoch_transition_for(fork_hash).unwrap().block_number, 0);
}
}
}

View File

@ -17,13 +17,16 @@
//! Blockchain DB extras.
use bloomchain;
use util::*;
use util::kvdb::PREFIX_LEN as DB_PREFIX_LEN;
use rlp::*;
use blooms::{GroupPosition, BloomGroup};
use db::Key;
use engines::epoch::{Transition as EpochTransition};
use header::BlockNumber;
use receipt::Receipt;
use db::Key;
use blooms::{GroupPosition, BloomGroup};
use rlp::*;
use util::*;
use util::kvdb::PREFIX_LEN as DB_PREFIX_LEN;
/// Represents index of extra data in database
#[derive(Copy, Debug, Hash, Eq, PartialEq, Clone)]
@ -40,6 +43,8 @@ pub enum ExtrasIndex {
BlockReceipts = 4,
/// Epoch transition data index.
EpochTransitions = 5,
/// Pending epoch transition data index.
PendingEpochTransition = 6,
}
fn with_index(hash: &H256, i: ExtrasIndex) -> H264 {
@ -137,6 +142,14 @@ impl Key<BlockReceipts> for H256 {
}
}
impl Key<::engines::epoch::PendingTransition> for H256 {
type Target = H264;
fn key(&self) -> H264 {
with_index(self, ExtrasIndex::PendingEpochTransition)
}
}
/// length of epoch keys.
pub const EPOCH_KEY_LEN: usize = DB_PREFIX_LEN + 16;
@ -296,41 +309,6 @@ impl Decodable for EpochTransitions {
}
}
#[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)]
mod tests {
use rlp::*;

View File

@ -31,6 +31,5 @@ pub mod generator;
pub use self::blockchain::{BlockProvider, BlockChain};
pub use self::cache::CacheSize;
pub use self::config::Config;
pub use self::extras::EpochTransition;
pub use types::tree_route::TreeRoute;
pub use self::import_route::ImportRoute;

View File

@ -18,8 +18,8 @@
use std::sync::Arc;
use engines::{Engine, EpochVerifier, EpochChange};
use error::Error;
use blockchain::BlockChain;
use engines::{Engine, EpochVerifier};
use header::Header;
use rand::Rng;
@ -46,21 +46,21 @@ impl AncientVerifier {
/// Verify the next block header, randomly choosing whether to do heavy or light
/// verification. If the block is the end of an epoch, updates the epoch verifier.
pub fn verify<R: Rng, F: Fn(u64) -> Result<Box<EpochVerifier>, Error>>(
pub fn verify<R: Rng>(
&self,
rng: &mut R,
header: &Header,
block: &[u8],
receipts: &[::receipt::Receipt],
load_verifier: F,
chain: &BlockChain,
) -> Result<(), ::error::Error> {
match rng.gen::<f32>() <= HEAVY_VERIFY_RATE {
true => self.cur_verifier.read().verify_heavy(header)?,
false => self.cur_verifier.read().verify_light(header)?,
}
if let EpochChange::Yes(num) = self.engine.is_epoch_end(header, Some(block), Some(receipts)) {
*self.cur_verifier.write() = load_verifier(num)?;
// ancient import will only use transitions obtained from the snapshot.
if let Some(transition) = chain.epoch_transition(header.number(), header.hash()) {
let v = self.engine.epoch_verifier(&header, &transition.proof).known_confirmed()?;
*self.cur_verifier.write() = v;
}
Ok(())

View File

@ -32,7 +32,7 @@ use util::kvdb::*;
// other
use basic_types::Seal;
use block::*;
use blockchain::{BlockChain, BlockProvider, EpochTransition, TreeRoute, ImportRoute};
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
use blockchain::extras::TransactionAddress;
use client::ancient_import::AncientVerifier;
use client::Error as ClientError;
@ -42,7 +42,7 @@ use client::{
ChainNotify, PruningInfo, ProvingBlockChainClient,
};
use encoded;
use engines::Engine;
use engines::{Engine, EpochTransition};
use env_info::EnvInfo;
use env_info::LastHashes;
use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError};
@ -256,9 +256,31 @@ impl Client {
{
let chain = client.chain.read();
let gh = spec.genesis_header();
if chain.epoch_transition(0, spec.genesis_header().hash()).is_none() {
if chain.epoch_transition(0, gh.hash()).is_none() {
trace!(target: "client", "No genesis transition found.");
client.generate_epoch_proof(&gh, 0, &*chain);
let proof = client.with_proving_caller(
BlockId::Number(0),
|call| client.engine.genesis_epoch_data(&gh, call)
);
let proof = match proof {
Ok(proof) => proof,
Err(e) => {
warn!(target: "client", "Error generating genesis epoch data: {}. Snapshots generated may not be complete.", e);
Vec::new()
}
};
debug!(target: "client", "Obtained genesis transition proof: {:?}", proof);
let mut batch = DBTransaction::new();
chain.insert_epoch_transition(&mut batch, 0, EpochTransition {
block_hash: gh.hash(),
block_number: 0,
proof: proof,
});
client.db.read().write_buffered(batch);
}
}
@ -405,7 +427,16 @@ impl Client {
let last_hashes = self.build_last_hashes(header.parent_hash().clone());
let db = self.state_db.lock().boxed_clone_canon(header.parent_hash());
let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone());
let is_epoch_begin = chain.epoch_transition(parent.number(), *header.parent_hash()).is_some();
let enact_result = enact_verified(block,
engine,
self.tracedb.read().tracing_enabled(),
db,
&parent,
last_hashes,
self.factories.clone(),
is_epoch_begin,
);
let mut locked_block = enact_result.map_err(|e| {
warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
})?;
@ -555,18 +586,9 @@ impl Client {
{
// closure for verifying a block.
let verify_with = |verifier: &AncientVerifier| -> Result<(), ::error::Error> {
// verify the block, passing a closure used to load an epoch verifier
// by number.
verifier.verify(
&mut *self.rng.lock(),
&header,
&block_bytes,
&receipts,
|epoch_num| chain.epoch_transition(epoch_num, hash)
.ok_or(BlockError::UnknownEpochTransition(epoch_num))
.map_err(Into::into)
.and_then(|t| self.engine.epoch_verifier(&header, &t.proof))
)
// verify the block, passing the chain for updating the epoch
// verifier.
verifier.verify(&mut *self.rng.lock(), &header, &chain)
};
// initialize the ancient block verifier if we don't have one already.
@ -583,7 +605,8 @@ impl Client {
.map(|(_, t)| t.proof)
.expect("At least one epoch entry (genesis) always stored; qed");
let current_verifier = self.engine.epoch_verifier(&header, &current_epoch_data)?;
let current_verifier = self.engine.epoch_verifier(&header, &current_epoch_data)
.known_confirmed()?;
let current_verifier = AncientVerifier::new(self.engine.clone(), current_verifier);
verify_with(&current_verifier)?;
@ -606,6 +629,7 @@ impl Client {
fn commit_block<B>(&self, block: B, hash: &H256, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain {
let number = block.header().number();
let parent = block.header().parent_hash().clone();
let header = block.header().clone(); // TODO: optimize and avoid copy.
let chain = self.chain.read();
// Commit results
@ -619,28 +643,13 @@ impl Client {
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
// already-imported block of the same number.
// TODO: Prove it with a test.
let mut state = block.drain();
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.clone());
self.tracedb.read().import(&mut batch, TraceImportRequest {
traces: traces.into(),
@ -655,65 +664,102 @@ impl Client {
// Final commit to the DB
self.db.read().write_buffered(batch);
chain.commit();
// check for epoch end. do this after writing first batch so we can prove
// transactions on the block's state.
// TODO: work these changes into the existing DBTransaction.
self.check_epoch_end_signal(&header, block_data, &receipts, &chain);
self.check_epoch_end(&header, &chain);
self.update_last_hashes(&parent, hash);
if let Err(e) = self.prune_ancient(state, &chain) {
warn!("Failed to prune ancient state data: {}", e);
}
if let Some((header, epoch)) = entering_new_epoch {
self.generate_epoch_proof(&header, epoch, &chain);
}
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;
// check for epoch end signal and write pending transition if it occurs.
// state for the given block must be available.
fn check_epoch_end_signal(&self, header: &Header, block: &[u8], receipts: &[Receipt], chain: &BlockChain) {
use engines::EpochChange;
let mut batch = DBTransaction::new();
let hash = header.hash();
debug!(target: "client", "Generating validation proof for epoch {} at block {}",
epoch_number, hash);
match self.engine.signals_epoch_end(header, Some(block), Some(&receipts)) {
EpochChange::Yes(proof) => {
use engines::epoch::PendingTransition;
use engines::Proof;
// 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."))?;
let proof = match proof {
Proof::Known(proof) => proof,
Proof::WithState(with_state) =>
match self.with_proving_caller(BlockId::Hash(hash), move |c| with_state(c)) {
Ok(proof) => proof,
Err(e) => {
warn!(target: "client", "Failed to generate transition proof for block {}: {}", hash, e);
warn!(target: "client", "Snapshots produced by this client may be incomplete");
read_values.borrow_mut().extend(items);
Ok(result)
};
Vec::new()
}
},
};
self.engine.epoch_proof(&header, &call)
};
debug!(target: "client", "Block {} signals epoch end.", hash);
// 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(),
});
// write pending transition to DB.
let mut batch = DBTransaction::new();
let pending = PendingTransition { proof: proof };
chain.insert_pending_transition(&mut batch, hash, pending);
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.");
},
EpochChange::No => {},
EpochChange::Unsure(_) => {
warn!(target: "client", "Detected invalid engine implementation.");
warn!(target: "client", "Engine claims to require more block data, but everything provided.");
}
}
}
// check for ending of epoch and write transition if it occurs.
fn check_epoch_end<'a>(&self, header: &'a Header, chain: &BlockChain) {
let is_epoch_end = self.engine.is_epoch_end(
header,
&(|hash| chain.block_header(&hash)),
&(|hash| chain.get_pending_transition(hash)), // TODO: limit to current epoch.
);
if let Some(proof) = is_epoch_end {
debug!(target: "client", "Epoch transition at block {}", header.hash());
let mut batch = DBTransaction::new();
chain.insert_epoch_transition(&mut batch, header.number(), EpochTransition {
block_hash: header.hash(),
block_number: header.number(),
proof: proof,
});
self.db.read().write_buffered(batch);
}
}
// use a state-proving closure for the given block.
fn with_proving_caller<F, T>(&self, id: BlockId, with_call: F) -> T
where F: FnOnce(&::engines::Call) -> T
{
let call = |a, d| {
let tx = self.contract_call_tx(id, a, d);
let (result, items) = self.prove_transaction(tx, id)
.ok_or_else(|| format!("Unable to make call. State unavailable?"))?;
let items = items.into_iter().map(|x| x.to_vec()).collect();
Ok((result, items))
};
with_call(&call)
}
// 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> {
let number = match state_db.journal_db().latest_era() {
@ -810,7 +856,7 @@ impl Client {
}
let root = header.state_root();
State::from_existing(db, root, self.engine.account_start_nonce(), self.factories.clone()).ok()
State::from_existing(db, root, self.engine.account_start_nonce(block_number), self.factories.clone()).ok()
})
}
@ -835,7 +881,7 @@ impl Client {
State::from_existing(
self.state_db.lock().boxed_clone_canon(&header.hash()),
header.state_root(),
self.engine.account_start_nonce(),
self.engine.account_start_nonce(header.number()),
self.factories.clone())
.expect("State root of best block header always valid.")
}
@ -987,7 +1033,7 @@ impl Client {
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()),
nonce: self.nonce(&from, block_id).unwrap_or_else(|| self.engine.account_start_nonce(0)),
action: Action::Call(address),
gas: U256::from(50_000_000),
gas_price: U256::default(),
@ -1622,17 +1668,21 @@ impl MiningBlockChainClient for Client {
let engine = &*self.engine;
let chain = self.chain.read();
let h = chain.best_block_hash();
let best_header = &chain.block_header(&h)
.expect("h is best block hash: so its header must exist: qed");
let is_epoch_begin = chain.epoch_transition(best_header.number(), h).is_some();
let mut open_block = OpenBlock::new(
engine,
self.factories.clone(),
false, // TODO: this will need to be parameterised once we want to do immediate mining insertion.
self.state_db.lock().boxed_clone_canon(&h),
&chain.block_header(&h).expect("h is best block hash: so its header must exist: qed"),
best_header,
self.build_last_hashes(h.clone()),
author,
gas_range_target,
extra_data,
is_epoch_begin,
).expect("OpenBlock::new only fails if parent state root invalid; state root of best block's header is never invalid; qed");
// Add uncles
@ -1717,6 +1767,10 @@ impl EngineClient for Client {
fn broadcast_consensus_message(&self, message: Bytes) {
self.notify(|notify| notify.broadcast(message.clone()));
}
fn epoch_transition_for(&self, parent_hash: H256) -> Option<::engines::EpochTransition> {
self.chain.read().epoch_transition_for(parent_hash)
}
}
impl ProvingBlockChainClient for Client {
@ -1746,7 +1800,10 @@ impl ProvingBlockChainClient for Client {
match res {
Err(ExecutionError::Internal(_)) => None,
Err(_) => Some((Vec::new(), state.drop().1.extract_proof())),
Err(e) => {
trace!(target: "client", "Proved call failed: {}", e);
Some((Vec::new(), state.drop().1.extract_proof()))
}
Ok(res) => Some((res.output, state.drop().1.extract_proof())),
}
}

View File

@ -89,7 +89,7 @@ impl EvmTestClient {
-> Result<(U256, Vec<u8>), EvmTestError>
{
let genesis = self.spec.genesis_header();
let mut state = state::State::from_existing(self.state_db.boxed_clone(), *genesis.state_root(), self.spec.engine.account_start_nonce(), self.factories.clone())
let mut state = state::State::from_existing(self.state_db.boxed_clone(), *genesis.state_root(), self.spec.engine.account_start_nonce(0), self.factories.clone())
.map_err(EvmTestError::Trie)?;
let info = client::EnvInfo {
number: genesis.number(),

View File

@ -372,7 +372,8 @@ impl MiningBlockChainClient for TestBlockChainClient {
Arc::new(last_hashes),
author,
gas_range_target,
extra_data
extra_data,
false,
).expect("Opening block for tests will not fail.");
// TODO [todr] Override timestamp for predictability (set_timestamp_now kind of sucks)
open_block.set_timestamp(*self.latest_block_timestamp.read());
@ -786,4 +787,8 @@ impl EngineClient for TestBlockChainClient {
}
fn broadcast_consensus_message(&self, _message: Bytes) {}
fn epoch_transition_for(&self, _block_hash: H256) -> Option<::engines::EpochTransition> {
None
}
}

View File

@ -311,6 +311,13 @@ pub trait EngineClient: MiningBlockChainClient {
/// Broadcast a consensus message to the network.
fn broadcast_consensus_message(&self, message: Bytes);
/// Get the transition to the epoch the given parent hash is part of
/// or transitions to.
/// This will give the epoch that any children of this parent belong to.
///
/// The block corresponding the the parent hash must be stored already.
fn epoch_transition_for(&self, parent_hash: H256) -> Option<::engines::EpochTransition>;
}
/// Extended client interface for providing proofs of the state.

View File

@ -0,0 +1,187 @@
// 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/>.
//! Finality proof generation and checking.
use std::collections::{VecDeque};
use std::collections::hash_map::{HashMap, Entry};
use util::{Address, H256};
use engines::validator_set::SimpleList;
/// Error indicating unknown validator.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct UnknownValidator;
/// Rolling finality checker for authority round consensus.
/// Stores a chain of unfinalized hashes that can be pushed onto.
pub struct RollingFinality {
headers: VecDeque<(H256, Address)>,
signers: SimpleList,
sign_count: HashMap<Address, usize>,
last_pushed: Option<H256>,
}
impl RollingFinality {
/// Create a blank finality checker under the given validator set.
pub fn blank(signers: Vec<Address>) -> Self {
RollingFinality {
headers: VecDeque::new(),
signers: SimpleList::new(signers),
sign_count: HashMap::new(),
last_pushed: None,
}
}
/// Extract unfinalized subchain from ancestry iterator.
/// Clears the current subchain.
///
/// Fails if any provided signature isn't part of the signers set.
pub fn build_ancestry_subchain<I>(&mut self, iterable: I) -> Result<(), UnknownValidator>
where I: IntoIterator<Item=(H256, Address)>
{
self.clear();
for (hash, signer) in iterable {
if !self.signers.contains(&signer) { return Err(UnknownValidator) }
if self.last_pushed.is_none() { self.last_pushed = Some(hash) }
// break when we've got our first finalized block.
{
let current_signed = self.sign_count.len();
let would_be_finalized = (current_signed + 1) * 2 > self.signers.len();
let entry = self.sign_count.entry(signer);
if let (true, &Entry::Vacant(_)) = (would_be_finalized, &entry) {
break
}
*entry.or_insert(0) += 1;
}
self.headers.push_front((hash, signer));
}
Ok(())
}
/// Clear the finality status, but keeps the validator set.
pub fn clear(&mut self) {
self.headers.clear();
self.sign_count.clear();
self.last_pushed = None;
}
/// Returns the last pushed hash.
pub fn subchain_head(&self) -> Option<H256> {
self.last_pushed
}
/// Get an iterator over stored hashes in order.
pub fn unfinalized_hashes(&self) -> Iter { Iter(self.headers.iter()) }
/// Get the validator set.
pub fn validators(&self) -> &SimpleList { &self.signers }
/// Push a hash onto the rolling finality checker (implying `subchain_head` == head.parent)
///
/// Fails if `signer` isn't a member of the active validator set.
/// Returns a list of all newly finalized headers.
// TODO: optimize with smallvec.
pub fn push_hash(&mut self, head: H256, signer: Address) -> Result<Vec<H256>, UnknownValidator> {
if !self.signers.contains(&signer) { return Err(UnknownValidator) }
self.headers.push_back((head, signer));
*self.sign_count.entry(signer).or_insert(0) += 1;
let mut newly_finalized = Vec::new();
while self.sign_count.len() * 2 > self.signers.len() {
let (hash, signer) = self.headers.pop_front()
.expect("headers length always greater than sign count length; qed");
newly_finalized.push(hash);
match self.sign_count.entry(signer) {
Entry::Occupied(mut entry) => {
// decrement count for this signer and purge on zero.
*entry.get_mut() -= 1;
if *entry.get() == 0 {
entry.remove();
}
}
Entry::Vacant(_) => panic!("all hashes in `header` should have an entry in `sign_count` for their signer; qed"),
}
}
Ok(newly_finalized)
}
}
pub struct Iter<'a>(::std::collections::vec_deque::Iter<'a, (H256, Address)>);
impl<'a> Iterator for Iter<'a> {
type Item = H256;
fn next(&mut self) -> Option<H256> {
self.0.next().map(|&(h, _)| h)
}
}
#[cfg(test)]
mod tests {
use util::{Address, H256};
use super::RollingFinality;
#[test]
fn rejects_unknown_signer() {
let signers = (0..3).map(|_| Address::random()).collect();
let mut finality = RollingFinality::blank(signers);
assert!(finality.push_hash(H256::random(), Address::random()).is_err());
}
#[test]
fn finalize_multiple() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let mut finality = RollingFinality::blank(signers.clone());
let hashes: Vec<_> = (0..7).map(|_| H256::random()).collect();
// 3 / 6 signers is < 51% so no finality.
for (i, hash) in hashes.iter().take(6).cloned().enumerate() {
let i = i % 3;
assert!(finality.push_hash(hash, signers[i]).unwrap().len() == 0);
}
// after pushing a block signed by a fourth validator, the first four
// blocks of the unverified chain become verified.
assert_eq!(finality.push_hash(hashes[6], signers[4]).unwrap(),
vec![hashes[0], hashes[1], hashes[2], hashes[3]]);
}
#[test]
fn from_ancestry() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let hashes: Vec<_> = (0..12).map(|i| (H256::random(), signers[i % 6])).collect();
let mut finality = RollingFinality::blank(signers.clone());
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();
assert_eq!(finality.unfinalized_hashes().count(), 3);
assert_eq!(finality.subchain_head(), Some(hashes[11].0));
}
}

View File

@ -19,24 +19,32 @@
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering};
use std::sync::Weak;
use std::time::{UNIX_EPOCH, Duration};
use util::*;
use ethkey::{verify_address, Signature};
use rlp::{UntrustedRlp, encode};
use account_provider::AccountProvider;
use block::*;
use spec::CommonParams;
use engines::{Call, Engine, Seal, EngineError};
use header::Header;
use builtin::Builtin;
use client::{Client, EngineClient};
use engines::{Call, Engine, Seal, EngineError, ConstructedVerifier};
use error::{Error, TransactionError, BlockError};
use ethjson;
use io::{IoContext, IoHandler, TimerToken, IoService};
use builtin::Builtin;
use transaction::UnverifiedTransaction;
use client::{Client, EngineClient};
use header::{Header, BlockNumber};
use spec::CommonParams;
use state::CleanupMode;
use transaction::UnverifiedTransaction;
use super::signer::EngineSigner;
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
use self::finality::RollingFinality;
use ethkey::{verify_address, Signature};
use io::{IoContext, IoHandler, TimerToken, IoService};
use itertools::{self, Itertools};
use rlp::{UntrustedRlp, encode};
use util::*;
mod finality;
/// `AuthorityRound` params.
pub struct AuthorityRoundParams {
/// Gas limit divisor.
@ -57,6 +65,8 @@ pub struct AuthorityRoundParams {
pub eip155_transition: u64,
/// Monotonic step validation transition block.
pub validate_step_transition: u64,
/// Immediate transitions.
pub immediate_transitions: bool,
}
impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
@ -71,6 +81,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
validate_score_transition: p.validate_score_transition.map_or(0, Into::into),
eip155_transition: p.eip155_transition.map_or(0, Into::into),
validate_step_transition: p.validate_step_transition.map_or(0, Into::into),
immediate_transitions: p.immediate_transitions.unwrap_or(false),
}
}
}
@ -114,6 +125,92 @@ impl Step {
}
}
struct EpochManager {
epoch_transition_hash: H256,
epoch_transition_number: BlockNumber,
finality_checker: RollingFinality,
force: bool,
}
impl EpochManager {
fn blank() -> Self {
EpochManager {
epoch_transition_hash: H256::default(),
epoch_transition_number: 0,
finality_checker: RollingFinality::blank(Vec::new()),
force: true,
}
}
// zoom to epoch for given header. returns true if succeeded, false otherwise.
fn zoom_to(&mut self, client: &EngineClient, engine: &Engine, validators: &ValidatorSet, header: &Header) -> bool {
let last_was_parent = self.finality_checker.subchain_head() == Some(header.parent_hash().clone());
// early exit for current target == chain head, but only if the epochs are
// the same.
if last_was_parent && !self.force {
return true;
}
self.force = false;
debug!(target: "engine", "Zooming to epoch for block {}", header.hash());
// epoch_transition_for can be an expensive call, but in the absence of
// forks it will only need to be called for the block directly after
// epoch transition, in which case it will be O(1) and require a single
// DB lookup.
let last_transition = match client.epoch_transition_for(*header.parent_hash()) {
Some(t) => t,
None => {
// this really should never happen unless the block passed
// hasn't got a parent in the database.
debug!(target: "engine", "No genesis transition found.");
return false;
}
};
// extract other epoch set if it's not the same as the last.
if last_transition.block_hash != self.epoch_transition_hash {
let (signal_number, set_proof, _) = destructure_proofs(&last_transition.proof)
.expect("proof produced by this engine; therefore it is valid; qed");
trace!(target: "engine", "extracting epoch set for epoch ({}, {}) signalled at #{}",
last_transition.block_number, last_transition.block_hash, signal_number);
let first = signal_number == 0;
let epoch_set = validators.epoch_set(
first,
engine,
signal_number, // use signal number so multi-set first calculation is correct.
set_proof,
)
.ok()
.map(|(list, _)| list.into_inner())
.expect("proof produced by this engine; therefore it is valid; qed");
self.finality_checker = RollingFinality::blank(epoch_set);
}
self.epoch_transition_hash = last_transition.block_hash;
self.epoch_transition_number = last_transition.block_number;
true
}
// note new epoch hash. this will force the next block to re-load
// the epoch set
// TODO: optimize and don't require re-loading after epoch change.
fn note_new_epoch(&mut self) {
self.force = true;
}
/// Get validator set. Zoom to the correct epoch first.
fn validators(&self) -> &SimpleList {
self.finality_checker.validators()
}
}
/// Engine using `AuthorityRound` proof-of-authority BFT consensus.
pub struct AuthorityRound {
params: CommonParams,
@ -130,22 +227,63 @@ pub struct AuthorityRound {
validate_score_transition: u64,
eip155_transition: u64,
validate_step_transition: u64,
epoch_manager: Mutex<EpochManager>,
immediate_transitions: bool,
}
// 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)
verify_external(header, &self.subchain_validators, &*self.step, |_| {})
}
fn check_finality_proof(&self, proof: &[u8]) -> Option<Vec<H256>> {
macro_rules! otry {
($e: expr) => {
match $e {
Some(x) => x,
None => return None,
}
}
}
let mut finality_checker = RollingFinality::blank(self.subchain_validators.clone().into_inner());
let mut finalized = Vec::new();
let headers: Vec<Header> = otry!(UntrustedRlp::new(proof).as_list().ok());
for header in &headers {
// ensure all headers have correct number of seal fields so we can `verify_external`
// without panic.
//
// `verify_external` checks that signature is correct and author == signer.
if header.seal().len() != 2 { return None }
otry!(verify_external(header, &self.subchain_validators, &*self.step, |_| {}).ok());
let newly_finalized = otry!(finality_checker.push_hash(header.hash(), header.author().clone()).ok());
finalized.extend(newly_finalized);
}
if finalized.is_empty() { None } else { Some(finalized) }
}
}
// Report misbehavior
#[derive(Debug)]
#[allow(dead_code)]
enum Report {
// Malicious behavior
Malicious(Address, BlockNumber, Bytes),
// benign misbehavior
Benign(Address, BlockNumber),
}
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
@ -156,13 +294,25 @@ 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)
}
fn verify_external(header: &Header, validators: &ValidatorSet, step: &Step) -> Result<(), Error> {
fn step_proposer(validators: &ValidatorSet, bh: &H256, step: usize) -> Address {
let proposer = validators.get(bh, step);
trace!(target: "engine", "Fetched proposer for step {}: {}", step, proposer);
proposer
}
fn is_step_proposer(validators: &ValidatorSet, bh: &H256, step: usize, address: &Address) -> bool {
step_proposer(validators, bh, step) == *address
}
fn verify_external<F: Fn(Report)>(header: &Header, validators: &ValidatorSet, step: &Step, report: F)
-> 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(), header.number());
report(Report::Benign(*header.author(), header.number()));
Err(BlockError::InvalidSeal)?
} else {
let proposer_signature = header_signature(header)?;
@ -179,6 +329,21 @@ fn verify_external(header: &Header, validators: &ValidatorSet, step: &Step) -> R
}
}
fn combine_proofs(signal_number: BlockNumber, set_proof: &[u8], finality_proof: &[u8]) -> Vec<u8> {
let mut stream = ::rlp::RlpStream::new_list(3);
stream.append(&signal_number).append(&set_proof).append(&finality_proof);
stream.out()
}
fn destructure_proofs(combined: &[u8]) -> Result<(BlockNumber, &[u8], &[u8]), Error> {
let rlp = UntrustedRlp::new(combined);
Ok((
rlp.at(0)?.as_val()?,
rlp.at(1)?.data()?,
rlp.at(2)?.data()?,
))
}
trait AsMillis {
fn as_millis(&self) -> u64;
}
@ -214,7 +379,10 @@ impl AuthorityRound {
validate_score_transition: our_params.validate_score_transition,
eip155_transition: our_params.eip155_transition,
validate_step_transition: our_params.validate_step_transition,
epoch_manager: Mutex::new(EpochManager::blank()),
immediate_transitions: our_params.immediate_transitions,
});
// Do not initialize timeouts for tests.
if should_timeout {
let handler = TransitionHandler { engine: Arc::downgrade(&engine) };
@ -222,14 +390,6 @@ impl AuthorityRound {
}
Ok(engine)
}
fn step_proposer(&self, bh: &H256, step: usize) -> Address {
self.validators.get(bh, step)
}
fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool {
self.step_proposer(bh, step) == *address
}
}
fn unix_now() -> Duration {
@ -325,7 +485,33 @@ impl Engine for AuthorityRound {
let header = block.header();
let step = self.step.load();
if self.is_step_proposer(header.parent_hash(), step, header.author()) {
// fetch correct validator set for current epoch, taking into account
// finality of previous transitions.
let active_set;
let validators = if self.immediate_transitions {
&*self.validators
} else {
let mut epoch_manager = self.epoch_manager.lock();
let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) {
Some(client) => client,
None => {
warn!(target: "engine", "Unable to generate seal: missing client ref.");
return Seal::None;
}
};
if !epoch_manager.zoom_to(&*client, self, &*self.validators, header) {
debug!(target: "engine", "Unable to zoom to epoch.");
return Seal::None;
}
active_set = epoch_manager.validators().clone();
&active_set as &_
};
if is_step_proposer(validators, header.parent_hash(), step, header.author()) {
if let Ok(signature) = self.signer.sign(header.bare_hash()) {
trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step);
@ -337,11 +523,43 @@ impl Engine for AuthorityRound {
warn!(target: "engine", "generate_seal: FAIL: Accounts secret key unavailable.");
}
} else {
trace!(target: "engine", "generate_seal: Not a proposer for step {}.", step);
trace!(target: "engine", "generate_seal: {} not a proposer for step {}.",
header.author(), step);
}
Seal::None
}
fn on_new_block(
&self,
block: &mut ExecutedBlock,
last_hashes: Arc<::env_info::LastHashes>,
epoch_begin: bool,
) -> Result<(), Error> {
let parent_hash = block.fields().header.parent_hash().clone();
::engines::common::push_last_hash(block, last_hashes.clone(), self, &parent_hash)?;
if !epoch_begin { return Ok(()) }
// genesis is never a new block, but might as well check.
let header = block.fields().header.clone();
let first = header.number() == 0;
let mut call = |to, data| {
let result = ::engines::common::execute_as_system(
block,
last_hashes.clone(),
self,
to,
U256::max_value(), // unbounded gas? maybe make configurable.
Some(data),
);
result.map_err(|e| format!("{}", e))
};
self.validators.on_epoch_begin(first, &header, &mut call)
}
/// Apply the block reward on finalisation of the block.
fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> {
let fields = block.fields_mut();
@ -386,19 +604,26 @@ impl Engine for AuthorityRound {
}
let parent_step = header_step(parent)?;
// Ensure header is from the step after parent.
if step == parent_step
|| (header.number() >= self.validate_step_transition && step <= parent_step) {
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
self.validators.report_malicious(header.author(), header.number(), Default::default());
self.validators.report_malicious(header.author(), header.number(), header.number(), Default::default());
Err(EngineError::DoubleVote(header.author().clone()))?;
}
// Report skipped primaries.
if step > parent_step + 1 {
// TODO: use epochmanager to get correct validator set for reporting?
// or just rely on the fact that in general these will be the same
// and some reports might go missing?
trace!(target: "engine", "Author {} built block with step gap. current step: {}, parent step: {}",
header.author(), step, parent_step);
for s in parent_step + 1..step {
let skipped_primary = self.step_proposer(&parent.hash(), s);
trace!(target: "engine", "Author {} did not build his block on top of the intermediate designated primary {}.", header.author(), skipped_primary);
self.validators.report_benign(&skipped_primary, header.number());
let skipped_primary = step_proposer(&*self.validators, &parent.hash(), s);
self.validators.report_benign(&skipped_primary, header.number(), header.number());
}
}
@ -413,30 +638,164 @@ impl Engine for AuthorityRound {
// Check the validators.
fn verify_block_external(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
verify_external(header, &*self.validators, &*self.step)
// fetch correct validator set for current epoch, taking into account
// finality of previous transitions.
let active_set;
let (validators, set_number) = if self.immediate_transitions {
(&*self.validators, header.number())
} else {
// get correct validator set for epoch.
let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) {
Some(client) => client,
None => {
debug!(target: "engine", "Unable to verify sig: missing client ref.");
return Err(EngineError::RequiresClient.into())
}
};
let mut epoch_manager = self.epoch_manager.lock();
if !epoch_manager.zoom_to(&*client, self, &*self.validators, header) {
debug!(target: "engine", "Unable to zoom to epoch.");
return Err(EngineError::RequiresClient.into())
}
active_set = epoch_manager.validators().clone();
(&active_set as &_, epoch_manager.epoch_transition_number)
};
let report = |report| match report {
Report::Benign(address, block_number) =>
self.validators.report_benign(&address, set_number, block_number),
Report::Malicious(address, block_number, proof) =>
self.validators.report_malicious(&address, set_number, block_number, proof),
};
// verify signature against fixed list, but reports should go to the
// contract itself.
verify_external(header, validators, &*self.step, report)
}
// 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 genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
self.validators.genesis_epoch_data(header, call)
.map(|set_proof| combine_proofs(0, &set_proof, &[]))
}
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
fn signals_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> super::EpochChange
{
self.validators.is_epoch_end(header, block, receipts)
if self.immediate_transitions { return super::EpochChange::No }
let first = header.number() == 0;
self.validators.signals_epoch_end(first, 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)?;
fn is_epoch_end(
&self,
chain_head: &Header,
chain: &super::Headers,
transition_store: &super::PendingTransitionStore,
) -> Option<Vec<u8>> {
// epochs only matter if we want to support light clients.
if self.immediate_transitions { return None }
Ok(Box::new(EpochVerifier {
epoch_number: num,
step: self.step.clone(),
subchain_validators: simple_list,
}))
let first = chain_head.number() == 0;
// apply immediate transitions.
if let Some(change) = self.validators.is_epoch_end(first, chain_head) {
return Some(change)
}
let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) {
Some(client) => client,
None => {
warn!(target: "engine", "Unable to check for epoch end: missing client ref.");
return None;
}
};
// find most recently finalized blocks, then check transition store for pending transitions.
let mut epoch_manager = self.epoch_manager.lock();
if !epoch_manager.zoom_to(&*client, self, &*self.validators, chain_head) {
return None;
}
if epoch_manager.finality_checker.subchain_head() != Some(*chain_head.parent_hash()) {
// build new finality checker from ancestry of chain head,
// not including chain head itself yet.
let mut hash = chain_head.parent_hash().clone();
let epoch_transition_hash = epoch_manager.epoch_transition_hash;
// walk the chain within current epoch backwards.
// author == ec_recover(sig) known since
// the blocks are in the DB.
let ancestry_iter = itertools::repeat_call(move || {
chain(hash).and_then(|header| {
if header.number() == 0 { return None }
let res = (hash, header.author().clone());
hash = header.parent_hash().clone();
Some(res)
})
})
.while_some()
.take_while(|&(h, _)| h != epoch_transition_hash);
if let Err(_) = epoch_manager.finality_checker.build_ancestry_subchain(ancestry_iter) {
debug!(target: "engine", "inconsistent validator set within epoch");
return None;
}
}
{
if let Ok(finalized) = epoch_manager.finality_checker.push_hash(chain_head.hash(), *chain_head.author()) {
let mut finalized = finalized.into_iter();
while let Some(hash) = finalized.next() {
if let Some(pending) = transition_store(hash) {
let finality_proof = ::std::iter::once(hash)
.chain(finalized)
.chain(epoch_manager.finality_checker.unfinalized_hashes())
.map(|hash| chain(hash)
.expect("these headers fetched before when constructing finality checker; qed"))
.collect::<Vec<Header>>();
// this gives us the block number for `hash`, assuming it's ancestry.
let signal_number = chain_head.number()
- finality_proof.len() as BlockNumber
+ 1;
let finality_proof = ::rlp::encode_list(&finality_proof);
epoch_manager.note_new_epoch();
return Some(combine_proofs(signal_number, &pending.proof, &*finality_proof));
}
}
}
}
None
}
fn epoch_verifier<'a>(&self, _header: &Header, proof: &'a [u8]) -> ConstructedVerifier<'a> {
let (signal_number, set_proof, finality_proof) = match destructure_proofs(proof) {
Ok(x) => x,
Err(e) => return ConstructedVerifier::Err(e),
};
let first = signal_number == 0;
match self.validators.epoch_set(first, self, signal_number, set_proof) {
Ok((list, finalize)) => {
let verifier = Box::new(EpochVerifier {
step: self.step.clone(),
subchain_validators: list,
});
match finalize {
Some(finalize) => ConstructedVerifier::Unconfirmed(verifier, finality_proof, finalize),
None => ConstructedVerifier::Trusted(verifier),
}
}
Err(e) => ConstructedVerifier::Err(e),
}
}
fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> result::Result<(), Error> {
@ -465,7 +824,11 @@ impl Engine for AuthorityRound {
}
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
Some(Box::new(::snapshot::PoaSnapshot))
if self.immediate_transitions {
None
} else {
Some(Box::new(::snapshot::PoaSnapshot))
}
}
}
@ -535,9 +898,9 @@ mod tests {
let db1 = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let db2 = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![]).unwrap();
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b1 = b1.close_and_lock();
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![]).unwrap();
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b2 = b2.close_and_lock();
engine.set_signer(tap.clone(), addr1, "1".into());
@ -643,6 +1006,7 @@ mod tests {
validate_score_transition: 0,
validate_step_transition: 0,
eip155_transition: 0,
immediate_transitions: true,
};
let aura = AuthorityRound::new(Default::default(), params, Default::default()).unwrap();

View File

@ -23,7 +23,7 @@ use account_provider::AccountProvider;
use block::*;
use builtin::Builtin;
use spec::CommonParams;
use engines::{Engine, EngineError, Seal, Call, EpochChange};
use engines::{Engine, Seal, Call, ConstructedVerifier, EngineError};
use error::{BlockError, Error};
use evm::Schedule;
use ethjson;
@ -51,12 +51,10 @@ impl From<ethjson::spec::BasicAuthorityParams> for BasicAuthorityParams {
}
struct EpochVerifier {
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)
}
@ -187,26 +185,54 @@ impl Engine for BasicAuthority {
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 genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
self.validators.genesis_epoch_data(header, call)
}
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> EpochChange
#[cfg(not(test))]
fn signals_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[::receipt::Receipt]>)
-> super::EpochChange
{
self.validators.is_epoch_end(header, block, receipts)
// don't bother signalling even though a contract might try.
super::EpochChange::No
}
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)?;
#[cfg(test)]
fn signals_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> super::EpochChange
{
// in test mode, always signal even though they don't be finalized.
let first = header.number() == 0;
self.validators.signals_epoch_end(first, header, block, receipts)
}
Ok(Box::new(EpochVerifier {
epoch_number: num,
list: simple_list,
}))
fn is_epoch_end(
&self,
chain_head: &Header,
_chain: &super::Headers,
_transition_store: &super::PendingTransitionStore,
) -> Option<Vec<u8>> {
let first = chain_head.number() == 0;
// finality never occurs so only apply immediate transitions.
self.validators.is_epoch_end(first, chain_head)
}
fn epoch_verifier<'a>(&self, header: &Header, proof: &'a [u8]) -> ConstructedVerifier<'a> {
let first = header.number() == 0;
match self.validators.epoch_set(first, self, header.number(), proof) {
Ok((list, finalize)) => {
let verifier = Box::new(EpochVerifier { list: list });
// our epoch verifier will ensure no unverified verifier is ever verified.
match finalize {
Some(finalize) => ConstructedVerifier::Unconfirmed(verifier, proof, finalize),
None => ConstructedVerifier::Trusted(verifier),
}
}
Err(e) => ConstructedVerifier::Err(e),
}
}
fn register_client(&self, client: Weak<Client>) {
@ -222,7 +248,7 @@ impl Engine for BasicAuthority {
}
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
Some(Box::new(::snapshot::PoaSnapshot))
None
}
}
@ -292,7 +318,7 @@ mod tests {
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = b.close_and_lock();
if let Seal::Regular(seal) = engine.generate_seal(b.block()) {
assert!(b.try_seal(engine, seal).is_ok());

View File

@ -0,0 +1,102 @@
// 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 and transitions.
use error::Error;
use header::Header;
use rlp::{Encodable, Decodable, DecoderError, RlpStream, UntrustedRlp};
use util::H256;
/// A full epoch transition.
#[derive(Debug, Clone)]
pub struct Transition {
/// Block hash at which the transition occurred.
pub block_hash: H256,
/// Block number at which the transition occurred.
pub block_number: u64,
/// "transition/epoch" proof from the engine combined with a finality proof.
pub proof: Vec<u8>,
}
impl Encodable for Transition {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(3)
.append(&self.block_hash)
.append(&self.block_number)
.append(&self.proof);
}
}
impl Decodable for Transition {
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
Ok(Transition {
block_hash: rlp.val_at(0)?,
block_number: rlp.val_at(1)?,
proof: rlp.val_at(2)?,
})
}
}
/// An epoch transition pending a finality proof.
/// Not all transitions need one.
pub struct PendingTransition {
/// "transition/epoch" proof from the engine.
pub proof: Vec<u8>,
}
impl Encodable for PendingTransition {
fn rlp_append(&self, s: &mut RlpStream) {
s.append(&self.proof);
}
}
impl Decodable for PendingTransition {
fn decode(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
Ok(PendingTransition {
proof: rlp.as_val()?,
})
}
}
/// 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: Send + Sync {
/// 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)
}
/// Check a finality proof against this epoch verifier.
/// Returns `Some(hashes)` if the proof proves finality of these hashes.
/// Returns `None` if the proof doesn't prove anything.
fn check_finality_proof(&self, _proof: &[u8]) -> Option<Vec<H256>> {
None
}
}
/// Special "no-op" verifier for stateless, epoch-less engines.
pub struct NoOp;
impl EpochVerifier for NoOp {
fn verify_light(&self, _header: &Header) -> Result<(), Error> { Ok(()) }
}

View File

@ -1,45 +0,0 @@
// 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: Send + 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(()) }
}

View File

@ -79,7 +79,7 @@ mod tests {
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let genesis_header = spec.genesis_header();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = b.close_and_lock();
if let Seal::Regular(seal) = engine.generate_seal(b.block()) {
assert!(b.try_seal(engine, seal).is_ok());

View File

@ -18,7 +18,6 @@
mod authority_round;
mod basic_authority;
mod epoch_verifier;
mod instant_seal;
mod null_engine;
mod signer;
@ -27,15 +26,19 @@ mod transition;
mod validator_set;
mod vote_collector;
pub mod epoch;
pub use self::authority_round::AuthorityRound;
pub use self::basic_authority::BasicAuthority;
pub use self::epoch_verifier::EpochVerifier;
pub use self::epoch::{EpochVerifier, Transition as EpochTransition};
pub use self::instant_seal::InstantSeal;
pub use self::null_engine::NullEngine;
pub use self::tendermint::Tendermint;
use std::sync::Weak;
use self::epoch::PendingTransition;
use account_provider::AccountProvider;
use block::ExecutedBlock;
use builtin::Builtin;
@ -72,6 +75,10 @@ pub enum EngineError {
BadSealFieldSize(OutOfBounds<usize>),
/// Validation proof insufficient.
InsufficientProof(String),
/// Failed system call.
FailedSystemCall(String),
/// Requires client ref, but none registered.
RequiresClient,
}
impl fmt::Display for EngineError {
@ -84,6 +91,8 @@ impl fmt::Display for EngineError {
UnexpectedMessage => "This Engine should not be fed messages.".into(),
BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob),
InsufficientProof(ref msg) => format!("Insufficient validation proof: {}", msg),
FailedSystemCall(ref msg) => format!("Failed to make system call: {}", msg),
RequiresClient => format!("Call requires client but none registered"),
};
f.write_fmt(format_args!("Engine error ({})", msg))
@ -102,17 +111,53 @@ pub enum Seal {
}
/// Type alias for a function we can make calls through synchronously.
pub type Call<'a> = Fn(Address, Bytes) -> Result<Bytes, String> + 'a;
/// Returns the call result and state proof for each call.
pub type Call<'a> = Fn(Address, Bytes) -> Result<(Bytes, Vec<Vec<u8>>), String> + 'a;
/// Type alias for a function we can get headers by hash through.
pub type Headers<'a> = Fn(H256) -> Option<Header> + 'a;
/// Type alias for a function we can query pending transitions by block hash through.
pub type PendingTransitionStore<'a> = Fn(H256) -> Option<PendingTransition> + 'a;
/// Proof generated on epoch change.
pub enum Proof {
/// Known proof (exctracted from signal)
Known(Vec<u8>),
/// Extract proof from caller.
WithState(Box<Fn(&Call) -> Result<Vec<u8>, String>>),
}
/// Generated epoch verifier.
pub enum ConstructedVerifier<'a> {
/// Fully trusted verifier.
Trusted(Box<EpochVerifier>),
/// Verifier unconfirmed. Check whether given finality proof finalizes given hash
/// under previous epoch.
Unconfirmed(Box<EpochVerifier>, &'a [u8], H256),
/// Error constructing verifier.
Err(Error),
}
impl<'a> ConstructedVerifier<'a> {
/// Convert to a result, indicating that any necessary confirmation has been done
/// already.
pub fn known_confirmed(self) -> Result<Box<EpochVerifier>, Error> {
match self {
ConstructedVerifier::Trusted(v) | ConstructedVerifier::Unconfirmed(v, _, _) => Ok(v),
ConstructedVerifier::Err(e) => Err(e),
}
}
}
/// 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.
Yes(u64),
/// The epoch will change, with proof.
Yes(Proof),
}
/// More data required to determine if an epoch change occurred at a given block.
@ -161,11 +206,23 @@ pub trait Engine : Sync + Send {
fn maximum_uncle_count(&self) -> usize { 2 }
/// The number of generations back that uncles can be.
fn maximum_uncle_age(&self) -> usize { 6 }
/// The nonce with which accounts begin.
fn account_start_nonce(&self) -> U256 { self.params().account_start_nonce }
/// The nonce with which accounts begin at given block.
fn account_start_nonce(&self, block: u64) -> U256 {
if block >= self.params().dust_protection_transition {
U256::from(self.params().nonce_cap_increment) * U256::from(block)
} else {
self.params().account_start_nonce
}
}
/// Block transformation functions, before the transactions.
fn on_new_block(&self, block: &mut ExecutedBlock, last_hashes: Arc<LastHashes>) -> Result<(), Error> {
/// `epoch_begin` set to true if this block kicks off an epoch.
fn on_new_block(
&self,
block: &mut ExecutedBlock,
last_hashes: Arc<LastHashes>,
_epoch_begin: bool,
) -> Result<(), Error> {
let parent_hash = block.fields().header.parent_hash().clone();
common::push_last_hash(block, last_hashes, self, &parent_hash)
}
@ -227,46 +284,44 @@ pub trait Engine : Sync + Send {
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`
///
/// It must be possible to generate an epoch proof for any block in an epoch,
/// and it should always be equivalent to the proof of the transition block.
fn epoch_proof(&self, _header: &Header, _caller: &Call)
-> Result<Vec<u8>, Error>
{
Ok(Vec::new())
}
/// Genesis epoch data.
fn genesis_epoch_data(&self, _header: &Header, _call: &Call) -> Result<Vec<u8>, String> { Ok(Vec::new()) }
/// Whether an epoch change occurred at the given header.
/// Whether an epoch change is signalled at the given header but will require finality.
/// If a change can be enacted immediately then return `No` from this function but
/// `Yes` from `is_epoch_end`.
///
/// If the block or receipts are required, return `Unsure` and the function will be
/// called again with them.
/// Return `Yes` or `No` when the answer is definitively known.
///
/// Should not interact with state.
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[Receipt]>)
fn signals_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[Receipt]>)
-> EpochChange
{
EpochChange::No
}
/// Create an epoch verifier from validation proof.
/// Whether a block is the end of an epoch.
///
/// 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))
/// This either means that an immediate transition occurs or a block signalling transition
/// has reached finality. The `Headers` given are not guaranteed to return any blocks
/// from any epoch other than the current.
///
/// Return optional transition proof.
fn is_epoch_end(
&self,
_chain_head: &Header,
_chain: &Headers,
_transition_store: &PendingTransitionStore,
) -> Option<Vec<u8>> {
None
}
/// Create an epoch verifier from validation proof and a flag indicating
/// whether finality is required.
fn epoch_verifier<'a>(&self, _header: &Header, _proof: &'a [u8]) -> ConstructedVerifier<'a> {
ConstructedVerifier::Trusted(Box::new(self::epoch::NoOp))
}
/// Populate a header's fields based on its parent's header.
@ -323,7 +378,11 @@ pub trait Engine : Sync + Send {
/// Returns new contract address generation scheme at given block number.
fn create_address_scheme(&self, number: BlockNumber) -> CreateContractAddress {
if number >= self.params().eip86_transition { CreateContractAddress::FromCodeHash } else { CreateContractAddress::FromSenderAndNonce }
if number >= self.params().eip86_transition {
CreateContractAddress::FromCodeHash
} else {
CreateContractAddress::FromSenderAndNonce
}
}
}
@ -343,6 +402,52 @@ pub mod common {
use util::*;
use super::Engine;
/// Execute a call as the system address.
pub fn execute_as_system<E: Engine + ?Sized>(
block: &mut ExecutedBlock,
last_hashes: Arc<LastHashes>,
engine: &E,
contract_address: Address,
gas: U256,
data: Option<Bytes>,
) -> Result<Bytes, Error> {
let env_info = {
let header = block.fields().header;
EnvInfo {
number: header.number(),
author: header.author().clone(),
timestamp: header.timestamp(),
difficulty: header.difficulty().clone(),
last_hashes: last_hashes,
gas_used: U256::zero(),
gas_limit: gas,
}
};
let mut state = block.fields_mut().state;
let params = ActionParams {
code_address: contract_address.clone(),
address: contract_address.clone(),
sender: SYSTEM_ADDRESS.clone(),
origin: SYSTEM_ADDRESS.clone(),
gas: gas,
gas_price: 0.into(),
value: ActionValue::Transfer(0.into()),
code: state.code(&contract_address)?,
code_hash: state.code_hash(&contract_address)?,
data: data,
call_type: CallType::Call,
};
let mut ex = Executive::new(&mut state, &env_info, engine);
let mut substate = Substate::new();
let mut output = Vec::new();
if let Err(e) = ex.call(params, &mut substate, BytesRef::Flexible(&mut output), &mut NoopTracer, &mut NoopVMTracer) {
warn!("Encountered error on making system call: {}", e);
}
Ok(output)
}
/// Push last known block hash to the state.
pub fn push_last_hash<E: Engine + ?Sized>(block: &mut ExecutedBlock, last_hashes: Arc<LastHashes>, engine: &E, hash: &H256) -> Result<(), Error> {
if block.fields().header.number() == engine.params().eip210_transition {
@ -350,39 +455,14 @@ pub mod common {
state.init_code(&engine.params().eip210_contract_address, engine.params().eip210_contract_code.clone())?;
}
if block.fields().header.number() >= engine.params().eip210_transition {
let env_info = {
let header = block.fields().header;
EnvInfo {
number: header.number(),
author: header.author().clone(),
timestamp: header.timestamp(),
difficulty: header.difficulty().clone(),
last_hashes: last_hashes,
gas_used: U256::zero(),
gas_limit: engine.params().eip210_contract_gas,
}
};
let mut state = block.fields_mut().state;
let contract_address = engine.params().eip210_contract_address;
let params = ActionParams {
code_address: contract_address.clone(),
address: contract_address.clone(),
sender: SYSTEM_ADDRESS.clone(),
origin: SYSTEM_ADDRESS.clone(),
gas: engine.params().eip210_contract_gas,
gas_price: 0.into(),
value: ActionValue::Transfer(0.into()),
code: state.code(&contract_address)?,
code_hash: state.code_hash(&contract_address)?,
data: Some(hash.to_vec()),
call_type: CallType::Call,
};
let mut ex = Executive::new(&mut state, &env_info, engine);
let mut substate = Substate::new();
let mut output = [];
if let Err(e) = ex.call(params, &mut substate, BytesRef::Fixed(&mut output), &mut NoopTracer, &mut NoopVMTracer) {
warn!("Encountered error on updating last hashes: {}", e);
}
let _ = execute_as_system(
block,
last_hashes,
engine,
engine.params().eip210_contract_address,
engine.params().eip210_contract_gas,
Some(hash.to_vec()),
)?;
}
Ok(())
}

View File

@ -466,7 +466,8 @@ impl Engine for Tendermint {
}
self.broadcast_message(rlp.as_raw().to_vec());
if let Some(double) = self.votes.vote(message.clone(), &sender) {
self.validators.report_malicious(&sender, message.vote_step.height as BlockNumber, ::rlp::encode(&double).to_vec());
let height = message.vote_step.height as BlockNumber;
self.validators.report_malicious(&sender, height, height, ::rlp::encode(&double).to_vec());
return Err(EngineError::DoubleVote(sender).into());
}
trace!(target: "engine", "Handling a valid {:?} from {}.", message, sender);
@ -493,8 +494,8 @@ impl Engine for Tendermint {
let seal_length = header.seal().len();
if seal_length == self.seal_fields() {
// Either proposal or commit.
if (header.seal()[1] == ::rlp::NULL_RLP.to_vec())
!= (header.seal()[2] == ::rlp::EMPTY_LIST_RLP.to_vec()) {
if (header.seal()[1] == ::rlp::NULL_RLP)
!= (header.seal()[2] == ::rlp::EMPTY_LIST_RLP) {
Ok(())
} else {
warn!(target: "engine", "verify_block_basic: Block is neither a Commit nor Proposal.");
@ -555,7 +556,7 @@ impl Engine for Tendermint {
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor;
if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas {
self.validators.report_malicious(header.author(), header.number(), Default::default());
self.validators.report_malicious(header.author(), header.number(), header.number(), Default::default());
return Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }).into());
}
@ -607,7 +608,7 @@ impl Engine for Tendermint {
// Report the proposer if no proposal was received.
let height = self.height.load(AtomicOrdering::SeqCst);
let current_proposer = self.view_proposer(&*self.proposal_parent.read(), height, self.view.load(AtomicOrdering::SeqCst));
self.validators.report_benign(&current_proposer, height as BlockNumber);
self.validators.report_benign(&current_proposer, height as BlockNumber, height as BlockNumber);
}
Step::Prevote
},
@ -674,7 +675,7 @@ mod tests {
let db = spec.ensure_db_good(db, &Default::default()).unwrap();
let genesis_header = spec.genesis_header();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = b.close();
if let Seal::Proposal(seal) = spec.engine.generate_seal(b.block()) {
(b, seal)

View File

@ -36,7 +36,7 @@ pub struct TransitionHandler<S: Sync + Send + Clone> {
timeouts: Box<Timeouts<S>>,
}
impl <S> TransitionHandler<S> where S: Sync + Send + Clone {
impl<S> TransitionHandler<S> where S: Sync + Send + Clone {
/// New step caller by timeouts.
pub fn new(engine: Weak<Engine>, timeouts: Box<Timeouts<S>>) -> Self {
TransitionHandler {
@ -54,7 +54,7 @@ fn set_timeout<S: Sync + Send + Clone>(io: &IoContext<S>, timeout: Duration) {
.unwrap_or_else(|e| warn!(target: "engine", "Failed to set consensus step timeout: {}.", e))
}
impl <S> IoHandler<S> for TransitionHandler<S> where S: Sync + Send + Clone + 'static {
impl<S> IoHandler<S> for TransitionHandler<S> where S: Sync + Send + Clone + 'static {
fn initialize(&self, io: &IoContext<S>) {
let initial = self.timeouts.initial();
trace!(target: "engine", "Setting the initial timeout to {}.", initial);

View File

@ -24,10 +24,10 @@ use futures::Future;
use native_contracts::ValidatorReport as Provider;
use client::{Client, BlockChainClient};
use engines::Call;
use engines::{Call, Engine};
use header::{Header, BlockNumber};
use super::ValidatorSet;
use super::{ValidatorSet, SimpleList, SystemCall};
use super::safe_contract::ValidatorSafeContract;
/// A validator contract with reporting.
@ -51,7 +51,7 @@ 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> {
fn transact(&self) -> Box<Fn(Address, Bytes) -> Result<Bytes, String>> {
let client = self.client.read().clone();
Box::new(move |a, d| client.as_ref()
.and_then(Weak::upgrade)
@ -66,18 +66,30 @@ impl ValidatorSet for ValidatorContract {
self.validators.default_caller(id)
}
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> ::engines::EpochChange
{
self.validators.is_epoch_end(header, block, receipts)
fn on_epoch_begin(&self, first: bool, header: &Header, call: &mut SystemCall) -> Result<(), ::error::Error> {
self.validators.on_epoch_begin(first, header, call)
}
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
self.validators.epoch_proof(header, caller)
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
self.validators.genesis_epoch_data(header, call)
}
fn epoch_set(&self, header: &Header, proof: &[u8]) -> Result<(u64, super::SimpleList), ::error::Error> {
self.validators.epoch_set(header, proof)
fn is_epoch_end(&self, first: bool, chain_head: &Header) -> Option<Vec<u8>> {
self.validators.is_epoch_end(first, chain_head)
}
fn signals_epoch_end(
&self,
first: bool,
header: &Header,
block: Option<&[u8]>,
receipts: Option<&[::receipt::Receipt]>,
) -> ::engines::EpochChange {
self.validators.signals_epoch_end(first, header, block, receipts)
}
fn epoch_set(&self, first: bool, engine: &Engine, number: BlockNumber, proof: &[u8]) -> Result<(SimpleList, Option<H256>), ::error::Error> {
self.validators.epoch_set(first, engine, number, proof)
}
fn contains_with_caller(&self, bh: &H256, address: &Address, caller: &Call) -> bool {
@ -92,14 +104,14 @@ impl ValidatorSet for ValidatorContract {
self.validators.count_with_caller(bh, caller)
}
fn report_malicious(&self, address: &Address, block: BlockNumber, proof: Bytes) {
fn report_malicious(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber, proof: Bytes) {
match self.provider.report_malicious(&*self.transact(), *address, block.into(), proof).wait() {
Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address),
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),
}
}
fn report_benign(&self, address: &Address, block: BlockNumber) {
fn report_benign(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber) {
match self.provider.report_benign(&*self.transact(), *address, block.into()).wait() {
Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address),
Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s),

View File

@ -37,7 +37,10 @@ use self::contract::ValidatorContract;
use self::safe_contract::ValidatorSafeContract;
use self::multi::Multi;
use super::Call;
use super::{Call, Engine};
/// A system-calling closure. Enacts calls on a block's state from the system address.
pub type SystemCall<'a> = FnMut(Address, Bytes) -> Result<Bytes, String> + 'a;
/// Creates a validator set from spec.
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
@ -64,6 +67,7 @@ pub trait ValidatorSet: Send + Sync {
let default = self.default_caller(BlockId::Hash(*parent));
self.contains_with_caller(parent, address, &*default)
}
/// Draws an validator nonce modulo number of validators.
fn get(&self, parent: &H256, nonce: usize) -> Address {
let default = self.default_caller(BlockId::Hash(*parent));
@ -76,48 +80,66 @@ pub trait ValidatorSet: Send + Sync {
self.count_with_caller(parent, &*default)
}
/// Signalling that a new epoch has begun.
///
/// All calls here will be from the `SYSTEM_ADDRESS`: 2^160 - 2
/// and will have an effect on the block's state.
/// The caller provided here may not generate proofs.
///
/// `first` is true if this is the first block in the set.
fn on_epoch_begin(&self, _first: bool, _header: &Header, _call: &mut SystemCall) -> Result<(), ::error::Error> {
Ok(())
}
/// Extract genesis epoch data from the genesis state and header.
fn genesis_epoch_data(&self, _header: &Header, _call: &Call) -> Result<Vec<u8>, String> { Ok(Vec::new()) }
/// 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.
/// Indicates that the validator set changed at the given block in a manner
/// that doesn't require finality.
///
/// 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;
/// `first` is true if this is the first block in the set.
fn is_epoch_end(&self, first: bool, chain_head: &Header) -> Option<Vec<u8>>;
/// 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>;
/// Whether the given block signals the end of an epoch, but change won't take effect
/// until finality.
///
/// Engine should set `first` only if the header is genesis. Multiplexing validator
/// sets can set `first` to internal changes.
fn signals_epoch_end(
&self,
first: bool,
header: &Header,
block: Option<&[u8]>,
receipts: Option<&[::receipt::Receipt]>,
) -> ::engines::EpochChange;
/// Recover the validator set for all
/// Recover the validator set from the given proof, the block number, and
/// whether this header is first in its set.
///
/// 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>;
/// Returns the set, along with a flag indicating whether finality of a specific
/// hash should be proven.
fn epoch_set(&self, first: bool, engine: &Engine, number: BlockNumber, proof: &[u8])
-> Result<(SimpleList, Option<H256>), ::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.
fn report_malicious(&self, _validator: &Address, _block: BlockNumber, _proof: Bytes) {}
fn report_malicious(&self, _validator: &Address, _set_block: BlockNumber, _block: BlockNumber, _proof: Bytes) {}
/// Notifies about benign misbehaviour.
fn report_benign(&self, _validator: &Address, _block: BlockNumber) {}
fn report_benign(&self, _validator: &Address, _set_block: BlockNumber, _block: BlockNumber) {}
/// Allows blockchain state access.
fn register_contract(&self, _client: Weak<Client>) {}
}

View File

@ -18,12 +18,12 @@
use std::collections::BTreeMap;
use std::sync::Weak;
use engines::{Call, EpochChange};
use engines::{Call, Engine};
use util::{Bytes, H256, Address, RwLock};
use ids::BlockId;
use header::{BlockNumber, Header};
use client::{Client, BlockChainClient};
use super::ValidatorSet;
use super::{SystemCall, ValidatorSet};
type BlockNumberLookup = Box<Fn(BlockId) -> Result<BlockNumber, String> + Send + Sync + 'static>;
@ -72,49 +72,38 @@ impl ValidatorSet for Multi {
.unwrap_or(Box::new(|_, _| Err("No validator set for given ID.".into())))
}
fn is_epoch_end(&self, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> EpochChange
fn on_epoch_begin(&self, _first: bool, header: &Header, call: &mut SystemCall) -> Result<(), ::error::Error> {
let (set_block, set) = self.correct_set_by_number(header.number());
let first = set_block == header.number();
set.on_epoch_begin(first, header, call)
}
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
self.correct_set_by_number(0).1.genesis_epoch_data(header, call)
}
fn is_epoch_end(&self, _first: bool, chain_head: &Header) -> Option<Vec<u8>> {
let (set_block, set) = self.correct_set_by_number(chain_head.number());
let first = set_block == chain_head.number();
set.is_epoch_end(first, chain_head)
}
fn signals_epoch_end(&self, _first: bool, header: &Header, block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
-> ::engines::EpochChange
{
let (set_block, set) = self.correct_set_by_number(header.number());
let (next_set_block, _) = self.correct_set_by_number(header.number() + 1);
let first = set_block == header.number();
// multi-set transitions require epoch changes.
if next_set_block != set_block {
return EpochChange::Yes(next_set_block);
}
match set.is_epoch_end(header, block, receipts) {
EpochChange::Yes(num) => EpochChange::Yes(set_block + num),
other => other,
}
set.signals_epoch_end(first, header, block, receipts)
}
fn epoch_proof(&self, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
let (set_block, set) = self.correct_set_by_number(header.number());
let (next_set_block, next_set) = self.correct_set_by_number(header.number() + 1);
fn epoch_set(&self, _first: bool, engine: &Engine, number: BlockNumber, proof: &[u8]) -> Result<(super::SimpleList, Option<H256>), ::error::Error> {
let (set_block, set) = self.correct_set_by_number(number);
let first = set_block == number;
if next_set_block != set_block {
return next_set.epoch_proof(header, caller);
}
set.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 (next_set_block, next_set) = self.correct_set_by_number(header.number() + 1);
// this block kicks off a new validator set -- get the validator set
// starting there.
if next_set_block != set_block {
let (inner_epoch, list) = next_set.epoch_set(header, proof)?;
Ok((next_set_block + inner_epoch, list))
} else {
let (inner_epoch, list) = set.epoch_set(header, proof)?;
Ok((set_block + inner_epoch, list))
}
set.epoch_set(first, engine, number, proof)
}
fn contains_with_caller(&self, bh: &H256, address: &Address, caller: &Call) -> bool {
@ -132,12 +121,12 @@ impl ValidatorSet for Multi {
.map_or_else(usize::max_value, |set| set.count_with_caller(bh, caller))
}
fn report_malicious(&self, validator: &Address, block: BlockNumber, proof: Bytes) {
self.correct_set_by_number(block).1.report_malicious(validator, block, proof);
fn report_malicious(&self, validator: &Address, set_block: BlockNumber, block: BlockNumber, proof: Bytes) {
self.correct_set_by_number(set_block).1.report_malicious(validator, set_block, block, proof);
}
fn report_benign(&self, validator: &Address, block: BlockNumber) {
self.correct_set_by_number(block).1.report_benign(validator, block);
fn report_benign(&self, validator: &Address, set_block: BlockNumber, block: BlockNumber) {
self.correct_set_by_number(set_block).1.report_benign(validator, set_block, block);
}
fn register_contract(&self, client: Weak<Client>) {
@ -153,18 +142,24 @@ impl ValidatorSet for Multi {
#[cfg(test)]
mod tests {
use util::*;
use types::ids::BlockId;
use spec::Spec;
use account_provider::AccountProvider;
use client::{BlockChainClient, EngineClient};
use engines::EpochChange;
use engines::validator_set::ValidatorSet;
use ethkey::Secret;
use header::Header;
use miner::MinerService;
use spec::Spec;
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
use types::ids::BlockId;
use util::*;
use super::Multi;
#[test]
fn uses_current_set() {
::env_logger::init().unwrap();
let _ = ::env_logger::init();
let tap = Arc::new(AccountProvider::transient_provider());
let s0: Secret = "0".sha3().into();
let v0 = tap.insert_account(s0.clone(), "").unwrap();
@ -205,4 +200,39 @@ mod tests {
sync_client.flush_queue();
assert_eq!(sync_client.chain_info().best_block_number, 3);
}
#[test]
fn transition_to_fixed_list_instant() {
use super::super::SimpleList;
let mut map: BTreeMap<_, Box<ValidatorSet>> = BTreeMap::new();
let list1: Vec<_> = (0..10).map(|_| Address::random()).collect();
let list2 = {
let mut list = list1.clone();
list.push(Address::random());
list
};
map.insert(0, Box::new(SimpleList::new(list1)));
map.insert(500, Box::new(SimpleList::new(list2)));
let multi = Multi::new(map);
let mut header = Header::new();
header.set_number(499);
match multi.signals_epoch_end(false, &header, None, None) {
EpochChange::No => {},
_ => panic!("Expected no epoch signal change."),
}
assert!(multi.is_epoch_end(false, &header).is_none());
header.set_number(500);
match multi.signals_epoch_end(false, &header, None, None) {
EpochChange::No => {},
_ => panic!("Expected no epoch signal change."),
}
assert!(multi.is_epoch_end(false, &header).is_some());
}
}

View File

@ -22,21 +22,23 @@ use native_contracts::ValidatorSet as Provider;
use util::*;
use util::cache::MemoryLruCache;
use rlp::{UntrustedRlp, RlpStream};
use basic_types::LogBloom;
use client::{Client, BlockChainClient};
use engines::Call;
use engines::{Call, Engine};
use header::Header;
use ids::BlockId;
use log_entry::LogEntry;
use receipt::Receipt;
use super::ValidatorSet;
use super::{SystemCall, ValidatorSet};
use super::simple_list::SimpleList;
const MEMOIZE_CAPACITY: usize = 500;
// TODO: ethabi should be able to generate this.
const EVENT_NAME: &'static [u8] = &*b"ValidatorsChanged(bytes32,uint256,address[])";
const EVENT_NAME: &'static [u8] = &*b"InitiateChange(bytes32,address[])";
lazy_static! {
static ref EVENT_NAME_HASH: H256 = EVENT_NAME.sha3();
@ -50,14 +52,68 @@ pub struct ValidatorSafeContract {
client: RwLock<Option<Weak<Client>>>, // TODO [keorn]: remove
}
fn encode_proof(nonce: U256, validators: &[Address]) -> Bytes {
use rlp::RlpStream;
// first proof is just a state proof call of `getValidators` at header's state.
fn encode_first_proof(header: &Header, state_items: &[Vec<u8>]) -> Bytes {
let mut stream = RlpStream::new_list(2);
stream.append(&nonce).append_list(validators);
stream.append(header).begin_list(state_items.len());
for item in state_items {
stream.append(item);
}
stream.out()
}
fn decode_first_proof(rlp: &UntrustedRlp) -> Result<(Header, Vec<DBValue>), ::error::Error> {
let header = rlp.val_at(0)?;
let state_items = rlp.at(1)?.iter().map(|x| {
let mut val = DBValue::new();
val.append_slice(x.data()?);
Ok(val)
}).collect::<Result<_, ::error::Error>>()?;
Ok((header, state_items))
}
// inter-contract proofs are a header and receipts.
// checking will involve ensuring that the receipts match the header and
// extracting the validator set from the receipts.
fn encode_proof(header: &Header, receipts: &[Receipt]) -> Bytes {
let mut stream = RlpStream::new_list(2);
stream.append(header).append_list(receipts);
stream.drain().to_vec()
}
fn decode_proof(rlp: &UntrustedRlp) -> Result<(Header, Vec<Receipt>), ::error::Error> {
Ok((rlp.val_at(0)?, rlp.list_at(1)?))
}
// given a provider and caller, generate proof. this will just be a state proof
// of `getValidators`.
fn prove_initial(provider: &Provider, header: &Header, caller: &Call) -> Result<Vec<u8>, String> {
use std::cell::RefCell;
let epoch_proof = RefCell::new(None);
let res = {
let caller = |a, d| {
let (result, proof) = caller(a, d)?;
*epoch_proof.borrow_mut() = Some(encode_first_proof(header, &proof));
Ok(result)
};
provider.get_validators(caller)
.wait()
};
res.map(|validators| {
let proof = epoch_proof.into_inner().expect("epoch_proof always set after call; qed");
trace!(target: "engine", "obtained proof for initial set: {} validators, {} bytes",
validators.len(), proof.len());
proof
})
}
impl ValidatorSafeContract {
pub fn new(contract_address: Address) -> Self {
ValidatorSafeContract {
@ -70,6 +126,7 @@ impl ValidatorSafeContract {
/// Queries the state and gets the set of validators.
fn get_list(&self, caller: &Call) -> Option<SimpleList> {
let caller = move |a, d| caller(a, d).map(|x| x.0);
match self.provider.get_validators(caller).wait() {
Ok(new) => {
debug!(target: "engine", "Set of validators obtained: {:?}", new);
@ -82,17 +139,6 @@ impl ValidatorSafeContract {
}
}
/// 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
}
}
}
// Whether the header matches the expected bloom.
//
// The expected log should have 3 topics:
@ -109,12 +155,70 @@ impl ValidatorSafeContract {
//
// The log data is an array of all new validator addresses.
fn expected_bloom(&self, header: &Header) -> LogBloom {
let topics = vec![*EVENT_NAME_HASH, *header.parent_hash()];
debug!(target: "engine", "Expected topics for header {}: {:?}",
header.hash(), topics);
LogEntry {
address: self.address,
topics: vec![*EVENT_NAME_HASH, *header.parent_hash()],
topics: topics,
data: Vec::new(), // irrelevant for bloom.
}.bloom()
}
// check receipts for log event. bloom should be `expected_bloom` for the
// header the receipts correspond to.
fn extract_from_event(&self, bloom: LogBloom, header: &Header, receipts: &[Receipt]) -> Option<SimpleList> {
let check_log = |log: &LogEntry| {
log.address == self.address &&
log.topics.len() == 2 &&
log.topics[0] == *EVENT_NAME_HASH &&
log.topics[1] == *header.parent_hash()
};
let event = Provider::contract(&self.provider)
.event("InitiateChange".into())
.expect("Contract known ahead of time to have `InitiateChange` 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();
event.decode_log(topics, log.data.clone()).ok()
});
match decoded_events.next() {
None => None,
Some(matched_event) => {
// decode log manually until the native contract generator is
// good enough to do it for us.
let &(_, _, ref validators_token) = &matched_event.params[1];
let validators = validators_token.clone().to_array()
.and_then(|a| a.into_iter()
.map(|x| x.to_address().map(H160))
.collect::<Option<Vec<_>>>()
)
.map(SimpleList::new);
if validators.is_none() {
debug!(target: "engine", "Successfully decoded log turned out to be bad.");
}
trace!(target: "engine", "decoded log. validators: {:?}", validators);
validators
}
}
}
}
impl ValidatorSet for ValidatorSafeContract {
@ -123,97 +227,144 @@ impl ValidatorSet for ValidatorSafeContract {
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)))
.and_then(|c| c.call_contract(id, addr, data))
.map(|out| (out, Vec::new()))) // generate no proofs in general
}
fn is_epoch_end(&self, header: &Header, _block: Option<&[u8]>, receipts: Option<&[::receipt::Receipt]>)
fn on_epoch_begin(&self, first: bool, _header: &Header, caller: &mut SystemCall) -> Result<(), ::error::Error> {
if first { return Ok(()) } // only signalled changes need to be noted.
self.provider.finalize_change(caller)
.wait()
.map_err(::engines::EngineError::FailedSystemCall)
.map_err(Into::into)
}
fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result<Vec<u8>, String> {
prove_initial(&self.provider, header, call)
}
fn is_epoch_end(&self, _first: bool, _chain_head: &Header) -> Option<Vec<u8>> {
None // no immediate transitions to contract.
}
fn signals_epoch_end(&self, first: bool, header: &Header, _block: Option<&[u8]>, receipts: Option<&[Receipt]>)
-> ::engines::EpochChange
{
// transition to the first block of a contract requires finality but has no log event.
if first {
debug!(target: "engine", "signalling transition to fresh contract.");
let (provider, header) = (self.provider.clone(), header.clone());
let with_caller: Box<Fn(&Call) -> _> = Box::new(move |caller| prove_initial(&provider, &header, caller));
return ::engines::EpochChange::Yes(::engines::Proof::WithState(with_caller))
}
// otherwise, we're checking for logs.
let bloom = self.expected_bloom(header);
let header_bloom = header.log_bloom();
if &bloom & header_bloom != bloom { return ::engines::EpochChange::No }
trace!(target: "engine", "detected epoch change event bloom");
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.
};
Some(receipts) => match self.extract_from_event(bloom, header, receipts) {
None => ::engines::EpochChange::No,
Some(_) => {
debug!(target: "engine", "signalling transition within contract");
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(_)) => {
let new_epoch = nonce.low_u64();
::engines::EpochChange::Yes(new_epoch)
}
_ => {
debug!(target: "engine", "Successfully decoded log turned out to be bad.");
::engines::EpochChange::No
}
}
}
let proof = encode_proof(&header, receipts);
::engines::EpochChange::Yes(::engines::Proof::Known(proof))
}
}
},
}
}
// 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;
fn epoch_set(&self, first: bool, engine: &Engine, _number: ::header::BlockNumber, proof: &[u8])
-> Result<(SimpleList, Option<H256>), ::error::Error>
{
use transaction::{Action, Transaction};
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)))
if first {
trace!(target: "engine", "Recovering initial epoch set");
// TODO: match client contract_call_tx more cleanly without duplication.
const PROVIDED_GAS: u64 = 50_000_000;
let (old_header, state_items) = decode_first_proof(&rlp)?;
let old_hash = old_header.hash();
let env_info = ::env_info::EnvInfo {
number: old_header.number(),
author: *old_header.author(),
difficulty: *old_header.difficulty(),
gas_limit: PROVIDED_GAS.into(),
timestamp: old_header.timestamp(),
last_hashes: {
// this will break if we don't inclue all 256 last hashes.
let mut last_hashes: Vec<_> = (0..256).map(|_| H256::default()).collect();
last_hashes[255] = *old_header.parent_hash();
Arc::new(last_hashes)
},
gas_used: 0.into(),
};
// check state proof using given engine.
let number = old_header.number();
let addresses = self.provider.get_validators(move |a, d| {
let from = Address::default();
let tx = Transaction {
nonce: engine.account_start_nonce(number),
action: Action::Call(a),
gas: PROVIDED_GAS.into(),
gas_price: U256::default(),
value: U256::default(),
data: d,
}.fake_sign(from);
let res = ::state::check_proof(
&state_items,
*old_header.state_root(),
&tx,
engine,
&env_info,
);
match res {
::state::ProvedExecution::BadProof => Err("Bad proof".into()),
::state::ProvedExecution::Failed(e) => Err(format!("Failed call: {}", e)),
::state::ProvedExecution::Complete(e) => Ok(e.output),
}
}).wait().map_err(::engines::EngineError::InsufficientProof)?;
trace!(target: "engine", "extracted epoch set at #{}: {} addresses",
number, addresses.len());
Ok((SimpleList::new(addresses), Some(old_hash)))
} else {
let (old_header, receipts) = decode_proof(&rlp)?;
// ensure receipts match header.
// TODO: optimize? these were just decoded.
let found_root = ::util::triehash::ordered_trie_root(
receipts.iter().map(::rlp::encode).map(|x| x.to_vec())
);
if found_root != *old_header.receipts_root() {
return Err(::error::BlockError::InvalidReceiptsRoot(
Mismatch { expected: *old_header.receipts_root(), found: found_root }
).into());
}
let bloom = self.expected_bloom(&old_header);
match self.extract_from_event(bloom, &old_header, &receipts) {
Some(list) => Ok((list, Some(old_header.hash()))),
None => Err(::engines::EngineError::InsufficientProof("No log event in proof.".into()).into()),
}
}
}
fn contains_with_caller(&self, block_hash: &H256, address: &Address, caller: &Call) -> bool {
@ -371,6 +522,7 @@ mod tests {
let last_hash = client.best_block_header().hash();
let mut new_header = Header::default();
new_header.set_parent_hash(last_hash);
new_header.set_number(1); // so the validator set looks for a log.
// first, try without the parent hash.
let mut event = LogEntry {
@ -380,12 +532,35 @@ mod tests {
};
new_header.set_log_bloom(event.bloom());
assert_eq!(engine.is_epoch_end(&new_header, None, None), EpochChange::No);
match engine.signals_epoch_end(&new_header, None, None) {
EpochChange::No => {},
_ => panic!("Expected bloom to be unrecognized."),
};
// 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));
match engine.signals_epoch_end(&new_header, None, None) {
EpochChange::Unsure(Unsure::NeedsReceipts) => {},
_ => panic!("Expected bloom to be recognized."),
};
}
#[test]
fn initial_contract_is_signal() {
use header::Header;
use engines::{EpochChange, Proof};
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
let engine = client.engine().clone();
let mut new_header = Header::default();
new_header.set_number(0); // so the validator set doesn't look for a log
match engine.signals_epoch_end(&new_header, None, None) {
EpochChange::Yes(Proof::WithState(_)) => {},
_ => panic!("Expected state to be required to prove initial signal"),
};
}
}

View File

@ -18,8 +18,8 @@
use util::{H256, Address, HeapSizeOf};
use engines::Call;
use header::Header;
use engines::{Call, Engine};
use header::{BlockNumber, Header};
use super::ValidatorSet;
/// Validator set containing a known set of addresses.
@ -42,6 +42,20 @@ impl SimpleList {
}
}
impl ::std::ops::Deref for SimpleList {
type Target = [Address];
fn deref(&self) -> &[Address] { &self.validators }
}
impl From<Vec<Address>> for SimpleList {
fn from(validators: Vec<Address>) -> Self {
SimpleList {
validators: validators,
}
}
}
impl HeapSizeOf for SimpleList {
fn heap_size_of_children(&self) -> usize {
self.validators.heap_size_of_children()
@ -53,18 +67,21 @@ impl ValidatorSet for SimpleList {
Box::new(|_, _| Err("Simple list doesn't require calls.".into()))
}
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[::receipt::Receipt]>)
fn is_epoch_end(&self, first: bool, _chain_head: &Header) -> Option<Vec<u8>> {
match first {
true => Some(Vec::new()), // allow transition to fixed list, and instantly
false => None,
}
}
fn signals_epoch_end(&self, _: bool, _: &Header, _: Option<&[u8]>, _: 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 epoch_set(&self, _first: bool, _: &Engine, _: BlockNumber, _: &[u8]) -> Result<(SimpleList, Option<H256>), ::error::Error> {
Ok((self.clone(), None))
}
fn contains_with_caller(&self, _bh: &H256, address: &Address, _: &Call) -> bool {
@ -73,6 +90,11 @@ impl ValidatorSet for SimpleList {
fn get_with_caller(&self, _bh: &H256, nonce: usize, _: &Call) -> Address {
let validator_n = self.validators.len();
if validator_n == 0 {
panic!("Cannot operate with an empty validator set.");
}
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()
}

View File

@ -20,7 +20,7 @@ use std::str::FromStr;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use util::{Arc, Bytes, H256, Address, HeapSizeOf};
use engines::Call;
use engines::{Call, Engine};
use header::{Header, BlockNumber};
use super::{ValidatorSet, SimpleList};
@ -52,18 +52,16 @@ impl ValidatorSet for TestSet {
Box::new(|_, _| Err("Test set doesn't require calls.".into()))
}
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[::receipt::Receipt]>)
fn is_epoch_end(&self, _first: bool, _chain_head: &Header) -> Option<Vec<u8>> { None }
fn signals_epoch_end(&self, _: bool, _: &Header, _: Option<&[u8]>, _: 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.validator.clone()))
fn epoch_set(&self, _: bool, _: &Engine, _: BlockNumber, _: &[u8]) -> Result<(SimpleList, Option<H256>), ::error::Error> {
Ok((self.validator.clone(), None))
}
fn contains_with_caller(&self, bh: &H256, address: &Address, _: &Call) -> bool {
@ -78,11 +76,11 @@ impl ValidatorSet for TestSet {
1
}
fn report_malicious(&self, _validator: &Address, block: BlockNumber, _proof: Bytes) {
fn report_malicious(&self, _validator: &Address, _set_block: BlockNumber, block: BlockNumber, _proof: Bytes) {
self.last_malicious.store(block as usize, AtomicOrdering::SeqCst)
}
fn report_benign(&self, _validator: &Address, block: BlockNumber) {
fn report_benign(&self, _validator: &Address, _set_block: BlockNumber, block: BlockNumber) {
self.last_benign.store(block as usize, AtomicOrdering::SeqCst)
}
}

View File

@ -165,6 +165,8 @@ pub enum BlockError {
InvalidNumber(Mismatch<BlockNumber>),
/// Block number isn't sensible.
RidiculousNumber(OutOfBounds<BlockNumber>),
/// Too many transactions from a particular address.
TooManyTransactions(Address),
/// Parent given is unknown.
UnknownParent(H256),
/// Uncle parent given is unknown.
@ -205,6 +207,7 @@ impl fmt::Display for BlockError {
UnknownParent(ref hash) => format!("Unknown parent: {}", hash),
UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash),
UnknownEpochTransition(ref num) => format!("Unknown transition to epoch number: {}", num),
TooManyTransactions(ref address) => format!("Too many transactions from: {}", address),
};
f.write_fmt(format_args!("Block error ({})", msg))

View File

@ -166,7 +166,6 @@ impl Ethash {
// 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)
@ -256,7 +255,12 @@ impl Engine for Arc<Ethash> {
// info!("ethash: populate_from_parent #{}: difficulty={} and gas_limit={}", header.number(), header.difficulty(), header.gas_limit());
}
fn on_new_block(&self, block: &mut ExecutedBlock, last_hashes: Arc<LastHashes>) -> Result<(), Error> {
fn on_new_block(
&self,
block: &mut ExecutedBlock,
last_hashes: Arc<LastHashes>,
_begins_epoch: bool,
) -> Result<(), Error> {
let parent_hash = block.fields().header.parent_hash().clone();
::engines::common::push_last_hash(block, last_hashes, self, &parent_hash)?;
if block.fields().header.number() == self.ethash_params.dao_hardfork_transition {
@ -400,8 +404,8 @@ impl Engine for Arc<Ethash> {
Ok(())
}
fn epoch_verifier(&self, _header: &Header, _proof: &[u8]) -> Result<Box<::engines::EpochVerifier>, Error> {
Ok(Box::new(self.clone()))
fn epoch_verifier<'a>(&self, _header: &Header, _proof: &'a [u8]) -> ::engines::ConstructedVerifier<'a> {
::engines::ConstructedVerifier::Trusted(Box::new(self.clone()))
}
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
@ -559,7 +563,7 @@ mod tests {
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = b.close();
assert_eq!(b.state().balance(&Address::zero()).unwrap(), U256::from_str("4563918244f40000").unwrap());
}
@ -571,7 +575,7 @@ mod tests {
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let mut b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
let mut b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let mut uncle = Header::new();
let uncle_author: Address = "ef2d6d194084c2de36e0dabfce45d046b37d1106".into();
uncle.set_author(uncle_author);

View File

@ -98,8 +98,7 @@ mod tests {
let engine = &spec.engine;
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let s = State::from_existing(db, genesis_header.state_root().clone(), engine.account_start_nonce(), Default::default()).unwrap();
let s = State::from_existing(db, genesis_header.state_root().clone(), engine.account_start_nonce(0), Default::default()).unwrap();
assert_eq!(s.balance(&"0000000000000000000000000000000000000001".into()).unwrap(), 1u64.into());
assert_eq!(s.balance(&"0000000000000000000000000000000000000002".into()).unwrap(), 1u64.into());
assert_eq!(s.balance(&"0000000000000000000000000000000000000003".into()).unwrap(), 1u64.into());

View File

@ -38,5 +38,5 @@ pub use self::ext::{Ext, ContractCreateResult, MessageCallResult, CreateContract
pub use self::instructions::{InstructionInfo, INSTRUCTIONS, push_bytes};
pub use self::vmtype::VMType;
pub use self::factory::Factory;
pub use self::schedule::Schedule;
pub use self::schedule::{Schedule, CleanDustMode};
pub use types::executed::CallType;

View File

@ -108,6 +108,19 @@ pub struct Schedule {
pub blockhash_gas: usize,
/// Static Call opcode enabled.
pub have_static_call: bool,
/// Kill basic accounts below this balance if touched.
pub kill_dust: CleanDustMode,
}
/// Dust accounts cleanup mode.
#[derive(PartialEq, Eq)]
pub enum CleanDustMode {
/// Dust cleanup is disabled.
Off,
/// Basic dust accounts will be removed.
BasicOnly,
/// Basic and contract dust accounts will be removed.
WithCodeAndStorage,
}
impl Schedule {
@ -168,15 +181,16 @@ impl Schedule {
kill_empty: kill_empty,
blockhash_gas: 20,
have_static_call: false,
kill_dust: CleanDustMode::Off,
}
}
/// Schedule for the Metropolis era from common spec params.
/// Schedule for the post-EIP-150-era of the Ethereum main net.
pub fn from_params(block_number: u64, params: &CommonParams) -> Schedule {
let mut schedule = Schedule::new_post_eip150(usize::max_value(), true, true, true);
schedule.apply_params(block_number, params);
schedule
}
}
/// Apply common spec config parameters to the schedule.
pub fn apply_params(&mut self, block_number: u64, params: &CommonParams) {
@ -186,6 +200,9 @@ impl Schedule {
if block_number >= params.eip210_transition {
self.blockhash_gas = 350;
}
if block_number >= params.dust_protection_transition {
self.kill_dust = if params.remove_dust_contracts { CleanDustMode::WithCodeAndStorage } else { CleanDustMode::BasicOnly };
}
}
/// Schedule for the Metropolis of the Ethereum main net.
@ -244,6 +261,7 @@ impl Schedule {
kill_empty: false,
blockhash_gas: 20,
have_static_call: false,
kill_dust: CleanDustMode::Off,
}
}
}

View File

@ -22,7 +22,7 @@ use engines::Engine;
use types::executed::CallType;
use env_info::EnvInfo;
use error::ExecutionError;
use evm::{self, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData};
use evm::{self, Ext, Finalize, CreateContractAddress, FinalizationResult, ReturnData, CleanDustMode};
use externalities::*;
use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer};
use transaction::{Action, SignedTransaction};
@ -164,6 +164,10 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
return Err(From::from(ExecutionError::NotEnoughBaseGas { required: base_gas_required, got: t.gas }));
}
if !t.is_unsigned() && check_nonce && schedule.kill_dust != CleanDustMode::Off && !self.state.exists(&sender)? {
return Err(From::from(ExecutionError::SenderMustExist));
}
let init_gas = t.gas - base_gas_required;
// validate transaction nonce
@ -191,13 +195,13 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
return Err(From::from(ExecutionError::NotEnoughCash { required: total_cost, got: balance512 }));
}
let mut substate = Substate::new();
// NOTE: there can be no invalid transactions from this point.
if !t.is_unsigned() {
self.state.inc_nonce(&sender)?;
}
self.state.sub_balance(&sender, &U256::from(gas_cost))?;
let mut substate = Substate::new();
self.state.sub_balance(&sender, &U256::from(gas_cost), &mut substate.to_cleanup_mode(&schedule))?;
let (result, output) = match t.action {
Action::Create => {
@ -434,7 +438,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
let nonce_offset = if schedule.no_empty {1} else {0}.into();
let prev_bal = self.state.balance(&params.address)?;
if let ActionValue::Transfer(val) = params.value {
self.state.sub_balance(&params.sender, &val)?;
self.state.sub_balance(&params.sender, &val, &mut substate.to_cleanup_mode(&schedule))?;
self.state.new_contract(&params.address, val + prev_bal, nonce_offset);
} else {
self.state.new_contract(&params.address, prev_bal, nonce_offset);
@ -512,11 +516,8 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
}
// perform garbage-collection
for address in &substate.garbage {
if self.state.exists(address)? && !self.state.exists_and_not_null(address)? {
self.state.kill_account(address);
}
}
let min_balance = if schedule.kill_dust != CleanDustMode::Off { Some(U256::from(schedule.tx_gas) * t.gas_price) } else { None };
self.state.kill_garbage(&substate.touched, schedule.kill_empty, &min_balance, schedule.kill_dust == CleanDustMode::WithCodeAndStorage)?;
match result {
Err(evm::Error::Internal(msg)) => Err(ExecutionError::Internal(msg)),

View File

@ -17,7 +17,7 @@
//! Transaction Execution environment.
use util::*;
use action_params::{ActionParams, ActionValue};
use state::{Backend as StateBackend, State, Substate};
use state::{Backend as StateBackend, State, Substate, CleanupMode};
use engines::Engine;
use env_info::EnvInfo;
use executive::*;
@ -347,7 +347,7 @@ impl<'a, T: 'a, V: 'a, B: 'a, E: 'a> Ext for Externalities<'a, T, V, B, E>
let balance = self.balance(&address)?;
if &address == refund_address {
// TODO [todr] To be consistent with CPP client we set balance to 0 in that case.
self.state.sub_balance(&address, &balance)?;
self.state.sub_balance(&address, &balance, &mut CleanupMode::NoEmpty)?;
} else {
trace!(target: "ext", "Suiciding {} -> {} (xfer: {})", address, refund_address, balance);
self.state.transfer_balance(

View File

@ -51,7 +51,6 @@ impl ToV9 {
}
impl Migration for ToV9 {
fn columns(&self) -> Option<u32> { Some(5) }
fn version(&self) -> u32 { 9 }

View File

@ -328,7 +328,10 @@ impl Miner {
let _timer = PerfTimer::new("prepare_block");
let chain_info = chain.chain_info();
let (transactions, mut open_block, original_work_hash) = {
let transactions = {self.transaction_queue.read().top_transactions_at(chain_info.best_block_number, chain_info.best_block_timestamp)};
let nonce_cap = if chain_info.best_block_number + 1 >= self.engine.params().dust_protection_transition {
Some((self.engine.params().nonce_cap_increment * (chain_info.best_block_number + 1)).into())
} else { None };
let transactions = {self.transaction_queue.read().top_transactions_at(chain_info.best_block_number, chain_info.best_block_timestamp, nonce_cap)};
let mut sealing_work = self.sealing_work.lock();
let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash());
let best_hash = chain_info.best_block_hash;

View File

@ -1084,11 +1084,11 @@ impl TransactionQueue {
/// Returns top transactions from the queue ordered by priority.
pub fn top_transactions(&self) -> Vec<SignedTransaction> {
self.top_transactions_at(BlockNumber::max_value(), u64::max_value())
self.top_transactions_at(BlockNumber::max_value(), u64::max_value(), None)
}
fn filter_pending_transaction<F>(&self, best_block: BlockNumber, best_timestamp: u64, mut f: F)
fn filter_pending_transaction<F>(&self, best_block: BlockNumber, best_timestamp: u64, nonce_cap: Option<U256>, mut f: F)
where F: FnMut(&VerifiedTransaction) {
let mut delayed = HashSet::new();
@ -1098,6 +1098,11 @@ impl TransactionQueue {
if delayed.contains(&sender) {
continue;
}
if let Some(max_nonce) = nonce_cap {
if tx.nonce() >= max_nonce {
continue;
}
}
let delay = match tx.condition {
Some(Condition::Number(n)) => n > best_block,
Some(Condition::Timestamp(t)) => t > best_timestamp,
@ -1112,16 +1117,16 @@ impl TransactionQueue {
}
/// Returns top transactions from the queue ordered by priority.
pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<SignedTransaction> {
pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64, nonce_cap: Option<U256>) -> Vec<SignedTransaction> {
let mut r = Vec::new();
self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(tx.transaction.clone()));
self.filter_pending_transaction(best_block, best_timestamp, nonce_cap, |tx| r.push(tx.transaction.clone()));
r
}
/// Return all ready transactions.
pub fn pending_transactions(&self, best_block: BlockNumber, best_timestamp: u64) -> Vec<PendingTransaction> {
let mut r = Vec::new();
self.filter_pending_transaction(best_block, best_timestamp, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.condition.clone())));
self.filter_pending_transaction(best_block, best_timestamp, None, |tx| r.push(PendingTransaction::new(tx.transaction.clone(), tx.condition.clone())));
r
}
@ -2205,9 +2210,9 @@ pub mod test {
// then
assert_eq!(res1, TransactionImportResult::Current);
assert_eq!(res2, TransactionImportResult::Current);
let top = txq.top_transactions_at(0, 0);
let top = txq.top_transactions_at(0, 0, None);
assert_eq!(top.len(), 0);
let top = txq.top_transactions_at(1, 0);
let top = txq.top_transactions_at(1, 0, None);
assert_eq!(top.len(), 2);
}
@ -2809,4 +2814,19 @@ pub mod test {
// then
assert_eq!(txq.top_transactions().len(), 1);
}
#[test]
fn should_not_return_transactions_over_nonce_cap() {
// given
let keypair = Random.generate().unwrap();
let mut txq = TransactionQueue::default();
// when
for nonce in 123..130 {
let tx = new_unsigned_tx(nonce.into(), default_gas_val(), default_gas_price()).sign(keypair.secret(), None);
txq.add(tx, TransactionOrigin::External, 0, None, &default_tx_provider()).unwrap();
}
// then
assert_eq!(txq.top_transactions_at(BlockNumber::max_value(), u64::max_value(), Some(127.into())).len(), 4);
}
}

View File

@ -24,34 +24,28 @@ use super::{SnapshotComponents, Rebuilder, ChunkSink};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use blockchain::{BlockChain, BlockProvider, EpochTransition};
use engines::{Engine, EpochVerifier};
use env_info::EnvInfo;
use blockchain::{BlockChain, BlockProvider};
use engines::{Engine, EpochVerifier, EpochTransition};
use ids::BlockId;
use header::Header;
use receipt::Receipt;
use snapshot::{Error, ManifestData};
use state_db::StateDB;
use itertools::{Position, Itertools};
use rlp::{RlpStream, UntrustedRlp};
use util::{Address, Bytes, H256, KeyValueDB, DBValue};
use util::{Bytes, H256, KeyValueDB};
/// Snapshot creation and restoration for PoA chains.
/// Chunk format:
///
/// [FLAG, [header, epoch_number, epoch data, state proof, last hashes], ...]
/// [FLAG, [header, epoch data], ...]
/// - Header data at which transition occurred,
/// - epoch data (usually list of validators)
/// - state items required to check epoch data
/// - last 256 hashes before the transition; required for checking state changes.
/// - epoch data (usually list of validators and proof of change)
///
/// FLAG is a bool: true for last chunk, false otherwise.
///
/// The last item of the last chunk will be a list containing data for the warp target block:
/// [header, transactions, uncles, receipts, last_hashes, parent_td].
/// If this block is not a transition block, the epoch data should be the same as that
/// for the last transition.
/// [header, transactions, uncles, receipts, parent_td].
pub struct PoaSnapshot;
impl SnapshotComponents for PoaSnapshot {
@ -68,35 +62,23 @@ impl SnapshotComponents for PoaSnapshot {
let mut pending_size = 0;
let mut rlps = Vec::new();
// TODO: this will become irrelevant after recent block hashes are moved into
// the state. can we optimize it out in that case?
let make_last_hashes = |parent_hash| chain.ancestry_iter(parent_hash)
.into_iter()
.flat_map(|inner| inner)
.take(255)
.collect::<Vec<_>>();
for (epoch_number, transition) in chain.epoch_transitions()
for (_, transition) in chain.epoch_transitions()
.take_while(|&(_, ref t)| t.block_number <= number)
{
// this can happen when our starting block is non-canonical.
if transition.block_number == number && transition.block_hash != block_at {
break
}
let header = chain.block_header_data(&transition.block_hash)
.ok_or(Error::BlockNotFound(transition.block_hash))?;
let last_hashes: Vec<_> = make_last_hashes(header.parent_hash());
let entry = {
let mut entry_stream = RlpStream::new_list(5);
let mut entry_stream = RlpStream::new_list(2);
entry_stream
.append_raw(&header.into_inner(), 1)
.append(&epoch_number)
.append(&transition.proof);
entry_stream.begin_list(transition.state_proof.len());
for item in transition.state_proof {
entry_stream.append(&&*item);
}
entry_stream.append_list(&last_hashes);
entry_stream.out()
};
@ -121,16 +103,13 @@ impl SnapshotComponents for PoaSnapshot {
.map(|d| d.total_difficulty)
.ok_or(Error::BlockNotFound(block_at))?;
let last_hashes = make_last_hashes(*block.header.parent_hash());
rlps.push({
let mut stream = RlpStream::new_list(6);
let mut stream = RlpStream::new_list(5);
stream
.append(&block.header)
.append_list(&block.transactions)
.append_list(&block.uncles)
.append(&receipts)
.append_list(&last_hashes)
.append(&parent_td);
stream.out()
});
@ -153,7 +132,7 @@ impl SnapshotComponents for PoaSnapshot {
db: db,
had_genesis: false,
unverified_firsts: Vec::new(),
last_proofs: Vec::new(),
last_epochs: Vec::new(),
}))
}
@ -178,7 +157,7 @@ fn write_chunk(last: bool, chunk_data: &mut Vec<Bytes>, sink: &mut ChunkSink) ->
// transition header is verifiable from the epoch data of the one prior.
struct ChunkRebuilder {
manifest: ManifestData,
warp_target: Option<(Header, Vec<H256>)>,
warp_target: Option<Header>,
chain: BlockChain,
db: Arc<KeyValueDB>,
had_genesis: bool,
@ -186,52 +165,16 @@ struct ChunkRebuilder {
// sorted vectors of unverified first blocks in a chunk
// and epoch data from last blocks in chunks.
// verification for these will be done at the end.
unverified_firsts: Vec<(u64, Header)>,
last_proofs: Vec<(u64, Header, Bytes)>,
unverified_firsts: Vec<(Header, Bytes, H256)>,
last_epochs: Vec<(Header, Box<EpochVerifier>)>,
}
// verified data.
struct Verified {
epoch_number: u64,
epoch_transition: EpochTransition,
header: Header,
}
// make a transaction and env info.
// TODO: hardcoded 50M to match constants in client.
// would be nice to extract magic numbers, or better yet
// off-chain transaction execution, into its own module.
fn make_tx_and_env(
engine: &Engine,
addr: Address,
data: Bytes,
header: &Header,
last_hashes: Arc<Vec<H256>>,
) -> (::transaction::SignedTransaction, EnvInfo) {
use transaction::{Action, Transaction};
let transaction = Transaction {
nonce: engine.account_start_nonce(),
action: Action::Call(addr),
gas: 50_000_000.into(),
gas_price: 0.into(),
value: 0.into(),
data: data,
}.fake_sign(Default::default());
let env = EnvInfo {
number: header.number(),
author: *header.author(),
timestamp: header.timestamp(),
difficulty: *header.difficulty(),
gas_limit: 50_000_000.into(),
last_hashes: last_hashes,
gas_used: 0.into(),
};
(transaction, env)
}
impl ChunkRebuilder {
fn verify_transition(
&mut self,
@ -239,69 +182,49 @@ impl ChunkRebuilder {
transition_rlp: UntrustedRlp,
engine: &Engine,
) -> Result<Verified, ::error::Error> {
use engines::ConstructedVerifier;
// decode.
let header: Header = transition_rlp.val_at(0)?;
let epoch_number: u64 = transition_rlp.val_at(1)?;
let epoch_data: Bytes = transition_rlp.val_at(2)?;
let state_proof: Vec<DBValue> = transition_rlp.at(3)?
.iter()
.map(|x| Ok(DBValue::from_slice(x.data()?)))
.collect::<Result<_, ::rlp::DecoderError>>()?;
let last_hashes: Vec<H256> = transition_rlp.list_at(4)?;
let last_hashes = Arc::new(last_hashes);
let epoch_data: Bytes = transition_rlp.val_at(1)?;
trace!(target: "snapshot", "verifying transition to epoch {}", epoch_number);
trace!(target: "snapshot", "verifying transition to epoch at block {}", header.number());
// check current transition against validators of last epoch.
if let Some(verifier) = last_verifier.as_ref() {
verifier.verify_heavy(&header)?;
}
let new_verifier = match engine.epoch_verifier(&header, &epoch_data) {
ConstructedVerifier::Trusted(v) => v,
ConstructedVerifier::Unconfirmed(v, finality_proof, hash) => {
match *last_verifier {
Some(ref last) =>
if last.check_finality_proof(finality_proof).map_or(true, |hashes| !hashes.contains(&hash))
{
return Err(Error::BadEpochProof(header.number()).into());
},
None if header.number() != 0 => {
// genesis never requires additional validation.
{
// check the provided state proof actually leads to the
// given epoch data.
let caller = |addr, data| {
use state::{check_proof, ProvedExecution};
let idx = self.unverified_firsts
.binary_search_by_key(&header.number(), |&(ref h, _, _)| h.number())
.unwrap_or_else(|x| x);
let (transaction, env_info) = make_tx_and_env(
engine,
addr,
data,
&header,
last_hashes.clone(),
);
let result = check_proof(
&state_proof,
header.state_root().clone(),
&transaction,
engine,
&env_info,
);
match result {
ProvedExecution::Complete(executed) => Ok(executed.output),
_ => Err("Bad state proof".into()),
let entry = (header.clone(), finality_proof.to_owned(), hash);
self.unverified_firsts.insert(idx, entry);
}
None => {}
}
};
let extracted_proof = engine.epoch_proof(&header, &caller)
.map_err(|_| Error::BadEpochProof(epoch_number))?;
if extracted_proof != epoch_data {
return Err(Error::BadEpochProof(epoch_number).into());
v
}
}
ConstructedVerifier::Err(e) => return Err(e),
};
// create new epoch verifier.
*last_verifier = Some(engine.epoch_verifier(&header, &epoch_data)?);
*last_verifier = Some(new_verifier);
Ok(Verified {
epoch_number: epoch_number,
epoch_transition: EpochTransition {
block_hash: header.hash(),
block_number: header.number(),
state_proof: state_proof,
proof: epoch_data,
},
header: header,
@ -327,6 +250,10 @@ impl Rebuilder for ChunkRebuilder {
num_items - 1
};
if num_transitions == 0 && !is_last_chunk {
return Err(Error::WrongChunkFormat("Found non-last chunk without any data.".into()).into());
}
let mut last_verifier = None;
let mut last_number = None;
for transition_rlp in rlp.iter().skip(1).take(num_transitions).with_position() {
@ -356,41 +283,33 @@ impl Rebuilder for ChunkRebuilder {
if is_first {
// make sure the genesis transition was included,
// but it doesn't need verification later.
if verified.epoch_number == 0 && verified.header.number() == 0 {
if verified.header.number() == 0 {
if verified.header.hash() != self.chain.genesis_hash() {
return Err(Error::WrongBlockHash(0, verified.header.hash(), self.chain.genesis_hash()).into());
}
self.had_genesis = true;
} else {
let idx = self.unverified_firsts
.binary_search_by_key(&verified.epoch_number, |&(a, _)| a)
.unwrap_or_else(|x| x);
let entry = (verified.epoch_number, verified.header.clone());
self.unverified_firsts.insert(idx, entry);
}
}
if is_last {
let idx = self.last_proofs
.binary_search_by_key(&verified.epoch_number, |&(a, _, _)| a)
let idx = self.last_epochs
.binary_search_by_key(&verified.header.number(), |&(ref h, _)| h.number())
.unwrap_or_else(|x| x);
let entry = (
verified.epoch_number,
verified.header.clone(),
verified.epoch_transition.proof.clone()
last_verifier.take().expect("last_verifier always set after verify_transition; qed"),
);
self.last_proofs.insert(idx, entry);
self.last_epochs.insert(idx, entry);
}
// write epoch transition into database.
let mut batch = self.db.transaction();
self.chain.insert_epoch_transition(&mut batch, verified.epoch_number,
self.chain.insert_epoch_transition(&mut batch, verified.header.number(),
verified.epoch_transition);
self.db.write_buffered(batch);
trace!(target: "snapshot", "Verified epoch transition for epoch {}", verified.epoch_number);
trace!(target: "snapshot", "Verified epoch transition for epoch at block {}", verified.header.number());
}
if is_last_chunk {
@ -413,84 +332,57 @@ impl Rebuilder for ChunkRebuilder {
}
}
let last_hashes: Vec<H256> = last_rlp.list_at(4)?;
let parent_td: ::util::U256 = last_rlp.val_at(5)?;
let parent_td: ::util::U256 = last_rlp.val_at(4)?;
let mut batch = self.db.transaction();
self.chain.insert_unordered_block(&mut batch, &block_data, receipts, Some(parent_td), true, false);
self.db.write_buffered(batch);
self.warp_target = Some((block.header, last_hashes));
self.warp_target = Some(block.header);
}
Ok(())
}
fn finalize(&mut self, db: StateDB, engine: &Engine) -> Result<(), ::error::Error> {
use state::State;
fn finalize(&mut self, _engine: &Engine) -> Result<(), ::error::Error> {
if !self.had_genesis {
return Err(Error::WrongChunkFormat("No genesis transition included.".into()).into());
}
let (target_header, target_last_hashes) = match self.warp_target.take() {
let target_header = match self.warp_target.take() {
Some(x) => x,
None => return Err(Error::WrongChunkFormat("Warp target block not included.".into()).into()),
};
// we store the last data even for the last chunk for easier verification
// of warp target, but we don't store genesis transition data.
// other than that, there should be a one-to-one correspondence of
// chunk ends to chunk beginnings.
if self.last_proofs.len() != self.unverified_firsts.len() + 1 {
return Err(Error::WrongChunkFormat("More than one 'last' chunk".into()).into());
}
// verify the first entries of chunks we couldn't before.
let lasts_iter = self.last_proofs.iter().map(|&(_, ref hdr, ref proof)| (hdr, &proof[..]));
let firsts_iter = self.unverified_firsts.iter().map(|&(_, ref hdr)| hdr);
// we store all last verifiers, but not all firsts.
// match each unverified first epoch with a last epoch verifier.
let mut lasts_reversed = self.last_epochs.iter().rev();
for &(ref header, ref finality_proof, hash) in self.unverified_firsts.iter().rev() {
let mut found = false;
while let Some(&(ref last_header, ref last_verifier)) = lasts_reversed.next() {
if last_header.number() < header.number() {
if last_verifier.check_finality_proof(&finality_proof).map_or(true, |hashes| !hashes.contains(&hash)) {
return Err(Error::BadEpochProof(header.number()).into());
}
found = true;
break;
}
}
for ((last_hdr, last_proof), first_hdr) in lasts_iter.zip(firsts_iter) {
let verifier = engine.epoch_verifier(&last_hdr, &last_proof)?;
verifier.verify_heavy(&first_hdr)?;
if !found {
return Err(Error::WrongChunkFormat("Inconsistent chunk ordering.".into()).into());
}
}
// verify that the validator set of the warp target is the same as that of the
// most recent transition. if the warp target was a transition itself,
// `last_data` will still be correct
let &(_, _, ref last_data) = self.last_proofs.last()
.expect("last_proofs known to have at least one element by the check above; qed");
// verify that the warp target verifies correctly the
// most recent epoch. if the warp target was a transition itself,
// it's already verified and doesn't need any more verification.
let &(ref header, ref last_epoch) = self.last_epochs.last()
.expect("last_epochs known to have at least one element by the check above; qed");
let target_last_hashes = Arc::new(target_last_hashes);
let caller = |addr, data| {
use executive::{Executive, TransactOptions};
let factories = ::factory::Factories::default();
let mut state = State::from_existing(
db.boxed_clone(),
self.manifest.state_root.clone(),
engine.account_start_nonce(),
factories,
).map_err(|e| format!("State root mismatch: {}", e))?;
let (tx, env_info) = make_tx_and_env(
engine,
addr,
data,
&target_header,
target_last_hashes.clone(),
);
let options = TransactOptions { tracing: false, vm_tracing: false, check_nonce: false };
Executive::new(&mut state, &env_info, engine)
.transact_virtual(&tx, options)
.map(|e| e.output)
.map_err(|e| format!("Error executing: {}", e))
};
let data = engine.epoch_proof(&target_header, &caller)?;
if &data[..] != &last_data[..] {
return Err(Error::WrongChunkFormat("Warp target has different epoch data than epoch transition.".into()).into())
if header != &target_header {
last_epoch.verify_heavy(&target_header)?;
}
Ok(())

View File

@ -93,7 +93,5 @@ pub trait Rebuilder: Send {
///
/// This should apply the necessary "glue" between chunks,
/// and verify against the restored state.
///
/// The database passed contains the state for the warp target block.
fn finalize(&mut self, db: ::state_db::StateDB, engine: &Engine) -> Result<(), ::error::Error>;
fn finalize(&mut self, engine: &Engine) -> Result<(), ::error::Error>;
}

View File

@ -283,7 +283,7 @@ impl Rebuilder for PowRebuilder {
}
/// Glue together any disconnected chunks and check that the chain is complete.
fn finalize(&mut self, _: ::state_db::StateDB, _: &Engine) -> Result<(), ::error::Error> {
fn finalize(&mut self, _: &Engine) -> Result<(), ::error::Error> {
let mut batch = self.db.transaction();
for (first_num, first_hash) in self.disconnected.drain(..) {
@ -299,12 +299,12 @@ impl Rebuilder for PowRebuilder {
}
let genesis_hash = self.chain.genesis_hash();
self.chain.insert_epoch_transition(&mut batch, 0, ::blockchain::EpochTransition {
self.chain.insert_epoch_transition(&mut batch, 0, ::engines::EpochTransition {
block_number: 0,
block_hash: genesis_hash,
proof: vec![],
state_proof: vec![],
});
self.db.write_buffered(batch);
Ok(())
}

View File

@ -164,11 +164,10 @@ impl Restoration {
}
// check for missing code.
let db = self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?;
let db = ::state_db::StateDB::new(db, 0);
self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?;
// connect out-of-order chunks and verify chain integrity.
self.secondary.finalize(db, engine)?;
self.secondary.finalize(engine)?;
if let Some(writer) = self.writer {
writer.finish(self.manifest)?;

View File

@ -26,7 +26,6 @@ use client::{BlockChainClient, Client};
use engines::Engine;
use snapshot::{StateRebuilder};
use snapshot::io::{SnapshotReader, PackedWriter, PackedReader};
use state_db::StateDB;
use devtools::{RandomTempPath, GuardedTempResult};
use rand::Rng;
@ -195,8 +194,7 @@ pub fn restore(
secondary.feed(&snappy_buffer[..len], engine, &flag)?;
}
let jdb = state.finalize(manifest.block_number, manifest.block_hash)?;
let state_db = StateDB::new(jdb, 0);
secondary.finalize(state_db, engine)
trace!(target: "snapshot", "finalizing");
state.finalize(manifest.block_number, manifest.block_hash)?;
secondary.finalize(engine)
}

View File

@ -21,11 +21,9 @@ use std::sync::Arc;
use std::str::FromStr;
use account_provider::AccountProvider;
use client::{Client, BlockChainClient, MiningBlockChainClient};
use client::{Client, BlockChainClient};
use ethkey::Secret;
use engines::Seal;
use futures::Future;
use miner::MinerService;
use native_contracts::test_contracts::ValidatorSet;
use snapshot::tests::helpers as snapshot_helpers;
use spec::Spec;
@ -37,7 +35,7 @@ use util::kvdb;
const PASS: &'static str = "";
const TRANSITION_BLOCK_1: usize = 2; // block at which the contract becomes activated.
const TRANSITION_BLOCK_2: usize = 6; // block at which the second contract activates.
const TRANSITION_BLOCK_2: usize = 10; // block at which the second contract activates.
macro_rules! secret {
($e: expr) => { Secret::from_slice(&$e.sha3()) }
@ -54,10 +52,10 @@ lazy_static! {
}
/// Contract code used here: https://gist.github.com/rphmeier/2de14fd365a969e3a9e10d77eb9a1e37
/// Contract code used here: https://gist.github.com/anonymous/2a43783647e0f0dfcc359bd6fd81d6d9
/// Account with secrets "1".sha3() is initially the validator.
/// Transitions to the contract at block 2, initially same validator set.
/// Create a new Spec with BasicAuthority which uses a contract at address 5 to determine the current validators using `getValidators`.
/// Create a new Spec with AuthorityRound which uses a contract at address 5 to determine the current validators using `getValidators`.
/// `native_contracts::test_contracts::ValidatorSet` provides a native wrapper for the ABi.
fn spec_fixed_to_contract() -> Spec {
let data = include_bytes!("test_validator_contract.json");
@ -101,45 +99,41 @@ fn make_chain(accounts: Arc<AccountProvider>, blocks_beyond: usize, transitions:
{
// push a block with given number, signed by one of the signers, with given transactions.
let push_block = |signers: &[Address], n, txs: Vec<SignedTransaction>| {
use block::IsBlock;
use miner::MinerService;
let idx = n as usize % signers.len();
trace!(target: "snapshot", "Pushing block #{}, {} txs, author={}",
n, txs.len(), signers[idx]);
client.miner().set_author(signers[idx]);
client.miner().import_external_transactions(&*client,
txs.into_iter().map(Into::into).collect());
let engine = client.engine();
let idx = n as usize % signers.len();
engine.set_signer(accounts.clone(), signers[idx], PASS.to_owned());
engine.step();
trace!(target: "snapshot", "Pushing block #{}, {} txs, author={}", n, txs.len(), signers[idx]);
let mut open_block = client.prepare_open_block(signers[idx], (5_000_000.into(), 5_000_000.into()), Vec::new());
for tx in txs {
open_block.push_transaction(tx, None).unwrap();
}
let block = open_block.close_and_lock();
let seal = match engine.generate_seal(block.block()) {
Seal::Regular(seal) => seal,
_ => panic!("Unable to generate seal for dummy chain block #{}", n),
};
let block = block.seal(&*engine, seal).unwrap();
client.import_sealed_block(block).unwrap();
assert_eq!(client.chain_info().best_block_number, n);
};
// execution callback for native contract: push transaction to be sealed.
let nonce = RefCell::new(client.engine().account_start_nonce());
let exec = |addr, data| {
let nonce = RefCell::new(client.engine().account_start_nonce(0));
// create useless transactions vector so we don't have to dig in
// and force sealing.
let make_useless_transactions = || {
let mut nonce = nonce.borrow_mut();
let transaction = Transaction {
nonce: *nonce,
gas_price: 0.into(),
gas: 1_000_000.into(),
action: Action::Call(addr),
value: 0.into(),
data: data,
gas_price: 1.into(),
gas: 21_000.into(),
action: Action::Call(Address::new()),
value: 1.into(),
data: Vec::new(),
}.sign(&*RICH_SECRET, client.signing_network_id());
client.miner().import_own_transaction(&*client, transaction.into()).unwrap();
*nonce = *nonce + 1.into();
Ok(Vec::new())
vec![transaction]
};
let contract_1 = ValidatorSet::new(*CONTRACT_ADDR_1);
@ -156,8 +150,12 @@ fn make_chain(accounts: Arc<AccountProvider>, blocks_beyond: usize, transitions:
panic!("Bad test: issued epoch change before transition to contract.");
}
if (num as u64) < client.chain_info().best_block_number {
panic!("Bad test: issued epoch change before previous transition finalized.");
}
for number in client.chain_info().best_block_number + 1 .. num as u64 {
push_block(&cur_signers, number, vec![]);
push_block(&cur_signers, number, make_useless_transactions());
}
let pending = if manual {
@ -167,22 +165,48 @@ fn make_chain(accounts: Arc<AccountProvider>, blocks_beyond: usize, transitions:
false => &contract_1,
};
contract.set_validators(&exec, new_set.clone()).wait().unwrap();
client.ready_transactions()
.into_iter()
.map(|x| x.transaction)
.collect()
let mut pending = Vec::new();
{
let mut exec = |addr, data| {
let mut nonce = nonce.borrow_mut();
let transaction = Transaction {
nonce: *nonce,
gas_price: 0.into(),
gas: 1_000_000.into(),
action: Action::Call(addr),
value: 0.into(),
data: data,
}.sign(&*RICH_SECRET, client.signing_network_id());
pending.push(transaction);
*nonce = *nonce + 1.into();
Ok(Vec::new())
};
contract.set_validators(&mut exec, new_set.clone()).wait().unwrap();
}
pending
} else {
Vec::new()
make_useless_transactions()
};
// push transition block.
push_block(&cur_signers, num as u64, pending);
// push blocks to finalize transition
for finalization_count in 1.. {
if finalization_count * 2 > cur_signers.len() { break }
push_block(&cur_signers, (num + finalization_count) as u64, make_useless_transactions());
}
cur_signers = new_set;
}
// make blocks beyond.
for number in (client.chain_info().best_block_number..).take(blocks_beyond) {
push_block(&cur_signers, number + 1, vec![]);
push_block(&cur_signers, number + 1, make_useless_transactions());
}
}
@ -190,7 +214,7 @@ fn make_chain(accounts: Arc<AccountProvider>, blocks_beyond: usize, transitions:
}
#[test]
fn fixed_to_contract() {
fn fixed_to_contract_only() {
let (provider, addrs) = make_accounts(&[
RICH_SECRET.clone(),
secret!("foo"),
@ -204,17 +228,21 @@ fn fixed_to_contract() {
assert!(provider.has_account(*RICH_ADDR).unwrap());
let client = make_chain(provider, 1, vec![
let client = make_chain(provider, 3, vec![
Transition::Manual(3, vec![addrs[2], addrs[3], addrs[5], addrs[7]]),
Transition::Manual(4, vec![addrs[0], addrs[1], addrs[4], addrs[6]]),
Transition::Manual(6, vec![addrs[0], addrs[1], addrs[4], addrs[6]]),
]);
assert_eq!(client.chain_info().best_block_number, 5);
// 6, 7, 8 prove finality for transition at 6.
// 3 beyond gets us to 11.
assert_eq!(client.chain_info().best_block_number, 11);
let reader = snapshot_helpers::snap(&*client);
let new_db = kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0));
let spec = spec_fixed_to_contract();
// ensure fresh engine's step matches.
for _ in 0..11 { spec.engine.step() }
snapshot_helpers::restore(Arc::new(new_db), &*spec.engine, &**reader, &spec.genesis_block()).unwrap();
}
@ -233,17 +261,18 @@ fn fixed_to_contract_to_contract() {
assert!(provider.has_account(*RICH_ADDR).unwrap());
let client = make_chain(provider, 2, vec![
let client = make_chain(provider, 3, vec![
Transition::Manual(3, vec![addrs[2], addrs[3], addrs[5], addrs[7]]),
Transition::Manual(4, vec![addrs[0], addrs[1], addrs[4], addrs[6]]),
Transition::Implicit(5, vec![addrs[0]]),
Transition::Manual(8, vec![addrs[2], addrs[4], addrs[6], addrs[7]]),
Transition::Manual(6, vec![addrs[0], addrs[1], addrs[4], addrs[6]]),
Transition::Implicit(10, vec![addrs[0]]),
Transition::Manual(13, vec![addrs[2], addrs[4], addrs[6], addrs[7]]),
]);
assert_eq!(client.chain_info().best_block_number, 10);
assert_eq!(client.chain_info().best_block_number, 16);
let reader = snapshot_helpers::snap(&*client);
let new_db = kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0));
let spec = spec_fixed_to_contract();
for _ in 0..16 { spec.engine.step() }
snapshot_helpers::restore(Arc::new(new_db), &*spec.engine, &**reader, &spec.genesis_block()).unwrap();
}

View File

@ -23,10 +23,8 @@ use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
use blockchain::BlockChain;
use snapshot::{chunk_secondary, Error as SnapshotError, Progress, SnapshotComponents};
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
use state_db::StateDB;
use util::{Mutex, snappy};
use util::journaldb::{self, Algorithm};
use util::kvdb::{self, KeyValueDB, DBTransaction};
use std::sync::Arc;
@ -83,7 +81,6 @@ fn chunk_and_restore(amount: u64) {
// restore it.
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_state = StateDB::new(journaldb::new(new_db.clone(), Algorithm::Archive, None), 0);
let mut rebuilder = SNAPSHOT_MODE.rebuilder(new_chain, new_db.clone(), &manifest).unwrap();
let reader = PackedReader::new(&snapshot_path).unwrap().unwrap();
@ -94,7 +91,7 @@ fn chunk_and_restore(amount: u64) {
rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap();
}
rebuilder.finalize(new_state, engine.as_ref()).unwrap();
rebuilder.finalize(engine.as_ref()).unwrap();
drop(rebuilder);
// and test it.

View File

@ -1,15 +1,16 @@
{
"name": "TestValidatorContract",
"engine": {
"basicAuthority": {
"authorityRound": {
"params": {
"gasLimitBoundDivisor": "0x0400",
"durationLimit": "0x0d",
"stepDuration": 1,
"startStep": 0,
"validators": {
"multi": {
"0": { "list": ["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"] },
"2": { "contract": "0x0000000000000000000000000000000000000005" },
"6": { "contract": "0x0000000000000000000000000000000000000006" }
"10": { "contract": "0x0000000000000000000000000000000000000006" }
}
}
}
@ -23,7 +24,10 @@
},
"genesis": {
"seal": {
"generic": "0xc180"
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"author": "0x0000000000000000000000000000000000000000",
@ -39,11 +43,11 @@
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
"0000000000000000000000000000000000000005": {
"balance": "1",
"constructor": "6060604052604060405190810160405280737d577a597b2742b498cb5cf0c26cdcd726d39e6e73ffffffffffffffffffffffffffffffffffffffff1681526020017382a978b3f5962a5b0957d9ee9eef472ee55b42f173ffffffffffffffffffffffffffffffffffffffff16815250600290600261007e929190610096565b50341561008757fe5b5b60006001819055505b610163565b82805482825590600052602060002090810192821561010f579160200282015b8281111561010e5782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550916020019190600101906100b6565b5b50905061011c9190610120565b5090565b61016091905b8082111561015c57600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905550600101610126565b5090565b90565b61045d806101726000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063303e98e5146100675780639300c9261461008d578063b7ab4db5146100e4578063bfc708a014610159578063fd6e1b501461018f575bfe5b341561006f57fe5b6100776101c5565b6040518082815260200191505060405180910390f35b341561009557fe5b6100e26004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437820191505050505050919050506101d0565b005b34156100ec57fe5b6100f46102b3565b6040518080602001828103825283818151815260200191508051906020019060200280838360008314610146575b80518252602083111561014657602082019150602081019050602083039250610122565b5050509050019250505060405180910390f35b341561016157fe5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610348565b005b341561019757fe5b6101c3600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061034c565b005b600060015490505b90565b600081600290805190602001906101e8929190610350565b50600143034090506000546000191681600019161415156102ae578060008160001916905550600160016000828254019250508190555060015481600019167f47e91f47ccfdcb578564e1af55da55c5e5d33403372fe68e4fed3dfd385764a184604051808060200182810382528381815181526020019150805190602001906020028083836000831461029b575b80518252602083111561029b57602082019150602081019050602083039250610277565b5050509050019250505060405180910390a35b5b5050565b6102bb6103da565b600280548060200260200160405190810160405280929190818152602001828054801561033d57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116102f3575b505050505090505b90565b5b50565b5b50565b8280548282559060005260206000209081019282156103c9579160200282015b828111156103c85782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555091602001919060010190610370565b5b5090506103d691906103ee565b5090565b602060405190810160405280600081525090565b61042e91905b8082111561042a57600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016103f4565b5090565b905600a165627a7a723058205c9ed1e1da2b93682907ac47377a662b21a5f9d89c4b21be40b098bdb00254360029"
"constructor": "6060604052602060405190810160405280737d577a597b2742b498cb5cf0c26cdcd726d39e6e73ffffffffffffffffffffffffffffffffffffffff16815250600090600161004e92919061005c565b50341561005757fe5b610129565b8280548282559060005260206000209081019282156100d5579160200282015b828111156100d45782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509160200191906001019061007c565b5b5090506100e291906100e6565b5090565b61012691905b8082111561012257600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016100ec565b5090565b90565b61056f806101386000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806375286211146100675780639300c92614610079578063b7ab4db5146100d0578063c476dd4014610145578063d69f13bb146101c7575bfe5b341561006f57fe5b610077610206565b005b341561008157fe5b6100ce600480803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091905050610275565b005b34156100d857fe5b6100e061031f565b6040518080602001828103825283818151815260200191508051906020019060200280838360008314610132575b8051825260208311156101325760208201915060208101905060208303925061010e565b5050509050019250505060405180910390f35b341561014d57fe5b6101c5600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919050506103b4565b005b34156101cf57fe5b610204600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506103ba565b005b73fffffffffffffffffffffffffffffffffffffffe3373ffffffffffffffffffffffffffffffffffffffff1614151561023f5760006000fd5b600060018054905014151561027257600160009080546102609291906103bf565b5060006001816102709190610411565b505b5b565b806001908051906020019061028b92919061043d565b506001430340600019167f55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c89826040518080602001828103825283818151815260200191508051906020019060200280838360008314610309575b805182526020831115610309576020820191506020810190506020830392506102e5565b5050509050019250505060405180910390a25b50565b6103276104c7565b60008054806020026020016040519081016040528092919081815260200182805480156103a957602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001906001019080831161035f575b505050505090505b90565b5b505050565b5b5050565b8280548282559060005260206000209081019282156104005760005260206000209182015b828111156103ff5782548255916001019190600101906103e4565b5b50905061040d91906104db565b5090565b81548183558181151161043857818360005260206000209182019101610437919061051e565b5b505050565b8280548282559060005260206000209081019282156104b6579160200282015b828111156104b55782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509160200191906001019061045d565b5b5090506104c391906104db565b5090565b602060405190810160405280600081525090565b61051b91905b8082111561051757600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016104e1565b5090565b90565b61054091905b8082111561053c576000816000905550600101610524565b5090565b905600a165627a7a7230582041ce7e5c820bc89b1a330a3233c4f3013e77433ecba368fa234adf758d87fe1d0029"
},
"0000000000000000000000000000000000000006": {
"balance": "1",
"constructor": "6060604052602060405190810160405280737d577a597b2742b498cb5cf0c26cdcd726d39e6e73ffffffffffffffffffffffffffffffffffffffff16815250600290600161004e929190610066565b50341561005757fe5b5b60006001819055505b610133565b8280548282559060005260206000209081019282156100df579160200282015b828111156100de5782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555091602001919060010190610086565b5b5090506100ec91906100f0565b5090565b61013091905b8082111561012c57600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016100f6565b5090565b90565b61045d806101426000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063303e98e5146100675780639300c9261461008d578063b7ab4db5146100e4578063bfc708a014610159578063fd6e1b501461018f575bfe5b341561006f57fe5b6100776101c5565b6040518082815260200191505060405180910390f35b341561009557fe5b6100e26004808035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437820191505050505050919050506101d0565b005b34156100ec57fe5b6100f46102b3565b6040518080602001828103825283818151815260200191508051906020019060200280838360008314610146575b80518252602083111561014657602082019150602081019050602083039250610122565b5050509050019250505060405180910390f35b341561016157fe5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610348565b005b341561019757fe5b6101c3600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061034c565b005b600060015490505b90565b600081600290805190602001906101e8929190610350565b50600143034090506000546000191681600019161415156102ae578060008160001916905550600160016000828254019250508190555060015481600019167f47e91f47ccfdcb578564e1af55da55c5e5d33403372fe68e4fed3dfd385764a184604051808060200182810382528381815181526020019150805190602001906020028083836000831461029b575b80518252602083111561029b57602082019150602081019050602083039250610277565b5050509050019250505060405180910390a35b5b5050565b6102bb6103da565b600280548060200260200160405190810160405280929190818152602001828054801561033d57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116102f3575b505050505090505b90565b5b50565b5b50565b8280548282559060005260206000209081019282156103c9579160200282015b828111156103c85782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555091602001919060010190610370565b5b5090506103d691906103ee565b5090565b602060405190810160405280600081525090565b61042e91905b8082111561042a57600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016103f4565b5090565b905600a165627a7a723058203070810251dcb89c9838d957eb3dbeef357bef0902e0245e3dc3849b6143c3960029"
"constructor": "6060604052602060405190810160405280737d577a597b2742b498cb5cf0c26cdcd726d39e6e73ffffffffffffffffffffffffffffffffffffffff16815250600090600161004e92919061005c565b50341561005757fe5b610129565b8280548282559060005260206000209081019282156100d5579160200282015b828111156100d45782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509160200191906001019061007c565b5b5090506100e291906100e6565b5090565b61012691905b8082111561012257600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016100ec565b5090565b90565b61056f806101386000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806375286211146100675780639300c92614610079578063b7ab4db5146100d0578063c476dd4014610145578063d69f13bb146101c7575bfe5b341561006f57fe5b610077610206565b005b341561008157fe5b6100ce600480803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091905050610275565b005b34156100d857fe5b6100e061031f565b6040518080602001828103825283818151815260200191508051906020019060200280838360008314610132575b8051825260208311156101325760208201915060208101905060208303925061010e565b5050509050019250505060405180910390f35b341561014d57fe5b6101c5600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919050506103b4565b005b34156101cf57fe5b610204600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506103ba565b005b73fffffffffffffffffffffffffffffffffffffffe3373ffffffffffffffffffffffffffffffffffffffff1614151561023f5760006000fd5b600060018054905014151561027257600160009080546102609291906103bf565b5060006001816102709190610411565b505b5b565b806001908051906020019061028b92919061043d565b506001430340600019167f55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c89826040518080602001828103825283818151815260200191508051906020019060200280838360008314610309575b805182526020831115610309576020820191506020810190506020830392506102e5565b5050509050019250505060405180910390a25b50565b6103276104c7565b60008054806020026020016040519081016040528092919081815260200182805480156103a957602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001906001019080831161035f575b505050505090505b90565b5b505050565b5b5050565b8280548282559060005260206000209081019282156104005760005260206000209182015b828111156103ff5782548255916001019190600101906103e4565b5b50905061040d91906104db565b5090565b81548183558181151161043857818360005260206000209182019101610437919061051e565b5b505050565b8280548282559060005260206000209081019282156104b6579160200282015b828111156104b55782518260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055509160200191906001019061045d565b5b5090506104c391906104db565b5090565b602060405190810160405280600081525090565b61051b91905b8082111561051757600081816101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055506001016104e1565b5090565b90565b61054091905b8082111561053c576000816000905550600101610524565b5090565b905600a165627a7a7230582041ce7e5c820bc89b1a330a3233c4f3013e77433ecba368fa234adf758d87fe1d0029"
},
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "1606938044258990275541962092341162602522202993782792835301376" },
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }

View File

@ -76,6 +76,12 @@ pub struct CommonParams {
pub eip211_transition: BlockNumber,
/// Number of first block where EIP-214 rules begin.
pub eip214_transition: BlockNumber,
/// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin.
pub dust_protection_transition: BlockNumber,
/// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled.
pub nonce_cap_increment : u64,
/// Enable dust cleanup for contracts.
pub remove_dust_contracts : bool,
}
impl From<ethjson::spec::Params> for CommonParams {
@ -100,6 +106,9 @@ impl From<ethjson::spec::Params> for CommonParams {
eip210_contract_gas: p.eip210_contract_gas.map_or(1000000.into(), Into::into),
eip211_transition: p.eip211_transition.map_or(BlockNumber::max_value(), Into::into),
eip214_transition: p.eip214_transition.map_or(BlockNumber::max_value(), Into::into),
dust_protection_transition: p.dust_protection_transition.map_or(BlockNumber::max_value(), Into::into),
nonce_cap_increment: p.nonce_cap_increment.map_or(64, Into::into),
remove_dust_contracts: p.remove_dust_contracts.unwrap_or(false),
}
}
}
@ -224,7 +233,7 @@ impl Spec {
);
}
let start_nonce = self.engine.account_start_nonce();
let start_nonce = self.engine.account_start_nonce(0);
let (root, db) = {
let mut state = State::from_existing(
@ -460,7 +469,7 @@ mod tests {
::ethcore_logger::init_log();
let spec = Spec::new_test_constructor();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let state = State::from_existing(db.boxed_clone(), spec.state_root(), spec.engine.account_start_nonce(), Default::default()).unwrap();
let state = State::from_existing(db.boxed_clone(), spec.state_root(), spec.engine.account_start_nonce(0), Default::default()).unwrap();
let expected = H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap();
assert_eq!(state.storage_at(&Address::from_str("0000000000000000000000000000000000000005").unwrap(), &H256::zero()).unwrap(), expected);
}

View File

@ -315,6 +315,11 @@ impl Account {
self.code_hash == SHA3_EMPTY
}
/// Check if account is basic (Has no code).
pub fn is_basic(&self) -> bool {
self.code_hash == SHA3_EMPTY
}
/// Return the storage root associated with this account or None if it has been altered via the overlay.
pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} }

View File

@ -96,7 +96,11 @@ enum AccountState {
/// Account entry can contain existing (`Some`) or non-existing
/// account (`None`)
struct AccountEntry {
/// Account entry. `None` if account known to be non-existant.
account: Option<Account>,
/// Unmodified account balance.
old_balance: Option<U256>,
/// Entry state.
state: AccountState,
}
@ -107,6 +111,10 @@ impl AccountEntry {
self.state == AccountState::Dirty
}
fn exists_and_is_null(&self) -> bool {
self.account.as_ref().map_or(false, |a| a.is_null())
}
/// Clone dirty data into new `AccountEntry`. This includes
/// basic account data and modified storage keys.
/// Returns None if clean.
@ -121,6 +129,7 @@ impl AccountEntry {
/// basic account data and modified storage keys.
fn clone_dirty(&self) -> AccountEntry {
AccountEntry {
old_balance: self.old_balance,
account: self.account.as_ref().map(Account::clone_dirty),
state: self.state,
}
@ -129,6 +138,7 @@ impl AccountEntry {
// Create a new account entry and mark it as dirty.
fn new_dirty(account: Option<Account>) -> AccountEntry {
AccountEntry {
old_balance: account.as_ref().map(|a| a.balance().clone()),
account: account,
state: AccountState::Dirty,
}
@ -137,6 +147,7 @@ impl AccountEntry {
// Create a new account entry and mark it as clean.
fn new_clean(account: Option<Account>) -> AccountEntry {
AccountEntry {
old_balance: account.as_ref().map(|a| a.balance().clone()),
account: account,
state: AccountState::CleanFresh,
}
@ -145,6 +156,7 @@ impl AccountEntry {
// Create a new account entry and mark it as clean and cached.
fn new_clean_cached(account: Option<Account>) -> AccountEntry {
AccountEntry {
old_balance: account.as_ref().map(|a| a.balance().clone()),
account: account,
state: AccountState::CleanCached,
}
@ -181,7 +193,7 @@ pub fn check_proof(
let res = State::from_existing(
backend,
root,
engine.account_start_nonce(),
engine.account_start_nonce(env_info.number),
factories
);
@ -266,8 +278,8 @@ pub enum CleanupMode<'a> {
ForceCreate,
/// Don't delete null accounts upon touching, but also don't create them.
NoEmpty,
/// Add encountered null accounts to the provided kill-set, to be deleted later.
KillEmpty(&'a mut HashSet<Address>),
/// Mark all touched accounts.
TrackTouched(&'a mut HashSet<Address>),
}
const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with valid root. Creating a SecTrieDB with a valid root will not fail. \
@ -549,31 +561,30 @@ impl<B: Backend> State<B> {
let is_value_transfer = !incr.is_zero();
if is_value_transfer || (cleanup_mode == CleanupMode::ForceCreate && !self.exists(a)?) {
self.require(a, false)?.add_balance(incr);
} else {
match cleanup_mode {
CleanupMode::KillEmpty(set) => if !is_value_transfer && self.exists(a)? && !self.exists_and_not_null(a)? {
set.insert(a.clone());
},
_ => {}
} else if let CleanupMode::TrackTouched(set) = cleanup_mode {
if self.exists(a)? {
set.insert(*a);
self.touch(a)?;
}
}
Ok(())
}
/// Subtract `decr` from the balance of account `a`.
pub fn sub_balance(&mut self, a: &Address, decr: &U256) -> trie::Result<()> {
pub fn sub_balance(&mut self, a: &Address, decr: &U256, cleanup_mode: &mut CleanupMode) -> trie::Result<()> {
trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a)?);
if !decr.is_zero() || !self.exists(a)? {
self.require(a, false)?.sub_balance(decr);
}
if let CleanupMode::TrackTouched(ref mut set) = *cleanup_mode {
set.insert(*a);
}
Ok(())
}
/// Subtracts `by` from the balance of `from` and adds it to that of `to`.
pub fn transfer_balance(&mut self, from: &Address, to: &Address, by: &U256, cleanup_mode: CleanupMode) -> trie::Result<()> {
self.sub_balance(from, by)?;
pub fn transfer_balance(&mut self, from: &Address, to: &Address, by: &U256, mut cleanup_mode: CleanupMode) -> trie::Result<()> {
self.sub_balance(from, by, &mut cleanup_mode)?;
self.add_balance(to, by, cleanup_mode)?;
Ok(())
}
@ -640,33 +651,33 @@ impl<B: Backend> State<B> {
}
}
/// Commit accounts to SecTrieDBMut. This is similar to cpp-ethereum's dev::eth::commit.
/// `accounts` is mutable because we may need to commit the code or storage and record that.
fn touch(&mut self, a: &Address) -> trie::Result<()> {
self.require(a, false)?;
Ok(())
}
/// Commits our cached account changes into the trie.
#[cfg_attr(feature="dev", allow(match_ref_pats))]
#[cfg_attr(feature="dev", allow(needless_borrow))]
fn commit_into(
factories: &Factories,
db: &mut B,
root: &mut H256,
accounts: &mut HashMap<Address, AccountEntry>
) -> Result<(), Error> {
pub fn commit(&mut self) -> Result<(), Error> {
// first, commit the sub trees.
let mut accounts = self.cache.borrow_mut();
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
if let Some(ref mut account) = a.account {
let addr_hash = account.address_hash(address);
{
let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash);
account.commit_storage(&factories.trie, account_db.as_hashdb_mut())?;
let mut account_db = self.factories.accountdb.create(self.db.as_hashdb_mut(), addr_hash);
account.commit_storage(&self.factories.trie, account_db.as_hashdb_mut())?;
account.commit_code(account_db.as_hashdb_mut());
}
if !account.is_empty() {
db.note_non_null_account(address);
self.db.note_non_null_account(address);
}
}
}
{
let mut trie = factories.trie.from_existing(db.as_hashdb_mut(), root)?;
let mut trie = self.factories.trie.from_existing(self.db.as_hashdb_mut(), &mut self.root)?;
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
a.state = AccountState::Committed;
match a.account {
@ -676,7 +687,7 @@ impl<B: Backend> State<B> {
None => {
trie.remove(address)?;
},
}
};
}
}
@ -692,17 +703,30 @@ impl<B: Backend> State<B> {
}
}
/// Commits our cached account changes into the trie.
pub fn commit(&mut self) -> Result<(), Error> {
assert!(self.checkpoints.borrow().is_empty());
Self::commit_into(&self.factories, &mut self.db, &mut self.root, &mut *self.cache.borrow_mut())
}
/// Clear state cache
pub fn clear(&mut self) {
self.cache.borrow_mut().clear();
}
/// Remove any touched empty or dust accounts.
pub fn kill_garbage(&mut self, touched: &HashSet<Address>, remove_empty_touched: bool, min_balance: &Option<U256>, kill_contracts: bool) -> trie::Result<()> {
let to_kill: HashSet<_> = {
self.cache.borrow().iter().filter_map(|(address, ref entry)|
if touched.contains(address) && // Check all touched accounts
((remove_empty_touched && entry.exists_and_is_null()) // Remove all empty touched accounts.
|| min_balance.map_or(false, |ref balance| entry.account.as_ref().map_or(false, |account|
(account.is_basic() || kill_contracts) // Remove all basic and optionally contract accounts where balance has been decreased.
&& account.balance() < balance && entry.old_balance.as_ref().map_or(false, |b| account.balance() < b)))) {
Some(address.clone())
} else { None }).collect()
};
for address in to_kill {
self.kill_account(&address);
}
Ok(())
}
#[cfg(test)]
#[cfg(feature = "json-tests")]
/// Populate the state from `accounts`.
@ -1926,7 +1950,7 @@ mod tests {
assert_eq!(state.balance(&a).unwrap(), U256::from(69u64));
state.commit().unwrap();
assert_eq!(state.balance(&a).unwrap(), U256::from(69u64));
state.sub_balance(&a, &U256::from(42u64)).unwrap();
state.sub_balance(&a, &U256::from(42u64), &mut CleanupMode::NoEmpty).unwrap();
assert_eq!(state.balance(&a).unwrap(), U256::from(27u64));
state.commit().unwrap();
assert_eq!(state.balance(&a).unwrap(), U256::from(27u64));
@ -2026,4 +2050,44 @@ mod tests {
new_state.diff_from(state).unwrap();
}
#[test]
fn should_kill_garbage() {
let a = 10.into();
let b = 20.into();
let c = 30.into();
let d = 40.into();
let e = 50.into();
let x = 0.into();
let db = get_temp_state_db();
let (root, db) = {
let mut state = State::new(db, U256::from(0), Default::default());
state.add_balance(&a, &U256::default(), CleanupMode::ForceCreate).unwrap(); // create an empty account
state.add_balance(&b, &100.into(), CleanupMode::ForceCreate).unwrap(); // create a dust account
state.add_balance(&c, &101.into(), CleanupMode::ForceCreate).unwrap(); // create a normal account
state.add_balance(&d, &99.into(), CleanupMode::ForceCreate).unwrap(); // create another dust account
state.new_contract(&e, 100.into(), 1.into()); // create a contract account
state.init_code(&e, vec![0x00]).unwrap();
state.commit().unwrap();
state.drop()
};
let mut state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap();
let mut touched = HashSet::new();
state.add_balance(&a, &U256::default(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account
state.transfer_balance(&b, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance
state.transfer_balance(&c, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance
state.transfer_balance(&e, &x, &1.into(), CleanupMode::TrackTouched(&mut touched)).unwrap(); // touch an account decreasing its balance
state.kill_garbage(&touched, true, &None, false).unwrap();
assert!(!state.exists(&a).unwrap());
assert!(state.exists(&b).unwrap());
state.kill_garbage(&touched, true, &Some(100.into()), false).unwrap();
assert!(!state.exists(&b).unwrap());
assert!(state.exists(&c).unwrap());
assert!(state.exists(&d).unwrap());
assert!(state.exists(&e).unwrap());
state.kill_garbage(&touched, true, &Some(100.into()), true).unwrap();
assert!(state.exists(&c).unwrap());
assert!(state.exists(&d).unwrap());
assert!(!state.exists(&e).unwrap());
}
}

View File

@ -18,7 +18,7 @@
use std::collections::HashSet;
use util::{Address, U256};
use log_entry::LogEntry;
use evm::Schedule;
use evm::{Schedule, CleanDustMode};
use super::CleanupMode;
/// State changes which should be applied in finalize,
@ -28,8 +28,8 @@ pub struct Substate {
/// Any accounts that have suicided.
pub suicides: HashSet<Address>,
/// Any accounts that are tagged for garbage collection.
pub garbage: HashSet<Address>,
/// Any accounts that are touched.
pub touched: HashSet<Address>,
/// Any logs.
pub logs: Vec<LogEntry>,
@ -50,7 +50,7 @@ impl Substate {
/// Merge secondary substate `s` into self, accruing each element correspondingly.
pub fn accrue(&mut self, s: Substate) {
self.suicides.extend(s.suicides.into_iter());
self.garbage.extend(s.garbage.into_iter());
self.touched.extend(s.touched.into_iter());
self.logs.extend(s.logs.into_iter());
self.sstore_clears_count = self.sstore_clears_count + s.sstore_clears_count;
self.contracts_created.extend(s.contracts_created.into_iter());
@ -59,10 +59,10 @@ impl Substate {
/// Get the cleanup mode object from this.
#[cfg_attr(feature="dev", allow(wrong_self_convention))]
pub fn to_cleanup_mode(&mut self, schedule: &Schedule) -> CleanupMode {
match (schedule.no_empty, schedule.kill_empty) {
(false, _) => CleanupMode::ForceCreate,
(true, false) => CleanupMode::NoEmpty,
(true, true) => CleanupMode::KillEmpty(&mut self.garbage),
match (schedule.kill_dust != CleanDustMode::Off, schedule.no_empty, schedule.kill_empty) {
(false, false, _) => CleanupMode::ForceCreate,
(false, true, false) => CleanupMode::NoEmpty,
(false, true, true) | (true, _, _,) => CleanupMode::TrackTouched(&mut self.touched),
}
}
}

View File

@ -193,7 +193,8 @@ pub fn generate_dummy_client_with_spec_accounts_and_data<F>(get_test_spec: F, ac
Arc::new(last_hashes.clone()),
author.clone(),
(3141562.into(), 31415620.into()),
vec![]
vec![],
false,
).unwrap();
b.set_difficulty(U256::from(0x20000));
rolling_timestamp += 10;

View File

@ -151,6 +151,8 @@ pub enum ExecutionError {
},
/// When execution tries to modify the state in static context
MutableCallInStaticContext,
/// Returned when transacting from a non-existing account with dust protection enabled.
SenderMustExist,
/// Returned when internal evm error occurs.
Internal(String),
/// Returned when generic transaction occurs
@ -179,6 +181,7 @@ impl fmt::Display for ExecutionError {
format!("Cost of transaction exceeds sender balance. {} is required \
but the sender only has {}", required, got),
MutableCallInStaticContext => "Mutable Call in static context".to_owned(),
SenderMustExist => "Transacting from an empty account".to_owned(),
Internal(ref msg) => msg.clone(),
TransactionMalformed(ref err) => format!("Malformed transaction: {}", err),
};

View File

@ -80,10 +80,18 @@ pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine, che
}
// Verify transactions.
let mut transactions = Vec::new();
let nonce_cap = if header.number() >= engine.params().dust_protection_transition {
Some((engine.params().nonce_cap_increment * header.number()).into())
} else { None };
{
let v = BlockView::new(&bytes);
for t in v.transactions() {
let t = engine.verify_transaction(t, &header)?;
if let Some(max_nonce) = nonce_cap {
if t.nonce >= max_nonce {
return Err(BlockError::TooManyTransactions(t.sender()).into());
}
}
transactions.push(t);
}
}
@ -391,6 +399,12 @@ mod tests {
verify_block_family(&header, bytes, engine, bc)
}
fn unordered_test(bytes: &[u8], engine: &Engine) -> Result<(), Error> {
let header = BlockView::new(bytes).header();
verify_block_unordered(header, bytes.to_vec(), engine, false)?;
Ok(())
}
#[test]
#[cfg_attr(feature="dev", allow(similar_names))]
fn test_verify_block() {
@ -556,4 +570,34 @@ mod tests {
// TODO: some additional uncle checks
}
#[test]
fn dust_protection() {
use ethkey::{Generator, Random};
use types::transaction::{Transaction, Action};
use engines::NullEngine;
let mut params = CommonParams::default();
params.dust_protection_transition = 0;
params.nonce_cap_increment = 2;
let mut header = Header::default();
header.set_number(1);
let keypair = Random.generate().unwrap();
let bad_transactions: Vec<_> = (0..3).map(|i| Transaction {
action: Action::Create,
value: U256::zero(),
data: Vec::new(),
gas: 0.into(),
gas_price: U256::zero(),
nonce: i.into(),
}.sign(keypair.secret(), None)).collect();
let good_transactions = [bad_transactions[0].clone(), bad_transactions[1].clone()];
let engine = NullEngine::new(params, BTreeMap::new());
check_fail(unordered_test(&create_test_block_with_data(&header, &bad_transactions, &[]), &engine), TooManyTransactions(keypair.address()));
unordered_test(&create_test_block_with_data(&header, &good_transactions, &[]), &engine).unwrap();
}
}

View File

@ -1,6 +1,6 @@
{
"name": "parity.js",
"version": "1.7.94",
"version": "1.7.96",
"main": "release/index.js",
"jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>",

View File

@ -24,7 +24,8 @@ import { Db, Eth, Parity, Net, Personal, Shh, Signer, Trace, Web3 } from './rpc'
import Subscriptions from './subscriptions';
import util from './util';
import { isFunction } from './util/types';
import { LocalAccountsMiddleware } from './local';
import LocalAccountsMiddleware from '~/api/local';
export default class Api extends EventEmitter {
constructor (provider, allowSubscriptions = true) {
@ -51,7 +52,7 @@ export default class Api extends EventEmitter {
}
// Doing a request here in test env would cause an error
if (process.env.NODE_ENV !== 'test') {
if (LocalAccountsMiddleware && process.env.NODE_ENV !== 'test') {
const middleware = this.parity
.nodeKind()
.then((nodeKind) => {

View File

@ -14,8 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
if (process.env.NODE_ENV !== 'test') {
process.browser = true;
}
export LocalAccountsMiddleware from './localAccountsMiddleware';
export default null;

View File

@ -20,10 +20,10 @@ export default {
},
status: {
consensus: {
capable: `Capable`,
capableUntil: `Capable until #{blockNumber}`,
incapableSince: `Incapable since #{blockNumber}`,
unknown: `Unknown capability`
capable: `Upgrade not required.`,
capableUntil: `Upgrade required before #{blockNumber}`,
incapableSince: `Upgrade required since #{blockNumber}`,
unknown: `Upgrade status is unknown.`
},
upgrade: `Upgrade`
}

View File

@ -29,7 +29,7 @@ export default function DappIcon ({ app, className, small }, { api }) {
src={
app.type === 'local'
? `${dappsUrl}/${app.id}/${app.iconUrl}`
: `${dappsUrl}${app.image}`
: `${app.image}`
}
/>
);

View File

@ -64,7 +64,7 @@ describe('ui/DappIcon', () => {
it('renders other apps with correct URL', () => {
expect(render({ app: { id: 'test', image: '/test.img' } }).props().src).to.equal(
`${DAPPS_URL}/test.img`
`/test.img`
);
});
});

View File

@ -47,7 +47,8 @@ class TxRow extends Component {
className: PropTypes.string,
cancelTransaction: PropTypes.func,
editTransaction: PropTypes.func,
historic: PropTypes.bool
historic: PropTypes.bool,
killTransaction: PropTypes.func
};
static defaultProps = {
@ -368,8 +369,21 @@ class TxRow extends Component {
return 'pending';
}
killTx = () => {
const { killTransaction, tx } = this.props;
killTransaction(this, tx);
}
cancelTx = () => {
const { cancelTransaction, tx } = this.props;
const pendingStatus = this.getCondition();
const isPending = pendingStatus === 'pending';
if (isPending) {
this.killTx();
return;
}
cancelTransaction(this, tx);
}

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import BigNumber from 'bignumber.js';
export default class Store {
@observable blocks = {};
@ -125,6 +126,30 @@ export default class Store {
});
}
killTransaction = (txComponent, tx) => {
const { hash, gasPrice, from } = tx;
this._api.parity
.removeTransaction(hash)
.then(() => {
return this._api.parity.postTransaction({
from: from,
to: from, // set to owner
gas: new BigNumber(21000), // set default gas
gasPrice: gasPrice.times(1.25), // must be a minimum of 10% growth to be recognized as a replacement by miners (incentive)
value: new BigNumber(0), // zero out the value
condition: null, // ensure to post this instantly
data: '0x'
});
})
.then(() => {
tx.Component.setState({ canceled: true });
})
.catch((err) => {
this._onNewError({ message: err });
});
}
editTransaction = (txComponent, tx) => {
const { hash, gas, gasPrice, to, from, value, input, condition } = tx;

View File

@ -60,7 +60,7 @@ class TxList extends Component {
renderRows () {
const { address, netVersion, blockNumber } = this.props;
const { editTransaction, cancelTransaction } = this.store;
const { editTransaction, cancelTransaction, killTransaction } = this.store;
return this.store.sortedHashes.map((txhash) => {
const tx = this.store.transactions[txhash];
@ -77,6 +77,7 @@ class TxList extends Component {
netVersion={ netVersion }
editTransaction={ editTransaction }
cancelTransaction={ cancelTransaction }
killTransaction={ killTransaction }
/>
);
});

View File

@ -0,0 +1,152 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { BlockStatus } from '~/ui';
import styles from './status.css';
class Status extends Component {
static propTypes = {
clientVersion: PropTypes.string,
isTest: PropTypes.bool,
netChain: PropTypes.string,
netPeers: PropTypes.object,
upgradeStore: PropTypes.object.isRequired
}
render () {
const { clientVersion, isTest, netChain, netPeers } = this.props;
return (
<div className={ styles.status }>
<div className={ styles.version }>
{ clientVersion }
</div>
<div className={ styles.upgrade }>
{ this.renderConsensus() }
{ this.renderUpgradeButton() }
</div>
<div className={ styles.netinfo }>
<BlockStatus />
<div className={ `${styles.network} ${styles[isTest ? 'test' : 'live']}` }>
{ netChain }
</div>
<div className={ styles.peers }>
{ netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers
</div>
</div>
</div>
);
}
renderConsensus () {
const { upgradeStore } = this.props;
if (!upgradeStore || !upgradeStore.consensusCapability) {
return null;
}
if (upgradeStore.consensusCapability === 'capable') {
return (
<div>
<FormattedMessage
id='application.status.consensus.capable'
defaultMessage='Upgrade not required.'
/>
</div>
);
}
if (upgradeStore.consensusCapability.capableUntil) {
return (
<div>
<FormattedMessage
id='application.status.consensus.capableUntil'
defaultMessage='Upgrade required before #{blockNumber}'
values={ {
blockNumber: upgradeStore.consensusCapability.capableUntil
} }
/>
</div>
);
}
if (upgradeStore.consensusCapability.incapableSince) {
return (
<div>
<FormattedMessage
id='application.status.consensus.incapableSince'
defaultMessage='Upgrade required since #{blockNumber}'
values={ {
blockNumber: upgradeStore.consensusCapability.incapableSince
} }
/>
</div>
);
}
return (
<div>
<FormattedMessage
id='application.status.consensus.unknown'
defaultMessage='Upgrade status is unknown.'
/>
</div>
);
}
renderUpgradeButton () {
const { upgradeStore } = this.props;
if (!upgradeStore.available) {
return null;
}
return (
<div>
<a
href='javascript:void(0)'
onClick={ upgradeStore.openModal }
>
<FormattedMessage
id='application.status.upgrade'
defaultMessage='Upgrade'
/>
</a>
</div>
);
}
}
function mapStateToProps (state) {
const { clientVersion, netPeers, netChain, isTest } = state.nodeStatus;
return {
clientVersion,
netPeers,
netChain,
isTest
};
}
export default connect(
mapStateToProps,
null
)(Status);

View File

@ -146,11 +146,10 @@ module.exports = {
resolve: {
alias: {
'~/api/local': path.resolve(__dirname, '../src/api/local/localAccountsMiddleware.js'),
'~': path.resolve(__dirname, '../src'),
'@parity/wordlist': path.resolve(__dirname, '../node_modules/@parity/wordlist'),
'@parity': path.resolve(__dirname, '../src'),
'keythereum': path.resolve(__dirname, '../node_modules/keythereum/dist/keythereum'),
'vertx': 'empty-module'
'@parity': path.resolve(__dirname, '../src')
},
modules: [
path.join(__dirname, '../node_modules')

View File

@ -45,10 +45,7 @@ module.exports = {
alias: {
'~': path.resolve(__dirname, '../src'),
'@parity/wordlist': path.resolve(__dirname, '../node_modules/@parity/wordlist'),
'@parity': path.resolve(__dirname, '../src'),
'keythereum': 'empty-module',
'secp256k1': 'empty-module',
'vertx': 'empty-module'
'@parity': path.resolve(__dirname, '../src')
}
},

View File

@ -77,10 +77,7 @@ module.exports = {
alias: {
'~': path.resolve(__dirname, '../src'),
'@parity/wordlist': path.resolve(__dirname, '../node_modules/@parity/wordlist'),
'@parity': path.resolve(__dirname, '../src'),
'keythereum': 'empty-module',
'secp256k1': 'empty-module',
'vertx': 'empty-module'
'@parity': path.resolve(__dirname, '../src')
},
modules: [
path.resolve('./src'),

View File

@ -49,6 +49,9 @@ pub struct AuthorityRoundParams {
/// Block from which monotonic steps start.
#[serde(rename="validateStepTransition")]
pub validate_step_transition: Option<Uint>,
/// Whether transitions should be immediate.
#[serde(rename="immediateTransitions")]
pub immediate_transitions: Option<bool>,
}
/// Authority engine deserialization.
@ -92,5 +95,6 @@ mod tests {
assert!(deserialized.params.registrar.is_none());
assert_eq!(deserialized.params.start_step, Some(Uint(U256::from(24))));
assert_eq!(deserialized.params.eip155_transition, Some(Uint(U256::from(0x42))));
assert_eq!(deserialized.params.immediate_transitions, None);
}
}

View File

@ -81,6 +81,14 @@ pub struct Params {
/// See `CommonParams` docs.
#[serde(rename="eip214Transition")]
pub eip214_transition: Option<Uint>,
/// See `CommonParams` docs.
#[serde(rename="dustProtectionTransition")]
pub dust_protection_transition: Option<Uint>,
/// See `CommonParams` docs.
#[serde(rename="nonceCapIncrement")]
pub nonce_cap_increment: Option<Uint>,
/// See `CommonParams` docs.
pub remove_dust_contracts : Option<bool>,
}
#[cfg(test)]

View File

@ -315,7 +315,8 @@ fn start_client(
fat_db: Switch,
compaction: DatabaseCompactionProfile,
wal: bool,
cache_config: CacheConfig
cache_config: CacheConfig,
require_fat_db: bool,
) -> Result<ClientService, String> {
// load spec file
@ -343,6 +344,9 @@ fn start_client(
// check if fatdb is on
let fat_db = fatdb_switch_to_bool(fat_db, &user_defaults, algorithm)?;
if !fat_db && require_fat_db {
return Err("This command requires Parity to be synced with --fat-db on.".to_owned());
}
// prepare client and snapshot paths.
let client_path = db_dirs.client_path(algorithm);
@ -395,7 +399,8 @@ fn execute_export(cmd: ExportBlockchain) -> Result<(), String> {
cmd.fat_db,
cmd.compaction,
cmd.wal,
cmd.cache_config
cmd.cache_config,
false,
)?;
let format = cmd.format.unwrap_or_default();
@ -435,7 +440,8 @@ fn execute_export_state(cmd: ExportState) -> Result<(), String> {
cmd.fat_db,
cmd.compaction,
cmd.wal,
cmd.cache_config
cmd.cache_config,
true
)?;
let client = service.client();

View File

@ -107,7 +107,7 @@ impl ContractClient for LightRegistrar {
self.on_demand
.request(ctx, on_demand::request::TransactionProof {
tx: Transaction {
nonce: self.client.engine().account_start_nonce(),
nonce: self.client.engine().account_start_nonce(header.number()),
action: Action::Call(address),
gas: 50_000_000.into(),
gas_price: 0.into(),

View File

@ -158,7 +158,7 @@ impl Informant {
false => String::new(),
},
match (&sync_status, &network_config) {
(&Some(ref sync_info), &Some(ref net_config)) => format!("{}{}/{}/{} peers",
(&Some(ref sync_info), &Some(ref net_config)) => format!("{}{}/{} peers",
match importing {
true => format!("{} ", paint(Green.bold(), format!("{:>8}", format!("#{}", sync_info.last_imported_block_number.unwrap_or(chain_info.best_block_number))))),
false => match sync_info.last_imported_old_block_number {
@ -166,7 +166,6 @@ impl Informant {
None => String::new(),
}
},
paint(Cyan.bold(), format!("{:2}", sync_info.num_active_peers)),
paint(Cyan.bold(), format!("{:2}", sync_info.num_peers)),
paint(Cyan.bold(), format!("{:2}", sync_info.current_max_peers(net_config.min_peers, net_config.max_peers))),
),

View File

@ -67,7 +67,7 @@ impl IoHandler<ClientIoMessage> for QueueCull {
let (sync, on_demand, txq) = (self.sync.clone(), self.on_demand.clone(), self.txq.clone());
let best_header = self.client.best_block_header();
let start_nonce = self.client.engine().account_start_nonce();
let start_nonce = self.client.engine().account_start_nonce(best_header.number());
info!(target: "cull", "Attempting to cull queued transactions from {} senders.", senders.len());
self.remote.spawn_with_timeout(move || {

View File

@ -443,7 +443,13 @@ impl LightDependencies {
}
},
Api::EthPubSub => {
let client = EthPubSubClient::new(self.client.clone(), self.remote.clone());
let client = EthPubSubClient::light(
self.client.clone(),
self.on_demand.clone(),
self.sync.clone(),
self.cache.clone(),
self.remote.clone(),
);
self.client.add_listener(
Arc::downgrade(&client.handler()) as Weak<::light::client::LightChainNotify>
);

View File

@ -227,7 +227,8 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) ->
}
// start on_demand service.
let on_demand = Arc::new(::light::on_demand::OnDemand::new(cache.clone()));
let account_start_nonce = service.client().engine().account_start_nonce(0);
let on_demand = Arc::new(::light::on_demand::OnDemand::new(cache.clone(), account_start_nonce));
// set network path.
net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned());

View File

@ -59,7 +59,7 @@ extern crate stats;
#[macro_use]
extern crate log;
#[macro_use]
#[cfg_attr(test, macro_use)]
extern crate ethcore_util as util;
#[macro_use]
extern crate jsonrpc_macros;

View File

@ -140,6 +140,9 @@ fn add_security_headers(res: &mut ws::ws::Response) {
headers.push(("X-Frame-Options".into(), b"SAMEORIGIN".to_vec()));
headers.push(("X-XSS-Protection".into(), b"1; mode=block".to_vec()));
headers.push(("X-Content-Type-Options".into(), b"nosniff".to_vec()));
headers.push(("Content-Security-Policy".into(),
b"default-src 'self';form-action 'none';block-all-mixed-content;sandbox allow-scripts;".to_vec()
));
}
fn auth_token_hash(codes_path: &Path, protocol: &str, save_file: bool) -> Option<H256> {

View File

@ -284,7 +284,7 @@ impl LightDispatcher {
}
let best_header = self.client.best_block_header();
let account_start_nonce = self.client.engine().account_start_nonce();
let account_start_nonce = self.client.engine().account_start_nonce(best_header.number());
let nonce_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account {
header: best_header.into(),
address: addr,

View File

@ -22,6 +22,7 @@ use ethcore::basic_account::BasicAccount;
use ethcore::encoded;
use ethcore::executed::{Executed, ExecutionError};
use ethcore::ids::BlockId;
use ethcore::filter::Filter as EthcoreFilter;
use ethcore::transaction::{Action, Transaction as EthTransaction};
use futures::{future, Future, BoxFuture};
@ -38,7 +39,7 @@ use ethsync::LightSync;
use util::{Address, Mutex, U256};
use v1::helpers::{CallRequest as CallRequestHelper, errors, dispatch};
use v1::types::{BlockNumber, CallRequest};
use v1::types::{BlockNumber, CallRequest, Log};
/// Helper for fetching blockchain data either from the light client or the network
/// as necessary.
@ -259,4 +260,63 @@ impl LightFetch {
None => future::err(errors::network_disabled()).boxed()
}
}
/// get transaction logs
pub fn logs(&self, filter: EthcoreFilter) -> BoxFuture<Vec<Log>, Error> {
use std::collections::BTreeMap;
use futures::stream::{self, Stream};
const NO_INVALID_BACK_REFS: &'static str = "Fails only on invalid back-references; back-references here known to be valid; qed";
// early exit for "to" block before "from" block.
let best_number = self.client.chain_info().best_block_number;
let block_number = |id| match id {
BlockId::Earliest => Some(0),
BlockId::Latest | BlockId::Pending => Some(best_number),
BlockId::Hash(h) => self.client.block_header(BlockId::Hash(h)).map(|hdr| hdr.number()),
BlockId::Number(x) => Some(x),
};
match (block_number(filter.to_block), block_number(filter.from_block)) {
(Some(to), Some(from)) if to < from => return future::ok(Vec::new()).boxed(),
(Some(_), Some(_)) => {},
_ => return future::err(errors::unknown_block()).boxed(),
}
let maybe_future = self.sync.with_context(move |ctx| {
// find all headers which match the filter, and fetch the receipts for each one.
// match them with their numbers for easy sorting later.
let bit_combos = filter.bloom_possibilities();
let receipts_futures: Vec<_> = self.client.ancestry_iter(filter.to_block)
.take_while(|ref hdr| BlockId::Number(hdr.number()) != filter.from_block)
.take_while(|ref hdr| BlockId::Hash(hdr.hash()) != filter.from_block)
.filter(|ref hdr| {
let hdr_bloom = hdr.log_bloom();
bit_combos.iter().find(|&bloom| hdr_bloom & *bloom == *bloom).is_some()
})
.map(|hdr| (hdr.number(), request::BlockReceipts(hdr.into())))
.map(|(num, req)| self.on_demand.request(ctx, req).expect(NO_INVALID_BACK_REFS).map(move |x| (num, x)))
.collect();
// as the receipts come in, find logs within them which match the filter.
// insert them into a BTreeMap to maintain order by number and block index.
stream::futures_unordered(receipts_futures)
.fold(BTreeMap::new(), move |mut matches, (num, receipts)| {
for (block_index, log) in receipts.into_iter().flat_map(|r| r.logs).enumerate() {
if filter.matches(&log) {
matches.insert((num, block_index), log.into());
}
}
future::ok(matches)
}) // and then collect them into a vector.
.map(|matches| matches.into_iter().map(|(_, v)| v).collect())
.map_err(errors::on_demand_cancel)
});
match maybe_future {
Some(fut) => fut.boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
}
}

View File

@ -95,7 +95,7 @@ impl<T> Subscribers<T> {
}
}
impl <T> Subscribers<Sink<T>> {
impl<T> Subscribers<Sink<T>> {
/// Assigns id and adds a subscriber to the list.
pub fn push(&mut self, sub: Subscriber<T>) {
let id = self.next_id();
@ -106,6 +106,17 @@ impl <T> Subscribers<Sink<T>> {
}
}
impl<T, V> Subscribers<(Sink<T>, V)> {
/// Assigns id and adds a subscriber to the list.
pub fn push(&mut self, sub: Subscriber<T>, val: V) {
let id = self.next_id();
if let Ok(sink) = sub.assign_id(SubscriptionId::String(id.as_string())) {
debug!(target: "pubsub", "Adding subscription id={:?}", id);
self.subscriptions.insert(id, (sink, val));
}
}
}
impl<T> ops::Deref for Subscribers<T> {
type Target = HashMap<Id, T>;

View File

@ -19,40 +19,51 @@
use std::sync::Arc;
use std::collections::BTreeMap;
use futures::{self, BoxFuture, Future};
use futures::{self, future, BoxFuture, Future};
use jsonrpc_core::Error;
use jsonrpc_macros::Trailing;
use jsonrpc_macros::pubsub::{Sink, Subscriber};
use jsonrpc_pubsub::SubscriptionId;
use v1::helpers::{errors, Subscribers};
use v1::helpers::{errors, limit_logs, Subscribers};
use v1::helpers::light_fetch::LightFetch;
use v1::metadata::Metadata;
use v1::traits::EthPubSub;
use v1::types::{pubsub, RichHeader};
use v1::types::{pubsub, RichHeader, Log};
use ethcore::encoded;
use ethcore::filter::Filter as EthFilter;
use ethcore::client::{BlockChainClient, ChainNotify, BlockId};
use ethsync::LightSync;
use light::cache::Cache;
use light::on_demand::OnDemand;
use light::client::{LightChainClient, LightChainNotify};
use parity_reactor::Remote;
use util::{Mutex, H256, Bytes};
use util::{RwLock, Mutex, H256, Bytes};
type Client = Sink<pubsub::Result>;
/// Eth PubSub implementation.
pub struct EthPubSubClient<C> {
handler: Arc<ChainNotificationHandler<C>>,
heads_subscribers: Arc<Mutex<Subscribers<Sink<pubsub::Result>>>>,
heads_subscribers: Arc<RwLock<Subscribers<Client>>>,
logs_subscribers: Arc<RwLock<Subscribers<(Client, EthFilter)>>>,
}
impl<C> EthPubSubClient<C> {
/// Creates new `EthPubSubClient`.
pub fn new(client: Arc<C>, remote: Remote) -> Self {
let heads_subscribers = Arc::new(Mutex::new(Subscribers::default()));
let heads_subscribers = Arc::new(RwLock::new(Subscribers::default()));
let logs_subscribers = Arc::new(RwLock::new(Subscribers::default()));
EthPubSubClient {
handler: Arc::new(ChainNotificationHandler {
client,
remote,
heads_subscribers: heads_subscribers.clone(),
logs_subscribers: logs_subscribers.clone(),
}),
heads_subscribers,
logs_subscribers,
}
}
@ -60,7 +71,8 @@ impl<C> EthPubSubClient<C> {
#[cfg(test)]
pub fn new_test(client: Arc<C>, remote: Remote) -> Self {
let client = Self::new(client, remote);
*client.heads_subscribers.lock() = Subscribers::new_test();
*client.heads_subscribers.write() = Subscribers::new_test();
*client.logs_subscribers.write() = Subscribers::new_test();
client
}
@ -70,42 +82,116 @@ impl<C> EthPubSubClient<C> {
}
}
impl EthPubSubClient<LightFetch> {
/// Creates a new `EthPubSubClient` for `LightClient`.
pub fn light(
client: Arc<LightChainClient>,
on_demand: Arc<OnDemand>,
sync: Arc<LightSync>,
cache: Arc<Mutex<Cache>>,
remote: Remote,
) -> Self {
let fetch = LightFetch {
client,
on_demand,
sync,
cache
};
EthPubSubClient::new(Arc::new(fetch), remote)
}
}
/// PubSub Notification handler.
pub struct ChainNotificationHandler<C> {
client: Arc<C>,
remote: Remote,
heads_subscribers: Arc<Mutex<Subscribers<Sink<pubsub::Result>>>>,
heads_subscribers: Arc<RwLock<Subscribers<Client>>>,
logs_subscribers: Arc<RwLock<Subscribers<(Client, EthFilter)>>>,
}
impl<C> ChainNotificationHandler<C> {
fn notify(&self, blocks: Vec<(encoded::Header, BTreeMap<String, String>)>) {
for subscriber in self.heads_subscribers.lock().values() {
for &(ref block, ref extra_info) in &blocks {
self.remote.spawn(subscriber
.notify(Ok(pubsub::Result::Header(RichHeader {
inner: block.into(),
extra_info: extra_info.clone(),
})))
.map(|_| ())
.map_err(|e| warn!(target: "rpc", "Unable to send notification: {}", e))
);
fn notify(remote: &Remote, subscriber: &Client, result: pubsub::Result) {
remote.spawn(subscriber
.notify(Ok(result))
.map(|_| ())
.map_err(|e| warn!(target: "rpc", "Unable to send notification: {}", e))
);
}
fn notify_heads(&self, headers: &[(encoded::Header, BTreeMap<String, String>)]) {
for subscriber in self.heads_subscribers.read().values() {
for &(ref header, ref extra_info) in headers {
Self::notify(&self.remote, subscriber, pubsub::Result::Header(RichHeader {
inner: header.into(),
extra_info: extra_info.clone(),
}));
}
}
}
fn notify_logs<F>(&self, enacted: &[H256], logs: F) where
F: Fn(EthFilter) -> BoxFuture<Vec<Log>, Error>,
{
for &(ref subscriber, ref filter) in self.logs_subscribers.read().values() {
let logs = futures::future::join_all(enacted
.iter()
.map(|hash| {
let mut filter = filter.clone();
filter.from_block = BlockId::Hash(*hash);
filter.to_block = filter.from_block.clone();
logs(filter)
})
.collect::<Vec<_>>()
);
let limit = filter.limit;
let remote = self.remote.clone();
let subscriber = subscriber.clone();
self.remote.spawn(logs
.map(move |logs| {
let logs = logs.into_iter().flat_map(|log| log).collect();
let logs = limit_logs(logs, limit);
if !logs.is_empty() {
Self::notify(&remote, &subscriber, pubsub::Result::Logs(logs));
}
})
.map_err(|e| warn!("Unable to fetch latest logs: {:?}", e))
);
}
}
}
impl<C: LightChainClient> LightChainNotify for ChainNotificationHandler<C> {
/// A light client wrapper struct.
pub trait LightClient: Send + Sync {
/// Get a recent block header.
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
/// Fetch logs.
fn logs(&self, filter: EthFilter) -> BoxFuture<Vec<Log>, Error>;
}
impl LightClient for LightFetch {
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
self.client.block_header(id)
}
fn logs(&self, filter: EthFilter) -> BoxFuture<Vec<Log>, Error> {
LightFetch::logs(self, filter)
}
}
impl<C: LightClient> LightChainNotify for ChainNotificationHandler<C> {
fn new_headers(
&self,
headers: &[H256],
enacted: &[H256],
) {
let blocks = headers
let headers = enacted
.iter()
.filter_map(|hash| self.client.block_header(BlockId::Hash(*hash)))
.map(|header| (header, Default::default()))
.collect();
.collect::<Vec<_>>();
self.notify(blocks);
self.notify_heads(&headers);
self.notify_logs(&enacted, |filter| self.client.logs(filter))
}
}
@ -115,22 +201,37 @@ impl<C: BlockChainClient> ChainNotify for ChainNotificationHandler<C> {
_imported: Vec<H256>,
_invalid: Vec<H256>,
enacted: Vec<H256>,
_retracted: Vec<H256>,
retracted: Vec<H256>,
_sealed: Vec<H256>,
// Block bytes.
_proposed: Vec<Bytes>,
_duration: u64,
) {
const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched earlier), extra_info is always available if object exists; qed";
let blocks = enacted
.into_iter()
.filter_map(|hash| self.client.block_header(BlockId::Hash(hash)))
let headers = enacted
.iter()
.filter_map(|hash| self.client.block_header(BlockId::Hash(*hash)))
.map(|header| {
let hash = header.hash();
(header, self.client.block_extra_info(BlockId::Hash(hash)).expect(EXTRA_INFO_PROOF))
})
.collect();
self.notify(blocks);
.collect::<Vec<_>>();
// Headers
self.notify_heads(&headers);
// Enacted logs
self.notify_logs(&enacted, |filter| {
future::ok(self.client.logs(filter).into_iter().map(Into::into).collect()).boxed()
});
// Retracted logs
self.notify_logs(&retracted, |filter| {
future::ok(self.client.logs(filter).into_iter().map(Into::into).map(|mut log: Log| {
log.log_type = "removed".into();
log
}).collect()).boxed()
});
}
}
@ -144,10 +245,12 @@ impl<C: Send + Sync + 'static> EthPubSub for EthPubSubClient<C> {
kind: pubsub::Kind,
params: Trailing<pubsub::Params>,
) {
let params: Option<pubsub::Params> = params.into();
match (kind, params) {
match (kind, params.into()) {
(pubsub::Kind::NewHeads, None) => {
self.heads_subscribers.lock().push(subscriber)
self.heads_subscribers.write().push(subscriber)
},
(pubsub::Kind::Logs, Some(pubsub::Params::Logs(filter))) => {
self.logs_subscribers.write().push(subscriber, filter.into());
},
_ => {
let _ = subscriber.reject(errors::unimplemented(None));
@ -156,7 +259,9 @@ impl<C: Send + Sync + 'static> EthPubSub for EthPubSubClient<C> {
}
fn unsubscribe(&self, id: SubscriptionId) -> BoxFuture<bool, Error> {
let res = self.heads_subscribers.lock().remove(&id).is_some();
futures::future::ok(res).boxed()
let res = self.heads_subscribers.write().remove(&id).is_some();
let res2 = self.logs_subscribers.write().remove(&id).is_some();
future::ok(res || res2).boxed()
}
}

View File

@ -477,60 +477,7 @@ impl Filterable for EthClient {
}
fn logs(&self, filter: EthcoreFilter) -> BoxFuture<Vec<Log>, Error> {
use std::collections::BTreeMap;
use futures::stream::{self, Stream};
use util::H2048;
// early exit for "to" block before "from" block.
let best_number = self.client.chain_info().best_block_number;
let block_number = |id| match id {
BlockId::Earliest => Some(0),
BlockId::Latest | BlockId::Pending => Some(best_number),
BlockId::Hash(h) => self.client.block_header(BlockId::Hash(h)).map(|hdr| hdr.number()),
BlockId::Number(x) => Some(x),
};
match (block_number(filter.to_block), block_number(filter.from_block)) {
(Some(to), Some(from)) if to < from => return future::ok(Vec::new()).boxed(),
(Some(_), Some(_)) => {},
_ => return future::err(errors::unknown_block()).boxed(),
}
let maybe_future = self.sync.with_context(move |ctx| {
// find all headers which match the filter, and fetch the receipts for each one.
// match them with their numbers for easy sorting later.
let bit_combos = filter.bloom_possibilities();
let receipts_futures: Vec<_> = self.client.ancestry_iter(filter.to_block)
.take_while(|ref hdr| BlockId::Number(hdr.number()) != filter.from_block)
.take_while(|ref hdr| BlockId::Hash(hdr.hash()) != filter.from_block)
.filter(|ref hdr| {
let hdr_bloom = hdr.log_bloom();
bit_combos.iter().find(|&bloom| hdr_bloom & *bloom == *bloom).is_some()
})
.map(|hdr| (hdr.number(), request::BlockReceipts(hdr.into())))
.map(|(num, req)| self.on_demand.request(ctx, req).expect(NO_INVALID_BACK_REFS).map(move |x| (num, x)))
.collect();
// as the receipts come in, find logs within them which match the filter.
// insert them into a BTreeMap to maintain order by number and block index.
stream::futures_unordered(receipts_futures)
.fold(BTreeMap::new(), move |mut matches, (num, receipts)| {
for (block_index, log) in receipts.into_iter().flat_map(|r| r.logs).enumerate() {
if filter.matches(&log) {
matches.insert((num, block_index), log.into());
}
}
future::ok(matches)
}) // and then collect them into a vector.
.map(|matches| matches.into_iter().map(|(_, v)| v).collect())
.map_err(errors::on_demand_cancel)
});
match maybe_future {
Some(fut) => fut.boxed(),
None => future::err(errors::network_disabled()).boxed(),
}
self.fetcher().logs(filter)
}
fn pending_logs(&self, _block_number: u64, _filter: &EthcoreFilter) -> Vec<Log> {

View File

@ -16,7 +16,7 @@
//! Account management (personal) rpc implementation
use std::sync::Arc;
use std::collections::BTreeMap;
use std::collections::btree_map::{BTreeMap, Entry};
use util::Address;
use ethkey::{Brain, Generator, Secret};
@ -27,7 +27,7 @@ use jsonrpc_core::Error;
use v1::helpers::errors;
use v1::helpers::accounts::unwrap_provider;
use v1::traits::ParityAccounts;
use v1::types::{H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, DappId, Derive, DeriveHierarchical, DeriveHash};
use v1::types::{H160 as RpcH160, H256 as RpcH256, H520 as RpcH520, DappId, Derive, DeriveHierarchical, DeriveHash, ExtAccountInfo};
/// Account management (personal) rpc implementation.
pub struct ParityAccountsClient {
@ -50,26 +50,36 @@ impl ParityAccountsClient {
}
impl ParityAccounts for ParityAccountsClient {
fn all_accounts_info(&self) -> Result<BTreeMap<RpcH160, BTreeMap<String, String>>, Error> {
fn all_accounts_info(&self) -> Result<BTreeMap<RpcH160, ExtAccountInfo>, Error> {
let store = self.account_provider()?;
let info = store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
let other = store.addresses_info();
Ok(info
.into_iter()
.chain(other.into_iter())
.map(|(address, v)| {
let mut m = map![
"name".to_owned() => v.name,
"meta".to_owned() => v.meta
];
if let &Some(ref uuid) = &v.uuid {
m.insert("uuid".to_owned(), format!("{}", uuid));
}
(address.into(), m)
})
.collect()
)
let account_iter = info
.into_iter()
.chain(other.into_iter())
.map(|(address, v)| (address.into(), ExtAccountInfo {
name: v.name,
meta: v.meta,
uuid: v.uuid.map(|uuid| uuid.to_string())
}));
let mut accounts: BTreeMap<RpcH160, ExtAccountInfo> = BTreeMap::new();
for (address, account) in account_iter {
match accounts.entry(address) {
/// Insert only if occupied entry isn't already an account with UUID
Entry::Occupied(ref mut occupied) if occupied.get().uuid.is_none() => {
occupied.insert(account);
},
Entry::Vacant(vacant) => {
vacant.insert(account);
},
_ => {}
};
}
Ok(accounts)
}
fn new_account_from_phrase(&self, phrase: String, pass: String) -> Result<RpcH160, Error> {

View File

@ -78,6 +78,78 @@ fn should_subscribe_to_new_heads() {
assert_eq!(res, None);
}
#[test]
fn should_subscribe_to_logs() {
use ethcore::log_entry::{LocalizedLogEntry, LogEntry};
use ethcore::ids::BlockId;
use ethcore::client::BlockChainClient;
// given
let el = EventLoop::spawn();
let mut client = TestBlockChainClient::new();
// Insert some blocks
client.add_blocks(1, EachBlockWith::Transaction);
let h1 = client.block_hash_delta_minus(1);
let block = client.block(BlockId::Hash(h1)).unwrap();
let tx_hash = block.transactions()[0].hash();
client.set_logs(vec![
LocalizedLogEntry {
entry: LogEntry {
address: 5.into(),
topics: vec![1.into(), 2.into(), 0.into(), 0.into()],
data: vec![],
},
block_hash: h1,
block_number: block.header().number(),
transaction_hash: tx_hash,
transaction_index: 0,
log_index: 0,
transaction_log_index: 0,
}
]);
let pubsub = EthPubSubClient::new_test(Arc::new(client), el.remote());
let handler = pubsub.handler();
let pubsub = pubsub.to_delegate();
let mut io = MetaIoHandler::default();
io.extend_with(pubsub);
let mut metadata = Metadata::default();
let (sender, receiver) = futures::sync::mpsc::channel(8);
metadata.session = Some(Arc::new(Session::new(sender)));
// Subscribe
let request = r#"{"jsonrpc": "2.0", "method": "eth_subscribe", "params": ["logs", {}], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":"0x416d77337e24399d","id":1}"#;
assert_eq!(io.handle_request_sync(request, metadata.clone()), Some(response.to_owned()));
// Check notifications (enacted)
handler.new_blocks(vec![], vec![], vec![h1], vec![], vec![], vec![], 0);
let (res, receiver) = receiver.into_future().wait().unwrap();
let response = r#"{"jsonrpc":"2.0","method":"eth_subscription","params":{"result":[{"address":"0x0000000000000000000000000000000000000005","blockHash":"0x3457d2fa2e3dd33c78ac681cf542e429becf718859053448748383af67e23218","blockNumber":"0x1","data":"0x","logIndex":"0x0","topics":["0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash":""#.to_owned()
+ &format!("0x{:?}", tx_hash)
+ r#"","transactionIndex":"0x0","transactionLogIndex":"0x0","type":"mined"}],"subscription":"0x416d77337e24399d"}}"#;
assert_eq!(res, Some(response.into()));
// Check notifications (retracted)
handler.new_blocks(vec![], vec![], vec![], vec![h1], vec![], vec![], 0);
let (res, receiver) = receiver.into_future().wait().unwrap();
let response = r#"{"jsonrpc":"2.0","method":"eth_subscription","params":{"result":[{"address":"0x0000000000000000000000000000000000000005","blockHash":"0x3457d2fa2e3dd33c78ac681cf542e429becf718859053448748383af67e23218","blockNumber":"0x1","data":"0x","logIndex":"0x0","topics":["0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000000"],"transactionHash":""#.to_owned()
+ &format!("0x{:?}", tx_hash)
+ r#"","transactionIndex":"0x0","transactionLogIndex":"0x0","type":"removed"}],"subscription":"0x416d77337e24399d"}}"#;
assert_eq!(res, Some(response.into()));
// And unsubscribe
let request = r#"{"jsonrpc": "2.0", "method": "eth_unsubscribe", "params": ["0x416d77337e24399d"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(io.handle_request_sync(request, metadata), Some(response.to_owned()));
let (res, _receiver) = receiver.into_future().wait().unwrap();
assert_eq!(res, None);
}
#[test]
fn should_return_unimplemented() {
// given
@ -97,8 +169,6 @@ fn should_return_unimplemented() {
let response = r#"{"jsonrpc":"2.0","error":{"code":-32000,"message":"This request is not implemented yet. Please create an issue on Github repo."},"id":1}"#;
let request = r#"{"jsonrpc": "2.0", "method": "eth_subscribe", "params": ["newPendingTransactions"], "id": 1}"#;
assert_eq!(io.handle_request_sync(request, metadata.clone()), Some(response.to_owned()));
let request = r#"{"jsonrpc": "2.0", "method": "eth_subscribe", "params": ["logs"], "id": 1}"#;
assert_eq!(io.handle_request_sync(request, metadata.clone()), Some(response.to_owned()));
let request = r#"{"jsonrpc": "2.0", "method": "eth_subscribe", "params": ["syncing"], "id": 1}"#;
assert_eq!(io.handle_request_sync(request, metadata.clone()), Some(response.to_owned()));
}

View File

@ -563,7 +563,7 @@ fn should_generate_new_web_proxy_token() {
let request = r#"{
"jsonrpc":"2.0",
"method":"signer_generateWebProxyAccessToken",
"params":[],
"params":["https://parity.io"],
"id":1
}"#;
let response = tester.io.handle_request_sync(&request).unwrap();
@ -571,7 +571,7 @@ fn should_generate_new_web_proxy_token() {
if let Response::Single(Output::Success(ref success)) = result {
if let Value::String(ref token) = success.result {
assert!(tester.signer.is_valid_web_proxy_access_token(&token), "It should return valid web proxy token.");
assert_eq!(tester.signer.web_proxy_access_token_domain(&token), Some("https://parity.io".into()));
return;
}
}

View File

@ -19,14 +19,14 @@ use std::collections::BTreeMap;
use jsonrpc_core::Error;
use ethstore::KeyFile;
use v1::types::{H160, H256, H520, DappId, DeriveHash, DeriveHierarchical};
use v1::types::{H160, H256, H520, DappId, DeriveHash, DeriveHierarchical, ExtAccountInfo};
build_rpc_trait! {
/// Personal Parity rpc interface.
pub trait ParityAccounts {
/// Returns accounts information.
#[rpc(name = "parity_allAccountsInfo")]
fn all_accounts_info(&self) -> Result<BTreeMap<H160, BTreeMap<String, String>>, Error>;
fn all_accounts_info(&self) -> Result<BTreeMap<H160, ExtAccountInfo>, Error>;
/// Creates new account from the given phrase using standard brainwallet mechanism.
/// Second parameter is password for the new account.

View File

@ -21,6 +21,18 @@ pub struct AccountInfo {
pub name: String,
}
/// Extended account information (used by `parity_allAccountInfo`).
#[derive(Debug, Default, Clone, PartialEq, Serialize)]
pub struct ExtAccountInfo {
/// Account name
pub name: String,
/// Account meta JSON
pub meta: String,
/// Account UUID (`None` for address book entries)
#[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<String>,
}
/// Hardware wallet information.
#[derive(Debug, Default, Clone, PartialEq, Serialize)]
pub struct HwAccountInfo {
@ -29,3 +41,4 @@ pub struct HwAccountInfo {
/// Device manufacturer.
pub manufacturer: String,
}

View File

@ -100,7 +100,7 @@ pub struct Block {
}
/// Block header representation.
#[derive(Debug, Serialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct Header {
/// Hash of the block
pub hash: Option<H256>,
@ -186,7 +186,7 @@ pub type RichBlock = Rich<Block>;
pub type RichHeader = Rich<Header>;
/// Value representation with additional info
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rich<T> {
/// Standard value.
pub inner: T,

View File

@ -46,7 +46,7 @@ mod work;
pub mod pubsub;
pub use self::account_info::{AccountInfo, HwAccountInfo};
pub use self::account_info::{AccountInfo, ExtAccountInfo, HwAccountInfo};
pub use self::bytes::Bytes;
pub use self::block::{RichBlock, Block, BlockTransactions, Header, RichHeader, Rich};
pub use self::block_number::BlockNumber;

View File

@ -16,14 +16,18 @@
//! Pub-Sub types.
use serde::{Serialize, Serializer};
use v1::types::{RichHeader, Filter};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::Error;
use serde_json::{Value, from_value};
use v1::types::{RichHeader, Filter, Log};
/// Subscription result.
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Result {
/// New block header.
Header(RichHeader),
/// Logs
Logs(Vec<Log>),
}
impl Serialize for Result {
@ -32,6 +36,7 @@ impl Serialize for Result {
{
match *self {
Result::Header(ref header) => header.serialize(serializer),
Result::Logs(ref logs) => logs.serialize(serializer),
}
}
}
@ -55,8 +60,7 @@ pub enum Kind {
}
/// Subscription kind.
#[derive(Debug, Deserialize, PartialEq, Eq, Hash, Clone)]
#[serde(deny_unknown_fields)]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Params {
/// No parameters passed.
None,
@ -70,11 +74,26 @@ impl Default for Params {
}
}
impl Deserialize for Params {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Params, D::Error>
where D: Deserializer {
let v: Value = Deserialize::deserialize(deserializer)?;
if v.is_null() {
return Ok(Params::None);
}
from_value(v.clone()).map(Params::Logs)
.map_err(|_| D::Error::custom("Invalid type."))
}
}
#[cfg(test)]
mod tests {
use serde_json;
use super::{Result, Kind};
use v1::types::{RichHeader, Header};
use super::{Result, Kind, Params};
use v1::types::{RichHeader, Header, Filter};
use v1::types::filter::VariadicValue;
#[test]
fn should_deserialize_kind() {
@ -84,6 +103,41 @@ mod tests {
assert_eq!(serde_json::from_str::<Kind>(r#""syncing""#).unwrap(), Kind::Syncing);
}
#[test]
fn should_deserialize_logs() {
let none = serde_json::from_str::<Params>(r#"null"#).unwrap();
assert_eq!(none, Params::None);
let logs1 = serde_json::from_str::<Params>(r#"{}"#).unwrap();
let logs2 = serde_json::from_str::<Params>(r#"{"limit":10}"#).unwrap();
let logs3 = serde_json::from_str::<Params>(
r#"{"topics":["0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"]}"#
).unwrap();
assert_eq!(logs1, Params::Logs(Filter {
from_block: None,
to_block: None,
address: None,
topics: None,
limit: None,
}));
assert_eq!(logs2, Params::Logs(Filter {
from_block: None,
to_block: None,
address: None,
topics: None,
limit: Some(10),
}));
assert_eq!(logs3, Params::Logs(Filter {
from_block: None,
to_block: None,
address: None,
topics: Some(vec![
VariadicValue::Single("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b".parse().unwrap()
)]),
limit: None,
}));
}
#[test]
fn should_serialize_header() {
let header = Result::Header(RichHeader {

View File

@ -92,14 +92,10 @@ impl Snapshot {
/// Find a chunk to download
pub fn needed_chunk(&mut self) -> Option<H256> {
// check state chunks first
let mut chunk = self.pending_state_chunks.iter()
let chunk = self.pending_state_chunks.iter()
.chain(self.pending_block_chunks.iter())
.find(|&h| !self.downloading_chunks.contains(h) && !self.completed_chunks.contains(h))
.cloned();
if chunk.is_none() {
chunk = self.pending_block_chunks.iter()
.find(|&h| !self.downloading_chunks.contains(h) && !self.completed_chunks.contains(h))
.cloned();
}
if let Some(hash) = chunk {
self.downloading_chunks.insert(hash.clone());

View File

@ -50,22 +50,26 @@ const MAX_HANDSHAKES: usize = 1024;
const DEFAULT_PORT: u16 = 30303;
// Tokens
const TCP_ACCEPT: usize = SYS_TIMER + 1;
const IDLE: usize = SYS_TIMER + 2;
const DISCOVERY: usize = SYS_TIMER + 3;
const DISCOVERY_REFRESH: usize = SYS_TIMER + 4;
const DISCOVERY_ROUND: usize = SYS_TIMER + 5;
const NODE_TABLE: usize = SYS_TIMER + 6;
const FIRST_SESSION: usize = 0;
const LAST_SESSION: usize = FIRST_SESSION + MAX_SESSIONS - 1;
const USER_TIMER: usize = LAST_SESSION + 256;
const SYS_TIMER: usize = LAST_SESSION + 1;
// StreamToken/TimerToken
const TCP_ACCEPT: StreamToken = SYS_TIMER + 1;
const IDLE: TimerToken = SYS_TIMER + 2;
const DISCOVERY: StreamToken = SYS_TIMER + 3;
const DISCOVERY_REFRESH: TimerToken = SYS_TIMER + 4;
const DISCOVERY_ROUND: TimerToken = SYS_TIMER + 5;
const NODE_TABLE: TimerToken = SYS_TIMER + 6;
const FIRST_SESSION: StreamToken = 0;
const LAST_SESSION: StreamToken = FIRST_SESSION + MAX_SESSIONS - 1;
const USER_TIMER: TimerToken = LAST_SESSION + 256;
const SYS_TIMER: TimerToken = LAST_SESSION + 1;
// Timeouts
// for IDLE TimerToken
const MAINTENANCE_TIMEOUT: u64 = 1000;
// for DISCOVERY_REFRESH TimerToken
const DISCOVERY_REFRESH_TIMEOUT: u64 = 60_000;
// for DISCOVERY_ROUND TimerToken
const DISCOVERY_ROUND_TIMEOUT: u64 = 300;
// for NODE_TABLE TimerToken
const NODE_TABLE_TIMEOUT: u64 = 300_000;
#[derive(Debug, PartialEq, Clone)]

View File

@ -177,7 +177,7 @@ impl JournalDB for ArchiveDB {
fn latest_era(&self) -> Option<u64> { self.latest_era }
fn state(&self, id: &H256) -> Option<Bytes> {
self.backing.get_by_prefix(self.column, &id[0..DB_PREFIX_LEN]).map(|b| b.to_vec())
self.backing.get_by_prefix(self.column, &id[0..DB_PREFIX_LEN]).map(|b| b.into_vec())
}
fn is_pruned(&self) -> bool { false }

Some files were not shown because too many files have changed in this diff Show More