mirror of
https://github.com/cerc-io/watcher-ts
synced 2024-11-19 20:36:19 +00:00
Codegen flag for watcher kind and eventsInRange query support (#254)
* Add watcher kind argument. * Process eventsInRange query.
This commit is contained in:
parent
338cef9954
commit
8e3093c684
@ -13,7 +13,7 @@
|
||||
* Run the following command to generate a watcher from a contract file:
|
||||
|
||||
```bash
|
||||
yarn codegen --input-file <input-file-path> --contract-name <contract-name> --output-folder [output-folder] --mode [eth_call | storage | all] --flatten [true | false]
|
||||
yarn codegen --input-file <input-file-path> --contract-name <contract-name> --output-folder [output-folder] --mode [eth_call | storage | all] --flatten [true | false] --kind [lazy | active]
|
||||
```
|
||||
|
||||
* `input-file`(alias: `i`): Input contract file path or an URL (required).
|
||||
@ -21,24 +21,25 @@
|
||||
* `output-folder`(alias: `o`): Output folder path. (logs output using `stdout` if not provided).
|
||||
* `mode`(alias: `m`): Code generation mode (default: `all`).
|
||||
* `flatten`(alias: `f`): Flatten the input contract file (default: `true`).
|
||||
* `kind` (alias: `k`): Kind of watcher (default; `active`).
|
||||
|
||||
**Note**: When passed an *URL* as `input-file`, it is assumed that it points to an already flattened contract file.
|
||||
|
||||
Examples:
|
||||
|
||||
Generate code in both eth_call and storage mode.
|
||||
Generate code in both `eth_call` and `storage` mode, `active` kind.
|
||||
```bash
|
||||
yarn codegen --input-file ./test/examples/contracts/ERC20.sol --contract-name ERC20 --output-folder ../my-erc20-watcher --mode all
|
||||
yarn codegen --input-file ./test/examples/contracts/ERC20.sol --contract-name ERC20 --output-folder ../my-erc20-watcher --mode all --kind active
|
||||
```
|
||||
|
||||
Generate code in eth_call mode using a contract provided by URL.
|
||||
Generate code in `eth_call` mode using a contract provided by an URL.
|
||||
```bash
|
||||
yarn codegen --input-file https://git.io/Jupci --contract-name ERC721 --output-folder ../my-erc721-watcher --mode eth_call
|
||||
```
|
||||
|
||||
Generate code in storage mode.
|
||||
Generate code in `storage` mode, `lazy` kind.
|
||||
```bash
|
||||
yarn codegen --input-file ./test/examples/contracts/ERC721.sol --contract-name ERC721 --output-folder ../my-erc721-watcher --mode storage
|
||||
yarn codegen --input-file ./test/examples/contracts/ERC721.sol --contract-name ERC721 --output-folder ../my-erc721-watcher --mode storage --kind lazy
|
||||
```
|
||||
|
||||
## Demo
|
||||
@ -60,7 +61,7 @@
|
||||
* Generate a watcher from a contract file:
|
||||
|
||||
```bash
|
||||
yarn codegen --input-file ../../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol --contract-name ERC20 --output-folder ../demo-erc20-watcher --mode eth_call
|
||||
yarn codegen --input-file ../../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol --contract-name ERC20 --output-folder ../demo-erc20-watcher --mode eth_call --kind active
|
||||
```
|
||||
|
||||
This will create a folder called `demo-erc20-watcher` containing the generated code at the specified path. Follow the steps in `demo-erc20-watcher/README.md` to setup and run the generated watcher.
|
||||
@ -68,7 +69,7 @@
|
||||
* Generate a watcher from a flattened contract file from an URL:
|
||||
|
||||
```bash
|
||||
yarn codegen --input-file https://git.io/Jupci --contract-name ERC721 --output-folder ../demo-erc721-watcher --mode eth_call
|
||||
yarn codegen --input-file https://git.io/Jupci --contract-name ERC721 --output-folder ../demo-erc721-watcher --mode all --kind active
|
||||
```
|
||||
|
||||
## References
|
||||
|
@ -11,13 +11,15 @@ const TEMPLATE_FILE = './templates/config-template.handlebars';
|
||||
|
||||
/**
|
||||
* Writes the config file generated from a template to a stream.
|
||||
* @param watcherKind Watcher kind to be passed to the template.
|
||||
* @param folderName Watcher folder name to be passed to the template.
|
||||
* @param outStream A writable output stream to write the config file to.
|
||||
*/
|
||||
export function exportConfig (folderName: string, outStream: Writable): void {
|
||||
export function exportConfig (watcherKind: string, folderName: string, outStream: Writable): void {
|
||||
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
const template = Handlebars.compile(templateString);
|
||||
const config = template({
|
||||
watcherKind,
|
||||
folderName
|
||||
});
|
||||
outStream.write(config);
|
||||
|
@ -10,6 +10,7 @@ import { hideBin } from 'yargs/helpers';
|
||||
import { flatten } from '@poanet/solidity-flattener';
|
||||
|
||||
import { parse, visit } from '@solidity-parser/parser';
|
||||
import { KIND_ACTIVE, KIND_LAZY } from '@vulcanize/util';
|
||||
|
||||
import { MODE_ETH_CALL, MODE_STORAGE, MODE_ALL } from './utils/constants';
|
||||
import { Visitor } from './visitor';
|
||||
@ -51,6 +52,13 @@ const main = async (): Promise<void> => {
|
||||
default: MODE_ALL,
|
||||
choices: [MODE_ETH_CALL, MODE_STORAGE, MODE_ALL]
|
||||
})
|
||||
.option('kind', {
|
||||
alias: 'k',
|
||||
describe: 'Watcher kind.',
|
||||
type: 'string',
|
||||
default: KIND_ACTIVE,
|
||||
choices: [KIND_ACTIVE, KIND_LAZY]
|
||||
})
|
||||
.option('flatten', {
|
||||
alias: 'f',
|
||||
describe: 'Flatten the input contract file.',
|
||||
@ -146,7 +154,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'environments/local.toml'))
|
||||
: process.stdout;
|
||||
exportConfig(path.basename(outputDir), outStream);
|
||||
exportConfig(argv.kind, path.basename(outputDir), outStream);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/artifacts/', `${inputFileName}.json`))
|
||||
|
@ -1,6 +1,7 @@
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 3008
|
||||
kind = "{{watcherKind}}"
|
||||
|
||||
[database]
|
||||
type = "postgres"
|
||||
|
@ -45,7 +45,7 @@ export class Database {
|
||||
|
||||
{{#each queries as | query |}}
|
||||
// eslint-disable-next-line camelcase
|
||||
{{#if (banTypeCheck (capitalize query.name tillIndex=1)) }}
|
||||
{{#if (reservedNameCheck (capitalize query.name tillIndex=1)) }}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
{{/if}}
|
||||
async get{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
|
||||
@ -65,7 +65,7 @@ export class Database {
|
||||
|
||||
{{~#each queries as | query |}}
|
||||
// eslint-disable-next-line camelcase
|
||||
{{#if (banTypeCheck (capitalize query.name tillIndex=1)) }}
|
||||
{{#if (reservedNameCheck (capitalize query.name tillIndex=1)) }}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
{{/if}}
|
||||
async save{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
|
||||
|
@ -201,6 +201,14 @@ export class Indexer {
|
||||
return this._baseIndexer.isWatchedContract(address);
|
||||
}
|
||||
|
||||
async getProcessedBlockCountForRange (fromBlockNumber: number, toBlockNumber: number): Promise<{ expected: number, actual: number }> {
|
||||
return this._baseIndexer.getProcessedBlockCountForRange(fromBlockNumber, toBlockNumber);
|
||||
}
|
||||
|
||||
async getEventsInRange (fromBlockNumber: number, toBlockNumber: number): Promise<Array<Event>> {
|
||||
return this._baseIndexer.getEventsInRange(fromBlockNumber, toBlockNumber);
|
||||
}
|
||||
|
||||
async getSyncStatus (): Promise<SyncStatus | undefined> {
|
||||
return this._baseIndexer.getSyncStatus();
|
||||
}
|
||||
|
@ -15,7 +15,9 @@
|
||||
createdb {{folderName}}
|
||||
```
|
||||
|
||||
* Create database for the job queue and enable the `pgcrypto` extension on them (https://github.com/timgit/pg-boss/blob/master/docs/usage.md#intro):
|
||||
* If the watcher is an `active` watcher:
|
||||
|
||||
Create database for the job queue and enable the `pgcrypto` extension on them (https://github.com/timgit/pg-boss/blob/master/docs/usage.md#intro):
|
||||
|
||||
```
|
||||
createdb {{folderName}}-job-queue
|
||||
@ -45,16 +47,18 @@
|
||||
yarn server
|
||||
```
|
||||
|
||||
* Run the job-runner:
|
||||
|
||||
```bash
|
||||
yarn job-runner
|
||||
```
|
||||
|
||||
GQL console: http://localhost:3008/graphql
|
||||
|
||||
* To watch a contract:
|
||||
* If the watcher is an `active` watcher:
|
||||
|
||||
```bash
|
||||
yarn watch:contract --address CONTRACT_ADDRESS --kind {{contractName}} --starting-block BLOCK_NUMBER
|
||||
```
|
||||
* Run the job-runner:
|
||||
|
||||
```bash
|
||||
yarn job-runner
|
||||
```
|
||||
|
||||
* To watch a contract:
|
||||
|
||||
```bash
|
||||
yarn watch:contract --address CONTRACT_ADDRESS --kind {{contractName}} --starting-block BLOCK_NUMBER
|
||||
```
|
||||
|
@ -48,6 +48,18 @@ export const createResolvers = async (indexer: Indexer): Promise<any> => {
|
||||
|
||||
const events = await indexer.getEventsByFilter(blockHash, contractAddress, name);
|
||||
return events.map(event => indexer.getResultEvent(event));
|
||||
},
|
||||
|
||||
eventsInRange: async (_: any, { fromBlockNumber, toBlockNumber }: { fromBlockNumber: number, toBlockNumber: number }) => {
|
||||
log('eventsInRange', fromBlockNumber, toBlockNumber);
|
||||
|
||||
const { expected, actual } = await indexer.getProcessedBlockCountForRange(fromBlockNumber, toBlockNumber);
|
||||
if (expected !== actual) {
|
||||
throw new Error(`Range not available, expected ${expected}, got ${actual} blocks in range`);
|
||||
}
|
||||
|
||||
const events = await indexer.getEventsInRange(fromBlockNumber, toBlockNumber);
|
||||
return events.map(event => indexer.getResultEvent(event));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ import { getDefaultProvider } from 'ethers';
|
||||
|
||||
import { getCache } from '@vulcanize/cache';
|
||||
import { EthClient } from '@vulcanize/ipld-eth-client';
|
||||
import { DEFAULT_CONFIG_PATH, getConfig, JobQueue } from '@vulcanize/util';
|
||||
import { DEFAULT_CONFIG_PATH, getConfig, JobQueue, KIND_ACTIVE } from '@vulcanize/util';
|
||||
|
||||
import { createResolvers } from './resolvers';
|
||||
import { Indexer } from './indexer';
|
||||
@ -41,7 +41,7 @@ export const main = async (): Promise<any> => {
|
||||
|
||||
assert(config.server, 'Missing server config');
|
||||
|
||||
const { host, port } = config.server;
|
||||
const { host, port, kind: watcherKind } = config.server;
|
||||
|
||||
const { upstream, database: dbConfig, jobQueue: jobQueueConfig } = config;
|
||||
|
||||
@ -64,20 +64,23 @@ export const main = async (): Promise<any> => {
|
||||
|
||||
const ethProvider = getDefaultProvider(rpcProviderEndpoint);
|
||||
|
||||
// Note: In-memory pubsub works fine for now, as each watcher is a single process anyway.
|
||||
// Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries
|
||||
const pubsub = new PubSub();
|
||||
const indexer = new Indexer(db, ethClient, ethProvider);
|
||||
|
||||
assert(jobQueueConfig, 'Missing job queue config');
|
||||
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
|
||||
assert(dbConnectionString, 'Missing job queue db connection string');
|
||||
if (watcherKind === KIND_ACTIVE) {
|
||||
// Note: In-memory pubsub works fine for now, as each watcher is a single process anyway.
|
||||
// Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries
|
||||
const pubsub = new PubSub();
|
||||
|
||||
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
|
||||
await jobQueue.start();
|
||||
assert(jobQueueConfig, 'Missing job queue config');
|
||||
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
|
||||
assert(dbConnectionString, 'Missing job queue db connection string');
|
||||
|
||||
const eventWatcher = new EventWatcher(ethClient, indexer, pubsub, jobQueue);
|
||||
await eventWatcher.start();
|
||||
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
|
||||
await jobQueue.start();
|
||||
|
||||
const eventWatcher = new EventWatcher(ethClient, indexer, pubsub, jobQueue);
|
||||
await eventWatcher.start();
|
||||
}
|
||||
|
||||
const resolvers = await createResolvers(indexer);
|
||||
|
||||
|
@ -5,12 +5,12 @@
|
||||
import assert from 'assert';
|
||||
import Handlebars from 'handlebars';
|
||||
|
||||
import { bannedTypes } from './types';
|
||||
import { reservedNames } from './types';
|
||||
|
||||
export function registerHandlebarHelpers (): void {
|
||||
Handlebars.registerHelper('compare', compareHelper);
|
||||
Handlebars.registerHelper('capitalize', capitalizeHelper);
|
||||
Handlebars.registerHelper('banTypeCheck', banTypeCheckHelper);
|
||||
Handlebars.registerHelper('reservedNameCheck', reservedNameCheckHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,6 +54,6 @@ function capitalizeHelper (value: string, options: any): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
function banTypeCheckHelper (value: string): boolean {
|
||||
return bannedTypes.has(value);
|
||||
function reservedNameCheckHelper (value: string): boolean {
|
||||
return reservedNames.has(value);
|
||||
}
|
||||
|
@ -7,6 +7,6 @@ export interface Param {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export const bannedTypes = new Set([
|
||||
export const reservedNames = new Set([
|
||||
'Symbol'
|
||||
]);
|
||||
|
@ -23,6 +23,7 @@ export interface Config {
|
||||
host: string;
|
||||
port: number;
|
||||
mode: string;
|
||||
kind: string;
|
||||
};
|
||||
database: ConnectionOptions;
|
||||
upstream: {
|
||||
|
@ -14,3 +14,6 @@ export const JOB_KIND_PRUNE = 'prune';
|
||||
export const DEFAULT_CONFIG_PATH = 'environments/local.toml';
|
||||
|
||||
export const UNKNOWN_EVENT_NAME = '__unknown__';
|
||||
|
||||
export const KIND_ACTIVE = 'active';
|
||||
export const KIND_LAZY = 'lazy';
|
||||
|
Loading…
Reference in New Issue
Block a user