Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feat: integrate with infura (#1579)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmurdoch committed Nov 18, 2021
1 parent 469d198 commit 39e7539
Show file tree
Hide file tree
Showing 18 changed files with 560 additions and 232 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Expand Up @@ -39,4 +39,4 @@ jobs:
- run: npm test
env:
FORCE_COLOR: 1
INFURA_KEY: ${{ secrets.INFURA_KEY }}
INFURA_KEY: ${{ secrets.TEST_INFURA_KEY }}
2 changes: 1 addition & 1 deletion .github/workflows/push.yml
Expand Up @@ -86,4 +86,4 @@ jobs:
- run: npm test
env:
FORCE_COLOR: 1
INFURA_KEY: ${{ secrets.INFURA_KEY }}
INFURA_KEY: ${{ secrets.TEST_INFURA_KEY }}
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Expand Up @@ -31,7 +31,7 @@ jobs:
run: npm test
env:
FORCE_COLOR: 1
INFURA_KEY: ${{ secrets.INFURA_KEY }}
INFURA_KEY: ${{ secrets.TEST_INFURA_KEY }}

- name: Update package versions
# ${GITHUB_REF##*/} is the branch name
Expand All @@ -41,6 +41,8 @@ jobs:
- name: Run build
run: npm run build
env:
INFURA_KEY: ${{ secrets.INFURA_KEY }}

- name: Update documentation
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -7,4 +7,4 @@ npm-debug.log
src/**/*/lib
coverage
dist/
typings/
typings/
15 changes: 12 additions & 3 deletions src/chains/ethereum/ethereum/src/api.ts
Expand Up @@ -308,17 +308,26 @@ export default class EthereumApi implements Api {
// Developers like to move the blockchain forward by thousands of blocks
// at a time and doing this would make it way faster
for (let i = 0; i < blocks; i++) {
const transactions = await blockchain.mine(
const { transactions, blockNumber } = await blockchain.mine(
Capacity.FillBlock,
timestamp,
true
);
// wait until the blocks are fully saved before mining the next ones
await new Promise(resolve => {
const off = blockchain.on("block", block => {
if (block.header.number.toBuffer().equals(blockNumber)) {
off();
resolve(void 0);
}
});
});
if (vmErrorsOnRPCResponse) {
assertExceptionalTransactions(transactions);
}
}
} else {
const transactions = await blockchain.mine(
const { transactions } = await blockchain.mine(
Capacity.FillBlock,
arg as number | null,
true
Expand Down Expand Up @@ -577,7 +586,7 @@ export default class EthereumApi implements Api {
@assertArgLength(0, 1)
async miner_start(threads: number = 1) {
if (this.#options.miner.legacyInstamine === true) {
const transactions = await this.#blockchain.resume(threads);
const { transactions } = await this.#blockchain.resume(threads);
if (transactions != null && this.#options.chain.vmErrorsOnRPCResponse) {
assertExceptionalTransactions(transactions);
}
Expand Down
13 changes: 10 additions & 3 deletions src/chains/ethereum/ethereum/src/blockchain.ts
Expand Up @@ -586,7 +586,14 @@ export default class Blockchain extends Emittery.Typed<
) => {
await this.#blockBeingSavedPromise;
const nextBlock = this.#readyNextBlock(this.blocks.latest, timestamp);
return this.#miner.mine(nextBlock, maxTransactions, onlyOneBlock);
return {
transactions: await this.#miner.mine(
nextBlock,
maxTransactions,
onlyOneBlock
),
blockNumber: nextBlock.header.number.toBuffer()
};
};

#isPaused = () => {
Expand Down Expand Up @@ -1011,7 +1018,7 @@ export default class Blockchain extends Emittery.Typed<
const common = this.fallback
? this.fallback.getCommonForBlockNumber(
this.common,
transaction.block.header.number
BigInt(transaction.block.header.number.toString())
)
: this.common;

Expand Down Expand Up @@ -1135,7 +1142,7 @@ export default class Blockchain extends Emittery.Typed<
const common = this.fallback
? this.fallback.getCommonForBlockNumber(
this.common,
newBlock.header.number
BigInt(newBlock.header.number.toString())
)
: this.common;

Expand Down
55 changes: 39 additions & 16 deletions src/chains/ethereum/ethereum/src/data-managers/block-manager.ts
Expand Up @@ -132,7 +132,16 @@ export default class BlockManager extends Manager<Block> {
blockNumber,
true
]);
return json == null ? null : BlockManager.rawFromJSON(json, this.#common);
if (json == null) {
return null;
} else {
const common = fallback.getCommonForBlockNumber(
this.#common,
BigInt(json.number)
);

return BlockManager.rawFromJSON(json, common);
}
};

getBlockByTag(tag: Tag) {
Expand Down Expand Up @@ -178,23 +187,25 @@ export default class BlockManager extends Manager<Block> {
async getByHash(hash: string | Buffer | Tag) {
const number = await this.getNumberFromHash(hash);
if (number === null) {
if (this.#blockchain.fallback) {
const fallback = this.#blockchain.fallback;
const fallback = this.#blockchain.fallback;
if (fallback) {
const json = await fallback.request<any>("eth_getBlockByHash", [
Data.from(hash),
true
]);
if (json && BigInt(json.number) <= fallback.blockNumber.toBigInt()) {
return new Block(
BlockManager.rawFromJSON(json, this.#common),
this.#common
);
} else {
return null;
if (json) {
const blockNumber = BigInt(json.number);
if (blockNumber <= fallback.blockNumber.toBigInt()) {
const common = fallback.getCommonForBlockNumber(
this.#common,
blockNumber
);
return new Block(BlockManager.rawFromJSON(json, common), common);
}
}
} else {
return null;
}

return null;
} else {
return this.get(number);
}
Expand All @@ -219,10 +230,22 @@ export default class BlockManager extends Manager<Block> {
if (block) return block;
}

const block = await this.getRawByBlockNumber(
Quantity.from(tagOrBlockNumber)
);
if (block) return new Block(block, this.#common);
const blockNumber = Quantity.from(tagOrBlockNumber);
const block = await this.getRaw(blockNumber.toBuffer());
const common = this.#common;
if (block) return new Block(block, common);
else {
const fallback = this.#blockchain.fallback;
if (fallback) {
const block = await this.fromFallback(blockNumber);
if (block) {
return new Block(
block,
fallback.getCommonForBlockNumber(common, blockNumber.toBigInt())
);
}
}
}

throw new Error("header not found");
}
Expand Down
Expand Up @@ -65,7 +65,11 @@ export default class TransactionManager extends Manager<NoOp> {
index.toBuffer(),
Quantity.from(tx.gasPrice).toBuffer()
];
const runTx = TransactionFactory.fromRpc(tx, fallback.common, extra);
const common = fallback.getCommonForBlockNumber(
fallback.common,
blockNumber.toBigInt()
);
const runTx = TransactionFactory.fromRpc(tx, common, extra);
return runTx.serializeForDb(blockHash, blockNumber, index);
};

Expand Down
80 changes: 60 additions & 20 deletions src/chains/ethereum/ethereum/src/forking/fork.ts
Expand Up @@ -12,7 +12,7 @@ import { Account } from "@ganache/ethereum-utils";
import BlockManager from "../data-managers/block-manager";
import { ProviderHandler } from "./handlers/provider-handler";
import { PersistentCache } from "./persistent-cache/persistent-cache";
import { BN } from "ethereumjs-util";
import { URL } from "url";

async function fetchChainId(fork: Fork) {
const chainIdHex = await fork.request<string>("eth_chainId", []);
Expand Down Expand Up @@ -57,7 +57,7 @@ export class Fork {
this.#hardfork = options.chain.hardfork;
this.#accounts = accounts;

const { url } = forkingOptions;
const { url, network } = forkingOptions;
if (url) {
const { protocol } = url;

Expand All @@ -82,6 +82,25 @@ export class Fork {
options,
this.#abortController.signal
);
} else if (network) {
let normalizedNetwork: string;
if (network === "görli") {
forkingOptions.network = normalizedNetwork = "goerli";
} else {
normalizedNetwork = network;
}
// Note: `process.env.INFURA_KEY` is replaced by webpack with an infura
// key.
const infuraKey = process.env.INFURA_KEY;
if (!infuraKey) {
throw new Error(
"The INFURA_KEY environment variable was not given and is required when using Ganache's integrated archive network feature."
);
}
forkingOptions.url = new URL(
`wss://${normalizedNetwork}.infura.io/ws/v3/${infuraKey}`
);
this.#handler = new WsHandler(options, this.#abortController.signal);
}
}

Expand All @@ -98,11 +117,18 @@ export class Fork {
{
name: "ganache-fork",
defaultHardfork: this.#hardfork,
// use the remote chain's network id mostly because truffle's debugger
// needs it to match in order to fetch sources
networkId,
// we use ganache's own chain id for blocks _after_ the fork to prevent
// replay attacks
chainId: this.#options.chain.chainId,
comment: "Local test network fork"
}
);
// disable listeners to common since we don't actually cause any `emit`s,
// but other EVM parts to listen and will make node complain about too
// many listeners.
(this.common as any).on = () => {};
};

Expand Down Expand Up @@ -130,7 +156,10 @@ export class Fork {
this.stateRoot = Data.from(block.stateRoot);
await this.#syncAccounts(this.blockNumber);
return block;
} else if (typeof options.blockNumber === "number") {
} else if (
Number.isInteger(options.blockNumber) &&
options.blockNumber >= 0
) {
const blockNumber = Quantity.from(options.blockNumber);
const [block] = await Promise.all([
fetchBlock(this, blockNumber).then(async block => {
Expand Down Expand Up @@ -187,10 +216,11 @@ export class Fork {
cacheProm,
this.#setCommonFromChain(chainIdPromise)
]);
this.block = new Block(
BlockManager.rawFromJSON(block, this.common),
this.common
const common = this.getCommonForBlockNumber(
this.common,
this.blockNumber.toBigInt()
);
this.block = new Block(BlockManager.rawFromJSON(block, common), common);
if (cache) await this.initCache(cache);
}
private async initCache(cache: PersistentCache) {
Expand Down Expand Up @@ -228,7 +258,7 @@ export class Fork {
: this.blockNumber;
}

/**
/**
* If the `blockNumber` is before our `fork.blockNumber`, return a `Common`
* instance, applying the rules from the remote chain's `common` via its
* original `chainId`. If the remote chain's `chainId` is now "known", return
Expand All @@ -238,24 +268,34 @@ export class Fork {
* @param common
* @param blockNumber
*/
public getCommonForBlockNumber(common: Common, blockNumber: BN) {
const bigIntBlockNumber = Quantity.from(blockNumber.toBuffer()).toBigInt();
if (bigIntBlockNumber <= this.blockNumber.toBigInt()) {
public getCommonForBlockNumber(common: Common, blockNumber: BigInt) {
if (blockNumber <= this.blockNumber.toBigInt()) {
// we are at or before our fork block

if (KNOWN_CHAINIDS.has(this.chainId)) {
// we support this chain id, so let's use its rules
const common = new Common({ chain: this.chainId });
common.setHardforkByBlockNumber(blockNumber);
return common;
} else {
// we don't know about this chain, so just carry on per usual
return Common.forCustomChain(
1,
{ chainId: this.chainId },
common.hardfork()
);
let hardfork;
// hardforks are iterated from earliest to latest
for (const hf of common.hardforks()) {
if (hf.block === null) continue;
if (blockNumber >= BigInt(hf.block)) {
hardfork = hf.name;
} else {
break;
}
}
return new Common({ chain: this.chainId, hardfork });
}

// we don't know about this chain or hardfork, so just carry on per usual,
// but with the fork's chainId (instead of our local chainId)
return Common.forCustomChain(
1,
{
chainId: this.chainId
},
common.hardfork()
);
} else {
return common;
}
Expand Down
5 changes: 4 additions & 1 deletion src/chains/ethereum/ethereum/src/provider.ts
Expand Up @@ -144,7 +144,10 @@ export default class EthereumProvider

const wallet = (this.#wallet = new Wallet(providerOptions.wallet));
const accounts = wallet.initialAccounts;
const fork = providerOptions.fork.url || providerOptions.fork.provider;
const fork =
providerOptions.fork.url ||
providerOptions.fork.provider ||
providerOptions.fork.network;
const fallback = fork ? new Fork(providerOptions, accounts) : null;
const coinbase = parseCoinbase(providerOptions.miner.coinbase, accounts);
const blockchain = new Blockchain(providerOptions, coinbase, fallback);
Expand Down

0 comments on commit 39e7539

Please sign in to comment.