Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0dc6081
client: implmement eth_chainId RPC method
gabrocheleau Jun 21, 2021
00e3073
Merge branch 'master' into feat/rayonism-post-merge
gabrocheleau Jun 21, 2021
6485290
client: eth_chainId tests
gabrocheleau Jun 21, 2021
dd572ca
client: formatting
gabrocheleau Jun 21, 2021
51aa997
client: add synchronized boolean to client class
gabrocheleau Jun 22, 2021
dc1d0ac
Merge branch 'master' into client/chainId_sync_RPC
gabrocheleau Jun 28, 2021
351a454
Merge branch 'master' into client/chainId_sync_RPC
gabrocheleau Jul 2, 2021
125c76b
Merge branch 'client/chainId_sync_RPC' of https://github.com/ethereum…
gabrocheleau Jul 2, 2021
95f24c0
client: test synchronized boolean in client
gabrocheleau Jul 3, 2021
9c17544
client: [WIP] test eth_syncing rpc method
gabrocheleau Jul 3, 2021
428d9c1
client: [WIP] eth_syncing spec cleanup
gabrocheleau Jul 3, 2021
7abe74c
client: fix linting
gabrocheleau Jul 3, 2021
fa3ceaf
client: implement startingBlock and highestBlock in eth_syncing
gabrocheleau Jul 3, 2021
37ca40a
client: implementing latest method in lightSync synchronizer
gabrocheleau Jul 3, 2021
995bdec
client: temporarily comment out eth_syncing test to avoid CI timeout
gabrocheleau Jul 3, 2021
068679e
client: eth_syncing mocking and testing
gabrocheleau Jul 5, 2021
1ddea8a
chore: client: remove unused variable
gabrocheleau Jul 5, 2021
dcf70de
client: reset testDouble in eth_syncing test
gabrocheleau Jul 5, 2021
b480c0e
Merge branch 'master' into client/chainId_sync_RPC
gabrocheleau Jul 6, 2021
9562b5e
eth_syncing: fix hanging by using createClient mock
ryanio Jul 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/client/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default class EthereumClient extends events.EventEmitter {

public opened: boolean
public started: boolean
public synchronized: boolean

/**
* Create new node
Expand All @@ -68,6 +69,7 @@ export default class EthereumClient extends events.EventEmitter {
]
this.opened = false
this.started = false
this.synchronized = false
}

/**
Expand All @@ -92,6 +94,7 @@ export default class EthereumClient extends events.EventEmitter {
})
s.on('synchronized', () => {
this.emit('synchronized')
this.synchronized = true
})
})
await Promise.all(this.services.map((s) => s.open()))
Expand Down
53 changes: 52 additions & 1 deletion packages/client/lib/rpc/modules/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { INTERNAL_ERROR, INVALID_PARAMS, PARSE_ERROR } from '../error-code'
import { RpcTx } from '../types'
import type { Chain } from '../../blockchain'
import type { EthereumClient } from '../..'
import type { EthereumService } from '../../service'
import { EthereumService } from '../../service'
import type { EthProtocol } from '../../net/protocol'
import type VM from '@ethereumjs/vm'
import { Block } from '@ethereumjs/block'
Expand Down Expand Up @@ -50,6 +50,8 @@ export class Eth {
[validators.blockOption],
])

this.chainId = middleware(this.chainId.bind(this), 0, [])

this.estimateGas = middleware(this.estimateGas.bind(this), 2, [
[validators.transaction()],
[validators.blockOption],
Expand Down Expand Up @@ -95,6 +97,8 @@ export class Eth {
this.sendRawTransaction = middleware(this.sendRawTransaction.bind(this), 1, [[validators.hex]])

this.protocolVersion = middleware(this.protocolVersion.bind(this), 0, [])

this.syncing = middleware(this.syncing.bind(this), 0, [])
}

/**
Expand Down Expand Up @@ -161,6 +165,16 @@ export class Eth {
return bufferToHex(execResult.returnValue)
}

/**
* Returns the currently configured chain id, a value used in replay-protected transaction signing as introduced by EIP-155.
* @param _params An empty array
* @returns The chain ID.
*/
async chainId(_params = []) {
const chainId = this._chain.config.chainCommon.chainIdBN()
return `0x${chainId.toString(16)}`
}

/**
* Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
* The transaction will not be added to the blockchain.
Expand Down Expand Up @@ -490,4 +504,41 @@ export class Eth {
}
}
}
/**
* Returns an object with data about the sync status or false.
* @param params An empty array
* @returns An object with sync status data or false (when not syncing)
* * startingBlock - The block at which the import started (will only be reset after the sync reached his head)
* * currentBlock - The current block, same as eth_blockNumber
* * highestBlock - The estimated highest block
*/
async syncing(_params = []) {
if (this.client.synchronized) {
return false
}

const currentBlockHeader = await this._chain.getLatestHeader()
const currentBlock = `0x${currentBlockHeader.number.toString(16)}`

const synchronizer = this.client.services[0].synchronizer
const startingBlock = `0x${synchronizer.startingBlock.toString(16)}`
const bestPeer = synchronizer.best()
if (!bestPeer) {
return {
code: INTERNAL_ERROR,
message: `no peer available for synchronization`,
}
}

const highestBlockHeader = await synchronizer.latest(bestPeer)
if (!highestBlockHeader) {
return {
code: INTERNAL_ERROR,
message: `highest block header unavailable`,
}
}
const highestBlock = `0x${highestBlockHeader.number.toString(16)}`

return { startingBlock, currentBlock, highestBlock }
}
}
1 change: 1 addition & 0 deletions packages/client/lib/sync/fullsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export class FullSynchronizer extends Synchronizer {
const number = this.chain.blocks.height.toNumber()
const td = this.chain.blocks.td.toString(10)
const hash = this.chain.blocks.latest!.hash()
this.startingBlock = number
this.config.chainCommon.setHardforkByBlockNumber(number)
this.config.logger.info(
`Latest local block: number=${number} td=${td} hash=${short(
Expand Down
15 changes: 14 additions & 1 deletion packages/client/lib/sync/lightsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ export class LightSynchronizer extends Synchronizer {
return best
}

/**
* Get latest header of peer
* @return {Promise} Resolves with header
*/
async latest(peer: Peer) {
const headers = await peer.eth?.getBlockHeaders({
block: peer.eth!.status.bestHash,
max: 1,
})
return headers?.[0]
}

/**
* Sync all headers and state from peer starting from current height.
* @param peer remote peer to sync with
Expand Down Expand Up @@ -118,9 +130,10 @@ export class LightSynchronizer extends Synchronizer {
async open(): Promise<void> {
await this.chain.open()
await this.pool.open()
const number = this.chain.headers.height.toString(10)
const number = this.chain.headers.height.toNumber()
const td = this.chain.headers.td.toString(10)
const hash = this.chain.blocks.latest!.hash()
this.startingBlock = number
this.config.logger.info(`Latest local header: number=${number} td=${td} hash=${short(hash)}`)
}

Expand Down
2 changes: 2 additions & 0 deletions packages/client/lib/sync/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export abstract class Synchronizer extends EventEmitter {
protected interval: number
public running: boolean
protected forceSync: boolean
public startingBlock: number

/**
* Create new node
Expand All @@ -55,6 +56,7 @@ export abstract class Synchronizer extends EventEmitter {
this.interval = options.interval ?? 1000
this.running = false
this.forceSync = false
this.startingBlock = 0

this.pool.on('added', (peer: Peer) => {
if (this.syncable(peer)) {
Expand Down
13 changes: 13 additions & 0 deletions packages/client/test/client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,23 @@ tape('[EthereumClient]', async (t) => {
servers[0].emit('listening', 'details0')
client.services[0].emit('error', 'err1')
client.services[0].emit('synchronized')

t.ok(client.opened, 'opened')
t.equals(await client.open(), false, 'already opened')
})

t.test('should set synchronized to true once synchronized is emitted', async (t) => {
const servers = [new Server()] as any
const config = new Config({ servers })
const client = new EthereumClient({ config })

t.equals(client.synchronized, false, 'not synchronized yet')
await client.open()
client.services[0].emit('synchronized')
t.equals(client.synchronized, true, 'synchronized')
t.end()
})

t.test('should start/stop', async (t) => {
const servers = [new Server()] as any
const config = new Config({ servers })
Expand Down
61 changes: 61 additions & 0 deletions packages/client/test/rpc/eth/chainId.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import tape from 'tape'
import Common from '../../../../common/dist'
import { BN } from '../../../../util/dist'
import { baseSetup, params, baseRequest, createClient, createManager, startRPC } from '../helpers'

const method = 'eth_chainId'

tape(`${method}: calls`, (t) => {
const server = baseSetup()

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'chainId should be a string'
if (typeof res.body.result !== 'string') {
throw new Error(msg)
} else {
t.pass(msg)
}
}
baseRequest(t, server, req, 200, expectRes)
})

tape(`${method}: returns 1 for Mainnet`, (t) => {
const server = baseSetup()

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return chainId 1'
t.equal(res.body.result, '0x1', msg)
}
baseRequest(t, server, req, 200, expectRes)
})

tape(`${method}: returns 3 for Ropsten`, (t) => {
const manager = createManager(
createClient({ opened: true, commonChain: new Common({ chain: 'ropsten' }) })
)
const server = startRPC(manager.getMethods())

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return chainId 3'
t.equal(res.body.result, '0x3', msg)
}
baseRequest(t, server, req, 200, expectRes)
})

tape(`${method}: returns 42 for Kovan`, (t) => {
const manager = createManager(
createClient({ opened: true, commonChain: new Common({ chain: 'kovan' }) })
)
const server = startRPC(manager.getMethods())

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return chainId 42'
const chainId = new BN(42).toString(16)
t.equal(res.body.result, `0x${chainId}`, msg)
}
baseRequest(t, server, req, 200, expectRes)
})
106 changes: 106 additions & 0 deletions packages/client/test/rpc/eth/syncing.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import tape from 'tape-catch'
import td from 'testdouble'
import { baseRequest, createManager, createClient, params, startRPC } from '../helpers'
import { BN } from 'ethereumjs-util'

const method = 'eth_syncing'

tape(`${method}: should return false when the client is synchronized`, async (t) => {
const client = createClient()
const manager = createManager(client)
const server = startRPC(manager.getMethods())

t.equals(client.synchronized, false, 'not synchronized yet')
client.synchronized = true
t.equals(client.synchronized, true, 'synchronized')

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return false'
if (res.body.result === false) {
t.pass(msg)
} else {
throw new Error(msg)
}
}
baseRequest(t, server, req, 200, expectRes)
})

tape(`${method}: should return no peer available error`, async (t) => {
const client = createClient({ noPeers: true })
const manager = createManager(client)
const rpcServer = startRPC(manager.getMethods())

t.equals(client.synchronized, false, 'not synchronized yet')

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return no peer available error'
if (res.body.result.message === 'no peer available for synchronization') {
t.pass(msg)
} else {
throw new Error(msg)
}
}

baseRequest(t, rpcServer, req, 200, expectRes)
})

tape(`${method}: should return highest block header unavailable error`, async (t) => {
const client = createClient()
const manager = createManager(client)
const rpcServer = startRPC(manager.getMethods())

const synchronizer = client.services[0].synchronizer
synchronizer.best = td.func<typeof synchronizer['best']>()
td.when(synchronizer.best()).thenReturn('peer')

t.equals(client.synchronized, false, 'not synchronized yet')

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return highest block header unavailable error'
if (res.body.result.message === 'highest block header unavailable') {
t.pass(msg)
} else {
throw new Error(msg)
}
}

baseRequest(t, rpcServer, req, 200, expectRes)
})

tape(`${method}: should return syncing status object when unsynced`, async (t) => {
const client = createClient()
const manager = createManager(client)
const rpcServer = startRPC(manager.getMethods())

const synchronizer = client.services[0].synchronizer
synchronizer.best = td.func<typeof synchronizer['best']>()
synchronizer.latest = td.func<typeof synchronizer['latest']>()
td.when(synchronizer.best()).thenReturn('peer')
td.when(synchronizer.latest('peer' as any)).thenResolve({ number: new BN(2) })

t.equals(client.synchronized, false, 'not synchronized yet')

const req = params(method, [])
const expectRes = (res: any) => {
const msg = 'should return syncing status object'
if (
res.body.result.startingBlock === '0x0' &&
res.body.result.currentBlock === '0x0' &&
res.body.result.highestBlock === '0x2'
) {
t.pass(msg)
} else {
throw new Error(msg)
}
}

baseRequest(t, rpcServer, req, 200, expectRes)
})

tape('should reset td', (t) => {
td.reset()
t.end()
})
13 changes: 11 additions & 2 deletions packages/client/test/rpc/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,17 @@ export function createClient(clientOpts: any = {}) {
}),
]

let synchronizer
let synchronizer: any = {
startingBlock: 0,
best: () => {
return undefined
},
latest: () => {
return undefined
},
}
if (clientOpts.includeVM) {
synchronizer = { execution: { vm: new VM({ blockchain, common }) } }
synchronizer = { ...synchronizer, execution: { vm: new VM({ blockchain, common }) } }
}

let peers = [1, 2, 3]
Expand All @@ -63,6 +71,7 @@ export function createClient(clientOpts: any = {}) {
}

const client: any = {
synchronized: false,
config,
services: [
{
Expand Down