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:
|
* Run the following command to generate a watcher from a contract file:
|
||||||
|
|
||||||
```bash
|
```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).
|
* `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).
|
* `output-folder`(alias: `o`): Output folder path. (logs output using `stdout` if not provided).
|
||||||
* `mode`(alias: `m`): Code generation mode (default: `all`).
|
* `mode`(alias: `m`): Code generation mode (default: `all`).
|
||||||
* `flatten`(alias: `f`): Flatten the input contract file (default: `true`).
|
* `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.
|
**Note**: When passed an *URL* as `input-file`, it is assumed that it points to an already flattened contract file.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
Generate code in both eth_call and storage mode.
|
Generate code in both `eth_call` and `storage` mode, `active` kind.
|
||||||
```bash
|
```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
|
```bash
|
||||||
yarn codegen --input-file https://git.io/Jupci --contract-name ERC721 --output-folder ../my-erc721-watcher --mode eth_call
|
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
|
```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
|
## Demo
|
||||||
@ -60,7 +61,7 @@
|
|||||||
* Generate a watcher from a contract file:
|
* Generate a watcher from a contract file:
|
||||||
|
|
||||||
```bash
|
```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.
|
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:
|
* Generate a watcher from a flattened contract file from an URL:
|
||||||
|
|
||||||
```bash
|
```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
|
## References
|
||||||
|
@ -11,13 +11,15 @@ const TEMPLATE_FILE = './templates/config-template.handlebars';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the config file generated from a template to a stream.
|
* 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 folderName Watcher folder name to be passed to the template.
|
||||||
* @param outStream A writable output stream to write the config file to.
|
* @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 templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||||
const template = Handlebars.compile(templateString);
|
const template = Handlebars.compile(templateString);
|
||||||
const config = template({
|
const config = template({
|
||||||
|
watcherKind,
|
||||||
folderName
|
folderName
|
||||||
});
|
});
|
||||||
outStream.write(config);
|
outStream.write(config);
|
||||||
|
@ -10,6 +10,7 @@ import { hideBin } from 'yargs/helpers';
|
|||||||
import { flatten } from '@poanet/solidity-flattener';
|
import { flatten } from '@poanet/solidity-flattener';
|
||||||
|
|
||||||
import { parse, visit } from '@solidity-parser/parser';
|
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 { MODE_ETH_CALL, MODE_STORAGE, MODE_ALL } from './utils/constants';
|
||||||
import { Visitor } from './visitor';
|
import { Visitor } from './visitor';
|
||||||
@ -51,6 +52,13 @@ const main = async (): Promise<void> => {
|
|||||||
default: MODE_ALL,
|
default: MODE_ALL,
|
||||||
choices: [MODE_ETH_CALL, MODE_STORAGE, 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', {
|
.option('flatten', {
|
||||||
alias: 'f',
|
alias: 'f',
|
||||||
describe: 'Flatten the input contract file.',
|
describe: 'Flatten the input contract file.',
|
||||||
@ -146,7 +154,7 @@ function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
|||||||
outStream = outputDir
|
outStream = outputDir
|
||||||
? fs.createWriteStream(path.join(outputDir, 'environments/local.toml'))
|
? fs.createWriteStream(path.join(outputDir, 'environments/local.toml'))
|
||||||
: process.stdout;
|
: process.stdout;
|
||||||
exportConfig(path.basename(outputDir), outStream);
|
exportConfig(argv.kind, path.basename(outputDir), outStream);
|
||||||
|
|
||||||
outStream = outputDir
|
outStream = outputDir
|
||||||
? fs.createWriteStream(path.join(outputDir, 'src/artifacts/', `${inputFileName}.json`))
|
? fs.createWriteStream(path.join(outputDir, 'src/artifacts/', `${inputFileName}.json`))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
[server]
|
[server]
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
port = 3008
|
port = 3008
|
||||||
|
kind = "{{watcherKind}}"
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
type = "postgres"
|
type = "postgres"
|
||||||
|
@ -45,7 +45,7 @@ export class Database {
|
|||||||
|
|
||||||
{{#each queries as | query |}}
|
{{#each queries as | query |}}
|
||||||
// eslint-disable-next-line camelcase
|
// 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
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
{{/if}}
|
{{/if}}
|
||||||
async get{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
|
async get{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
|
||||||
@ -65,7 +65,7 @@ export class Database {
|
|||||||
|
|
||||||
{{~#each queries as | query |}}
|
{{~#each queries as | query |}}
|
||||||
// eslint-disable-next-line camelcase
|
// 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
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
{{/if}}
|
{{/if}}
|
||||||
async save{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
|
async save{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
|
||||||
|
@ -201,6 +201,14 @@ export class Indexer {
|
|||||||
return this._baseIndexer.isWatchedContract(address);
|
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> {
|
async getSyncStatus (): Promise<SyncStatus | undefined> {
|
||||||
return this._baseIndexer.getSyncStatus();
|
return this._baseIndexer.getSyncStatus();
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
createdb {{folderName}}
|
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
|
createdb {{folderName}}-job-queue
|
||||||
@ -45,16 +47,18 @@
|
|||||||
yarn server
|
yarn server
|
||||||
```
|
```
|
||||||
|
|
||||||
* Run the job-runner:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn job-runner
|
|
||||||
```
|
|
||||||
|
|
||||||
GQL console: http://localhost:3008/graphql
|
GQL console: http://localhost:3008/graphql
|
||||||
|
|
||||||
* To watch a contract:
|
* If the watcher is an `active` watcher:
|
||||||
|
|
||||||
```bash
|
* Run the job-runner:
|
||||||
yarn watch:contract --address CONTRACT_ADDRESS --kind {{contractName}} --starting-block BLOCK_NUMBER
|
|
||||||
```
|
```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);
|
const events = await indexer.getEventsByFilter(blockHash, contractAddress, name);
|
||||||
return events.map(event => indexer.getResultEvent(event));
|
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 { getCache } from '@vulcanize/cache';
|
||||||
import { EthClient } from '@vulcanize/ipld-eth-client';
|
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 { createResolvers } from './resolvers';
|
||||||
import { Indexer } from './indexer';
|
import { Indexer } from './indexer';
|
||||||
@ -41,7 +41,7 @@ export const main = async (): Promise<any> => {
|
|||||||
|
|
||||||
assert(config.server, 'Missing server config');
|
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;
|
const { upstream, database: dbConfig, jobQueue: jobQueueConfig } = config;
|
||||||
|
|
||||||
@ -64,20 +64,23 @@ export const main = async (): Promise<any> => {
|
|||||||
|
|
||||||
const ethProvider = getDefaultProvider(rpcProviderEndpoint);
|
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);
|
const indexer = new Indexer(db, ethClient, ethProvider);
|
||||||
|
|
||||||
assert(jobQueueConfig, 'Missing job queue config');
|
if (watcherKind === KIND_ACTIVE) {
|
||||||
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
|
// Note: In-memory pubsub works fine for now, as each watcher is a single process anyway.
|
||||||
assert(dbConnectionString, 'Missing job queue db connection string');
|
// Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries
|
||||||
|
const pubsub = new PubSub();
|
||||||
|
|
||||||
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
|
assert(jobQueueConfig, 'Missing job queue config');
|
||||||
await jobQueue.start();
|
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
|
||||||
|
assert(dbConnectionString, 'Missing job queue db connection string');
|
||||||
|
|
||||||
const eventWatcher = new EventWatcher(ethClient, indexer, pubsub, jobQueue);
|
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
|
||||||
await eventWatcher.start();
|
await jobQueue.start();
|
||||||
|
|
||||||
|
const eventWatcher = new EventWatcher(ethClient, indexer, pubsub, jobQueue);
|
||||||
|
await eventWatcher.start();
|
||||||
|
}
|
||||||
|
|
||||||
const resolvers = await createResolvers(indexer);
|
const resolvers = await createResolvers(indexer);
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import Handlebars from 'handlebars';
|
import Handlebars from 'handlebars';
|
||||||
|
|
||||||
import { bannedTypes } from './types';
|
import { reservedNames } from './types';
|
||||||
|
|
||||||
export function registerHandlebarHelpers (): void {
|
export function registerHandlebarHelpers (): void {
|
||||||
Handlebars.registerHelper('compare', compareHelper);
|
Handlebars.registerHelper('compare', compareHelper);
|
||||||
Handlebars.registerHelper('capitalize', capitalizeHelper);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function banTypeCheckHelper (value: string): boolean {
|
function reservedNameCheckHelper (value: string): boolean {
|
||||||
return bannedTypes.has(value);
|
return reservedNames.has(value);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@ export interface Param {
|
|||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bannedTypes = new Set([
|
export const reservedNames = new Set([
|
||||||
'Symbol'
|
'Symbol'
|
||||||
]);
|
]);
|
||||||
|
@ -23,6 +23,7 @@ export interface Config {
|
|||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
mode: string;
|
mode: string;
|
||||||
|
kind: string;
|
||||||
};
|
};
|
||||||
database: ConnectionOptions;
|
database: ConnectionOptions;
|
||||||
upstream: {
|
upstream: {
|
||||||
|
@ -14,3 +14,6 @@ export const JOB_KIND_PRUNE = 'prune';
|
|||||||
export const DEFAULT_CONFIG_PATH = 'environments/local.toml';
|
export const DEFAULT_CONFIG_PATH = 'environments/local.toml';
|
||||||
|
|
||||||
export const UNKNOWN_EVENT_NAME = '__unknown__';
|
export const UNKNOWN_EVENT_NAME = '__unknown__';
|
||||||
|
|
||||||
|
export const KIND_ACTIVE = 'active';
|
||||||
|
export const KIND_LAZY = 'lazy';
|
||||||
|
Loading…
Reference in New Issue
Block a user