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

Commit

Permalink
Added support for Eth64 protocol version
Browse files Browse the repository at this point in the history
  • Loading branch information
holgerd77 committed Jul 3, 2020
1 parent 2466843 commit d4a3627
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 41 deletions.
2 changes: 1 addition & 1 deletion examples/peer-communication-les.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const GENESIS_HASH = Buffer.from(
'hex',
)

const Common = require('ethereumjs-common').default
const Common = require('@ethereumjs/common').default
const common = new Common('rinkeby')
const bootstrapNodes = common.bootstrapNodes()
const BOOTNODES = bootstrapNodes.map((node: any) => {
Expand Down
4 changes: 2 additions & 2 deletions examples/peer-communication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import rlp from 'rlp-encoding'

const PRIVATE_KEY = randomBytes(32)

const Common = require('ethereumjs-common').default
const Common = require('@ethereumjs/common').default
const common = new Common('mainnet')
const bootstrapNodes = common.bootstrapNodes()
const BOOTNODES = bootstrapNodes.map((node: any) => {
Expand Down Expand Up @@ -61,7 +61,7 @@ dpt.on('error', err => console.error(chalk.red(`DPT error: ${err}`)))
const rlpx = new devp2p.RLPx(PRIVATE_KEY, {
dpt: dpt,
maxPeers: 25,
capabilities: [devp2p.ETH.eth63, devp2p.ETH.eth62],
capabilities: [devp2p.ETH.eth64],
common: common,
remoteClientIdFilter: REMOTE_CLIENTID_FILTER,
listenPort: null,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@
"test": "node_modules/tape/bin/tape -r ts-node/register ./test/index.ts"
},
"dependencies": {
"@ethereumjs/common": "file:ethereumjs-common-1.5.1.tgz",
"babel-runtime": "^6.11.6",
"bl": "^1.1.2",
"debug": "^2.2.0",
"ethereumjs-common": "^1.5.1",
"inherits": "^2.0.1",
"ip": "^1.1.3",
"k-bucket": "^3.2.1",
Expand Down
110 changes: 97 additions & 13 deletions src/eth/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from 'assert'
import { EventEmitter } from 'events'
import rlp from 'rlp-encoding'
import ms from 'ms'
Expand All @@ -18,6 +19,12 @@ export class ETH extends EventEmitter {
_statusTimeoutId: NodeJS.Timeout
_send: SendMethod

// Eth64
_hardfork: string = 'chainstart'
_latestBlock: number = 0
_forkHash: string = ''
_nextForkBlock: number = 0

constructor(version: number, peer: Peer, send: SendMethod) {
super()

Expand All @@ -30,10 +37,24 @@ export class ETH extends EventEmitter {
this._statusTimeoutId = setTimeout(() => {
this._peer.disconnect(DISCONNECT_REASONS.TIMEOUT)
}, ms('5s'))

// Set forkHash and nextForkBlock
if (this._version >= 64) {
const c = this._peer._common
this._hardfork = c.hardfork() ? (c.hardfork() as string) : this._hardfork
// Set latestBlock minimally to start block of fork to have some more
// accurate basis if no latestBlock is provided along status send
this._latestBlock = c.hardforkBlock(this._hardfork)
this._forkHash = c.forkHash(this._hardfork)
// Next fork block number or 0 if none available
const nextForkBlock = c.nextHardforkBlock(this._hardfork)
this._nextForkBlock = nextForkBlock ? nextForkBlock : 0
}
}

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

_handleMessage(code: ETH.MESSAGE_CODES, data: any) {
const payload = rlp.decode(data)
Expand Down Expand Up @@ -80,6 +101,42 @@ export class ETH extends EventEmitter {
this.emit('message', code, payload)
}

/**
* Eth 64 Fork ID validation (EIP-2124)
* @param forkId Remote fork ID
*/
_validateForkId(forkId: Buffer[]) {
const c = this._peer._common

const peerForkHash =`0x${forkId[0].toString('hex')}`
const peerNextFork = buffer2int(forkId[1])

if (this._forkHash === peerForkHash) {
if (peerNextFork) {
if (this._latestBlock >= peerNextFork) {
const msg = 'Remote is advertising a future fork that passed locally'
debug(msg)
throw new assert.AssertionError({ message: msg })
}

}
}
const peerFork: any = c.hardforkForForkHash(peerForkHash)
if (peerFork === null) {
const msg = 'Unknown fork hash'
debug(msg)
throw new assert.AssertionError({ message: msg })
}

if (!c.hardforkGteHardfork(peerFork.name, this._hardfork)) {
if (peerNextFork === null || c.nextHardforkBlock(peerFork.name) !== peerNextFork) {
const msg = 'Outdated fork status, remote needs software update'
debug(msg)
throw new assert.AssertionError({ message: msg })
}
}
}

_handleStatus(): void {
if (this._status === null || this._peerStatus === null) return
clearTimeout(this._statusTimeoutId)
Expand All @@ -88,26 +145,48 @@ export class ETH extends EventEmitter {
assertEq(this._status[1], this._peerStatus[1], 'NetworkId mismatch', debug)
assertEq(this._status[4], this._peerStatus[4], 'Genesis block mismatch', debug)

this.emit('status', {
let status: any = {
networkId: this._peerStatus[1],
td: Buffer.from(this._peerStatus[2]),
bestHash: Buffer.from(this._peerStatus[3]),
genesisHash: Buffer.from(this._peerStatus[4]),
})
}

if (this._version >= 64) {
assertEq(this._peerStatus[5].length, 2, 'Incorrect forkId msg format', debug)
this._validateForkId(this._peerStatus[5] as Buffer[])
console.log(`Successful Eth64 validation with ${this._peer._socket.remoteAddress}`)
status['forkId'] = this._peerStatus[5]
}

this.emit('status', status)
}

getVersion() {
return this._version
}

_forkHashFromForkId(forkId: Buffer): string {
return `0x${forkId.toString('hex')}`
}

_nextForkFromForkId(forkId: Buffer): number {
return buffer2int(forkId)
}

_getStatusString(status: ETH.StatusMsg) {
let sStr = `[V:${buffer2int(status[0])}, NID:${buffer2int(status[1])}, TD:${buffer2int(
status[2],
let sStr = `[V:${buffer2int(status[0] as Buffer)}, NID:${buffer2int(status[1] as Buffer)}, TD:${buffer2int(
status[2] as Buffer,
)}`
sStr += `, BestH:${formatLogId(status[3].toString('hex'), verbose)}, GenH:${formatLogId(
status[4].toString('hex'),
verbose,
)}]`
)}`
if (this._version >= 64) {
sStr += `, ForkHash: 0x${(status[5][0] as Buffer).toString('hex')}`
sStr += `, ForkNext: ${buffer2int(status[5][1] as Buffer)}`
}
sStr += `]`
return sStr
}

Expand All @@ -120,6 +199,17 @@ export class ETH extends EventEmitter {
status.bestHash,
status.genesisHash,
]
if (this._version >= 64) {
if (status.latestBlock) {
if (status.latestBlock < this._latestBlock) {
throw new Error('latest block provided is not matching the HF setting of the Common instance (Rlpx)')
}
this._latestBlock = status.latestBlock
}
const forkHashB = Buffer.from(this._forkHash.substr(2), 'hex')
const nextForkB = Buffer.from(this._nextForkBlock.toString(16), 'hex')
this._status.push([forkHashB, nextForkB])
}

debug(
`Send STATUS message to ${this._peer._socket.remoteAddress}:${
Expand Down Expand Up @@ -171,20 +261,14 @@ export class ETH extends EventEmitter {
}

export namespace ETH {
export type StatusMsg = {
0: Buffer
1: Buffer
2: Buffer
3: Buffer
4: Buffer
length: 5
}
export interface StatusMsg extends Array<Buffer | Buffer[]> {}

export type StatusOpts = {
version: number
// networkId: number
td: Buffer
bestHash: Buffer
latestBlock?: number
genesisHash: Buffer
}

Expand Down
2 changes: 1 addition & 1 deletion src/rlpx/peer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { debug as createDebugLogger } from 'debug'
import { EventEmitter } from 'events'
import ms from 'ms'
import rlp from 'rlp-encoding'
import Common from 'ethereumjs-common'
import Common from '@ethereumjs/common'
import { ECIES } from './ecies'
import { ETH, LES } from '../'
import { int2buffer, buffer2int, formatLogData } from '../util'
Expand Down
2 changes: 1 addition & 1 deletion src/rlpx/rlpx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { publicKeyCreate } from 'secp256k1'
import { EventEmitter } from 'events'
import { debug as createDebugLogger } from 'debug'
import LRUCache from 'lru-cache'
import Common from 'ethereumjs-common'
import Common from '@ethereumjs/common'
// note: relative path only valid in .js file in dist
const { version: pVersion } = require('../../package.json')
import { pk2id, createDeferred, formatLogId } from '../util'
Expand Down
77 changes: 58 additions & 19 deletions test/integration/eth-simulator.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import test from 'tape'
import * as devp2p from '../../src'
import * as util from './util'
import Common from 'ethereumjs-common'
import Common from '@ethereumjs/common'

const GENESIS_TD = 17179869184
const GENESIS_HASH = Buffer.from(
'd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3',
'hex',
)

var capabilities = [devp2p.ETH.eth63, devp2p.ETH.eth62]
const capabilities = [devp2p.ETH.eth63, devp2p.ETH.eth62]

const status = {
td: devp2p.int2buffer(GENESIS_TD),
Expand Down Expand Up @@ -64,12 +64,12 @@ test('ETH: send status message (Genesis block mismatch)', async t => {
util.twoPeerMsgExchange(t, opts, capabilities)
})

test('ETH: send allowed eth63', async t => {
function sendWithProtocolVersion(t: test.Test, version: number, cap?: Object) {
let opts: any = {}
opts.status0 = Object.assign({}, status)
opts.status1 = Object.assign({}, status)
opts.onOnceStatus0 = function(rlpxs: any, eth: any) {
t.equal(eth.getVersion(), 63, 'should use eth63 as protocol version')
t.equal(eth.getVersion(), version, `should use eth${version} as protocol version`)
eth.sendMessage(devp2p.ETH.MESSAGE_CODES.NEW_BLOCK_HASHES, [437000, 1, 0, 0])
t.pass('should send NEW_BLOCK_HASHES message')
}
Expand All @@ -80,26 +80,65 @@ test('ETH: send allowed eth63', async t => {
t.end()
}
}
util.twoPeerMsgExchange(t, opts, capabilities)
util.twoPeerMsgExchange(t, opts, cap)
}

test('ETH: should use latest protocol version on default', async t => {
sendWithProtocolVersion(t, 64)
})

test('ETH: send allowed eth62', async t => {
let cap = [devp2p.ETH.eth62]
test('ETH: should work with allowed eth64', async t => {
sendWithProtocolVersion(t, 64)
})

test('ETH -> Eth64 -> sendStatus(): should throw on non-matching latest block provided', async t => {
const cap = [devp2p.ETH.eth64]
const common = new Common('mainnet', 'byzantium')
let status0: any = Object.assign({}, status)
status0['latestBlock'] = 100000 // lower than Byzantium fork block 4370000

const rlpxs = util.initTwoPeerRLPXSetup(null, cap, common)
rlpxs[0].on('peer:added', function(peer: any) {
const protocol = peer.getProtocols()[0]
t.throws(() => { protocol.sendStatus(status0) }, /latest block provided is not matching the HF setting/)
util.destroyRLPXs(rlpxs)
t.end()
})
})

test('ETH -> Eth64 -> ForkId validation 1a)', async t => {
let opts: any = {}
opts.status0 = Object.assign({}, status)
const cap = [devp2p.ETH.eth64]
const common = new Common('mainnet', 'byzantium')
let status0: any = Object.assign({}, status)
// Take a latest block > next mainnet fork block (constantinople)
// to trigger validation condition
status0['latestBlock'] = 9069000
opts.status0 = status0
opts.status1 = Object.assign({}, status)
opts.onOnceStatus0 = function(rlpxs: any, eth: any) {
eth.sendMessage(devp2p.ETH.MESSAGE_CODES.NEW_BLOCK_HASHES, [437000, 1, 0, 0])
t.pass('should send NEW_BLOCK_HASHES message')
}
opts.onOnMsg1 = function(rlpxs: any, eth: any, code: any, payload: any) {
if (code === devp2p.ETH.MESSAGE_CODES.NEW_BLOCK_HASHES) {
t.pass('should receive NEW_BLOCK_HASHES message')
util.destroyRLPXs(rlpxs)
t.end()
}
opts.onPeerError0 = function(err: Error, rlpxs: any) {
const msg = 'Remote is advertising a future fork that passed locally'
t.equal(err.message, msg, `should emit error: ${msg}`)
util.destroyRLPXs(rlpxs)
t.end()
}
util.twoPeerMsgExchange(t, opts, cap)

util.twoPeerMsgExchange(t, opts, cap, common)
})

test('ETH: should work with allowed eth63', async t => {
let cap = [devp2p.ETH.eth63]
sendWithProtocolVersion(t, 63, cap)
})

test('ETH: should work with allowed eth63', async t => {
let cap = [devp2p.ETH.eth63]
sendWithProtocolVersion(t, 63, cap)
})

test('ETH: work with allowed eth62', async t => {
let cap = [devp2p.ETH.eth62]
sendWithProtocolVersion(t, 62, cap)
})

test('ETH: send not-allowed eth62', async t => {
Expand Down
2 changes: 1 addition & 1 deletion test/integration/les-simulator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from 'tape'
import Common from 'ethereumjs-common'
import Common from '@ethereumjs/common'
import * as devp2p from '../../src'
import * as util from './util'

Expand Down
4 changes: 2 additions & 2 deletions test/integration/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Test } from 'tape'

import { DPT, ETH, RLPx, genPrivateKey } from '../../src'
import Common from 'ethereumjs-common'
import Common from '@ethereumjs/common'

export const localhost = '127.0.0.1'
export const basePort = 30306
Expand Down Expand Up @@ -43,7 +43,7 @@ export function getTestRLPXs(
) {
const rlpxs = []
if (!capabilities) {
capabilities = [ETH.eth63, ETH.eth62]
capabilities = [ETH.eth64, ETH.eth63, ETH.eth62]
}
if (!common) {
common = new Common('mainnet')
Expand Down

0 comments on commit d4a3627

Please sign in to comment.