Implement graph-ts numbers, typeConversion and log host APIs (#64)

* Implement host api for bigint operations

* Implement type conversion APIs

* Avoid use of exports as variable name for conflict

* Implement log API with debug and levels

* Fix job-runnner hook error after stopping and starting chain
This commit is contained in:
nikugogoi 2021-11-29 18:37:11 +05:30 committed by nabarun
parent 970092ece2
commit faf046d181
10 changed files with 304 additions and 44 deletions

View File

@ -72,3 +72,11 @@ export class Bar {
return this.prop; return this.prop;
} }
} }
export function testLog (): void {
log.debug('Debug message: {}, {}', ['value1', 'value2']);
log.info('Info message: {}', ['value1', 'value2']);
log.warning('Warning message', []);
log.error('Error message', []);
log.critical('Critical message', []);
}

View File

@ -30,7 +30,7 @@
"asbuild:debug": "asc assembly/index.ts --lib ./node_modules --exportRuntime --target debug --runPasses asyncify", "asbuild:debug": "asc assembly/index.ts --lib ./node_modules --exportRuntime --target debug --runPasses asyncify",
"asbuild:release": "asc assembly/index.ts --lib ./node_modules --exportRuntime --target release --runPasses asyncify", "asbuild:release": "asc assembly/index.ts --lib ./node_modules --exportRuntime --target release --runPasses asyncify",
"asbuild": "yarn asbuild:debug && yarn asbuild:release", "asbuild": "yarn asbuild:debug && yarn asbuild:release",
"test": "yarn asbuild:debug && mocha src/**/*.test.ts", "test": "yarn asbuild:debug && DEBUG=vulcanize:* mocha src/**/*.test.ts",
"build:example": "cd test/subgraph/example1 && yarn && yarn build", "build:example": "cd test/subgraph/example1 && yarn && yarn build",
"watch": "DEBUG=vulcanize:* nodemon --watch src src/watcher.ts", "watch": "DEBUG=vulcanize:* nodemon --watch src src/watcher.ts",
"compare-entity": "DEBUG=vulcanize:* ts-node src/cli/compare/compare-entity.ts" "compare-entity": "DEBUG=vulcanize:* ts-node src/cli/compare/compare-entity.ts"

View File

@ -64,4 +64,11 @@ describe('wasm loader tests', () => {
const bar = await Bar.__new(await __newString('test')); const bar = await Bar.__new(await __newString('test'));
expect(__getString(await bar.prop)).to.equal('test'); expect(__getString(await bar.prop)).to.equal('test');
}); });
it('should log messages', async () => {
const { testLog } = exports;
// Should print all log messages for different levels.
await testLog();
});
}); });

View File

@ -13,11 +13,12 @@ import {
} from 'ethers'; } from 'ethers';
import JSONbig from 'json-bigint'; import JSONbig from 'json-bigint';
import BN from 'bn.js'; import BN from 'bn.js';
import debug from 'debug';
import loader from '@vulcanize/assemblyscript/lib/loader'; import loader from '@vulcanize/assemblyscript/lib/loader';
import { IndexerInterface } from '@vulcanize/util'; import { IndexerInterface } from '@vulcanize/util';
import { TypeId } from './types'; import { TypeId, Level } from './types';
import { Block, fromEthereumValue, toEthereumValue, resolveEntityFieldConflicts, GraphDecimal, digitsToString } from './utils'; import { Block, fromEthereumValue, toEthereumValue, resolveEntityFieldConflicts, GraphDecimal, digitsToString } from './utils';
import { Database } from './database'; import { Database } from './database';
@ -52,6 +53,8 @@ export interface Context {
} }
} }
const log = debug('vulcanize:graph-node');
export const instantiate = async ( export const instantiate = async (
database: Database, database: Database,
indexer: IndexerInterface, indexer: IndexerInterface,
@ -76,7 +79,7 @@ export const instantiate = async (
return null; return null;
} }
return database.toGraphEntity(exports, entityName, entityData); return database.toGraphEntity(instanceExports, entityName, entityData);
}, },
'store.set': async (entity: number, id: number, data: number) => { 'store.set': async (entity: number, id: number, data: number) => {
const entityName = __getString(entity); const entityName = __getString(entity);
@ -84,7 +87,7 @@ export const instantiate = async (
const entityInstance = await Entity.wrap(data); const entityInstance = await Entity.wrap(data);
assert(context.event.block); assert(context.event.block);
let dbData = await database.fromGraphEntity(exports, context.event.block, entityName, entityInstance); let dbData = await database.fromGraphEntity(instanceExports, context.event.block, entityName, entityInstance);
await database.saveEntity(entityName, dbData); await database.saveEntity(entityName, dbData);
// Resolve any field name conflicts in the dbData for auto-diff. // Resolve any field name conflicts in the dbData for auto-diff.
@ -103,8 +106,8 @@ export const instantiate = async (
await indexer.createDiffStaged(dataSource.address, context.event.block.blockHash, diffData); await indexer.createDiffStaged(dataSource.address, context.event.block.blockHash, diffData);
}, },
'log.log': (_: number, msg: number) => { 'log.log': (level: number, msg: number) => {
console.log('log.log', __getString(msg)); log('log %s | %s', Level[level], __getString(msg));
}, },
'test.asyncMethod': async () => { 'test.asyncMethod': async () => {
@ -148,7 +151,7 @@ export const instantiate = async (
try { try {
const functionParamsPromise = functionParams.map(async param => { const functionParamsPromise = functionParams.map(async param => {
const ethereumValue = await ethereum.Value.wrap(param); const ethereumValue = await ethereum.Value.wrap(param);
return fromEthereumValue(exports, ethereumValue); return fromEthereumValue(instanceExports, ethereumValue);
}); });
functionParams = await Promise.all(functionParamsPromise); functionParams = await Promise.all(functionParamsPromise);
@ -172,7 +175,7 @@ export const instantiate = async (
output: any, output: any,
index: number index: number
) => toEthereumValue( ) => toEthereumValue(
exports, instanceExports,
output, output,
result[index] result[index]
) )
@ -214,8 +217,14 @@ export const instantiate = async (
return ptr; return ptr;
}, },
'typeConversion.bigIntToHex': () => { 'typeConversion.bigIntToHex': async (bigInt: number) => {
console.log('index typeConversion.bigIntToHex'); const bigIntInstance = await BigInt.wrap(bigInt);
const bigIntString = await bigIntInstance.toString();
const bigNumber = BigNumber.from(__getString(bigIntString));
const bigNumberHex = bigNumber.toHexString();
return __newString(bigNumberHex);
}, },
'typeConversion.bytesToHex': async (bytes: number) => { 'typeConversion.bytesToHex': async (bytes: number) => {
@ -225,11 +234,19 @@ export const instantiate = async (
return ptr; return ptr;
}, },
'typeConversion.bytesToString': () => { 'typeConversion.bytesToString': async (bytes: number) => {
console.log('index typeConversion.bytesToString'); const byteArray = __getArray(bytes);
const string = utils.toUtf8String(byteArray);
const ptr = await __newString(string);
return ptr;
}, },
'typeConversion.bytesToBase58': () => { 'typeConversion.bytesToBase58': async (n: number) => {
console.log('index typeConversion.bytesToBase58'); const uint8Array = __getArray(n);
const string = utils.base58.encode(uint8Array);
const ptr = await __newString(string);
return ptr;
} }
}, },
numbers: { numbers: {
@ -464,20 +481,80 @@ export const instantiate = async (
return remainderBigInt; return remainderBigInt;
}, },
'bigInt.bitOr': () => { 'bigInt.bitOr': async (x: number, y: number) => {
console.log('bigInt.bitOr'); // Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Create a bigNumber y.
const yBigInt = await BigInt.wrap(y);
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
// Perform the bigNumber bit or operation.
const res = xBigNumber.or(yBigNumber);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
return resBigInt;
}, },
'bigInt.bitAnd': () => { 'bigInt.bitAnd': async (x: number, y: number) => {
console.log('bigInt.bitAnd'); // Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Create a bigNumber y.
const yBigInt = await BigInt.wrap(y);
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
// Perform the bigNumber bit and operation.
const res = xBigNumber.and(yBigNumber);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
return resBigInt;
}, },
'bigInt.leftShift': () => { 'bigInt.leftShift': async (x: number, y: number) => {
console.log('bigInt.leftShift'); // Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Perform the bigNumber left shift operation.
const res = xBigNumber.shl(y);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
return resBigInt;
}, },
'bigInt.rightShift': () => { 'bigInt.rightShift': async (x: number, y: number) => {
console.log('bigInt.rightShift'); // Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Perform the bigNumber right shift operation.
const res = xBigNumber.shr(y);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
return resBigInt;
}, },
'bigInt.pow': () => { 'bigInt.pow': async (x: number, y: number) => {
console.log('bigInt.pow'); // Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Perform the bigNumber pow operation.
const res = xBigNumber.pow(y);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
return resBigInt;
} }
}, },
datasource: { datasource: {
@ -490,17 +567,17 @@ export const instantiate = async (
}; };
const instance = await loader.instantiate(buffer, imports); const instance = await loader.instantiate(buffer, imports);
const { exports } = instance; const { exports: instanceExports } = instance;
const { __getString, __newString, __getArray, __newArray } = exports; const { __getString, __newString, __getArray, __newArray } = instanceExports;
// TODO: Assign from types file generated by graph-cli // TODO: Assign from types file generated by graph-cli
const getIdOfType: idOfType = exports.id_of_type as idOfType; const getIdOfType: idOfType = instanceExports.id_of_type as idOfType;
const BigDecimal: any = exports.BigDecimal as any; const BigDecimal: any = instanceExports.BigDecimal as any;
const BigInt: any = exports.BigInt as any; const BigInt: any = instanceExports.BigInt as any;
const Address: any = exports.Address as any; const Address: any = instanceExports.Address as any;
const ethereum: any = exports.ethereum as any; const ethereum: any = instanceExports.ethereum as any;
const Entity: any = exports.Entity as any; const Entity: any = instanceExports.Entity as any;
return instance; return instance;
}; };

View File

@ -125,6 +125,41 @@ describe('numbers wasm tests', () => {
expect(__getString(ptr)).to.equal('1283174719'); expect(__getString(ptr)).to.equal('1283174719');
}); });
it('should execute bigInt bitOr API', async () => {
const { testBigIntBitOr, __getString, __newString } = exports;
const ptr = await testBigIntBitOr(await __newString('2315432122132354'), await __newString('5465265645'));
expect(__getString(ptr)).to.equal('2315433208543215');
});
it('should execute bigInt bitAnd API', async () => {
const { testBigIntBitAnd, __getString, __newString } = exports;
const ptr = await testBigIntBitAnd(await __newString('2315432122132354'), await __newString('5465265645'));
expect(__getString(ptr)).to.equal('4378854784');
});
it('should execute bigInt leftShift API', async () => {
const { testBigIntLeftShift, __getString, __newString } = exports;
const ptr = await testBigIntLeftShift(await __newString('2315432122132354'), 3);
expect(__getString(ptr)).to.equal('18523456977058832');
});
it('should execute bigInt rightShift API', async () => {
const { testBigIntRightShift, __getString, __newString } = exports;
const ptr = await testBigIntRightShift(await __newString('2315432122132354'), 3);
expect(__getString(ptr)).to.equal('289429015266544');
});
it('should execute bigInt pow API', async () => {
const { testBigIntPow, __getString, __newString } = exports;
const ptr = await testBigIntPow(await __newString('2315432'), 5);
expect(__getString(ptr)).to.equal('66551853520489467542782546706432');
});
it('should execute bigDecimal toString API', async () => { it('should execute bigDecimal toString API', async () => {
const { testBigDecimalToString, __newString, __getString } = exports; const { testBigDecimalToString, __newString, __getString } = exports;

View File

@ -4,6 +4,7 @@
import path from 'path'; import path from 'path';
import { expect } from 'chai'; import { expect } from 'chai';
import { utils, BigNumber } from 'ethers';
import { instantiate } from './loader'; import { instantiate } from './loader';
import { getTestDatabase, getTestIndexer } from '../test/utils'; import { getTestDatabase, getTestIndexer } from '../test/utils';
@ -56,4 +57,39 @@ describe('typeConversion wasm tests', () => {
const ptr = await testStringToH160(); const ptr = await testStringToH160();
expect(__getString(ptr)).to.equal('0xafad925b5eae1e370196cba39893e858ff7257d5'); expect(__getString(ptr)).to.equal('0xafad925b5eae1e370196cba39893e858ff7257d5');
}); });
it('should execute typeConversion bigIntToHex API', async () => {
const { testBigIntToHex, __getString, __getArray, __newString } = exports;
// Using smaller to also test with BigInt.fromI32
const bigNumber = BigNumber.from('2342353');
const value = await __newString(bigNumber.toString());
const ptr = await testBigIntToHex(value);
const ptrs = __getArray(ptr);
expect(__getString(ptrs[0])).to.equal(__getString(ptrs[1]));
expect(__getString(ptrs[0])).to.equal(bigNumber.toHexString());
});
it('should execute typeConversion bytesToString API', async () => {
const { testBytesToString, __getString, __newString } = exports;
const testString = 'test string';
const value = await __newString(testString);
const ptr = await testBytesToString(value);
expect(__getString(ptr)).to.equal(testString);
});
it('should execute typeConversion bytesToBase58 API', async () => {
const { testBytesToBase58, __getString, __newString } = exports;
const testString = 'test base58';
const value = await __newString(testString);
const ptr = await testBytesToBase58(value);
const base58String = utils.base58.encode(utils.toUtf8Bytes(testString));
expect(__getString(ptr)).to.equal(base58String);
});
}); });

View File

@ -2,7 +2,7 @@
// Copyright 2021 Vulcanize, Inc. // Copyright 2021 Vulcanize, Inc.
// //
// TypeId from https://github.com/graphprotocol/graph-ts/blob/master/global/global.ts // Enum types from @graphprotocol/graph-ts.
export enum TypeId { export enum TypeId {
String = 0, String = 0,
ArrayBuffer = 1, ArrayBuffer = 1,
@ -58,7 +58,6 @@ export enum TypeId {
ArrayBigDecimal = 51, ArrayBigDecimal = 51,
} }
// ethereum ValueKind from https://github.com/graphprotocol/graph-ts/blob/master/chain/ethereum.ts#L13
export enum EthereumValueKind { export enum EthereumValueKind {
ADDRESS = 0, ADDRESS = 0,
FIXED_BYTES = 1, FIXED_BYTES = 1,
@ -72,7 +71,6 @@ export enum EthereumValueKind {
TUPLE = 9, TUPLE = 9,
} }
// ValueKind from https://github.com/graphprotocol/graph-ts/blob/master/common/value.ts#L8
export enum ValueKind { export enum ValueKind {
STRING = 0, STRING = 0,
INT = 1, INT = 1,
@ -83,3 +81,11 @@ export enum ValueKind {
BYTES = 6, BYTES = 6,
BIGINT = 7, BIGINT = 7,
} }
export enum Level {
CRITICAL = 0,
ERROR = 1,
WARNING = 2,
INFO = 3,
DEBUG = 4,
}

View File

@ -129,7 +129,7 @@ export class GraphWatcher {
return; return;
} }
const { instance: { exports }, contractInterface } = this._dataSourceMap[contract]; const { instance: { exports: instanceExports }, contractInterface } = this._dataSourceMap[contract];
const eventFragment = contractInterface.getEvent(eventSignature); const eventFragment = contractInterface.getEvent(eventSignature);
@ -142,9 +142,9 @@ export class GraphWatcher {
}; };
// Create ethereum event to be passed to the wasm event handler. // Create ethereum event to be passed to the wasm event handler.
const ethereumEvent = await createEvent(exports, contract, data); const ethereumEvent = await createEvent(instanceExports, contract, data);
await exports[eventHandler.handler](ethereumEvent); await instanceExports[eventHandler.handler](ethereumEvent);
} }
async handleBlock (blockHash: string) { async handleBlock (blockHash: string) {
@ -159,14 +159,14 @@ export class GraphWatcher {
continue; continue;
} }
const { instance: { exports } } = this._dataSourceMap[dataSource.source.address]; const { instance: { exports: instanceExports } } = this._dataSourceMap[dataSource.source.address];
// Create ethereum block to be passed to a wasm block handler. // Create ethereum block to be passed to a wasm block handler.
const ethereumBlock = await createBlock(exports, blockData); const ethereumBlock = await createBlock(instanceExports, blockData);
// Call all the block handlers one after the another for a contract. // Call all the block handlers one after the another for a contract.
const blockHandlerPromises = dataSource.mapping.blockHandlers.map(async (blockHandler: any): Promise<void> => { const blockHandlerPromises = dataSource.mapping.blockHandlers.map(async (blockHandler: any): Promise<void> => {
await exports[blockHandler.handler](ethereumBlock); await instanceExports[blockHandler.handler](ethereumBlock);
}); });
await Promise.all(blockHandlerPromises); await Promise.all(blockHandlerPromises);

View File

@ -112,7 +112,6 @@ export function testGetEthCall (): void {
log.debug('In test get eth call', []); log.debug('In test get eth call', []);
// Bind the contract to the address that emitted the event. // Bind the contract to the address that emitted the event.
// TODO: Address.fromString throws error in WASM module instantiation.
const contractAddress = dataSource.address(); const contractAddress = dataSource.address();
const contract = Example1.bind(contractAddress); const contract = Example1.bind(contractAddress);
@ -170,6 +169,26 @@ export function testBytesToHex (): string {
return res; return res;
} }
export function testBytesToString (value: string): string {
log.debug('In test bytesToString', []);
const byteArray = ByteArray.fromUTF8(value);
const res = byteArray.toString();
log.debug('typeConversion.bytesToString result: {}', [res]);
return res;
}
export function testBytesToBase58 (value: string): string {
log.debug('In test bytesToBase58', []);
const byteArray = ByteArray.fromUTF8(value);
const res = byteArray.toBase58();
log.debug('typeConversion.bytesToBase58 result: {}', [res]);
return res;
}
export function testBigIntToString (): string { export function testBigIntToString (): string {
log.debug('In test bigIntToString', []); log.debug('In test bigIntToString', []);
@ -325,6 +344,61 @@ export function testBigIntMod (value1: string, value2: string): string {
return res.toString(); return res.toString();
} }
export function testBigIntBitOr (value1: string, value2: string): string {
log.debug('In test bigInt.bitOr', []);
const bigInt1 = BigInt.fromString(value1);
const bigInt2 = BigInt.fromString(value2);
const res = bigInt1 | bigInt2;
log.debug('bigInt.bitOr result: {}', [res.toString()]);
return res.toString();
}
export function testBigIntBitAnd (value1: string, value2: string): string {
log.debug('In test bigInt.bitAnd', []);
const bigInt1 = BigInt.fromString(value1);
const bigInt2 = BigInt.fromString(value2);
const res = bigInt1 & bigInt2;
log.debug('bigInt.bitAnd result: {}', [res.toString()]);
return res.toString();
}
export function testBigIntLeftShift (value1: string, value2: u8): string {
log.debug('In test bigInt.leftShift', []);
const bigInt1 = BigInt.fromString(value1);
const bits = value2;
const res = bigInt1 << bits;
log.debug('bigInt.leftShift result: {}', [res.toString()]);
return res.toString();
}
export function testBigIntRightShift (value1: string, value2: u8): string {
log.debug('In test bigInt.RightShift', []);
const bigInt1 = BigInt.fromString(value1);
const bits = value2;
const res = bigInt1 >> bits;
log.debug('bigInt.RightShift result: {}', [res.toString()]);
return res.toString();
}
export function testBigIntPow (value1: string, value2: u8): string {
log.debug('In test bigInt.pow', []);
const bigInt1 = BigInt.fromString(value1);
const exp = value2;
const res = bigInt1.pow(exp);
log.debug('bigInt.pow result: {}', [res.toString()]);
return res.toString();
}
export function testBigIntFromString (value: string): string { export function testBigIntFromString (value: string): string {
log.debug('In test bigInt.fromString', []); log.debug('In test bigInt.fromString', []);
@ -354,3 +428,20 @@ export function testBigIntWithI32 (value: string): string[] {
return [res1, res2, res3]; return [res1, res2, res3];
} }
export function testBigIntToHex (value: string): string[] {
log.debug('In testBigIntToHex', []);
const variableI32: i32 = parseInt(value) as i32;
const bigInt1 = BigInt.fromI32(variableI32);
const bigInt2 = BigInt.fromString(value);
const res1 = bigInt1.toHex();
log.debug('bigInt.toHex result 1: {}', [res1]);
const res2 = bigInt2.toHex();
log.debug('bigInt.toHex result 2: {}', [res2]);
return [res1, res2];
}

View File

@ -88,11 +88,11 @@ export class EventWatcher {
switch (kind) { switch (kind) {
case JOB_KIND_INDEX: case JOB_KIND_INDEX:
this._handleIndexingComplete(data); await this._handleIndexingComplete(data);
break; break;
case JOB_KIND_PRUNE: case JOB_KIND_PRUNE:
this._handlePruningComplete(data); await this._handlePruningComplete(data);
break; break;
default: default: