Implement eth/64 (EIP-2364) and drop support for eth/62 (#11472)
* sync: make code friendlier to future conditional parsing * Implement eth/64 (EIP-2364) and drop support for eth/62
This commit is contained in:
parent
a49950e9c0
commit
06df521eff
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -999,18 +999,6 @@ dependencies = [
|
||||
"rustc-hex 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eip-2124"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crc",
|
||||
"ethereum-types",
|
||||
"hex-literal",
|
||||
"maplit",
|
||||
"rlp",
|
||||
"rlp-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eip-712"
|
||||
version = "0.1.1"
|
||||
@ -1631,6 +1619,7 @@ dependencies = [
|
||||
"ethcore-network",
|
||||
"ethcore-network-devp2p",
|
||||
"ethcore-private-tx",
|
||||
"ethereum-forkid",
|
||||
"ethereum-types",
|
||||
"fastmap",
|
||||
"futures",
|
||||
@ -1654,6 +1643,20 @@ dependencies = [
|
||||
"triehash-ethereum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-forkid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2f547713a9a1e69a55529f3981dbded2c59c69629c7df795bdc7deef3375f59"
|
||||
dependencies = [
|
||||
"crc",
|
||||
"ethereum-types",
|
||||
"maplit",
|
||||
"parity-util-mem",
|
||||
"rlp",
|
||||
"rlp-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-types"
|
||||
version = "0.8.0"
|
||||
@ -4798,6 +4801,7 @@ dependencies = [
|
||||
"kvdb-memorydb",
|
||||
"log",
|
||||
"machine",
|
||||
"maplit",
|
||||
"null-engine",
|
||||
"parity-bytes",
|
||||
"pod",
|
||||
|
@ -128,5 +128,4 @@ members = [
|
||||
"chainspec",
|
||||
"ethcore/wasm/run",
|
||||
"evmbin",
|
||||
"util/EIP-2124"
|
||||
]
|
||||
|
@ -28,6 +28,7 @@ keccak-hash = "0.4.0"
|
||||
kvdb-memorydb = "0.4.0"
|
||||
log = "0.4.8"
|
||||
machine = { path = "../machine" }
|
||||
maplit = "1"
|
||||
null-engine = { path = "../engines/null-engine" }
|
||||
pod = { path = "../pod" }
|
||||
rlp = "0.4.2"
|
||||
|
@ -17,7 +17,7 @@
|
||||
//! Parameters for a block chain.
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
convert::TryFrom,
|
||||
fmt,
|
||||
io::Read,
|
||||
@ -47,6 +47,7 @@ use instant_seal::{InstantSeal, InstantSealParams};
|
||||
use keccak_hash::{KECCAK_NULL_RLP, keccak};
|
||||
use log::{trace, warn};
|
||||
use machine::{executive::Executive, Machine, substate::Substate};
|
||||
use maplit::btreeset;
|
||||
use null_engine::NullEngine;
|
||||
use pod::PodState;
|
||||
use rlp::{Rlp, RlpStream};
|
||||
@ -218,6 +219,8 @@ pub struct Spec {
|
||||
pub seal_rlp: Bytes,
|
||||
/// Hardcoded synchronization. Allows the light client to immediately jump to a specific block.
|
||||
pub hardcoded_sync: Option<SpecHardcodedSync>,
|
||||
/// List of hard forks in the network.
|
||||
pub hard_forks: BTreeSet<BlockNumber>,
|
||||
/// Contract constructors to be executed on genesis.
|
||||
pub constructors: Vec<(Address, Bytes)>,
|
||||
/// May be pre-populated if we know this in advance.
|
||||
@ -280,7 +283,7 @@ fn load_from(spec_params: SpecParams, s: ethjson::spec::Spec) -> Result<Spec, Er
|
||||
|
||||
let hardcoded_sync = s.hardcoded_sync.map(Into::into);
|
||||
|
||||
let engine = Spec::engine(spec_params, s.engine, params, builtins);
|
||||
let (engine, hard_forks) = Spec::engine(spec_params, s.engine, params, builtins);
|
||||
let author = g.author;
|
||||
let timestamp = g.timestamp;
|
||||
let difficulty = g.difficulty;
|
||||
@ -317,6 +320,7 @@ fn load_from(spec_params: SpecParams, s: ethjson::spec::Spec) -> Result<Spec, Er
|
||||
timestamp,
|
||||
extra_data: g.extra_data,
|
||||
seal_rlp,
|
||||
hard_forks,
|
||||
hardcoded_sync,
|
||||
constructors,
|
||||
genesis_state,
|
||||
@ -347,12 +351,64 @@ impl Spec {
|
||||
engine_spec: ethjson::spec::Engine,
|
||||
params: CommonParams,
|
||||
builtins: BTreeMap<Address, Builtin>,
|
||||
) -> Arc<dyn Engine> {
|
||||
) -> (Arc<dyn Engine>, BTreeSet<BlockNumber>) {
|
||||
let mut hard_forks = btreeset![
|
||||
params.eip150_transition,
|
||||
params.eip160_transition,
|
||||
params.eip161abc_transition,
|
||||
params.eip161d_transition,
|
||||
params.eip98_transition,
|
||||
params.eip658_transition,
|
||||
params.eip155_transition,
|
||||
params.validate_receipts_transition,
|
||||
params.validate_chain_id_transition,
|
||||
params.eip140_transition,
|
||||
params.eip210_transition,
|
||||
params.eip211_transition,
|
||||
params.eip214_transition,
|
||||
params.eip145_transition,
|
||||
params.eip1052_transition,
|
||||
params.eip1283_transition,
|
||||
params.eip1283_disable_transition,
|
||||
params.eip1283_reenable_transition,
|
||||
params.eip1014_transition,
|
||||
params.eip1706_transition,
|
||||
params.eip1344_transition,
|
||||
params.eip1884_transition,
|
||||
params.eip2028_transition,
|
||||
params.eip2200_advance_transition,
|
||||
params.dust_protection_transition,
|
||||
params.wasm_activation_transition,
|
||||
params.kip4_transition,
|
||||
params.kip6_transition,
|
||||
params.max_code_size_transition,
|
||||
params.transaction_permission_contract_transition,
|
||||
];
|
||||
// BUG: Rinkeby has homestead transition at block 1 but we can't reflect that in specs for non-Ethash networks
|
||||
if params.network_id == 0x4 {
|
||||
hard_forks.insert(1);
|
||||
}
|
||||
|
||||
let machine = Self::machine(&engine_spec, params, builtins);
|
||||
|
||||
match engine_spec {
|
||||
let engine: Arc<dyn Engine> = match engine_spec {
|
||||
ethjson::spec::Engine::Null(null) => Arc::new(NullEngine::new(null.params.into(), machine)),
|
||||
ethjson::spec::Engine::Ethash(ethash) => Arc::new(Ethash::new(spec_params.cache_dir, ethash.params.into(), machine, spec_params.optimization_setting)),
|
||||
ethjson::spec::Engine::Ethash(ethash) => {
|
||||
// Specific transitions for Ethash-based networks
|
||||
for block in &[ethash.params.homestead_transition, ethash.params.dao_hardfork_transition] {
|
||||
if let Some(block) = *block {
|
||||
hard_forks.insert(block.into());
|
||||
}
|
||||
}
|
||||
|
||||
// Ethereum's difficulty bomb delay is a fork too
|
||||
if let Some(delays) = ðash.params.difficulty_bomb_delays {
|
||||
for delay in delays.keys().copied() {
|
||||
hard_forks.insert(delay.into());
|
||||
}
|
||||
}
|
||||
Arc::new(Ethash::new(spec_params.cache_dir, ethash.params.into(), machine, spec_params.optimization_setting))
|
||||
},
|
||||
ethjson::spec::Engine::InstantSeal(Some(instant_seal)) => Arc::new(InstantSeal::new(instant_seal.params.into(), machine)),
|
||||
ethjson::spec::Engine::InstantSeal(None) => Arc::new(InstantSeal::new(InstantSealParams::default(), machine)),
|
||||
ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(basic_authority.params.into(), machine)),
|
||||
@ -360,7 +416,12 @@ impl Spec {
|
||||
.expect("Failed to start Clique consensus engine."),
|
||||
ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(authority_round.params.into(), machine)
|
||||
.expect("Failed to start AuthorityRound consensus engine."),
|
||||
}
|
||||
};
|
||||
|
||||
// Dummy value is a filler for non-existent transitions
|
||||
hard_forks.remove(&BlockNumber::max_value());
|
||||
|
||||
(engine, hard_forks)
|
||||
}
|
||||
|
||||
/// Get common blockchain parameters.
|
||||
|
@ -16,6 +16,7 @@ devp2p = { package = "ethcore-network-devp2p", path = "../../util/network-devp2p
|
||||
enum_primitive = "0.1.1"
|
||||
ethcore-io = { path = "../../util/io" }
|
||||
ethcore-private-tx = { path = "../private-tx" }
|
||||
ethereum-forkid = "0.1"
|
||||
ethereum-types = "0.8.0"
|
||||
fastmap = { path = "../../util/fastmap" }
|
||||
futures = "0.1"
|
||||
|
@ -15,7 +15,7 @@
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::{Arc, mpsc, atomic};
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::collections::{BTreeSet, HashMap, BTreeMap};
|
||||
use std::io;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::time::Duration;
|
||||
@ -27,10 +27,11 @@ use crate::sync_io::NetSyncIo;
|
||||
use crate::light_sync::{self, SyncInfo};
|
||||
use crate::private_tx::PrivateTxHandler;
|
||||
use crate::chain::{
|
||||
fork_filter::ForkFilterApi,
|
||||
sync_packet::SyncPacket::{PrivateTransactionPacket, SignedPrivateTransactionPacket},
|
||||
ChainSyncApi, SyncState, SyncStatus as EthSyncStatus, ETH_PROTOCOL_VERSION_62,
|
||||
ETH_PROTOCOL_VERSION_63, PAR_PROTOCOL_VERSION_1, PAR_PROTOCOL_VERSION_2,
|
||||
PAR_PROTOCOL_VERSION_3, PAR_PROTOCOL_VERSION_4,
|
||||
ChainSyncApi, SyncState, SyncStatus as EthSyncStatus,
|
||||
ETH_PROTOCOL_VERSION_63, ETH_PROTOCOL_VERSION_64,
|
||||
PAR_PROTOCOL_VERSION_1, PAR_PROTOCOL_VERSION_2, PAR_PROTOCOL_VERSION_3, PAR_PROTOCOL_VERSION_4,
|
||||
};
|
||||
|
||||
use bytes::Bytes;
|
||||
@ -268,6 +269,8 @@ pub struct Params {
|
||||
pub executor: Executor,
|
||||
/// Blockchain client.
|
||||
pub chain: Arc<dyn BlockChainClient>,
|
||||
/// Forks.
|
||||
pub forks: BTreeSet<BlockNumber>,
|
||||
/// Snapshot service.
|
||||
pub snapshot_service: Arc<dyn SnapshotService>,
|
||||
/// Private tx service.
|
||||
@ -348,10 +351,13 @@ impl EthSync {
|
||||
})
|
||||
};
|
||||
|
||||
let fork_filter = ForkFilterApi::new(&*params.chain, params.forks);
|
||||
|
||||
let (priority_tasks_tx, priority_tasks_rx) = mpsc::channel();
|
||||
let sync = ChainSyncApi::new(
|
||||
params.config,
|
||||
&*params.chain,
|
||||
fork_filter,
|
||||
params.private_tx_handler.as_ref().cloned(),
|
||||
priority_tasks_rx,
|
||||
);
|
||||
@ -605,7 +611,7 @@ impl ChainNotify for EthSync {
|
||||
_ => {},
|
||||
}
|
||||
|
||||
self.network.register_protocol(self.eth_handler.clone(), self.subprotocol_name, &[ETH_PROTOCOL_VERSION_62, ETH_PROTOCOL_VERSION_63])
|
||||
self.network.register_protocol(self.eth_handler.clone(), self.subprotocol_name, &[ETH_PROTOCOL_VERSION_63, ETH_PROTOCOL_VERSION_64])
|
||||
.unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e));
|
||||
// register the warp sync subprotocol
|
||||
self.network.register_protocol(self.eth_handler.clone(), WARP_SYNC_PROTOCOL_ID, &[PAR_PROTOCOL_VERSION_1, PAR_PROTOCOL_VERSION_2, PAR_PROTOCOL_VERSION_3, PAR_PROTOCOL_VERSION_4])
|
||||
|
143
ethcore/sync/src/chain/fork_filter.rs
Normal file
143
ethcore/sync/src/chain/fork_filter.rs
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This module contains a wrapper that connects this codebase with `ethereum-forkid` crate which provides `FORK_ID`
|
||||
//! to support Ethereum network protocol, version 64 and above.
|
||||
|
||||
// Re-export ethereum-forkid crate contents here.
|
||||
pub use ethereum_forkid::{BlockNumber, ForkId, RejectReason};
|
||||
|
||||
use client_traits::ChainInfo;
|
||||
use ethereum_forkid::ForkFilter;
|
||||
use parity_util_mem::MallocSizeOf;
|
||||
|
||||
/// Wrapper around fork filter that provides integration with `ForkFilter`.
|
||||
#[derive(MallocSizeOf)]
|
||||
pub struct ForkFilterApi {
|
||||
inner: ForkFilter,
|
||||
}
|
||||
|
||||
impl ForkFilterApi {
|
||||
/// Create `ForkFilterApi` from `ChainInfo` and an `Iterator` over the hard forks.
|
||||
pub fn new<C: ?Sized + ChainInfo, I: IntoIterator<Item = BlockNumber>>(client: &C, forks: I) -> Self {
|
||||
let chain_info = client.chain_info();
|
||||
Self {
|
||||
inner: ForkFilter::new(chain_info.best_block_number, chain_info.genesis_hash, forks),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Dummy version of ForkFilterApi with no forks.
|
||||
pub fn new_dummy<C: ?Sized + ChainInfo>(client: &C) -> Self {
|
||||
let chain_info = client.chain_info();
|
||||
Self {
|
||||
inner: ForkFilter::new(chain_info.best_block_number, chain_info.genesis_hash, vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_head<C: ?Sized + ChainInfo>(&mut self, client: &C) {
|
||||
self.inner.set_head(client.chain_info().best_block_number);
|
||||
}
|
||||
|
||||
/// Wrapper for `ForkFilter::current`
|
||||
pub fn current<C: ?Sized + ChainInfo>(&mut self, client: &C) -> ForkId {
|
||||
self.update_head(client);
|
||||
self.inner.current()
|
||||
}
|
||||
|
||||
/// Wrapper for `ForkFilter::is_compatible`
|
||||
pub fn is_compatible<C: ?Sized + ChainInfo>(&mut self, client: &C, fork_id: ForkId) -> Result<(), RejectReason> {
|
||||
self.update_head(client);
|
||||
self.inner.is_compatible(fork_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use spec::Spec;
|
||||
use ethcore::test_helpers::TestBlockChainClient;
|
||||
|
||||
fn test_spec<F: Fn() -> Spec>(spec_builder: F, forks: Vec<BlockNumber>) {
|
||||
let spec = (spec_builder)();
|
||||
let genesis_hash = spec.genesis_header().hash();
|
||||
let spec_forks = spec.hard_forks.clone();
|
||||
let client = TestBlockChainClient::new_with_spec(spec);
|
||||
|
||||
assert_eq!(
|
||||
ForkFilterApi::new(&client, spec_forks).inner,
|
||||
ForkFilter::new(0, genesis_hash, forks)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ethereum_spec() {
|
||||
test_spec(
|
||||
|| spec::new_foundation(&String::new()),
|
||||
vec![
|
||||
1_150_000,
|
||||
1_920_000,
|
||||
2_463_000,
|
||||
2_675_000,
|
||||
4_370_000,
|
||||
7_280_000,
|
||||
9_069_000,
|
||||
9_200_000,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ropsten_spec() {
|
||||
test_spec(
|
||||
|| spec::new_ropsten(&String::new()),
|
||||
vec![
|
||||
10,
|
||||
1_700_000,
|
||||
4_230_000,
|
||||
4_939_394,
|
||||
6_485_846,
|
||||
7_117_117,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rinkeby_spec() {
|
||||
test_spec(
|
||||
|| spec::new_rinkeby(&String::new()),
|
||||
vec![
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
1_035_301,
|
||||
3_660_663,
|
||||
4_321_234,
|
||||
5_435_345,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goerli_spec() {
|
||||
test_spec(
|
||||
|| spec::new_goerli(&String::new()),
|
||||
vec![
|
||||
1_561_651,
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ use std::{mem, cmp};
|
||||
use crate::{
|
||||
snapshot_sync::ChunkType,
|
||||
sync_io::SyncIo,
|
||||
api::WARP_SYNC_PROTOCOL_ID,
|
||||
api::{ETH_PROTOCOL, WARP_SYNC_PROTOCOL_ID},
|
||||
block_sync::{BlockDownloaderImportError as DownloaderImportError, DownloadAction},
|
||||
chain::{
|
||||
sync_packet::{
|
||||
@ -32,7 +32,7 @@ use crate::{
|
||||
}
|
||||
},
|
||||
BlockSet, ChainSync, ForkConfirmation, PacketDecodeError, PeerAsking, PeerInfo, SyncRequester,
|
||||
SyncState, ETH_PROTOCOL_VERSION_62, ETH_PROTOCOL_VERSION_63, MAX_NEW_BLOCK_AGE, MAX_NEW_HASHES,
|
||||
SyncState, ETH_PROTOCOL_VERSION_63, ETH_PROTOCOL_VERSION_64, MAX_NEW_BLOCK_AGE, MAX_NEW_HASHES,
|
||||
PAR_PROTOCOL_VERSION_1, PAR_PROTOCOL_VERSION_3, PAR_PROTOCOL_VERSION_4,
|
||||
}
|
||||
};
|
||||
@ -562,17 +562,34 @@ impl SyncHandler {
|
||||
|
||||
/// Called by peer to report status
|
||||
fn on_peer_status(sync: &mut ChainSync, io: &mut dyn SyncIo, peer_id: PeerId, r: &Rlp) -> Result<(), DownloaderImportError> {
|
||||
let mut r = r.iter();
|
||||
sync.handshaking_peers.remove(&peer_id);
|
||||
let protocol_version: u8 = r.val_at(0)?;
|
||||
let protocol_version: u8 = r.next().ok_or(rlp::DecoderError::RlpIsTooShort)?.as_val()?;
|
||||
let eth_protocol_version = io.protocol_version(Ð_PROTOCOL, peer_id);
|
||||
let warp_protocol_version = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer_id);
|
||||
let warp_protocol = warp_protocol_version != 0;
|
||||
let private_tx_protocol = warp_protocol_version >= PAR_PROTOCOL_VERSION_3.0;
|
||||
let network_id = r.next().ok_or(rlp::DecoderError::RlpIsTooShort)?.as_val()?;
|
||||
let difficulty = Some(r.next().ok_or(rlp::DecoderError::RlpIsTooShort)?.as_val()?);
|
||||
let latest_hash = r.next().ok_or(rlp::DecoderError::RlpIsTooShort)?.as_val()?;
|
||||
let genesis = r.next().ok_or(rlp::DecoderError::RlpIsTooShort)?.as_val()?;
|
||||
let forkid_validation_error = {
|
||||
if eth_protocol_version >= ETH_PROTOCOL_VERSION_64.0 {
|
||||
let fork_id = r.next().ok_or(rlp::DecoderError::RlpIsTooShort)?.as_val()?;
|
||||
sync.fork_filter.is_compatible(io.chain(), fork_id).err().map(|e| (fork_id, e))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let snapshot_hash = if warp_protocol { Some(r.next().ok_or(rlp::DecoderError::RlpIsTooShort)?.as_val()?) } else { None };
|
||||
let snapshot_number = if warp_protocol { Some(r.next().ok_or(rlp::DecoderError::RlpIsTooShort)?.as_val()?) } else { None };
|
||||
let private_tx_enabled = if private_tx_protocol { r.next().and_then(|v| v.as_val().ok()).unwrap_or(false) } else { false };
|
||||
let peer = PeerInfo {
|
||||
protocol_version,
|
||||
network_id: r.val_at(1)?,
|
||||
difficulty: Some(r.val_at(2)?),
|
||||
latest_hash: r.val_at(3)?,
|
||||
genesis: r.val_at(4)?,
|
||||
network_id,
|
||||
difficulty,
|
||||
latest_hash,
|
||||
genesis,
|
||||
asking: PeerAsking::Nothing,
|
||||
asking_blocks: Vec::new(),
|
||||
asking_hash: None,
|
||||
@ -583,10 +600,10 @@ impl SyncHandler {
|
||||
expired: false,
|
||||
confirmation: if sync.fork_block.is_none() { ForkConfirmation::Confirmed } else { ForkConfirmation::Unconfirmed },
|
||||
asking_snapshot_data: None,
|
||||
snapshot_hash: if warp_protocol { Some(r.val_at(5)?) } else { None },
|
||||
snapshot_number: if warp_protocol { Some(r.val_at(6)?) } else { None },
|
||||
snapshot_hash,
|
||||
snapshot_number,
|
||||
block_set: None,
|
||||
private_tx_enabled: if private_tx_protocol { r.val_at(7).unwrap_or(false) } else { false },
|
||||
private_tx_enabled,
|
||||
client_version: ClientVersion::from(io.peer_version(peer_id)),
|
||||
};
|
||||
|
||||
@ -627,10 +644,14 @@ impl SyncHandler {
|
||||
trace!(target: "sync", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, sync.network_id, peer.network_id);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
if let Some((fork_id, reason)) = forkid_validation_error {
|
||||
trace!(target: "sync", "Peer {} incompatible fork id (fork id: {:#x}/{}, error: {:?})", peer_id, fork_id.hash.0, fork_id.next, reason);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
|
||||
if false
|
||||
|| (warp_protocol && (peer.protocol_version < PAR_PROTOCOL_VERSION_1.0 || peer.protocol_version > PAR_PROTOCOL_VERSION_4.0))
|
||||
|| (!warp_protocol && (peer.protocol_version < ETH_PROTOCOL_VERSION_62.0 || peer.protocol_version > ETH_PROTOCOL_VERSION_63.0))
|
||||
|| (!warp_protocol && (peer.protocol_version < ETH_PROTOCOL_VERSION_63.0 || peer.protocol_version > ETH_PROTOCOL_VERSION_64.0))
|
||||
{
|
||||
trace!(target: "sync", "Peer {} unsupported eth protocol ({})", peer_id, peer.protocol_version);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
|
@ -92,6 +92,7 @@ mod propagator;
|
||||
mod requester;
|
||||
mod supplier;
|
||||
|
||||
pub mod fork_filter;
|
||||
pub mod sync_packet;
|
||||
|
||||
use std::sync::{Arc, mpsc};
|
||||
@ -100,9 +101,10 @@ use std::cmp;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::{
|
||||
EthProtocolInfo as PeerInfoDigest, PriorityTask, SyncConfig, WarpSync, WARP_SYNC_PROTOCOL_ID,
|
||||
ETH_PROTOCOL, EthProtocolInfo as PeerInfoDigest, PriorityTask, SyncConfig, WarpSync, WARP_SYNC_PROTOCOL_ID,
|
||||
api::{Notification, PRIORITY_TIMER_INTERVAL},
|
||||
block_sync::{BlockDownloader, DownloadAction},
|
||||
chain::fork_filter::ForkFilterApi,
|
||||
sync_io::SyncIo,
|
||||
snapshot_sync::Snapshot,
|
||||
transactions_stats::{TransactionsStats, Stats as TransactionStats},
|
||||
@ -147,10 +149,10 @@ malloc_size_of_is_0!(PeerInfo);
|
||||
|
||||
pub type PacketDecodeError = DecoderError;
|
||||
|
||||
/// 63 version of Ethereum protocol.
|
||||
/// Version 64 of the Ethereum protocol and number of packet IDs reserved by the protocol (packet count).
|
||||
pub const ETH_PROTOCOL_VERSION_64: (u8, u8) = (64, 0x11);
|
||||
/// Version 63 of the Ethereum protocol and number of packet IDs reserved by the protocol (packet count).
|
||||
pub const ETH_PROTOCOL_VERSION_63: (u8, u8) = (63, 0x11);
|
||||
/// 62 version of Ethereum protocol.
|
||||
pub const ETH_PROTOCOL_VERSION_62: (u8, u8) = (62, 0x11);
|
||||
/// 1 version of Parity protocol and the packet count.
|
||||
pub const PAR_PROTOCOL_VERSION_1: (u8, u8) = (1, 0x15);
|
||||
/// 2 version of Parity protocol (consensus messages added).
|
||||
@ -427,11 +429,12 @@ impl ChainSyncApi {
|
||||
pub fn new(
|
||||
config: SyncConfig,
|
||||
chain: &dyn BlockChainClient,
|
||||
fork_filter: ForkFilterApi,
|
||||
private_tx_handler: Option<Arc<dyn PrivateTxHandler>>,
|
||||
priority_tasks: mpsc::Receiver<PriorityTask>,
|
||||
) -> Self {
|
||||
ChainSyncApi {
|
||||
sync: RwLock::new(ChainSync::new(config, chain, private_tx_handler)),
|
||||
sync: RwLock::new(ChainSync::new(config, chain, fork_filter, private_tx_handler)),
|
||||
priority_tasks: Mutex::new(priority_tasks),
|
||||
}
|
||||
}
|
||||
@ -660,6 +663,8 @@ pub struct ChainSync {
|
||||
last_sent_block_number: BlockNumber,
|
||||
/// Network ID
|
||||
network_id: u64,
|
||||
/// Fork filter
|
||||
fork_filter: ForkFilterApi,
|
||||
/// Optional fork block to check
|
||||
fork_block: Option<(BlockNumber, H256)>,
|
||||
/// Snapshot downloader.
|
||||
@ -688,6 +693,7 @@ impl ChainSync {
|
||||
pub fn new(
|
||||
config: SyncConfig,
|
||||
chain: &dyn BlockChainClient,
|
||||
fork_filter: ForkFilterApi,
|
||||
private_tx_handler: Option<Arc<dyn PrivateTxHandler>>,
|
||||
) -> Self {
|
||||
let chain_info = chain.chain_info();
|
||||
@ -705,6 +711,7 @@ impl ChainSync {
|
||||
old_blocks: None,
|
||||
last_sent_block_number: 0,
|
||||
network_id: config.network_id,
|
||||
fork_filter,
|
||||
fork_block: config.fork_block,
|
||||
download_old_blocks: config.download_old_blocks,
|
||||
snapshot: Snapshot::new(),
|
||||
@ -723,7 +730,7 @@ impl ChainSync {
|
||||
let last_imported_number = self.new_blocks.last_imported_block_number();
|
||||
SyncStatus {
|
||||
state: self.state.clone(),
|
||||
protocol_version: ETH_PROTOCOL_VERSION_63.0,
|
||||
protocol_version: ETH_PROTOCOL_VERSION_64.0,
|
||||
network_id: self.network_id,
|
||||
start_block_number: self.starting_block,
|
||||
last_imported_block_number: Some(last_imported_number),
|
||||
@ -1240,10 +1247,11 @@ impl ChainSync {
|
||||
|
||||
/// Send Status message
|
||||
fn send_status(&mut self, io: &mut dyn SyncIo, peer: PeerId) -> Result<(), network::Error> {
|
||||
let eth_protocol_version = io.protocol_version(Ð_PROTOCOL, peer);
|
||||
let warp_protocol_version = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer);
|
||||
let warp_protocol = warp_protocol_version != 0;
|
||||
let private_tx_protocol = warp_protocol_version >= PAR_PROTOCOL_VERSION_3.0;
|
||||
let protocol = if warp_protocol { warp_protocol_version } else { ETH_PROTOCOL_VERSION_63.0 };
|
||||
let protocol = if warp_protocol { warp_protocol_version } else { eth_protocol_version };
|
||||
trace!(target: "sync", "Sending status to {}, protocol version {}", peer, protocol);
|
||||
let mut packet = RlpStream::new();
|
||||
packet.begin_unbounded_list();
|
||||
@ -1253,6 +1261,9 @@ impl ChainSync {
|
||||
packet.append(&chain.total_difficulty);
|
||||
packet.append(&chain.best_block_hash);
|
||||
packet.append(&chain.genesis_hash);
|
||||
if eth_protocol_version >= ETH_PROTOCOL_VERSION_64.0 {
|
||||
packet.append(&self.fork_filter.current(io.chain()));
|
||||
}
|
||||
if warp_protocol {
|
||||
let manifest = io.snapshot_service().manifest();
|
||||
let block_number = manifest.as_ref().map_or(0, |m| m.block_number);
|
||||
@ -1499,6 +1510,7 @@ pub mod tests {
|
||||
|
||||
use crate::{
|
||||
api::SyncConfig,
|
||||
chain::ForkFilterApi,
|
||||
tests::{helpers::TestIo, snapshot::TestSnapshotService},
|
||||
};
|
||||
|
||||
@ -1594,8 +1606,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
pub fn dummy_sync_with_peer(peer_latest_hash: H256, client: &dyn BlockChainClient) -> ChainSync {
|
||||
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), client, None,);
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), client, ForkFilterApi::new_dummy(client), None,);
|
||||
insert_dummy_peer(&mut sync, 0, peer_latest_hash);
|
||||
sync
|
||||
}
|
||||
|
@ -338,7 +338,10 @@ mod tests {
|
||||
|
||||
use crate::{
|
||||
api::SyncConfig,
|
||||
chain::{ChainSync, ForkConfirmation, PeerAsking, PeerInfo},
|
||||
chain::{
|
||||
fork_filter::ForkFilterApi,
|
||||
ChainSync, ForkConfirmation, PeerAsking, PeerInfo
|
||||
},
|
||||
tests::{helpers::TestIo, snapshot::TestSnapshotService},
|
||||
};
|
||||
|
||||
@ -423,7 +426,7 @@ mod tests {
|
||||
client.add_blocks(2, EachBlockWith::Uncle);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let block = client.block(BlockId::Latest).unwrap().into_inner();
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), &client, None);
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), &client, ForkFilterApi::new_dummy(&client), None);
|
||||
sync.peers.insert(0,
|
||||
PeerInfo {
|
||||
// Messaging protocol
|
||||
@ -514,7 +517,7 @@ mod tests {
|
||||
client.add_blocks(100, EachBlockWith::Uncle);
|
||||
client.insert_transaction_to_queue();
|
||||
// Sync with no peers
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), &client, None);
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), &client, ForkFilterApi::new_dummy(&client), None);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None, None);
|
||||
@ -584,7 +587,7 @@ mod tests {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.insert_transaction_with_gas_price_to_queue(U256::zero());
|
||||
let block_hash = client.block_hash_delta_minus(1);
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), &client, None);
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), &client, ForkFilterApi::new_dummy(&client), None);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None, None);
|
||||
@ -614,7 +617,7 @@ mod tests {
|
||||
let tx1_hash = client.insert_transaction_to_queue();
|
||||
let tx2_hash = client.insert_transaction_with_gas_price_to_queue(U256::zero());
|
||||
let block_hash = client.block_hash_delta_minus(1);
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), &client, None);
|
||||
let mut sync = ChainSync::new(SyncConfig::default(), &client, ForkFilterApi::new_dummy(&client), None);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None, None);
|
||||
|
@ -20,11 +20,12 @@ use std::sync::Arc;
|
||||
use crate::{
|
||||
api::{SyncConfig, WARP_SYNC_PROTOCOL_ID},
|
||||
chain::{
|
||||
fork_filter::ForkFilterApi,
|
||||
sync_packet::{
|
||||
PacketInfo,
|
||||
SyncPacket::{self, PrivateTransactionPacket, SignedPrivateTransactionPacket}
|
||||
},
|
||||
ChainSync, SyncSupplier, ETH_PROTOCOL_VERSION_63, PAR_PROTOCOL_VERSION_4
|
||||
ChainSync, SyncSupplier, ETH_PROTOCOL_VERSION_64, PAR_PROTOCOL_VERSION_4
|
||||
},
|
||||
private_tx::SimplePrivateTxHandler,
|
||||
sync_io::SyncIo,
|
||||
@ -156,7 +157,7 @@ impl<'p, C> SyncIo for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p {
|
||||
}
|
||||
|
||||
fn protocol_version(&self, protocol: &ProtocolId, _peer_id: PeerId) -> u8 {
|
||||
if protocol == &WARP_SYNC_PROTOCOL_ID { PAR_PROTOCOL_VERSION_4.0 } else { ETH_PROTOCOL_VERSION_63.0 }
|
||||
if protocol == &WARP_SYNC_PROTOCOL_ID { PAR_PROTOCOL_VERSION_4.0 } else { ETH_PROTOCOL_VERSION_64.0 }
|
||||
}
|
||||
|
||||
fn is_expired(&self) -> bool {
|
||||
@ -380,7 +381,7 @@ impl TestNet<EthPeer<TestBlockChainClient>> {
|
||||
let chain = TestBlockChainClient::new();
|
||||
let ss = Arc::new(TestSnapshotService::new());
|
||||
let private_tx_handler = Arc::new(SimplePrivateTxHandler::default());
|
||||
let sync = ChainSync::new(config.clone(), &chain, Some(private_tx_handler.clone()));
|
||||
let sync = ChainSync::new(config.clone(), &chain, ForkFilterApi::new_dummy(&chain), Some(private_tx_handler.clone()));
|
||||
net.peers.push(Arc::new(EthPeer {
|
||||
sync: RwLock::new(sync),
|
||||
snapshot_service: ss,
|
||||
@ -431,10 +432,11 @@ impl TestNet<EthPeer<EthcoreClient>> {
|
||||
miner.clone(),
|
||||
channel.clone()
|
||||
).unwrap();
|
||||
let fork_filter = ForkFilterApi::new(&*client, spec.hard_forks.clone());
|
||||
|
||||
let private_tx_handler = Arc::new(SimplePrivateTxHandler::default());
|
||||
let ss = Arc::new(TestSnapshotService::new());
|
||||
let sync = ChainSync::new(config, &*client, Some(private_tx_handler.clone()));
|
||||
let sync = ChainSync::new(config, &*client, fork_filter, Some(private_tx_handler.clone()));
|
||||
let peer = Arc::new(EthPeer {
|
||||
sync: RwLock::new(sync),
|
||||
snapshot_service: ss,
|
||||
|
@ -14,9 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::{Arc, mpsc};
|
||||
use std::{collections::BTreeSet, sync::{Arc, mpsc}};
|
||||
|
||||
use client_traits::{BlockChainClient, ChainNotify};
|
||||
use types::BlockNumber;
|
||||
use sync::{self, SyncConfig, NetworkConfiguration, Params, ConnectionFilter};
|
||||
use snapshot::SnapshotService;
|
||||
use ethcore_private_tx::PrivateStateDB;
|
||||
@ -38,6 +39,7 @@ pub fn sync(
|
||||
executor: Executor,
|
||||
network_config: NetworkConfiguration,
|
||||
chain: Arc<dyn BlockChainClient>,
|
||||
forks: BTreeSet<BlockNumber>,
|
||||
snapshot_service: Arc<dyn SnapshotService>,
|
||||
private_tx_handler: Option<Arc<dyn PrivateTxHandler>>,
|
||||
private_state: Option<Arc<PrivateStateDB>>,
|
||||
@ -49,6 +51,7 @@ pub fn sync(
|
||||
config,
|
||||
executor,
|
||||
chain,
|
||||
forks,
|
||||
provider,
|
||||
snapshot_service,
|
||||
private_tx_handler,
|
||||
|
@ -576,6 +576,7 @@ fn execute_impl<Cr, Rr>(
|
||||
cmd.private_encryptor_conf,
|
||||
).map_err(|e| format!("Client service error: {:?}", e))?;
|
||||
|
||||
let forks = spec.hard_forks.clone();
|
||||
let connection_filter_address = spec.params().node_permission_contract;
|
||||
// drop the spec to free up genesis state.
|
||||
drop(spec);
|
||||
@ -651,6 +652,7 @@ fn execute_impl<Cr, Rr>(
|
||||
runtime.executor(),
|
||||
net_conf.clone().into(),
|
||||
client.clone(),
|
||||
forks,
|
||||
snapshot_service.clone(),
|
||||
private_tx_sync,
|
||||
private_state,
|
||||
|
@ -46,7 +46,7 @@ impl TestSyncProvider {
|
||||
status: RwLock::new(SyncStatus {
|
||||
state: SyncState::Idle,
|
||||
network_id: config.network_id,
|
||||
protocol_version: 63,
|
||||
protocol_version: 64,
|
||||
start_block_number: 0,
|
||||
last_imported_block_number: None,
|
||||
highest_block_number: None,
|
||||
@ -82,11 +82,11 @@ impl SyncProvider for TestSyncProvider {
|
||||
PeerInfo {
|
||||
id: Some("node1".to_owned()),
|
||||
client_version: ClientVersion::from("Parity-Ethereum/1/v2.4.0/linux/rustc"),
|
||||
capabilities: vec!["eth/62".to_owned(), "eth/63".to_owned()],
|
||||
capabilities: vec!["eth/63".to_owned(), "eth/64".to_owned()],
|
||||
remote_address: "127.0.0.1:7777".to_owned(),
|
||||
local_address: "127.0.0.1:8888".to_owned(),
|
||||
eth_info: Some(EthProtocolInfo {
|
||||
version: 62,
|
||||
version: 63,
|
||||
difficulty: Some(40.into()),
|
||||
head: H256::from_low_u64_be(50),
|
||||
}),
|
||||
@ -95,11 +95,11 @@ impl SyncProvider for TestSyncProvider {
|
||||
PeerInfo {
|
||||
id: None,
|
||||
client_version: ClientVersion::from("Parity-Ethereum/2/v2.4.0/linux/rustc"),
|
||||
capabilities: vec!["eth/63".to_owned(), "eth/64".to_owned()],
|
||||
capabilities: vec!["eth/64".to_owned(), "eth/65".to_owned()],
|
||||
remote_address: "Handshake".to_owned(),
|
||||
local_address: "127.0.0.1:3333".to_owned(),
|
||||
eth_info: Some(EthProtocolInfo {
|
||||
version: 64,
|
||||
version: 65,
|
||||
difficulty: None,
|
||||
head: H256::from_low_u64_be(60),
|
||||
}),
|
||||
|
@ -125,7 +125,7 @@ impl EthTester {
|
||||
#[test]
|
||||
fn rpc_eth_protocol_version() {
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "eth_protocolVersion", "params": [], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":"63","id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":"64","id":1}"#;
|
||||
|
||||
assert_eq!(EthTester::default().io.handle_request_sync(request), Some(response.to_owned()));
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ fn rpc_parity_net_peers() {
|
||||
let io = deps.default_client();
|
||||
|
||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_netPeers", "params":[], "id": 1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":{"active":0,"connected":120,"max":50,"peers":[{"caps":["eth/62","eth/63"],"id":"node1","name":{"ParityClient":{"can_handle_large_requests":true,"compiler":"rustc","identity":"1","name":"Parity-Ethereum","os":"linux","semver":"2.4.0"}},"network":{"localAddress":"127.0.0.1:8888","remoteAddress":"127.0.0.1:7777"},"protocols":{"eth":{"difficulty":"0x28","head":"0000000000000000000000000000000000000000000000000000000000000032","version":62},"pip":null}},{"caps":["eth/63","eth/64"],"id":null,"name":{"ParityClient":{"can_handle_large_requests":true,"compiler":"rustc","identity":"2","name":"Parity-Ethereum","os":"linux","semver":"2.4.0"}},"network":{"localAddress":"127.0.0.1:3333","remoteAddress":"Handshake"},"protocols":{"eth":{"difficulty":null,"head":"000000000000000000000000000000000000000000000000000000000000003c","version":64},"pip":null}}]},"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","result":{"active":0,"connected":120,"max":50,"peers":[{"caps":["eth/63","eth/64"],"id":"node1","name":{"ParityClient":{"can_handle_large_requests":true,"compiler":"rustc","identity":"1","name":"Parity-Ethereum","os":"linux","semver":"2.4.0"}},"network":{"localAddress":"127.0.0.1:8888","remoteAddress":"127.0.0.1:7777"},"protocols":{"eth":{"difficulty":"0x28","head":"0000000000000000000000000000000000000000000000000000000000000032","version":63},"pip":null}},{"caps":["eth/64","eth/65"],"id":null,"name":{"ParityClient":{"can_handle_large_requests":true,"compiler":"rustc","identity":"2","name":"Parity-Ethereum","os":"linux","semver":"2.4.0"}},"network":{"localAddress":"127.0.0.1:3333","remoteAddress":"Handshake"},"protocols":{"eth":{"difficulty":null,"head":"000000000000000000000000000000000000000000000000000000000000003c","version":65},"pip":null}}]},"id":1}"#;
|
||||
|
||||
assert_eq!(io.handle_request_sync(request), Some(response.to_owned()));
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "eip-2124"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
repository = "https://github.com/paritytech/parity-ethereum"
|
||||
documentation = "https://docs.rs/eip-2124"
|
||||
readme = "README.md"
|
||||
description = "EIP-2124 Fork ID implementation"
|
||||
keywords = ["eip-2124", "eip"]
|
||||
license = "GPL-3.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
crc = "1"
|
||||
ethereum-types = "0.8.0"
|
||||
maplit = "1"
|
||||
rlp = "0.4"
|
||||
rlp-derive = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2"
|
@ -1,328 +0,0 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! EIP-2124 implementation based on <https://eips.ethereum.org/EIPS/eip-2124>.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::pedantic,
|
||||
clippy::nursery,
|
||||
)]
|
||||
|
||||
use crc::crc32;
|
||||
use ethereum_types::H256;
|
||||
use maplit::btreemap;
|
||||
use rlp::{DecoderError, Rlp, RlpStream};
|
||||
use rlp_derive::{RlpDecodable, RlpEncodable};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// Block number.
|
||||
pub type BlockNumber = u64;
|
||||
|
||||
/// `CRC32` hash of all previous forks starting from genesis block.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ForkHash(pub u32);
|
||||
|
||||
impl rlp::Encodable for ForkHash {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
s.encoder().encode_value(&self.0.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl rlp::Decodable for ForkHash {
|
||||
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
|
||||
rlp.decoder().decode_value(|b| {
|
||||
if b.len() != 4 {
|
||||
return Err(DecoderError::RlpInvalidLength);
|
||||
}
|
||||
|
||||
let mut blob = [0; 4];
|
||||
blob.copy_from_slice(&b[..]);
|
||||
|
||||
Ok(Self(u32::from_be_bytes(blob)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<H256> for ForkHash {
|
||||
fn from(genesis: H256) -> Self {
|
||||
Self(crc32::checksum_ieee(&genesis[..]))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<BlockNumber> for ForkHash {
|
||||
fn add_assign(&mut self, height: BlockNumber) {
|
||||
let blob = height.to_be_bytes();
|
||||
self.0 = crc32::update(self.0, &crc32::IEEE_TABLE, &blob)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<BlockNumber> for ForkHash {
|
||||
type Output = Self;
|
||||
fn add(mut self, height: BlockNumber) -> Self {
|
||||
self += height;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A fork identifier as defined by EIP-2124.
|
||||
/// Serves as the chain compatibility identifier.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable)]
|
||||
pub struct ForkId {
|
||||
/// CRC32 checksum of the all fork blocks from genesis.
|
||||
pub hash: ForkHash,
|
||||
/// Next upcoming fork block number, 0 if not yet known.
|
||||
pub next: BlockNumber
|
||||
}
|
||||
|
||||
/// Reason for rejecting provided `ForkId`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum RejectReason {
|
||||
/// Remote node is outdated and needs a software update.
|
||||
RemoteStale,
|
||||
/// Local node is on an incompatible chain or needs a sofwtare update.
|
||||
LocalIncompatibleOrStale,
|
||||
}
|
||||
|
||||
/// Filter that describes the state of blockchain and can be used to check incoming `ForkId`s for compatibility.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ForkFilter {
|
||||
/// Blockchain head
|
||||
pub head: BlockNumber,
|
||||
past_forks: BTreeMap<BlockNumber, ForkHash>,
|
||||
next_forks: BTreeSet<BlockNumber>,
|
||||
}
|
||||
|
||||
impl ForkFilter {
|
||||
/// Create the filter from provided head, genesis block hash, past forks and expected future forks.
|
||||
pub fn new<PF, NF>(head: BlockNumber, genesis: H256, past_forks: PF, next_forks: NF) -> Self
|
||||
where
|
||||
PF: IntoIterator<Item = BlockNumber>,
|
||||
NF: IntoIterator<Item = BlockNumber>,
|
||||
{
|
||||
let genesis_fork_hash = ForkHash::from(genesis);
|
||||
Self {
|
||||
head,
|
||||
past_forks: past_forks.into_iter().fold((btreemap! { 0 => genesis_fork_hash }, genesis_fork_hash), |(mut acc, base_hash), block| {
|
||||
let fork_hash = base_hash + block;
|
||||
acc.insert(block, fork_hash);
|
||||
(acc, fork_hash)
|
||||
}).0,
|
||||
next_forks: next_forks.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_fork_hash(&self) -> ForkHash {
|
||||
*self.past_forks.values().next_back().expect("there is always at least one - genesis - fork hash; qed")
|
||||
}
|
||||
|
||||
fn future_fork_hashes(&self) -> Vec<ForkHash> {
|
||||
self.next_forks.iter().fold((Vec::new(), self.current_fork_hash()), |(mut acc, hash), fork| {
|
||||
let next = hash + *fork;
|
||||
acc.push(next);
|
||||
(acc, next)
|
||||
}).0
|
||||
}
|
||||
|
||||
/// Insert a new past fork
|
||||
pub fn insert_past_fork(&mut self, height: BlockNumber) {
|
||||
self.past_forks.insert(height, self.current_fork_hash() + height);
|
||||
}
|
||||
|
||||
/// Insert a new upcoming fork
|
||||
pub fn insert_next_fork(&mut self, height: BlockNumber) {
|
||||
self.next_forks.insert(height);
|
||||
}
|
||||
|
||||
/// Mark an upcoming fork as already happened and immutable.
|
||||
/// Returns `false` if no such fork existed and the call was a no-op.
|
||||
pub fn promote_next_fork(&mut self, height: BlockNumber) -> bool {
|
||||
let promoted = self.next_forks.remove(&height);
|
||||
if promoted {
|
||||
self.insert_past_fork(height);
|
||||
}
|
||||
promoted
|
||||
}
|
||||
|
||||
/// Check whether the provided `ForkId` is compatible based on the validation rules in `EIP-2124`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a `RejectReason` if the `ForkId` is not compatible.
|
||||
pub fn is_valid(&self, fork_id: ForkId) -> Result<(), RejectReason> {
|
||||
// 1) If local and remote FORK_HASH matches...
|
||||
if self.current_fork_hash() == fork_id.hash {
|
||||
if fork_id.next == 0 {
|
||||
// 1b) No remotely announced fork, connect.
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
//... compare local head to FORK_NEXT.
|
||||
if self.head >= fork_id.next {
|
||||
// 1a) A remotely announced but remotely not passed block is already passed locally, disconnect,
|
||||
// since the chains are incompatible.
|
||||
return Err(RejectReason::LocalIncompatibleOrStale)
|
||||
} else {
|
||||
// 1b) Remotely announced fork not yet passed locally, connect.
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// 2) If the remote FORK_HASH is a subset of the local past forks...
|
||||
let mut it = self.past_forks.iter();
|
||||
while let Some((_, hash)) = it.next() {
|
||||
if *hash == fork_id.hash {
|
||||
// ...and the remote FORK_NEXT matches with the locally following fork block number, connect.
|
||||
if let Some((actual_fork_block, _)) = it.next() {
|
||||
if *actual_fork_block == fork_id.next {
|
||||
return Ok(())
|
||||
} else {
|
||||
return Err(RejectReason::RemoteStale);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 3) If the remote FORK_HASH is a superset of the local past forks and can be completed with locally known future forks, connect.
|
||||
for future_fork_hash in self.future_fork_hashes() {
|
||||
if future_fork_hash == fork_id.hash {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Reject in all other cases.
|
||||
Err(RejectReason::LocalIncompatibleOrStale)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use hex_literal::hex;
|
||||
|
||||
const GENESIS_HASH: &str = "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3";
|
||||
const BYZANTIUM_FORK_HEIGHT: BlockNumber = 4370000;
|
||||
const PETERSBURG_FORK_HEIGHT: BlockNumber = 7280000;
|
||||
|
||||
// EIP test vectors.
|
||||
|
||||
#[test]
|
||||
fn test_forkhash() {
|
||||
let mut fork_hash = ForkHash::from(GENESIS_HASH.parse::<H256>().unwrap());
|
||||
assert_eq!(fork_hash.0, 0xfc64ec04);
|
||||
|
||||
fork_hash += 1150000;
|
||||
assert_eq!(fork_hash.0, 0x97c2c34c);
|
||||
|
||||
fork_hash += 1920000;
|
||||
assert_eq!(fork_hash.0, 0x91d1f948);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compatibility_check() {
|
||||
let spurious_filter = ForkFilter::new(
|
||||
4369999,
|
||||
GENESIS_HASH.parse().unwrap(),
|
||||
vec![1150000, 1920000, 2463000, 2675000],
|
||||
vec![BYZANTIUM_FORK_HEIGHT]
|
||||
);
|
||||
let mut byzantium_filter = spurious_filter.clone();
|
||||
byzantium_filter.promote_next_fork(BYZANTIUM_FORK_HEIGHT);
|
||||
byzantium_filter.insert_next_fork(PETERSBURG_FORK_HEIGHT);
|
||||
byzantium_filter.head = 7279999;
|
||||
|
||||
let mut petersburg_filter = byzantium_filter.clone();
|
||||
petersburg_filter.promote_next_fork(PETERSBURG_FORK_HEIGHT);
|
||||
petersburg_filter.head = 7987396;
|
||||
|
||||
// Local is mainnet Petersburg, remote announces the same. No future fork is announced.
|
||||
assert_eq!(petersburg_filter.is_valid(ForkId { hash: ForkHash(0x668db0af), next: 0 }), Ok(()));
|
||||
|
||||
// Local is mainnet Petersburg, remote announces the same. Remote also announces a next fork
|
||||
// at block 0xffffffff, but that is uncertain.
|
||||
assert_eq!(petersburg_filter.is_valid(ForkId { hash: ForkHash(0x668db0af), next: BlockNumber::max_value() }), Ok(()));
|
||||
|
||||
// Local is mainnet currently in Byzantium only (so it's aware of Petersburg),remote announces
|
||||
// also Byzantium, but it's not yet aware of Petersburg (e.g. non updated node before the fork).
|
||||
// In this case we don't know if Petersburg passed yet or not.
|
||||
assert_eq!(byzantium_filter.is_valid(ForkId { hash: ForkHash(0xa00bc324), next: 0 }), Ok(()));
|
||||
|
||||
// Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces
|
||||
// also Byzantium, and it's also aware of Petersburg (e.g. updated node before the fork). We
|
||||
// don't know if Petersburg passed yet (will pass) or not.
|
||||
assert_eq!(byzantium_filter.is_valid(ForkId { hash: ForkHash(0xa00bc324), next: PETERSBURG_FORK_HEIGHT }), Ok(()));
|
||||
|
||||
// Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces
|
||||
// also Byzantium, and it's also aware of some random fork (e.g. misconfigured Petersburg). As
|
||||
// neither forks passed at neither nodes, they may mismatch, but we still connect for now.
|
||||
assert_eq!(byzantium_filter.is_valid(ForkId { hash: ForkHash(0xa00bc324), next: BlockNumber::max_value() }), Ok(()));
|
||||
|
||||
// Local is mainnet Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote is simply out of sync, accept.
|
||||
assert_eq!(petersburg_filter.is_valid(ForkId { hash: ForkHash(0xa00bc324), next: PETERSBURG_FORK_HEIGHT }), Ok(()));
|
||||
|
||||
// Local is mainnet Petersburg, remote announces Spurious + knowledge about Byzantium. Remote
|
||||
// is definitely out of sync. It may or may not need the Petersburg update, we don't know yet.
|
||||
assert_eq!(petersburg_filter.is_valid(ForkId { hash: ForkHash(0x3edd5b10), next: 4370000 }), Ok(()));
|
||||
|
||||
// Local is mainnet Byzantium, remote announces Petersburg. Local is out of sync, accept.
|
||||
assert_eq!(byzantium_filter.is_valid(ForkId { hash: ForkHash(0x668db0af), next: 0 }), Ok(()));
|
||||
|
||||
// Local is mainnet Spurious, remote announces Byzantium, but is not aware of Petersburg. Local
|
||||
// out of sync. Local also knows about a future fork, but that is uncertain yet.
|
||||
assert_eq!(spurious_filter.is_valid(ForkId { hash: ForkHash(0xa00bc324), next: 0 }), Ok(()));
|
||||
|
||||
// Local is mainnet Petersburg. remote announces Byzantium but is not aware of further forks.
|
||||
// Remote needs software update.
|
||||
assert_eq!(petersburg_filter.is_valid(ForkId { hash: ForkHash(0xa00bc324), next: 0 }), Err(RejectReason::RemoteStale));
|
||||
|
||||
// Local is mainnet Petersburg, and isn't aware of more forks. Remote announces Petersburg +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
assert_eq!(petersburg_filter.is_valid(ForkId { hash: ForkHash(0x5cddc0e1), next: 0 }), Err(RejectReason::LocalIncompatibleOrStale));
|
||||
|
||||
// Local is mainnet Byzantium, and is aware of Petersburg. Remote announces Petersburg +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
assert_eq!(byzantium_filter.is_valid(ForkId { hash: ForkHash(0x5cddc0e1), next: 0 }), Err(RejectReason::LocalIncompatibleOrStale));
|
||||
|
||||
// Local is mainnet Petersburg, remote is Rinkeby Petersburg.
|
||||
assert_eq!(petersburg_filter.is_valid(ForkId { hash: ForkHash(0xafec6b27), next: 0 }), Err(RejectReason::LocalIncompatibleOrStale));
|
||||
|
||||
// Local is mainnet Petersburg, far in the future. Remote announces Gopherium (non existing fork)
|
||||
// at some future block 88888888, for itself, but past block for local. Local is incompatible.
|
||||
//
|
||||
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
|
||||
let mut far_away_petersburg = petersburg_filter.clone();
|
||||
far_away_petersburg.head = 88888888;
|
||||
assert_eq!(far_away_petersburg.is_valid(ForkId { hash: ForkHash(0x668db0af), next: 88888888 }), Err(RejectReason::LocalIncompatibleOrStale));
|
||||
|
||||
// Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing
|
||||
// fork) at block 7279999, before Petersburg. Local is incompatible.
|
||||
assert_eq!(byzantium_filter.is_valid(ForkId { hash: ForkHash(0xa00bc324), next: 7279999 }), Err(RejectReason::LocalIncompatibleOrStale));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_forkid_serialization() {
|
||||
assert_eq!(rlp::encode(&ForkId { hash: ForkHash(0), next: 0 }), hex!("c6840000000080"));
|
||||
assert_eq!(rlp::encode(&ForkId { hash: ForkHash(0xdeadbeef), next: 0xBADDCAFE }), hex!("ca84deadbeef84baddcafe"));
|
||||
assert_eq!(rlp::encode(&ForkId { hash: ForkHash(u32::max_value()), next: u64::max_value() }), hex!("ce84ffffffff88ffffffffffffffff"));
|
||||
|
||||
assert_eq!(rlp::decode::<ForkId>(&hex!("c6840000000080")).unwrap(), ForkId { hash: ForkHash(0), next: 0 });
|
||||
assert_eq!(rlp::decode::<ForkId>(&hex!("ca84deadbeef84baddcafe")).unwrap(), ForkId { hash: ForkHash(0xdeadbeef), next: 0xBADDCAFE });
|
||||
assert_eq!(rlp::decode::<ForkId>(&hex!("ce84ffffffff88ffffffffffffffff")).unwrap(), ForkId { hash: ForkHash(u32::max_value()), next: u64::max_value() });
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user