diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ff03eb79e..26b1cc753 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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= { + 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(), + ]); } diff --git a/devtools/src/http_client.rs b/devtools/src/http_client.rs index de59a7a71..29a6d9c7c 100644 --- a/devtools/src/http_client.rs +++ b/devtools/src/http_client.rs @@ -121,4 +121,8 @@ pub fn assert_security_headers_present(headers: &[String], port: Option) { 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 + ) } diff --git a/ethcore/native_contracts/build.rs b/ethcore/native_contracts/build.rs index 6b3b4bfac..cec830929 100644 --- a/ethcore/native_contracts/build.rs +++ b/ethcore/native_contracts/build.rs @@ -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(); diff --git a/ethcore/native_contracts/generator/src/lib.rs b/ethcore/native_contracts/generator/src/lib.rs index 540d2ec2b..08ce1c97b 100644 --- a/ethcore/native_contracts/generator/src/lib.rs +++ b/ethcore/native_contracts/generator/src/lib.rs @@ -108,7 +108,7 @@ fn generate_functions(contract: &Contract) -> Result { /// Outputs: {abi_outputs:?} pub fn {snake_name}(&self, call: F, {params}) -> BoxFuture<{output_type}, String> where - F: Fn(util::Address, Vec) -> U, + F: FnOnce(util::Address, Vec) -> U, U: IntoFuture, Error=String>, U::Future: Send + 'static {{ diff --git a/ethcore/native_contracts/res/test_validator_set.json b/ethcore/native_contracts/res/test_validator_set.json new file mode 100644 index 000000000..7ed6a000f --- /dev/null +++ b/ethcore/native_contracts/res/test_validator_set.json @@ -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"} +] diff --git a/ethcore/native_contracts/res/validator_set.json b/ethcore/native_contracts/res/validator_set.json index 8c2a5e8c8..d861e16fd 100644 --- a/ethcore/native_contracts/res/validator_set.json +++ b/ethcore/native_contracts/res/validator_set.json @@ -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"} ] diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index dba7e28a5..d8014ff25 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -11,7 +11,8 @@ "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" ] - } + }, + "immediateTransitions": true } } }, diff --git a/ethcore/res/validator_contract.json b/ethcore/res/validator_contract.json index 4ef27e30e..3faec7665 100644 --- a/ethcore/res/validator_contract.json +++ b/ethcore/res/validator_contract.json @@ -8,7 +8,8 @@ "startStep": 2, "validators": { "contract": "0x0000000000000000000000000000000000000005" - } + }, + "immediateTransitions": true } } }, diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 60c7531e4..0eb1405a8 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -260,8 +260,10 @@ impl<'x> OpenBlock<'x> { author: Address, gas_range_target: (U256, U256), extra_data: Bytes, + is_epoch_begin: bool, ) -> Result { - 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, factories: Factories, + is_epoch_begin: bool, ) -> Result { { 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, factories: Factories, + is_epoch_begin: bool, ) -> Result { 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, 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(); diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 2690564e5..0434a7e7e 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -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 { - 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 { - 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 { + 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 { + // 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 { + 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![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::>(); + 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); + } + } } diff --git a/ethcore/src/blockchain/extras.rs b/ethcore/src/blockchain/extras.rs index 7de49f3bf..04c12d53a 100644 --- a/ethcore/src/blockchain/extras.rs +++ b/ethcore/src/blockchain/extras.rs @@ -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 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, // "transition/epoch" proof from the engine. - pub state_proof: Vec, // 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 { - 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::, _>>()?, - }) - } -} - #[cfg(test)] mod tests { use rlp::*; diff --git a/ethcore/src/blockchain/mod.rs b/ethcore/src/blockchain/mod.rs index 2d6d6ddb3..55a67e2b6 100644 --- a/ethcore/src/blockchain/mod.rs +++ b/ethcore/src/blockchain/mod.rs @@ -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; diff --git a/ethcore/src/client/ancient_import.rs b/ethcore/src/client/ancient_import.rs index b62450641..d4d8696d2 100644 --- a/ethcore/src/client/ancient_import.rs +++ b/ethcore/src/client/ancient_import.rs @@ -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 Result, Error>>( + pub fn verify( &self, rng: &mut R, header: &Header, - block: &[u8], - receipts: &[::receipt::Receipt], - load_verifier: F, + chain: &BlockChain, ) -> Result<(), ::error::Error> { match rng.gen::() <= 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(()) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index a42b5723e..204e6595a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -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, ¤t_epoch_data)?; + let current_verifier = self.engine.epoch_verifier(&header, ¤t_epoch_data) + .known_confirmed()?; let current_verifier = AncientVerifier::new(self.engine.clone(), current_verifier); verify_with(¤t_verifier)?; @@ -606,6 +629,7 @@ impl Client { fn commit_block(&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(&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())), } } diff --git a/ethcore/src/client/evm_test_client.rs b/ethcore/src/client/evm_test_client.rs index 945de14d7..300e2fc92 100644 --- a/ethcore/src/client/evm_test_client.rs +++ b/ethcore/src/client/evm_test_client.rs @@ -89,7 +89,7 @@ impl EvmTestClient { -> Result<(U256, Vec), 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(), diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 19c208b45..19fdd74c8 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -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 + } } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index d65642610..879620f0a 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -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. diff --git a/ethcore/src/engines/authority_round/finality.rs b/ethcore/src/engines/authority_round/finality.rs new file mode 100644 index 000000000..a6bee1ce4 --- /dev/null +++ b/ethcore/src/engines/authority_round/finality.rs @@ -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 . + +//! 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, + last_pushed: Option, +} + +impl RollingFinality { + /// Create a blank finality checker under the given validator set. + pub fn blank(signers: Vec
) -> 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(&mut self, iterable: I) -> Result<(), UnknownValidator> + where I: IntoIterator + { + 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 { + 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, 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 { + 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)); + } +} diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round/mod.rs similarity index 62% rename from ethcore/src/engines/authority_round.rs rename to ethcore/src/engines/authority_round/mod.rs index a00fe70ca..687bd6155 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -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 for AuthorityRoundParams { @@ -71,6 +81,7 @@ impl From 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, + immediate_transitions: bool, } // header-chain validator. struct EpochVerifier { - epoch_number: u64, step: Arc, 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> { + 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
= 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 { @@ -156,13 +294,25 @@ fn header_signature(header: &Header) -> Result { UntrustedRlp::new(&header.seal().get(1).expect("was checked with verify_block_basic; has 2 fields; qed")).as_val::().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(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 { + 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 { - self.validators.epoch_proof(header, caller) - .map_err(|e| EngineError::InsufficientProof(e).into()) + fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result, 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, 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> { + // 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::>(); + + // 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> { - 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(); diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 75a4806b8..e5fc478ae 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -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 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 { - self.validators.epoch_proof(header, caller) - .map_err(|e| EngineError::InsufficientProof(e).into()) + fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result, 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, 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> { + 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) { @@ -222,7 +248,7 @@ impl Engine for BasicAuthority { } fn snapshot_components(&self) -> Option> { - 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()); diff --git a/ethcore/src/engines/epoch.rs b/ethcore/src/engines/epoch.rs new file mode 100644 index 000000000..f738113cf --- /dev/null +++ b/ethcore/src/engines/epoch.rs @@ -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 . + +//! 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, +} + +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 { + 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, +} + +impl Encodable for PendingTransition { + fn rlp_append(&self, s: &mut RlpStream) { + s.append(&self.proof); + } +} + +impl Decodable for PendingTransition { + fn decode(rlp: &UntrustedRlp) -> Result { + 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> { + 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(()) } +} diff --git a/ethcore/src/engines/epoch_verifier.rs b/ethcore/src/engines/epoch_verifier.rs deleted file mode 100644 index 5fc794ec1..000000000 --- a/ethcore/src/engines/epoch_verifier.rs +++ /dev/null @@ -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 . - -// 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(()) } -} diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 249aa8ac2..83e70970d 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -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()); diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 849e08cf2..d24325803 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -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), /// 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 + 'a; +/// Returns the call result and state proof for each call. +pub type Call<'a> = Fn(Address, Bytes) -> Result<(Bytes, Vec>), String> + 'a; + +/// Type alias for a function we can get headers by hash through. +pub type Headers<'a> = Fn(H256) -> Option
+ 'a; + +/// Type alias for a function we can query pending transitions by block hash through. +pub type PendingTransitionStore<'a> = Fn(H256) -> Option + 'a; + +/// Proof generated on epoch change. +pub enum Proof { + /// Known proof (exctracted from signal) + Known(Vec), + /// Extract proof from caller. + WithState(Box Result, String>>), +} + +/// Generated epoch verifier. +pub enum ConstructedVerifier<'a> { + /// Fully trusted verifier. + Trusted(Box), + /// Verifier unconfirmed. Check whether given finality proof finalizes given hash + /// under previous epoch. + Unconfirmed(Box, &'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, 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) -> 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, + _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, Error> - { - Ok(Vec::new()) - } + /// Genesis epoch data. + fn genesis_epoch_data(&self, _header: &Header, _call: &Call) -> Result, 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, 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> { + 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( + block: &mut ExecutedBlock, + last_hashes: Arc, + engine: &E, + contract_address: Address, + gas: U256, + data: Option, + ) -> Result { + 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(block: &mut ExecutedBlock, last_hashes: Arc, 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(()) } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 1a462a634..7cb2c61bd 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -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(¤t_proposer, height as BlockNumber); + self.validators.report_benign(¤t_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) diff --git a/ethcore/src/engines/transition.rs b/ethcore/src/engines/transition.rs index 0211d21a8..590fb6db2 100644 --- a/ethcore/src/engines/transition.rs +++ b/ethcore/src/engines/transition.rs @@ -36,7 +36,7 @@ pub struct TransitionHandler { timeouts: Box>, } -impl TransitionHandler where S: Sync + Send + Clone { +impl TransitionHandler where S: Sync + Send + Clone { /// New step caller by timeouts. pub fn new(engine: Weak, timeouts: Box>) -> Self { TransitionHandler { @@ -54,7 +54,7 @@ fn set_timeout(io: &IoContext, timeout: Duration) { .unwrap_or_else(|e| warn!(target: "engine", "Failed to set consensus step timeout: {}.", e)) } -impl IoHandler for TransitionHandler where S: Sync + Send + Clone + 'static { +impl IoHandler for TransitionHandler where S: Sync + Send + Clone + 'static { fn initialize(&self, io: &IoContext) { let initial = self.timeouts.initial(); trace!(target: "engine", "Setting the initial timeout to {}.", initial); diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index b3c06aed1..5c8d51138 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -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 { + fn transact(&self) -> Box Result> { 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, String> { - self.validators.epoch_proof(header, caller) + fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result, 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> { + 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), ::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), diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs index 622793a73..56cff365f 100644 --- a/ethcore/src/engines/validator_set/mod.rs +++ b/ethcore/src/engines/validator_set/mod.rs @@ -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 + 'a; /// Creates a validator set from spec. pub fn new_validator_set(spec: ValidatorSpec) -> Box { @@ -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, 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>; - /// 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, 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), ::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) {} } diff --git a/ethcore/src/engines/validator_set/multi.rs b/ethcore/src/engines/validator_set/multi.rs index 27570ed27..5835dbcdb 100644 --- a/ethcore/src/engines/validator_set/multi.rs +++ b/ethcore/src/engines/validator_set/multi.rs @@ -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 Result + 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, String> { + self.correct_set_by_number(0).1.genesis_epoch_data(header, call) + } + + fn is_epoch_end(&self, _first: bool, chain_head: &Header) -> Option> { + 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, 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), ::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) { @@ -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> = 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()); + } } diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 27415418e..80b92247c 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -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>>, // 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]) -> 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), ::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::>()?; + + 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), ::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, 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 { + 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 { + 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::>>() + ) + .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, String> { + prove_initial(&self.provider, header, call) + } + + fn is_epoch_end(&self, _first: bool, _chain_head: &Header) -> Option> { + 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 _> = 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 = 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::>>() - ); - - 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, 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), ::error::Error> + { + use transaction::{Action, Transaction}; let rlp = UntrustedRlp::new(proof); - let nonce: u64 = rlp.val_at(0)?; - let validators: Vec
= 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"), + }; } } diff --git a/ethcore/src/engines/validator_set/simple_list.rs b/ethcore/src/engines/validator_set/simple_list.rs index 15cf141f6..8b8d1942d 100644 --- a/ethcore/src/engines/validator_set/simple_list.rs +++ b/ethcore/src/engines/validator_set/simple_list.rs @@ -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> for SimpleList { + fn from(validators: Vec
) -> 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> { + 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, 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), ::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() } diff --git a/ethcore/src/engines/validator_set/test.rs b/ethcore/src/engines/validator_set/test.rs index f1d0e76cd..4960ee7be 100644 --- a/ethcore/src/engines/validator_set/test.rs +++ b/ethcore/src/engines/validator_set/test.rs @@ -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> { 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, 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), ::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) } } diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index dd9b7464c..e3a32db01 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -165,6 +165,8 @@ pub enum BlockError { InvalidNumber(Mismatch), /// Block number isn't sensible. RidiculousNumber(OutOfBounds), + /// 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)) diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 8d4d18911..05f7ca1e1 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -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 { - 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 { // 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) -> Result<(), Error> { + fn on_new_block( + &self, + block: &mut ExecutedBlock, + last_hashes: Arc, + _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 { Ok(()) } - fn epoch_verifier(&self, _header: &Header, _proof: &[u8]) -> Result, 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> { @@ -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); diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index 475496c2b..f94c39b33 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -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()); diff --git a/ethcore/src/evm/mod.rs b/ethcore/src/evm/mod.rs index 91b44a543..a48004290 100644 --- a/ethcore/src/evm/mod.rs +++ b/ethcore/src/evm/mod.rs @@ -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; diff --git a/ethcore/src/evm/schedule.rs b/ethcore/src/evm/schedule.rs index de4406767..8971aa16c 100644 --- a/ethcore/src/evm/schedule.rs +++ b/ethcore/src/evm/schedule.rs @@ -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, } } } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 7e582ccae..b486aa8d4 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -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(¶ms.address)?; if let ActionValue::Transfer(val) = params.value { - self.state.sub_balance(¶ms.sender, &val)?; + self.state.sub_balance(¶ms.sender, &val, &mut substate.to_cleanup_mode(&schedule))?; self.state.new_contract(¶ms.address, val + prev_bal, nonce_offset); } else { self.state.new_contract(¶ms.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)), diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 72c13f062..6abb6e3fa 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -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( diff --git a/ethcore/src/migrations/v9.rs b/ethcore/src/migrations/v9.rs index 4ca6e3a82..67b2a934e 100644 --- a/ethcore/src/migrations/v9.rs +++ b/ethcore/src/migrations/v9.rs @@ -51,7 +51,6 @@ impl ToV9 { } impl Migration for ToV9 { - fn columns(&self) -> Option { Some(5) } fn version(&self) -> u32 { 9 } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index e5edc6d05..c772490ba 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -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; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 03a2d37ad..59ab3eacf 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -1084,11 +1084,11 @@ impl TransactionQueue { /// Returns top transactions from the queue ordered by priority. pub fn top_transactions(&self) -> Vec { - 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(&self, best_block: BlockNumber, best_timestamp: u64, mut f: F) + fn filter_pending_transaction(&self, best_block: BlockNumber, best_timestamp: u64, nonce_cap: Option, 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 { + pub fn top_transactions_at(&self, best_block: BlockNumber, best_timestamp: u64, nonce_cap: Option) -> Vec { 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 { 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); + } } diff --git a/ethcore/src/snapshot/consensus/authority.rs b/ethcore/src/snapshot/consensus/authority.rs index 0d7595c76..1a1215522 100644 --- a/ethcore/src/snapshot/consensus/authority.rs +++ b/ethcore/src/snapshot/consensus/authority.rs @@ -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::>(); - - 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, sink: &mut ChunkSink) -> // transition header is verifiable from the epoch data of the one prior. struct ChunkRebuilder { manifest: ManifestData, - warp_target: Option<(Header, Vec)>, + warp_target: Option
, chain: BlockChain, db: Arc, 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)>, } // 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>, -) -> (::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 { + 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 = transition_rlp.at(3)? - .iter() - .map(|x| Ok(DBValue::from_slice(x.data()?))) - .collect::>()?; - let last_hashes: Vec = 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 = 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(()) diff --git a/ethcore/src/snapshot/consensus/mod.rs b/ethcore/src/snapshot/consensus/mod.rs index 0ed8c909d..3f583893b 100644 --- a/ethcore/src/snapshot/consensus/mod.rs +++ b/ethcore/src/snapshot/consensus/mod.rs @@ -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>; } diff --git a/ethcore/src/snapshot/consensus/work.rs b/ethcore/src/snapshot/consensus/work.rs index ff193ad00..4975203b7 100644 --- a/ethcore/src/snapshot/consensus/work.rs +++ b/ethcore/src/snapshot/consensus/work.rs @@ -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(()) } diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index 1f2aef9f7..fd4c2cc1e 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -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)?; diff --git a/ethcore/src/snapshot/tests/helpers.rs b/ethcore/src/snapshot/tests/helpers.rs index cfd7af9be..14377bdd0 100644 --- a/ethcore/src/snapshot/tests/helpers.rs +++ b/ethcore/src/snapshot/tests/helpers.rs @@ -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) } diff --git a/ethcore/src/snapshot/tests/proof_of_authority.rs b/ethcore/src/snapshot/tests/proof_of_authority.rs index 5958a5f64..4428ea2ad 100644 --- a/ethcore/src/snapshot/tests/proof_of_authority.rs +++ b/ethcore/src/snapshot/tests/proof_of_authority.rs @@ -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, 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| { - 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, 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, 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, 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(); } diff --git a/ethcore/src/snapshot/tests/proof_of_work.rs b/ethcore/src/snapshot/tests/proof_of_work.rs index e3b65aec4..e7d0a0964 100644 --- a/ethcore/src/snapshot/tests/proof_of_work.rs +++ b/ethcore/src/snapshot/tests/proof_of_work.rs @@ -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. diff --git a/ethcore/src/snapshot/tests/test_validator_contract.json b/ethcore/src/snapshot/tests/test_validator_contract.json index b422ebde3..e2485fe82 100644 --- a/ethcore/src/snapshot/tests/test_validator_contract.json +++ b/ethcore/src/snapshot/tests/test_validator_contract.json @@ -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" } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index d1a7ce484..1d0d2bc6f 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -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 for CommonParams { @@ -100,6 +106,9 @@ impl From 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); } diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index d8aad430a..205ba6af4 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -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} } diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 42289c3f8..501601fde 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -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, + /// Unmodified account balance. + old_balance: Option, + /// 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) -> 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) -> 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) -> 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
), + /// Mark all touched accounts. + TrackTouched(&'a mut HashSet
), } 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 State { 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 State { } } - /// 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 - ) -> 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 State { None => { trie.remove(address)?; }, - } + }; } } @@ -692,17 +703,30 @@ impl State { } } - /// 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
, remove_empty_touched: bool, min_balance: &Option, 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()); + } } diff --git a/ethcore/src/state/substate.rs b/ethcore/src/state/substate.rs index 61824c898..76f6eaed7 100644 --- a/ethcore/src/state/substate.rs +++ b/ethcore/src/state/substate.rs @@ -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
, - /// Any accounts that are tagged for garbage collection. - pub garbage: HashSet
, + /// Any accounts that are touched. + pub touched: HashSet
, /// Any logs. pub logs: Vec, @@ -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), } } } diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index c54933fd1..02029f638 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -193,7 +193,8 @@ pub fn generate_dummy_client_with_spec_accounts_and_data(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; diff --git a/ethcore/src/types/executed.rs b/ethcore/src/types/executed.rs index 571d8af97..37c8748aa 100644 --- a/ethcore/src/types/executed.rs +++ b/ethcore/src/types/executed.rs @@ -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), }; diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 096b348c3..0df4b86b6 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -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(); + } } diff --git a/js/package.json b/js/package.json index b92190a56..3342abd0a 100644 --- a/js/package.json +++ b/js/package.json @@ -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 ", diff --git a/js/src/api/api.js b/js/src/api/api.js index ba3c35cd5..0b1c94629 100644 --- a/js/src/api/api.js +++ b/js/src/api/api.js @@ -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) => { diff --git a/js/src/api/local/index.js b/js/src/api/local/index.js index cc7b27e68..190a38ea4 100644 --- a/js/src/api/local/index.js +++ b/js/src/api/local/index.js @@ -14,8 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -if (process.env.NODE_ENV !== 'test') { - process.browser = true; -} - -export LocalAccountsMiddleware from './localAccountsMiddleware'; +export default null; diff --git a/js/src/i18n/_default/application.js b/js/src/i18n/_default/application.js index 4469d83b2..f2e6be1ee 100644 --- a/js/src/i18n/_default/application.js +++ b/js/src/i18n/_default/application.js @@ -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` } diff --git a/js/src/ui/DappIcon/dappIcon.js b/js/src/ui/DappIcon/dappIcon.js index cd7d2f47e..ddff7b47d 100644 --- a/js/src/ui/DappIcon/dappIcon.js +++ b/js/src/ui/DappIcon/dappIcon.js @@ -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}` } /> ); diff --git a/js/src/ui/DappIcon/dappIcon.spec.js b/js/src/ui/DappIcon/dappIcon.spec.js index b7bc81653..fce104685 100644 --- a/js/src/ui/DappIcon/dappIcon.spec.js +++ b/js/src/ui/DappIcon/dappIcon.spec.js @@ -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` ); }); }); diff --git a/js/src/ui/TxList/TxRow/txRow.js b/js/src/ui/TxList/TxRow/txRow.js index 976066c2b..521a10a5d 100644 --- a/js/src/ui/TxList/TxRow/txRow.js +++ b/js/src/ui/TxList/TxRow/txRow.js @@ -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); } diff --git a/js/src/ui/TxList/store.js b/js/src/ui/TxList/store.js index 180882b2d..56f9541de 100644 --- a/js/src/ui/TxList/store.js +++ b/js/src/ui/TxList/store.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . 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; diff --git a/js/src/ui/TxList/txList.js b/js/src/ui/TxList/txList.js index 140217a23..eb75e28e3 100644 --- a/js/src/ui/TxList/txList.js +++ b/js/src/ui/TxList/txList.js @@ -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 } /> ); }); diff --git a/js/src/views/Application/Status/status.js b/js/src/views/Application/Status/status.js new file mode 100644 index 000000000..303449363 --- /dev/null +++ b/js/src/views/Application/Status/status.js @@ -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 . + +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 ( +
+
+ { clientVersion } +
+
+ { this.renderConsensus() } + { this.renderUpgradeButton() } +
+
+ +
+ { netChain } +
+
+ { netPeers.connected.toFormat() }/{ netPeers.max.toFormat() } peers +
+
+
+ ); + } + + renderConsensus () { + const { upgradeStore } = this.props; + + if (!upgradeStore || !upgradeStore.consensusCapability) { + return null; + } + + if (upgradeStore.consensusCapability === 'capable') { + return ( +
+ +
+ ); + } + + if (upgradeStore.consensusCapability.capableUntil) { + return ( +
+ +
+ ); + } + + if (upgradeStore.consensusCapability.incapableSince) { + return ( +
+ +
+ ); + } + + return ( +
+ +
+ ); + } + + renderUpgradeButton () { + const { upgradeStore } = this.props; + + if (!upgradeStore.available) { + return null; + } + + return ( +
+ + + +
+ ); + } +} + +function mapStateToProps (state) { + const { clientVersion, netPeers, netChain, isTest } = state.nodeStatus; + + return { + clientVersion, + netPeers, + netChain, + isTest + }; +} + +export default connect( + mapStateToProps, + null +)(Status); diff --git a/js/webpack/app.js b/js/webpack/app.js index ae588940f..523a3bdc7 100644 --- a/js/webpack/app.js +++ b/js/webpack/app.js @@ -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') diff --git a/js/webpack/libraries.js b/js/webpack/libraries.js index 85f9e22b7..fe3cde01a 100644 --- a/js/webpack/libraries.js +++ b/js/webpack/libraries.js @@ -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') } }, diff --git a/js/webpack/npm.js b/js/webpack/npm.js index 2932faecb..c3f19e22a 100644 --- a/js/webpack/npm.js +++ b/js/webpack/npm.js @@ -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'), diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index ae518eebc..0fdbfbfb3 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -49,6 +49,9 @@ pub struct AuthorityRoundParams { /// Block from which monotonic steps start. #[serde(rename="validateStepTransition")] pub validate_step_transition: Option, + /// Whether transitions should be immediate. + #[serde(rename="immediateTransitions")] + pub immediate_transitions: Option, } /// 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); } } diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index b17067426..8103515cb 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -81,6 +81,14 @@ pub struct Params { /// See `CommonParams` docs. #[serde(rename="eip214Transition")] pub eip214_transition: Option, + /// See `CommonParams` docs. + #[serde(rename="dustProtectionTransition")] + pub dust_protection_transition: Option, + /// See `CommonParams` docs. + #[serde(rename="nonceCapIncrement")] + pub nonce_cap_increment: Option, + /// See `CommonParams` docs. + pub remove_dust_contracts : Option, } #[cfg(test)] diff --git a/parity/blockchain.rs b/parity/blockchain.rs index fe34fa56c..17e486b5b 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -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 { // 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(); diff --git a/parity/dapps.rs b/parity/dapps.rs index b19eaebb6..7e1cf82c1 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -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(), diff --git a/parity/informant.rs b/parity/informant.rs index 1f309848c..3dfd5147d 100644 --- a/parity/informant.rs +++ b/parity/informant.rs @@ -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))), ), diff --git a/parity/light_helpers/queue_cull.rs b/parity/light_helpers/queue_cull.rs index 090521ba5..982e69e48 100644 --- a/parity/light_helpers/queue_cull.rs +++ b/parity/light_helpers/queue_cull.rs @@ -67,7 +67,7 @@ impl IoHandler 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 || { diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index ec76bf5f4..d1b0253d5 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -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> ); diff --git a/parity/run.rs b/parity/run.rs index bcb18aeeb..35515d54f 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -227,7 +227,8 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> } // 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()); diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 22c9b98cd..7b8e207b8 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -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; diff --git a/rpc/src/v1/extractors.rs b/rpc/src/v1/extractors.rs index 261d3bfbf..67f121526 100644 --- a/rpc/src/v1/extractors.rs +++ b/rpc/src/v1/extractors.rs @@ -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 { diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index be46a4763..ce978422d 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -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, diff --git a/rpc/src/v1/helpers/light_fetch.rs b/rpc/src/v1/helpers/light_fetch.rs index 2655f7351..9ecdffe80 100644 --- a/rpc/src/v1/helpers/light_fetch.rs +++ b/rpc/src/v1/helpers/light_fetch.rs @@ -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, 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(), + } + } } diff --git a/rpc/src/v1/helpers/subscribers.rs b/rpc/src/v1/helpers/subscribers.rs index e1fc42ae4..11dd45d11 100644 --- a/rpc/src/v1/helpers/subscribers.rs +++ b/rpc/src/v1/helpers/subscribers.rs @@ -95,7 +95,7 @@ impl Subscribers { } } -impl Subscribers> { +impl Subscribers> { /// Assigns id and adds a subscriber to the list. pub fn push(&mut self, sub: Subscriber) { let id = self.next_id(); @@ -106,6 +106,17 @@ impl Subscribers> { } } +impl Subscribers<(Sink, V)> { + /// Assigns id and adds a subscriber to the list. + pub fn push(&mut self, sub: Subscriber, 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 ops::Deref for Subscribers { type Target = HashMap; diff --git a/rpc/src/v1/impls/eth_pubsub.rs b/rpc/src/v1/impls/eth_pubsub.rs index 2e513560c..6ba88be21 100644 --- a/rpc/src/v1/impls/eth_pubsub.rs +++ b/rpc/src/v1/impls/eth_pubsub.rs @@ -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; /// Eth PubSub implementation. pub struct EthPubSubClient { handler: Arc>, - heads_subscribers: Arc>>>, + heads_subscribers: Arc>>, + logs_subscribers: Arc>>, } impl EthPubSubClient { /// Creates new `EthPubSubClient`. pub fn new(client: Arc, 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 EthPubSubClient { #[cfg(test)] pub fn new_test(client: Arc, 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 EthPubSubClient { } } +impl EthPubSubClient { + /// Creates a new `EthPubSubClient` for `LightClient`. + pub fn light( + client: Arc, + on_demand: Arc, + sync: Arc, + cache: Arc>, + remote: Remote, + ) -> Self { + let fetch = LightFetch { + client, + on_demand, + sync, + cache + }; + EthPubSubClient::new(Arc::new(fetch), remote) + } +} + /// PubSub Notification handler. pub struct ChainNotificationHandler { client: Arc, remote: Remote, - heads_subscribers: Arc>>>, + heads_subscribers: Arc>>, + logs_subscribers: Arc>>, } impl ChainNotificationHandler { - fn notify(&self, blocks: Vec<(encoded::Header, BTreeMap)>) { - 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)]) { + 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(&self, enacted: &[H256], logs: F) where + F: Fn(EthFilter) -> BoxFuture, 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::>() + ); + 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 LightChainNotify for ChainNotificationHandler { +/// A light client wrapper struct. +pub trait LightClient: Send + Sync { + /// Get a recent block header. + fn block_header(&self, id: BlockId) -> Option; + + /// Fetch logs. + fn logs(&self, filter: EthFilter) -> BoxFuture, Error>; +} + +impl LightClient for LightFetch { + fn block_header(&self, id: BlockId) -> Option { + self.client.block_header(id) + } + + fn logs(&self, filter: EthFilter) -> BoxFuture, Error> { + LightFetch::logs(self, filter) + } +} + +impl LightChainNotify for ChainNotificationHandler { 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::>(); - self.notify(blocks); + self.notify_heads(&headers); + self.notify_logs(&enacted, |filter| self.client.logs(filter)) } } @@ -115,22 +201,37 @@ impl ChainNotify for ChainNotificationHandler { _imported: Vec, _invalid: Vec, enacted: Vec, - _retracted: Vec, + retracted: Vec, _sealed: Vec, // Block bytes. _proposed: Vec, _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::>(); + + // 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 EthPubSub for EthPubSubClient { kind: pubsub::Kind, params: Trailing, ) { - let params: Option = 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 EthPubSub for EthPubSubClient { } fn unsubscribe(&self, id: SubscriptionId) -> BoxFuture { - 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() } } diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index dac63005e..e133b3e7a 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -477,60 +477,7 @@ impl Filterable for EthClient { } fn logs(&self, filter: EthcoreFilter) -> BoxFuture, 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 { diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index 6bf8a069e..1a121699a 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -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>, Error> { + fn all_accounts_info(&self) -> Result, 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 = 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 { diff --git a/rpc/src/v1/tests/mocked/eth_pubsub.rs b/rpc/src/v1/tests/mocked/eth_pubsub.rs index b31d78796..199830025 100644 --- a/rpc/src/v1/tests/mocked/eth_pubsub.rs +++ b/rpc/src/v1/tests/mocked/eth_pubsub.rs @@ -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())); } diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index ebb113f3b..f92486d16 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -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; } } diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index 7d49148b1..73c40ffd4 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -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>, Error>; + fn all_accounts_info(&self) -> Result, Error>; /// Creates new account from the given phrase using standard brainwallet mechanism. /// Second parameter is password for the new account. diff --git a/rpc/src/v1/types/account_info.rs b/rpc/src/v1/types/account_info.rs index 077110df0..f9cabb450 100644 --- a/rpc/src/v1/types/account_info.rs +++ b/rpc/src/v1/types/account_info.rs @@ -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, +} + /// Hardware wallet information. #[derive(Debug, Default, Clone, PartialEq, Serialize)] pub struct HwAccountInfo { @@ -29,3 +41,4 @@ pub struct HwAccountInfo { /// Device manufacturer. pub manufacturer: String, } + diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index 50a081356..525d2c090 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -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, @@ -186,7 +186,7 @@ pub type RichBlock = Rich; pub type RichHeader = Rich
; /// Value representation with additional info -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Rich { /// Standard value. pub inner: T, diff --git a/rpc/src/v1/types/mod.rs b/rpc/src/v1/types/mod.rs index ef90d844b..3e463f958 100644 --- a/rpc/src/v1/types/mod.rs +++ b/rpc/src/v1/types/mod.rs @@ -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; diff --git a/rpc/src/v1/types/pubsub.rs b/rpc/src/v1/types/pubsub.rs index 8bc4f9079..1be0d19e3 100644 --- a/rpc/src/v1/types/pubsub.rs +++ b/rpc/src/v1/types/pubsub.rs @@ -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), } 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(deserializer: D) -> ::std::result::Result + 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::(r#""syncing""#).unwrap(), Kind::Syncing); } + #[test] + fn should_deserialize_logs() { + let none = serde_json::from_str::(r#"null"#).unwrap(); + assert_eq!(none, Params::None); + + let logs1 = serde_json::from_str::(r#"{}"#).unwrap(); + let logs2 = serde_json::from_str::(r#"{"limit":10}"#).unwrap(); + let logs3 = serde_json::from_str::( + 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 { diff --git a/sync/src/snapshot.rs b/sync/src/snapshot.rs index ffdaecd79..cecfbe425 100644 --- a/sync/src/snapshot.rs +++ b/sync/src/snapshot.rs @@ -92,14 +92,10 @@ impl Snapshot { /// Find a chunk to download pub fn needed_chunk(&mut self) -> Option { // 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()); diff --git a/util/network/src/host.rs b/util/network/src/host.rs index 93cbce804..ec211805e 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -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)] diff --git a/util/src/journaldb/archivedb.rs b/util/src/journaldb/archivedb.rs index 9099ff03b..cf21fbd9f 100644 --- a/util/src/journaldb/archivedb.rs +++ b/util/src/journaldb/archivedb.rs @@ -177,7 +177,7 @@ impl JournalDB for ArchiveDB { fn latest_era(&self) -> Option { self.latest_era } fn state(&self, id: &H256) -> Option { - 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 } diff --git a/util/src/journaldb/earlymergedb.rs b/util/src/journaldb/earlymergedb.rs index 66e5a1cfd..7eb3f3259 100644 --- a/util/src/journaldb/earlymergedb.rs +++ b/util/src/journaldb/earlymergedb.rs @@ -371,7 +371,7 @@ impl JournalDB for EarlyMergeDB { } fn state(&self, id: &H256) -> Option { - 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 journal_under(&mut self, batch: &mut DBTransaction, now: u64, id: &H256) -> Result { diff --git a/util/src/journaldb/overlayrecentdb.rs b/util/src/journaldb/overlayrecentdb.rs index af92d3b40..93eec118d 100644 --- a/util/src/journaldb/overlayrecentdb.rs +++ b/util/src/journaldb/overlayrecentdb.rs @@ -244,7 +244,7 @@ impl JournalDB for OverlayRecentDB { let key = to_short_key(key); journal_overlay.backing_overlay.get(&key).map(|v| v.to_vec()) .or_else(|| journal_overlay.pending_overlay.get(&key).map(|d| d.clone().to_vec())) - .or_else(|| self.backing.get_by_prefix(self.column, &key[0..DB_PREFIX_LEN]).map(|b| b.to_vec())) + .or_else(|| self.backing.get_by_prefix(self.column, &key[0..DB_PREFIX_LEN]).map(|b| b.into_vec())) } fn journal_under(&mut self, batch: &mut DBTransaction, now: u64, id: &H256) -> Result { diff --git a/util/src/journaldb/refcounteddb.rs b/util/src/journaldb/refcounteddb.rs index 13c2189c8..4f8600bde 100644 --- a/util/src/journaldb/refcounteddb.rs +++ b/util/src/journaldb/refcounteddb.rs @@ -115,7 +115,7 @@ impl JournalDB for RefCountedDB { fn latest_era(&self) -> Option { self.latest_era } fn state(&self, id: &H256) -> Option { - 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 journal_under(&mut self, batch: &mut DBTransaction, now: u64, id: &H256) -> Result {