@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.
- package/LICENSE +373 -0
- package/README.md +466 -0
- package/dist/cjs/block/block.d.ts +147 -0
- package/dist/cjs/block/block.d.ts.map +1 -0
- package/dist/cjs/block/block.js +415 -0
- package/dist/cjs/block/block.js.map +1 -0
- package/dist/cjs/block/constructors.d.ts +77 -0
- package/dist/cjs/block/constructors.d.ts.map +1 -0
- package/dist/cjs/block/constructors.js +298 -0
- package/dist/cjs/block/constructors.js.map +1 -0
- package/dist/cjs/block/index.d.ts +3 -0
- package/dist/cjs/block/index.d.ts.map +1 -0
- package/dist/cjs/block/index.js +19 -0
- package/dist/cjs/block/index.js.map +1 -0
- package/dist/cjs/consensus/clique.d.ts +52 -0
- package/dist/cjs/consensus/clique.d.ts.map +1 -0
- package/dist/cjs/consensus/clique.js +144 -0
- package/dist/cjs/consensus/clique.js.map +1 -0
- package/dist/cjs/consensus/ethash.d.ts +9 -0
- package/dist/cjs/consensus/ethash.d.ts.map +1 -0
- package/dist/cjs/consensus/ethash.js +13 -0
- package/dist/cjs/consensus/ethash.js.map +1 -0
- package/dist/cjs/consensus/index.d.ts +3 -0
- package/dist/cjs/consensus/index.d.ts.map +1 -0
- package/dist/cjs/consensus/index.js +29 -0
- package/dist/cjs/consensus/index.js.map +1 -0
- package/dist/cjs/from-beacon-payload.d.ts +36 -0
- package/dist/cjs/from-beacon-payload.d.ts.map +1 -0
- package/dist/cjs/from-beacon-payload.js +48 -0
- package/dist/cjs/from-beacon-payload.js.map +1 -0
- package/dist/cjs/header/constructors.d.ts +39 -0
- package/dist/cjs/header/constructors.d.ts.map +1 -0
- package/dist/cjs/header/constructors.js +127 -0
- package/dist/cjs/header/constructors.js.map +1 -0
- package/dist/cjs/header/header.d.ts +134 -0
- package/dist/cjs/header/header.d.ts.map +1 -0
- package/dist/cjs/header/header.js +699 -0
- package/dist/cjs/header/header.js.map +1 -0
- package/dist/cjs/header/index.d.ts +3 -0
- package/dist/cjs/header/index.d.ts.map +1 -0
- package/dist/cjs/header/index.js +19 -0
- package/dist/cjs/header/index.js.map +1 -0
- package/dist/cjs/helpers.d.ts +59 -0
- package/dist/cjs/helpers.d.ts.map +1 -0
- package/dist/cjs/helpers.js +172 -0
- package/dist/cjs/helpers.js.map +1 -0
- package/dist/cjs/index.d.ts +8 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +31 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/params.d.ts +3 -0
- package/dist/cjs/params.d.ts.map +1 -0
- package/dist/cjs/params.js +97 -0
- package/dist/cjs/params.js.map +1 -0
- package/dist/cjs/types.d.ts +228 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/block/block.d.ts +147 -0
- package/dist/esm/block/block.d.ts.map +1 -0
- package/dist/esm/block/block.js +411 -0
- package/dist/esm/block/block.js.map +1 -0
- package/dist/esm/block/constructors.d.ts +77 -0
- package/dist/esm/block/constructors.d.ts.map +1 -0
- package/dist/esm/block/constructors.js +286 -0
- package/dist/esm/block/constructors.js.map +1 -0
- package/dist/esm/block/index.d.ts +3 -0
- package/dist/esm/block/index.d.ts.map +1 -0
- package/dist/esm/block/index.js +3 -0
- package/dist/esm/block/index.js.map +1 -0
- package/dist/esm/consensus/clique.d.ts +52 -0
- package/dist/esm/consensus/clique.d.ts.map +1 -0
- package/dist/esm/consensus/clique.js +132 -0
- package/dist/esm/consensus/clique.js.map +1 -0
- package/dist/esm/consensus/ethash.d.ts +9 -0
- package/dist/esm/consensus/ethash.d.ts.map +1 -0
- package/dist/esm/consensus/ethash.js +10 -0
- package/dist/esm/consensus/ethash.js.map +1 -0
- package/dist/esm/consensus/index.d.ts +3 -0
- package/dist/esm/consensus/index.d.ts.map +1 -0
- package/dist/esm/consensus/index.js +3 -0
- package/dist/esm/consensus/index.js.map +1 -0
- package/dist/esm/from-beacon-payload.d.ts +36 -0
- package/dist/esm/from-beacon-payload.d.ts.map +1 -0
- package/dist/esm/from-beacon-payload.js +45 -0
- package/dist/esm/from-beacon-payload.js.map +1 -0
- package/dist/esm/header/constructors.d.ts +39 -0
- package/dist/esm/header/constructors.d.ts.map +1 -0
- package/dist/esm/header/constructors.js +120 -0
- package/dist/esm/header/constructors.js.map +1 -0
- package/dist/esm/header/header.d.ts +134 -0
- package/dist/esm/header/header.d.ts.map +1 -0
- package/dist/esm/header/header.js +695 -0
- package/dist/esm/header/header.js.map +1 -0
- package/dist/esm/header/index.d.ts +3 -0
- package/dist/esm/header/index.d.ts.map +1 -0
- package/dist/esm/header/index.js +3 -0
- package/dist/esm/header/index.js.map +1 -0
- package/dist/esm/helpers.d.ts +59 -0
- package/dist/esm/helpers.d.ts.map +1 -0
- package/dist/esm/helpers.js +161 -0
- package/dist/esm/helpers.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/params.d.ts +3 -0
- package/dist/esm/params.d.ts.map +1 -0
- package/dist/esm/params.js +94 -0
- package/dist/esm/params.js.map +1 -0
- package/dist/esm/types.d.ts +228 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
- package/package.json +75 -0
- package/src/block/block.ts +526 -0
- package/src/block/constructors.ts +407 -0
- package/src/block/index.ts +2 -0
- package/src/consensus/clique.ts +171 -0
- package/src/consensus/ethash.ts +11 -0
- package/src/consensus/index.ts +12 -0
- package/src/from-beacon-payload.ts +82 -0
- package/src/header/constructors.ts +169 -0
- package/src/header/header.ts +890 -0
- package/src/header/index.ts +2 -0
- package/src/helpers.ts +223 -0
- package/src/index.ts +13 -0
- package/src/params.ts +95 -0
- 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,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
|
+
}
|