mirror of
https://github.com/cerc-io/watcher-ts
synced 2024-11-19 20:36:19 +00:00
Implement subgraph store host API (#35)
* Implement store get api without blockHash and blockNumber * Pass database instance to GraphWatcher * Implement store set without block data * Store blockHash and blockNumber in database entity table * Implement getting entity in subgraph from store.get * Add block data present in postgraphile * Pass db and context to instantiate method in tests * GQL API in graph-test-watcher to test store.set * Remove contract address from subgraph file * Fix block in dummy event data * Pass just blockHash to get an entity from the database * Review changes and add TODOs Co-authored-by: prathamesh <prathamesh.musale0@gmail.com>
This commit is contained in:
parent
43d64f9e4b
commit
83775608ec
@ -5,9 +5,13 @@
|
|||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphprotocol/graph-ts": "^0.22.0",
|
"@graphprotocol/graph-ts": "^0.22.0",
|
||||||
|
"@types/chai": "^4.2.18",
|
||||||
|
"@types/chai-spies": "^1.0.3",
|
||||||
"@types/js-yaml": "^4.0.4",
|
"@types/js-yaml": "^4.0.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
||||||
"@typescript-eslint/parser": "^4.25.0",
|
"@typescript-eslint/parser": "^4.25.0",
|
||||||
|
"chai": "^4.3.4",
|
||||||
|
"chai-spies": "^1.0.0",
|
||||||
"eslint": "^7.27.0",
|
"eslint": "^7.27.0",
|
||||||
"eslint-config-semistandard": "^15.0.1",
|
"eslint-config-semistandard": "^15.0.1",
|
||||||
"eslint-config-standard": "^16.0.3",
|
"eslint-config-standard": "^16.0.3",
|
||||||
@ -32,6 +36,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vulcanize/assemblyscript": "0.0.1",
|
"@vulcanize/assemblyscript": "0.0.1",
|
||||||
"js-yaml": "^4.1.0"
|
"js-yaml": "^4.1.0",
|
||||||
|
"typeorm": "^0.2.32"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,54 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import chai, { assert, expect } from 'chai';
|
||||||
|
import spies from 'chai-spies';
|
||||||
|
|
||||||
import { getDummyEventData } from '../test/utils';
|
import { getDummyEventData, getTestDatabase } from '../test/utils';
|
||||||
import { instantiate } from './loader';
|
import { instantiate } from './loader';
|
||||||
import { createEvent } from './utils';
|
import { createEvent, Block } from './utils';
|
||||||
|
import { Database } from './database';
|
||||||
|
|
||||||
|
chai.use(spies);
|
||||||
|
|
||||||
|
const sandbox = chai.spy.sandbox();
|
||||||
|
|
||||||
describe('call handler in mapping code', () => {
|
describe('call handler in mapping code', () => {
|
||||||
let exports: any;
|
let exports: any;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
const eventData = getDummyEventData();
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
db = getTestDatabase();
|
||||||
|
|
||||||
|
sandbox.on(db, 'getEntity', (blockHash: string, entityString: string, idString: string) => {
|
||||||
|
assert(blockHash);
|
||||||
|
assert(entityString);
|
||||||
|
assert(idString);
|
||||||
|
});
|
||||||
|
|
||||||
|
sandbox.on(db, 'fromGraphEntity', async (instanceExports: any, block: Block, entity: string, entityInstance: any) => {
|
||||||
|
const entityFields = [
|
||||||
|
{ type: 'varchar', propertyName: 'blockHash' },
|
||||||
|
{ type: 'integer', propertyName: 'blockNumber' },
|
||||||
|
{ type: 'bigint', propertyName: 'count' },
|
||||||
|
{ type: 'varchar', propertyName: 'param1' },
|
||||||
|
{ type: 'integer', propertyName: 'param2' }
|
||||||
|
];
|
||||||
|
|
||||||
|
return db.getEntityValues(instanceExports, block, entityInstance, entityFields);
|
||||||
|
});
|
||||||
|
|
||||||
|
sandbox.on(db, 'saveEntity', (entity: string, data: any) => {
|
||||||
|
assert(entity);
|
||||||
|
assert(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should load the subgraph example wasm', async () => {
|
it('should load the subgraph example wasm', async () => {
|
||||||
const filePath = path.resolve(__dirname, '../test/subgraph/example1/build/Example1/Example1.wasm');
|
const filePath = path.resolve(__dirname, '../test/subgraph/example1/build/Example1/Example1.wasm');
|
||||||
const instance = await instantiate(filePath);
|
const instance = await instantiate(db, { event: { block: eventData.block } }, filePath);
|
||||||
exports = instance.exports;
|
exports = instance.exports;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -27,8 +64,6 @@ describe('call handler in mapping code', () => {
|
|||||||
// TODO: Check api version https://github.com/graphprotocol/graph-node/blob/6098daa8955bdfac597cec87080af5449807e874/runtime/wasm/src/module/mod.rs#L533
|
// TODO: Check api version https://github.com/graphprotocol/graph-node/blob/6098daa8955bdfac597cec87080af5449807e874/runtime/wasm/src/module/mod.rs#L533
|
||||||
_start();
|
_start();
|
||||||
|
|
||||||
const eventData = getDummyEventData();
|
|
||||||
|
|
||||||
// Create event params data.
|
// Create event params data.
|
||||||
eventData.eventParams = [
|
eventData.eventParams = [
|
||||||
{
|
{
|
||||||
@ -50,5 +85,13 @@ describe('call handler in mapping code', () => {
|
|||||||
const test = await createEvent(exports, contractAddress, eventData);
|
const test = await createEvent(exports, contractAddress, eventData);
|
||||||
|
|
||||||
await handleTest(test);
|
await handleTest(test);
|
||||||
|
|
||||||
|
expect(db.getEntity).to.have.been.called();
|
||||||
|
expect(db.fromGraphEntity).to.have.been.called();
|
||||||
|
expect(db.saveEntity).to.have.been.called();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
136
packages/graph-node/src/database.ts
Normal file
136
packages/graph-node/src/database.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 Vulcanize, Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
import assert from 'assert';
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
ConnectionOptions,
|
||||||
|
FindOneOptions
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Database as BaseDatabase
|
||||||
|
} from '@vulcanize/util';
|
||||||
|
|
||||||
|
import { Block, fromEntityValue, toEntityValue } from './utils';
|
||||||
|
|
||||||
|
export class Database {
|
||||||
|
_config: ConnectionOptions
|
||||||
|
_conn!: Connection
|
||||||
|
_baseDatabase: BaseDatabase
|
||||||
|
|
||||||
|
constructor (config: ConnectionOptions, entitiesPath: string) {
|
||||||
|
assert(config);
|
||||||
|
|
||||||
|
this._config = {
|
||||||
|
name: 'subgraph',
|
||||||
|
...config,
|
||||||
|
entities: [entitiesPath]
|
||||||
|
};
|
||||||
|
|
||||||
|
this._baseDatabase = new BaseDatabase(this._config);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init (): Promise<void> {
|
||||||
|
this._conn = await this._baseDatabase.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async close (): Promise<void> {
|
||||||
|
return this._baseDatabase.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntity (blockHash: string, entity: string, id: string): Promise<any> {
|
||||||
|
const queryRunner = this._conn.createQueryRunner();
|
||||||
|
const repo = queryRunner.manager.getRepository(entity);
|
||||||
|
const whereOptions: { [key: string]: any } = { id };
|
||||||
|
|
||||||
|
if (blockHash) {
|
||||||
|
whereOptions.blockHash = blockHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findOptions = {
|
||||||
|
where: whereOptions,
|
||||||
|
order: {
|
||||||
|
blockNumber: 'DESC'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let entityData = await repo.findOne(findOptions as FindOneOptions<any>);
|
||||||
|
|
||||||
|
if (!entityData && findOptions.where.blockHash) {
|
||||||
|
entityData = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveEntity (entity: string, data: any): Promise<void> {
|
||||||
|
const repo = this._conn.getRepository(entity);
|
||||||
|
|
||||||
|
const dbEntity: any = repo.create(data);
|
||||||
|
await repo.save(dbEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
async toGraphEntity (instanceExports: any, entity: string, data: any): Promise<any> {
|
||||||
|
// TODO: Cache schema/columns.
|
||||||
|
const repo = this._conn.getRepository(entity);
|
||||||
|
const entityFields = repo.metadata.columns;
|
||||||
|
|
||||||
|
const { Entity } = instanceExports;
|
||||||
|
const entityInstance = await Entity.__new();
|
||||||
|
|
||||||
|
const entityValuePromises = entityFields.filter(field => {
|
||||||
|
const { propertyName } = field;
|
||||||
|
|
||||||
|
// TODO: Will clash if entity has blockHash and blockNumber fields.
|
||||||
|
if (propertyName === 'blockHash' || propertyName === 'blockNumber') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).map(async (field) => {
|
||||||
|
const { type, propertyName } = field;
|
||||||
|
|
||||||
|
return toEntityValue(instanceExports, entityInstance, data, type.toString(), propertyName);
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
await Promise.all(entityValuePromises);
|
||||||
|
|
||||||
|
return entityInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fromGraphEntity (instanceExports: any, block: Block, entity: string, entityInstance: any): Promise<{ [key: string]: any } > {
|
||||||
|
// TODO: Cache schema/columns.
|
||||||
|
const repo = this._conn.getRepository(entity);
|
||||||
|
const entityFields = repo.metadata.columns;
|
||||||
|
|
||||||
|
return this.getEntityValues(instanceExports, block, entityInstance, entityFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntityValues (instanceExports: any, block: Block, entityInstance: any, entityFields: any): Promise<{ [key: string]: any } > {
|
||||||
|
const entityValuePromises = entityFields.map(async (field: any) => {
|
||||||
|
const { type, propertyName } = field;
|
||||||
|
|
||||||
|
// TODO: Will clash if entity has blockHash and blockNumber fields.
|
||||||
|
if (propertyName === 'blockHash') {
|
||||||
|
return block.blockHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyName === 'blockNumber') {
|
||||||
|
return block.blockNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromEntityValue(instanceExports, entityInstance, type.toString(), propertyName);
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const entityValues = await Promise.all(entityValuePromises);
|
||||||
|
|
||||||
|
return entityFields.reduce((acc: { [key: string]: any }, field: any, index: number) => {
|
||||||
|
const { propertyName } = field;
|
||||||
|
acc[propertyName] = entityValues[index];
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
@ -5,19 +5,48 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import chai from 'chai';
|
||||||
|
import spies from 'chai-spies';
|
||||||
|
|
||||||
import { instantiate } from './loader';
|
import { instantiate } from './loader';
|
||||||
import { createEvent } from './utils';
|
import { createEvent, Block } from './utils';
|
||||||
import edenNetworkAbi from '../test/subgraph/eden/EdenNetwork/abis/EdenNetwork.json';
|
import edenNetworkAbi from '../test/subgraph/eden/EdenNetwork/abis/EdenNetwork.json';
|
||||||
import merkleDistributorAbi from '../test/subgraph/eden/EdenNetworkDistribution/abis/MerkleDistributor.json';
|
import merkleDistributorAbi from '../test/subgraph/eden/EdenNetworkDistribution/abis/MerkleDistributor.json';
|
||||||
import distributorGovernanceAbi from '../test/subgraph/eden/EdenNetworkGovernance/abis/DistributorGovernance.json';
|
import distributorGovernanceAbi from '../test/subgraph/eden/EdenNetworkGovernance/abis/DistributorGovernance.json';
|
||||||
import { getDummyEventData } from '../test/utils';
|
import { getDummyEventData, getTestDatabase } from '../test/utils';
|
||||||
|
import { Database } from './database';
|
||||||
|
|
||||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
|
|
||||||
describe('eden wasm loader tests', () => {
|
chai.use(spies);
|
||||||
|
|
||||||
|
const sandbox = chai.spy.sandbox();
|
||||||
|
|
||||||
|
describe('eden wasm loader tests', async () => {
|
||||||
|
let db: Database;
|
||||||
const eventData = getDummyEventData();
|
const eventData = getDummyEventData();
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
db = getTestDatabase();
|
||||||
|
|
||||||
|
sandbox.on(db, 'getEntity', (blockHash: string, entityString: string, idString: string) => {
|
||||||
|
assert(blockHash);
|
||||||
|
assert(entityString);
|
||||||
|
assert(idString);
|
||||||
|
});
|
||||||
|
|
||||||
|
sandbox.on(db, 'fromGraphEntity', async (instanceExports: any, block: Block, entity: string, entityInstance: any) => {
|
||||||
|
const entityFields: any = [];
|
||||||
|
|
||||||
|
return db.getEntityValues(instanceExports, block, entityInstance, entityFields);
|
||||||
|
});
|
||||||
|
|
||||||
|
sandbox.on(db, 'saveEntity', (entity: string, data: any) => {
|
||||||
|
assert(entity);
|
||||||
|
assert(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('EdenNetwork wasm', () => {
|
describe('EdenNetwork wasm', () => {
|
||||||
let exports: any;
|
let exports: any;
|
||||||
|
|
||||||
@ -36,7 +65,7 @@ describe('eden wasm loader tests', () => {
|
|||||||
|
|
||||||
it('should load the subgraph network wasm', async () => {
|
it('should load the subgraph network wasm', async () => {
|
||||||
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetwork/EdenNetwork.wasm');
|
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetwork/EdenNetwork.wasm');
|
||||||
({ exports } = await instantiate(filePath, data));
|
({ exports } = await instantiate(db, { event: { block: eventData.block } }, filePath, data));
|
||||||
const { _start } = exports;
|
const { _start } = exports;
|
||||||
_start();
|
_start();
|
||||||
});
|
});
|
||||||
@ -195,7 +224,7 @@ describe('eden wasm loader tests', () => {
|
|||||||
|
|
||||||
it('should load the subgraph network distribution wasm', async () => {
|
it('should load the subgraph network distribution wasm', async () => {
|
||||||
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetworkDistribution/EdenNetworkDistribution.wasm');
|
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetworkDistribution/EdenNetworkDistribution.wasm');
|
||||||
({ exports } = await instantiate(filePath, data));
|
({ exports } = await instantiate(db, { event: { block: eventData.block } }, filePath, data));
|
||||||
const { _start } = exports;
|
const { _start } = exports;
|
||||||
_start();
|
_start();
|
||||||
});
|
});
|
||||||
@ -339,7 +368,7 @@ describe('eden wasm loader tests', () => {
|
|||||||
|
|
||||||
it('should load the subgraph network governance wasm', async () => {
|
it('should load the subgraph network governance wasm', async () => {
|
||||||
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetworkGovernance/EdenNetworkGovernance.wasm');
|
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetworkGovernance/EdenNetworkGovernance.wasm');
|
||||||
({ exports } = await instantiate(filePath, data));
|
({ exports } = await instantiate(db, { event: { block: eventData.block } }, filePath, data));
|
||||||
const { _start } = exports;
|
const { _start } = exports;
|
||||||
_start();
|
_start();
|
||||||
});
|
});
|
||||||
@ -427,4 +456,8 @@ describe('eden wasm loader tests', () => {
|
|||||||
await rewardScheduleChanged(rewardScheduleChangedEvent);
|
await rewardScheduleChanged(rewardScheduleChangedEvent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,9 +7,12 @@ import path from 'path';
|
|||||||
|
|
||||||
import { instantiate } from './loader';
|
import { instantiate } from './loader';
|
||||||
import exampleAbi from '../test/subgraph/example1/build/Example1/abis/Example1.json';
|
import exampleAbi from '../test/subgraph/example1/build/Example1/abis/Example1.json';
|
||||||
|
import { getTestDatabase } from '../test/utils';
|
||||||
|
import { Database } from './database';
|
||||||
|
|
||||||
describe('eth-call wasm tests', () => {
|
describe('eth-call wasm tests', () => {
|
||||||
let exports: any;
|
let exports: any;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
const contractAddress = process.env.EXAMPLE_CONTRACT_ADDRESS;
|
const contractAddress = process.env.EXAMPLE_CONTRACT_ADDRESS;
|
||||||
assert(contractAddress);
|
assert(contractAddress);
|
||||||
@ -23,9 +26,13 @@ describe('eth-call wasm tests', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
db = getTestDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
it('should load the subgraph example wasm', async () => {
|
it('should load the subgraph example wasm', async () => {
|
||||||
const filePath = path.resolve(__dirname, '../test/subgraph/example1/build/Example1/Example1.wasm');
|
const filePath = path.resolve(__dirname, '../test/subgraph/example1/build/Example1/Example1.wasm');
|
||||||
const instance = await instantiate(filePath, data);
|
const instance = await instantiate(db, { event: {} }, filePath, data);
|
||||||
exports = instance.exports;
|
exports = instance.exports;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from './watcher';
|
export * from './watcher';
|
||||||
|
export * from './database';
|
||||||
|
@ -6,15 +6,21 @@ import path from 'path';
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
|
||||||
import { instantiate } from './loader';
|
import { instantiate } from './loader';
|
||||||
|
import { getTestDatabase } from '../test/utils';
|
||||||
|
import { Database } from './database';
|
||||||
|
|
||||||
const WASM_FILE_PATH = '../build/debug.wasm';
|
const WASM_FILE_PATH = '../build/debug.wasm';
|
||||||
|
|
||||||
describe('wasm loader tests', () => {
|
describe('wasm loader tests', () => {
|
||||||
let exports: any;
|
let exports: any;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
db = getTestDatabase();
|
||||||
|
|
||||||
const filePath = path.resolve(__dirname, WASM_FILE_PATH);
|
const filePath = path.resolve(__dirname, WASM_FILE_PATH);
|
||||||
const instance = await instantiate(filePath);
|
const instance = await instantiate(db, { event: {} }, filePath);
|
||||||
|
|
||||||
exports = instance.exports;
|
exports = instance.exports;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ import {
|
|||||||
} from 'ethers';
|
} from 'ethers';
|
||||||
|
|
||||||
import { TypeId } from './types';
|
import { TypeId } from './types';
|
||||||
import { fromEthereumValue, toEthereumValue } from './utils';
|
import { Block, fromEthereumValue, toEthereumValue } from './utils';
|
||||||
|
import { Database } from './database';
|
||||||
|
|
||||||
const NETWORK_URL = 'http://127.0.0.1:8081';
|
const NETWORK_URL = 'http://127.0.0.1:8081';
|
||||||
|
|
||||||
@ -29,7 +30,13 @@ interface GraphData {
|
|||||||
dataSource?: DataSource;
|
dataSource?: DataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const instantiate = async (filePath: string, data: GraphData = {}): Promise<loader.ResultObject & { exports: any }> => {
|
export interface Context {
|
||||||
|
event: {
|
||||||
|
block?: Block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const instantiate = async (database: Database, context: Context, filePath: string, data: GraphData = {}): Promise<loader.ResultObject & { exports: any }> => {
|
||||||
const { abis = {}, dataSource } = data;
|
const { abis = {}, dataSource } = data;
|
||||||
const buffer = await fs.readFile(filePath);
|
const buffer = await fs.readFile(filePath);
|
||||||
const provider = getDefaultProvider(NETWORK_URL);
|
const provider = getDefaultProvider(NETWORK_URL);
|
||||||
@ -37,31 +44,26 @@ export const instantiate = async (filePath: string, data: GraphData = {}): Promi
|
|||||||
const imports: WebAssembly.Imports = {
|
const imports: WebAssembly.Imports = {
|
||||||
index: {
|
index: {
|
||||||
'store.get': async (entity: number, id: number) => {
|
'store.get': async (entity: number, id: number) => {
|
||||||
console.log('store.get');
|
const entityName = __getString(entity);
|
||||||
|
const entityId = __getString(id);
|
||||||
|
|
||||||
const entityString = __getString(entity);
|
assert(context.event.block);
|
||||||
console.log('entity:', entityString);
|
const entityData = await database.getEntity(context.event.block.blockHash, entityName, entityId);
|
||||||
const idString = __getString(id);
|
|
||||||
console.log('id:', idString);
|
|
||||||
|
|
||||||
// TODO: Implement store get to fetch from DB using entity and id.
|
if (!entityData) {
|
||||||
|
|
||||||
// TODO: Fill entity with field values.
|
|
||||||
// return Entity.__new()
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return database.toGraphEntity(exports, entityName, entityData);
|
||||||
},
|
},
|
||||||
'store.set': async (entity: number, id: number, data: number) => {
|
'store.set': async (entity: number, id: number, data: number) => {
|
||||||
console.log('store.set');
|
const entityName = __getString(entity);
|
||||||
|
|
||||||
const entityString = __getString(entity);
|
|
||||||
console.log('entity:', entityString);
|
|
||||||
const idString = __getString(id);
|
|
||||||
console.log('id:', idString);
|
|
||||||
const entityInstance = await Entity.wrap(data);
|
const entityInstance = await Entity.wrap(data);
|
||||||
const entityInstanceId = __getString(await entityInstance.getString(await __newString('id')));
|
|
||||||
console.log('entity instance id:', entityInstanceId);
|
|
||||||
|
|
||||||
// TODO: Implement store set to save entity in db with values from entityInstance.
|
assert(context.event.block);
|
||||||
|
const dbData = await database.fromGraphEntity(exports, context.event.block, entityName, entityInstance);
|
||||||
|
await database.saveEntity(entityName, dbData);
|
||||||
},
|
},
|
||||||
|
|
||||||
'typeConversion.stringToH160': () => {
|
'typeConversion.stringToH160': () => {
|
||||||
|
@ -6,15 +6,20 @@ import path from 'path';
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
|
||||||
import { instantiate } from './loader';
|
import { instantiate } from './loader';
|
||||||
|
import { getTestDatabase } from '../test/utils';
|
||||||
|
import { Database } from './database';
|
||||||
|
|
||||||
const EXAMPLE_WASM_FILE_PATH = '../test/subgraph/example1/build/Example1/Example1.wasm';
|
const EXAMPLE_WASM_FILE_PATH = '../test/subgraph/example1/build/Example1/Example1.wasm';
|
||||||
|
|
||||||
describe('numbers wasm tests', () => {
|
describe('numbers wasm tests', () => {
|
||||||
let exports: any;
|
let exports: any;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
db = getTestDatabase();
|
||||||
|
|
||||||
const filePath = path.resolve(__dirname, EXAMPLE_WASM_FILE_PATH);
|
const filePath = path.resolve(__dirname, EXAMPLE_WASM_FILE_PATH);
|
||||||
const instance = await instantiate(filePath);
|
const instance = await instantiate(db, { event: {} }, filePath);
|
||||||
exports = instance.exports;
|
exports = instance.exports;
|
||||||
const { _start } = exports;
|
const { _start } = exports;
|
||||||
|
|
||||||
|
@ -6,15 +6,20 @@ import path from 'path';
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
|
||||||
import { instantiate } from './loader';
|
import { instantiate } from './loader';
|
||||||
|
import { getTestDatabase } from '../test/utils';
|
||||||
|
import { Database } from './database';
|
||||||
|
|
||||||
const EXAMPLE_WASM_FILE_PATH = '../test/subgraph/example1/build/Example1/Example1.wasm';
|
const EXAMPLE_WASM_FILE_PATH = '../test/subgraph/example1/build/Example1/Example1.wasm';
|
||||||
|
|
||||||
describe('typeConversion wasm tests', () => {
|
describe('typeConversion wasm tests', () => {
|
||||||
let exports: any;
|
let exports: any;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
db = getTestDatabase();
|
||||||
|
|
||||||
const filePath = path.resolve(__dirname, EXAMPLE_WASM_FILE_PATH);
|
const filePath = path.resolve(__dirname, EXAMPLE_WASM_FILE_PATH);
|
||||||
const instance = await instantiate(filePath);
|
const instance = await instantiate(db, { event: {} }, filePath);
|
||||||
exports = instance.exports;
|
exports = instance.exports;
|
||||||
const { _start } = exports;
|
const { _start } = exports;
|
||||||
|
|
||||||
|
@ -14,13 +14,6 @@ interface EventParam {
|
|||||||
kind: string;
|
kind: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Block {
|
|
||||||
hash: string;
|
|
||||||
number: number;
|
|
||||||
timestamp: number;
|
|
||||||
parentHash: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Transaction {
|
interface Transaction {
|
||||||
hash: string;
|
hash: string;
|
||||||
index: number;
|
index: number;
|
||||||
@ -28,6 +21,17 @@ interface Transaction {
|
|||||||
to: string;
|
to: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Block {
|
||||||
|
blockHash: string;
|
||||||
|
blockNumber: string;
|
||||||
|
timestamp: string;
|
||||||
|
parentHash: string;
|
||||||
|
stateRoot: string;
|
||||||
|
td: string;
|
||||||
|
txRoot: string;
|
||||||
|
receiptRoot: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EventData {
|
export interface EventData {
|
||||||
block: Block;
|
block: Block;
|
||||||
tx: Transaction;
|
tx: Transaction;
|
||||||
@ -161,41 +165,48 @@ export const createEvent = async (instanceExports: any, contractAddress: string,
|
|||||||
} = instanceExports;
|
} = instanceExports;
|
||||||
|
|
||||||
// Fill block data.
|
// Fill block data.
|
||||||
const blockHashByteArray = await ByteArray.fromHexString(await __newString(blockData.hash));
|
const blockHashByteArray = await ByteArray.fromHexString(await __newString(blockData.blockHash));
|
||||||
const blockHash = await Bytes.fromByteArray(blockHashByteArray);
|
const blockHash = await Bytes.fromByteArray(blockHashByteArray);
|
||||||
|
|
||||||
const parentHashByteArray = await ByteArray.fromHexString(await __newString(blockData.parentHash));
|
const parentHashByteArray = await ByteArray.fromHexString(await __newString(blockData.parentHash));
|
||||||
const parentHash = await Bytes.fromByteArray(parentHashByteArray);
|
const parentHash = await Bytes.fromByteArray(parentHashByteArray);
|
||||||
|
|
||||||
const blockNumber = await BigInt.fromI32(blockData.number);
|
const blockNumber = await BigInt.fromString(await __newString(blockData.blockNumber));
|
||||||
|
|
||||||
const blockTimestamp = await BigInt.fromI32(blockData.timestamp);
|
const blockTimestamp = await BigInt.fromString(await __newString(blockData.timestamp));
|
||||||
|
|
||||||
|
const stateRootByteArray = await ByteArray.fromHexString(await __newString(blockData.stateRoot));
|
||||||
|
const stateRoot = await Bytes.fromByteArray(stateRootByteArray);
|
||||||
|
|
||||||
|
const transactionsRootByteArray = await ByteArray.fromHexString(await __newString(blockData.txRoot));
|
||||||
|
const transactionsRoot = await Bytes.fromByteArray(transactionsRootByteArray);
|
||||||
|
|
||||||
|
const receiptsRootByteArray = await ByteArray.fromHexString(await __newString(blockData.receiptRoot));
|
||||||
|
const receiptsRoot = await Bytes.fromByteArray(receiptsRootByteArray);
|
||||||
|
|
||||||
|
const totalDifficulty = await BigInt.fromString(await __newString(blockData.td));
|
||||||
|
|
||||||
// Missing fields from watcher in block data:
|
// Missing fields from watcher in block data:
|
||||||
// unclesHash
|
// unclesHash
|
||||||
// author
|
// author
|
||||||
// stateRoot
|
|
||||||
// transactionsRoot
|
|
||||||
// receiptsRoot
|
|
||||||
// gasUsed
|
// gasUsed
|
||||||
// gasLimit
|
// gasLimit
|
||||||
// difficulty
|
// difficulty
|
||||||
// totalDifficulty
|
|
||||||
// size
|
// size
|
||||||
const block = await ethereum.Block.__new(
|
const block = await ethereum.Block.__new(
|
||||||
blockHash,
|
blockHash,
|
||||||
parentHash,
|
parentHash,
|
||||||
await Bytes.empty(),
|
await Bytes.empty(),
|
||||||
await Address.zero(),
|
await Address.zero(),
|
||||||
await Bytes.empty(),
|
stateRoot,
|
||||||
await Bytes.empty(),
|
transactionsRoot,
|
||||||
await Bytes.empty(),
|
receiptsRoot,
|
||||||
blockNumber,
|
blockNumber,
|
||||||
await BigInt.fromI32(0),
|
await BigInt.fromI32(0),
|
||||||
await BigInt.fromI32(0),
|
await BigInt.fromI32(0),
|
||||||
blockTimestamp,
|
blockTimestamp,
|
||||||
await BigInt.fromI32(0),
|
await BigInt.fromI32(0),
|
||||||
await BigInt.fromI32(0),
|
totalDifficulty,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -266,3 +277,55 @@ export const getSubgraphConfig = async (subgraphPath: string): Promise<any> => {
|
|||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const toEntityValue = async (instanceExports: any, entityInstance: any, data: any, type: string, key: string) => {
|
||||||
|
const { __newString, BigInt: ExportBigInt } = instanceExports;
|
||||||
|
const entityKey = await __newString(key);
|
||||||
|
const value = data[key];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'varchar': {
|
||||||
|
const entityValue = await __newString(value);
|
||||||
|
|
||||||
|
return entityInstance.setString(entityKey, entityValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'integer': {
|
||||||
|
return entityInstance.setI32(entityKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'bigint': {
|
||||||
|
const bigInt = await ExportBigInt.fromString(await __newString(value.toString()));
|
||||||
|
|
||||||
|
return entityInstance.setBigInt(entityKey, bigInt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support more types.
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported type: ${type}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fromEntityValue = async (instanceExports: any, entityInstance: any, type: string, key: string): Promise<any> => {
|
||||||
|
const { __newString, __getString, BigInt: ExportBigInt } = instanceExports;
|
||||||
|
const entityKey = await __newString(key);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'varchar': {
|
||||||
|
return __getString(await entityInstance.getString(entityKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'integer': {
|
||||||
|
return entityInstance.getI32(entityKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'bigint': {
|
||||||
|
const bigInt = ExportBigInt.wrap(await entityInstance.getBigInt(entityKey));
|
||||||
|
return BigInt(__getString(await bigInt.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support more types.
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported type: ${type}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -9,9 +9,11 @@ import fs from 'fs';
|
|||||||
import { ContractInterface, utils } from 'ethers';
|
import { ContractInterface, utils } from 'ethers';
|
||||||
|
|
||||||
import { ResultObject } from '@vulcanize/assemblyscript/lib/loader';
|
import { ResultObject } from '@vulcanize/assemblyscript/lib/loader';
|
||||||
|
import { EthClient } from '@vulcanize/ipld-eth-client';
|
||||||
|
|
||||||
import { createEvent, getSubgraphConfig } from './utils';
|
import { createEvent, getSubgraphConfig } from './utils';
|
||||||
import { instantiate } from './loader';
|
import { Context, instantiate } from './loader';
|
||||||
|
import { Database } from './database';
|
||||||
|
|
||||||
const log = debug('vulcanize:graph-watcher');
|
const log = debug('vulcanize:graph-watcher');
|
||||||
|
|
||||||
@ -21,11 +23,19 @@ interface DataSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class GraphWatcher {
|
export class GraphWatcher {
|
||||||
|
_database: Database;
|
||||||
|
_postgraphileClient: EthClient;
|
||||||
_subgraphPath: string;
|
_subgraphPath: string;
|
||||||
_dataSources: any[] = [];
|
_dataSources: any[] = [];
|
||||||
_dataSourceMap: { [key: string]: DataSource } = {};
|
_dataSourceMap: { [key: string]: DataSource } = {};
|
||||||
|
|
||||||
constructor (subgraphPath: string) {
|
_context: Context = {
|
||||||
|
event: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (database: Database, postgraphileClient: EthClient, subgraphPath: string) {
|
||||||
|
this._database = database;
|
||||||
|
this._postgraphileClient = postgraphileClient;
|
||||||
this._subgraphPath = subgraphPath;
|
this._subgraphPath = subgraphPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +68,7 @@ export class GraphWatcher {
|
|||||||
const filePath = path.join(this._subgraphPath, file);
|
const filePath = path.join(this._subgraphPath, file);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instance: await instantiate(filePath, data),
|
instance: await instantiate(this._database, this._context, filePath, data),
|
||||||
contractInterface
|
contractInterface
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
@ -83,6 +93,16 @@ export class GraphWatcher {
|
|||||||
async handleEvent (eventData: any) {
|
async handleEvent (eventData: any) {
|
||||||
const { contract, event, eventSignature, block, tx, eventIndex } = eventData;
|
const { contract, event, eventSignature, block, tx, eventIndex } = eventData;
|
||||||
|
|
||||||
|
const {
|
||||||
|
allEthHeaderCids: {
|
||||||
|
nodes: [
|
||||||
|
blockData
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} = await this._postgraphileClient.getBlocks({ blockHash: block.hash });
|
||||||
|
|
||||||
|
this._context.event.block = blockData;
|
||||||
|
|
||||||
// Get dataSource in subgraph yaml based on contract address.
|
// Get dataSource in subgraph yaml based on contract address.
|
||||||
const dataSource = this._dataSources.find(dataSource => dataSource.source.address === contract);
|
const dataSource = this._dataSources.find(dataSource => dataSource.source.address === contract);
|
||||||
|
|
||||||
@ -113,7 +133,7 @@ export class GraphWatcher {
|
|||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
eventParams: eventParams,
|
eventParams: eventParams,
|
||||||
block,
|
block: blockData,
|
||||||
tx,
|
tx,
|
||||||
eventIndex
|
eventIndex
|
||||||
};
|
};
|
||||||
@ -123,4 +143,8 @@ export class GraphWatcher {
|
|||||||
|
|
||||||
await exports[eventHandler.handler](ethereumEvent);
|
await exports[eventHandler.handler](ethereumEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEntity (blockHash: string, entity: string, id: string): Promise<any> {
|
||||||
|
return this._database.getEntity(blockHash, entity, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,36 +4,37 @@ import {
|
|||||||
Example1,
|
Example1,
|
||||||
Test
|
Test
|
||||||
} from '../generated/Example1/Example1';
|
} from '../generated/Example1/Example1';
|
||||||
// import { ExampleEntity } from '../generated/schema';
|
import { ExampleEntity } from '../generated/schema';
|
||||||
|
|
||||||
export function handleTest (event: Test): void {
|
export function handleTest (event: Test): void {
|
||||||
log.debug('event.address: {}', [event.address.toHexString()]);
|
log.debug('event.address: {}', [event.address.toHexString()]);
|
||||||
log.debug('event.params.param1: {}', [event.params.param1]);
|
log.debug('event.params.param1: {}', [event.params.param1]);
|
||||||
log.debug('event.params.param2: {}', [event.params.param2.toString()]);
|
log.debug('event.params.param2: {}', [event.params.param2.toString()]);
|
||||||
log.debug('event.block.hash: {}', [event.block.hash.toHexString()]);
|
log.debug('event.block.hash: {}', [event.block.hash.toHexString()]);
|
||||||
|
log.debug('event.block.stateRoot: {}', [event.block.stateRoot.toHexString()]);
|
||||||
|
|
||||||
// Entities can be loaded from the store using a string ID; this ID
|
// Entities can be loaded from the store using a string ID; this ID
|
||||||
// needs to be unique across all entities of the same type
|
// needs to be unique across all entities of the same type
|
||||||
// let entity = ExampleEntity.load(event.transaction.from.toHex());
|
let entity = ExampleEntity.load(event.transaction.from.toHex());
|
||||||
|
|
||||||
// Entities only exist after they have been saved to the store;
|
// Entities only exist after they have been saved to the store;
|
||||||
// `null` checks allow to create entities on demand
|
// `null` checks allow to create entities on demand
|
||||||
// if (!entity) {
|
if (!entity) {
|
||||||
// entity = new ExampleEntity(event.transaction.from.toHex());
|
entity = new ExampleEntity(event.transaction.from.toHex());
|
||||||
|
|
||||||
// // Entity fields can be set using simple assignments
|
// Entity fields can be set using simple assignments
|
||||||
// entity.count = BigInt.fromI32(0);
|
entity.count = BigInt.fromString('0');
|
||||||
// }
|
}
|
||||||
|
|
||||||
// BigInt and BigDecimal math are supported
|
// BigInt and BigDecimal math are supported
|
||||||
// entity.count = entity.count + BigInt.fromI32(1)
|
entity.count = entity.count + BigInt.fromString('1');
|
||||||
|
|
||||||
// Entity fields can be set based on event parameters
|
// Entity fields can be set based on event parameters
|
||||||
// entity.param1 = event.params.param1;
|
entity.param1 = event.params.param1;
|
||||||
// entity.param2 = event.params.param2;
|
entity.param2 = event.params.param2;
|
||||||
|
|
||||||
// Entities can be written to the store with `.save()`
|
// Entities can be written to the store with `.save()`
|
||||||
// entity.save();
|
entity.save();
|
||||||
|
|
||||||
// Note: If a handler doesn't require existing field values, it is faster
|
// Note: If a handler doesn't require existing field values, it is faster
|
||||||
// _not_ to load the entity from the store. Instead, create it fresh with
|
// _not_ to load the entity from the store. Instead, create it fresh with
|
||||||
|
@ -6,7 +6,7 @@ dataSources:
|
|||||||
name: Example1
|
name: Example1
|
||||||
network: mainnet
|
network: mainnet
|
||||||
source:
|
source:
|
||||||
address: "0x4Ab7aE18973491Df21d6103dfA55170fdB2CCC98"
|
address: ""
|
||||||
abi: Example1
|
abi: Example1
|
||||||
mapping:
|
mapping:
|
||||||
kind: ethereum/events
|
kind: ethereum/events
|
||||||
|
@ -3,16 +3,21 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { EventData } from '../../src/utils';
|
import { EventData } from '../../src/utils';
|
||||||
|
import { Database } from '../../src/database';
|
||||||
|
|
||||||
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
export const ZERO_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
export const ZERO_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
|
||||||
export const getDummyEventData = (): EventData => {
|
export const getDummyEventData = (): EventData => {
|
||||||
const block = {
|
const block = {
|
||||||
hash: ZERO_HASH,
|
blockHash: ZERO_HASH,
|
||||||
number: 0,
|
blockNumber: '0',
|
||||||
timestamp: 0,
|
timestamp: '0',
|
||||||
parentHash: ZERO_HASH
|
parentHash: ZERO_HASH,
|
||||||
|
stateRoot: ZERO_HASH,
|
||||||
|
td: ZERO_HASH,
|
||||||
|
txRoot: ZERO_HASH,
|
||||||
|
receiptRoot: ZERO_HASH
|
||||||
};
|
};
|
||||||
|
|
||||||
const tx = {
|
const tx = {
|
||||||
@ -29,3 +34,7 @@ export const getDummyEventData = (): EventData => {
|
|||||||
eventIndex: 0
|
eventIndex: 0
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTestDatabase = (): Database => {
|
||||||
|
return new Database({ type: 'postgres' }, '');
|
||||||
|
};
|
||||||
|
27
packages/graph-test-watcher/src/entity/ExampleEntity.ts
Normal file
27
packages/graph-test-watcher/src/entity/ExampleEntity.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 Vulcanize, Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class ExampleEntity {
|
||||||
|
@PrimaryColumn('varchar')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
// https://typeorm.io/#/entities/primary-columns
|
||||||
|
@PrimaryColumn('varchar', { length: 66 })
|
||||||
|
blockHash!: string
|
||||||
|
|
||||||
|
@Column('integer')
|
||||||
|
blockNumber!: number;
|
||||||
|
|
||||||
|
@Column('bigint')
|
||||||
|
count!: bigint
|
||||||
|
|
||||||
|
@Column('varchar')
|
||||||
|
param1!: string
|
||||||
|
|
||||||
|
@Column('integer')
|
||||||
|
param2!: number
|
||||||
|
}
|
@ -8,10 +8,12 @@ import yargs from 'yargs';
|
|||||||
import { hideBin } from 'yargs/helpers';
|
import { hideBin } from 'yargs/helpers';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { PubSub } from 'apollo-server-express';
|
import { PubSub } from 'apollo-server-express';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
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 { getConfig, fillBlocks, JobQueue, DEFAULT_CONFIG_PATH, getCustomProvider } from '@vulcanize/util';
|
import { getConfig, fillBlocks, JobQueue, DEFAULT_CONFIG_PATH, getCustomProvider } from '@vulcanize/util';
|
||||||
|
import { GraphWatcher, Database as GraphDatabase } from '@vulcanize/graph-node';
|
||||||
|
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import { Indexer } from './indexer';
|
import { Indexer } from './indexer';
|
||||||
@ -46,7 +48,7 @@ export const main = async (): Promise<any> => {
|
|||||||
|
|
||||||
assert(config.server, 'Missing server config');
|
assert(config.server, 'Missing server config');
|
||||||
|
|
||||||
const { upstream, database: dbConfig, jobQueue: jobQueueConfig } = config;
|
const { upstream, database: dbConfig, jobQueue: jobQueueConfig, server: { subgraphPath } } = config;
|
||||||
|
|
||||||
assert(dbConfig, 'Missing database config');
|
assert(dbConfig, 'Missing database config');
|
||||||
|
|
||||||
@ -72,10 +74,16 @@ export const main = async (): Promise<any> => {
|
|||||||
|
|
||||||
const ethProvider = getCustomProvider(rpcProviderEndpoint);
|
const ethProvider = getCustomProvider(rpcProviderEndpoint);
|
||||||
|
|
||||||
|
const graphDb = new GraphDatabase(dbConfig, path.resolve(__dirname, 'entity/*'));
|
||||||
|
await graphDb.init();
|
||||||
|
|
||||||
|
const graphWatcher = new GraphWatcher(graphDb, postgraphileClient, subgraphPath);
|
||||||
|
await graphWatcher.init();
|
||||||
|
|
||||||
// Note: In-memory pubsub works fine for now, as each watcher is a single process anyway.
|
// 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
|
// Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries
|
||||||
const pubsub = new PubSub();
|
const pubsub = new PubSub();
|
||||||
const indexer = new Indexer(db, ethClient, postgraphileClient, ethProvider);
|
const indexer = new Indexer(db, ethClient, postgraphileClient, ethProvider, graphWatcher);
|
||||||
|
|
||||||
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
|
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
|
||||||
assert(dbConnectionString, 'Missing job queue db connection string');
|
assert(dbConnectionString, 'Missing job queue db connection string');
|
||||||
|
@ -167,6 +167,10 @@ export class Indexer {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getExampleEntity (blockHash: string, id: string): Promise<string> {
|
||||||
|
return this._graphWatcher.getEntity(blockHash, 'ExampleEntity', id);
|
||||||
|
}
|
||||||
|
|
||||||
async triggerIndexingOnEvent (event: Event): Promise<void> {
|
async triggerIndexingOnEvent (event: Event): Promise<void> {
|
||||||
const resultEvent = this.getResultEvent(event);
|
const resultEvent = this.getResultEvent(event);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import 'reflect-metadata';
|
|||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import { hideBin } from 'yargs/helpers';
|
import { hideBin } from 'yargs/helpers';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
import { getCache } from '@vulcanize/cache';
|
import { getCache } from '@vulcanize/cache';
|
||||||
import { EthClient } from '@vulcanize/ipld-eth-client';
|
import { EthClient } from '@vulcanize/ipld-eth-client';
|
||||||
@ -20,7 +21,7 @@ import {
|
|||||||
DEFAULT_CONFIG_PATH,
|
DEFAULT_CONFIG_PATH,
|
||||||
getCustomProvider
|
getCustomProvider
|
||||||
} from '@vulcanize/util';
|
} from '@vulcanize/util';
|
||||||
import { GraphWatcher } from '@vulcanize/graph-node';
|
import { GraphWatcher, Database as GraphDatabase } from '@vulcanize/graph-node';
|
||||||
|
|
||||||
import { Indexer } from './indexer';
|
import { Indexer } from './indexer';
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
@ -107,7 +108,10 @@ export const main = async (): Promise<any> => {
|
|||||||
cache
|
cache
|
||||||
});
|
});
|
||||||
|
|
||||||
const graphWatcher = new GraphWatcher(subgraphPath);
|
const graphDb = new GraphDatabase(dbConfig, path.resolve(__dirname, 'entity/*'));
|
||||||
|
await graphDb.init();
|
||||||
|
|
||||||
|
const graphWatcher = new GraphWatcher(graphDb, postgraphileClient, subgraphPath);
|
||||||
await graphWatcher.init();
|
await graphWatcher.init();
|
||||||
|
|
||||||
const ethProvider = getCustomProvider(rpcProviderEndpoint);
|
const ethProvider = getCustomProvider(rpcProviderEndpoint);
|
||||||
|
@ -73,6 +73,14 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
|
|||||||
|
|
||||||
const events = await indexer.getEventsInRange(fromBlockNumber, toBlockNumber);
|
const events = await indexer.getEventsInRange(fromBlockNumber, toBlockNumber);
|
||||||
return events.map(event => indexer.getResultEvent(event));
|
return events.map(event => indexer.getResultEvent(event));
|
||||||
|
},
|
||||||
|
|
||||||
|
exampleEntity: async (_: any, { blockHash, id }: { blockHash: string, id: string }) => {
|
||||||
|
log('exampleEntity', blockHash, id);
|
||||||
|
|
||||||
|
const exampleEntity = await indexer.getExampleEntity(blockHash, id);
|
||||||
|
|
||||||
|
return JSON.stringify(exampleEntity, undefined, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@ type Query {
|
|||||||
eventsInRange(fromBlockNumber: Int!, toBlockNumber: Int!): [ResultEvent!]
|
eventsInRange(fromBlockNumber: Int!, toBlockNumber: Int!): [ResultEvent!]
|
||||||
getMethod(blockHash: String!, contractAddress: String!): ResultString!
|
getMethod(blockHash: String!, contractAddress: String!): ResultString!
|
||||||
_test(blockHash: String!, contractAddress: String!): ResultBigInt!
|
_test(blockHash: String!, contractAddress: String!): ResultBigInt!
|
||||||
|
exampleEntity(blockHash: String!, id: String!): String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResultEvent {
|
type ResultEvent {
|
||||||
|
@ -17,7 +17,7 @@ import { createServer } from 'http';
|
|||||||
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, KIND_ACTIVE, getCustomProvider } from '@vulcanize/util';
|
import { DEFAULT_CONFIG_PATH, getConfig, JobQueue, KIND_ACTIVE, getCustomProvider } from '@vulcanize/util';
|
||||||
import { GraphWatcher } from '@vulcanize/graph-node';
|
import { GraphWatcher, Database as GraphDatabase } from '@vulcanize/graph-node';
|
||||||
|
|
||||||
import { createResolvers } from './resolvers';
|
import { createResolvers } from './resolvers';
|
||||||
import { Indexer } from './indexer';
|
import { Indexer } from './indexer';
|
||||||
@ -70,7 +70,10 @@ export const main = async (): Promise<any> => {
|
|||||||
|
|
||||||
const ethProvider = getCustomProvider(rpcProviderEndpoint);
|
const ethProvider = getCustomProvider(rpcProviderEndpoint);
|
||||||
|
|
||||||
const graphWatcher = new GraphWatcher(subgraphPath);
|
const graphDb = new GraphDatabase(dbConfig, path.resolve(__dirname, 'entity/*'));
|
||||||
|
await graphDb.init();
|
||||||
|
|
||||||
|
const graphWatcher = new GraphWatcher(graphDb, postgraphileClient, subgraphPath);
|
||||||
await graphWatcher.init();
|
await graphWatcher.init();
|
||||||
|
|
||||||
const indexer = new Indexer(db, ethClient, postgraphileClient, ethProvider, graphWatcher);
|
const indexer = new Indexer(db, ethClient, postgraphileClient, ethProvider, graphWatcher);
|
||||||
|
12
yarn.lock
12
yarn.lock
@ -2287,6 +2287,13 @@
|
|||||||
"@types/connect" "*"
|
"@types/connect" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/chai-spies@^1.0.3":
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/chai-spies/-/chai-spies-1.0.3.tgz#a52dc61af3853ec9b80965040811d15dfd401542"
|
||||||
|
integrity sha512-RBZjhVuK7vrg4rWMt04UF5zHYwfHnpk5mIWu3nQvU3AKGDixXzSjZ6v0zke6pBcaJqMv3IBZ5ibLWPMRDL0sLw==
|
||||||
|
dependencies:
|
||||||
|
"@types/chai" "*"
|
||||||
|
|
||||||
"@types/chai@*", "@types/chai@^4.2.18":
|
"@types/chai@*", "@types/chai@^4.2.18":
|
||||||
version "4.2.18"
|
version "4.2.18"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.18.tgz#0c8e298dbff8205e2266606c1ea5fbdba29b46e4"
|
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.18.tgz#0c8e298dbff8205e2266606c1ea5fbdba29b46e4"
|
||||||
@ -4617,6 +4624,11 @@ catering@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/catering/-/catering-2.0.0.tgz#15ce31bcbffafbf62855ea7677b0e5d23581233d"
|
resolved "https://registry.yarnpkg.com/catering/-/catering-2.0.0.tgz#15ce31bcbffafbf62855ea7677b0e5d23581233d"
|
||||||
integrity sha512-aD/WmxhGwUGsVPrj8C80vH7C7GphJilYVSdudoV4u16XdrLF7CVyfBmENsc4tLTVsJJzCRid8GbwJ7mcPLee6Q==
|
integrity sha512-aD/WmxhGwUGsVPrj8C80vH7C7GphJilYVSdudoV4u16XdrLF7CVyfBmENsc4tLTVsJJzCRid8GbwJ7mcPLee6Q==
|
||||||
|
|
||||||
|
chai-spies@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chai-spies/-/chai-spies-1.0.0.tgz#d16b39336fb316d03abf8c375feb23c0c8bb163d"
|
||||||
|
integrity sha512-elF2ZUczBsFoP07qCfMO/zeggs8pqCf3fZGyK5+2X4AndS8jycZYID91ztD9oQ7d/0tnS963dPkd0frQEThDsg==
|
||||||
|
|
||||||
chai@^4.3.4:
|
chai@^4.3.4:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49"
|
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49"
|
||||||
|
Loading…
Reference in New Issue
Block a user