Skip to content

Commit d2fbe9a

Browse files
authored
util: remove undefined handling from bytesToHex (#4004)
* util: document and remove undefined handling from bytesToHex * util: remove typecasting * util: refactor account handling * chore: simplify handling * util: deprecate account constructor and update docs * vm: fix event test * client: fix client tests * lint: remove console olgs * client: fix more client tests * lint: remove empty block * client: remove unnecessary optional chaining * client: remove it.only
1 parent aba2ffc commit d2fbe9a

File tree

10 files changed

+104
-131
lines changed

10 files changed

+104
-131
lines changed

packages/client/src/net/peerpool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export class PeerPool {
213213
* @emits {@link Event.POOL_PEER_ADDED}
214214
*/
215215
add(peer?: Peer) {
216-
if (peer && peer.id && !this.pool.get(peer.id)) {
216+
if (peer?.id !== undefined && !this.pool.get(peer.id)) {
217217
this.pool.set(peer.id, peer)
218218
peer.pooled = true
219219
this.config.events.emit(Event.POOL_PEER_ADDED, peer)

packages/client/src/rpc/modules/admin.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,10 @@ export class Admin {
9595
name: peer.rlpxPeer?.['_hello']?.clientId ?? null,
9696
protocols: {
9797
eth: {
98-
head: peer.eth?.updatedBestHeader
99-
? bytesToHex(peer.eth.updatedBestHeader?.hash())
100-
: bytesToHex(peer.eth?.status.bestHash),
98+
head:
99+
peer.eth?.updatedBestHeader !== undefined
100+
? bytesToHex(peer.eth.updatedBestHeader.hash())
101+
: bytesToHex(peer.eth?.status.bestHash ?? new Uint8Array()),
101102
difficulty: peer.eth?.status.td.toString(10),
102103
version: peer.eth?.['versions'].slice(-1)[0] ?? null,
103104
},

packages/client/src/sync/fetcher/accountfetcher.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,10 @@ export class AccountFetcher extends Fetcher<JobTask, AccountData[], AccountData>
337337
const { task, partialResult } = job
338338
const { first } = task
339339
// Snap protocol will automatically pad it with 32 bytes left, so we don't need to worry
340-
const origin = partialResult
341-
? bigIntToBytes(bytesToBigInt(partialResult[partialResult.length - 1].hash) + BIGINT_1)
342-
: bigIntToBytes(first)
340+
const origin =
341+
partialResult !== undefined
342+
? bigIntToBytes(bytesToBigInt(partialResult[partialResult.length - 1].hash) + BIGINT_1)
343+
: bigIntToBytes(first)
343344
return setLengthLeft(origin, 32)
344345
}
345346

@@ -470,9 +471,12 @@ export class AccountFetcher extends Fetcher<JobTask, AccountData[], AccountData>
470471
const fullResult = (job.partialResult ?? []).concat(result)
471472

472473
// update highest known hash
473-
const highestReceivedhash = result.at(-1)?.hash as Uint8Array
474+
const highestReceivedhash = result.at(-1)?.hash
474475
if (this.highestKnownHash) {
475-
if (compareBytes(highestReceivedhash, this.highestKnownHash) > 0) {
476+
if (
477+
highestReceivedhash !== undefined &&
478+
compareBytes(highestReceivedhash, this.highestKnownHash) > 0
479+
) {
476480
this.highestKnownHash = highestReceivedhash
477481
}
478482
} else {

packages/client/test/sync/fetcher/accountfetcher.spec.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -188,20 +188,19 @@ describe('[AccountFetcher]', async () => {
188188
address: 'random',
189189
latest: vi.fn(),
190190
}
191-
const partialResult: any = [
192-
[
193-
{
194-
hash: new Uint8Array(0),
195-
body: [new Uint8Array(0), new Uint8Array(0), new Uint8Array(0), new Uint8Array(0)],
196-
},
197-
{
198-
hash: new Uint8Array(0),
199-
body: [new Uint8Array(0), new Uint8Array(0), new Uint8Array(0), new Uint8Array(0)],
200-
},
201-
],
191+
const partialResult = [
192+
{
193+
hash: new Uint8Array(0),
194+
body: [new Uint8Array(0), new Uint8Array(0), new Uint8Array(0), new Uint8Array(0)],
195+
},
196+
{
197+
hash: new Uint8Array(0),
198+
body: [new Uint8Array(0), new Uint8Array(0), new Uint8Array(0), new Uint8Array(0)],
199+
},
202200
]
203201
const job = { peer, partialResult, task }
204-
const result = (await fetcher.request(job as any)) as any
202+
const result = await fetcher.request(job as any)
203+
assert.isDefined(result)
205204
assert.equal(
206205
JSON.stringify(result[0]),
207206
JSON.stringify({ skipped: true }),
@@ -219,17 +218,15 @@ describe('[AccountFetcher]', async () => {
219218
first: BigInt(1),
220219
count: BigInt(3),
221220
})
222-
const partialResult: any = [
223-
[
224-
{
225-
hash: new Uint8Array(0),
226-
body: [new Uint8Array(0), new Uint8Array(0), new Uint8Array(0), new Uint8Array(0)],
227-
},
228-
{
229-
hash: new Uint8Array(0),
230-
body: [new Uint8Array(0), new Uint8Array(0), new Uint8Array(0), new Uint8Array(0)],
231-
},
232-
],
221+
const partialResult = [
222+
{
223+
hash: new Uint8Array(0),
224+
body: [new Uint8Array(0), new Uint8Array(0), new Uint8Array(0), new Uint8Array(0)],
225+
},
226+
{
227+
hash: new Uint8Array(0),
228+
body: [new Uint8Array(0), new Uint8Array(0), new Uint8Array(0), new Uint8Array(0)],
229+
},
233230
]
234231

235232
const task = { count: 3, first: BigInt(1) }

packages/client/test/sync/fetcher/storagefetcher.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,10 @@ describe('[StorageFetcher]', async () => {
255255
pool,
256256
root: utf8ToBytes(''),
257257
} as StorageFetcherOptions)
258-
const partialResult: any = [
258+
const partialResult = [
259259
[
260-
[{ hash: utf8ToBytes(''), body: utf8ToBytes('') }],
261-
[{ hash: utf8ToBytes(''), body: utf8ToBytes('') }],
260+
{ hash: utf8ToBytes(''), body: utf8ToBytes('') },
261+
{ hash: utf8ToBytes(''), body: utf8ToBytes('') },
262262
],
263263
]
264264

@@ -276,7 +276,7 @@ describe('[StorageFetcher]', async () => {
276276
},
277277
],
278278
}
279-
const resData = RLP.decode(hexToBytes(_storageRangesRLP)) as unknown
279+
const resData = RLP.decode(hexToBytes(_storageRangesRLP))
280280
const res = p.decode(
281281
p.messages.filter((message) => message.name === 'StorageRanges')[0],
282282
resData,

packages/util/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { hexToBytes, isValidChecksumAddress } from '@ethereumjs/util'
2929

3030
### Module: [account](src/account.ts)
3131

32-
Class representing an `Account` and providing private/public key and address-related functionality (creation, validation, conversion).
32+
Class representing an `Account` and providing private/public key and address-related functionality (creation, validation, conversion). It is not recommended to use this constructor directly. Instead use the static factory methods to assist in creating an Account from varying data types.
3333

3434
```ts
3535
// ./examples/account.ts

packages/util/src/account.ts

Lines changed: 45 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { EthereumJSErrorWithoutCode } from './errors.ts'
1919
import { assertIsBytes, assertIsHexString, assertIsString } from './helpers.ts'
2020
import { stripHexPrefix } from './internal.ts'
2121

22-
import type { BigIntLike, BytesLike, PrefixedHexString } from './types.ts'
22+
import type { BigIntLike, BytesLike, NestedUint8Array, PrefixedHexString } from './types.ts'
2323

2424
export interface AccountData {
2525
nonce?: BigIntLike
@@ -39,6 +39,35 @@ export interface PartialAccountData {
3939

4040
export type AccountBodyBytes = [Uint8Array, Uint8Array, Uint8Array, Uint8Array]
4141

42+
/**
43+
* Handles the null indicator for RLP encoded accounts
44+
* @returns {null} is the null indicator is 0
45+
* @returns The unchanged value is the null indicator is 1
46+
* @throws if the null indicator is > 1
47+
* @throws if the length of values is < 2
48+
* @param value The value to convert
49+
* @returns The converted value
50+
*/
51+
function handleNullIndicator(values: NestedUint8Array | Uint8Array): Uint8Array | null {
52+
// Needed if some values are not provided to the array (e.g. partial account RLP)
53+
if (values[0] === undefined) {
54+
return null
55+
}
56+
57+
const nullIndicator = bytesToInt(values[0] as Uint8Array)
58+
59+
if (nullIndicator === 0) {
60+
return null
61+
}
62+
if (nullIndicator > 1) {
63+
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${nullIndicator}`)
64+
}
65+
if (values.length < 2) {
66+
throw EthereumJSErrorWithoutCode(`Invalid values length=${values.length}`)
67+
}
68+
return values[1] as Uint8Array
69+
}
70+
4271
/**
4372
* Account class to load and maintain the basic account objects.
4473
* Supports partial loading and access required for verkle with null
@@ -126,8 +155,10 @@ export class Account {
126155

127156
/**
128157
* This constructor assigns and validates the values.
129-
* Use the static factory methods to assist in creating an Account from varying data types.
130-
* undefined get assigned with the defaults present, but null args are retained as is
158+
* It is not recommended to use this constructor directly. Instead use the static
159+
* factory methods to assist in creating an Account from varying data types.
160+
* undefined get assigned with the defaults, but null args are retained as is
161+
* @deprecated
131162
*/
132163
constructor(
133164
nonce: bigint | null = BIGINT_0,
@@ -325,91 +356,26 @@ export function createAccountFromRLP(serialized: Uint8Array) {
325356
}
326357

327358
export function createPartialAccountFromRLP(serialized: Uint8Array) {
328-
const values = RLP.decode(serialized) as Uint8Array[][]
359+
const values = RLP.decode(serialized)
329360

330361
if (!Array.isArray(values)) {
331362
throw EthereumJSErrorWithoutCode('Invalid serialized account input. Must be array')
332363
}
333364

334-
let nonce = null
335-
if (!Array.isArray(values[0])) {
336-
throw EthereumJSErrorWithoutCode('Invalid partial nonce encoding. Must be array')
337-
} else {
338-
const isNotNullIndicator = bytesToInt(values[0][0])
339-
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
340-
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for nonce`)
341-
}
342-
if (isNotNullIndicator === 1) {
343-
nonce = bytesToBigInt(values[0][1])
365+
for (const value of values) {
366+
// Ensure that each array item is an array
367+
if (!Array.isArray(value)) {
368+
throw EthereumJSErrorWithoutCode('Invalid partial encoding. Each item must be an array')
344369
}
345370
}
346371

347-
let balance = null
348-
if (!Array.isArray(values[1])) {
349-
throw EthereumJSErrorWithoutCode('Invalid partial balance encoding. Must be array')
350-
} else {
351-
const isNotNullIndicator = bytesToInt(values[1][0])
352-
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
353-
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for balance`)
354-
}
355-
if (isNotNullIndicator === 1) {
356-
balance = bytesToBigInt(values[1][1])
357-
}
358-
}
372+
const [nonceRaw, balanceRaw, storageRoot, codeHash, codeSizeRaw, versionRaw] =
373+
values.map(handleNullIndicator)
359374

360-
let storageRoot = null
361-
if (!Array.isArray(values[2])) {
362-
throw EthereumJSErrorWithoutCode('Invalid partial storageRoot encoding. Must be array')
363-
} else {
364-
const isNotNullIndicator = bytesToInt(values[2][0])
365-
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
366-
throw EthereumJSErrorWithoutCode(
367-
`Invalid isNullIndicator=${isNotNullIndicator} for storageRoot`,
368-
)
369-
}
370-
if (isNotNullIndicator === 1) {
371-
storageRoot = values[2][1]
372-
}
373-
}
374-
375-
let codeHash = null
376-
if (!Array.isArray(values[3])) {
377-
throw EthereumJSErrorWithoutCode('Invalid partial codeHash encoding. Must be array')
378-
} else {
379-
const isNotNullIndicator = bytesToInt(values[3][0])
380-
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
381-
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for codeHash`)
382-
}
383-
if (isNotNullIndicator === 1) {
384-
codeHash = values[3][1]
385-
}
386-
}
387-
388-
let codeSize = null
389-
if (!Array.isArray(values[4])) {
390-
throw EthereumJSErrorWithoutCode('Invalid partial codeSize encoding. Must be array')
391-
} else {
392-
const isNotNullIndicator = bytesToInt(values[4][0])
393-
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
394-
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for codeSize`)
395-
}
396-
if (isNotNullIndicator === 1) {
397-
codeSize = bytesToInt(values[4][1])
398-
}
399-
}
400-
401-
let version = null
402-
if (!Array.isArray(values[5])) {
403-
throw EthereumJSErrorWithoutCode('Invalid partial version encoding. Must be array')
404-
} else {
405-
const isNotNullIndicator = bytesToInt(values[5][0])
406-
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
407-
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for version`)
408-
}
409-
if (isNotNullIndicator === 1) {
410-
version = bytesToInt(values[5][1])
411-
}
412-
}
375+
const nonce = nonceRaw === null ? null : bytesToBigInt(nonceRaw)
376+
const balance = balanceRaw === null ? null : bytesToBigInt(balanceRaw)
377+
const codeSize = codeSizeRaw === null ? null : bytesToInt(codeSizeRaw)
378+
const version = versionRaw === null ? null : bytesToInt(versionRaw)
413379

414380
return createPartialAccount({ balance, nonce, storageRoot, codeHash, codeSize, version })
415381
}

packages/util/src/bytes.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,13 @@ export const unprefixedHexToBytes = (hex: string): Uint8Array => {
3434
return nobleH2B(padToEven(hex))
3535
}
3636

37+
/**
38+
* Converts a {@link Uint8Array} to a {@link PrefixedHexString}
39+
* @param {Uint8Array} bytes the bytes to convert
40+
* @returns {PrefixedHexString} the hex string
41+
* @dev Returns `0x` if provided an empty Uint8Array
42+
*/
3743
export const bytesToHex = (bytes: Uint8Array): PrefixedHexString => {
38-
if (bytes === undefined || bytes.length === 0) return '0x'
3944
const unprefixedHex = bytesToUnprefixedHex(bytes)
4045
return `0x${unprefixedHex}`
4146
}
@@ -92,7 +97,7 @@ export const intToHex = (i: number): PrefixedHexString => {
9297
if (!Number.isSafeInteger(i) || i < 0) {
9398
throw EthereumJSErrorWithoutCode(`Received an invalid integer type: ${i}`)
9499
}
95-
return ('0x' + i.toString(16)) as PrefixedHexString
100+
return `0x${i.toString(16)}`
96101
}
97102

98103
/**

0 commit comments

Comments
 (0)