From 85391f99ac9132c0202afbc619e10a594d2e5112 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 25 Mar 2021 14:37:01 +0100 Subject: [PATCH] Backport AuRa commits for xdai (#330) * Add SealingState; don't prepare block when not ready. (#10529) * Fix a few typos and unused warnings. #10803 * Configuration map of block reward contract addresses (#10875) * Step duration map configuration parameter ported from the POA Network fork (#10902) * Add a 2/3 quorum option to Authority Round. (#10909) * Additional arithmetic EVM opcode benchmarks (#10916) * RPC method for clearing the engine signer (#10920) * authority_round: Fix next_step_time_duration. (#11379) * Aura: Report malice on sibling blocks from the same validator (#11160) * TxPermissions ver 3: gas price & data (#11170) * Add randomness contract support to AuthorityRound. (#10946) * Set the block gas limit to the value returned by a contract call (#10928) * AuthorityEngine: Minor cleanups. (#11408) * Add POSDAO transition and malice report queue. (#11245) * PoA call validators on_close_block * Actualize spec files for POA Networks * Some fixes after merge * Crypto error desc * AuRa on_close_block Error::Old fix Co-authored-by: POA <33550681+poa@users.noreply.github.com> --- Cargo.lock | 1 + crates/accounts/ethkey/src/error.rs | 88 ++ crates/ethcore/Cargo.toml | 1 + crates/ethcore/res/chainspec/kovan.json | 104 +- crates/ethcore/res/chainspec/poacore.json | 51 +- crates/ethcore/res/chainspec/poasokol.json | 53 +- .../authority_round_randomness_contract.json | 100 ++ .../res/chainspec/test/contract_ver_3.sol | 60 + .../test/contract_ver_3_genesis.json | 44 + .../chainspec/test/validator_contract.json | 2 +- .../res/chainspec/test/validator_contract.sol | 119 ++ crates/ethcore/res/chainspec/xdai.json | 83 +- .../res/contracts/authority_round_random.json | 133 ++ .../res/contracts/block_gas_limit.json | 16 + .../test_authority_round_random.json | 265 ++++ .../contracts/test_authority_round_random.sol | 101 ++ .../res/contracts/tx_acl_gas_price.json | 83 ++ .../res/contracts/validator_report.json | 3 +- .../ethcore/res/contracts/validator_set.json | 54 +- crates/ethcore/src/client/client.rs | 61 +- crates/ethcore/src/client/test_client.rs | 38 +- crates/ethcore/src/client/traits.rs | 62 +- .../authority_round/block_gas_limit.rs | 39 + .../src/engines/authority_round/finality.rs | 231 ++- .../src/engines/authority_round/mod.rs | 1245 ++++++++++++++--- .../src/engines/authority_round/randomness.rs | 257 ++++ .../src/engines/authority_round/util.rs | 119 ++ crates/ethcore/src/engines/basic_authority.rs | 28 +- crates/ethcore/src/engines/clique/mod.rs | 21 +- crates/ethcore/src/engines/instant_seal.rs | 6 +- crates/ethcore/src/engines/mod.rs | 54 +- crates/ethcore/src/engines/signer.rs | 30 +- .../src/engines/validator_set/contract.rs | 143 +- .../ethcore/src/engines/validator_set/mod.rs | 45 +- .../src/engines/validator_set/multi.rs | 60 +- .../engines/validator_set/safe_contract.rs | 248 +++- .../src/engines/validator_set/simple_list.rs | 17 +- .../ethcore/src/engines/validator_set/test.rs | 33 +- crates/ethcore/src/lib.rs | 1 + crates/ethcore/src/miner/miner.rs | 144 +- crates/ethcore/src/miner/mod.rs | 2 +- crates/ethcore/src/snapshot/service.rs | 3 +- crates/ethcore/src/snapshot/tests/service.rs | 16 +- crates/ethcore/src/spec/spec.rs | 6 + crates/ethcore/src/test_helpers.rs | 10 +- crates/ethcore/src/trace/types/trace.rs | 4 +- crates/ethcore/src/tx_filter.rs | 98 +- .../ethcore/src/verification/verification.rs | 67 +- crates/ethcore/types/src/transaction/error.rs | 2 +- crates/ethjson/src/spec/authority_round.rs | 116 +- crates/ethjson/src/spec/mod.rs | 2 + crates/ethjson/src/spec/step_duration.rs | 36 + crates/rpc/src/v1/helpers/engine_signer.rs | 17 +- crates/rpc/src/v1/impls/parity_set.rs | 5 + .../rpc/src/v1/tests/helpers/miner_service.rs | 14 +- crates/rpc/src/v1/traits/parity_set.rs | 4 + crates/vm/evm/benches/basic.rs | 208 +++ 57 files changed, 4278 insertions(+), 575 deletions(-) create mode 100644 crates/accounts/ethkey/src/error.rs create mode 100644 crates/ethcore/res/chainspec/test/authority_round_randomness_contract.json create mode 100644 crates/ethcore/res/chainspec/test/contract_ver_3.sol create mode 100644 crates/ethcore/res/chainspec/test/contract_ver_3_genesis.json create mode 100644 crates/ethcore/res/chainspec/test/validator_contract.sol create mode 100644 crates/ethcore/res/contracts/authority_round_random.json create mode 100644 crates/ethcore/res/contracts/block_gas_limit.json create mode 100644 crates/ethcore/res/contracts/test_authority_round_random.json create mode 100644 crates/ethcore/res/contracts/test_authority_round_random.sol create mode 100644 crates/ethcore/res/contracts/tx_acl_gas_price.json create mode 100644 crates/ethcore/src/engines/authority_round/block_gas_limit.rs create mode 100644 crates/ethcore/src/engines/authority_round/randomness.rs create mode 100644 crates/ethcore/src/engines/authority_round/util.rs create mode 100644 crates/ethjson/src/spec/step_duration.rs diff --git a/Cargo.lock b/Cargo.lock index c2ade4a62..d3611b47a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -952,6 +952,7 @@ dependencies = [ "common-types", "criterion 0.2.11", "crossbeam-utils 0.6.6", + "derive_more", "eip-152", "env_logger", "error-chain", diff --git a/crates/accounts/ethkey/src/error.rs b/crates/accounts/ethkey/src/error.rs new file mode 100644 index 000000000..6d26c1fbe --- /dev/null +++ b/crates/accounts/ethkey/src/error.rs @@ -0,0 +1,88 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of OpenEthereum. + +// OpenEthereum 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. + +// OpenEthereum 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 OpenEthereum. If not, see . + +use crypto::Error as CryptoError; +use std::{error, fmt}; + +#[derive(Debug)] +/// Crypto error +pub enum Error { + /// Invalid secret key + InvalidSecret, + /// Invalid public key + InvalidPublic, + /// Invalid address + InvalidAddress, + /// Invalid EC signature + InvalidSignature, + /// Invalid AES message + InvalidMessage, + /// IO Error + Io(::std::io::Error), + /// Custom + Custom(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let msg = match *self { + Error::InvalidSecret => "Invalid secret".into(), + Error::InvalidPublic => "Invalid public".into(), + Error::InvalidAddress => "Invalid address".into(), + Error::InvalidSignature => "Invalid EC signature".into(), + Error::InvalidMessage => "Invalid AES message".into(), + Error::Io(ref err) => format!("I/O error: {}", err), + Error::Custom(ref s) => s.clone(), + }; + + f.write_fmt(format_args!("Crypto error ({})", msg)) + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + format!("{:?}", &self) + } +} + +impl Into for Error { + fn into(self) -> String { + format!("{}", self) + } +} + +impl From for Error { + fn from(e: CryptoError) -> Error { + Error::Custom(e.to_string()) + } +} + +impl From<::secp256k1::Error> for Error { + fn from(e: ::secp256k1::Error) -> Error { + match e { + ::secp256k1::Error::InvalidMessage => Error::InvalidMessage, + ::secp256k1::Error::InvalidPublicKey => Error::InvalidPublic, + ::secp256k1::Error::InvalidSecretKey => Error::InvalidSecret, + _ => Error::InvalidSignature, + } + } +} + +impl From<::std::io::Error> for Error { + fn from(err: ::std::io::Error) -> Error { + Error::Io(err) + } +} diff --git a/crates/ethcore/Cargo.toml b/crates/ethcore/Cargo.toml index 1d18f1648..e1b83ebc9 100644 --- a/crates/ethcore/Cargo.toml +++ b/crates/ethcore/Cargo.toml @@ -76,6 +76,7 @@ using_queue = { path = "../concensus/miner/using-queue" } vm = { path = "../vm/vm" } walkdir = "2.3" wasm = { path = "../vm/wasm" } +derive_more = "0.99" scopeguard = "1.1.0" [dev-dependencies] diff --git a/crates/ethcore/res/chainspec/kovan.json b/crates/ethcore/res/chainspec/kovan.json index e41127c3c..89e988c2e 100644 --- a/crates/ethcore/res/chainspec/kovan.json +++ b/crates/ethcore/res/chainspec/kovan.json @@ -88,9 +88,13 @@ "builtin": { "name": "ecrecover", "pricing": { - "linear": { - "base": 3000, - "word": 0 + "0": { + "price": { + "linear": { + "base": 3000, + "word": 0 + } + } } } } @@ -100,9 +104,13 @@ "builtin": { "name": "sha256", "pricing": { - "linear": { - "base": 60, - "word": 12 + "0": { + "price": { + "linear": { + "base": 60, + "word": 12 + } + } } } } @@ -112,9 +120,13 @@ "builtin": { "name": "ripemd160", "pricing": { - "linear": { - "base": 600, - "word": 120 + "0": { + "price": { + "linear": { + "base": 600, + "word": 120 + } + } } } } @@ -124,9 +136,13 @@ "builtin": { "name": "identity", "pricing": { - "linear": { - "base": 15, - "word": 3 + "0": { + "price": { + "linear": { + "base": 15, + "word": 3 + } + } } } } @@ -134,10 +150,13 @@ "0x0000000000000000000000000000000000000005": { "builtin": { "name": "modexp", - "activate_at": "0x4d50f8", "pricing": { - "modexp": { - "divisor": 20 + "0x4d50f8": { + "price": { + "modexp": { + "divisor": 20 + } + } } } } @@ -147,11 +166,19 @@ "name": "alt_bn128_add", "pricing": { "0x4d50f8": { - "price": { "alt_bn128_const_operations": { "price": 500 }} + "price": { + "alt_bn128_const_operations": { + "price": 500 + } + } }, "0xd751a5": { "info": "EIP 1108 transition at block 14_111_141 (0xd751a5)", - "price": { "alt_bn128_const_operations": { "price": 150 }} + "price": { + "alt_bn128_const_operations": { + "price": 150 + } + } } } } @@ -161,11 +188,19 @@ "name": "alt_bn128_mul", "pricing": { "0x4d50f8": { - "price": { "alt_bn128_const_operations": { "price": 40000 }} + "price": { + "alt_bn128_const_operations": { + "price": 40000 + } + } }, "0xd751a5": { "info": "EIP 1108 transition at block 14_111_141 (0xd751a5)", - "price": { "alt_bn128_const_operations": { "price": 6000 }} + "price": { + "alt_bn128_const_operations": { + "price": 6000 + } + } } } } @@ -175,11 +210,21 @@ "name": "alt_bn128_pairing", "pricing": { "0x4d50f8": { - "price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }} + "price": { + "alt_bn128_pairing": { + "base": 100000, + "pair": 80000 + } + } }, "0xd751a5": { "info": "EIP 1108 transition at block 14_111_141 (0xd751a5)", - "price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }} + "price": { + "alt_bn128_pairing": { + "base": 45000, + "pair": 34000 + } + } } } } @@ -187,10 +232,13 @@ "0x0000000000000000000000000000000000000009": { "builtin": { "name": "blake2_f", - "activate_at": "0xd751a5", "pricing": { - "blake2_f": { - "gas_per_round": 1 + "0xd751a5": { + "price": { + "blake2_f": { + "gas_per_round": 1 + } + } } } } @@ -200,13 +248,9 @@ } }, "nodes": [ - "enode://f6e37b943bad3a78cb8589b1798d30d210ffd39cfcd2c8f2de4f098467fd49c667980100d919da7ca46cd50505d30989abda87f0b9339377de13d6592c22caf8@34.198.49.72:30303", "enode://16898006ba2cd4fa8bf9a3dfe32684c178fa861df144bfc21fe800dc4838a03e342056951fa9fd533dcb0be1219e306106442ff2cf1f7e9f8faa5f2fc1a3aa45@116.203.116.241:30303", "enode://2909846f78c37510cc0e306f185323b83bb2209e5ff4fdd279d93c60e3f365e3c6e62ad1d2133ff11f9fd6d23ad9c3dad73bb974d53a22f7d1ac5b7dea79d0b0@3.217.96.11:30303", - "enode://56abaf065581a5985b8c5f4f88bd202526482761ba10be9bfdcd14846dd01f652ec33fde0f8c0fd1db19b59a4c04465681fcef50e11380ca88d25996191c52de@40.71.221.215:30303", - "enode://d07827483dc47b368eaf88454fb04b41b7452cf454e194e2bd4c14f98a3278fed5d819dbecd0d010407fc7688d941ee1e58d4f9c6354d3da3be92f55c17d7ce3@52.166.117.77:30303", - "enode://38e6e7fd416293ed120d567a2675fe078c0205ab0671abf16982ce969823bd1f3443d590c18b321dfae7dcbe1f6ba98ef8702f255c3c9822a188abb82c53adca@51.77.66.187:30303", - "enode://6f289111f7c77c68651b0f4803c3a47bcec801f9c618bb41231a1a24a6dbb9c76f2fdb63ba7a21357c41ebb7f6922c17397c1b5c8f71f7d3ef7965505d4945de@144.217.72.209:30303", - "enode://b6340eb94c3db1362ee517801389fe21cce6354275376b1006f8ce84f8a5cfa2b836268b3727be9db7cd3e581f356f39da39418c4ec1d63d959abc235d99cd86@145.239.7.213:30303" + "enode://740e1c8ea64e71762c71a463a04e2046070a0c9394fcab5891d41301dc473c0cff00ebab5a9bc87fbcb610ab98ac18225ff897bc8b7b38def5975d5ceb0a7d7c@108.61.170.124:30303", + "enode://2909846f78c37510cc0e306f185323b83bb2209e5ff4fdd279d93c60e3f365e3c6e62ad1d2133ff11f9fd6d23ad9c3dad73bb974d53a22f7d1ac5b7dea79d0b0@157.230.31.163:30303" ] } diff --git a/crates/ethcore/res/chainspec/poacore.json b/crates/ethcore/res/chainspec/poacore.json index ecdd04c51..83591aee4 100644 --- a/crates/ethcore/res/chainspec/poacore.json +++ b/crates/ethcore/res/chainspec/poacore.json @@ -22,7 +22,10 @@ } }, "blockRewardContractAddress": "0x4d0153D434384128D17243409e02fca1B3EE21D6", - "blockRewardContractTransition": 5761140 + "blockRewardContractTransition": 5761140, + "randomnessContractAddress": { + "14350721": "0x67e90a54AeEA85f21949c645082FE95d77BC1E70" + } } } }, @@ -55,12 +58,12 @@ "gasLimit": "0x663BE0" }, "nodes": [ + "enode://b39c9c438c533ff7aed6aaefb1c116a350ee3acc3f37c2d6cb85d2a949296238174fe1cb9a04d6a1204b38ca12f63fe4827eaa860fd889a898735fbaba4cca58@178.128.175.244:30303", + "enode://24f3bf1307b8e9cd53abaaa339db5653aa3db64d579f7f37788b8a9b938420cc10004dc9af4edbb0f0611a57b741a4912dd081917b3d373e4409d788c422e7e6@134.209.22.144:30303", + "enode://b39c9c438c533ff7aed6aaefb1c116a350ee3acc3f37c2d6cb85d2a949296238174fe1cb9a04d6a1204b38ca12f63fe4827eaa860fd889a898735fbaba4cca58@142.93.3.70:30303", "enode://6e3d1b39cbd2a9c4f053a27e68fd90d0bac83691dfdc4a13c59f2555078a71e63c5daaee5a82aa6db500512760a5456f86076bf8bbe8011c27c82ed7d6f5fb26@45.77.140.210:30303", "enode://31dffed97f8fed1f34fe66453280a89cbeeda60cf28f6fbb212ebbefd7c7566a02c1c7d5c00bbbb49b9fa8a49f157e0f786f379ca9bcbf2fea24de70d70a22b6@206.156.242.61:30303", - "enode://6bdc7553ab2e4914cb47774c1e6d8c8f47ac7c3981891f85f65d06f208ea1bc4d3bf982b330950e0a0cd127efd7145c4df7113159a1d4a06ed722e6c16d0ac6c@45.32.215.190:30303", - "enode://872d82a24144bc007658fb6fac0dcdfb9b63aeb05ef563a06d0186f2d1e5ffbfc5c4f1244891a8a86ef70682b9d24382e654b305224883698862e2df647a4d23@45.76.236.247:30303", - "enode://b11fbc6cde81c80be69508aca8ffea8460680a25a9c151b683293f8617282062b8e8e139bf91e88cedf60068a3cf927b0d48832fda5169b58a8f7ce442de6fb4@206.189.76.132:30303", - "enode://96678da10ac83769ab3f63114a41b57b700476c5ac02719b878fa89909a936551bb7609aa09b068bf89903206fa03f23e1b5b9117ca278de304c2570b87dcb27@35.175.15.164:30303" + "enode://b39c9c438c533ff7aed6aaefb1c116a350ee3acc3f37c2d6cb85d2a949296238174fe1cb9a04d6a1204b38ca12f63fe4827eaa860fd889a898735fbaba4cca58@178.128.175.244:30303" ], "accounts": { "0x0000000000000000000000000000000000000005": { @@ -82,11 +85,19 @@ "name": "alt_bn128_add", "pricing": { "0": { - "price": { "alt_bn128_const_operations": { "price": 500 }} + "price": { + "alt_bn128_const_operations": { + "price": 500 + } + } }, "12598600": { "info": "EIP 1108 transition", - "price": { "alt_bn128_const_operations": { "price": 150 }} + "price": { + "alt_bn128_const_operations": { + "price": 150 + } + } } } } @@ -96,11 +107,19 @@ "name": "alt_bn128_mul", "pricing": { "0": { - "price": { "alt_bn128_const_operations": { "price": 40000 }} + "price": { + "alt_bn128_const_operations": { + "price": 40000 + } + } }, "12598600": { "info": "EIP 1108 transition", - "price": { "alt_bn128_const_operations": { "price": 6000 }} + "price": { + "alt_bn128_const_operations": { + "price": 6000 + } + } } } } @@ -110,11 +129,21 @@ "name": "alt_bn128_pairing", "pricing": { "0": { - "price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }} + "price": { + "alt_bn128_pairing": { + "base": 100000, + "pair": 80000 + } + } }, "12598600": { "info": "EIP 1108 transition", - "price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }} + "price": { + "alt_bn128_pairing": { + "base": 45000, + "pair": 34000 + } + } } } } diff --git a/crates/ethcore/res/chainspec/poasokol.json b/crates/ethcore/res/chainspec/poasokol.json index 31fb85c49..5629aaf99 100644 --- a/crates/ethcore/res/chainspec/poasokol.json +++ b/crates/ethcore/res/chainspec/poasokol.json @@ -25,7 +25,10 @@ } }, "blockRewardContractAddress": "0x3145197AD50D7083D0222DE4fCCf67d9BD05C30D", - "blockRewardContractTransition": 4639000 + "blockRewardContractTransition": 4639000, + "randomnessContractAddress": { + "13391641": "0x8f2b78169B0970F11a762e56659Db52B59CBCf1B" + } } } }, @@ -60,14 +63,10 @@ "gasLimit": "0x663BE0" }, "nodes": [ - "enode://bdcd6f875583df2bd8094f08ae58c7c2db6ed67795ca8c0e6415a30721d3657291aec9b933d15e17e0b36ad7a76424a1447ddbfc75809a04f7a0ffef5617dd56@3.91.206.172:30303", - "enode://8e0af07c86ec36590bb6368e7ad0c45b6dc658f5fb66ec68889a614affddda5e021bd513bcf4fb2fae4a3bbe08cf0de84f037cd58478a89665dfce1ded2595c7@34.236.37.74:30303", + "enode://f11a0f80939b49a28bf99581da9b351a592ec1504b9d32a7dfda79b36510a891e96631239c4166e5c73368c21e9bb3241e7fd6929b899772e5a8fe9a7b7c3af6@45.77.52.149:30303", + "enode://e08adce358fc26dfbe1f24ee578dceaa29575ca44a39d9041203131db5135aceba6241840a9b57b1540eeaf7b4eff1aead28a74641be43342c35af454abb31b3@199.247.18.10:30313", "enode://f1a5100a81cb73163ae450c584d06b1f644aa4fad4486c6aeb4c384b343c54bb66c744aa5f133af66ea1b25f0f4a454f04878f3e96ee4cd2390c047396d6357b@209.97.158.4:30303", - "enode://0d1e0372f63a3f0b82d66635ea101ecc0f6797788a078805cc933dd93e6a22f7c9fa51ab4e2d21da02d04480ef19f3bbb9a2b41dd1c262085d295a354bb8b0f9@18.217.47.209:30303", - "enode://875e1bd1b98019a5d6d588c23f68534b75462dd6ecbb3dd058221dbf7aa923f0ab782ab93bb82d42edc9996f7f0816a318bdc761e55c02b95e1169cef66f7edc@159.203.24.35:30303", - "enode://8e0af07c86ec36590bb6368e7ad0c45b6dc658f5fb66ec68889a614affddda5e021bd513bcf4fb2fae4a3bbe08cf0de84f037cd58478a89665dfce1ded2595c7@34.236.37.74:30303", - "enode://182ee200ca134dc4d6390f3d5aadbcd80df0f7f24335830335d142573eacce4eeb919d30e82c5df588034e167e6ba6dd11187502ac9264a71005127f6b146a99@159.203.95.241:30303", - "enode://b022ff70b5fcaf9596ae5efed99a8198b4ae0578ee9d17b733609d803a75cef95d3a2a18e50dca9a7c3b26139f158c59eaf8b5fb8d1d331c9a46934a78acabe8@206.189.76.128:30303" + "enode://f11a0f80939b49a28bf99581da9b351a592ec1504b9d32a7dfda79b36510a891e96631239c4166e5c73368c21e9bb3241e7fd6929b899772e5a8fe9a7b7c3af6@45.77.52.149:30303" ], "accounts": { "0000000000000000000000000000000000000005": { @@ -89,11 +88,19 @@ "name": "alt_bn128_add", "pricing": { "0": { - "price": { "alt_bn128_const_operations": { "price": 500 }} + "price": { + "alt_bn128_const_operations": { + "price": 500 + } + } }, "12095200": { "info": "EIP 1108 transition", - "price": { "alt_bn128_const_operations": { "price": 150 }} + "price": { + "alt_bn128_const_operations": { + "price": 150 + } + } } } } @@ -103,11 +110,19 @@ "name": "alt_bn128_mul", "pricing": { "0": { - "price": { "alt_bn128_const_operations": { "price": 40000 }} + "price": { + "alt_bn128_const_operations": { + "price": 40000 + } + } }, "12095200": { "info": "EIP 1108 transition", - "price": { "alt_bn128_const_operations": { "price": 6000 }} + "price": { + "alt_bn128_const_operations": { + "price": 6000 + } + } } } } @@ -117,11 +132,21 @@ "name": "alt_bn128_pairing", "pricing": { "0": { - "price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }} + "price": { + "alt_bn128_pairing": { + "base": 100000, + "pair": 80000 + } + } }, "12095200": { "info": "EIP 1108 transition", - "price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }} + "price": { + "alt_bn128_pairing": { + "base": 45000, + "pair": 34000 + } + } } } } diff --git a/crates/ethcore/res/chainspec/test/authority_round_randomness_contract.json b/crates/ethcore/res/chainspec/test/authority_round_randomness_contract.json new file mode 100644 index 000000000..2c820ee77 --- /dev/null +++ b/crates/ethcore/res/chainspec/test/authority_round_randomness_contract.json @@ -0,0 +1,100 @@ +{ + "name": "TestAuthorityRoundRandomnessContract", + "engine": { + "authorityRound": { + "params": { + "stepDuration": 1, + "startStep": 2, + "validators": { + "list": [ + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" + ] + }, + "immediateTransitions": true, + "maximumEmptySteps": "2", + "randomnessContractAddress": { + "0": "0x0000000000000000000000000000000000000042" + } + } + } + }, + "params": { + "gasLimitBoundDivisor": "0x0400", + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x69", + "eip140Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip658Transition": "0x0" + }, + "genesis": { + "seal": { + "authorityRound": { + "step": "0x0", + "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x222222" + }, + "accounts": { + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "100000000000" }, + "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0000000000000000000000000000000000000005": { "balance": "1", "builtin": { "name": "modexp", "activate_at": 0, "pricing": { "modexp": { "divisor": 20 } } } }, + "0000000000000000000000000000000000000006": { + "balance": "1", + "builtin": { + "name": "alt_bn128_add", + "pricing": { + "0x0": { + "price": { "linear": { "base": 500, "word": 0 }} + }, + "0x7fffffffffffff": { + "price": { "linear": { "base": 150, "word": 0 }} + } + } + } + }, + "0000000000000000000000000000000000000007": { + "balance": "1", + "builtin": { + "name": "alt_bn128_mul", + "pricing": { + "0x0": { + "price": { "linear": { "base": 40000, "word": 0 }} + }, + "0x7fffffffffffff": { + "price": { "linear": { "base": 6000, "word": 0 }} + } + } + } + }, + "0000000000000000000000000000000000000008": { + "balance": "1", + "builtin": { + "name": "alt_bn128_pairing", + "pricing": { + "0x0": { + "price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }} + }, + "0x7fffffffffffff": { + "price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }} + } + } + } + }, + "0000000000000000000000000000000000000042": { + "balance": "1", + "constructor": "608060405234801561001057600080fd5b50610820806100206000396000f3fe608060405234801561001057600080fd5b50600436106100ec576000357c01000000000000000000000000000000000000000000000000000000009004806363f160e6116100a95780637a3e286b116100835780637a3e286b14610378578063baf11cab14610380578063c358ced0146103ac578063fe7d567d146103b4576100ec565b806363f160e614610285578063695e89f6146102c557806374ce906714610370576100ec565b806304fdb016146100f15780630b61ba8514610192578063209652551461020b5780632e8a8dd5146102255780633fa4f245146102515780635580e58b14610259575b600080fd5b61011d6004803603604081101561010757600080fd5b5080359060200135600160a060020a03166103d1565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561015757818101518382015260200161013f565b50505050905090810190601f1680156101845780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610209600480360360408110156101a857600080fd5b813591908101906040810160208201356401000000008111156101ca57600080fd5b8201836020820111156101dc57600080fd5b803590602001918460018302840111640100000000831117156101fe57600080fd5b509092509050610475565b005b6102136104fa565b60408051918252519081900360200190f35b6102136004803603604081101561023b57600080fd5b5080359060200135600160a060020a0316610501565b61021361051b565b6102136004803603604081101561026f57600080fd5b5080359060200135600160a060020a0316610521565b6102b16004803603604081101561029b57600080fd5b5080359060200135600160a060020a031661053e565b604080519115158252519081900360200190f35b6102f1600480360360408110156102db57600080fd5b5080359060200135600160a060020a0316610568565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561033457818101518382015260200161031c565b50505050905090810190601f1680156103615780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b6102b1610639565b610213610649565b6102b16004803603604081101561039657600080fd5b5080359060200135600160a060020a0316610654565b6102b161067c565b610209600480360360208110156103ca57600080fd5b5035610687565b600160208181526000938452604080852082529284529282902080548351600293821615610100026000190190911692909204601f8101859004850283018501909352828252909290919083018282801561046d5780601f106104425761010080835404028352916020019161046d565b820191906000526020600020905b81548152906001019060200180831161045057829003601f168201915b505050505081565b41331461048157600080fd5b61048d60014303610735565b61049657600080fd5b60006104a460014303610740565b90506104b08133610654565b156104ba57600080fd5b600081815260208181526040808320338085529083528184208890558484526001835281842090845290915290206104f3908484610753565b5050505050565b6003545b90565b600060208181529281526040808220909352908152205481565b60035481565b600260209081526000928352604080842090915290825290205481565b6000918252600260209081526040808420600160a060020a03939093168452919052902054151590565b600082815260208181526040808320600160a060020a03851680855290835281842054868552600180855283862092865291845282852080548451600294821615610100026000190190911693909304601f810186900486028401860190945283835260609491939092918391908301828280156106275780601f106105fc57610100808354040283529160200191610627565b820191906000526020600020905b81548152906001019060200180831161060a57829003601f168201915b50505050509050915091509250929050565b600061064443610735565b905090565b600061064443610740565b600091825260208281526040808420600160a060020a03939093168452919052902054151590565b600061064443610747565b41331461069357600080fd5b61069f60014303610747565b6106a857600080fd5b60006106b660014303610740565b90506106c2813361053e565b156106cc57600080fd5b60408051602080820185905282518083038201815291830183528151918101919091206000848152808352838120338252909252919020541461070e57600080fd5b60009081526002602090815260408083203384529091529020819055600380549091189055565b600360069091061090565b6006900490565b60036006909106101590565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106107945782800160ff198235161785556107c1565b828001600101855582156107c1579182015b828111156107c15782358255916020019190600101906107a6565b506107cd9291506107d1565b5090565b6104fe91905b808211156107cd57600081556001016107d756fea265627a7a7230582008bb7311af9026bd70ddb998741333d414a366275b9b433a2943bbd6bedc27ae64736f6c634300050a0032" + } + } +} \ No newline at end of file diff --git a/crates/ethcore/res/chainspec/test/contract_ver_3.sol b/crates/ethcore/res/chainspec/test/contract_ver_3.sol new file mode 100644 index 000000000..dd28c8e33 --- /dev/null +++ b/crates/ethcore/res/chainspec/test/contract_ver_3.sol @@ -0,0 +1,60 @@ +pragma solidity ^0.4.20; + +// Adapted from https://gist.github.com/VladLupashevskyi/84f18eabb1e4afadf572cf92af3e7e7f +// and: https://github.com/poanetwork/posdao-contracts/blob/master/contracts/TxPermission.sol + +contract TxPermission { + /// Allowed transaction types mask + uint32 constant None = 0; + uint32 constant All = 0xffffffff; + uint32 constant Basic = 0x01; + uint32 constant Call = 0x02; + uint32 constant Create = 0x04; + uint32 constant Private = 0x08; + + /// Contract name + function contractName() public constant returns (string) { + return "TX_PERMISSION_CONTRACT"; + } + + /// Contract name hash + function contractNameHash() public constant returns (bytes32) { + return keccak256(contractName()); + } + + /// Contract version + function contractVersion() public constant returns (uint256) { + return 3; + } + + /// @dev Defines the allowed transaction types which may be initiated by the specified sender with + /// the specified gas price and data. Used by the Parity engine each time a transaction is about to be + /// included into a block. See https://wiki.parity.io/Permissioning.html#how-it-works-1 + /// @param _sender Transaction sender address. + /// @param _to Transaction recipient address. If creating a contract, the `_to` address is zero. + /// @param _value Transaction amount in wei. + /// @param _gasPrice Gas price in wei for the transaction. + /// @param _data Transaction data. + /// @return `uint32 typesMask` - Set of allowed transactions for `_sender` depending on tx `_to` address, + /// `_gasPrice`, and `_data`. The result is represented as a set of flags: + /// 0x01 - basic transaction (e.g. ether transferring to user wallet); + /// 0x02 - contract call; + /// 0x04 - contract creation; + /// 0x08 - private transaction. + /// `bool cache` - If `true` is returned, the same permissions will be applied from the same + /// `_sender` without calling this contract again. + function allowedTxTypes( + address _sender, + address _to, + uint256 _value, + uint256 _gasPrice, + bytes memory _data + ) + public + view + returns(uint32 typesMask, bool cache) + { + if (_gasPrice > 0 || _data.length < 4) return (All, false); + return (None, false); + } +} \ No newline at end of file diff --git a/crates/ethcore/res/chainspec/test/contract_ver_3_genesis.json b/crates/ethcore/res/chainspec/test/contract_ver_3_genesis.json new file mode 100644 index 000000000..28c44d8de --- /dev/null +++ b/crates/ethcore/res/chainspec/test/contract_ver_3_genesis.json @@ -0,0 +1,44 @@ +{ + "name": "TestNodeFilterContract", + "engine": { + "authorityRound": { + "params": { + "stepDuration": 1, + "startStep": 2, + "validators": { + "contract": "0x0000000000000000000000000000000000000000" + } + } + } + }, + "params": { + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x69", + "gasLimitBoundDivisor": "0x0400", + "transactionPermissionContract": "0x0000000000000000000000000000000000000005", + "transactionPermissionContractTransition": "1" + }, + "genesis": { + "seal": { + "generic": "0xc180" + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x222222" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0000000000000000000000000000000000000005": { + "balance": "1", + "constructor": "6060604052341561000f57600080fd5b61035e8061001e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063469ab1e31461006757806375d0c0dc14610098578063a0a8e46014610126578063b9056afa1461014f575b600080fd5b341561007257600080fd5b61007a610227565b60405180826000191660001916815260200191505060405180910390f35b34156100a357600080fd5b6100ab610298565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100eb5780820151818401526020810190506100d0565b50505050905090810190601f1680156101185780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561013157600080fd5b6101396102db565b6040518082815260200191505060405180910390f35b341561015a57600080fd5b6101fa600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190803590602001909190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919050506102e4565b604051808363ffffffff1663ffffffff168152602001821515151581526020019250505060405180910390f35b6000610231610298565b6040518082805190602001908083835b6020831015156102665780518252602082019150602081019050602083039250610241565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020905090565b6102a061031e565b6040805190810160405280601681526020017f54585f5045524d495353494f4e5f434f4e545241435400000000000000000000815250905090565b60006003905090565b60008060008411806102f7575060048351105b1561030c5763ffffffff600091509150610314565b600080915091505b9550959350505050565b6020604051908101604052806000815250905600a165627a7a72305820be61565bc09fec6e9223a1fecd2e94783ca5c6f506c03f71d479a8c3285493310029" + } + } +} \ No newline at end of file diff --git a/crates/ethcore/res/chainspec/test/validator_contract.json b/crates/ethcore/res/chainspec/test/validator_contract.json index 9d007ac23..841fd2249 100644 --- a/crates/ethcore/res/chainspec/test/validator_contract.json +++ b/crates/ethcore/res/chainspec/test/validator_contract.json @@ -37,7 +37,7 @@ "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, "0000000000000000000000000000000000000005": { "balance": "1", - "constructor": "60a06040819052737d577a597b2742b498cb5cf0c26cdcd726d39e6e60609081527382a978b3f5962a5b0957d9ee9eef472ee55b42f1608052600080546002825581805290927f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639182019291905b828111156100a25782518254600160a060020a031916600160a060020a0390911617825560209092019160019091019061006d565b5b506100cd9291505b808211156100c9578054600160a060020a03191681556001016100ab565b5090565b505034610000575b60005b60005481101561012f578060016000600084815481101561000057906000526020600020900160005b9054600160a060020a036101009290920a90041681526020810191909152604001600020555b6001016100d8565b5b505b610453806101416000396000f3006060604052361561005c5763ffffffff60e060020a60003504166335aa2e4481146100615780634d238c8e1461008d578063b7ab4db5146100a8578063c476dd4014610110578063d69f13bb14610172578063d8f2e0bf14610190575b610000565b34610000576100716004356101b9565b60408051600160a060020a039092168252519081900360200190f35b34610000576100a6600160a060020a03600435166101e9565b005b34610000576100b5610260565b60408051602080825283518183015283519192839290830191858101910280838382156100fd575b8051825260208311156100fd57601f1990920191602091820191016100dd565b5050509050019250505060405180910390f35b3461000057604080516020600460443581810135601f81018490048402850184019095528484526100a6948235600160a060020a03169460248035956064949293919092019181908401838280828437509496506102ca95505050505050565b005b34610000576100a6600160a060020a03600435166024356103eb565b005b3461000057610071610418565b60408051600160a060020a039092168252519081900360200190f35b600081815481101561000057906000526020600020900160005b915054906101000a9004600160a060020a031681565b6000805480600101828181548183558181151161022b5760008381526020902061022b9181019083015b808211156102275760008155600101610213565b5090565b5b505050916000526020600020900160005b8154600160a060020a038086166101009390930a92830292021916179055505b50565b60408051602081810183526000808352805484518184028101840190955280855292939290918301828280156102bf57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116102a1575b505050505090505b90565b6000805460001981019081101561000057906000526020600020900160005b9054906101000a9004600160a060020a031660006001600086600160a060020a0316600160a060020a0316815260200190815260200160002054815481101561000057906000526020600020900160005b8154600160a060020a039384166101009290920a918202918402191617905583166000908152600160205260408120819055805460001981019081101561000057906000526020600020900160005b6101000a815490600160a060020a03021916905560008054809190600190038154818355818115116103e0576000838152602090206103e09181019083015b808211156102275760008155600101610213565b5090565b5b505050505b505050565b6002805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0384161790555b5050565b600254600160a060020a0316815600a165627a7a72305820f7876e17abd5f0927fff16788b4b3c9028ed64e6db740d788b07fc5f0a8f10920029" + "constructor": "60c0604052737d577a597b2742b498cb5cf0c26cdcd726d39e6e60809081527382a978b3f5962a5b0957d9ee9eef472ee55b42f160a05261004390600290816100a3565b5034801561005057600080fd5b5060005b60025481101561009d5780600560006002848154811061007057fe5b6000918252602080832090910154600160a060020a03168352820192909252604001902055600101610054565b5061012f565b8280548282559060005260206000209081019282156100f8579160200282015b828111156100f85782518254600160a060020a031916600160a060020a039091161782556020909201916001909101906100c3565b50610104929150610108565b5090565b61012c91905b80821115610104578054600160a060020a031916815560010161010e565b90565b6108e28061013e6000396000f3fe608060405234801561001057600080fd5b50600436106100d1576000357c010000000000000000000000000000000000000000000000000000000090048063b56b366b1161008e578063b56b366b14610206578063b7ab4db514610282578063c476dd401461028a578063cbd2d5281461030f578063d69f13bb14610345578063d8f2e0bf14610371576100d1565b806335aa2e44146100d65780633d3b54581461010f578063752862111461012b5780639300c9261461013557806393b4e25e146101d8578063a92252ae146101e0575b600080fd5b6100f3600480360360208110156100ec57600080fd5b5035610379565b60408051600160a060020a039092168252519081900360200190f35b6101176103a0565b604080519115158252519081900360200190f35b6101336103a7565b005b6101336004803603602081101561014b57600080fd5b81019060208101813564010000000081111561016657600080fd5b82018360208201111561017857600080fd5b8035906020019184602083028401116401000000008311171561019a57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506103a9945050505050565b6101336103c8565b610117600480360360208110156101f657600080fd5b5035600160a060020a031661044f565b6102326004803603604081101561021c57600080fd5b50600160a060020a038135169060200135610464565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561026e578181015183820152602001610256565b505050509050019250505060405180910390f35b6102326104e3565b610133600480360360608110156102a057600080fd5b600160a060020a03823516916020810135918101906060810160408201356401000000008111156102d057600080fd5b8201836020820111156102e257600080fd5b8035906020019184600183028401116401000000008311171561030457600080fd5b509092509050610545565b6101176004803603606081101561032557600080fd5b50600160a060020a038135811691602081013590911690604001356106eb565b6101336004803603604081101561035b57600080fd5b50600160a060020a038135169060200135610788565b6100f36107b8565b6002818154811061038657fe5b600091825260209091200154600160a060020a0316905081565b4315155b90565b565b80516103bc9060029060208401906107c7565b506103c56103c8565b50565b60014303407f55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c8960026040518080602001828103825283818154815260200191508054801561043f57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610421575b50509250505060405180910390a2565b60016020526000908152604090205460ff1681565b600160a060020a03821660009081526003602090815260408083208484528252918290208054835181840281018401909452808452606093928301828280156104d657602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116104b8575b5050505050905092915050565b6060600280548060200260200160405190810160405280929190818152602001828054801561053b57602002820191906000526020600020905b8154600160a060020a0316815260019091019060200180831161051d575b5050505050905090565b600160a060020a0384166000818152600360209081526040808320878452825280832080546001818101835591855283852001805473ffffffffffffffffffffffffffffffffffffffff1916339081179091558585526004845282852089865284528285208186528452828520805460ff19908116841790915586865282855283862080549091169092179091556005909252909120546002805492939290919081106105ee57fe5b600091825260209091200154600160a060020a031614156106e45760028054600019810190811061061b57fe5b6000918252602080832090910154600160a060020a038881168452600590925260409092205460028054929093169291811061065357fe5b6000918252602080832091909101805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a039485161790559187168152600590915260408120556002805460001981019081106106a957fe5b6000918252602090912001805473ffffffffffffffffffffffffffffffffffffffff1916905560028054906106e2906000198301610839565b505b5050505050565b60004380831115610700576000915050610781565b60648111801561071257508260648203115b15610721576000915050610781565b600160a060020a03841660009081526001602052604090205460ff161561074c576000915050610781565b5050600160a060020a03808316600090815260046020908152604080832085845282528083209387168352929052205460ff16155b9392505050565b506000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0392909216919091179055565b600054600160a060020a031681565b828054828255906000526020600020908101928215610829579160200282015b82811115610829578251825473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a039091161782556020909201916001909101906107e7565b50610835929150610862565b5090565b81548183558181111561085d5760008381526020902061085d918101908301610893565b505050565b6103a491905b8082111561083557805473ffffffffffffffffffffffffffffffffffffffff19168155600101610868565b6103a491905b80821115610835576000815560010161089956fea265627a7a7231582073e77a97d79ab4382d5a28644654c335489310e72aa6c8f737e424bdb6c2bbd664736f6c63430005100032" }, "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }, "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } diff --git a/crates/ethcore/res/chainspec/test/validator_contract.sol b/crates/ethcore/res/chainspec/test/validator_contract.sol new file mode 100644 index 000000000..568c8dd36 --- /dev/null +++ b/crates/ethcore/res/chainspec/test/validator_contract.sol @@ -0,0 +1,119 @@ +// Source for the test AuRa validator set contract. DO NOT USE IN PRODUCTION. +// +// Contains POSDAO features. The full POSDAO ValidatorSet contract production code is available at +// https://github.com/poanetwork/posdao-contracts/blob/master/contracts/ValidatorSetAuRa.sol +// +// The bytecode of this contract is included in `validator_contract.json` as the +// constructor of address `0x0000..0005`. + +pragma solidity ^0.5.0; + +contract TestValidatorSet { + + address public disliked; // contains the address of validator reported by `reportBenign` + mapping(address => bool) public isValidatorBanned; // if the validator is banned by `reportMalicious` + + // The initial set of validators + address[] public validators = [ + 0x7d577a597B2742b498Cb5Cf0C26cDCD726d39E6e, + 0x82A978B3f5962A5b0957d9ee9eEf472EE55B42F1 + ]; + + // The mappings used by POSDAO features testing (see `reportMalicious` and `shouldValidatorReport` functions below) + mapping(address => mapping(uint256 => address[])) private _maliceReportedForBlock; + mapping(address => mapping(uint256 => mapping(address => bool))) private _maliceReportedForBlockMapped; + mapping(address => uint256) private _validatorIndex; + + // The standard event to notify the engine about the validator set changing in the contract + event InitiateChange(bytes32 indexed parentHash, address[] newSet); + + constructor() public { + // Initialize validator indices to be able to correctly remove + // a malicious validator from the validator set later + for (uint i = 0; i < validators.length; i++) { + _validatorIndex[validators[i]] = i; + } + } + + // Emits an `InitiateChange` event with the current (or new) validator set + function emitInitiateChange() public { + emit InitiateChange(blockhash(block.number - 1), validators); + } + + // Applies a validator set change in production code. Does nothing in the test + function finalizeChange() pure public {} + + // Benign validator behaviour report. Kept here for regression testing + function reportBenign(address _validator, uint256) public { + disliked = _validator; + } + + // Removes a malicious validator from the list + function reportMalicious(address _validator, uint256 _blockNum, bytes calldata) external { + address reportingValidator = msg.sender; + + // Mark the `_validator` as reported by `reportingValidator` for the block `_blockNum` + _maliceReportedForBlock[_validator][_blockNum].push(reportingValidator); + _maliceReportedForBlockMapped[_validator][_blockNum][reportingValidator] = true; + isValidatorBanned[_validator] = true; + + // If the passed validator is in the validator set + if (validators[_validatorIndex[_validator]] == _validator) { + // Remove the validator from the set + validators[_validatorIndex[_validator]] = validators[validators.length - 1]; + delete _validatorIndex[_validator]; + delete validators[validators.length - 1]; + validators.length--; + } + } + + // Tests validator set changing and emitting the `InitiateChange` event + function setValidators(address[] memory _validators) public { + validators = _validators; + emitInitiateChange(); + } + + // Checks if `emitInitiateChange` can be called (used by POSDAO tests) + function emitInitiateChangeCallable() view public returns(bool) { + return block.number > 0; + } + + // Returns the current validator set + function getValidators() public view returns(address[] memory) { + return validators; + } + + // Returns the list of all validators that reported the given validator + // as malicious for the given block. Used by POSDAO tests + function maliceReportedForBlock(address _validator, uint256 _blockNum) public view returns(address[] memory) { + return _maliceReportedForBlock[_validator][_blockNum]; + } + + // Returns a boolean flag indicating whether the specified validator + // should report about some validator's misbehaviour at the specified block. + // Used by POSDAO tests. + // `_reportingValidator` is the address of validator who reports. + // `_maliciousValidator` is the address of malicious validator. + // `_blockNumber` is the block number at which the malicious validator misbehaved. + function shouldValidatorReport( + address _reportingValidator, + address _maliciousValidator, + uint256 _blockNumber + ) public view returns(bool) { + uint256 currentBlock = block.number; + if (_blockNumber > currentBlock) { + return false; + } + if (currentBlock > 100 && currentBlock - 100 > _blockNumber) { + return false; + } + if (isValidatorBanned[_maliciousValidator]) { + // We shouldn't report the malicious validator + // as it has already been reported and banned + return false; + } + // Return `false` if already reported by the same `_reportingValidator` for the same `_blockNumber` + return !_maliceReportedForBlockMapped[_maliciousValidator][_blockNumber][_reportingValidator]; + } + +} \ No newline at end of file diff --git a/crates/ethcore/res/chainspec/xdai.json b/crates/ethcore/res/chainspec/xdai.json index cecc79171..2bb2d2480 100644 --- a/crates/ethcore/res/chainspec/xdai.json +++ b/crates/ethcore/res/chainspec/xdai.json @@ -11,15 +11,27 @@ "validators": { "multi": { "0": { - "list": ["0xcace5b3c29211740e595850e80478416ee77ca21"] + "list": [ + "0xcace5b3c29211740e595850e80478416ee77ca21" + ] }, "1300": { "safeContract": "0x22e1229a2c5b95a60983b5577f745a603284f535" + }, + "9186425": { + "contract": "0xB87BE9f7196F2AE084Ca1DE6af5264292976e013" } } }, "blockRewardContractAddress": "0x867305d19606aadba405ce534e303d0e225f9556", - "blockRewardContractTransition": 1310 + "blockRewardContractTransition": 1310, + "blockRewardContractTransitions": { + "9186425": "0x481c034c6d9441db23Ea48De68BCAe812C5d39bA" + }, + "randomnessContractAddress": { + "9186425": "0x5870b0527DeDB1cFBD9534343Feda1a41Ce47766" + }, + "posdaoTransition": 9186425 } } }, @@ -42,7 +54,9 @@ "eip1706Transition": 7298030, "eip1884Transition": 7298030, "eip2028Transition": 7298030, - "registrar": "0x1ec97dc137f5168af053c24460a1200502e1a9d2" + "registrar": "0x6B53721D4f2Fb9514B85f5C49b197D857e36Cf03", + "transactionPermissionContract": "0x7Dd7032AA75A37ea0b150f57F899119C7379A78b", + "transactionPermissionContractTransition": 9186425 }, "genesis": { "seal": { @@ -54,6 +68,25 @@ "difficulty": "0x20000", "gasLimit": "0x989680" }, + "nodes": [ + "enode://4716883567b5317aad93ea28e707fad0631fb4aa5ac7c5fbd485380b01d8801c21a8cbf4d6ee3a2c9b2b070a270a49d4a2a0da9e1d47a1f433dafbaf7b2edd06@157.245.92.222:30303", + "enode://ab7f6c633ba2dc54795dfd2c739ba7d964f499541c0b8d8ba9d275bd3df1b789470a21a921a469fa515a3dfccc96a434a3fd016a169d88d0043fc6744f34288e@67.205.180.17:30303", + "enode://0caa2d84aef00d0bc5de6cf9db3e736da245d882ec8f91e201b3e1635960e62cbb2f8bfc57e679ff3e1d53da2773e31df624a56b2f457ecb51d09fdf9970c86b@67.205.145.143:30303", + "enode://bd75111424c42c349fc255db017ac0be370b37b558627e3bbc41319071ef7642c04cdbd2b674193a99aa35d67a83016ab293b8ab87ed4a4606e69f114ac95535@157.230.185.80:30303", + "enode://bd75111424c42c349fc255db017ac0be370b37b558627e3bbc41319071ef7642c04cdbd2b674193a99aa35d67a83016ab293b8ab87ed4a4606e69f114ac95535@161.35.51.60:30303", + "enode://ef94ffb10c440dd990c5c4be1c85046f5f7329ba60d23db7a68c8b91b6a721081f8190369f3a32f3c02d213127b2066eb42ee0444998d354ba0923378522acb3@161.35.62.72:30303", + "enode://4a0eadf22d6a37c5596fd2df2a53a26a5b59dd863e67246ab94e6a81b31765e08d9f70a4dd9683221e63cc2120c8a808a6a457455bd658bdf49c688c62db2011@51.81.244.170:30303", + "enode://e75a1e9f080bd6012b39321c0f2d984567172625280b3e7362e962a42578c5e79c847b3eb83aa7e2a4cdeefbfadf0c36ed2719cad1d5e6377ccd6ebe314cc6bc@64.227.97.130:30303", + "enode://6674773f7aac78d5527fa90c847dcbca198de4081306406a8fec5c15f7a2e141362344041291dd10d0aafa7706a3d8f21a08b6f6834a5b1aab9cccd8ca35ccee@143.110.226.15:30303", + "enode://0caa2d84aef00d0bc5de6cf9db3e736da245d882ec8f91e201b3e1635960e62cbb2f8bfc57e679ff3e1d53da2773e31df624a56b2f457ecb51d09fdf9970c86b@134.122.24.231:30303", + "enode://0caa2d84aef00d0bc5de6cf9db3e736da245d882ec8f91e201b3e1635960e62cbb2f8bfc57e679ff3e1d53da2773e31df624a56b2f457ecb51d09fdf9970c86b@67.205.145.143:30303", + "enode://0caa2d84aef00d0bc5de6cf9db3e736da245d882ec8f91e201b3e1635960e62cbb2f8bfc57e679ff3e1d53da2773e31df624a56b2f457ecb51d09fdf9970c86b@162.243.164.98:30303", + "enode://0caa2d84aef00d0bc5de6cf9db3e736da245d882ec8f91e201b3e1635960e62cbb2f8bfc57e679ff3e1d53da2773e31df624a56b2f457ecb51d09fdf9970c86b@167.99.4.175:30303", + "enode://da2449aaba873c40c6daf764de55f4b9eae24c4738daec893ef95b6ada96463c6b9624f8e376e1073d21dd820c5bb361e14575121b09bbd7735b6b556ee1b768@67.205.176.117:30303", + "enode://e8c7a0db430429bb374c981438c0dbd95e565088a483388aa46d8377a3bd62f02cd83d7e2c7e5fc77606141bfef29d23d4285a7c1d9b7e743cf3029314506df7@80.240.16.221:30303", + "enode://2cde5ae04ed57bba5bac8311a97be056838d5304bc3bcee698066e5fc532e846f785198f87e8b84b57b03622a22aac5dd2853c5203d1ece2c9f25b48487d145b@149.28.57.8:30303", + "enode://90b0a0e74a9a1ad258531b4ceec25587d8b52ff2cfb36206a34bf6ba1a8d21b2abd20da13260102508a2ac67afbeb2d2ab7a5e9d6bea3bce845cd81e655585cc@45.77.110.159:30303" + ], "accounts": { "0x0000000000000000000000000000000000000005": { "builtin": { @@ -74,11 +107,19 @@ "name": "alt_bn128_add", "pricing": { "0": { - "price": { "alt_bn128_const_operations": { "price": 500 }} + "price": { + "alt_bn128_const_operations": { + "price": 500 + } + } }, "7298030": { "info": "EIP 1108 transition", - "price": { "alt_bn128_const_operations": { "price": 150 }} + "price": { + "alt_bn128_const_operations": { + "price": 150 + } + } } } } @@ -88,11 +129,19 @@ "name": "alt_bn128_mul", "pricing": { "0": { - "price": { "alt_bn128_const_operations": { "price": 40000 }} + "price": { + "alt_bn128_const_operations": { + "price": 40000 + } + } }, "7298030": { "info": "EIP 1108 transition", - "price": { "alt_bn128_const_operations": { "price": 6000 }} + "price": { + "alt_bn128_const_operations": { + "price": 6000 + } + } } } } @@ -102,11 +151,21 @@ "name": "alt_bn128_pairing", "pricing": { "0": { - "price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }} + "price": { + "alt_bn128_pairing": { + "base": 100000, + "pair": 80000 + } + } }, "7298030": { "info": "EIP 1108 transition", - "price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }} + "price": { + "alt_bn128_pairing": { + "base": 45000, + "pair": 34000 + } + } } } } @@ -190,10 +249,6 @@ } } } - }, - "nodes": [ - "enode://1c19ba0a77dd663b843c33beb9020e7eb41fc34b47b98424dbc427f74692115d74c7c27b6c0aa2b59bb1d8f710650cae1090153d10b6909ca7bdcdfb183b1c59@54.39.190.172:30303", - "enode://c1c3a604950119f82d78189792b73f5a82a239017c77465e3c32fc51c1d758a9a772ffddd58436d465342f2cfa6d4a442a49e526743f4d8354d7c5ce794c3ee5@95.179.222.48:30303" - ] + } } diff --git a/crates/ethcore/res/contracts/authority_round_random.json b/crates/ethcore/res/contracts/authority_round_random.json new file mode 100644 index 000000000..5b41014a5 --- /dev/null +++ b/crates/ethcore/res/contracts/authority_round_random.json @@ -0,0 +1,133 @@ +[{ + "constant": false, + "inputs": [{ + "name": "_secretHash", + "type": "bytes32" + }, + { + "name": "_cipher", + "type": "bytes" + } + ], + "name": "commitHash", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "constant": false, + "inputs": [{ + "name": "_number", + "type": "uint256" + }], + "name": "revealNumber", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" +}, +{ + "constant": true, + "inputs": [], + "name": "currentCollectRound", + "outputs": [{ + "name": "", + "type": "uint256" + }], + "payable": false, + "stateMutability": "view", + "type": "function" +}, +{ + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "getCommitAndCipher", + "outputs": [ + { + "name": "", + "type": "bytes32" + }, + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" +}, +{ + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "isCommitted", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" +}, +{ + "constant": true, + "inputs": [], + "name": "isCommitPhase", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" +}, +{ + "constant": true, + "inputs": [], + "name": "isRevealPhase", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" +}, +{ + "constant": true, + "inputs": [{ + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_validator", + "type": "address" + } + ], + "name": "sentReveal", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "stateMutability": "view", + "type": "function" +} +] \ No newline at end of file diff --git a/crates/ethcore/res/contracts/block_gas_limit.json b/crates/ethcore/res/contracts/block_gas_limit.json new file mode 100644 index 000000000..8ca9cf969 --- /dev/null +++ b/crates/ethcore/res/contracts/block_gas_limit.json @@ -0,0 +1,16 @@ +[ + { + "constant": true, + "inputs": [], + "name": "blockGasLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/crates/ethcore/res/contracts/test_authority_round_random.json b/crates/ethcore/res/contracts/test_authority_round_random.json new file mode 100644 index 000000000..beab82ab4 --- /dev/null +++ b/crates/ethcore/res/contracts/test_authority_round_random.json @@ -0,0 +1,265 @@ +[ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "address" + } + ], + "name": "ciphers", + "outputs": [ + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "getCipher", + "outputs": [ + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_secretHash", + "type": "bytes32" + }, + { + "name": "_cipher", + "type": "bytes" + } + ], + "name": "commitHash", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getValue", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "address" + } + ], + "name": "hashes", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "value", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "address" + } + ], + "name": "secrets", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "sentReveal", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isCommitPhase", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_number", + "type": "uint256" + } + ], + "name": "revealNumber", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getRound", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "isCommitted", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isRevealPhase", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_collectRound", + "type": "uint256" + }, + { + "name": "_miningAddress", + "type": "address" + } + ], + "name": "getCommit", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/crates/ethcore/res/contracts/test_authority_round_random.sol b/crates/ethcore/res/contracts/test_authority_round_random.sol new file mode 100644 index 000000000..66c971786 --- /dev/null +++ b/crates/ethcore/res/contracts/test_authority_round_random.sol @@ -0,0 +1,101 @@ +pragma solidity 0.5.10; + +/// @dev Randomness test contract based on https://github.com/poanetwork/posdao-contracts. +/// Generates and stores random numbers in a RANDAO manner and accumulates a random seed. +contract Random { + mapping(uint256 => mapping(address => bytes32)) public hashes; + mapping(uint256 => mapping(address => bytes)) public ciphers; + mapping(uint256 => mapping(address => uint256)) public secrets; + uint256 public value; + + /// @dev Called by the validator's node to store a hash and a cipher of the validator's secret on each collection + /// round. The validator's node must use its mining address to call this function. + /// This function can only be called once per collection round (during the `commits phase`). + /// @param _secretHash The Keccak-256 hash of the validator's secret. + /// @param _cipher The cipher of the validator's secret. Can be used by the node to decrypt and reveal. + function commitHash(bytes32 _secretHash, bytes calldata _cipher) external { + require(block.coinbase == msg.sender); + require(_isCommitPhase(block.number - 1)); + uint256 round = _collectRound(block.number - 1); + require(!isCommitted(round, msg.sender)); + hashes[round][msg.sender] = _secretHash; + ciphers[round][msg.sender] = _cipher; + } + + /// @dev Called by the validator's node to XOR its secret with the current random seed. + /// The validator's node must use its mining address to call this function. + /// This function can only be called once per collection round (during the `reveals phase`). + /// @param _number The validator's secret. + function revealNumber(uint256 _number) external { + require(block.coinbase == msg.sender); + require(_isRevealPhase(block.number - 1)); + uint256 round = _collectRound(block.number - 1); + require(!sentReveal(round, msg.sender)); + require(hashes[round][msg.sender] == keccak256(abi.encodePacked(_number))); + secrets[round][msg.sender] = _number; + value ^= _number; + } + + /// @dev Returns the Keccak-256 hash and cipher of the validator's secret for the specified collection round + /// and the specified validator stored by the validator through the `commitHash` function. + /// @param _collectRound The serial number of the collection round for which hash and cipher should be retrieved. + /// @param _miningAddress The mining address of validator. + function getCommitAndCipher( + uint256 _collectRound, + address _miningAddress + ) public view returns(bytes32, bytes memory) { + return (hashes[_collectRound][_miningAddress], ciphers[_collectRound][_miningAddress]); + } + + /// @dev Returns a boolean flag indicating whether the specified validator has committed their secret's hash for the + /// specified collection round. + /// @param _collectRound The serial number of the collection round for which the checkup should be done. + /// @param _miningAddress The mining address of the validator. + function isCommitted(uint256 _collectRound, address _miningAddress) public view returns(bool) { + return hashes[_collectRound][_miningAddress] != bytes32(0); + } + + /// @dev Returns a boolean flag indicating whether the current phase of the current collection round + /// is a `commits phase`. Used by the validator's node to determine if it should commit the hash of + /// the secret during the current collection round. + function isCommitPhase() public view returns(bool) { + return _isCommitPhase(block.number); + } + + /// @dev Returns a boolean flag indicating whether the current phase of the current collection round + /// is a `reveals phase`. Used by the validator's node to determine if it should reveal the secret during + /// the current collection round. + function isRevealPhase() public view returns(bool) { + return _isRevealPhase(block.number); + } + + /// @dev Returns a boolean flag of whether the specified validator has revealed their secret for the + /// specified collection round. + /// @param _collectRound The serial number of the collection round for which the checkup should be done. + /// @param _miningAddress The mining address of the validator. + function sentReveal(uint256 _collectRound, address _miningAddress) public view returns(bool) { + return secrets[_collectRound][_miningAddress] != uint256(0); + } + + /// @dev Returns the current collect round number. + function currentCollectRound() public view returns(uint256) { + return _collectRound(block.number); + } + + /// @dev Returns the current random value. + function getValue() public view returns(uint256) { + return value; + } + + function _collectRound(uint256 blockNumber) private pure returns(uint256) { + return blockNumber / 6; + } + + function _isCommitPhase(uint256 blockNumber) private pure returns(bool) { + return blockNumber % 6 < 3; + } + + function _isRevealPhase(uint256 blockNumber) private pure returns(bool) { + return blockNumber % 6 >= 3; + } +} diff --git a/crates/ethcore/res/contracts/tx_acl_gas_price.json b/crates/ethcore/res/contracts/tx_acl_gas_price.json new file mode 100644 index 000000000..37b08e9f0 --- /dev/null +++ b/crates/ethcore/res/contracts/tx_acl_gas_price.json @@ -0,0 +1,83 @@ +[ + { + "constant": true, + "inputs": [], + "name": "contractNameHash", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "contractName", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "contractVersion", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "gasPrice", + "type": "uint256" + }, + { + "name": "data", + "type": "bytes" + } + ], + "name": "allowedTxTypes", + "outputs": [ + { + "name": "", + "type": "uint32" + }, + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/crates/ethcore/res/contracts/validator_report.json b/crates/ethcore/res/contracts/validator_report.json index 093f4bebd..e0c011432 100644 --- a/crates/ethcore/res/contracts/validator_report.json +++ b/crates/ethcore/res/contracts/validator_report.json @@ -1,4 +1,5 @@ [ {"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"},{"name":"proof","type":"bytes"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"}, - {"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"} + {"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"}, + {"constant": true, "inputs": [ { "name": "validator", "type": "address" }, { "name": "blockNum", "type": "uint256" } ], "name": "maliceReportedForBlock", "outputs": [ { "name": "", "type": "address[]" } ], "payable": false, "stateMutability": "view", "type": "function" } ] diff --git a/crates/ethcore/res/contracts/validator_set.json b/crates/ethcore/res/contracts/validator_set.json index d861e16fd..660e8b614 100644 --- a/crates/ethcore/res/contracts/validator_set.json +++ b/crates/ethcore/res/contracts/validator_set.json @@ -1,5 +1,55 @@ [ {"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":false,"name":"_new_set","type":"address[]"}],"name":"InitiateChange","type":"event"} -] + {"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"InitiateChange","type":"event"}, + { + "constant": true, + "inputs": [], + "name": "emitInitiateChangeCallable", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "emitInitiateChange", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_reportingValidator", + "type": "address" + }, + { + "name": "_maliciousValidator", + "type": "address" + }, + { + "name": "_blockNumber", + "type": "uint256" + } + ], + "name": "shouldValidatorReport", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ] \ No newline at end of file diff --git a/crates/ethcore/src/client/client.rs b/crates/ethcore/src/client/client.rs index 71966cd9c..7f707a915 100644 --- a/crates/ethcore/src/client/client.rs +++ b/crates/ethcore/src/client/client.rs @@ -64,17 +64,20 @@ use ansi_term::Colour; use block::{enact_verified, ClosedBlock, Drain, LockedBlock, OpenBlock, SealedBlock}; use call_contract::RegistryInfo; use client::{ - ancient_import::AncientVerifier, bad_blocks, traits::ForceUpdateSealing, AccountData, - BadBlocks, Balance, BlockChain as BlockChainTrait, BlockChainClient, BlockChainReset, BlockId, - BlockInfo, BlockProducer, BroadcastProposalBlock, Call, CallAnalytics, ChainInfo, - ChainMessageType, ChainNotify, ChainRoute, ClientConfig, ClientIoMessage, EngineInfo, - ImportBlock, ImportExportBlocks, ImportSealedBlock, IoClient, Mode, NewBlocks, Nonce, - PrepareOpenBlock, ProvingBlockChainClient, PruningInfo, ReopenBlock, ScheduleInfo, - SealedBlockImporter, StateClient, StateInfo, StateOrBlock, TraceFilter, TraceId, TransactionId, - TransactionInfo, UncleId, + ancient_import::AncientVerifier, + bad_blocks, + traits::{ForceUpdateSealing, TransactionRequest}, + AccountData, BadBlocks, Balance, BlockChain as BlockChainTrait, BlockChainClient, + BlockChainReset, BlockId, BlockInfo, BlockProducer, BroadcastProposalBlock, Call, + CallAnalytics, ChainInfo, ChainMessageType, ChainNotify, ChainRoute, ClientConfig, + ClientIoMessage, EngineInfo, ImportBlock, ImportExportBlocks, ImportSealedBlock, IoClient, + Mode, NewBlocks, Nonce, PrepareOpenBlock, ProvingBlockChainClient, PruningInfo, ReopenBlock, + ScheduleInfo, SealedBlockImporter, StateClient, StateInfo, StateOrBlock, TraceFilter, TraceId, + TransactionId, TransactionInfo, UncleId, }; use engines::{ - epoch::PendingTransition, EngineError, EpochTransition, EthEngine, ForkChoice, MAX_UNCLE_AGE, + epoch::PendingTransition, EngineError, EpochTransition, EthEngine, ForkChoice, SealingState, + MAX_UNCLE_AGE, }; use error::{ BlockError, CallError, Error as EthcoreError, ErrorKind as EthcoreErrorKind, EthcoreResult, @@ -2613,31 +2616,46 @@ impl BlockChainClient for Client { } } - fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { + fn create_transaction( + &self, + TransactionRequest { + action, + data, + gas, + gas_price, + nonce, + }: TransactionRequest, + ) -> Result { let authoring_params = self.importer.miner.authoring_params(); let service_transaction_checker = self.importer.miner.service_transaction_checker(); let gas_price = if let Some(checker) = service_transaction_checker { match checker.check_address(self, authoring_params.author) { Ok(true) => U256::zero(), - _ => self.importer.miner.sensible_gas_price(), + _ => gas_price.unwrap_or_else(|| self.importer.miner.sensible_gas_price()), } } else { self.importer.miner.sensible_gas_price() }; let transaction = TypedTransaction::Legacy(transaction::Transaction { - nonce: self.latest_nonce(&authoring_params.author), - action: Action::Call(address), - gas: self.importer.miner.sensible_gas_limit(), + nonce: nonce.unwrap_or_else(|| self.latest_nonce(&authoring_params.author)), + action, + gas: gas.unwrap_or_else(|| self.importer.miner.sensible_gas_limit()), gas_price, value: U256::zero(), - data: data, + data, }); let chain_id = self.engine.signing_chain_id(&self.latest_env_info()); let signature = self .engine .sign(transaction.signature_hash(chain_id)) .map_err(|e| transaction::Error::InvalidSignature(e.to_string()))?; - let signed = SignedTransaction::new(transaction.with_signature(signature, chain_id))?; + Ok(SignedTransaction::new( + transaction.with_signature(signature, chain_id), + )?) + } + + fn transact(&self, tx_request: TransactionRequest) -> Result<(), transaction::Error> { + let signed = self.create_transaction(tx_request)?; self.importer .miner .import_own_transaction(self, signed.into()) @@ -2906,7 +2924,7 @@ impl ImportSealedBlock for Client { &[], route.enacted(), route.retracted(), - self.engine.seals_internally().is_some(), + self.engine.sealing_state() != SealingState::External, ); self.notify(|notify| { notify.new_blocks(NewBlocks::new( @@ -3599,8 +3617,13 @@ mod tests { #[test] fn should_mark_finalization_correctly_for_parent() { - let client = - generate_dummy_client_with_spec_and_data(Spec::new_test_with_finality, 2, 0, &[]); + let client = generate_dummy_client_with_spec_and_data( + Spec::new_test_with_finality, + 2, + 0, + &[], + false, + ); let chain = client.chain(); let block1_details = chain.block_hash(1).and_then(|h| chain.block_details(&h)); diff --git a/crates/ethcore/src/client/test_client.rs b/crates/ethcore/src/client/test_client.rs index e59b18631..09e880746 100644 --- a/crates/ethcore/src/client/test_client.rs +++ b/crates/ethcore/src/client/test_client.rs @@ -59,12 +59,12 @@ use vm::Schedule; use block::{ClosedBlock, OpenBlock, SealedBlock}; use call_contract::{CallContract, RegistryInfo}; use client::{ - traits::ForceUpdateSealing, AccountData, BadBlocks, Balance, BlockChain, BlockChainClient, - BlockChainInfo, BlockId, BlockInfo, BlockProducer, BlockStatus, BroadcastProposalBlock, Call, - CallAnalytics, ChainInfo, EngineInfo, ImportBlock, ImportSealedBlock, IoClient, LastHashes, - Mode, Nonce, PrepareOpenBlock, ProvingBlockChainClient, ReopenBlock, ScheduleInfo, - SealedBlockImporter, StateClient, StateOrBlock, TraceFilter, TraceId, TransactionId, - TransactionInfo, UncleId, + traits::{ForceUpdateSealing, TransactionRequest}, + AccountData, BadBlocks, Balance, BlockChain, BlockChainClient, BlockChainInfo, BlockId, + BlockInfo, BlockProducer, BlockStatus, BroadcastProposalBlock, Call, CallAnalytics, ChainInfo, + EngineInfo, ImportBlock, ImportSealedBlock, IoClient, LastHashes, Mode, Nonce, + PrepareOpenBlock, ProvingBlockChainClient, ReopenBlock, ScheduleInfo, SealedBlockImporter, + StateClient, StateOrBlock, TraceFilter, TraceId, TransactionId, TransactionInfo, UncleId, }; use engines::EthEngine; use error::{Error, EthcoreResult}; @@ -1039,12 +1039,22 @@ impl BlockChainClient for TestBlockChainClient { } } - fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { + fn create_transaction( + &self, + TransactionRequest { + action, + data, + gas, + gas_price, + nonce, + }: TransactionRequest, + ) -> Result { let transaction = TypedTransaction::Legacy(Transaction { - nonce: self.latest_nonce(&self.miner.authoring_params().author), - action: Action::Call(address), - gas: self.spec.gas_limit, - gas_price: U256::zero(), + nonce: nonce + .unwrap_or_else(|| self.latest_nonce(&self.miner.authoring_params().author)), + action, + gas: gas.unwrap_or(self.spec.gas_limit), + gas_price: gas_price.unwrap_or_else(U256::zero), value: U256::default(), data: data, }); @@ -1054,7 +1064,11 @@ impl BlockChainClient for TestBlockChainClient { .engine .sign(transaction.signature_hash(chain_id)) .unwrap(); - let signed = SignedTransaction::new(transaction.with_signature(sig, chain_id)).unwrap(); + Ok(SignedTransaction::new(transaction.with_signature(sig, chain_id)).unwrap()) + } + + fn transact(&self, tx_request: TransactionRequest) -> Result<(), transaction::Error> { + let signed = self.create_transaction(tx_request)?; self.miner.import_own_transaction(self, signed.into()) } diff --git a/crates/ethcore/src/client/traits.rs b/crates/ethcore/src/client/traits.rs index 62ce8ef38..f314741e4 100644 --- a/crates/ethcore/src/client/traits.rs +++ b/crates/ethcore/src/client/traits.rs @@ -40,7 +40,7 @@ use types::{ pruning_info::PruningInfo, receipt::LocalizedReceipt, trace_filter::Filter as TraceFilter, - transaction::{self, LocalizedTransaction, SignedTransaction}, + transaction::{self, Action, LocalizedTransaction, SignedTransaction}, BlockNumber, }; use vm::LastHashes; @@ -427,8 +427,15 @@ pub trait BlockChainClient: /// Returns information about pruning/data availability. fn pruning_info(&self) -> PruningInfo; - /// Schedule state-altering transaction to be executed on the next pending block. - fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error>; + /// Returns a transaction signed with the key configured in the engine signer. + fn create_transaction( + &self, + tx_request: TransactionRequest, + ) -> Result; + + /// Schedule state-altering transaction to be executed on the next pending + /// block with the given gas and nonce parameters. + fn transact(&self, tx_request: TransactionRequest) -> Result<(), transaction::Error>; /// Get the address of the registry itself. fn registrar_address(&self) -> Option
; @@ -437,6 +444,55 @@ pub trait BlockChainClient: fn is_processing_fork(&self) -> bool; } +/// The data required for a `Client` to create a transaction. +/// +/// Gas limit, gas price, or nonce can be set explicitly, e.g. to create service +/// transactions with zero gas price, or sequences of transactions with consecutive nonces. +/// Added for AuRa needs. +pub struct TransactionRequest { + /// Transaction action + pub action: Action, + /// Transaction data + pub data: Bytes, + /// Transaction gas usage + pub gas: Option, + /// Transaction gas price + pub gas_price: Option, + /// Transaction nonce + pub nonce: Option, +} + +impl TransactionRequest { + /// Creates a request to call a contract at `address` with the specified call data. + pub fn call(address: Address, data: Bytes) -> TransactionRequest { + TransactionRequest { + action: Action::Call(address), + data, + gas: None, + gas_price: None, + nonce: None, + } + } + + /// Sets a gas limit. If this is not specified, a sensible default is used. + pub fn gas(mut self, gas: U256) -> TransactionRequest { + self.gas = Some(gas); + self + } + + /// Sets a gas price. If this is not specified or `None`, a sensible default is used. + pub fn gas_price>>(mut self, gas_price: T) -> TransactionRequest { + self.gas_price = gas_price.into(); + self + } + + /// Sets a nonce. If this is not specified, the appropriate latest nonce for the author is used. + pub fn nonce(mut self, nonce: U256) -> TransactionRequest { + self.nonce = Some(nonce); + self + } +} + /// Provides `reopen_block` method pub trait ReopenBlock { /// Reopens an OpenBlock and updates uncles. diff --git a/crates/ethcore/src/engines/authority_round/block_gas_limit.rs b/crates/ethcore/src/engines/authority_round/block_gas_limit.rs new file mode 100644 index 000000000..b4f98dc75 --- /dev/null +++ b/crates/ethcore/src/engines/authority_round/block_gas_limit.rs @@ -0,0 +1,39 @@ +// Copyright 2015-2019 Parity Technologies (UK) Ltd. +// This file is part of Parity Ethereum. + +// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see . + +//! A client interface for interacting with the block gas limit contract. + +use client::{BlockChainClient, BlockId}; +use types::header::Header; +use ethabi::FunctionOutputDecoder; +use ethabi_contract::use_contract; +use ethereum_types::{Address, U256}; +use log::{debug, error}; + +use_contract!(contract, "res/contracts/block_gas_limit.json"); + +pub fn block_gas_limit(full_client: &dyn BlockChainClient, header: &Header, address: Address) -> Option { + let (data, decoder) = contract::functions::block_gas_limit::call(); + let value = full_client.call_contract(BlockId::Hash(*header.parent_hash()), address, data).map_err(|err| { + error!(target: "block_gas_limit", "Contract call failed. Not changing the block gas limit. {:?}", err); + }).ok()?; + if value.is_empty() { + debug!(target: "block_gas_limit", "Contract call returned nothing. Not changing the block gas limit."); + None + } else { + decoder.decode(&value).ok() + } +} \ No newline at end of file diff --git a/crates/ethcore/src/engines/authority_round/finality.rs b/crates/ethcore/src/engines/authority_round/finality.rs index b75425254..cd7e9128d 100644 --- a/crates/ethcore/src/engines/authority_round/finality.rs +++ b/crates/ethcore/src/engines/authority_round/finality.rs @@ -22,6 +22,7 @@ use std::collections::{ }; use ethereum_types::{Address, H256}; +use types::BlockNumber; use engines::validator_set::SimpleList; @@ -32,20 +33,23 @@ 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, Vec
)>, + headers: VecDeque<(H256, BlockNumber, Vec
)>, signers: SimpleList, sign_count: HashMap, last_pushed: Option, + /// First block for which a 2/3 quorum (instead of 1/2) is required. + two_thirds_majority_transition: BlockNumber, } impl RollingFinality { /// Create a blank finality checker under the given validator set. - pub fn blank(signers: Vec
) -> Self { + pub fn blank(signers: Vec
, two_thirds_majority_transition: BlockNumber) -> Self { RollingFinality { headers: VecDeque::new(), signers: SimpleList::new(signers), sign_count: HashMap::new(), last_pushed: None, + two_thirds_majority_transition, } } @@ -55,45 +59,35 @@ impl RollingFinality { /// 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)>, + I: IntoIterator)>, { self.clear(); - for (hash, signers) in iterable { + for (hash, number, signers) in iterable { if signers.iter().any(|s| !self.signers.contains(s)) { return Err(UnknownValidator); } if self.last_pushed.is_none() { self.last_pushed = Some(hash) } - + self.add_signers(&signers); + self.headers.push_front((hash, number, signers)); // break when we've got our first finalized block. - { - let current_signed = self.sign_count.len(); - - let new_signers = signers - .iter() - .filter(|s| !self.sign_count.contains_key(s)) - .count(); - let would_be_finalized = (current_signed + new_signers) * 2 > self.signers.len(); - - if would_be_finalized { - trace!(target: "finality", "Encountered already finalized block {}", hash); - break; - } - - for signer in signers.iter() { - *self.sign_count.entry(*signer).or_insert(0) += 1; - } + if self.is_finalized() { + let (hash, _, signers) = self + .headers + .pop_front() + .expect("we just pushed a block; qed"); + self.remove_signers(&signers); + trace!(target: "finality", "Encountered already finalized block {}", hash); + break; } - - self.headers.push_front((hash, signers)); } trace!(target: "finality", "Rolling finality state: {:?}", self.headers); Ok(()) } - /// Clear the finality status, but keeps the validator set. + /// Clears the finality status, but keeps the validator set. pub fn clear(&mut self) { self.headers.clear(); self.sign_count.clear(); @@ -108,7 +102,7 @@ impl RollingFinality { /// Get an iterator over stored hashes in order. #[cfg(test)] pub fn unfinalized_hashes(&self) -> impl Iterator { - self.headers.iter().map(|(h, _)| h) + self.headers.iter().map(|(h, _, _)| h) } /// Get the validator set. @@ -124,41 +118,26 @@ impl RollingFinality { pub fn push_hash( &mut self, head: H256, + number: BlockNumber, signers: Vec
, ) -> Result, UnknownValidator> { if signers.iter().any(|s| !self.signers.contains(s)) { return Err(UnknownValidator); } - for signer in signers.iter() { - *self.sign_count.entry(*signer).or_insert(0) += 1; - } - - self.headers.push_back((head, signers)); + self.add_signers(&signers); + self.headers.push_back((head, number, signers)); let mut newly_finalized = Vec::new(); - while self.sign_count.len() * 2 > self.signers.len() { - let (hash, signers) = self + while self.is_finalized() { + let (hash, _, signers) = self .headers .pop_front() .expect("headers length always greater than sign count length; qed"); + self.remove_signers(&signers); newly_finalized.push(hash); - - for signer in signers { - 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 entries in `sign_count` for their signers; qed"), - } - } } trace!(target: "finality", "Blocks finalized by {:?}: {:?}", head, newly_finalized); @@ -166,19 +145,62 @@ impl RollingFinality { self.last_pushed = Some(head); Ok(newly_finalized) } + + /// Returns the first block for which a 2/3 quorum (instead of 1/2) is required. + pub fn two_thirds_majority_transition(&self) -> BlockNumber { + self.two_thirds_majority_transition + } + + /// Returns whether the first entry in `self.headers` is finalized. + fn is_finalized(&self) -> bool { + match self.headers.front() { + None => false, + Some((_, number, _)) if *number < self.two_thirds_majority_transition => { + self.sign_count.len() * 2 > self.signers.len() + } + Some((_, _, _)) => self.sign_count.len() * 3 > self.signers.len() * 2, + } + } + + /// Adds the signers to the sign count. + fn add_signers(&mut self, signers: &[Address]) { + for signer in signers { + *self.sign_count.entry(*signer).or_insert(0) += 1; + } + } + + /// Removes the signers from the sign count. + fn remove_signers(&mut self, signers: &[Address]) { + for signer in signers { + match self.sign_count.entry(*signer) { + Entry::Occupied(mut entry) => { + // decrement count for this signer and purge on zero. + if *entry.get() <= 1 { + entry.remove(); + } else { + *entry.get_mut() -= 1; + } + } + Entry::Vacant(_) => { + panic!("all hashes in `header` should have entries in `sign_count` for their signers; qed"); + } + } + } + } } #[cfg(test)] mod tests { use super::RollingFinality; use ethereum_types::{Address, H256}; + use types::BlockNumber; #[test] fn rejects_unknown_signers() { let signers = (0..3).map(|_| Address::random()).collect::>(); - let mut finality = RollingFinality::blank(signers.clone()); + let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value()); assert!(finality - .push_hash(H256::random(), vec![signers[0], Address::random()]) + .push_hash(H256::random(), 0, vec![signers[0], Address::random()]) .is_err()); } @@ -186,19 +208,25 @@ mod tests { fn finalize_multiple() { let signers: Vec<_> = (0..6).map(|_| Address::random()).collect(); - let mut finality = RollingFinality::blank(signers.clone()); + let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value()); 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, vec![signers[i]]).unwrap().len() == 0); + assert!( + finality + .push_hash(hash, i as u64, vec![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], vec![signers[4]]).unwrap(), + finality.push_hash(hashes[6], 6, vec![signers[4]]).unwrap(), vec![hashes[0], hashes[1], hashes[2], hashes[3]] ); } @@ -206,12 +234,12 @@ mod tests { #[test] fn finalize_multiple_signers() { let signers: Vec<_> = (0..6).map(|_| Address::random()).collect(); - let mut finality = RollingFinality::blank(signers.clone()); + let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value()); let hash = H256::random(); // after pushing a block signed by four validators, it becomes verified right away. assert_eq!( - finality.push_hash(hash, signers[0..4].to_vec()).unwrap(), + finality.push_hash(hash, 0, signers[0..4].to_vec()).unwrap(), vec![hash] ); } @@ -220,10 +248,9 @@ mod tests { fn from_ancestry() { let signers: Vec<_> = (0..6).map(|_| Address::random()).collect(); let hashes: Vec<_> = (0..12) - .map(|i| (H256::random(), vec![signers[i % 6]])) + .map(|i| (H256::random(), i as u64, vec![signers[i % 6]])) .collect(); - - let mut finality = RollingFinality::blank(signers.clone()); + let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value()); finality .build_ancestry_subchain(hashes.iter().rev().cloned()) .unwrap(); @@ -239,12 +266,13 @@ mod tests { .map(|i| { ( H256::random(), + i as u64, vec![signers[i % 6], signers[(i + 1) % 6], signers[(i + 2) % 6]], ) }) .collect(); - let mut finality = RollingFinality::blank(signers.clone()); + let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value()); finality .build_ancestry_subchain(hashes.iter().rev().cloned()) .unwrap(); @@ -254,4 +282,91 @@ mod tests { assert_eq!(finality.unfinalized_hashes().next(), Some(&hashes[11].0)); assert_eq!(finality.subchain_head(), Some(hashes[11].0)); } + + #[test] + fn rejects_unknown_signers_2_3() { + let signers = (0..3).map(|_| Address::random()).collect::>(); + let mut finality = RollingFinality::blank(signers.clone(), 0); + assert!(finality + .push_hash(H256::random(), 0, vec![signers[0], Address::random()]) + .is_err()); + } + + #[test] + fn finalize_multiple_2_3() { + let signers: Vec<_> = (0..7).map(|_| Address::random()).collect(); + + let mut finality = RollingFinality::blank(signers.clone(), 0); + let hashes: Vec<_> = (0..9).map(|_| H256::random()).collect(); + + // 4 / 7 signers is < 67% so no finality. + for (i, hash) in hashes.iter().take(8).cloned().enumerate() { + let i = i % 4; + assert!( + finality + .push_hash(hash, i as u64, vec![signers[i]]) + .unwrap() + .len() + == 0 + ); + } + + // after pushing a block signed by a fifth validator, the first five + // blocks of the unverified chain become verified. + assert_eq!( + finality.push_hash(hashes[8], 8, vec![signers[4]]).unwrap(), + vec![hashes[0], hashes[1], hashes[2], hashes[3], hashes[4]] + ); + } + + #[test] + fn finalize_multiple_signers_2_3() { + let signers: Vec<_> = (0..5).map(|_| Address::random()).collect(); + let mut finality = RollingFinality::blank(signers.clone(), 0); + let hash = H256::random(); + + // after pushing a block signed by four validators, it becomes verified right away. + assert_eq!( + finality.push_hash(hash, 0, signers[0..4].to_vec()).unwrap(), + vec![hash] + ); + } + + #[test] + fn from_ancestry_2_3() { + let signers: Vec<_> = (0..6).map(|_| Address::random()).collect(); + let hashes: Vec<_> = (0..12) + .map(|i| (H256::random(), i as u64, vec![signers[i % 6]])) + .collect(); + + let mut finality = RollingFinality::blank(signers, 0); + finality + .build_ancestry_subchain(hashes.iter().rev().cloned()) + .unwrap(); + + // The last four hashes, with index 11, 10, 9, and 8, have been pushed. 7 would have finalized a block. + assert_eq!(finality.unfinalized_hashes().count(), 4); + assert_eq!(finality.subchain_head(), Some(hashes[11].0)); + } + + #[test] + fn from_ancestry_multiple_signers_2_3() { + let signers: Vec<_> = (0..6).map(|_| Address::random()).collect(); + let hashes: Vec<_> = (0..12) + .map(|i| { + let hash_signers = signers.iter().cycle().skip(i).take(4).cloned().collect(); + (H256::random(), i as u64, hash_signers) + }) + .collect(); + + let mut finality = RollingFinality::blank(signers.clone(), 0); + finality + .build_ancestry_subchain(hashes.iter().rev().cloned()) + .unwrap(); + + // only the last hash has < 67% of authorities' signatures + assert_eq!(finality.unfinalized_hashes().count(), 1); + assert_eq!(finality.unfinalized_hashes().next(), Some(&hashes[11].0)); + assert_eq!(finality.subchain_head(), Some(hashes[11].0)); + } } diff --git a/crates/ethcore/src/engines/authority_round/mod.rs b/crates/ethcore/src/engines/authority_round/mod.rs index 9afdedfc0..4591e5e25 100644 --- a/crates/ethcore/src/engines/authority_round/mod.rs +++ b/crates/ethcore/src/engines/authority_round/mod.rs @@ -15,32 +15,52 @@ // along with OpenEthereum. If not, see . //! A blockchain engine that supports a non-instant BFT proof-of-authority. +//! +//! It is recommended to use the `two_thirds_majority_transition` option, to defend against the +//! ["Attack of the Clones"](https://arxiv.org/pdf/1902.10244.pdf). Newly started networks can +//! set this option to `0`, to use a 2/3 quorum from the beginning. +//! +//! To support on-chain governance, the [ValidatorSet] is pluggable: Aura supports simple +//! constant lists of validators as well as smart contract-based dynamic validator sets. +//! Misbehavior is reported to the [ValidatorSet] as well, so that e.g. governance contracts +//! can penalize or ban attacker's nodes. +//! +//! * "Benign" misbehavior are faults that can happen in normal operation, like failing +//! to propose a block in your slot, which could be due to a temporary network outage, or +//! wrong timestamps (due to out-of-sync clocks). +//! * "Malicious" reports are made only if the sender misbehaved deliberately (or due to a +//! software bug), e.g. if they proposed multiple blocks with the same step number. use std::{ cmp, collections::{BTreeMap, BTreeSet, HashSet}, fmt, - iter::FromIterator, + iter::{self, FromIterator}, ops::Deref, sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering}, + atomic::{AtomicBool, AtomicU64, Ordering as AtomicOrdering}, Arc, Weak, }, time::{Duration, UNIX_EPOCH}, + u64, }; use self::finality::RollingFinality; use super::{ signer::EngineSigner, - validator_set::{new_validator_set, SimpleList, ValidatorSet}, + validator_set::{new_validator_set_posdao, SimpleList, ValidatorSet}, }; use block::*; -use client::{traits::ForceUpdateSealing, EngineClient}; +use bytes::Bytes; +use client::{ + traits::{ForceUpdateSealing, TransactionRequest}, + EngineClient, +}; use crypto::publickey::{self, Signature}; use engines::{ block_reward, block_reward::{BlockRewardContract, RewardKind}, - ConstructedVerifier, Engine, EngineError, Seal, + ConstructedVerifier, Engine, EngineError, Seal, SealingState, }; use error::{BlockError, Error, ErrorKind}; use ethereum_types::{Address, H256, H520, U128, U256}; @@ -49,27 +69,35 @@ use ethjson::{self, uint::Uint}; use hash::keccak; use io::{IoContext, IoHandler, IoService, TimerToken}; use itertools::{self, Itertools}; +use lru_cache::LruCache; use machine::{AuxiliaryData, Call, EthereumMachine}; use parking_lot::{Mutex, RwLock}; +use rand::rngs::OsRng; use rlp::{encode, Decodable, DecoderError, Encodable, Rlp, RlpStream}; use time_utils::CheckedSystemTime; use types::{ ancestry_action::AncestryAction, header::{ExtendedHeader, Header}, + ids::BlockId, + transaction::SignedTransaction, BlockNumber, }; use unexpected::{Mismatch, OutOfBounds}; +//mod block_gas_limit as crate_block_gas_limit; mod finality; +mod randomness; +pub(crate) mod util; /// `AuthorityRound` params. pub struct AuthorityRoundParams { - /// Time to wait before next block or authority switching, - /// in seconds. + /// A map defining intervals of blocks with the given times (in seconds) to wait before next + /// block or authority switching. The keys in the map are steps of starting blocks of those + /// periods. The entry at `0` should be defined. /// - /// Deliberately typed as u16 as too high of a value leads - /// to slow block issuance. - pub step_duration: u16, + /// Wait times (durations) are additionally required to be less than 65535 since larger values + /// lead to slow block issuance. + pub step_durations: BTreeMap, /// Starting step, pub start_step: Option, /// Valid validators. @@ -82,34 +110,110 @@ pub struct AuthorityRoundParams { pub immediate_transitions: bool, /// Block reward in base units. pub block_reward: BTreeMap, - /// Block reward contract transition block. - pub block_reward_contract_transition: u64, - /// Block reward contract. - pub block_reward_contract: Option, + /// Block reward contract addresses with their associated starting block numbers. + pub block_reward_contract_transitions: BTreeMap, /// Number of accepted uncles transition block. pub maximum_uncle_count_transition: u64, /// Number of accepted uncles. pub maximum_uncle_count: usize, /// Empty step messages transition block. pub empty_steps_transition: u64, + /// First block for which a 2/3 quorum (instead of 1/2) is required. + pub two_thirds_majority_transition: BlockNumber, /// Number of accepted empty steps. pub maximum_empty_steps: usize, /// Transition block to strict empty steps validation. pub strict_empty_steps_transition: u64, + /// If set, enables random number contract integration. It maps the transition block to the contract address. + pub randomness_contract_address: BTreeMap, + /// The addresses of contracts that determine the block gas limit with their associated block + /// numbers. + pub block_gas_limit_contract_transitions: BTreeMap, + /// If set, this is the block number at which the consensus engine switches from AuRa to AuRa + /// with POSDAO modifications. + pub posdao_transition: Option, } const U16_MAX: usize = ::std::u16::MAX as usize; +/// The number of recent block hashes for which the gas limit override is memoized. +const GAS_LIMIT_OVERRIDE_CACHE_CAPACITY: usize = 10; + impl From for AuthorityRoundParams { fn from(p: ethjson::spec::AuthorityRoundParams) -> Self { - let mut step_duration_usize: usize = p.step_duration.into(); - if step_duration_usize > U16_MAX { - step_duration_usize = U16_MAX; - warn!(target: "engine", "step_duration is too high ({}), setting it to {}", step_duration_usize, U16_MAX); + let map_step_duration = |u: ethjson::uint::Uint| { + let mut step_duration_usize: usize = u.into(); + if step_duration_usize == 0 { + panic!("AuthorityRoundParams: step duration cannot be 0"); + } + if step_duration_usize > U16_MAX { + warn!(target: "engine", "step duration is too high ({}), setting it to {}", step_duration_usize, U16_MAX); + step_duration_usize = U16_MAX; + } + step_duration_usize as u64 + }; + let step_durations: BTreeMap<_, _> = match p.step_duration { + ethjson::spec::StepDuration::Single(u) => { + iter::once((0, map_step_duration(u))).collect() + } + ethjson::spec::StepDuration::Transitions(tr) => { + if tr.is_empty() { + panic!("AuthorityRoundParams: step duration transitions cannot be empty"); + } + tr.into_iter() + .map(|(timestamp, u)| (timestamp.into(), map_step_duration(u))) + .collect() + } + }; + let transition_block_num = p.block_reward_contract_transition.map_or(0, Into::into); + let mut br_transitions: BTreeMap<_, _> = p + .block_reward_contract_transitions + .unwrap_or_default() + .into_iter() + .map(|(block_num, address)| { + ( + block_num.into(), + BlockRewardContract::new_from_address(address.into()), + ) + }) + .collect(); + if (p.block_reward_contract_code.is_some() || p.block_reward_contract_address.is_some()) + && br_transitions + .keys() + .next() + .map_or(false, |&block_num| block_num <= transition_block_num) + { + let s = "blockRewardContractTransition"; + panic!("{} should be less than any of the keys in {}s", s, s); } + if let Some(code) = p.block_reward_contract_code { + br_transitions.insert( + transition_block_num, + BlockRewardContract::new_from_code(Arc::new(code.into())), + ); + } else if let Some(address) = p.block_reward_contract_address { + br_transitions.insert( + transition_block_num, + BlockRewardContract::new_from_address(address.into()), + ); + } + let randomness_contract_address = + p.randomness_contract_address + .map_or_else(BTreeMap::new, |transitions| { + transitions + .into_iter() + .map(|(ethjson::uint::Uint(block), addr)| (block.as_u64(), addr.into())) + .collect() + }); + let block_gas_limit_contract_transitions: BTreeMap<_, _> = p + .block_gas_limit_contract_transitions + .unwrap_or_default() + .into_iter() + .map(|(block_num, address)| (block_num.into(), address.into())) + .collect(); AuthorityRoundParams { - step_duration: step_duration_usize as u16, - validators: new_validator_set(p.validators), + step_durations, + validators: new_validator_set_posdao(p.validators, p.posdao_transition.map(Into::into)), start_step: p.start_step.map(Into::into), validate_score_transition: p.validate_score_transition.map_or(0, Into::into), validate_step_transition: p.validate_step_transition.map_or(0, Into::into), @@ -141,77 +245,122 @@ impl From for AuthorityRoundParams { } }, ), - block_reward_contract_transition: p - .block_reward_contract_transition - .map_or(0, Into::into), - block_reward_contract: match ( - p.block_reward_contract_code, - p.block_reward_contract_address, - ) { - (Some(code), _) => Some(BlockRewardContract::new_from_code(Arc::new(code.into()))), - (_, Some(address)) => Some(BlockRewardContract::new_from_address(address.into())), - (None, None) => None, - }, + block_reward_contract_transitions: br_transitions, maximum_uncle_count_transition: p.maximum_uncle_count_transition.map_or(0, Into::into), maximum_uncle_count: p.maximum_uncle_count.map_or(0, Into::into), empty_steps_transition: p .empty_steps_transition .map_or(u64::max_value(), |n| ::std::cmp::max(n.into(), 1)), maximum_empty_steps: p.maximum_empty_steps.map_or(0, Into::into), + two_thirds_majority_transition: p + .two_thirds_majority_transition + .map_or_else(BlockNumber::max_value, Into::into), strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into), + randomness_contract_address, + block_gas_limit_contract_transitions, + posdao_transition: p.posdao_transition.map(Into::into), } } } -// Helper for managing the step. +/// A triple containing the first step number and the starting timestamp of the given step duration. +#[derive(Clone, Copy, Debug)] +struct StepDurationInfo { + transition_step: u64, + transition_timestamp: u64, + step_duration: u64, +} + +/// Helper for managing the step. #[derive(Debug)] struct Step { calibrate: bool, // whether calibration is enabled. - inner: AtomicUsize, - duration: u16, + inner: AtomicU64, + /// Planned durations of steps. + durations: Vec, } impl Step { fn load(&self) -> u64 { - self.inner.load(AtomicOrdering::SeqCst) as u64 + self.inner.load(AtomicOrdering::SeqCst) } + fn duration_remaining(&self) -> Duration { - let now = unix_now(); - let expected_seconds = self - .load() - .checked_add(1) - .and_then(|ctr| ctr.checked_mul(self.duration as u64)) - .map(Duration::from_secs); - - match expected_seconds { - Some(step_end) if step_end > now => step_end - now, - Some(_) => Duration::from_secs(0), - None => { - let ctr = self.load(); - error!(target: "engine", "Step counter is too high: {}, aborting", ctr); - panic!("step counter is too high: {}", ctr) - } - } + self.opt_duration_remaining().unwrap_or_else(|| { + let ctr = self.load(); + error!(target: "engine", "Step counter under- or overflow: {}, aborting", ctr); + panic!("step counter under- or overflow: {}", ctr) + }) } + /// Finds the remaining duration of the current step. Returns `None` if there was a counter + /// under- or overflow. + fn opt_duration_remaining(&self) -> Option { + let next_step = self.load().checked_add(1)?; + let StepDurationInfo { + transition_step, + transition_timestamp, + step_duration, + } = self + .durations + .iter() + .take_while(|info| info.transition_step < next_step) + .last() + .expect("durations cannot be empty") + .clone(); + let next_time = transition_timestamp.checked_add( + next_step + .checked_sub(transition_step)? + .checked_mul(step_duration)?, + )?; + Some(Duration::from_secs( + next_time.saturating_sub(unix_now().as_secs()), + )) + } + + /// Increments the step number. + /// + /// Panics if the new step number is `u64::MAX`. fn increment(&self) { - use std::usize; // fetch_add won't panic on overflow but will rather wrap // around, leading to zero as the step counter, which might // lead to unexpected situations, so it's better to shut down. - if self.inner.fetch_add(1, AtomicOrdering::SeqCst) == usize::MAX { - error!(target: "engine", "Step counter is too high: {}, aborting", usize::MAX); - panic!("step counter is too high: {}", usize::MAX); + if self.inner.fetch_add(1, AtomicOrdering::SeqCst) == u64::MAX { + error!(target: "engine", "Step counter is too high: {}, aborting", u64::MAX); + panic!("step counter is too high: {}", u64::MAX); } } fn calibrate(&self) { if self.calibrate { - let new_step = unix_now().as_secs() / (self.duration as u64); - self.inner.store(new_step as usize, AtomicOrdering::SeqCst); + if self.opt_calibrate().is_none() { + let ctr = self.load(); + error!(target: "engine", "Step counter under- or overflow: {}, aborting", ctr); + panic!("step counter under- or overflow: {}", ctr) + } } } + /// Calibrates the AuRa step number according to the current time. + fn opt_calibrate(&self) -> Option<()> { + let now = unix_now().as_secs(); + let StepDurationInfo { + transition_step, + transition_timestamp, + step_duration, + } = self + .durations + .iter() + .take_while(|info| info.transition_timestamp < now) + .last() + .expect("durations cannot be empty") + .clone(); + let new_step = (now.checked_sub(transition_timestamp)? / step_duration) + .checked_add(transition_step)?; + self.inner.store(new_step, AtomicOrdering::SeqCst); + Some(()) + } + fn check_future(&self, given: u64) -> Result<(), Option>> { const REJECTED_STEP_DRIFT: u64 = 4; @@ -229,7 +378,13 @@ impl Step { Err(None) // wait a bit for blocks in near future } else if given > current { - let d = self.duration as u64; + let d = self + .durations + .iter() + .take_while(|info| info.transition_step <= current) + .last() + .expect("Duration map has at least a 0 entry.") + .step_duration; Err(Some(OutOfBounds { min: None, max: Some(d * current), @@ -255,24 +410,24 @@ struct EpochManager { } impl EpochManager { - fn blank() -> Self { + fn blank(two_thirds_majority_transition: BlockNumber) -> Self { EpochManager { epoch_transition_hash: H256::default(), epoch_transition_number: 0, - finality_checker: RollingFinality::blank(Vec::new()), + finality_checker: RollingFinality::blank(Vec::new(), two_thirds_majority_transition), force: true, } } - // zoom to epoch for given header. returns true if succeeded, false otherwise. - fn zoom_to( + /// Zooms to the epoch after the header with the given hash. Returns true if succeeded, false otherwise. + fn zoom_to_after( &mut self, client: &dyn EngineClient, machine: &EthereumMachine, validators: &dyn ValidatorSet, - header: &Header, + hash: H256, ) -> bool { - let last_was_parent = self.finality_checker.subchain_head() == Some(*header.parent_hash()); + let last_was_parent = self.finality_checker.subchain_head() == Some(hash); // early exit for current target == chain head, but only if the epochs are // the same. @@ -281,13 +436,13 @@ impl EpochManager { } self.force = false; - debug!(target: "engine", "Zooming to epoch for block {}", header.hash()); + debug!(target: "engine", "Zooming to epoch after block {}", 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()) { + let last_transition = match client.epoch_transition_for(hash) { Some(t) => t, None => { // this really should never happen unless the block passed @@ -302,22 +457,31 @@ impl EpochManager { 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); + trace!( + target: "engine", + "extracting epoch validator set for epoch ({}, {}) signalled at #{}", + last_transition.block_number, last_transition.block_hash, signal_number + ); let first = signal_number == 0; - let epoch_set = validators + let (list, _) = validators .epoch_set( first, machine, 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); + trace!( + target: "engine", + "Updating finality checker with new validator set extracted from epoch ({}, {}): {:?}", + last_transition.block_number, last_transition.block_hash, &list + ); + let epoch_set = list.into_inner(); + let two_thirds_majority_transition = + self.finality_checker.two_thirds_majority_transition(); + self.finality_checker = + RollingFinality::blank(epoch_set, two_thirds_majority_transition); } self.epoch_transition_hash = last_transition.block_hash; @@ -342,10 +506,22 @@ impl EpochManager { /// A message broadcast by authorities when it's their turn to seal a block but there are no /// transactions. Other authorities accumulate these messages and later include them in the seal as /// proof. +/// +/// An empty step message is created _instead of_ a block if there are no pending transactions. +/// It cannot itself be a parent, and `parent_hash` always points to the most recent block. E.g.: +/// * Validator A creates block `bA`. +/// * Validator B has no pending transactions, so it signs an empty step message `mB` +/// instead whose hash points to block `bA`. +/// * Validator C also has no pending transactions, so it also signs an empty step message `mC` +/// instead whose hash points to block `bA`. +/// * Validator D creates block `bD`. The parent is block `bA`, and the header includes `mB` and `mC`. #[derive(Clone, Debug, PartialEq, Eq)] struct EmptyStep { + /// The signature of the other two fields, by the message's author. signature: H520, + /// This message's step number. step: u64, + /// The hash of the most recent block. parent_hash: H256, } @@ -354,6 +530,7 @@ impl PartialOrd for EmptyStep { Some(self.cmp(other)) } } + impl Ord for EmptyStep { fn cmp(&self, other: &Self) -> cmp::Ordering { self.step @@ -375,6 +552,7 @@ impl EmptyStep { } } + /// Returns `true` if the message has a valid signature by the expected proposer in the message's step. fn verify(&self, validators: &dyn ValidatorSet) -> Result { let message = keccak(empty_step_rlp(self.step, &self.parent_hash)); let correct_proposer = step_proposer(validators, &self.parent_hash, self.step); @@ -485,14 +663,26 @@ pub struct AuthorityRound { epoch_manager: Mutex, immediate_transitions: bool, block_reward: BTreeMap, - block_reward_contract_transition: u64, - block_reward_contract: Option, + block_reward_contract_transitions: BTreeMap, maximum_uncle_count_transition: u64, maximum_uncle_count: usize, empty_steps_transition: u64, strict_empty_steps_transition: u64, + two_thirds_majority_transition: BlockNumber, maximum_empty_steps: usize, machine: EthereumMachine, + /// History of step hashes recently received from peers. + received_step_hashes: RwLock>, + /// If set, enables random number contract integration. It maps the transition block to the contract address. + randomness_contract_address: BTreeMap, + /// The addresses of contracts that determine the block gas limit. + block_gas_limit_contract_transitions: BTreeMap, + /// Memoized gas limit overrides, by block hash. + gas_limit_override_cache: Mutex>>, + /// The block number at which the consensus engine switches from AuRa to AuRa with POSDAO + /// modifications. For details about POSDAO, see the whitepaper: + /// https://www.xdaichain.com/for-validators/posdao-whitepaper + posdao_transition: Option, } // header-chain validator. @@ -500,6 +690,8 @@ struct EpochVerifier { step: Arc, subchain_validators: SimpleList, empty_steps_transition: u64, + /// First block for which a 2/3 quorum (instead of 1/2) is required. + two_thirds_majority_transition: BlockNumber, } impl super::EpochVerifier for EpochVerifier { @@ -519,8 +711,9 @@ impl super::EpochVerifier for EpochVerifier { } fn check_finality_proof(&self, proof: &[u8]) -> Option> { + let signers = self.subchain_validators.clone().into_inner(); let mut finality_checker = - RollingFinality::blank(self.subchain_validators.clone().into_inner()); + RollingFinality::blank(signers, self.two_thirds_majority_transition); let mut finalized = Vec::new(); let headers: Vec
= Rlp::new(proof).as_list().ok()?; @@ -557,7 +750,7 @@ impl super::EpochVerifier for EpochVerifier { signers.push(*parent_header.author()); let newly_finalized = finality_checker - .push_hash(parent_header.hash(), signers) + .push_hash(parent_header.hash(), parent_header.number(), signers) .ok()?; finalized.extend(newly_finalized); @@ -602,8 +795,8 @@ fn header_expected_seal_fields(header: &Header, empty_steps_transition: u64) -> fn header_step(header: &Header, empty_steps_transition: u64) -> Result { Rlp::new(&header.seal().get(0).unwrap_or_else(|| - panic!("was either checked with verify_block_basic or is genesis; has {} fields; qed (Make sure the spec - file has a correct genesis seal)", header_expected_seal_fields(header, empty_steps_transition)) + panic!("was either checked with verify_block_basic or is genesis; has {} fields; qed (Make sure the spec \ + file has a correct genesis seal)", header_expected_seal_fields(header, empty_steps_transition)) )) .as_val() } @@ -733,7 +926,7 @@ fn verify_external( } fn combine_proofs(signal_number: BlockNumber, set_proof: &[u8], finality_proof: &[u8]) -> Vec { - let mut stream = ::rlp::RlpStream::new_list(3); + let mut stream = RlpStream::new_list(3); stream .append(&signal_number) .append(&set_proof) @@ -782,22 +975,52 @@ impl AuthorityRound { our_params: AuthorityRoundParams, machine: EthereumMachine, ) -> Result, Error> { - if our_params.step_duration == 0 { - error!(target: "engine", "Authority Round step duration can't be zero, aborting"); - panic!("authority_round: step duration can't be zero") + if !our_params.step_durations.contains_key(&0) { + error!(target: "engine", "Authority Round step 0 duration is undefined, aborting"); + return Err(Error::from_kind(ErrorKind::Engine(EngineError::Custom( + String::from("step 0 duration is undefined"), + )))); } + if our_params.step_durations.values().any(|v| *v == 0) { + error!(target: "engine", "Authority Round step duration cannot be 0"); + return Err(Error::from_kind(ErrorKind::Engine(EngineError::Custom( + String::from("step duration cannot be 0"), + )))); + } + let should_timeout = our_params.start_step.is_none(); - let initial_step = our_params - .start_step - .unwrap_or_else(|| (unix_now().as_secs() / (our_params.step_duration as u64))); + + let initial_step = our_params.start_step.unwrap_or(0); + + let mut durations = Vec::new(); + { + let mut dur_info = StepDurationInfo { + transition_step: 0u64, + transition_timestamp: 0u64, + step_duration: our_params.step_durations[&0], + }; + durations.push(dur_info); + for (time, dur) in our_params.step_durations.iter().skip(1) { + let (step, time) = next_step_time_duration(dur_info, *time) + .ok_or(BlockError::TimestampOverflow)?; + dur_info.transition_step = step; + dur_info.transition_timestamp = time; + dur_info.step_duration = *dur; + durations.push(dur_info); + } + } + + let step = Step { + inner: AtomicU64::new(initial_step), + calibrate: our_params.start_step.is_none(), + durations, + }; + step.calibrate(); + let engine = Arc::new(AuthorityRound { transition_service: IoService::<()>::start("AuRa")?, step: Arc::new(PermissionedStep { - inner: Step { - inner: AtomicUsize::new(initial_step as usize), - calibrate: our_params.start_step.is_none(), - duration: our_params.step_duration, - }, + inner: step, can_propose: AtomicBool::new(true), }), client: Arc::new(RwLock::new(None)), @@ -806,17 +1029,24 @@ impl AuthorityRound { validate_score_transition: our_params.validate_score_transition, validate_step_transition: our_params.validate_step_transition, empty_steps: Default::default(), - epoch_manager: Mutex::new(EpochManager::blank()), + epoch_manager: Mutex::new(EpochManager::blank( + our_params.two_thirds_majority_transition, + )), immediate_transitions: our_params.immediate_transitions, block_reward: our_params.block_reward, - block_reward_contract_transition: our_params.block_reward_contract_transition, - block_reward_contract: our_params.block_reward_contract, + block_reward_contract_transitions: our_params.block_reward_contract_transitions, maximum_uncle_count_transition: our_params.maximum_uncle_count_transition, maximum_uncle_count: our_params.maximum_uncle_count, empty_steps_transition: our_params.empty_steps_transition, maximum_empty_steps: our_params.maximum_empty_steps, + two_thirds_majority_transition: our_params.two_thirds_majority_transition, strict_empty_steps_transition: our_params.strict_empty_steps_transition, machine: machine, + received_step_hashes: RwLock::new(Default::default()), + randomness_contract_address: our_params.randomness_contract_address, + block_gas_limit_contract_transitions: our_params.block_gas_limit_contract_transitions, + gas_limit_override_cache: Mutex::new(LruCache::new(GAS_LIMIT_OVERRIDE_CACHE_CAPACITY)), + posdao_transition: our_params.posdao_transition, }); // Do not initialize timeouts for tests. @@ -842,15 +1072,14 @@ impl AuthorityRound { (CowLike::Borrowed(&*self.validators), header.number()) } 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 => { - debug!(target: "engine", "Unable to verify sig: missing client ref."); - return Err(EngineError::RequiresClient.into()); - } - }; + let client = self.upgrade_client_or("Unable to verify sig")?; - if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, header) { + if !epoch_manager.zoom_to_after( + &*client, + &self.machine, + &*self.validators, + *header.parent_hash(), + ) { debug!(target: "engine", "Unable to zoom to epoch."); return Err(EngineError::RequiresClient.into()); } @@ -923,10 +1152,8 @@ impl AuthorityRound { } fn broadcast_message(&self, message: Vec) { - if let Some(ref weak) = *self.client.read() { - if let Some(c) = weak.upgrade() { - c.broadcast_consensus_message(message); - } + if let Ok(c) = self.upgrade_client_or(None) { + c.broadcast_consensus_message(message); } } @@ -958,6 +1185,12 @@ impl AuthorityRound { if !reported.insert(skipped_primary) { break; } + trace!( + target: "engine", + "Reporting benign misbehaviour (cause: skipped step) at block #{}, \ + epoch set number {}, step proposer={:#x}. Own address: {}", + header.number(), set_number, skipped_primary, me + ); self.validators .report_benign(&skipped_primary, set_number, header.number()); } @@ -975,16 +1208,18 @@ impl AuthorityRound { return Vec::new(); } - let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { - Some(client) => client, - None => { - warn!(target: "engine", "Unable to apply ancestry actions: missing client ref."); - return Vec::new(); - } + let client = match self.upgrade_client_or("Unable to apply ancestry actions") { + Ok(client) => client, + Err(_) => return Vec::new(), }; let mut epoch_manager = self.epoch_manager.lock(); - if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, chain_head) { + if !epoch_manager.zoom_to_after( + &*client, + &self.machine, + &*self.validators, + *chain_head.parent_hash(), + ) { return Vec::new(); } @@ -1012,7 +1247,7 @@ impl AuthorityRound { signers.extend(parent_empty_steps_signers.drain(..)); if let Ok(empty_step_signers) = header_empty_steps_signers(&header, self.empty_steps_transition) { - let res = (header.hash(), signers); + let res = (header.hash(), header.number(), signers); trace!(target: "finality", "Ancestry iteration: yielding {:?}", res); parent_empty_steps_signers = empty_step_signers; @@ -1025,7 +1260,7 @@ impl AuthorityRound { } }) .while_some() - .take_while(|&(h, _)| h != epoch_transition_hash); + .take_while(|&(h, _, _)| h != epoch_transition_hash); if let Err(e) = epoch_manager .finality_checker @@ -1036,11 +1271,140 @@ impl AuthorityRound { } } - let finalized = epoch_manager - .finality_checker - .push_hash(chain_head.hash(), vec![*chain_head.author()]); + let finalized = epoch_manager.finality_checker.push_hash( + chain_head.hash(), + chain_head.number(), + vec![*chain_head.author()], + ); finalized.unwrap_or_default() } + + fn address(&self) -> Option
{ + self.signer.read().as_ref().map(|s| s.address()) + } + + /// Make calls to the randomness contract. + fn run_randomness_phase(&self, block: &ExecutedBlock) -> Result, Error> { + let contract_addr = match self + .randomness_contract_address + .range(..=block.header.number()) + .last() + { + Some((_, &contract_addr)) => contract_addr, + None => return Ok(Vec::new()), // No randomness contract. + }; + + let opt_signer = self.signer.read(); + let signer = match opt_signer.as_ref() { + Some(signer) => signer, + None => return Ok(Vec::new()), // We are not a validator, so we shouldn't call the contracts. + }; + let our_addr = signer.address(); + let client = self.upgrade_client_or("Unable to prepare block")?; + let full_client = client.as_full_client().ok_or_else(|| { + EngineError::FailedSystemCall("Failed to upgrade to BlockchainClient.".to_string()) + })?; + + // Random number generation + let contract = util::BoundContract::new(&*client, BlockId::Latest, contract_addr); + let phase = randomness::RandomnessPhase::load(&contract, our_addr) + .map_err(|err| EngineError::Custom(format!("Randomness error in load(): {:?}", err)))?; + let data = match phase + .advance(&contract, &mut OsRng, signer.as_ref()) + .map_err(|err| { + EngineError::Custom(format!("Randomness error in advance(): {:?}", err)) + })? { + Some(data) => data, + None => return Ok(Vec::new()), // Nothing to commit or reveal at the moment. + }; + + let nonce = block.state.nonce(&our_addr)?; + let tx_request = TransactionRequest::call(contract_addr, data) + .gas_price(U256::zero()) + .nonce(nonce); + Ok(vec![full_client.create_transaction(tx_request)?]) + } + + /// Returns the reference to the client, if registered. + fn upgrade_client_or<'a, T>( + &self, + opt_error_msg: T, + ) -> Result, EngineError> + where + T: Into>, + { + self.client + .read() + .as_ref() + .and_then(|weak| weak.upgrade()) + .ok_or_else(|| { + if let Some(error_msg) = opt_error_msg.into() { + debug!(target: "engine", "{}: missing client ref.", error_msg); + } + EngineError::RequiresClient + }) + } + + fn run_posdao( + &self, + block: &ExecutedBlock, + nonce: Option, + ) -> Result, Error> { + // Skip the rest of the function unless there has been a transition to POSDAO AuRa. + if self + .posdao_transition + .map_or(true, |posdao_block| block.header.number() < posdao_block) + { + trace!(target: "engine", "Skipping POSDAO calls to validator set contracts"); + return Ok(Vec::new()); + } + + let opt_signer = self.signer.read(); + let signer = match opt_signer.as_ref() { + Some(signer) => signer, + None => return Ok(Vec::new()), // We are not a validator, so we shouldn't call the contracts. + }; + let our_addr = signer.address(); + let client = self.upgrade_client_or("Unable to prepare block")?; + let full_client = client.as_full_client().ok_or_else(|| { + EngineError::FailedSystemCall("Failed to upgrade to BlockchainClient.".to_string()) + })?; + + // Makes a constant contract call. + let mut call = |to: Address, data: Bytes| { + full_client + .call_contract(BlockId::Latest, to, data) + .map_err(|e| format!("{}", e)) + }; + + // Our current account nonce. The transactions must have consecutive nonces, starting with this one. + let mut tx_nonce = if let Some(tx_nonce) = nonce { + tx_nonce + } else { + block.state.nonce(&our_addr)? + }; + let mut transactions = Vec::new(); + + // Creates and signs a transaction with the given contract call. + let mut make_transaction = |to: Address, data: Bytes| -> Result { + let tx_request = TransactionRequest::call(to, data) + .gas_price(U256::zero()) + .nonce(tx_nonce); + tx_nonce += U256::one(); // Increment the nonce for the next transaction. + Ok(full_client.create_transaction(tx_request)?) + }; + + // Genesis is never a new block, but might as well check. + let first = block.header.number() == 0; + for (addr, data) in + self.validators + .generate_engine_transactions(first, &block.header, &mut call)? + { + transactions.push(make_transaction(addr, data)?); + } + + Ok(transactions) + } } fn unix_now() -> Duration { @@ -1080,8 +1444,10 @@ impl IoHandler<()> for TransitionHandler { } } - let next_run_at = AsMillis::as_millis(&self.step.inner.duration_remaining()) >> 2; - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, Duration::from_millis(next_run_at)) + let next_run_at = Duration::from_millis( + AsMillis::as_millis(&self.step.inner.duration_remaining()) >> 2, + ); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, next_run_at) .unwrap_or_else( |e| warn!(target: "engine", "Failed to restart consensus step timer: {}.", e), ) @@ -1107,10 +1473,8 @@ impl Engine for AuthorityRound { fn step(&self) { self.step.inner.increment(); self.step.can_propose.store(true, AtomicOrdering::SeqCst); - if let Some(ref weak) = *self.client.read() { - if let Some(c) = weak.upgrade() { - c.update_sealing(ForceUpdateSealing::No); - } + if let Ok(c) = self.upgrade_client_or(None) { + c.update_sealing(ForceUpdateSealing::No); } } @@ -1179,15 +1543,67 @@ impl Engine for AuthorityRound { let score = calculate_score(parent_step, current_step, current_empty_steps_len); header.set_difficulty(score); + if let Some(gas_limit) = self.gas_limit_override(header) { + trace!(target: "engine", "Setting gas limit to {} for block {}.", gas_limit, header.number()); + let parent_gas_limit = *parent.gas_limit(); + header.set_gas_limit(gas_limit); + if parent_gas_limit != gas_limit { + info!(target: "engine", "Block gas limit was changed from {} to {}.", parent_gas_limit, gas_limit); + } + } } - fn seals_internally(&self) -> Option { - // TODO: accept a `&Call` here so we can query the validator set. - Some(self.signer.read().is_some()) + fn sealing_state(&self) -> SealingState { + let our_addr = match *self.signer.read() { + Some(ref signer) => signer.address(), + None => { + warn!(target: "engine", "Not preparing block; cannot sign."); + return SealingState::NotReady; + } + }; + + let client = match self.upgrade_client_or("Not preparing block") { + Ok(client) => client, + Err(_) => return SealingState::NotReady, + }; + + let parent = match client.as_full_client() { + Some(full_client) => full_client.best_block_header(), + None => { + debug!(target: "engine", "Not preparing block: not a full client."); + return SealingState::NotReady; + } + }; + + let validators = if self.immediate_transitions { + CowLike::Borrowed(&*self.validators) + } else { + let mut epoch_manager = self.epoch_manager.lock(); + if !epoch_manager.zoom_to_after( + &*client, + &self.machine, + &*self.validators, + parent.hash(), + ) { + debug!(target: "engine", "Not preparing block: Unable to zoom to epoch."); + return SealingState::NotReady; + } + CowLike::Owned(epoch_manager.validators().clone()) + }; + + let step = self.step.inner.load(); + + if !is_step_proposer(&*validators, &parent.hash(), step, &our_addr) { + trace!(target: "engine", "Not preparing block: not a proposer for step {}. (Our address: {})", + step, our_addr); + return SealingState::NotReady; + } + + SealingState::Ready } fn handle_message(&self, rlp: &[u8]) -> Result<(), EngineError> { - fn fmt_err(x: T) -> EngineError { + fn fmt_err(x: T) -> EngineError { EngineError::MalformedMessage(format!("{:?}", x)) } @@ -1370,6 +1786,11 @@ impl Engine for AuthorityRound { /// Apply the block reward on finalisation of the block. fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> { let mut beneficiaries = Vec::new(); + + if block.header.number() == self.two_thirds_majority_transition { + info!(target: "engine", "Block {}: Transitioning to 2/3 quorum.", self.two_thirds_majority_transition); + } + if block.header.number() >= self.empty_steps_transition { let empty_steps = if block.header.seal().is_empty() { // this is a new block, calculate rewards based on the empty steps messages we have accumulated @@ -1404,33 +1825,52 @@ impl Engine for AuthorityRound { let number = block.header.number(); beneficiaries.push((author, RewardKind::Author)); - let rewards: Vec<_> = match self.block_reward_contract { - Some(ref c) if number >= self.block_reward_contract_transition => { - let mut call = super::default_system_or_code_call(&self.machine, block); - - let rewards = c.reward(&beneficiaries, &mut call)?; - rewards - .into_iter() - .map(|(author, amount)| (author, RewardKind::External, amount)) - .collect() - } - _ => { - let (_, reward) = self.block_reward.iter() + let block_reward_contract_transition = self + .block_reward_contract_transitions + .range(..=block.header.number()) + .last(); + let rewards: Vec<_> = if let Some((_, contract)) = block_reward_contract_transition { + let mut call = crate::engines::default_system_or_code_call(&self.machine, block); + let rewards = contract.reward(&beneficiaries, &mut call)?; + rewards + .into_iter() + .map(|(author, amount)| (author, RewardKind::External, amount)) + .collect() + } else { + let (_, reward) = self + .block_reward + .iter() .rev() .find(|&(block, _)| *block <= number) - .expect("Current block's reward is not found; this indicates a chain config error; qed"); - let reward = *reward; + .expect( + "Current block's reward is not found; this indicates a chain config error; qed", + ); + let reward = *reward; - beneficiaries - .into_iter() - .map(|(author, reward_kind)| (author, reward_kind, reward)) - .collect() - } + beneficiaries + .into_iter() + .map(|(author, reward_kind)| (author, reward_kind, reward)) + .collect() }; + if let Some(signer) = self.signer.read().as_ref() { + let our_addr = signer.address(); + self.validators.on_close_block(&block.header, &our_addr)? + } + block_reward::apply_block_rewards(&rewards, block, &self.machine) } + fn generate_engine_transactions( + &self, + block: &ExecutedBlock, + ) -> Result, Error> { + let mut transactions = self.run_randomness_phase(block)?; + let nonce = transactions.last().map(|tx| tx.tx().nonce + U256::one()); + transactions.extend(self.run_posdao(block, nonce)?); + Ok(transactions) + } + /// Check the number of seal fields. fn verify_block_basic(&self, header: &Header) -> Result<(), Error> { if header.number() >= self.validate_score_transition @@ -1491,6 +1931,38 @@ impl Engine for AuthorityRound { Err(EngineError::DoubleVote(*header.author()))?; } + // Report malice if the validator produced other sibling blocks in the same step. + let received_step_key = (step, *header.author()); + let new_hash = header.hash(); + if self + .received_step_hashes + .read() + .get(&received_step_key) + .map_or(false, |h| *h != new_hash) + { + trace!(target: "engine", "Validator {} produced sibling blocks in the same step", header.author()); + self.validators.report_malicious( + header.author(), + set_number, + header.number(), + Default::default(), + ); + } else { + self.received_step_hashes + .write() + .insert(received_step_key, new_hash); + } + + // Remove hash records older than two full rounds of steps (picked as a reasonable trade-off between + // memory consumption and fault-tolerance). + let sibling_malice_detection_period = 2 * validators.count(&parent.hash()) as u64; + let oldest_step = parent_step.saturating_sub(sibling_malice_detection_period); + if oldest_step > 0 { + let mut rsh = self.received_step_hashes.write(); + let new_rsh = rsh.split_off(&(oldest_step, Address::zero())); + *rsh = new_rsh; + } + // If empty step messages are enabled we will validate the messages in the seal, missing messages are not // reported as there's no way to tell whether the empty step message was never sent or simply not included. let empty_steps_len = if header.number() >= self.empty_steps_transition { @@ -1545,6 +2017,12 @@ impl Engine for AuthorityRound { match validate_empty_steps() { Ok(len) => len, Err(err) => { + trace!( + target: "engine", + "Reporting benign misbehaviour (cause: invalid empty steps) \ + at block #{}, epoch set number {}. Own address: {}", + header.number(), set_number, self.address().unwrap_or_default() + ); self.validators .report_benign(header.author(), set_number, header.number()); return Err(err); @@ -1579,6 +2057,11 @@ impl Engine for AuthorityRound { let res = verify_external(header, &*validators, self.empty_steps_transition); match res { Err(Error(ErrorKind::Engine(EngineError::NotProposer(_)), _)) => { + trace!( + target: "engine", + "Reporting benign misbehaviour (cause: block from incorrect proposer) \ + at block #{}, epoch set number {}. Own address: {}", + header.number(), set_number, self.address().unwrap_or_default()); self.validators .report_benign(header.author(), set_number, header.number()); } @@ -1627,7 +2110,11 @@ impl Engine for AuthorityRound { // Apply transitions that don't require finality and should be enacted immediately (e.g from chain spec) if let Some(change) = self.validators.is_epoch_end(first, chain_head) { - info!(target: "engine", "Immediately applying validator set change signalled at block {}", chain_head.number()); + info!( + target: "engine", + "Immediately applying validator set change signalled at block {}", + chain_head.number() + ); self.epoch_manager.lock().note_new_epoch(); let change = combine_proofs(chain_head.number(), &change, &[]); return Some(change); @@ -1712,6 +2199,7 @@ impl Engine for AuthorityRound { step: self.step.clone(), subchain_validators: list, empty_steps_transition: self.empty_steps_transition, + two_thirds_majority_transition: self.two_thirds_majority_transition, }); match finalize { @@ -1730,8 +2218,8 @@ impl Engine for AuthorityRound { self.validators.register_client(client); } - fn set_signer(&self, signer: Box) { - *self.signer.write() = Some(signer); + fn set_signer(&self, signer: Option>) { + *self.signer.write() = signer; } fn sign(&self, hash: H256) -> Result { @@ -1774,35 +2262,84 @@ impl Engine for AuthorityRound { .map(AncestryAction::MarkFinalized) .collect() } + + fn gas_limit_override(&self, header: &Header) -> Option { + let (_, &address) = self + .block_gas_limit_contract_transitions + .range(..=header.number()) + .last()?; + let client = self.upgrade_client_or("Unable to prepare block").ok()?; + let full_client = match client.as_full_client() { + Some(full_client) => full_client, + None => { + error!(target: "engine", "Failed to upgrade to BlockchainClient."); + return None; + } + }; + if let Some(limit) = self.gas_limit_override_cache.lock().get_mut(&header.hash()) { + return *limit; + } + let limit = util::block_gas_limit(full_client, header, address); + self.gas_limit_override_cache + .lock() + .insert(header.hash(), limit); + limit + } +} + +/// A helper accumulator function mapping a step duration and a step duration transition timestamp +/// to the corresponding step number and the correct starting second of the step. +fn next_step_time_duration(info: StepDurationInfo, time: u64) -> Option<(u64, u64)> { + let step_diff = time + .checked_add(info.step_duration)? + .checked_sub(1)? + .checked_sub(info.transition_timestamp)? + .checked_div(info.step_duration)?; + let time_diff = step_diff.checked_mul(info.step_duration)?; + Some(( + info.transition_step.checked_add(step_diff)?, + info.transition_timestamp.checked_add(time_diff)?, + )) } #[cfg(test)] mod tests { use super::{ - calculate_score, AuthorityRound, AuthorityRoundParams, EmptyStep, SealedEmptyStep, + calculate_score, next_step_time_duration, util::BoundContract, AuthorityRound, + AuthorityRoundParams, EmptyStep, SealedEmptyStep, StepDurationInfo, }; use accounts::AccountProvider; use block::*; use crypto::publickey::Signature; use engines::{ + block_reward::BlockRewardContract, validator_set::{SimpleList, TestSet}, Engine, EngineError, EthEngine, Seal, }; use error::{Error, ErrorKind}; + use ethabi_contract::use_contract; use ethereum_types::{Address, H256, H520, U256}; + use ethjson; use hash::keccak; + use miner::{Author, MinerService}; use rlp::encode; use spec::Spec; use std::{ collections::BTreeMap, + str::FromStr, sync::{ - atomic::{AtomicUsize, Ordering as AtomicOrdering}, + atomic::{AtomicU64, AtomicUsize, Ordering as AtomicOrdering}, Arc, }, + time::Duration, + }; + use test_helpers::{ + generate_dummy_client_with_spec, generate_dummy_client_with_spec_and_data, + get_temp_state_db, TestNotify, }; - use test_helpers::{generate_dummy_client_with_spec, get_temp_state_db, TestNotify}; use types::{ header::Header, + ids::BlockId, transaction::{Action, Transaction, TypedTransaction}, }; @@ -1811,7 +2348,7 @@ mod tests { F: FnOnce(&mut AuthorityRoundParams), { let mut params = AuthorityRoundParams { - step_duration: 1, + step_durations: [(0, 1)].to_vec().into_iter().collect(), start_step: Some(1), validators: Box::new(TestSet::default()), validate_score_transition: 0, @@ -1822,9 +2359,12 @@ mod tests { empty_steps_transition: u64::max_value(), maximum_empty_steps: 0, block_reward: Default::default(), - block_reward_contract_transition: 0, - block_reward_contract: Default::default(), + block_reward_contract_transitions: Default::default(), strict_empty_steps_transition: 0, + two_thirds_majority_transition: 0, + randomness_contract_address: BTreeMap::new(), + block_gas_limit_contract_transitions: BTreeMap::new(), + posdao_transition: Some(0), }; // mutate aura params @@ -1837,6 +2377,22 @@ mod tests { AuthorityRound::new(params, machine).unwrap() } + #[test] + fn test_next_step_time_duration() { + // At step 7 (time 1000), we transitioned to step duration 10. + let info = StepDurationInfo { + step_duration: 10, + transition_step: 7, + transition_timestamp: 1000, + }; + // So the next transition can happen e.g. at step 12 (time 1050) or 13 (time 1060). + assert_eq!(Some((12, 1050)), next_step_time_duration(info, 1050)); + assert_eq!(Some((13, 1060)), next_step_time_duration(info, 1051)); + assert_eq!(Some((13, 1060)), next_step_time_duration(info, 1055)); + // The next transition could also happen immediately. + assert_eq!(Some((7, 1000)), next_step_time_duration(info, 1000)); + } + #[test] fn has_valid_metadata() { let engine = Spec::new_test_round().engine; @@ -1865,7 +2421,6 @@ mod tests { fn generates_seal_and_does_not_double_propose() { let tap = Arc::new(AccountProvider::transient_provider()); let addr1 = tap.insert_account(keccak("1").into(), &"1".into()).unwrap(); - let addr2 = tap.insert_account(keccak("2").into(), &"2".into()).unwrap(); let spec = Spec::new_test_round(); let engine = &*spec.engine; @@ -1873,9 +2428,6 @@ 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, @@ -1892,12 +2444,73 @@ mod tests { ) .unwrap(); let b1 = b1.close_and_lock().unwrap(); + + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); + if let Seal::Regular(seal) = engine.generate_seal(&b1, &genesis_header) { + assert!(b1.clone().try_seal(engine, seal).is_ok()); + // Second proposal is forbidden. + assert!(engine.generate_seal(&b1, &genesis_header) == Seal::None); + } else { + panic!("block 1 not sealed"); + } + } + + #[test] + fn generates_seal_iff_sealer_is_set() { + let tap = Arc::new(AccountProvider::transient_provider()); + let addr1 = tap.insert_account(keccak("1").into(), &"1".into()).unwrap(); + let spec = Spec::new_test_round(); + let engine = &*spec.engine; + let genesis_header = spec.genesis_header(); + let db1 = 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![], + false, + None, + ) + .unwrap() + .close_and_lock() + .unwrap(); + // Not a signer. A seal cannot be generated. + assert!(engine.generate_seal(&b1, &genesis_header) == Seal::None); + // Become a signer. + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); + if let Seal::Regular(seal) = engine.generate_seal(&b1, &genesis_header) { + assert!(b1.clone().try_seal(engine, seal).is_ok()); + // Second proposal is forbidden. + assert!(engine.generate_seal(&b1, &genesis_header) == Seal::None); + } else { + panic!("block 1 not sealed"); + } + // Stop being a signer. + engine.set_signer(None); + // Make a step first and then create a new block in that new step. + engine.step(); + let addr2 = tap.insert_account(keccak("0").into(), &"0".into()).unwrap(); + let mut header2 = genesis_header.clone(); + header2.set_number(2); + header2.set_author(addr2); + header2.set_parent_hash(header2.hash()); + let db2 = spec + .ensure_db_good(get_temp_state_db(), &Default::default()) + .unwrap(); let b2 = OpenBlock::new( engine, Default::default(), false, db2, - &genesis_header, + &header2, last_hashes, addr2, (3141562.into(), 31415620.into()), @@ -1905,21 +2518,19 @@ mod tests { false, None, ) + .unwrap() + .close_and_lock() .unwrap(); - let b2 = b2.close_and_lock().unwrap(); - - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); - if let Seal::Regular(seal) = engine.generate_seal(&b1, &genesis_header) { - assert!(b1.clone().try_seal(engine, seal).is_ok()); - // Second proposal is forbidden. - assert!(engine.generate_seal(&b1, &genesis_header) == Seal::None); - } - - engine.set_signer(Box::new((tap, addr2, "2".into()))); - if let Seal::Regular(seal) = engine.generate_seal(&b2, &genesis_header) { + // Not a signer. A seal cannot be generated. + assert!(engine.generate_seal(&b2, &header2) == Seal::None); + // Become a signer once more. + engine.set_signer(Some(Box::new((tap, addr2, "0".into())))); + if let Seal::Regular(seal) = engine.generate_seal(&b2, &header2) { assert!(b2.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(&b2, &genesis_header) == Seal::None); + assert!(engine.generate_seal(&b2, &header2) == Seal::None); + } else { + panic!("block 2 not sealed"); } } @@ -1972,13 +2583,13 @@ mod tests { .unwrap(); let b2 = b2.close_and_lock().unwrap(); - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); match engine.generate_seal(&b1, &genesis_header) { Seal::None | Seal::Proposal(_) => panic!("wrong seal"), Seal::Regular(_) => { engine.step(); - engine.set_signer(Box::new((tap.clone(), addr2, "0".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr2, "0".into())))); match engine.generate_seal(&b2, &genesis_header) { Seal::Regular(_) | Seal::Proposal(_) => { panic!("sealed despite wrong difficulty") @@ -2096,11 +2707,11 @@ mod tests { assert!(aura.verify_block_family(&header, &parent_header).is_ok()); assert_eq!(last_benign.load(AtomicOrdering::SeqCst), 0); - aura.set_signer(Box::new(( + aura.set_signer(Some(Box::new(( Arc::new(AccountProvider::transient_provider()), Default::default(), "".into(), - ))); + )))); // Do not report on steps skipped between genesis and first block. header.set_number(1); @@ -2113,6 +2724,38 @@ mod tests { assert_eq!(last_benign.load(AtomicOrdering::SeqCst), 2); } + #[test] + fn reports_multiple_blocks_per_step() { + let tap = AccountProvider::transient_provider(); + let addr0 = tap.insert_account(keccak("0").into(), &"0".into()).unwrap(); + let addr1 = tap.insert_account(keccak("1").into(), &"1".into()).unwrap(); + + let validator_set = TestSet::from_validators(vec![addr0, addr1]); + let aura = aura(|p| p.validators = Box::new(validator_set.clone())); + + aura.set_signer(Some(Box::new((Arc::new(tap), addr0, "0".into())))); + + let mut parent_header: Header = Header::default(); + parent_header.set_number(2); + parent_header.set_seal(vec![encode(&1usize)]); + parent_header.set_gas_limit("222222".parse::().unwrap()); + let mut header: Header = Header::default(); + header.set_number(3); + header.set_difficulty(calculate_score(1, 2, 0)); + header.set_gas_limit("222222".parse::().unwrap()); + header.set_seal(vec![encode(&2usize)]); + header.set_author(addr1); + + // First sibling block. + assert!(aura.verify_block_family(&header, &parent_header).is_ok()); + assert_eq!(validator_set.last_malicious(), 0); + + // Second sibling block: should be reported. + header.set_gas_limit("222223".parse::().unwrap()); + assert!(aura.verify_block_family(&header, &parent_header).is_ok()); + assert_eq!(validator_set.last_malicious(), 3); + } + #[test] fn test_uncles_transition() { let aura = aura(|params| { @@ -2130,29 +2773,88 @@ mod tests { use super::Step; let step = Step { calibrate: false, - inner: AtomicUsize::new(::std::usize::MAX), - duration: 1, + inner: AtomicU64::new(::std::u64::MAX), + durations: [StepDurationInfo { + transition_step: 0, + transition_timestamp: 0, + step_duration: 1, + }] + .to_vec() + .into_iter() + .collect(), }; step.increment(); } #[test] - #[should_panic(expected = "counter is too high")] + #[should_panic(expected = "step counter under- or overflow")] fn test_counter_duration_remaining_too_high() { use super::Step; let step = Step { calibrate: false, - inner: AtomicUsize::new(::std::usize::MAX), - duration: 1, + inner: AtomicU64::new(::std::u64::MAX), + durations: [StepDurationInfo { + transition_step: 0, + transition_timestamp: 0, + step_duration: 1, + }] + .to_vec() + .into_iter() + .collect(), }; step.duration_remaining(); } #[test] - #[should_panic(expected = "authority_round: step duration can't be zero")] + fn test_change_step_duration() { + use super::Step; + use std::thread; + + let now = super::unix_now().as_secs(); + let step = Step { + calibrate: true, + inner: AtomicU64::new(::std::u64::MAX), + durations: [ + StepDurationInfo { + transition_step: 0, + transition_timestamp: 0, + step_duration: 1, + }, + StepDurationInfo { + transition_step: now, + transition_timestamp: now, + step_duration: 2, + }, + StepDurationInfo { + transition_step: now + 1, + transition_timestamp: now + 2, + step_duration: 4, + }, + ] + .to_vec() + .into_iter() + .collect(), + }; + // calibrated step `now` + step.calibrate(); + let duration_remaining = step.duration_remaining(); + assert_eq!(step.inner.load(AtomicOrdering::SeqCst), now); + assert!(duration_remaining <= Duration::from_secs(2)); + thread::sleep(duration_remaining); + step.increment(); + // calibrated step `now + 1` + step.calibrate(); + let duration_remaining = step.duration_remaining(); + assert_eq!(step.inner.load(AtomicOrdering::SeqCst), now + 1); + assert!(duration_remaining > Duration::from_secs(2)); + assert!(duration_remaining <= Duration::from_secs(4)); + } + + #[test] + #[should_panic] fn test_step_duration_zero() { aura(|params| { - params.step_duration = 0; + params.step_durations = [(0, 0)].to_vec().into_iter().collect(); }); } @@ -2198,7 +2900,7 @@ mod tests { ]); } - fn assert_insufficient_proof(result: Result, contains: &str) { + fn assert_insufficient_proof(result: Result, contains: &str) { match result { Err(Error(ErrorKind::Engine(EngineError::InsufficientProof(ref s)), _)) => { assert!( @@ -2231,7 +2933,7 @@ mod tests { client.add_notify(notify.clone()); engine.register_client(Arc::downgrade(&client) as _); - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); let b1 = OpenBlock::new( engine, @@ -2305,7 +3007,7 @@ mod tests { let b1 = b1.close_and_lock().unwrap(); // since the block is empty it isn't sealed and we generate empty steps - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); assert_eq!(engine.generate_seal(&b1, &genesis_header), Seal::None); engine.step(); @@ -2340,9 +3042,9 @@ mod tests { let b2 = b2.close_and_lock().unwrap(); // we will now seal a block with 1tx and include the accumulated empty step message - engine.set_signer(Box::new((tap.clone(), addr2, "0".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr2, "0".into())))); if let Seal::Regular(seal) = engine.generate_seal(&b2, &genesis_header) { - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); let empty_step2 = sealed_empty_step(engine, 2, &genesis_header.hash()); let empty_steps = ::rlp::encode_list(&vec![empty_step2]); @@ -2395,7 +3097,7 @@ mod tests { let b1 = b1.close_and_lock().unwrap(); // since the block is empty it isn't sealed and we generate empty steps - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); assert_eq!(engine.generate_seal(&b1, &genesis_header), Seal::None); engine.step(); @@ -2415,7 +3117,7 @@ mod tests { ) .unwrap(); let b2 = b2.close_and_lock().unwrap(); - engine.set_signer(Box::new((tap.clone(), addr2, "0".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr2, "0".into())))); assert_eq!(engine.generate_seal(&b2, &genesis_header), Seal::None); engine.step(); @@ -2437,10 +3139,10 @@ mod tests { .unwrap(); let b3 = b3.close_and_lock().unwrap(); - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); if let Seal::Regular(seal) = engine.generate_seal(&b3, &genesis_header) { let empty_step2 = sealed_empty_step(engine, 2, &genesis_header.hash()); - engine.set_signer(Box::new((tap.clone(), addr2, "0".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr2, "0".into())))); let empty_step3 = sealed_empty_step(engine, 3, &genesis_header.hash()); let empty_steps = ::rlp::encode_list(&vec![empty_step2, empty_step3]); @@ -2488,7 +3190,7 @@ mod tests { let b1 = b1.close_and_lock().unwrap(); // since the block is empty it isn't sealed and we generate empty steps - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); assert_eq!(engine.generate_seal(&b1, &genesis_header), Seal::None); engine.step(); @@ -2563,7 +3265,7 @@ mod tests { ); // empty step with valid signature from incorrect proposer for step - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); let empty_steps = vec![sealed_empty_step(engine, 1, &parent_header.hash())]; set_empty_steps_seal(&mut header, 2, &signature, &empty_steps); @@ -2573,9 +3275,9 @@ mod tests { ); // valid empty steps - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); let empty_step2 = sealed_empty_step(engine, 2, &parent_header.hash()); - engine.set_signer(Box::new((tap.clone(), addr2, "0".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr2, "0".into())))); let empty_step3 = sealed_empty_step(engine, 3, &parent_header.hash()); let empty_steps = vec![empty_step2, empty_step3]; @@ -2627,7 +3329,7 @@ mod tests { let b1 = b1.close_and_lock().unwrap(); // since the block is empty it isn't sealed and we generate empty steps - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); assert_eq!(engine.generate_seal(&b1, &genesis_header), Seal::None); engine.step(); @@ -2660,13 +3362,75 @@ mod tests { ) } + #[test] + fn randomness_contract() -> Result<(), super::util::CallError> { + use_contract!( + rand_contract, + "res/contracts/test_authority_round_random.json" + ); + + env_logger::init(); + + let contract_addr = Address::from_str("0000000000000000000000000000000000000042").unwrap(); + let client = generate_dummy_client_with_spec_and_data( + Spec::new_test_round_randomness_contract, + 0, + 0, + &[], + true, + ); + + let tap = Arc::new(AccountProvider::transient_provider()); + + let addr1 = tap.insert_account(keccak("1").into(), &"1".into()).unwrap(); + // Unlock account so that the engine can decrypt the secret. + tap.unlock_account_permanently(addr1, "1".into()) + .expect("unlock"); + + let signer = Box::new((tap.clone(), addr1, "1".into())); + client.miner().set_author(Author::Sealer(signer.clone())); + client + .miner() + .set_gas_range_target((U256::from(1000000), U256::from(1000000))); + + let engine = client.engine(); + engine.set_signer(Some(signer)); + engine.register_client(Arc::downgrade(&client) as _); + let bc = BoundContract::new(&*client, BlockId::Latest, contract_addr); + + // First the contract is in the commit phase, and we haven't committed yet. + assert!(bc.call_const(rand_contract::functions::is_commit_phase::call())?); + assert!(!bc.call_const(rand_contract::functions::is_committed::call(0, addr1))?); + + // We produce a block and commit. + engine.step(); + assert!(bc.call_const(rand_contract::functions::is_committed::call(0, addr1))?); + + // After two more blocks we are in the reveal phase... + engine.step(); + engine.step(); + assert!(bc.call_const(rand_contract::functions::is_reveal_phase::call())?); + assert!(!bc.call_const(rand_contract::functions::sent_reveal::call(0, addr1))?); + assert!(bc + .call_const(rand_contract::functions::get_value::call())? + .is_zero()); + + // ...so in the next step, we reveal our random value, and the contract's random value is not zero anymore. + engine.step(); + assert!(bc.call_const(rand_contract::functions::sent_reveal::call(0, addr1))?); + assert!(!bc + .call_const(rand_contract::functions::get_value::call())? + .is_zero()); + Ok(()) + } + #[test] fn extra_info_from_seal() { let (spec, tap, accounts) = setup_empty_steps(); let engine = &*spec.engine; let addr1 = accounts[0]; - engine.set_signer(Box::new((tap.clone(), addr1, "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), addr1, "1".into())))); let mut header: Header = Header::default(); let empty_step = empty_step(engine, 1, &header.parent_hash()); @@ -2699,7 +3463,7 @@ mod tests { #[test] fn test_empty_steps() { let engine = aura(|p| { - p.step_duration = 4; + p.step_durations = [(0, 4)].to_vec().into_iter().collect(); p.empty_steps_transition = 0; p.maximum_empty_steps = 0; }); @@ -2736,7 +3500,7 @@ mod tests { let (_spec, tap, accounts) = setup_empty_steps(); let engine = aura(|p| { p.validators = Box::new(SimpleList::new(accounts.clone())); - p.step_duration = 4; + p.step_durations = [(0, 4)].to_vec().into_iter().collect(); p.empty_steps_transition = 0; p.maximum_empty_steps = 0; }); @@ -2750,7 +3514,7 @@ mod tests { header.set_author(accounts[0]); // when - engine.set_signer(Box::new((tap.clone(), accounts[1], "0".into()))); + engine.set_signer(Some(Box::new((tap.clone(), accounts[1], "0".into())))); let empty_steps = vec![ sealed_empty_step(&*engine, 1, &parent.hash()), sealed_empty_step(&*engine, 1, &parent.hash()), @@ -2775,7 +3539,7 @@ mod tests { let (_spec, tap, accounts) = setup_empty_steps(); let engine = aura(|p| { p.validators = Box::new(SimpleList::new(accounts.clone())); - p.step_duration = 4; + p.step_durations = [(0, 4)].to_vec().into_iter().collect(); p.empty_steps_transition = 0; p.maximum_empty_steps = 0; }); @@ -2789,9 +3553,9 @@ mod tests { header.set_author(accounts[0]); // when - engine.set_signer(Box::new((tap.clone(), accounts[1], "0".into()))); + engine.set_signer(Some(Box::new((tap.clone(), accounts[1], "0".into())))); let es1 = sealed_empty_step(&*engine, 1, &parent.hash()); - engine.set_signer(Box::new((tap.clone(), accounts[0], "1".into()))); + engine.set_signer(Some(Box::new((tap.clone(), accounts[0], "1".into())))); let es2 = sealed_empty_step(&*engine, 2, &parent.hash()); let mut empty_steps = vec![es2, es1]; @@ -2814,4 +3578,75 @@ mod tests { set_empty_steps_seal(&mut header, step, &signature, &empty_steps); assert_eq!(engine.verify_block_family(&header, &parent).unwrap(), ()); } + + #[test] + fn should_collect_block_reward_transitions() { + let config = r#"{ + "params": { + "stepDuration": "5", + "validators": { + "list" : ["0x1000000000000000000000000000000000000001"] + }, + "blockRewardContractTransition": "0", + "blockRewardContractAddress": "0x2000000000000000000000000000000000000002", + "blockRewardContractTransitions": { + "7": "0x3000000000000000000000000000000000000003", + "42": "0x4000000000000000000000000000000000000004" + } + } + }"#; + let deserialized: ethjson::spec::AuthorityRound = serde_json::from_str(config).unwrap(); + let params = AuthorityRoundParams::from(deserialized.params); + for ((block_num1, address1), (block_num2, address2)) in + params.block_reward_contract_transitions.iter().zip( + [ + ( + 0u64, + BlockRewardContract::new_from_address( + Address::from_str("2000000000000000000000000000000000000002").unwrap(), + ), + ), + ( + 7u64, + BlockRewardContract::new_from_address( + Address::from_str("3000000000000000000000000000000000000003").unwrap(), + ), + ), + ( + 42u64, + BlockRewardContract::new_from_address( + Address::from_str("4000000000000000000000000000000000000004").unwrap(), + ), + ), + ] + .iter(), + ) + { + assert_eq!(block_num1, block_num2); + assert_eq!(address1, address2); + } + } + + #[test] + #[should_panic( + expected = "blockRewardContractTransition should be less than any of the keys in blockRewardContractTransitions" + )] + fn should_reject_out_of_order_block_reward_transition() { + let config = r#"{ + "params": { + "stepDuration": "5", + "validators": { + "list" : ["0x1000000000000000000000000000000000000001"] + }, + "blockRewardContractTransition": "7", + "blockRewardContractAddress": "0x2000000000000000000000000000000000000002", + "blockRewardContractTransitions": { + "0": "0x3000000000000000000000000000000000000003", + "42": "0x4000000000000000000000000000000000000004" + } + } + }"#; + let deserialized: ethjson::spec::AuthorityRound = serde_json::from_str(config).unwrap(); + AuthorityRoundParams::from(deserialized.params); + } } diff --git a/crates/ethcore/src/engines/authority_round/randomness.rs b/crates/ethcore/src/engines/authority_round/randomness.rs new file mode 100644 index 000000000..0113d8041 --- /dev/null +++ b/crates/ethcore/src/engines/authority_round/randomness.rs @@ -0,0 +1,257 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of OpenEthereum. + +// OpenEthereum 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. + +// OpenEthereum 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 OpenEthereum. If not, see . + +//! On-chain randomness generation for authority round +//! +//! This module contains the support code for the on-chain randomness generation used by AuRa. Its +//! core is the finite state machine `RandomnessPhase`, which can be loaded from the blockchain +//! state, then asked to perform potentially necessary transaction afterwards using the `advance()` +//! method. +//! +//! No additional state is kept inside the `RandomnessPhase`, it must be passed in each time. +//! +//! The process of generating random numbers is a simple finite state machine: +//! +//! ```text +//! + +//! | +//! | +//! | +//! +--------------+ +-------v-------+ +//! | | | | +//! | BeforeCommit <------------------------------+ Waiting | +//! | | enter commit phase | | +//! +------+-------+ +-------^-------+ +//! | | +//! | call | +//! | `commitHash()` | call +//! | | `revealNumber()` +//! | | +//! +------v-------+ +-------+-------+ +//! | | | | +//! | Committed +------------------------------> Reveal | +//! | | enter reveal phase | | +//! +--------------+ +---------------+ +//! ``` +//! +//! Phase transitions are performed by the smart contract and simply queried by the engine. +//! +//! Randomness generation works as follows: +//! * During the commit phase, all validators locally generate a random number, and commit that number's hash to the +//! contract. +//! * During the reveal phase, all validators reveal their local random number to the contract. The contract should +//! verify that it matches the committed hash. +//! * Finally, the XOR of all revealed numbers is used as an on-chain random number. +//! +//! An adversary can only influence that number by either controlling _all_ validators who committed, or, to a lesser +//! extent, by not revealing committed numbers. +//! The length of the commit and reveal phases, as well as any penalties for failure to reveal, are defined by the +//! contract. +//! +//! A typical case of using `RandomnessPhase` is: +//! +//! 1. `RandomnessPhase::load()` the phase from the blockchain data. +//! 2. Call `RandomnessPhase::advance()`. +//! +//! A production implementation of a randomness contract can be found here: +//! https://github.com/poanetwork/posdao-contracts/blob/4fddb108993d4962951717b49222327f3d94275b/contracts/RandomAuRa.sol + +use bytes::Bytes; +use crypto::publickey::{ecies, Error as CryptoError}; +use derive_more::Display; +use engines::signer::EngineSigner; +use ethabi::Hash; +use ethabi_contract::use_contract; +use ethereum_types::{Address, H256, U256}; +use hash::keccak; +use log::{debug, error}; +use rand::Rng; + +use super::util::{BoundContract, CallError}; + +/// Random number type expected by the contract: This is generated locally, kept secret during the commit phase, and +/// published in the reveal phase. +pub type RandNumber = H256; + +use_contract!(aura_random, "res/contracts/authority_round_random.json"); + +/// Validated randomness phase state. +#[derive(Debug)] +pub enum RandomnessPhase { + // NOTE: Some states include information already gathered during `load` (e.g. `our_address`, + // `round`) for efficiency reasons. + /// Waiting for the next phase. + /// + /// This state indicates either the successful revelation in this round or having missed the + /// window to make a commitment, i.e. having failed to commit during the commit phase. + Waiting, + /// Indicates a commitment is possible, but still missing. + BeforeCommit, + /// Indicates a successful commitment, waiting for the commit phase to end. + Committed, + /// Indicates revealing is expected as the next step. + Reveal { our_address: Address, round: U256 }, +} + +/// Phase loading error for randomness generation state machine. +/// +/// This error usually indicates a bug in either the smart contract, the phase loading function or +/// some state being lost. +/// +/// `BadRandNumber` will usually result in punishment by the contract or the other validators. +#[derive(Debug, Display)] +pub enum PhaseError { + /// The smart contract reported that we already revealed something while still being in the + /// commit phase. + #[display(fmt = "Revealed during commit phase")] + RevealedInCommit, + /// Failed to load contract information. + #[display(fmt = "Error loading randomness contract information: {:?}", _0)] + LoadFailed(CallError), + /// Failed to load the stored encrypted random number. + #[display(fmt = "Failed to load random number from the randomness contract")] + BadRandNumber, + /// Failed to encrypt random number. + #[display(fmt = "Failed to encrypt random number: {}", _0)] + Crypto(CryptoError), + /// Failed to get the engine signer's public key. + #[display(fmt = "Failed to get the engine signer's public key")] + MissingPublicKey, +} + +impl From for PhaseError { + fn from(err: CryptoError) -> PhaseError { + PhaseError::Crypto(err) + } +} + +impl RandomnessPhase { + /// Determine randomness generation state from the contract. + /// + /// Calls various constant contract functions to determine the precise state that needs to be + /// handled (that is, the phase and whether or not the current validator still needs to send + /// commitments or reveal random numbers). + pub fn load( + contract: &BoundContract, + our_address: Address, + ) -> Result { + // Determine the current round and which phase we are in. + let round = contract + .call_const(aura_random::functions::current_collect_round::call()) + .map_err(PhaseError::LoadFailed)?; + let is_commit_phase = contract + .call_const(aura_random::functions::is_commit_phase::call()) + .map_err(PhaseError::LoadFailed)?; + + // Ensure we are not committing or revealing twice. + let committed = contract + .call_const(aura_random::functions::is_committed::call( + round, + our_address, + )) + .map_err(PhaseError::LoadFailed)?; + let revealed: bool = contract + .call_const(aura_random::functions::sent_reveal::call( + round, + our_address, + )) + .map_err(PhaseError::LoadFailed)?; + + // With all the information known, we can determine the actual state we are in. + if is_commit_phase { + if revealed { + return Err(PhaseError::RevealedInCommit); + } + + if !committed { + Ok(RandomnessPhase::BeforeCommit) + } else { + Ok(RandomnessPhase::Committed) + } + } else { + if !committed { + // We apparently entered too late to make a commitment, wait until we get a chance again. + return Ok(RandomnessPhase::Waiting); + } + + if !revealed { + Ok(RandomnessPhase::Reveal { our_address, round }) + } else { + Ok(RandomnessPhase::Waiting) + } + } + } + + /// Advance the random seed construction process as far as possible. + /// + /// Returns the encoded contract call necessary to advance the randomness contract's state. + /// + /// **Warning**: After calling the `advance()` function, wait until the returned transaction has been included in + /// a block before calling it again; otherwise spurious transactions resulting in punishments might be executed. + pub fn advance( + self, + contract: &BoundContract, + rng: &mut R, + signer: &dyn EngineSigner, + ) -> Result, PhaseError> { + match self { + RandomnessPhase::Waiting | RandomnessPhase::Committed => Ok(None), + RandomnessPhase::BeforeCommit => { + // Generate a new random number, but don't reveal it yet. Instead, we publish its hash to the + // randomness contract, together with the number encrypted to ourselves. That way we will later be + // able to decrypt and reveal it, and other parties are able to verify it against the hash. + let number: RandNumber = rng.gen(); + let number_hash: Hash = keccak(number.0); + let public = signer.public().ok_or(PhaseError::MissingPublicKey)?; + let cipher = ecies::encrypt(&public, number_hash.as_bytes(), number.as_bytes())?; + + debug!(target: "engine", "Randomness contract: committing {}.", number_hash); + // Return the call data for the transaction that commits the hash and the encrypted number. + let (data, _decoder) = + aura_random::functions::commit_hash::call(number_hash, cipher); + Ok(Some(data)) + } + RandomnessPhase::Reveal { round, our_address } => { + // Load the hash and encrypted number that we stored in the commit phase. + let call = aura_random::functions::get_commit_and_cipher::call(round, our_address); + let (committed_hash, cipher) = + contract.call_const(call).map_err(PhaseError::LoadFailed)?; + + // Decrypt the number and check against the hash. + let number_bytes = signer.decrypt(&committed_hash.0, &cipher)?; + let number = if number_bytes.len() == 32 { + RandNumber::from_slice(&number_bytes) + } else { + // This can only happen if there is a bug in the smart contract, + // or if the entire network goes awry. + error!(target: "engine", "Decrypted random number has the wrong length."); + return Err(PhaseError::BadRandNumber); + }; + let number_hash: Hash = keccak(number.0); + if number_hash != committed_hash { + error!(target: "engine", "Decrypted random number doesn't agree with the hash."); + return Err(PhaseError::BadRandNumber); + } + + debug!(target: "engine", "Randomness contract: scheduling tx to reveal our random number {} (round={}, our_address={}).", number_hash, round, our_address); + // We are now sure that we have the correct secret and can reveal it. So we return the call data for the + // transaction that stores the revealed random bytes on the contract. + let (data, _decoder) = aura_random::functions::reveal_number::call(number.0); + Ok(Some(data)) + } + } + } +} diff --git a/crates/ethcore/src/engines/authority_round/util.rs b/crates/ethcore/src/engines/authority_round/util.rs new file mode 100644 index 000000000..3b50a087f --- /dev/null +++ b/crates/ethcore/src/engines/authority_round/util.rs @@ -0,0 +1,119 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of OpenEthereum. + +// OpenEthereum 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. + +// OpenEthereum 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 OpenEthereum. If not, see . + +//! Utility functions. +//! +//! Contains small functions used by the AuRa engine that are not strictly limited to that scope. + +use std::fmt; + +use client::{traits::EngineClient, BlockChainClient}; +use ethabi::{self, FunctionOutputDecoder}; +use ethabi_contract::use_contract; +use ethereum_types::{Address, U256}; +use log::{debug, error}; +use types::{header::Header, ids::BlockId}; + +/// A contract bound to a client and block number. +/// +/// A bound contract is a combination of a `Client` reference, a `BlockId` and a contract `Address`. +/// These three parts are enough to call a contract's function; return values are automatically +/// decoded. +pub struct BoundContract<'a> { + client: &'a dyn EngineClient, + block_id: BlockId, + contract_addr: Address, +} + +/// Contract call failed error. +#[derive(Debug)] +pub enum CallError { + /// The call itself failed. + CallFailed(String), + /// Decoding the return value failed or the decoded value was a failure. + DecodeFailed(ethabi::Error), + /// The passed in client reference could not be upgraded to a `BlockchainClient`. + NotFullClient, +} + +impl<'a> fmt::Debug for BoundContract<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("BoundContract") + .field("client", &(self.client as *const dyn EngineClient)) + .field("block_id", &self.block_id) + .field("contract_addr", &self.contract_addr) + .finish() + } +} + +impl<'a> BoundContract<'a> { + /// Create a new `BoundContract`. + pub fn new( + client: &dyn EngineClient, + block_id: BlockId, + contract_addr: Address, + ) -> BoundContract { + BoundContract { + client, + block_id, + contract_addr, + } + } + + /// Perform a function call to an Ethereum machine that doesn't create a transaction or change the state. + /// + /// Runs a constant function call on `client`. The `call` value can be serialized by calling any + /// api function generated by the `use_contract!` macro. This does not create any transactions, it only produces a + /// result based on the state at the current block: It is constant in the sense that it does not alter the EVM + /// state. + pub fn call_const(&self, call: (ethabi::Bytes, D)) -> Result + where + D: ethabi::FunctionOutputDecoder, + { + let (data, output_decoder) = call; + + let call_return = self + .client + .as_full_client() + .ok_or(CallError::NotFullClient)? + .call_contract(self.block_id, self.contract_addr, data) + .map_err(CallError::CallFailed)?; + + // Decode the result and return it. + output_decoder + .decode(call_return.as_slice()) + .map_err(CallError::DecodeFailed) + } +} + +use_contract!(contract, "res/contracts/block_gas_limit.json"); + +pub fn block_gas_limit( + full_client: &dyn BlockChainClient, + header: &Header, + address: Address, +) -> Option { + let (data, decoder) = contract::functions::block_gas_limit::call(); + let value = full_client.call_contract(BlockId::Hash(*header.parent_hash()), address, data).map_err(|err| { + error!(target: "block_gas_limit", "Contract call failed. Not changing the block gas limit. {:?}", err); + }).ok()?; + if value.is_empty() { + debug!(target: "block_gas_limit", "Contract call returned nothing. Not changing the block gas limit."); + None + } else { + decoder.decode(&value).ok() + } +} diff --git a/crates/ethcore/src/engines/basic_authority.rs b/crates/ethcore/src/engines/basic_authority.rs index f1b9e7baf..91add1a8b 100644 --- a/crates/ethcore/src/engines/basic_authority.rs +++ b/crates/ethcore/src/engines/basic_authority.rs @@ -20,7 +20,7 @@ use super::validator_set::{new_validator_set, SimpleList, ValidatorSet}; use block::*; use client::EngineClient; use crypto::publickey::{self, Signature}; -use engines::{signer::EngineSigner, ConstructedVerifier, Engine, EngineError, Seal}; +use engines::{signer::EngineSigner, ConstructedVerifier, Engine, EngineError, Seal, SealingState}; use error::{BlockError, Error}; use ethereum_types::{H256, H520}; use ethjson; @@ -104,8 +104,12 @@ impl Engine for BasicAuthority { 1 } - fn seals_internally(&self) -> Option { - Some(self.signer.read().is_some()) + fn sealing_state(&self) -> SealingState { + if self.signer.read().is_some() { + SealingState::Ready + } else { + SealingState::NotReady + } } /// Attempt to seal the block internally. @@ -197,8 +201,8 @@ impl Engine for BasicAuthority { self.validators.register_client(client); } - fn set_signer(&self, signer: Box) { - *self.signer.write() = Some(signer); + fn set_signer(&self, signer: Option>) { + *self.signer.write() = signer; } fn sign(&self, hash: H256) -> Result { @@ -223,7 +227,7 @@ impl Engine for BasicAuthority { mod tests { use accounts::AccountProvider; use block::*; - use engines::Seal; + use engines::{Seal, SealingState}; use ethereum_types::H520; use hash::keccak; use spec::Spec; @@ -269,7 +273,7 @@ mod tests { let spec = new_test_authority(); let engine = &*spec.engine; - engine.set_signer(Box::new((Arc::new(tap), addr, "".into()))); + engine.set_signer(Some(Box::new((Arc::new(tap), addr, "".into())))); let genesis_header = spec.genesis_header(); let db = spec .ensure_db_good(get_temp_state_db(), &Default::default()) @@ -296,13 +300,15 @@ mod tests { } #[test] - fn seals_internally() { + fn sealing_state() { let tap = AccountProvider::transient_provider(); let authority = tap.insert_account(keccak("").into(), &"".into()).unwrap(); let engine = new_test_authority().engine; - assert!(!engine.seals_internally().unwrap()); - engine.set_signer(Box::new((Arc::new(tap), authority, "".into()))); - assert!(engine.seals_internally().unwrap()); + assert_eq!(SealingState::NotReady, engine.sealing_state()); + engine.set_signer(Some(Box::new((Arc::new(tap), authority, "".into())))); + assert_eq!(SealingState::Ready, engine.sealing_state()); + engine.set_signer(None); + assert_eq!(SealingState::NotReady, engine.sealing_state()); } } diff --git a/crates/ethcore/src/engines/clique/mod.rs b/crates/ethcore/src/engines/clique/mod.rs index 9952e87a1..6ab84f6d8 100644 --- a/crates/ethcore/src/engines/clique/mod.rs +++ b/crates/ethcore/src/engines/clique/mod.rs @@ -38,8 +38,8 @@ /// /// 1. Set a signer using `Engine::set_signer()`. If a miner account was set up through /// a config file or CLI flag `MinerService::set_author()` will eventually set the signer -/// 2. We check that the engine seals internally through `Clique::seals_internally()` -/// Note: This is always true for Clique +/// 2. We check that the engine is ready for sealing through `Clique::sealing_state()` +/// Note: This is always `SealingState::Ready` for Clique /// 3. Calling `Clique::new()` will spawn a `StepService` thread. This thread will call `Engine::step()` /// periodically. Internally, the Clique `step()` function calls `Client::update_sealing()`, which is /// what makes and seals a block. @@ -71,7 +71,7 @@ use client::{traits::ForceUpdateSealing, BlockId, EngineClient}; use crypto::publickey::Signature; use engines::{ clique::util::{extract_signers, recover_creator}, - Engine, EngineError, Seal, + Engine, EngineError, Seal, SealingState, }; use error::{BlockError, Error}; use ethereum_types::{Address, H160, H256, H64, U256}; @@ -476,8 +476,8 @@ impl Engine for Clique { } /// Clique doesn't require external work to seal, so we always return true here. - fn seals_internally(&self) -> Option { - Some(true) + fn sealing_state(&self) -> SealingState { + SealingState::Ready } /// Returns if we are ready to seal, the real sealing (signing extra_data) is actually done in `on_seal_block()`. @@ -766,9 +766,14 @@ impl Engine for Clique { } } - fn set_signer(&self, signer: Box) { - trace!(target: "engine", "set_signer: {}", signer.address()); - *self.signer.write() = Some(signer); + fn set_signer(&self, signer: Option>) { + let mut current_signer = self.signer.write(); + if let Some(signer) = signer.as_ref() { + trace!(target: "engine", "set_signer: {:?}", signer.address()); + } else if let Some(signer) = &*current_signer { + trace!(target: "engine", "set_signer: cleared; previous signer: {:?})", signer.address()); + } + *current_signer = signer; } fn register_client(&self, client: Weak) { diff --git a/crates/ethcore/src/engines/instant_seal.rs b/crates/ethcore/src/engines/instant_seal.rs index 6d944a52f..8410476d8 100644 --- a/crates/ethcore/src/engines/instant_seal.rs +++ b/crates/ethcore/src/engines/instant_seal.rs @@ -15,7 +15,7 @@ // along with OpenEthereum. If not, see . use block::ExecutedBlock; -use engines::{Engine, Seal}; +use engines::{Engine, Seal, SealingState}; use machine::Machine; use std::sync::atomic::{AtomicU64, Ordering}; use types::header::{ExtendedHeader, Header}; @@ -63,8 +63,8 @@ impl Engine for InstantSeal { &self.machine } - fn seals_internally(&self) -> Option { - Some(true) + fn sealing_state(&self) -> SealingState { + SealingState::Ready } fn should_reseal_on_update(&self) -> bool { diff --git a/crates/ethcore/src/engines/mod.rs b/crates/ethcore/src/engines/mod.rs index f2c2f66ae..aff6c5e7b 100644 --- a/crates/ethcore/src/engines/mod.rs +++ b/crates/ethcore/src/engines/mod.rs @@ -89,6 +89,10 @@ pub enum EngineError { InsufficientProof(String), /// Failed system call. FailedSystemCall(String), + /// Failed to decode the result of a system call. + SystemCallResultDecoding(String), + /// The result of a system call is invalid. + SystemCallResultInvalid(String), /// Malformed consensus message. MalformedMessage(String), /// Requires client ref, but none registered. @@ -153,6 +157,12 @@ impl fmt::Display for EngineError { 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), + SystemCallResultDecoding(ref msg) => { + format!("Failed to decode the result of a system call: {}", msg) + } + SystemCallResultInvalid(ref msg) => { + format!("The result of a system call is invalid: {}", msg) + } MalformedMessage(ref msg) => format!("Received malformed consensus message: {}", msg), RequiresClient => format!("Call requires client but none registered"), RequiresSigner => format!("Call requires signer but none registered"), @@ -180,6 +190,17 @@ pub enum Seal { None, } +/// The type of sealing the engine is currently able to perform. +#[derive(Debug, PartialEq, Eq)] +pub enum SealingState { + /// The engine is ready to seal a block. + Ready, + /// The engine can't seal at the moment, and no block should be prepared and queued. + NotReady, + /// The engine does not seal internally. + External, +} + /// A system-calling closure. Enacts calls on a block's state from the system address. pub type SystemCall<'a> = dyn FnMut(Address, Vec) -> Result, String> + 'a; @@ -329,11 +350,9 @@ pub trait Engine: Sync + Send { Ok(()) } - /// None means that it requires external input (e.g. PoW) to seal a block. - /// Some(true) means the engine is currently prime for seal generation (i.e. node is the current validator). - /// Some(false) means that the node might seal internally but is not qualified now. - fn seals_internally(&self) -> Option { - None + /// Returns the engine's current sealing state. + fn sealing_state(&self) -> SealingState { + SealingState::External } /// Called in `miner.chain_new_blocks` if the engine wishes to `update_sealing` @@ -449,7 +468,7 @@ pub trait Engine: Sync + Send { } /// Register a component which signs consensus messages. - fn set_signer(&self, _signer: Box) {} + fn set_signer(&self, _signer: Option>) {} /// Sign using the EngineSigner, to be used for consensus tx signing. fn sign(&self, _hash: H256) -> Result { @@ -505,6 +524,24 @@ pub trait Engine: Sync + Send { fn executive_author(&self, header: &Header) -> Result { Ok(*header.author()) } + + /// Returns a list of transactions for a new block if we are the author. + /// + /// This is called when the miner prepares a new block that this node will author and seal. It returns a list of + /// transactions that will be added to the block before any other transactions from the queue. + /// Added for AuRa needs. + fn generate_engine_transactions( + &self, + _block: &ExecutedBlock, + ) -> Result, Error> { + Ok(Vec::new()) + } + + /// Overrides the block gas limit. Whenever this returns `Some` for a header, the next block's gas limit must be + /// exactly that value. used by AuRa engine. + fn gas_limit_override(&self, _header: &Header) -> Option { + None + } } /// t_nb 9.3 Check whether a given block is the best block based on the default total difficulty rule. @@ -610,6 +647,11 @@ pub trait EthEngine: Engine<::machine::EthereumMachine> { let schedule = self.schedule(best_block_number); self.machine().decode_transaction(transaction, &schedule) } + + /// The configured minimum gas limit. Used by AuRa Engine. + fn min_gas_limit(&self) -> U256 { + self.params().min_gas_limit + } } // convenience wrappers for existing functions. diff --git a/crates/ethcore/src/engines/signer.rs b/crates/ethcore/src/engines/signer.rs index 74fb61f9c..373661e88 100644 --- a/crates/ethcore/src/engines/signer.rs +++ b/crates/ethcore/src/engines/signer.rs @@ -16,8 +16,9 @@ //! A signer used by Engines which need to sign messages. -use crypto::publickey::{self, Signature}; +use crypto::publickey::{self, ecies, Error, Public, Signature}; use ethereum_types::{Address, H256}; +//TODO dr /// Everything that an Engine needs to sign messages. pub trait EngineSigner: Send + Sync { @@ -26,6 +27,12 @@ pub trait EngineSigner: Send + Sync { /// Signing address fn address(&self) -> Address; + + /// Decrypt a message that was encrypted to this signer's key. + fn decrypt(&self, auth_data: &[u8], cipher: &[u8]) -> Result, Error>; + + /// The signer's public key, if available. + fn public(&self) -> Option; } /// Creates a new `EngineSigner` from given key pair. @@ -43,6 +50,14 @@ impl EngineSigner for Signer { fn address(&self) -> Address { self.0.address() } + + fn decrypt(&self, auth_data: &[u8], cipher: &[u8]) -> Result, Error> { + ecies::decrypt(self.0.secret(), auth_data, cipher).map_err(From::from) + } + + fn public(&self) -> Option { + Some(*self.0.public()) + } } #[cfg(test)] @@ -78,5 +93,18 @@ mod test_signer { fn address(&self) -> Address { self.1 } + + fn decrypt(&self, auth_data: &[u8], cipher: &[u8]) -> Result, Error> { + self.0 + .decrypt(self.1, None, auth_data, cipher) + .map_err(|e| { + warn!("Unable to decrypt message: {:?}", e); + Error::InvalidMessage + }) + } + + fn public(&self) -> Option { + self.0.account_public(self.1, &self.2).ok() + } } } diff --git a/crates/ethcore/src/engines/validator_set/contract.rs b/crates/ethcore/src/engines/validator_set/contract.rs index 451d8e39c..a4b781689 100644 --- a/crates/ethcore/src/engines/validator_set/contract.rs +++ b/crates/ethcore/src/engines/validator_set/contract.rs @@ -19,12 +19,14 @@ use std::sync::Weak; use bytes::Bytes; -use ethereum_types::{Address, H256}; +use ethereum_types::{Address, H256, U256}; use machine::{AuxiliaryData, Call, EthereumMachine}; use parking_lot::RwLock; -use types::{header::Header, BlockNumber}; +use types::{header::Header, ids::BlockId, transaction, BlockNumber}; -use client::EngineClient; +use client::{traits::TransactionRequest, EngineClient}; + +use error::Error as EthcoreError; use super::{safe_contract::ValidatorSafeContract, SimpleList, SystemCall, ValidatorSet}; @@ -35,34 +37,87 @@ pub struct ValidatorContract { contract_address: Address, validators: ValidatorSafeContract, client: RwLock>>, // TODO [keorn]: remove + posdao_transition: Option, } impl ValidatorContract { - pub fn new(contract_address: Address) -> Self { + pub fn new(contract_address: Address, posdao_transition: Option) -> Self { ValidatorContract { contract_address, - validators: ValidatorSafeContract::new(contract_address), + validators: ValidatorSafeContract::new(contract_address, posdao_transition), client: RwLock::new(None), + posdao_transition, } } -} -impl ValidatorContract { - fn transact(&self, data: Bytes) -> Result<(), String> { + fn transact( + &self, + data: Bytes, + gas_price: Option, + client: &dyn EngineClient, + ) -> Result<(), String> { + let full_client = client.as_full_client().ok_or("No full client!")?; + let tx_request = TransactionRequest::call(self.contract_address, data).gas_price(gas_price); + match full_client.transact(tx_request) { + Ok(()) | Err(transaction::Error::AlreadyImported) => Ok(()), + Err(e) => Err(e.to_string())?, + } + } + + fn do_report_malicious( + &self, + address: &Address, + block: BlockNumber, + proof: Bytes, + ) -> Result<(), EthcoreError> { let client = self .client .read() .as_ref() .and_then(Weak::upgrade) - .ok_or_else(|| "No client!")?; + .ok_or("No client!")?; + let latest = client + .block_header(BlockId::Latest) + .ok_or("No latest block!")?; + if !self.contains(&latest.parent_hash(), address) { + warn!(target: "engine", "Not reporting {} on block {}: Not a validator", address, block); + return Ok(()); + } + let data = + validator_report::functions::report_malicious::encode_input(*address, block, proof); + self.validators + .enqueue_report(*address, block, data.clone()); + let gas_price = self.report_gas_price(latest.number()); + self.transact(data, gas_price, &*client)?; + warn!(target: "engine", "Reported malicious validator {} at block {}", address, block); + Ok(()) + } - match client.as_full_client() { - Some(c) => { - c.transact_contract(self.contract_address, data) - .map_err(|e| format!("Transaction import error: {}", e))?; - Ok(()) - } - None => Err("No full client!".into()), + fn do_report_benign(&self, address: &Address, block: BlockNumber) -> Result<(), EthcoreError> { + let client = self + .client + .read() + .as_ref() + .and_then(Weak::upgrade) + .ok_or("No client!")?; + let latest = client + .block_header(BlockId::Latest) + .ok_or("No latest block!")?; + let data = validator_report::functions::report_benign::encode_input(*address, block); + let gas_price = self.report_gas_price(latest.number()); + self.transact(data, gas_price, &*client)?; + warn!(target: "engine", "Benign report for validator {} at block {}", address, block); + Ok(()) + } + + /// Returns the gas price for report transactions. + /// + /// After `posdaoTransition`, this is zero. Otherwise it is the default (`None`). + fn report_gas_price(&self, block: BlockNumber) -> Option { + if self.posdao_transition? <= block { + Some(0.into()) + } else { + None } } } @@ -72,6 +127,20 @@ impl ValidatorSet for ValidatorContract { self.validators.default_caller(id) } + fn generate_engine_transactions( + &self, + first: bool, + header: &Header, + call: &mut SystemCall, + ) -> Result, EthcoreError> { + self.validators + .generate_engine_transactions(first, header, call) + } + + fn on_close_block(&self, header: &Header, address: &Address) -> Result<(), EthcoreError> { + self.validators.on_close_block(header, address) + } + fn on_epoch_begin( &self, first: bool, @@ -127,19 +196,14 @@ impl ValidatorSet for ValidatorContract { block: BlockNumber, proof: Bytes, ) { - let data = - validator_report::functions::report_malicious::encode_input(*address, block, proof); - match self.transact(data) { - Ok(_) => warn!(target: "engine", "Reported malicious validator {}", address), - Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), + if let Err(s) = self.do_report_malicious(address, block, proof) { + warn!(target: "engine", "Validator {} could not be reported ({}) on block {}", address, s, block); } } fn report_benign(&self, address: &Address, _set_block: BlockNumber, block: BlockNumber) { - let data = validator_report::functions::report_benign::encode_input(*address, block); - match self.transact(data) { - Ok(_) => warn!(target: "engine", "Reported benign validator misbehaviour {}", address), - Err(s) => warn!(target: "engine", "Validator {} could not be reported {}", address, s), + if let Err(s) = self.do_report_benign(address, block) { + warn!(target: "engine", "Validator {} could not be reported ({}) on block {}", address, s, block); } } @@ -155,7 +219,8 @@ mod tests { use accounts::AccountProvider; use bytes::ToPretty; use call_contract::CallContract; - use client::{BlockChainClient, BlockInfo, ChainInfo}; + use client::{traits::TransactionRequest, BlockChainClient, BlockInfo, ChainInfo}; + use ethabi::FunctionOutputDecoder; use ethereum_types::{Address, H520}; use hash::keccak; use miner::{self, MinerService}; @@ -169,11 +234,8 @@ mod tests { #[test] fn fetches_validators() { let client = generate_dummy_client_with_spec(Spec::new_validator_contract); - let vc = Arc::new(ValidatorContract::new( - "0000000000000000000000000000000000000005" - .parse::
() - .unwrap(), - )); + let addr: Address = "0000000000000000000000000000000000000005".parse().unwrap(); + let vc = Arc::new(ValidatorContract::new(addr, None)); vc.register_client(Arc::downgrade(&client) as _); let last_hash = client.best_block_header().hash(); assert!(vc.contains( @@ -219,6 +281,8 @@ mod tests { assert!(client.engine().verify_block_external(&header).is_err()); client.engine().step(); assert_eq!(client.chain_info().best_block_number, 0); + // `reportBenign` when the designated proposer releases block from the future (bad clock). + assert!(client.engine().verify_block_basic(&header).is_err()); // Now create one that is more in future. That one should be rejected and validator should be reported. let mut header = Header::default(); @@ -232,7 +296,7 @@ mod tests { // Seal a block. client.engine().step(); assert_eq!(client.chain_info().best_block_number, 1); - // Check if the unresponsive validator is `disliked`. + // Check if the unresponsive validator is `disliked`. "d8f2e0bf" accesses the field `disliked`.. assert_eq!( client .call_contract( @@ -254,10 +318,23 @@ mod tests { client.engine().step(); client.engine().step(); assert_eq!(client.chain_info().best_block_number, 2); + let (data, decoder) = + super::validator_report::functions::malice_reported_for_block::call(v1, 1); + let reported_enc = client + .call_contract(BlockId::Latest, validator_contract, data) + .expect("call failed"); + assert_ne!( + Vec::
::new(), + decoder.decode(&reported_enc).expect("decoding failed") + ); // Check if misbehaving validator was removed. + client - .transact_contract(Default::default(), Default::default()) + .transact(TransactionRequest::call( + Default::default(), + Default::default(), + )) .unwrap(); client.engine().step(); client.engine().step(); diff --git a/crates/ethcore/src/engines/validator_set/mod.rs b/crates/ethcore/src/engines/validator_set/mod.rs index e9f2d6b99..c47e747f5 100644 --- a/crates/ethcore/src/engines/validator_set/mod.rs +++ b/crates/ethcore/src/engines/validator_set/mod.rs @@ -33,31 +33,49 @@ use types::{header::Header, ids::BlockId, BlockNumber}; use client::EngineClient; +use error::Error as EthcoreError; + pub use self::simple_list::SimpleList; #[cfg(test)] pub use self::test::TestSet; use self::{contract::ValidatorContract, multi::Multi, safe_contract::ValidatorSafeContract}; use super::SystemCall; -/// Creates a validator set from spec. -pub fn new_validator_set(spec: ValidatorSpec) -> Box { +/// Creates a validator set from the given spec and initializes a transition to POSDAO AuRa consensus. +pub fn new_validator_set_posdao( + spec: ValidatorSpec, + posdao_transition: Option, +) -> Box { match spec { ValidatorSpec::List(list) => { Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())) } - ValidatorSpec::SafeContract(address) => { - Box::new(ValidatorSafeContract::new(address.into())) + ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new( + address.into(), + posdao_transition, + )), + ValidatorSpec::Contract(address) => { + Box::new(ValidatorContract::new(address.into(), posdao_transition)) } - ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())), ValidatorSpec::Multi(sequence) => Box::new(Multi::new( sequence .into_iter() - .map(|(block, set)| (block.into(), new_validator_set(set))) + .map(|(block, set)| { + ( + block.into(), + new_validator_set_posdao(set, posdao_transition), + ) + }) .collect(), )), } } +/// Creates a validator set from the given spec. +pub fn new_validator_set(spec: ValidatorSpec) -> Box { + new_validator_set_posdao(spec, None) +} + /// A validator set. pub trait ValidatorSet: Send + Sync + 'static { /// Get the default "Call" helper, for use in general operation. @@ -65,6 +83,21 @@ pub trait ValidatorSet: Send + Sync + 'static { // a strict dependency on state always being available. fn default_caller(&self, block_id: BlockId) -> Box; + /// Called for each new block this node is creating. If this block is + /// the first block of an epoch, this is called *after* `on_epoch_begin()`, + /// but with the same parameters. + /// + /// Returns a list of contract calls to be pushed onto the new block. + fn generate_engine_transactions( + &self, + _first: bool, + _header: &Header, + _call: &mut SystemCall, + ) -> Result, EthcoreError>; + + /// Called on the close of every block. + fn on_close_block(&self, _header: &Header, _address: &Address) -> Result<(), EthcoreError>; + /// Checks if a given address is a validator, /// using underlying, default call mechanism. fn contains(&self, parent: &H256, address: &Address) -> bool { diff --git a/crates/ethcore/src/engines/validator_set/multi.rs b/crates/ethcore/src/engines/validator_set/multi.rs index 01618e5af..358413618 100644 --- a/crates/ethcore/src/engines/validator_set/multi.rs +++ b/crates/ethcore/src/engines/validator_set/multi.rs @@ -25,6 +25,7 @@ use types::{header::Header, ids::BlockId, BlockNumber}; use super::{SystemCall, ValidatorSet}; use client::EngineClient; +use error::Error as EthcoreError; use machine::{AuxiliaryData, Call, EthereumMachine}; type BlockNumberLookup = @@ -47,6 +48,15 @@ impl Multi { } } + fn map_children(&self, header: &Header, mut func: F) -> Result + where + F: FnMut(&dyn ValidatorSet, bool) -> Result, + { + let (set_block, set) = self.correct_set_by_number(header.number()); + let first = set_block == header.number(); + func(set, first) + } + fn correct_set(&self, id: BlockId) -> Option<&dyn ValidatorSet> { match self.block_number.read()(id) .map(|parent_block| self.correct_set_by_number(parent_block)) @@ -81,16 +91,31 @@ impl ValidatorSet for Multi { .unwrap_or_else(|| Box::new(|_, _| Err("No validator set for given ID.".into()))) } + fn generate_engine_transactions( + &self, + _first: bool, + header: &Header, + call: &mut SystemCall, + ) -> Result, EthcoreError> { + self.map_children(header, &mut |set: &dyn ValidatorSet, first| { + set.generate_engine_transactions(first, header, call) + }) + } + fn on_close_block(&self, header: &Header, address: &Address) -> Result<(), EthcoreError> { + self.map_children(header, &mut |set: &dyn ValidatorSet, _first| { + set.on_close_block(header, address) + }) + } + 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) + ) -> Result<(), EthcoreError> { + self.map_children(header, &mut |set: &dyn ValidatorSet, first| { + set.on_epoch_begin(first, header, call) + }) } fn genesis_epoch_data(&self, header: &Header, call: &Call) -> Result, String> { @@ -182,7 +207,10 @@ impl ValidatorSet for Multi { #[cfg(test)] mod tests { use accounts::AccountProvider; - use client::{traits::ForceUpdateSealing, BlockChainClient, BlockInfo, ChainInfo, ImportBlock}; + use client::{ + traits::{ForceUpdateSealing, TransactionRequest}, + BlockChainClient, BlockInfo, ChainInfo, ImportBlock, + }; use crypto::publickey::Secret; use engines::{validator_set::ValidatorSet, EpochChange}; use ethereum_types::Address; @@ -190,7 +218,7 @@ mod tests { use miner::{self, MinerService}; use spec::Spec; use std::{collections::BTreeMap, sync::Arc}; - use test_helpers::{generate_dummy_client_with_spec, generate_dummy_client_with_spec_and_data}; + use test_helpers::generate_dummy_client_with_spec; use types::{header::Header, ids::BlockId}; use verification::queue::kind::blocks::Unverified; @@ -216,7 +244,10 @@ mod tests { let signer = Box::new((tap.clone(), v1, "".into())); client.miner().set_author(miner::Author::Sealer(signer)); client - .transact_contract(Default::default(), Default::default()) + .transact(TransactionRequest::call( + Default::default(), + Default::default(), + )) .unwrap(); ::client::EngineClient::update_sealing(&*client, ForceUpdateSealing::No); assert_eq!(client.chain_info().best_block_number, 0); @@ -227,7 +258,10 @@ mod tests { assert_eq!(client.chain_info().best_block_number, 1); // This time v0 is wrong. client - .transact_contract(Default::default(), Default::default()) + .transact(TransactionRequest::call( + Default::default(), + Default::default(), + )) .unwrap(); ::client::EngineClient::update_sealing(&*client, ForceUpdateSealing::No); assert_eq!(client.chain_info().best_block_number, 1); @@ -237,14 +271,16 @@ mod tests { assert_eq!(client.chain_info().best_block_number, 2); // v1 is still good. client - .transact_contract(Default::default(), Default::default()) + .transact(TransactionRequest::call( + Default::default(), + Default::default(), + )) .unwrap(); ::client::EngineClient::update_sealing(&*client, ForceUpdateSealing::No); assert_eq!(client.chain_info().best_block_number, 3); // Check syncing. - let sync_client = - generate_dummy_client_with_spec_and_data(Spec::new_validator_multi, 0, 0, &[]); + let sync_client = generate_dummy_client_with_spec(Spec::new_validator_multi); sync_client .engine() .register_client(Arc::downgrade(&sync_client) as _); diff --git a/crates/ethcore/src/engines/validator_set/safe_contract.rs b/crates/ethcore/src/engines/validator_set/safe_contract.rs index 30e8403f1..451617e2f 100644 --- a/crates/ethcore/src/engines/validator_set/safe_contract.rs +++ b/crates/ethcore/src/engines/validator_set/safe_contract.rs @@ -15,25 +15,39 @@ // along with OpenEthereum. If not, see . /// Validator set maintained in a contract, updated using `getValidators` method. -use std::sync::{Arc, Weak}; +use std::{ + collections::VecDeque, + sync::{Arc, Weak}, +}; use bytes::Bytes; +use error::{Error as EthcoreError, ErrorKind as EthcoreErrorKind}; use ethabi::FunctionOutputDecoder; use ethereum_types::{Address, Bloom, H256, U256}; use hash::keccak; use kvdb::DBValue; use memory_cache::MemoryLruCache; -use parking_lot::RwLock; +use parking_lot::{Mutex, RwLock}; use rlp::{Rlp, RlpStream}; -use types::{header::Header, ids::BlockId, log_entry::LogEntry, receipt::TypedReceipt}; +use types::{ + header::Header, ids::BlockId, log_entry::LogEntry, receipt::TypedReceipt, transaction, + BlockNumber, +}; use unexpected::Mismatch; use super::{simple_list::SimpleList, SystemCall, ValidatorSet}; -use client::EngineClient; +use client::{traits::TransactionRequest, BlockChainClient, EngineClient}; use machine::{AuxiliaryData, AuxiliaryRequest, Call, EthereumMachine}; use_contract!(validator_set, "res/contracts/validator_set.json"); +/// The maximum number of reports to keep queued. +const MAX_QUEUED_REPORTS: usize = 10; +/// The maximum number of malice reports to include when creating a new block. +const MAX_REPORTS_PER_BLOCK: usize = 10; +/// Don't re-send malice reports every block. Skip this many before retrying. +const REPORTS_SKIP_BLOCKS: u64 = 1; + const MEMOIZE_CAPACITY: usize = 500; // TODO: ethabi should be able to generate this. @@ -71,6 +85,12 @@ pub struct ValidatorSafeContract { contract_address: Address, validators: RwLock>, client: RwLock>>, // TODO [keorn]: remove + report_queue: Mutex, + /// The block number where we resent the queued reports last time. + resent_reports_in_block: Mutex, + /// If set, this is the block number at which the consensus engine switches from AuRa to AuRa + /// with POSDAO modifications. + posdao_transition: Option, } // first proof is just a state proof call of `getValidators` at header's state. @@ -203,14 +223,54 @@ fn prove_initial( } impl ValidatorSafeContract { - pub fn new(contract_address: Address) -> Self { + pub fn new(contract_address: Address, posdao_transition: Option) -> Self { ValidatorSafeContract { contract_address, validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)), client: RwLock::new(None), + report_queue: Mutex::new(ReportQueue::default()), + resent_reports_in_block: Mutex::new(0), + posdao_transition, } } + fn transact(&self, data: Bytes, nonce: U256) -> Result<(), EthcoreError> { + let client = self + .client + .read() + .as_ref() + .and_then(Weak::upgrade) + .ok_or("No client!")?; + let full_client = client.as_full_client().ok_or("No full client!")?; + + let tx_request = TransactionRequest::call(self.contract_address, data) + .gas_price(U256::zero()) + .nonce(nonce); + match full_client.transact(tx_request) { + Ok(()) | Err(transaction::Error::AlreadyImported) => Ok(()), + Err(e) => Err(e)?, + } + } + + /// Puts a malice report into the queue for later resending. + /// + /// # Arguments + /// + /// * `addr` - The address of the misbehaving validator. + /// * `block` - The block number at which the misbehavior occurred. + /// * `data` - The call data for the `reportMalicious` contract call. + pub(crate) fn enqueue_report(&self, addr: Address, block: BlockNumber, data: Vec) { + // Skip the rest of the function unless there has been a transition to POSDAO AuRa. + if self + .posdao_transition + .map_or(true, |block_num| block < block_num) + { + trace!(target: "engine", "Skipping queueing a malicious behavior report"); + return; + } + self.report_queue.lock().push(addr, block, data) + } + /// Queries the state and gets the set of validators. fn get_list(&self, caller: &Call) -> Option { let contract_address = self.contract_address; @@ -314,6 +374,110 @@ impl ValidatorSet for ValidatorSafeContract { }) // generate no proofs in general } + fn generate_engine_transactions( + &self, + _first: bool, + header: &Header, + caller: &mut SystemCall, + ) -> Result, EthcoreError> { + // Skip the rest of the function unless there has been a transition to POSDAO AuRa. + if self + .posdao_transition + .map_or(true, |block_num| header.number() < block_num) + { + trace!(target: "engine", "Skipping a call to emitInitiateChange"); + return Ok(Vec::new()); + } + let mut transactions = Vec::new(); + + // Create the `InitiateChange` event if necessary. + let (data, decoder) = validator_set::functions::emit_initiate_change_callable::call(); + let emit_initiate_change_callable = caller(self.contract_address, data) + .and_then(|x| { + decoder + .decode(&x) + .map_err(|x| format!("chain spec bug: could not decode: {:?}", x)) + }) + .map_err(::engines::EngineError::FailedSystemCall)?; + if !emit_initiate_change_callable { + trace!(target: "engine", "New block #{} issued ― no need to call emitInitiateChange()", header.number()); + } else { + trace!(target: "engine", "New block issued #{} ― calling emitInitiateChange()", header.number()); + let (data, _decoder) = validator_set::functions::emit_initiate_change::call(); + transactions.push((self.contract_address, data)); + } + + let client = self + .client + .read() + .as_ref() + .and_then(Weak::upgrade) + .ok_or("No client!")?; + let client = client.as_full_client().ok_or("No full client!")?; + + // Retry all pending reports. + let mut report_queue = self.report_queue.lock(); + report_queue.filter(client, header.author(), self.contract_address); + for (_address, _block, data) in report_queue.iter().take(MAX_REPORTS_PER_BLOCK) { + transactions.push((self.contract_address, data.clone())) + } + + Ok(transactions) + } + + fn on_close_block(&self, header: &Header, our_address: &Address) -> Result<(), EthcoreError> { + // Skip the rest of the function unless there has been a transition to POSDAO AuRa. + if self + .posdao_transition + .map_or(true, |block_num| header.number() < block_num) + { + trace!(target: "engine", "Skipping resending of queued malicious behavior reports"); + return Ok(()); + } + + let client = self + .client + .read() + .as_ref() + .and_then(Weak::upgrade) + .ok_or("No client!")?; + let client = client.as_full_client().ok_or("No full client!")?; + + let mut report_queue = self.report_queue.lock(); + report_queue.filter(client, our_address, self.contract_address); + report_queue.truncate(); + + let mut resent_reports_in_block = self.resent_reports_in_block.lock(); + + // Skip at least one block after sending malicious reports last time. + if header.number() > *resent_reports_in_block + REPORTS_SKIP_BLOCKS { + *resent_reports_in_block = header.number(); + let mut nonce = client.latest_nonce(our_address); + for (address, block, data) in report_queue.iter() { + debug!(target: "engine", "Retrying to report validator {} for misbehavior on block {} with nonce {}.", + address, block, nonce); + while match self.transact(data.clone(), nonce) { + Ok(()) => false, + Err(EthcoreError( + EthcoreErrorKind::Transaction(transaction::Error::Old), + _, + )) => true, + Err(err) => { + warn!(target: "engine", "Cannot report validator {} for misbehavior on block {}: {}", + address, block, err); + false + } + } { + warn!(target: "engine", "Nonce {} already used. Incrementing.", nonce); + nonce += U256::from(1); + } + nonce += U256::from(1); + } + } + + Ok(()) + } + fn on_epoch_begin( &self, _first: bool, @@ -473,6 +637,68 @@ impl ValidatorSet for ValidatorSafeContract { } } +/// A queue containing pending reports of malicious validators. +#[derive(Debug, Default)] +struct ReportQueue(VecDeque<(Address, BlockNumber, Vec)>); + +impl ReportQueue { + /// Pushes a report to the end of the queue. + fn push(&mut self, addr: Address, block: BlockNumber, data: Vec) { + self.0.push_back((addr, block, data)); + } + + /// Filters reports of validators that have already been reported or are banned. + fn filter( + &mut self, + client: &dyn BlockChainClient, + our_address: &Address, + contract_address: Address, + ) { + self.0.retain(|&(malicious_validator_address, block, ref _data)| { + trace!( + target: "engine", + "Checking if report of malicious validator {} at block {} should be removed from cache", + malicious_validator_address, + block + ); + // Check if the validator should be reported. + let (data, decoder) = validator_set::functions::should_validator_report::call( + *our_address, malicious_validator_address, block + ); + match client.call_contract(BlockId::Latest, contract_address, data) + .and_then(|result| decoder.decode(&result[..]).map_err(|e| e.to_string())) + { + Ok(false) => { + trace!(target: "engine", "Successfully removed report from report cache"); + false + } + Ok(true) => true, + Err(err) => { + warn!(target: "engine", "Failed to query report status {:?}, dropping pending report.", err); + false + } + } + }); + } + + /// Returns an iterator over all transactions in the queue. + fn iter(&self) -> impl Iterator)> { + self.0.iter() + } + + /// Removes reports from the queue if it contains more than `MAX_QUEUED_REPORTS` entries. + fn truncate(&mut self) { + if self.0.len() > MAX_QUEUED_REPORTS { + warn!( + target: "engine", + "Removing {} reports from report cache, even though it has not been finalized", + self.0.len() - MAX_QUEUED_REPORTS + ); + self.0.truncate(MAX_QUEUED_REPORTS); + } + } +} + #[cfg(test)] mod tests { use super::{super::ValidatorSet, ValidatorSafeContract, EVENT_NAME_HASH}; @@ -488,7 +714,7 @@ mod tests { use rustc_hex::FromHex; use spec::Spec; use std::sync::Arc; - use test_helpers::{generate_dummy_client_with_spec, generate_dummy_client_with_spec_and_data}; + use test_helpers::generate_dummy_client_with_spec; use types::{ ids::BlockId, transaction::{Action, Transaction, TypedTransaction}, @@ -498,11 +724,8 @@ mod tests { #[test] fn fetches_validators() { let client = generate_dummy_client_with_spec(Spec::new_validator_safe_contract); - let vc = Arc::new(ValidatorSafeContract::new( - "0000000000000000000000000000000000000005" - .parse::
() - .unwrap(), - )); + let addr: Address = "0000000000000000000000000000000000000005".parse().unwrap(); + let vc = Arc::new(ValidatorSafeContract::new(addr, None)); vc.register_client(Arc::downgrade(&client) as _); let last_hash = client.best_block_header().hash(); assert!(vc.contains( @@ -600,8 +823,7 @@ mod tests { assert_eq!(client.chain_info().best_block_number, 3); // Check syncing. - let sync_client = - generate_dummy_client_with_spec_and_data(Spec::new_validator_safe_contract, 0, 0, &[]); + let sync_client = generate_dummy_client_with_spec(Spec::new_validator_safe_contract); sync_client .engine() .register_client(Arc::downgrade(&sync_client) as _); diff --git a/crates/ethcore/src/engines/validator_set/simple_list.rs b/crates/ethcore/src/engines/validator_set/simple_list.rs index 8e4420462..c608d68a0 100644 --- a/crates/ethcore/src/engines/validator_set/simple_list.rs +++ b/crates/ethcore/src/engines/validator_set/simple_list.rs @@ -18,7 +18,9 @@ use ethereum_types::{Address, H256}; /// Preconfigured validator list. use parity_util_mem::MallocSizeOf; -use super::ValidatorSet; +use super::{SystemCall, ValidatorSet}; +use bytes::Bytes; +use error::Error as EthcoreError; use machine::{AuxiliaryData, Call, EthereumMachine}; use types::{header::Header, BlockNumber}; @@ -63,6 +65,19 @@ impl ValidatorSet for SimpleList { Box::new(|_, _| Err("Simple list doesn't require calls.".into())) } + fn generate_engine_transactions( + &self, + _first: bool, + _header: &Header, + _call: &mut SystemCall, + ) -> Result, EthcoreError> { + Ok(Vec::new()) + } + + fn on_close_block(&self, _header: &Header, _address: &Address) -> Result<(), EthcoreError> { + Ok(()) + } + fn is_epoch_end(&self, first: bool, _chain_head: &Header) -> Option> { match first { true => Some(Vec::new()), // allow transition to fixed list, and instantly diff --git a/crates/ethcore/src/engines/validator_set/test.rs b/crates/ethcore/src/engines/validator_set/test.rs index ab96a42e6..88db87f75 100644 --- a/crates/ethcore/src/engines/validator_set/test.rs +++ b/crates/ethcore/src/engines/validator_set/test.rs @@ -26,11 +26,12 @@ use ethereum_types::{Address, H256}; use parity_util_mem::MallocSizeOf; use types::{header::Header, BlockNumber}; -use super::{SimpleList, ValidatorSet}; +use super::{SimpleList, SystemCall, ValidatorSet}; +use error::Error as EthcoreError; use machine::{AuxiliaryData, Call, EthereumMachine}; /// Set used for testing with a single validator. -#[derive(MallocSizeOf)] +#[derive(Clone, MallocSizeOf)] pub struct TestSet { validator: SimpleList, last_malicious: Arc, @@ -54,6 +55,21 @@ impl TestSet { last_benign, } } + + pub fn from_validators(validators: Vec
) -> Self { + let mut ts = TestSet::new(Default::default(), Default::default()); + ts.validator = SimpleList::new(validators); + ts + } + + pub fn last_malicious(&self) -> usize { + self.last_malicious.load(AtomicOrdering::SeqCst) + } + + #[allow(dead_code)] + pub fn last_benign(&self) -> usize { + self.last_benign.load(AtomicOrdering::SeqCst) + } } impl ValidatorSet for TestSet { @@ -61,6 +77,19 @@ impl ValidatorSet for TestSet { Box::new(|_, _| Err("Test set doesn't require calls.".into())) } + fn generate_engine_transactions( + &self, + _first: bool, + _header: &Header, + _call: &mut SystemCall, + ) -> Result, EthcoreError> { + Ok(Vec::new()) + } + + fn on_close_block(&self, _header: &Header, _address: &Address) -> Result<(), EthcoreError> { + Ok(()) + } + fn is_epoch_end(&self, _first: bool, _chain_head: &Header) -> Option> { None } diff --git a/crates/ethcore/src/lib.rs b/crates/ethcore/src/lib.rs index 1b8dcac70..5a0c1b0e3 100644 --- a/crates/ethcore/src/lib.rs +++ b/crates/ethcore/src/lib.rs @@ -21,6 +21,7 @@ extern crate ansi_term; extern crate common_types as types; extern crate crossbeam_utils; +extern crate derive_more; extern crate ethabi; extern crate ethash; extern crate ethcore_blockchain as blockchain; diff --git a/crates/ethcore/src/miner/miner.rs b/crates/ethcore/src/miner/miner.rs index d56127d38..d9336e538 100644 --- a/crates/ethcore/src/miner/miner.rs +++ b/crates/ethcore/src/miner/miner.rs @@ -60,7 +60,7 @@ use client::{ BlockChain, BlockId, BlockProducer, ChainInfo, ClientIoMessage, Nonce, SealedBlockImporter, TransactionId, TransactionInfo, }; -use engines::{EngineSigner, EthEngine, Seal}; +use engines::{EngineSigner, EthEngine, Seal, SealingState}; use error::{Error, ErrorKind}; use executed::ExecutionError; use executive::contract_address; @@ -288,7 +288,8 @@ impl Miner { Miner { sealing: Mutex::new(SealingWork { queue: UsingQueue::new(options.work_queue_size), - enabled: options.force_sealing || spec.engine.seals_internally().is_some(), + enabled: options.force_sealing + || spec.engine.sealing_state() != SealingState::External, next_allowed_reseal: Instant::now(), next_mandatory_reseal: Instant::now() + options.reseal_max_period, last_request: None, @@ -319,6 +320,17 @@ impl Miner { /// /// NOTE This should be only used for tests. pub fn new_for_tests(spec: &Spec, accounts: Option>) -> Miner { + Miner::new_for_tests_force_sealing(spec, accounts, false) + } + + /// Creates new instance of miner with given spec and accounts. + /// + /// NOTE This should be only used for tests. + pub fn new_for_tests_force_sealing( + spec: &Spec, + accounts: Option>, + force_sealing: bool, + ) -> Miner { let minimal_gas_price = 0.into(); Miner::new( MinerOptions { @@ -329,6 +341,7 @@ impl Miner { no_early_reject: false, }, reseal_min_period: Duration::from_secs(0), + force_sealing, ..Default::default() }, GasPricer::new_fixed(minimal_gas_price), @@ -419,8 +432,8 @@ impl Miner { trace_time!("prepare_block"); let chain_info = chain.chain_info(); - // Open block - let (mut open_block, original_work_hash) = { + // Some engines add transactions to the block for their own purposes, e.g. AuthorityRound RANDAO. + let (mut open_block, original_work_hash, engine_txs) = { let mut sealing = self.sealing.lock(); let last_work_hash = sealing.queue.peek_last_ref().map(|pb| pb.header.hash()); let best_hash = chain_info.best_block_hash; @@ -431,41 +444,50 @@ impl Miner { // if at least one was pushed successfully, close and enqueue new ClosedBlock; // otherwise, leave everything alone. // otherwise, author a fresh block. - let mut open_block = match sealing + match sealing .queue .get_pending_if(|b| b.header.parent_hash() == &best_hash) { Some(old_block) => { trace!(target: "miner", "prepare_block: Already have previous work; updating and returning"); // add transactions to old_block - chain.reopen_block(old_block) + (chain.reopen_block(old_block), last_work_hash, Vec::new()) } None => { // block not found - create it. trace!(target: "miner", "prepare_block: No existing work - making new block"); let params = self.params.read().clone(); - match chain.prepare_open_block( + let block = match chain.prepare_open_block( params.author, params.gas_range_target, params.extra_data, ) { Ok(block) => block, Err(err) => { - warn!(target: "miner", "Open new block failed with error {:?}. This is likely an error in chain specificiations or on-chain consensus smart contracts.", err); + warn!(target: "miner", "Open new block failed with error {:?}. This is likely an error in \ + chain specification or on-chain consensus smart contracts.", err); + return None; + } + }; + // Before adding from the queue to the new block, give the engine a chance to add transactions. + match self.engine.generate_engine_transactions(&block) { + Ok(transactions) => (block, last_work_hash, transactions), + Err(err) => { + error!(target: "miner", "Failed to prepare engine transactions for new block: {:?}. \ + This is likely an error in chain specification or on-chain consensus smart \ + contracts.", err); return None; } } } - }; - - if self.options.infinite_pending_block { - open_block.remove_gas_limit(); } - - (open_block, last_work_hash) }; + if self.options.infinite_pending_block { + open_block.remove_gas_limit(); + } + let mut invalid_transactions = HashSet::new(); let mut not_allowed_transactions = HashSet::new(); let mut senders_to_penalize = HashSet::new(); @@ -501,13 +523,13 @@ impl Miner { ) }; - let pending: Vec> = self.transaction_queue.pending( + let queue_txs: Vec> = self.transaction_queue.pending( client.clone(), pool::PendingSettings { block_number: chain_info.best_block_number, current_timestamp: chain_info.best_block_timestamp, nonce_cap, - max_len: max_transactions, + max_len: max_transactions.saturating_sub(engine_txs.len()), ordering: miner::PendingOrdering::Priority, }, ); @@ -517,12 +539,14 @@ impl Miner { }; let block_start = Instant::now(); - debug!(target: "miner", "Attempting to push {} transactions.", pending.len()); + debug!(target: "miner", "Attempting to push {} transactions.", engine_txs.len() + queue_txs.len()); - for tx in pending { + for transaction in engine_txs + .into_iter() + .chain(queue_txs.into_iter().map(|tx| tx.signed().clone())) + { let start = Instant::now(); - let transaction = tx.signed().clone(); let hash = transaction.hash(); let sender = transaction.sender(); @@ -670,7 +694,7 @@ impl Miner { // keep sealing enabled if any of the conditions is met let sealing_enabled = self.forced_sealing() || self.transaction_queue.has_local_pending_transactions() - || self.engine.seals_internally() == Some(true) + || self.engine.sealing_state() == SealingState::Ready || had_requests; let should_disable_sealing = !sealing_enabled; @@ -679,7 +703,7 @@ impl Miner { should_disable_sealing, self.forced_sealing(), self.transaction_queue.has_local_pending_transactions(), - self.engine.seals_internally(), + self.engine.sealing_state(), had_requests, ); @@ -862,6 +886,11 @@ impl Miner { } }; + if self.engine.sealing_state() != SealingState::External { + trace!(target: "miner", "prepare_pending_block: engine not sealing externally; not preparing"); + return BlockPreparationStatus::NotPrepared; + } + let preparation_status = if prepare_new { // -------------------------------------------------------------------------- // | NOTE Code below requires sealing locks. | @@ -896,7 +925,9 @@ impl Miner { fn prepare_and_update_sealing(&self, chain: &C) { // Make sure to do it after transaction is imported and lock is dropped. // We need to create pending block and enable sealing. - if self.engine.seals_internally().unwrap_or(false) + let sealing_state = self.engine.sealing_state(); + + if sealing_state == SealingState::Ready || self.prepare_pending_block(chain) == BlockPreparationStatus::NotPrepared { // If new block has not been prepared (means we already had one) @@ -924,21 +955,38 @@ impl miner::MinerService for Miner { self.params.write().extra_data = extra_data; } - fn set_author(&self, author: Author) { - self.params.write().author = author.address(); + fn set_author>>(&self, author: T) { + let author_opt = author.into(); + self.params.write().author = author_opt.as_ref().map(Author::address).unwrap_or_default(); - if let Author::Sealer(signer) = author { - if self.engine.seals_internally().is_some() { - // Enable sealing - self.sealing.lock().enabled = true; - // -------------------------------------------------------------------------- - // | NOTE Code below may require author and sealing locks | - // | (some `Engine`s call `EngineClient.update_sealing()`) | - // | Make sure to release the locks before calling that method. | - // -------------------------------------------------------------------------- - self.engine.set_signer(signer); - } else { - warn!("Setting an EngineSigner while Engine does not require one."); + match author_opt { + Some(Author::Sealer(signer)) => { + if self.engine.sealing_state() != SealingState::External { + // Enable sealing + self.sealing.lock().enabled = true; + // -------------------------------------------------------------------------- + // | NOTE Code below may require author and sealing locks | + // | (some `Engine`s call `EngineClient.update_sealing()`) | + // | Make sure to release the locks before calling that method. | + // -------------------------------------------------------------------------- + self.engine.set_signer(Some(signer)); + } else { + warn!("Setting an EngineSigner while Engine does not require one."); + } + } + Some(Author::External(_address)) => (), + None => { + // Clear the author. + if self.engine.sealing_state() != SealingState::External { + // Disable sealing. + self.sealing.lock().enabled = false; + // -------------------------------------------------------------------------- + // | NOTE Code below may require author and sealing locks | + // | (some `Engine`s call `EngineClient.update_sealing()`) | + // | Make sure to release the locks before calling that method. | + // -------------------------------------------------------------------------- + self.engine.set_signer(None); + } } } } @@ -1257,6 +1305,11 @@ impl miner::MinerService for Miner { return; } + let sealing_state = self.engine.sealing_state(); + if sealing_state == SealingState::NotReady { + return; + } + // -------------------------------------------------------------------------- // | NOTE Code below requires sealing locks. | // | Make sure to release the locks before calling that method. | @@ -1277,20 +1330,23 @@ impl miner::MinerService for Miner { } } - match self.engine.seals_internally() { - Some(true) => { + match sealing_state { + SealingState::Ready => { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); if self.seal_and_import_block_internally(chain, block) { trace!(target: "miner", "update_sealing: imported internally sealed block"); } return; } - Some(false) => { - trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now"); - // anyway, save the block for later use - self.sealing.lock().queue.set_pending(block); + SealingState::NotReady => { + unreachable!("We returned right after sealing_state was computed. qed.") } - None => { + // => { + // trace!(target: "miner", "update_sealing: engine is not keen to seal internally right now"); + // // anyway, save the block for later use + // self.sealing.lock().queue.set_pending(block); + // } + SealingState::External => { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); self.prepare_work(block, original_work_hash); } @@ -1305,7 +1361,7 @@ impl miner::MinerService for Miner { where C: BlockChain + CallContract + BlockProducer + SealedBlockImporter + Nonce + Sync, { - if self.engine.seals_internally().is_some() { + if self.engine.sealing_state() != SealingState::External { return None; } diff --git a/crates/ethcore/src/miner/mod.rs b/crates/ethcore/src/miner/mod.rs index ddd0f7630..1efb98197 100644 --- a/crates/ethcore/src/miner/mod.rs +++ b/crates/ethcore/src/miner/mod.rs @@ -152,7 +152,7 @@ pub trait MinerService: Send + Sync { /// Set info necessary to sign consensus messages and block authoring. /// /// On chains where sealing is done externally (e.g. PoW) we provide only reward beneficiary. - fn set_author(&self, author: Author); + fn set_author>>(&self, author: T); // Transaction Pool diff --git a/crates/ethcore/src/snapshot/service.rs b/crates/ethcore/src/snapshot/service.rs index c212c3d7f..3b31d97ff 100644 --- a/crates/ethcore/src/snapshot/service.rs +++ b/crates/ethcore/src/snapshot/service.rs @@ -1012,7 +1012,8 @@ mod tests { #[test] fn sends_async_messages() { let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()]; - let client = generate_dummy_client_with_spec_and_data(Spec::new_null, 400, 5, &gas_prices); + let client = + generate_dummy_client_with_spec_and_data(Spec::new_null, 400, 5, &gas_prices, false); let service = IoService::::start("Test").unwrap(); let spec = Spec::new_test(); diff --git a/crates/ethcore/src/snapshot/tests/service.rs b/crates/ethcore/src/snapshot/tests/service.rs index 801c11a20..7046d4b47 100644 --- a/crates/ethcore/src/snapshot/tests/service.rs +++ b/crates/ethcore/src/snapshot/tests/service.rs @@ -46,8 +46,13 @@ fn restored_is_equivalent() { const TX_PER: usize = 5; let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()]; - let client = - generate_dummy_client_with_spec_and_data(Spec::new_null, NUM_BLOCKS, TX_PER, &gas_prices); + let client = generate_dummy_client_with_spec_and_data( + Spec::new_null, + NUM_BLOCKS, + TX_PER, + &gas_prices, + false, + ); let tempdir = TempDir::new("").unwrap(); let client_db = tempdir.path().join("client_db"); @@ -112,7 +117,8 @@ fn restored_is_equivalent() { #[test] fn guards_delete_folders() { let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()]; - let client = generate_dummy_client_with_spec_and_data(Spec::new_null, 400, 5, &gas_prices); + let client = + generate_dummy_client_with_spec_and_data(Spec::new_null, 400, 5, &gas_prices, false); let spec = Spec::new_null(); let tempdir = TempDir::new("").unwrap(); @@ -178,7 +184,7 @@ fn keep_ancient_blocks() { let spec_f = Spec::new_null; let spec = spec_f(); let client = - generate_dummy_client_with_spec_and_data(spec_f, NUM_BLOCKS as u32, 5, &gas_prices); + generate_dummy_client_with_spec_and_data(spec_f, NUM_BLOCKS as u32, 5, &gas_prices, false); let bc = client.chain(); @@ -298,7 +304,7 @@ fn recover_aborted_recovery() { const NUM_BLOCKS: u32 = 400; let gas_prices = vec![1.into(), 2.into(), 3.into(), 999.into()]; let client = - generate_dummy_client_with_spec_and_data(Spec::new_null, NUM_BLOCKS, 5, &gas_prices); + generate_dummy_client_with_spec_and_data(Spec::new_null, NUM_BLOCKS, 5, &gas_prices, false); let spec = Spec::new_null(); let tempdir = TempDir::new("oe_snapshot").unwrap(); diff --git a/crates/ethcore/src/spec/spec.rs b/crates/ethcore/src/spec/spec.rs index 2d0bb719d..395afea39 100644 --- a/crates/ethcore/src/spec/spec.rs +++ b/crates/ethcore/src/spec/spec.rs @@ -1063,6 +1063,12 @@ impl Spec { load_bundled!("test/constructor") } + /// Create a new Spec with Autority Round randomness contract + #[cfg(any(test, feature = "test-helpers"))] + pub fn new_test_round_randomness_contract() -> Spec { + load_bundled!("test/authority_round_randomness_contract") + } + /// Create a new Spec with AuthorityRound consensus which does internal sealing (not /// requiring work). /// Accounts with secrets keccak("0") and keccak("1") are the validators. diff --git a/crates/ethcore/src/test_helpers.rs b/crates/ethcore/src/test_helpers.rs index 13a145571..a61586291 100644 --- a/crates/ethcore/src/test_helpers.rs +++ b/crates/ethcore/src/test_helpers.rs @@ -112,7 +112,7 @@ pub fn create_test_block_with_data( /// Generates dummy client (not test client) with corresponding amount of blocks pub fn generate_dummy_client(block_number: u32) -> Arc { - generate_dummy_client_with_spec_and_data(Spec::new_test, block_number, 0, &[]) + generate_dummy_client_with_spec_and_data(Spec::new_test, block_number, 0, &[], false) } /// Generates dummy client (not test client) with corresponding amount of blocks and txs per every block @@ -126,6 +126,7 @@ pub fn generate_dummy_client_with_data( block_number, txs_per_block, tx_gas_prices, + false, ) } @@ -134,7 +135,7 @@ pub fn generate_dummy_client_with_spec(test_spec: F) -> Arc where F: Fn() -> Spec, { - generate_dummy_client_with_spec_and_data(test_spec, 0, 0, &[]) + generate_dummy_client_with_spec_and_data(test_spec, 0, 0, &[], false) } /// Generates dummy client (not test client) with corresponding amount of blocks, txs per block and spec @@ -143,6 +144,7 @@ pub fn generate_dummy_client_with_spec_and_data( block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256], + force_sealing: bool, ) -> Arc where F: Fn() -> Spec, @@ -150,11 +152,13 @@ where let test_spec = test_spec(); let client_db = new_db(); + let miner = Miner::new_for_tests_force_sealing(&test_spec, None, force_sealing); + let client = Client::new( ClientConfig::default(), &test_spec, client_db, - Arc::new(Miner::new_for_tests(&test_spec, None)), + Arc::new(miner), IoChannel::disconnected(), ) .unwrap(); diff --git a/crates/ethcore/src/trace/types/trace.rs b/crates/ethcore/src/trace/types/trace.rs index 62a1160dd..02034f513 100644 --- a/crates/ethcore/src/trace/types/trace.rs +++ b/crates/ethcore/src/trace/types/trace.rs @@ -51,7 +51,7 @@ impl CreateResult { } } -/// Description of a _call_ action, either a `CALL` operation or a message transction. +/// Description of a _call_ action, either a `CALL` operation or a message transaction. #[derive(Debug, Clone, PartialEq, RlpEncodable, RlpDecodable)] pub struct Call { /// The sending account. @@ -102,7 +102,7 @@ impl Call { } } -/// Description of a _create_ action, either a `CREATE` operation or a create transction. +/// Description of a _create_ action, either a `CREATE` operation or a create transaction. #[derive(Debug, Clone, PartialEq, RlpEncodable, RlpDecodable)] pub struct Create { /// The address of the creator. diff --git a/crates/ethcore/src/tx_filter.rs b/crates/ethcore/src/tx_filter.rs index 3bcc7df90..30f0744e7 100644 --- a/crates/ethcore/src/tx_filter.rs +++ b/crates/ethcore/src/tx_filter.rs @@ -35,6 +35,10 @@ use_contract!( "res/contracts/tx_acl_deprecated.json" ); use_contract!(transact_acl, "res/contracts/tx_acl.json"); +use_contract!( + transact_acl_gas_price, + "res/contracts/tx_acl_gas_price.json" +); const MAX_CACHE_SIZE: usize = 4096; @@ -99,6 +103,7 @@ impl TransactionFilter { let sender = transaction.sender(); let value = transaction.tx().value; + let gas_price = transaction.tx().gas_price; let key = (*parent_hash, sender); if let Some(permissions) = permission_cache.get_mut(&key) { @@ -138,6 +143,24 @@ impl TransactionFilter { (tx_permissions::NONE, true) }) } + 3 => { + trace!(target: "tx_filter", "Using filter with gas price and data"); + let (data, decoder) = + transact_acl_gas_price::functions::allowed_tx_types::call( + sender, + to, + value, + gas_price, + transaction.tx().data.clone(), + ); + client.call_contract(BlockId::Hash(*parent_hash), contract_address, data) + .and_then(|value| decoder.decode(&value).map_err(|e| e.to_string())) + .map(|(p, f)| (p.low_u32(), f)) + .unwrap_or_else(|e| { + error!(target: "tx_filter", "Error calling tx permissions contract: {:?}", e); + (tx_permissions::NONE, true) + }) + } _ => { error!(target: "tx_filter", "Unknown version of tx permissions contract is used"); (tx_permissions::NONE, true) @@ -161,10 +184,7 @@ impl TransactionFilter { if filter_only_sender { permission_cache.insert((*parent_hash, sender), permissions); } - trace!(target: "tx_filter", - "Given transaction data: sender: {:?} to: {:?} value: {}. Permissions required: {:X}, got: {:X}", - sender, to, value, tx_type, permissions - ); + trace!(target: "tx_filter", "Given transaction data: sender: {:?} to: {:?} value: {}, gas_price: {}. Permissions required: {:X}, got: {:X}", sender, to, value, gas_price, tx_type, permissions); permissions & tx_type != 0 } } @@ -185,7 +205,7 @@ mod test { /// Contract code: https://gist.github.com/VladLupashevskyi/84f18eabb1e4afadf572cf92af3e7e7f #[test] - fn transaction_filter() { + fn transaction_filter_ver_2() { let spec_data = include_str!("../res/chainspec/test/contract_ver_2_genesis.json"); let db = test_helpers::new_db(); @@ -418,6 +438,74 @@ mod test { )); } + /// Contract code: res/tx_permission_tests/contract_ver_3.sol + #[test] + fn transaction_filter_ver_3() { + let spec_data = include_str!("../res/chainspec/test/contract_ver_3_genesis.json"); + + let db = test_helpers::new_db(); + let tempdir = TempDir::new("").unwrap(); + let spec = Spec::load(&tempdir.path(), spec_data.as_bytes()).unwrap(); + + let client = Client::new( + ClientConfig::default(), + &spec, + db, + Arc::new(Miner::new_for_tests(&spec, None)), + IoChannel::disconnected(), + ) + .unwrap(); + let key1 = KeyPair::from_secret( + Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(), + ) + .unwrap(); + + // The only difference to version 2 is that the contract now knows the transaction's gas price and data. + // So we only test those: The contract allows only transactions with either nonzero gas price or short data. + + let filter = TransactionFilter::from_params(spec.params()).unwrap(); + let mut tx = TypedTransaction::Legacy(Transaction::default()); + tx.tx_mut().action = + Action::Call(Address::from_str("0000000000000000000000000000000000000042").unwrap()); + tx.tx_mut().data = b"01234567".to_vec(); + tx.tx_mut().gas_price = 0.into(); + + let genesis = client.block_hash(BlockId::Latest).unwrap(); + let block_number = 1; + + // Data too long and gas price zero. This transaction is not allowed. + assert!(!filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + + // But if we either set a nonzero gas price or short data or both, it is allowed. + tx.tx_mut().gas_price = 1.into(); + assert!(filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + tx.tx_mut().data = b"01".to_vec(); + assert!(filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + tx.tx_mut().gas_price = 0.into(); + assert!(filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + } + /// Contract code: https://gist.github.com/arkpar/38a87cb50165b7e683585eec71acb05a #[test] fn transaction_filter_deprecated() { diff --git a/crates/ethcore/src/verification/verification.rs b/crates/ethcore/src/verification/verification.rs index f09cef8d3..6a11b8b18 100644 --- a/crates/ethcore/src/verification/verification.rs +++ b/crates/ethcore/src/verification/verification.rs @@ -83,7 +83,16 @@ pub fn verify_block_basic( } } - // t_nb 4.6 call engine.gas_limit_override (Used only by Aura) TODO added in new version + // t_nb 4.6 call engine.gas_limit_override (Used only by Aura) + if let Some(gas_limit) = engine.gas_limit_override(&block.header) { + if *block.header.gas_limit() != gas_limit { + return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { + min: Some(gas_limit), + max: Some(gas_limit), + found: *block.header.gas_limit(), + }))); + } + } // t_nb 4.7 for every transaction call engine.verify_transaction_basic for t in &block.transactions { @@ -358,25 +367,26 @@ pub fn verify_header_params( found: *header.gas_used(), }))); } - let min_gas_limit = engine.params().min_gas_limit; - if header.gas_limit() < &min_gas_limit { - return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { - min: Some(min_gas_limit), - max: None, - found: *header.gas_limit(), - }))); - } - if let Some(limit) = engine.maximum_gas_limit() { - if header.gas_limit() > &limit { - return Err(From::from(::error::BlockError::InvalidGasLimit( - OutOfBounds { + if engine.gas_limit_override(header).is_none() { + let min_gas_limit = engine.min_gas_limit(); + if header.gas_limit() < &min_gas_limit { + return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { + min: Some(min_gas_limit), + max: None, + found: *header.gas_limit(), + }))); + } + if let Some(limit) = engine.maximum_gas_limit() { + if header.gas_limit() > &limit { + return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: None, max: Some(limit), found: *header.gas_limit(), - }, - ))); + }))); + } } } + let maximum_extra_data_size = engine.maximum_extra_data_size(); if header.number() != 0 && header.extra_data().len() > maximum_extra_data_size { return Err(From::from(BlockError::ExtraDataOutOfBounds(OutOfBounds { @@ -435,8 +445,6 @@ fn verify_parent(header: &Header, parent: &Header, engine: &dyn EthEngine) -> Re "Parent hash should already have been verified; qed" ); - let gas_limit_divisor = engine.params().gas_limit_bound_divisor; - if !engine.is_timestamp_valid(header.timestamp(), parent.timestamp()) { let now = SystemTime::now(); let min = CheckedSystemTime::checked_add( @@ -468,15 +476,18 @@ fn verify_parent(header: &Header, parent: &Header, engine: &dyn EthEngine) -> Re .into()); } - let parent_gas_limit = *parent.gas_limit(); - let min_gas = parent_gas_limit - parent_gas_limit / gas_limit_divisor; - let max_gas = parent_gas_limit + parent_gas_limit / gas_limit_divisor; - if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas { - return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { - min: Some(min_gas), - max: Some(max_gas), - found: *header.gas_limit(), - }))); + if engine.gas_limit_override(header).is_none() { + let gas_limit_divisor = engine.params().gas_limit_bound_divisor; + let parent_gas_limit = *parent.gas_limit(); + let min_gas = parent_gas_limit - parent_gas_limit / gas_limit_divisor; + let max_gas = parent_gas_limit + parent_gas_limit / gas_limit_divisor; + if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas { + return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { + min: Some(min_gas), + max: Some(max_gas), + found: *header.gas_limit(), + }))); + } } Ok(()) @@ -740,7 +751,7 @@ mod tests { // that's an invalid transaction list rlp let invalid_transactions = vec![vec![0u8]]; header.set_transactions_root(ordered_trie_root(&invalid_transactions)); - header.set_gas_limit(engine.params().min_gas_limit); + header.set_gas_limit(engine.min_gas_limit()); rlp.append(&header); rlp.append_list::, _>(&invalid_transactions); rlp.append_raw(&rlp::EMPTY_LIST_RLP, 1); @@ -759,7 +770,7 @@ mod tests { let spec = Spec::new_test(); let engine = &*spec.engine; - let min_gas_limit = engine.params().min_gas_limit; + let min_gas_limit = engine.min_gas_limit(); good.set_gas_limit(min_gas_limit); good.set_timestamp(40); good.set_number(10); diff --git a/crates/ethcore/types/src/transaction/error.rs b/crates/ethcore/types/src/transaction/error.rs index d3351cad8..d90be937f 100644 --- a/crates/ethcore/types/src/transaction/error.rs +++ b/crates/ethcore/types/src/transaction/error.rs @@ -131,7 +131,7 @@ impl fmt::Display for Error { InvalidChainId => "Transaction of this chain ID is not allowed on this chain.".into(), InvalidSignature(ref err) => format!("Transaction has invalid signature: {}.", err), NotAllowed => { - "Sender does not have permissions to execute this type of transction".into() + "Sender does not have permissions to execute this type of transaction".into() } TooBig => "Transaction too big".into(), InvalidRlp(ref err) => format!("Transaction has invalid RLP structure: {}.", err), diff --git a/crates/ethjson/src/spec/authority_round.rs b/crates/ethjson/src/spec/authority_round.rs index f84e933a9..a2893394f 100644 --- a/crates/ethjson/src/spec/authority_round.rs +++ b/crates/ethjson/src/spec/authority_round.rs @@ -14,10 +14,33 @@ // You should have received a copy of the GNU General Public License // along with OpenEthereum. If not, see . -//! Authority params deserialization. +//! Authority Round parameter deserialization. +//! +//! Here is an example of input parameters where the step duration is constant at 5 seconds, the set +//! of validators is decided by the contract at address `0x10..01` starting from block 0, and where +//! the address of the contract that computes block rewards is set to `0x20..02` for blocks 0 +//! through 41 and to `0x30.03` for all blocks starting from block 42. +//! +//! ```ignore +//! "params": { +//! "stepDuration": "5", +//! "validators": { +//! "multi": { +//! "0": { +//! "contract": "0x1000000000000000000000000000000000000001" +//! } +//! } +//! }, +//! "blockRewardContractTransitions": { +//! "0": "0x2000000000000000000000000000000000000002", +//! "42": "0x3000000000000000000000000000000000000003" +//! } +//! } +//! ``` -use super::{BlockReward, ValidatorSet}; +use super::{BlockReward, StepDuration, ValidatorSet}; use crate::{bytes::Bytes, hash::Address, uint::Uint}; +use std::collections::BTreeMap; /// Authority params deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -25,7 +48,7 @@ use crate::{bytes::Bytes, hash::Address, uint::Uint}; #[serde(rename_all = "camelCase")] pub struct AuthorityRoundParams { /// Block duration, in seconds. - pub step_duration: Uint, + pub step_duration: StepDuration, /// Valid authorities pub validators: ValidatorSet, /// Starting step. Determined automatically if not specified. @@ -39,11 +62,24 @@ pub struct AuthorityRoundParams { pub immediate_transitions: Option, /// Reward per block in wei. pub block_reward: Option, - /// Block at which the block reward contract should start being used. + /// Block at which the block reward contract should start being used. This option allows one to + /// add a single block reward contract transition and is compatible with the multiple address + /// option `block_reward_contract_transitions` below. pub block_reward_contract_transition: Option, - /// Block reward contract address (setting the block reward contract - /// overrides the static block reward definition). + /// Block reward contract address which overrides the `block_reward` setting. This option allows + /// one to add a single block reward contract address and is compatible with the multiple + /// address option `block_reward_contract_transitions` below. pub block_reward_contract_address: Option
, + /// Block reward contract addresses with their associated starting block numbers. + /// + /// Setting the block reward contract overrides `block_reward`. If the single block reward + /// contract address is also present then it is added into the map at the block number stored in + /// `block_reward_contract_transition` or 0 if that block number is not provided. Therefore both + /// a single block reward contract transition and a map of reward contract transitions can be + /// used simulataneously in the same configuration. In such a case the code requires that the + /// block number of the single transition is strictly less than any of the block numbers in the + /// map. + pub block_reward_contract_transitions: Option>, /// Block reward code. This overrides the block reward contract address. pub block_reward_contract_code: Option, /// Block at which maximum uncle count should be considered. @@ -56,13 +92,23 @@ pub struct AuthorityRoundParams { pub maximum_empty_steps: Option, /// Strict validation of empty steps transition block. pub strict_empty_steps_transition: Option, + /// First block for which a 2/3 quorum (instead of 1/2) is required. + pub two_thirds_majority_transition: Option, + /// The random number contract's address, or a map of contract transitions. + pub randomness_contract_address: Option>, + /// The addresses of contracts that determine the block gas limit starting from the block number + /// associated with each of those contracts. + pub block_gas_limit_contract_transitions: Option>, + /// The block number at which the consensus engine switches from AuRa to AuRa with POSDAO + /// modifications. + pub posdao_transition: Option, } /// Authority engine deserialization. #[derive(Debug, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] pub struct AuthorityRound { - /// Ethash params. + /// Authority Round parameters. pub params: AuthorityRoundParams, } @@ -73,7 +119,10 @@ mod tests { use super::BlockReward; use crate::{ hash::Address, - spec::{authority_round::AuthorityRound, validator_set::ValidatorSet}, + spec::{ + authority_round::AuthorityRound, step_duration::StepDuration, + validator_set::ValidatorSet, + }, uint::Uint, }; use ethereum_types::{H160, U256}; @@ -92,12 +141,23 @@ mod tests { "validateStepTransition": 150, "blockReward": 5000000, "maximumUncleCountTransition": 10000000, - "maximumUncleCount": 5 + "maximumUncleCount": 5, + "randomnessContractAddress": { + "10": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "20": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "blockGasLimitContractTransitions": { + "10": "0x1000000000000000000000000000000000000001", + "20": "0x2000000000000000000000000000000000000002" + } } }"#; let deserialized: AuthorityRound = serde_json::from_str(s).unwrap(); - assert_eq!(deserialized.params.step_duration, Uint(U256::from(0x02))); + assert_eq!( + deserialized.params.step_duration, + StepDuration::Single(Uint(U256::from(2))) + ); assert_eq!( deserialized.params.validators, ValidatorSet::List(vec![Address( @@ -115,9 +175,34 @@ mod tests { Some(Uint(5.into())) ); assert_eq!( - deserialized.params.block_reward, - Some(BlockReward::Single(Uint(5000000.into()))) - ) + deserialized.params.randomness_contract_address.unwrap(), + vec![ + ( + Uint(10.into()), + Address(H160::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap()) + ), + ( + Uint(20.into()), + Address(H160::from_str("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").unwrap()) + ), + ] + .into_iter() + .collect() + ); + let expected_bglc = [ + ( + Uint(10.into()), + Address(H160::from_str("1000000000000000000000000000000000000001").unwrap()), + ), + ( + Uint(20.into()), + Address(H160::from_str("2000000000000000000000000000000000000002").unwrap()), + ), + ]; + assert_eq!( + deserialized.params.block_gas_limit_contract_transitions, + Some(expected_bglc.to_vec().into_iter().collect()) + ); } #[test] @@ -136,7 +221,10 @@ mod tests { }"#; let deserialized: AuthorityRound = serde_json::from_str(s).unwrap(); - assert_eq!(deserialized.params.step_duration, Uint(U256::from(0x02))); + assert_eq!( + deserialized.params.step_duration, + StepDuration::Single(Uint(U256::from(0x02))) + ); assert_eq!( deserialized.params.validators, ValidatorSet::Contract(Address( diff --git a/crates/ethjson/src/spec/mod.rs b/crates/ethjson/src/spec/mod.rs index 12955a2d9..208aa636e 100644 --- a/crates/ethjson/src/spec/mod.rs +++ b/crates/ethjson/src/spec/mod.rs @@ -30,6 +30,7 @@ pub mod params; pub mod seal; pub mod spec; pub mod state; +pub mod step_duration; pub mod validator_set; pub use self::{ @@ -47,5 +48,6 @@ pub use self::{ seal::{AuthorityRoundSeal, Ethereum, Seal, TendermintSeal}, spec::{ForkSpec, Spec}, state::State, + step_duration::StepDuration, validator_set::ValidatorSet, }; diff --git a/crates/ethjson/src/spec/step_duration.rs b/crates/ethjson/src/spec/step_duration.rs new file mode 100644 index 000000000..8e14b70e8 --- /dev/null +++ b/crates/ethjson/src/spec/step_duration.rs @@ -0,0 +1,36 @@ +// Copyright 2015-2019 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 . + +//! Step duration configuration parameter + +use std::collections::BTreeMap; + +use serde::Deserialize; + +use crate::uint::Uint; + +/// Step duration can be specified either as a `Uint` (in seconds), in which case it will be +/// constant, or as a list of pairs consisting of a timestamp of type `Uint` and a duration, in +/// which case the duration of a step will be determined by a mapping arising from that list. +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum StepDuration { + /// Duration of all steps. + Single(Uint), + /// Step duration transitions: a mapping of timestamp to step durations. + Transitions(BTreeMap), +} diff --git a/crates/rpc/src/v1/helpers/engine_signer.rs b/crates/rpc/src/v1/helpers/engine_signer.rs index 365ddae6a..f98e8111f 100644 --- a/crates/rpc/src/v1/helpers/engine_signer.rs +++ b/crates/rpc/src/v1/helpers/engine_signer.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use accounts::AccountProvider; -use crypto::publickey::{self, Address}; +use crypto::publickey::{self, Address, Error, Public}; use ethkey::Password; /// An implementation of EngineSigner using internal account management. @@ -49,7 +49,22 @@ impl ethcore::engines::EngineSigner for EngineSigner { } } + fn decrypt(&self, auth_data: &[u8], cipher: &[u8]) -> Result, Error> { + self.accounts + .decrypt(self.address, None, auth_data, cipher) + .map_err(|e| { + warn!("Unable to decrypt message: {:?}", e); + Error::InvalidMessage + }) + } + fn address(&self) -> Address { self.address } + + fn public(&self) -> Option { + self.accounts + .account_public(self.address, &self.password) + .ok() + } } diff --git a/crates/rpc/src/v1/impls/parity_set.rs b/crates/rpc/src/v1/impls/parity_set.rs index 62c1d801c..5e2b137ae 100644 --- a/crates/rpc/src/v1/impls/parity_set.rs +++ b/crates/rpc/src/v1/impls/parity_set.rs @@ -157,6 +157,11 @@ where Ok(true) } + fn clear_engine_signer(&self) -> Result { + self.miner.set_author(None); + Ok(true) + } + fn add_reserved_peer(&self, peer: String) -> Result { match self.net.add_reserved_peer(peer) { Ok(()) => Ok(true), diff --git a/crates/rpc/src/v1/tests/helpers/miner_service.rs b/crates/rpc/src/v1/tests/helpers/miner_service.rs index eb49da6ad..3af4a04aa 100644 --- a/crates/rpc/src/v1/tests/helpers/miner_service.rs +++ b/crates/rpc/src/v1/tests/helpers/miner_service.rs @@ -134,10 +134,16 @@ impl MinerService for TestMinerService { self.authoring_params.read().clone() } - fn set_author(&self, author: miner::Author) { - self.authoring_params.write().author = author.address(); - if let miner::Author::Sealer(signer) = author { - *self.signer.write() = Some(signer); + fn set_author>>(&self, author: T) { + let author_opt = author.into(); + self.authoring_params.write().author = author_opt + .as_ref() + .map(miner::Author::address) + .unwrap_or_default(); + match author_opt { + Some(miner::Author::Sealer(signer)) => *self.signer.write() = Some(signer), + Some(miner::Author::External(_addr)) => (), + None => *self.signer.write() = None, } } diff --git a/crates/rpc/src/v1/traits/parity_set.rs b/crates/rpc/src/v1/traits/parity_set.rs index 2d64e613d..fbaacb0ca 100644 --- a/crates/rpc/src/v1/traits/parity_set.rs +++ b/crates/rpc/src/v1/traits/parity_set.rs @@ -57,6 +57,10 @@ pub trait ParitySet { #[rpc(name = "parity_setEngineSignerSecret")] fn set_engine_signer_secret(&self, _: H256) -> Result; + /// Unsets the engine signer account address. + #[rpc(name = "parity_clearEngineSigner")] + fn clear_engine_signer(&self) -> Result; + /// Sets the limits for transaction queue. #[rpc(name = "parity_setTransactionsLimit")] fn set_transactions_limit(&self, _: usize) -> Result; diff --git a/crates/vm/evm/benches/basic.rs b/crates/vm/evm/benches/basic.rs index 4d2d8e4a6..6199aa910 100644 --- a/crates/vm/evm/benches/basic.rs +++ b/crates/vm/evm/benches/basic.rs @@ -28,6 +28,7 @@ extern crate parking_lot; extern crate rustc_hex; extern crate vm; +use bytes::Bytes; use criterion::{black_box, Bencher, Criterion}; use ethereum_types::{Address, U256}; use evm::Factory; @@ -37,6 +38,28 @@ use vm::{tests::FakeExt, ActionParams, Ext, GasLeft, Result}; criterion_group!( basic, + mul500, + mul1000, + div500, + div1000, + sdiv500, + sdiv1000, + mod500, + mod1000, + smod500, + smod1000, + addmod500, + addmod1000, + mulmod500, + mulmod1000, + mulmod1_500, + mulmod1_1000, + mulmod5_500, + mulmod5_1000, + mulmod11_500, + mulmod11_1000, + mulmod_big_500, + mulmod_big_1000, simple_loop_log0_usize, simple_loop_log0_u256, mem_gas_calculation_same_usize, @@ -206,3 +229,188 @@ fn result(r: Result) -> U256 { _ => U256::zero(), } } + +/// Runs a given EVM bytecode. +fn run_code(b: &mut Bencher, code: Bytes) { + let factory = Factory::default(); + let mut ext = FakeExt::new(); + b.iter(|| { + let mut params = ActionParams::default(); + params.address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); + params.gas = U256::MAX; + params.code = Some(Arc::new(black_box(code.clone()))); + let vm = factory.create(params, ext.schedule(), 0); + result(vm.exec(&mut ext).ok().unwrap()) + }); +} + +/// Compute mulmod(U256::MAX, U256::MAX, 1) 500 times. +fn mulmod1_500(b: &mut Criterion) { + b.bench_function("mulmod modulo 1, 500 times", |b| { + run_code(b, "6101f45b6001900360017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80095080600357".from_hex().unwrap()); + }); +} + +/// Compute mulmod(U256::MAX, U256::MAX, 1) 1000 times. +fn mulmod1_1000(b: &mut Criterion) { + b.bench_function("mulmod modulo 1, 1000 times", |b| { + run_code(b, "6103e85b6001900360017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80095080600357".from_hex().unwrap()); + }); +} + +/// Compute mulmod(U256::MAX, U256::MAX, 5) 500 times. +fn mulmod5_500(b: &mut Criterion) { + b.bench_function("mulmod modulo 5, 500 times", |b| { + run_code(b, "6101f45b6001900360057fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80095080600357".from_hex().unwrap()); + }); +} + +/// Compute mulmod(U256::MAX, U256::MAX, 5) 1000 times. +fn mulmod5_1000(b: &mut Criterion) { + b.bench_function("mulmod modulo 5, 1000 times", |b| { + run_code(b, "6103e85b6001900360057fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80095080600357".from_hex().unwrap()); + }); +} + +/// Compute mulmod(U256::MAX, U256::MAX, 11) 500 times. +fn mulmod11_500(b: &mut Criterion) { + b.bench_function("mulmod modulo 11, 500 times", |b| { + run_code(b, "6101f45b60019003600b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80095080600357".from_hex().unwrap()); + }); +} + +/// Compute mulmod(U256::MAX, U256::MAX, 11) 1000 times. +fn mulmod11_1000(b: &mut Criterion) { + b.bench_function("mulmod modulo 11, 1000 times", |b| { + run_code(b, "6103e85b60019003600b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80095080600357".from_hex().unwrap()); + }); +} + +/// Compute mulmod(U256::MAX, U256::MAX, 0x58bca9711298bc76cd73f173352c8bc1d1640f977c1ec9a849dfde6fdbfbd591) 500 times. +fn mulmod_big_500(b: &mut Criterion) { + b.bench_function("mulmod modulo random 256-bit number, 500 times", |b| { + run_code(b, "6101f45b600190037f58bca9711298bc76cd73f173352c8bc1d1640f977c1ec9a849dfde6fdbfbd5917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80095080600357".from_hex().unwrap()); + }); +} + +/// Compute mulmod(U256::MAX, U256::MAX, 0x58bca9711298bc76cd73f173352c8bc1d1640f977c1ec9a849dfde6fdbfbd591) 1000 times. +fn mulmod_big_1000(b: &mut Criterion) { + b.bench_function("mulmod modulo random 256-bit number, 1000 times", |b| { + run_code(b, "6103e85b600190037f58bca9711298bc76cd73f173352c8bc1d1640f977c1ec9a849dfde6fdbfbd5917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80095080600357".from_hex().unwrap()); + }); +} + +/// Compute mulmod(a, b, c) for random 256-bit a, b and c. Iterate 500 times. +/// +/// Source: +/// ``` +/// PUSH2 0x01F4 +/// JUMPDEST +/// PUSH1 0x01 +/// SWAP1 +/// SUB +/// PUSH32 0x5ed6db9489224124a1a4110ec8bec8b01369c8b549a4b8c4388a1796dc35a937 +/// PUSH32 0xb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db +/// PUSH32 0xcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca +/// MULMOD +/// POP +/// DUP1 +/// PUSH1 0x03 +/// JUMPI +/// ``` +fn mulmod500(b: &mut Criterion) { + b.bench_function("mulmod randomly generated ints, 500 times", |b| { + run_code(b, "6101f45b600190037f5ed6db9489224124a1a4110ec8bec8b01369c8b549a4b8c4388a1796dc35a9377fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca095080600357".from_hex().unwrap()); + }); +} + +/// Compute mulmod(a, b, c) for random 256-bit a, b and c. Iterate 1000 times. +fn mulmod1000(b: &mut Criterion) { + b.bench_function("mulmod randomly generated ints, 1000 times", |b| { + run_code(b, "6103e85b600190037f5ed6db9489224124a1a4110ec8bec8b01369c8b549a4b8c4388a1796dc35a9377fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca095080600357".from_hex().unwrap()); + }); +} + +/// Compute addmod(a, b, c) for random 256-bit a, b and c. Iterate 500 times. +fn addmod500(b: &mut Criterion) { + b.bench_function("addmod randomly generated ints, 500 times", |b| { + run_code(b, "6101f45b600190037f5ed6db9489224124a1a4110ec8bec8b01369c8b549a4b8c4388a1796dc35a9377fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca085080600357".from_hex().unwrap()); + }); +} + +/// Compute addmod(a, b, c) for random 256-bit a, b and c. Iterate 1000 times. +fn addmod1000(b: &mut Criterion) { + b.bench_function("addmod randomly generated ints, 1000 times", |b| { + run_code(b, "6103e85b600190037f5ed6db9489224124a1a4110ec8bec8b01369c8b549a4b8c4388a1796dc35a9377fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca085080600357".from_hex().unwrap()); + }); +} + +/// Compute mul(a, b) for random 256-bit a and b. Iterate 500 times. +fn mul500(b: &mut Criterion) { + b.bench_function("mul randomly generated ints, 500 times", |b| { + run_code(b, "6101f45b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca025080600357".from_hex().unwrap()); + }); +} + +/// Compute mul(a, b) for random 256-bit a and b. Iterate 1000 times. +fn mul1000(b: &mut Criterion) { + b.bench_function("mul randomly generated ints, 1000 times", |b| { + run_code(b, "6103e85b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca025080600357".from_hex().unwrap()); + }); +} + +/// Compute div(a, b) for random 256-bit a and b. Iterate 500 times. +fn div500(b: &mut Criterion) { + b.bench_function("div randomly generated ints, 500 times", |b| { + run_code(b, "6101f45b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca045080600357".from_hex().unwrap()); + }); +} + +/// Compute div(a, b) for random 256-bit a and b. Iterate 1000 times. +fn div1000(b: &mut Criterion) { + b.bench_function("div randomly generated ints, 1000 times", |b| { + run_code(b, "6103e85b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca045080600357".from_hex().unwrap()); + }); +} + +/// Compute sdiv(a, b) for random 256-bit a and b. Iterate 500 times. +fn sdiv500(b: &mut Criterion) { + b.bench_function("sdiv randomly generated ints, 500 times", |b| { + run_code(b, "6101f45b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca055080600357".from_hex().unwrap()); + }); +} + +/// Compute sdiv(a, b) for random 256-bit a and b. Iterate 1000 times. +fn sdiv1000(b: &mut Criterion) { + b.bench_function("sdiv randomly generated ints, 1000 times", |b| { + run_code(b, "6103e85b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca055080600357".from_hex().unwrap()); + }); +} + +/// Compute mod(a, b) for random 256-bit a and b. Iterate 500 times. +fn mod500(b: &mut Criterion) { + b.bench_function("mod randomly generated ints, 500 times", |b| { + run_code(b, "6101f45b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca065080600357".from_hex().unwrap()); + }); +} + +/// Compute mod(a, b) for random 256-bit a and b. Iterate 1000 times. +fn mod1000(b: &mut Criterion) { + b.bench_function("mod randomly generated ints, 1000 times", |b| { + run_code(b, "6103e85b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca065080600357".from_hex().unwrap()); + }); +} + +/// Compute smod(a, b) for random 256-bit a and b. Iterate 500 times. +fn smod500(b: &mut Criterion) { + b.bench_function("smod randomly generated ints, 500 times", |b| { + run_code(b, "6101f45b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca075080600357".from_hex().unwrap()); + }); +} + +/// Compute smod(a, b) for random 256-bit a and b. Iterate 1000 times. +fn smod1000(b: &mut Criterion) { + b.bench_function("smod randomly generated ints, 1000 times", |b| { + run_code(b, "6103e85b600190037fb8e0a2b6b1587398c28bf9e9d34ea24ba34df308eec2acedca363b2fce2c25db7fcc2de1f8ec6cc9a24ed2c48b856637f9e45f0a5feee21a196aa42a290ef454ca075080600357".from_hex().unwrap()); + }); +}