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

Commit e35ad2c

Browse files
author
Andres Adjimann
committed
feat: get logs from a fork network directly
1 parent 74c3a7b commit e35ad2c

File tree

5 files changed

+435
-61
lines changed

5 files changed

+435
-61
lines changed

src/chains/ethereum/ethereum/src/data-managers/block-manager.ts

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -177,37 +177,56 @@ export default class BlockManager extends Manager<Block> {
177177
}
178178

179179
async getNumberFromHash(hash: string | Buffer | Tag) {
180-
return this.#blockIndexes.get(Data.toBuffer(hash)).catch(e => {
181-
if (e.status === NOTFOUND) return null;
182-
throw e;
183-
}) as Promise<Buffer | null>;
180+
const number = await this.#blockIndexes
181+
.get(Data.toBuffer(hash))
182+
.catch(e => {
183+
if (e.status === NOTFOUND) return null;
184+
throw e;
185+
});
186+
if (number !== null) {
187+
return Quantity.from(number);
188+
}
189+
const fallback = this.#blockchain.fallback;
190+
if (fallback) {
191+
const json = await fallback.request<any>("eth_getBlockByHash", [
192+
Data.from(hash),
193+
true
194+
]);
195+
if (json) {
196+
return Quantity.from(json.number);
197+
}
198+
}
199+
return null;
184200
}
185201

186202
async getByHash(hash: string | Buffer | Tag) {
187-
const number = await this.getNumberFromHash(hash);
188-
if (number === null) {
189-
const fallback = this.#blockchain.fallback;
190-
if (fallback) {
191-
const json = await fallback.request<any>("eth_getBlockByHash", [
192-
Data.from(hash),
193-
true
194-
]);
195-
if (json) {
196-
const blockNumber = BigInt(json.number);
197-
if (blockNumber <= fallback.blockNumber.toBigInt()) {
198-
const common = fallback.getCommonForBlockNumber(
199-
this.#common,
200-
blockNumber
201-
);
202-
return new Block(BlockManager.rawFromJSON(json, common), common);
203-
}
203+
const number = await this.#blockIndexes
204+
.get(Data.toBuffer(hash))
205+
.catch(e => {
206+
if (e.status === NOTFOUND) return null;
207+
throw e;
208+
});
209+
if (number !== null) {
210+
return this.get(number);
211+
}
212+
const fallback = this.#blockchain.fallback;
213+
if (fallback) {
214+
const json = await fallback.request<any>("eth_getBlockByHash", [
215+
Data.from(hash),
216+
true
217+
]);
218+
if (json) {
219+
const blockNumber = BigInt(json.number);
220+
if (blockNumber <= fallback.blockNumber.toBigInt()) {
221+
const common = fallback.getCommonForBlockNumber(
222+
this.#common,
223+
blockNumber
224+
);
225+
return new Block(BlockManager.rawFromJSON(json, common), common);
204226
}
205227
}
206-
207-
return null;
208-
} else {
209-
return this.get(number);
210228
}
229+
return null;
211230
}
212231

213232
async getRawByBlockNumber(blockNumber: Quantity): Promise<Buffer> {

src/chains/ethereum/ethereum/src/data-managers/blocklog-manager.ts

Lines changed: 87 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { GanacheLevelUp } from "../database";
88

99
export default class BlockLogManager extends Manager<BlockLogs> {
1010
#blockchain: Blockchain;
11+
1112
constructor(base: GanacheLevelUp, blockchain: Blockchain) {
1213
super(base, BlockLogs);
1314
this.#blockchain = blockchain;
@@ -19,11 +20,13 @@ export default class BlockLogManager extends Manager<BlockLogs> {
1920
log.blockNumber = Quantity.from(key);
2021
} else if (this.#blockchain.fallback) {
2122
const block = Quantity.from(key);
22-
const res = await this.#blockchain.fallback.request<any[] | null>(
23-
"eth_getLogs",
24-
[{ fromBlock: block, toBlock: block }]
25-
);
26-
return BlockLogs.fromJSON(res);
23+
if (this.#blockchain.fallback.isValidForkBlockNumber(block)) {
24+
const res = await this.#blockchain.fallback.request<any[] | null>(
25+
"eth_getLogs",
26+
[{ fromBlock: block, toBlock: block }]
27+
);
28+
return BlockLogs.fromJSON(res);
29+
}
2730
}
2831
return log;
2932
}
@@ -35,42 +38,90 @@ export default class BlockLogManager extends Manager<BlockLogs> {
3538
const blockNumber = await blockchain.blocks.getNumberFromHash(
3639
filter.blockHash
3740
);
38-
if (!blockNumber) return [];
39-
40-
const logs = await this.get(blockNumber);
41+
if (!blockNumber) {
42+
return [];
43+
}
44+
const logs = await this.get(blockNumber.toBuffer());
4145
return logs ? [...logs.filter(addresses, topics)] : [];
42-
} else {
43-
const { addresses, topics, fromBlock, toBlockNumber } = parseFilter(
44-
filter,
45-
blockchain
46+
}
47+
const { fromBlock, toBlock } = parseFilter(filter, blockchain);
48+
if (fromBlock.toBigInt() > toBlock.toBigInt()) {
49+
throw new Error(
50+
"One of the blocks specified in filter (fromBlock, toBlock or blockHash) cannot be found."
4651
);
52+
}
4753

48-
const pendingLogsPromises: Promise<BlockLogs>[] = [
49-
this.get(fromBlock.toBuffer())
50-
];
51-
52-
const fromBlockNumber = fromBlock.toNumber();
53-
// if we have a range of blocks to search, do that here:
54-
if (fromBlockNumber !== toBlockNumber) {
55-
// fetch all the blockLogs in-between `fromBlock` and `toBlock` (excluding
56-
// from, because we already started fetching that one)
57-
for (let i = fromBlockNumber + 1, l = toBlockNumber + 1; i < l; i++) {
58-
pendingLogsPromises.push(this.get(Quantity.toBuffer(i)));
59-
}
60-
}
54+
const fork = this.#blockchain.fallback;
55+
if (!fork) {
56+
return await this.getLocal(
57+
fromBlock.toNumber(),
58+
toBlock.toNumber(),
59+
filter
60+
);
61+
}
62+
const from = Quantity.min(fromBlock, toBlock);
63+
const ret: Ethereum.Logs = [];
64+
if (fork.isValidForkBlockNumber(from)) {
65+
ret.push(
66+
...(await this.getFromFork(
67+
from,
68+
Quantity.min(toBlock, fork.blockNumber),
69+
filter
70+
))
71+
);
72+
}
73+
if (!fork.isValidForkBlockNumber(toBlock)) {
74+
ret.push(
75+
...(await this.getLocal(
76+
Math.max(from.toNumber(), fork.blockNumber.toNumber() + 1),
77+
toBlock.toNumber(),
78+
filter
79+
))
80+
);
81+
}
82+
return ret;
83+
}
6184

62-
// now filter and compute all the blocks' blockLogs (in block order)
63-
return Promise.all(pendingLogsPromises).then(blockLogsRange => {
64-
const filteredBlockLogs: Ethereum.Logs = [];
65-
blockLogsRange.forEach(blockLogs => {
66-
// TODO(perf): this loops over all addresses for every block.
67-
// Maybe make it loop only once?
68-
// Issue: https://github.com/trufflesuite/ganache/issues/3482
69-
if (blockLogs)
70-
filteredBlockLogs.push(...blockLogs.filter(addresses, topics));
71-
});
72-
return filteredBlockLogs;
85+
getLocal(
86+
from: number,
87+
to: number,
88+
filter: FilterArgs
89+
): Promise<Ethereum.Logs> {
90+
const { addresses, topics } = parseFilterDetails(filter);
91+
const pendingLogsPromises: Promise<BlockLogs>[] = [];
92+
for (let i = from; i <= to; i++) {
93+
pendingLogsPromises.push(this.get(Quantity.toBuffer(i)));
94+
}
95+
return Promise.all(pendingLogsPromises).then(blockLogsRange => {
96+
const filteredBlockLogs: Ethereum.Logs = [];
97+
blockLogsRange.forEach(blockLogs => {
98+
// TODO(perf): this loops over all addresses for every block.
99+
// Maybe make it loop only once?
100+
// Issue: https://github.com/trufflesuite/ganache/issues/3482
101+
if (blockLogs)
102+
filteredBlockLogs.push(...blockLogs.filter(addresses, topics));
73103
});
104+
return filteredBlockLogs;
105+
});
106+
}
107+
108+
async getFromFork(
109+
from: Quantity,
110+
to: Quantity,
111+
filter: FilterArgs
112+
): Promise<Ethereum.Logs> {
113+
const { addresses, topics } = parseFilterDetails(filter);
114+
const f = this.#blockchain.fallback;
115+
if (!f || !f.isValidForkBlockNumber(from)) {
116+
return [];
74117
}
118+
return await f.request<Ethereum.Logs | null>("eth_getLogs", [
119+
{
120+
fromBlock: from,
121+
toBlock: f.selectValidForkBlockNumber(to),
122+
address: addresses,
123+
topics
124+
}
125+
]);
75126
}
76127
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.11;
3+
4+
5+
/**
6+
* @dev Interface of the ERC20 standard as defined in the EIP.
7+
*/
8+
interface IERC20 {
9+
/**
10+
* @dev Emitted when `value` tokens are moved from one account (`from`) to
11+
* another (`to`).
12+
*
13+
* Note that `value` may be zero.
14+
*/
15+
event Transfer(address indexed from, address indexed to, uint256 value);
16+
17+
/**
18+
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
19+
* a call to {approve}. `value` is the new allowance.
20+
*/
21+
event Approval(address indexed owner, address indexed spender, uint256 value);
22+
23+
/**
24+
* @dev Returns the amount of tokens in existence.
25+
*/
26+
function totalSupply() external view returns (uint256);
27+
28+
/**
29+
* @dev Returns the amount of tokens owned by `account`.
30+
*/
31+
function balanceOf(address account) external view returns (uint256);
32+
33+
/**
34+
* @dev Moves `amount` tokens from the caller's account to `to`.
35+
*
36+
* Returns a boolean value indicating whether the operation succeeded.
37+
*
38+
* Emits a {Transfer} event.
39+
*/
40+
function transfer(address to, uint256 amount) external returns (bool);
41+
42+
/**
43+
* @dev Returns the remaining number of tokens that `spender` will be
44+
* allowed to spend on behalf of `owner` through {transferFrom}. This is
45+
* zero by default.
46+
*
47+
* This value changes when {approve} or {transferFrom} are called.
48+
*/
49+
function allowance(address owner, address spender) external view returns (uint256);
50+
51+
/**
52+
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
53+
*
54+
* Returns a boolean value indicating whether the operation succeeded.
55+
*
56+
* IMPORTANT: Beware that changing an allowance with this method brings the risk
57+
* that someone may use both the old and the new allowance by unfortunate
58+
* transaction ordering. One possible solution to mitigate this race
59+
* condition is to first reduce the spender's allowance to 0 and set the
60+
* desired value afterwards:
61+
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
62+
*
63+
* Emits an {Approval} event.
64+
*/
65+
function approve(address spender, uint256 amount) external returns (bool);
66+
67+
/**
68+
* @dev Moves `amount` tokens from `from` to `to` using the
69+
* allowance mechanism. `amount` is then deducted from the caller's
70+
* allowance.
71+
*
72+
* Returns a boolean value indicating whether the operation succeeded.
73+
*
74+
* Emits a {Transfer} event.
75+
*/
76+
function transferFrom(address from, address to, uint256 amount) external returns (bool);
77+
}

0 commit comments

Comments
 (0)