hardhat 2.8.4 → 2.9.0-dev.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 (162) 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/jsonrpc/types/input/blockTag.d.ts +3 -3
  30. package/internal/core/jsonrpc/types/input/blockTag.d.ts.map +1 -1
  31. package/internal/core/providers/accounts.d.ts +1 -1
  32. package/internal/core/providers/accounts.d.ts.map +1 -1
  33. package/internal/core/providers/accounts.js +2 -2
  34. package/internal/core/providers/accounts.js.map +1 -1
  35. package/internal/core/providers/construction.d.ts.map +1 -1
  36. package/internal/core/providers/construction.js +1 -1
  37. package/internal/core/providers/construction.js.map +1 -1
  38. package/internal/core/providers/http.d.ts +5 -1
  39. package/internal/core/providers/http.d.ts.map +1 -1
  40. package/internal/core/providers/http.js +35 -31
  41. package/internal/core/providers/http.js.map +1 -1
  42. package/internal/core/providers/util.d.ts +1 -1
  43. package/internal/core/providers/util.d.ts.map +1 -1
  44. package/internal/core/providers/util.js +3 -3
  45. package/internal/core/providers/util.js.map +1 -1
  46. package/internal/hardhat-network/jsonrpc/server.d.ts.map +1 -1
  47. package/internal/hardhat-network/jsonrpc/server.js.map +1 -1
  48. package/internal/hardhat-network/provider/BlockchainBase.d.ts +26 -0
  49. package/internal/hardhat-network/provider/BlockchainBase.d.ts.map +1 -0
  50. package/internal/hardhat-network/provider/BlockchainBase.js +92 -0
  51. package/internal/hardhat-network/provider/BlockchainBase.js.map +1 -0
  52. package/internal/hardhat-network/provider/BlockchainData.d.ts +32 -0
  53. package/internal/hardhat-network/provider/BlockchainData.d.ts.map +1 -1
  54. package/internal/hardhat-network/provider/BlockchainData.js +78 -1
  55. package/internal/hardhat-network/provider/BlockchainData.js.map +1 -1
  56. package/internal/hardhat-network/provider/HardhatBlockchain.d.ts +9 -15
  57. package/internal/hardhat-network/provider/HardhatBlockchain.d.ts.map +1 -1
  58. package/internal/hardhat-network/provider/HardhatBlockchain.js +15 -73
  59. package/internal/hardhat-network/provider/HardhatBlockchain.js.map +1 -1
  60. package/internal/hardhat-network/provider/fork/ForkBlockchain.d.ts +6 -14
  61. package/internal/hardhat-network/provider/fork/ForkBlockchain.d.ts.map +1 -1
  62. package/internal/hardhat-network/provider/fork/ForkBlockchain.js +23 -73
  63. package/internal/hardhat-network/provider/fork/ForkBlockchain.js.map +1 -1
  64. package/internal/hardhat-network/provider/modules/eth.js +2 -2
  65. package/internal/hardhat-network/provider/modules/eth.js.map +1 -1
  66. package/internal/hardhat-network/provider/modules/hardhat.d.ts +3 -0
  67. package/internal/hardhat-network/provider/modules/hardhat.d.ts.map +1 -1
  68. package/internal/hardhat-network/provider/modules/hardhat.js +40 -5
  69. package/internal/hardhat-network/provider/modules/hardhat.js.map +1 -1
  70. package/internal/hardhat-network/provider/modules/logger.d.ts +4 -2
  71. package/internal/hardhat-network/provider/modules/logger.d.ts.map +1 -1
  72. package/internal/hardhat-network/provider/modules/logger.js +38 -12
  73. package/internal/hardhat-network/provider/modules/logger.js.map +1 -1
  74. package/internal/hardhat-network/provider/node-types.d.ts +1 -1
  75. package/internal/hardhat-network/provider/node-types.d.ts.map +1 -1
  76. package/internal/hardhat-network/provider/node.d.ts +9 -1
  77. package/internal/hardhat-network/provider/node.d.ts.map +1 -1
  78. package/internal/hardhat-network/provider/node.js +57 -8
  79. package/internal/hardhat-network/provider/node.js.map +1 -1
  80. package/internal/hardhat-network/provider/provider.d.ts +1 -1
  81. package/internal/hardhat-network/provider/provider.d.ts.map +1 -1
  82. package/internal/hardhat-network/provider/provider.js.map +1 -1
  83. package/internal/hardhat-network/provider/types/HardhatBlockchainInterface.d.ts +2 -1
  84. package/internal/hardhat-network/provider/types/HardhatBlockchainInterface.d.ts.map +1 -1
  85. package/internal/hardhat-network/provider/utils/makeForkClient.d.ts.map +1 -1
  86. package/internal/hardhat-network/provider/utils/makeForkClient.js +2 -1
  87. package/internal/hardhat-network/provider/utils/makeForkClient.js.map +1 -1
  88. package/internal/hardhat-network/provider/utils/putGenesisBlock.js +1 -1
  89. package/internal/hardhat-network/stack-traces/debug.js +1 -1
  90. package/internal/hardhat-network/stack-traces/debug.js.map +1 -1
  91. package/internal/hardhat-network/stack-traces/error-inferrer.d.ts.map +1 -1
  92. package/internal/hardhat-network/stack-traces/error-inferrer.js +21 -12
  93. package/internal/hardhat-network/stack-traces/error-inferrer.js.map +1 -1
  94. package/internal/hardhat-network/stack-traces/solidity-errors.js +2 -2
  95. package/internal/hardhat-network/stack-traces/solidity-errors.js.map +1 -1
  96. package/internal/hardhat-network/stack-traces/solidity-stack-trace.d.ts +3 -2
  97. package/internal/hardhat-network/stack-traces/solidity-stack-trace.d.ts.map +1 -1
  98. package/internal/solidity/compilation-job.d.ts.map +1 -1
  99. package/internal/solidity/compilation-job.js.map +1 -1
  100. package/internal/solidity/compiler/downloader.d.ts +2 -2
  101. package/internal/solidity/compiler/downloader.d.ts.map +1 -1
  102. package/internal/solidity/compiler/downloader.js +3 -3
  103. package/internal/solidity/compiler/downloader.js.map +1 -1
  104. package/internal/util/chunked-promise-all.d.ts +7 -0
  105. package/internal/util/chunked-promise-all.d.ts.map +1 -0
  106. package/internal/util/chunked-promise-all.js +31 -0
  107. package/internal/util/chunked-promise-all.js.map +1 -0
  108. package/internal/util/download.d.ts.map +1 -1
  109. package/internal/util/download.js +22 -24
  110. package/internal/util/download.js.map +1 -1
  111. package/internal/util/glob.d.ts.map +1 -1
  112. package/internal/util/glob.js.map +1 -1
  113. package/internal/util/global-dir.d.ts.map +1 -1
  114. package/internal/util/global-dir.js.map +1 -1
  115. package/internal/util/keys-derivation.d.ts +1 -1
  116. package/internal/util/keys-derivation.d.ts.map +1 -1
  117. package/internal/util/keys-derivation.js +2 -2
  118. package/internal/util/keys-derivation.js.map +1 -1
  119. package/package.json +10 -10
  120. package/sample-projects/advanced-ts/README.md +1 -1
  121. package/src/builtin-tasks/compile.ts +94 -76
  122. package/src/builtin-tasks/node.ts +2 -1
  123. package/src/builtin-tasks/test.ts +69 -11
  124. package/src/builtin-tasks/utils/solidity-files-cache.ts +3 -2
  125. package/src/internal/cli/analytics.ts +2 -2
  126. package/src/internal/cli/cli.ts +3 -3
  127. package/src/internal/cli/project-creation.ts +40 -0
  128. package/src/internal/core/config/config-loading.ts +2 -1
  129. package/src/internal/core/config/config-validation.ts +2 -0
  130. package/src/internal/core/config/default-config.ts +1 -0
  131. package/src/internal/core/errors-list.ts +13 -0
  132. package/src/internal/core/providers/accounts.ts +4 -2
  133. package/src/internal/core/providers/construction.ts +3 -1
  134. package/src/internal/core/providers/http.ts +47 -16
  135. package/src/internal/core/providers/util.ts +6 -3
  136. package/src/internal/hardhat-network/jsonrpc/server.ts +2 -1
  137. package/src/internal/hardhat-network/provider/BlockchainBase.ts +137 -0
  138. package/src/internal/hardhat-network/provider/BlockchainData.ts +144 -0
  139. package/src/internal/hardhat-network/provider/HardhatBlockchain.ts +34 -87
  140. package/src/internal/hardhat-network/provider/fork/ForkBlockchain.ts +45 -90
  141. package/src/internal/hardhat-network/provider/modules/eth.ts +2 -2
  142. package/src/internal/hardhat-network/provider/modules/hardhat.ts +51 -5
  143. package/src/internal/hardhat-network/provider/modules/logger.ts +50 -14
  144. package/src/internal/hardhat-network/provider/node-types.ts +2 -2
  145. package/src/internal/hardhat-network/provider/node.ts +81 -8
  146. package/src/internal/hardhat-network/provider/provider.ts +9 -8
  147. package/src/internal/hardhat-network/provider/types/HardhatBlockchainInterface.ts +7 -1
  148. package/src/internal/hardhat-network/provider/utils/makeForkClient.ts +2 -1
  149. package/src/internal/hardhat-network/provider/utils/putGenesisBlock.ts +1 -1
  150. package/src/internal/hardhat-network/stack-traces/debug.ts +1 -1
  151. package/src/internal/hardhat-network/stack-traces/error-inferrer.ts +21 -13
  152. package/src/internal/hardhat-network/stack-traces/solidity-errors.ts +2 -2
  153. package/src/internal/hardhat-network/stack-traces/solidity-stack-trace.ts +3 -2
  154. package/src/internal/solidity/compilation-job.ts +2 -1
  155. package/src/internal/solidity/compiler/downloader.ts +5 -3
  156. package/src/internal/util/download.ts +26 -31
  157. package/src/internal/util/glob.ts +1 -0
  158. package/src/internal/util/global-dir.ts +2 -1
  159. package/src/internal/util/keys-derivation.ts +3 -2
  160. package/src/types/config.ts +4 -0
  161. package/types/config.d.ts +4 -0
  162. package/types/config.d.ts.map +1 -1
@@ -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,8 @@
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";
5
6
 
6
7
  import {
7
8
  EIP1193Provider,
@@ -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
  }
@@ -1,60 +1,50 @@
1
1
  import { Block } from "@ethereumjs/block";
2
+ import Common from "@ethereumjs/common";
2
3
  import { TypedTransaction } from "@ethereumjs/tx";
3
4
  import { BN, zeros } from "ethereumjs-util";
4
5
 
5
- import { BlockchainData } from "./BlockchainData";
6
+ import { BlockchainBase } from "./BlockchainBase";
6
7
  import { FilterParams } from "./node-types";
7
- import { RpcLogOutput, RpcReceiptOutput } from "./output";
8
+ import { RpcLogOutput } from "./output";
8
9
  import { HardhatBlockchainInterface } from "./types/HardhatBlockchainInterface";
9
10
 
10
11
  /* eslint-disable @nomiclabs/hardhat-internal-rules/only-hardhat-error */
11
12
 
12
- export class HardhatBlockchain implements HardhatBlockchainInterface {
13
- private readonly _data = new BlockchainData();
13
+ export class HardhatBlockchain
14
+ extends BlockchainBase
15
+ implements HardhatBlockchainInterface
16
+ {
14
17
  private _length = 0;
15
18
 
16
- public async getLatestBlock(): Promise<Block> {
17
- const block = this._data.getBlockByNumber(new BN(this._length - 1));
18
- if (block === undefined) {
19
- throw new Error("No block available");
20
- }
21
- return block;
19
+ constructor(common: Common) {
20
+ super(common);
22
21
  }
23
22
 
24
- public async getBlock(
25
- blockHashOrNumber: Buffer | BN | number
26
- ): Promise<Block | null> {
27
- if (typeof blockHashOrNumber === "number") {
28
- return this._data.getBlockByNumber(new BN(blockHashOrNumber)) ?? null;
29
- }
30
- if (BN.isBN(blockHashOrNumber)) {
31
- return this._data.getBlockByNumber(blockHashOrNumber) ?? null;
32
- }
33
- return this._data.getBlockByHash(blockHashOrNumber) ?? null;
23
+ public getLatestBlockNumber(): BN {
24
+ return new BN(this._length - 1);
34
25
  }
35
26
 
36
27
  public async addBlock(block: Block): Promise<Block> {
37
28
  this._validateBlock(block);
38
- const totalDifficulty = this._computeTotalDifficulty(block);
29
+ const totalDifficulty = await this._computeTotalDifficulty(block);
39
30
  this._data.addBlock(block, totalDifficulty);
40
31
  this._length += 1;
41
32
  return block;
42
33
  }
43
34
 
44
- public async putBlock(block: Block): Promise<void> {
45
- await this.addBlock(block);
46
- }
47
-
48
- public deleteBlock(blockHash: Buffer) {
49
- const block = this._data.getBlockByHash(blockHash);
50
- if (block === undefined) {
51
- throw new Error("Block not found");
52
- }
53
- this._delBlock(block);
54
- }
55
-
56
- public async delBlock(blockHash: Buffer) {
57
- this.deleteBlock(blockHash);
35
+ public reserveBlocks(
36
+ count: BN,
37
+ interval: BN,
38
+ previousBlockStateRoot: Buffer,
39
+ previousBlockTotalDifficulty: BN
40
+ ) {
41
+ super.reserveBlocks(
42
+ count,
43
+ interval,
44
+ previousBlockStateRoot,
45
+ previousBlockTotalDifficulty
46
+ );
47
+ this._length = this._length + count.toNumber();
58
48
  }
59
49
 
60
50
  public deleteLaterBlocks(block: Block): void {
@@ -62,12 +52,8 @@ export class HardhatBlockchain implements HardhatBlockchainInterface {
62
52
  if (actual === undefined) {
63
53
  throw new Error("Invalid block");
64
54
  }
65
- const nextBlock = this._data.getBlockByNumber(
66
- new BN(actual.header.number).addn(1)
67
- );
68
- if (nextBlock !== undefined) {
69
- this._delBlock(nextBlock);
70
- }
55
+
56
+ this._delBlock(actual.header.number.addn(1));
71
57
  }
72
58
 
73
59
  public async getTotalDifficulty(blockHash: Buffer): Promise<BN> {
@@ -84,12 +70,6 @@ export class HardhatBlockchain implements HardhatBlockchainInterface {
84
70
  return this.getLocalTransaction(transactionHash);
85
71
  }
86
72
 
87
- public getLocalTransaction(
88
- transactionHash: Buffer
89
- ): TypedTransaction | undefined {
90
- return this._data.getTransaction(transactionHash);
91
- }
92
-
93
73
  public async getBlockByTransactionHash(
94
74
  transactionHash: Buffer
95
75
  ): Promise<Block | null> {
@@ -101,36 +81,21 @@ export class HardhatBlockchain implements HardhatBlockchainInterface {
101
81
  return this._data.getTransactionReceipt(transactionHash) ?? null;
102
82
  }
103
83
 
104
- public addTransactionReceipts(receipts: RpcReceiptOutput[]) {
105
- for (const receipt of receipts) {
106
- this._data.addTransactionReceipt(receipt);
107
- }
108
- }
109
-
110
84
  public async getLogs(filterParams: FilterParams): Promise<RpcLogOutput[]> {
111
85
  return this._data.getLogs(filterParams);
112
86
  }
113
87
 
114
- public iterator(
115
- _name: string,
116
- _onBlock: (block: Block, reorg: boolean) => void | Promise<void>
117
- ): Promise<number | void> {
118
- throw new Error("Method not implemented.");
119
- }
120
-
121
- public async getBaseFee(): Promise<BN> {
122
- const latestBlock = await this.getLatestBlock();
123
- return latestBlock.header.calcNextBaseFee();
124
- }
125
-
126
88
  private _validateBlock(block: Block) {
127
89
  const blockNumber = block.header.number.toNumber();
128
90
  const parentHash = block.header.parentHash;
129
91
  const parent = this._data.getBlockByNumber(new BN(blockNumber - 1));
130
92
 
131
93
  if (this._length !== blockNumber) {
132
- throw new Error("Invalid block number");
94
+ throw new Error(
95
+ `Invalid block number ${blockNumber}. Expected ${this._length}.`
96
+ );
133
97
  }
98
+
134
99
  if (
135
100
  (blockNumber === 0 && !parentHash.equals(zeros(32))) ||
136
101
  (blockNumber > 0 &&
@@ -141,26 +106,8 @@ export class HardhatBlockchain implements HardhatBlockchainInterface {
141
106
  }
142
107
  }
143
108
 
144
- private _computeTotalDifficulty(block: Block): BN {
145
- const difficulty = new BN(block.header.difficulty);
146
- if (block.header.parentHash.equals(zeros(32))) {
147
- return difficulty;
148
- }
149
- const parentTD = this._data.getTotalDifficulty(block.header.parentHash);
150
- if (parentTD === undefined) {
151
- throw new Error("This should never happen");
152
- }
153
- return parentTD.add(difficulty);
154
- }
155
-
156
- private _delBlock(block: Block): void {
157
- const blockNumber = block.header.number.toNumber();
158
- for (let i = blockNumber; i < this._length; i++) {
159
- const current = this._data.getBlockByNumber(new BN(i));
160
- if (current !== undefined) {
161
- this._data.removeBlock(current);
162
- }
163
- }
164
- this._length = blockNumber;
109
+ protected _delBlock(blockNumber: BN): void {
110
+ super._delBlock(blockNumber);
111
+ this._length = blockNumber.toNumber();
165
112
  }
166
113
  }