Skip to content

Commit 86bef57

Browse files
RLP: Move EthereumJSError form util to RLP and start using EthereumJSError in RLP (#3924)
* util/rlp: move ethereumjserror to rlp * rlp: use ethereumjserror * rlp: add linter rules to not allow `new Error` (use EthereumJSError in RLP also) --------- Co-authored-by: Holger Drewes <[email protected]>
1 parent 2b2ff83 commit 86bef57

File tree

5 files changed

+98
-72
lines changed

5 files changed

+98
-72
lines changed

config/eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export default [
157157
},
158158
},
159159
{
160-
files: ['packages/devp2p/src/ext/**', 'packages/client/src/ext/**', 'packages/rlp/**', '**/test/**/*.ts',],
160+
files: ['packages/devp2p/src/ext/**', 'packages/client/src/ext/**', '**/test/**/*.ts',],
161161
rules: {
162162
'no-restricted-syntax': 'off'
163163
},

packages/rlp/eslint.config.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export default [
1212
{
1313
rules: {
1414
'@typescript-eslint/no-use-before-define': 'off',
15-
'no-restricted-syntax': 'off',
1615
},
1716
},
1817
{

packages/rlp/src/errors.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Generic EthereumJS error class with metadata attached
3+
*
4+
* Kudos to https://github.com/ChainSafe/lodestar monorepo
5+
* for the inspiration :-)
6+
* See: https://github.com/ChainSafe/lodestar/blob/unstable/packages/utils/src/errors.ts
7+
*/
8+
export type EthereumJSErrorMetaData = Record<string, string | number | null>
9+
export type EthereumJSErrorObject = {
10+
message: string
11+
stack: string
12+
className: string
13+
type: EthereumJSErrorMetaData
14+
}
15+
16+
// In order to update all our errors to use `EthereumJSError`, temporarily include the
17+
// unset error code. All errors throwing this code should be updated to use the relevant
18+
// error code.
19+
export const DEFAULT_ERROR_CODE = 'ETHEREUMJS_DEFAULT_ERROR_CODE'
20+
21+
/**
22+
* Generic EthereumJS error with attached metadata
23+
*/
24+
export class EthereumJSError<T extends { code: string }> extends Error {
25+
type: T
26+
constructor(type: T, message?: string, stack?: string) {
27+
super(message ?? type.code)
28+
this.type = type
29+
if (stack !== undefined) this.stack = stack
30+
}
31+
32+
getMetadata(): EthereumJSErrorMetaData {
33+
return this.type
34+
}
35+
36+
/**
37+
* Get the metadata and the stacktrace for the error.
38+
*/
39+
toObject(): EthereumJSErrorObject {
40+
return {
41+
type: this.getMetadata(),
42+
message: this.message ?? '',
43+
stack: this.stack ?? '',
44+
className: this.constructor.name,
45+
}
46+
}
47+
}
48+
49+
/**
50+
* @deprecated Use `EthereumJSError` with a set error code instead
51+
* @param message Optional error message
52+
* @param stack Optional stack trace
53+
* @returns
54+
*/
55+
export function EthereumJSErrorWithoutCode(message?: string, stack?: string) {
56+
return new EthereumJSError({ code: DEFAULT_ERROR_CODE }, message, stack)
57+
}

packages/rlp/src/index.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { EthereumJSErrorWithoutCode } from './errors.ts'
2+
3+
export * from './errors.ts'
4+
15
export type Input = string | number | bigint | Uint8Array | Array<Input> | null | undefined
26

37
export type NestedUint8Array = Array<Uint8Array | NestedUint8Array>
@@ -13,7 +17,7 @@ export interface Decoded {
1317
*/
1418
function decodeLength(v: Uint8Array): number {
1519
if (v[0] === 0) {
16-
throw new Error('invalid RLP: extra zeros')
20+
throw EthereumJSErrorWithoutCode('invalid RLP: extra zeros')
1721
}
1822
return parseHexByte(bytesToHex(v))
1923
}
@@ -37,7 +41,9 @@ function encodeLength(len: number, offset: number): Uint8Array {
3741
*/
3842
function safeSlice(input: Uint8Array, start: number, end: number) {
3943
if (end > input.length) {
40-
throw new Error('invalid RLP (safeSlice): end slice of Uint8Array out-of-bounds')
44+
throw EthereumJSErrorWithoutCode(
45+
'invalid RLP (safeSlice): end slice of Uint8Array out-of-bounds',
46+
)
4147
}
4248
return input.slice(start, end)
4349
}
@@ -67,7 +73,9 @@ function _decode(input: Uint8Array): Decoded {
6773
}
6874

6975
if (length === 2 && data[0] < 0x80) {
70-
throw new Error('invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed')
76+
throw EthereumJSErrorWithoutCode(
77+
'invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed',
78+
)
7179
}
7280

7381
return {
@@ -79,11 +87,11 @@ function _decode(input: Uint8Array): Decoded {
7987
// followed by the length, followed by the string
8088
lLength = firstByte - 0xb6
8189
if (input.length - 1 < lLength) {
82-
throw new Error('invalid RLP: not enough bytes for string length')
90+
throw EthereumJSErrorWithoutCode('invalid RLP: not enough bytes for string length')
8391
}
8492
length = decodeLength(safeSlice(input, 1, lLength))
8593
if (length <= 55) {
86-
throw new Error('invalid RLP: expected string length to be greater than 55')
94+
throw EthereumJSErrorWithoutCode('invalid RLP: expected string length to be greater than 55')
8795
}
8896
data = safeSlice(input, lLength, length + lLength)
8997

@@ -110,11 +118,11 @@ function _decode(input: Uint8Array): Decoded {
110118
lLength = firstByte - 0xf6
111119
length = decodeLength(safeSlice(input, 1, lLength))
112120
if (length < 56) {
113-
throw new Error('invalid RLP: encoded list too short')
121+
throw EthereumJSErrorWithoutCode('invalid RLP: encoded list too short')
114122
}
115123
const totalLength = lLength + length
116124
if (totalLength > input.length) {
117-
throw new Error('invalid RLP: total length is larger than the data')
125+
throw EthereumJSErrorWithoutCode('invalid RLP: total length is larger than the data')
118126
}
119127

120128
innerRemainder = safeSlice(input, lLength, totalLength)
@@ -144,7 +152,7 @@ function bytesToHex(uint8a: Uint8Array): string {
144152

145153
function parseHexByte(hexByte: string): number {
146154
const byte = Number.parseInt(hexByte, 16)
147-
if (Number.isNaN(byte)) throw new Error('Invalid byte sequence')
155+
if (Number.isNaN(byte)) throw EthereumJSErrorWithoutCode('Invalid byte sequence')
148156
return byte
149157
}
150158

@@ -163,17 +171,21 @@ function asciiToBase16(char: number): number | undefined {
163171
*/
164172
export function hexToBytes(hex: string): Uint8Array {
165173
if (hex.slice(0, 2) === '0x') hex = hex.slice(0, 2)
166-
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex)
174+
if (typeof hex !== 'string')
175+
throw EthereumJSErrorWithoutCode('hex string expected, got ' + typeof hex)
167176
const hl = hex.length
168177
const al = hl / 2
169-
if (hl % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + hl)
178+
if (hl % 2)
179+
throw EthereumJSErrorWithoutCode('padded hex string expected, got unpadded hex of length ' + hl)
170180
const array = new Uint8Array(al)
171181
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
172182
const n1 = asciiToBase16(hex.charCodeAt(hi))
173183
const n2 = asciiToBase16(hex.charCodeAt(hi + 1))
174184
if (n1 === undefined || n2 === undefined) {
175185
const char = hex[hi] + hex[hi + 1]
176-
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi)
186+
throw EthereumJSErrorWithoutCode(
187+
'hex string expected, got non-hex character "' + char + '" at index ' + hi,
188+
)
177189
}
178190
array[ai] = n1 * 16 + n2
179191
}
@@ -204,7 +216,7 @@ function utf8ToBytes(utf: string): Uint8Array {
204216
/** Transform an integer into its hexadecimal value */
205217
function numberToHex(integer: number | bigint): string {
206218
if (integer < 0) {
207-
throw new Error('Invalid integer as argument, must be unsigned!')
219+
throw EthereumJSErrorWithoutCode('Invalid integer as argument, must be unsigned!')
208220
}
209221
const hex = integer.toString(16)
210222
return hex.length % 2 ? `0${hex}` : hex
@@ -248,7 +260,7 @@ function toBytes(v: Input): Uint8Array {
248260
if (v === null || v === undefined) {
249261
return Uint8Array.from([])
250262
}
251-
throw new Error('toBytes: received unsupported type ' + typeof v)
263+
throw EthereumJSErrorWithoutCode('toBytes: received unsupported type ' + typeof v)
252264
}
253265

254266
/**
@@ -299,7 +311,7 @@ export function decode(input: Input, stream = false): Uint8Array | NestedUint8Ar
299311
}
300312
}
301313
if (decoded.remainder.length !== 0) {
302-
throw new Error('invalid RLP: remainder must be zero')
314+
throw EthereumJSErrorWithoutCode('invalid RLP: remainder must be zero')
303315
}
304316

305317
return decoded.data

packages/util/src/errors.ts

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,17 @@
1-
/**
2-
* Generic EthereumJS error class with metadata attached
3-
*
4-
* Kudos to https://github.com/ChainSafe/lodestar monorepo
5-
* for the inspiration :-)
6-
* See: https://github.com/ChainSafe/lodestar/blob/unstable/packages/utils/src/errors.ts
7-
*/
8-
export type EthereumJSErrorMetaData = Record<string, string | number | null>
9-
export type EthereumJSErrorObject = {
10-
message: string
11-
stack: string
12-
className: string
13-
type: EthereumJSErrorMetaData
14-
}
15-
16-
// In order to update all our errors to use `EthereumJSError`, temporarily include the
17-
// unset error code. All errors throwing this code should be updated to use the relevant
18-
// error code.
19-
export const DEFAULT_ERROR_CODE = 'ETHEREUMJS_DEFAULT_ERROR_CODE'
20-
21-
/**
22-
* Generic EthereumJS error with attached metadata
23-
*/
24-
export class EthereumJSError<T extends { code: string }> extends Error {
25-
type: T
26-
constructor(type: T, message?: string, stack?: string) {
27-
super(message ?? type.code)
28-
this.type = type
29-
if (stack !== undefined) this.stack = stack
30-
}
31-
32-
getMetadata(): EthereumJSErrorMetaData {
33-
return this.type
34-
}
35-
36-
/**
37-
* Get the metadata and the stacktrace for the error.
38-
*/
39-
toObject(): EthereumJSErrorObject {
40-
return {
41-
type: this.getMetadata(),
42-
message: this.message ?? '',
43-
stack: this.stack ?? '',
44-
className: this.constructor.name,
45-
}
46-
}
47-
}
48-
49-
/**
50-
* @deprecated Use `EthereumJSError` with a set error code instead
51-
* @param message Optional error message
52-
* @param stack Optional stack trace
53-
* @returns
54-
*/
55-
export function EthereumJSErrorWithoutCode(message?: string, stack?: string) {
56-
return new EthereumJSError({ code: DEFAULT_ERROR_CODE }, message, stack)
1+
import {
2+
DEFAULT_ERROR_CODE,
3+
EthereumJSError,
4+
type EthereumJSErrorMetaData,
5+
type EthereumJSErrorObject,
6+
EthereumJSErrorWithoutCode,
7+
} from '@ethereumjs/rlp'
8+
9+
export {
10+
DEFAULT_ERROR_CODE,
11+
EthereumJSError,
12+
EthereumJSErrorWithoutCode,
13+
type EthereumJSErrorMetaData,
14+
type EthereumJSErrorObject,
5715
}
5816

5917
// Below here: specific monorepo-wide errors (examples and commented out)

0 commit comments

Comments
 (0)