Handle type and field name conflicts for eden-watcher subgraph entities (#50)

* Rename Block, Transaction types and add block query in eden-watcher schema

* Handle field name conflicts in eden subgraph entities

* Resolve entity field name conflicts while sending data for auto-diff
This commit is contained in:
prathamesh0 2021-11-17 10:55:05 +05:30 committed by nabarun
parent fd86b4d5e3
commit 158c3928c9
9 changed files with 103 additions and 37 deletions

View File

@ -3,6 +3,7 @@
//
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintTransformer } from '@vulcanize/util';
@Entity()
export class ProducerRewardCollectorChange {
@ -15,6 +16,9 @@ export class ProducerRewardCollectorChange {
@Column('integer')
blockNumber!: number;
@Column('bigint', { transformer: bigintTransformer })
_blockNumber!: bigint;
@Column('varchar')
producer!: string;

View File

@ -3,6 +3,7 @@
//
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintTransformer } from '@vulcanize/util';
enum ProducerSetChangeType {
Added,
@ -20,6 +21,9 @@ export class ProducerSetChange {
@Column('integer')
blockNumber!: number;
@Column('bigint', { transformer: bigintTransformer })
_blockNumber!: bigint;
@Column('varchar')
producer!: string;

View File

@ -524,7 +524,7 @@ export class Indexer implements IndexerInterface {
return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== '');
}
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash: string): Promise<Entity | undefined> {
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash: string): Promise<any> {
return this._graphWatcher.getEntity(entity, id, blockHash);
}

View File

@ -16,6 +16,7 @@ import { ProducerRewardCollectorChange } from './entity/ProducerRewardCollectorC
import { RewardScheduleEntry } from './entity/RewardScheduleEntry';
import { RewardSchedule } from './entity/RewardSchedule';
import { ProducerEpoch } from './entity/ProducerEpoch';
import { Block } from './entity/Block';
import { Epoch } from './entity/Epoch';
import { SlotClaim } from './entity/SlotClaim';
import { Slot } from './entity/Slot';
@ -58,109 +59,109 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
},
Query: {
producer: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Producer | undefined> => {
producer: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('producer', id, blockHash);
return indexer.getSubgraphEntity(Producer, id, blockHash);
},
producerSet: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<ProducerSet | undefined> => {
producerSet: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('producerSet', id, blockHash);
return indexer.getSubgraphEntity(ProducerSet, id, blockHash);
},
producerSetChange: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<ProducerSetChange | undefined> => {
producerSetChange: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('producerSetChange', id, blockHash);
return indexer.getSubgraphEntity(ProducerSetChange, id, blockHash);
},
producerRewardCollectorChange: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<ProducerRewardCollectorChange | undefined> => {
producerRewardCollectorChange: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('producerRewardCollectorChange', id, blockHash);
return indexer.getSubgraphEntity(ProducerRewardCollectorChange, id, blockHash);
},
rewardScheduleEntry: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<RewardScheduleEntry | undefined> => {
rewardScheduleEntry: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('rewardScheduleEntry', id, blockHash);
return indexer.getSubgraphEntity(RewardScheduleEntry, id, blockHash);
},
rewardSchedule: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<RewardSchedule | undefined> => {
rewardSchedule: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('rewardSchedule', id, blockHash);
return indexer.getSubgraphEntity(RewardSchedule, id, blockHash);
},
producerEpoch: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<ProducerEpoch | undefined> => {
producerEpoch: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('producerEpoch', id, blockHash);
return indexer.getSubgraphEntity(ProducerEpoch, id, blockHash);
},
// block: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Block | undefined> => {
// log('block', id, blockHash);
block: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('block', id, blockHash);
// return indexer.getSubgraphEntity(Block, id, blockHash);
// },
return indexer.getSubgraphEntity(Block, id, blockHash);
},
epoch: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Epoch | undefined> => {
epoch: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('epoch', id, blockHash);
return indexer.getSubgraphEntity(Epoch, id, blockHash);
},
slotClaim: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<SlotClaim | undefined> => {
slotClaim: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('slotClaim', id, blockHash);
return indexer.getSubgraphEntity(SlotClaim, id, blockHash);
},
slot: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Slot | undefined> => {
slot: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('slot', id, blockHash);
return indexer.getSubgraphEntity(Slot, id, blockHash);
},
staker: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Staker | undefined> => {
staker: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('staker', id, blockHash);
return indexer.getSubgraphEntity(Staker, id, blockHash);
},
network: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Network | undefined> => {
network: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('network', id, blockHash);
return indexer.getSubgraphEntity(Network, id, blockHash);
},
distributor: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Distributor | undefined> => {
distributor: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('distributor', id, blockHash);
return indexer.getSubgraphEntity(Distributor, id, blockHash);
},
distribution: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Distribution | undefined> => {
distribution: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('distribution', id, blockHash);
return indexer.getSubgraphEntity(Distribution, id, blockHash);
},
claim: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Claim | undefined> => {
claim: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('claim', id, blockHash);
return indexer.getSubgraphEntity(Claim, id, blockHash);
},
slash: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Slash | undefined> => {
slash: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('slash', id, blockHash);
return indexer.getSubgraphEntity(Slash, id, blockHash);
},
account: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<Account | undefined> => {
account: async (_: any, { id, blockHash }: { id: string, blockHash: string }) => {
log('account', id, blockHash);
return indexer.getSubgraphEntity(Account, id, blockHash);

View File

@ -24,7 +24,7 @@ type ResultBigInt {
proof: Proof
}
type Block {
type _Block_ {
cid: String!
hash: String!
number: Int!
@ -32,7 +32,7 @@ type Block {
parentHash: String!
}
type Transaction {
type _Transaction_ {
hash: String!
index: Int!
from: String!
@ -40,8 +40,8 @@ type Transaction {
}
type ResultEvent {
block: Block!
tx: Transaction!
block: _Block_!
tx: _Transaction_!
contract: String!
eventIndex: Int!
event: Event!
@ -204,7 +204,7 @@ type RoleRevokedEvent {
}
type ResultIPLDBlock {
block: Block!
block: _Block_!
contractAddress: String!
cid: String!
kind: String!
@ -221,6 +221,7 @@ type Query {
rewardScheduleEntry(id: String!, blockHash: String!): RewardScheduleEntry!
rewardSchedule(id: String!, blockHash: String!): RewardSchedule!
producerEpoch(id: String!, blockHash: String!): ProducerEpoch!
block(id: String!, blockHash: String!): Block!
epoch(id: String!, blockHash: String!): Epoch!
slotClaim(id: String!, blockHash: String!): SlotClaim!
slot(id: String!, blockHash: String!): Slot!
@ -283,6 +284,25 @@ type RewardSchedule {
activeRewardScheduleEntry: RewardScheduleEntry
}
type Block {
id: ID!
fromActiveProducer: Boolean!
hash: String!
parentHash: String!
unclesHash: String!
author: String!
stateRoot: String!
transactionsRoot: String!
receiptsRoot: String!
number: BigInt!
gasUsed: BigInt!
gasLimit: BigInt!
timestamp: BigInt!
difficulty: BigInt!
totalDifficulty: BigInt!
size: BigInt
}
type Epoch {
id: ID!
finalized: Boolean!

View File

@ -83,7 +83,7 @@ export class Database {
const entityValuePromises = entityFields.filter(field => {
const { propertyName } = field;
// TODO: Will clash if entity has blockHash and blockNumber fields.
// Filter out blockHash and blockNumber from entity fields to fill the entityInstance (wasm).
if (propertyName === 'blockHash' || propertyName === 'blockNumber') {
return false;
}
@ -92,6 +92,11 @@ export class Database {
}).map(async (field) => {
const { type, propertyName } = field;
// Fill _blockNumber as blockNumber and _blockHash as blockHash in the entityInstance (wasm).
if (['_blockNumber', '_blockHash'].includes(propertyName)) {
return toEntityValue(instanceExports, entityInstance, data, type.toString(), propertyName.slice(1));
}
return toEntityValue(instanceExports, entityInstance, data, type.toString(), propertyName);
}, {});
@ -112,15 +117,21 @@ export class Database {
const entityValuePromises = entityFields.map(async (field: any) => {
const { type, propertyName } = field;
// TODO: Will clash if entity has blockHash and blockNumber fields.
// Get blockHash property for db entry from block instance.
if (propertyName === 'blockHash') {
return block.blockHash;
}
// Get blockNumber property for db entry from block instance.
if (propertyName === 'blockNumber') {
return block.blockNumber;
}
// Get blockNumber as _blockNumber and blockHash as _blockHash from the entityInstance (wasm).
if (['_blockNumber', '_blockHash'].includes(propertyName)) {
return fromEntityValue(instanceExports, entityInstance, type.toString(), propertyName.slice(1));
}
return fromEntityValue(instanceExports, entityInstance, type.toString(), propertyName);
}, {});

View File

@ -18,7 +18,7 @@ import JSONbig from 'json-bigint';
import { IndexerInterface } from '@vulcanize/util';
import { TypeId } from './types';
import { Block, fromEthereumValue, toEthereumValue } from './utils';
import { Block, fromEthereumValue, toEthereumValue, resolveEntityFieldConflicts } from './utils';
import { Database } from './database';
const NETWORK_URL = 'http://127.0.0.1:8081';
@ -66,15 +66,15 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
const entityInstance = await Entity.wrap(data);
assert(context.event.block);
const dbData = await database.fromGraphEntity(exports, context.event.block, entityName, entityInstance);
let dbData = await database.fromGraphEntity(exports, context.event.block, entityName, entityInstance);
await database.saveEntity(entityName, dbData);
// Remove blockNumber and blockHash from dbData for auto-diff.
delete dbData.blockNumber;
delete dbData.blockHash;
// Resolve any field name conflicts in the dbData for auto-diff.
dbData = resolveEntityFieldConflicts(dbData);
// Prepare the diff data.
const diffData: any = { state: {} };
// JSON stringify and parse data for handling unknown types when encoding.
// For example, decimal.js values are converted to string in the diff data.
diffData.state[entityName] = JSONbig.parse(JSONbig.stringify(dbData));

View File

@ -396,3 +396,25 @@ export const fromEntityValue = async (instanceExports: any, entityInstance: any,
throw new Error(`Unsupported type: ${type}`);
}
};
export const resolveEntityFieldConflicts = (entity: any): any => {
if (entity) {
// Remove fields blockHash and blockNumber from the entity.
delete entity.blockHash;
delete entity.blockNumber;
// Rename _blockHash -> blockHash.
if ('_blockHash' in entity) {
entity.blockHash = entity._blockHash;
delete entity._blockHash;
}
// Rename _blockNumber -> blockNumber.
if ('_blockNumber' in entity) {
entity.blockNumber = entity._blockNumber;
delete entity._blockNumber;
}
}
return entity;
};

View File

@ -13,7 +13,7 @@ import { ResultObject } from '@vulcanize/assemblyscript/lib/loader';
import { EthClient } from '@vulcanize/ipld-eth-client';
import { IndexerInterface } from '@vulcanize/util';
import { createBlock, createEvent, getSubgraphConfig } from './utils';
import { createBlock, createEvent, getSubgraphConfig, resolveEntityFieldConflicts } from './utils';
import { Context, instantiate } from './loader';
import { Database } from './database';
@ -183,7 +183,11 @@ export class GraphWatcher {
this._indexer = indexer;
}
async getEntity<Entity> (entity: new () => Entity, id: string, blockHash: string): Promise<Entity | undefined> {
return this._database.getEntity(entity, id, blockHash);
async getEntity<Entity> (entity: new () => Entity, id: string, blockHash: string): Promise<any> {
// Get entity from the database.
const result = await this._database.getEntity(entity, id, blockHash) as any;
// Resolve any field name conflicts in the entity result.
return resolveEntityFieldConflicts(result);
}
}