Skip to content

Commit

Permalink
Merge branch 'master' into run-block-as-test
Browse files Browse the repository at this point in the history
  • Loading branch information
fvictorio committed Dec 15, 2020
2 parents 73975c8 + 28ad393 commit d821012
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Block } from "../types/Block";
import { Blockchain } from "../types/Blockchain";
import { PBlockchain, toBlockchain } from "../types/PBlockchain";

import { ForkTransaction } from "./ForkTransaction";
import { rpcToBlockData } from "./rpcToBlockData";
import { rpcToTxData } from "./rpcToTxData";

Expand Down Expand Up @@ -227,7 +228,24 @@ export class ForkBlockchain implements PBlockchain {
) {
return undefined;
}
const block = new Block(rpcToBlockData(rpcBlock), { common: this._common });

// we don't include the transactions to add our own custom ForkTransaction txs
const blockData = rpcToBlockData({
...rpcBlock,
transactions: [],
});

const block = new Block(blockData, { common: this._common });
const chainId = this._jsonRpcClient.getNetworkId();

for (const transaction of rpcBlock.transactions) {
block.transactions.push(
new ForkTransaction(chainId, rpcToTxData(transaction), {
common: this._common,
})
);
}

this._data.addBlock(block, rpcBlock.totalDifficulty);
return block;
}
Expand Down Expand Up @@ -277,10 +295,18 @@ export class ForkBlockchain implements PBlockchain {
) {
return undefined;
}
const transaction = new Transaction(rpcToTxData(rpcTransaction), {
common: this._common,
});

const chainId = this._jsonRpcClient.getNetworkId();
const transaction = new ForkTransaction(
chainId,
rpcToTxData(rpcTransaction),
{
common: this._common,
}
);

this._data.addTransaction(transaction);

return transaction;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
BufferLike,
PrefixedHexString,
Transaction,
TransactionOptions,
TxData,
} from "ethereumjs-tx";
import { BN, bufferToInt, ecrecover } from "ethereumjs-util";

import { InternalError } from "../errors";

// tslint:disable only-hardhat-error

/**
* Custom Transaction class to avoid EIP155 errors when hardhat is forked
*/
export class ForkTransaction extends Transaction {
private readonly _chainId: number;

constructor(
chainId: number,
data: Buffer | PrefixedHexString | BufferLike[] | TxData = {},
opts: TransactionOptions = {}
) {
super(data, opts);

this._chainId = chainId;

const msgHash = this.hash(false);
const v = bufferToInt(this.v);

const senderPubKey = ecrecover(
msgHash,
v,
this.r,
this.s,
(this as any)._implementsEIP155() ? chainId : undefined
);

(this as any)._senderPubKey = senderPubKey;
}

public verifySignature(): boolean {
return true;
}

public getChainId(): number {
return this._chainId;
}

public sign() {
throw new InternalError("`sign` is not implemented in ForkTransaction");
}
public getDataFee(): BN {
throw new InternalError(
"`getDataFee` is not implemented in ForkTransaction"
);
}
public getBaseFee(): BN {
throw new InternalError(
"`getBaseFee` is not implemented in ForkTransaction"
);
}
public getUpfrontCost(): BN {
throw new InternalError(
"`getUpfrontCost` is not implemented in ForkTransaction"
);
}

public validate(_?: false): boolean;
public validate(_: true): string;
public validate(_: boolean = false): boolean | string {
throw new InternalError("`validate` is not implemented in ForkTransaction");
}

public toCreationAddress(): boolean {
throw new InternalError(
"`toCreationAddress` is not implemented in ForkTransaction"
);
}
}

// override private methods
const ForkTransactionPrototype: any = ForkTransaction.prototype;

// make _validateV a noop
ForkTransactionPrototype._validateV = function () {};

ForkTransactionPrototype._implementsEIP155 = function (): boolean {
const chainId = this.getChainId();
const v = bufferToInt(this.v);

return v === chainId * 2 + 35 || v === chainId * 2 + 36;
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,25 @@ import { Block } from "./types/Block";
export type NodeConfig = LocalNodeConfig | ForkedNodeConfig;

interface CommonConfig {
allowUnlimitedContractSize?: boolean;
blockGasLimit: number;
chainId: number;
genesisAccounts: GenesisAccount[];
allowUnlimitedContractSize?: boolean;
tracingConfig?: TracingConfig;
}

export interface LocalNodeConfig extends CommonConfig {
type: "local";
hardfork: string;
networkName: string;
chainId: number;
networkId: number;
initialDate?: Date;
networkId: number;
networkName: string;
tracingConfig?: TracingConfig;
}

export type LocalNodeConfig = CommonConfig;

export interface ForkConfig {
jsonRpcUrl: string;
blockNumber?: number;
}

export interface ForkedNodeConfig extends CommonConfig {
type: "forked";
forkConfig: ForkConfig;
forkCachePath?: string;
}
Expand Down
54 changes: 42 additions & 12 deletions packages/hardhat-core/src/internal/hardhat-network/provider/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import EventEmitter from "events";

import { CompilerInput, CompilerOutput } from "../../../types";
import { HARDHAT_NETWORK_DEFAULT_GAS_PRICE } from "../../core/config/default-config";
import { assertHardhatInvariant } from "../../core/errors";
import { Reporter } from "../../sentry/reporter";
import { getDifferenceInSeconds } from "../../util/date";
import { createModelsAndDecodeBytecodes } from "../stack-traces/compiler-to-model";
Expand Down Expand Up @@ -100,12 +101,12 @@ export class HardhatNode extends EventEmitter {
let blockchain: HardhatBlockchain | ForkBlockchain;
let initialBlockTimeOffset: BN | undefined;

if (config.type === "forked") {
if ("forkConfig" in config) {
const { forkClient, forkBlockNumber } = await makeForkClient(
config.forkConfig,
config.forkCachePath
);
common = await makeForkCommon(forkClient, forkBlockNumber);
common = await makeForkCommon(config);

stateManager = new ForkStateManager(
forkClient,
Expand Down Expand Up @@ -399,7 +400,7 @@ export class HardhatNode extends EventEmitter {
});

const result = await this._runInBlockContext(blockNumber, () =>
this._runTxAndRevertMutations(tx, blockNumber === null)
this._runTxAndRevertMutations(tx, blockNumber ?? undefined, true)
);

let vmTrace = this._vmTracer.getLastTopLevelMessageTrace();
Expand Down Expand Up @@ -1351,21 +1352,21 @@ If you are using a wallet or dapp, try resetting your wallet's accounts.`
/**
* This function runs a transaction and reverts all the modifications that it
* makes.
*
* If throwOnError is true, errors are managed locally and thrown on
* failure. If it's false, the tx's RunTxResult is returned, and the vmTracer
* inspected/reset.
*/
private async _runTxAndRevertMutations(
tx: Transaction,
runOnNewBlock: boolean = true
blockNumber?: BN,
// See: https://github.com/ethereumjs/ethereumjs-vm/issues/1014
workaroundEthCallGasLimitIssue = false
): Promise<EVMResult> {
const initialStateRoot = await this._stateManager.getStateRoot();

let blockContext: Block | undefined;
let previousGasLimit: Buffer | undefined;

try {
let blockContext;
// if the context is to estimate gas or run calls in pending block
if (runOnNewBlock) {
if (blockNumber === undefined) {
const [blockTimestamp] = this._calculateTimestampAndOffset();

blockContext = await this._getNextBlockTemplate(blockTimestamp);
Expand All @@ -1384,8 +1385,26 @@ If you are using a wallet or dapp, try resetting your wallet's accounts.`
// possible, hence putting the tx to the block is good to have here.
await this._addTransactionToBlock(blockContext, tx);
} else {
// if the context is to run calls with the latest block
blockContext = await this.getLatestBlock();
// if the context is to run calls with a block
// We know that this block number exists, because otherwise
// there would be an error in the RPC layer.
const block = await this.getBlockByNumber(blockNumber);
assertHardhatInvariant(
block !== undefined,
"Tried to run a tx in the context of a non-existent block"
);

blockContext = block;
}

if (workaroundEthCallGasLimitIssue) {
const txGasLimit = new BN(tx.gasLimit);
const blockGasLimit = new BN(blockContext.header.gasLimit);

if (txGasLimit.gt(blockGasLimit)) {
previousGasLimit = blockContext.header.gasLimit;
blockContext.header.gasLimit = tx.gasLimit;
}
}

return await this._vm.runTx({
Expand All @@ -1395,6 +1414,17 @@ If you are using a wallet or dapp, try resetting your wallet's accounts.`
skipBalance: true,
});
} finally {
// If we changed the block's gas limit of an already existing block,
// we restore it here.
if (
blockContext !== undefined &&
workaroundEthCallGasLimitIssue &&
previousGasLimit !== undefined &&
blockNumber !== undefined
) {
blockContext.header.gasLimit = previousGasLimit;
}

await this._stateManager.setStateRoot(initialStateRoot);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,31 +248,27 @@ export class HardhatNetworkProvider extends EventEmitter
return;
}

let config: NodeConfig;

const commonConfig = {
blockGasLimit: this._blockGasLimit,
genesisAccounts: this._genesisAccounts,
allowUnlimitedContractSize: this._allowUnlimitedContractSize,
tracingConfig: await this._makeTracingConfig(),
};

if (this._forkConfig === undefined) {
config = {
type: "local",
hardfork: this._hardfork,
networkName: this._networkName,
chainId: this._chainId,
networkId: this._networkId,
initialDate: this._initialDate,
...commonConfig,
};
} else {
let config: NodeConfig = {
hardfork: this._hardfork,
networkName: this._networkName,
chainId: this._chainId,
networkId: this._networkId,
initialDate: this._initialDate,
...commonConfig,
};

if (this._forkConfig !== undefined) {
config = {
type: "forked",
forkConfig: this._forkConfig,
forkCachePath: this._forkCachePath,
...commonConfig,
...config,
};
}

Expand Down Expand Up @@ -302,6 +298,8 @@ export class HardhatNetworkProvider extends EventEmitter
}
);

this._logger.enable(this._loggingEnabled);

this._forwardNodeEvents(node);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import Common from "ethereumjs-common";
import { BN } from "ethereumjs-util";

import { JsonRpcClient } from "../../jsonrpc/client";
import { ForkedNodeConfig } from "../node-types";

export async function makeForkCommon(
forkClient: JsonRpcClient,
forkBlockNumber: BN
) {
const common = new Common(forkClient.getNetworkId());
common.setHardfork(common.activeHardfork(forkBlockNumber.toNumber()));
return common;
export async function makeForkCommon(config: ForkedNodeConfig) {
return Common.forCustomChain(
"mainnet",
{
chainId: config.chainId,
networkId: config.networkId,
name: config.networkName,
},
config.hardfork
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ class BytecodeTrie {
public readonly descendants: Bytecode[] = [];
public match?: Bytecode;

constructor(
public readonly depth: number,
public readonly parent?: BytecodeTrie
) {}
constructor(public readonly depth: number) {}

public add(bytecode: Bytecode) {
// tslint:disable-next-line no-this-assignment
Expand All @@ -53,7 +50,7 @@ class BytecodeTrie {

let childNode = trieNode.childNodes.get(byte);
if (childNode === undefined) {
childNode = new BytecodeTrie(currentCodeByte, trieNode);
childNode = new BytecodeTrie(currentCodeByte);
trieNode.childNodes.set(byte, childNode);
}

Expand Down
Loading

0 comments on commit d821012

Please sign in to comment.