From 0266db46f15cc19aa46177ffbae7f4492e995aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 19 Jun 2019 14:58:24 +0300 Subject: [PATCH 01/12] EIP 2124: Zero RTT netsplit on chain mismatch --- EIPS/eip-2124.md | 258 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 EIPS/eip-2124.md diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md new file mode 100644 index 00000000000000..1b4bd1e1258ebb --- /dev/null +++ b/EIPS/eip-2124.md @@ -0,0 +1,258 @@ +--- +eip: +title: Zero RTT netsplit on chain mismatch +author: Péter Szilágyi (peterke@gmail.com) +discussions-to: https://github.com/ethereum/EIPs/issues/2125 +status: Draft +type: Standards Track +category: Networking +requires: 778 +created: 2019-05-03 +--- + +## Abstract + +There are many public and private Ethereum networks, but the discovery protocol doesn't differentiate between them. The only way to check if a peer is good or bad (same chain or not), is to establish a TCP/IP connection, wrap it with RLPx cryptography, then execute an `eth` handshake. This is an extreme cost to bear if it turns out that the remote peer is on a different network. This cost is magnified for small networks, where a lot more trial and errors are needed to find good nodes. + +Even if the peer **is** on the same network, during non-controversial consensus upgrades, not everybody updates their nodes in time (developer nodes, leftovers, etc). These stale nodes put a meaningless burden on the peer-to-peer network, since they just latch on to good nodes, but don't accept upgraded blocks. This causes valuable peer slots and bandwidth to be lost until the stale nodes finally update. This is an even more pronounced issue for test networks, where leftover nodes can linger for many months. + +This EIP proposes an enhancement to the ENR extension of the discovery protocol to detect when two nodes are incompatible and never connect them in the first place, instead of wasting resources in vain. The EIP solves a a number of issues: + + * If two nodes are on different networks, they should never even consider connecting. + * If a hard fork passes, upgraded nodes should reject non-upgraded ones, but **NOT** before. + * If two chains share the same genesis, but not forks (ETH / ETC), they should reject each other. + * Ideally the rejection should be short circuited during discovery, before the expensive RLPx handshakes. + +This EIP does not attempt to solve the clean separation of 3-way-forks! If at the same future block number, the network splits into three (non-fork, fork-A and fork-B), separating the forkers from each another will need case-by-case special handling. Not handling this keeps the proposal pragmatic, simple and also avoids making it too easy to fork off mainnet. + +## Motivation + +Peer-to-peer networking is messy and hard due to firewalls and network address translations. Generally only a small fraction of nodes have publicly routed addresses, and P2P networks rely mainly on these for relaying data for everyone else. The best way to maximize the utility of the public nodes is by ensuring their resources aren't wasted on tasks that are worthless to the network. + +By aggressively cutting off incompatible nodes from each other we can extract a lot more value from the public nodes, making the entire P2P network much more robust and reliable. Supporting this network partitioning at a discovery layer can further enhance performance as we avoid the costly crypto and latency/bandwidth hit associated with establishing a stream connection in the first place. + +## Specification + +Each node maintains the following values: + + - **`GENESIS_CHECKSUM`**: Bitwise XOR of the genesis hash split into 4-byte chunks (4 bytes). + - **`FORK_CHECKSUM`**: Bitwise XOR of all fork block numbers that the local chain already passed (4 bytes). + - If multiple forks are applied at the same block, the block number should be XOR-ed only once. + - **`FORK_NEXT`**: Block number of the next upcoming fork (4 bytes, `0x00000000` if no next fork is known). + +When advertising the local chain to a remote node (ENR during discovery), each node shares its own `(GENESIS_CHECKSUM, FORK_CHECKSUM, FORK_NEXT)` tuple. These are cross validated (**NOT** compared) to accept or reject connectivity. Both parties must come to the same conclusion to avoid indefinite reconnect attempts from one side. + +#### Validation rules + +0) If the local and remote `GENESIS_CHECKSUM` doesn't match, reject. +1) If local and remote `FORK_CHECKSUM` matches, connect. + - The two nodes are in the same fork state currently. They might know of differing future forks, but that's not relevant until the fork triggers (might be postponed, nodes might be updated to match). +2) If the remote `FORK_CHECKSUM` is a subset of the local past forks and the remote `FORK_NEXT` matches with the locally following fork block number, connect. + - Remote node is currently syncing. It might eventually diverge from us, but at this current point in time we don't have enough information. +3) If the remote `FORK_CHECKSUM` is a superset of the local past forks and can be completed with locally known future forks, connect. + - Local node is currently syncing. It might eventually diverge from the remote, but at this current point in time we don't have enough information. +4) Reject in all other cases. + +#### Stale software examples + +The examples below try to exhaust the fork combination possibilities that arise when nodes do not run matching software versions, but otherwise follow the same chain (mainnet nodes, testnet nodes, etc). + +| Past forks | Future forks | Remote `FORK_CHECKSUM` | Remote `FORK_NEXT` | Connect | Reason | +|:---:|:---:|:---:|:---:|:---:|:---:| +| A | | A | | Yes (1) | Same forks, same sync state. | +| A | | A | B | Yes (1) | Remote is advertising a future fork, but that is uncertain. | +| A | B | A | | Yes (1) | Local knows about a future fork, but that is uncertain. | +| A | B | A | B | Yes (1) | Both know about a future fork, but that is uncertain. | +| A | B1 | A | B2 | Yes (1) | Both know about differing future forks, but those are uncertain. | +| [A,B] | | A | B | Yes (2) | Remote out of sync. | +| [A,B,C] | | A | B | Yes¹ (2) | Remote out of sync. Remote will need a software update, but we don't know it yet. | +| A | B | A ⊕ B | | Yes (3) | Local out of sync. | +| A | B,C | A ⊕ B | | Yes (3) | Local out of sync. Local also knows about a future fork, but that is uncertain yet. | +| A | | A ⊕ B | | No (4) | Local needs software update. | +| A | B | A ⊕ B ⊕ C | | No² (4) | Local needs software update. | +| [A,B] | | A | | No (4) | Remote needs software update. | + +*Note, there's one asymmetry in the table, marked with ¹ and ². Since we don't have access to a remote node's future fork list (just the next one), we can't detect that it's software is stale until it syncs up. This is acceptable as 1) the remote node will disconnect from us anyway, and 2) this is a temporary fluke during sync, not permanent with a leftover node.* + +#### Mismatching chain examples (local perspective) + +TODO: Give some examples as to what happens if the nodes follow different forks. + +## Rationale + +##### Why flatten the genesis into 4 bytes? Why not share the entire 32 bytes? + +Whilst the `eth` devp2p protocol permits arbitrarily much data to be transmitted, the discovery protocol relies on MTU-limited UDP messages. The total space allowance for all ENR entries is 300 bytes. + +Reducing the genesis into a 4 bytes checksum ensures that we leave ample room in the ENR for future extensions; and 4 bytes is more than enough for arbitrarilly many Ethereum networks from a collision perspective. + +##### Why flatten the fork block numbers into 4 bytes? Why not share as a list? + +Whilst the `eth` devp2p protocol permits arbitrarily much data to be transmitted, the discovery protocol relies on MTU-limited UDP messages. The total space allowance for all ENR entries is 300 bytes. + +Flattening the list of fork blocks into a single value ensures that independent of how many hard-forks Ethereum progresses through, we will never hit the discovery protocol limits. + +##### We're not using `FORK_NEXT` for much, can't we get rid of it somehow? + +We need to be able to differentiate whether a remote node is out of sync or whether its software is stale. Sharing only the past forks cannot tell us if the node is legitimately behind or stuck. + +##### Why advertise only one next fork, instead of hashing all known future ones? + +Opposed to past forks that have already passed (for us locally) and can be considered immutable, we don't know anything about future ones. Maybe we're out of sync or maybe the fork didn't pass yet. If it didn't pass yet, it might be postponed, so enforcing it would split the network apart. It could also happen that we're not yet aware of all future forks (haven't updated our software in a while). + +## Backwards Compatibility + +- Consensus wise, this EIP is backwards compatible as it only touches networking. +- Discovery protocol wise ENR is not yet supported across the ecosystem. These restrictions are only enforced between nodes supporting ENR and also advertising the fork fields. Nodes without ENR support, without the necessary fields, or mixed nodes will simply revert to enforcing the network split on the `eth` protocol layer. + +## Test Cases + +Here's a full suite of tests for all possible ENR entries that Mainnet, Ropsten, Rinkeby and Görli can advertise given the Petersburg fork cap (time of writing). + +```go +type testcase struct { + head uint64 + want ENR +} +tests := []struct { + config *params.ChainConfig + genesis common.Hash + cases []testcase +}{ + // Mainnet test cases + { + params.MainnetChainConfig, + params.MainnetGenesisHash, + []testcase{ + {0, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x8c, 0x30}}, // Unsynced + {1149999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x8c, 0x30}}, // Last Frontier block + {1150000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x11, 0x8c, 0x30, 0x00, 0x1d, 0x4c, 0x00}}, // First Homestead block + {1919999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x11, 0x8c, 0x30, 0x00, 0x1d, 0x4c, 0x00}}, // Last Homestead block + {1920000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x0c, 0xc0, 0x30, 0x00, 0x25, 0x95, 0x18}}, // First DAO block + {2462999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x0c, 0xc0, 0x30, 0x00, 0x25, 0x95, 0x18}}, // Last DAO block + {2463000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x29, 0x55, 0x28, 0x00, 0x28, 0xd1, 0x38}}, // First Tangerine block + {2674999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x29, 0x55, 0x28, 0x00, 0x28, 0xd1, 0x38}}, // Last Tangerine block + {2675000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x01, 0x84, 0x10, 0x00, 0x42, 0xae, 0x50}}, // First Spurious block + {4369999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x01, 0x84, 0x10, 0x00, 0x42, 0xae, 0x50}}, // Last Spurious block + {4370000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x6f, 0x15, 0x80}}, // First Byzantium block + {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x6f, 0x15, 0x80}}, // Last Byzantium block + {7280000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00}}, // First and last Constantinople, first Petersburg block + {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00}}, // Today Petersburg block + }, + }, + // Ropsten test cases + { + params.TestnetChainConfig, + params.TestnetGenesisHash, + []testcase{ + {0, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a}}, // Unsynced, last Frontier, Homestead and first Tangerine block + {9, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a}}, // Last Tangerine block + {10, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x19, 0xf0, 0xa0}}, // First Spurious block + {1699999, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x19, 0xf0, 0xa0}}, // Last Spurious block + {1700000, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x19, 0xf0, 0xaa, 0x00, 0x40, 0x8b, 0x70}}, // First Byzantium block + {4229999, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x19, 0xf0, 0xaa, 0x00, 0x40, 0x8b, 0x70}}, // Last Byzantium block + {4230000, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x59, 0x7b, 0xda, 0x00, 0x4b, 0x5e, 0x82}}, // First Constantinople block + {4939393, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x59, 0x7b, 0xda, 0x00, 0x4b, 0x5e, 0x82}}, // Last Constantinople block + {4939394, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x12, 0x25, 0x58, 0x00, 0x00, 0x00, 0x00}}, // First Petersburg block + {5822692, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x12, 0x25, 0x58, 0x00, 0x00, 0x00, 0x00}}, // Today Petersburg block + }, + }, + // Rinkeby test cases + { + params.RinkebyChainConfig, + params.RinkebyGenesisHash, + []testcase{ + {0, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}, // Unsynced, last Frontier block + {1, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02}}, // First and last Homestead block + {2, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03}}, // First and last Tangerine block + {3, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xcc, 0x25}}, // First Spurious block + {1035300, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xcc, 0x25}}, // Last Spurious block + {1035301, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x0f, 0xcc, 0x25, 0x00, 0x37, 0xdb, 0x77}}, // First Byzantium block + {3660662, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x0f, 0xcc, 0x25, 0x00, 0x37, 0xdb, 0x77}}, // Last Byzantium block + {3660663, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x38, 0x17, 0x52, 0x00, 0x41, 0xef, 0xd2}}, // First Constantinople block + {4321233, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x38, 0x17, 0x52, 0x00, 0x41, 0xef, 0xd2}}, // Last Constantinople block + {4321234, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x79, 0xf8, 0x80, 0x00, 0x00, 0x00, 0x00}}, // First Petersburg block + {4586649, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x79, 0xf8, 0x80, 0x00, 0x00, 0x00, 0x00}}, // Today Petersburg block + }, + }, + // Goerli test cases + { + params.GoerliChainConfig, + params.GoerliGenesisHash, + []testcase{ + {0, ENR{0xc7, 0x24, 0x73, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block + {795329, ENR{0xc7, 0x24, 0x73, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, // Today Petersburg block + }, + }, +} + ``` + + Here's a suite of tests of the different states a Mainnet node might be in and the different ENR announcements it might be required to validate and decide to accept or reject: + + ```go +tests := []struct { + head uint64 + enr ENR + err error +}{ + // Local is mainnet Petersburg, remote announces the same. No future fork is announced. + {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00}, nil}, + + // Local is mainnet Petersburg, remote announces the same. Remote also announces a next fork + // at block 0xffffffff, but that is uncertain. + {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0xff, 0xff, 0xff, 0xff}, nil}, + + // 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. + {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x00, 0x00, 0x00}, nil}, + + // 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. + {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x6f, 0x15, 0x80}, nil}, + + // 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. + {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0xff, 0xff, 0xff, 0xff}, nil}, + + // Local is mainnet Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote + // is simply out of sync, accept. + {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x6f, 0x15, 0x80}, nil}, + + // 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. + {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x01, 0x84, 0x10, 0x00, 0x42, 0xae, 0x50}, nil}, + + // Local is mainnet Byzantium, remote announces Petersburg. Local is out of sync, accept. + {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00}, nil}, + + // 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. + {4369999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x00, 0x00, 0x00}, nil}, + + // Local is mainnet Petersburg, and isn't aware of more forks. Remote announces Petersburg + + // 0xffffffff. Local needs software update, reject. + {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0xff, 0xd3, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0x00}, errENRLocalStale}, + + // Local is mainnet Byzantium, and is aware of Petersburg. Remote announces Petersburg + + // 0xffffffff. Local needs software update, reject. + {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0xff, 0xd3, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0x00}, errENRLocalStale}, + + // Local is mainnet Petersburg. remote announces Byzantium but is not aware of further forks. + // Remote needs software update. + {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x00, 0x00, 0x00}, errENRRemoteStale}, + + // Local is mainnet Petersburg, remote is Rinkeby Petersburg. + {7987396, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x79, 0xf8, 0x80, 0x00, 0x00, 0x00, 0x00}, errENRGenesisMismatch}, +} + ``` + +## Implementation + +https://github.com/ethereum/go-ethereum/pull/19738 + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). From 0a883927a8bdf82e6f47de81fdd440fecf496610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 19 Jun 2019 15:03:32 +0300 Subject: [PATCH 02/12] Assign number to EIP 2124 --- EIPS/eip-2124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index 1b4bd1e1258ebb..a4366723e53902 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -1,5 +1,5 @@ --- -eip: +eip: 2124 title: Zero RTT netsplit on chain mismatch author: Péter Szilágyi (peterke@gmail.com) discussions-to: https://github.com/ethereum/EIPs/issues/2125 From 8925dfbe040743f873dfb4927c54139e32558950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 19 Jun 2019 16:19:51 +0300 Subject: [PATCH 03/12] Address review comments on EIP 2124. --- EIPS/eip-2124.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index a4366723e53902..c618412a3f20d3 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -10,13 +10,17 @@ requires: 778 created: 2019-05-03 --- +## Simple Summary + +Currently nodes in the Ethereum network try to find each other by establishing random connections to remote machines "looking" like an Ethereum node (public networks, private networks, test networks, cloned networks, etc) and hoping for the best that they found a useful peer. This procedure of shooting in the dark is both time consuming and wasteful; and could be compared to connecting to random websites hoping they are Google. This EIP proposes an extension to the discovery protocol, to allow deciding in advance if a remote machine is useful, without ever having to connect. + ## Abstract There are many public and private Ethereum networks, but the discovery protocol doesn't differentiate between them. The only way to check if a peer is good or bad (same chain or not), is to establish a TCP/IP connection, wrap it with RLPx cryptography, then execute an `eth` handshake. This is an extreme cost to bear if it turns out that the remote peer is on a different network. This cost is magnified for small networks, where a lot more trial and errors are needed to find good nodes. Even if the peer **is** on the same network, during non-controversial consensus upgrades, not everybody updates their nodes in time (developer nodes, leftovers, etc). These stale nodes put a meaningless burden on the peer-to-peer network, since they just latch on to good nodes, but don't accept upgraded blocks. This causes valuable peer slots and bandwidth to be lost until the stale nodes finally update. This is an even more pronounced issue for test networks, where leftover nodes can linger for many months. -This EIP proposes an enhancement to the ENR extension of the discovery protocol to detect when two nodes are incompatible and never connect them in the first place, instead of wasting resources in vain. The EIP solves a a number of issues: +This EIP proposes an enhancement to the [Ethereum Node Record (ENR)](http://eips.ethereum.org/EIPS/eip-778) extension of the discovery protocol to detect when two nodes are incompatible and never connect them in the first place, instead of wasting resources in vain. The EIP solves a a number of issues: * If two nodes are on different networks, they should never even consider connecting. * If a hard fork passes, upgraded nodes should reject non-upgraded ones, but **NOT** before. @@ -38,9 +42,11 @@ Each node maintains the following values: - **`GENESIS_CHECKSUM`**: Bitwise XOR of the genesis hash split into 4-byte chunks (4 bytes). - **`FORK_CHECKSUM`**: Bitwise XOR of all fork block numbers that the local chain already passed (4 bytes). - If multiple forks are applied at the same block, the block number should be XOR-ed only once. + - Block numbers are truncated to `uint32` and encoded in big endian format before XOR-ing. - **`FORK_NEXT`**: Block number of the next upcoming fork (4 bytes, `0x00000000` if no next fork is known). + - The upcoming fork block number is truncated to `uint32` and encoded in big endian format. -When advertising the local chain to a remote node (ENR during discovery), each node shares its own `(GENESIS_CHECKSUM, FORK_CHECKSUM, FORK_NEXT)` tuple. These are cross validated (**NOT** compared) to accept or reject connectivity. Both parties must come to the same conclusion to avoid indefinite reconnect attempts from one side. +When advertising the local chain to a remote node (ENR during discovery), each node shares its own `RLP(GENESIS_CHECKSUM || FORK_CHECKSUM || FORK_NEXT)` under the `eth` ENR key. These are cross validated (**NOT** compared) to accept or reject connectivity. Both parties must come to the same conclusion to avoid indefinite reconnect attempts from one side. #### Validation rules From 3926e3928e88a3f7e361c9bb6aea215549323c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 19 Jun 2019 17:38:00 +0300 Subject: [PATCH 04/12] Rename EIP 2124 to better reflect content --- EIPS/eip-2124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index c618412a3f20d3..8af39c341c1bfc 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -1,6 +1,6 @@ --- eip: 2124 -title: Zero RTT netsplit on chain mismatch +title: Zero RTT chain compatibility checks author: Péter Szilágyi (peterke@gmail.com) discussions-to: https://github.com/ethereum/EIPs/issues/2125 status: Draft From 753fb10597b60c40304b8a6a47ea467ffab6de22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 19 Jun 2019 17:39:01 +0300 Subject: [PATCH 05/12] Fix cardinality --- EIPS/eip-2124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index 8af39c341c1bfc..03825a2af97967 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -1,6 +1,6 @@ --- eip: 2124 -title: Zero RTT chain compatibility checks +title: Zero RTT chain compatibility check author: Péter Szilágyi (peterke@gmail.com) discussions-to: https://github.com/ethereum/EIPs/issues/2125 status: Draft From aac35accd72de5b6738922dbd619e371b3370aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 24 Jun 2019 13:55:49 +0300 Subject: [PATCH 06/12] Fix email address in 2124 --- EIPS/eip-2124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index 03825a2af97967..b820b225a72c86 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -1,7 +1,7 @@ --- eip: 2124 title: Zero RTT chain compatibility check -author: Péter Szilágyi (peterke@gmail.com) +author: Péter Szilágyi discussions-to: https://github.com/ethereum/EIPs/issues/2125 status: Draft type: Standards Track From 406dadf0fe58ad45b4af207a0e908fed2ff4dd48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 3 Jul 2019 18:23:43 +0300 Subject: [PATCH 07/12] Update fork identifier EIP based on review discussions. --- EIPS/eip-2124.md | 182 +++++++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 87 deletions(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index b820b225a72c86..4b39e833a0048c 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -1,37 +1,41 @@ --- eip: 2124 -title: Zero RTT chain compatibility check +title: Fork identifier for compatibility checks author: Péter Szilágyi discussions-to: https://github.com/ethereum/EIPs/issues/2125 status: Draft type: Standards Track category: Networking -requires: 778 created: 2019-05-03 --- ## Simple Summary -Currently nodes in the Ethereum network try to find each other by establishing random connections to remote machines "looking" like an Ethereum node (public networks, private networks, test networks, cloned networks, etc) and hoping for the best that they found a useful peer. This procedure of shooting in the dark is both time consuming and wasteful; and could be compared to connecting to random websites hoping they are Google. This EIP proposes an extension to the discovery protocol, to allow deciding in advance if a remote machine is useful, without ever having to connect. +Currently nodes in the Ethereum network try to find each other by establishing random connections to remote machines "looking" like an Ethereum node (public networks, private networks, test networks, etc), hoping that they found a useful peer (same genesis, same forks). This wastes time and resources, especially for smaller networks. + +To avoid this overhead, Ethereum needs a mechanism that can precisely identify whether a node will be useful, as early as possible. Such a mechanism requires a way to summarize chain configurations, as well as a way to disseminate said summaries in the network. + +This proposal focuses only on the definition of said summary - a generally useful *fork identifier* - and it's validation rules, allowing it to be embedded into arbitrary network protocols (e.g. [discovery ENRs](https://eips.ethereum.org/EIPS/eip-778) or `eth/6x` handshakes). ## Abstract -There are many public and private Ethereum networks, but the discovery protocol doesn't differentiate between them. The only way to check if a peer is good or bad (same chain or not), is to establish a TCP/IP connection, wrap it with RLPx cryptography, then execute an `eth` handshake. This is an extreme cost to bear if it turns out that the remote peer is on a different network. This cost is magnified for small networks, where a lot more trial and errors are needed to find good nodes. +There are many public and private Ethereum networks, but the discovery protocol doesn't differentiate between them. The only way to check if a peer is good or bad (same chain or not), is to establish a TCP/IP connection, wrap it with RLPx cryptography, then execute an `eth` handshake. This is an extreme cost to bear if it turns out that the remote peer is on a different network and it's not even precise enough to differentiate Ethereum and Ethereum Classic. This cost is magnified for small networks, where a lot more trial and errors are needed to find good nodes. -Even if the peer **is** on the same network, during non-controversial consensus upgrades, not everybody updates their nodes in time (developer nodes, leftovers, etc). These stale nodes put a meaningless burden on the peer-to-peer network, since they just latch on to good nodes, but don't accept upgraded blocks. This causes valuable peer slots and bandwidth to be lost until the stale nodes finally update. This is an even more pronounced issue for test networks, where leftover nodes can linger for many months. +Even if the peer **is** on the same chain, during non-controversial consensus upgrades, not everybody updates their nodes in time (developer nodes, leftovers, etc). These stale nodes put a meaningless burden on the peer-to-peer network, since they just latch on to good nodes, but don't accept upgraded blocks. This causes valuable peer slots and bandwidth to be lost until the stale nodes finally update. This is a serious issue for test networks, where leftovers can linger for months. -This EIP proposes an enhancement to the [Ethereum Node Record (ENR)](http://eips.ethereum.org/EIPS/eip-778) extension of the discovery protocol to detect when two nodes are incompatible and never connect them in the first place, instead of wasting resources in vain. The EIP solves a a number of issues: +This EIP proposes a new identity scheme to both precisely and concisely summarize the chain's current status (genesis and all applied forks). The conciseness is particularly important to make the identity useful across datagram protocols too. The EIP solves a number of issues: * If two nodes are on different networks, they should never even consider connecting. * If a hard fork passes, upgraded nodes should reject non-upgraded ones, but **NOT** before. * If two chains share the same genesis, but not forks (ETH / ETC), they should reject each other. - * Ideally the rejection should be short circuited during discovery, before the expensive RLPx handshakes. This EIP does not attempt to solve the clean separation of 3-way-forks! If at the same future block number, the network splits into three (non-fork, fork-A and fork-B), separating the forkers from each another will need case-by-case special handling. Not handling this keeps the proposal pragmatic, simple and also avoids making it too easy to fork off mainnet. +To keep the scope limited, this EIP only defines the identity scheme and validation rules. The same scheme and algorithm can be embedded into various networking protocols, allowing both the `eth/6x` handshake to be more precise (Ethereum vs. Ethereum Classic); as well as the discovery to be more useful (reject surely peers without ever connecting). + ## Motivation -Peer-to-peer networking is messy and hard due to firewalls and network address translations. Generally only a small fraction of nodes have publicly routed addresses, and P2P networks rely mainly on these for relaying data for everyone else. The best way to maximize the utility of the public nodes is by ensuring their resources aren't wasted on tasks that are worthless to the network. +Peer-to-peer networking is messy and hard due to firewalls and network address translation (NAT). Generally only a small fraction of nodes have publicly routed addresses and P2P networks rely mainly on these for forwarding data for everyone else. The best way to maximize the utility of the public nodes is to ensure their resources aren't wasted on tasks that are worthless to the network. By aggressively cutting off incompatible nodes from each other we can extract a lot more value from the public nodes, making the entire P2P network much more robust and reliable. Supporting this network partitioning at a discovery layer can further enhance performance as we avoid the costly crypto and latency/bandwidth hit associated with establishing a stream connection in the first place. @@ -39,23 +43,28 @@ By aggressively cutting off incompatible nodes from each other we can extract a Each node maintains the following values: - - **`GENESIS_CHECKSUM`**: Bitwise XOR of the genesis hash split into 4-byte chunks (4 bytes). - - **`FORK_CHECKSUM`**: Bitwise XOR of all fork block numbers that the local chain already passed (4 bytes). - - If multiple forks are applied at the same block, the block number should be XOR-ed only once. - - Block numbers are truncated to `uint32` and encoded in big endian format before XOR-ing. - - **`FORK_NEXT`**: Block number of the next upcoming fork (4 bytes, `0x00000000` if no next fork is known). - - The upcoming fork block number is truncated to `uint32` and encoded in big endian format. +- **`FORK_HASH`**: IEEE CRC32 checksum (`[4]byte`) of the genesis hash and fork blocks numbers that already passed. + - The fork block numbers are fed into the CRC32 checksum in ascending order. + - If multiple forks are applied at the same block, the block number is checksummed only once. + - Block numbers are regarded as `uint64` integers, encoded in big endian format when checksumming. + - If a chain is configured to start with a non-Frontier ruleset already in its genesis, that is NOT considered a fork. +- **`FORK_NEXT`**: Block number (`uint64`) of the next upcoming fork, or `0` if no next fork is known. + +E.g. `FORK_HASH` for mainnet would be: -When advertising the local chain to a remote node (ENR during discovery), each node shares its own `RLP(GENESIS_CHECKSUM || FORK_CHECKSUM || FORK_NEXT)` under the `eth` ENR key. These are cross validated (**NOT** compared) to accept or reject connectivity. Both parties must come to the same conclusion to avoid indefinite reconnect attempts from one side. +- forkhash₀ = `0xfc64ec04` (Genesis) = `CRC32()` +- forkhash₁ = `0x97c2c34c` (Homestead) = `CRC32( || uint64(1150000))` +- forkhash₂ = `0x91d1f948` (DAO fork) = `CRC32( || uint64(1150000) || uint64(1920000))` + +The *fork identifier* is defined as `RLP([FORK_HASH, FORK_NEXT])`. This `forkid` is cross validated (**NOT** naively compared) to assess a remote chain's compatibility. Irrespective of fork state, both parties must come to the same conclusion to avoid indefinite reconnect attempts from one side. #### Validation rules -0) If the local and remote `GENESIS_CHECKSUM` doesn't match, reject. -1) If local and remote `FORK_CHECKSUM` matches, connect. +1) If local and remote `FORK_HASH` matches, connect. - The two nodes are in the same fork state currently. They might know of differing future forks, but that's not relevant until the fork triggers (might be postponed, nodes might be updated to match). -2) If the remote `FORK_CHECKSUM` is a subset of the local past forks and the remote `FORK_NEXT` matches with the locally following fork block number, connect. +2) If the remote `FORK_HASH` is a subset of the local past forks and the remote `FORK_NEXT` matches with the locally following fork block number, connect. - Remote node is currently syncing. It might eventually diverge from us, but at this current point in time we don't have enough information. -3) If the remote `FORK_CHECKSUM` is a superset of the local past forks and can be completed with locally known future forks, connect. +3) If the remote `FORK_HASH` is a superset of the local past forks and can be completed with locally known future forks, connect. - Local node is currently syncing. It might eventually diverge from the remote, but at this current point in time we don't have enough information. 4) Reject in all other cases. @@ -63,7 +72,7 @@ When advertising the local chain to a remote node (ENR during discovery), each n The examples below try to exhaust the fork combination possibilities that arise when nodes do not run matching software versions, but otherwise follow the same chain (mainnet nodes, testnet nodes, etc). -| Past forks | Future forks | Remote `FORK_CHECKSUM` | Remote `FORK_NEXT` | Connect | Reason | +| Past forks | Future forks | Remote `FORK_HASH` | Remote `FORK_NEXT` | Connect | Reason | |:---:|:---:|:---:|:---:|:---:|:---:| | A | | A | | Yes (1) | Same forks, same sync state. | | A | | A | B | Yes (1) | Remote is advertising a future fork, but that is uncertain. | @@ -86,39 +95,38 @@ TODO: Give some examples as to what happens if the nodes follow different forks. ## Rationale -##### Why flatten the genesis into 4 bytes? Why not share the entire 32 bytes? +##### Why flatten `FORK_HASH` into 4 bytes? Why not share the entire genesis and fork list? Whilst the `eth` devp2p protocol permits arbitrarily much data to be transmitted, the discovery protocol relies on MTU-limited UDP messages. The total space allowance for all ENR entries is 300 bytes. -Reducing the genesis into a 4 bytes checksum ensures that we leave ample room in the ENR for future extensions; and 4 bytes is more than enough for arbitrarilly many Ethereum networks from a collision perspective. +Reducing the `FORK_HASH` into a 4 bytes checksum ensures that we leave ample room in the ENR for future extensions; and 4 bytes is more than enough for arbitrarily many Ethereum networks from a (practical) collision perspective. -##### Why flatten the fork block numbers into 4 bytes? Why not share as a list? +##### Why use IEEE CRC32 as the checksum instead of Keccak256? -Whilst the `eth` devp2p protocol permits arbitrarily much data to be transmitted, the discovery protocol relies on MTU-limited UDP messages. The total space allowance for all ENR entries is 300 bytes. +We need a mechanism that can flatten arbitrary data into 4 bytes, without ignoring any of the input. Any other checksum or hashing algorithm would work, but since nodes can lie at any time, there's no value in cryptographic hash functions. -Flattening the list of fork blocks into a single value ensures that independent of how many hard-forks Ethereum progresses through, we will never hit the discovery protocol limits. +Instead of just taking the first 4 bytes of a Keccak256 hash (seems odd) or XOR-ing all the 4-byte groups (messy), CRC32 is a better alternative, as this is exactly what it was designed for. IEEE CRC32 is also used by ethernet, gzip, zip, png, etc, so every programming language support should not be a problem. ##### We're not using `FORK_NEXT` for much, can't we get rid of it somehow? We need to be able to differentiate whether a remote node is out of sync or whether its software is stale. Sharing only the past forks cannot tell us if the node is legitimately behind or stuck. -##### Why advertise only one next fork, instead of hashing all known future ones? +##### Why advertise only one next fork, instead of "hashing" all known future ones like the `FORK_HASH`? Opposed to past forks that have already passed (for us locally) and can be considered immutable, we don't know anything about future ones. Maybe we're out of sync or maybe the fork didn't pass yet. If it didn't pass yet, it might be postponed, so enforcing it would split the network apart. It could also happen that we're not yet aware of all future forks (haven't updated our software in a while). ## Backwards Compatibility -- Consensus wise, this EIP is backwards compatible as it only touches networking. -- Discovery protocol wise ENR is not yet supported across the ecosystem. These restrictions are only enforced between nodes supporting ENR and also advertising the fork fields. Nodes without ENR support, without the necessary fields, or mixed nodes will simply revert to enforcing the network split on the `eth` protocol layer. +This EIP only defines an identity scheme, it does not define functional changes. ## Test Cases -Here's a full suite of tests for all possible ENR entries that Mainnet, Ropsten, Rinkeby and Görli can advertise given the Petersburg fork cap (time of writing). +Here's a full suite of tests for all possible fork IDs that Mainnet, Ropsten, Rinkeby and Görli can advertise given the Petersburg fork cap (time of writing). ```go type testcase struct { head uint64 - want ENR + want ID } tests := []struct { config *params.ChainConfig @@ -130,20 +138,20 @@ tests := []struct { params.MainnetChainConfig, params.MainnetGenesisHash, []testcase{ - {0, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x8c, 0x30}}, // Unsynced - {1149999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x8c, 0x30}}, // Last Frontier block - {1150000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x11, 0x8c, 0x30, 0x00, 0x1d, 0x4c, 0x00}}, // First Homestead block - {1919999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x11, 0x8c, 0x30, 0x00, 0x1d, 0x4c, 0x00}}, // Last Homestead block - {1920000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x0c, 0xc0, 0x30, 0x00, 0x25, 0x95, 0x18}}, // First DAO block - {2462999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x0c, 0xc0, 0x30, 0x00, 0x25, 0x95, 0x18}}, // Last DAO block - {2463000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x29, 0x55, 0x28, 0x00, 0x28, 0xd1, 0x38}}, // First Tangerine block - {2674999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x29, 0x55, 0x28, 0x00, 0x28, 0xd1, 0x38}}, // Last Tangerine block - {2675000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x01, 0x84, 0x10, 0x00, 0x42, 0xae, 0x50}}, // First Spurious block - {4369999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x01, 0x84, 0x10, 0x00, 0x42, 0xae, 0x50}}, // Last Spurious block - {4370000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x6f, 0x15, 0x80}}, // First Byzantium block - {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x6f, 0x15, 0x80}}, // Last Byzantium block - {7280000, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00}}, // First and last Constantinople, first Petersburg block - {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00}}, // Today Petersburg block + {0, ID{Hash: 0xfc64ec04, Next: 1150000}}, // Unsynced + {1149999, ID{Hash: 0xfc64ec04, Next: 1150000}}, // Last Frontier block + {1150000, ID{Hash: 0x97c2c34c, Next: 1920000}}, // First Homestead block + {1919999, ID{Hash: 0x97c2c34c, Next: 1920000}}, // Last Homestead block + {1920000, ID{Hash: 0x91d1f948, Next: 2463000}}, // First DAO block + {2462999, ID{Hash: 0x91d1f948, Next: 2463000}}, // Last DAO block + {2463000, ID{Hash: 0x7a64da13, Next: 2675000}}, // First Tangerine block + {2674999, ID{Hash: 0x7a64da13, Next: 2675000}}, // Last Tangerine block + {2675000, ID{Hash: 0x3edd5b10, Next: 4370000}}, // First Spurious block + {4369999, ID{Hash: 0x3edd5b10, Next: 4370000}}, // Last Spurious block + {4370000, ID{Hash: 0xa00bc324, Next: 7280000}}, // First Byzantium block + {7279999, ID{Hash: 0xa00bc324, Next: 7280000}}, // Last Byzantium block + {7280000, ID{Hash: 0x668db0af, Next: 0}}, // First and last Constantinople, first Petersburg block + {7987396, ID{Hash: 0x668db0af, Next: 0}}, // Today Petersburg block }, }, // Ropsten test cases @@ -151,16 +159,16 @@ tests := []struct { params.TestnetChainConfig, params.TestnetGenesisHash, []testcase{ - {0, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a}}, // Unsynced, last Frontier, Homestead and first Tangerine block - {9, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a}}, // Last Tangerine block - {10, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x19, 0xf0, 0xa0}}, // First Spurious block - {1699999, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x19, 0xf0, 0xa0}}, // Last Spurious block - {1700000, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x19, 0xf0, 0xaa, 0x00, 0x40, 0x8b, 0x70}}, // First Byzantium block - {4229999, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x19, 0xf0, 0xaa, 0x00, 0x40, 0x8b, 0x70}}, // Last Byzantium block - {4230000, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x59, 0x7b, 0xda, 0x00, 0x4b, 0x5e, 0x82}}, // First Constantinople block - {4939393, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x59, 0x7b, 0xda, 0x00, 0x4b, 0x5e, 0x82}}, // Last Constantinople block - {4939394, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x12, 0x25, 0x58, 0x00, 0x00, 0x00, 0x00}}, // First Petersburg block - {5822692, ENR{0xe4, 0x91, 0x9a, 0xeb, 0x00, 0x12, 0x25, 0x58, 0x00, 0x00, 0x00, 0x00}}, // Today Petersburg block + {0, ID{Hash: 0x30c7ddbc, Next: 10}}, // Unsynced, last Frontier, Homestead and first Tangerine block + {9, ID{Hash: 0x30c7ddbc, Next: 10}}, // Last Tangerine block + {10, ID{Hash: 0x63760190, Next: 1700000}}, // First Spurious block + {1699999, ID{Hash: 0x63760190, Next: 1700000}}, // Last Spurious block + {1700000, ID{Hash: 0x3ea159c7, Next: 4230000}}, // First Byzantium block + {4229999, ID{Hash: 0x3ea159c7, Next: 4230000}}, // Last Byzantium block + {4230000, ID{Hash: 0x97b544f3, Next: 4939394}}, // First Constantinople block + {4939393, ID{Hash: 0x97b544f3, Next: 4939394}}, // Last Constantinople block + {4939394, ID{Hash: 0xd6e2149b, Next: 0}}, // First Petersburg block + {5822692, ID{Hash: 0xd6e2149b, Next: 0}}, // Today Petersburg block }, }, // Rinkeby test cases @@ -168,17 +176,17 @@ tests := []struct { params.RinkebyChainConfig, params.RinkebyGenesisHash, []testcase{ - {0, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}, // Unsynced, last Frontier block - {1, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02}}, // First and last Homestead block - {2, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03}}, // First and last Tangerine block - {3, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xcc, 0x25}}, // First Spurious block - {1035300, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xcc, 0x25}}, // Last Spurious block - {1035301, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x0f, 0xcc, 0x25, 0x00, 0x37, 0xdb, 0x77}}, // First Byzantium block - {3660662, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x0f, 0xcc, 0x25, 0x00, 0x37, 0xdb, 0x77}}, // Last Byzantium block - {3660663, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x38, 0x17, 0x52, 0x00, 0x41, 0xef, 0xd2}}, // First Constantinople block - {4321233, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x38, 0x17, 0x52, 0x00, 0x41, 0xef, 0xd2}}, // Last Constantinople block - {4321234, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x79, 0xf8, 0x80, 0x00, 0x00, 0x00, 0x00}}, // First Petersburg block - {4586649, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x79, 0xf8, 0x80, 0x00, 0x00, 0x00, 0x00}}, // Today Petersburg block + {0, ID{Hash: 0x3b8e0691, Next: 1}}, // Unsynced, last Frontier block + {1, ID{Hash: 0x60949295, Next: 2}}, // First and last Homestead block + {2, ID{Hash: 0x8bde40dd, Next: 3}}, // First and last Tangerine block + {3, ID{Hash: 0xcb3a64bb, Next: 1035301}}, // First Spurious block + {1035300, ID{Hash: 0xcb3a64bb, Next: 1035301}}, // Last Spurious block + {1035301, ID{Hash: 0x8d748b57, Next: 3660663}}, // First Byzantium block + {3660662, ID{Hash: 0x8d748b57, Next: 3660663}}, // Last Byzantium block + {3660663, ID{Hash: 0xe49cab14, Next: 4321234}}, // First Constantinople block + {4321233, ID{Hash: 0xe49cab14, Next: 4321234}}, // Last Constantinople block + {4321234, ID{Hash: 0xafec6b27, Next: 0}}, // First Petersburg block + {4586649, ID{Hash: 0xafec6b27, Next: 0}}, // Today Petersburg block }, }, // Goerli test cases @@ -186,74 +194,74 @@ tests := []struct { params.GoerliChainConfig, params.GoerliGenesisHash, []testcase{ - {0, ENR{0xc7, 0x24, 0x73, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block - {795329, ENR{0xc7, 0x24, 0x73, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, // Today Petersburg block + {0, ID{Hash: 0xa3f5ab08, Next: 0}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block + {795329, ID{Hash: 0xa3f5ab08, Next: 0}}, // Today Petersburg block }, }, } - ``` +``` Here's a suite of tests of the different states a Mainnet node might be in and the different ENR announcements it might be required to validate and decide to accept or reject: - ```go +```go tests := []struct { head uint64 - enr ENR + id ID err error }{ // Local is mainnet Petersburg, remote announces the same. No future fork is announced. - {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00}, nil}, + {7987396, ID{Hash: 0x668db0af, Next: 0}, nil}, // Local is mainnet Petersburg, remote announces the same. Remote also announces a next fork // at block 0xffffffff, but that is uncertain. - {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0xff, 0xff, 0xff, 0xff}, nil}, + {7987396, ID{Hash: 0x668db0af, Next: math.MaxUint64}, nil}, // 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. - {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x00, 0x00, 0x00}, nil}, + {7279999, ID{Hash: 0xa00bc324, Next: 0}, nil}, // 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. - {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x6f, 0x15, 0x80}, nil}, + {7279999, ID{Hash: 0xa00bc324, Next: 7280000}, nil}, // 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. - {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0xff, 0xff, 0xff, 0xff}, nil}, + {7279999, ID{Hash: 0xa00bc324, Next: math.MaxUint64}, nil}, // Local is mainnet Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote // is simply out of sync, accept. - {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x6f, 0x15, 0x80}, nil}, + {7987396, ID{Hash: 0x668db0af, Next: 7280000}, nil}, // 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. - {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x01, 0x84, 0x10, 0x00, 0x42, 0xae, 0x50}, nil}, + {7987396, ID{Hash: 0x3edd5b10, Next: 4370000}, nil}, // Local is mainnet Byzantium, remote announces Petersburg. Local is out of sync, accept. - {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x2c, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00}, nil}, + {7279999, ID{Hash: 0x668db0af, Next: 0}, nil}, // 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. - {4369999, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x00, 0x00, 0x00}, nil}, + {4369999, ID{Hash: 0xa00bc324, Next: 0}, nil}, + + // Local is mainnet Petersburg. remote announces Byzantium but is not aware of further forks. + // Remote needs software update. + {7987396, ID{Hash: 0xa00bc324, Next: 0}, ErrRemoteStale}, // Local is mainnet Petersburg, and isn't aware of more forks. Remote announces Petersburg + // 0xffffffff. Local needs software update, reject. - {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0xff, 0xd3, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0x00}, errENRLocalStale}, + {7987396, ID{Hash: 0x5cddc0e1, Next: 0}, ErrLocalIncompatibleOrStale}, // Local is mainnet Byzantium, and is aware of Petersburg. Remote announces Petersburg + // 0xffffffff. Local needs software update, reject. - {7279999, ENR{0x52, 0xba, 0xab, 0x2d, 0xff, 0xd3, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0x00}, errENRLocalStale}, - - // Local is mainnet Petersburg. remote announces Byzantium but is not aware of further forks. - // Remote needs software update. - {7987396, ENR{0x52, 0xba, 0xab, 0x2d, 0x00, 0x43, 0x2a, 0x40, 0x00, 0x00, 0x00, 0x00}, errENRRemoteStale}, + {7279999, ID{Hash: 0x5cddc0e1, Next: 0}, ErrLocalIncompatibleOrStale}, // Local is mainnet Petersburg, remote is Rinkeby Petersburg. - {7987396, ENR{0xca, 0xb4, 0xc3, 0x84, 0x00, 0x79, 0xf8, 0x80, 0x00, 0x00, 0x00, 0x00}, errENRGenesisMismatch}, + {7987396, ID{Hash: 0xafec6b27, Next: 0}, ErrLocalIncompatibleOrStale}, } - ``` +``` ## Implementation From ef75b4110f5f1544b55feafb9c7ed7aeea11d1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 3 Jul 2019 18:25:03 +0300 Subject: [PATCH 08/12] Fixup EIP name for the fork identifier. --- EIPS/eip-2124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index 4b39e833a0048c..bf6195e89456c7 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -1,6 +1,6 @@ --- eip: 2124 -title: Fork identifier for compatibility checks +title: Fork identifier for chain compatibility checks author: Péter Szilágyi discussions-to: https://github.com/ethereum/EIPs/issues/2125 status: Draft From 9ee95e27aacd38d946c844c6948d14eaf819ced9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 3 Jul 2019 18:46:27 +0300 Subject: [PATCH 09/12] Remove stale mention of ENR from forkid EIP. --- EIPS/eip-2124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index bf6195e89456c7..d50d3a78f0815b 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -201,7 +201,7 @@ tests := []struct { } ``` - Here's a suite of tests of the different states a Mainnet node might be in and the different ENR announcements it might be required to validate and decide to accept or reject: + Here's a suite of tests of the different states a Mainnet node might be in and the different remote fork identifiers it might be required to validate and decide to accept or reject: ```go tests := []struct { From 8616237a324613ad99ad9ad12f2bc386596ede18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 3 Jul 2019 19:17:53 +0300 Subject: [PATCH 10/12] Add RLP encoding test cases for forkid EIP. --- EIPS/eip-2124.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index d50d3a78f0815b..f5823a4734bc7a 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -263,6 +263,28 @@ tests := []struct { } ``` +Here's a couple of tests to verify the proper RLP encoding (since `FORK_HASH` is a 4 byte binary but `FORK_NEXT` is an 8 byte quantity): + +```go +tests := []struct { + id ID + want []byte +}{ + { + ID{Hash: checksumToBytes(0), Next: 0}, + common.Hex2Bytes("c6840000000080"), + }, + { + ID{Hash: checksumToBytes(0xdeadbeef), Next: 0xBADDCAFE}, + common.Hex2Bytes("ca84deadbeef84baddcafe"), + }, + { + ID{Hash: checksumToBytes(math.MaxUint32), Next: math.MaxUint64}, + common.Hex2Bytes("ce84ffffffff88ffffffffffffffff"), + }, +} +``` + ## Implementation https://github.com/ethereum/go-ethereum/pull/19738 From 9f8e59d0f39418df350811be8cb02fa7be14d3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 3 Jul 2019 19:30:49 +0300 Subject: [PATCH 11/12] Add Felix too to the forkid EIP author list. --- EIPS/eip-2124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index f5823a4734bc7a..b79844d915f170 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -1,7 +1,7 @@ --- eip: 2124 title: Fork identifier for chain compatibility checks -author: Péter Szilágyi +author: Péter Szilágyi , Felix Lange discussions-to: https://github.com/ethereum/EIPs/issues/2125 status: Draft type: Standards Track From b4306af44be84b2a0bdf18e7223ba426152405bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 3 Jul 2019 21:01:10 +0300 Subject: [PATCH 12/12] Get rid of mentioning the MTU stuffin the forkid EIP. --- EIPS/eip-2124.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-2124.md b/EIPS/eip-2124.md index b79844d915f170..d2fadc4e369b7a 100644 --- a/EIPS/eip-2124.md +++ b/EIPS/eip-2124.md @@ -97,7 +97,7 @@ TODO: Give some examples as to what happens if the nodes follow different forks. ##### Why flatten `FORK_HASH` into 4 bytes? Why not share the entire genesis and fork list? -Whilst the `eth` devp2p protocol permits arbitrarily much data to be transmitted, the discovery protocol relies on MTU-limited UDP messages. The total space allowance for all ENR entries is 300 bytes. +Whilst the `eth` devp2p protocol permits arbitrarily much data to be transmitted, the discovery protocol's total space allowance for all ENR entries is 300 bytes. Reducing the `FORK_HASH` into a 4 bytes checksum ensures that we leave ample room in the ENR for future extensions; and 4 bytes is more than enough for arbitrarily many Ethereum networks from a (practical) collision perspective.