-
Notifications
You must be signed in to change notification settings - Fork 772
/
index.ts
700 lines (641 loc) · 21.4 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
import { buf as crc32Buffer } from 'crc-32'
import { _getInitializedChains } from './chains'
import { hardforks as HARDFORK_CHANGES } from './hardforks'
import { EIPs } from './eips'
import { Chain } from './types'
/**
* Options for instantiating a [[Common]] instance.
*/
export interface CommonOpts {
/**
* Chain name ('mainnet') or id (1), either from a chain directly supported
* or a custom chain passed in via `customChains`
*/
chain: string | number | object
/**
* String identifier ('byzantium') for hardfork
*
* Default: `istanbul`
*/
hardfork?: string
/**
* Limit parameter returns to the given hardforks
*/
supportedHardforks?: Array<string>
/**
* Selected EIPs which can be activated, please use an array for instantiation
* (e.g. `eips: [ 2537, ]`)
*
* Currently supported:
*
* - [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537) - BLS12-381 precompiles
*/
eips?: number[]
/**
* Initialize (in addition to the supported chains) with the selected
* custom chains
*
* Usage (directly with the respective chain intialization via the `chain` option):
*
* ```javascript
* import myCustomChain1 from '[PATH_TO_MY_CHAINS]/myCustomChain1.json'
* const common = new Common({ chain: 'myCustomChain1', customChains: [ myCustomChain1 ]})
* ```
*/
customChains?: Chain[]
}
interface hardforkOptions {
/** optional, only allow supported HFs (default: false) */
onlySupported?: boolean
/** optional, only active HFs (default: false) */
onlyActive?: boolean
}
/**
* Common class to access chain and hardfork parameters
*/
export default class Common {
readonly DEFAULT_HARDFORK: string
private _chainParams: Chain
private _hardfork: string
private _supportedHardforks: Array<string> = []
private _eips: number[] = []
private _customChains: Chain[]
/**
* Creates a Common object for a custom chain, based on a standard one. It uses all the [[Chain]]
* params from [[baseChain]] except the ones overridden in [[customChainParams]].
*
* @param baseChain The name (`mainnet`) or id (`1`) of a standard chain used to base the custom
* chain params on.
* @param customChainParams The custom parameters of the chain.
* @param hardfork String identifier ('byzantium') for hardfork (optional)
* @param supportedHardforks Limit parameter returns to the given hardforks (optional)
*/
static forCustomChain(
baseChain: string | number,
customChainParams: Partial<Chain>,
hardfork?: string,
supportedHardforks?: Array<string>
): Common {
const standardChainParams = Common._getChainParams(baseChain)
return new Common({
chain: {
...standardChainParams,
...customChainParams,
},
hardfork: hardfork,
supportedHardforks: supportedHardforks,
})
}
private static _getChainParams(chain: string | number, customChains?: Chain[]): Chain {
const initializedChains: any = _getInitializedChains(customChains)
if (typeof chain === 'number') {
if (initializedChains['names'][chain]) {
const name: string = initializedChains['names'][chain]
return initializedChains[name]
}
throw new Error(`Chain with ID ${chain} not supported`)
}
if (initializedChains[chain]) {
return initializedChains[chain]
}
throw new Error(`Chain with name ${chain} not supported`)
}
/**
* @constructor
*/
constructor(opts: CommonOpts) {
this._customChains = opts.customChains ?? []
this._chainParams = this.setChain(opts.chain)
this.DEFAULT_HARDFORK = this._chainParams.defaultHardfork ?? 'istanbul'
this._hardfork = this.DEFAULT_HARDFORK
if (opts.supportedHardforks) {
this._supportedHardforks = opts.supportedHardforks
}
if (opts.hardfork) {
this.setHardfork(opts.hardfork)
}
if (opts.eips) {
this.setEIPs(opts.eips)
}
}
/**
* Sets the chain
* @param chain String ('mainnet') or Number (1) chain
* representation. Or, a Dictionary of chain parameters for a private network.
* @returns The dictionary with parameters set as chain
*/
setChain(chain: string | number | object): any {
if (typeof chain === 'number' || typeof chain === 'string') {
this._chainParams = Common._getChainParams(chain, this._customChains)
} else if (typeof chain === 'object') {
if (this._customChains.length > 0) {
throw new Error(
'Chain must be a string or number when initialized with customChains passed in'
)
}
const required = ['networkId', 'genesis', 'hardforks', 'bootstrapNodes']
for (const param of required) {
if ((<any>chain)[param] === undefined) {
throw new Error(`Missing required chain parameter: ${param}`)
}
}
this._chainParams = chain as Chain
} else {
throw new Error('Wrong input format')
}
return this._chainParams
}
/**
* Sets the hardfork to get params for
* @param hardfork String identifier (e.g. 'byzantium')
*/
setHardfork(hardfork: string): void {
if (!this._isSupportedHardfork(hardfork)) {
throw new Error(`Hardfork ${hardfork} not set as supported in supportedHardforks`)
}
let changed = false
for (const hfChanges of HARDFORK_CHANGES) {
if (hfChanges[0] === hardfork) {
this._hardfork = hardfork
changed = true
}
}
if (!changed) {
throw new Error(`Hardfork with name ${hardfork} not supported`)
}
}
/**
* Returns the hardfork based on the block number provided
* @param blockNumber
* @returns The name of the HF
*/
getHardforkByBlockNumber(blockNumber: number): string {
let hardfork = 'chainstart'
for (const hf of this.hardforks()) {
const hardforkBlock = hf.block
// Skip comparison for not applied HFs
if (hardforkBlock === null) {
continue
}
if (blockNumber >= hardforkBlock) {
hardfork = hf.name
}
}
return hardfork
}
/**
* Sets a new hardfork based on the block number provided
* @param blockNumber
* @returns The name of the HF set
*/
setHardforkByBlockNumber(blockNumber: number): string {
const hardfork = this.getHardforkByBlockNumber(blockNumber)
this.setHardfork(hardfork)
return hardfork
}
/**
* Internal helper function to choose between hardfork set and hardfork provided as param
* @param hardfork Hardfork given to function as a parameter
* @returns Hardfork chosen to be used
*/
_chooseHardfork(hardfork?: string | null, onlySupported: boolean = true): string {
if (!hardfork) {
hardfork = this._hardfork
} else if (onlySupported && !this._isSupportedHardfork(hardfork)) {
throw new Error(`Hardfork ${hardfork} not set as supported in supportedHardforks`)
}
return hardfork
}
/**
* Internal helper function, returns the params for the given hardfork for the chain set
* @param hardfork Hardfork name
* @returns Dictionary with hardfork params
*/
_getHardfork(hardfork: string): any {
const hfs = this.hardforks()
for (const hf of hfs) {
if (hf['name'] === hardfork) return hf
}
throw new Error(`Hardfork ${hardfork} not defined for chain ${this.chainName()}`)
}
/**
* Internal helper function to check if a hardfork is set to be supported by the library
* @param hardfork Hardfork name
* @returns True if hardfork is supported
*/
_isSupportedHardfork(hardfork: string | null): boolean {
if (this._supportedHardforks.length > 0) {
for (const supportedHf of this._supportedHardforks) {
if (hardfork === supportedHf) return true
}
} else {
return true
}
return false
}
/**
* Sets the active EIPs
* @param eips
*/
setEIPs(eips: number[] = []) {
for (const eip of eips) {
if (!(eip in EIPs)) {
throw new Error(`${eip} not supported`)
}
const minHF = this.gteHardfork(EIPs[eip]['minimumHardfork'])
if (!minHF) {
throw new Error(
`${eip} cannot be activated on hardfork ${this.hardfork()}, minimumHardfork: ${minHF}`
)
}
}
this._eips = eips
}
/**
* Returns a parameter for the current chain setup
*
* If the parameter is present in an EIP, the EIP always takes precendence.
* Otherwise the parameter if taken from the latest applied HF with
* a change on the respective parameter.
*
* @param topic Parameter topic ('gasConfig', 'gasPrices', 'vm', 'pow')
* @param name Parameter name (e.g. 'minGasLimit' for 'gasConfig' topic)
* @returns The value requested or `null` if not found
*/
param(topic: string, name: string): any {
// TODO: consider the case that different active EIPs
// can change the same parameter
let value = null
for (const eip of this._eips) {
value = this.paramByEIP(topic, name, eip)
if (value !== null) {
return value
}
}
return this.paramByHardfork(topic, name, this._hardfork)
}
/**
* Returns the parameter corresponding to a hardfork
* @param topic Parameter topic ('gasConfig', 'gasPrices', 'vm', 'pow')
* @param name Parameter name (e.g. 'minGasLimit' for 'gasConfig' topic)
* @param hardfork Hardfork name
* @returns The value requested or `null` if not found
*/
paramByHardfork(topic: string, name: string, hardfork: string): any {
hardfork = this._chooseHardfork(hardfork)
let value = null
for (const hfChanges of HARDFORK_CHANGES) {
// EIP-referencing HF file (e.g. berlin.json)
if (hfChanges[1].hasOwnProperty('eips')) { // eslint-disable-line
const hfEIPs = hfChanges[1]['eips']
for (const eip of hfEIPs) {
const valueEIP = this.paramByEIP(topic, name, eip)
value = valueEIP !== null ? valueEIP : value
}
// Paramater-inlining HF file (e.g. istanbul.json)
} else {
if (!hfChanges[1][topic]) {
throw new Error(`Topic ${topic} not defined`)
}
if (hfChanges[1][topic][name] !== undefined) {
value = hfChanges[1][topic][name].v
}
}
if (hfChanges[0] === hardfork) break
}
return value
}
/**
* Returns a parameter corresponding to an EIP
* @param topic Parameter topic ('gasConfig', 'gasPrices', 'vm', 'pow')
* @param name Parameter name (e.g. 'minGasLimit' for 'gasConfig' topic)
* @param eip Number of the EIP
* @returns The value requested or `null` if not found
*/
paramByEIP(topic: string, name: string, eip: number): any {
if (!(eip in EIPs)) {
throw new Error(`${eip} not supported`)
}
const eipParams = EIPs[eip]
if (!(topic in eipParams)) {
throw new Error(`Topic ${topic} not defined`)
}
if (eipParams[topic][name] === undefined) {
return null
}
const value = eipParams[topic][name].v
return value
}
/**
* Returns a parameter for the hardfork active on block number
* @param topic Parameter topic
* @param name Parameter name
* @param blockNumber Block number
*/
paramByBlock(topic: string, name: string, blockNumber: number): any {
const activeHfs = this.activeHardforks(blockNumber)
const hardfork = activeHfs[activeHfs.length - 1]['name']
return this.paramByHardfork(topic, name, hardfork)
}
/**
* Checks if set or provided hardfork is active on block number
* @param hardfork Hardfork name or null (for HF set)
* @param blockNumber
* @param opts Hardfork options (onlyActive unused)
* @returns True if HF is active on block number
*/
hardforkIsActiveOnBlock(
hardfork: string | null,
blockNumber: number,
opts?: hardforkOptions
): boolean {
opts = opts !== undefined ? opts : {}
const onlySupported = opts.onlySupported === undefined ? false : opts.onlySupported
hardfork = this._chooseHardfork(hardfork, onlySupported)
const hfBlock = this.hardforkBlock(hardfork)
if (hfBlock !== null && blockNumber >= hfBlock) return true
return false
}
/**
* Alias to hardforkIsActiveOnBlock when hardfork is set
* @param blockNumber
* @param opts Hardfork options (onlyActive unused)
* @returns True if HF is active on block number
*/
activeOnBlock(blockNumber: number, opts?: hardforkOptions): boolean {
return this.hardforkIsActiveOnBlock(null, blockNumber, opts)
}
/**
* Sequence based check if given or set HF1 is greater than or equal HF2
* @param hardfork1 Hardfork name or null (if set)
* @param hardfork2 Hardfork name
* @param opts Hardfork options
* @returns True if HF1 gte HF2
*/
hardforkGteHardfork(
hardfork1: string | null,
hardfork2: string,
opts?: hardforkOptions
): boolean {
opts = opts !== undefined ? opts : {}
const onlyActive = opts.onlyActive === undefined ? false : opts.onlyActive
hardfork1 = this._chooseHardfork(hardfork1, opts.onlySupported)
let hardforks
if (onlyActive) {
hardforks = this.activeHardforks(null, opts)
} else {
hardforks = this.hardforks()
}
let posHf1 = -1,
posHf2 = -1
let index = 0
for (const hf of hardforks) {
if (hf['name'] === hardfork1) posHf1 = index
if (hf['name'] === hardfork2) posHf2 = index
index += 1
}
return posHf1 >= posHf2
}
/**
* Alias to hardforkGteHardfork when hardfork is set
* @param hardfork Hardfork name
* @param opts Hardfork options
* @returns True if hardfork set is greater than hardfork provided
*/
gteHardfork(hardfork: string, opts?: hardforkOptions): boolean {
return this.hardforkGteHardfork(null, hardfork, opts)
}
/**
* Checks if given or set hardfork is active on the chain
* @param hardfork Hardfork name, optional if HF set
* @param opts Hardfork options (onlyActive unused)
* @returns True if hardfork is active on the chain
*/
hardforkIsActiveOnChain(hardfork?: string | null, opts?: hardforkOptions): boolean {
opts = opts !== undefined ? opts : {}
const onlySupported = opts.onlySupported === undefined ? false : opts.onlySupported
hardfork = this._chooseHardfork(hardfork, onlySupported)
for (const hf of this.hardforks()) {
if (hf['name'] === hardfork && hf['block'] !== null) return true
}
return false
}
/**
* Returns the active hardfork switches for the current chain
* @param blockNumber up to block if provided, otherwise for the whole chain
* @param opts Hardfork options (onlyActive unused)
* @return Array with hardfork arrays
*/
activeHardforks(blockNumber?: number | null, opts?: hardforkOptions): Array<any> {
opts = opts !== undefined ? opts : {}
const activeHardforks = []
const hfs = this.hardforks()
for (const hf of hfs) {
if (hf['block'] === null) continue
if (blockNumber !== undefined && blockNumber !== null && blockNumber < hf['block']) break
if (opts.onlySupported && !this._isSupportedHardfork(hf['name'])) continue
activeHardforks.push(hf)
}
return activeHardforks
}
/**
* Returns the latest active hardfork name for chain or block or throws if unavailable
* @param blockNumber up to block if provided, otherwise for the whole chain
* @param opts Hardfork options (onlyActive unused)
* @return Hardfork name
*/
activeHardfork(blockNumber?: number | null, opts?: hardforkOptions): string {
opts = opts !== undefined ? opts : {}
const activeHardforks = this.activeHardforks(blockNumber, opts)
if (activeHardforks.length > 0) {
return activeHardforks[activeHardforks.length - 1]['name']
} else {
throw new Error(`No (supported) active hardfork found`)
}
}
/**
* Returns the hardfork change block for hardfork provided or set
* @param hardfork Hardfork name, optional if HF set
* @returns Block number
*/
hardforkBlock(hardfork?: string): number {
hardfork = this._chooseHardfork(hardfork, false)
return this._getHardfork(hardfork)['block']
}
/**
* True if block number provided is the hardfork (given or set) change block
* @param blockNumber Number of the block to check
* @param hardfork Hardfork name, optional if HF set
* @returns True if blockNumber is HF block
*/
isHardforkBlock(blockNumber: number, hardfork?: string): boolean {
hardfork = this._chooseHardfork(hardfork, false)
return this.hardforkBlock(hardfork) === blockNumber
}
/**
* Returns the change block for the next hardfork after the hardfork provided or set
* @param hardfork Hardfork name, optional if HF set
* @returns Block number or null if not available
*/
nextHardforkBlock(hardfork?: string): number | null {
hardfork = this._chooseHardfork(hardfork, false)
const hfBlock = this.hardforkBlock(hardfork)
// Next fork block number or null if none available
// Logic: if accumulator is still null and on the first occurence of
// a block greater than the current hfBlock set the accumulator,
// pass on the accumulator as the final result from this time on
const nextHfBlock = this.hardforks().reduce((acc: number, hf: any) => {
return hf.block > hfBlock && acc === null ? hf.block : acc
}, null)
return nextHfBlock
}
/**
* True if block number provided is the hardfork change block following the hardfork given or set
* @param blockNumber Number of the block to check
* @param hardfork Hardfork name, optional if HF set
* @returns True if blockNumber is HF block
*/
isNextHardforkBlock(blockNumber: number, hardfork?: string): boolean {
hardfork = this._chooseHardfork(hardfork, false)
return this.nextHardforkBlock(hardfork) === blockNumber
}
/**
* Internal helper function to calculate a fork hash
* @param hardfork Hardfork name
* @returns Fork hash as hex string
*/
_calcForkHash(hardfork: string) {
const genesis = Buffer.from(this.genesis().hash.substr(2), 'hex')
let hfBuffer = Buffer.alloc(0)
let prevBlock = 0
for (const hf of this.hardforks()) {
const block = hf.block
// Skip for chainstart (0), not applied HFs (null) and
// when already applied on same block number HFs
if (block !== 0 && block !== null && block !== prevBlock) {
const hfBlockBuffer = Buffer.from(block.toString(16).padStart(16, '0'), 'hex')
hfBuffer = Buffer.concat([hfBuffer, hfBlockBuffer])
}
if (hf.name === hardfork) break
prevBlock = block
}
const inputBuffer = Buffer.concat([genesis, hfBuffer])
// CRC32 delivers result as signed (negative) 32-bit integer,
// convert to hex string
const forkhash = new Number(crc32Buffer(inputBuffer) >>> 0).toString(16)
return `0x${forkhash}`
}
/**
* Returns an eth/64 compliant fork hash (EIP-2124)
* @param hardfork Hardfork name, optional if HF set
*/
forkHash(hardfork?: string) {
hardfork = this._chooseHardfork(hardfork, false)
const data = this._getHardfork(hardfork)
if (data['block'] === null) {
const msg = 'No fork hash calculation possible for non-applied or future hardfork'
throw new Error(msg)
}
if (data['forkHash'] !== undefined) {
return data['forkHash']
}
return this._calcForkHash(hardfork)
}
/**
*
* @param forkHash Fork hash as a hex string
* @returns Array with hardfork data (name, block, forkHash)
*/
hardforkForForkHash(forkHash: string): any | null {
const resArray = this.hardforks().filter((hf: any) => {
return hf.forkHash === forkHash
})
return resArray.length === 1 ? resArray[0] : null
}
/**
* Returns the Genesis parameters of current chain
* @returns Genesis dictionary
*/
genesis(): any {
return (<any>this._chainParams)['genesis']
}
/**
* Returns the hardforks for current chain
* @returns {Array} Array with arrays of hardforks
*/
hardforks(): any {
return (<any>this._chainParams)['hardforks']
}
/**
* Returns bootstrap nodes for the current chain
* @returns {Dictionary} Dict with bootstrap nodes
*/
bootstrapNodes(): any {
return (<any>this._chainParams)['bootstrapNodes']
}
/**
* Returns the hardfork set
* @returns Hardfork name
*/
hardfork(): string {
return this._hardfork
}
/**
* Returns the Id of current chain
* @returns chain Id
*/
chainId(): number {
return <number>(<any>this._chainParams)['chainId']
}
/**
* Returns the name of current chain
* @returns chain name (lower case)
*/
chainName(): string {
return (<any>this._chainParams)['name']
}
/**
* Returns the Id of current network
* @returns network Id
*/
networkId(): number {
return (<any>this._chainParams)['networkId']
}
/**
* Returns the active EIPs
* @returns List of EIPs
*/
eips(): number[] {
return this._eips
}
/**
* Returns the consensus type of the network
* Possible values: "pow"|"poa"
*/
consensusType(): string {
return (<any>this._chainParams)['consensus']['type']
}
/**
* Returns the concrete consensus implementation
* algorithm or protocol for the network
* e.g. "ethash" for "pow" consensus type or
* "clique" for "poa" consensus type
*/
consensusAlgorithm(): string {
return (<any>this._chainParams)['consensus']['algorithm']
}
/**
* Returns a dictionary with consensus configuration
* parameters based on the consensus algorithm
*
* Expected returns (parameters must be present in
* the respective chain json files):
*
* ethash: -
* clique: period, epoch
* aura: -
*/
consensusConfig(): any {
return (<any>this._chainParams)['consensus'][this.consensusAlgorithm()]
}
}