hardhat 2.8.4 → 2.9.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 (157) hide show
  1. package/LICENSE +3 -61
  2. package/README.md +1 -1
  3. package/builtin-tasks/compile.js +86 -51
  4. package/builtin-tasks/compile.js.map +1 -1
  5. package/builtin-tasks/node.js.map +1 -1
  6. package/builtin-tasks/test.js +30 -5
  7. package/builtin-tasks/test.js.map +1 -1
  8. package/builtin-tasks/utils/solidity-files-cache.d.ts.map +1 -1
  9. package/builtin-tasks/utils/solidity-files-cache.js.map +1 -1
  10. package/internal/cli/analytics.js +2 -2
  11. package/internal/cli/analytics.js.map +1 -1
  12. package/internal/cli/cli.js +3 -3
  13. package/internal/cli/project-creation.d.ts.map +1 -1
  14. package/internal/cli/project-creation.js +23 -0
  15. package/internal/cli/project-creation.js.map +1 -1
  16. package/internal/core/config/config-loading.d.ts.map +1 -1
  17. package/internal/core/config/config-loading.js.map +1 -1
  18. package/internal/core/config/config-validation.d.ts.map +1 -1
  19. package/internal/core/config/config-validation.js +2 -2
  20. package/internal/core/config/config-validation.js.map +1 -1
  21. package/internal/core/config/default-config.d.ts +2 -0
  22. package/internal/core/config/default-config.d.ts.map +1 -1
  23. package/internal/core/config/default-config.js +1 -0
  24. package/internal/core/config/default-config.js.map +1 -1
  25. package/internal/core/errors-list.d.ts +7 -0
  26. package/internal/core/errors-list.d.ts.map +1 -1
  27. package/internal/core/errors-list.js +13 -0
  28. package/internal/core/errors-list.js.map +1 -1
  29. package/internal/core/providers/accounts.d.ts +1 -1
  30. package/internal/core/providers/accounts.d.ts.map +1 -1
  31. package/internal/core/providers/accounts.js +2 -2
  32. package/internal/core/providers/accounts.js.map +1 -1
  33. package/internal/core/providers/construction.d.ts.map +1 -1
  34. package/internal/core/providers/construction.js +1 -1
  35. package/internal/core/providers/construction.js.map +1 -1
  36. package/internal/core/providers/http.d.ts +5 -1
  37. package/internal/core/providers/http.d.ts.map +1 -1
  38. package/internal/core/providers/http.js +35 -31
  39. package/internal/core/providers/http.js.map +1 -1
  40. package/internal/core/providers/util.d.ts +1 -1
  41. package/internal/core/providers/util.d.ts.map +1 -1
  42. package/internal/core/providers/util.js +3 -3
  43. package/internal/core/providers/util.js.map +1 -1
  44. package/internal/hardhat-network/jsonrpc/server.d.ts.map +1 -1
  45. package/internal/hardhat-network/jsonrpc/server.js +7 -2
  46. package/internal/hardhat-network/jsonrpc/server.js.map +1 -1
  47. package/internal/hardhat-network/provider/BlockchainBase.d.ts +26 -0
  48. package/internal/hardhat-network/provider/BlockchainBase.d.ts.map +1 -0
  49. package/internal/hardhat-network/provider/BlockchainBase.js +92 -0
  50. package/internal/hardhat-network/provider/BlockchainBase.js.map +1 -0
  51. package/internal/hardhat-network/provider/BlockchainData.d.ts +32 -0
  52. package/internal/hardhat-network/provider/BlockchainData.d.ts.map +1 -1
  53. package/internal/hardhat-network/provider/BlockchainData.js +78 -1
  54. package/internal/hardhat-network/provider/BlockchainData.js.map +1 -1
  55. package/internal/hardhat-network/provider/HardhatBlockchain.d.ts +9 -15
  56. package/internal/hardhat-network/provider/HardhatBlockchain.d.ts.map +1 -1
  57. package/internal/hardhat-network/provider/HardhatBlockchain.js +15 -73
  58. package/internal/hardhat-network/provider/HardhatBlockchain.js.map +1 -1
  59. package/internal/hardhat-network/provider/fork/ForkBlockchain.d.ts +6 -14
  60. package/internal/hardhat-network/provider/fork/ForkBlockchain.d.ts.map +1 -1
  61. package/internal/hardhat-network/provider/fork/ForkBlockchain.js +23 -73
  62. package/internal/hardhat-network/provider/fork/ForkBlockchain.js.map +1 -1
  63. package/internal/hardhat-network/provider/modules/eth.js +2 -2
  64. package/internal/hardhat-network/provider/modules/eth.js.map +1 -1
  65. package/internal/hardhat-network/provider/modules/hardhat.d.ts +3 -0
  66. package/internal/hardhat-network/provider/modules/hardhat.d.ts.map +1 -1
  67. package/internal/hardhat-network/provider/modules/hardhat.js +40 -5
  68. package/internal/hardhat-network/provider/modules/hardhat.js.map +1 -1
  69. package/internal/hardhat-network/provider/modules/logger.d.ts +4 -2
  70. package/internal/hardhat-network/provider/modules/logger.d.ts.map +1 -1
  71. package/internal/hardhat-network/provider/modules/logger.js +38 -12
  72. package/internal/hardhat-network/provider/modules/logger.js.map +1 -1
  73. package/internal/hardhat-network/provider/node-types.d.ts +1 -1
  74. package/internal/hardhat-network/provider/node-types.d.ts.map +1 -1
  75. package/internal/hardhat-network/provider/node.d.ts +9 -1
  76. package/internal/hardhat-network/provider/node.d.ts.map +1 -1
  77. package/internal/hardhat-network/provider/node.js +57 -8
  78. package/internal/hardhat-network/provider/node.js.map +1 -1
  79. package/internal/hardhat-network/provider/provider.d.ts +1 -1
  80. package/internal/hardhat-network/provider/provider.d.ts.map +1 -1
  81. package/internal/hardhat-network/provider/provider.js.map +1 -1
  82. package/internal/hardhat-network/provider/types/HardhatBlockchainInterface.d.ts +2 -1
  83. package/internal/hardhat-network/provider/types/HardhatBlockchainInterface.d.ts.map +1 -1
  84. package/internal/hardhat-network/provider/utils/makeForkClient.d.ts.map +1 -1
  85. package/internal/hardhat-network/provider/utils/makeForkClient.js +2 -1
  86. package/internal/hardhat-network/provider/utils/makeForkClient.js.map +1 -1
  87. package/internal/hardhat-network/provider/utils/putGenesisBlock.js +1 -1
  88. package/internal/hardhat-network/stack-traces/debug.js +1 -1
  89. package/internal/hardhat-network/stack-traces/debug.js.map +1 -1
  90. package/internal/hardhat-network/stack-traces/error-inferrer.d.ts.map +1 -1
  91. package/internal/hardhat-network/stack-traces/error-inferrer.js +21 -12
  92. package/internal/hardhat-network/stack-traces/error-inferrer.js.map +1 -1
  93. package/internal/hardhat-network/stack-traces/solidity-errors.js +2 -2
  94. package/internal/hardhat-network/stack-traces/solidity-errors.js.map +1 -1
  95. package/internal/hardhat-network/stack-traces/solidity-stack-trace.d.ts +3 -2
  96. package/internal/hardhat-network/stack-traces/solidity-stack-trace.d.ts.map +1 -1
  97. package/internal/solidity/compilation-job.d.ts.map +1 -1
  98. package/internal/solidity/compilation-job.js.map +1 -1
  99. package/internal/solidity/compiler/downloader.d.ts +2 -2
  100. package/internal/solidity/compiler/downloader.d.ts.map +1 -1
  101. package/internal/solidity/compiler/downloader.js +3 -3
  102. package/internal/solidity/compiler/downloader.js.map +1 -1
  103. package/internal/util/download.d.ts.map +1 -1
  104. package/internal/util/download.js +22 -24
  105. package/internal/util/download.js.map +1 -1
  106. package/internal/util/glob.d.ts.map +1 -1
  107. package/internal/util/glob.js.map +1 -1
  108. package/internal/util/global-dir.d.ts.map +1 -1
  109. package/internal/util/global-dir.js.map +1 -1
  110. package/internal/util/keys-derivation.d.ts +1 -1
  111. package/internal/util/keys-derivation.d.ts.map +1 -1
  112. package/internal/util/keys-derivation.js +2 -2
  113. package/internal/util/keys-derivation.js.map +1 -1
  114. package/package.json +10 -10
  115. package/sample-projects/advanced-ts/README.md +1 -1
  116. package/src/builtin-tasks/compile.ts +94 -76
  117. package/src/builtin-tasks/node.ts +2 -1
  118. package/src/builtin-tasks/test.ts +69 -11
  119. package/src/builtin-tasks/utils/solidity-files-cache.ts +3 -2
  120. package/src/internal/cli/analytics.ts +2 -2
  121. package/src/internal/cli/cli.ts +3 -3
  122. package/src/internal/cli/project-creation.ts +40 -0
  123. package/src/internal/core/config/config-loading.ts +2 -1
  124. package/src/internal/core/config/config-validation.ts +2 -0
  125. package/src/internal/core/config/default-config.ts +1 -0
  126. package/src/internal/core/errors-list.ts +13 -0
  127. package/src/internal/core/providers/accounts.ts +4 -2
  128. package/src/internal/core/providers/construction.ts +3 -1
  129. package/src/internal/core/providers/http.ts +47 -16
  130. package/src/internal/core/providers/util.ts +6 -3
  131. package/src/internal/hardhat-network/jsonrpc/server.ts +16 -3
  132. package/src/internal/hardhat-network/provider/BlockchainBase.ts +137 -0
  133. package/src/internal/hardhat-network/provider/BlockchainData.ts +144 -0
  134. package/src/internal/hardhat-network/provider/HardhatBlockchain.ts +34 -87
  135. package/src/internal/hardhat-network/provider/fork/ForkBlockchain.ts +45 -90
  136. package/src/internal/hardhat-network/provider/modules/eth.ts +2 -2
  137. package/src/internal/hardhat-network/provider/modules/hardhat.ts +51 -5
  138. package/src/internal/hardhat-network/provider/modules/logger.ts +50 -14
  139. package/src/internal/hardhat-network/provider/node-types.ts +2 -2
  140. package/src/internal/hardhat-network/provider/node.ts +81 -8
  141. package/src/internal/hardhat-network/provider/provider.ts +9 -8
  142. package/src/internal/hardhat-network/provider/types/HardhatBlockchainInterface.ts +7 -1
  143. package/src/internal/hardhat-network/provider/utils/makeForkClient.ts +2 -1
  144. package/src/internal/hardhat-network/provider/utils/putGenesisBlock.ts +1 -1
  145. package/src/internal/hardhat-network/stack-traces/debug.ts +1 -1
  146. package/src/internal/hardhat-network/stack-traces/error-inferrer.ts +21 -13
  147. package/src/internal/hardhat-network/stack-traces/solidity-errors.ts +2 -2
  148. package/src/internal/hardhat-network/stack-traces/solidity-stack-trace.ts +3 -2
  149. package/src/internal/solidity/compilation-job.ts +2 -1
  150. package/src/internal/solidity/compiler/downloader.ts +5 -3
  151. package/src/internal/util/download.ts +26 -31
  152. package/src/internal/util/glob.ts +1 -0
  153. package/src/internal/util/global-dir.ts +2 -1
  154. package/src/internal/util/keys-derivation.ts +3 -2
  155. package/src/types/config.ts +4 -0
  156. package/types/config.d.ts +4 -0
  157. package/types/config.d.ts.map +1 -1
@@ -216,6 +216,19 @@ If you were trying to create a new project, please try again using Windows Subsy
216
216
  You can learn how to use Hardhat by reading the [Getting Started guide](../getting-started).`,
217
217
  shouldBeReported: false,
218
218
  },
219
+ CONFLICTING_FILES: {
220
+ number: 16,
221
+ message: `The directory %dest% contains files that could conflict:
222
+
223
+ %conflicts%
224
+
225
+ Either try using a new directory, or remove the files listed above.`,
226
+ title: "conflicting files during project creation",
227
+ description: `You are trying to create a new hardhat project, but there are existing files that would be overwritten by the creation process.
228
+
229
+ Either try using a new directory name, or remove the conflicting files.`,
230
+ shouldBeReported: false,
231
+ },
219
232
  },
220
233
  NETWORK: {
221
234
  CONFIG_NOT_FOUND: {
@@ -307,13 +307,15 @@ export class HDWalletProvider extends LocalAccountsProvider {
307
307
  mnemonic: string,
308
308
  hdpath: string = "m/44'/60'/0'/0/",
309
309
  initialIndex: number = 0,
310
- count: number = 10
310
+ count: number = 10,
311
+ passphrase: string = ""
311
312
  ) {
312
313
  const privateKeys = derivePrivateKeys(
313
314
  mnemonic,
314
315
  hdpath,
315
316
  initialIndex,
316
- count
317
+ count,
318
+ passphrase
317
319
  );
318
320
 
319
321
  const { bufferToHex } = require("ethereumjs-util");
@@ -11,6 +11,7 @@ import type {
11
11
  NetworkConfig,
12
12
  ProjectPathsConfig,
13
13
  } from "../../../types";
14
+
14
15
  import { HARDHAT_NETWORK_NAME } from "../../constants";
15
16
  import { ModulesLogger } from "../../hardhat-network/provider/modules/logger";
16
17
  import {
@@ -190,7 +191,8 @@ export function applyProviderWrappers(
190
191
  accounts.mnemonic,
191
192
  accounts.path,
192
193
  accounts.initialIndex,
193
- accounts.count
194
+ accounts.count,
195
+ accounts.passphrase
194
196
  );
195
197
  }
196
198
 
@@ -1,5 +1,5 @@
1
1
  import { EventEmitter } from "events";
2
- import type { Response } from "node-fetch";
2
+ import { Dispatcher, Pool } from "undici";
3
3
 
4
4
  import { EIP1193Provider, RequestArguments } from "../../../types";
5
5
  import {
@@ -13,6 +13,7 @@ import {
13
13
  parseJsonResponse,
14
14
  SuccessfulJsonRpcResponse,
15
15
  } from "../../util/jsonrpc";
16
+ import { getHardhatVersion } from "../../util/packageInfo";
16
17
  import { HardhatError } from "../errors";
17
18
  import { ERRORS } from "../errors-list";
18
19
 
@@ -27,16 +28,40 @@ const MAX_RETRY_AWAIT_SECONDS = 5;
27
28
 
28
29
  const TOO_MANY_REQUEST_STATUS = 429;
29
30
 
31
+ const hardhatVersion = getHardhatVersion();
32
+
30
33
  export class HttpProvider extends EventEmitter implements EIP1193Provider {
31
34
  private _nextRequestId = 1;
35
+ private _dispatcher: Dispatcher;
36
+ private _path: string;
37
+ private _authHeader: string | undefined;
32
38
 
33
39
  constructor(
34
40
  private readonly _url: string,
35
41
  private readonly _networkName: string,
36
42
  private readonly _extraHeaders: { [name: string]: string } = {},
37
- private readonly _timeout = 20000
43
+ private readonly _timeout = 20000,
44
+ client: Dispatcher | undefined = undefined
38
45
  ) {
39
46
  super();
47
+ const url = new URL(this._url);
48
+ this._path = url.pathname;
49
+ this._authHeader =
50
+ url.username === ""
51
+ ? undefined
52
+ : `Basic ${Buffer.from(
53
+ `${url.username}:${url.password}`,
54
+ "utf-8"
55
+ ).toString("base64")}`;
56
+ try {
57
+ this._dispatcher = client ?? new Pool(url.origin);
58
+ } catch (e) {
59
+ if (e instanceof TypeError && e.message === "Invalid URL") {
60
+ e.message += ` ${url.origin}`;
61
+ }
62
+ // eslint-disable-next-line @nomiclabs/hardhat-internal-rules/only-hardhat-error
63
+ throw e;
64
+ }
40
65
  }
41
66
 
42
67
  public get url(): string {
@@ -134,28 +159,32 @@ export class HttpProvider extends EventEmitter implements EIP1193Provider {
134
159
  request: JsonRpcRequest | JsonRpcRequest[],
135
160
  retryNumber = 0
136
161
  ): Promise<JsonRpcResponse | JsonRpcResponse[]> {
137
- const { default: fetch } = await import("node-fetch");
138
-
139
162
  try {
140
- const response = await fetch(this._url, {
163
+ const response = await this._dispatcher.request({
141
164
  method: "POST",
165
+ path: this._path,
142
166
  body: JSON.stringify(request),
143
- redirect: "follow",
144
- timeout:
167
+ maxRedirections: 10,
168
+ headersTimeout:
145
169
  process.env.DO_NOT_SET_THIS_ENV_VAR____IS_HARDHAT_CI !== undefined
146
170
  ? 0
147
171
  : this._timeout,
148
172
  headers: {
149
173
  "Content-Type": "application/json",
174
+ "User-Agent": `hardhat ${hardhatVersion}`,
175
+ Authorization: this._authHeader,
150
176
  ...this._extraHeaders,
151
177
  },
152
178
  });
153
179
 
154
180
  if (this._isRateLimitResponse(response)) {
155
- // Consume the response stream and discard its result
156
- // See: https://github.com/node-fetch/node-fetch/issues/83
157
- const _discarded = await response.text();
158
-
181
+ // "The Fetch Standard allows users to skip consuming the response body
182
+ // by relying on garbage collection to release connection resources.
183
+ // Undici does not do the same. Therefore, it is important to always
184
+ // either consume or cancel the response body."
185
+ // https://undici.nodejs.org/#/?id=garbage-collection
186
+ // It's not clear how to "cancel", so we'll just consume:
187
+ await response.body.text();
159
188
  const seconds = this._getRetryAfterSeconds(response);
160
189
  if (seconds !== undefined && this._shouldRetry(retryNumber, seconds)) {
161
190
  return await this._retry(request, seconds, retryNumber);
@@ -170,7 +199,7 @@ export class HttpProvider extends EventEmitter implements EIP1193Provider {
170
199
  );
171
200
  }
172
201
 
173
- return parseJsonResponse(await response.text());
202
+ return parseJsonResponse(await response.body.text());
174
203
  } catch (error: any) {
175
204
  if (error.code === "ECONNREFUSED") {
176
205
  throw new HardhatError(
@@ -222,12 +251,14 @@ export class HttpProvider extends EventEmitter implements EIP1193Provider {
222
251
  return true;
223
252
  }
224
253
 
225
- private _isRateLimitResponse(response: Response) {
226
- return response.status === TOO_MANY_REQUEST_STATUS;
254
+ private _isRateLimitResponse(response: Dispatcher.ResponseData) {
255
+ return response.statusCode === TOO_MANY_REQUEST_STATUS;
227
256
  }
228
257
 
229
- private _getRetryAfterSeconds(response: Response): number | undefined {
230
- const header = response.headers.get("Retry-After");
258
+ private _getRetryAfterSeconds(
259
+ response: Dispatcher.ResponseData
260
+ ): number | undefined {
261
+ const header = response.headers["retry-after"];
231
262
 
232
263
  if (header === undefined || header === null) {
233
264
  return undefined;
@@ -13,7 +13,8 @@ export function derivePrivateKeys(
13
13
  mnemonic: string,
14
14
  hdpath: string,
15
15
  initialIndex: number,
16
- count: number
16
+ count: number,
17
+ passphrase: string
17
18
  ): Buffer[] {
18
19
  if (hdpath.match(HD_PATH_REGEX) === null) {
19
20
  throw new HardhatError(ERRORS.NETWORK.INVALID_HD_PATH, { path: hdpath });
@@ -28,7 +29,8 @@ export function derivePrivateKeys(
28
29
  for (let i = initialIndex; i < initialIndex + count; i++) {
29
30
  const privateKey = deriveKeyFromMnemonicAndPath(
30
31
  mnemonic,
31
- hdpath + i.toString()
32
+ hdpath + i.toString(),
33
+ passphrase
32
34
  );
33
35
 
34
36
  if (privateKey === undefined) {
@@ -57,7 +59,8 @@ export function normalizeHardhatNetworkAccountsConfig(
57
59
  accountsConfig.mnemonic,
58
60
  accountsConfig.path,
59
61
  accountsConfig.initialIndex,
60
- accountsConfig.count
62
+ accountsConfig.count,
63
+ accountsConfig.passphrase
61
64
  ).map((pk) => ({
62
65
  privateKey: bufferToHex(pk),
63
66
  balance: accountsConfig.accountsBalance ?? DEFAULT_HARDHAT_NETWORK_BALANCE,
@@ -1,7 +1,9 @@
1
+ import type WsT from "ws";
2
+
1
3
  import debug from "debug";
2
4
  import http, { Server } from "http";
3
5
  import { AddressInfo } from "net";
4
- import type WsT from "ws";
6
+ import { Client } from "undici";
5
7
 
6
8
  import {
7
9
  EIP1193Provider,
@@ -42,9 +44,20 @@ export class JsonRpcServer implements IJsonRpcServer {
42
44
  }
43
45
 
44
46
  public getProvider = (name = "json-rpc"): EIP1193Provider => {
45
- const { address, port } = this._httpServer.address() as AddressInfo; // TCP sockets return AddressInfo
47
+ const { address, port } = this._httpServer.address() as AddressInfo;
48
+
49
+ const dispatcher = new Client(`http://${address}:${port}/`, {
50
+ keepAliveTimeout: 10,
51
+ keepAliveMaxTimeout: 10,
52
+ });
46
53
 
47
- return new HttpProvider(`http://${address}:${port}/`, name);
54
+ return new HttpProvider(
55
+ `http://${address}:${port}/`,
56
+ name,
57
+ {},
58
+ 20000,
59
+ dispatcher
60
+ );
48
61
  };
49
62
 
50
63
  public listen = (): Promise<{ address: string; port: number }> => {
@@ -0,0 +1,137 @@
1
+ import { Block } from "@ethereumjs/block";
2
+ import Common from "@ethereumjs/common";
3
+ import { TypedTransaction } from "@ethereumjs/tx";
4
+ import { BN } from "ethereumjs-util";
5
+
6
+ import { assertHardhatInvariant } from "../../core/errors";
7
+ import { BlockchainData } from "./BlockchainData";
8
+ import { RpcReceiptOutput } from "./output";
9
+
10
+ /* eslint-disable @nomiclabs/hardhat-internal-rules/only-hardhat-error */
11
+
12
+ export abstract class BlockchainBase {
13
+ protected readonly _data: BlockchainData;
14
+
15
+ constructor(protected _common: Common) {
16
+ this._data = new BlockchainData(_common);
17
+ }
18
+
19
+ public abstract addBlock(block: Block): Promise<Block>;
20
+
21
+ public addTransactionReceipts(receipts: RpcReceiptOutput[]) {
22
+ for (const receipt of receipts) {
23
+ this._data.addTransactionReceipt(receipt);
24
+ }
25
+ }
26
+
27
+ public async delBlock(blockHash: Buffer) {
28
+ this.deleteBlock(blockHash);
29
+ }
30
+
31
+ public deleteBlock(blockHash: Buffer) {
32
+ const block = this._data.getBlockByHash(blockHash);
33
+ if (block === undefined) {
34
+ throw new Error("Block not found");
35
+ }
36
+ this._delBlock(block.header.number);
37
+ }
38
+
39
+ public async getBlock(
40
+ blockHashOrNumber: Buffer | BN | number
41
+ ): Promise<Block | null> {
42
+ if (
43
+ (typeof blockHashOrNumber === "number" || BN.isBN(blockHashOrNumber)) &&
44
+ this._data.isReservedBlock(new BN(blockHashOrNumber))
45
+ ) {
46
+ this._data.fulfillBlockReservation(new BN(blockHashOrNumber));
47
+ }
48
+
49
+ if (typeof blockHashOrNumber === "number") {
50
+ return this._data.getBlockByNumber(new BN(blockHashOrNumber)) ?? null;
51
+ }
52
+ if (BN.isBN(blockHashOrNumber)) {
53
+ return this._data.getBlockByNumber(blockHashOrNumber) ?? null;
54
+ }
55
+ return this._data.getBlockByHash(blockHashOrNumber) ?? null;
56
+ }
57
+
58
+ public abstract getLatestBlockNumber(): BN;
59
+
60
+ public async getLatestBlock(): Promise<Block> {
61
+ const block = await this.getBlock(this.getLatestBlockNumber());
62
+ if (block === null) {
63
+ throw new Error("Block not found");
64
+ }
65
+ return block;
66
+ }
67
+
68
+ public getLocalTransaction(
69
+ transactionHash: Buffer
70
+ ): TypedTransaction | undefined {
71
+ return this._data.getTransaction(transactionHash);
72
+ }
73
+
74
+ public iterator(
75
+ _name: string,
76
+ _onBlock: (block: Block, reorg: boolean) => void | Promise<void>
77
+ ): Promise<number | void> {
78
+ throw new Error("Method not implemented.");
79
+ }
80
+
81
+ public async putBlock(block: Block): Promise<void> {
82
+ await this.addBlock(block);
83
+ }
84
+
85
+ public reserveBlocks(
86
+ count: BN,
87
+ interval: BN,
88
+ previousBlockStateRoot: Buffer,
89
+ previousBlockTotalDifficulty: BN
90
+ ) {
91
+ this._data.reserveBlocks(
92
+ this.getLatestBlockNumber().addn(1),
93
+ count,
94
+ interval,
95
+ previousBlockStateRoot,
96
+ previousBlockTotalDifficulty
97
+ );
98
+ }
99
+
100
+ protected _delBlock(blockNumber: BN): void {
101
+ let i = blockNumber;
102
+
103
+ while (i.lte(this.getLatestBlockNumber())) {
104
+ if (this._data.isReservedBlock(i)) {
105
+ const reservation = this._data.cancelReservationWithBlock(i);
106
+ i = reservation.last.addn(1);
107
+ } else {
108
+ const current = this._data.getBlockByNumber(i);
109
+ if (current !== undefined) {
110
+ this._data.removeBlock(current);
111
+ }
112
+ i = i.addn(1);
113
+ }
114
+ }
115
+ }
116
+
117
+ protected async _computeTotalDifficulty(block: Block): Promise<BN> {
118
+ const difficulty = block.header.difficulty;
119
+ const blockNumber = block.header.number;
120
+
121
+ if (blockNumber.eqn(0)) {
122
+ return difficulty;
123
+ }
124
+
125
+ const parentBlock = await this.getBlock(blockNumber.subn(1));
126
+ assertHardhatInvariant(parentBlock !== null, "Parent block should exist");
127
+
128
+ const parentHash = parentBlock.hash();
129
+ const parentTD = this._data.getTotalDifficulty(parentHash);
130
+ assertHardhatInvariant(
131
+ parentTD !== undefined,
132
+ "Parent block should have total difficulty"
133
+ );
134
+
135
+ return parentTD.add(difficulty);
136
+ }
137
+ }
@@ -1,12 +1,22 @@
1
1
  import { Block } from "@ethereumjs/block";
2
+ import Common from "@ethereumjs/common";
2
3
  import { TypedTransaction } from "@ethereumjs/tx";
3
4
  import Bloom from "@ethereumjs/vm/dist/bloom";
4
5
  import { BN, bufferToHex } from "ethereumjs-util";
5
6
 
7
+ import { assertHardhatInvariant } from "../../core/errors";
6
8
  import { bloomFilter, filterLogs } from "./filter";
7
9
  import { FilterParams } from "./node-types";
8
10
  import { RpcLogOutput, RpcReceiptOutput } from "./output";
9
11
 
12
+ interface Reservation {
13
+ first: BN;
14
+ last: BN;
15
+ interval: BN;
16
+ previousBlockStateRoot: Buffer;
17
+ previousBlockTotalDifficulty: BN;
18
+ }
19
+
10
20
  export class BlockchainData {
11
21
  private _blocksByNumber: Map<number, Block> = new Map();
12
22
  private _blocksByHash: Map<string, Block> = new Map();
@@ -14,6 +24,26 @@ export class BlockchainData {
14
24
  private _transactions: Map<string, TypedTransaction> = new Map();
15
25
  private _transactionReceipts: Map<string, RpcReceiptOutput> = new Map();
16
26
  private _totalDifficulty: Map<string, BN> = new Map();
27
+ private _blockReservations: Reservation[] = new Array();
28
+
29
+ constructor(private _common: Common) {}
30
+
31
+ public reserveBlocks(
32
+ first: BN,
33
+ count: BN,
34
+ interval: BN,
35
+ previousBlockStateRoot: Buffer,
36
+ previousBlockTotalDifficulty: BN
37
+ ) {
38
+ const reservation: Reservation = {
39
+ first,
40
+ last: first.add(count.subn(1)),
41
+ interval,
42
+ previousBlockStateRoot,
43
+ previousBlockTotalDifficulty,
44
+ };
45
+ this._blockReservations.push(reservation);
46
+ }
17
47
 
18
48
  public getBlockByNumber(blockNumber: BN) {
19
49
  return this._blocksByNumber.get(blockNumber.toNumber());
@@ -88,6 +118,11 @@ export class BlockchainData {
88
118
  }
89
119
  }
90
120
 
121
+ /**
122
+ * WARNING: this method can leave the blockchain in an invalid state where
123
+ * there are gaps between blocks. Ideally we should have a method that removes
124
+ * the given block and all the following blocks.
125
+ */
91
126
  public removeBlock(block: Block) {
92
127
  const blockHash = bufferToHex(block.hash());
93
128
  const blockNumber = new BN(block.header.number).toNumber();
@@ -110,4 +145,113 @@ export class BlockchainData {
110
145
  public addTransactionReceipt(receipt: RpcReceiptOutput) {
111
146
  this._transactionReceipts.set(receipt.transactionHash, receipt);
112
147
  }
148
+
149
+ public isReservedBlock(blockNumber: BN): boolean {
150
+ return this._findBlockReservation(blockNumber) !== -1;
151
+ }
152
+
153
+ private _findBlockReservation(blockNumber: BN): number {
154
+ return this._blockReservations.findIndex(
155
+ (reservation) =>
156
+ reservation.first.lte(blockNumber) && blockNumber.lte(reservation.last)
157
+ );
158
+ }
159
+
160
+ /**
161
+ * WARNING: this method only removes the given reservation and can result in
162
+ * gaps in the reservations array. Ideally we should have a method that
163
+ * removes the given reservation and all the following reservations.
164
+ */
165
+ private _removeReservation(index: number): Reservation {
166
+ assertHardhatInvariant(
167
+ index in this._blockReservations,
168
+ `Reservation ${index} does not exist`
169
+ );
170
+ const reservation = this._blockReservations[index];
171
+
172
+ this._blockReservations.splice(index, 1);
173
+
174
+ return reservation;
175
+ }
176
+
177
+ /**
178
+ * Cancel and return the reservation that has block `blockNumber`
179
+ */
180
+ public cancelReservationWithBlock(blockNumber: BN): Reservation {
181
+ return this._removeReservation(this._findBlockReservation(blockNumber));
182
+ }
183
+
184
+ public fulfillBlockReservation(blockNumber: BN) {
185
+ // in addition to adding the given block, the reservation needs to be split
186
+ // in two in order to accomodate access to the given block.
187
+
188
+ const reservationIndex = this._findBlockReservation(blockNumber);
189
+ assertHardhatInvariant(
190
+ reservationIndex !== -1,
191
+ `No reservation to fill for block number ${blockNumber}`
192
+ );
193
+
194
+ // capture the timestamp before removing the reservation:
195
+ const timestamp = this._calculateTimestampForReservedBlock(blockNumber);
196
+
197
+ // split the block reservation:
198
+ const oldReservation = this._removeReservation(reservationIndex);
199
+
200
+ if (!blockNumber.eq(oldReservation.first)) {
201
+ this._blockReservations.push({
202
+ ...oldReservation,
203
+ last: blockNumber.subn(1),
204
+ });
205
+ }
206
+
207
+ if (!blockNumber.eq(oldReservation.last)) {
208
+ this._blockReservations.push({
209
+ ...oldReservation,
210
+ first: blockNumber.addn(1),
211
+ });
212
+ }
213
+
214
+ this.addBlock(
215
+ Block.fromBlockData(
216
+ {
217
+ header: {
218
+ number: blockNumber,
219
+ stateRoot: oldReservation.previousBlockStateRoot,
220
+ timestamp,
221
+ },
222
+ },
223
+ { common: this._common }
224
+ ),
225
+ oldReservation.previousBlockTotalDifficulty
226
+ );
227
+ }
228
+
229
+ private _calculateTimestampForReservedBlock(blockNumber: BN): BN {
230
+ const reservationIndex = this._findBlockReservation(blockNumber);
231
+
232
+ assertHardhatInvariant(
233
+ reservationIndex !== -1,
234
+ `Block ${blockNumber.toString()} does not lie within any of the reservations.`
235
+ );
236
+
237
+ const reservation = this._blockReservations[reservationIndex];
238
+
239
+ const blockNumberBeforeReservation = reservation.first.subn(1);
240
+
241
+ const blockBeforeReservation = this.getBlockByNumber(
242
+ blockNumberBeforeReservation
243
+ );
244
+ assertHardhatInvariant(
245
+ blockBeforeReservation !== undefined,
246
+ `Reservation after block ${blockNumberBeforeReservation.toString()} cannot be created because that block does not exist`
247
+ );
248
+
249
+ const previousTimestamp = this.isReservedBlock(blockNumberBeforeReservation)
250
+ ? this._calculateTimestampForReservedBlock(blockNumberBeforeReservation)
251
+ : blockBeforeReservation.header.timestamp;
252
+
253
+ return previousTimestamp.add(
254
+ reservation.interval.mul(blockNumber.sub(reservation.first).addn(1))
255
+ );
256
+ }
113
257
  }