@feelyourprotocol/blockchain 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 (132) hide show
  1. package/README.md +269 -0
  2. package/dist/cjs/blockchain.d.ts +372 -0
  3. package/dist/cjs/blockchain.d.ts.map +1 -0
  4. package/dist/cjs/blockchain.js +1105 -0
  5. package/dist/cjs/blockchain.js.map +1 -0
  6. package/dist/cjs/consensus/casper.d.ts +16 -0
  7. package/dist/cjs/consensus/casper.d.ts.map +1 -0
  8. package/dist/cjs/consensus/casper.js +28 -0
  9. package/dist/cjs/consensus/casper.js.map +1 -0
  10. package/dist/cjs/consensus/clique.d.ts +164 -0
  11. package/dist/cjs/consensus/clique.d.ts.map +1 -0
  12. package/dist/cjs/consensus/clique.js +520 -0
  13. package/dist/cjs/consensus/clique.js.map +1 -0
  14. package/dist/cjs/consensus/ethash.d.ts +29 -0
  15. package/dist/cjs/consensus/ethash.d.ts.map +1 -0
  16. package/dist/cjs/consensus/ethash.js +48 -0
  17. package/dist/cjs/consensus/ethash.js.map +1 -0
  18. package/dist/cjs/consensus/index.d.ts +5 -0
  19. package/dist/cjs/consensus/index.d.ts.map +1 -0
  20. package/dist/cjs/consensus/index.js +10 -0
  21. package/dist/cjs/consensus/index.js.map +1 -0
  22. package/dist/cjs/constructors.d.ts +13 -0
  23. package/dist/cjs/constructors.d.ts.map +1 -0
  24. package/dist/cjs/constructors.js +80 -0
  25. package/dist/cjs/constructors.js.map +1 -0
  26. package/dist/cjs/db/cache.d.ts +17 -0
  27. package/dist/cjs/db/cache.d.ts.map +1 -0
  28. package/dist/cjs/db/cache.js +38 -0
  29. package/dist/cjs/db/cache.js.map +1 -0
  30. package/dist/cjs/db/constants.d.ts +23 -0
  31. package/dist/cjs/db/constants.d.ts.map +1 -0
  32. package/dist/cjs/db/constants.js +54 -0
  33. package/dist/cjs/db/constants.js.map +1 -0
  34. package/dist/cjs/db/helpers.d.ts +9 -0
  35. package/dist/cjs/db/helpers.d.ts.map +1 -0
  36. package/dist/cjs/db/helpers.js +68 -0
  37. package/dist/cjs/db/helpers.js.map +1 -0
  38. package/dist/cjs/db/manager.d.ts +78 -0
  39. package/dist/cjs/db/manager.d.ts.map +1 -0
  40. package/dist/cjs/db/manager.js +203 -0
  41. package/dist/cjs/db/manager.js.map +1 -0
  42. package/dist/cjs/db/operation.d.ts +45 -0
  43. package/dist/cjs/db/operation.d.ts.map +1 -0
  44. package/dist/cjs/db/operation.js +110 -0
  45. package/dist/cjs/db/operation.js.map +1 -0
  46. package/dist/cjs/helpers.d.ts +19 -0
  47. package/dist/cjs/helpers.d.ts.map +1 -0
  48. package/dist/cjs/helpers.js +34 -0
  49. package/dist/cjs/helpers.js.map +1 -0
  50. package/dist/cjs/index.d.ts +7 -0
  51. package/dist/cjs/index.d.ts.map +1 -0
  52. package/dist/cjs/index.js +33 -0
  53. package/dist/cjs/index.js.map +1 -0
  54. package/dist/cjs/package.json +3 -0
  55. package/dist/cjs/types.d.ts +219 -0
  56. package/dist/cjs/types.d.ts.map +1 -0
  57. package/dist/cjs/types.js +3 -0
  58. package/dist/cjs/types.js.map +1 -0
  59. package/dist/esm/blockchain.d.ts +372 -0
  60. package/dist/esm/blockchain.d.ts.map +1 -0
  61. package/dist/esm/blockchain.js +1101 -0
  62. package/dist/esm/blockchain.js.map +1 -0
  63. package/dist/esm/consensus/casper.d.ts +16 -0
  64. package/dist/esm/consensus/casper.d.ts.map +1 -0
  65. package/dist/esm/consensus/casper.js +24 -0
  66. package/dist/esm/consensus/casper.js.map +1 -0
  67. package/dist/esm/consensus/clique.d.ts +164 -0
  68. package/dist/esm/consensus/clique.d.ts.map +1 -0
  69. package/dist/esm/consensus/clique.js +516 -0
  70. package/dist/esm/consensus/clique.js.map +1 -0
  71. package/dist/esm/consensus/ethash.d.ts +29 -0
  72. package/dist/esm/consensus/ethash.d.ts.map +1 -0
  73. package/dist/esm/consensus/ethash.js +44 -0
  74. package/dist/esm/consensus/ethash.js.map +1 -0
  75. package/dist/esm/consensus/index.d.ts +5 -0
  76. package/dist/esm/consensus/index.d.ts.map +1 -0
  77. package/dist/esm/consensus/index.js +5 -0
  78. package/dist/esm/consensus/index.js.map +1 -0
  79. package/dist/esm/constructors.d.ts +13 -0
  80. package/dist/esm/constructors.d.ts.map +1 -0
  81. package/dist/esm/constructors.js +76 -0
  82. package/dist/esm/constructors.js.map +1 -0
  83. package/dist/esm/db/cache.d.ts +17 -0
  84. package/dist/esm/db/cache.d.ts.map +1 -0
  85. package/dist/esm/db/cache.js +34 -0
  86. package/dist/esm/db/cache.js.map +1 -0
  87. package/dist/esm/db/constants.d.ts +23 -0
  88. package/dist/esm/db/constants.d.ts.map +1 -0
  89. package/dist/esm/db/constants.js +46 -0
  90. package/dist/esm/db/constants.js.map +1 -0
  91. package/dist/esm/db/helpers.d.ts +9 -0
  92. package/dist/esm/db/helpers.d.ts.map +1 -0
  93. package/dist/esm/db/helpers.js +61 -0
  94. package/dist/esm/db/helpers.js.map +1 -0
  95. package/dist/esm/db/manager.d.ts +78 -0
  96. package/dist/esm/db/manager.d.ts.map +1 -0
  97. package/dist/esm/db/manager.js +199 -0
  98. package/dist/esm/db/manager.js.map +1 -0
  99. package/dist/esm/db/operation.d.ts +45 -0
  100. package/dist/esm/db/operation.d.ts.map +1 -0
  101. package/dist/esm/db/operation.js +106 -0
  102. package/dist/esm/db/operation.js.map +1 -0
  103. package/dist/esm/helpers.d.ts +19 -0
  104. package/dist/esm/helpers.d.ts.map +1 -0
  105. package/dist/esm/helpers.js +30 -0
  106. package/dist/esm/helpers.js.map +1 -0
  107. package/dist/esm/index.d.ts +7 -0
  108. package/dist/esm/index.d.ts.map +1 -0
  109. package/dist/esm/index.js +7 -0
  110. package/dist/esm/index.js.map +1 -0
  111. package/dist/esm/package.json +3 -0
  112. package/dist/esm/types.d.ts +219 -0
  113. package/dist/esm/types.d.ts.map +1 -0
  114. package/dist/esm/types.js +2 -0
  115. package/dist/esm/types.js.map +1 -0
  116. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
  117. package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
  118. package/package.json +75 -0
  119. package/src/blockchain.ts +1353 -0
  120. package/src/consensus/casper.ts +33 -0
  121. package/src/consensus/clique.ts +633 -0
  122. package/src/consensus/ethash.ts +69 -0
  123. package/src/consensus/index.ts +5 -0
  124. package/src/constructors.ts +119 -0
  125. package/src/db/cache.ts +39 -0
  126. package/src/db/constants.ts +73 -0
  127. package/src/db/helpers.ts +81 -0
  128. package/src/db/manager.ts +243 -0
  129. package/src/db/operation.ts +152 -0
  130. package/src/helpers.ts +37 -0
  131. package/src/index.ts +12 -0
  132. package/src/types.ts +256 -0
@@ -0,0 +1,33 @@
1
+ import { ConsensusAlgorithm } from '@feelyourprotocol/common'
2
+ import { BIGINT_0, EthereumJSErrorWithoutCode } from '@feelyourprotocol/util'
3
+
4
+ import type { BlockHeader } from '@feelyourprotocol/block'
5
+ import type { Consensus } from '../types.ts'
6
+
7
+ /**
8
+ * This class encapsulates Casper-related consensus functionality when used with the Blockchain class.
9
+ */
10
+ export class CasperConsensus implements Consensus {
11
+ algorithm: ConsensusAlgorithm
12
+
13
+ constructor() {
14
+ this.algorithm = ConsensusAlgorithm.Casper
15
+ }
16
+
17
+ public async genesisInit(): Promise<void> {}
18
+
19
+ public async setup(): Promise<void> {}
20
+
21
+ public async validateConsensus(): Promise<void> {}
22
+
23
+ public async validateDifficulty(header: BlockHeader): Promise<void> {
24
+ // TODO: This is not really part of consensus validation and it should be analyzed
25
+ // if it is possible to replace by a more generic hardfork check between block and
26
+ // blockchain along adding new blocks or headers
27
+ if (header.difficulty !== BIGINT_0) {
28
+ const msg = 'invalid difficulty. PoS blocks must have difficulty 0'
29
+ throw EthereumJSErrorWithoutCode(`${msg} ${header.errorStr()}`)
30
+ }
31
+ }
32
+ public async newBlock(): Promise<void> {}
33
+ }
@@ -0,0 +1,633 @@
1
+ import {
2
+ cliqueEpochTransitionSigners,
3
+ cliqueIsEpochTransition,
4
+ cliqueSigner,
5
+ cliqueVerifySignature,
6
+ } from '@feelyourprotocol/block'
7
+ import { ConsensusAlgorithm } from '@feelyourprotocol/common'
8
+ import { RLP } from '@feelyourprotocol/rlp'
9
+ import {
10
+ Address,
11
+ BIGINT_0,
12
+ BIGINT_1,
13
+ BIGINT_2,
14
+ EthereumJSErrorWithoutCode,
15
+ type NestedUint8Array,
16
+ TypeOutput,
17
+ bigIntToBytes,
18
+ bytesToBigInt,
19
+ equalsBytes,
20
+ hexToBytes,
21
+ isDebugEnabled,
22
+ toType,
23
+ } from '@feelyourprotocol/util'
24
+ import debugDefault from 'debug'
25
+
26
+ import type { Block, BlockHeader } from '@feelyourprotocol/block'
27
+ import type { CliqueConfig } from '@feelyourprotocol/common'
28
+ import type { Blockchain } from '../index.ts'
29
+ import type { Consensus, ConsensusOptions } from '../types.ts'
30
+
31
+ const debug = debugDefault('blockchain:clique')
32
+
33
+ // Magic nonce number to vote on adding a new signer
34
+ export const CLIQUE_NONCE_AUTH = new Uint8Array(
35
+ hexToBytes('0xffffffffffffffff').buffer as ArrayBuffer,
36
+ )
37
+ // Magic nonce number to vote on removing a signer.
38
+ export const CLIQUE_NONCE_DROP = new Uint8Array(8)
39
+
40
+ const CLIQUE_SIGNERS_KEY = 'CliqueSigners'
41
+ const CLIQUE_VOTES_KEY = 'CliqueVotes'
42
+ const CLIQUE_BLOCK_SIGNERS_SNAPSHOT_KEY = 'CliqueBlockSignersSnapshot'
43
+
44
+ // Block difficulty for in-turn signatures
45
+ export const CLIQUE_DIFF_INTURN = BIGINT_2
46
+ // Block difficulty for out-of-turn signatures
47
+ export const CLIQUE_DIFF_NOTURN = BIGINT_1
48
+
49
+ // Clique Signer State
50
+ type CliqueSignerState = [blockNumber: bigint, signers: Address[]]
51
+ type CliqueLatestSignerStates = CliqueSignerState[]
52
+
53
+ // Clique Vote
54
+ type CliqueVote = [
55
+ blockNumber: bigint,
56
+ vote: [signer: Address, beneficiary: Address, cliqueNonce: Uint8Array],
57
+ ]
58
+ type CliqueLatestVotes = CliqueVote[]
59
+
60
+ // Clique Block Signer
61
+ type CliqueBlockSigner = [blockNumber: bigint, signer: Address]
62
+ type CliqueLatestBlockSigners = CliqueBlockSigner[]
63
+
64
+ /**
65
+ * This class encapsulates Clique-related consensus functionality when used with the Blockchain class.
66
+ * Note: reorgs which happen between epoch transitions, which change the internal voting state over the reorg
67
+ * will result in failure and is currently not supported.
68
+ * The hotfix for this could be: re-load the latest epoch block (this has the clique state in the extraData of the header)
69
+ * Now replay all blocks on top of it. This should validate the chain up to the new/reorged tip which previously threw.
70
+ */
71
+ export class CliqueConsensus implements Consensus {
72
+ blockchain: Blockchain | undefined
73
+ algorithm: ConsensusAlgorithm
74
+
75
+ /**
76
+ * Keep signer history data (signer states and votes)
77
+ * for all block numbers >= HEAD_BLOCK - CLIQUE_SIGNER_HISTORY_BLOCK_LIMIT
78
+ *
79
+ * This defines a limit for reorgs on PoA clique chains.
80
+ */
81
+ private CLIQUE_SIGNER_HISTORY_BLOCK_LIMIT = 200
82
+
83
+ /**
84
+ * List with the latest signer states checkpointed on blocks where
85
+ * a change (added new or removed a signer) occurred.
86
+ *
87
+ * Format:
88
+ * [ [BLOCK_NUMBER_1, [SIGNER1, SIGNER 2,]], [BLOCK_NUMBER2, [SIGNER1, SIGNER3]], ...]
89
+ *
90
+ * The top element from the array represents the list of current signers.
91
+ * On reorgs elements from the array are removed until BLOCK_NUMBER > REORG_BLOCK.
92
+ *
93
+ * Always keep at least one item on the stack.
94
+ */
95
+ public _cliqueLatestSignerStates: CliqueLatestSignerStates = []
96
+
97
+ /**
98
+ * List with the latest signer votes.
99
+ *
100
+ * Format:
101
+ * [ [BLOCK_NUMBER_1, [SIGNER, BENEFICIARY, AUTH]], [BLOCK_NUMBER_1, [SIGNER, BENEFICIARY, AUTH]] ]
102
+ * where AUTH = CLIQUE_NONCE_AUTH | CLIQUE_NONCE_DROP
103
+ *
104
+ * For votes all elements here must be taken into account with a
105
+ * block number >= LAST_EPOCH_BLOCK
106
+ * (nevertheless keep entries with blocks before EPOCH_BLOCK in case a reorg happens
107
+ * during an epoch change)
108
+ *
109
+ * On reorgs elements from the array are removed until BLOCK_NUMBER > REORG_BLOCK.
110
+ */
111
+ public _cliqueLatestVotes: CliqueLatestVotes = []
112
+
113
+ /**
114
+ * List of signers for the last consecutive {@link Blockchain.cliqueSignerLimit} blocks.
115
+ * Kept as a snapshot for quickly checking for "recently signed" error.
116
+ * Format: [ [BLOCK_NUMBER, SIGNER_ADDRESS], ...]
117
+ *
118
+ * On reorgs elements from the array are removed until BLOCK_NUMBER > REORG_BLOCK.
119
+ */
120
+ public _cliqueLatestBlockSigners: CliqueLatestBlockSigners = []
121
+
122
+ DEBUG: boolean // Guard for debug logs
123
+ constructor() {
124
+ // Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables
125
+ this.DEBUG = isDebugEnabled('ethjs')
126
+
127
+ this.algorithm = ConsensusAlgorithm.Clique
128
+ }
129
+
130
+ /**
131
+ *
132
+ * @param param dictionary containing a {@link Blockchain} object
133
+ *
134
+ * Note: this method must be called before consensus checks are used or type errors will occur
135
+ */
136
+ async setup({ blockchain }: ConsensusOptions): Promise<void> {
137
+ this.blockchain = blockchain
138
+ this._cliqueLatestSignerStates = await this.getCliqueLatestSignerStates()
139
+ this._cliqueLatestSignerStates.sort((a, b) => (a[0] > b[0] ? 1 : -1))
140
+ this._cliqueLatestVotes = await this.getCliqueLatestVotes()
141
+ this._cliqueLatestBlockSigners = await this.getCliqueLatestBlockSigners()
142
+ }
143
+
144
+ async genesisInit(genesisBlock: Block): Promise<void> {
145
+ await this.cliqueSaveGenesisSigners(genesisBlock)
146
+ }
147
+
148
+ async validateConsensus(block: Block): Promise<void> {
149
+ if (!this.blockchain) {
150
+ throw EthereumJSErrorWithoutCode('blockchain not provided')
151
+ }
152
+
153
+ const { header } = block
154
+ const valid = cliqueVerifySignature(header, this.cliqueActiveSigners(header.number))
155
+ if (!valid) {
156
+ throw EthereumJSErrorWithoutCode('invalid PoA block signature (clique)')
157
+ }
158
+ if (this.cliqueCheckRecentlySigned(header)) {
159
+ throw EthereumJSErrorWithoutCode('recently signed')
160
+ }
161
+
162
+ // validate checkpoint signers towards active signers on epoch transition blocks
163
+ if (cliqueIsEpochTransition(header)) {
164
+ // note: keep votes on epoch transition blocks in case of reorgs.
165
+ // only active (non-stale) votes will counted (if vote.blockNumber >= lastEpochBlockNumber
166
+
167
+ const checkpointSigners = cliqueEpochTransitionSigners(header)
168
+ const activeSigners = this.cliqueActiveSigners(header.number)
169
+ for (const [i, cSigner] of checkpointSigners.entries()) {
170
+ if (activeSigners[i]?.equals(cSigner) !== true) {
171
+ throw EthereumJSErrorWithoutCode(
172
+ `checkpoint signer not found in active signers list at index ${i}: ${cSigner}`,
173
+ )
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ async validateDifficulty(header: BlockHeader): Promise<void> {
180
+ if (!this.blockchain) {
181
+ throw EthereumJSErrorWithoutCode('blockchain not provided')
182
+ }
183
+
184
+ if (header.difficulty !== CLIQUE_DIFF_INTURN && header.difficulty !== CLIQUE_DIFF_NOTURN) {
185
+ const msg = `difficulty for clique block must be INTURN (2) or NOTURN (1), received: ${header.difficulty}`
186
+ throw EthereumJSErrorWithoutCode(`${msg} ${header.errorStr()}`)
187
+ }
188
+
189
+ const signers = this.cliqueActiveSigners(header.number)
190
+ if (signers.length === 0) {
191
+ // abort if signers are unavailable
192
+ const msg = 'no signers available'
193
+ throw EthereumJSErrorWithoutCode(`${msg} ${header.errorStr()}`)
194
+ }
195
+ const signerIndex = signers.findIndex((address: Address) =>
196
+ address.equals(cliqueSigner(header)),
197
+ )
198
+ const inTurn = header.number % BigInt(signers.length) === BigInt(signerIndex)
199
+ if (
200
+ (inTurn && header.difficulty === CLIQUE_DIFF_INTURN) ||
201
+ (!inTurn && header.difficulty === CLIQUE_DIFF_NOTURN)
202
+ ) {
203
+ return
204
+ }
205
+ throw EthereumJSErrorWithoutCode(`'invalid clique difficulty ${header.errorStr()}`)
206
+ }
207
+
208
+ async newBlock(block: Block, commonAncestor: BlockHeader | undefined): Promise<void> {
209
+ // Clique: update signer votes and state
210
+ const { header } = block
211
+ const commonAncestorNumber = commonAncestor?.number
212
+ if (commonAncestorNumber !== undefined) {
213
+ await this._cliqueDeleteSnapshots(commonAncestorNumber + BIGINT_1)
214
+ for (let number = commonAncestorNumber + BigInt(1); number <= header.number; number++) {
215
+ const canonicalHeader = await this.blockchain!.getCanonicalHeader(number)
216
+ await this._cliqueBuildSnapshots(canonicalHeader)
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Save genesis signers to db
223
+ * @param genesisBlock genesis block
224
+ * @hidden
225
+ */
226
+ private async cliqueSaveGenesisSigners(genesisBlock: Block) {
227
+ const genesisSignerState: CliqueSignerState = [
228
+ BIGINT_0,
229
+ cliqueEpochTransitionSigners(genesisBlock.header),
230
+ ]
231
+ await this.cliqueUpdateSignerStates(genesisSignerState)
232
+ this.DEBUG && debug(`[Block 0] Genesis block -> update signer states`)
233
+ await this.cliqueUpdateVotes()
234
+ }
235
+
236
+ /**
237
+ * Save signer state to db
238
+ * @param signerState
239
+ * @hidden
240
+ */
241
+ private async cliqueUpdateSignerStates(signerState?: CliqueSignerState) {
242
+ if (signerState) {
243
+ const blockNumber = signerState[0]
244
+ const known = this._cliqueLatestSignerStates.find((value) => {
245
+ if (value[0] === blockNumber) {
246
+ return true
247
+ }
248
+ })
249
+ if (known !== undefined) {
250
+ return
251
+ }
252
+ this._cliqueLatestSignerStates.push(signerState)
253
+ this._cliqueLatestSignerStates.sort((a, b) => (a[0] > b[0] ? 1 : -1))
254
+ }
255
+
256
+ // trim to CLIQUE_SIGNER_HISTORY_BLOCK_LIMIT
257
+ const limit = this.CLIQUE_SIGNER_HISTORY_BLOCK_LIMIT
258
+ const blockSigners = this._cliqueLatestBlockSigners
259
+ const lastBlockNumber = blockSigners[blockSigners.length - 1]?.[0]
260
+ if (lastBlockNumber) {
261
+ const blockLimit = lastBlockNumber - BigInt(limit)
262
+ const states = this._cliqueLatestSignerStates
263
+ const lastItem = states[states.length - 1]
264
+ this._cliqueLatestSignerStates = states.filter((state) => state[0] >= blockLimit)
265
+ if (this._cliqueLatestSignerStates.length === 0) {
266
+ // always keep at least one item on the stack
267
+ this._cliqueLatestSignerStates.push(lastItem)
268
+ }
269
+ }
270
+
271
+ // save to db
272
+ const formatted = this._cliqueLatestSignerStates.map((state) => [
273
+ bigIntToBytes(state[0]),
274
+ state[1].map((a) => a.toBytes()),
275
+ ])
276
+ await this.blockchain!.db.put(CLIQUE_SIGNERS_KEY, RLP.encode(formatted))
277
+ // Output active signers for debugging purposes
278
+ if (signerState !== undefined) {
279
+ let i = 0
280
+ try {
281
+ for (const signer of this.cliqueActiveSigners(signerState[0])) {
282
+ this.DEBUG && debug(`Clique signer [${i}]: ${signer} (block: ${signerState[0]})`)
283
+ i++
284
+ }
285
+ // eslint-disable-next-line no-empty
286
+ } catch {}
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Update clique votes and save to db
292
+ * @param header BlockHeader
293
+ * @hidden
294
+ */
295
+ private async cliqueUpdateVotes(header?: BlockHeader) {
296
+ // Block contains a vote on a new signer
297
+ if (header && !header.coinbase.isZero()) {
298
+ const signer = cliqueSigner(header)
299
+ const beneficiary = header.coinbase
300
+ const nonce = header.nonce
301
+ const latestVote: CliqueVote = [header.number, [signer, beneficiary, nonce]]
302
+
303
+ // Do two rounds here, one to execute on a potential previously reached consensus
304
+ // on the newly touched beneficiary, one with the added new vote
305
+ for (let round = 1; round <= 2; round++) {
306
+ // See if there is a new majority consensus to update the signer list
307
+ const lastEpochBlockNumber =
308
+ header.number -
309
+ (header.number %
310
+ BigInt((this.blockchain!.common.consensusConfig() as CliqueConfig).epoch))
311
+ const limit = this.cliqueSignerLimit(header.number)
312
+ let activeSigners = [...this.cliqueActiveSigners(header.number)]
313
+ let consensus = false
314
+
315
+ // AUTH vote analysis
316
+ let votes = this._cliqueLatestVotes.filter((vote) => {
317
+ return (
318
+ vote[0] >= BigInt(lastEpochBlockNumber) &&
319
+ !vote[1][0].equals(signer) &&
320
+ vote[1][1].equals(beneficiary) &&
321
+ equalsBytes(vote[1][2], CLIQUE_NONCE_AUTH)
322
+ )
323
+ })
324
+ const beneficiaryVotesAUTH: Address[] = []
325
+ for (const vote of votes) {
326
+ const num = beneficiaryVotesAUTH.filter((voteCMP) => {
327
+ return voteCMP.equals(vote[1][0])
328
+ }).length
329
+ if (num === 0) {
330
+ beneficiaryVotesAUTH.push(vote[1][0])
331
+ }
332
+ }
333
+ let numBeneficiaryVotesAUTH = beneficiaryVotesAUTH.length
334
+ if (round === 2 && equalsBytes(nonce, CLIQUE_NONCE_AUTH)) {
335
+ numBeneficiaryVotesAUTH += 1
336
+ }
337
+ // Majority consensus
338
+ if (numBeneficiaryVotesAUTH >= limit) {
339
+ consensus = true
340
+ // Authorize new signer
341
+ activeSigners.push(beneficiary)
342
+ activeSigners.sort((a, b) => {
343
+ // Sort by array size
344
+ const result =
345
+ toType(a.toString(), TypeOutput.BigInt) < toType(b.toString(), TypeOutput.BigInt)
346
+ if (result) {
347
+ return -1
348
+ } else {
349
+ return 1
350
+ }
351
+ })
352
+ // Discard votes for added signer
353
+ this._cliqueLatestVotes = this._cliqueLatestVotes.filter(
354
+ (vote) => !vote[1][1].equals(beneficiary),
355
+ )
356
+ this.DEBUG &&
357
+ debug(`[Block ${header.number}] Clique majority consensus (AUTH ${beneficiary})`)
358
+ }
359
+ // DROP vote
360
+ votes = this._cliqueLatestVotes.filter((vote) => {
361
+ return (
362
+ vote[0] >= BigInt(lastEpochBlockNumber) &&
363
+ !vote[1][0].equals(signer) &&
364
+ vote[1][1].equals(beneficiary) &&
365
+ equalsBytes(vote[1][2], CLIQUE_NONCE_DROP)
366
+ )
367
+ })
368
+ const beneficiaryVotesDROP: Address[] = []
369
+ for (const vote of votes) {
370
+ const num = beneficiaryVotesDROP.filter((voteCMP) => {
371
+ return voteCMP.equals(vote[1][0])
372
+ }).length
373
+ if (num === 0) {
374
+ beneficiaryVotesDROP.push(vote[1][0])
375
+ }
376
+ }
377
+ let numBeneficiaryVotesDROP = beneficiaryVotesDROP.length
378
+
379
+ if (round === 2 && equalsBytes(nonce, CLIQUE_NONCE_DROP)) {
380
+ numBeneficiaryVotesDROP += 1
381
+ }
382
+ // Majority consensus
383
+ if (numBeneficiaryVotesDROP >= limit) {
384
+ consensus = true
385
+ // Drop signer
386
+ activeSigners = activeSigners.filter((signer) => !signer.equals(beneficiary))
387
+ this._cliqueLatestVotes = this._cliqueLatestVotes.filter(
388
+ // Discard votes from removed signer and for removed signer
389
+ (vote) => !vote[1][0].equals(beneficiary) && !vote[1][1].equals(beneficiary),
390
+ )
391
+ this.DEBUG &&
392
+ debug(`[Block ${header.number}] Clique majority consensus (DROP ${beneficiary})`)
393
+ }
394
+ if (round === 1) {
395
+ // Always add the latest vote to the history no matter if already voted
396
+ // the same vote or not
397
+ this._cliqueLatestVotes.push(latestVote)
398
+ this.DEBUG &&
399
+ debug(
400
+ `[Block ${header.number}] New clique vote: ${signer} -> ${beneficiary} ${
401
+ equalsBytes(nonce, CLIQUE_NONCE_AUTH) ? 'AUTH' : 'DROP'
402
+ }`,
403
+ )
404
+ }
405
+ if (consensus) {
406
+ if (round === 1) {
407
+ this.DEBUG &&
408
+ debug(
409
+ `[Block ${header.number}] Clique majority consensus on existing votes -> update signer states`,
410
+ )
411
+ } else {
412
+ this.DEBUG &&
413
+ debug(
414
+ `[Block ${header.number}] Clique majority consensus on new vote -> update signer states`,
415
+ )
416
+ }
417
+ const newSignerState: CliqueSignerState = [header.number, activeSigners]
418
+ await this.cliqueUpdateSignerStates(newSignerState)
419
+ return
420
+ }
421
+ }
422
+ }
423
+
424
+ // trim to lastEpochBlockNumber - CLIQUE_SIGNER_HISTORY_BLOCK_LIMIT
425
+ const limit = this.CLIQUE_SIGNER_HISTORY_BLOCK_LIMIT
426
+ const blockSigners = this._cliqueLatestBlockSigners
427
+ const lastBlockNumber = blockSigners[blockSigners.length - 1]?.[0]
428
+ if (lastBlockNumber) {
429
+ const lastEpochBlockNumber =
430
+ lastBlockNumber -
431
+ (lastBlockNumber %
432
+ BigInt((this.blockchain!.common.consensusConfig() as CliqueConfig).epoch))
433
+ const blockLimit = lastEpochBlockNumber - BigInt(limit)
434
+ this._cliqueLatestVotes = this._cliqueLatestVotes.filter((state) => state[0] >= blockLimit)
435
+ }
436
+
437
+ // save votes to db
438
+ const formatted = this._cliqueLatestVotes.map((v) => [
439
+ bigIntToBytes(v[0]),
440
+ [v[1][0].toBytes(), v[1][1].toBytes(), v[1][2]],
441
+ ])
442
+ await this.blockchain!.db.put(CLIQUE_VOTES_KEY, RLP.encode(formatted))
443
+ }
444
+
445
+ /**
446
+ * Returns a list with the current block signers
447
+ */
448
+ cliqueActiveSigners(blockNum: bigint): Address[] {
449
+ const signers = this._cliqueLatestSignerStates
450
+ if (signers.length === 0) {
451
+ return []
452
+ }
453
+ for (let i = signers.length - 1; i >= 0; i--) {
454
+ if (signers[i][0] < blockNum) {
455
+ return signers[i][1]
456
+ }
457
+ }
458
+ throw EthereumJSErrorWithoutCode(`Could not load signers for block ${blockNum}`)
459
+ }
460
+
461
+ /**
462
+ * Number of consecutive blocks out of which a signer may only sign one.
463
+ * Defined as `Math.floor(SIGNER_COUNT / 2) + 1` to enforce majority consensus.
464
+ * signer count -> signer limit:
465
+ * 1 -> 1, 2 -> 2, 3 -> 2, 4 -> 2, 5 -> 3, ...
466
+ * @hidden
467
+ */
468
+ private cliqueSignerLimit(blockNum: bigint) {
469
+ return Math.floor(this.cliqueActiveSigners(blockNum).length / 2) + 1
470
+ }
471
+
472
+ /**
473
+ * Checks if signer was recently signed.
474
+ * Returns true if signed too recently: more than once per {@link CliqueConsensus.cliqueSignerLimit} consecutive blocks.
475
+ * @param header BlockHeader
476
+ * @hidden
477
+ */
478
+ private cliqueCheckRecentlySigned(header: BlockHeader): boolean {
479
+ if (header.isGenesis() || header.number === BigInt(1)) {
480
+ // skip genesis, first block
481
+ return false
482
+ }
483
+ const limit = this.cliqueSignerLimit(header.number)
484
+ // construct recent block signers list with this block
485
+ let signers = this._cliqueLatestBlockSigners
486
+ signers = signers.slice(signers.length < limit ? 0 : 1)
487
+ if (signers.length > 0 && signers[signers.length - 1][0] !== header.number - BigInt(1)) {
488
+ // if the last signed block is not one minus the head we are trying to compare
489
+ // we do not have a complete picture of the state to verify if too recently signed
490
+ return false
491
+ }
492
+ signers.push([header.number, cliqueSigner(header)])
493
+ const seen = signers.filter((s) => s[1].equals(cliqueSigner(header))).length
494
+ return seen > 1
495
+ }
496
+
497
+ /**
498
+ * Remove clique snapshots with blockNumber higher than input.
499
+ * @param blockNumber - the block number from which we start deleting
500
+ * @hidden
501
+ */
502
+ private async _cliqueDeleteSnapshots(blockNumber: bigint) {
503
+ // remove blockNumber from clique snapshots
504
+ // (latest signer states, latest votes, latest block signers)
505
+ this._cliqueLatestSignerStates = this._cliqueLatestSignerStates.filter(
506
+ (s) => s[0] <= blockNumber,
507
+ )
508
+ await this.cliqueUpdateSignerStates()
509
+
510
+ this._cliqueLatestVotes = this._cliqueLatestVotes.filter((v) => v[0] <= blockNumber)
511
+ await this.cliqueUpdateVotes()
512
+
513
+ this._cliqueLatestBlockSigners = this._cliqueLatestBlockSigners.filter(
514
+ (s) => s[0] <= blockNumber,
515
+ )
516
+ await this.cliqueUpdateLatestBlockSigners()
517
+ }
518
+
519
+ /**
520
+ * Update snapshot of latest clique block signers.
521
+ * Used for checking for 'recently signed' error.
522
+ * Length trimmed to {@link Blockchain.cliqueSignerLimit}.
523
+ * @param header BlockHeader
524
+ * @hidden
525
+ */
526
+ private async cliqueUpdateLatestBlockSigners(header?: BlockHeader) {
527
+ if (header) {
528
+ if (header.isGenesis()) {
529
+ return
530
+ }
531
+ // add this block's signer
532
+ const signer: CliqueBlockSigner = [header.number, cliqueSigner(header)]
533
+ this._cliqueLatestBlockSigners.push(signer)
534
+
535
+ // trim length to `this.cliqueSignerLimit()`
536
+ const length = this._cliqueLatestBlockSigners.length
537
+ const limit = this.cliqueSignerLimit(header.number)
538
+ if (length > limit) {
539
+ this._cliqueLatestBlockSigners = this._cliqueLatestBlockSigners.slice(
540
+ length - limit,
541
+ length,
542
+ )
543
+ }
544
+ }
545
+
546
+ // save to db
547
+ const formatted = this._cliqueLatestBlockSigners.map((b) => [
548
+ bigIntToBytes(b[0]),
549
+ b[1].toBytes(),
550
+ ])
551
+ await this.blockchain!.db.put(CLIQUE_BLOCK_SIGNERS_SNAPSHOT_KEY, RLP.encode(formatted))
552
+ }
553
+
554
+ /**
555
+ * Fetches clique signers.
556
+ * @hidden
557
+ */
558
+ private async getCliqueLatestSignerStates(): Promise<CliqueLatestSignerStates> {
559
+ const signerStates = await this.blockchain!.db.get(CLIQUE_SIGNERS_KEY)
560
+ if (signerStates === undefined) return []
561
+ const states = RLP.decode(signerStates as Uint8Array) as NestedUint8Array
562
+ return states.map((state) => {
563
+ const blockNum = bytesToBigInt(state[0] as Uint8Array)
564
+ const addresses: Address[] = (state[1] as Uint8Array[]).map((bytes: Uint8Array): Address => {
565
+ const address = new Address(bytes)
566
+ return address
567
+ })
568
+ return [blockNum, addresses]
569
+ }) as CliqueLatestSignerStates
570
+ }
571
+
572
+ /**
573
+ * Fetches clique votes.
574
+ * @hidden
575
+ */
576
+ private async getCliqueLatestVotes(): Promise<CliqueLatestVotes> {
577
+ const signerVotes = await this.blockchain!.db.get(CLIQUE_VOTES_KEY)
578
+ if (signerVotes === undefined) return []
579
+ const votes = RLP.decode(signerVotes as Uint8Array) as [
580
+ Uint8Array,
581
+ [Uint8Array, Uint8Array, Uint8Array],
582
+ ]
583
+ return votes.map((vote) => {
584
+ const blockNum = bytesToBigInt(vote[0] as Uint8Array)
585
+ const signer = new Address((vote[1] as any)[0])
586
+ const beneficiary = new Address((vote[1] as any)[1])
587
+ const nonce = (vote[1] as any)[2]
588
+ return [blockNum, [signer, beneficiary, nonce]]
589
+ }) as CliqueLatestVotes
590
+ }
591
+
592
+ /**
593
+ * Fetches snapshot of clique signers.
594
+ * @hidden
595
+ */
596
+ private async getCliqueLatestBlockSigners(): Promise<CliqueLatestBlockSigners> {
597
+ const blockSigners = await this.blockchain!.db.get(CLIQUE_BLOCK_SIGNERS_SNAPSHOT_KEY)
598
+ if (blockSigners === undefined) return []
599
+ const signers = RLP.decode(blockSigners as Uint8Array) as [Uint8Array, Uint8Array][]
600
+ return signers.map((s) => {
601
+ const blockNum = bytesToBigInt(s[0] as Uint8Array)
602
+ const signer = new Address(s[1])
603
+ return [blockNum, signer]
604
+ }) as CliqueLatestBlockSigners
605
+ }
606
+
607
+ /**
608
+ * Build clique snapshots.
609
+ * @param header - the new block header
610
+ * @hidden
611
+ */
612
+ private async _cliqueBuildSnapshots(header: BlockHeader) {
613
+ if (!cliqueIsEpochTransition(header)) {
614
+ await this.cliqueUpdateVotes(header)
615
+ }
616
+ await this.cliqueUpdateLatestBlockSigners(header)
617
+ }
618
+
619
+ /**
620
+ * Helper to determine if a signer is in or out of turn for the next block.
621
+ * @param signer The signer address
622
+ */
623
+ async cliqueSignerInTurn(signer: Address, blockNum: bigint): Promise<boolean> {
624
+ const signers = this.cliqueActiveSigners(blockNum)
625
+ const signerIndex = signers.findIndex((address) => address.equals(signer))
626
+ if (signerIndex === -1) {
627
+ throw EthereumJSErrorWithoutCode('Signer not found')
628
+ }
629
+ const { number } = await this.blockchain!.getCanonicalHeadHeader()
630
+
631
+ return (number + BigInt(1)) % BigInt(signers.length) === BigInt(signerIndex)
632
+ }
633
+ }