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

@ -1,5 +1,12 @@
import { BaseContract, Provider, EventLog, TransactionResponse, getAddress, Block, ContractEventName } from 'ethers'; 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 * as graph from '../graphql';
import { import {
BatchEventsService, BatchEventsService,
@ -21,6 +28,7 @@ import type {
GovernanceUndelegatedEvents, GovernanceUndelegatedEvents,
RegistersEvents, RegistersEvents,
BaseGraphEvents, BaseGraphEvents,
EchoEvents,
} from './types'; } from './types';
export const DEPOSIT = 'deposit'; 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 = { export type BaseEncryptedNotesServiceConstructor = {
netId: number | string; netId: number | string;
provider: Provider; provider: Provider;
@ -556,7 +634,7 @@ export class BaseGovernanceService extends BaseEventsService<BaseGovernanceEvent
} }
getGraphMethod() { getGraphMethod() {
return 'governanceEvents'; return 'getGovernanceEvents';
} }
async formatEvents(events: EventLog[]): Promise<BaseGovernanceEventTypes[]> { async formatEvents(events: EventLog[]): Promise<BaseGovernanceEventTypes[]> {

@ -12,8 +12,17 @@ import {
BaseGovernanceServiceConstructor, BaseGovernanceServiceConstructor,
BaseRegistryServiceConstructor, BaseRegistryServiceConstructor,
BaseGovernanceEventTypes, BaseGovernanceEventTypes,
BaseEchoServiceConstructor,
BaseEchoService,
} from './base'; } 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 & { export type NodeDepositsServiceConstructor = BaseDepositsServiceConstructor & {
cacheDirectory?: string; 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 & { export type NodeEncryptedNotesServiceConstructor = BaseEncryptedNotesServiceConstructor & {
cacheDirectory?: string; cacheDirectory?: string;
userDirectory?: string; userDirectory?: string;

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

@ -8,6 +8,7 @@ import type {
WithdrawalsEvents, WithdrawalsEvents,
EncryptedNotesEvents, EncryptedNotesEvents,
BatchGraphOnProgress, BatchGraphOnProgress,
EchoEvents,
} from '../events'; } from '../events';
import { import {
_META, _META,
@ -17,6 +18,7 @@ import {
GET_WITHDRAWALS, GET_WITHDRAWALS,
GET_NOTE_ACCOUNTS, GET_NOTE_ACCOUNTS,
GET_ENCRYPTED_NOTES, GET_ENCRYPTED_NOTES,
GET_ECHO_EVENTS,
} from './queries'; } from './queries';
export * 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 { export interface GraphEncryptedNotes {
encryptedNotes: { encryptedNotes: {
blockNumber: string; 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 = ` export const GET_ENCRYPTED_NOTES = `
query getEncryptedNotes($first: Int, $fromBlock: Int) { query getEncryptedNotes($first: Int, $fromBlock: Int) {
encryptedNotes(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock }) { encryptedNotes(first: $first, orderBy: blockNumber, orderDirection: asc, where: { blockNumber_gte: $fromBlock }) {