Skip to content
This repository was archived by the owner on Dec 10, 2020. It is now read-only.

Commit aa7f6f0

Browse files
committed
Added support for Eth64 protocol version
1 parent ac09ad2 commit aa7f6f0

File tree

6 files changed

+175
-29
lines changed

6 files changed

+175
-29
lines changed

examples/peer-communication.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ dpt.on('error', err => console.error(chalk.red(`DPT error: ${err}`)))
6464
const rlpx = new devp2p.RLPx(PRIVATE_KEY, {
6565
dpt: dpt,
6666
maxPeers: 25,
67-
capabilities: [devp2p.ETH.eth63, devp2p.ETH.eth62],
67+
capabilities: [devp2p.ETH.eth64],
6868
common: common,
6969
remoteClientIdFilter: REMOTE_CLIENTID_FILTER,
7070
listenPort: null

package.json

+2-5
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,11 @@
5656
"test": "node_modules/tape/bin/tape -r ts-node/register ./test/index.ts"
5757
},
5858
"dependencies": {
59-
"@types/bl": "^2.1.0",
60-
"@types/k-bucket": "^5.0.0",
61-
"@types/lru-cache": "^5.1.0",
59+
"@ethereumjs/common": "^2.0.0-beta.1",
6260
"babel-runtime": "^6.11.6",
6361
"bl": "^1.1.2",
6462
"debug": "^2.2.0",
65-
"ethereumjs-common": "^1.5.1",
63+
"ethereumjs-common": "^1.5.2",
6664
"inherits": "^2.0.1",
6765
"ip": "^1.1.3",
6866
"k-bucket": "^5.0.0",
@@ -74,7 +72,6 @@
7472
},
7573
"devDependencies": {
7674
"@ethereumjs/block": "^3.0.0-beta.1",
77-
"@ethereumjs/common": "^2.0.0-beta.1",
7875
"@ethereumjs/config-coverage": "^2.0.0",
7976
"@ethereumjs/config-typescript": "^2.0.0",
8077
"@ethereumjs/eslint-config-defaults": "^2.0.0",

src/eth/index.ts

+101-16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import assert from 'assert'
12
import { EventEmitter } from 'events'
23
import * as rlp from 'rlp'
34
import ms from 'ms'
@@ -18,6 +19,12 @@ export class ETH extends EventEmitter {
1819
_statusTimeoutId: NodeJS.Timeout
1920
_send: SendMethod
2021

22+
// Eth64
23+
_hardfork: string = 'chainstart'
24+
_latestBlock: number = 0
25+
_forkHash: string = ''
26+
_nextForkBlock: number = 0
27+
2128
constructor(version: number, peer: Peer, send: SendMethod) {
2229
super()
2330

@@ -30,10 +37,24 @@ export class ETH extends EventEmitter {
3037
this._statusTimeoutId = setTimeout(() => {
3138
this._peer.disconnect(DISCONNECT_REASONS.TIMEOUT)
3239
}, ms('5s'))
40+
41+
// Set forkHash and nextForkBlock
42+
if (this._version >= 64) {
43+
const c = this._peer._common
44+
this._hardfork = c.hardfork() ? (c.hardfork() as string) : this._hardfork
45+
// Set latestBlock minimally to start block of fork to have some more
46+
// accurate basis if no latestBlock is provided along status send
47+
this._latestBlock = c.hardforkBlock(this._hardfork)
48+
this._forkHash = c.forkHash(this._hardfork)
49+
// Next fork block number or 0 if none available
50+
const nextForkBlock = c.nextHardforkBlock(this._hardfork)
51+
this._nextForkBlock = nextForkBlock ? nextForkBlock : 0
52+
}
3353
}
3454

3555
static eth62 = { name: 'eth', version: 62, length: 8, constructor: ETH }
3656
static eth63 = { name: 'eth', version: 63, length: 17, constructor: ETH }
57+
static eth64 = { name: 'eth', version: 64, length: 29, constructor: ETH }
3758

3859
_handleMessage(code: ETH.MESSAGE_CODES, data: any) {
3960
const payload = rlp.decode(data) as unknown
@@ -80,6 +101,41 @@ export class ETH extends EventEmitter {
80101
this.emit('message', code, payload)
81102
}
82103

104+
/**
105+
* Eth 64 Fork ID validation (EIP-2124)
106+
* @param forkId Remote fork ID
107+
*/
108+
_validateForkId(forkId: Buffer[]) {
109+
const c = this._peer._common
110+
111+
const peerForkHash = `0x${forkId[0].toString('hex')}`
112+
const peerNextFork = buffer2int(forkId[1])
113+
114+
if (this._forkHash === peerForkHash) {
115+
if (peerNextFork) {
116+
if (this._latestBlock >= peerNextFork) {
117+
const msg = 'Remote is advertising a future fork that passed locally'
118+
debug(msg)
119+
throw new assert.AssertionError({ message: msg })
120+
}
121+
}
122+
}
123+
const peerFork: any = c.hardforkForForkHash(peerForkHash)
124+
if (peerFork === null) {
125+
const msg = 'Unknown fork hash'
126+
debug(msg)
127+
throw new assert.AssertionError({ message: msg })
128+
}
129+
130+
if (!c.hardforkGteHardfork(peerFork.name, this._hardfork)) {
131+
if (peerNextFork === null || c.nextHardforkBlock(peerFork.name) !== peerNextFork) {
132+
const msg = 'Outdated fork status, remote needs software update'
133+
debug(msg)
134+
throw new assert.AssertionError({ message: msg })
135+
}
136+
}
137+
}
138+
83139
_handleStatus(): void {
84140
if (this._status === null || this._peerStatus === null) return
85141
clearTimeout(this._statusTimeoutId)
@@ -88,26 +144,48 @@ export class ETH extends EventEmitter {
88144
assertEq(this._status[1], this._peerStatus[1], 'NetworkId mismatch', debug)
89145
assertEq(this._status[4], this._peerStatus[4], 'Genesis block mismatch', debug)
90146

91-
this.emit('status', {
147+
let status: any = {
92148
networkId: this._peerStatus[1],
93149
td: Buffer.from(this._peerStatus[2]),
94150
bestHash: Buffer.from(this._peerStatus[3]),
95-
genesisHash: Buffer.from(this._peerStatus[4])
96-
})
151+
genesisHash: Buffer.from(this._peerStatus[4]),
152+
}
153+
154+
if (this._version >= 64) {
155+
assertEq(this._peerStatus[5].length, 2, 'Incorrect forkId msg format', debug)
156+
this._validateForkId(this._peerStatus[5] as Buffer[])
157+
console.log(`Successful Eth64 validation with ${this._peer._socket.remoteAddress}`)
158+
status['forkId'] = this._peerStatus[5]
159+
}
160+
161+
this.emit('status', status)
97162
}
98163

99164
getVersion() {
100165
return this._version
101166
}
102167

168+
_forkHashFromForkId(forkId: Buffer): string {
169+
return `0x${forkId.toString('hex')}`
170+
}
171+
172+
_nextForkFromForkId(forkId: Buffer): number {
173+
return buffer2int(forkId)
174+
}
175+
103176
_getStatusString(status: ETH.StatusMsg) {
104-
let sStr = `[V:${buffer2int(status[0])}, NID:${buffer2int(status[1])}, TD:${buffer2int(
105-
status[2]
106-
)}`
177+
let sStr = `[V:${buffer2int(status[0] as Buffer)}, NID:${buffer2int(
178+
status[1] as Buffer,
179+
)}, TD:${buffer2int(status[2] as Buffer)}`
107180
sStr += `, BestH:${formatLogId(status[3].toString('hex'), verbose)}, GenH:${formatLogId(
108181
status[4].toString('hex'),
109-
verbose
110-
)}]`
182+
verbose,
183+
)}`
184+
if (this._version >= 64) {
185+
sStr += `, ForkHash: 0x${(status[5][0] as Buffer).toString('hex')}`
186+
sStr += `, ForkNext: ${buffer2int(status[5][1] as Buffer)}`
187+
}
188+
sStr += `]`
111189
return sStr
112190
}
113191

@@ -120,6 +198,19 @@ export class ETH extends EventEmitter {
120198
status.bestHash,
121199
status.genesisHash
122200
]
201+
if (this._version >= 64) {
202+
if (status.latestBlock) {
203+
if (status.latestBlock < this._latestBlock) {
204+
throw new Error(
205+
'latest block provided is not matching the HF setting of the Common instance (Rlpx)',
206+
)
207+
}
208+
this._latestBlock = status.latestBlock
209+
}
210+
const forkHashB = Buffer.from(this._forkHash.substr(2), 'hex')
211+
const nextForkB = Buffer.from(this._nextForkBlock.toString(16), 'hex')
212+
this._status.push([forkHashB, nextForkB])
213+
}
123214

124215
debug(
125216
`Send STATUS message to ${this._peer._socket.remoteAddress}:${
@@ -171,20 +262,14 @@ export class ETH extends EventEmitter {
171262
}
172263

173264
export namespace ETH {
174-
export type StatusMsg = {
175-
0: Buffer
176-
1: Buffer
177-
2: Buffer
178-
3: Buffer
179-
4: Buffer
180-
length: 5
181-
}
265+
export interface StatusMsg extends Array<Buffer | Buffer[]> {}
182266

183267
export type StatusOpts = {
184268
version: number
185269
// networkId: number
186270
td: Buffer
187271
bestHash: Buffer
272+
latestBlock?: number
188273
genesisHash: Buffer
189274
}
190275

test/integration/eth-simulator.ts

+68-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import test from 'tape'
22
import * as devp2p from '../../src'
33
import * as util from './util'
4-
import Common from 'ethereumjs-common'
4+
import Common from '@ethereumjs/common'
55

66
const GENESIS_TD = 17179869184
77
const GENESIS_HASH = Buffer.from(
@@ -64,12 +64,17 @@ test('ETH: send status message (Genesis block mismatch)', async t => {
6464
util.twoPeerMsgExchange(t, opts, capabilities)
6565
})
6666

67+
<<<<<<< HEAD
6768
test('ETH: send allowed eth63', async t => {
6869
const opts: any = {}
70+
=======
71+
function sendWithProtocolVersion(t: test.Test, version: number, cap?: Object) {
72+
let opts: any = {}
73+
>>>>>>> Added support for Eth64 protocol version
6974
opts.status0 = Object.assign({}, status)
7075
opts.status1 = Object.assign({}, status)
7176
opts.onOnceStatus0 = function(rlpxs: any, eth: any) {
72-
t.equal(eth.getVersion(), 63, 'should use eth63 as protocol version')
77+
t.equal(eth.getVersion(), version, `should use eth${version} as protocol version`)
7378
eth.sendMessage(devp2p.ETH.MESSAGE_CODES.NEW_BLOCK_HASHES, [437000, 1, 0, 0])
7479
t.pass('should send NEW_BLOCK_HASHES message')
7580
}
@@ -80,9 +85,14 @@ test('ETH: send allowed eth63', async t => {
8085
t.end()
8186
}
8287
}
83-
util.twoPeerMsgExchange(t, opts, capabilities)
88+
util.twoPeerMsgExchange(t, opts, cap)
89+
}
90+
91+
test('ETH: should use latest protocol version on default', async t => {
92+
sendWithProtocolVersion(t, 64)
8493
})
8594

95+
<<<<<<< HEAD
8696
test('ETH: send allowed eth62', async t => {
8797
const cap = [devp2p.ETH.eth62]
8898
const opts: any = {}
@@ -98,8 +108,62 @@ test('ETH: send allowed eth62', async t => {
98108
util.destroyRLPXs(rlpxs)
99109
t.end()
100110
}
111+
=======
112+
test('ETH: should work with allowed eth64', async t => {
113+
sendWithProtocolVersion(t, 64)
114+
})
115+
116+
test('ETH -> Eth64 -> sendStatus(): should throw on non-matching latest block provided', async t => {
117+
const cap = [devp2p.ETH.eth64]
118+
const common = new Common('mainnet', 'byzantium')
119+
let status0: any = Object.assign({}, status)
120+
status0['latestBlock'] = 100000 // lower than Byzantium fork block 4370000
121+
122+
const rlpxs = util.initTwoPeerRLPXSetup(null, cap, common)
123+
rlpxs[0].on('peer:added', function(peer: any) {
124+
const protocol = peer.getProtocols()[0]
125+
t.throws(() => {
126+
protocol.sendStatus(status0)
127+
}, /latest block provided is not matching the HF setting/)
128+
util.destroyRLPXs(rlpxs)
129+
t.end()
130+
})
131+
})
132+
133+
test('ETH -> Eth64 -> ForkId validation 1a)', async t => {
134+
let opts: any = {}
135+
const cap = [devp2p.ETH.eth64]
136+
const common = new Common('mainnet', 'byzantium')
137+
let status0: any = Object.assign({}, status)
138+
// Take a latest block > next mainnet fork block (constantinople)
139+
// to trigger validation condition
140+
status0['latestBlock'] = 9069000
141+
opts.status0 = status0
142+
opts.status1 = Object.assign({}, status)
143+
opts.onPeerError0 = function(err: Error, rlpxs: any) {
144+
const msg = 'Remote is advertising a future fork that passed locally'
145+
t.equal(err.message, msg, `should emit error: ${msg}`)
146+
util.destroyRLPXs(rlpxs)
147+
t.end()
148+
>>>>>>> Added support for Eth64 protocol version
101149
}
102-
util.twoPeerMsgExchange(t, opts, cap)
150+
151+
util.twoPeerMsgExchange(t, opts, cap, common)
152+
})
153+
154+
test('ETH: should work with allowed eth63', async t => {
155+
let cap = [devp2p.ETH.eth63]
156+
sendWithProtocolVersion(t, 63, cap)
157+
})
158+
159+
test('ETH: should work with allowed eth63', async t => {
160+
let cap = [devp2p.ETH.eth63]
161+
sendWithProtocolVersion(t, 63, cap)
162+
})
163+
164+
test('ETH: work with allowed eth62', async t => {
165+
let cap = [devp2p.ETH.eth62]
166+
sendWithProtocolVersion(t, 62, cap)
103167
})
104168

105169
test('ETH: send not-allowed eth62', async t => {

test/integration/les-simulator.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import test from 'tape'
2-
import Common from 'ethereumjs-common'
2+
import Common from '@ethereumjs/common'
33
import * as devp2p from '../../src'
44
import * as util from './util'
55

test/integration/util.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Test } from 'tape'
22
import { DPT, ETH, RLPx, genPrivateKey } from '../../src'
3-
import Common from 'ethereumjs-common'
3+
import Common from '@ethereumjs/common'
44

55
export const localhost = '127.0.0.1'
66
export const basePort = 30306
@@ -42,7 +42,7 @@ export function getTestRLPXs(
4242
) {
4343
const rlpxs = []
4444
if (!capabilities) {
45-
capabilities = [ETH.eth63, ETH.eth62]
45+
capabilities = [ETH.eth64, ETH.eth63, ETH.eth62]
4646
}
4747
if (!common) {
4848
common = new Common('mainnet')

0 commit comments

Comments
 (0)