@feelyourprotocol/block 8141.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +466 -0
  3. package/dist/cjs/block/block.d.ts +147 -0
  4. package/dist/cjs/block/block.d.ts.map +1 -0
  5. package/dist/cjs/block/block.js +415 -0
  6. package/dist/cjs/block/block.js.map +1 -0
  7. package/dist/cjs/block/constructors.d.ts +77 -0
  8. package/dist/cjs/block/constructors.d.ts.map +1 -0
  9. package/dist/cjs/block/constructors.js +298 -0
  10. package/dist/cjs/block/constructors.js.map +1 -0
  11. package/dist/cjs/block/index.d.ts +3 -0
  12. package/dist/cjs/block/index.d.ts.map +1 -0
  13. package/dist/cjs/block/index.js +19 -0
  14. package/dist/cjs/block/index.js.map +1 -0
  15. package/dist/cjs/consensus/clique.d.ts +52 -0
  16. package/dist/cjs/consensus/clique.d.ts.map +1 -0
  17. package/dist/cjs/consensus/clique.js +144 -0
  18. package/dist/cjs/consensus/clique.js.map +1 -0
  19. package/dist/cjs/consensus/ethash.d.ts +9 -0
  20. package/dist/cjs/consensus/ethash.d.ts.map +1 -0
  21. package/dist/cjs/consensus/ethash.js +13 -0
  22. package/dist/cjs/consensus/ethash.js.map +1 -0
  23. package/dist/cjs/consensus/index.d.ts +3 -0
  24. package/dist/cjs/consensus/index.d.ts.map +1 -0
  25. package/dist/cjs/consensus/index.js +29 -0
  26. package/dist/cjs/consensus/index.js.map +1 -0
  27. package/dist/cjs/from-beacon-payload.d.ts +36 -0
  28. package/dist/cjs/from-beacon-payload.d.ts.map +1 -0
  29. package/dist/cjs/from-beacon-payload.js +48 -0
  30. package/dist/cjs/from-beacon-payload.js.map +1 -0
  31. package/dist/cjs/header/constructors.d.ts +39 -0
  32. package/dist/cjs/header/constructors.d.ts.map +1 -0
  33. package/dist/cjs/header/constructors.js +127 -0
  34. package/dist/cjs/header/constructors.js.map +1 -0
  35. package/dist/cjs/header/header.d.ts +134 -0
  36. package/dist/cjs/header/header.d.ts.map +1 -0
  37. package/dist/cjs/header/header.js +699 -0
  38. package/dist/cjs/header/header.js.map +1 -0
  39. package/dist/cjs/header/index.d.ts +3 -0
  40. package/dist/cjs/header/index.d.ts.map +1 -0
  41. package/dist/cjs/header/index.js +19 -0
  42. package/dist/cjs/header/index.js.map +1 -0
  43. package/dist/cjs/helpers.d.ts +59 -0
  44. package/dist/cjs/helpers.d.ts.map +1 -0
  45. package/dist/cjs/helpers.js +172 -0
  46. package/dist/cjs/helpers.js.map +1 -0
  47. package/dist/cjs/index.d.ts +8 -0
  48. package/dist/cjs/index.d.ts.map +1 -0
  49. package/dist/cjs/index.js +31 -0
  50. package/dist/cjs/index.js.map +1 -0
  51. package/dist/cjs/package.json +3 -0
  52. package/dist/cjs/params.d.ts +3 -0
  53. package/dist/cjs/params.d.ts.map +1 -0
  54. package/dist/cjs/params.js +97 -0
  55. package/dist/cjs/params.js.map +1 -0
  56. package/dist/cjs/types.d.ts +228 -0
  57. package/dist/cjs/types.d.ts.map +1 -0
  58. package/dist/cjs/types.js +3 -0
  59. package/dist/cjs/types.js.map +1 -0
  60. package/dist/esm/block/block.d.ts +147 -0
  61. package/dist/esm/block/block.d.ts.map +1 -0
  62. package/dist/esm/block/block.js +411 -0
  63. package/dist/esm/block/block.js.map +1 -0
  64. package/dist/esm/block/constructors.d.ts +77 -0
  65. package/dist/esm/block/constructors.d.ts.map +1 -0
  66. package/dist/esm/block/constructors.js +286 -0
  67. package/dist/esm/block/constructors.js.map +1 -0
  68. package/dist/esm/block/index.d.ts +3 -0
  69. package/dist/esm/block/index.d.ts.map +1 -0
  70. package/dist/esm/block/index.js +3 -0
  71. package/dist/esm/block/index.js.map +1 -0
  72. package/dist/esm/consensus/clique.d.ts +52 -0
  73. package/dist/esm/consensus/clique.d.ts.map +1 -0
  74. package/dist/esm/consensus/clique.js +132 -0
  75. package/dist/esm/consensus/clique.js.map +1 -0
  76. package/dist/esm/consensus/ethash.d.ts +9 -0
  77. package/dist/esm/consensus/ethash.d.ts.map +1 -0
  78. package/dist/esm/consensus/ethash.js +10 -0
  79. package/dist/esm/consensus/ethash.js.map +1 -0
  80. package/dist/esm/consensus/index.d.ts +3 -0
  81. package/dist/esm/consensus/index.d.ts.map +1 -0
  82. package/dist/esm/consensus/index.js +3 -0
  83. package/dist/esm/consensus/index.js.map +1 -0
  84. package/dist/esm/from-beacon-payload.d.ts +36 -0
  85. package/dist/esm/from-beacon-payload.d.ts.map +1 -0
  86. package/dist/esm/from-beacon-payload.js +45 -0
  87. package/dist/esm/from-beacon-payload.js.map +1 -0
  88. package/dist/esm/header/constructors.d.ts +39 -0
  89. package/dist/esm/header/constructors.d.ts.map +1 -0
  90. package/dist/esm/header/constructors.js +120 -0
  91. package/dist/esm/header/constructors.js.map +1 -0
  92. package/dist/esm/header/header.d.ts +134 -0
  93. package/dist/esm/header/header.d.ts.map +1 -0
  94. package/dist/esm/header/header.js +695 -0
  95. package/dist/esm/header/header.js.map +1 -0
  96. package/dist/esm/header/index.d.ts +3 -0
  97. package/dist/esm/header/index.d.ts.map +1 -0
  98. package/dist/esm/header/index.js +3 -0
  99. package/dist/esm/header/index.js.map +1 -0
  100. package/dist/esm/helpers.d.ts +59 -0
  101. package/dist/esm/helpers.d.ts.map +1 -0
  102. package/dist/esm/helpers.js +161 -0
  103. package/dist/esm/helpers.js.map +1 -0
  104. package/dist/esm/index.d.ts +8 -0
  105. package/dist/esm/index.d.ts.map +1 -0
  106. package/dist/esm/index.js +8 -0
  107. package/dist/esm/index.js.map +1 -0
  108. package/dist/esm/package.json +3 -0
  109. package/dist/esm/params.d.ts +3 -0
  110. package/dist/esm/params.d.ts.map +1 -0
  111. package/dist/esm/params.js +94 -0
  112. package/dist/esm/params.js.map +1 -0
  113. package/dist/esm/types.d.ts +228 -0
  114. package/dist/esm/types.d.ts.map +1 -0
  115. package/dist/esm/types.js +2 -0
  116. package/dist/esm/types.js.map +1 -0
  117. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
  118. package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
  119. package/package.json +75 -0
  120. package/src/block/block.ts +526 -0
  121. package/src/block/constructors.ts +407 -0
  122. package/src/block/index.ts +2 -0
  123. package/src/consensus/clique.ts +171 -0
  124. package/src/consensus/ethash.ts +11 -0
  125. package/src/consensus/index.ts +12 -0
  126. package/src/from-beacon-payload.ts +82 -0
  127. package/src/header/constructors.ts +169 -0
  128. package/src/header/header.ts +890 -0
  129. package/src/header/index.ts +2 -0
  130. package/src/helpers.ts +223 -0
  131. package/src/index.ts +13 -0
  132. package/src/params.ts +95 -0
  133. package/src/types.ts +254 -0
@@ -0,0 +1,407 @@
1
+ import { MerklePatriciaTrie } from '@feelyourprotocol/mpt'
2
+ import { RLP } from '@feelyourprotocol/rlp'
3
+ import type { TxOptions, TypedTransaction } from '@feelyourprotocol/tx'
4
+ import {
5
+ createTx,
6
+ createTxFromBlockBodyData,
7
+ createTxFromRLP,
8
+ normalizeTxParams,
9
+ } from '@feelyourprotocol/tx'
10
+ import {
11
+ EthereumJSErrorWithoutCode,
12
+ bigIntToHex,
13
+ bytesToHex,
14
+ createWithdrawal,
15
+ equalsBytes,
16
+ fetchFromProvider,
17
+ getProvider,
18
+ hexToBytes,
19
+ intToHex,
20
+ isHexString,
21
+ } from '@feelyourprotocol/util'
22
+
23
+ import { generateCliqueBlockExtraData } from '../consensus/clique.ts'
24
+ import { genTransactionsTrieRoot, genWithdrawalsTrieRoot } from '../helpers.ts'
25
+ import {
26
+ Block,
27
+ createBlockHeader,
28
+ createBlockHeaderFromBytesArray,
29
+ createBlockHeaderFromRPC,
30
+ executionPayloadFromBeaconPayload,
31
+ } from '../index.ts'
32
+
33
+ import type { EthersProvider, WithdrawalBytes } from '@feelyourprotocol/util'
34
+ import type { BeaconPayloadJSON } from '../from-beacon-payload.ts'
35
+ import type {
36
+ BlockBytes,
37
+ BlockData,
38
+ BlockOptions,
39
+ ExecutionPayload,
40
+ HeaderData,
41
+ JSONRPCBlock,
42
+ WithdrawalsBytes,
43
+ } from '../types.ts'
44
+
45
+ /**
46
+ * Static constructor to create a block from a block data dictionary
47
+ *
48
+ * @param blockData
49
+ * @param opts
50
+ * @returns a new {@link Block} object
51
+ */
52
+ export function createBlock(blockData: BlockData = {}, opts?: BlockOptions): Block {
53
+ const {
54
+ header: headerData,
55
+ transactions: txsData,
56
+ uncleHeaders: uhsData,
57
+ withdrawals: withdrawalsData,
58
+ } = blockData
59
+
60
+ const header = createBlockHeader(headerData, opts)
61
+
62
+ // parse transactions
63
+ const transactions = []
64
+ for (const txData of txsData ?? []) {
65
+ const tx = createTx(txData, {
66
+ ...opts,
67
+ // Use header common in case of setHardfork being activated
68
+ common: header.common,
69
+ } as TxOptions)
70
+ transactions.push(tx)
71
+ }
72
+
73
+ // parse uncle headers
74
+ const uncleHeaders = []
75
+ const uncleOpts: BlockOptions = {
76
+ ...opts,
77
+ // Use header common in case of setHardfork being activated
78
+ common: header.common,
79
+ // Disable this option here (all other options carried over), since this overwrites the provided Difficulty to an incorrect value
80
+ calcDifficultyFromHeader: undefined,
81
+ }
82
+ // Uncles are obsolete post-merge, any hardfork by option implies setHardfork
83
+ if (opts?.setHardfork !== undefined) {
84
+ uncleOpts.setHardfork = true
85
+ }
86
+ for (const uhData of uhsData ?? []) {
87
+ const uh = createBlockHeader(uhData, uncleOpts)
88
+ uncleHeaders.push(uh)
89
+ }
90
+
91
+ const withdrawals = withdrawalsData?.map(createWithdrawal)
92
+
93
+ return new Block(header, transactions, uncleHeaders, withdrawals, opts)
94
+ }
95
+
96
+ /**
97
+ * Simple static constructor if only an empty block is needed
98
+ * (tree shaking advantages since it does not draw all the tx constructors in)
99
+ *
100
+ * @param headerData
101
+ * @param opts
102
+ * @returns a new {@link Block} object
103
+ */
104
+ export function createEmptyBlock(headerData: HeaderData, opts?: BlockOptions): Block {
105
+ const header = createBlockHeader(headerData, opts)
106
+ return new Block(header)
107
+ }
108
+
109
+ /**
110
+ * Static constructor to create a block from an array of Bytes values
111
+ *
112
+ * @param values
113
+ * @param opts
114
+ * @returns a new {@link Block} object
115
+ */
116
+ export function createBlockFromBytesArray(values: BlockBytes, opts?: BlockOptions): Block {
117
+ if (values.length > 5) {
118
+ throw EthereumJSErrorWithoutCode(
119
+ `invalid More values=${values.length} than expected were received (at most 5)`,
120
+ )
121
+ }
122
+
123
+ // First try to load header so that we can use its common (in case of setHardfork being activated)
124
+ // to correctly make checks on the hardforks
125
+ const [headerData, txsData, uhsData, ...valuesTail] = values
126
+ const header = createBlockHeaderFromBytesArray(headerData, opts)
127
+
128
+ // conditional assignment of rest of values and splicing them out from the valuesTail
129
+ const withdrawalBytes = header.common.isActivatedEIP(4895)
130
+ ? (valuesTail.splice(0, 1)[0] as WithdrawalsBytes)
131
+ : undefined
132
+
133
+ if (
134
+ header.common.isActivatedEIP(4895) &&
135
+ (withdrawalBytes === undefined || !Array.isArray(withdrawalBytes))
136
+ ) {
137
+ throw EthereumJSErrorWithoutCode(
138
+ 'Invalid serialized block input: EIP-4895 is active, and no withdrawals were provided as array',
139
+ )
140
+ }
141
+
142
+ // parse transactions
143
+ const transactions = []
144
+ for (const txData of txsData ?? []) {
145
+ transactions.push(
146
+ createTxFromBlockBodyData(txData, {
147
+ ...opts,
148
+ // Use header common in case of setHardfork being activated
149
+ common: header.common,
150
+ }),
151
+ )
152
+ }
153
+
154
+ // parse uncle headers
155
+ const uncleHeaders = []
156
+ const uncleOpts: BlockOptions = {
157
+ ...opts,
158
+ // Use header common in case of setHardfork being activated
159
+ common: header.common,
160
+ // Disable this option here (all other options carried over), since this overwrites the provided Difficulty to an incorrect value
161
+ calcDifficultyFromHeader: undefined,
162
+ }
163
+ // Uncles are obsolete post-merge, any hardfork by option implies setHardfork
164
+ if (opts?.setHardfork !== undefined) {
165
+ uncleOpts.setHardfork = true
166
+ }
167
+ for (const uncleHeaderData of uhsData ?? []) {
168
+ uncleHeaders.push(createBlockHeaderFromBytesArray(uncleHeaderData, uncleOpts))
169
+ }
170
+
171
+ const withdrawals = (withdrawalBytes as WithdrawalBytes[])
172
+ ?.map(([index, validatorIndex, address, amount]) => ({
173
+ index,
174
+ validatorIndex,
175
+ address,
176
+ amount,
177
+ }))
178
+ ?.map(createWithdrawal)
179
+
180
+ return new Block(header, transactions, uncleHeaders, withdrawals, opts)
181
+ }
182
+
183
+ /**
184
+ * Static constructor to create a block from a RLP-serialized block
185
+ *
186
+ * @param serialized
187
+ * @param opts
188
+ * @returns a new {@link Block} object
189
+ */
190
+ export function createBlockFromRLP(serialized: Uint8Array, opts?: BlockOptions): Block {
191
+ if (opts?.common?.isActivatedEIP(7934) === true) {
192
+ const maxRlpBlockSize = opts.common.param('maxRlpBlockSize')
193
+ if (serialized.length > maxRlpBlockSize) {
194
+ throw EthereumJSErrorWithoutCode(
195
+ `Block size exceeds limit: ${serialized.length} > ${maxRlpBlockSize}`,
196
+ )
197
+ }
198
+ }
199
+ const values = RLP.decode(Uint8Array.from(serialized)) as BlockBytes
200
+
201
+ if (!Array.isArray(values)) {
202
+ throw EthereumJSErrorWithoutCode('Invalid serialized block input. Must be array')
203
+ }
204
+
205
+ return createBlockFromBytesArray(values, opts)
206
+ }
207
+
208
+ /**
209
+ * Creates a new block object from Ethereum JSON RPC.
210
+ *
211
+ * @param blockParams - Ethereum JSON RPC of block (eth_getBlockByNumber)
212
+ * @param uncles - Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex)
213
+ * @param opts - An object describing the blockchain
214
+ * @returns a new {@link Block} object
215
+ */
216
+ export function createBlockFromRPC(
217
+ blockParams: JSONRPCBlock,
218
+ uncles: any[] = [],
219
+ options?: BlockOptions,
220
+ ): Block {
221
+ const header = createBlockHeaderFromRPC(blockParams, options)
222
+
223
+ const transactions: TypedTransaction[] = []
224
+ const opts = { common: header.common }
225
+ for (const _txParams of blockParams.transactions ?? []) {
226
+ const txParams = normalizeTxParams(_txParams)
227
+ const tx = createTx(txParams, opts)
228
+ transactions.push(tx)
229
+ }
230
+
231
+ const uncleHeaders = uncles.map((uh) => createBlockHeaderFromRPC(uh, options))
232
+
233
+ return createBlock(
234
+ { header, transactions, uncleHeaders, withdrawals: blockParams.withdrawals },
235
+ options,
236
+ )
237
+ }
238
+
239
+ /**
240
+ * Method to retrieve a block from a JSON-RPC provider and format as a {@link Block}
241
+ * @param provider either a url for a remote provider or an Ethers JSONRPCProvider object
242
+ * @param blockTag block hash or block number to be run
243
+ * @param opts {@link BlockOptions}
244
+ * @returns a new {@link Block} object specified by `blockTag`
245
+ */
246
+ export const createBlockFromJSONRPCProvider = async (
247
+ provider: string | EthersProvider,
248
+ blockTag: string | bigint,
249
+ opts: BlockOptions,
250
+ ): Promise<Block> => {
251
+ let blockData
252
+ const providerUrl = getProvider(provider)
253
+
254
+ if (typeof blockTag === 'string' && blockTag.length === 66) {
255
+ blockData = await fetchFromProvider(providerUrl, {
256
+ method: 'eth_getBlockByHash',
257
+ params: [blockTag, true],
258
+ })
259
+ } else if (typeof blockTag === 'bigint') {
260
+ blockData = await fetchFromProvider(providerUrl, {
261
+ method: 'eth_getBlockByNumber',
262
+ params: [bigIntToHex(blockTag), true],
263
+ })
264
+ } else if (
265
+ isHexString(blockTag) ||
266
+ blockTag === 'latest' ||
267
+ blockTag === 'earliest' ||
268
+ blockTag === 'pending' ||
269
+ blockTag === 'finalized' ||
270
+ blockTag === 'safe'
271
+ ) {
272
+ blockData = await fetchFromProvider(providerUrl, {
273
+ method: 'eth_getBlockByNumber',
274
+ params: [blockTag, true],
275
+ })
276
+ } else {
277
+ throw EthereumJSErrorWithoutCode(
278
+ `expected blockTag to be block hash, bigint, hex prefixed string, or earliest/latest/pending; got ${blockTag}`,
279
+ )
280
+ }
281
+
282
+ if (blockData === null) {
283
+ throw EthereumJSErrorWithoutCode('No block data returned from provider')
284
+ }
285
+
286
+ const uncleHeaders = []
287
+ if (blockData.uncles.length > 0) {
288
+ for (let x = 0; x < blockData.uncles.length; x++) {
289
+ const headerData = await fetchFromProvider(providerUrl, {
290
+ method: 'eth_getUncleByBlockHashAndIndex',
291
+ params: [blockData.hash, intToHex(x)],
292
+ })
293
+ uncleHeaders.push(headerData)
294
+ }
295
+ }
296
+
297
+ return createBlockFromRPC(blockData, uncleHeaders, opts)
298
+ }
299
+
300
+ /**
301
+ * Method to retrieve a block from an execution payload
302
+ * @param payload Execution payload constructed from beacon payload data
303
+ * @param opts {@link BlockOptions}
304
+ * @returns The constructed {@link Block} object
305
+ */
306
+ export async function createBlockFromExecutionPayload(
307
+ payload: ExecutionPayload,
308
+ opts?: BlockOptions,
309
+ ): Promise<Block> {
310
+ const {
311
+ blockNumber: number,
312
+ receiptsRoot: receiptTrie,
313
+ prevRandao: mixHash,
314
+ feeRecipient: coinbase,
315
+ transactions,
316
+ withdrawals: withdrawalsData,
317
+ } = payload
318
+
319
+ const txs = []
320
+ for (const [index, serializedTx] of transactions.entries()) {
321
+ try {
322
+ const tx = createTxFromRLP(hexToBytes(serializedTx), {
323
+ common: opts?.common,
324
+ })
325
+ txs.push(tx)
326
+ } catch (error) {
327
+ const validationError = `Invalid tx at index ${index}: ${error}`
328
+ throw validationError
329
+ }
330
+ }
331
+
332
+ const transactionsTrie = await genTransactionsTrieRoot(
333
+ txs,
334
+ new MerklePatriciaTrie({ common: opts?.common }),
335
+ )
336
+ const withdrawals = withdrawalsData?.map((wData) => createWithdrawal(wData))
337
+ const withdrawalsRoot = withdrawals
338
+ ? await genWithdrawalsTrieRoot(withdrawals, new MerklePatriciaTrie({ common: opts?.common }))
339
+ : undefined
340
+
341
+ const header: HeaderData = {
342
+ ...payload,
343
+ number,
344
+ receiptTrie,
345
+ transactionsTrie,
346
+ withdrawalsRoot,
347
+ mixHash,
348
+ coinbase,
349
+ }
350
+
351
+ // we are not setting setHardfork as common is already set to the correct hf
352
+ const block = createBlock({ header, transactions: txs, withdrawals }, opts)
353
+ // Verify blockHash matches payload
354
+ if (!equalsBytes(block.hash(), hexToBytes(payload.blockHash))) {
355
+ const validationError = `Invalid blockHash, expected: ${
356
+ payload.blockHash
357
+ }, received: ${bytesToHex(block.hash())}`
358
+ throw Error(validationError)
359
+ }
360
+
361
+ return block
362
+ }
363
+
364
+ /**
365
+ * Method to retrieve a block from a beacon payload JSON
366
+ * @param payload JSON of a beacon block fetched from beacon APIs
367
+ * @param opts {@link BlockOptions}
368
+ * @returns The constructed {@link Block} object
369
+ */
370
+ export async function createBlockFromBeaconPayloadJSON(
371
+ payload: BeaconPayloadJSON,
372
+ opts?: BlockOptions,
373
+ ): Promise<Block> {
374
+ const executionPayload = executionPayloadFromBeaconPayload(payload)
375
+ return createBlockFromExecutionPayload(executionPayload, opts)
376
+ }
377
+
378
+ /**
379
+ * Creates a block for Clique networks with the seal applied during instantiation.
380
+ * @param blockData Block fields used to build the block
381
+ * @param cliqueSigner Private key bytes used to sign the header
382
+ * @param opts {@link BlockOptions}
383
+ * @returns A sealed Clique {@link Block} object
384
+ */
385
+ export function createSealedCliqueBlock(
386
+ blockData: BlockData = {},
387
+ cliqueSigner: Uint8Array,
388
+ opts: BlockOptions = {},
389
+ ): Block {
390
+ const sealedCliqueBlock = createBlock(blockData, {
391
+ ...opts,
392
+ ...{ freeze: false, skipConsensusFormatValidation: true },
393
+ })
394
+ ;(sealedCliqueBlock.header.extraData as any) = generateCliqueBlockExtraData(
395
+ sealedCliqueBlock.header,
396
+ cliqueSigner,
397
+ )
398
+ if (opts?.freeze === true) {
399
+ // We have to freeze here since we can't freeze the block when constructing it since we are overwriting `extraData`
400
+ Object.freeze(sealedCliqueBlock)
401
+ }
402
+ if (opts?.skipConsensusFormatValidation === false) {
403
+ // We need to validate the consensus format here since we skipped it when constructing the block
404
+ sealedCliqueBlock.header['_consensusFormatValidation']()
405
+ }
406
+ return sealedCliqueBlock
407
+ }
@@ -0,0 +1,2 @@
1
+ export * from './block.ts'
2
+ export * from './constructors.ts'
@@ -0,0 +1,171 @@
1
+ import { ConsensusAlgorithm } from '@feelyourprotocol/common'
2
+ import { RLP } from '@feelyourprotocol/rlp'
3
+ import {
4
+ Address,
5
+ BIGINT_0,
6
+ BIGINT_27,
7
+ EthereumJSErrorWithoutCode,
8
+ bytesToBigInt,
9
+ concatBytes,
10
+ createAddressFromPublicKey,
11
+ createZeroAddress,
12
+ ecrecover,
13
+ equalsBytes,
14
+ } from '@feelyourprotocol/util'
15
+ import { secp256k1 } from '@noble/curves/secp256k1.js'
16
+
17
+ import type { CliqueConfig } from '@feelyourprotocol/common'
18
+ import type { BlockHeader } from '../index.ts'
19
+
20
+ // Fixed number of extra-data prefix bytes reserved for signer vanity
21
+ export const CLIQUE_EXTRA_VANITY = 32
22
+ // Fixed number of extra-data suffix bytes reserved for signer seal
23
+ export const CLIQUE_EXTRA_SEAL = 65
24
+
25
+ // This function is not exported in the index file to keep it internal
26
+ export function requireClique(header: BlockHeader, name: string) {
27
+ if (header.common.consensusAlgorithm() !== ConsensusAlgorithm.Clique) {
28
+ const msg = header['_errorMsg'](
29
+ `BlockHeader.${name}() call only supported for clique PoA networks`,
30
+ )
31
+ throw EthereumJSErrorWithoutCode(msg)
32
+ }
33
+ }
34
+
35
+ /**
36
+ * PoA clique signature hash without the seal.
37
+ */
38
+ export function cliqueSigHash(header: BlockHeader) {
39
+ requireClique(header, 'cliqueSigHash')
40
+ const raw = header.raw()
41
+ raw[12] = header.extraData.subarray(0, header.extraData.length - CLIQUE_EXTRA_SEAL)
42
+ return header['keccakFunction'](RLP.encode(raw))
43
+ }
44
+
45
+ /**
46
+ * Checks if the block header is an epoch transition
47
+ * header (only clique PoA, throws otherwise)
48
+ */
49
+ export function cliqueIsEpochTransition(header: BlockHeader): boolean {
50
+ requireClique(header, 'cliqueIsEpochTransition')
51
+ const epoch = BigInt((header.common.consensusConfig() as CliqueConfig).epoch)
52
+ // Epoch transition block if the block number has no
53
+ // remainder on the division by the epoch length
54
+ return header.number % epoch === BIGINT_0
55
+ }
56
+
57
+ /**
58
+ * Returns extra vanity data
59
+ * (only clique PoA, throws otherwise)
60
+ */
61
+ export function cliqueExtraVanity(header: BlockHeader): Uint8Array {
62
+ requireClique(header, 'cliqueExtraVanity')
63
+ return header.extraData.subarray(0, CLIQUE_EXTRA_VANITY)
64
+ }
65
+
66
+ /**
67
+ * Returns extra seal data
68
+ * (only clique PoA, throws otherwise)
69
+ */
70
+ export function cliqueExtraSeal(header: BlockHeader): Uint8Array {
71
+ requireClique(header, 'cliqueExtraSeal')
72
+ return header.extraData.subarray(-CLIQUE_EXTRA_SEAL)
73
+ }
74
+
75
+ /**
76
+ * Returns a list of signers
77
+ * (only clique PoA, throws otherwise)
78
+ *
79
+ * This function throws if not called on an epoch
80
+ * transition block and should therefore be used
81
+ * in conjunction with {@link cliqueIsEpochTransition}
82
+ */
83
+ export function cliqueEpochTransitionSigners(header: BlockHeader): Address[] {
84
+ requireClique(header, 'cliqueEpochTransitionSigners')
85
+ if (!cliqueIsEpochTransition(header)) {
86
+ const msg = header['_errorMsg']('Signers are only included in epoch transition blocks (clique)')
87
+ throw EthereumJSErrorWithoutCode(msg)
88
+ }
89
+
90
+ const start = CLIQUE_EXTRA_VANITY
91
+ const end = header.extraData.length - CLIQUE_EXTRA_SEAL
92
+ const signerBytes = header.extraData.subarray(start, end)
93
+
94
+ const signerList: Uint8Array[] = []
95
+ const signerLength = 20
96
+ for (let start = 0; start <= signerBytes.length - signerLength; start += signerLength) {
97
+ signerList.push(signerBytes.subarray(start, start + signerLength))
98
+ }
99
+ return signerList.map((buf) => new Address(buf))
100
+ }
101
+
102
+ /**
103
+ * Returns the signer address
104
+ */
105
+ export function cliqueSigner(header: BlockHeader): Address {
106
+ requireClique(header, 'cliqueSigner')
107
+ const extraSeal = cliqueExtraSeal(header)
108
+ // Reasonable default for default blocks
109
+ if (extraSeal.length === 0 || equalsBytes(extraSeal, new Uint8Array(65))) {
110
+ return createZeroAddress()
111
+ }
112
+ const r = extraSeal.subarray(0, 32)
113
+ const s = extraSeal.subarray(32, 64)
114
+ const v = bytesToBigInt(extraSeal.subarray(64, 65)) + BIGINT_27
115
+ const pubKey = ecrecover(cliqueSigHash(header), v, r, s)
116
+ return createAddressFromPublicKey(pubKey)
117
+ }
118
+
119
+ /**
120
+ * Verifies the signature of the block (last 65 bytes of extraData field)
121
+ * (only clique PoA, throws otherwise)
122
+ *
123
+ * Method throws if signature is invalid
124
+ */
125
+ export function cliqueVerifySignature(header: BlockHeader, signerList: Address[]): boolean {
126
+ requireClique(header, 'cliqueVerifySignature')
127
+ const signerAddress = cliqueSigner(header)
128
+ const signerFound = signerList.find((signer) => {
129
+ return signer.equals(signerAddress)
130
+ })
131
+ return !!signerFound
132
+ }
133
+
134
+ /**
135
+ * Generates the extraData from a sealed block header
136
+ * @param header block header from which to retrieve extraData
137
+ * @param cliqueSigner clique signer key used for creating sealed block
138
+ * @returns clique seal (i.e. extradata) for the block
139
+ */
140
+ export function generateCliqueBlockExtraData(
141
+ header: BlockHeader,
142
+ cliqueSigner: Uint8Array,
143
+ ): Uint8Array {
144
+ // Ensure extraData is at least length CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL
145
+ const minExtraDataLength = CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL
146
+ if (header.extraData.length < minExtraDataLength) {
147
+ const remainingLength = minExtraDataLength - header.extraData.length
148
+ ;(header.extraData as any) = concatBytes(header.extraData, new Uint8Array(remainingLength))
149
+ }
150
+
151
+ requireClique(header, 'generateCliqueBlockExtraData')
152
+
153
+ const msgHash = cliqueSigHash(header)
154
+
155
+ // Use custom ecsign if provided, otherwise use secp256k1.sign
156
+ const ecSignFunction = header.common.customCrypto?.ecsign ?? secp256k1.sign
157
+
158
+ // Use noble/curves secp256k1.sign with recovered format (returns 65-byte Uint8Array)
159
+ // sigBytes format: [recovery (1 byte) | r (32 bytes) | s (32 bytes)]
160
+ const sigBytes = ecSignFunction(msgHash, cliqueSigner, { prehash: false, format: 'recovered' })
161
+
162
+ // clique format: [r (32 bytes) | s (32 bytes) | recovery (1 byte)]
163
+ const cliqueSignature = concatBytes(sigBytes.subarray(1), sigBytes.subarray(0, 1))
164
+
165
+ const extraDataWithoutSeal = header.extraData.subarray(
166
+ 0,
167
+ header.extraData.length - CLIQUE_EXTRA_SEAL,
168
+ )
169
+ const extraData = concatBytes(extraDataWithoutSeal, cliqueSignature)
170
+ return extraData
171
+ }
@@ -0,0 +1,11 @@
1
+ import type { Block } from '../index.ts'
2
+
3
+ /**
4
+ * Returns the canonical difficulty for this block.
5
+ *
6
+ * @param block - the block whose difficulty should be calculated
7
+ * @param parentBlock - the parent of this `Block`
8
+ */
9
+ export function ethashCanonicalDifficulty(block: Block, parentBlock: Block): bigint {
10
+ return block.header.ethashCanonicalDifficulty(parentBlock.header)
11
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ CLIQUE_EXTRA_SEAL,
3
+ CLIQUE_EXTRA_VANITY,
4
+ cliqueEpochTransitionSigners,
5
+ cliqueExtraSeal,
6
+ cliqueExtraVanity,
7
+ cliqueIsEpochTransition,
8
+ cliqueSigHash,
9
+ cliqueSigner,
10
+ cliqueVerifySignature,
11
+ } from './clique.ts'
12
+ export * from './ethash.ts'
@@ -0,0 +1,82 @@
1
+ import { bigIntToHex } from '@feelyourprotocol/util'
2
+
3
+ import type { NumericString, PrefixedHexString } from '@feelyourprotocol/util'
4
+ import type { ExecutionPayload } from './types.ts'
5
+
6
+ type BeaconWithdrawal = {
7
+ index: PrefixedHexString
8
+ validator_index: PrefixedHexString
9
+ address: PrefixedHexString
10
+ amount: PrefixedHexString
11
+ }
12
+
13
+ // Payload JSON that one gets using the beacon apis
14
+ // curl localhost:5052/eth/v2/beacon/blocks/56610 | jq .data.message.body.execution_payload
15
+ export type BeaconPayloadJSON = {
16
+ parent_hash: PrefixedHexString
17
+ fee_recipient: PrefixedHexString
18
+ state_root: PrefixedHexString
19
+ receipts_root: PrefixedHexString
20
+ logs_bloom: PrefixedHexString
21
+ prev_randao: PrefixedHexString
22
+ block_number: NumericString
23
+ gas_limit: NumericString
24
+ gas_used: NumericString
25
+ timestamp: NumericString
26
+ extra_data: PrefixedHexString
27
+ base_fee_per_gas: NumericString
28
+ block_hash: PrefixedHexString
29
+ transactions: PrefixedHexString[]
30
+ withdrawals?: BeaconWithdrawal[]
31
+ blob_gas_used?: NumericString
32
+ excess_blob_gas?: NumericString
33
+ parent_beacon_block_root?: PrefixedHexString
34
+ requests_hash?: PrefixedHexString
35
+ }
36
+
37
+ /**
38
+ * Converts a beacon block execution payload JSON object {@link BeaconPayloadJSON} to the {@link ExecutionPayload} data needed to construct a {@link Block}.
39
+ * The JSON data can be retrieved from a consensus layer (CL) client on this Beacon API `/eth/v2/beacon/blocks/[block number]`
40
+ */
41
+ export function executionPayloadFromBeaconPayload(payload: BeaconPayloadJSON): ExecutionPayload {
42
+ const executionPayload: ExecutionPayload = {
43
+ parentHash: payload.parent_hash,
44
+ feeRecipient: payload.fee_recipient,
45
+ stateRoot: payload.state_root,
46
+ receiptsRoot: payload.receipts_root,
47
+ logsBloom: payload.logs_bloom,
48
+ prevRandao: payload.prev_randao,
49
+ blockNumber: bigIntToHex(BigInt(payload.block_number)),
50
+ gasLimit: bigIntToHex(BigInt(payload.gas_limit)),
51
+ gasUsed: bigIntToHex(BigInt(payload.gas_used)),
52
+ timestamp: bigIntToHex(BigInt(payload.timestamp)),
53
+ extraData: payload.extra_data,
54
+ baseFeePerGas: bigIntToHex(BigInt(payload.base_fee_per_gas)),
55
+ blockHash: payload.block_hash,
56
+ transactions: payload.transactions,
57
+ }
58
+
59
+ if (payload.withdrawals !== undefined && payload.withdrawals !== null) {
60
+ executionPayload.withdrawals = payload.withdrawals.map((wd) => ({
61
+ index: bigIntToHex(BigInt(wd.index)),
62
+ validatorIndex: bigIntToHex(BigInt(wd.validator_index)),
63
+ address: wd.address,
64
+ amount: bigIntToHex(BigInt(wd.amount)),
65
+ }))
66
+ }
67
+
68
+ if (payload.blob_gas_used !== undefined && payload.blob_gas_used !== null) {
69
+ executionPayload.blobGasUsed = bigIntToHex(BigInt(payload.blob_gas_used))
70
+ }
71
+ if (payload.excess_blob_gas !== undefined && payload.excess_blob_gas !== null) {
72
+ executionPayload.excessBlobGas = bigIntToHex(BigInt(payload.excess_blob_gas))
73
+ }
74
+ if (payload.parent_beacon_block_root !== undefined && payload.parent_beacon_block_root !== null) {
75
+ executionPayload.parentBeaconBlockRoot = payload.parent_beacon_block_root
76
+ }
77
+ if (payload.requests_hash !== undefined && payload.requests_hash !== null) {
78
+ executionPayload.requestsHash = payload.requests_hash
79
+ }
80
+
81
+ return executionPayload
82
+ }