@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.
- package/README.md +269 -0
- package/dist/cjs/blockchain.d.ts +372 -0
- package/dist/cjs/blockchain.d.ts.map +1 -0
- package/dist/cjs/blockchain.js +1105 -0
- package/dist/cjs/blockchain.js.map +1 -0
- package/dist/cjs/consensus/casper.d.ts +16 -0
- package/dist/cjs/consensus/casper.d.ts.map +1 -0
- package/dist/cjs/consensus/casper.js +28 -0
- package/dist/cjs/consensus/casper.js.map +1 -0
- package/dist/cjs/consensus/clique.d.ts +164 -0
- package/dist/cjs/consensus/clique.d.ts.map +1 -0
- package/dist/cjs/consensus/clique.js +520 -0
- package/dist/cjs/consensus/clique.js.map +1 -0
- package/dist/cjs/consensus/ethash.d.ts +29 -0
- package/dist/cjs/consensus/ethash.d.ts.map +1 -0
- package/dist/cjs/consensus/ethash.js +48 -0
- package/dist/cjs/consensus/ethash.js.map +1 -0
- package/dist/cjs/consensus/index.d.ts +5 -0
- package/dist/cjs/consensus/index.d.ts.map +1 -0
- package/dist/cjs/consensus/index.js +10 -0
- package/dist/cjs/consensus/index.js.map +1 -0
- package/dist/cjs/constructors.d.ts +13 -0
- package/dist/cjs/constructors.d.ts.map +1 -0
- package/dist/cjs/constructors.js +80 -0
- package/dist/cjs/constructors.js.map +1 -0
- package/dist/cjs/db/cache.d.ts +17 -0
- package/dist/cjs/db/cache.d.ts.map +1 -0
- package/dist/cjs/db/cache.js +38 -0
- package/dist/cjs/db/cache.js.map +1 -0
- package/dist/cjs/db/constants.d.ts +23 -0
- package/dist/cjs/db/constants.d.ts.map +1 -0
- package/dist/cjs/db/constants.js +54 -0
- package/dist/cjs/db/constants.js.map +1 -0
- package/dist/cjs/db/helpers.d.ts +9 -0
- package/dist/cjs/db/helpers.d.ts.map +1 -0
- package/dist/cjs/db/helpers.js +68 -0
- package/dist/cjs/db/helpers.js.map +1 -0
- package/dist/cjs/db/manager.d.ts +78 -0
- package/dist/cjs/db/manager.d.ts.map +1 -0
- package/dist/cjs/db/manager.js +203 -0
- package/dist/cjs/db/manager.js.map +1 -0
- package/dist/cjs/db/operation.d.ts +45 -0
- package/dist/cjs/db/operation.d.ts.map +1 -0
- package/dist/cjs/db/operation.js +110 -0
- package/dist/cjs/db/operation.js.map +1 -0
- package/dist/cjs/helpers.d.ts +19 -0
- package/dist/cjs/helpers.d.ts.map +1 -0
- package/dist/cjs/helpers.js +34 -0
- package/dist/cjs/helpers.js.map +1 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +33 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/types.d.ts +219 -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/blockchain.d.ts +372 -0
- package/dist/esm/blockchain.d.ts.map +1 -0
- package/dist/esm/blockchain.js +1101 -0
- package/dist/esm/blockchain.js.map +1 -0
- package/dist/esm/consensus/casper.d.ts +16 -0
- package/dist/esm/consensus/casper.d.ts.map +1 -0
- package/dist/esm/consensus/casper.js +24 -0
- package/dist/esm/consensus/casper.js.map +1 -0
- package/dist/esm/consensus/clique.d.ts +164 -0
- package/dist/esm/consensus/clique.d.ts.map +1 -0
- package/dist/esm/consensus/clique.js +516 -0
- package/dist/esm/consensus/clique.js.map +1 -0
- package/dist/esm/consensus/ethash.d.ts +29 -0
- package/dist/esm/consensus/ethash.d.ts.map +1 -0
- package/dist/esm/consensus/ethash.js +44 -0
- package/dist/esm/consensus/ethash.js.map +1 -0
- package/dist/esm/consensus/index.d.ts +5 -0
- package/dist/esm/consensus/index.d.ts.map +1 -0
- package/dist/esm/consensus/index.js +5 -0
- package/dist/esm/consensus/index.js.map +1 -0
- package/dist/esm/constructors.d.ts +13 -0
- package/dist/esm/constructors.d.ts.map +1 -0
- package/dist/esm/constructors.js +76 -0
- package/dist/esm/constructors.js.map +1 -0
- package/dist/esm/db/cache.d.ts +17 -0
- package/dist/esm/db/cache.d.ts.map +1 -0
- package/dist/esm/db/cache.js +34 -0
- package/dist/esm/db/cache.js.map +1 -0
- package/dist/esm/db/constants.d.ts +23 -0
- package/dist/esm/db/constants.d.ts.map +1 -0
- package/dist/esm/db/constants.js +46 -0
- package/dist/esm/db/constants.js.map +1 -0
- package/dist/esm/db/helpers.d.ts +9 -0
- package/dist/esm/db/helpers.d.ts.map +1 -0
- package/dist/esm/db/helpers.js +61 -0
- package/dist/esm/db/helpers.js.map +1 -0
- package/dist/esm/db/manager.d.ts +78 -0
- package/dist/esm/db/manager.d.ts.map +1 -0
- package/dist/esm/db/manager.js +199 -0
- package/dist/esm/db/manager.js.map +1 -0
- package/dist/esm/db/operation.d.ts +45 -0
- package/dist/esm/db/operation.d.ts.map +1 -0
- package/dist/esm/db/operation.js +106 -0
- package/dist/esm/db/operation.js.map +1 -0
- package/dist/esm/helpers.d.ts +19 -0
- package/dist/esm/helpers.d.ts.map +1 -0
- package/dist/esm/helpers.js +30 -0
- package/dist/esm/helpers.js.map +1 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/types.d.ts +219 -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/blockchain.ts +1353 -0
- package/src/consensus/casper.ts +33 -0
- package/src/consensus/clique.ts +633 -0
- package/src/consensus/ethash.ts +69 -0
- package/src/consensus/index.ts +5 -0
- package/src/constructors.ts +119 -0
- package/src/db/cache.ts +39 -0
- package/src/db/constants.ts +73 -0
- package/src/db/helpers.ts +81 -0
- package/src/db/manager.ts +243 -0
- package/src/db/operation.ts +152 -0
- package/src/helpers.ts +37 -0
- package/src/index.ts +12 -0
- package/src/types.ts +256 -0
|
@@ -0,0 +1,1105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Blockchain = void 0;
|
|
4
|
+
const block_1 = require("@feelyourprotocol/block");
|
|
5
|
+
const common_1 = require("@feelyourprotocol/common");
|
|
6
|
+
const util_1 = require("@feelyourprotocol/util");
|
|
7
|
+
const debug_1 = require("debug");
|
|
8
|
+
const eventemitter3_1 = require("eventemitter3");
|
|
9
|
+
const casper_ts_1 = require("./consensus/casper.js");
|
|
10
|
+
const helpers_ts_1 = require("./db/helpers.js");
|
|
11
|
+
const manager_ts_1 = require("./db/manager.js");
|
|
12
|
+
const operation_ts_1 = require("./db/operation.js");
|
|
13
|
+
/**
|
|
14
|
+
* Blockchain implementation to create and maintain a valid canonical chain
|
|
15
|
+
* of block headers or blocks with support for reorgs and the ability to provide
|
|
16
|
+
* custom DB backends.
|
|
17
|
+
*
|
|
18
|
+
* By default consensus validation is not provided since with the switch to
|
|
19
|
+
* Proof-of-Stake consensus is validated by the Ethereum consensus layer.
|
|
20
|
+
* If consensus validation is desired for Ethash or Clique blockchains the
|
|
21
|
+
* optional `consensusDict` option can be used to pass in validation objects.
|
|
22
|
+
*
|
|
23
|
+
* A Blockchain object can be created with the constructor method:
|
|
24
|
+
*
|
|
25
|
+
* - {@link createBlockchain}
|
|
26
|
+
*/
|
|
27
|
+
class Blockchain {
|
|
28
|
+
/**
|
|
29
|
+
* Creates new Blockchain object.
|
|
30
|
+
*
|
|
31
|
+
* @deprecated The direct usage of this constructor is discouraged since
|
|
32
|
+
* non-finalized async initialization might lead to side effects. Please
|
|
33
|
+
* use the async {@link createBlockchain} constructor instead (same API).
|
|
34
|
+
*
|
|
35
|
+
* @param opts An object with the options that this constructor takes. See
|
|
36
|
+
* {@link BlockchainOptions}.
|
|
37
|
+
*/
|
|
38
|
+
constructor(opts = {}) {
|
|
39
|
+
/**
|
|
40
|
+
* This is used to track which canonical blocks are deleted. After a method calls
|
|
41
|
+
* `_deleteCanonicalChainReferences`, if this array has any items, the
|
|
42
|
+
* `deletedCanonicalBlocks` event is emitted with the array as argument.
|
|
43
|
+
*/
|
|
44
|
+
this._deletedBlocks = [];
|
|
45
|
+
this.DEBUG = (0, util_1.isDebugEnabled)('ethjs');
|
|
46
|
+
this._debug = (0, debug_1.default)('blockchain:#');
|
|
47
|
+
if (opts.common) {
|
|
48
|
+
this.common = opts.common;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const DEFAULT_CHAIN = common_1.Mainnet;
|
|
52
|
+
const DEFAULT_HARDFORK = common_1.Hardfork.Chainstart;
|
|
53
|
+
this.common = new common_1.Common({
|
|
54
|
+
chain: DEFAULT_CHAIN,
|
|
55
|
+
hardfork: DEFAULT_HARDFORK,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
this._hardforkByHeadBlockNumber = opts.hardforkByHeadBlockNumber ?? false;
|
|
59
|
+
this._validateBlocks = opts.validateBlocks ?? true;
|
|
60
|
+
this._validateConsensus = opts.validateConsensus ?? false;
|
|
61
|
+
this._customGenesisState = opts.genesisState;
|
|
62
|
+
this.db = opts.db ?? new util_1.MapDB();
|
|
63
|
+
this.dbManager = new manager_ts_1.DBManager(this.db, this.common);
|
|
64
|
+
this.events = new eventemitter3_1.EventEmitter();
|
|
65
|
+
this._consensusDict = {};
|
|
66
|
+
this._consensusDict[common_1.ConsensusAlgorithm.Casper] = new casper_ts_1.CasperConsensus();
|
|
67
|
+
if (opts.consensusDict !== undefined) {
|
|
68
|
+
this._consensusDict = { ...this._consensusDict, ...opts.consensusDict };
|
|
69
|
+
}
|
|
70
|
+
this._consensusCheck();
|
|
71
|
+
this._heads = {};
|
|
72
|
+
this._lock = new util_1.Lock();
|
|
73
|
+
if (opts.genesisBlock && !opts.genesisBlock.isGenesis()) {
|
|
74
|
+
throw 'supplied block is not a genesis block';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
_consensusCheck() {
|
|
78
|
+
if (this._validateConsensus && this.consensus === undefined) {
|
|
79
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`Consensus object for ${this.common.consensusAlgorithm()} must be passed (see consensusDict option) if consensus validation is activated`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Returns an eventual consensus object matching the current consensus algorithm from Common
|
|
84
|
+
* or undefined if non available
|
|
85
|
+
*/
|
|
86
|
+
get consensus() {
|
|
87
|
+
return this._consensusDict[this.common.consensusAlgorithm()];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Returns a deep copy of this {@link Blockchain} instance.
|
|
91
|
+
*
|
|
92
|
+
* Note: this does not make a copy of the underlying db
|
|
93
|
+
* since it is unknown if the source is on disk or in memory.
|
|
94
|
+
* This should not be a significant issue in most usage since
|
|
95
|
+
* the queries will only reflect the instance's known data.
|
|
96
|
+
* If you would like this copied blockchain to use another db
|
|
97
|
+
* set the {@link db} of this returned instance to a copy of
|
|
98
|
+
* the original.
|
|
99
|
+
*/
|
|
100
|
+
shallowCopy() {
|
|
101
|
+
const copiedBlockchain = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this));
|
|
102
|
+
copiedBlockchain.common = this.common.copy();
|
|
103
|
+
return copiedBlockchain;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Run a function after acquiring a lock. It is implied that we have already
|
|
107
|
+
* initialized the module (or we are calling this from the init function, like
|
|
108
|
+
* `_setCanonicalGenesisBlock`)
|
|
109
|
+
* @param action - function to run after acquiring a lock
|
|
110
|
+
* @hidden
|
|
111
|
+
*/
|
|
112
|
+
async runWithLock(action) {
|
|
113
|
+
try {
|
|
114
|
+
await this._lock.acquire();
|
|
115
|
+
const value = await action();
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
this._lock.release();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Returns the specified iterator head.
|
|
124
|
+
*
|
|
125
|
+
* This function replaces the old Blockchain.getHead() method. Note that
|
|
126
|
+
* the function deviates from the old behavior and returns the
|
|
127
|
+
* genesis hash instead of the current head block if an iterator
|
|
128
|
+
* has not been run. This matches the behavior of {@link Blockchain.iterator}.
|
|
129
|
+
*
|
|
130
|
+
* @param name - Optional name of the iterator head (default: 'vm')
|
|
131
|
+
*/
|
|
132
|
+
async getIteratorHead(name = 'vm') {
|
|
133
|
+
return this.runWithLock(async () => {
|
|
134
|
+
return (await this.getHead(name, false));
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* This method differs from `getIteratorHead`. If the head is not found, it returns `undefined`.
|
|
139
|
+
* @param name - Optional name of the iterator head (default: 'vm')
|
|
140
|
+
* @returns
|
|
141
|
+
*/
|
|
142
|
+
async getIteratorHeadSafe(name = 'vm') {
|
|
143
|
+
return this.runWithLock(async () => {
|
|
144
|
+
return this.getHead(name, true);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
async getHead(name, returnUndefinedIfNotSet = false) {
|
|
148
|
+
const headHash = this._heads[name];
|
|
149
|
+
if (headHash === undefined && returnUndefinedIfNotSet) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const hash = this._heads[name] ?? this.genesisBlock.hash();
|
|
153
|
+
const block = await this.getBlock(hash);
|
|
154
|
+
return block;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Returns the latest header in the canonical chain.
|
|
158
|
+
*/
|
|
159
|
+
async getCanonicalHeadHeader() {
|
|
160
|
+
return this.runWithLock(async () => {
|
|
161
|
+
if (!this._headHeaderHash)
|
|
162
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)('No head header set');
|
|
163
|
+
const header = await this._getHeader(this._headHeaderHash);
|
|
164
|
+
return header;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Returns the latest full block in the canonical chain.
|
|
169
|
+
*/
|
|
170
|
+
async getCanonicalHeadBlock() {
|
|
171
|
+
return this.runWithLock(async () => {
|
|
172
|
+
if (!this._headBlockHash)
|
|
173
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)('No head block set');
|
|
174
|
+
return this.getBlock(this._headBlockHash);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Adds blocks to the blockchain.
|
|
179
|
+
*
|
|
180
|
+
* If an invalid block is met the function will throw, blocks before will
|
|
181
|
+
* nevertheless remain in the DB. If any of the saved blocks has a higher
|
|
182
|
+
* total difficulty than the current max total difficulty the canonical
|
|
183
|
+
* chain is rebuilt and any stale heads/hashes are overwritten.
|
|
184
|
+
* @param blocks - The blocks to be added to the blockchain
|
|
185
|
+
*/
|
|
186
|
+
async putBlocks(blocks) {
|
|
187
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
188
|
+
await this.putBlock(blocks[i]);
|
|
189
|
+
}
|
|
190
|
+
this.DEBUG && this._debug(`put ${blocks.length} blocks`);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Adds a block to the blockchain.
|
|
194
|
+
*
|
|
195
|
+
* If the block is valid and has a higher total difficulty than the current
|
|
196
|
+
* max total difficulty, the canonical chain is rebuilt and any stale
|
|
197
|
+
* heads/hashes are overwritten.
|
|
198
|
+
* @param block - The block to be added to the blockchain
|
|
199
|
+
*/
|
|
200
|
+
async putBlock(block) {
|
|
201
|
+
await this._putBlockOrHeader(block);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Adds many headers to the blockchain.
|
|
205
|
+
*
|
|
206
|
+
* If an invalid header is met the function will throw, headers before will
|
|
207
|
+
* nevertheless remain in the DB. If any of the saved headers has a higher
|
|
208
|
+
* total difficulty than the current max total difficulty the canonical
|
|
209
|
+
* chain is rebuilt and any stale heads/hashes are overwritten.
|
|
210
|
+
* @param headers - The headers to be added to the blockchain
|
|
211
|
+
*/
|
|
212
|
+
async putHeaders(headers) {
|
|
213
|
+
for (let i = 0; i < headers.length; i++) {
|
|
214
|
+
await this.putHeader(headers[i]);
|
|
215
|
+
}
|
|
216
|
+
this.DEBUG && this._debug(`put ${headers.length} headers`);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Adds a header to the blockchain.
|
|
220
|
+
*
|
|
221
|
+
* If this header is valid and it has a higher total difficulty than the current
|
|
222
|
+
* max total difficulty, the canonical chain is rebuilt and any stale
|
|
223
|
+
* heads/hashes are overwritten.
|
|
224
|
+
* @param header - The header to be added to the blockchain
|
|
225
|
+
*/
|
|
226
|
+
async putHeader(header) {
|
|
227
|
+
await this._putBlockOrHeader(header);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Resets the canonical chain to canonicalHead number
|
|
231
|
+
*
|
|
232
|
+
* This updates the head hashes (if affected) to the hash corresponding to
|
|
233
|
+
* canonicalHead and cleans up canonical references greater than canonicalHead
|
|
234
|
+
* @param canonicalHead - The number to which chain should be reset to
|
|
235
|
+
*/
|
|
236
|
+
async resetCanonicalHead(canonicalHead) {
|
|
237
|
+
let hash;
|
|
238
|
+
let canonicalHeadHash;
|
|
239
|
+
if (this.DEBUG) {
|
|
240
|
+
canonicalHeadHash = (await this.getCanonicalHeadHeader()).hash();
|
|
241
|
+
}
|
|
242
|
+
await this.runWithLock(async () => {
|
|
243
|
+
hash = await this.dbManager.numberToHash(canonicalHead);
|
|
244
|
+
if (hash === undefined) {
|
|
245
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`no block for ${canonicalHead} found in DB`);
|
|
246
|
+
}
|
|
247
|
+
const header = await this._getHeader(hash, canonicalHead);
|
|
248
|
+
const dbOps = [];
|
|
249
|
+
await this._deleteCanonicalChainReferences(canonicalHead + util_1.BIGINT_1, hash, dbOps);
|
|
250
|
+
const ops = dbOps.concat(this._saveHeadOps());
|
|
251
|
+
await this.dbManager.batch(ops);
|
|
252
|
+
await this.checkAndTransitionHardForkByNumber(canonicalHead, header.timestamp);
|
|
253
|
+
});
|
|
254
|
+
if (this._deletedBlocks.length > 0) {
|
|
255
|
+
this.events.emit('deletedCanonicalBlocks', this._deletedBlocks);
|
|
256
|
+
for (const block of this._deletedBlocks)
|
|
257
|
+
this.DEBUG &&
|
|
258
|
+
this._debug(`deleted block along head reset: number ${block.header.number} hash ${(0, util_1.bytesToHex)(block.hash())}`);
|
|
259
|
+
this.DEBUG &&
|
|
260
|
+
this._debug(`Canonical head set from ${(0, util_1.bytesToHex)(canonicalHeadHash)} to ${(0, util_1.bytesToHex)(hash)} (number ${(0, util_1.bigIntToHex)(canonicalHead)})`);
|
|
261
|
+
this._deletedBlocks = [];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Entrypoint for putting any block or block header. Verifies this block,
|
|
266
|
+
* checks the total TD: if this TD is higher than the current highest TD, we
|
|
267
|
+
* have thus found a new canonical block and have to rewrite the canonical
|
|
268
|
+
* chain. This also updates the head block hashes. If any of the older known
|
|
269
|
+
* canonical chains just became stale, then we also reset every _heads header
|
|
270
|
+
* which points to a stale header to the last verified header which was in the
|
|
271
|
+
* old canonical chain, but also in the new canonical chain. This thus rolls
|
|
272
|
+
* back these headers so that these can be updated to the "new" canonical
|
|
273
|
+
* header using the iterator method.
|
|
274
|
+
* @hidden
|
|
275
|
+
*/
|
|
276
|
+
async _putBlockOrHeader(item) {
|
|
277
|
+
await this.runWithLock(async () => {
|
|
278
|
+
// Save the current sane state in case _putBlockOrHeader midway with some
|
|
279
|
+
// dirty changes in head trackers
|
|
280
|
+
const oldHeads = Object.assign({}, this._heads);
|
|
281
|
+
const oldHeadHeaderHash = this._headHeaderHash;
|
|
282
|
+
const oldHeadBlockHash = this._headBlockHash;
|
|
283
|
+
try {
|
|
284
|
+
const block = item instanceof block_1.BlockHeader
|
|
285
|
+
? new block_1.Block(item, undefined, undefined, undefined, { common: item.common })
|
|
286
|
+
: item;
|
|
287
|
+
const isGenesis = block.isGenesis();
|
|
288
|
+
// we cannot overwrite the Genesis block after initializing the Blockchain
|
|
289
|
+
if (isGenesis) {
|
|
290
|
+
if ((0, util_1.equalsBytes)(this.genesisBlock.hash(), block.hash())) {
|
|
291
|
+
// Try to re-put the existing genesis block, accept this
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)('Cannot put a different genesis block than current blockchain genesis: create a new Blockchain');
|
|
295
|
+
}
|
|
296
|
+
const { header } = block;
|
|
297
|
+
const blockHash = header.hash();
|
|
298
|
+
const blockNumber = header.number;
|
|
299
|
+
let td = header.difficulty;
|
|
300
|
+
const currentTd = { header: util_1.BIGINT_0, block: util_1.BIGINT_0 };
|
|
301
|
+
let dbOps = [];
|
|
302
|
+
if (block.common.chainId() !== this.common.chainId()) {
|
|
303
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`Chain mismatch while trying to put block or header. Chain ID of block: ${block.common.chainId}, chain ID of blockchain : ${this.common.chainId}`);
|
|
304
|
+
}
|
|
305
|
+
if (this._validateBlocks && !isGenesis && item instanceof block_1.Block) {
|
|
306
|
+
// this calls into `getBlock`, which is why we cannot lock yet
|
|
307
|
+
await this.validateBlock(block);
|
|
308
|
+
}
|
|
309
|
+
if (this._validateConsensus) {
|
|
310
|
+
await this.consensus.validateConsensus(block);
|
|
311
|
+
}
|
|
312
|
+
// set total difficulty in the current context scope
|
|
313
|
+
if (this._headHeaderHash) {
|
|
314
|
+
currentTd.header = await this.getTotalDifficulty(this._headHeaderHash);
|
|
315
|
+
}
|
|
316
|
+
if (this._headBlockHash) {
|
|
317
|
+
currentTd.block = await this.getTotalDifficulty(this._headBlockHash);
|
|
318
|
+
}
|
|
319
|
+
// calculate the total difficulty of the new block
|
|
320
|
+
const parentTd = await this.getParentTD(header);
|
|
321
|
+
if (!block.isGenesis()) {
|
|
322
|
+
td += parentTd;
|
|
323
|
+
}
|
|
324
|
+
// save total difficulty to the database
|
|
325
|
+
dbOps = dbOps.concat((0, helpers_ts_1.DBSetTD)(td, blockNumber, blockHash));
|
|
326
|
+
// save header/block to the database, but save the input not our wrapper block
|
|
327
|
+
dbOps = dbOps.concat((0, helpers_ts_1.DBSetBlockOrHeader)(item));
|
|
328
|
+
let commonAncestor;
|
|
329
|
+
let ancestorHeaders;
|
|
330
|
+
// if total difficulty is higher than current, add it to canonical chain
|
|
331
|
+
if (block.isGenesis() ||
|
|
332
|
+
td > currentTd.header ||
|
|
333
|
+
block.common.consensusType() === common_1.ConsensusType.ProofOfStake) {
|
|
334
|
+
const foundCommon = await this.findCommonAncestor(header);
|
|
335
|
+
commonAncestor = foundCommon.commonAncestor;
|
|
336
|
+
ancestorHeaders = foundCommon.ancestorHeaders;
|
|
337
|
+
this._headHeaderHash = blockHash;
|
|
338
|
+
if (item instanceof block_1.Block) {
|
|
339
|
+
this._headBlockHash = blockHash;
|
|
340
|
+
}
|
|
341
|
+
if (this._hardforkByHeadBlockNumber) {
|
|
342
|
+
await this.checkAndTransitionHardForkByNumber(blockNumber, header.timestamp);
|
|
343
|
+
}
|
|
344
|
+
// delete higher number assignments and overwrite stale canonical chain
|
|
345
|
+
await this._deleteCanonicalChainReferences(blockNumber + util_1.BIGINT_1, blockHash, dbOps);
|
|
346
|
+
// from the current header block, check the blockchain in reverse (i.e.
|
|
347
|
+
// traverse `parentHash`) until `numberToHash` matches the current
|
|
348
|
+
// number/hash in the canonical chain also: overwrite any heads if these
|
|
349
|
+
// heads are stale in `_heads` and `_headBlockHash`
|
|
350
|
+
await this._rebuildCanonical(header, dbOps);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
// the TD is lower than the current highest TD so we will add the block
|
|
354
|
+
// to the DB, but will not mark it as the canonical chain.
|
|
355
|
+
if (td > currentTd.block && item instanceof block_1.Block) {
|
|
356
|
+
this._headBlockHash = blockHash;
|
|
357
|
+
}
|
|
358
|
+
// save hash to number lookup info even if rebuild not needed
|
|
359
|
+
dbOps.push((0, helpers_ts_1.DBSetHashToNumber)(blockHash, blockNumber));
|
|
360
|
+
}
|
|
361
|
+
const ops = dbOps.concat(this._saveHeadOps());
|
|
362
|
+
await this.dbManager.batch(ops);
|
|
363
|
+
await this.consensus?.newBlock(block, commonAncestor, ancestorHeaders);
|
|
364
|
+
this.DEBUG &&
|
|
365
|
+
this._debug(`put block number=${block.header.number} hash=${(0, util_1.bytesToHex)(blockHash)}`);
|
|
366
|
+
}
|
|
367
|
+
catch (e) {
|
|
368
|
+
// restore head to the previously sane state
|
|
369
|
+
this._heads = oldHeads;
|
|
370
|
+
this._headHeaderHash = oldHeadHeaderHash;
|
|
371
|
+
this._headBlockHash = oldHeadBlockHash;
|
|
372
|
+
throw e;
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
if (this._deletedBlocks.length > 0) {
|
|
376
|
+
this.events.emit('deletedCanonicalBlocks', this._deletedBlocks);
|
|
377
|
+
for (const block of this._deletedBlocks)
|
|
378
|
+
this.DEBUG &&
|
|
379
|
+
this._debug(`delete stale canonical block number=${block.header.number} hash=${(0, util_1.bytesToHex)(block.hash())}`);
|
|
380
|
+
this._deletedBlocks = [];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Validates a block header, throwing if invalid. It is being validated against the reported `parentHash`.
|
|
385
|
+
* It verifies the current block against the `parentHash`:
|
|
386
|
+
* - The `parentHash` is part of the blockchain (it is a valid header)
|
|
387
|
+
* - Current block number is parent block number + 1
|
|
388
|
+
* - Current block has a strictly higher timestamp
|
|
389
|
+
* - Additional PoW checks ->
|
|
390
|
+
* - Current block has valid difficulty and gas limit
|
|
391
|
+
* - In case that the header is an uncle header, it should not be too old or young in the chain.
|
|
392
|
+
* - Additional PoA clique checks ->
|
|
393
|
+
* - Checks on coinbase and mixHash
|
|
394
|
+
* - Current block has a timestamp diff greater or equal to PERIOD
|
|
395
|
+
* - Current block has difficulty correctly marked as INTURN or NOTURN
|
|
396
|
+
* @param header - header to be validated
|
|
397
|
+
* @param height - If this is an uncle header, this is the height of the block that is including it
|
|
398
|
+
*/
|
|
399
|
+
async validateHeader(header, height) {
|
|
400
|
+
if (header.isGenesis()) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const parentHeader = await this._getHeader(header.parentHash);
|
|
404
|
+
const { number } = header;
|
|
405
|
+
if (number !== parentHeader.number + util_1.BIGINT_1) {
|
|
406
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`invalid number ${header.errorStr()}`);
|
|
407
|
+
}
|
|
408
|
+
if (header.timestamp <= parentHeader.timestamp) {
|
|
409
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`invalid timestamp ${header.errorStr()}`);
|
|
410
|
+
}
|
|
411
|
+
if (!(header.common.consensusType() === 'pos'))
|
|
412
|
+
await this.consensus?.validateDifficulty(header);
|
|
413
|
+
if (this.common.consensusAlgorithm() === common_1.ConsensusAlgorithm.Clique) {
|
|
414
|
+
const period = this.common.consensusConfig().period;
|
|
415
|
+
// Timestamp diff between blocks is lower than PERIOD (clique)
|
|
416
|
+
if (parentHeader.timestamp + BigInt(period) > header.timestamp) {
|
|
417
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`invalid timestamp diff (lower than period) ${header.errorStr()}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
header.validateGasLimit(parentHeader);
|
|
421
|
+
if (height !== undefined) {
|
|
422
|
+
const dif = height - parentHeader.number;
|
|
423
|
+
if (!(dif < util_1.BIGINT_8 && dif > util_1.BIGINT_1)) {
|
|
424
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`uncle block has a parent that is too old or too young ${header.errorStr()}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// check blockchain dependent EIP1559 values
|
|
428
|
+
if (header.common.isActivatedEIP(1559)) {
|
|
429
|
+
// check if the base fee is correct
|
|
430
|
+
let expectedBaseFee;
|
|
431
|
+
const londonHfBlock = this.common.hardforkBlock(common_1.Hardfork.London);
|
|
432
|
+
const isInitialEIP1559Block = number === londonHfBlock;
|
|
433
|
+
if (isInitialEIP1559Block) {
|
|
434
|
+
expectedBaseFee = header.common.param('initialBaseFee');
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
expectedBaseFee = parentHeader.calcNextBaseFee();
|
|
438
|
+
}
|
|
439
|
+
if (header.baseFeePerGas !== expectedBaseFee) {
|
|
440
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`Invalid block: base fee not correct ${header.errorStr()}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (header.common.isActivatedEIP(4844)) {
|
|
444
|
+
const expectedExcessBlobGas = parentHeader.calcNextExcessBlobGas(header.common);
|
|
445
|
+
if (header.excessBlobGas !== expectedExcessBlobGas) {
|
|
446
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`expected blob gas: ${expectedExcessBlobGas}, got: ${header.excessBlobGas}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (header.common.isActivatedEIP(7685)) {
|
|
450
|
+
if (header.requestsHash === undefined) {
|
|
451
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`requestsHash must be provided when EIP-7685 is active`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Validates a block, by validating the header against the current chain, any uncle headers, and then
|
|
457
|
+
* whether the block is internally consistent
|
|
458
|
+
* @param block block to be validated
|
|
459
|
+
*/
|
|
460
|
+
async validateBlock(block) {
|
|
461
|
+
await this.validateHeader(block.header);
|
|
462
|
+
await this._validateUncleHeaders(block);
|
|
463
|
+
await block.validateData(false);
|
|
464
|
+
// TODO: Rethink how validateHeader vs validateBlobTransactions works since the parentHeader is retrieved multiple times
|
|
465
|
+
// (one for each uncle header and then for validateBlobTxs).
|
|
466
|
+
const parentBlock = await this.getBlock(block.header.parentHash);
|
|
467
|
+
block.validateBlobTransactions(parentBlock.header);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* The following rules are checked in this method:
|
|
471
|
+
* Uncle Header is a valid header.
|
|
472
|
+
* Uncle Header is an orphan, i.e. it is not one of the headers of the canonical chain.
|
|
473
|
+
* Uncle Header has a parentHash which points to the canonical chain. This parentHash is within the last 7 blocks.
|
|
474
|
+
* Uncle Header is not already included as uncle in another block.
|
|
475
|
+
* @param block - block for which uncles are being validated
|
|
476
|
+
*/
|
|
477
|
+
async _validateUncleHeaders(block) {
|
|
478
|
+
const uncleHeaders = block.uncleHeaders;
|
|
479
|
+
if (uncleHeaders.length === 0) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
// Each Uncle Header is a valid header
|
|
483
|
+
await Promise.all(uncleHeaders.map((uh) => this.validateHeader(uh, block.header.number)));
|
|
484
|
+
// Check how many blocks we should get in order to validate the uncle.
|
|
485
|
+
// In the worst case, we get 8 blocks, in the best case, we only get 1 block.
|
|
486
|
+
const canonicalBlockMap = [];
|
|
487
|
+
let lowestUncleNumber = block.header.number;
|
|
488
|
+
uncleHeaders.map((header) => {
|
|
489
|
+
if (header.number < lowestUncleNumber) {
|
|
490
|
+
lowestUncleNumber = header.number;
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
// Helper variable: set hash to `true` if hash is part of the canonical chain
|
|
494
|
+
const canonicalChainHashes = {};
|
|
495
|
+
// Helper variable: set hash to `true` if uncle hash is included in any canonical block
|
|
496
|
+
const includedUncles = {};
|
|
497
|
+
// Due to the header validation check above, we know that `getBlocks` is between 1 and 8 inclusive.
|
|
498
|
+
const getBlocks = Number(block.header.number - lowestUncleNumber + util_1.BIGINT_1);
|
|
499
|
+
// See Geth: https://github.com/ethereum/go-ethereum/blob/b63bffe8202d46ea10ac8c4f441c582642193ac8/consensus/ethash/consensus.go#L207
|
|
500
|
+
// Here we get the necessary blocks from the chain.
|
|
501
|
+
let parentHash = block.header.parentHash;
|
|
502
|
+
for (let i = 0; i < getBlocks; i++) {
|
|
503
|
+
const parentBlock = await this.getBlock(parentHash);
|
|
504
|
+
canonicalBlockMap.push(parentBlock);
|
|
505
|
+
// mark block hash as part of the canonical chain
|
|
506
|
+
// Using deprecated bytesToUnprefixedHex for performance: used as object keys for hash lookups.
|
|
507
|
+
canonicalChainHashes[(0, util_1.bytesToUnprefixedHex)(parentBlock.hash())] = true;
|
|
508
|
+
// for each of the uncles, mark the uncle as included
|
|
509
|
+
parentBlock.uncleHeaders.map((uh) => {
|
|
510
|
+
includedUncles[(0, util_1.bytesToUnprefixedHex)(uh.hash())] = true;
|
|
511
|
+
});
|
|
512
|
+
parentHash = parentBlock.header.parentHash;
|
|
513
|
+
}
|
|
514
|
+
// Here we check:
|
|
515
|
+
// Uncle Header is an orphan, i.e. it is not one of the headers of the canonical chain.
|
|
516
|
+
// Uncle Header is not already included as uncle in another block.
|
|
517
|
+
// Uncle Header has a parentHash which points to the canonical chain.
|
|
518
|
+
uncleHeaders.map((uh) => {
|
|
519
|
+
// Using deprecated bytesToUnprefixedHex for performance: used as object keys for hash lookups.
|
|
520
|
+
const uncleHash = (0, util_1.bytesToUnprefixedHex)(uh.hash());
|
|
521
|
+
const parentHash = (0, util_1.bytesToUnprefixedHex)(uh.parentHash);
|
|
522
|
+
if (!canonicalChainHashes[parentHash]) {
|
|
523
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`The parent hash of the uncle header is not part of the canonical chain ${block.errorStr()}`);
|
|
524
|
+
}
|
|
525
|
+
if (includedUncles[uncleHash]) {
|
|
526
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`The uncle is already included in the canonical chain ${block.errorStr()}`);
|
|
527
|
+
}
|
|
528
|
+
if (canonicalChainHashes[uncleHash]) {
|
|
529
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`The uncle is a canonical block ${block.errorStr()}`);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Gets a block by its hash or number. If a number is provided, the returned
|
|
535
|
+
* block will be the canonical block at that number in the chain
|
|
536
|
+
*
|
|
537
|
+
* @param blockId - The block's hash or number. If a hash is provided, then
|
|
538
|
+
* this will be immediately looked up, otherwise it will wait until we have
|
|
539
|
+
* unlocked the DB
|
|
540
|
+
*/
|
|
541
|
+
async getBlock(blockId) {
|
|
542
|
+
// cannot wait for a lock here: it is used both in `validate` of `Block`
|
|
543
|
+
// (calls `getBlock` to get `parentHash`) it is also called from `runBlock`
|
|
544
|
+
// in the `VM` if we encounter a `BLOCKHASH` opcode: then a bigint is used we
|
|
545
|
+
// need to then read the block from the canonical chain Q: is this safe? We
|
|
546
|
+
// know it is OK if we call it from the iterator... (runBlock)
|
|
547
|
+
const block = await this.dbManager.getBlock(blockId);
|
|
548
|
+
if (block === undefined) {
|
|
549
|
+
if (typeof blockId === 'object') {
|
|
550
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`Block with hash ${(0, util_1.bytesToHex)(blockId)} not found in DB`);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`Block number ${blockId} not found in DB`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return block;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Gets total difficulty for a block specified by hash and number
|
|
560
|
+
*/
|
|
561
|
+
async getTotalDifficulty(hash, number) {
|
|
562
|
+
if (number === undefined) {
|
|
563
|
+
number = await this.dbManager.hashToNumber(hash);
|
|
564
|
+
if (number === undefined) {
|
|
565
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`Block with hash ${(0, util_1.bytesToHex)(hash)} not found in DB`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return this.dbManager.getTotalDifficulty(hash, number);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Gets total difficulty for a header's parent, helpful for determining terminal block
|
|
572
|
+
* @param header - Block header whose parent td is desired
|
|
573
|
+
*/
|
|
574
|
+
async getParentTD(header) {
|
|
575
|
+
return header.number === util_1.BIGINT_0
|
|
576
|
+
? header.difficulty
|
|
577
|
+
: this.getTotalDifficulty(header.parentHash, header.number - util_1.BIGINT_1);
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Looks up many blocks relative to blockId Note: due to `GetBlockHeaders
|
|
581
|
+
* (0x03)` (ETH wire protocol) we have to support skip/reverse as well.
|
|
582
|
+
* @param blockId - The block's hash or number
|
|
583
|
+
* @param maxBlocks - Max number of blocks to return
|
|
584
|
+
* @param skip - Number of blocks to skip apart
|
|
585
|
+
* @param reverse - Fetch blocks in reverse
|
|
586
|
+
*/
|
|
587
|
+
async getBlocks(blockId, maxBlocks, skip, reverse) {
|
|
588
|
+
return this.runWithLock(async () => {
|
|
589
|
+
const blocks = [];
|
|
590
|
+
let i = -1;
|
|
591
|
+
const nextBlock = async (blockId) => {
|
|
592
|
+
let block;
|
|
593
|
+
try {
|
|
594
|
+
block = await this.getBlock(blockId);
|
|
595
|
+
}
|
|
596
|
+
catch (err) {
|
|
597
|
+
if (err.message.includes('not found in DB') === true) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
else
|
|
601
|
+
throw err;
|
|
602
|
+
}
|
|
603
|
+
i++;
|
|
604
|
+
const nextBlockNumber = block.header.number + BigInt(reverse ? -1 : 1);
|
|
605
|
+
if (i !== 0 && skip && i % (skip + 1) !== 0) {
|
|
606
|
+
return nextBlock(nextBlockNumber);
|
|
607
|
+
}
|
|
608
|
+
blocks.push(block);
|
|
609
|
+
if (blocks.length < maxBlocks) {
|
|
610
|
+
await nextBlock(nextBlockNumber);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
await nextBlock(blockId);
|
|
614
|
+
return blocks;
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Given an ordered array, returns an array of hashes that are not in the
|
|
619
|
+
* blockchain yet. Uses binary search to find out what hashes are missing.
|
|
620
|
+
* Therefore, the array needs to be ordered upon number.
|
|
621
|
+
* @param hashes - Ordered array of hashes (ordered on `number`).
|
|
622
|
+
*/
|
|
623
|
+
async selectNeededHashes(hashes) {
|
|
624
|
+
return this.runWithLock(async () => {
|
|
625
|
+
let max;
|
|
626
|
+
let mid;
|
|
627
|
+
let min;
|
|
628
|
+
max = hashes.length - 1;
|
|
629
|
+
mid = min = 0;
|
|
630
|
+
while (max >= min) {
|
|
631
|
+
let number;
|
|
632
|
+
try {
|
|
633
|
+
number = await this.dbManager.hashToNumber(hashes[mid]);
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
if (err.message.includes('not found in DB') === true) {
|
|
637
|
+
number = undefined;
|
|
638
|
+
}
|
|
639
|
+
else
|
|
640
|
+
throw err;
|
|
641
|
+
}
|
|
642
|
+
if (number !== undefined) {
|
|
643
|
+
min = mid + 1;
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
max = mid - 1;
|
|
647
|
+
}
|
|
648
|
+
mid = Math.floor((min + max) / 2);
|
|
649
|
+
}
|
|
650
|
+
return hashes.slice(min);
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Completely deletes a block from the blockchain including any references to
|
|
655
|
+
* this block. If this block was in the canonical chain, then also each child
|
|
656
|
+
* block of this block is deleted Also, if this was a canonical block, each
|
|
657
|
+
* head header which is part of this now stale chain will be set to the
|
|
658
|
+
* parentHeader of this block An example reason to execute is when running the
|
|
659
|
+
* block in the VM invalidates this block: this will then reset the canonical
|
|
660
|
+
* head to the past block (which has been validated in the past by the VM, so
|
|
661
|
+
* we can be sure it is correct).
|
|
662
|
+
* @param blockHash - The hash of the block to be deleted
|
|
663
|
+
*/
|
|
664
|
+
async delBlock(blockHash) {
|
|
665
|
+
// Q: is it safe to make this not wait for a lock? this is called from
|
|
666
|
+
// `BlockchainTestsRunner` in case `runBlock` throws (i.e. the block is invalid).
|
|
667
|
+
// But is this the way to go? If we know this is called from the
|
|
668
|
+
// iterator we are safe, but if this is called from anywhere
|
|
669
|
+
// else then this might lead to a concurrency problem?
|
|
670
|
+
await this._delBlock(blockHash);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* @hidden
|
|
674
|
+
*/
|
|
675
|
+
async _delBlock(blockHash) {
|
|
676
|
+
const dbOps = [];
|
|
677
|
+
// get header
|
|
678
|
+
const header = await this._getHeader(blockHash);
|
|
679
|
+
const blockHeader = header;
|
|
680
|
+
const blockNumber = blockHeader.number;
|
|
681
|
+
const parentHash = blockHeader.parentHash;
|
|
682
|
+
// check if block is in the canonical chain
|
|
683
|
+
const canonicalHash = await this.safeNumberToHash(blockNumber);
|
|
684
|
+
const inCanonical = canonicalHash !== false && (0, util_1.equalsBytes)(canonicalHash, blockHash);
|
|
685
|
+
// delete the block, and if block is in the canonical chain, delete all
|
|
686
|
+
// children as well
|
|
687
|
+
await this._delChild(blockHash, blockNumber, inCanonical ? parentHash : null, dbOps);
|
|
688
|
+
// delete all number to hash mappings for deleted block number and above
|
|
689
|
+
if (inCanonical) {
|
|
690
|
+
await this._deleteCanonicalChainReferences(blockNumber, parentHash, dbOps);
|
|
691
|
+
}
|
|
692
|
+
await this.dbManager.batch(dbOps);
|
|
693
|
+
if (this._deletedBlocks.length > 0) {
|
|
694
|
+
this.events.emit('deletedCanonicalBlocks', this._deletedBlocks);
|
|
695
|
+
for (const block of this._deletedBlocks)
|
|
696
|
+
this.DEBUG &&
|
|
697
|
+
this._debug(`delete stale canonical block number=${block.header.number} hash=${blockHash})}`);
|
|
698
|
+
this._deletedBlocks = [];
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Updates the `DatabaseOperation` list to delete a block from the DB,
|
|
703
|
+
* identified by `blockHash` and `blockNumber`. Deletes fields from `Header`,
|
|
704
|
+
* `Body`, `HashToNumber` and `TotalDifficulty` tables. If child blocks of
|
|
705
|
+
* this current block are in the canonical chain, delete these as well. Does
|
|
706
|
+
* not actually commit these changes to the DB. Sets `_headHeaderHash` and
|
|
707
|
+
* `_headBlockHash` to `headHash` if any of these matches the current child to
|
|
708
|
+
* be deleted.
|
|
709
|
+
* @param blockHash - the block hash to delete
|
|
710
|
+
* @param blockNumber - the number corresponding to the block hash
|
|
711
|
+
* @param headHash - the current head of the chain (if null, do not update
|
|
712
|
+
* `_headHeaderHash` and `_headBlockHash`)
|
|
713
|
+
* @param ops - the `DatabaseOperation` list to add the delete operations to
|
|
714
|
+
* @hidden
|
|
715
|
+
*/
|
|
716
|
+
async _delChild(blockHash, blockNumber, headHash, ops) {
|
|
717
|
+
// delete header, body, hash to number mapping and td
|
|
718
|
+
ops.push(helpers_ts_1.DBOp.del(operation_ts_1.DBTarget.Header, { blockHash, blockNumber }));
|
|
719
|
+
ops.push(helpers_ts_1.DBOp.del(operation_ts_1.DBTarget.Body, { blockHash, blockNumber }));
|
|
720
|
+
ops.push(helpers_ts_1.DBOp.del(operation_ts_1.DBTarget.HashToNumber, { blockHash }));
|
|
721
|
+
ops.push(helpers_ts_1.DBOp.del(operation_ts_1.DBTarget.TotalDifficulty, { blockHash, blockNumber }));
|
|
722
|
+
if (!headHash) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (this._headHeaderHash !== undefined &&
|
|
726
|
+
(0, util_1.equalsBytes)(this._headHeaderHash, blockHash) === true) {
|
|
727
|
+
this._headHeaderHash = headHash;
|
|
728
|
+
}
|
|
729
|
+
if (this._headBlockHash !== undefined && (0, util_1.equalsBytes)(this._headBlockHash, blockHash)) {
|
|
730
|
+
this._headBlockHash = headHash;
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
const childHeader = await this.getCanonicalHeader(blockNumber + util_1.BIGINT_1);
|
|
734
|
+
await this._delChild(childHeader.hash(), childHeader.number, headHash, ops);
|
|
735
|
+
}
|
|
736
|
+
catch (err) {
|
|
737
|
+
if (err.message.includes('not found in canonical chain') !== true) {
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Iterates through blocks starting at the specified iterator head and calls
|
|
744
|
+
* the onBlock function on each block. The current location of an iterator
|
|
745
|
+
* head can be retrieved using {@link Blockchain.getIteratorHead}.
|
|
746
|
+
*
|
|
747
|
+
* @param name - Name of the state root head
|
|
748
|
+
* @param onBlock - Function called on each block with params (block, reorg)
|
|
749
|
+
* @param maxBlocks - How many blocks to run. By default, run all unprocessed blocks in the canonical chain.
|
|
750
|
+
* @param releaseLockOnCallback - Do not lock the blockchain for running the callback (default: `false`)
|
|
751
|
+
* @returns number of blocks actually iterated
|
|
752
|
+
*/
|
|
753
|
+
async iterator(name, onBlock, maxBlocks, releaseLockOnCallback) {
|
|
754
|
+
return this.runWithLock(async () => {
|
|
755
|
+
let headHash = this._heads[name] ?? this.genesisBlock.hash();
|
|
756
|
+
if (typeof maxBlocks === 'number' && maxBlocks < 0) {
|
|
757
|
+
throw 'If maxBlocks is provided, it has to be a non-negative number';
|
|
758
|
+
}
|
|
759
|
+
let headBlockNumber = await this.dbManager.hashToNumber(headHash);
|
|
760
|
+
// `headBlockNumber` should always exist since it defaults to the genesis block
|
|
761
|
+
let nextBlockNumber = headBlockNumber + util_1.BIGINT_1;
|
|
762
|
+
let blocksRanCounter = 0;
|
|
763
|
+
let lastBlock;
|
|
764
|
+
try {
|
|
765
|
+
while (maxBlocks !== blocksRanCounter) {
|
|
766
|
+
try {
|
|
767
|
+
let nextBlock = await this.getBlock(nextBlockNumber);
|
|
768
|
+
const reorg = lastBlock
|
|
769
|
+
? !(0, util_1.equalsBytes)(lastBlock.hash(), nextBlock.header.parentHash)
|
|
770
|
+
: false;
|
|
771
|
+
if (reorg) {
|
|
772
|
+
// If reorg has happened, the _heads must have been updated so lets reload the counters
|
|
773
|
+
headHash = this._heads[name] ?? this.genesisBlock.hash();
|
|
774
|
+
headBlockNumber = await this.dbManager.hashToNumber(headHash);
|
|
775
|
+
nextBlockNumber = headBlockNumber + util_1.BIGINT_1;
|
|
776
|
+
nextBlock = await this.getBlock(nextBlockNumber);
|
|
777
|
+
}
|
|
778
|
+
// While running onBlock with released lock, reorgs can happen via putBlocks
|
|
779
|
+
let reorgWhileOnBlock = false;
|
|
780
|
+
if (releaseLockOnCallback === true) {
|
|
781
|
+
this._lock.release();
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
await onBlock(nextBlock, reorg);
|
|
785
|
+
}
|
|
786
|
+
finally {
|
|
787
|
+
if (releaseLockOnCallback === true) {
|
|
788
|
+
await this._lock.acquire();
|
|
789
|
+
// If lock was released check if reorg occurred
|
|
790
|
+
const nextBlockMayBeReorged = await this.getBlock(nextBlockNumber).catch((_e) => null);
|
|
791
|
+
reorgWhileOnBlock = nextBlockMayBeReorged
|
|
792
|
+
? !(0, util_1.equalsBytes)(nextBlockMayBeReorged.hash(), nextBlock.hash())
|
|
793
|
+
: true;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
// if there was no reorg, update head
|
|
797
|
+
if (!reorgWhileOnBlock) {
|
|
798
|
+
this._heads[name] = nextBlock.hash();
|
|
799
|
+
lastBlock = nextBlock;
|
|
800
|
+
nextBlockNumber++;
|
|
801
|
+
}
|
|
802
|
+
// Successful execution of onBlock, move the head pointer
|
|
803
|
+
blocksRanCounter++;
|
|
804
|
+
}
|
|
805
|
+
catch (error) {
|
|
806
|
+
if (error.message.includes('not found in DB')) {
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return blocksRanCounter;
|
|
815
|
+
}
|
|
816
|
+
finally {
|
|
817
|
+
await this._saveHeads();
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Set header hash of a certain `tag`.
|
|
823
|
+
* When calling the iterator, the iterator will start running the first child block after the header hash currently stored.
|
|
824
|
+
* @param tag - The tag to save the headHash to
|
|
825
|
+
* @param headHash - The head hash to save
|
|
826
|
+
*/
|
|
827
|
+
async setIteratorHead(tag, headHash) {
|
|
828
|
+
await this.runWithLock(async () => {
|
|
829
|
+
this._heads[tag] = headHash;
|
|
830
|
+
await this._saveHeads();
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
/* Methods regarding reorg operations */
|
|
834
|
+
/**
|
|
835
|
+
* Find the common ancestor of the new block and the old block.
|
|
836
|
+
* @param newHeader - the new block header
|
|
837
|
+
*/
|
|
838
|
+
async findCommonAncestor(newHeader) {
|
|
839
|
+
if (!this._headHeaderHash)
|
|
840
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)('No head header set');
|
|
841
|
+
const ancestorHeaders = new Set();
|
|
842
|
+
let header = await this._getHeader(this._headHeaderHash);
|
|
843
|
+
if (header.number > newHeader.number) {
|
|
844
|
+
header = await this.getCanonicalHeader(newHeader.number);
|
|
845
|
+
ancestorHeaders.add(header);
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
while (header.number !== newHeader.number && newHeader.number > util_1.BIGINT_0) {
|
|
849
|
+
newHeader = await this._getHeader(newHeader.parentHash, newHeader.number - util_1.BIGINT_1);
|
|
850
|
+
ancestorHeaders.add(newHeader);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (header.number !== newHeader.number) {
|
|
854
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)('Failed to find ancient header');
|
|
855
|
+
}
|
|
856
|
+
while (!(0, util_1.equalsBytes)(header.hash(), newHeader.hash()) && header.number > util_1.BIGINT_0) {
|
|
857
|
+
header = await this.getCanonicalHeader(header.number - util_1.BIGINT_1);
|
|
858
|
+
ancestorHeaders.add(header);
|
|
859
|
+
newHeader = await this._getHeader(newHeader.parentHash, newHeader.number - util_1.BIGINT_1);
|
|
860
|
+
ancestorHeaders.add(newHeader);
|
|
861
|
+
}
|
|
862
|
+
if (!(0, util_1.equalsBytes)(header.hash(), newHeader.hash())) {
|
|
863
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)('Failed to find ancient header');
|
|
864
|
+
}
|
|
865
|
+
this.DEBUG && this._debug(`found common ancestor with hash=${(0, util_1.bytesToHex)(header.hash())}`);
|
|
866
|
+
this.DEBUG && this._debug(`total ancestor headers num=${ancestorHeaders.size}`);
|
|
867
|
+
return {
|
|
868
|
+
commonAncestor: header,
|
|
869
|
+
ancestorHeaders: Array.from(ancestorHeaders),
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Pushes DB operations to delete canonical number assignments for specified
|
|
874
|
+
* block number and above. This only deletes `NumberToHash` references and not
|
|
875
|
+
* the blocks themselves. Note: this does not write to the DB but only pushes
|
|
876
|
+
* to a DB operations list.
|
|
877
|
+
* @param blockNumber - the block number from which we start deleting
|
|
878
|
+
* canonical chain assignments (including this block)
|
|
879
|
+
* @param headHash - the hash of the current canonical chain head. The _heads
|
|
880
|
+
* reference matching any hash of any of the deleted blocks will be set to
|
|
881
|
+
* this
|
|
882
|
+
* @param ops - the DatabaseOperation list to write DatabaseOperations to
|
|
883
|
+
* @hidden
|
|
884
|
+
*/
|
|
885
|
+
async _deleteCanonicalChainReferences(blockNumber, headHash, ops) {
|
|
886
|
+
try {
|
|
887
|
+
let hash;
|
|
888
|
+
hash = await this.safeNumberToHash(blockNumber);
|
|
889
|
+
while (hash !== false) {
|
|
890
|
+
ops.push(helpers_ts_1.DBOp.del(operation_ts_1.DBTarget.NumberToHash, { blockNumber }));
|
|
891
|
+
if (this.events.listenerCount('deletedCanonicalBlocks') > 0) {
|
|
892
|
+
const block = await this.getBlock(blockNumber);
|
|
893
|
+
this._deletedBlocks.push(block);
|
|
894
|
+
}
|
|
895
|
+
// reset stale iterator heads to current canonical head this can, for
|
|
896
|
+
// instance, make the VM run "older" (i.e. lower number blocks than last
|
|
897
|
+
// executed block) blocks to verify the chain up to the current, actual,
|
|
898
|
+
// head.
|
|
899
|
+
for (const name of Object.keys(this._heads)) {
|
|
900
|
+
if ((0, util_1.equalsBytes)(this._heads[name], hash)) {
|
|
901
|
+
this._heads[name] = headHash;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
// reset stale headHeader to current canonical
|
|
905
|
+
if (this._headHeaderHash !== undefined &&
|
|
906
|
+
(0, util_1.equalsBytes)(this._headHeaderHash, hash) === true) {
|
|
907
|
+
this._headHeaderHash = headHash;
|
|
908
|
+
}
|
|
909
|
+
// reset stale headBlock to current canonical
|
|
910
|
+
if (this._headBlockHash !== undefined && (0, util_1.equalsBytes)(this._headBlockHash, hash) === true) {
|
|
911
|
+
this._headBlockHash = headHash;
|
|
912
|
+
}
|
|
913
|
+
blockNumber++;
|
|
914
|
+
hash = await this.safeNumberToHash(blockNumber);
|
|
915
|
+
}
|
|
916
|
+
this.DEBUG &&
|
|
917
|
+
this._deletedBlocks.length > 0 &&
|
|
918
|
+
this._debug(`deleted ${this._deletedBlocks.length} stale canonical blocks in total`);
|
|
919
|
+
}
|
|
920
|
+
catch (e) {
|
|
921
|
+
// Ensure that if this method throws, `_deletedBlocks` is reset to the empty array
|
|
922
|
+
this._deletedBlocks = [];
|
|
923
|
+
throw e;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Given a `header`, put all operations to change the canonical chain directly
|
|
928
|
+
* into `ops`. This walks the supplied `header` backwards. It is thus assumed
|
|
929
|
+
* that this header should be canonical header. For each header the
|
|
930
|
+
* corresponding hash corresponding to the current canonical chain in the DB
|
|
931
|
+
* is checked. If the number => hash reference does not correspond to the
|
|
932
|
+
* reference in the DB, we overwrite this reference with the implied number =>
|
|
933
|
+
* hash reference Also, each `_heads` member is checked; if these point to a
|
|
934
|
+
* stale hash, then the hash which we terminate the loop (i.e. the first hash
|
|
935
|
+
* which matches the number => hash of the implied chain) is put as this stale
|
|
936
|
+
* head hash. The same happens to _headBlockHash.
|
|
937
|
+
* @param header - The canonical header.
|
|
938
|
+
* @param ops - The database operations list.
|
|
939
|
+
* @hidden
|
|
940
|
+
*/
|
|
941
|
+
async _rebuildCanonical(header, ops) {
|
|
942
|
+
let currentNumber = header.number;
|
|
943
|
+
let currentCanonicalHash = header.hash();
|
|
944
|
+
// track the staleHash: this is the hash currently in the DB which matches
|
|
945
|
+
// the block number of the provided header.
|
|
946
|
+
let staleHash = false;
|
|
947
|
+
let staleHeads = [];
|
|
948
|
+
let staleHeadBlock = false;
|
|
949
|
+
const loopCondition = async () => {
|
|
950
|
+
staleHash = await this.safeNumberToHash(currentNumber);
|
|
951
|
+
currentCanonicalHash = header.hash();
|
|
952
|
+
return staleHash === false || !(0, util_1.equalsBytes)(currentCanonicalHash, staleHash);
|
|
953
|
+
};
|
|
954
|
+
while (await loopCondition()) {
|
|
955
|
+
// handle genesis block
|
|
956
|
+
const blockHash = header.hash();
|
|
957
|
+
const blockNumber = header.number;
|
|
958
|
+
if (blockNumber === util_1.BIGINT_0) {
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
(0, helpers_ts_1.DBSaveLookups)(blockHash, blockNumber).map((op) => {
|
|
962
|
+
ops.push(op);
|
|
963
|
+
});
|
|
964
|
+
// mark each key `_heads` which is currently set to the hash in the DB as
|
|
965
|
+
// stale to overwrite later in `_deleteCanonicalChainReferences`.
|
|
966
|
+
for (const name of Object.keys(this._heads)) {
|
|
967
|
+
if (staleHash && (0, util_1.equalsBytes)(this._heads[name], staleHash)) {
|
|
968
|
+
staleHeads.push(name);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// flag stale headBlock for reset
|
|
972
|
+
if (staleHash &&
|
|
973
|
+
this._headBlockHash !== undefined &&
|
|
974
|
+
(0, util_1.equalsBytes)(this._headBlockHash, staleHash) === true) {
|
|
975
|
+
staleHeadBlock = true;
|
|
976
|
+
}
|
|
977
|
+
header = await this._getHeader(header.parentHash, --currentNumber);
|
|
978
|
+
if (header === undefined) {
|
|
979
|
+
staleHeads = [];
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
// When the stale hash is equal to the blockHash of the provided header,
|
|
984
|
+
// set stale heads to last previously valid canonical block
|
|
985
|
+
for (const name of staleHeads) {
|
|
986
|
+
this._heads[name] = currentCanonicalHash;
|
|
987
|
+
}
|
|
988
|
+
// set stale headBlock to last previously valid canonical block
|
|
989
|
+
if (staleHeadBlock) {
|
|
990
|
+
this._headBlockHash = currentCanonicalHash;
|
|
991
|
+
}
|
|
992
|
+
this.DEBUG && this._debug(`stale heads found num=${staleHeads.length}`);
|
|
993
|
+
}
|
|
994
|
+
/* Helper functions */
|
|
995
|
+
/**
|
|
996
|
+
* Builds the `DatabaseOperation[]` list which describes the DB operations to
|
|
997
|
+
* write the heads, head header hash and the head header block to the DB
|
|
998
|
+
* @hidden
|
|
999
|
+
*/
|
|
1000
|
+
_saveHeadOps() {
|
|
1001
|
+
// Convert DB heads to hex strings for efficient storage in DB
|
|
1002
|
+
// LevelDB doesn't handle Uint8Arrays properly when they are part
|
|
1003
|
+
// of a JSON object being stored as a value in the DB
|
|
1004
|
+
// Using deprecated bytesToUnprefixedHex for performance: used for JSON serialization where unprefixed hex is needed.
|
|
1005
|
+
const hexHeads = Object.fromEntries(Object.entries(this._heads).map((entry) => [entry[0], (0, util_1.bytesToUnprefixedHex)(entry[1])]));
|
|
1006
|
+
return [
|
|
1007
|
+
helpers_ts_1.DBOp.set(operation_ts_1.DBTarget.Heads, hexHeads),
|
|
1008
|
+
helpers_ts_1.DBOp.set(operation_ts_1.DBTarget.HeadHeader, this._headHeaderHash),
|
|
1009
|
+
helpers_ts_1.DBOp.set(operation_ts_1.DBTarget.HeadBlock, this._headBlockHash),
|
|
1010
|
+
];
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Gets the `DatabaseOperation[]` list to save `_heads`, `_headHeaderHash` and
|
|
1014
|
+
* `_headBlockHash` and writes these to the DB
|
|
1015
|
+
* @hidden
|
|
1016
|
+
*/
|
|
1017
|
+
async _saveHeads() {
|
|
1018
|
+
return this.dbManager.batch(this._saveHeadOps());
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Gets a header by hash and number. Header can exist outside the canonical
|
|
1022
|
+
* chain
|
|
1023
|
+
*
|
|
1024
|
+
* @hidden
|
|
1025
|
+
*/
|
|
1026
|
+
async _getHeader(hash, number) {
|
|
1027
|
+
if (number === undefined) {
|
|
1028
|
+
number = await this.dbManager.hashToNumber(hash);
|
|
1029
|
+
if (number === undefined)
|
|
1030
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`no header for ${(0, util_1.bytesToHex)(hash)} found in DB`);
|
|
1031
|
+
}
|
|
1032
|
+
return this.dbManager.getHeader(hash, number);
|
|
1033
|
+
}
|
|
1034
|
+
async checkAndTransitionHardForkByNumber(number, timestamp) {
|
|
1035
|
+
this.common.setHardforkBy({
|
|
1036
|
+
blockNumber: number,
|
|
1037
|
+
timestamp,
|
|
1038
|
+
});
|
|
1039
|
+
this._consensusCheck();
|
|
1040
|
+
await this.consensus?.setup({ blockchain: this });
|
|
1041
|
+
await this.consensus?.genesisInit(this.genesisBlock);
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Gets a header by number. Header must be in the canonical chain
|
|
1045
|
+
*/
|
|
1046
|
+
async getCanonicalHeader(number) {
|
|
1047
|
+
const hash = await this.dbManager.numberToHash(number);
|
|
1048
|
+
if (hash === undefined) {
|
|
1049
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)(`header with number ${number} not found in canonical chain`);
|
|
1050
|
+
}
|
|
1051
|
+
return this._getHeader(hash, number);
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* This method either returns a Uint8Array if there exists one in the DB or if it
|
|
1055
|
+
* does not exist then return false If DB throws
|
|
1056
|
+
* any other error, this function throws.
|
|
1057
|
+
* @param number
|
|
1058
|
+
*/
|
|
1059
|
+
async safeNumberToHash(number) {
|
|
1060
|
+
const hash = await this.dbManager.numberToHash(number);
|
|
1061
|
+
return hash ?? false;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* The genesis {@link Block} for the blockchain.
|
|
1065
|
+
*/
|
|
1066
|
+
get genesisBlock() {
|
|
1067
|
+
if (!this._genesisBlock)
|
|
1068
|
+
throw (0, util_1.EthereumJSErrorWithoutCode)('genesis block not set (init may not be finished)');
|
|
1069
|
+
return this._genesisBlock;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Creates a genesis {@link Block} for the blockchain with params from {@link Common.genesis}
|
|
1073
|
+
* @param stateRoot The genesis stateRoot
|
|
1074
|
+
*/
|
|
1075
|
+
createGenesisBlock(stateRoot) {
|
|
1076
|
+
const common = this.common.copy();
|
|
1077
|
+
common.setHardforkBy({
|
|
1078
|
+
blockNumber: 0,
|
|
1079
|
+
timestamp: common.genesis().timestamp,
|
|
1080
|
+
});
|
|
1081
|
+
const header = {
|
|
1082
|
+
...common.genesis(),
|
|
1083
|
+
number: 0,
|
|
1084
|
+
stateRoot,
|
|
1085
|
+
withdrawalsRoot: common.isActivatedEIP(4895) ? util_1.KECCAK256_RLP : undefined,
|
|
1086
|
+
requestsHash: common.isActivatedEIP(7685) ? util_1.SHA256_NULL : undefined,
|
|
1087
|
+
};
|
|
1088
|
+
if (common.consensusType() === 'poa') {
|
|
1089
|
+
if (common.genesis().extraData) {
|
|
1090
|
+
// Ensure extra data is populated from genesis data if provided
|
|
1091
|
+
header.extraData = common.genesis().extraData;
|
|
1092
|
+
}
|
|
1093
|
+
else {
|
|
1094
|
+
// Add required extraData (32 bytes vanity + 65 bytes filled with zeroes
|
|
1095
|
+
header.extraData = (0, util_1.concatBytes)(new Uint8Array(32), new Uint8Array(65));
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return (0, block_1.createBlock)({
|
|
1099
|
+
header,
|
|
1100
|
+
withdrawals: common.isActivatedEIP(4895) ? [] : undefined,
|
|
1101
|
+
}, { common });
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
exports.Blockchain = Blockchain;
|
|
1105
|
+
//# sourceMappingURL=blockchain.js.map
|