Added Echo Service

This commit is contained in:
Tornado Contrib 2024-04-25 21:02:27 +00:00
parent bf6f5f21ae
commit 3f55d5ca99
Signed by: tornadocontrib
GPG Key ID: 60B4DF1A076C64B1
6 changed files with 403 additions and 4 deletions

@ -13,6 +13,7 @@ import {
RelayerRegistry__factory,
Aggregator__factory,
Governance__factory,
Echoer__factory,
} from '@tornado/contracts';
import {
JsonRpcProvider,
@ -62,6 +63,7 @@ import {
TornadoFeeOracle,
TokenPriceOracle,
calculateSnarkProof,
NodeEchoService,
NodeEncryptedNotesService,
NodeGovernanceService,
RelayerClient,
@ -1257,10 +1259,11 @@ export function tornadoProgram() {
registrySubgraph,
tokens,
routerContract,
echoContract,
registryContract,
['governance.contract.tornadocash.eth']: governanceContract,
deployedBlock,
constants: { GOVERNANCE_BLOCK, REGISTRY_BLOCK, ENCRYPTED_NOTES_BLOCK },
constants: { GOVERNANCE_BLOCK, REGISTRY_BLOCK, NOTE_ACCOUNT_BLOCK, ENCRYPTED_NOTES_BLOCK },
} = config;
const provider = getProgramProvider(netId, rpc, config, {
@ -1301,6 +1304,20 @@ export function tornadoProgram() {
await registryService.updateEvents();
}
const echoService = new NodeEchoService({
netId,
provider,
graphApi,
subgraphName: tornadoSubgraph,
Echoer: Echoer__factory.connect(echoContract, provider),
deployedBlock: NOTE_ACCOUNT_BLOCK,
fetchDataOptions,
cacheDirectory: EVENTS_DIR,
userDirectory: SAVED_DIR,
});
await echoService.updateEvents();
const encryptedNotesService = new NodeEncryptedNotesService({
netId,
provider,

@ -1,5 +1,12 @@
import { BaseContract, Provider, EventLog, TransactionResponse, getAddress, Block, ContractEventName } from 'ethers';
import type { Tornado, TornadoRouter, TornadoProxyLight, Governance, RelayerRegistry } from '@tornado/contracts';
import type {
Tornado,
TornadoRouter,
TornadoProxyLight,
Governance,
RelayerRegistry,
Echoer,
} from '@tornado/contracts';
import * as graph from '../graphql';
import {
BatchEventsService,
@ -21,6 +28,7 @@ import type {
GovernanceUndelegatedEvents,
RegistersEvents,
BaseGraphEvents,
EchoEvents,
} from './types';
export const DEPOSIT = 'deposit';
@ -454,6 +462,76 @@ export class BaseDepositsService extends BaseEventsService<DepositsEvents | With
}
}
export type BaseEchoServiceConstructor = {
netId: number | string;
provider: Provider;
graphApi?: string;
subgraphName?: string;
Echoer: Echoer;
deployedBlock?: number;
fetchDataOptions?: fetchDataOptions;
};
export class BaseEchoService extends BaseEventsService<EchoEvents> {
constructor({
netId,
provider,
graphApi,
subgraphName,
Echoer,
deployedBlock,
fetchDataOptions,
}: BaseEchoServiceConstructor) {
super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions });
}
getInstanceName(): string {
return `echo_${this.netId}`;
}
getType(): string {
return 'Echo';
}
getGraphMethod(): string {
return 'getAllGraphEchoEvents';
}
async formatEvents(events: EventLog[]) {
return events
.map(({ blockNumber, index: logIndex, transactionHash, args }) => {
const { who, data } = args;
if (who && data) {
const eventObjects = {
blockNumber,
logIndex,
transactionHash,
};
return {
...eventObjects,
address: who,
encryptedAccount: data,
};
}
})
.filter((e) => e) as EchoEvents[];
}
async getEventsFromGraph({ fromBlock }: { fromBlock: number }): Promise<BaseEvents<EchoEvents>> {
// TheGraph doesn't support our batch sync due to missing blockNumber field
if (!this.graphApi || this.graphApi.includes('api.thegraph.com')) {
return {
events: [],
lastBlock: fromBlock,
};
}
return super.getEventsFromGraph({ fromBlock });
}
}
export type BaseEncryptedNotesServiceConstructor = {
netId: number | string;
provider: Provider;
@ -556,7 +634,7 @@ export class BaseGovernanceService extends BaseEventsService<BaseGovernanceEvent
}
getGraphMethod() {
return 'governanceEvents';
return 'getGovernanceEvents';
}
async formatEvents(events: EventLog[]): Promise<BaseGovernanceEventTypes[]> {

@ -12,8 +12,17 @@ import {
BaseGovernanceServiceConstructor,
BaseRegistryServiceConstructor,
BaseGovernanceEventTypes,
BaseEchoServiceConstructor,
BaseEchoService,
} from './base';
import type { BaseEvents, DepositsEvents, WithdrawalsEvents, EncryptedNotesEvents, RegistersEvents } from './types';
import type {
BaseEvents,
DepositsEvents,
WithdrawalsEvents,
EncryptedNotesEvents,
RegistersEvents,
EchoEvents,
} from './types';
export type NodeDepositsServiceConstructor = BaseDepositsServiceConstructor & {
cacheDirectory?: string;
@ -184,6 +193,151 @@ export class NodeDepositsService extends BaseDepositsService {
}
}
export type NodeEchoServiceConstructor = BaseEchoServiceConstructor & {
cacheDirectory?: string;
userDirectory?: string;
};
export class NodeEchoService extends BaseEchoService {
cacheDirectory?: string;
userDirectory?: string;
constructor({
netId,
provider,
graphApi,
subgraphName,
Echoer,
deployedBlock,
fetchDataOptions,
cacheDirectory,
userDirectory,
}: NodeEchoServiceConstructor) {
super({
netId,
provider,
graphApi,
subgraphName,
Echoer,
deployedBlock,
fetchDataOptions,
});
this.cacheDirectory = cacheDirectory;
this.userDirectory = userDirectory;
}
updateEventProgress({ type, fromBlock, toBlock, count }: Parameters<BatchEventOnProgress>[0]) {
if (toBlock) {
console.log(`fromBlock - ${fromBlock}`);
console.log(`toBlock - ${toBlock}`);
if (count) {
console.log(`downloaded ${type} events count - ${count}`);
console.log('____________________________________________');
console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`);
}
}
}
updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters<BatchEventOnProgress>[0]) {
if (toBlock) {
console.log(`fromBlock - ${fromBlock}`);
console.log(`toBlock - ${toBlock}`);
if (count) {
console.log(`downloaded ${type} events from graph node count - ${count}`);
console.log('____________________________________________');
console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`);
}
}
}
async getEventsFromDB() {
if (!this.userDirectory) {
console.log(`Updating events for ${this.netId} chain echo events\n`);
console.log(`savedEvents count - ${0}`);
console.log(`savedEvents lastBlock - ${this.deployedBlock}\n`);
return {
events: [],
lastBlock: this.deployedBlock,
};
}
const savedEvents = await loadSavedEvents<EchoEvents>({
name: this.getInstanceName(),
userDirectory: this.userDirectory,
deployedBlock: this.deployedBlock,
});
console.log(`Updating events for ${this.netId} chain echo events\n`);
console.log(`savedEvents count - ${savedEvents.events.length}`);
console.log(`savedEvents lastBlock - ${savedEvents.lastBlock}\n`);
return savedEvents;
}
async getEventsFromCache() {
if (!this.cacheDirectory) {
console.log(`cachedEvents count - ${0}`);
console.log(`cachedEvents lastBlock - ${this.deployedBlock}\n`);
return {
events: [],
lastBlock: this.deployedBlock,
};
}
const cachedEvents = await loadCachedEvents<EchoEvents>({
name: this.getInstanceName(),
cacheDirectory: this.cacheDirectory,
deployedBlock: this.deployedBlock,
});
console.log(`cachedEvents count - ${cachedEvents.events.length}`);
console.log(`cachedEvents lastBlock - ${cachedEvents.lastBlock}\n`);
return cachedEvents;
}
async saveEvents({ events, lastBlock }: BaseEvents<EchoEvents>) {
const instanceName = this.getInstanceName();
console.log('\ntotalEvents count - ', events.length);
console.log(
`totalEvents lastBlock - ${events[events.length - 1] ? events[events.length - 1].blockNumber : lastBlock}\n`,
);
const eventTable = new Table();
eventTable.push(
[{ colSpan: 2, content: 'Echo Accounts', hAlign: 'center' }],
['Network', `${this.netId} chain`],
['Events', `${events.length} events`],
[{ colSpan: 2, content: 'Latest events' }],
...events
.slice(events.length - 10)
.reverse()
.map(({ blockNumber }, index) => {
const eventIndex = events.length - index;
return [eventIndex, blockNumber];
}),
);
console.log(eventTable.toString() + '\n');
if (this.userDirectory) {
await saveEvents<EchoEvents>({
name: instanceName,
userDirectory: this.userDirectory,
events,
});
}
}
}
export type NodeEncryptedNotesServiceConstructor = BaseEncryptedNotesServiceConstructor & {
cacheDirectory?: string;
userDirectory?: string;

@ -64,6 +64,11 @@ export type WithdrawalsEvents = MinimalEvents & {
timestamp: number;
};
export type EchoEvents = MinimalEvents & {
address: string;
encryptedAccount: string;
};
export type EncryptedNotesEvents = MinimalEvents & {
encryptedNote: string;
};

@ -8,6 +8,7 @@ import type {
WithdrawalsEvents,
EncryptedNotesEvents,
BatchGraphOnProgress,
EchoEvents,
} from '../events';
import {
_META,
@ -17,6 +18,7 @@ import {
GET_WITHDRAWALS,
GET_NOTE_ACCOUNTS,
GET_ENCRYPTED_NOTES,
GET_ECHO_EVENTS,
} from './queries';
export * from './queries';
@ -662,6 +664,132 @@ export async function getNoteAccounts({
}
}
export interface GraphEchoEvents {
noteAccounts: {
id: string;
blockNumber: string;
address: string;
encryptedAccount: string;
}[];
_meta: {
block: {
number: number;
};
hasIndexingErrors: boolean;
};
}
export interface getGraphEchoEventsParams {
graphApi: string;
subgraphName: string;
fromBlock: number;
fetchDataOptions?: fetchDataOptions;
onProgress?: BatchGraphOnProgress;
}
export function getGraphEchoEvents({
graphApi,
subgraphName,
fromBlock,
fetchDataOptions,
}: getGraphEchoEventsParams): Promise<GraphEchoEvents> {
return queryGraph<GraphEchoEvents>({
graphApi,
subgraphName,
query: GET_ECHO_EVENTS,
variables: {
first,
fromBlock,
},
fetchDataOptions,
});
}
export async function getAllGraphEchoEvents({
graphApi,
subgraphName,
fromBlock,
fetchDataOptions,
onProgress,
}: getGraphEchoEventsParams): Promise<BaseGraphEvents<EchoEvents>> {
try {
const events = [];
let lastSyncBlock = fromBlock;
// eslint-disable-next-line no-constant-condition
while (true) {
let {
noteAccounts: result,
_meta: {
// eslint-disable-next-line prefer-const
block: { number: currentBlock },
},
} = await getGraphEchoEvents({ graphApi, subgraphName, fromBlock, fetchDataOptions });
lastSyncBlock = currentBlock;
if (isEmptyArray(result)) {
break;
}
const [firstEvent] = result;
const [lastEvent] = result.slice(-1);
if (typeof onProgress === 'function') {
onProgress({
type: 'EchoEvents',
fromBlock: Number(firstEvent.blockNumber),
toBlock: Number(lastEvent.blockNumber),
count: result.length,
});
}
if (result.length < 900) {
events.push(...result);
break;
}
result = result.filter(({ blockNumber }) => blockNumber !== lastEvent.blockNumber);
fromBlock = Number(lastEvent.blockNumber);
events.push(...result);
}
if (!events.length) {
return {
events: [],
lastSyncBlock,
};
}
const result = events.map((e) => {
const [transactionHash, logIndex] = e.id.split('-');
return {
blockNumber: Number(e.blockNumber),
logIndex: Number(logIndex),
transactionHash: transactionHash,
address: e.address,
encryptedAccount: e.encryptedAccount,
};
});
const [lastEvent] = result.slice(-1);
return {
events: result,
lastSyncBlock: lastEvent && lastEvent.blockNumber >= lastSyncBlock ? lastEvent.blockNumber + 1 : lastSyncBlock,
};
} catch (err) {
console.log('Error from getAllGraphEchoEvents query');
console.log(err);
return {
events: [],
lastSyncBlock: fromBlock,
};
}
}
export interface GraphEncryptedNotes {
encryptedNotes: {
blockNumber: string;

@ -107,6 +107,23 @@ export const GET_NOTE_ACCOUNTS = `
}
`;
export const GET_ECHO_EVENTS = `
query getNoteAccounts($first: Int, $fromBlock: Int) {
noteAccounts(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock }) {
id
blockNumber
address
encryptedAccount
}
_meta {
block {
number
}
hasIndexingErrors
}
}
`;
export const GET_ENCRYPTED_NOTES = `
query getEncryptedNotes($first: Int, $fromBlock: Int) {
encryptedNotes(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock }) {