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

Commit

Permalink
fix: ensure forking works with batch transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmurdoch committed Jun 17, 2020
1 parent 4508d17 commit f57a517
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 11 deletions.
46 changes: 35 additions & 11 deletions lib/utils/forkedblockchain.js
Expand Up @@ -69,19 +69,43 @@ function ForkedBlockchain(options) {
// simply cache every non-error result because all requests to the
// fork should be deterministic.
this.fork.sendAsync = this.fork.send = (payload, callback) => {
const key = JSON.stringify(Object.assign({}, payload, { id: null }));

if (cache.has(key)) {
callback(null, Object.assign({}, JSON.parse(cache.get(key)), { id: payload.id }));
let payloads;
const sendArray = Array.isArray(payload);
if (sendArray) {
payloads = payload;
} else {
send.call(this.fork, payload, (error, result) => {
if (!error) {
cache.set(key, JSON.stringify(Object.assign({}, result, { id: null })));
}

callback(error, result);
});
payloads = [payload];
}
Promise.all(payloads.map(payload => {
const key = JSON.stringify(Object.assign({}, payload, { id: null }));

if (cache.has(key)) {
const result = Object.assign({}, JSON.parse(cache.get(key)), { id: payload.id });
return Promise.resolve({ error: null, result });
} else {
return new Promise(resolve => {
send.call(this.fork, payload, (error, result) => {
if (!error) {
cache.set(key, JSON.stringify(Object.assign({}, result, { id: null })));
}
resolve({ error, result });
});
});
}
})).then((errResults) => {
if (!sendArray) {
const errResult = errResults[0];
callback(errResult.error, errResult.result);
} else {
const errors = [];
const results = [];
errResults.forEach(({ error, result }) => {
errors.push(error);
results.push(result);
});
callback(errors, results);
}
});
};
}

Expand Down
47 changes: 47 additions & 0 deletions test/forking.js
Expand Up @@ -193,6 +193,34 @@ describe("Forking", function() {
assert.strictEqual(id, forkedWeb3NetworkId);
});

it("should return from the cache on successive calls to the same forked data", (done) => {
const params = [contractAddress, contract.position_of_value];
const tx = { id: 1, method: "eth_getStorageAt", jsonrpc: "2.0", params };
const oldSend = forkedServer.provider.send;
let callCount = 0;
// patch the original server's send so we can listen in on calls made to it.
forkedServer.provider.send = (...args) => {
const payload = args[0];
if (payload.method === "eth_getStorageAt" && payload.params[0] === contractAddress.toLowerCase()) {
callCount++;
}
return oldSend.apply(forkedServer.provider, args);
};
const provider = mainWeb3.currentProvider;
provider.send(tx, (_, result) => {
const result1 = Object.assign({}, result, { id: null });
assert.strictEqual(parseInt(result1.result), 7, "return value is incorrect");
tx.id = 2;
provider.send(tx, (_, result) => {
const result2 = Object.assign({}, result, { id: null });
assert.deepStrictEqual(result2, result1);
assert.strictEqual(callCount, 1, "cache didn't work");
forkedServer.provider.send = oldSend;
done();
});
});
});

it("should fetch a contract from the forked provider via the main provider", async() => {
const mainCode = await mainWeb3.eth.getCode(contractAddress);
// Ensure there's *something* there.
Expand All @@ -205,6 +233,25 @@ describe("Forking", function() {
assert.strictEqual(mainCode, forkedCode);
});

it("should handle batched transactions", (done) => {
const tx1 = { id: 1, method: "eth_accounts", jsonrpc: "2.0" };
const tx2 = { id: 2, method: "eth_getBalance", jsonrpc: "2.0", params: [forkedAccounts[0]] };
const tx3 = { id: 3, method: "eth_chainId", jsonrpc: "2.0" };

mainWeb3.currentProvider.send([tx1, tx2, tx3], (mainErr, mainResults) => {
forkedWeb3.currentProvider.send([tx1, tx2, tx3], (_, forkedResults) => {
assert.strictEqual(mainErr, null);
assert.strictEqual(mainResults[0].id, 1);
assert.strictEqual(mainResults[1].id, 2);
assert.strictEqual(mainResults[2].id, 3);
assert.strictEqual(mainResults[0].result.length, 10);
assert.strictEqual(mainResults[1].result, forkedResults[1].result);
assert.strictEqual(mainResults[2].result, forkedResults[2].result);
done();
});
});
});

it("should get the balance of an address in the forked provider via the main provider", async() => {
// Assert preconditions
const firstForkedAccount = forkedAccounts[0];
Expand Down

0 comments on commit f57a517

Please sign in to comment.