diff --git a/Cargo.lock b/Cargo.lock index f533dd956..449940980 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,7 +284,7 @@ version = "0.1.0" dependencies = [ "byteorder", "criterion 0.3.0", - "ethbloom", + "ethbloom 0.9.2", "parking_lot 0.11.1", "tempdir", "tiny-keccak 1.5.0", @@ -421,7 +421,7 @@ dependencies = [ name = "cli-signer" version = "1.4.0" dependencies = [ - "ethereum-types", + "ethereum-types 0.9.2", "futures", "parity-rpc", "parity-rpc-client", @@ -450,7 +450,7 @@ dependencies = [ name = "common-types" version = "0.1.0" dependencies = [ - "ethereum-types", + "ethereum-types 0.9.2", "ethkey", "hex", "inflate", @@ -750,7 +750,7 @@ name = "dir" version = "0.1.2" dependencies = [ "app_dirs", - "ethereum-types", + "ethereum-types 0.9.2", "home 0.3.4", "journaldb", ] @@ -785,7 +785,7 @@ name = "eip-712" version = "0.1.0" dependencies = [ "ethabi", - "ethereum-types", + "ethereum-types 0.9.2", "failure", "indexmap", "itertools 0.7.11", @@ -871,7 +871,7 @@ dependencies = [ "num-bigint 0.2.3", "num-traits 0.2.8", "once_cell", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -891,7 +891,7 @@ version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052a565e3de82944527d6d10a465697e6bb92476b772ca7141080c901f6a63c6" dependencies = [ - "ethereum-types", + "ethereum-types 0.9.2", "rustc-hex 2.1.0", "serde", "serde_json", @@ -925,7 +925,7 @@ dependencies = [ "criterion 0.2.11", "crunchy 0.1.6", "either", - "ethereum-types", + "ethereum-types 0.9.2", "keccak-hash", "log", "memmap", @@ -937,6 +937,19 @@ dependencies = [ "tiny-keccak 2.0.2", ] +[[package]] +name = "ethbloom" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0584482a6433370908dee84ea13992c2cf39c569600e4dbfafe520bb3b90d1" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash 0.4.0", + "impl-rlp", + "impl-serde 0.2.3", + "tiny-keccak 1.5.0", +] + [[package]] name = "ethbloom" version = "0.9.2" @@ -944,9 +957,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71a6567e6fd35589fea0c63b94b4cf2e55573e413901bdbe60ab15cf0e25e5df" dependencies = [ "crunchy 0.2.2", - "fixed-hash", + "fixed-hash 0.6.1", "impl-rlp", - "impl-serde", + "impl-serde 0.3.1", "tiny-keccak 2.0.2", ] @@ -976,7 +989,7 @@ dependencies = [ "ethcore-io", "ethcore-miner", "ethcore-stratum", - "ethereum-types", + "ethereum-types 0.9.2", "ethjson", "ethkey", "evm", @@ -1040,7 +1053,7 @@ name = "ethcore-accounts" version = "0.1.0" dependencies = [ "common-types", - "ethereum-types", + "ethereum-types 0.9.2", "ethkey", "ethstore", "log", @@ -1061,7 +1074,7 @@ dependencies = [ "common-types", "env_logger", "ethcore-db", - "ethereum-types", + "ethereum-types 0.9.2", "ethkey", "itertools 0.5.10", "keccak-hash", @@ -1098,7 +1111,7 @@ dependencies = [ "byteorder", "eip-152", "eth_pairings", - "ethereum-types", + "ethereum-types 0.9.2", "ethjson", "ethkey", "hex-literal", @@ -1117,7 +1130,7 @@ name = "ethcore-call-contract" version = "0.1.0" dependencies = [ "common-types", - "ethereum-types", + "ethereum-types 0.9.2", "parity-bytes", ] @@ -1126,7 +1139,7 @@ name = "ethcore-db" version = "0.1.0" dependencies = [ "common-types", - "ethereum-types", + "ethereum-types 0.9.2", "kvdb", "kvdb-memorydb", "kvdb-rocksdb", @@ -1182,7 +1195,7 @@ dependencies = [ "ethabi-derive", "ethash", "ethcore-call-contract", - "ethereum-types", + "ethereum-types 0.9.2", "ethkey", "fetch", "futures", @@ -1201,7 +1214,7 @@ dependencies = [ "serde_derive", "serde_json", "trace-time", - "transaction-pool", + "txpool", "url 2.1.0", ] @@ -1212,7 +1225,7 @@ dependencies = [ "assert_matches", "error-chain", "ethcore-io", - "ethereum-types", + "ethereum-types 0.9.2", "ethkey", "ipnetwork", "lazy_static", @@ -1236,7 +1249,7 @@ dependencies = [ "error-chain", "ethcore-io", "ethcore-network", - "ethereum-types", + "ethereum-types 0.9.2", "ethkey", "igd", "ipnetwork", @@ -1273,7 +1286,7 @@ dependencies = [ "ethcore-db", "ethcore-io", "ethcore-sync", - "ethereum-types", + "ethereum-types 0.9.2", "kvdb", "kvdb-rocksdb", "log", @@ -1286,7 +1299,7 @@ name = "ethcore-stratum" version = "1.12.0" dependencies = [ "env_logger", - "ethereum-types", + "ethereum-types 0.9.2", "jsonrpc-core", "jsonrpc-tcp-server", "keccak-hash", @@ -1309,7 +1322,7 @@ dependencies = [ "ethcore-network", "ethcore-network-devp2p", "ethereum-forkid", - "ethereum-types", + "ethereum-types 0.9.2", "ethkey", "ethstore", "fastmap", @@ -1324,7 +1337,7 @@ dependencies = [ "parity-crypto", "parity-util-mem", "parking_lot 0.11.1", - "primitive-types", + "primitive-types 0.7.2", "rand 0.7.3", "rand_xorshift 0.2.0", "rlp", @@ -1341,24 +1354,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3010d8372e3a76d4e2c44de0a080257ab62b6d108857ee7bd70fe8dfb2815f13" dependencies = [ "crc", - "ethereum-types", + "ethereum-types 0.9.2", "maplit", "parity-util-mem", "rlp", "rlp-derive", ] +[[package]] +name = "ethereum-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5a7777cb75d9ee2b8d3752634b15e4e4e70d2ef81a227e9d157acfa18592b1" +dependencies = [ + "ethbloom 0.7.0", + "fixed-hash 0.4.0", + "impl-rlp", + "impl-serde 0.2.3", + "primitive-types 0.5.1", + "uint", +] + [[package]] name = "ethereum-types" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "473aecff686bd8e7b9db0165cbbb53562376b39bf35b427f0c60446a9e1634b0" dependencies = [ - "ethbloom", - "fixed-hash", + "ethbloom 0.9.2", + "fixed-hash 0.6.1", "impl-rlp", - "impl-serde", - "primitive-types", + "impl-serde 0.3.1", + "primitive-types 0.7.2", "uint", ] @@ -1367,7 +1394,7 @@ name = "ethjson" version = "0.1.0" dependencies = [ "common-types", - "ethereum-types", + "ethereum-types 0.9.2", "macros", "maplit", "parity-crypto", @@ -1383,7 +1410,7 @@ version = "0.3.0" dependencies = [ "edit-distance", "eth-secp256k1", - "ethereum-types", + "ethereum-types 0.9.2", "lazy_static", "log", "memzero", @@ -1417,7 +1444,7 @@ dependencies = [ name = "ethstore" version = "0.2.1" dependencies = [ - "ethereum-types", + "ethereum-types 0.9.2", "ethkey", "itertools 0.5.10", "lazy_static", @@ -1461,7 +1488,7 @@ dependencies = [ "bit-set", "criterion 0.2.11", "ethcore-builtin", - "ethereum-types", + "ethereum-types 0.9.2", "hex-literal", "keccak-hash", "lazy_static", @@ -1484,7 +1511,7 @@ dependencies = [ "docopt", "env_logger", "ethcore", - "ethereum-types", + "ethereum-types 0.9.2", "ethjson", "evm", "panic_hook", @@ -1539,7 +1566,7 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" name = "fastmap" version = "0.1.0" dependencies = [ - "ethereum-types", + "ethereum-types 0.9.2", "plain_hasher", ] @@ -1566,6 +1593,18 @@ dependencies = [ "url 2.1.0", ] +[[package]] +name = "fixed-hash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516877b7b9a1cc2d0293cbce23cd6203f0edbfd4090e6ca4489fecb5aa73050e" +dependencies = [ + "byteorder", + "rand 0.5.6", + "rustc-hex 2.1.0", + "static_assertions 0.2.5", +] + [[package]] name = "fixed-hash" version = "0.6.1" @@ -1575,7 +1614,7 @@ dependencies = [ "byteorder", "rand 0.7.3", "rustc-hex 2.1.0", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -2060,6 +2099,15 @@ dependencies = [ "rlp", ] +[[package]] +name = "impl-serde" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e3cae7e99c7ff5a995da2cf78dd0a5383740eda71d98cf7b1910c301ac69b8" +dependencies = [ + "serde", +] + [[package]] name = "impl-serde" version = "0.3.1" @@ -2167,7 +2215,7 @@ version = "0.2.0" dependencies = [ "env_logger", "ethcore-db", - "ethereum-types", + "ethereum-types 0.9.2", "fastmap", "hash-db 0.11.0", "keccak-hash", @@ -2298,7 +2346,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f58a51ef3df9398cf2434bea8d4eb61fb748d0feb1571f87388579a120a4c8f" dependencies = [ - "primitive-types", + "primitive-types 0.7.2", "tiny-keccak 2.0.2", ] @@ -2306,7 +2354,7 @@ dependencies = [ name = "keccak-hasher" version = "0.1.1" dependencies = [ - "ethereum-types", + "ethereum-types 0.9.2", "hash-db 0.11.0", "plain_hasher", "tiny-keccak 1.5.0", @@ -2745,7 +2793,7 @@ dependencies = [ "ethcore-io", "ethcore-network", "ethcore-network-devp2p", - "ethereum-types", + "ethereum-types 0.9.2", "kvdb-memorydb", "log", "lru-cache", @@ -2904,7 +2952,7 @@ dependencies = [ "ethcore-network", "ethcore-service", "ethcore-sync", - "ethereum-types", + "ethereum-types 0.9.2", "ethkey", "ethstore", "fake-fetch", @@ -2991,7 +3039,7 @@ dependencies = [ "aes-ctr", "block-modes", "digest", - "ethereum-types", + "ethereum-types 0.9.2", "hmac", "lazy_static", "pbkdf2", @@ -3084,7 +3132,7 @@ dependencies = [ "ethcore-miner", "ethcore-network", "ethcore-sync", - "ethereum-types", + "ethereum-types 0.9.2", "ethjson", "ethkey", "ethstore", @@ -3119,8 +3167,8 @@ dependencies = [ "tempdir", "tiny-keccak 1.5.0", "tokio-timer 0.1.2", - "transaction-pool", "transient-hashmap", + "txpool", "vm", ] @@ -3128,7 +3176,7 @@ dependencies = [ name = "parity-rpc-client" version = "1.4.0" dependencies = [ - "ethereum-types", + "ethereum-types 0.9.2", "futures", "jsonrpc-core", "jsonrpc-ws-server", @@ -3208,13 +3256,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "297ff91fa36aec49ce183484b102f6b75b46776822bd81525bfc4cc9b0dd0f5c" dependencies = [ "cfg-if 0.1.10", - "ethereum-types", + "ethereum-types 0.9.2", "hashbrown 0.8.2", "impl-trait-for-tuples", "lru", "parity-util-mem-derive", "parking_lot 0.10.2", - "primitive-types", + "primitive-types 0.7.2", "smallvec 1.6.1", "winapi 0.3.8", ] @@ -3385,7 +3433,7 @@ name = "patricia-trie-ethereum" version = "0.1.0" dependencies = [ "elastic-array", - "ethereum-types", + "ethereum-types 0.9.2", "hash-db 0.11.0", "journaldb", "keccak-hash", @@ -3517,16 +3565,29 @@ dependencies = [ "smallvec 0.6.13", ] +[[package]] +name = "primitive-types" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ef7b3b965c0eadcb6838f34f827e1dfb2939bdd5ebd43f9647e009b12b0371" +dependencies = [ + "fixed-hash 0.4.0", + "impl-codec", + "impl-rlp", + "impl-serde 0.2.3", + "uint", +] + [[package]] name = "primitive-types" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55c21c64d0eaa4d7ed885d959ef2d62d9e488c27c0e02d9aa5ce6c877b7d5f8" dependencies = [ - "fixed-hash", + "fixed-hash 0.6.1", "impl-codec", "impl-rlp", - "impl-serde", + "impl-serde 0.3.1", "uint", ] @@ -4297,7 +4358,7 @@ dependencies = [ "byteorder", "crunchy 0.2.2", "rustc-hex 2.1.0", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -4373,6 +4434,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +[[package]] +name = "static_assertions" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" + [[package]] name = "static_assertions" version = "1.1.0" @@ -4945,17 +5012,6 @@ dependencies = [ "log", ] -[[package]] -name = "transaction-pool" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bbee24c711a878e7d8f89460569034cacf2d8c58dde785b5ffa06ed6b59663" -dependencies = [ - "log", - "smallvec 0.6.13", - "trace-time", -] - [[package]] name = "transient-hashmap" version = "0.4.1" @@ -4998,7 +5054,7 @@ dependencies = [ name = "triehash-ethereum" version = "0.2.0" dependencies = [ - "ethereum-types", + "ethereum-types 0.9.2", "keccak-hasher 0.1.1", "triehash", ] @@ -5015,6 +5071,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" +[[package]] +name = "txpool" +version = "1.0.0-alpha" +dependencies = [ + "ethereum-types 0.7.0", + "log", + "smallvec 0.6.13", + "trace-time", +] + [[package]] name = "typenum" version = "1.11.2" @@ -5036,7 +5102,7 @@ dependencies = [ "byteorder", "crunchy 0.2.2", "rustc-hex 2.1.0", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -5188,7 +5254,7 @@ checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" name = "vm" version = "0.1.0" dependencies = [ - "ethereum-types", + "ethereum-types 0.9.2", "ethjson", "keccak-hash", "parity-bytes", @@ -5247,7 +5313,7 @@ version = "0.1.0" dependencies = [ "byteorder", "env_logger", - "ethereum-types", + "ethereum-types 0.9.2", "libc", "log", "parity-wasm", diff --git a/bin/oe/configuration.rs b/bin/oe/configuration.rs index 1e3743c5c..5d531b501 100644 --- a/bin/oe/configuration.rs +++ b/bin/oe/configuration.rs @@ -640,9 +640,10 @@ impl Configuration { fn pool_verification_options(&self) -> Result { Ok(pool::verifier::Options { - // NOTE min_gas_price and block_gas_limit will be overwritten right after start. + // NOTE min_gas_price,block_gas_limit and block_base_fee will be overwritten right after start. minimal_gas_price: U256::from(20_000_000) * 1_000u32, block_gas_limit: U256::max_value(), + block_base_fee: None, tx_gas_limit: match self.args.arg_tx_gas_limit { Some(ref d) => to_u256(d)?, None => U256::max_value(), diff --git a/bin/oe/run.rs b/bin/oe/run.rs index 503524c8b..809c3ed4e 100644 --- a/bin/oe/run.rs +++ b/bin/oe/run.rs @@ -254,6 +254,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result sync::WarpSync::Disabled, }; sync_config.download_old_blocks = cmd.download_old_blocks; + sync_config.eip1559_transition = spec.params().eip1559_transition; let passwords = passwords_from_files(&cmd.acc_conf.password_files)?; @@ -365,8 +366,11 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result Priority; /// Gets transaction gas price. - fn gas_price(&self) -> &U256; + fn effective_gas_price(&self, block_base_fee: Option) -> U256; /// Gets transaction nonce. fn nonce(&self) -> U256; @@ -191,9 +195,8 @@ impl ScoredTransaction for VerifiedTransaction { self.priority } - /// Gets transaction gas price. - fn gas_price(&self) -> &U256 { - &self.transaction.tx().gas_price + fn effective_gas_price(&self, block_base_fee: Option) -> U256 { + self.transaction.effective_gas_price(block_base_fee) } /// Gets transaction nonce. diff --git a/crates/concensus/miner/src/pool/queue.rs b/crates/concensus/miner/src/pool/queue.rs index 64c543c53..986fa6b9e 100644 --- a/crates/concensus/miner/src/pool/queue.rs +++ b/crates/concensus/miner/src/pool/queue.rs @@ -26,6 +26,7 @@ use std::{ }, }; +use self::scoring::ScoringEvent; use ethereum_types::{Address, H256, U256}; use parking_lot::RwLock; use txpool::{self, Verifier}; @@ -245,7 +246,10 @@ impl TransactionQueue { insertion_id: Default::default(), pool: RwLock::new(txpool::Pool::new( Default::default(), - scoring::NonceAndGasPrice(strategy), + scoring::NonceAndGasPrice { + strategy, + block_base_fee: verification_options.block_base_fee, + }, limits, )), options: RwLock::new(verification_options), @@ -257,6 +261,26 @@ impl TransactionQueue { } } + /// If latest block has different base fee than it's parent, then transaction pool scoring needs to be updated. + pub fn update_scoring(&self, block_base_fee: U256) { + let update_needed = match self.pool.read().scoring().block_base_fee { + Some(base_fee) => base_fee != block_base_fee, + None => true, + }; + + if update_needed { + self.pool.write().set_scoring( + scoring::NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: Some(block_base_fee), + }, + ScoringEvent::BlockBaseFeeChanged, + ); + + self.cached_pending.write().clear(); + } + } + /// Update verification options /// /// Some parameters of verification may vary in time (like block gas limit or minimal gas price). @@ -307,8 +331,11 @@ impl TransactionQueue { transaction_to_replace, ); - let mut replace = - replace::ReplaceByScoreAndReadiness::new(self.pool.read().scoring().clone(), client); + let mut replace = replace::ReplaceByScoreAndReadiness::new( + self.pool.read().scoring().clone(), + client, + self.options.read().block_base_fee, + ); let results = transactions .into_iter() @@ -353,7 +380,10 @@ impl TransactionQueue { /// Returns all transactions in the queue without explicit ordering. pub fn all_transactions(&self) -> Vec> { let ready = |_tx: &pool::VerifiedTransaction| txpool::Readiness::Ready; - self.pool.read().unordered_pending(ready).collect() + self.pool + .read() + .unordered_pending(ready, Default::default()) + .collect() } /// Returns all transaction hashes in the queue without explicit ordering. @@ -361,7 +391,7 @@ impl TransactionQueue { let ready = |_tx: &pool::VerifiedTransaction| txpool::Readiness::Ready; self.pool .read() - .unordered_pending(ready) + .unordered_pending(ready, Default::default()) .map(|tx| tx.hash) .collect() } @@ -376,7 +406,7 @@ impl TransactionQueue { let ready = ready::OptionalState::new(nonce); self.pool .read() - .unordered_pending(ready) + .unordered_pending(ready, Default::default()) .map(|tx| tx.hash) .collect() } @@ -400,6 +430,7 @@ impl TransactionQueue { nonce_cap, max_len, ordering, + includable_boundary, } = settings; if let Some(pending) = self.cached_pending.read().pending( block_number, @@ -425,15 +456,19 @@ impl TransactionQueue { return self .pool .read() - .unordered_pending(ready) + .unordered_pending(ready, includable_boundary) .take(max_len) .collect(); } - let pending: Vec<_> = - self.collect_pending(client, block_number, current_timestamp, nonce_cap, |i| { - i.take(max_len).collect() - }); + let pending: Vec<_> = self.collect_pending( + client, + includable_boundary, + block_number, + current_timestamp, + nonce_cap, + |i| i.take(max_len).collect(), + ); *cached_pending = CachedPending { block_number, @@ -461,6 +496,7 @@ impl TransactionQueue { { self.collect_pending( client, + settings.includable_boundary, settings.block_number, settings.current_timestamp, settings.nonce_cap, @@ -479,6 +515,7 @@ impl TransactionQueue { pub fn collect_pending( &self, client: C, + includable_boundary: U256, block_number: u64, current_timestamp: u64, nonce_cap: Option, @@ -498,7 +535,7 @@ impl TransactionQueue { debug!(target: "txqueue", "Re-computing pending set for block: {}", block_number); trace_time!("pool::collect_pending"); let ready = Self::ready(client, block_number, current_timestamp, nonce_cap); - collect(self.pool.read().pending(ready)) + collect(self.pool.read().pending(ready, includable_boundary)) } fn ready( @@ -563,7 +600,7 @@ impl TransactionQueue { self.pool .read() - .pending_from_sender(state_readiness, address) + .pending_from_sender(state_readiness, address, Default::default()) .last() .map(|tx| tx.signed().tx().nonce.saturating_add(U256::from(1))) } @@ -612,7 +649,7 @@ impl TransactionQueue { pub fn penalize<'a, T: IntoIterator>(&self, senders: T) { let mut pool = self.pool.write(); for sender in senders { - pool.update_scores(sender, ()); + pool.update_scores(sender, ScoringEvent::Penalize); } } diff --git a/crates/concensus/miner/src/pool/replace.rs b/crates/concensus/miner/src/pool/replace.rs index d7d9ac1d9..93760ee47 100644 --- a/crates/concensus/miner/src/pool/replace.rs +++ b/crates/concensus/miner/src/pool/replace.rs @@ -39,12 +39,18 @@ use txpool::{ pub struct ReplaceByScoreAndReadiness { scoring: S, client: C, + /// Block base fee of the latest block, exists if the EIP 1559 is activated + block_base_fee: Option, } impl ReplaceByScoreAndReadiness { /// Create a new `ReplaceByScoreAndReadiness` - pub fn new(scoring: S, client: C) -> Self { - ReplaceByScoreAndReadiness { scoring, client } + pub fn new(scoring: S, client: C, block_base_fee: Option) -> Self { + ReplaceByScoreAndReadiness { + scoring, + client, + block_base_fee, + } } } @@ -67,8 +73,9 @@ where } else if both_local { Choice::InsertNew } else { - let old_score = (old.priority(), old.gas_price()); - let new_score = (new.priority(), new.gas_price()); + let old_score = (old.priority(), old.effective_gas_price(self.block_base_fee)); + let new_score = (new.priority(), new.effective_gas_price(self.block_base_fee)); + if new_score > old_score { // Check if this is a replacement transaction. // @@ -155,9 +162,12 @@ mod tests { #[test] fn should_always_accept_local_transactions_unless_same_sender_and_nonce() { - let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); + let scoring = NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: None, + }; let client = TestClient::new().with_nonce(1); - let replace = ReplaceByScoreAndReadiness::new(scoring, client); + let replace = ReplaceByScoreAndReadiness::new(scoring, client, None); // same sender txs let keypair = Random.generate(); @@ -249,9 +259,12 @@ mod tests { #[test] fn should_replace_same_sender_by_nonce() { - let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); + let scoring = NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: None, + }; let client = TestClient::new().with_nonce(1); - let replace = ReplaceByScoreAndReadiness::new(scoring, client); + let replace = ReplaceByScoreAndReadiness::new(scoring, client, None); let tx1 = Tx { nonce: 1, @@ -311,9 +324,12 @@ mod tests { #[test] fn should_replace_different_sender_by_priority_and_gas_price() { // given - let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); + let scoring = NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: None, + }; let client = TestClient::new().with_nonce(0); - let replace = ReplaceByScoreAndReadiness::new(scoring, client); + let replace = ReplaceByScoreAndReadiness::new(scoring, client, None); let tx_regular_low_gas = { let tx = Tx { @@ -406,9 +422,12 @@ mod tests { #[test] fn should_not_replace_ready_transaction_with_future_transaction() { - let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); + let scoring = NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: None, + }; let client = TestClient::new().with_nonce(1); - let replace = ReplaceByScoreAndReadiness::new(scoring, client); + let replace = ReplaceByScoreAndReadiness::new(scoring, client, None); let tx_ready_low_score = { let tx = Tx { @@ -436,9 +455,12 @@ mod tests { #[test] fn should_compute_readiness_with_pooled_transactions_from_the_same_sender_as_the_existing_transaction( ) { - let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); + let scoring = NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: None, + }; let client = TestClient::new().with_nonce(1); - let replace = ReplaceByScoreAndReadiness::new(scoring, client); + let replace = ReplaceByScoreAndReadiness::new(scoring, client, None); let old_sender = Random.generate(); let tx_old_ready_1 = { @@ -504,9 +526,12 @@ mod tests { #[test] fn should_compute_readiness_with_pooled_transactions_from_the_same_sender_as_the_new_transaction( ) { - let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); + let scoring = NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: None, + }; let client = TestClient::new().with_nonce(1); - let replace = ReplaceByScoreAndReadiness::new(scoring, client); + let replace = ReplaceByScoreAndReadiness::new(scoring, client, None); // current transaction is ready but has a lower gas price than the new one let old_tx = { @@ -572,9 +597,12 @@ mod tests { #[test] fn should_accept_local_tx_with_same_sender_and_nonce_with_better_gas_price() { - let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); + let scoring = NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: None, + }; let client = TestClient::new().with_nonce(1); - let replace = ReplaceByScoreAndReadiness::new(scoring, client); + let replace = ReplaceByScoreAndReadiness::new(scoring, client, None); // current transaction is ready let old_tx = { @@ -627,9 +655,12 @@ mod tests { #[test] fn should_reject_local_tx_with_same_sender_and_nonce_with_worse_gas_price() { - let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); + let scoring = NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: None, + }; let client = TestClient::new().with_nonce(1); - let replace = ReplaceByScoreAndReadiness::new(scoring, client); + let replace = ReplaceByScoreAndReadiness::new(scoring, client, None); // current transaction is ready let old_tx = { diff --git a/crates/concensus/miner/src/pool/scoring.rs b/crates/concensus/miner/src/pool/scoring.rs index d27ada124..4d9cad188 100644 --- a/crates/concensus/miner/src/pool/scoring.rs +++ b/crates/concensus/miner/src/pool/scoring.rs @@ -42,13 +42,25 @@ const GAS_PRICE_BUMP_SHIFT: usize = 3; // 2 = 25%, 3 = 12.5%, 4 = 6.25% fn bump_gas_price(old_gp: U256) -> U256 { old_gp.saturating_add(old_gp >> GAS_PRICE_BUMP_SHIFT) } - +/// List of events that trigger updating of scores +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum ScoringEvent { + /// Penalize transactions + Penalize, + /// Every time new block is added to blockchain, block base fee is changed and triggers score change. + BlockBaseFeeChanged, +} /// Simple, gas-price based scoring for transactions. /// /// NOTE: Currently penalization does not apply to new transactions that enter the pool. /// We might want to store penalization status in some persistent state. #[derive(Debug, Clone)] -pub struct NonceAndGasPrice(pub PrioritizationStrategy); +pub struct NonceAndGasPrice { + /// Strategy for prioritization + pub strategy: PrioritizationStrategy, + /// Block base fee. Exists if the EIP 1559 is activated. + pub block_base_fee: Option, +} impl NonceAndGasPrice { /// Decide if the transaction should even be considered into the pool (if the pool is full). @@ -67,7 +79,7 @@ impl NonceAndGasPrice { return true; } - &old.transaction.tx().gas_price > new.gas_price() + old.effective_gas_price(self.block_base_fee) > new.effective_gas_price(self.block_base_fee) } } @@ -76,7 +88,7 @@ where P: ScoredTransaction + txpool::VerifiedTransaction, { type Score = U256; - type Event = (); + type Event = ScoringEvent; fn compare(&self, old: &P, other: &P) -> cmp::Ordering { old.nonce().cmp(&other.nonce()) @@ -87,10 +99,10 @@ where return scoring::Choice::InsertNew; } - let old_gp = old.gas_price(); - let new_gp = new.gas_price(); + let old_gp = old.effective_gas_price(self.block_base_fee); + let new_gp = new.effective_gas_price(self.block_base_fee); - let min_required_gp = bump_gas_price(*old_gp); + let min_required_gp = bump_gas_price(old_gp); match min_required_gp.cmp(&new_gp) { cmp::Ordering::Greater => scoring::Choice::RejectNew, @@ -102,7 +114,7 @@ where &self, txs: &[txpool::Transaction

], scores: &mut [U256], - change: scoring::Change, + change: scoring::Change, ) { use self::scoring::Change; @@ -113,7 +125,7 @@ where assert!(i < txs.len()); assert!(i < scores.len()); - scores[i] = *txs[i].transaction.gas_price(); + scores[i] = txs[i].effective_gas_price(self.block_base_fee); let boost = match txs[i].priority() { super::Priority::Local => 15, super::Priority::Retracted => 10, @@ -123,11 +135,20 @@ where } // We are only sending an event in case of penalization. // So just lower the priority of all non-local transactions. - Change::Event(_) => { - for (score, tx) in scores.iter_mut().zip(txs) { - // Never penalize local transactions. - if !tx.priority().is_local() { - *score = *score >> 3; + Change::Event(event) => { + match event { + ScoringEvent::Penalize => { + for (score, tx) in scores.iter_mut().zip(txs) { + // Never penalize local transactions. + if !tx.priority().is_local() { + *score = *score >> 3; + } + } + } + ScoringEvent::BlockBaseFeeChanged => { + for i in 0..txs.len() { + scores[i] = txs[i].transaction.effective_gas_price(self.block_base_fee); + } } } } @@ -150,7 +171,10 @@ mod tests { #[test] fn should_calculate_score_correctly() { // given - let scoring = NonceAndGasPrice(PrioritizationStrategy::GasPriceOnly); + let scoring = NonceAndGasPrice { + strategy: PrioritizationStrategy::GasPriceOnly, + block_base_fee: None, + }; let (tx1, tx2, tx3) = Tx::default().signed_triple(); let transactions = vec![tx1, tx2, tx3] .into_iter() @@ -200,7 +224,11 @@ mod tests { assert_eq!(scores, vec![32768.into(), 1024.into(), 1.into()]); // Check penalization - scoring.update_scores(&transactions, &mut *scores, scoring::Change::Event(())); + scoring.update_scores( + &transactions, + &mut *scores, + scoring::Change::Event(ScoringEvent::Penalize), + ); assert_eq!(scores, vec![32768.into(), 128.into(), 0.into()]); } } diff --git a/crates/concensus/miner/src/pool/tests/mod.rs b/crates/concensus/miner/src/pool/tests/mod.rs index 8d0fc9482..807c3baee 100644 --- a/crates/concensus/miner/src/pool/tests/mod.rs +++ b/crates/concensus/miner/src/pool/tests/mod.rs @@ -46,6 +46,7 @@ fn new_queue() -> TransactionQueue { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, + block_base_fee: None, }, PrioritizationStrategy::GasPriceOnly, ) @@ -64,6 +65,7 @@ fn should_return_correct_nonces_when_dropped_because_of_limit() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, + block_base_fee: None, }, PrioritizationStrategy::GasPriceOnly, ); @@ -124,6 +126,7 @@ fn should_never_drop_local_transactions_from_different_senders() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, + block_base_fee: None, }, PrioritizationStrategy::GasPriceOnly, ); @@ -541,6 +544,7 @@ fn should_prefer_current_transactions_when_hitting_the_limit() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, + block_base_fee: None, }, PrioritizationStrategy::GasPriceOnly, ); @@ -898,6 +902,7 @@ fn should_not_return_transactions_over_nonce_cap() { nonce_cap: Some(123.into()), max_len: usize::max_value(), ordering: PendingOrdering::Priority, + includable_boundary: Default::default(), }, ); @@ -932,6 +937,7 @@ fn should_return_cached_pending_even_if_unordered_is_requested() { nonce_cap: None, max_len: 3, ordering: PendingOrdering::Unordered, + includable_boundary: Default::default(), }, ); @@ -960,6 +966,7 @@ fn should_return_unordered_and_not_populate_the_cache() { nonce_cap: None, max_len: usize::max_value(), ordering: PendingOrdering::Unordered, + includable_boundary: Default::default(), }, ); @@ -1035,6 +1042,7 @@ fn should_include_local_transaction_to_a_full_pool() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, + block_base_fee: None, }, PrioritizationStrategy::GasPriceOnly, ); @@ -1067,6 +1075,7 @@ fn should_avoid_verifying_transaction_already_in_pool() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, + block_base_fee: None, }, PrioritizationStrategy::GasPriceOnly, ); @@ -1102,6 +1111,7 @@ fn should_avoid_reverifying_recently_rejected_transactions() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, + block_base_fee: None, }, PrioritizationStrategy::GasPriceOnly, ); @@ -1150,6 +1160,7 @@ fn should_reject_early_in_case_gas_price_is_less_than_min_effective() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: false, + block_base_fee: None, }, PrioritizationStrategy::GasPriceOnly, ); @@ -1192,6 +1203,7 @@ fn should_not_reject_early_in_case_gas_price_is_less_than_min_effective() { block_gas_limit: 1_000_000.into(), tx_gas_limit: 1_000_000.into(), no_early_reject: true, + block_base_fee: None, }, PrioritizationStrategy::GasPriceOnly, ); diff --git a/crates/concensus/miner/src/pool/verifier.rs b/crates/concensus/miner/src/pool/verifier.rs index aa1999121..b8053bb8f 100644 --- a/crates/concensus/miner/src/pool/verifier.rs +++ b/crates/concensus/miner/src/pool/verifier.rs @@ -46,6 +46,8 @@ pub struct Options { pub minimal_gas_price: U256, /// Current block gas limit. pub block_gas_limit: U256, + /// Block base fee. Exists if the EIP 1559 is activated. + pub block_base_fee: Option, /// Maximal gas limit for a single transaction. pub tx_gas_limit: U256, /// Skip checks for early rejection, to make sure that local transactions are always imported. @@ -58,6 +60,7 @@ impl Default for Options { Options { minimal_gas_price: 0.into(), block_gas_limit: U256::max_value(), + block_base_fee: None, tx_gas_limit: U256::max_value(), no_early_reject: false, } @@ -93,7 +96,7 @@ impl Transaction { } } - /// Return transaction gas price + /// Return transaction gas price for non 1559 transactions or maxFeePerGas for 1559 transactions. pub fn gas_price(&self) -> &U256 { match *self { Transaction::Unverified(ref tx) => &tx.tx().gas_price, @@ -110,6 +113,15 @@ impl Transaction { } } + /// Return actual gas price of the transaction depending on the current block base fee + pub fn effective_gas_price(&self, block_base_fee: Option) -> U256 { + match *self { + Transaction::Unverified(ref tx) => tx.effective_gas_price(block_base_fee), + Transaction::Retracted(ref tx) => tx.effective_gas_price(block_base_fee), + Transaction::Local(ref tx) => tx.effective_gas_price(block_base_fee), + } + } + fn transaction(&self) -> &transaction::TypedTransaction { match *self { Transaction::Unverified(ref tx) => &*tx, @@ -213,22 +225,24 @@ impl txpool::Verifier } let is_own = tx.is_local(); + let gas_price = tx.effective_gas_price(self.options.block_base_fee); // Quick exit for non-service and non-local transactions // // We're checking if the transaction is below configured minimal gas price // or the effective minimal gas price in case the pool is full. - if !tx.gas_price().is_zero() && !is_own { - if tx.gas_price() < &self.options.minimal_gas_price { + + if !gas_price.is_zero() && !is_own { + if gas_price < self.options.minimal_gas_price { trace!( target: "txqueue", "[{:?}] Rejected tx below minimal gas price threshold: {} < {}", hash, - tx.gas_price(), + gas_price, self.options.minimal_gas_price, ); bail!(transaction::Error::InsufficientGasPrice { minimal: self.options.minimal_gas_price, - got: *tx.gas_price(), + got: gas_price, }); } @@ -238,12 +252,15 @@ impl txpool::Verifier target: "txqueue", "[{:?}] Rejected tx early, cause it doesn't have any chance to get to the pool: (gas price: {} < {})", hash, - tx.gas_price(), - vtx.transaction.tx().gas_price, + gas_price, + vtx.transaction.effective_gas_price(self.options.block_base_fee), ); return Err(transaction::Error::TooCheapToReplace { - prev: Some(vtx.transaction.tx().gas_price), - new: Some(*tx.gas_price()), + prev: Some( + vtx.transaction + .effective_gas_price(self.options.block_base_fee), + ), + new: Some(gas_price), }); } } @@ -280,7 +297,9 @@ impl txpool::Verifier let sender = transaction.sender(); let account_details = self.client.account_details(&sender); - if transaction.tx().gas_price < self.options.minimal_gas_price { + let gas_price = transaction.tx().gas_price; + + if gas_price < self.options.minimal_gas_price { let transaction_type = self.client.transaction_type(&transaction); if let TransactionType::Service = transaction_type { debug!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); @@ -291,20 +310,24 @@ impl txpool::Verifier target: "txqueue", "[{:?}] Rejected tx below minimal gas price threshold: {} < {}", hash, - transaction.tx().gas_price, + gas_price, self.options.minimal_gas_price, ); bail!(transaction::Error::InsufficientGasPrice { minimal: self.options.minimal_gas_price, - got: transaction.tx().gas_price, + got: gas_price, }); } } - let (full_gas_price, overflow_1) = transaction - .tx() - .gas_price - .overflowing_mul(transaction.tx().gas); + if gas_price < transaction.max_priority_fee_per_gas() { + bail!(transaction::Error::InsufficientGasPrice { + minimal: transaction.max_priority_fee_per_gas(), + got: gas_price, + }); + } + + let (full_gas_price, overflow_1) = gas_price.overflowing_mul(transaction.tx().gas); let (cost, overflow_2) = transaction.tx().value.overflowing_add(full_gas_price); if overflow_1 || overflow_2 { trace!( diff --git a/crates/ethcore/blockchain/src/blockchain.rs b/crates/ethcore/blockchain/src/blockchain.rs index 76778d34b..548cb6478 100644 --- a/crates/ethcore/blockchain/src/blockchain.rs +++ b/crates/ethcore/blockchain/src/blockchain.rs @@ -153,8 +153,9 @@ pub trait BlockProvider { /// Get a list of uncles for a given block. /// Returns None if block does not exist. - fn uncles(&self, hash: &H256) -> Option> { - self.block_body(hash).map(|body| body.uncles()) + fn uncles(&self, hash: &H256, eip1559_transition: BlockNumber) -> Option> { + self.block_body(hash) + .map(|body| body.uncles(eip1559_transition)) } /// Get a list of uncle hashes for a given block. @@ -272,6 +273,9 @@ pub struct BlockChain { pending_block_hashes: RwLock>, pending_block_details: RwLock>, pending_transaction_addresses: RwLock>>, + + /// Number of first block where EIP-1559 rules begin. New encoding/decoding block format. + pub eip1559_transition: BlockNumber, } impl BlockProvider for BlockChain { @@ -559,7 +563,7 @@ impl<'a> Iterator for AncestryWithMetadataIter<'a> { } else { let details = self.chain.block_details(&self.current); let header = self.chain.block_header_data(&self.current).map(|h| { - h.decode() + h.decode(self.chain.eip1559_transition) .expect("Stored block header data is valid RLP; qed") }); @@ -630,7 +634,12 @@ impl<'a> Iterator for EpochTransitionIter<'a> { impl BlockChain { /// Create new instance of blockchain from given Genesis. - pub fn new(config: Config, genesis: &[u8], db: Arc) -> BlockChain { + pub fn new( + config: Config, + genesis: &[u8], + db: Arc, + eip1559_transition: BlockNumber, + ) -> BlockChain { // 400 is the average size of the key let cache_man = CacheManager::new(config.pref_cache_size, config.max_cache_size, 400); @@ -656,6 +665,7 @@ impl BlockChain { pending_block_hashes: RwLock::new(HashMap::new()), pending_block_details: RwLock::new(HashMap::new()), pending_transaction_addresses: RwLock::new(HashMap::new()), + eip1559_transition, }; // load best block @@ -715,7 +725,7 @@ impl BlockChain { let mut best_block = bc.best_block.write(); *best_block = BestBlock { total_difficulty: best_block_total_difficulty, - header: best_block_rlp.decode_header(), + header: best_block_rlp.decode_header(eip1559_transition), block: best_block_rlp, }; } @@ -1044,7 +1054,7 @@ impl BlockChain { let mut best_block = self.best_block.write(); *best_block = BestBlock { total_difficulty: best_block_total_difficulty, - header: best_block_rlp.decode_header(), + header: best_block_rlp.decode_header(self.eip1559_transition), block: best_block_rlp, }; } @@ -1463,7 +1473,7 @@ impl BlockChain { batch.put(db::COL_EXTRA, b"best", update.info.hash.as_bytes()); *best_block = Some(BestBlock { total_difficulty: update.info.total_difficulty, - header: update.block.decode_header(), + header: update.block.decode_header(self.eip1559_transition), block: update.block, }); } @@ -1991,8 +2001,12 @@ mod tests { Arc::new(db) } - fn new_chain(genesis: encoded::Block, db: Arc) -> BlockChain { - BlockChain::new(Config::default(), genesis.raw(), db) + fn new_chain( + genesis: encoded::Block, + db: Arc, + eip1559_transition: BlockNumber, + ) -> BlockChain { + BlockChain::new(Config::default(), genesis.raw(), db, eip1559_transition) } fn insert_block( @@ -2058,7 +2072,11 @@ mod tests { let first = genesis.add_block(); let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); assert_eq!(bc.best_block_number(), 0); // when @@ -2086,7 +2104,7 @@ mod tests { let first_hash = first.hash(); let db = new_db(); - let bc = new_chain(genesis.encoded(), db.clone()); + let bc = new_chain(genesis.encoded(), db.clone(), BlockNumber::max_value()); assert_eq!(bc.genesis_hash(), genesis_hash); assert_eq!(bc.best_block_hash(), genesis_hash); @@ -2118,7 +2136,11 @@ mod tests { let generator = BlockGenerator::new(vec![first_10]); let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let mut block_hashes = vec![genesis.last().hash()]; let mut batch = db.key_value().transaction(); @@ -2165,7 +2187,11 @@ mod tests { let generator = BlockGenerator::new(vec![b1a, b1b, b2a, b2b, b3a, b3b, b4a, b4b, b5a, b5b]); let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); for b in generator { insert_block(&db, &bc, b.encoded(), vec![]); @@ -2204,7 +2230,11 @@ mod tests { let b2_hash = b2.last().hash(); let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let mut batch = db.key_value().transaction(); let _ = insert_block_batch(&mut batch, &bc, b1a.last().encoded(), vec![]); @@ -2291,7 +2321,11 @@ mod tests { let t3_hash = t3.hash(); let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let mut batch = db.key_value().transaction(); let _ = insert_block_batch(&mut batch, &bc, b1a.last().encoded(), vec![]); @@ -2364,7 +2398,11 @@ mod tests { let best_block_hash = b3a_hash; let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let mut batch = db.key_value().transaction(); let ir1 = insert_block_batch(&mut batch, &bc, b1.last().encoded(), vec![]); @@ -2490,7 +2528,11 @@ mod tests { let db = new_db(); { - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); assert_eq!(bc.best_block_hash(), genesis_hash); let mut batch = db.key_value().transaction(); insert_block_batch(&mut batch, &bc, first.last().encoded(), vec![]); @@ -2500,7 +2542,11 @@ mod tests { } { - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); assert_eq!(bc.best_block_hash(), first_hash); } @@ -2515,7 +2561,11 @@ mod tests { .unwrap(); let db = new_db(); - let bc = new_chain(encoded::Block::new(genesis), db.clone()); + let bc = new_chain( + encoded::Block::new(genesis), + db.clone(), + BlockNumber::max_value(), + ); let mut batch = db.key_value().transaction(); insert_block_batch(&mut batch, &bc, encoded::Block::new(b1), vec![]); db.key_value().write(batch).unwrap(); @@ -2599,7 +2649,11 @@ mod tests { let b3_number = b3.last().number(); let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); insert_block( &db, &bc, @@ -2783,7 +2837,11 @@ mod tests { let b2a = b1a.add_block_with_bloom(bloom_ba); let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let blocks_b1 = bc.blocks_with_bloom(Some(&bloom_b1), 0, 5); let blocks_b2 = bc.blocks_with_bloom(Some(&bloom_b2), 0, 5); @@ -2845,7 +2903,11 @@ mod tests { let b1_total_difficulty = genesis.last().difficulty() + b1.last().difficulty(); let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let mut batch = db.key_value().transaction(); bc.insert_unordered_block( &mut batch, @@ -2885,7 +2947,11 @@ mod tests { let db = new_db(); { - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let mut batch = db.key_value().transaction(); // create a longer fork @@ -2901,7 +2967,7 @@ mod tests { } // re-loading the blockchain should load the correct best block. - let bc = new_chain(genesis.last().encoded(), db); + let bc = new_chain(genesis.last().encoded(), db, BlockNumber::max_value()); assert_eq!(bc.best_block_number(), 5); } @@ -2916,7 +2982,11 @@ mod tests { let db = new_db(); { - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let mut batch = db.key_value().transaction(); // create a longer fork @@ -2958,7 +3028,7 @@ mod tests { } // re-loading the blockchain should load the correct best block. - let bc = new_chain(genesis.last().encoded(), db); + let bc = new_chain(genesis.last().encoded(), db, BlockNumber::max_value()); assert_eq!(bc.best_block_number(), 5); assert_eq!( @@ -2982,7 +3052,11 @@ mod tests { let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let mut batch = db.key_value().transaction(); bc.insert_epoch_transition( @@ -3067,7 +3141,11 @@ mod tests { let bootstrap_chain = |blocks: Vec<&BlockBuilder>| { let db = new_db(); - let bc = new_chain(genesis.last().encoded(), db.clone()); + let bc = new_chain( + genesis.last().encoded(), + db.clone(), + BlockNumber::max_value(), + ); let mut batch = db.key_value().transaction(); for block in blocks { insert_block_batch(&mut batch, &bc, block.last().encoded(), vec![]); diff --git a/crates/ethcore/res/chainspec/aleut.json b/crates/ethcore/res/chainspec/aleut.json new file mode 100644 index 000000000..a31c61afc --- /dev/null +++ b/crates/ethcore/res/chainspec/aleut.json @@ -0,0 +1,195 @@ +{ + "name": "Aleut", + "engine": { + "clique": { + "params": { + "epoch": 30000, + "period": 15 + } + } + }, + "params": { + "accountStartNonce": "0x00", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip1283ReenableTransition": "0x0", + "eip1283Transition": "0x0", + "eip1344Transition": "0x0", + "eip140Transition": "0x0", + "eip145Transition": "0x0", + "eip150Transition": "0x0", + "eip155Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip1706Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip2929Transition": "0x0", + "eip2930Transition": "0x0", + "eip658Transition": "0x0", + "eip1559Transition": "0xa", + "eip3198Transition": "0xa", + "gasLimitBoundDivisor": "0x0400", + "maxCodeSize": 24576, + "maxCodeSizeTransition": "0x0", + "maximumExtraDataSize": "0xffff", + "minGasLimit": "0x1388", + "networkID": "7822", + "chainID": "7822", + "registrar": "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "eip1559BaseFeeMaxChangeDenominator": "0x8", + "eip1559ElasticityMultiplier": "0x2", + "eip1559BaseFeeInitialValue": "0x3B9ACA00" + }, + "genesis": { + "author": "0x0000000000000000000000000000000000000000", + "difficulty": "0x400", + "extraData": "0x000000000000000000000000000000000000000000000000000000000000000036267c845cc42b57ccb869d655e5d5fb620cc69a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x1312D00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "seal": { + "ethereum": { + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000" + } + }, + "timestamp": "0x0" + }, + "nodes": [ "enode://0c72e2b7873e4342d725b5990c17adb2b159aad2ff5853de7e4910b25522a1f9e78f9cd802a8a3225b8fae4e994e522b50d6bd5a163eb3a7b49a0a73ca9a1c7e@3.12.166.199:30303", "enode://aec88fd902744bf67705c098bf532b01017ccc3a156395508e2d9c4e7c22699ecccae1e7316614f8a2d4c5698a9be3fe6151ee25b9ed4aa052f88e112c65387a@164.90.171.157:31559" + ], + "accounts": { + "0000000000000000000000000000000000000001": { + "builtin": { + "name": "ecrecover", + "pricing": { + "linear": { + "base": 3000, + "word": 0 + } + } + } + }, + "0000000000000000000000000000000000000002": { + "builtin": { + "name": "sha256", + "pricing": { + "linear": { + "base": 60, + "word": 12 + } + } + } + }, + "0000000000000000000000000000000000000003": { + "builtin": { + "name": "ripemd160", + "pricing": { + "linear": { + "base": 600, + "word": 120 + } + } + } + }, + "0000000000000000000000000000000000000004": { + "builtin": { + "name": "identity", + "pricing": { + "linear": { + "base": 15, + "word": 3 + } + } + } + }, + "0000000000000000000000000000000000000005": { + "builtin": { + "activate_at": "0x00", + "name": "modexp", + "pricing": { + "0": { + "price": { + "modexp2565": {} + } + } + } + } + }, + "0000000000000000000000000000000000000006": { + "builtin": { + "name": "alt_bn128_add", + "pricing": { + "0": { + "info": "EIP 1108 transition", + "price": { + "alt_bn128_const_operations": { + "price": 150 + } + } + } + } + } + }, + "0000000000000000000000000000000000000007": { + "builtin": { + "name": "alt_bn128_mul", + "pricing": { + "0": { + "info": "EIP 1108 transition", + "price": { + "alt_bn128_const_operations": { + "price": 6000 + } + } + } + } + } + }, + "0000000000000000000000000000000000000008": { + "builtin": { + "name": "alt_bn128_pairing", + "pricing": { + "0": { + "info": "EIP 1108 transition", + "price": { + "alt_bn128_pairing": { + "base": 45000, + "pair": 34000 + } + } + } + } + } + }, + "0000000000000000000000000000000000000009": { + "builtin": { + "activate_at": "0x00", + "name": "blake2_f", + "pricing": { + "blake2_f": { + "gas_per_round": 1 + } + } + } + }, + "0xfe3b557e8fb62b89f4916b721be55ceb828dbd73": { + "balance": "90000000000000000000000" + }, + "0x627306090abaB3A6e1400e9345bC60c78a8BEf57": { + "balance": "90000000000000000000000" + }, + "0xf17f52151EbEF6C7334FAD080c5704D77216b732": { + "balance": "90000000000000000000000" + }, + "0xb8c3bfFb71F76BeE2B2f81bdBC53Ad4C43e3f58E": { + "balance": "90000000000000000000000" + }, + "0x60AdC0F89a41AF237ce73554EDe170D733ec14E0": { + "balance": "90000000000000000000000" + } + } +} \ No newline at end of file diff --git a/crates/ethcore/res/chainspec/baikal.json b/crates/ethcore/res/chainspec/baikal.json new file mode 100644 index 000000000..8f7bccce8 --- /dev/null +++ b/crates/ethcore/res/chainspec/baikal.json @@ -0,0 +1,953 @@ +{ + "name": "Baikal", + "engine": { + "clique": { + "params": { + "epoch": 30000, + "period": 30 + } + } + }, + "params": { + "accountStartNonce": "0x00", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip1283ReenableTransition": "0x0", + "eip1283Transition": "0x0", + "eip1344Transition": "0x0", + "eip140Transition": "0x0", + "eip145Transition": "0x0", + "eip150Transition": "0x0", + "eip155Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip1706Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip2929Transition": "0x0", + "eip2930Transition": "0x0", + "eip658Transition": "0x0", + "eip1559Transition": "0x1F4", + "eip3198Transition": "0x1F4", + "eip3529Transition": "0x1F4", + "gasLimitBoundDivisor": "0x0400", + "maxCodeSize": 24576, + "maxCodeSizeTransition": "0x0", + "maximumExtraDataSize": "0xffff", + "minGasLimit": "0x1388", + "networkID": "1642", + "chainID": "1642", + "registrar": "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "eip1559BaseFeeMaxChangeDenominator": "0x8", + "eip1559ElasticityMultiplier": "0x2", + "eip1559BaseFeeInitialValue": "0x3B9ACA00" + }, + "genesis": { + "author": "0x0000000000000000000000000000000000000000", + "difficulty": "0x1", + "extraData": "0x00000000000000000000000000000000000000000000000000000000000000005211cea3870c7ba7c6c44b185e62eecdb864cd8c560228ce57d31efbf64c200b2c200aacec78cf17a7148e784fe95a7a750335f8b9572ee28d72e7650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x47b760", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "seal": { + "ethereum": { + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000" + } + }, + "timestamp": "0x6092ca7f" + }, + "nodes": [ + "enode://39eb08bbfad87481553c471a63ff2a4b4885fffa4ff50f1cf46744d9ad6e2f764ede146fe4df563fa9ccda1a46b9b1a88fb08135e1bf1d71b320912499da773d@3.21.156.138:30303" + ], + "accounts": { + "0000000000000000000000000000000000000000": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000001": { + "builtin": { + "name": "ecrecover", + "pricing": { + "linear": { + "base": 3000, + "word": 0 + } + } + }, + "balance": "0x1" + }, + "0000000000000000000000000000000000000002": { + "builtin": { + "name": "sha256", + "pricing": { + "linear": { + "base": 60, + "word": 12 + } + } + }, + "balance": "0x1" + }, + "0000000000000000000000000000000000000003": { + "builtin": { + "name": "ripemd160", + "pricing": { + "linear": { + "base": 600, + "word": 120 + } + } + }, + "balance": "0x1" + }, + "0000000000000000000000000000000000000004": { + "builtin": { + "name": "identity", + "pricing": { + "linear": { + "base": 15, + "word": 3 + } + } + }, + "balance": "0x1" + }, + "0000000000000000000000000000000000000005": { + "builtin": { + "activate_at": "0x00", + "name": "modexp", + "pricing": { + "0": { + "price": { + "modexp2565": {} + } + } + } + }, + "balance": "0x1" + }, + "0000000000000000000000000000000000000006": { + "builtin": { + "name": "alt_bn128_add", + "pricing": { + "0": { + "info": "EIP 1108 transition", + "price": { + "alt_bn128_const_operations": { + "price": 150 + } + } + } + } + }, + "balance": "0x1" + }, + "0000000000000000000000000000000000000007": { + "builtin": { + "name": "alt_bn128_mul", + "pricing": { + "0": { + "info": "EIP 1108 transition", + "price": { + "alt_bn128_const_operations": { + "price": 6000 + } + } + } + } + }, + "balance": "0x1" + }, + "0000000000000000000000000000000000000008": { + "builtin": { + "name": "alt_bn128_pairing", + "pricing": { + "0": { + "info": "EIP 1108 transition", + "price": { + "alt_bn128_pairing": { + "base": 45000, + "pair": 34000 + } + } + } + } + }, + "balance": "0x1" + }, + "0000000000000000000000000000000000000009": { + "builtin": { + "activate_at": "0x00", + "name": "blake2_f", + "pricing": { + "blake2_f": { + "gas_per_round": 1 + } + } + }, + "balance": "0x1" + }, + "000000000000000000000000000000000000000a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000010": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000011": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000012": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000013": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000014": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000015": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000016": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000017": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000018": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000019": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000020": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000021": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000022": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000023": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000024": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000025": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000026": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000027": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000028": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000029": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000030": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000031": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000032": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000033": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000034": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000035": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000036": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000037": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000038": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000039": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000040": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000041": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000042": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000043": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000044": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000045": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000046": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000047": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000048": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000049": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000050": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000051": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000052": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000053": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000054": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000055": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000056": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000057": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000058": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000059": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000060": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000061": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000062": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000063": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000064": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000065": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000066": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000067": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000068": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000069": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000070": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000071": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000072": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000073": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000074": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000075": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000076": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000077": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000078": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000079": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000080": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000081": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000082": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000083": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000084": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000085": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000086": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000087": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000088": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000089": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000090": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000091": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000092": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000093": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000094": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000095": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000096": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000097": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000098": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000099": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009f": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000aa": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ab": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ac": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ad": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ae": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000af": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ba": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000be": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bf": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ca": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ce": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cf": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000da": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000db": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000dc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000dd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000de": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000df": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ea": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000eb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ec": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ed": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ee": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ef": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fa": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fe": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ff": { + "balance": "0x1" + }, + "0x0e89e2aedb1cfcdb9424d41a1f218f4132738172": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + }, + "0x60adc0f89a41af237ce73554ede170d733ec14e0": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + }, + "0x799d329e5f583419167cd722962485926e338f4a": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + }, + "0x7cf5b79bfe291a67ab02b393e456ccc4c266f753": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + }, + "0x8ba1f109551bd432803012645ac136ddd64dba72": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + }, + "0xb02a2eda1b317fbd16760128836b0ac59b560e9d": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + } + } +} \ No newline at end of file diff --git a/crates/ethcore/res/chainspec/test/london_test.json b/crates/ethcore/res/chainspec/test/london_test.json new file mode 100644 index 000000000..1a388fbcc --- /dev/null +++ b/crates/ethcore/res/chainspec/test/london_test.json @@ -0,0 +1,212 @@ +{ + "name": "London (test)", + "engine": { + "Ethash": { + "params": { + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x1BC16D674EC80000", + "homesteadTransition": "0x0", + "eip100bTransition": "0x0", + "difficultyBombDelays": { + "0": 5000000 + } + } + } + }, + "params": { + "gasLimitBoundDivisor": "0x0400", + "registrar": "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "accountStartNonce": "0x00", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID": "0x1", + "maxCodeSize": 24576, + "maxCodeSizeTransition": "0x0", + "eip150Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip140Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip155Transition": "0x0", + "eip658Transition": "0x0", + "eip145Transition": "0x0", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip1283ReenableTransition": "0x0", + "eip1344Transition": "0x0", + "eip1706Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip2929Transition": "0x0", + "eip2930Transition": "0x0", + "eip1559Transition": "0x0", + "eip3198Transition": "0x0", + "eip3541Transition": "0x0", + "eip3529Transition": "0x0", + "eip1559BaseFeeMaxChangeDenominator": "0x8", + "eip1559ElasticityMultiplier": "0x2", + "eip1559BaseFeeInitialValue": "0x3B9ACA00" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x400000000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x1388" + }, + "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": { + "builtin": { + "name": "modexp", + "activate_at": "0x00", + "pricing": { + "0": { + "price": { + "modexp2565": {} + } + } + } + } + }, + "0000000000000000000000000000000000000006": { + "builtin": { + "name": "alt_bn128_add", + "pricing": { + "0": { + "price": { + "alt_bn128_const_operations": { + "price": 500 + } + } + }, + "0": { + "info": "EIP 1108 transition", + "price": { + "alt_bn128_const_operations": { + "price": 150 + } + } + } + } + } + }, + "0000000000000000000000000000000000000007": { + "builtin": { + "name": "alt_bn128_mul", + "pricing": { + "0": { + "price": { + "alt_bn128_const_operations": { + "price": 40000 + } + } + }, + "0": { + "info": "EIP 1108 transition", + "price": { + "alt_bn128_const_operations": { + "price": 6000 + } + } + } + } + } + }, + "0000000000000000000000000000000000000008": { + "builtin": { + "name": "alt_bn128_pairing", + "pricing": { + "0": { + "price": { + "alt_bn128_pairing": { + "base": 100000, + "pair": 80000 + } + } + }, + "0": { + "info": "EIP 1108 transition", + "price": { + "alt_bn128_pairing": { + "base": 45000, + "pair": 34000 + } + } + } + } + } + }, + "0000000000000000000000000000000000000009": { + "builtin": { + "name": "blake2_f", + "activate_at": "0x00", + "pricing": { + "blake2_f": { + "gas_per_round": 1 + } + } + } + } + } +} \ No newline at end of file diff --git a/crates/ethcore/res/json_tests b/crates/ethcore/res/json_tests index 644967e34..8a81997b2 160000 --- a/crates/ethcore/res/json_tests +++ b/crates/ethcore/res/json_tests @@ -1 +1 @@ -Subproject commit 644967e345bbc6642fab613e1b1737abbe131f78 +Subproject commit 8a81997b22475f1dd90f0d01128c43b18f8877b2 diff --git a/crates/ethcore/src/block.rs b/crates/ethcore/src/block.rs index 9c31849f6..2e749c9c6 100644 --- a/crates/ethcore/src/block.rs +++ b/crates/ethcore/src/block.rs @@ -139,7 +139,8 @@ impl ExecutedBlock { difficulty: self.header.difficulty().clone(), last_hashes: self.last_hashes.clone(), gas_used: self.receipts.last().map_or(U256::zero(), |r| r.gas_used), - gas_limit: self.header.gas_limit().clone(), + gas_limit: *self.header.gas_limit(), + base_fee: self.header.base_fee(), } } @@ -196,6 +197,9 @@ impl<'x> OpenBlock<'x> { .header .set_timestamp(engine.open_block_header_timestamp(parent.timestamp())); r.block.header.set_extra_data(extra_data); + r.block + .header + .set_base_fee(engine.calculate_base_fee(parent)); let gas_floor_target = cmp::max(gas_range_target.0, engine.params().min_gas_limit); let gas_ceil_target = cmp::max(gas_range_target.1, gas_floor_target); @@ -227,6 +231,11 @@ impl<'x> OpenBlock<'x> { self.block.header.set_gas_limit(U256::max_value()); } + /// Set block gas limit. + pub fn set_gas_limit(&mut self, gas_limit: U256) { + self.block.header.set_gas_limit(gas_limit); + } + // t_nb 8.4 Add an uncle to the block, if possible. /// /// NOTE Will check chain constraints and the uncle number but will NOT check @@ -559,11 +568,9 @@ pub(crate) fn enact( )?; if let Some(ref s) = trace_state { - let env = b.env_info(); - let root = s.root(); - let author_balance = s.balance(&env.author)?; + let author_balance = s.balance(&b.header.author())?; trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n", - b.block.header.number(), root, env.author, author_balance); + b.block.header.number(), s.root(), b.header.author(), author_balance); } // t_nb 8.2 transfer all field from current header to OpenBlock header that we created @@ -632,7 +639,7 @@ mod tests { last_hashes: Arc, factories: Factories, ) -> Result { - let block = Unverified::from_rlp(block_bytes)?; + let block = Unverified::from_rlp(block_bytes, engine.params().eip1559_transition)?; let header = block.header; let transactions: Result, Error> = block .transactions @@ -689,7 +696,8 @@ mod tests { last_hashes: Arc, factories: Factories, ) -> Result { - let header = Unverified::from_rlp(block_bytes.clone())?.header; + let header = + Unverified::from_rlp(block_bytes.clone(), engine.params().eip1559_transition)?.header; Ok(enact_bytes( block_bytes, engine, @@ -846,7 +854,7 @@ mod tests { let bytes = e.rlp_bytes(); assert_eq!(bytes, orig_bytes); - let uncles = view!(BlockView, &bytes).uncles(); + let uncles = view!(BlockView, &bytes).uncles(engine.params().eip1559_transition); assert_eq!(uncles[1].extra_data(), b"uncle2"); let db = e.drain().state.drop().1; diff --git a/crates/ethcore/src/client/bad_blocks.rs b/crates/ethcore/src/client/bad_blocks.rs index e38d66339..c93852935 100644 --- a/crates/ethcore/src/client/bad_blocks.rs +++ b/crates/ethcore/src/client/bad_blocks.rs @@ -21,6 +21,7 @@ use ethereum_types::H256; use itertools::Itertools; use memory_cache::MemoryLruCache; use parking_lot::RwLock; +use types::BlockNumber; use verification::queue::kind::blocks::Unverified; /// Recently seen bad blocks. @@ -38,8 +39,8 @@ impl Default for BadBlocks { impl BadBlocks { /// Reports given RLP as invalid block. - pub fn report(&self, raw: Bytes, message: String) { - match Unverified::from_rlp(raw) { + pub fn report(&self, raw: Bytes, message: String, eip1559_transition: BlockNumber) { + match Unverified::from_rlp(raw, eip1559_transition) { Ok(unverified) => { error!( target: "client", @@ -69,14 +70,14 @@ impl BadBlocks { } /// Returns a list of recently detected bad blocks with error descriptions. - pub fn bad_blocks(&self) -> Vec<(Unverified, String)> { + pub fn bad_blocks(&self, eip1559_transition: BlockNumber) -> Vec<(Unverified, String)> { self.last_blocks .read() .backstore() .iter() .map(|(_k, (unverified, message))| { ( - Unverified::from_rlp(unverified.bytes.clone()) + Unverified::from_rlp(unverified.bytes.clone(), eip1559_transition) .expect("Bytes coming from UnverifiedBlock so decodable; qed"), message.clone(), ) diff --git a/crates/ethcore/src/client/client.rs b/crates/ethcore/src/client/client.rs index 3ac3544b1..04746cd59 100644 --- a/crates/ethcore/src/client/client.rs +++ b/crates/ethcore/src/client/client.rs @@ -355,7 +355,11 @@ impl Importer { .accrue_block(&header, transactions_len); } Err(err) => { - self.bad_blocks.report(bytes, format!("{:?}", err)); + self.bad_blocks.report( + bytes, + format!("{:?}", err), + self.engine.params().eip1559_transition, + ); invalid_blocks.insert(hash); } } @@ -629,7 +633,7 @@ impl Importer { let header = chain .block_header_data(&hash) .expect("Best block is in the database; qed") - .decode() + .decode(self.engine.params().eip1559_transition) .expect("Stored block header is valid RLP; qed"); let details = chain .block_details(&hash) @@ -767,6 +771,7 @@ impl Importer { last_hashes: client.build_last_hashes(header.parent_hash()), gas_used: U256::default(), gas_limit: u64::max_value().into(), + base_fee: header.base_fee(), }; let call = move |addr, data| { @@ -905,7 +910,12 @@ impl Client { } let gb = spec.genesis_block(); - let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone())); + let chain = Arc::new(BlockChain::new( + config.blockchain.clone(), + &gb, + db.clone(), + spec.params().eip1559_transition, + )); let tracedb = RwLock::new(TraceDB::new( config.tracing.clone(), db.clone(), @@ -1144,6 +1154,11 @@ impl Client { last_hashes: self.build_last_hashes(&header.parent_hash()), gas_used: U256::default(), gas_limit: header.gas_limit(), + base_fee: if header.number() >= self.engine.params().eip1559_transition { + Some(header.base_fee()) + } else { + None + }, }) } @@ -1646,7 +1661,9 @@ impl Client { BlockId::Number(number) if number == self.chain.read().best_block_number() => { Some(self.chain.read().best_block_header()) } - _ => self.block_header(id).and_then(|h| h.decode().ok()), + _ => self + .block_header(id) + .and_then(|h| h.decode(self.engine.params().eip1559_transition).ok()), } } } @@ -1673,6 +1690,7 @@ impl snapshot::DatabaseRestore for Client { self.config.blockchain.clone(), &[], db.clone(), + self.engine.params().eip1559_transition, )); *tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone()); Ok(()) @@ -1884,9 +1902,11 @@ impl ImportBlock for Client { } // t_nb 2.5 if block is not okay print error. we only care about block errors (not import errors) Err((Some(block), EthcoreError(EthcoreErrorKind::Block(err), _))) => { - self.importer - .bad_blocks - .report(block.bytes, err.to_string()); + self.importer.bad_blocks.report( + block.bytes, + err.to_string(), + self.engine.params().eip1559_transition, + ); bail!(EthcoreErrorKind::Block(err)) } Err((None, EthcoreError(EthcoreErrorKind::Block(err), _))) => { @@ -1934,6 +1954,7 @@ impl Call for Client { last_hashes: self.build_last_hashes(header.parent_hash()), gas_used: U256::default(), gas_limit: U256::max_value(), + base_fee: header.base_fee(), }; let machine = self.engine.machine(); @@ -1954,6 +1975,7 @@ impl Call for Client { last_hashes: self.build_last_hashes(header.parent_hash()), gas_used: U256::default(), gas_limit: U256::max_value(), + base_fee: header.base_fee(), }; let mut results = Vec::with_capacity(transactions.len()); @@ -1986,6 +2008,7 @@ impl Call for Client { last_hashes: self.build_last_hashes(header.parent_hash()), gas_used: U256::default(), gas_limit: max, + base_fee: header.base_fee(), }; (init, max, env_info) @@ -2069,7 +2092,9 @@ impl EngineInfo for Client { impl BadBlocks for Client { fn bad_blocks(&self) -> Vec<(Unverified, String)> { - self.importer.bad_blocks.bad_blocks() + self.importer + .bad_blocks + .bad_blocks(self.engine.params().eip1559_transition) } } @@ -2654,8 +2679,11 @@ impl BlockChainClient for Client { } fn uncle_extra_info(&self, id: UncleId) -> Option> { - self.uncle(id) - .and_then(|h| h.decode().map(|dh| self.engine.extra_info(&dh)).ok()) + self.uncle(id).and_then(|h| { + h.decode(self.engine.params().eip1559_transition) + .map(|dh| self.engine.extra_info(&dh)) + .ok() + }) } fn pruning_info(&self) -> PruningInfo { @@ -2837,7 +2865,9 @@ impl ReopenBlock for Client { let uncle = chain .block_header_data(&h) .expect("find_uncle_hashes only returns hashes for existing headers; qed"); - let uncle = uncle.decode().expect("decoding failure"); + let uncle = uncle + .decode(self.engine.params().eip1559_transition) + .expect("decoding failure"); block.push_uncle(uncle).expect( "pushing up to maximum_uncle_count; push_uncle is not ok only if more than maximum_uncle_count is pushed; @@ -2889,7 +2919,10 @@ impl PrepareOpenBlock for Client { .take(engine.maximum_uncle_count(open_block.header.number())) .foreach(|h| { open_block - .push_uncle(h.decode().expect("decoding failure")) + .push_uncle( + h.decode(engine.params().eip1559_transition) + .expect("decoding failure"), + ) .expect( "pushing maximum_uncle_count; open_block was just created; @@ -2925,6 +2958,7 @@ impl ImportSealedBlock for Client { self.importer.bad_blocks.report( block.rlp_bytes(), format!("Detected an issue with locally sealed block: {}", e), + self.engine.params().eip1559_transition, ); return Err(e.into()); } @@ -3155,7 +3189,8 @@ impl ImportExportBlocks for Client { }; let do_import = |bytes: Vec| { - let block = Unverified::from_rlp(bytes).map_err(|_| "Invalid block rlp")?; + let block = Unverified::from_rlp(bytes, self.engine.params().eip1559_transition) + .map_err(|_| "Invalid block rlp")?; let number = block.header.number(); while self.queue_info().is_full() { std::thread::sleep(Duration::from_secs(1)); diff --git a/crates/ethcore/src/client/evm_test_client.rs b/crates/ethcore/src/client/evm_test_client.rs index bc9eac40d..5ccb7b14d 100644 --- a/crates/ethcore/src/client/evm_test_client.rs +++ b/crates/ethcore/src/client/evm_test_client.rs @@ -107,6 +107,7 @@ impl<'a> EvmTestClient<'a> { Some(ethereum::new_byzantium_to_constantinoplefixat5_test()) } ForkSpec::Berlin => Some(ethereum::new_berlin_test()), + ForkSpec::London => Some(ethereum::new_london_test()), ForkSpec::FrontierToHomesteadAt5 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 @@ -248,6 +249,7 @@ impl<'a> EvmTestClient<'a> { last_hashes: Arc::new([H256::default(); 256].to_vec()), gas_used: 0.into(), gas_limit: *genesis.gas_limit(), + base_fee: genesis.base_fee(), }; self.call_envinfo(params, tracer, vm_tracer, info) } diff --git a/crates/ethcore/src/client/test_client.rs b/crates/ethcore/src/client/test_client.rs index 5dbb70002..6924e1ca7 100644 --- a/crates/ethcore/src/client/test_client.rs +++ b/crates/ethcore/src/client/test_client.rs @@ -322,7 +322,7 @@ impl TestBlockChainClient { rlp.append(&header); rlp.append_raw(&txs, 1); rlp.append_raw(uncles.as_raw(), 1); - let unverified = Unverified::from_rlp(rlp.out()).unwrap(); + let unverified = Unverified::from_rlp(rlp.out(), BlockNumber::max_value()).unwrap(); self.import_block(unverified).unwrap(); } @@ -339,7 +339,7 @@ impl TestBlockChainClient { let mut header: Header = self .block_header(BlockId::Number(n)) .unwrap() - .decode() + .decode(BlockNumber::max_value()) .expect("decoding failed"); header.set_parent_hash(H256::from_low_u64_be(42)); let mut rlp = RlpStream::new_list(3); @@ -550,7 +550,7 @@ impl BlockInfo for TestBlockChainClient { fn best_block_header(&self) -> Header { self.block_header(BlockId::Hash(self.chain_info().best_block_hash)) .expect("Best block always has header.") - .decode() + .decode(BlockNumber::max_value()) .expect("decoding failed") } @@ -608,7 +608,7 @@ impl ImportBlock for TestBlockChainClient { if number > 0 { match self.blocks.read().get(header.parent_hash()) { Some(parent) => { - let parent = view!(BlockView, parent).header(); + let parent = view!(BlockView, parent).header(BlockNumber::max_value()); if parent.number() != (header.number() - 1) { panic!("Unexpected block parent"); } @@ -638,7 +638,7 @@ impl ImportBlock for TestBlockChainClient { *self.numbers.write().get_mut(&n).unwrap() = parent_hash.clone(); n -= 1; parent_hash = view!(BlockView, &self.blocks.read()[&parent_hash]) - .header() + .header(BlockNumber::max_value()) .parent_hash() .clone(); } @@ -720,7 +720,7 @@ impl StateClient for TestBlockChainClient { impl EngineInfo for TestBlockChainClient { fn engine(&self) -> &dyn EthEngine { - unimplemented!() + &*self.spec.engine } } @@ -882,7 +882,7 @@ impl BlockChainClient for TestBlockChainClient { fn block_extra_info(&self, id: BlockId) -> Option> { self.block(id) - .map(|block| block.view().header()) + .map(|block| block.view().header(BlockNumber::max_value())) .map(|header| self.spec.engine.extra_info(&header)) } diff --git a/crates/ethcore/src/engines/authority_round/mod.rs b/crates/ethcore/src/engines/authority_round/mod.rs index 4591e5e25..18ff69c39 100644 --- a/crates/ethcore/src/engines/authority_round/mod.rs +++ b/crates/ethcore/src/engines/authority_round/mod.rs @@ -49,6 +49,7 @@ use self::finality::RollingFinality; use super::{ signer::EngineSigner, validator_set::{new_validator_set_posdao, SimpleList, ValidatorSet}, + EthEngine, }; use block::*; use bytes::Bytes; @@ -692,6 +693,7 @@ struct EpochVerifier { empty_steps_transition: u64, /// First block for which a 2/3 quorum (instead of 1/2) is required. two_thirds_majority_transition: BlockNumber, + eip1559_transition: BlockNumber, } impl super::EpochVerifier for EpochVerifier { @@ -716,7 +718,9 @@ impl super::EpochVerifier for EpochVerifier { RollingFinality::blank(signers, self.two_thirds_majority_transition); let mut finalized = Vec::new(); - let headers: Vec

= Rlp::new(proof).as_list().ok()?; + let proof_rlp = Rlp::new(proof); + let headers: Vec
= + Header::decode_rlp_list(&proof_rlp, self.eip1559_transition).ok()?; { let mut push_header = |parent_header: &Header, header: Option<&Header>| { @@ -1805,7 +1809,7 @@ impl Engine for AuthorityRound { let parent = client .block_header(::client::BlockId::Hash(*block.header.parent_hash())) .expect("hash is from parent; parent header must exist; qed") - .decode()?; + .decode(self.params().eip1559_transition)?; let parent_step = header_step(&parent, self.empty_steps_transition)?; let current_step = self.step.inner.load(); @@ -2200,6 +2204,7 @@ impl Engine for AuthorityRound { subchain_validators: list, empty_steps_transition: self.empty_steps_transition, two_thirds_majority_transition: self.two_thirds_majority_transition, + eip1559_transition: self.params().eip1559_transition, }); match finalize { diff --git a/crates/ethcore/src/engines/clique/mod.rs b/crates/ethcore/src/engines/clique/mod.rs index 6ab84f6d8..c5107c117 100644 --- a/crates/ethcore/src/engines/clique/mod.rs +++ b/crates/ethcore/src/engines/clique/mod.rs @@ -316,7 +316,8 @@ impl Clique { return Err(BlockError::UnknownParent(last_parent_hash))?; } Some(next) => { - chain.push_front(next.decode()?); + chain + .push_front(next.decode(self.machine.params().eip1559_transition)?); } } } @@ -332,7 +333,7 @@ impl Clique { None => { return Err(EngineError::CliqueMissingCheckpoint(last_checkpoint_hash))? } - Some(header) => header.decode()?, + Some(header) => header.decode(self.machine.params().eip1559_transition)?, }; let last_checkpoint_state = match block_state_by_hash.get_mut(&last_checkpoint_hash) diff --git a/crates/ethcore/src/engines/mod.rs b/crates/ethcore/src/engines/mod.rs index aff6c5e7b..c96284179 100644 --- a/crates/ethcore/src/engines/mod.rs +++ b/crates/ethcore/src/engines/mod.rs @@ -648,6 +648,14 @@ pub trait EthEngine: Engine<::machine::EthereumMachine> { self.machine().decode_transaction(transaction, &schedule) } + /// Calculates base fee for the block that should be mined next. + /// This base fee is calculated based on the parent header (last block in blockchain / best block). + /// + /// Introduced by EIP1559 to support new market fee mechanism. + fn calculate_base_fee(&self, parent: &Header) -> Option { + self.machine().calc_base_fee(parent) + } + /// The configured minimum gas limit. Used by AuRa Engine. fn min_gas_limit(&self) -> U256 { self.params().min_gas_limit diff --git a/crates/ethcore/src/engines/validator_set/multi.rs b/crates/ethcore/src/engines/validator_set/multi.rs index 358413618..29c7392f8 100644 --- a/crates/ethcore/src/engines/validator_set/multi.rs +++ b/crates/ethcore/src/engines/validator_set/multi.rs @@ -287,8 +287,11 @@ mod tests { for i in 1..4 { sync_client .import_block( - Unverified::from_rlp(client.block(BlockId::Number(i)).unwrap().into_inner()) - .unwrap(), + Unverified::from_rlp( + client.block(BlockId::Number(i)).unwrap().into_inner(), + client.engine().params().eip1559_transition, + ) + .unwrap(), ) .unwrap(); } diff --git a/crates/ethcore/src/engines/validator_set/safe_contract.rs b/crates/ethcore/src/engines/validator_set/safe_contract.rs index 451617e2f..8c15318d9 100644 --- a/crates/ethcore/src/engines/validator_set/safe_contract.rs +++ b/crates/ethcore/src/engines/validator_set/safe_contract.rs @@ -70,8 +70,9 @@ impl ::engines::StateDependentProof for StateProof { } fn check_proof(&self, machine: &EthereumMachine, proof: &[u8]) -> Result<(), String> { - let (header, state_items) = decode_first_proof(&Rlp::new(proof)) - .map_err(|e| format!("proof incorrectly encoded: {}", e))?; + let (header, state_items) = + decode_first_proof(&Rlp::new(proof), machine.params().eip1559_transition) + .map_err(|e| format!("proof incorrectly encoded: {}", e))?; if &header != &self.header { return Err("wrong header in proof".into()); } @@ -129,6 +130,7 @@ fn check_first_proof( Arc::new(last_hashes) }, gas_used: 0.into(), + base_fee: old_header.base_fee(), }; // check state proof using given machine. @@ -163,8 +165,11 @@ fn check_first_proof( } } -fn decode_first_proof(rlp: &Rlp) -> Result<(Header, Vec), ::error::Error> { - let header = rlp.val_at(0)?; +fn decode_first_proof( + rlp: &Rlp, + eip1559_transition: BlockNumber, +) -> Result<(Header, Vec), ::error::Error> { + let header = Header::decode_rlp(&rlp.at(0)?, eip1559_transition)?; let state_items = rlp .at(1)? .iter() @@ -188,8 +193,14 @@ fn encode_proof(header: &Header, receipts: &[TypedReceipt]) -> Bytes { stream.drain() } -fn decode_proof(rlp: &Rlp) -> Result<(Header, Vec), ::error::Error> { - Ok((rlp.val_at(0)?, TypedReceipt::decode_rlp_list(&rlp.at(1)?)?)) +fn decode_proof( + rlp: &Rlp, + eip1559_transition: BlockNumber, +) -> Result<(Header, Vec), ::error::Error> { + Ok(( + Header::decode_rlp(&rlp.at(0)?, eip1559_transition)?, + TypedReceipt::decode_rlp_list(&rlp.at(1)?)?, + )) } // given a provider and caller, generate proof. this will just be a state proof @@ -554,7 +565,8 @@ impl ValidatorSet for ValidatorSafeContract { if first { trace!(target: "engine", "Recovering initial epoch set"); - let (old_header, state_items) = decode_first_proof(&rlp)?; + let (old_header, state_items) = + decode_first_proof(&rlp, machine.params().eip1559_transition)?; let number = old_header.number(); let old_hash = old_header.hash(); let addresses = @@ -566,7 +578,7 @@ impl ValidatorSet for ValidatorSafeContract { Ok((SimpleList::new(addresses), Some(old_hash))) } else { - let (old_header, receipts) = decode_proof(&rlp)?; + let (old_header, receipts) = decode_proof(&rlp, machine.params().eip1559_transition)?; // ensure receipts match header. // TODO: optimize? these were just decoded. @@ -830,8 +842,11 @@ mod tests { for i in 1..4 { sync_client .import_block( - Unverified::from_rlp(client.block(BlockId::Number(i)).unwrap().into_inner()) - .unwrap(), + Unverified::from_rlp( + client.block(BlockId::Number(i)).unwrap().into_inner(), + client.engine().params().eip1559_transition, + ) + .unwrap(), ) .unwrap(); } diff --git a/crates/ethcore/src/error.rs b/crates/ethcore/src/error.rs index 5a246f12c..2a27c34b6 100644 --- a/crates/ethcore/src/error.rs +++ b/crates/ethcore/src/error.rs @@ -46,6 +46,10 @@ pub enum BlockError { InvalidSealArity(Mismatch), /// Block has too much gas used. TooMuchGasUsed(OutOfBounds), + /// Gas target increased too much from previous block + GasTargetTooBig(OutOfBounds), + /// Gas target decreased too much from previous block + GasTargetTooSmall(OutOfBounds), /// Uncles hash in header is invalid. InvalidUnclesHash(Mismatch), /// An uncle is from a generation too old. @@ -79,6 +83,8 @@ pub enum BlockError { InvalidSeal, /// Gas limit header field is invalid. InvalidGasLimit(OutOfBounds), + /// Base fee is incorrect; base fee is different from the expected calculated value. + IncorrectBaseFee(Mismatch), /// Receipts trie root header field is invalid. InvalidReceiptsRoot(Mismatch), /// Timestamp header field is invalid. @@ -112,6 +118,8 @@ impl fmt::Display for BlockError { ExtraDataOutOfBounds(ref oob) => format!("Extra block data too long. {}", oob), InvalidSealArity(ref mis) => format!("Block seal in incorrect format: {}", mis), TooMuchGasUsed(ref oob) => format!("Block has too much gas used. {}", oob), + GasTargetTooBig(ref oob) => format!("Gas target is bigger then expected. {}", oob), + GasTargetTooSmall(ref oob) => format!("Gas target is smaller then expected. {}", oob), InvalidUnclesHash(ref mis) => format!("Block has invalid uncles hash: {}", mis), UncleTooOld(ref oob) => format!("Uncle block is too old. {}", oob), UncleIsBrother(ref oob) => format!("Uncle from same generation as block. {}", oob), @@ -131,6 +139,7 @@ impl fmt::Display for BlockError { InvalidProofOfWork(ref oob) => format!("Block has invalid PoW: {}", oob), InvalidSeal => "Block has invalid seal.".into(), InvalidGasLimit(ref oob) => format!("Invalid gas limit: {}", oob), + IncorrectBaseFee(ref mis) => format!("Incorrect base fee: {}", mis), InvalidReceiptsRoot(ref mis) => { format!("Invalid receipts trie root in header: {}", mis) } diff --git a/crates/ethcore/src/ethereum/ethash.rs b/crates/ethcore/src/ethereum/ethash.rs index ffd55eb84..1040d144f 100644 --- a/crates/ethcore/src/ethereum/ethash.rs +++ b/crates/ethcore/src/ethereum/ethash.rs @@ -14,7 +14,12 @@ // You should have received a copy of the GNU General Public License // along with OpenEthereum. If not, see . -use std::{cmp, collections::BTreeMap, path::Path, sync::Arc}; +use std::{ + cmp::{self}, + collections::BTreeMap, + path::Path, + sync::Arc, +}; use ethereum_types::{H256, H64, U256}; use ethjson::{self, uint::Uint}; diff --git a/crates/ethcore/src/ethereum/mod.rs b/crates/ethcore/src/ethereum/mod.rs index 0b412b12e..15b6fe903 100644 --- a/crates/ethcore/src/ethereum/mod.rs +++ b/crates/ethcore/src/ethereum/mod.rs @@ -274,6 +274,14 @@ pub fn new_berlin_test() -> Spec { ) } +/// Create a new Foundation London era spec. +pub fn new_london_test() -> Spec { + load( + None, + include_bytes!("../../res/chainspec/test/london_test.json"), + ) +} + /// Create a new Musicoin-MCIP3-era spec. pub fn new_mcip3_test() -> Spec { load( @@ -298,6 +306,11 @@ pub fn new_homestead_test_machine() -> EthereumMachine { )) } +/// Create a new Foundation London era chain spec. +pub fn new_london_test_machine() -> EthereumMachine { + load_machine(include_bytes!("../../res/chainspec/test/london_test.json")) +} + /// Create a new Foundation Homestead-EIP210-era chain spec as though it never changed from Homestead/Frontier. pub fn new_eip210_test_machine() -> EthereumMachine { load_machine(include_bytes!("../../res/chainspec/test/eip210_test.json")) diff --git a/crates/ethcore/src/executed.rs b/crates/ethcore/src/executed.rs index fc403699b..6e609f8e2 100644 --- a/crates/ethcore/src/executed.rs +++ b/crates/ethcore/src/executed.rs @@ -91,6 +91,13 @@ pub enum ExecutionError { /// Amount of gas in block. gas: U256, }, + /// Transaction's max gas price is lower then block base fee. + GasPriceLowerThanBaseFee { + /// Max gas price of the transaction. + gas_price: U256, + /// Block base fee. + base_fee: U256, + }, /// Returned when transaction nonce does not match state nonce. InvalidNonce { /// Nonce expected. @@ -148,6 +155,13 @@ impl fmt::Display for ExecutionError { already been used, and {} more is required", gas_limit, gas_used, gas ), + GasPriceLowerThanBaseFee { + ref gas_price, + ref base_fee, + } => format!( + "Max gas price is lowert than block base fee. Gas price is {}, while base fee is {}", + gas_price, base_fee + ), InvalidNonce { ref expected, ref got, diff --git a/crates/ethcore/src/executive.rs b/crates/ethcore/src/executive.rs index 76306b5a7..0ddbc6fc9 100644 --- a/crates/ethcore/src/executive.rs +++ b/crates/ethcore/src/executive.rs @@ -1134,6 +1134,13 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { )); } } + TypedTransaction::EIP1559Transaction(_) => { + if !schedule.eip1559 { + return Err(ExecutionError::TransactionMalformed( + "1559 type of transactions not enabled".into(), + )); + } + } TypedTransaction::Legacy(_) => (), //legacy transactions are allways valid }; @@ -1151,17 +1158,15 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { access_list.insert_address(*address); } } - if schedule.eip2930 { - // optional access list - if let TypedTransaction::AccessList(al_tx) = t.as_unsigned() { - for item in al_tx.access_list.iter() { - access_list.insert_address(item.0); - base_gas_required += vm::schedule::EIP2930_ACCESS_LIST_ADDRESS_COST.into(); - for key in item.1.iter() { - access_list.insert_storage_key(item.0, *key); - base_gas_required += - vm::schedule::EIP2930_ACCESS_LIST_STORAGE_KEY_COST.into(); - } + + if let Some(al) = t.access_list() { + for item in al.iter() { + access_list.insert_address(item.0); + base_gas_required += vm::schedule::EIP2930_ACCESS_LIST_ADDRESS_COST.into(); + for key in item.1.iter() { + access_list.insert_storage_key(item.0, *key); + base_gas_required += + vm::schedule::EIP2930_ACCESS_LIST_STORAGE_KEY_COST.into(); } } } @@ -1201,16 +1206,35 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { }); } + // ensure that the user was willing to at least pay the base fee + if t.tx().gas_price < self.info.base_fee.unwrap_or_default() { + return Err(ExecutionError::GasPriceLowerThanBaseFee { + gas_price: t.tx().gas_price, + base_fee: self.info.base_fee.unwrap_or_default(), + }); + } + + // verify that transaction max_fee_per_gas is higher or equal to max_priority_fee_per_gas + if t.tx().gas_price < t.max_priority_fee_per_gas() { + return Err(ExecutionError::TransactionMalformed( + "maxPriorityFeePerGas higher than maxFeePerGas".into(), + )); + } + // TODO: we might need bigints here, or at least check overflows. let balance = self.state.balance(&sender)?; - let gas_cost = t.tx().gas.full_mul(t.tx().gas_price); - let total_cost = U512::from(t.tx().value) + gas_cost; + let gas_cost_effective = t + .tx() + .gas + .full_mul(t.effective_gas_price(self.info.base_fee)); + let gas_cost_max = t.tx().gas.full_mul(t.tx().gas_price); + let needed_balance = U512::from(t.tx().value) + gas_cost_max; // avoid unaffordable transactions let balance512 = U512::from(balance); - if balance512 < total_cost { + if balance512 < needed_balance { return Err(ExecutionError::NotEnoughCash { - required: total_cost, + required: needed_balance, got: balance512, }); } @@ -1223,7 +1247,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { } self.state.sub_balance( &sender, - &U256::try_from(gas_cost).expect("Total cost (value + gas_cost) is lower than max allowed balance (U256); gas_cost has to fit U256; qed"), + &U256::try_from(gas_cost_effective).expect("Total cost (value + gas_cost_effective) is lower than max allowed balance (U256); gas_cost has to fit U256; qed"), &mut substate.to_cleanup_mode(&schedule), )?; @@ -1243,7 +1267,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { sender: sender.clone(), origin: sender.clone(), gas: init_gas, - gas_price: t.tx().gas_price, + gas_price: t.effective_gas_price(self.info.base_fee), value: ActionValue::Transfer(t.tx().value), code: Some(Arc::new(t.tx().data.clone())), data: None, @@ -1266,7 +1290,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { sender: sender.clone(), origin: sender.clone(), gas: init_gas, - gas_price: t.tx().gas_price, + gas_price: t.effective_gas_price(self.info.base_fee), value: ActionValue::Transfer(t.tx().value), code: self.state.code(address)?, code_hash: self.state.code_hash(address)?, @@ -1479,14 +1503,32 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { let gas_left = gas_left_prerefund + refunded; let gas_used = t.tx().gas.saturating_sub(gas_left); - let (refund_value, overflow_1) = gas_left.overflowing_mul(t.tx().gas_price); - let (fees_value, overflow_2) = gas_used.overflowing_mul(t.tx().gas_price); + let (refund_value, overflow_1) = + gas_left.overflowing_mul(t.effective_gas_price(self.info.base_fee)); + let (fees_value, overflow_2) = + gas_used.overflowing_mul(t.effective_gas_price(self.info.base_fee)); if overflow_1 || overflow_2 { return Err(ExecutionError::TransactionMalformed( "U256 Overflow".to_string(), )); } + // Up until now, fees_value is calculated for each type of transaction based on their gas prices + // Now, if eip1559 is activated, burn the base fee + // miner only receives the inclusion fee; note that the base fee is not given to anyone (it is burned) + let fees_value = fees_value.saturating_sub(if schedule.eip1559 { + let (base_fee, overflow_3) = + gas_used.overflowing_mul(self.info.base_fee.unwrap_or_default()); + if overflow_3 { + return Err(ExecutionError::TransactionMalformed( + "U256 Overflow".to_string(), + )); + } + base_fee + } else { + U256::from(0) + }); + trace!("exec::finalize: t.gas={}, sstore_refunds={}, suicide_refunds={}, refunds_bound={}, gas_left_prerefund={}, refunded={}, gas_left={}, gas_used={}, refund_value={}, fees_value={}\n", t.tx().gas, sstore_refunds, suicide_refunds, refunds_bound, gas_left_prerefund, refunded, gas_left, gas_used, refund_value, fees_value); @@ -1519,7 +1561,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { let min_balance = if schedule.kill_dust != CleanDustMode::Off { Some( U256::from(schedule.tx_gas) - .overflowing_mul(t.tx().gas_price) + .overflowing_mul(t.effective_gas_price(self.info.base_fee)) .0, ) } else { @@ -1585,7 +1627,9 @@ mod tests { trace, ExecutiveTracer, ExecutiveVMTracer, FlatTrace, MemoryDiff, NoopTracer, NoopVMTracer, StorageDiff, Tracer, VMExecutedOperation, VMOperation, VMTrace, VMTracer, }; - use types::transaction::{Action, Transaction, TypedTransaction}; + use types::transaction::{ + AccessListTx, Action, EIP1559TransactionTx, Transaction, TypedTransaction, + }; use vm::{ActionParams, ActionValue, CallType, CreateContractAddress, EnvInfo}; fn make_frontier_machine(max_depth: usize) -> EthereumMachine { @@ -1600,6 +1644,12 @@ mod tests { machine } + fn make_london_machine(max_depth: usize) -> EthereumMachine { + let mut machine = ::ethereum::new_london_test_machine(); + machine.set_schedule_creation_rules(Box::new(move |s, _| s.max_depth = max_depth)); + machine + } + #[test] fn test_contract_address() { let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); @@ -2611,6 +2661,53 @@ mod tests { } } + evm_test! {test_transact_eip1559: test_transact_eip1559_int} + fn test_transact_eip1559(factory: Factory) { + let keypair = Random.generate(); + let t = TypedTransaction::EIP1559Transaction(EIP1559TransactionTx { + transaction: AccessListTx::new( + Transaction { + action: Action::Create, + value: U256::from(17), + data: "3331600055".from_hex().unwrap(), + gas: U256::from(100_000), + gas_price: U256::from(150), + nonce: U256::zero(), + }, + vec![ + ( + H160::from_low_u64_be(10), + vec![H256::from_low_u64_be(102), H256::from_low_u64_be(103)], + ), + (H160::from_low_u64_be(400), vec![]), + ], + ), + max_priority_fee_per_gas: U256::from(30), + }) + .sign(keypair.secret(), None); + + let sender = t.sender(); + + let mut state = get_temp_state_with_factory(factory); + state + .add_balance(&sender, &U256::from(15000017), CleanupMode::NoEmpty) + .unwrap(); + let mut info = EnvInfo::default(); + info.gas_limit = U256::from(100_000); + info.base_fee = Some(U256::from(100)); + let machine = make_london_machine(0); + let schedule = machine.schedule(info.number); + + let res = { + let mut ex = Executive::new(&mut state, &info, &machine, &schedule); + let opts = TransactOptions::with_no_tracing(); + ex.transact(&t, opts).unwrap() + }; + + assert_eq!(res.gas, U256::from(100_000)); + assert_eq!(res.gas_used, U256::from(83873)); + } + evm_test! {test_not_enough_cash: test_not_enough_cash_int} fn test_not_enough_cash(factory: Factory) { let keypair = Random.generate(); diff --git a/crates/ethcore/src/externalities.rs b/crates/ethcore/src/externalities.rs index b13bbf109..efcdfcefe 100644 --- a/crates/ethcore/src/externalities.rs +++ b/crates/ethcore/src/externalities.rs @@ -588,6 +588,7 @@ mod tests { last_hashes: Arc::new(vec![]), gas_used: 0.into(), gas_limit: 0.into(), + base_fee: None, } } diff --git a/crates/ethcore/src/json_tests/chain.rs b/crates/ethcore/src/json_tests/chain.rs index b58f4e1fc..cd1c93941 100644 --- a/crates/ethcore/src/json_tests/chain.rs +++ b/crates/ethcore/src/json_tests/chain.rs @@ -204,7 +204,7 @@ pub fn json_chain_test( for b in blockchain.blocks_rlp() { let bytes_len = b.len(); - let block = Unverified::from_rlp(b); + let block = Unverified::from_rlp(b, spec.params().eip1559_transition); match block { Ok(block) => { let num = block.header.number(); diff --git a/crates/ethcore/src/json_tests/local.rs b/crates/ethcore/src/json_tests/local.rs index ebbf25253..dbb5e240c 100644 --- a/crates/ethcore/src/json_tests/local.rs +++ b/crates/ethcore/src/json_tests/local.rs @@ -4,7 +4,10 @@ use ethjson::{self, blockchain::Block}; use log::warn; use rlp::RlpStream; use std::path::Path; -use types::transaction::{TypedTransaction, TypedTxId, UnverifiedTransaction}; +use types::{ + transaction::{TypedTransaction, TypedTxId, UnverifiedTransaction}, + BlockNumber, +}; use verification::queue::kind::blocks::Unverified; pub fn json_local_block_en_de_test( @@ -22,7 +25,7 @@ pub fn json_local_block_en_de_test( for (name, ref_block) in tests.into_iter() { start_stop_hook(&name, HookType::OnStart); - let block = Unverified::from_rlp(ref_block.rlp()); + let block = Unverified::from_rlp(ref_block.rlp(), BlockNumber::max_value()); let block = match block { Ok(block) => block, Err(decoder_err) => { @@ -148,10 +151,11 @@ pub fn is_same_block(ref_block: &Block, block: &Unverified) -> bool { TypedTxId::Legacy => { test_exp(tx.legacy_v() == ref_tx.v.0.as_u64(), "Original Sig V") } - TypedTxId::AccessList => { + TypedTxId::AccessList | TypedTxId::EIP1559Transaction => { test_exp(tx.standard_v() as u64 == ref_tx.v.0.as_u64(), "Sig V"); let al = match tx.as_unsigned() { TypedTransaction::AccessList(tx) => &tx.access_list, + TypedTransaction::EIP1559Transaction(tx) => &tx.transaction.access_list, _ => { println!("Wrong data in tx type"); continue; diff --git a/crates/ethcore/src/machine/impls.rs b/crates/ethcore/src/machine/impls.rs index f5924e044..1f0a6464c 100644 --- a/crates/ethcore/src/machine/impls.rs +++ b/crates/ethcore/src/machine/impls.rs @@ -17,7 +17,7 @@ //! Ethereum-like state machine definition. use std::{ - cmp, + cmp::{self, max}, collections::{BTreeMap, HashMap}, sync::Arc, }; @@ -47,9 +47,6 @@ use state::{CleanupMode, Substate}; use trace::{NoopTracer, NoopVMTracer}; use tx_filter::TransactionFilter; -/// Open tries to round block.gas_limit to multiple of this constant -pub const GAS_LIMIT_DETERMINANT: U256 = U256([37, 0, 0, 0]); - /// Ethash-specific extensions. #[derive(Debug, Clone)] pub struct EthashExtensions { @@ -269,55 +266,31 @@ impl EthereumMachine { gas_ceil_target: U256, ) { header.set_difficulty(parent.difficulty().clone()); - let gas_limit = parent.gas_limit().clone(); + let gas_limit = parent.gas_limit() * self.schedule(header.number()).eip1559_gas_limit_bump; assert!(!gas_limit.is_zero(), "Gas limit should be > 0"); - if let Some(ref ethash_params) = self.ethash_extensions { - let gas_limit = { - let bound_divisor = self.params().gas_limit_bound_divisor; - let lower_limit = gas_limit - gas_limit / bound_divisor + 1; - let upper_limit = gas_limit + gas_limit / bound_divisor - 1; - let gas_limit = if gas_limit < gas_floor_target { - let gas_limit = cmp::min(gas_floor_target, upper_limit); - round_block_gas_limit(gas_limit, lower_limit, upper_limit) - } else if gas_limit > gas_ceil_target { - let gas_limit = cmp::max(gas_ceil_target, lower_limit); - round_block_gas_limit(gas_limit, lower_limit, upper_limit) - } else { - let total_lower_limit = cmp::max(lower_limit, gas_floor_target); - let total_upper_limit = cmp::min(upper_limit, gas_ceil_target); - let gas_limit = cmp::max( - gas_floor_target, - cmp::min( - total_upper_limit, - lower_limit + (header.gas_used().clone() * 6u32 / 5) / bound_divisor, - ), - ); - round_block_gas_limit(gas_limit, total_lower_limit, total_upper_limit) - }; - // ensure that we are not violating protocol limits - debug_assert!(gas_limit >= lower_limit); - debug_assert!(gas_limit <= upper_limit); - gas_limit - }; + let gas_limit_target = if self.schedule(header.number()).eip1559 { + gas_ceil_target + } else { + gas_floor_target + }; - header.set_gas_limit(gas_limit); + header.set_gas_limit({ + let bound_divisor = self.params().gas_limit_bound_divisor; + if gas_limit < gas_limit_target { + cmp::min(gas_limit_target, gas_limit + gas_limit / bound_divisor - 1) + } else { + cmp::max(gas_limit_target, gas_limit - gas_limit / bound_divisor + 1) + } + }); + + if let Some(ref ethash_params) = self.ethash_extensions { if header.number() >= ethash_params.dao_hardfork_transition && header.number() <= ethash_params.dao_hardfork_transition + 9 { header.set_extra_data(b"dao-hard-fork"[..].to_owned()); } - return; } - - header.set_gas_limit({ - let bound_divisor = self.params().gas_limit_bound_divisor; - if gas_limit < gas_floor_target { - cmp::min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1) - } else { - cmp::max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1) - } - }); } /// Get the general parameters of the chain. @@ -400,8 +373,16 @@ impl EthereumMachine { pub fn verify_transaction_unordered( &self, t: UnverifiedTransaction, - _header: &Header, + header: &Header, ) -> Result { + // ensure that the user was willing to at least pay the base fee + if t.tx().gas_price < header.base_fee().unwrap_or_default() { + return Err(transaction::Error::GasPriceLowerThanBaseFee { + gas_price: t.tx().gas_price, + base_fee: header.base_fee().unwrap_or_default(), + }); + } + Ok(SignedTransaction::new(t)?) } @@ -472,11 +453,61 @@ impl EthereumMachine { transaction::TypedTxId::AccessList if !schedule.eip2930 => { return Err(transaction::Error::TransactionTypeNotEnabled) } + transaction::TypedTxId::EIP1559Transaction if !schedule.eip1559 => { + return Err(transaction::Error::TransactionTypeNotEnabled) + } _ => (), }; Ok(tx) } + + /// Calculates base fee for the block that should be mined next. + /// Base fee is calculated based on the parent header (last block in blockchain / best block). + /// + /// Introduced by EIP1559 to support new market fee mechanism. + pub fn calc_base_fee(&self, parent: &Header) -> Option { + // Block eip1559_transition - 1 has base_fee = None + if parent.number() + 1 < self.params().eip1559_transition { + return None; + } + + // Block eip1559_transition has base_fee = self.params().eip1559_base_fee_initial_value + if parent.number() + 1 == self.params().eip1559_transition { + return Some(self.params().eip1559_base_fee_initial_value); + } + + // Block eip1559_transition + 1 has base_fee = calculated + let base_fee_denominator = match self.params().eip1559_base_fee_max_change_denominator { + None => panic!("Can't calculate base fee if base fee denominator does not exist."), + Some(denominator) if denominator == U256::from(0) => { + panic!("Can't calculate base fee if base fee denominator is zero.") + } + Some(denominator) => denominator, + }; + + let parent_base_fee = parent.base_fee().unwrap_or_default(); + let parent_gas_target = parent.gas_limit() / self.params().eip1559_elasticity_multiplier; + if parent_gas_target == U256::zero() { + panic!("Can't calculate base fee if parent gas target is zero."); + } + + if parent.gas_used() == &parent_gas_target { + Some(parent_base_fee) + } else if parent.gas_used() > &parent_gas_target { + let gas_used_delta = parent.gas_used() - parent_gas_target; + let base_fee_per_gas_delta = max( + parent_base_fee * gas_used_delta / parent_gas_target / base_fee_denominator, + U256::from(1), + ); + Some(parent_base_fee + base_fee_per_gas_delta) + } else { + let gas_used_delta = parent_gas_target - parent.gas_used(); + let base_fee_per_gas_delta = + parent_base_fee * gas_used_delta / parent_gas_target / base_fee_denominator; + Some(max(parent_base_fee - base_fee_per_gas_delta, U256::zero())) + } + } } /// Auxiliary data fetcher for an Ethereum machine. In Ethereum-like machines @@ -525,27 +556,10 @@ impl super::Machine for EthereumMachine { } } -// Try to round gas_limit a bit so that: -// 1) it will still be in desired range -// 2) it will be a nearest (with tendency to increase) multiple of GAS_LIMIT_DETERMINANT -fn round_block_gas_limit(gas_limit: U256, lower_limit: U256, upper_limit: U256) -> U256 { - let increased_gas_limit = - gas_limit + (GAS_LIMIT_DETERMINANT - gas_limit % GAS_LIMIT_DETERMINANT); - if increased_gas_limit > upper_limit { - let decreased_gas_limit = increased_gas_limit - GAS_LIMIT_DETERMINANT; - if decreased_gas_limit < lower_limit { - gas_limit - } else { - decreased_gas_limit - } - } else { - increased_gas_limit - } -} - #[cfg(test)] mod tests { use super::*; + use crate::ethereum::new_london_test_machine; use ethereum_types::H160; use std::str::FromStr; @@ -585,79 +599,65 @@ mod tests { } #[test] - fn ethash_gas_limit_is_multiple_of_determinant() { - use ethereum_types::U256; + fn calculate_base_fee_success() { + let machine = new_london_test_machine(); + let parent_base_fees = [ + U256::from(1000000000), + U256::from(1000000000), + U256::from(1000000000), + U256::from(1072671875), + U256::from(1059263476), + U256::from(1049238967), + U256::from(1049238967), + U256::from(0), + U256::from(1), + U256::from(2), + ]; + let parent_gas_used = [ + U256::from(10000000), + U256::from(10000000), + U256::from(10000000), + U256::from(9000000), + U256::from(10001000), + U256::from(0), + U256::from(10000000), + U256::from(10000000), + U256::from(10000000), + U256::from(10000000), + ]; + let parent_gas_limit = [ + U256::from(10000000), + U256::from(12000000), + U256::from(14000000), + U256::from(10000000), + U256::from(14000000), + U256::from(2000000), + U256::from(18000000), + U256::from(18000000), + U256::from(18000000), + U256::from(18000000), + ]; + let expected_base_fee = [ + U256::from(1125000000), + U256::from(1083333333), + U256::from(1053571428), + U256::from(1179939062), + U256::from(1116028649), + U256::from(918084097), + U256::from(1063811730), + U256::from(1), + U256::from(2), + U256::from(3), + ]; - let spec = ::ethereum::new_homestead_test(); - let ethparams = get_default_ethash_extensions(); + for i in 0..parent_base_fees.len() { + let mut parent_header = Header::default(); + parent_header.set_base_fee(Some(parent_base_fees[i])); + parent_header.set_gas_used(parent_gas_used[i]); + parent_header.set_gas_limit(parent_gas_limit[i]); - let machine = EthereumMachine::with_ethash_extensions( - spec.params().clone(), - Default::default(), - ethparams, - ); - - let mut parent = ::types::header::Header::new(); - let mut header = ::types::header::Header::new(); - header.set_number(1); - - // this test will work for this constant only - assert_eq!(GAS_LIMIT_DETERMINANT, U256::from(37)); - - // when parent.gas_limit < gas_floor_target: - parent.set_gas_limit(U256::from(50_000)); - machine.populate_from_parent( - &mut header, - &parent, - U256::from(100_000), - U256::from(200_000), - ); - assert_eq!(*header.gas_limit(), U256::from(50_024)); - - // when parent.gas_limit > gas_ceil_target: - parent.set_gas_limit(U256::from(250_000)); - machine.populate_from_parent( - &mut header, - &parent, - U256::from(100_000), - U256::from(200_000), - ); - assert_eq!(*header.gas_limit(), U256::from(249_787)); - - // when parent.gas_limit is in miner's range - header.set_gas_used(U256::from(150_000)); - parent.set_gas_limit(U256::from(150_000)); - machine.populate_from_parent( - &mut header, - &parent, - U256::from(100_000), - U256::from(200_000), - ); - assert_eq!(*header.gas_limit(), U256::from(150_035)); - - // when parent.gas_limit is in miner's range - // && we can NOT increase it to be multiple of constant - header.set_gas_used(U256::from(150_000)); - parent.set_gas_limit(U256::from(150_000)); - machine.populate_from_parent( - &mut header, - &parent, - U256::from(100_000), - U256::from(150_002), - ); - assert_eq!(*header.gas_limit(), U256::from(149_998)); - - // when parent.gas_limit is in miner's range - // && we can NOT increase it to be multiple of constant - // && we can NOT decrease it to be multiple of constant - header.set_gas_used(U256::from(150_000)); - parent.set_gas_limit(U256::from(150_000)); - machine.populate_from_parent( - &mut header, - &parent, - U256::from(150_000), - U256::from(150_002), - ); - assert_eq!(*header.gas_limit(), U256::from(150_002)); + let base_fee = machine.calc_base_fee(&parent_header); + assert_eq!(expected_base_fee[i], base_fee.unwrap()); + } } } diff --git a/crates/ethcore/src/miner/miner.rs b/crates/ethcore/src/miner/miner.rs index d9336e538..cebb53f2b 100644 --- a/crates/ethcore/src/miner/miner.rs +++ b/crates/ethcore/src/miner/miner.rs @@ -187,6 +187,7 @@ impl Default for MinerOptions { pool_verification_options: pool::verifier::Options { minimal_gas_price: DEFAULT_MINIMAL_GAS_PRICE.into(), block_gas_limit: U256::max_value(), + block_base_fee: None, tx_gas_limit: U256::max_value(), no_early_reject: false, }, @@ -337,6 +338,7 @@ impl Miner { pool_verification_options: pool::verifier::Options { minimal_gas_price, block_gas_limit: U256::max_value(), + block_base_fee: None, tx_gas_limit: U256::max_value(), no_early_reject: false, }, @@ -376,7 +378,11 @@ impl Miner { /// Updates transaction queue verification limits. /// /// Limits consist of current block gas limit and minimal gas price. - pub fn update_transaction_queue_limits(&self, block_gas_limit: U256) { + pub fn update_transaction_queue_limits( + &self, + block_gas_limit: U256, + block_base_fee: Option, + ) { trace!(target: "miner", "minimal_gas_price: recalibrating..."); let txq = self.transaction_queue.clone(); let mut options = self.options.pool_verification_options.clone(); @@ -384,8 +390,14 @@ impl Miner { debug!(target: "miner", "minimal_gas_price: Got gas price! {}", gas_price); options.minimal_gas_price = gas_price; options.block_gas_limit = block_gas_limit; + options.block_base_fee = block_base_fee; txq.set_verifier_options(options); }); + + match block_base_fee { + Some(block_base_fee) => self.transaction_queue.update_scoring(block_base_fee), + None => (), + } } /// Returns ServiceTransactionChecker @@ -498,11 +510,9 @@ impl Miner { let client = self.pool_client(chain); let engine_params = self.engine.params(); - let min_tx_gas: U256 = self - .engine - .schedule(chain_info.best_block_number) - .tx_gas - .into(); + let schedule = self.engine.schedule(block_number); + let min_tx_gas: U256 = schedule.tx_gas.into(); + let gas_limit = open_block.header.gas_limit(); let nonce_cap: Option = if chain_info.best_block_number + 1 >= engine_params.dust_protection_transition { @@ -515,11 +525,7 @@ impl Miner { usize::max_value() } else { MAX_SKIPPED_TRANSACTIONS.saturating_add( - cmp::min( - *open_block.header.gas_limit() / min_tx_gas, - u64::max_value().into(), - ) - .as_u64() as usize, + cmp::min(gas_limit / min_tx_gas, u64::max_value().into()).as_u64() as usize, ) }; @@ -531,6 +537,10 @@ impl Miner { nonce_cap, max_len: max_transactions.saturating_sub(engine_txs.len()), ordering: miner::PendingOrdering::Priority, + includable_boundary: self + .engine + .calculate_base_fee(&chain.best_block_header()) + .unwrap_or_default(), }, ); @@ -740,7 +750,7 @@ impl Miner { trace!(target: "miner", "seal_block_internally: attempting internal seal."); let parent_header = match chain.block_header(BlockId::Hash(*block.header.parent_hash())) { - Some(h) => match h.decode() { + Some(h) => match h.decode(self.engine.params().eip1559_transition) { Ok(decoded_hdr) => decoded_hdr, Err(_) => return false, }, @@ -1176,6 +1186,7 @@ impl miner::MinerService for Miner { nonce_cap, max_len, ordering, + includable_boundary: Default::default(), }; if let Some(ref f) = filter { @@ -1432,8 +1443,18 @@ impl miner::MinerService for Miner { } // t_nb 10.1 First update gas limit in transaction queue and minimal gas price. - let gas_limit = *chain.best_block_header().gas_limit(); - self.update_transaction_queue_limits(gas_limit); + let base_fee = self.engine.calculate_base_fee(&chain.best_block_header()); + let gas_limit = chain.best_block_header().gas_limit() + // multiplication neccesary only if OE nodes are the only miners in network, not really essential but wont hurt + * if self.engine.gas_limit_override(&chain.best_block_header()).is_none() { + self + .engine + .schedule(chain.best_block_header().number() + 1) + .eip1559_gas_limit_bump + } else { + 1 + }; + self.update_transaction_queue_limits(gas_limit, base_fee); // t_nb 10.2 Then import all transactions from retracted blocks (retracted means from side chain). let client = self.pool_client(chain); @@ -1632,6 +1653,7 @@ mod tests { pool_verification_options: pool::verifier::Options { minimal_gas_price: 0.into(), block_gas_limit: U256::max_value(), + block_base_fee: None, tx_gas_limit: U256::max_value(), no_early_reject: false, }, diff --git a/crates/ethcore/src/miner/pool_client.rs b/crates/ethcore/src/miner/pool_client.rs index d4e9f198a..f9fdbc768 100644 --- a/crates/ethcore/src/miner/pool_client.rs +++ b/crates/ethcore/src/miner/pool_client.rs @@ -155,9 +155,8 @@ where ) -> Result { self.engine .verify_transaction_basic(&tx, &self.best_block_header)?; - let tx = self - .engine - .verify_transaction_unordered(tx, &self.best_block_header)?; + + let tx = SignedTransaction::new(tx)?; self.engine .machine() diff --git a/crates/ethcore/src/snapshot/block.rs b/crates/ethcore/src/snapshot/block.rs index 9b276621c..57004a756 100644 --- a/crates/ethcore/src/snapshot/block.rs +++ b/crates/ethcore/src/snapshot/block.rs @@ -17,15 +17,17 @@ //! Block RLP compression. use bytes::Bytes; -use ethereum_types::H256; +use ethereum_types::{H256, U256}; use hash::keccak; use rlp::{DecoderError, Rlp, RlpStream}; use triehash::ordered_trie_root; -use types::{block::Block, header::Header, transaction::TypedTransaction, views::BlockView}; +use types::{ + block::Block, header::Header, transaction::TypedTransaction, views::BlockView, BlockNumber, +}; const HEADER_FIELDS: usize = 8; const BLOCK_FIELDS: usize = 2; - +#[derive(Debug)] pub struct AbridgedBlock { rlp: Bytes, } @@ -43,12 +45,19 @@ impl AbridgedBlock { /// Given a full block view, trim out the parent hash and block number, /// producing new rlp. - pub fn from_block_view(block_view: &BlockView) -> Self { + pub fn from_block_view(block_view: &BlockView, eip1559_transition: BlockNumber) -> Self { let header = block_view.header_view(); - let seal_fields = header.seal(); + let eip1559 = header.number() >= eip1559_transition; + let seal_fields = header.seal(eip1559); + + let nmb_of_elements = if eip1559 { + HEADER_FIELDS + seal_fields.len() + BLOCK_FIELDS + 1 + } else { + HEADER_FIELDS + seal_fields.len() + BLOCK_FIELDS + }; // 10 header fields, unknown number of seal fields, and 2 block fields. - let mut stream = RlpStream::new_list(HEADER_FIELDS + seal_fields.len() + BLOCK_FIELDS); + let mut stream = RlpStream::new_list(nmb_of_elements); // write header values. stream @@ -64,13 +73,17 @@ impl AbridgedBlock { // write block values. TypedTransaction::rlp_append_list(&mut stream, &block_view.transactions()); - stream.append_list(&block_view.uncles()); + stream.append_list(&block_view.uncles(eip1559_transition)); // write seal fields. for field in seal_fields { stream.append_raw(&field, 1); } + if eip1559 { + stream.append(&header.base_fee()); + } + AbridgedBlock { rlp: stream.out() } } @@ -82,6 +95,7 @@ impl AbridgedBlock { parent_hash: H256, number: u64, receipts_root: H256, + eip1559_transition: BlockNumber, ) -> Result { let rlp = Rlp::new(&self.rlp); @@ -98,7 +112,7 @@ impl AbridgedBlock { header.set_extra_data(rlp.val_at(7)?); let transactions = TypedTransaction::decode_rlp_list(&rlp.at(8)?)?; - let uncles: Vec
= rlp.list_at(9)?; + let uncles = Header::decode_rlp_list(&rlp.at(9)?, eip1559_transition)?; header.set_transactions_root(ordered_trie_root(rlp.at(8)?.iter().map(|r| { if r.is_list() { @@ -115,13 +129,21 @@ impl AbridgedBlock { header.set_uncles_hash(keccak(uncles_rlp.as_raw())); let mut seal_fields = Vec::new(); - for i in (HEADER_FIELDS + BLOCK_FIELDS)..rlp.item_count()? { + let last_seal_index = if number >= eip1559_transition { + rlp.item_count()? - 1 + } else { + rlp.item_count()? + }; + for i in (HEADER_FIELDS + BLOCK_FIELDS)..last_seal_index { let seal_rlp = rlp.at(i)?; seal_fields.push(seal_rlp.as_raw().to_owned()); } - header.set_seal(seal_fields); + if number >= eip1559_transition { + header.set_base_fee(Some(rlp.val_at::(rlp.item_count()? - 1)?)); + } + Ok(Block { header: header, transactions: transactions, @@ -141,6 +163,7 @@ mod tests { transaction::{Action, Transaction, TypedTransaction}, view, views::BlockView, + BlockNumber, }; fn encode_block(b: &Block) -> Bytes { @@ -153,10 +176,29 @@ mod tests { let receipts_root = b.header.receipts_root().clone(); let encoded = encode_block(&b); - let abridged = AbridgedBlock::from_block_view(&view!(BlockView, &encoded)); + let abridged = + AbridgedBlock::from_block_view(&view!(BlockView, &encoded), BlockNumber::max_value()); assert_eq!( abridged - .to_block(H256::default(), 0, receipts_root) + .to_block(H256::default(), 0, receipts_root, BlockNumber::max_value()) + .unwrap(), + b + ); + } + + #[test] + fn eip1559_block_abridging() { + let mut b = Block::default(); + b.header.set_base_fee(Some(U256::from(100))); + b.header.set_seal(vec![vec![50u8], vec![60u8]]); + let receipts_root = b.header.receipts_root().clone(); + let encoded = encode_block(&b); + + let abridged = + AbridgedBlock::from_block_view(&view!(BlockView, &encoded), BlockNumber::default()); + assert_eq!( + abridged + .to_block(H256::default(), 0, receipts_root, BlockNumber::default()) .unwrap(), b ); @@ -169,10 +211,11 @@ mod tests { let receipts_root = b.header.receipts_root().clone(); let encoded = encode_block(&b); - let abridged = AbridgedBlock::from_block_view(&view!(BlockView, &encoded)); + let abridged = + AbridgedBlock::from_block_view(&view!(BlockView, &encoded), BlockNumber::max_value()); assert_eq!( abridged - .to_block(H256::default(), 2, receipts_root) + .to_block(H256::default(), 2, receipts_root, BlockNumber::max_value()) .unwrap(), b ); @@ -213,10 +256,13 @@ mod tests { let encoded = encode_block(&b); - let abridged = AbridgedBlock::from_block_view(&view!(BlockView, &encoded[..])); + let abridged = AbridgedBlock::from_block_view( + &view!(BlockView, &encoded[..]), + BlockNumber::max_value(), + ); assert_eq!( abridged - .to_block(H256::default(), 0, receipts_root) + .to_block(H256::default(), 0, receipts_root, BlockNumber::max_value()) .unwrap(), b ); diff --git a/crates/ethcore/src/snapshot/consensus/authority.rs b/crates/ethcore/src/snapshot/consensus/authority.rs index 4b03d6588..07a84ff33 100644 --- a/crates/ethcore/src/snapshot/consensus/authority.rs +++ b/crates/ethcore/src/snapshot/consensus/authority.rs @@ -38,6 +38,7 @@ use itertools::{Itertools, Position}; use rlp::{Rlp, RlpStream}; use types::{ encoded, header::Header, ids::BlockId, receipt::TypedReceipt, transaction::TypedTransaction, + BlockNumber, }; /// Snapshot creation and restoration for PoA chains. @@ -61,6 +62,7 @@ impl SnapshotComponents for PoaSnapshot { sink: &mut ChunkSink, _progress: &Progress, preferred_size: usize, + eip1559_transition: BlockNumber, ) -> Result<(), Error> { let number = chain .block_number(&block_at) @@ -107,7 +109,7 @@ impl SnapshotComponents for PoaSnapshot { .block(&block_at) .and_then(|b| chain.block_receipts(&block_at).map(|r| (b, r))) .ok_or_else(|| Error::BlockNotFound(block_at))?; - let block = block.decode()?; + let block = block.decode(eip1559_transition)?; let parent_td = chain .block_details(block.header.parent_hash()) @@ -200,7 +202,8 @@ impl ChunkRebuilder { use engines::ConstructedVerifier; // decode. - let header: Header = transition_rlp.val_at(0)?; + let header = + Header::decode_rlp(&transition_rlp.at(0)?, engine.params().eip1559_transition)?; let epoch_data: Bytes = transition_rlp.val_at(1)?; trace!(target: "snapshot", "verifying transition to epoch at block {}", header.number()); @@ -350,9 +353,12 @@ impl Rebuilder for ChunkRebuilder { let last_rlp = rlp.at(num_items - 1)?; let block = Block { - header: last_rlp.val_at(0)?, + header: Header::decode_rlp(&last_rlp.at(0)?, engine.params().eip1559_transition)?, transactions: TypedTransaction::decode_rlp_list(&last_rlp.at(1)?)?, - uncles: last_rlp.list_at(2)?, + uncles: Header::decode_rlp_list( + &last_rlp.at(2)?, + engine.params().eip1559_transition, + )?, }; let block_data = block.rlp_bytes(); let receipts = TypedReceipt::decode_rlp_list(&last_rlp.at(3)?)?; diff --git a/crates/ethcore/src/snapshot/consensus/mod.rs b/crates/ethcore/src/snapshot/consensus/mod.rs index 0a0865a70..44db5f724 100644 --- a/crates/ethcore/src/snapshot/consensus/mod.rs +++ b/crates/ethcore/src/snapshot/consensus/mod.rs @@ -22,6 +22,7 @@ use std::sync::{atomic::AtomicBool, Arc}; use blockchain::{BlockChain, BlockChainDB}; use engines::EthEngine; use snapshot::{Error, ManifestData, Progress}; +use types::BlockNumber; use ethereum_types::H256; @@ -49,6 +50,7 @@ pub trait SnapshotComponents: Send { chunk_sink: &mut ChunkSink, progress: &Progress, preferred_size: usize, + eip1559_transition: BlockNumber, ) -> Result<(), Error>; /// Create a rebuilder, which will have chunks fed into it in aribtrary diff --git a/crates/ethcore/src/snapshot/consensus/work.rs b/crates/ethcore/src/snapshot/consensus/work.rs index 5c5d5305a..58a8525f3 100644 --- a/crates/ethcore/src/snapshot/consensus/work.rs +++ b/crates/ethcore/src/snapshot/consensus/work.rs @@ -38,7 +38,7 @@ use ethereum_types::H256; use rand::rngs::OsRng; use rlp::{Rlp, RlpStream}; use snapshot::{block::AbridgedBlock, Error, ManifestData, Progress}; -use types::encoded; +use types::{encoded, BlockNumber}; /// Snapshot creation and restoration for PoW chains. /// This includes blocks from the head of the chain as a @@ -70,6 +70,7 @@ impl SnapshotComponents for PowSnapshot { chunk_sink: &mut ChunkSink, progress: &Progress, preferred_size: usize, + eip1559_transition: BlockNumber, ) -> Result<(), Error> { PowWorker { chain: chain, @@ -79,7 +80,7 @@ impl SnapshotComponents for PowSnapshot { progress: progress, preferred_size: preferred_size, } - .chunk_all(self.blocks) + .chunk_all(self.blocks, eip1559_transition) } fn rebuilder( @@ -119,7 +120,11 @@ struct PowWorker<'a> { impl<'a> PowWorker<'a> { // Repeatedly fill the buffers and writes out chunks, moving backwards from starting block hash. // Loops until we reach the first desired block, and writes out the remainder. - fn chunk_all(&mut self, snapshot_blocks: u64) -> Result<(), Error> { + fn chunk_all( + &mut self, + snapshot_blocks: u64, + eip1559_transition: BlockNumber, + ) -> Result<(), Error> { let mut loaded_size = 0; let mut last = self.current_hash; @@ -140,7 +145,8 @@ impl<'a> PowWorker<'a> { }) .ok_or_else(|| Error::BlockNotFound(self.current_hash))?; - let abridged_rlp = AbridgedBlock::from_block_view(&block.view()).into_inner(); + let abridged_rlp = + AbridgedBlock::from_block_view(&block.view(), eip1559_transition).into_inner(); let pair = { let mut pair_stream = RlpStream::new_list(2); @@ -301,7 +307,12 @@ impl Rebuilder for PowRebuilder { } })); - let block = abridged_block.to_block(parent_hash, cur_number, receipts_root)?; + let block = abridged_block.to_block( + parent_hash, + cur_number, + receipts_root, + engine.params().eip1559_transition, + )?; let block_bytes = encoded::Block::new(block.rlp_bytes()); let is_best = cur_number == self.best_number; diff --git a/crates/ethcore/src/snapshot/mod.rs b/crates/ethcore/src/snapshot/mod.rs index 32182de39..1cf466d1a 100644 --- a/crates/ethcore/src/snapshot/mod.rs +++ b/crates/ethcore/src/snapshot/mod.rs @@ -273,6 +273,7 @@ pub fn chunk_secondary<'a>( &mut chunk_sink, progress, PREFERRED_CHUNK_SIZE, + chain.eip1559_transition, )?; } @@ -621,7 +622,8 @@ pub fn verify_old_block( if always || rng.gen::() <= POW_VERIFY_RATE { engine.verify_block_unordered(header)?; match chain.block_header_data(header.parent_hash()) { - Some(parent) => engine.verify_block_family(header, &parent.decode()?), + Some(parent) => engine + .verify_block_family(header, &parent.decode(engine.params().eip1559_transition)?), None => Ok(()), } } else { diff --git a/crates/ethcore/src/snapshot/service.rs b/crates/ethcore/src/snapshot/service.rs index 3b31d97ff..10519c06d 100644 --- a/crates/ethcore/src/snapshot/service.rs +++ b/crates/ethcore/src/snapshot/service.rs @@ -117,7 +117,12 @@ impl Restoration { let raw_db = params.db; - let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone()); + let chain = BlockChain::new( + Default::default(), + params.genesis, + raw_db.clone(), + params.engine.params().eip1559_transition, + ); let components = params .engine .snapshot_components() @@ -386,7 +391,12 @@ impl Service { let cur_chain_info = self.client.chain_info(); let next_db = self.restoration_db_handler.open(&rest_db)?; - let next_chain = BlockChain::new(Default::default(), &[], next_db.clone()); + let next_chain = BlockChain::new( + Default::default(), + &[], + next_db.clone(), + self.engine.params().eip1559_transition, + ); let next_chain_info = next_chain.chain_info(); // The old database looks like this: diff --git a/crates/ethcore/src/snapshot/tests/helpers.rs b/crates/ethcore/src/snapshot/tests/helpers.rs index fadc7067d..1bbc60961 100644 --- a/crates/ethcore/src/snapshot/tests/helpers.rs +++ b/crates/ethcore/src/snapshot/tests/helpers.rs @@ -171,7 +171,12 @@ pub fn restore( let mut state = StateRebuilder::new(db.key_value().clone(), journaldb::Algorithm::Archive); let mut secondary = { - let chain = BlockChain::new(Default::default(), genesis, db.clone()); + let chain = BlockChain::new( + Default::default(), + genesis, + db.clone(), + engine.params().eip1559_transition, + ); components.rebuilder(chain, db, manifest).unwrap() }; diff --git a/crates/ethcore/src/snapshot/tests/proof_of_work.rs b/crates/ethcore/src/snapshot/tests/proof_of_work.rs index 44506f23e..6a2dfac58 100644 --- a/crates/ethcore/src/snapshot/tests/proof_of_work.rs +++ b/crates/ethcore/src/snapshot/tests/proof_of_work.rs @@ -51,7 +51,12 @@ fn chunk_and_restore(amount: u64) { let snapshot_path = tempdir.path().join("SNAP"); let old_db = test_helpers::new_db(); - let bc = BlockChain::new(Default::default(), genesis.encoded().raw(), old_db.clone()); + let bc = BlockChain::new( + Default::default(), + genesis.encoded().raw(), + old_db.clone(), + engine.params().eip1559_transition, + ); // build the blockchain. let mut batch = DBTransaction::new(); @@ -96,7 +101,12 @@ fn chunk_and_restore(amount: u64) { // restore it. let new_db = test_helpers::new_db(); - let new_chain = BlockChain::new(Default::default(), genesis.encoded().raw(), new_db.clone()); + let new_chain = BlockChain::new( + Default::default(), + genesis.encoded().raw(), + new_db.clone(), + engine.params().eip1559_transition, + ); let mut rebuilder = SNAPSHOT_MODE .rebuilder(new_chain, new_db.clone(), &manifest) .unwrap(); @@ -113,7 +123,12 @@ fn chunk_and_restore(amount: u64) { drop(rebuilder); // and test it. - let new_chain = BlockChain::new(Default::default(), genesis.encoded().raw(), new_db); + let new_chain = BlockChain::new( + Default::default(), + genesis.encoded().raw(), + new_db, + engine.params().eip1559_transition, + ); assert_eq!(new_chain.best_block_hash(), best_hash); } @@ -150,6 +165,7 @@ fn checks_flag() { Default::default(), genesis.last().encoded().raw(), db.clone(), + engine.params().eip1559_transition, ); let manifest = ::snapshot::ManifestData { diff --git a/crates/ethcore/src/snapshot/tests/service.rs b/crates/ethcore/src/snapshot/tests/service.rs index 2c567b232..13e1d7756 100644 --- a/crates/ethcore/src/snapshot/tests/service.rs +++ b/crates/ethcore/src/snapshot/tests/service.rs @@ -240,7 +240,9 @@ fn keep_ancient_blocks() { let block_hash = bc.block_hash(block_number).unwrap(); let block = bc.block(&block_hash).unwrap(); client2 - .import_block(Unverified::from_rlp(block.into_inner()).unwrap()) + .import_block( + Unverified::from_rlp(block.into_inner(), spec.params().eip1559_transition).unwrap(), + ) .unwrap(); } diff --git a/crates/ethcore/src/spec/genesis.rs b/crates/ethcore/src/spec/genesis.rs index 88f3f7859..ef1df70ee 100644 --- a/crates/ethcore/src/spec/genesis.rs +++ b/crates/ethcore/src/spec/genesis.rs @@ -43,6 +43,8 @@ pub struct Genesis { pub gas_used: U256, /// Extra data. pub extra_data: Vec, + /// Base fee. + pub base_fee: Option, } impl From for Genesis { @@ -63,6 +65,7 @@ impl From for Genesis { state_root: g.state_root.map(Into::into), gas_used: g.gas_used.map_or_else(U256::zero, Into::into), extra_data: g.extra_data.map_or_else(Vec::new, Into::into), + base_fee: g.base_fee.map(Into::into), } } } diff --git a/crates/ethcore/src/spec/spec.rs b/crates/ethcore/src/spec/spec.rs index 3bdfe291b..ae3e7acbb 100644 --- a/crates/ethcore/src/spec/spec.rs +++ b/crates/ethcore/src/spec/spec.rs @@ -139,6 +139,10 @@ pub struct CommonParams { pub eip2929_transition: BlockNumber, /// Number of first block where EIP-2930 rules begin. pub eip2930_transition: BlockNumber, + /// Number of first block where EIP-1559 rules begin. + pub eip1559_transition: BlockNumber, + /// Number of first block where EIP-3198 rules begin. Basefee opcode. + pub eip3198_transition: BlockNumber, /// Number of first block where EIP-3529 rules begin. pub eip3529_transition: BlockNumber, /// Number of first block where EIP-3541 rule begins. @@ -173,6 +177,12 @@ pub struct CommonParams { pub transaction_permission_contract_transition: BlockNumber, /// Maximum size of transaction's RLP payload pub max_transaction_size: usize, + /// Base fee max change denominator + pub eip1559_base_fee_max_change_denominator: Option, + /// Elasticity multiplier + pub eip1559_elasticity_multiplier: U256, + /// Default value for the block base fee + pub eip1559_base_fee_initial_value: U256, } impl CommonParams { @@ -220,6 +230,17 @@ impl CommonParams { schedule.eip2929 = block_number >= self.eip2929_transition; schedule.eip2930 = block_number >= self.eip2930_transition; schedule.eip3541 = block_number >= self.eip3541_transition; + schedule.eip1559 = block_number >= self.eip1559_transition; + schedule.eip3198 = block_number >= self.eip3198_transition; + if schedule.eip1559 { + schedule.eip1559_elasticity_multiplier = self.eip1559_elasticity_multiplier.as_usize(); + + schedule.eip1559_gas_limit_bump = if block_number == self.eip1559_transition { + schedule.eip1559_elasticity_multiplier + } else { + 1 + }; + } if block_number >= self.eip1884_transition { schedule.have_selfbalance = true; @@ -388,6 +409,12 @@ impl From for CommonParams { eip2930_transition: p .eip2930_transition .map_or_else(BlockNumber::max_value, Into::into), + eip1559_transition: p + .eip1559_transition + .map_or_else(BlockNumber::max_value, Into::into), + eip3198_transition: p + .eip3198_transition + .map_or_else(BlockNumber::max_value, Into::into), eip3529_transition: p .eip3529_transition .map_or_else(BlockNumber::max_value, Into::into), @@ -423,6 +450,15 @@ impl From for CommonParams { kip6_transition: p .kip6_transition .map_or_else(BlockNumber::max_value, Into::into), + eip1559_base_fee_max_change_denominator: p + .eip1559_base_fee_max_change_denominator + .map(Into::into), + eip1559_elasticity_multiplier: p + .eip1559_elasticity_multiplier + .map_or_else(U256::zero, Into::into), + eip1559_base_fee_initial_value: p + .eip1559_base_fee_initial_value + .map_or_else(U256::zero, Into::into), } } } @@ -497,6 +533,8 @@ pub struct Spec { pub extra_data: Bytes, /// Each seal field, expressed as RLP, concatenated. pub seal_rlp: Bytes, + /// Base fee, + pub base_fee: Option, /// List of hard forks in the network. pub hard_forks: BTreeSet, @@ -533,6 +571,7 @@ impl Clone for Spec { constructors: self.constructors.clone(), state_root_memo: RwLock::new(*self.state_root_memo.read()), genesis_state: self.genesis_state.clone(), + base_fee: self.base_fee.clone(), } } } @@ -591,6 +630,7 @@ fn load_from(spec_params: SpecParams, s: ethjson::spec::Spec) -> Result, transactions: &[Signed .seal(test_engine, vec![]) .unwrap(); - if let Err(e) = client.import_block(Unverified::from_rlp(b.rlp_bytes()).unwrap()) { + if let Err(e) = client.import_block( + Unverified::from_rlp(b.rlp_bytes(), test_spec.params().eip1559_transition).unwrap(), + ) { panic!( "error importing block which is valid by definition: {:?}", e @@ -323,7 +333,9 @@ pub fn get_test_client_with_blocks(blocks: Vec) -> Arc { .unwrap(); for block in blocks { - if let Err(e) = client.import_block(Unverified::from_rlp(block).unwrap()) { + if let Err(e) = client.import_block( + Unverified::from_rlp(block, test_spec.params().eip1559_transition).unwrap(), + ) { panic!("error importing block which is well-formed: {:?}", e); } } @@ -456,6 +468,7 @@ pub fn generate_dummy_blockchain(block_number: u32) -> BlockChain { BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), + BlockNumber::max_value(), ); let mut batch = db.key_value().transaction(); @@ -483,6 +496,7 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> BlockChain { BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), + BlockNumber::max_value(), ); let mut batch = db.key_value().transaction(); @@ -514,6 +528,7 @@ pub fn generate_dummy_empty_blockchain() -> BlockChain { BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), + BlockNumber::max_value(), ); bc } diff --git a/crates/ethcore/src/tests/client.rs b/crates/ethcore/src/tests/client.rs index 035bf0405..777cec7ae 100644 --- a/crates/ethcore/src/tests/client.rs +++ b/crates/ethcore/src/tests/client.rs @@ -104,7 +104,7 @@ fn imports_good_block() { .unwrap(); let good_block = get_good_dummy_block(); if client - .import_block(Unverified::from_rlp(good_block).unwrap()) + .import_block(Unverified::from_rlp(good_block, spec.params().eip1559_transition).unwrap()) .is_err() { panic!("error importing block being good by definition"); @@ -147,7 +147,12 @@ fn returns_chain_info() { let client = get_test_client_with_blocks(vec![dummy_block.clone()]); let block = view!(BlockView, &dummy_block); let info = client.chain_info(); - assert_eq!(info.best_block_hash, block.header().hash()); + assert_eq!( + info.best_block_hash, + block + .header(client.engine().params().eip1559_transition) + .hash() + ); } #[test] @@ -188,7 +193,11 @@ fn returns_block_body() { let client = get_test_client_with_blocks(vec![dummy_block.clone()]); let block = view!(BlockView, &dummy_block); let body = client - .block_body(BlockId::Hash(block.header().hash())) + .block_body(BlockId::Hash( + block + .header(client.engine().params().eip1559_transition) + .hash(), + )) .unwrap(); let body = body.rlp(); assert_eq!(body.item_count().unwrap(), 2); diff --git a/crates/ethcore/src/tests/trace.rs b/crates/ethcore/src/tests/trace.rs index bb16edac1..af1664f57 100644 --- a/crates/ethcore/src/tests/trace.rs +++ b/crates/ethcore/src/tests/trace.rs @@ -100,14 +100,17 @@ fn can_trace_block_and_uncle_reward() { .seal(engine, vec![]) .unwrap(); - if let Err(e) = client.import_block(Unverified::from_rlp(root_block.rlp_bytes()).unwrap()) { + if let Err(e) = client.import_block( + Unverified::from_rlp(root_block.rlp_bytes(), spec.params().eip1559_transition).unwrap(), + ) { panic!( "error importing block which is valid by definition: {:?}", e ); } - last_header = view!(BlockView, &root_block.rlp_bytes()).header(); + last_header = + view!(BlockView, &root_block.rlp_bytes()).header(spec.params().eip1559_transition); let root_header = last_header.clone(); db = root_block.drain().state.drop().1; @@ -137,14 +140,17 @@ fn can_trace_block_and_uncle_reward() { .seal(engine, vec![]) .unwrap(); - if let Err(e) = client.import_block(Unverified::from_rlp(parent_block.rlp_bytes()).unwrap()) { + if let Err(e) = client.import_block( + Unverified::from_rlp(parent_block.rlp_bytes(), spec.params().eip1559_transition).unwrap(), + ) { panic!( "error importing block which is valid by definition: {:?}", e ); } - last_header = view!(BlockView, &parent_block.rlp_bytes()).header(); + last_header = + view!(BlockView, &parent_block.rlp_bytes()).header(spec.params().eip1559_transition); db = parent_block.drain().state.drop().1; last_hashes.push(last_header.hash()); @@ -201,7 +207,9 @@ fn can_trace_block_and_uncle_reward() { .seal(engine, vec![]) .unwrap(); - let res = client.import_block(Unverified::from_rlp(block.rlp_bytes()).unwrap()); + let res = client.import_block( + Unverified::from_rlp(block.rlp_bytes(), spec.params().eip1559_transition).unwrap(), + ); if res.is_err() { panic!("error importing block: {:#?}", res.err().unwrap()); } diff --git a/crates/ethcore/src/verification/queue/kind.rs b/crates/ethcore/src/verification/queue/kind.rs index af8f0e1ce..a5b575452 100644 --- a/crates/ethcore/src/verification/queue/kind.rs +++ b/crates/ethcore/src/verification/queue/kind.rs @@ -83,6 +83,7 @@ pub mod blocks { use types::{ header::Header, transaction::{TypedTransaction, UnverifiedTransaction}, + BlockNumber, }; use verification::{verify_block_basic, verify_block_unordered, PreverifiedBlock}; @@ -149,13 +150,16 @@ pub mod blocks { impl Unverified { /// Create an `Unverified` from raw bytes. - pub fn from_rlp(bytes: Bytes) -> Result { + pub fn from_rlp( + bytes: Bytes, + eip1559_transition: BlockNumber, + ) -> Result { use rlp::Rlp; let (header, transactions, uncles) = { let rlp = Rlp::new(&bytes); - let header = rlp.val_at(0)?; + let header = Header::decode_rlp(&rlp.at(0)?, eip1559_transition)?; let transactions = TypedTransaction::decode_rlp_list(&rlp.at(1)?)?; - let uncles = rlp.list_at(2)?; + let uncles = Header::decode_rlp_list(&rlp.at(2)?, eip1559_transition)?; (header, transactions, uncles) }; diff --git a/crates/ethcore/src/verification/queue/mod.rs b/crates/ethcore/src/verification/queue/mod.rs index 8d1a41639..61f128fcf 100644 --- a/crates/ethcore/src/verification/queue/mod.rs +++ b/crates/ethcore/src/verification/queue/mod.rs @@ -876,7 +876,7 @@ mod tests { use io::*; use spec::Spec; use test_helpers::{get_good_dummy_block, get_good_dummy_block_seq}; - use types::{view, views::BlockView}; + use types::{view, views::BlockView, BlockNumber}; // create a test block queue. // auto_scaling enables verifier adjustment. @@ -897,7 +897,7 @@ mod tests { } fn new_unverified(bytes: Bytes) -> Unverified { - Unverified::from_rlp(bytes).expect("Should be valid rlp") + Unverified::from_rlp(bytes, BlockNumber::max_value()).expect("Should be valid rlp") } #[test] @@ -941,7 +941,10 @@ mod tests { fn returns_total_difficulty() { let queue = get_test_queue(false); let block = get_good_dummy_block(); - let hash = view!(BlockView, &block).header().hash().clone(); + let hash = view!(BlockView, &block) + .header(BlockNumber::max_value()) + .hash() + .clone(); if let Err(e) = queue.import(new_unverified(block)) { panic!("error importing block that is valid by definition({:?})", e); } @@ -957,7 +960,10 @@ mod tests { fn returns_ok_for_drained_duplicates() { let queue = get_test_queue(false); let block = get_good_dummy_block(); - let hash = view!(BlockView, &block).header().hash().clone(); + let hash = view!(BlockView, &block) + .header(BlockNumber::max_value()) + .hash() + .clone(); if let Err(e) = queue.import(new_unverified(block)) { panic!("error importing block that is valid by definition({:?})", e); } diff --git a/crates/ethcore/src/verification/verification.rs b/crates/ethcore/src/verification/verification.rs index 6a11b8b18..b1e7244bb 100644 --- a/crates/ethcore/src/verification/verification.rs +++ b/crates/ethcore/src/verification/verification.rs @@ -84,11 +84,11 @@ pub fn verify_block_basic( } // 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 { + if let Some(expected_gas_limit) = engine.gas_limit_override(&block.header) { + if block.header.gas_limit() != &expected_gas_limit { return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { - min: Some(gas_limit), - max: Some(gas_limit), + min: Some(expected_gas_limit), + max: Some(expected_gas_limit), found: *block.header.gas_limit(), }))); } @@ -295,7 +295,7 @@ fn verify_uncles( ))); } - let uncle_parent = uncle_parent.decode()?; + let uncle_parent = uncle_parent.decode(engine.params().eip1559_transition)?; verify_parent(&uncle, &uncle_parent, engine)?; engine.verify_block_family(&uncle, &uncle_parent)?; verified.insert(uncle.hash()); @@ -360,6 +360,8 @@ pub fn verify_header_params( found: header.number(), }))); } + + // check if the block used too much gas if header.gas_used() > header.gas_limit() { return Err(From::from(BlockError::TooMuchGasUsed(OutOfBounds { max: Some(*header.gas_limit()), @@ -476,9 +478,11 @@ fn verify_parent(header: &Header, parent: &Header, engine: &dyn EthEngine) -> Re .into()); } + // check if the block changed the gas limit too much 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 parent_gas_limit = + parent.gas_limit() * engine.schedule(header.number()).eip1559_gas_limit_bump; 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 { @@ -490,6 +494,15 @@ fn verify_parent(header: &Header, parent: &Header, engine: &dyn EthEngine) -> Re } } + // check if the base fee is correct + let expected_base_fee = engine.calculate_base_fee(parent); + if expected_base_fee != header.base_fee() { + return Err(From::from(BlockError::IncorrectBaseFee(Mismatch { + expected: expected_base_fee.unwrap_or_default(), + found: header.base_fee().unwrap_or_default(), + }))); + }; + Ok(()) } @@ -599,7 +612,9 @@ mod tests { } pub fn insert(&mut self, bytes: Bytes) { - let header = Unverified::from_rlp(bytes.clone()).unwrap().header; + let header = Unverified::from_rlp(bytes.clone(), BlockNumber::max_value()) + .unwrap() + .header; let hash = header.hash(); self.blocks.insert(hash, bytes); self.numbers.insert(header.number(), hash); @@ -639,7 +654,9 @@ mod tests { /// Get the familial details concerning a block. fn block_details(&self, hash: &H256) -> Option { self.blocks.get(hash).map(|bytes| { - let header = Unverified::from_rlp(bytes.to_vec()).unwrap().header; + let header = Unverified::from_rlp(bytes.to_vec(), BlockNumber::max_value()) + .unwrap() + .header; BlockDetails { number: header.number(), total_difficulty: *header.difficulty(), @@ -693,7 +710,7 @@ mod tests { } fn basic_test(bytes: &[u8], engine: &dyn EthEngine) -> Result<(), Error> { - let unverified = Unverified::from_rlp(bytes.to_vec())?; + let unverified = Unverified::from_rlp(bytes.to_vec(), engine.params().eip1559_transition)?; verify_block_basic(&unverified, engine, true) } @@ -701,7 +718,8 @@ mod tests { where BC: BlockProvider, { - let block = Unverified::from_rlp(bytes.to_vec()).unwrap(); + let block = + Unverified::from_rlp(bytes.to_vec(), engine.params().eip1559_transition).unwrap(); let header = block.header; let transactions: Vec<_> = block .transactions @@ -717,7 +735,7 @@ mod tests { let parent = bc .block_header_data(header.parent_hash()) .ok_or(BlockError::UnknownParent(*header.parent_hash()))? - .decode()?; + .decode(engine.params().eip1559_transition)?; let block = PreverifiedBlock { header, @@ -735,7 +753,7 @@ mod tests { } fn unordered_test(bytes: &[u8], engine: &dyn EthEngine) -> Result<(), Error> { - let un = Unverified::from_rlp(bytes.to_vec())?; + let un = Unverified::from_rlp(bytes.to_vec(), engine.params().eip1559_transition)?; verify_block_unordered(un, engine, false)?; Ok(()) } diff --git a/crates/ethcore/sync/src/api.rs b/crates/ethcore/sync/src/api.rs index bc1d7fd78..14ad8773d 100644 --- a/crates/ethcore/sync/src/api.rs +++ b/crates/ethcore/sync/src/api.rs @@ -110,6 +110,8 @@ pub struct SyncConfig { pub fork_block: Option<(BlockNumber, H256)>, /// Enable snapshot sync pub warp_sync: WarpSync, + /// Number of first block where EIP-1559 rules begin. New encoding/decoding block format. + pub eip1559_transition: BlockNumber, } impl Default for SyncConfig { @@ -121,6 +123,7 @@ impl Default for SyncConfig { subprotocol_name: ETH_PROTOCOL, fork_block: None, warp_sync: WarpSync::Disabled, + eip1559_transition: BlockNumber::max_value(), } } } diff --git a/crates/ethcore/sync/src/block_sync.rs b/crates/ethcore/sync/src/block_sync.rs index 748e2199d..d7e27985a 100644 --- a/crates/ethcore/sync/src/block_sync.rs +++ b/crates/ethcore/sync/src/block_sync.rs @@ -250,6 +250,7 @@ impl BlockDownloader { io: &mut dyn SyncIo, r: &Rlp, expected_hash: H256, + eip1559_transition: BlockNumber, ) -> Result { let item_count = r.item_count().unwrap_or(0); if self.state == State::Idle { @@ -276,7 +277,7 @@ impl BlockDownloader { let mut hashes = Vec::new(); let mut last_header = None; for i in 0..item_count { - let info = SyncHeader::from_rlp(r.at(i)?.as_raw().to_vec())?; + let info = SyncHeader::from_rlp(r.at(i)?.as_raw().to_vec(), eip1559_transition)?; let number = BlockNumber::from(info.header.number()); let hash = info.header.hash(); @@ -421,6 +422,7 @@ impl BlockDownloader { &mut self, r: &Rlp, expected_hashes: &[H256], + eip1559_transition: BlockNumber, ) -> Result<(), BlockDownloaderImportError> { let item_count = r.item_count().unwrap_or(0); if item_count == 0 { @@ -430,7 +432,7 @@ impl BlockDownloader { } else { let mut bodies = Vec::with_capacity(item_count); for i in 0..item_count { - let body = SyncBody::from_rlp(r.at(i)?.as_raw())?; + let body = SyncBody::from_rlp(r.at(i)?.as_raw(), eip1559_transition)?; bodies.push(body); } @@ -796,21 +798,23 @@ mod tests { headers: &[BlockHeader], downloader: &mut BlockDownloader, io: &mut dyn SyncIo, + eip1559_transition: BlockNumber, ) -> Result { let mut stream = RlpStream::new(); stream.append_list(headers); let bytes = stream.out(); let rlp = Rlp::new(&bytes); let expected_hash = headers.first().unwrap().hash(); - downloader.import_headers(io, &rlp, expected_hash) + downloader.import_headers(io, &rlp, expected_hash, eip1559_transition) } fn import_headers_ok( headers: &[BlockHeader], downloader: &mut BlockDownloader, io: &mut dyn SyncIo, + eip1559_transition: BlockNumber, ) { - let res = import_headers(headers, downloader, io); + let res = import_headers(headers, downloader, io, eip1559_transition); assert!(res.is_ok()); } @@ -838,7 +842,12 @@ mod tests { let rlp_data = encode_list(&valid_headers); let valid_rlp = Rlp::new(&rlp_data); - match downloader.import_headers(&mut io, &valid_rlp, genesis_hash) { + match downloader.import_headers( + &mut io, + &valid_rlp, + genesis_hash, + spec.params().eip1559_transition, + ) { Ok(DownloadAction::Reset) => assert_eq!(downloader.state, State::Blocks), _ => panic!("expected transition to Blocks state"), }; @@ -852,7 +861,12 @@ mod tests { let rlp_data = encode_list(&invalid_start_block_headers); let invalid_start_block_rlp = Rlp::new(&rlp_data); - match downloader.import_headers(&mut io, &invalid_start_block_rlp, genesis_hash) { + match downloader.import_headers( + &mut io, + &invalid_start_block_rlp, + genesis_hash, + spec.params().eip1559_transition, + ) { Err(BlockDownloaderImportError::Invalid) => (), _ => panic!("expected BlockDownloaderImportError"), }; @@ -866,7 +880,12 @@ mod tests { let rlp_data = encode_list(&invalid_skip_headers); let invalid_skip_rlp = Rlp::new(&rlp_data); - match downloader.import_headers(&mut io, &invalid_skip_rlp, genesis_hash) { + match downloader.import_headers( + &mut io, + &invalid_skip_rlp, + genesis_hash, + spec.params().eip1559_transition, + ) { Err(BlockDownloaderImportError::Invalid) => (), _ => panic!("expected BlockDownloaderImportError"), }; @@ -883,7 +902,12 @@ mod tests { let rlp_data = encode_list(&too_many_headers); let too_many_rlp = Rlp::new(&rlp_data); - match downloader.import_headers(&mut io, &too_many_rlp, genesis_hash) { + match downloader.import_headers( + &mut io, + &too_many_rlp, + genesis_hash, + spec.params().eip1559_transition, + ) { Err(BlockDownloaderImportError::Invalid) => (), _ => panic!("expected BlockDownloaderImportError"), }; @@ -894,6 +918,7 @@ mod tests { ::env_logger::try_init().ok(); let mut chain = TestBlockChainClient::new(); + let eip1559_transition = BlockNumber::default(); let snapshot_service = TestSnapshotService::new(); let queue = RwLock::new(VecDeque::new()); let mut io = TestIo::new(&mut chain, &snapshot_service, &queue, None); @@ -913,7 +938,12 @@ mod tests { let rlp_data = encode_list(&headers); let headers_rlp = Rlp::new(&rlp_data); - match downloader.import_headers(&mut io, &headers_rlp, headers[0].hash()) { + match downloader.import_headers( + &mut io, + &headers_rlp, + headers[0].hash(), + eip1559_transition, + ) { Ok(DownloadAction::None) => (), _ => panic!("expected successful import"), }; @@ -923,7 +953,12 @@ mod tests { let rlp_data = encode_list(&headers); let headers_rlp = Rlp::new(&rlp_data); - match downloader.import_headers(&mut io, &headers_rlp, headers[0].hash()) { + match downloader.import_headers( + &mut io, + &headers_rlp, + headers[0].hash(), + eip1559_transition, + ) { Err(BlockDownloaderImportError::Invalid) => (), _ => panic!("expected BlockDownloaderImportError"), }; @@ -933,7 +968,12 @@ mod tests { let rlp_data = encode_list(&headers); let headers_rlp = Rlp::new(&rlp_data); - match downloader.import_headers(&mut io, &headers_rlp, headers[0].hash()) { + match downloader.import_headers( + &mut io, + &headers_rlp, + headers[0].hash(), + eip1559_transition, + ) { Err(BlockDownloaderImportError::Invalid) => (), _ => panic!("expected BlockDownloaderImportError"), }; @@ -944,6 +984,7 @@ mod tests { ::env_logger::try_init().ok(); let mut chain = TestBlockChainClient::new(); + let eip1559_transition = chain.spec.params().eip1559_transition; let snapshot_service = TestSnapshotService::new(); let queue = RwLock::new(VecDeque::new()); let mut io = TestIo::new(&mut chain, &snapshot_service, &queue, None); @@ -992,7 +1033,7 @@ mod tests { let rlp_data = encode_list(&headers[0..3]); let headers_rlp = Rlp::new(&rlp_data); assert!(downloader - .import_headers(&mut io, &headers_rlp, headers[0].hash()) + .import_headers(&mut io, &headers_rlp, headers[0].hash(), eip1559_transition) .is_ok()); // Import first body successfully. @@ -1000,7 +1041,11 @@ mod tests { rlp_data.append_raw(&bodies[0], 1); let bodies_rlp = Rlp::new(rlp_data.as_raw()); assert!(downloader - .import_bodies(&bodies_rlp, &[headers[0].hash(), headers[1].hash()]) + .import_bodies( + &bodies_rlp, + &[headers[0].hash(), headers[1].hash()], + eip1559_transition + ) .is_ok()); // Import second body successfully. @@ -1008,14 +1053,22 @@ mod tests { rlp_data.append_raw(&bodies[1], 1); let bodies_rlp = Rlp::new(rlp_data.as_raw()); assert!(downloader - .import_bodies(&bodies_rlp, &[headers[0].hash(), headers[1].hash()]) + .import_bodies( + &bodies_rlp, + &[headers[0].hash(), headers[1].hash()], + eip1559_transition + ) .is_ok()); // Import unexpected third body. let mut rlp_data = RlpStream::new_list(1); rlp_data.append_raw(&bodies[2], 1); let bodies_rlp = Rlp::new(rlp_data.as_raw()); - match downloader.import_bodies(&bodies_rlp, &[headers[0].hash(), headers[1].hash()]) { + match downloader.import_bodies( + &bodies_rlp, + &[headers[0].hash(), headers[1].hash()], + eip1559_transition, + ) { Err(BlockDownloaderImportError::Invalid) => (), _ => panic!("expected BlockDownloaderImportError"), }; @@ -1026,6 +1079,7 @@ mod tests { ::env_logger::try_init().ok(); let mut chain = TestBlockChainClient::new(); + let eip1559_transition = chain.spec.params().eip1559_transition; let snapshot_service = TestSnapshotService::new(); let queue = RwLock::new(VecDeque::new()); let mut io = TestIo::new(&mut chain, &snapshot_service, &queue, None); @@ -1069,7 +1123,7 @@ mod tests { let rlp_data = encode_list(&headers[0..3]); let headers_rlp = Rlp::new(&rlp_data); assert!(downloader - .import_headers(&mut io, &headers_rlp, headers[0].hash()) + .import_headers(&mut io, &headers_rlp, headers[0].hash(), eip1559_transition) .is_ok()); // Import second and third receipts successfully. @@ -1085,7 +1139,11 @@ mod tests { let mut rlp_data = RlpStream::new_list(1); rlp_data.append_raw(&receipts[3], 1); let bodies_rlp = Rlp::new(rlp_data.as_raw()); - match downloader.import_bodies(&bodies_rlp, &[headers[1].hash(), headers[2].hash()]) { + match downloader.import_bodies( + &bodies_rlp, + &[headers[1].hash(), headers[2].hash()], + eip1559_transition, + ) { Err(BlockDownloaderImportError::Invalid) => (), _ => panic!("expected BlockDownloaderImportError"), }; @@ -1114,8 +1172,18 @@ mod tests { let short_subchain = [dummy_header(1, genesis_hash)]; - import_headers_ok(&heads, &mut downloader, &mut io); - import_headers_ok(&short_subchain, &mut downloader, &mut io); + import_headers_ok( + &heads, + &mut downloader, + &mut io, + spec.params().eip1559_transition, + ); + import_headers_ok( + &short_subchain, + &mut downloader, + &mut io, + spec.params().eip1559_transition, + ); assert_eq!(downloader.state, State::Blocks); assert!(!downloader.blocks.is_empty()); @@ -1123,7 +1191,12 @@ mod tests { // simulate receiving useless headers let head = vec![short_subchain.last().unwrap().clone()]; for _ in 0..MAX_USELESS_HEADERS_PER_ROUND { - let res = import_headers(&head, &mut downloader, &mut io); + let res = import_headers( + &head, + &mut downloader, + &mut io, + spec.params().eip1559_transition, + ); assert!(res.is_err()); } @@ -1150,8 +1223,18 @@ mod tests { let short_subchain = [dummy_header(1, genesis_hash)]; - import_headers_ok(&heads, &mut downloader, &mut io); - import_headers_ok(&short_subchain, &mut downloader, &mut io); + import_headers_ok( + &heads, + &mut downloader, + &mut io, + spec.params().eip1559_transition, + ); + import_headers_ok( + &short_subchain, + &mut downloader, + &mut io, + spec.params().eip1559_transition, + ); assert_eq!(downloader.state, State::Blocks); assert!(!downloader.blocks.is_empty()); @@ -1159,7 +1242,12 @@ mod tests { // simulate receiving useless headers let head = vec![short_subchain.last().unwrap().clone()]; for _ in 0..MAX_USELESS_HEADERS_PER_ROUND { - let res = import_headers(&head, &mut downloader, &mut io); + let res = import_headers( + &head, + &mut downloader, + &mut io, + spec.params().eip1559_transition, + ); assert!(res.is_err()); } diff --git a/crates/ethcore/sync/src/blocks.rs b/crates/ethcore/sync/src/blocks.rs index f5c9ad675..9c5d28f95 100644 --- a/crates/ethcore/sync/src/blocks.rs +++ b/crates/ethcore/sync/src/blocks.rs @@ -26,6 +26,7 @@ use triehash_ethereum::ordered_trie_root; use types::{ header::Header as BlockHeader, transaction::{TypedTransaction, UnverifiedTransaction}, + BlockNumber, }; malloc_size_of_is_0!(HeaderId); @@ -37,9 +38,10 @@ pub struct SyncHeader { } impl SyncHeader { - pub fn from_rlp(bytes: Bytes) -> Result { + pub fn from_rlp(bytes: Bytes, eip1559_transition: BlockNumber) -> Result { + let rlp = Rlp::new(&bytes); let result = SyncHeader { - header: ::rlp::decode(&bytes)?, + header: BlockHeader::decode_rlp(&rlp, eip1559_transition)?, bytes, }; @@ -56,7 +58,7 @@ pub struct SyncBody { } impl SyncBody { - pub fn from_rlp(bytes: &[u8]) -> Result { + pub fn from_rlp(bytes: &[u8], eip1559_transition: BlockNumber) -> Result { let rlp = Rlp::new(bytes); let transactions_rlp = rlp.at(0)?; let uncles_rlp = rlp.at(1)?; @@ -65,7 +67,7 @@ impl SyncBody { transactions_bytes: transactions_rlp.as_raw().to_vec(), transactions: TypedTransaction::decode_rlp_list(&transactions_rlp)?, uncles_bytes: uncles_rlp.as_raw().to_vec(), - uncles: uncles_rlp.as_list()?, + uncles: BlockHeader::decode_rlp_list(&uncles_rlp, eip1559_transition)?, }; Ok(result) @@ -672,7 +674,13 @@ mod test { .collect(); let headers: Vec<_> = blocks .iter() - .map(|b| SyncHeader::from_rlp(Rlp::new(b).at(0).unwrap().as_raw().to_vec()).unwrap()) + .map(|b| { + SyncHeader::from_rlp( + Rlp::new(b).at(0).unwrap().as_raw().to_vec(), + client.spec.params().eip1559_transition, + ) + .unwrap() + }) .collect(); let hashes: Vec<_> = headers.iter().map(|h| h.header.hash()).collect(); let heads: Vec<_> = hashes @@ -707,7 +715,10 @@ mod test { bc.drain().into_iter().map(|b| b.block).collect::>(), blocks[0..6] .iter() - .map(|b| Unverified::from_rlp(b.to_vec()).unwrap()) + .map( + |b| Unverified::from_rlp(b.to_vec(), client.spec.params().eip1559_transition) + .unwrap() + ) .collect::>() ); assert!(!bc.contains(&hashes[0])); @@ -724,7 +735,10 @@ mod test { bc.drain().into_iter().map(|b| b.block).collect::>(), blocks[6..16] .iter() - .map(|b| Unverified::from_rlp(b.to_vec()).unwrap()) + .map( + |b| Unverified::from_rlp(b.to_vec(), client.spec.params().eip1559_transition) + .unwrap() + ) .collect::>() ); @@ -752,7 +766,13 @@ mod test { .collect(); let headers: Vec<_> = blocks .iter() - .map(|b| SyncHeader::from_rlp(Rlp::new(b).at(0).unwrap().as_raw().to_vec()).unwrap()) + .map(|b| { + SyncHeader::from_rlp( + Rlp::new(b).at(0).unwrap().as_raw().to_vec(), + client.spec.params().eip1559_transition, + ) + .unwrap() + }) .collect(); let hashes: Vec<_> = headers.iter().map(|h| h.header.hash()).collect(); let heads: Vec<_> = hashes @@ -788,7 +808,13 @@ mod test { .collect(); let headers: Vec<_> = blocks .iter() - .map(|b| SyncHeader::from_rlp(Rlp::new(b).at(0).unwrap().as_raw().to_vec()).unwrap()) + .map(|b| { + SyncHeader::from_rlp( + Rlp::new(b).at(0).unwrap().as_raw().to_vec(), + client.spec.params().eip1559_transition, + ) + .unwrap() + }) .collect(); let hashes: Vec<_> = headers.iter().map(|h| h.header.hash()).collect(); let heads: Vec<_> = hashes diff --git a/crates/ethcore/sync/src/chain/handler.rs b/crates/ethcore/sync/src/chain/handler.rs index 0797251c3..fdc4b9d90 100644 --- a/crates/ethcore/sync/src/chain/handler.rs +++ b/crates/ethcore/sync/src/chain/handler.rs @@ -159,7 +159,7 @@ impl SyncHandler { return Ok(()); } // t_nb 1.0 decode RLP - let block = Unverified::from_rlp(r.at(0)?.as_raw().to_vec())?; + let block = Unverified::from_rlp(r.at(0)?.as_raw().to_vec(), sync.eip1559_transition)?; let hash = block.header.hash(); let number = block.header.number(); trace!(target: "sync", "{} -> NewBlock ({})", peer_id, hash); @@ -360,7 +360,7 @@ impl SyncHandler { Some(ref mut blocks) => blocks, }, }; - downloader.import_bodies(r, expected_blocks.as_slice())?; + downloader.import_bodies(r, expected_blocks.as_slice(), sync.eip1559_transition)?; } sync.collect_blocks(io, block_set); Ok(()) @@ -479,7 +479,7 @@ impl SyncHandler { Some(ref mut blocks) => blocks, }, }; - downloader.import_headers(io, r, expected_hash)? + downloader.import_headers(io, r, expected_hash, sync.eip1559_transition)? }; if result == DownloadAction::Reset { diff --git a/crates/ethcore/sync/src/chain/mod.rs b/crates/ethcore/sync/src/chain/mod.rs index a8b3bdd60..dc72c88ea 100644 --- a/crates/ethcore/sync/src/chain/mod.rs +++ b/crates/ethcore/sync/src/chain/mod.rs @@ -693,6 +693,8 @@ pub struct ChainSync { download_old_blocks: bool, /// Enable warp sync. warp_sync: WarpSync, + /// New block encoding/decoding format is introduced by the EIP1559 + eip1559_transition: BlockNumber, } #[derive(Debug, Default)] @@ -778,6 +780,7 @@ impl ChainSync { sync_start_time: None, transactions_stats: TransactionsStats::default(), warp_sync: config.warp_sync, + eip1559_transition: config.eip1559_transition, }; sync.update_targets(chain); sync diff --git a/crates/ethcore/sync/src/chain/supplier.rs b/crates/ethcore/sync/src/chain/supplier.rs index ee0686ffa..931e9ea6d 100644 --- a/crates/ethcore/sync/src/chain/supplier.rs +++ b/crates/ethcore/sync/src/chain/supplier.rs @@ -464,14 +464,18 @@ mod test { rlp.append(&if reverse { 1u32 } else { 0u32 }); rlp.out() } - fn to_header_vec(rlp: ::chain::RlpResponseResult) -> Vec { + fn to_header_vec( + rlp: ::chain::RlpResponseResult, + eip1559_transition: BlockNumber, + ) -> Vec { Rlp::new(&rlp.unwrap().unwrap().1.out()) .iter() - .map(|r| SyncHeader::from_rlp(r.as_raw().to_vec()).unwrap()) + .map(|r| SyncHeader::from_rlp(r.as_raw().to_vec(), eip1559_transition).unwrap()) .collect() } let mut client = TestBlockChainClient::new(); + let eip1559_transition = client.spec.params().eip1559_transition; client.add_blocks(100, EachBlockWith::Nothing); let blocks: Vec<_> = (0..100) .map(|i| { @@ -483,7 +487,13 @@ mod test { .collect(); let headers: Vec<_> = blocks .iter() - .map(|b| SyncHeader::from_rlp(Rlp::new(b).at(0).unwrap().as_raw().to_vec()).unwrap()) + .map(|b| { + SyncHeader::from_rlp( + Rlp::new(b).at(0).unwrap().as_raw().to_vec(), + eip1559_transition, + ) + .unwrap() + }) .collect(); let hashes: Vec<_> = headers.iter().map(|h| h.header.hash()).collect(); @@ -497,27 +507,33 @@ mod test { &Rlp::new(&make_hash_req(&unknown, 1, 0, false)), 0, ); - assert!(to_header_vec(result).is_empty()); + assert!(to_header_vec(result, eip1559_transition).is_empty(),); let result = SyncSupplier::return_block_headers( &io, &Rlp::new(&make_hash_req(&unknown, 1, 0, true)), 0, ); - assert!(to_header_vec(result).is_empty()); + assert!(to_header_vec(result, eip1559_transition).is_empty()); let result = SyncSupplier::return_block_headers( &io, &Rlp::new(&make_hash_req(&hashes[2], 1, 0, true)), 0, ); - assert_eq!(to_header_vec(result), vec![headers[2].clone()]); + assert_eq!( + to_header_vec(result, eip1559_transition), + vec![headers[2].clone()] + ); let result = SyncSupplier::return_block_headers( &io, &Rlp::new(&make_hash_req(&hashes[2], 1, 0, false)), 0, ); - assert_eq!(to_header_vec(result), vec![headers[2].clone()]); + assert_eq!( + to_header_vec(result, eip1559_transition), + vec![headers[2].clone()] + ); let result = SyncSupplier::return_block_headers( &io, @@ -525,7 +541,7 @@ mod test { 0, ); assert_eq!( - to_header_vec(result), + to_header_vec(result, eip1559_transition), vec![ headers[50].clone(), headers[56].clone(), @@ -539,7 +555,7 @@ mod test { 0, ); assert_eq!( - to_header_vec(result), + to_header_vec(result, eip1559_transition), vec![ headers[50].clone(), headers[44].clone(), @@ -549,16 +565,22 @@ mod test { let result = SyncSupplier::return_block_headers(&io, &Rlp::new(&make_num_req(2, 1, 0, true)), 0); - assert_eq!(to_header_vec(result), vec![headers[2].clone()]); + assert_eq!( + to_header_vec(result, eip1559_transition), + vec![headers[2].clone()] + ); let result = SyncSupplier::return_block_headers(&io, &Rlp::new(&make_num_req(2, 1, 0, false)), 0); - assert_eq!(to_header_vec(result), vec![headers[2].clone()]); + assert_eq!( + to_header_vec(result, eip1559_transition), + vec![headers[2].clone()] + ); let result = SyncSupplier::return_block_headers(&io, &Rlp::new(&make_num_req(50, 3, 5, false)), 0); assert_eq!( - to_header_vec(result), + to_header_vec(result, eip1559_transition), vec![ headers[50].clone(), headers[56].clone(), @@ -569,7 +591,7 @@ mod test { let result = SyncSupplier::return_block_headers(&io, &Rlp::new(&make_num_req(50, 3, 5, true)), 0); assert_eq!( - to_header_vec(result), + to_header_vec(result, eip1559_transition), vec![ headers[50].clone(), headers[44].clone(), diff --git a/crates/ethcore/types/src/block.rs b/crates/ethcore/types/src/block.rs index d0c1cc3e8..c713e3817 100644 --- a/crates/ethcore/types/src/block.rs +++ b/crates/ethcore/types/src/block.rs @@ -36,8 +36,9 @@ use crate::bytes::Bytes; use crate::{ header::Header, transaction::{TypedTransaction, UnverifiedTransaction}, + BlockNumber, }; -use rlp::{Decodable, DecoderError, Rlp, RlpStream}; +use rlp::{DecoderError, Rlp, RlpStream}; /// A block, encoded as it is on the block chain. #[derive(Default, Debug, Clone, PartialEq)] @@ -59,10 +60,8 @@ impl Block { block_rlp.append_list(&self.uncles); block_rlp.out() } -} -impl Decodable for Block { - fn decode(rlp: &Rlp) -> Result { + pub fn decode_rlp(rlp: &Rlp, eip1559_transition: BlockNumber) -> Result { if rlp.as_raw().len() != rlp.payload_info()?.total() { return Err(DecoderError::RlpIsTooBig); } @@ -70,9 +69,9 @@ impl Decodable for Block { return Err(DecoderError::RlpIncorrectListLen); } Ok(Block { - header: rlp.val_at(0)?, + header: Header::decode_rlp(&rlp.at(0)?, eip1559_transition)?, transactions: TypedTransaction::decode_rlp_list(&rlp.at(1)?)?, - uncles: rlp.list_at(2)?, + uncles: Header::decode_rlp_list(&rlp.at(2)?, eip1559_transition)?, }) } } diff --git a/crates/ethcore/types/src/encoded.rs b/crates/ethcore/types/src/encoded.rs index 7bad7945e..ca6bb20af 100644 --- a/crates/ethcore/types/src/encoded.rs +++ b/crates/ethcore/types/src/encoded.rs @@ -49,8 +49,8 @@ impl Header { } /// Upgrade this encoded view to a fully owned `Header` object. - pub fn decode(&self) -> Result { - rlp::decode(&self.0) + pub fn decode(&self, eip1559_transition: BlockNumber) -> Result { + FullHeader::decode_rlp(&self.rlp(), eip1559_transition) } /// Get a borrowed header view onto the data. @@ -144,8 +144,13 @@ impl Header { } /// Engine-specific seal fields. - pub fn seal(&self) -> Vec> { - self.view().seal() + pub fn seal(&self, eip1559: bool) -> Vec> { + self.view().seal(eip1559) + } + + /// Base fee. + pub fn base_fee(&self) -> U256 { + self.view().base_fee() } } @@ -167,8 +172,14 @@ impl Body { } /// Fully decode this block body. - pub fn decode(&self) -> (Vec, Vec) { - (self.view().transactions(), self.view().uncles()) + pub fn decode( + &self, + eip1559_transition: BlockNumber, + ) -> (Vec, Vec) { + ( + self.view().transactions(), + self.view().uncles(eip1559_transition), + ) } /// Get the RLP of this block body. @@ -216,8 +227,8 @@ impl Body { } /// Decode uncle headers. - pub fn uncles(&self) -> Vec { - self.view().uncles() + pub fn uncles(&self, eip1559_transition: BlockNumber) -> Vec { + self.view().uncles(eip1559_transition) } /// Number of uncles. @@ -268,13 +279,20 @@ impl Block { } /// Decode to a full block. - pub fn decode(&self) -> Result { - rlp::decode(&self.0) + pub fn decode(&self, eip1559_transition: BlockNumber) -> Result { + FullBlock::decode_rlp(&self.rlp(), eip1559_transition) } /// Decode the header. - pub fn decode_header(&self) -> FullHeader { - self.view().rlp().val_at(0) + pub fn decode_header(&self, eip1559_transition: BlockNumber) -> FullHeader { + FullHeader::decode_rlp(&self.view().rlp().at(0).rlp, eip1559_transition).unwrap_or_else( + |e| { + panic!( + "block header, view rlp is trusted and should be valid: {:?}", + e + ) + }, + ) } /// Clone the encoded header. @@ -372,8 +390,8 @@ impl Block { } /// Engine-specific seal fields. - pub fn seal(&self) -> Vec> { - self.header_view().seal() + pub fn seal(&self, eip1559: bool) -> Vec> { + self.header_view().seal(eip1559) } } @@ -400,8 +418,8 @@ impl Block { } /// Decode uncle headers. - pub fn uncles(&self) -> Vec { - self.view().uncles() + pub fn uncles(&self, eip1559_transition: BlockNumber) -> Vec { + self.view().uncles(eip1559_transition) } /// Number of uncles. diff --git a/crates/ethcore/types/src/header.rs b/crates/ethcore/types/src/header.rs index a5f9b988e..f8541be45 100644 --- a/crates/ethcore/types/src/header.rs +++ b/crates/ethcore/types/src/header.rs @@ -23,7 +23,7 @@ use crate::{ }; use ethereum_types::{Address, Bloom, H256, U256}; use parity_util_mem::MallocSizeOf; -use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; +use rlp::{DecoderError, Encodable, Rlp, RlpStream}; /// Semantic boolean for when a seal/signature is included. #[derive(Debug, Clone, Copy)] @@ -51,6 +51,9 @@ pub struct ExtendedHeader { /// which is non-specific. /// /// Doesn't do all that much on its own. +/// +/// Two versions of header exist. First one is before EIP1559. Second version is after EIP1559. +/// EIP1559 version added field base_fee_per_gas. #[derive(Debug, Clone, Eq, MallocSizeOf)] pub struct Header { /// Parent hash. @@ -79,12 +82,14 @@ pub struct Header { gas_used: U256, /// Block gas limit. gas_limit: U256, - /// Block difficulty. difficulty: U256, /// Vector of post-RLP-encoded fields. seal: Vec, + /// Base fee per gas. Introduced by EIP1559. + base_fee_per_gas: Option, + /// Memoized hash of that header and the seal. hash: Option, } @@ -111,6 +116,7 @@ impl PartialEq for Header { && self.gas_limit == c.gas_limit && self.difficulty == c.difficulty && self.seal == c.seal + && self.base_fee_per_gas == c.base_fee_per_gas } } @@ -135,6 +141,7 @@ impl Default for Header { difficulty: U256::default(), seal: vec![], hash: None, + base_fee_per_gas: None, } } } @@ -215,6 +222,11 @@ impl Header { &self.seal } + /// Get the base fee field of the header. + pub fn base_fee(&self) -> Option { + self.base_fee_per_gas + } + /// Get the seal field with RLP-decoded values as bytes. pub fn decode_seal<'a, T: ::std::iter::FromIterator<&'a [u8]>>( &'a self, @@ -298,6 +310,11 @@ impl Header { hash } + /// Set the block base fee of the header. + pub fn set_base_fee(&mut self, a: Option) { + change_field(&mut self.hash, &mut self.base_fee_per_gas, a); + } + /// Get the hash of this header (keccak of the RLP with seal). pub fn hash(&self) -> H256 { self.hash.unwrap_or_else(|| keccak(self.rlp(Seal::With))) @@ -322,10 +339,16 @@ impl Header { /// Place this header into an RLP stream `s`, optionally `with_seal`. fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) { - if let Seal::With = with_seal { - s.begin_list(13 + self.seal.len()); + let stream_length_without_seal = if self.base_fee_per_gas.is_some() { + 14 } else { - s.begin_list(13); + 13 + }; + + if let Seal::With = with_seal { + s.begin_list(stream_length_without_seal + self.seal.len()); + } else { + s.begin_list(stream_length_without_seal); } s.append(&self.parent_hash); @@ -347,6 +370,10 @@ impl Header { s.append_raw(b, 1); } } + + if self.base_fee_per_gas.is_some() { + s.append(&self.base_fee_per_gas.unwrap()); + } } } @@ -361,8 +388,8 @@ where } } -impl Decodable for Header { - fn decode(r: &Rlp) -> Result { +impl Header { + pub fn decode_rlp(r: &Rlp, eip1559_transition: BlockNumber) -> Result { let mut blockheader = Header { parent_hash: r.val_at(0)?, uncles_hash: r.val_at(1)?, @@ -379,14 +406,37 @@ impl Decodable for Header { extra_data: r.val_at(12)?, seal: vec![], hash: keccak(r.as_raw()).into(), + base_fee_per_gas: None, }; - for i in 13..r.item_count()? { - blockheader.seal.push(r.at(i)?.as_raw().to_vec()) + if blockheader.number >= eip1559_transition { + for i in 13..r.item_count()? - 1 { + blockheader.seal.push(r.at(i)?.as_raw().to_vec()) + } + blockheader.base_fee_per_gas = Some(r.val_at(r.item_count()? - 1)?); + } else { + for i in 13..r.item_count()? { + blockheader.seal.push(r.at(i)?.as_raw().to_vec()) + } } Ok(blockheader) } + + pub fn decode_rlp_list( + rlp: &Rlp, + eip1559_transition: BlockNumber, + ) -> Result, DecoderError> { + if !rlp.is_list() { + // at least one byte needs to be present + return Err(DecoderError::RlpIncorrectListLen); + } + let mut output = Vec::with_capacity(rlp.item_count()?); + for h in rlp.iter() { + output.push(Self::decode_rlp(&h, eip1559_transition)?); + } + Ok(output) + } } impl Encodable for Header { @@ -404,8 +454,11 @@ impl ExtendedHeader { #[cfg(test)] mod tests { + use crate::BlockNumber; + use super::Header; - use rlp; + use ethereum_types::U256; + use rlp::{self, Rlp}; use rustc_hex::FromHex; #[test] @@ -421,7 +474,9 @@ mod tests { let nonce = "88ab4e252a7e8c2a23".from_hex().unwrap(); let nonce_decoded = "ab4e252a7e8c2a23".from_hex().unwrap(); - let header: Header = rlp::decode(&header_rlp).expect("error decoding header"); + let rlp = Rlp::new(&header_rlp); + let header: Header = + Header::decode_rlp(&rlp, BlockNumber::max_value()).expect("error decoding header"); let seal_fields = header.seal.clone(); assert_eq!(seal_fields.len(), 2); assert_eq!(seal_fields[0], mix_hash); @@ -433,12 +488,50 @@ mod tests { assert_eq!(decoded_seal[1], &*nonce_decoded); } + #[test] + fn test_header_seal_fields_after_1559() { + let header_rlp = "f901faa0d405da4e66f1445d455195229624e133f5baafe72b5cf7b3c36c12c8146e98b7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a05fb2b4bfdef7b314451cb138a534d225c922fc0e5fbe25e451142732c3e25c25a088d2ec6b9860aae1a2c3b299f72b6a5d70d7f7ba4722c78f2c49ba96273c2158a007c6fdfa8eea7e86b81f5b0fc0f78f90cc19f4aa60d323151e0cac660199e9a1b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008011832fefba82524d84568e932a80a0a0349d8c3df71f1a48a9df7d03fd5f14aeee7d91332c009ecaff0a71ead405bd88ab4e252a7e8c2a2364".from_hex().unwrap(); + let rlp = Rlp::new(&header_rlp); + let mut header: Header = + Header::decode_rlp(&rlp, BlockNumber::default()).expect("error decoding header"); + + assert_eq!(header.seal().len(), 2); + assert_eq!(header.base_fee().unwrap(), U256::from(100)); + + let new_base_fee = U256::from(200); + header.set_base_fee(Some(new_base_fee)); + assert_eq!(header.base_fee().unwrap(), new_base_fee); + + let seal = vec![vec![50u8], vec![60u8]]; + header.set_seal(seal.clone()); + assert_eq!(header.seal(), seal); + assert_eq!(header.base_fee().unwrap(), new_base_fee); + + let decoded_seal = header.decode_seal::>().unwrap(); + assert_eq!(decoded_seal.len(), 2); + } + #[test] fn decode_and_encode_header() { // that's rlp of block header created with ethash engine. let header_rlp = "f901f9a0d405da4e66f1445d455195229624e133f5baafe72b5cf7b3c36c12c8146e98b7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a05fb2b4bfdef7b314451cb138a534d225c922fc0e5fbe25e451142732c3e25c25a088d2ec6b9860aae1a2c3b299f72b6a5d70d7f7ba4722c78f2c49ba96273c2158a007c6fdfa8eea7e86b81f5b0fc0f78f90cc19f4aa60d323151e0cac660199e9a1b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008003832fefba82524d84568e932a80a0a0349d8c3df71f1a48a9df7d03fd5f14aeee7d91332c009ecaff0a71ead405bd88ab4e252a7e8c2a23".from_hex().unwrap(); + let rlp = Rlp::new(&header_rlp); - let header: Header = rlp::decode(&header_rlp).expect("error decoding header"); + let header: Header = + Header::decode_rlp(&rlp, BlockNumber::max_value()).expect("error decoding header"); + let encoded_header = rlp::encode(&header); + + assert_eq!(header_rlp, encoded_header); + } + + #[test] + fn decode_and_encode_header_after_1559() { + // that's rlp of block header created with ethash engine. + let header_rlp = "f901faa0d405da4e66f1445d455195229624e133f5baafe72b5cf7b3c36c12c8146e98b7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a05fb2b4bfdef7b314451cb138a534d225c922fc0e5fbe25e451142732c3e25c25a088d2ec6b9860aae1a2c3b299f72b6a5d70d7f7ba4722c78f2c49ba96273c2158a007c6fdfa8eea7e86b81f5b0fc0f78f90cc19f4aa60d323151e0cac660199e9a1b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008011832fefba82524d84568e932a80a0a0349d8c3df71f1a48a9df7d03fd5f14aeee7d91332c009ecaff0a71ead405bd88ab4e252a7e8c2a2364".from_hex().unwrap(); + let rlp = Rlp::new(&header_rlp); + + let header: Header = + Header::decode_rlp(&rlp, BlockNumber::default()).expect("error decoding header"); let encoded_header = rlp::encode(&header); assert_eq!(header_rlp, encoded_header); @@ -449,9 +542,23 @@ mod tests { // that's rlp of block header created with ethash engine. // The encoding contains a large timestamp (295147905179352825856) let header_rlp = "f901f9a0d405da4e66f1445d455195229624e133f5baafe72b5cf7b3c36c12c8146e98b7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a05fb2b4bfdef7b314451cb138a534d225c922fc0e5fbe25e451142732c3e25c25a088d2ec6b9860aae1a2c3b299f72b6a5d70d7f7ba4722c78f2c49ba96273c2158a007c6fdfa8eea7e86b81f5b0fc0f78f90cc19f4aa60d323151e0cac660199e9a1b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008003832fefba82524d891000000000000000000080a0a0349d8c3df71f1a48a9df7d03fd5f14aeee7d91332c009ecaff0a71ead405bd88ab4e252a7e8c2a23".from_hex().unwrap(); + let rlp = Rlp::new(&header_rlp); // This should fail decoding timestamp - let header: Result = rlp::decode(&header_rlp); + let header: Result = Header::decode_rlp(&rlp, BlockNumber::max_value()); assert_eq!(header.unwrap_err(), rlp::DecoderError::RlpIsTooBig); } + + #[test] + fn hash_should_be_different() { + let header_legacy = Header::new(); + let mut header_1559 = Header::new(); + + header_1559.set_base_fee(Some(U256::from(100))); + + let hash_legacy = header_legacy.hash(); + let hash_1559 = header_1559.hash(); + + assert_ne!(hash_legacy, hash_1559); + } } diff --git a/crates/ethcore/types/src/receipt.rs b/crates/ethcore/types/src/receipt.rs index 1ae47bc16..dd3eba9ef 100644 --- a/crates/ethcore/types/src/receipt.rs +++ b/crates/ethcore/types/src/receipt.rs @@ -112,6 +112,7 @@ impl LegacyReceipt { pub enum TypedReceipt { Legacy(LegacyReceipt), AccessList(LegacyReceipt), + EIP1559Transaction(LegacyReceipt), } impl TypedReceipt { @@ -119,6 +120,7 @@ impl TypedReceipt { pub fn new(type_id: TypedTxId, legacy_receipt: LegacyReceipt) -> Self { //curently we are using same receipt for both legacy and typed transaction match type_id { + TypedTxId::EIP1559Transaction => Self::EIP1559Transaction(legacy_receipt), TypedTxId::AccessList => Self::AccessList(legacy_receipt), TypedTxId::Legacy => Self::Legacy(legacy_receipt), } @@ -128,6 +130,7 @@ impl TypedReceipt { match self { Self::Legacy(_) => TypedTxId::Legacy, Self::AccessList(_) => TypedTxId::AccessList, + Self::EIP1559Transaction(_) => TypedTxId::EIP1559Transaction, } } @@ -135,6 +138,7 @@ impl TypedReceipt { match self { Self::Legacy(receipt) => receipt, Self::AccessList(receipt) => receipt, + Self::EIP1559Transaction(receipt) => receipt, } } @@ -142,6 +146,7 @@ impl TypedReceipt { match self { Self::Legacy(receipt) => receipt, Self::AccessList(receipt) => receipt, + Self::EIP1559Transaction(receipt) => receipt, } } @@ -156,6 +161,10 @@ impl TypedReceipt { } //other transaction types match id.unwrap() { + TypedTxId::EIP1559Transaction => { + let rlp = Rlp::new(&tx[1..]); + Ok(Self::EIP1559Transaction(LegacyReceipt::decode(&rlp)?)) + } TypedTxId::AccessList => { let rlp = Rlp::new(&tx[1..]); Ok(Self::AccessList(LegacyReceipt::decode(&rlp)?)) @@ -193,6 +202,11 @@ impl TypedReceipt { receipt.rlp_append(&mut rlps); s.append(&[&[TypedTxId::AccessList as u8], rlps.as_raw()].concat()); } + Self::EIP1559Transaction(receipt) => { + let mut rlps = RlpStream::new(); + receipt.rlp_append(&mut rlps); + s.append(&[&[TypedTxId::EIP1559Transaction as u8], rlps.as_raw()].concat()); + } } } @@ -215,6 +229,11 @@ impl TypedReceipt { receipt.rlp_append(&mut rlps); [&[TypedTxId::AccessList as u8], rlps.as_raw()].concat() } + Self::EIP1559Transaction(receipt) => { + let mut rlps = RlpStream::new(); + receipt.rlp_append(&mut rlps); + [&[TypedTxId::EIP1559Transaction as u8], rlps.as_raw()].concat() + } } } } @@ -372,6 +391,32 @@ mod tests { assert_eq!(decoded, r); } + #[test] + fn test_basic_eip1559() { + let expected = ::rustc_hex::FromHex::from_hex("02f90162a02f697d671e9ae4ee24a43c4b0d7e15f1cb4ba6de1561120d43b9a4e8c4a8a6ee83040caeb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000f838f794dcf421d093428b096ca501a7cd1a740855a7976fc0a00000000000000000000000000000000000000000000000000000000000000000").unwrap(); + let r = TypedReceipt::new( + TypedTxId::EIP1559Transaction, + LegacyReceipt::new( + TransactionOutcome::StateRoot( + H256::from_str( + "2f697d671e9ae4ee24a43c4b0d7e15f1cb4ba6de1561120d43b9a4e8c4a8a6ee", + ) + .unwrap(), + ), + 0x40cae.into(), + vec![LogEntry { + address: H160::from_str("dcf421d093428b096ca501a7cd1a740855a7976f").unwrap(), + topics: vec![], + data: vec![0u8; 32], + }], + ), + ); + let encoded = r.encode(); + assert_eq!(&encoded, &expected); + let decoded = TypedReceipt::decode(&encoded).expect("decoding receipt failed"); + assert_eq!(decoded, r); + } + #[test] fn test_status_code() { let expected = ::rustc_hex::FromHex::from_hex("f901428083040caeb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000f838f794dcf421d093428b096ca501a7cd1a740855a7976fc0a00000000000000000000000000000000000000000000000000000000000000000").unwrap(); diff --git a/crates/ethcore/types/src/transaction/error.rs b/crates/ethcore/types/src/transaction/error.rs index d90be937f..b8094d650 100644 --- a/crates/ethcore/types/src/transaction/error.rs +++ b/crates/ethcore/types/src/transaction/error.rs @@ -37,6 +37,13 @@ pub enum Error { /// Transaction gas price got: U256, }, + /// Transaction's max gas price is lower then block base fee. + GasPriceLowerThanBaseFee { + /// Transaction max gas price + gas_price: U256, + /// Current block base fee + base_fee: U256, + }, /// Transaction has too low fee /// (there is already a transaction with the same sender-nonce but higher gas price) TooCheapToReplace { @@ -114,6 +121,15 @@ impl fmt::Display for Error { InsufficientGasPrice { minimal, got } => { format!("Insufficient gas price. Min={}, Given={}", minimal, got) } + GasPriceLowerThanBaseFee { + gas_price, + base_fee, + } => { + format!( + "Max gas price is lower then required base fee. Gas price={}, Base fee={}", + gas_price, base_fee + ) + } InsufficientGas { minimal, got } => { format!("Insufficient gas. Min={}, Given={}", minimal, got) } diff --git a/crates/ethcore/types/src/transaction/transaction.rs b/crates/ethcore/types/src/transaction/transaction.rs index 892b00ea0..bd3f41048 100644 --- a/crates/ethcore/types/src/transaction/transaction.rs +++ b/crates/ethcore/types/src/transaction/transaction.rs @@ -25,7 +25,7 @@ use ethereum_types::{Address, BigEndianHash, H160, H256, U256}; use parity_util_mem::MallocSizeOf; use rlp::{self, DecoderError, Rlp, RlpStream}; -use std::ops::Deref; +use std::{cmp::min, ops::Deref}; pub type AccessListItem = (H160, Vec); pub type AccessList = Vec; @@ -130,7 +130,7 @@ pub mod signature { pub struct Transaction { /// Nonce. pub nonce: U256, - /// Gas price. + /// Gas price for non 1559 transactions. MaxFeePerGas for 1559 transactions. pub gas_price: U256, /// Gas paid up front for transaction execution. pub gas: U256, @@ -364,11 +364,149 @@ impl AccessListTx { } } +#[derive(Debug, Clone, Eq, PartialEq, MallocSizeOf)] +pub struct EIP1559TransactionTx { + pub transaction: AccessListTx, + pub max_priority_fee_per_gas: U256, +} + +impl EIP1559TransactionTx { + pub fn tx_type(&self) -> TypedTxId { + TypedTxId::EIP1559Transaction + } + + pub fn tx(&self) -> &Transaction { + &self.transaction.tx() + } + + pub fn tx_mut(&mut self) -> &mut Transaction { + self.transaction.tx_mut() + } + + // decode bytes by this payload spec: rlp([2, [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas(gasPrice), gasLimit, to, value, data, access_list, senderV, senderR, senderS]]) + pub fn decode(tx: &[u8]) -> Result { + let tx_rlp = &Rlp::new(tx); + + // we need to have 12 items in this list + if tx_rlp.item_count()? != 12 { + return Err(DecoderError::RlpIncorrectListLen); + } + + let chain_id = Some(tx_rlp.val_at(0)?); + + let max_priority_fee_per_gas = tx_rlp.val_at(2)?; + + let tx = Transaction { + nonce: tx_rlp.val_at(1)?, + gas_price: tx_rlp.val_at(3)?, //taken from max_fee_per_gas + gas: tx_rlp.val_at(4)?, + action: tx_rlp.val_at(5)?, + value: tx_rlp.val_at(6)?, + data: tx_rlp.val_at(7)?, + }; + + // access list we get from here + let accl_rlp = tx_rlp.at(8)?; + + // access_list pattern: [[{20 bytes}, [{32 bytes}...]]...] + let mut accl: AccessList = Vec::new(); + + for i in 0..accl_rlp.item_count()? { + let accounts = accl_rlp.at(i)?; + + // check if there is list of 2 items + if accounts.item_count()? != 2 { + return Err(DecoderError::Custom("Unknown access list length")); + } + accl.push((accounts.val_at(0)?, accounts.list_at(1)?)); + } + + // we get signature part from here + let signature = SignatureComponents { + standard_v: tx_rlp.val_at(9)?, + r: tx_rlp.val_at(10)?, + s: tx_rlp.val_at(11)?, + }; + + // and here we create UnverifiedTransaction and calculate its hash + Ok(UnverifiedTransaction::new( + TypedTransaction::EIP1559Transaction(EIP1559TransactionTx { + transaction: AccessListTx::new(tx, accl), + max_priority_fee_per_gas, + }), + chain_id, + signature, + H256::zero(), + ) + .compute_hash()) + } + + fn encode_payload( + &self, + chain_id: Option, + signature: Option<&SignatureComponents>, + ) -> RlpStream { + let mut stream = RlpStream::new(); + + let list_size = if signature.is_some() { 12 } else { 9 }; + stream.begin_list(list_size); + + // append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size. + stream.append(&(if let Some(n) = chain_id { n } else { 0 })); + + stream.append(&self.tx().nonce); + stream.append(&self.max_priority_fee_per_gas); + stream.append(&self.tx().gas_price); + stream.append(&self.tx().gas); + stream.append(&self.tx().action); + stream.append(&self.tx().value); + stream.append(&self.tx().data); + + // access list + stream.begin_list(self.transaction.access_list.len()); + for access in self.transaction.access_list.iter() { + stream.begin_list(2); + stream.append(&access.0); + stream.begin_list(access.1.len()); + for storage_key in access.1.iter() { + stream.append(storage_key); + } + } + + // append signature if any + if let Some(signature) = signature { + signature.rlp_append(&mut stream); + } + stream + } + + // encode by this payload spec: 0x02 | rlp([2, [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas(gasPrice), gasLimit, to, value, data, access_list, senderV, senderR, senderS]]) + pub fn encode( + &self, + chain_id: Option, + signature: Option<&SignatureComponents>, + ) -> Vec { + let stream = self.encode_payload(chain_id, signature); + // make as vector of bytes + [&[TypedTxId::EIP1559Transaction as u8], stream.as_raw()].concat() + } + + pub fn rlp_append( + &self, + rlp: &mut RlpStream, + chain_id: Option, + signature: &SignatureComponents, + ) { + rlp.append(&self.encode(chain_id, Some(signature))); + } +} + #[derive(Debug, Clone, Eq, PartialEq, MallocSizeOf)] pub enum TypedTransaction { - Legacy(Transaction), // old legacy RLP encoded transaction + Legacy(Transaction), // old legacy RLP encoded transaction AccessList(AccessListTx), // EIP-2930 Transaction with a list of addresses and storage keys that the transaction plans to access. - // Accesses outside the list are possible, but become more expensive. + // Accesses outside the list are possible, but become more expensive. + EIP1559Transaction(EIP1559TransactionTx), } impl TypedTransaction { @@ -376,6 +514,7 @@ impl TypedTransaction { match self { Self::Legacy(_) => TypedTxId::Legacy, Self::AccessList(_) => TypedTxId::AccessList, + Self::EIP1559Transaction(_) => TypedTxId::EIP1559Transaction, } } @@ -384,6 +523,7 @@ impl TypedTransaction { keccak(match self { Self::Legacy(tx) => tx.encode(chain_id, None), Self::AccessList(tx) => tx.encode(chain_id, None), + Self::EIP1559Transaction(tx) => tx.encode(chain_id, None), }) } @@ -472,6 +612,7 @@ impl TypedTransaction { match self { Self::Legacy(tx) => tx, Self::AccessList(ocl) => ocl.tx(), + Self::EIP1559Transaction(tx) => tx.tx(), } } @@ -479,6 +620,34 @@ impl TypedTransaction { match self { Self::Legacy(tx) => tx, Self::AccessList(ocl) => ocl.tx_mut(), + Self::EIP1559Transaction(tx) => tx.tx_mut(), + } + } + + pub fn access_list(&self) -> Option<&AccessList> { + match self { + Self::EIP1559Transaction(tx) => Some(&tx.transaction.access_list), + Self::AccessList(tx) => Some(&tx.access_list), + Self::Legacy(_) => None, + } + } + + pub fn effective_gas_price(&self, block_base_fee: Option) -> U256 { + match self { + Self::EIP1559Transaction(tx) => min( + self.tx().gas_price, + tx.max_priority_fee_per_gas + block_base_fee.unwrap_or_default(), + ), + Self::AccessList(_) => self.tx().gas_price, + Self::Legacy(_) => self.tx().gas_price, + } + } + + pub fn max_priority_fee_per_gas(&self) -> U256 { + match self { + Self::EIP1559Transaction(tx) => tx.max_priority_fee_per_gas, + Self::AccessList(tx) => tx.tx().gas_price, + Self::Legacy(tx) => tx.gas_price, } } @@ -493,6 +662,7 @@ impl TypedTransaction { } // other transaction types match id.unwrap() { + TypedTxId::EIP1559Transaction => EIP1559TransactionTx::decode(&tx[1..]), TypedTxId::AccessList => AccessListTx::decode(&tx[1..]), TypedTxId::Legacy => return Err(DecoderError::Custom("Unknown transaction legacy")), } @@ -543,6 +713,7 @@ impl TypedTransaction { match self { Self::Legacy(tx) => tx.rlp_append(s, chain_id, signature), Self::AccessList(opt) => opt.rlp_append(s, chain_id, signature), + Self::EIP1559Transaction(tx) => tx.rlp_append(s, chain_id, signature), } } @@ -558,6 +729,7 @@ impl TypedTransaction { match self { Self::Legacy(tx) => tx.encode(chain_id, signature), Self::AccessList(opt) => opt.encode(chain_id, signature), + Self::EIP1559Transaction(tx) => tx.encode(chain_id, signature), } } } @@ -990,8 +1162,6 @@ mod tests { }) .null_sign(1); - println!("transaction {:?}", t); - let res = SignedTransaction::new(t.transaction); match res { Err(publickey::Error::InvalidSignature) => {} @@ -1047,6 +1217,40 @@ mod tests { } } + #[test] + fn should_encode_decode_eip1559_tx() { + use self::publickey::{Generator, Random}; + let key = Random.generate(); + let t = TypedTransaction::EIP1559Transaction(EIP1559TransactionTx { + transaction: AccessListTx::new( + Transaction { + action: Action::Create, + nonce: U256::from(42), + gas_price: U256::from(3000), + gas: U256::from(50_000), + value: U256::from(1), + data: b"Hello!".to_vec(), + }, + vec![ + ( + H160::from_low_u64_be(10), + vec![H256::from_low_u64_be(102), H256::from_low_u64_be(103)], + ), + (H160::from_low_u64_be(400), vec![]), + ], + ), + max_priority_fee_per_gas: U256::from(100), + }) + .sign(&key.secret(), Some(69)); + let encoded = t.encode(); + + let t_new = + TypedTransaction::decode(&encoded).expect("Error on UnverifiedTransaction decoder"); + if t_new.unsigned != t.unsigned { + assert!(true, "encoded/decoded tx differs from original"); + } + } + #[test] fn should_decode_access_list_in_rlp() { use rustc_hex::FromHex; @@ -1055,6 +1259,14 @@ mod tests { .expect("decoding tx data failed"); } + #[test] + fn should_decode_eip1559_in_rlp() { + use rustc_hex::FromHex; + let encoded_tx = "b8cb01f8a7802a820bb882c35080018648656c6c6f21f872f85994000000000000000000000000000000000000000af842a00000000000000000000000000000000000000000000000000000000000000066a00000000000000000000000000000000000000000000000000000000000000067d6940000000000000000000000000000000000000190c080a00ea0f1fda860320f51e182fe68ea90a8e7611653d3975b9301580adade6b8aa4a023530a1a96e0f15f90959baf1cd2d9114f7c7568ac7d77f4413c0a6ca6cdac74"; + let _ = TypedTransaction::decode_rlp(&Rlp::new(&FromHex::from_hex(encoded_tx).unwrap())) + .expect("decoding tx data failed"); + } + #[test] fn should_decode_access_list_solo() { use rustc_hex::FromHex; @@ -1096,7 +1308,6 @@ mod tests { .expect("decoding tx data failed"); let signed = SignedTransaction::new(signed).unwrap(); assert_eq!(signed.sender(), H160::from_str(address).unwrap()); - println!("chainid: {:?}", signed.chain_id()); }; test_vector("f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", "f0f6f18bca1b28cd68e4357452947e021241e9ce"); diff --git a/crates/ethcore/types/src/transaction/transaction_id.rs b/crates/ethcore/types/src/transaction/transaction_id.rs index 458029ac8..5ca08003e 100644 --- a/crates/ethcore/types/src/transaction/transaction_id.rs +++ b/crates/ethcore/types/src/transaction/transaction_id.rs @@ -22,6 +22,7 @@ use serde_repr::*; #[derive(Serialize_repr, Eq, Hash, Deserialize_repr, Debug, Copy, Clone, PartialEq)] #[repr(u8)] pub enum TypedTxId { + EIP1559Transaction = 0x02, AccessList = 0x01, Legacy = 0x00, } @@ -32,12 +33,14 @@ impl TypedTxId { match n { 0 => Some(Self::Legacy), 1 => Some(Self::AccessList), + 2 => Some(Self::EIP1559Transaction), _ => None, } } pub fn try_from_wire_byte(n: u8) -> Result { match n { + x if x == TypedTxId::EIP1559Transaction as u8 => Ok(TypedTxId::EIP1559Transaction), x if x == TypedTxId::AccessList as u8 => Ok(TypedTxId::AccessList), x if (x & 0x80) != 0x00 => Ok(TypedTxId::Legacy), _ => Err(()), @@ -49,6 +52,7 @@ impl TypedTxId { match n.map(|t| t.as_u64()) { None => Some(Self::Legacy), Some(0x01) => Some(Self::AccessList), + Some(0x02) => Some(Self::EIP1559Transaction), _ => None, } } @@ -74,13 +78,17 @@ mod tests { #[test] fn typed_tx_id_try_from_wire() { + assert_eq!( + Ok(TypedTxId::EIP1559Transaction), + TypedTxId::try_from_wire_byte(0x02) + ); assert_eq!( Ok(TypedTxId::AccessList), TypedTxId::try_from_wire_byte(0x01) ); assert_eq!(Ok(TypedTxId::Legacy), TypedTxId::try_from_wire_byte(0x81)); assert_eq!(Err(()), TypedTxId::try_from_wire_byte(0x00)); - assert_eq!(Err(()), TypedTxId::try_from_wire_byte(0x02)); + assert_eq!(Err(()), TypedTxId::try_from_wire_byte(0x03)); } #[test] @@ -90,6 +98,10 @@ mod tests { Some(U64::from(0x01)), TypedTxId::AccessList.to_U64_option_id() ); + assert_eq!( + Some(U64::from(0x02)), + TypedTxId::EIP1559Transaction.to_U64_option_id() + ); } #[test] @@ -99,13 +111,21 @@ mod tests { Some(TypedTxId::AccessList), TypedTxId::from_U64_option_id(Some(U64::from(0x01))) ); - assert_eq!(None, TypedTxId::from_U64_option_id(Some(U64::from(0x02)))); + assert_eq!( + Some(TypedTxId::EIP1559Transaction), + TypedTxId::from_U64_option_id(Some(U64::from(0x02))) + ); + assert_eq!(None, TypedTxId::from_U64_option_id(Some(U64::from(0x03)))); } #[test] fn typed_tx_id_from_u8_id() { assert_eq!(Some(TypedTxId::Legacy), TypedTxId::from_u8_id(0)); assert_eq!(Some(TypedTxId::AccessList), TypedTxId::from_u8_id(1)); + assert_eq!( + Some(TypedTxId::EIP1559Transaction), + TypedTxId::from_u8_id(2) + ); assert_eq!(None, TypedTxId::from_u8_id(3)); } } diff --git a/crates/ethcore/types/src/views/block.rs b/crates/ethcore/types/src/views/block.rs index 06edfe9ea..99994f057 100644 --- a/crates/ethcore/types/src/views/block.rs +++ b/crates/ethcore/types/src/views/block.rs @@ -16,6 +16,8 @@ //! View onto block rlp. +use crate::BlockNumber; + use super::ViewRlp; use crate::{ bytes::Bytes, @@ -64,8 +66,13 @@ impl<'a> BlockView<'a> { } /// Create new Header object from header rlp. - pub fn header(&self) -> Header { - self.rlp.val_at(0) + pub fn header(&self, eip1559_transition: BlockNumber) -> Header { + Header::decode_rlp(&self.rlp.at(0).rlp, eip1559_transition).unwrap_or_else(|e| { + panic!( + "block header, view rlp is trusted and should be valid: {:?}", + e + ) + }) } /// Return header rlp. @@ -165,8 +172,13 @@ impl<'a> BlockView<'a> { } /// Return list of uncles of given block. - pub fn uncles(&self) -> Vec
{ - self.rlp.list_at(2) + pub fn uncles(&self, eip1559_transition: BlockNumber) -> Vec
{ + Header::decode_rlp_list(&self.rlp.at(2).rlp, eip1559_transition).unwrap_or_else(|e| { + panic!( + "block uncles, view rlp is trusted and should be valid: {:?}", + e + ) + }) } /// Return number of uncles in given block, without deserializing them. @@ -188,8 +200,15 @@ impl<'a> BlockView<'a> { } /// Return nth uncle. - pub fn uncle_at(&self, index: usize) -> Option
{ - self.uncles_rlp().iter().nth(index).map(|rlp| rlp.as_val()) + pub fn uncle_at(&self, index: usize, eip1559_transition: BlockNumber) -> Option
{ + self.uncles_rlp().iter().nth(index).map(|rlp| { + Header::decode_rlp(&rlp.rlp, eip1559_transition).unwrap_or_else(|e| { + panic!( + "block uncle_at, view rlp is trusted and should be valid.{:?}", + e + ) + }) + }) } /// Return nth uncle rlp. diff --git a/crates/ethcore/types/src/views/body.rs b/crates/ethcore/types/src/views/body.rs index 218e79185..6772ccd6b 100644 --- a/crates/ethcore/types/src/views/body.rs +++ b/crates/ethcore/types/src/views/body.rs @@ -147,8 +147,13 @@ impl<'a> BodyView<'a> { } /// Return list of uncles of given block. - pub fn uncles(&self) -> Vec
{ - self.rlp.list_at(1) + pub fn uncles(&self, eip1559_transition: BlockNumber) -> Vec
{ + Header::decode_rlp_list(&self.rlp.at(1).rlp, eip1559_transition).unwrap_or_else(|e| { + panic!( + "block uncles, view rlp is trusted and should be valid: {:?}", + e + ) + }) } /// Return number of uncles in given block, without deserializing them. @@ -170,8 +175,15 @@ impl<'a> BodyView<'a> { } /// Return nth uncle. - pub fn uncle_at(&self, index: usize) -> Option
{ - self.uncles_rlp().iter().nth(index).map(|rlp| rlp.as_val()) + pub fn uncle_at(&self, index: usize, eip1559_transition: BlockNumber) -> Option
{ + self.uncles_rlp().iter().nth(index).map(|rlp| { + Header::decode_rlp(&rlp.rlp, eip1559_transition).unwrap_or_else(|e| { + panic!( + "block uncle_at, view rlp is trusted and should be valid.{:?}", + e + ) + }) + }) } /// Return nth uncle rlp. diff --git a/crates/ethcore/types/src/views/header.rs b/crates/ethcore/types/src/views/header.rs index db33b2eb6..69f3175c7 100644 --- a/crates/ethcore/types/src/views/header.rs +++ b/crates/ethcore/types/src/views/header.rs @@ -123,17 +123,33 @@ impl<'a> HeaderView<'a> { } /// Returns a vector of post-RLP-encoded seal fields. - pub fn seal(&self) -> Vec { + /// If eip1559 is true, seal contains also base_fee_per_gas. Otherwise, it contains only seal fields. + pub fn seal(&self, eip1559: bool) -> Vec { + let last_seal_index = if eip1559 { + self.rlp.item_count() - 1 + } else { + self.rlp.item_count() + }; let mut seal = vec![]; - for i in 13..self.rlp.item_count() { + for i in 13..last_seal_index { seal.push(self.rlp.at(i).as_raw().to_vec()); } seal } + /// Returns block base fee. Should be called only for EIP1559 headers. + /// If called for non EIP1559 header, returns garbage + pub fn base_fee(&self) -> U256 { + match self.rlp.rlp.val_at::(self.rlp.item_count() - 1) { + Ok(base_fee) => base_fee, + Err(_) => Default::default(), + } + } + /// Returns a vector of seal fields (RLP-decoded). - pub fn decode_seal(&self) -> Result, rlp::DecoderError> { - let seal = self.seal(); + /// If eip1559 is true, seal contains also base_fee_per_gas. Otherwise, it contains only seal fields. + pub fn decode_seal(&self, eip1559: bool) -> Result, rlp::DecoderError> { + let seal = self.seal(eip1559); seal.into_iter() .map(|s| rlp::Rlp::new(&s).data().map(|x| x.to_vec())) .collect() @@ -198,6 +214,6 @@ mod tests { assert_eq!(view.gas_used(), 0x524d.into()); assert_eq!(view.timestamp(), 0x56_8e_93_2a); assert_eq!(view.extra_data(), vec![] as Vec); - assert_eq!(view.seal(), vec![mix_hash, nonce]); + assert_eq!(view.seal(false), vec![mix_hash, nonce]); } } diff --git a/crates/ethcore/types/src/views/typed_transaction.rs b/crates/ethcore/types/src/views/typed_transaction.rs index 39b670c65..e91f7122e 100644 --- a/crates/ethcore/types/src/views/typed_transaction.rs +++ b/crates/ethcore/types/src/views/typed_transaction.rs @@ -28,6 +28,8 @@ use rlp::Rlp; /// View onto transaction rlp. Assumption is this is part of block. /// Typed Transaction View. It handles raw bytes to search for particular field. +/// EIP1559 tx: +/// 2 | [chainId, nonce, maxPriorityFeePerGas, maxFeePerGas(gasPrice), gasLimit, to, value, data, access_list, senderV, senderR, senderS] /// Access tx: /// 1 | [chainId, nonce, gasPrice, gasLimit, to, value, data, access_list, senderV, senderR, senderS] /// Legacy tx: @@ -81,7 +83,10 @@ impl<'a> TypedTransactionView<'a> { } TypedTxId::AccessList => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) .rlp - .val_at(1), + .val_at(0), + TypedTxId::EIP1559Transaction => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(0), } } @@ -92,6 +97,9 @@ impl<'a> TypedTransactionView<'a> { TypedTxId::AccessList => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) .rlp .val_at(1), + TypedTxId::EIP1559Transaction => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(1), } } @@ -102,6 +110,9 @@ impl<'a> TypedTransactionView<'a> { TypedTxId::AccessList => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) .rlp .val_at(2), + TypedTxId::EIP1559Transaction => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(3), } } @@ -112,6 +123,9 @@ impl<'a> TypedTransactionView<'a> { TypedTxId::AccessList => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) .rlp .val_at(3), + TypedTxId::EIP1559Transaction => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(4), } } @@ -122,6 +136,9 @@ impl<'a> TypedTransactionView<'a> { TypedTxId::AccessList => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) .rlp .val_at(5), + TypedTxId::EIP1559Transaction => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(6), } } @@ -132,6 +149,9 @@ impl<'a> TypedTransactionView<'a> { TypedTxId::AccessList => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) .rlp .val_at(6), + TypedTxId::EIP1559Transaction => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(7), } } @@ -151,6 +171,18 @@ impl<'a> TypedTransactionView<'a> { chain_id, ) } + TypedTxId::EIP1559Transaction => { + let chain_id = match self.chain_id() { + 0 => None, + n => Some(n), + }; + signature::add_chain_replay_protection( + view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(9), + chain_id, + ) + } }; r as u8 } @@ -161,6 +193,9 @@ impl<'a> TypedTransactionView<'a> { TypedTxId::AccessList => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) .rlp .val_at(8), + TypedTxId::EIP1559Transaction => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(9), } } @@ -171,6 +206,9 @@ impl<'a> TypedTransactionView<'a> { TypedTxId::AccessList => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) .rlp .val_at(9), + TypedTxId::EIP1559Transaction => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(10), } } @@ -181,6 +219,9 @@ impl<'a> TypedTransactionView<'a> { TypedTxId::AccessList => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) .rlp .val_at(10), + TypedTxId::EIP1559Transaction => view!(Self, &self.rlp.rlp.data().unwrap()[1..]) + .rlp + .val_at(11), } } } @@ -219,7 +260,26 @@ mod tests { #[test] fn test_access_list_transaction_view() { let rlp = "b8c101f8be01010a8301e24194000000000000000000000000000000000000aaaa8080f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000080a082dc119130f280bd72e3fd4e10220e35b767031b84b8dd1f64085e0158f234dba072228551e678a8a6c6e9bae0ae786b8839c7fda0a994caddd23910f45f385cc0".from_hex().unwrap(); + let view = view!(TypedTransactionView, &rlp); + assert_eq!(view.nonce(), 0x1.into()); + assert_eq!(view.gas_price(), 0xa.into()); + assert_eq!(view.gas(), 0x1e241.into()); + assert_eq!(view.value(), 0x0.into()); + assert_eq!(view.data(), "".from_hex().unwrap()); + assert_eq!( + view.r(), + "82dc119130f280bd72e3fd4e10220e35b767031b84b8dd1f64085e0158f234db".into() + ); + assert_eq!( + view.s(), + "72228551e678a8a6c6e9bae0ae786b8839c7fda0a994caddd23910f45f385cc0".into() + ); + assert_eq!(view.standard_v(), 0x0); + } + #[test] + fn test_eip1559_transaction_view() { + let rlp = "b8c202f8bf01010a0a8301e24194000000000000000000000000000000000000aaaa8080f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000080a082dc119130f280bd72e3fd4e10220e35b767031b84b8dd1f64085e0158f234dba072228551e678a8a6c6e9bae0ae786b8839c7fda0a994caddd23910f45f385cc0".from_hex().unwrap(); let view = view!(TypedTransactionView, &rlp); assert_eq!(view.nonce(), 0x1.into()); assert_eq!(view.gas_price(), 0xa.into()); diff --git a/crates/ethjson/src/blockchain/blockchain.rs b/crates/ethjson/src/blockchain/blockchain.rs index 3f7e5b061..ae4e3f975 100644 --- a/crates/ethjson/src/blockchain/blockchain.rs +++ b/crates/ethjson/src/blockchain/blockchain.rs @@ -89,6 +89,7 @@ impl BlockChain { state_root: Some(self.genesis_block.state_root.clone()), gas_used: Some(self.genesis_block.gas_used), extra_data: Some(self.genesis_block.extra_data.clone()), + base_fee: self.genesis_block.base_fee, } } } diff --git a/crates/ethjson/src/blockchain/header.rs b/crates/ethjson/src/blockchain/header.rs index f03cb7dcf..1b32b40a6 100644 --- a/crates/ethjson/src/blockchain/header.rs +++ b/crates/ethjson/src/blockchain/header.rs @@ -62,6 +62,8 @@ pub struct Header { /// Uncles hash. #[serde(rename = "uncleHash")] pub uncles_hash: H256, + /// Base fee + pub base_fee: Option, } #[cfg(test)] diff --git a/crates/ethjson/src/spec/genesis.rs b/crates/ethjson/src/spec/genesis.rs index 0bbc95431..312deef0a 100644 --- a/crates/ethjson/src/spec/genesis.rs +++ b/crates/ethjson/src/spec/genesis.rs @@ -51,6 +51,8 @@ pub struct Genesis { pub gas_used: Option, /// Extra data. pub extra_data: Option, + /// Base fee. + pub base_fee: Option, } #[cfg(test)] @@ -122,6 +124,7 @@ mod tests { ) .unwrap() ), + base_fee: None, } ); } diff --git a/crates/ethjson/src/spec/params.rs b/crates/ethjson/src/spec/params.rs index 54407ae6a..218dd1cbe 100644 --- a/crates/ethjson/src/spec/params.rs +++ b/crates/ethjson/src/spec/params.rs @@ -112,6 +112,10 @@ pub struct Params { /// See `CommonParams` docs. pub eip2930_transition: Option, /// See `CommonParams` docs. + pub eip1559_transition: Option, + /// See `CommonParams` docs. + pub eip3198_transition: Option, + /// See `CommonParams` docs. pub eip3529_transition: Option, /// See `CommonParams` docs. pub eip3541_transition: Option, @@ -148,6 +152,12 @@ pub struct Params { pub kip4_transition: Option, /// KIP6 activiation block height. pub kip6_transition: Option, + /// Base fee max change denominator + pub eip1559_base_fee_max_change_denominator: Option, + /// Elasticity multiplier + pub eip1559_elasticity_multiplier: Option, + /// Default value for the block base fee + pub eip1559_base_fee_initial_value: Option, } #[cfg(test)] diff --git a/crates/ethjson/src/spec/spec.rs b/crates/ethjson/src/spec/spec.rs index 9db235ac6..0d45d63ff 100644 --- a/crates/ethjson/src/spec/spec.rs +++ b/crates/ethjson/src/spec/spec.rs @@ -38,6 +38,7 @@ pub enum ForkSpec { ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5, Berlin, + London, } /// Spec deserialization. diff --git a/crates/ethjson/src/vm/env.rs b/crates/ethjson/src/vm/env.rs index ad4caa682..932f86a62 100644 --- a/crates/ethjson/src/vm/env.rs +++ b/crates/ethjson/src/vm/env.rs @@ -35,6 +35,9 @@ pub struct Env { /// Timestamp. #[serde(rename = "currentTimestamp")] pub timestamp: Uint, + /// Block base fee. + #[serde(rename = "currentBlockBaseFee")] + pub base_fee: Option, } #[cfg(test)] diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 6b406605a..e2d0e8d64 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -64,7 +64,7 @@ ethcore-network = { path = "../net/network" } fake-fetch = { path = "../net/fake-fetch" } macros = { path = "../util/macros" } pretty_assertions = "0.1" -transaction-pool = "2.0.1" +txpool = { path = "../transaction-pool" } [features] accounts = ["ethcore-accounts"] diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 09b8bb4a6..e0973a720 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -92,8 +92,6 @@ extern crate serde_derive; #[cfg(test)] extern crate ethjson; -#[cfg(test)] -extern crate transaction_pool as txpool; #[cfg(test)] #[macro_use] diff --git a/crates/rpc/src/v1/helpers/dispatch/full.rs b/crates/rpc/src/v1/helpers/dispatch/full.rs index 87424755e..30daae085 100644 --- a/crates/rpc/src/v1/helpers/dispatch/full.rs +++ b/crates/rpc/src/v1/helpers/dispatch/full.rs @@ -125,9 +125,10 @@ impl Dispatcher used_default_from: request.from.is_none(), to: request.to, nonce, - gas_price: request.gas_price.unwrap_or_else(|| { + gas_price: Some(request.gas_price.unwrap_or_else(|| { default_gas_price(&*self.client, &*self.miner, self.gas_price_percentile) - }), + })), + max_fee_per_gas: request.max_fee_per_gas, gas: request .gas .unwrap_or_else(|| self.miner.sensible_gas_limit()), @@ -135,6 +136,7 @@ impl Dispatcher data: request.data.unwrap_or_else(Vec::new), condition: request.condition, access_list: request.access_list, + max_priority_fee_per_gas: request.max_priority_fee_per_gas, })) } diff --git a/crates/rpc/src/v1/helpers/dispatch/signing.rs b/crates/rpc/src/v1/helpers/dispatch/signing.rs index 43704a6b0..03f244ba7 100644 --- a/crates/rpc/src/v1/helpers/dispatch/signing.rs +++ b/crates/rpc/src/v1/helpers/dispatch/signing.rs @@ -22,7 +22,8 @@ use crypto::{publickey::Signature, DEFAULT_MAC}; use ethereum_types::{Address, H256, U256}; use jsonrpc_core::{Error, ErrorCode}; use types::transaction::{ - AccessListTx, Action, SignedTransaction, Transaction, TypedTransaction, TypedTxId, + AccessListTx, Action, EIP1559TransactionTx, SignedTransaction, Transaction, TypedTransaction, + TypedTxId, }; use jsonrpc_core::Result; @@ -50,11 +51,11 @@ impl super::Accounts for Signer { nonce: U256, password: SignWith, ) -> Result> { - let legacy_tx = Transaction { + let mut legacy_tx = Transaction { nonce, action: filled.to.map_or(Action::Create, Action::Call), gas: filled.gas, - gas_price: filled.gas_price, + gas_price: filled.gas_price.unwrap_or_default(), value: filled.value, data: filled.data, }; @@ -74,6 +75,31 @@ impl super::Accounts for Signer { .collect(), )) } + Some(TypedTxId::EIP1559Transaction) => { + if let Some(max_fee_per_gas) = filled.max_fee_per_gas { + legacy_tx.gas_price = max_fee_per_gas; + } else { + return Err(Error::new(ErrorCode::InvalidParams)); + } + + if let Some(max_priority_fee_per_gas) = filled.max_priority_fee_per_gas { + let transaction = AccessListTx::new( + legacy_tx, + filled + .access_list + .unwrap_or_default() + .into_iter() + .map(Into::into) + .collect(), + ); + TypedTransaction::EIP1559Transaction(EIP1559TransactionTx { + transaction, + max_priority_fee_per_gas, + }) + } else { + return Err(Error::new(ErrorCode::InvalidParams)); + } + } None => return Err(Error::new(ErrorCode::InvalidParams)), }; diff --git a/crates/rpc/src/v1/helpers/errors.rs b/crates/rpc/src/v1/helpers/errors.rs index 9d841bd6e..94c0eaf79 100644 --- a/crates/rpc/src/v1/helpers/errors.rs +++ b/crates/rpc/src/v1/helpers/errors.rs @@ -385,6 +385,9 @@ pub fn transaction_message(error: &TransactionError) -> String { InsufficientGasPrice { minimal, got } => { format!("Transaction gas price is too low. It does not satisfy your node's minimal gas price (minimal: {}, got: {}). Try increasing the gas price.", minimal, got) } + GasPriceLowerThanBaseFee { gas_price, base_fee} => { + format!("Transaction max gas price is lower then the required base fee (gas_price: {}, base_fee: {}). Try increasing the max gas price.", gas_price, base_fee) + } InsufficientBalance { balance, cost } => { format!("Insufficient funds. The account you tried to send transaction from does not have enough funds. Required {} and got: {}.", cost, balance) } diff --git a/crates/rpc/src/v1/helpers/external_signer/signing_queue.rs b/crates/rpc/src/v1/helpers/external_signer/signing_queue.rs index 6954c2772..23ddb4a2b 100644 --- a/crates/rpc/src/v1/helpers/external_signer/signing_queue.rs +++ b/crates/rpc/src/v1/helpers/external_signer/signing_queue.rs @@ -260,13 +260,15 @@ mod test { from: Address::from_low_u64_be(1), used_default_from: false, to: Some(Address::from_low_u64_be(2)), - gas_price: 0.into(), + gas_price: Some(U256::from(0)), + max_fee_per_gas: None, gas: 10_000.into(), value: 10_000_000.into(), data: vec![], nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, }) } diff --git a/crates/rpc/src/v1/helpers/fake_sign.rs b/crates/rpc/src/v1/helpers/fake_sign.rs index 5d3318146..2f10ea1c5 100644 --- a/crates/rpc/src/v1/helpers/fake_sign.rs +++ b/crates/rpc/src/v1/helpers/fake_sign.rs @@ -16,7 +16,8 @@ use std::cmp::min; use types::transaction::{ - AccessListTx, Action, SignedTransaction, Transaction, TypedTransaction, TypedTxId, + AccessListTx, Action, EIP1559TransactionTx, SignedTransaction, Transaction, TypedTransaction, + TypedTxId, }; use ethereum_types::U256; @@ -27,7 +28,7 @@ pub fn sign_call(request: CallRequest) -> Result { let max_gas = U256::from(500_000_000); let gas = min(request.gas.unwrap_or(max_gas), max_gas); let from = request.from.unwrap_or_default(); - let tx_legacy = Transaction { + let mut tx_legacy = Transaction { nonce: request.nonce.unwrap_or_default(), action: request.to.map_or(Action::Create, Action::Call), gas, @@ -51,6 +52,31 @@ pub fn sign_call(request: CallRequest) -> Result { .collect(), )) } + Some(TypedTxId::EIP1559Transaction) => { + if let Some(max_fee_per_gas) = request.max_fee_per_gas { + tx_legacy.gas_price = max_fee_per_gas; + } else { + return Err(Error::new(ErrorCode::InvalidParams)); + } + + if let Some(max_priority_fee_per_gas) = request.max_priority_fee_per_gas { + let transaction = AccessListTx::new( + tx_legacy, + request + .access_list + .unwrap_or_default() + .into_iter() + .map(Into::into) + .collect(), + ); + TypedTransaction::EIP1559Transaction(EIP1559TransactionTx { + transaction, + max_priority_fee_per_gas, + }) + } else { + return Err(Error::new(ErrorCode::InvalidParams)); + } + } _ => return Err(Error::new(ErrorCode::InvalidParams)), }; Ok(tx_typed.fake_sign(from)) diff --git a/crates/rpc/src/v1/helpers/requests.rs b/crates/rpc/src/v1/helpers/requests.rs index b8b32e506..e55644238 100644 --- a/crates/rpc/src/v1/helpers/requests.rs +++ b/crates/rpc/src/v1/helpers/requests.rs @@ -30,6 +30,8 @@ pub struct TransactionRequest { pub to: Option
, /// Gas Price pub gas_price: Option, + /// Max fee per gas + pub max_fee_per_gas: Option, /// Gas pub gas: Option, /// Value of transaction in wei @@ -42,6 +44,8 @@ pub struct TransactionRequest { pub condition: Option, /// Access list pub access_list: Option, + /// Miner bribe + pub max_priority_fee_per_gas: Option, } /// Transaction request coming from RPC with default values filled in. @@ -56,7 +60,9 @@ pub struct FilledTransactionRequest { /// Recipient pub to: Option
, /// Gas Price - pub gas_price: U256, + pub gas_price: Option, + /// Max fee per gas + pub max_fee_per_gas: Option, /// Gas pub gas: U256, /// Value of transaction in wei @@ -69,6 +75,8 @@ pub struct FilledTransactionRequest { pub condition: Option, /// Access list pub access_list: Option, + /// Miner bribe + pub max_priority_fee_per_gas: Option, } impl From for TransactionRequest { @@ -77,13 +85,15 @@ impl From for TransactionRequest { transaction_type: r.transaction_type, from: Some(r.from), to: r.to, - gas_price: Some(r.gas_price), + gas_price: r.gas_price, + max_fee_per_gas: r.max_fee_per_gas, gas: Some(r.gas), value: Some(r.value), data: Some(r.data), nonce: r.nonce, condition: r.condition, access_list: r.access_list.map(Into::into), + max_priority_fee_per_gas: r.max_priority_fee_per_gas, } } } @@ -99,6 +109,8 @@ pub struct CallRequest { pub to: Option
, /// Gas Price pub gas_price: Option, + /// Max fee per gas + pub max_fee_per_gas: Option, /// Gas pub gas: Option, /// Value @@ -109,6 +121,8 @@ pub struct CallRequest { pub nonce: Option, /// Access list pub access_list: Option, + /// Miner bribe + pub max_priority_fee_per_gas: Option, } /// Confirmation object diff --git a/crates/rpc/src/v1/impls/debug.rs b/crates/rpc/src/v1/impls/debug.rs index d72adce52..6c22fa780 100644 --- a/crates/rpc/src/v1/impls/debug.rs +++ b/crates/rpc/src/v1/impls/debug.rs @@ -76,6 +76,7 @@ impl Debug for DebugClient { .cloned() .map(Into::into) .collect(), + base_fee_per_gas: block.header.base_fee(), uncles: block.uncles.iter().map(Header::hash).collect(), transactions: BlockTransactions::Full( block diff --git a/crates/rpc/src/v1/impls/eth.rs b/crates/rpc/src/v1/impls/eth.rs index 2180bb323..ba3f0b107 100644 --- a/crates/rpc/src/v1/impls/eth.rs +++ b/crates/rpc/src/v1/impls/eth.rs @@ -259,6 +259,7 @@ where match (block, difficulty) { (Some(block), Some(total_difficulty)) => { let view = block.header_view(); + let eip1559_enabled = client.engine().schedule(view.number()).eip1559; Ok(Some(RichBlock { inner: Block { hash: match is_pending { @@ -286,7 +287,18 @@ where timestamp: view.timestamp().into(), difficulty: view.difficulty(), total_difficulty: Some(total_difficulty), - seal_fields: view.seal().into_iter().map(Into::into).collect(), + seal_fields: view + .seal(eip1559_enabled) + .into_iter() + .map(Into::into) + .collect(), + base_fee_per_gas: { + if eip1559_enabled { + Some(view.base_fee()) + } else { + None + } + }, uncles: block.uncle_hashes(), transactions: match include_txs { true => BlockTransactions::Full( @@ -408,7 +420,8 @@ where }; let uncle = match client.uncle(uncle_id) { - Some(hdr) => match hdr.decode() { + Some(hdr) => match hdr.decode(self.client.engine().params().eip1559_transition) + { Ok(h) => h, Err(e) => return Err(errors::decode(e)), }, @@ -456,6 +469,7 @@ where receipts_root: *uncle.receipts_root(), extra_data: uncle.extra_data().clone().into(), seal_fields: uncle.seal().iter().cloned().map(Into::into).collect(), + base_fee_per_gas: uncle.base_fee(), uncles: vec![], transactions: BlockTransactions::Hashes(vec![]), }, @@ -1125,7 +1139,9 @@ where .client .block_header(id) .ok_or_else(errors::state_pruned) - .and_then(|h| h.decode().map_err(errors::decode))); + .and_then(|h| h + .decode(self.client.engine().params().eip1559_transition) + .map_err(errors::decode))); (state, header) }; @@ -1166,7 +1182,9 @@ where .client .block_header(id) .ok_or_else(errors::state_pruned) - .and_then(|h| h.decode().map_err(errors::decode))); + .and_then(|h| h + .decode(self.client.engine().params().eip1559_transition) + .map_err(errors::decode))); (state, header) }; diff --git a/crates/rpc/src/v1/impls/eth_pubsub.rs b/crates/rpc/src/v1/impls/eth_pubsub.rs index 7bf41bee7..3e042d933 100644 --- a/crates/rpc/src/v1/impls/eth_pubsub.rs +++ b/crates/rpc/src/v1/impls/eth_pubsub.rs @@ -34,10 +34,12 @@ use v1::{ helpers::{errors, limit_logs, Subscribers}, metadata::Metadata, traits::EthPubSub, - types::{pubsub, Log, RichHeader}, + types::{pubsub, Header, Log, RichHeader}, }; -use ethcore::client::{BlockChainClient, BlockId, ChainNotify, ChainRouteType, NewBlocks}; +use ethcore::client::{ + BlockChainClient, BlockId, ChainNotify, ChainRouteType, EngineInfo, NewBlocks, +}; use ethereum_types::H256; use parity_runtime::Executor; use parking_lot::RwLock; @@ -100,7 +102,10 @@ pub struct ChainNotificationHandler { transactions_subscribers: Arc>>, } -impl ChainNotificationHandler { +impl ChainNotificationHandler +where + C: EngineInfo, +{ fn notify(executor: &Executor, subscriber: &Client, result: pubsub::Result) { executor.spawn( subscriber @@ -117,7 +122,10 @@ impl ChainNotificationHandler { &self.executor, subscriber, pubsub::Result::Header(Box::new(RichHeader { - inner: header.into(), + inner: Header::new( + header, + self.client.engine().params().eip1559_transition, + ), extra_info: extra_info.clone(), })), ); @@ -174,7 +182,7 @@ impl ChainNotificationHandler { } } -impl ChainNotify for ChainNotificationHandler { +impl ChainNotify for ChainNotificationHandler { // t_nb 11.3 RPC. Notify subscriber header/logs about new block fn new_blocks(&self, new_blocks: NewBlocks) { if self.heads_subscribers.read().is_empty() && self.logs_subscribers.read().is_empty() { diff --git a/crates/rpc/src/v1/impls/parity.rs b/crates/rpc/src/v1/impls/parity.rs index a41ae0a37..d7286f46f 100644 --- a/crates/rpc/src/v1/impls/parity.rs +++ b/crates/rpc/src/v1/impls/parity.rs @@ -19,7 +19,7 @@ use std::{collections::BTreeMap, str::FromStr, sync::Arc}; use crypto::{publickey::ecies, DEFAULT_MAC}; use ethcore::{ - client::{BlockChainClient, Call, StateClient}, + client::{BlockChainClient, Call, EngineInfo, StateClient}, miner::{self, MinerService, TransactionFilter}, snapshot::{RestorationStatus, SnapshotService}, state::StateInfo, @@ -43,7 +43,7 @@ use v1::{ metadata::Metadata, traits::Parity, types::{ - block_number_to_id, BlockNumber, Bytes, CallRequest, ChainStatus, Histogram, + block_number_to_id, BlockNumber, Bytes, CallRequest, ChainStatus, Header, Histogram, LocalTransactionStatus, Peers, Receipt, RecoveredAccount, RichHeader, RpcSettings, Transaction, TransactionStats, }, @@ -69,7 +69,7 @@ where impl ParityClient where - C: BlockChainClient + PrometheusMetrics, + C: BlockChainClient + PrometheusMetrics + EngineInfo, { /// Creates new `ParityClient`. pub fn new( @@ -105,6 +105,7 @@ where + PrometheusMetrics + StateClient + Call + + EngineInfo + 'static, M: MinerService + 'static, { @@ -386,7 +387,7 @@ where }; Box::new(future::ok(RichHeader { - inner: header.into(), + inner: Header::new(&header, self.client.engine().params().eip1559_transition), extra_info: extra.unwrap_or_default(), })) } @@ -449,7 +450,7 @@ where .client .block_header(id) .ok_or_else(errors::state_pruned)? - .decode() + .decode(self.client.engine().params().eip1559_transition) .map_err(errors::decode)?; (state, header) diff --git a/crates/rpc/src/v1/impls/signer.rs b/crates/rpc/src/v1/impls/signer.rs index 33a1f653e..8d785f8ab 100644 --- a/crates/rpc/src/v1/impls/signer.rs +++ b/crates/rpc/src/v1/impls/signer.rs @@ -124,8 +124,8 @@ impl SignerClient { // Altering sender should always reset the nonce. request.nonce = None; } - if let Some(gas_price) = modification.gas_price { - request.gas_price = gas_price; + if modification.gas_price.is_some() { + request.gas_price = modification.gas_price; } if let Some(gas) = modification.gas { request.gas = gas; diff --git a/crates/rpc/src/v1/impls/traces.rs b/crates/rpc/src/v1/impls/traces.rs index bf528e252..0022b895c 100644 --- a/crates/rpc/src/v1/impls/traces.rs +++ b/crates/rpc/src/v1/impls/traces.rs @@ -19,7 +19,8 @@ use std::sync::Arc; use ethcore::client::{ - BlockChainClient, BlockId, Call, CallAnalytics, StateClient, StateInfo, TraceId, TransactionId, + BlockChainClient, BlockId, Call, CallAnalytics, EngineInfo, StateClient, StateInfo, TraceId, + TransactionId, }; use ethereum_types::H256; use types::transaction::{SignedTransaction, TypedTransaction}; @@ -60,7 +61,7 @@ impl TracesClient { impl Traces for TracesClient where S: StateInfo + 'static, - C: BlockChainClient + StateClient + Call + 'static, + C: BlockChainClient + StateClient + Call + EngineInfo + 'static, { type Metadata = Metadata; @@ -135,7 +136,9 @@ where &signed, to_call_analytics(flags), &mut state, - &header.decode().map_err(errors::decode)?, + &header + .decode(self.client.engine().params().eip1559_transition) + .map_err(errors::decode)?, ) .map(TraceResults::from) .map_err(errors::call) @@ -181,7 +184,9 @@ where .call_many( &requests, &mut state, - &header.decode().map_err(errors::decode)?, + &header + .decode(self.client.engine().params().eip1559_transition) + .map_err(errors::decode)?, ) .map(|results| results.into_iter().map(TraceResults::from).collect()) .map_err(errors::call) @@ -224,7 +229,9 @@ where &signed, to_call_analytics(flags), &mut state, - &header.decode().map_err(errors::decode)?, + &header + .decode(self.client.engine().params().eip1559_transition) + .map_err(errors::decode)?, ) .map(TraceResults::from) .map_err(errors::call) diff --git a/crates/rpc/src/v1/tests/eth.rs b/crates/rpc/src/v1/tests/eth.rs index 620c22c0a..ec5ee644b 100644 --- a/crates/rpc/src/v1/tests/eth.rs +++ b/crates/rpc/src/v1/tests/eth.rs @@ -97,7 +97,9 @@ impl EthTester { }; for b in chain.blocks_rlp() { - if let Ok(block) = Unverified::from_rlp(b) { + if let Ok(block) = + Unverified::from_rlp(b, tester.client.engine().params().eip1559_transition) + { let _ = tester.client.import_block(block); tester.client.flush_queue(); tester.client.import_verified_blocks(); @@ -545,11 +547,9 @@ fn verify_transaction_counts(name: String, chain: BlockChain) { let tester = EthTester::from_chain(&chain); let mut id = 1; - for b in chain - .blocks_rlp() - .into_iter() - .filter_map(|b| Unverified::from_rlp(b).ok()) - { + for b in chain.blocks_rlp().into_iter().filter_map(|b| { + Unverified::from_rlp(b, tester.client.engine().params().eip1559_transition).ok() + }) { let count = b.transactions.len(); let hash = b.header.hash(); diff --git a/crates/rpc/src/v1/tests/helpers/miner_service.rs b/crates/rpc/src/v1/tests/helpers/miner_service.rs index 3af4a04aa..9930837ab 100644 --- a/crates/rpc/src/v1/tests/helpers/miner_service.rs +++ b/crates/rpc/src/v1/tests/helpers/miner_service.rs @@ -38,7 +38,6 @@ use miner::pool::{ VerifiedTransaction, }; use parking_lot::{Mutex, RwLock}; -use txpool; use types::{ block::Block, header::Header, @@ -335,6 +334,7 @@ impl MinerService for TestMinerService { block_gas_limit: 5_000_000.into(), tx_gas_limit: 5_000_000.into(), no_early_reject: false, + block_base_fee: None, }, status: txpool::LightStatus { mem_usage: 1_000, diff --git a/crates/rpc/src/v1/tests/mocked/signer.rs b/crates/rpc/src/v1/tests/mocked/signer.rs index 759659fff..28c01092c 100644 --- a/crates/rpc/src/v1/tests/mocked/signer.rs +++ b/crates/rpc/src/v1/tests/mocked/signer.rs @@ -95,13 +95,15 @@ fn should_return_list_of_items_to_confirm() { from: Address::from_low_u64_be(1), used_default_from: false, to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), - gas_price: U256::from(10_000), + gas_price: Some(U256::from(10_000)), + max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, }), Origin::Unknown, ) @@ -142,13 +144,15 @@ fn should_reject_transaction_from_queue_without_dispatching() { from: Address::from_low_u64_be(1), used_default_from: false, to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), - gas_price: U256::from(10_000), + gas_price: Some(U256::from(10_000)), + max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, }), Origin::Unknown, ) @@ -180,13 +184,15 @@ fn should_not_remove_transaction_if_password_is_invalid() { from: Address::from_low_u64_be(1), used_default_from: false, to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), - gas_price: U256::from(10_000), + gas_price: Some(U256::from(10_000)), + max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, }), Origin::Unknown, ) @@ -246,13 +252,15 @@ fn should_confirm_transaction_and_dispatch() { from: address, used_default_from: false, to: Some(recipient), - gas_price: U256::from(10_000), + gas_price: Some(U256::from(10_000)), + max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, }), Origin::Unknown, ) @@ -311,13 +319,15 @@ fn should_alter_the_sender_and_nonce() { from: Address::from_low_u64_be(0), used_default_from: false, to: Some(recipient), - gas_price: U256::from(10_000), + gas_price: Some(U256::from(10_000)), + max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: Some(10.into()), condition: None, access_list: None, + max_priority_fee_per_gas: None, }), Origin::Unknown, ) @@ -377,13 +387,15 @@ fn should_confirm_transaction_with_token() { from: address, used_default_from: false, to: Some(recipient), - gas_price: U256::from(10_000), + gas_price: Some(U256::from(10_000)), + max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, }), Origin::Unknown, ) @@ -445,13 +457,15 @@ fn should_confirm_transaction_with_rlp() { from: address, used_default_from: false, to: Some(recipient), - gas_price: U256::from(10_000), + gas_price: Some(U256::from(10_000)), + max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, }), Origin::Unknown, ) @@ -511,13 +525,15 @@ fn should_return_error_when_sender_does_not_match() { from: Address::default(), used_default_from: false, to: Some(recipient), - gas_price: U256::from(10_000), + gas_price: Some(U256::from(10_000)), + max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, }), Origin::Unknown, ) @@ -578,13 +594,15 @@ fn should_confirm_sign_transaction_with_rlp() { from: address, used_default_from: false, to: Some(recipient), - gas_price: U256::from(10_000), + gas_price: Some(U256::from(10_000)), + max_fee_per_gas: None, gas: U256::from(10_000_000), value: U256::from(1), data: vec![], nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, }), Origin::Unknown, ) diff --git a/crates/rpc/src/v1/types/block.rs b/crates/rpc/src/v1/types/block.rs index 0a620dc25..5eb989e59 100644 --- a/crates/rpc/src/v1/types/block.rs +++ b/crates/rpc/src/v1/types/block.rs @@ -18,7 +18,7 @@ use std::{collections::BTreeMap, ops::Deref}; use ethereum_types::{Bloom as H2048, H160, H256, U256}; use serde::{ser::Error, Serialize, Serializer}; -use types::encoded::Header as EthHeader; +use types::{encoded::Header as EthHeader, BlockNumber}; use v1::types::{Bytes, Transaction}; /// Block Transactions @@ -81,6 +81,9 @@ pub struct Block { pub total_difficulty: Option, /// Seal fields pub seal_fields: Vec, + /// Base fee + #[serde(skip_serializing_if = "Option::is_none")] + pub base_fee_per_gas: Option, /// Uncles' hashes pub uncles: Vec, /// Transactions @@ -126,20 +129,18 @@ pub struct Header { pub difficulty: U256, /// Seal fields pub seal_fields: Vec, + /// Base fee + #[serde(skip_serializing_if = "Option::is_none")] + pub base_fee_per_gas: Option, /// Size in bytes pub size: Option, } -impl From for Header { - fn from(h: EthHeader) -> Self { - (&h).into() - } -} - -impl<'a> From<&'a EthHeader> for Header { - fn from(h: &'a EthHeader) -> Self { +impl Header { + pub fn new(h: &EthHeader, eip1559_transition: BlockNumber) -> Self { + let eip1559_enabled = h.number() >= eip1559_transition; Header { - hash: Some(h.hash()), + hash: Some(h.hash()), size: Some(h.rlp().as_raw().len().into()), parent_hash: h.parent_hash(), uncles_hash: h.uncles_hash(), @@ -155,9 +156,16 @@ impl<'a> From<&'a EthHeader> for Header { timestamp: h.timestamp().into(), difficulty: h.difficulty(), extra_data: h.extra_data().into(), - seal_fields: h.view().decode_seal() + seal_fields: h.view().decode_seal(eip1559_enabled) .expect("Client/Miner returns only valid headers. We only serialize headers from Client/Miner; qed") .into_iter().map(Into::into).collect(), + base_fee_per_gas: { + if eip1559_enabled { + Some(h.base_fee()) + } else { + None + } + }, } } } @@ -221,7 +229,7 @@ mod tests { let serialized = serde_json::to_string(&t).unwrap(); assert_eq!( serialized, - r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"chainId":null,"v":"0x0","r":"0x0","s":"0x0","condition":null}]"# + r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"chainId":null,"v":"0x0","r":"0x0","s":"0x0","condition":null}]"# ); let t = BlockTransactions::Hashes(vec![H256::default().into()]); @@ -252,6 +260,7 @@ mod tests { difficulty: U256::default(), total_difficulty: Some(U256::default()), seal_fields: vec![Bytes::default(), Bytes::default()], + base_fee_per_gas: None, uncles: vec![], transactions: BlockTransactions::Hashes(vec![].into()), size: Some(69.into()), @@ -296,6 +305,7 @@ mod tests { difficulty: U256::default(), total_difficulty: Some(U256::default()), seal_fields: vec![Bytes::default(), Bytes::default()], + base_fee_per_gas: None, uncles: vec![], transactions: BlockTransactions::Hashes(vec![].into()), size: None, @@ -339,6 +349,7 @@ mod tests { timestamp: U256::default(), difficulty: U256::default(), seal_fields: vec![Bytes::default(), Bytes::default()], + base_fee_per_gas: None, size: Some(69.into()), }; let serialized_header = serde_json::to_string(&header).unwrap(); diff --git a/crates/rpc/src/v1/types/call_request.rs b/crates/rpc/src/v1/types/call_request.rs index 37a79c9b2..296210642 100644 --- a/crates/rpc/src/v1/types/call_request.rs +++ b/crates/rpc/src/v1/types/call_request.rs @@ -33,7 +33,11 @@ pub struct CallRequest { /// To pub to: Option, /// Gas Price + #[serde(skip_serializing_if = "Option::is_none")] pub gas_price: Option, + /// Max fee per gas + #[serde(skip_serializing_if = "Option::is_none")] + pub max_fee_per_gas: Option, /// Gas pub gas: Option, /// Value @@ -45,6 +49,9 @@ pub struct CallRequest { /// Access list #[serde(skip_serializing_if = "Option::is_none")] pub access_list: Option, + /// Miner bribe + #[serde(skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, } impl Into for CallRequest { @@ -54,11 +61,13 @@ impl Into for CallRequest { from: self.from.map(Into::into), to: self.to.map(Into::into), gas_price: self.gas_price.map(Into::into), + max_fee_per_gas: self.max_fee_per_gas, gas: self.gas.map(Into::into), value: self.value.map(Into::into), data: self.data.map(Into::into), nonce: self.nonce.map(Into::into), access_list: self.access_list.map(Into::into), + max_priority_fee_per_gas: self.max_priority_fee_per_gas.map(Into::into), } } } @@ -91,11 +100,13 @@ mod tests { from: Some(H160::from_low_u64_be(1)), to: Some(H160::from_low_u64_be(2)), gas_price: Some(U256::from(1)), + max_fee_per_gas: None, gas: Some(U256::from(2)), value: Some(U256::from(3)), data: Some(vec![0x12, 0x34, 0x56].into()), nonce: Some(U256::from(4)), access_list: None, + max_priority_fee_per_gas: None, } ); } @@ -117,11 +128,13 @@ mod tests { from: Some(H160::from_str("b60e8dd61c5d32be8058bb8eb970870f07233155").unwrap()), to: Some(H160::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: Some(U256::from_str("9184e72a000").unwrap()), + max_fee_per_gas: None, gas: Some(U256::from_str("76c0").unwrap()), value: Some(U256::from_str("9184e72a").unwrap()), data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()), - nonce: None, - access_list: None, + nonce: None, + access_list: None, + max_priority_fee_per_gas: None, }); } @@ -137,11 +150,13 @@ mod tests { from: Some(H160::from_low_u64_be(1)), to: None, gas_price: None, + max_fee_per_gas: None, gas: None, value: None, data: None, nonce: None, access_list: None, + max_priority_fee_per_gas: None, } ); } diff --git a/crates/rpc/src/v1/types/confirmations.rs b/crates/rpc/src/v1/types/confirmations.rs index 152821335..5471d8cc5 100644 --- a/crates/rpc/src/v1/types/confirmations.rs +++ b/crates/rpc/src/v1/types/confirmations.rs @@ -339,12 +339,14 @@ mod tests { used_default_from: false, to: None, gas: 15_000.into(), - gas_price: 10_000.into(), + gas_price: Some(10_000.into()), + max_fee_per_gas: None, value: 100_000.into(), data: vec![1, 2, 3], nonce: Some(1.into()), condition: None, access_list: None, + max_priority_fee_per_gas: None, }, ), origin: Origin::Signer { @@ -372,12 +374,14 @@ mod tests { used_default_from: false, to: None, gas: 15_000.into(), - gas_price: 10_000.into(), + gas_price: Some(10_000.into()), + max_fee_per_gas: None, value: 100_000.into(), data: vec![1, 2, 3], nonce: Some(1.into()), condition: None, access_list: None, + max_priority_fee_per_gas: None, }, ), origin: Origin::Unknown, diff --git a/crates/rpc/src/v1/types/pubsub.rs b/crates/rpc/src/v1/types/pubsub.rs index 219d44a3e..17fa3b878 100644 --- a/crates/rpc/src/v1/types/pubsub.rs +++ b/crates/rpc/src/v1/types/pubsub.rs @@ -189,6 +189,7 @@ mod tests { timestamp: Default::default(), difficulty: Default::default(), seal_fields: vec![Default::default(), Default::default()], + base_fee_per_gas: None, size: Some(69.into()), }, })); diff --git a/crates/rpc/src/v1/types/receipt.rs b/crates/rpc/src/v1/types/receipt.rs index 7a21d0800..100a10482 100644 --- a/crates/rpc/src/v1/types/receipt.rs +++ b/crates/rpc/src/v1/types/receipt.rs @@ -118,7 +118,7 @@ impl From for Receipt { impl From for Receipt { fn from(r: TypedReceipt) -> Self { let transaction_type = r.tx_type().to_U64_option_id(); - let r = r.receipt().clone(); + let legacy_receipt = r.receipt().clone(); Receipt { from: None, to: None, @@ -127,13 +127,13 @@ impl From for Receipt { transaction_index: None, block_hash: None, block_number: None, - cumulative_gas_used: r.gas_used, + cumulative_gas_used: legacy_receipt.gas_used, gas_used: None, contract_address: None, - logs: r.logs.into_iter().map(Into::into).collect(), - status_code: Self::outcome_to_status_code(&r.outcome), - state_root: Self::outcome_to_state_root(r.outcome), - logs_bloom: r.log_bloom, + logs: legacy_receipt.logs.into_iter().map(Into::into).collect(), + status_code: Self::outcome_to_status_code(&legacy_receipt.outcome), + state_root: Self::outcome_to_state_root(legacy_receipt.outcome), + logs_bloom: legacy_receipt.log_bloom, } } } diff --git a/crates/rpc/src/v1/types/transaction.rs b/crates/rpc/src/v1/types/transaction.rs index f84a8951e..c731ffe92 100644 --- a/crates/rpc/src/v1/types/transaction.rs +++ b/crates/rpc/src/v1/types/transaction.rs @@ -50,7 +50,11 @@ pub struct Transaction { /// Transfered value pub value: U256, /// Gas Price - pub gas_price: U256, + #[serde(skip_serializing_if = "Option::is_none")] + pub gas_price: Option, + /// Max fee per gas + #[serde(skip_serializing_if = "Option::is_none")] + pub max_fee_per_gas: Option, /// Gas pub gas: U256, /// Data @@ -77,6 +81,9 @@ pub struct Transaction { /// optional access list #[serde(skip_serializing_if = "Option::is_none")] pub access_list: Option, + /// miner bribe + #[serde(skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, } /// Local Transaction Status @@ -187,12 +194,34 @@ impl Transaction { let signature = t.signature(); let scheme = CreateContractAddress::FromSenderAndNonce; - let access_list = if let TypedTransaction::AccessList(al) = t.as_unsigned() { - Some(al.access_list.clone().into_iter().map(Into::into).collect()) - } else { - None + let access_list = match t.as_unsigned() { + TypedTransaction::AccessList(tx) => { + Some(tx.access_list.clone().into_iter().map(Into::into).collect()) + } + TypedTransaction::EIP1559Transaction(tx) => Some( + tx.transaction + .access_list + .clone() + .into_iter() + .map(Into::into) + .collect(), + ), + TypedTransaction::Legacy(_) => None, }; + let (gas_price, max_fee_per_gas) = match t.as_unsigned() { + TypedTransaction::Legacy(_) => (Some(t.tx().gas_price), None), + TypedTransaction::AccessList(_) => (Some(t.tx().gas_price), None), + TypedTransaction::EIP1559Transaction(_) => (None, Some(t.tx().gas_price)), + }; + + let max_priority_fee_per_gas = + if let TypedTransaction::EIP1559Transaction(tx) = t.as_unsigned() { + Some(tx.max_priority_fee_per_gas) + } else { + None + }; + let standard_v = if t.tx_type() == TypedTxId::Legacy { Some(t.standard_v()) } else { @@ -211,7 +240,8 @@ impl Transaction { Action::Call(ref address) => Some(*address), }, value: t.tx().value, - gas_price: t.tx().gas_price, + gas_price, + max_fee_per_gas, gas: t.tx().gas, input: Bytes::new(t.tx().data.clone()), creates: match t.tx().action { @@ -230,6 +260,7 @@ impl Transaction { condition: None, transaction_type: t.signed.tx_type().to_U64_option_id(), access_list, + max_priority_fee_per_gas, } } @@ -237,11 +268,35 @@ impl Transaction { pub fn from_signed(t: SignedTransaction) -> Transaction { let signature = t.signature(); let scheme = CreateContractAddress::FromSenderAndNonce; - let access_list = if let TypedTransaction::AccessList(al) = t.as_unsigned() { - Some(al.access_list.clone().into_iter().map(Into::into).collect()) - } else { - None + + let access_list = match t.as_unsigned() { + TypedTransaction::AccessList(tx) => { + Some(tx.access_list.clone().into_iter().map(Into::into).collect()) + } + TypedTransaction::EIP1559Transaction(tx) => Some( + tx.transaction + .access_list + .clone() + .into_iter() + .map(Into::into) + .collect(), + ), + TypedTransaction::Legacy(_) => None, }; + + let (gas_price, max_fee_per_gas) = match t.as_unsigned() { + TypedTransaction::Legacy(_) => (Some(t.tx().gas_price), None), + TypedTransaction::AccessList(_) => (Some(t.tx().gas_price), None), + TypedTransaction::EIP1559Transaction(_) => (None, Some(t.tx().gas_price)), + }; + + let max_priority_fee_per_gas = + if let TypedTransaction::EIP1559Transaction(tx) = t.as_unsigned() { + Some(tx.max_priority_fee_per_gas) + } else { + None + }; + let standard_v = if t.tx_type() == TypedTxId::Legacy { Some(t.standard_v()) } else { @@ -260,7 +315,8 @@ impl Transaction { Action::Call(ref address) => Some(*address), }, value: t.tx().value, - gas_price: t.tx().gas_price, + gas_price, + max_fee_per_gas, gas: t.tx().gas, input: Bytes::new(t.tx().data.clone()), creates: match t.tx().action { @@ -279,6 +335,7 @@ impl Transaction { condition: None, transaction_type: t.tx_type().to_U64_option_id(), access_list, + max_priority_fee_per_gas, } } @@ -330,7 +387,7 @@ mod tests { let serialized = serde_json::to_string(&t).unwrap(); assert_eq!( serialized, - r#"{"type":"0x1","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"chainId":null,"v":"0x0","r":"0x0","s":"0x0","condition":null,"accessList":[{"address":"0x0000000000000000000000000000000000000000","storageKeys":[]}]}"# + r#"{"type":"0x1","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x0","gas":"0x0","input":"0x","creates":null,"raw":"0x","publicKey":null,"chainId":null,"v":"0x0","r":"0x0","s":"0x0","condition":null,"accessList":[{"address":"0x0000000000000000000000000000000000000000","storageKeys":[]}]}"# ); } diff --git a/crates/rpc/src/v1/types/transaction_request.rs b/crates/rpc/src/v1/types/transaction_request.rs index 77d0282b2..1f2493405 100644 --- a/crates/rpc/src/v1/types/transaction_request.rs +++ b/crates/rpc/src/v1/types/transaction_request.rs @@ -38,7 +38,11 @@ pub struct TransactionRequest { /// Recipient pub to: Option, /// Gas Price + #[serde(skip_serializing_if = "Option::is_none")] pub gas_price: Option, + /// Max fee per gas + #[serde(skip_serializing_if = "Option::is_none")] + pub max_fee_per_gas: Option, /// Gas pub gas: Option, /// Value of transaction in wei @@ -52,6 +56,9 @@ pub struct TransactionRequest { /// Access list #[serde(skip_serializing_if = "Option::is_none")] pub access_list: Option, + /// Miner bribe + #[serde(skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, } pub fn format_ether(i: U256) -> String { @@ -107,12 +114,14 @@ impl From for TransactionRequest { from: r.from.map(Into::into), to: r.to.map(Into::into), gas_price: r.gas_price.map(Into::into), + max_fee_per_gas: r.max_fee_per_gas, gas: r.gas.map(Into::into), value: r.value.map(Into::into), data: r.data.map(Into::into), nonce: r.nonce.map(Into::into), condition: r.condition.map(Into::into), access_list: r.access_list.map(Into::into), + max_priority_fee_per_gas: r.max_priority_fee_per_gas.map(Into::into), } } } @@ -123,13 +132,15 @@ impl From for TransactionRequest { transaction_type: r.transaction_type, from: Some(r.from), to: r.to, - gas_price: Some(r.gas_price), + gas_price: r.gas_price, + max_fee_per_gas: r.max_fee_per_gas, gas: Some(r.gas), value: Some(r.value), data: Some(r.data.into()), nonce: r.nonce, condition: r.condition, access_list: r.access_list.map(Into::into), + max_priority_fee_per_gas: r.max_priority_fee_per_gas, } } } @@ -141,12 +152,14 @@ impl Into for TransactionRequest { from: self.from.map(Into::into), to: self.to.map(Into::into), gas_price: self.gas_price.map(Into::into), + max_fee_per_gas: self.max_fee_per_gas, gas: self.gas.map(Into::into), value: self.value.map(Into::into), data: self.data.map(Into::into), nonce: self.nonce.map(Into::into), condition: self.condition.map(Into::into), access_list: self.access_list.map(Into::into), + max_priority_fee_per_gas: self.max_priority_fee_per_gas.map(Into::into), } } } @@ -181,12 +194,49 @@ mod tests { from: Some(H160::from_low_u64_be(1)), to: Some(H160::from_low_u64_be(2)), gas_price: Some(U256::from(1)), + max_fee_per_gas: None, gas: Some(U256::from(2)), value: Some(U256::from(3)), data: Some(vec![0x12, 0x34, 0x56].into()), nonce: Some(U256::from(4)), condition: Some(TransactionCondition::Number(0x13)), access_list: None, + max_priority_fee_per_gas: None, + } + ); + } + + #[test] + fn transaction_request_deserialize_1559() { + let s = r#"{ + "type":"0x02", + "from":"0x0000000000000000000000000000000000000001", + "to":"0x0000000000000000000000000000000000000002", + "maxFeePerGas":"0x01", + "maxPriorityFeePerGas":"0x01", + "gas":"0x2", + "value":"0x3", + "data":"0x123456", + "nonce":"0x4", + "condition": { "block": 19 } + }"#; + let deserialized: TransactionRequest = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + TransactionRequest { + transaction_type: Some(U64::from(2)), + from: Some(H160::from_low_u64_be(1)), + to: Some(H160::from_low_u64_be(2)), + gas_price: None, + max_fee_per_gas: Some(U256::from(1)), + gas: Some(U256::from(2)), + value: Some(U256::from(3)), + data: Some(vec![0x12, 0x34, 0x56].into()), + nonce: Some(U256::from(4)), + condition: Some(TransactionCondition::Number(0x13)), + access_list: None, + max_priority_fee_per_gas: Some(U256::from(1)), } ); } @@ -208,12 +258,14 @@ mod tests { from: Some(H160::from_str("b60e8dd61c5d32be8058bb8eb970870f07233155").unwrap()), to: Some(H160::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: Some(U256::from_str("9184e72a000").unwrap()), + max_fee_per_gas: None, gas: Some(U256::from_str("76c0").unwrap()), value: Some(U256::from_str("9184e72a").unwrap()), data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()), nonce: None, - condition: None, - access_list: None, + condition: None, + access_list: None, + max_priority_fee_per_gas: None, }); } @@ -229,12 +281,14 @@ mod tests { from: Some(H160::from_low_u64_be(1).into()), to: None, gas_price: None, + max_fee_per_gas: None, gas: None, value: None, data: None, nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, } ); } @@ -258,12 +312,14 @@ mod tests { from: Some(H160::from_str("b5f7502a2807cb23615c7456055e1d65b2508625").unwrap()), to: Some(H160::from_str("895d32f2db7d01ebb50053f9e48aacf26584fe40").unwrap()), gas_price: Some(U256::from_str("0ba43b7400").unwrap()), + max_fee_per_gas: None, gas: Some(U256::from_str("2fd618").unwrap()), value: None, data: Some(vec![0x85, 0x95, 0xba, 0xb1].into()), nonce: None, condition: None, access_list: None, + max_priority_fee_per_gas: None, } ); } diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml new file mode 100644 index 000000000..db2478e95 --- /dev/null +++ b/crates/transaction-pool/Cargo.toml @@ -0,0 +1,21 @@ +# Copyright 2021 Gnosis Ltd. +# SPDX-License-Identifier: Apache-2.0 + +[package] +name = "txpool" +version = "1.0.0-alpha" +authors = ["Dragan Rakita "] +edition = "2018" +description = "Generic transaction pool." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies.log] +version = "0.4" + +[dependencies.smallvec] +version = "0.6" + +[dependencies.trace-time] +version = "0.1" +[dev-dependencies.ethereum-types] +version = "0.7" \ No newline at end of file diff --git a/crates/transaction-pool/src/error.rs b/crates/transaction-pool/src/error.rs new file mode 100644 index 000000000..1678c0c56 --- /dev/null +++ b/crates/transaction-pool/src/error.rs @@ -0,0 +1,70 @@ +// Copyright 2015-2018 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 . + +use std::{error, fmt, result}; + +/// Transaction Pool Error +#[derive(Debug)] +pub enum Error { + /// Transaction is already imported + AlreadyImported(Hash), + /// Transaction is too cheap to enter the queue + TooCheapToEnter(Hash, String), + /// Transaction is too cheap to replace existing transaction that occupies the same slot. + TooCheapToReplace(Hash, Hash), +} + +/// Transaction Pool Result +pub type Result = result::Result>; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::AlreadyImported(h) => write!(f, "[{:?}] already imported", h), + Error::TooCheapToEnter(hash, min_score) => write!( + f, + "[{:x}] too cheap to enter the pool. Min score: {}", + hash, min_score + ), + Error::TooCheapToReplace(old_hash, hash) => { + write!(f, "[{:x}] too cheap to replace: {:x}", hash, old_hash) + } + } + } +} + +impl error::Error for Error {} + +#[cfg(test)] +impl PartialEq for Error +where + H: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + use self::Error::*; + + match (self, other) { + (&AlreadyImported(ref h1), &AlreadyImported(ref h2)) => h1 == h2, + (&TooCheapToEnter(ref h1, ref s1), &TooCheapToEnter(ref h2, ref s2)) => { + h1 == h2 && s1 == s2 + } + (&TooCheapToReplace(ref old1, ref new1), &TooCheapToReplace(ref old2, ref new2)) => { + old1 == old2 && new1 == new2 + } + _ => false, + } + } +} diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs new file mode 100644 index 000000000..a15be0509 --- /dev/null +++ b/crates/transaction-pool/src/lib.rs @@ -0,0 +1,116 @@ +// Copyright 2015-2018 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 . + +//! Generic Transaction Pool +//! +//! An extensible and performant implementation of Ethereum Transaction Pool. +//! The pool stores ordered, verified transactions according to some pluggable +//! `Scoring` implementation. +//! The pool also allows you to construct a set of `pending` transactions according +//! to some notion of `Readiness` (pluggable). +//! +//! The pool is generic over transactions and should make no assumptions about them. +//! The only thing we can rely on is the `Scoring` that defines: +//! - the ordering of transactions from a single sender +//! - the priority of the transaction compared to other transactions from different senders +//! +//! NOTE: the transactions from a single sender are not ordered by priority, +//! but still when constructing pending set we always need to maintain the ordering +//! (i.e. `txs[1]` always needs to be included after `txs[0]` even if it has higher priority) +//! +//! ### Design Details +//! +//! Performance assumptions: +//! - Possibility to handle tens of thousands of transactions +//! - Fast insertions and replacements `O(per-sender + log(senders))` +//! - Reasonably fast removal of stalled transactions `O(per-sender)` +//! - Reasonably fast construction of pending set `O(txs * (log(senders) + log(per-sender))` +//! +//! The removal performance could be improved by trading some memory. Currently `SmallVec` is used +//! to store senders transactions, instead we could use `VecDeque` and efficiently `pop_front` +//! the best transactions. +//! +//! The pending set construction and insertion complexity could be reduced by introducing +//! a notion of `nonce` - an absolute, numeric ordering of transactions. +//! We don't do that because of possible implications of EIP208 where nonce might not be +//! explicitly available. +//! +//! 1. The pool groups transactions from particular sender together +//! and stores them ordered by `Scoring` within that group +//! i.e. `HashMap>`. +//! 2. Additionaly we maintain the best and the worst transaction from each sender +//! (by `Scoring` not `priority`) ordered by `priority`. +//! It means that we can easily identify the best transaction inside the entire pool +//! and the worst transaction. +//! 3. Whenever new transaction is inserted to the queue: +//! - first check all the limits (overall, memory, per-sender) +//! - retrieve all transactions from a sender +//! - binary search for position to insert the transaction +//! - decide if we are replacing existing transaction (3 outcomes: drop, replace, insert) +//! - update best and worst transaction from that sender if affected +//! 4. Pending List construction: +//! - Take the best transaction (by priority) from all senders to the List +//! - Replace the transaction with next transaction (by ordering) from that sender (if any) +//! - Repeat + +#![warn(missing_docs)] + +#[cfg(test)] +mod tests; + +mod error; +mod listener; +mod options; +mod pool; +mod ready; +mod replace; +mod status; +mod transactions; +mod verifier; + +pub mod scoring; + +pub use self::{ + error::Error, + listener::{Listener, NoopListener}, + options::Options, + pool::{PendingIterator, Pool, Transaction, UnorderedIterator}, + ready::{Readiness, Ready}, + replace::{ReplaceTransaction, ShouldReplace}, + scoring::Scoring, + status::{LightStatus, Status}, + verifier::Verifier, +}; + +use std::{fmt, hash::Hash}; + +/// Already verified transaction that can be safely queued. +pub trait VerifiedTransaction: fmt::Debug { + /// Transaction hash type. + type Hash: fmt::Debug + fmt::LowerHex + Eq + Clone + Hash; + + /// Transaction sender type. + type Sender: fmt::Debug + Eq + Clone + Hash + Send; + + /// Transaction hash + fn hash(&self) -> &Self::Hash; + + /// Memory usage + fn mem_usage(&self) -> usize; + + /// Transaction sender + fn sender(&self) -> &Self::Sender; +} diff --git a/crates/transaction-pool/src/listener.rs b/crates/transaction-pool/src/listener.rs new file mode 100644 index 000000000..7b090a3cc --- /dev/null +++ b/crates/transaction-pool/src/listener.rs @@ -0,0 +1,89 @@ +// Copyright 2015-2018 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 . + +use crate::error::Error; +use std::{ + fmt::{Debug, LowerHex}, + sync::Arc, +}; + +/// Transaction pool listener. +/// +/// Listener is being notified about status of every transaction in the pool. +pub trait Listener { + /// The transaction has been successfuly added to the pool. + /// If second argument is `Some` the transaction has took place of some other transaction + /// which was already in pool. + /// NOTE: You won't be notified about drop of `old` transaction separately. + fn added(&mut self, _tx: &Arc, _old: Option<&Arc>) {} + + /// The transaction was rejected from the pool. + /// It means that it was too cheap to replace any transaction already in the pool. + fn rejected(&mut self, _tx: &Arc, _reason: &Error) {} + + /// The transaction was pushed out from the pool because of the limit. + fn dropped(&mut self, _tx: &Arc, _by: Option<&T>) {} + + /// The transaction was marked as invalid by executor. + fn invalid(&mut self, _tx: &Arc) {} + + /// The transaction has been canceled. + fn canceled(&mut self, _tx: &Arc) {} + + /// The transaction has been culled from the pool. + fn culled(&mut self, _tx: &Arc) {} +} + +/// A no-op implementation of `Listener`. +#[derive(Debug)] +pub struct NoopListener; +impl Listener for NoopListener {} + +impl Listener for (A, B) +where + A: Listener, + B: Listener, +{ + fn added(&mut self, tx: &Arc, old: Option<&Arc>) { + self.0.added(tx, old); + self.1.added(tx, old); + } + + fn rejected(&mut self, tx: &Arc, reason: &Error) { + self.0.rejected(tx, reason); + self.1.rejected(tx, reason); + } + + fn dropped(&mut self, tx: &Arc, by: Option<&T>) { + self.0.dropped(tx, by); + self.1.dropped(tx, by); + } + + fn invalid(&mut self, tx: &Arc) { + self.0.invalid(tx); + self.1.invalid(tx); + } + + fn canceled(&mut self, tx: &Arc) { + self.0.canceled(tx); + self.1.canceled(tx); + } + + fn culled(&mut self, tx: &Arc) { + self.0.culled(tx); + self.1.culled(tx); + } +} diff --git a/crates/transaction-pool/src/options.rs b/crates/transaction-pool/src/options.rs new file mode 100644 index 000000000..6ea46ba5e --- /dev/null +++ b/crates/transaction-pool/src/options.rs @@ -0,0 +1,36 @@ +// Copyright 2015-2018 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 . + +/// Transaction Pool options. +#[derive(Clone, Debug, PartialEq)] +pub struct Options { + /// Maximal number of transactions in the pool. + pub max_count: usize, + /// Maximal number of transactions from single sender. + pub max_per_sender: usize, + /// Maximal memory usage. + pub max_mem_usage: usize, +} + +impl Default for Options { + fn default() -> Self { + Options { + max_count: 1024, + max_per_sender: 16, + max_mem_usage: 8 * 1024 * 1024, + } + } +} diff --git a/crates/transaction-pool/src/pool.rs b/crates/transaction-pool/src/pool.rs new file mode 100644 index 000000000..8ba64ea7b --- /dev/null +++ b/crates/transaction-pool/src/pool.rs @@ -0,0 +1,756 @@ +// Copyright 2015-2018 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 . + +use log::{trace, warn}; +use std::{ + collections::{hash_map, BTreeSet, HashMap}, + slice, + sync::Arc, +}; + +use crate::{ + error, + listener::{Listener, NoopListener}, + options::Options, + ready::{Readiness, Ready}, + replace::{ReplaceTransaction, ShouldReplace}, + scoring::{self, ScoreWithRef, Scoring}, + status::{LightStatus, Status}, + transactions::{AddResult, Transactions}, + VerifiedTransaction, +}; + +/// Internal representation of transaction. +/// +/// Includes unique insertion id that can be used for scoring explictly, +/// but internally is used to resolve conflicts in case of equal scoring +/// (newer transactionsa are preferred). +#[derive(Debug)] +pub struct Transaction { + /// Sequential id of the transaction + pub insertion_id: u64, + /// Shared transaction + pub transaction: Arc, +} + +impl Clone for Transaction { + fn clone(&self) -> Self { + Transaction { + insertion_id: self.insertion_id, + transaction: self.transaction.clone(), + } + } +} + +impl ::std::ops::Deref for Transaction { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.transaction + } +} +/// A transaction pool. +#[derive(Debug)] +pub struct Pool, L = NoopListener> { + listener: L, + scoring: S, + options: Options, + mem_usage: usize, + + transactions: HashMap>, + by_hash: HashMap>, + + best_transactions: BTreeSet>, + worst_transactions: BTreeSet>, + + insertion_id: u64, +} + +impl + Default> Default for Pool { + fn default() -> Self { + Self::with_scoring(S::default(), Options::default()) + } +} + +impl + Default> Pool { + /// Creates a new `Pool` with given options + /// and default `Scoring` and `Listener`. + pub fn with_options(options: Options) -> Self { + Self::with_scoring(S::default(), options) + } +} + +impl> Pool { + /// Creates a new `Pool` with given `Scoring` and options. + pub fn with_scoring(scoring: S, options: Options) -> Self { + Self::new(NoopListener, scoring, options) + } +} + +const INITIAL_NUMBER_OF_SENDERS: usize = 16; + +impl Pool +where + T: VerifiedTransaction, + S: Scoring, + L: Listener, +{ + /// Creates new `Pool` with given `Scoring`, `Listener` and options. + pub fn new(listener: L, scoring: S, options: Options) -> Self { + let transactions = HashMap::with_capacity(INITIAL_NUMBER_OF_SENDERS); + let by_hash = HashMap::with_capacity(options.max_count / 16); + + Pool { + listener, + scoring, + options, + mem_usage: 0, + transactions, + by_hash, + best_transactions: Default::default(), + worst_transactions: Default::default(), + insertion_id: 0, + } + } + + /// Attempts to import new transaction to the pool, returns a `Arc` or an `Error`. + /// + /// NOTE: Since `Ready`ness is separate from the pool it's possible to import stalled transactions. + /// It's the caller responsibility to make sure that's not the case. + /// + /// NOTE: The transaction may push out some other transactions from the pool + /// either because of limits (see `Options`) or because `Scoring` decides that the transaction + /// replaces an existing transaction from that sender. + /// + /// If any limit is reached the transaction with the lowest `Score` will be compared with the + /// new transaction via the supplied `ShouldReplace` implementation and may be evicted. + /// + /// The `Listener` will be informed on any drops or rejections. + pub fn import( + &mut self, + transaction: T, + replace: &dyn ShouldReplace, + ) -> error::Result, T::Hash> { + let mem_usage = transaction.mem_usage(); + + if self.by_hash.contains_key(transaction.hash()) { + return Err(error::Error::AlreadyImported(transaction.hash().clone())); + } + + self.insertion_id += 1; + let transaction = Transaction { + insertion_id: self.insertion_id, + transaction: Arc::new(transaction), + }; + + // TODO [ToDr] Most likely move this after the transaction is inserted. + // Avoid using should_replace, but rather use scoring for that. + { + let remove_worst = + |s: &mut Self, transaction| match s.remove_worst(transaction, replace) { + Err(err) => { + s.listener.rejected(transaction, &err); + Err(err) + } + Ok(None) => Ok(false), + Ok(Some(removed)) => { + s.listener.dropped(&removed, Some(transaction)); + s.finalize_remove(removed.hash()); + Ok(true) + } + }; + + while self.by_hash.len() + 1 > self.options.max_count { + trace!( + "Count limit reached: {} > {}", + self.by_hash.len() + 1, + self.options.max_count + ); + if !remove_worst(self, &transaction)? { + break; + } + } + + while self.mem_usage + mem_usage > self.options.max_mem_usage { + trace!( + "Mem limit reached: {} > {}", + self.mem_usage + mem_usage, + self.options.max_mem_usage + ); + if !remove_worst(self, &transaction)? { + break; + } + } + } + + let (result, prev_state, current_state) = { + let transactions = self + .transactions + .entry(transaction.sender().clone()) + .or_insert_with(Transactions::default); + // get worst and best transactions for comparison + let prev = transactions.worst_and_best(); + let result = transactions.add(transaction, &self.scoring, self.options.max_per_sender); + let current = transactions.worst_and_best(); + (result, prev, current) + }; + + // update best and worst transactions from this sender (if required) + self.update_senders_worst_and_best(prev_state, current_state); + + match result { + AddResult::Ok(tx) => { + self.listener.added(&tx, None); + self.finalize_insert(&tx, None); + Ok(tx.transaction) + } + AddResult::PushedOut { new, old } | AddResult::Replaced { new, old } => { + self.listener.added(&new, Some(&old)); + self.finalize_insert(&new, Some(&old)); + Ok(new.transaction) + } + AddResult::TooCheap { new, old } => { + let error = error::Error::TooCheapToReplace(old.hash().clone(), new.hash().clone()); + self.listener.rejected(&new, &error); + return Err(error); + } + AddResult::TooCheapToEnter(new, score) => { + let error = + error::Error::TooCheapToEnter(new.hash().clone(), format!("{:#x}", score)); + self.listener.rejected(&new, &error); + return Err(error); + } + } + } + + /// Updates state of the pool statistics if the transaction was added to a set. + fn finalize_insert(&mut self, new: &Transaction, old: Option<&Transaction>) { + self.mem_usage += new.mem_usage(); + self.by_hash.insert(new.hash().clone(), new.clone()); + + if let Some(old) = old { + self.finalize_remove(old.hash()); + } + } + + /// Updates the pool statistics if transaction was removed. + fn finalize_remove(&mut self, hash: &T::Hash) -> Option> { + self.by_hash.remove(hash).map(|old| { + self.mem_usage -= old.transaction.mem_usage(); + old.transaction + }) + } + + /// Updates best and worst transactions from a sender. + fn update_senders_worst_and_best( + &mut self, + previous: Option<((S::Score, Transaction), (S::Score, Transaction))>, + current: Option<((S::Score, Transaction), (S::Score, Transaction))>, + ) { + let worst_collection = &mut self.worst_transactions; + let best_collection = &mut self.best_transactions; + + let is_same = |a: &(S::Score, Transaction), b: &(S::Score, Transaction)| { + a.0 == b.0 && a.1.hash() == b.1.hash() + }; + + let update = |collection: &mut BTreeSet<_>, (score, tx), remove| { + if remove { + collection.remove(&ScoreWithRef::new(score, tx)); + } else { + collection.insert(ScoreWithRef::new(score, tx)); + } + }; + + match (previous, current) { + (None, Some((worst, best))) => { + update(worst_collection, worst, false); + update(best_collection, best, false); + } + (Some((worst, best)), None) => { + // all transactions from that sender has been removed. + // We can clear a hashmap entry. + self.transactions.remove(worst.1.sender()); + update(worst_collection, worst, true); + update(best_collection, best, true); + } + (Some((w1, b1)), Some((w2, b2))) => { + if !is_same(&w1, &w2) { + update(worst_collection, w1, true); + update(worst_collection, w2, false); + } + if !is_same(&b1, &b2) { + update(best_collection, b1, true); + update(best_collection, b2, false); + } + } + (None, None) => {} + } + } + + /// Attempts to remove the worst transaction from the pool if it's worse than the given one. + /// + /// Returns `None` in case we couldn't decide if the transaction should replace the worst transaction or not. + /// In such case we will accept the transaction even though it is going to exceed the limit. + fn remove_worst( + &mut self, + transaction: &Transaction, + replace: &dyn ShouldReplace, + ) -> error::Result>, T::Hash> { + let to_remove = match self.worst_transactions.iter().next_back() { + // No elements to remove? and the pool is still full? + None => { + warn!("The pool is full but there are no transactions to remove."); + return Err(error::Error::TooCheapToEnter( + transaction.hash().clone(), + "unknown".into(), + )); + } + Some(old) => { + let txs = &self.transactions; + let get_replace_tx = |tx| { + let sender_txs = txs + .get(transaction.sender()) + .map(|txs| txs.iter_transactions().as_slice()); + ReplaceTransaction::new(tx, sender_txs) + }; + let old_replace = get_replace_tx(&old.transaction); + let new_replace = get_replace_tx(transaction); + + match replace.should_replace(&old_replace, &new_replace) { + // We can't decide which of them should be removed, so accept both. + scoring::Choice::InsertNew => None, + // New transaction is better than the worst one so we can replace it. + scoring::Choice::ReplaceOld => Some(old.clone()), + // otherwise fail + scoring::Choice::RejectNew => { + return Err(error::Error::TooCheapToEnter( + transaction.hash().clone(), + format!("{:#x}", old.score), + )) + } + } + } + }; + + if let Some(to_remove) = to_remove { + // Remove from transaction set + self.remove_from_set(to_remove.transaction.sender(), |set, scoring| { + set.remove(&to_remove.transaction, scoring) + }); + + Ok(Some(to_remove.transaction)) + } else { + Ok(None) + } + } + + /// Removes transaction from sender's transaction `HashMap`. + fn remove_from_set, &S) -> R>( + &mut self, + sender: &T::Sender, + f: F, + ) -> Option { + let (prev, next, result) = if let Some(set) = self.transactions.get_mut(sender) { + let prev = set.worst_and_best(); + let result = f(set, &self.scoring); + (prev, set.worst_and_best(), result) + } else { + return None; + }; + + self.update_senders_worst_and_best(prev, next); + Some(result) + } + + /// Clears pool from all transactions. + /// This causes a listener notification that all transactions were dropped. + /// NOTE: the drop-notification order will be arbitrary. + pub fn clear(&mut self) { + self.mem_usage = 0; + self.transactions.clear(); + self.best_transactions.clear(); + self.worst_transactions.clear(); + + for (_hash, tx) in self.by_hash.drain() { + self.listener.dropped(&tx.transaction, None) + } + } + + /// Removes single transaction from the pool. + /// Depending on the `is_invalid` flag the listener + /// will either get a `cancelled` or `invalid` notification. + pub fn remove(&mut self, hash: &T::Hash, is_invalid: bool) -> Option> { + if let Some(tx) = self.finalize_remove(hash) { + self.remove_from_set(tx.sender(), |set, scoring| set.remove(&tx, scoring)); + if is_invalid { + self.listener.invalid(&tx); + } else { + self.listener.canceled(&tx); + } + Some(tx) + } else { + None + } + } + + /// Removes all stalled transactions from given sender. + fn remove_stalled>(&mut self, sender: &T::Sender, ready: &mut R) -> usize { + let removed_from_set = self.remove_from_set(sender, |transactions, scoring| { + transactions.cull(ready, scoring) + }); + + match removed_from_set { + Some(removed) => { + let len = removed.len(); + for tx in removed { + self.finalize_remove(tx.hash()); + self.listener.culled(&tx); + } + len + } + None => 0, + } + } + + /// Removes all stalled transactions from given sender list (or from all senders). + pub fn cull>(&mut self, senders: Option<&[T::Sender]>, mut ready: R) -> usize { + let mut removed = 0; + match senders { + Some(senders) => { + for sender in senders { + removed += self.remove_stalled(sender, &mut ready); + } + } + None => { + let senders = self.transactions.keys().cloned().collect::>(); + for sender in senders { + removed += self.remove_stalled(&sender, &mut ready); + } + } + } + + removed + } + + /// Returns a transaction if it's part of the pool or `None` otherwise. + pub fn find(&self, hash: &T::Hash) -> Option> { + self.by_hash.get(hash).map(|t| t.transaction.clone()) + } + + /// Returns worst transaction in the queue (if any). + pub fn worst_transaction(&self) -> Option> { + self.worst_transactions + .iter() + .next_back() + .map(|x| x.transaction.transaction.clone()) + } + + /// Returns true if the pool is at it's capacity. + pub fn is_full(&self) -> bool { + self.by_hash.len() >= self.options.max_count || self.mem_usage >= self.options.max_mem_usage + } + + /// Returns senders ordered by priority of their transactions. + pub fn senders(&self) -> impl Iterator { + self.best_transactions + .iter() + .map(|tx| tx.transaction.sender()) + } + + /// Returns an iterator of pending (ready) transactions. + pub fn pending>( + &self, + ready: R, + includable_boundary: S::Score, + ) -> PendingIterator { + PendingIterator { + ready, + best_transactions: self.best_transactions.clone(), + pool: self, + includable_boundary, + } + } + + /// Returns pending (ready) transactions from given sender. + pub fn pending_from_sender>( + &self, + ready: R, + sender: &T::Sender, + includable_boundary: S::Score, + ) -> PendingIterator { + let best_transactions = self + .transactions + .get(sender) + .and_then(|transactions| transactions.worst_and_best()) + .map(|(_, best)| ScoreWithRef::new(best.0, best.1)) + .map(|s| { + let mut set = BTreeSet::new(); + set.insert(s); + set + }) + .unwrap_or_default(); + + PendingIterator { + ready, + best_transactions, + pool: self, + includable_boundary, + } + } + + /// Returns unprioritized list of ready transactions. + pub fn unordered_pending>( + &self, + ready: R, + includable_boundary: S::Score, + ) -> UnorderedIterator { + UnorderedIterator { + ready, + senders: self.transactions.iter(), + transactions: None, + scores: None, + includable_boundary, + } + } + + /// Update score of transactions of a particular sender. + pub fn update_scores(&mut self, sender: &T::Sender, event: S::Event) { + let res = if let Some(set) = self.transactions.get_mut(sender) { + let prev = set.worst_and_best(); + set.update_scores(&self.scoring, event); + let current = set.worst_and_best(); + Some((prev, current)) + } else { + None + }; + + if let Some((prev, current)) = res { + self.update_senders_worst_and_best(prev, current); + } + } + + /// Update score of transactions of all senders + fn update_all_scores(&mut self, event: S::Event) { + let senders = self.transactions.keys().cloned().collect::>(); + for sender in senders { + self.update_scores(&sender, event); + } + } + + /// Computes the full status of the pool (including readiness). + pub fn status>(&self, mut ready: R) -> Status { + let mut status = Status::default(); + + for (_sender, transactions) in &self.transactions { + let len = transactions.len(); + for (idx, tx) in transactions.iter_transactions().enumerate() { + match ready.is_ready(tx) { + Readiness::Stale => status.stalled += 1, + Readiness::Ready => status.pending += 1, + Readiness::Future => { + status.future += len - idx; + break; + } + } + } + } + + status + } + + /// Returns light status of the pool. + pub fn light_status(&self) -> LightStatus { + LightStatus { + mem_usage: self.mem_usage, + transaction_count: self.by_hash.len(), + senders: self.transactions.len(), + } + } + + /// Returns current pool options. + pub fn options(&self) -> Options { + self.options.clone() + } + + /// Borrows listener instance. + pub fn listener(&self) -> &L { + &self.listener + } + + /// Borrows scoring instance. + pub fn scoring(&self) -> &S { + &self.scoring + } + + /// Set scoring instance. + pub fn set_scoring(&mut self, scoring: S, event: S::Event) { + self.scoring = scoring; + self.update_all_scores(event); + } + + /// Borrows listener mutably. + pub fn listener_mut(&mut self) -> &mut L { + &mut self.listener + } +} + +/// An iterator over all pending (ready) transactions in unoredered fashion. +/// +/// NOTE: Current implementation will iterate over all transactions from particular sender +/// ordered by nonce, but that might change in the future. +/// +/// NOTE: the transactions are not removed from the queue. +/// You might remove them later by calling `cull`. +/// +/// Note: includable_boundary is used to return only the subgroup of pending transaction with score greater or equal +/// to includable_boundary. If not needed, set includable_boundary to zero. +pub struct UnorderedIterator<'a, T, R, S> +where + T: VerifiedTransaction + 'a, + S: Scoring + 'a, +{ + ready: R, + senders: hash_map::Iter<'a, T::Sender, Transactions>, + transactions: Option>>, + scores: Option>, + includable_boundary: S::Score, +} + +impl<'a, T, R, S> Iterator for UnorderedIterator<'a, T, R, S> +where + T: VerifiedTransaction, + R: Ready, + S: Scoring, +{ + type Item = Arc; + + fn next(&mut self) -> Option { + // iterate through each sender + loop { + if let Some(transactions) = self.transactions.as_mut() { + if let Some(scores) = self.scores.as_mut() { + // iterate through each transaction from one sender + loop { + if let Some(tx) = transactions.next() { + if let Some(score) = scores.next() { + match self.ready.is_ready(&tx) { + Readiness::Ready => { + //return transaction with score higher or equal to desired + if score >= &self.includable_boundary { + return Some(tx.transaction.clone()); + } + } + state => { + trace!( + "[{:?}] Ignoring {:?} transaction.", + tx.hash(), + state + ) + } + } + } + } else { + break; + } + } + } + } + + // otherwise fallback and try next sender + let next_sender = self.senders.next()?; + self.transactions = Some(next_sender.1.iter_transactions()); + self.scores = Some(next_sender.1.iter_scores()); + } + } +} + +/// An iterator over all pending (ready) transactions. +/// NOTE: the transactions are not removed from the queue. +/// You might remove them later by calling `cull`. +/// +/// Note: includable_boundary is used to return only the subgroup of pending transaction with score greater or equal +/// to includable_boundary. If not needed, set includable_boundary to zero. +pub struct PendingIterator<'a, T, R, S, L> +where + T: VerifiedTransaction + 'a, + S: Scoring + 'a, + L: 'a, +{ + ready: R, + best_transactions: BTreeSet>, + pool: &'a Pool, + includable_boundary: S::Score, +} + +impl<'a, T, R, S, L> Iterator for PendingIterator<'a, T, R, S, L> +where + T: VerifiedTransaction, + R: Ready, + S: Scoring, +{ + type Item = Arc; + + fn next(&mut self) -> Option { + while !self.best_transactions.is_empty() { + let best = { + let best = self + .best_transactions + .iter() + .next() + .expect("current_best is not empty; qed") + .clone(); + self.best_transactions + .take(&best) + .expect("Just taken from iterator; qed") + }; + + let tx_state = self.ready.is_ready(&best.transaction); + // Add the next best sender's transaction when applicable + match tx_state { + Readiness::Ready | Readiness::Stale => { + // retrieve next one from the same sender. + let next = self + .pool + .transactions + .get(best.transaction.sender()) + .and_then(|s| s.find_next(&best.transaction, &self.pool.scoring)); + if let Some((score, tx)) = next { + self.best_transactions.insert(ScoreWithRef::new(score, tx)); + } + } + _ => (), + } + + if tx_state == Readiness::Ready { + //return transaction with score higher or equal to desired + if best.score >= self.includable_boundary { + return Some(best.transaction.transaction); + } + } + + trace!( + "[{:?}] Ignoring {:?} transaction. Score: {:?}, includable boundary: {:?}", + best.transaction.hash(), + tx_state, + best.score, + self.includable_boundary + ); + } + + None + } +} diff --git a/crates/transaction-pool/src/ready.rs b/crates/transaction-pool/src/ready.rs new file mode 100644 index 000000000..aa5baee41 --- /dev/null +++ b/crates/transaction-pool/src/ready.rs @@ -0,0 +1,58 @@ +// Copyright 2015-2018 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 . + +/// Transaction readiness. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Readiness { + /// The transaction is stale (and should/will be removed from the pool). + Stale, + /// The transaction is ready to be included in pending set. + Ready, + /// The transaction is not yet ready. + Future, +} + +/// A readiness indicator. +pub trait Ready { + /// Returns true if transaction is ready to be included in pending block, + /// given all previous transactions that were ready are already included. + /// + /// NOTE: readiness of transactions will be checked according to `Score` ordering, + /// the implementation should maintain a state of already checked transactions. + fn is_ready(&mut self, tx: &T) -> Readiness; +} + +impl Ready for F +where + F: FnMut(&T) -> Readiness, +{ + fn is_ready(&mut self, tx: &T) -> Readiness { + (*self)(tx) + } +} + +impl Ready for (A, B) +where + A: Ready, + B: Ready, +{ + fn is_ready(&mut self, tx: &T) -> Readiness { + match self.0.is_ready(tx) { + Readiness::Ready => self.1.is_ready(tx), + r => r, + } + } +} diff --git a/crates/transaction-pool/src/replace.rs b/crates/transaction-pool/src/replace.rs new file mode 100644 index 000000000..daed28fdf --- /dev/null +++ b/crates/transaction-pool/src/replace.rs @@ -0,0 +1,55 @@ +// 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 . + +//! When queue limits are reached, decide whether to replace an existing transaction from the pool + +use crate::{pool::Transaction, scoring::Choice}; + +/// Encapsulates a transaction to be compared, along with pooled transactions from the same sender +pub struct ReplaceTransaction<'a, T> { + /// The transaction to be compared for replacement + pub transaction: &'a Transaction, + /// Other transactions currently in the pool for the same sender + pub pooled_by_sender: Option<&'a [Transaction]>, +} + +impl<'a, T> ReplaceTransaction<'a, T> { + /// Creates a new `ReplaceTransaction` + pub fn new( + transaction: &'a Transaction, + pooled_by_sender: Option<&'a [Transaction]>, + ) -> Self { + ReplaceTransaction { + transaction, + pooled_by_sender, + } + } +} + +impl<'a, T> ::std::ops::Deref for ReplaceTransaction<'a, T> { + type Target = Transaction; + fn deref(&self) -> &Self::Target { + &self.transaction + } +} + +/// Chooses whether a new transaction should replace an existing transaction if the pool is full. +pub trait ShouldReplace { + /// Decides if `new` should push out `old` transaction from the pool. + /// + /// NOTE returning `InsertNew` here can lead to some transactions being accepted above pool limits. + fn should_replace(&self, old: &ReplaceTransaction, new: &ReplaceTransaction) -> Choice; +} diff --git a/crates/transaction-pool/src/scoring.rs b/crates/transaction-pool/src/scoring.rs new file mode 100644 index 000000000..177cc1e57 --- /dev/null +++ b/crates/transaction-pool/src/scoring.rs @@ -0,0 +1,189 @@ +// Copyright 2015-2018 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 . + +//! A transactions ordering abstraction. + +use crate::pool::Transaction; +use std::{cmp, fmt}; + +/// Represents a decision what to do with +/// a new transaction that tries to enter the pool. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Choice { + /// New transaction should be rejected + /// (i.e. the old transaction that occupies the same spot + /// is better). + RejectNew, + /// The old transaction should be dropped + /// in favour of the new one. + ReplaceOld, + /// The new transaction should be inserted + /// and both (old and new) should stay in the pool. + InsertNew, +} + +/// Describes a reason why the `Score` of transactions +/// should be updated. +/// The `Scoring` implementations can use this information +/// to update the `Score` table more efficiently. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Change { + /// New transaction has been inserted at given index. + /// The Score at that index is initialized with default value + /// and needs to be filled in. + InsertedAt(usize), + /// The transaction has been removed at given index and other transactions + /// shifted to it's place. + /// The scores were removed and shifted as well. + /// For simple scoring algorithms no action is required here. + RemovedAt(usize), + /// The transaction at given index has replaced a previous transaction. + /// The score at that index needs to be update (it contains value from previous transaction). + ReplacedAt(usize), + /// Given number of stalled transactions has been culled from the beginning. + /// The scores has been removed from the beginning as well. + /// For simple scoring algorithms no action is required here. + Culled(usize), + /// Custom event to update the score triggered outside of the pool. + /// Handling this event is up to scoring implementation. + Event(T), +} + +/// A transaction ordering. +/// +/// The implementation should decide on order of transactions in the pool. +/// Each transaction should also get assigned a `Score` which is used to later +/// prioritize transactions in the pending set. +/// +/// Implementation notes: +/// - Returned `Score`s should match ordering of `compare` method. +/// - `compare` will be called only within a context of transactions from the same sender. +/// - `choose` may be called even if `compare` returns `Ordering::Equal` +/// - `Score`s and `compare` should align with `Ready` implementation. +/// +/// Example: Natural ordering of Ethereum transactions. +/// - `compare`: compares transaction `nonce` () +/// - `choose`: compares transactions `gasPrice` (decides if old transaction should be replaced) +/// - `update_scores`: score defined as `gasPrice` if `n==0` and `max(scores[n-1], gasPrice)` if `n>0` +/// +pub trait Scoring: fmt::Debug { + /// A score of a transaction. + type Score: cmp::Ord + Clone + Default + fmt::Debug + Send + fmt::LowerHex; + /// Custom scoring update event type. + type Event: fmt::Debug + Copy; + + /// Decides on ordering of `T`s from a particular sender. + fn compare(&self, old: &T, other: &T) -> cmp::Ordering; + + /// Decides how to deal with two transactions from a sender that seem to occupy the same slot in the queue. + fn choose(&self, old: &T, new: &T) -> Choice; + + /// Updates the transaction scores given a list of transactions and a change to previous scoring. + /// NOTE: you can safely assume that both slices have the same length. + /// (i.e. score at index `i` represents transaction at the same index) + fn update_scores( + &self, + txs: &[Transaction], + scores: &mut [Self::Score], + change: Change, + ); + + /// Decides if the transaction should ignore per-sender limit in the pool. + /// + /// If you return `true` for given transaction it's going to be accepted even though + /// the per-sender limit is exceeded. + fn should_ignore_sender_limit(&self, _new: &T) -> bool { + false + } +} + +/// A score with a reference to the transaction. +#[derive(Debug)] +pub struct ScoreWithRef { + /// Score + pub score: S, + /// Shared transaction + pub transaction: Transaction, +} + +impl ScoreWithRef { + /// Creates a new `ScoreWithRef` + pub fn new(score: S, transaction: Transaction) -> Self { + ScoreWithRef { score, transaction } + } +} + +impl Clone for ScoreWithRef { + fn clone(&self) -> Self { + ScoreWithRef { + score: self.score.clone(), + transaction: self.transaction.clone(), + } + } +} + +impl Ord for ScoreWithRef { + fn cmp(&self, other: &Self) -> cmp::Ordering { + other.score.cmp(&self.score).then( + self.transaction + .insertion_id + .cmp(&other.transaction.insertion_id), + ) + } +} + +impl PartialOrd for ScoreWithRef { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for ScoreWithRef { + fn eq(&self, other: &Self) -> bool { + self.score == other.score && self.transaction.insertion_id == other.transaction.insertion_id + } +} + +impl Eq for ScoreWithRef {} + +#[cfg(test)] +mod tests { + use super::*; + + fn score(score: u64, insertion_id: u64) -> ScoreWithRef<(), u64> { + ScoreWithRef { + score, + transaction: Transaction { + insertion_id, + transaction: Default::default(), + }, + } + } + + #[test] + fn scoring_comparison() { + // the higher the score the better + assert_eq!(score(10, 0).cmp(&score(0, 0)), cmp::Ordering::Less); + assert_eq!(score(0, 0).cmp(&score(10, 0)), cmp::Ordering::Greater); + + // equal is equal + assert_eq!(score(0, 0).cmp(&score(0, 0)), cmp::Ordering::Equal); + + // lower insertion id is better + assert_eq!(score(0, 0).cmp(&score(0, 10)), cmp::Ordering::Less); + assert_eq!(score(0, 10).cmp(&score(0, 0)), cmp::Ordering::Greater); + } +} diff --git a/crates/transaction-pool/src/status.rs b/crates/transaction-pool/src/status.rs new file mode 100644 index 000000000..b423ee59d --- /dev/null +++ b/crates/transaction-pool/src/status.rs @@ -0,0 +1,40 @@ +// Copyright 2015-2018 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 . + +/// Light pool status. +/// This status is cheap to compute and can be called frequently. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct LightStatus { + /// Memory usage in bytes. + pub mem_usage: usize, + /// Total number of transactions in the pool. + pub transaction_count: usize, + /// Number of unique senders in the pool. + pub senders: usize, +} + +/// A full queue status. +/// To compute this status it is required to provide `Ready`. +/// NOTE: To compute the status we need to visit each transaction in the pool. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct Status { + /// Number of stalled transactions. + pub stalled: usize, + /// Number of pending (ready) transactions. + pub pending: usize, + /// Number of future (not ready) transactions. + pub future: usize, +} diff --git a/crates/transaction-pool/src/tests/helpers.rs b/crates/transaction-pool/src/tests/helpers.rs new file mode 100644 index 000000000..9a4291e48 --- /dev/null +++ b/crates/transaction-pool/src/tests/helpers.rs @@ -0,0 +1,140 @@ +// Copyright 2015-2018 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 . + +use std::{cmp, collections::HashMap}; + +use super::Transaction; +use crate::{pool, scoring, Readiness, Ready, ReplaceTransaction, Scoring, ShouldReplace}; +use ethereum_types::{H160 as Sender, U256}; + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum DummyScoringEvent { + /// Penalize transactions + Penalize, + /// Update scores to the gas price + UpdateScores, +} +#[derive(Debug, Default)] +pub struct DummyScoring { + always_insert: bool, +} + +impl DummyScoring { + pub fn always_insert() -> Self { + DummyScoring { + always_insert: true, + } + } +} + +impl Scoring for DummyScoring { + type Score = U256; + type Event = DummyScoringEvent; + + fn compare(&self, old: &Transaction, new: &Transaction) -> cmp::Ordering { + old.nonce.cmp(&new.nonce) + } + + fn choose(&self, old: &Transaction, new: &Transaction) -> scoring::Choice { + if old.nonce == new.nonce { + if new.gas_price > old.gas_price { + scoring::Choice::ReplaceOld + } else { + scoring::Choice::RejectNew + } + } else { + scoring::Choice::InsertNew + } + } + + fn update_scores( + &self, + txs: &[pool::Transaction], + scores: &mut [Self::Score], + change: scoring::Change, + ) { + match change { + scoring::Change::Event(event) => { + match event { + DummyScoringEvent::Penalize => { + println!("entered"); + // In case of penalize reset all scores to 0 + for i in 0..txs.len() { + scores[i] = 0.into(); + } + } + DummyScoringEvent::UpdateScores => { + // Set to a gas price otherwise + for i in 0..txs.len() { + scores[i] = txs[i].gas_price; + } + } + } + } + scoring::Change::InsertedAt(index) | scoring::Change::ReplacedAt(index) => { + scores[index] = txs[index].gas_price; + } + scoring::Change::RemovedAt(_) => {} + scoring::Change::Culled(_) => {} + } + } + + fn should_ignore_sender_limit(&self, _new: &Transaction) -> bool { + self.always_insert + } +} + +impl ShouldReplace for DummyScoring { + fn should_replace( + &self, + old: &ReplaceTransaction, + new: &ReplaceTransaction, + ) -> scoring::Choice { + if self.always_insert { + scoring::Choice::InsertNew + } else if new.gas_price > old.gas_price { + scoring::Choice::ReplaceOld + } else { + scoring::Choice::RejectNew + } + } +} + +#[derive(Default)] +pub struct NonceReady(HashMap, U256); + +impl NonceReady { + pub fn new>(min: T) -> Self { + let mut n = NonceReady::default(); + n.1 = min.into(); + n + } +} + +impl Ready for NonceReady { + fn is_ready(&mut self, tx: &Transaction) -> Readiness { + let min = self.1; + let nonce = self.0.entry(tx.sender).or_insert_with(|| min); + match tx.nonce.cmp(nonce) { + cmp::Ordering::Greater => Readiness::Future, + cmp::Ordering::Equal => { + *nonce += 1.into(); + Readiness::Ready + } + cmp::Ordering::Less => Readiness::Stale, + } + } +} diff --git a/crates/transaction-pool/src/tests/mod.rs b/crates/transaction-pool/src/tests/mod.rs new file mode 100644 index 000000000..5ba235d23 --- /dev/null +++ b/crates/transaction-pool/src/tests/mod.rs @@ -0,0 +1,961 @@ +// Copyright 2015-2018 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 . + +mod helpers; +mod tx_builder; + +use self::{ + helpers::{DummyScoring, NonceReady}, + tx_builder::TransactionBuilder, +}; + +use std::sync::Arc; + +use super::*; +use ethereum_types::{Address, H256, U256}; + +#[derive(Debug, PartialEq)] +pub struct Transaction { + pub hash: H256, + pub nonce: U256, + pub gas_price: U256, + pub gas: U256, + pub sender: Address, + pub mem_usage: usize, +} + +impl VerifiedTransaction for Transaction { + type Hash = H256; + type Sender = Address; + + fn hash(&self) -> &H256 { + &self.hash + } + fn mem_usage(&self) -> usize { + self.mem_usage + } + fn sender(&self) -> &Address { + &self.sender + } +} + +pub type SharedTransaction = Arc; + +type TestPool = Pool; + +impl TestPool { + pub fn with_limit(max_count: usize) -> Self { + Self::with_options(Options { + max_count, + ..Default::default() + }) + } +} + +fn import, L: Listener>( + txq: &mut Pool, + tx: Transaction, +) -> Result, Error<::Hash>> { + txq.import(tx, &mut DummyScoring::default()) +} + +#[test] +fn should_clear_queue() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + assert_eq!( + txq.light_status(), + LightStatus { + mem_usage: 0, + transaction_count: 0, + senders: 0, + } + ); + let tx1 = b.tx().nonce(0).new(); + let tx2 = b.tx().nonce(1).mem_usage(1).new(); + + // add + import(&mut txq, tx1).unwrap(); + import(&mut txq, tx2).unwrap(); + assert_eq!( + txq.light_status(), + LightStatus { + mem_usage: 1, + transaction_count: 2, + senders: 1, + } + ); + + // when + txq.clear(); + + // then + assert_eq!( + txq.light_status(), + LightStatus { + mem_usage: 0, + transaction_count: 0, + senders: 0, + } + ); +} + +#[test] +fn should_not_allow_same_transaction_twice() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + let tx1 = b.tx().nonce(0).new(); + let tx2 = b.tx().nonce(0).new(); + + // when + import(&mut txq, tx1).unwrap(); + import(&mut txq, tx2).unwrap_err(); + + // then + assert_eq!(txq.light_status().transaction_count, 1); +} + +#[test] +fn should_replace_transaction() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + let tx1 = b.tx().nonce(0).gas_price(1).new(); + let tx2 = b.tx().nonce(0).gas_price(2).new(); + + // when + import(&mut txq, tx1).unwrap(); + import(&mut txq, tx2).unwrap(); + + // then + assert_eq!(txq.light_status().transaction_count, 1); +} + +#[test] +fn should_reject_if_above_count() { + let b = TransactionBuilder::default(); + let mut txq = TestPool::with_options(Options { + max_count: 1, + ..Default::default() + }); + + // Reject second + let tx1 = b.tx().nonce(0).new(); + let tx2 = b.tx().nonce(1).new(); + let hash = tx2.hash.clone(); + import(&mut txq, tx1).unwrap(); + assert_eq!( + import(&mut txq, tx2).unwrap_err(), + error::Error::TooCheapToEnter(hash, "0x0".into()) + ); + assert_eq!(txq.light_status().transaction_count, 1); + + txq.clear(); + + // Replace first + let tx1 = b.tx().nonce(0).new(); + let tx2 = b.tx().nonce(0).sender(1).gas_price(2).new(); + import(&mut txq, tx1).unwrap(); + import(&mut txq, tx2).unwrap(); + assert_eq!(txq.light_status().transaction_count, 1); +} + +#[test] +fn should_reject_if_above_mem_usage() { + let b = TransactionBuilder::default(); + let mut txq = TestPool::with_options(Options { + max_mem_usage: 1, + ..Default::default() + }); + + // Reject second + let tx1 = b.tx().nonce(1).mem_usage(1).new(); + let tx2 = b.tx().nonce(2).mem_usage(2).new(); + let hash = tx2.hash.clone(); + import(&mut txq, tx1).unwrap(); + assert_eq!( + import(&mut txq, tx2).unwrap_err(), + error::Error::TooCheapToEnter(hash, "0x0".into()) + ); + assert_eq!(txq.light_status().transaction_count, 1); + + txq.clear(); + + // Replace first + let tx1 = b.tx().nonce(1).mem_usage(1).new(); + let tx2 = b.tx().nonce(1).sender(1).gas_price(2).mem_usage(1).new(); + import(&mut txq, tx1).unwrap(); + import(&mut txq, tx2).unwrap(); + assert_eq!(txq.light_status().transaction_count, 1); +} + +#[test] +fn should_reject_if_above_sender_count() { + let b = TransactionBuilder::default(); + let mut txq = TestPool::with_options(Options { + max_per_sender: 1, + ..Default::default() + }); + + // Reject second + let tx1 = b.tx().nonce(1).new(); + let tx2 = b.tx().nonce(2).new(); + let hash = tx2.hash.clone(); + import(&mut txq, tx1).unwrap(); + assert_eq!( + import(&mut txq, tx2).unwrap_err(), + error::Error::TooCheapToEnter(hash, "0x0".into()) + ); + assert_eq!(txq.light_status().transaction_count, 1); + + txq.clear(); + + // Replace first + let tx1 = b.tx().nonce(1).new(); + let tx2 = b.tx().nonce(2).gas_price(2).new(); + let hash = tx2.hash.clone(); + import(&mut txq, tx1).unwrap(); + // This results in error because we also compare nonces + assert_eq!( + import(&mut txq, tx2).unwrap_err(), + error::Error::TooCheapToEnter(hash, "0x0".into()) + ); + assert_eq!(txq.light_status().transaction_count, 1); +} + +#[test] +fn should_construct_pending() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + + let tx0 = import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + let tx1 = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap(); + + let tx9 = import(&mut txq, b.tx().sender(2).nonce(0).new()).unwrap(); + + let tx5 = import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap(); + let tx6 = import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap(); + let tx7 = import(&mut txq, b.tx().sender(1).nonce(2).new()).unwrap(); + let tx8 = import(&mut txq, b.tx().sender(1).nonce(3).gas_price(4).new()).unwrap(); + + let tx2 = import(&mut txq, b.tx().nonce(2).new()).unwrap(); + // this transaction doesn't get to the block despite high gas price + // because of block gas limit and simplistic ordering algorithm. + let tx3 = import(&mut txq, b.tx().nonce(3).gas_price(4).new()).unwrap(); + //gap + import(&mut txq, b.tx().nonce(5).new()).unwrap(); + + // gap + import(&mut txq, b.tx().sender(1).nonce(5).new()).unwrap(); + + assert_eq!(txq.light_status().transaction_count, 11); + assert_eq!( + txq.status(NonceReady::default()), + Status { + stalled: 0, + pending: 9, + future: 2, + } + ); + assert_eq!( + txq.status(NonceReady::new(1)), + Status { + stalled: 3, + pending: 6, + future: 2, + } + ); + + // get only includable part of the pending transactions + let mut includable = txq.pending(NonceReady::default(), U256::from(4)); + + assert_eq!(includable.next(), Some(tx0.clone())); + assert_eq!(includable.next(), Some(tx1.clone())); + assert_eq!(includable.next(), Some(tx8.clone())); + assert_eq!(includable.next(), Some(tx3.clone())); + assert_eq!(includable.next(), None); + + // get all pending transactions + let mut current_gas = U256::zero(); + let limit = (21_000 * 8).into(); + let mut pending = txq + .pending(NonceReady::default(), U256::from(0)) + .take_while(|tx| { + let should_take = tx.gas + current_gas <= limit; + if should_take { + current_gas = current_gas + tx.gas + } + should_take + }); + + assert_eq!(pending.next(), Some(tx0)); + assert_eq!(pending.next(), Some(tx1)); + assert_eq!(pending.next(), Some(tx9)); + assert_eq!(pending.next(), Some(tx5)); + assert_eq!(pending.next(), Some(tx6)); + assert_eq!(pending.next(), Some(tx7)); + assert_eq!(pending.next(), Some(tx8)); + assert_eq!(pending.next(), Some(tx2)); + assert_eq!(pending.next(), None); +} + +#[test] +fn should_skip_staled_pending_transactions() { + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + + let _tx0 = import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + let tx2 = import(&mut txq, b.tx().nonce(2).gas_price(5).new()).unwrap(); + let _tx1 = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap(); + + // tx0 and tx1 are Stale, tx2 is Ready + let mut pending = txq.pending(NonceReady::new(2), Default::default()); + + // tx0 and tx1 should be skipped, tx2 should be the next Ready + assert_eq!(pending.next(), Some(tx2)); + assert_eq!(pending.next(), None); +} + +#[test] +fn should_return_unordered_iterator() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + + let tx0 = import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + let tx1 = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap(); + let tx2 = import(&mut txq, b.tx().nonce(2).new()).unwrap(); + let tx3 = import(&mut txq, b.tx().nonce(3).gas_price(4).new()).unwrap(); + //gap + import(&mut txq, b.tx().nonce(5).new()).unwrap(); + + let tx5 = import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap(); + let tx6 = import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap(); + let tx7 = import(&mut txq, b.tx().sender(1).nonce(2).new()).unwrap(); + let tx8 = import(&mut txq, b.tx().sender(1).nonce(3).gas_price(4).new()).unwrap(); + // gap + import(&mut txq, b.tx().sender(1).nonce(5).new()).unwrap(); + + let tx9 = import(&mut txq, b.tx().sender(2).nonce(0).new()).unwrap(); + assert_eq!(txq.light_status().transaction_count, 11); + assert_eq!( + txq.status(NonceReady::default()), + Status { + stalled: 0, + pending: 9, + future: 2, + } + ); + assert_eq!( + txq.status(NonceReady::new(1)), + Status { + stalled: 3, + pending: 6, + future: 2, + } + ); + + // get all pending transaction in unordered way + let all: Vec<_> = txq + .unordered_pending(NonceReady::default(), Default::default()) + .collect(); + + let chain1 = vec![tx0, tx1, tx2, tx3]; + let chain2 = vec![tx5, tx6, tx7, tx8]; + let chain3 = vec![tx9]; + + assert_eq!(all.len(), chain1.len() + chain2.len() + chain3.len()); + + let mut options = vec![ + vec![chain1.clone(), chain2.clone(), chain3.clone()], + vec![chain2.clone(), chain1.clone(), chain3.clone()], + vec![chain2.clone(), chain3.clone(), chain1.clone()], + vec![chain3.clone(), chain2.clone(), chain1.clone()], + vec![chain3.clone(), chain1.clone(), chain2.clone()], + vec![chain1.clone(), chain3.clone(), chain2.clone()], + ] + .into_iter() + .map(|mut v| { + let mut first = v.pop().unwrap(); + for mut x in v { + first.append(&mut x); + } + first + }); + + assert!(options.any(|opt| all == opt)); + + // get only includable part of the pending transactions in unordered way + let includable: Vec<_> = txq + .unordered_pending(NonceReady::default(), U256::from(3)) + .collect(); + + assert_eq!(includable.len(), 4); +} + +#[test] +fn should_update_scoring_correctly() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + + let tx9 = import(&mut txq, b.tx().sender(2).nonce(0).new()).unwrap(); + + let tx5 = import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap(); + let tx6 = import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap(); + let tx7 = import(&mut txq, b.tx().sender(1).nonce(2).new()).unwrap(); + let tx8 = import(&mut txq, b.tx().sender(1).nonce(3).gas_price(4).new()).unwrap(); + + let tx0 = import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + let tx1 = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap(); + let tx2 = import(&mut txq, b.tx().nonce(2).new()).unwrap(); + // this transaction doesn't get to the block despite high gas price + // because of block gas limit and simplistic ordering algorithm. + import(&mut txq, b.tx().nonce(3).gas_price(4).new()).unwrap(); + //gap + import(&mut txq, b.tx().nonce(5).new()).unwrap(); + + // gap + import(&mut txq, b.tx().sender(1).nonce(5).new()).unwrap(); + + assert_eq!(txq.light_status().transaction_count, 11); + assert_eq!( + txq.status(NonceReady::default()), + Status { + stalled: 0, + pending: 9, + future: 2, + } + ); + assert_eq!( + txq.status(NonceReady::new(1)), + Status { + stalled: 3, + pending: 6, + future: 2, + } + ); + + txq.update_scores(&Address::zero(), helpers::DummyScoringEvent::Penalize); + + // when + let mut current_gas = U256::zero(); + let limit = (21_000 * 8).into(); + let mut pending = txq + .pending(NonceReady::default(), Default::default()) + .take_while(|tx| { + let should_take = tx.gas + current_gas <= limit; + if should_take { + current_gas = current_gas + tx.gas + } + should_take + }); + + assert_eq!(pending.next(), Some(tx9)); + assert_eq!(pending.next(), Some(tx5)); + assert_eq!(pending.next(), Some(tx6)); + assert_eq!(pending.next(), Some(tx7)); + assert_eq!(pending.next(), Some(tx8)); + // penalized transactions + assert_eq!(pending.next(), Some(tx0.clone())); + assert_eq!(pending.next(), Some(tx1.clone())); + assert_eq!(pending.next(), Some(tx2)); + assert_eq!(pending.next(), None); + + // update scores to initial values + txq.set_scoring( + DummyScoring::default(), + helpers::DummyScoringEvent::UpdateScores, + ); + + current_gas = U256::zero(); + let mut includable = txq + .pending(NonceReady::default(), Default::default()) + .take_while(|tx| { + let should_take = tx.gas + current_gas <= limit; + if should_take { + current_gas = current_gas + tx.gas + } + should_take + }); + + assert_eq!(includable.next(), Some(tx0)); + assert_eq!(includable.next(), Some(tx1)); +} + +#[test] +fn should_remove_transaction() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + + let tx1 = import(&mut txq, b.tx().nonce(0).new()).unwrap(); + let tx2 = import(&mut txq, b.tx().nonce(1).new()).unwrap(); + import(&mut txq, b.tx().nonce(2).new()).unwrap(); + assert_eq!(txq.light_status().transaction_count, 3); + + // when + assert!(txq.remove(&tx2.hash(), false).is_some()); + + // then + assert_eq!(txq.light_status().transaction_count, 2); + let mut pending = txq.pending(NonceReady::default(), Default::default()); + assert_eq!(pending.next(), Some(tx1)); + assert_eq!(pending.next(), None); +} + +#[test] +fn should_cull_stalled_transactions() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + + import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + import(&mut txq, b.tx().nonce(1).new()).unwrap(); + import(&mut txq, b.tx().nonce(3).new()).unwrap(); + + import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap(); + import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap(); + import(&mut txq, b.tx().sender(1).nonce(5).new()).unwrap(); + + assert_eq!( + txq.status(NonceReady::new(1)), + Status { + stalled: 2, + pending: 2, + future: 2, + } + ); + + // when + assert_eq!(txq.cull(None, NonceReady::new(1)), 2); + + // then + assert_eq!( + txq.status(NonceReady::new(1)), + Status { + stalled: 0, + pending: 2, + future: 2, + } + ); + assert_eq!( + txq.light_status(), + LightStatus { + transaction_count: 4, + senders: 2, + mem_usage: 0, + } + ); +} + +#[test] +fn should_cull_stalled_transactions_from_a_sender() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + + import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + import(&mut txq, b.tx().nonce(1).new()).unwrap(); + + import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap(); + import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap(); + import(&mut txq, b.tx().sender(1).nonce(2).new()).unwrap(); + + assert_eq!( + txq.status(NonceReady::new(2)), + Status { + stalled: 4, + pending: 1, + future: 0, + } + ); + + // when + let sender = Address::zero(); + assert_eq!(txq.cull(Some(&[sender]), NonceReady::new(2)), 2); + + // then + assert_eq!( + txq.status(NonceReady::new(2)), + Status { + stalled: 2, + pending: 1, + future: 0, + } + ); + assert_eq!( + txq.light_status(), + LightStatus { + transaction_count: 3, + senders: 1, + mem_usage: 0, + } + ); +} + +#[test] +fn should_re_insert_after_cull() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + + import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + import(&mut txq, b.tx().nonce(1).new()).unwrap(); + import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap(); + import(&mut txq, b.tx().sender(1).nonce(1).new()).unwrap(); + assert_eq!( + txq.status(NonceReady::new(1)), + Status { + stalled: 2, + pending: 2, + future: 0, + } + ); + + // when + assert_eq!(txq.cull(None, NonceReady::new(1)), 2); + assert_eq!( + txq.status(NonceReady::new(1)), + Status { + stalled: 0, + pending: 2, + future: 0, + } + ); + import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + import(&mut txq, b.tx().sender(1).nonce(0).new()).unwrap(); + + assert_eq!( + txq.status(NonceReady::new(1)), + Status { + stalled: 2, + pending: 2, + future: 0, + } + ); +} + +#[test] +fn should_return_worst_transaction() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::default(); + assert!(txq.worst_transaction().is_none()); + + // when + import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + import(&mut txq, b.tx().sender(1).nonce(0).gas_price(4).new()).unwrap(); + + // then + assert_eq!(txq.worst_transaction().unwrap().gas_price, 4.into()); +} + +#[test] +fn should_return_is_full() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::with_limit(2); + assert!(!txq.is_full()); + + // when + import(&mut txq, b.tx().nonce(0).gas_price(110).new()).unwrap(); + assert!(!txq.is_full()); + + import(&mut txq, b.tx().sender(1).nonce(0).gas_price(100).new()).unwrap(); + + // then + assert!(txq.is_full()); +} + +#[test] +fn should_import_even_if_limit_is_reached_and_should_replace_returns_insert_new() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::with_scoring( + DummyScoring::always_insert(), + Options { + max_count: 1, + ..Default::default() + }, + ); + txq.import( + b.tx().nonce(0).gas_price(5).new(), + &mut DummyScoring::always_insert(), + ) + .unwrap(); + assert_eq!( + txq.light_status(), + LightStatus { + transaction_count: 1, + senders: 1, + mem_usage: 0, + } + ); + + // when + txq.import( + b.tx().nonce(1).gas_price(5).new(), + &mut DummyScoring::always_insert(), + ) + .unwrap(); + + // then + assert_eq!( + txq.light_status(), + LightStatus { + transaction_count: 2, + senders: 1, + mem_usage: 0, + } + ); +} + +#[test] +fn should_not_import_even_if_limit_is_reached_and_should_replace_returns_false() { + use std::str::FromStr; + + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::with_scoring( + DummyScoring::default(), + Options { + max_count: 1, + ..Default::default() + }, + ); + import(&mut txq, b.tx().nonce(0).gas_price(5).new()).unwrap(); + assert_eq!( + txq.light_status(), + LightStatus { + transaction_count: 1, + senders: 1, + mem_usage: 0, + } + ); + + // when + let err = import(&mut txq, b.tx().nonce(1).gas_price(5).new()).unwrap_err(); + + // then + assert_eq!( + err, + error::Error::TooCheapToEnter( + H256::from_str("00000000000000000000000000000000000000000000000000000000000001f5") + .unwrap(), + "0x5".into() + ) + ); + assert_eq!( + txq.light_status(), + LightStatus { + transaction_count: 1, + senders: 1, + mem_usage: 0, + } + ); +} + +#[test] +fn should_import_even_if_sender_limit_is_reached() { + // given + let b = TransactionBuilder::default(); + let mut txq = TestPool::with_scoring( + DummyScoring::always_insert(), + Options { + max_count: 1, + max_per_sender: 1, + ..Default::default() + }, + ); + txq.import( + b.tx().nonce(0).gas_price(5).new(), + &mut DummyScoring::always_insert(), + ) + .unwrap(); + assert_eq!( + txq.light_status(), + LightStatus { + transaction_count: 1, + senders: 1, + mem_usage: 0, + } + ); + + // when + txq.import( + b.tx().nonce(1).gas_price(5).new(), + &mut DummyScoring::always_insert(), + ) + .unwrap(); + + // then + assert_eq!( + txq.light_status(), + LightStatus { + transaction_count: 2, + senders: 1, + mem_usage: 0, + } + ); +} + +mod listener { + use std::{cell::RefCell, fmt, rc::Rc}; + + use super::*; + + #[derive(Default)] + struct MyListener(pub Rc>>); + + impl Listener for MyListener { + fn added(&mut self, _tx: &SharedTransaction, old: Option<&SharedTransaction>) { + self.0 + .borrow_mut() + .push(if old.is_some() { "replaced" } else { "added" }); + } + + fn rejected( + &mut self, + _tx: &SharedTransaction, + _reason: &error::Error, + ) { + self.0.borrow_mut().push("rejected".into()); + } + + fn dropped(&mut self, _tx: &SharedTransaction, _new: Option<&Transaction>) { + self.0.borrow_mut().push("dropped".into()); + } + + fn invalid(&mut self, _tx: &SharedTransaction) { + self.0.borrow_mut().push("invalid".into()); + } + + fn canceled(&mut self, _tx: &SharedTransaction) { + self.0.borrow_mut().push("canceled".into()); + } + + fn culled(&mut self, _tx: &SharedTransaction) { + self.0.borrow_mut().push("culled".into()); + } + } + + #[test] + fn insert_transaction() { + let b = TransactionBuilder::default(); + let listener = MyListener::default(); + let results = listener.0.clone(); + let mut txq = Pool::new( + listener, + DummyScoring::default(), + Options { + max_per_sender: 1, + max_count: 2, + ..Default::default() + }, + ); + assert!(results.borrow().is_empty()); + + // Regular import + import(&mut txq, b.tx().nonce(1).new()).unwrap(); + assert_eq!(*results.borrow(), &["added"]); + // Already present (no notification) + import(&mut txq, b.tx().nonce(1).new()).unwrap_err(); + assert_eq!(*results.borrow(), &["added"]); + // Push out the first one + import(&mut txq, b.tx().nonce(1).gas_price(1).new()).unwrap(); + assert_eq!(*results.borrow(), &["added", "replaced"]); + // Reject + import(&mut txq, b.tx().nonce(1).new()).unwrap_err(); + assert_eq!(*results.borrow(), &["added", "replaced", "rejected"]); + results.borrow_mut().clear(); + // Different sender (accept) + import(&mut txq, b.tx().sender(1).nonce(1).gas_price(2).new()).unwrap(); + assert_eq!(*results.borrow(), &["added"]); + // Third sender push out low gas price + import(&mut txq, b.tx().sender(2).nonce(1).gas_price(4).new()).unwrap(); + assert_eq!(*results.borrow(), &["added", "dropped", "added"]); + // Reject (too cheap) + import(&mut txq, b.tx().sender(2).nonce(1).gas_price(2).new()).unwrap_err(); + assert_eq!( + *results.borrow(), + &["added", "dropped", "added", "rejected"] + ); + + assert_eq!(txq.light_status().transaction_count, 2); + } + + #[test] + fn remove_transaction() { + let b = TransactionBuilder::default(); + let listener = MyListener::default(); + let results = listener.0.clone(); + let mut txq = Pool::new(listener, DummyScoring::default(), Options::default()); + + // insert + let tx1 = import(&mut txq, b.tx().nonce(1).new()).unwrap(); + let tx2 = import(&mut txq, b.tx().nonce(2).new()).unwrap(); + + // then + txq.remove(&tx1.hash(), false); + assert_eq!(*results.borrow(), &["added", "added", "canceled"]); + txq.remove(&tx2.hash(), true); + assert_eq!( + *results.borrow(), + &["added", "added", "canceled", "invalid"] + ); + assert_eq!(txq.light_status().transaction_count, 0); + } + + #[test] + fn clear_queue() { + let b = TransactionBuilder::default(); + let listener = MyListener::default(); + let results = listener.0.clone(); + let mut txq = Pool::new(listener, DummyScoring::default(), Options::default()); + + // insert + import(&mut txq, b.tx().nonce(1).new()).unwrap(); + import(&mut txq, b.tx().nonce(2).new()).unwrap(); + + // when + txq.clear(); + + // then + assert_eq!(*results.borrow(), &["added", "added", "dropped", "dropped"]); + } + + #[test] + fn cull_stalled() { + let b = TransactionBuilder::default(); + let listener = MyListener::default(); + let results = listener.0.clone(); + let mut txq = Pool::new(listener, DummyScoring::default(), Options::default()); + + // insert + import(&mut txq, b.tx().nonce(1).new()).unwrap(); + import(&mut txq, b.tx().nonce(2).new()).unwrap(); + + // when + txq.cull(None, NonceReady::new(3)); + + // then + assert_eq!(*results.borrow(), &["added", "added", "culled", "culled"]); + } +} diff --git a/crates/transaction-pool/src/tests/tx_builder.rs b/crates/transaction-pool/src/tests/tx_builder.rs new file mode 100644 index 000000000..8d1f2f44d --- /dev/null +++ b/crates/transaction-pool/src/tests/tx_builder.rs @@ -0,0 +1,67 @@ +// Copyright 2015-2018 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 . + +use super::{Address, Transaction, H256, U256}; +use ethereum_types::BigEndianHash; + +#[derive(Debug, Default, Clone)] +pub struct TransactionBuilder { + nonce: U256, + gas_price: U256, + gas: U256, + sender: Address, + mem_usage: usize, +} + +impl TransactionBuilder { + pub fn tx(&self) -> Self { + self.clone() + } + + pub fn nonce(mut self, nonce: usize) -> Self { + self.nonce = U256::from(nonce); + self + } + + pub fn gas_price(mut self, gas_price: usize) -> Self { + self.gas_price = U256::from(gas_price); + self + } + + pub fn sender(mut self, sender: u64) -> Self { + self.sender = Address::from_low_u64_be(sender); + self + } + + pub fn mem_usage(mut self, mem_usage: usize) -> Self { + self.mem_usage = mem_usage; + self + } + + pub fn new(self) -> Transaction { + let hash: U256 = self.nonce + ^ (U256::from(100) * self.gas_price) + ^ (U256::from(100_000) * U256::from(self.sender.to_low_u64_be())); + Transaction { + hash: H256::from_uint(&hash), + nonce: self.nonce, + gas_price: self.gas_price, + gas: 21_000.into(), + sender: self.sender, + mem_usage: self.mem_usage, + } + } +} diff --git a/crates/transaction-pool/src/transactions.rs b/crates/transaction-pool/src/transactions.rs new file mode 100644 index 000000000..b194fc92e --- /dev/null +++ b/crates/transaction-pool/src/transactions.rs @@ -0,0 +1,268 @@ +// Copyright 2015-2018 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 . + +use std::{fmt, mem}; + +use log::warn; +use smallvec::SmallVec; + +use crate::{ + pool::Transaction, + ready::{Readiness, Ready}, + scoring::{self, Scoring}, +}; + +#[derive(Debug)] +pub enum AddResult { + Ok(T), + TooCheapToEnter(T, S), + TooCheap { old: T, new: T }, + Replaced { old: T, new: T }, + PushedOut { old: T, new: T }, +} + +/// Represents all transactions from a particular sender ordered by nonce. +const PER_SENDER: usize = 8; +#[derive(Debug)] +pub struct Transactions> { + // TODO [ToDr] Consider using something that doesn't require shifting all records. + transactions: SmallVec<[Transaction; PER_SENDER]>, + scores: SmallVec<[S::Score; PER_SENDER]>, +} + +impl> Default for Transactions { + fn default() -> Self { + Transactions { + transactions: Default::default(), + scores: Default::default(), + } + } +} + +impl> Transactions { + pub fn is_empty(&self) -> bool { + self.transactions.is_empty() + } + + pub fn len(&self) -> usize { + self.transactions.len() + } + + pub fn iter_transactions(&self) -> ::std::slice::Iter> { + self.transactions.iter() + } + + pub fn iter_scores(&self) -> ::std::slice::Iter { + self.scores.iter() + } + + pub fn worst_and_best( + &self, + ) -> Option<((S::Score, Transaction), (S::Score, Transaction))> { + let len = self.scores.len(); + self.scores.get(0).cloned().map(|best| { + let worst = self.scores[len - 1].clone(); + let best_tx = self.transactions[0].clone(); + let worst_tx = self.transactions[len - 1].clone(); + + ((worst, worst_tx), (best, best_tx)) + }) + } + + pub fn find_next(&self, tx: &T, scoring: &S) -> Option<(S::Score, Transaction)> { + self.transactions + .binary_search_by(|old| scoring.compare(old, &tx)) + .ok() + .and_then(|index| { + let index = index + 1; + if index < self.scores.len() { + Some((self.scores[index].clone(), self.transactions[index].clone())) + } else { + None + } + }) + } + + fn push_cheapest_transaction( + &mut self, + tx: Transaction, + scoring: &S, + max_count: usize, + ) -> AddResult, S::Score> { + let index = self.transactions.len(); + if index == max_count && !scoring.should_ignore_sender_limit(&tx) { + let min_score = self.scores[index - 1].clone(); + AddResult::TooCheapToEnter(tx, min_score) + } else { + self.transactions.push(tx.clone()); + self.scores.push(Default::default()); + scoring.update_scores( + &self.transactions, + &mut self.scores, + scoring::Change::InsertedAt(index), + ); + + AddResult::Ok(tx) + } + } + + pub fn update_scores(&mut self, scoring: &S, event: S::Event) { + scoring.update_scores( + &self.transactions, + &mut self.scores, + scoring::Change::Event(event), + ); + } + + pub fn add( + &mut self, + new: Transaction, + scoring: &S, + max_count: usize, + ) -> AddResult, S::Score> { + let index = match self + .transactions + .binary_search_by(|old| scoring.compare(old, &new)) + { + Ok(index) => index, + Err(index) => index, + }; + + // Insert at the end. + if index == self.transactions.len() { + return self.push_cheapest_transaction(new, scoring, max_count); + } + + // Decide if the transaction should replace some other. + match scoring.choose(&self.transactions[index], &new) { + // New transaction should be rejected + scoring::Choice::RejectNew => AddResult::TooCheap { + old: self.transactions[index].clone(), + new, + }, + // New transaction should be kept along with old ones. + scoring::Choice::InsertNew => { + self.transactions.insert(index, new.clone()); + self.scores.insert(index, Default::default()); + scoring.update_scores( + &self.transactions, + &mut self.scores, + scoring::Change::InsertedAt(index), + ); + + if self.transactions.len() > max_count { + let old = self.transactions.pop().expect("len is non-zero"); + self.scores.pop(); + scoring.update_scores( + &self.transactions, + &mut self.scores, + scoring::Change::RemovedAt(self.transactions.len()), + ); + + AddResult::PushedOut { old, new } + } else { + AddResult::Ok(new) + } + } + // New transaction is replacing some other transaction already in the queue. + scoring::Choice::ReplaceOld => { + let old = mem::replace(&mut self.transactions[index], new.clone()); + scoring.update_scores( + &self.transactions, + &mut self.scores, + scoring::Change::ReplacedAt(index), + ); + + AddResult::Replaced { old, new } + } + } + } + + pub fn remove(&mut self, tx: &T, scoring: &S) -> bool { + let index = match self + .transactions + .binary_search_by(|old| scoring.compare(old, tx)) + { + Ok(index) => index, + Err(_) => { + warn!("Attempting to remove non-existent transaction {:?}", tx); + return false; + } + }; + + self.transactions.remove(index); + self.scores.remove(index); + // Update scoring + scoring.update_scores( + &self.transactions, + &mut self.scores, + scoring::Change::RemovedAt(index), + ); + return true; + } + + pub fn cull>( + &mut self, + ready: &mut R, + scoring: &S, + ) -> SmallVec<[Transaction; PER_SENDER]> { + let mut result = SmallVec::new(); + if self.is_empty() { + return result; + } + + let mut first_non_stalled = 0; + for tx in &self.transactions { + match ready.is_ready(tx) { + Readiness::Stale => { + first_non_stalled += 1; + } + Readiness::Ready | Readiness::Future => break, + } + } + + if first_non_stalled == 0 { + return result; + } + + // reverse the vectors to easily remove first elements. + self.transactions.reverse(); + self.scores.reverse(); + + for _ in 0..first_non_stalled { + self.scores.pop(); + result.push( + self.transactions + .pop() + .expect("first_non_stalled is never greater than transactions.len(); qed"), + ); + } + + self.transactions.reverse(); + self.scores.reverse(); + + // update scoring + scoring.update_scores( + &self.transactions, + &mut self.scores, + scoring::Change::Culled(result.len()), + ); + + // reverse the result to maintain correct order. + result.reverse(); + result + } +} diff --git a/crates/transaction-pool/src/verifier.rs b/crates/transaction-pool/src/verifier.rs new file mode 100644 index 000000000..a84242ba2 --- /dev/null +++ b/crates/transaction-pool/src/verifier.rs @@ -0,0 +1,31 @@ +// Copyright 2015-2018 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 . + +use crate::VerifiedTransaction; + +/// Transaction verification. +/// +/// Verifier is responsible to decide if the transaction should even be considered for pool inclusion. +pub trait Verifier { + /// Verification error. + type Error; + + /// Verified transaction. + type VerifiedTransaction: VerifiedTransaction; + + /// Verifies a `UnverifiedTransaction` and produces `VerifiedTransaction` instance. + fn verify_transaction(&self, tx: U) -> Result; +} diff --git a/crates/vm/evm/src/instructions.rs b/crates/vm/evm/src/instructions.rs index e120f97df..46f228851 100644 --- a/crates/vm/evm/src/instructions.rs +++ b/crates/vm/evm/src/instructions.rs @@ -153,6 +153,8 @@ enum_with_from_u8! { CHAINID = 0x46, #[doc = "get balance of own account"] SELFBALANCE = 0x47, + #[doc = "get the block's base fee"] + BASEFEE = 0x48, #[doc = "remove item from stack"] POP = 0x50, @@ -517,6 +519,7 @@ lazy_static! { arr[GASLIMIT as usize] = Some(InstructionInfo::new("GASLIMIT", 0, 1, GasPriceTier::Base)); arr[CHAINID as usize] = Some(InstructionInfo::new("CHAINID", 0, 1, GasPriceTier::Base)); arr[SELFBALANCE as usize] = Some(InstructionInfo::new("SELFBALANCE", 0, 1, GasPriceTier::Low)); + arr[BASEFEE as usize] = Some(InstructionInfo::new("BASEFEE", 0, 1, GasPriceTier::Base)); arr[POP as usize] = Some(InstructionInfo::new("POP", 1, 0, GasPriceTier::Base)); arr[MLOAD as usize] = Some(InstructionInfo::new("MLOAD", 1, 1, GasPriceTier::VeryLow)); arr[MSTORE as usize] = Some(InstructionInfo::new("MSTORE", 2, 0, GasPriceTier::VeryLow)); diff --git a/crates/vm/evm/src/interpreter/mod.rs b/crates/vm/evm/src/interpreter/mod.rs index 302013f5e..e1b1e22fa 100644 --- a/crates/vm/evm/src/interpreter/mod.rs +++ b/crates/vm/evm/src/interpreter/mod.rs @@ -590,6 +590,7 @@ impl Interpreter { || (instruction == EXTCODEHASH && !schedule.have_extcodehash) || (instruction == CHAINID && !schedule.have_chain_id) || (instruction == SELFBALANCE && !schedule.have_selfbalance) + || (instruction == BASEFEE && !schedule.eip3198) || ((instruction == BEGINSUB || instruction == JUMPSUB || instruction == RETURNSUB) && !schedule.have_subs) { @@ -1164,6 +1165,9 @@ impl Interpreter { instructions::SELFBALANCE => { self.stack.push(ext.balance(&self.params.address)?); } + instructions::BASEFEE => { + self.stack.push(ext.env_info().base_fee.unwrap_or_default()); + } // Stack instructions instructions::DUP1 diff --git a/crates/vm/evm/src/tests.rs b/crates/vm/evm/src/tests.rs index 9de1dccc1..b97294ea0 100644 --- a/crates/vm/evm/src/tests.rs +++ b/crates/vm/evm/src/tests.rs @@ -461,6 +461,35 @@ fn test_difficulty(factory: super::Factory) { ); } +evm_test! {test_base_fee: test_base_fee_int} +fn test_base_fee(factory: super::Factory) { + let base_fee = Some(U256::from(0x07)); + let code = "48600055".from_hex().unwrap(); + + let mut params = ActionParams::default(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_london( + Address::from_str("0000000000000000000000000000000000000000").unwrap(), + Address::from_str("000000000000000000000000636F6E7472616374").unwrap(), + &[], + ); + ext.info.base_fee = base_fee; + + let gas_left = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap() + }; + + assert_eq!(gas_left, U256::from(77_895)); + println!("elements {}", ext.store.len()); + assert_store( + &ext, + 0, + "0000000000000000000000000000000000000000000000000000000000000007", + ); +} + evm_test! {test_gas_limit: test_gas_limit_int} fn test_gas_limit(factory: super::Factory) { let gas_limit = U256::from(0x1234); diff --git a/crates/vm/vm/src/env_info.rs b/crates/vm/vm/src/env_info.rs index df9d3c10e..e67389c6f 100644 --- a/crates/vm/vm/src/env_info.rs +++ b/crates/vm/vm/src/env_info.rs @@ -44,6 +44,8 @@ pub struct EnvInfo { pub last_hashes: Arc, /// The gas used. pub gas_used: U256, + /// Block base fee. + pub base_fee: Option, } impl Default for EnvInfo { @@ -56,6 +58,7 @@ impl Default for EnvInfo { gas_limit: 0.into(), last_hashes: Arc::new(vec![]), gas_used: 0.into(), + base_fee: None, } } } @@ -75,6 +78,7 @@ impl From for EnvInfo { .collect(), ), gas_used: U256::default(), + base_fee: e.base_fee.map(|i| i.into()), } } } @@ -96,6 +100,7 @@ mod tests { difficulty: ethjson::uint::Uint(U256::from(50_000)), gas_limit: ethjson::uint::Uint(U256::from(40_000)), timestamp: ethjson::uint::Uint(U256::from(1_100)), + base_fee: None, }); assert_eq!(env_info.number, 1112339); diff --git a/crates/vm/vm/src/schedule.rs b/crates/vm/vm/src/schedule.rs index 1273e5854..dc4f47ed0 100644 --- a/crates/vm/vm/src/schedule.rs +++ b/crates/vm/vm/src/schedule.rs @@ -161,6 +161,14 @@ pub struct Schedule { pub eip2929: bool, /// Enable EIP-2930 rules for optional access list transactions. it depends on EIP-2929 pub eip2930: bool, + /// Enable EIP-1559 rules + pub eip1559: bool, + /// Elasticity multiplier + pub eip1559_elasticity_multiplier: usize, + /// EIP-1559 bumps the gas_limit of fork block by elasticity_multiplier + pub eip1559_gas_limit_bump: usize, + /// Enable BASEFEE opcode + pub eip3198: bool, /// Gas used in transaction divided by this number is the maximum refundable amount. pub max_refund_quotient: usize, // Enable EIP-3541 rule @@ -312,6 +320,10 @@ impl Schedule { wasm: None, eip2929: false, eip2930: false, + eip1559: false, + eip1559_elasticity_multiplier: 1, + eip1559_gas_limit_bump: 1, + eip3198: false, max_refund_quotient: MAX_REFUND_QUOTIENT, eip3541: false, } @@ -371,10 +383,14 @@ impl Schedule { schedule } + /// Schedule for the London fork of the Ethereum main net. pub fn new_london() -> Schedule { let mut schedule = Self::new_berlin(); - // EIP-3529 changes + schedule.eip1559 = true; + schedule.eip1559_elasticity_multiplier = 2; + schedule.eip3198 = true; + schedule.suicide_refund_gas = 0; schedule.sstore_refund_gas = EIP3529_SSTORE_CLEARS_SCHEDULE; schedule.max_refund_quotient = EIP3529_MAX_REFUND_QUOTIENT; @@ -447,6 +463,10 @@ impl Schedule { wasm: None, eip2929: false, eip2930: false, + eip1559: false, + eip1559_elasticity_multiplier: 1, + eip1559_gas_limit_bump: 1, + eip3198: false, max_refund_quotient: MAX_REFUND_QUOTIENT, eip3541: false, } diff --git a/crates/vm/vm/src/tests.rs b/crates/vm/vm/src/tests.rs index 71fdc2d23..34336da13 100644 --- a/crates/vm/vm/src/tests.rs +++ b/crates/vm/vm/src/tests.rs @@ -131,6 +131,7 @@ impl FakeExt { ext } + /// New fake externalities with London schedule rules pub fn new_london(from: Address, to: Address, builtins: &[Address]) -> Self { let mut ext = FakeExt::new_berlin(from, to, builtins); ext.schedule = Schedule::new_london();