diff --git a/project.yaml b/project.yaml index bc069eb6c..b10f32455 100644 --- a/project.yaml +++ b/project.yaml @@ -130,3 +130,10 @@ dataSources: kind: cosmos/MessageHandler filter: type: "/cosmos.authz.v1beta1.MsgExec" + - handler: handleAlmanacRegistration + kind: cosmos/EventHandler + filter: + type: "wasm" + messageFilter: + type: "/cosmwasm.wasm.v1.MsgExecuteContract" + contractCall: "register" diff --git a/src/index.ts b/src/index.ts index fb59776f0..f3ae71100 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ //Exports all handler functions export * from "./mappings/mappingHandlers"; +export {parseAttributes} from "./mappings/utils"; diff --git a/src/mappings/almanac/index.ts b/src/mappings/almanac/index.ts new file mode 100644 index 000000000..7b1c60153 --- /dev/null +++ b/src/mappings/almanac/index.ts @@ -0,0 +1 @@ +export * from "./registrations"; diff --git a/src/mappings/almanac/registrations.ts b/src/mappings/almanac/registrations.ts new file mode 100644 index 000000000..6cd54db15 --- /dev/null +++ b/src/mappings/almanac/registrations.ts @@ -0,0 +1,110 @@ +import { + attemptHandling, + messageId, + parseAttributes, + unprocessedEventHandler, + WasmdEventAttributesI +} from "../utils"; +import {CosmosEvent} from "@subql/types-cosmos"; +import {Agent, AlmanacRecord, AlmanacRegistration, Contract} from "../../types"; + +export async function handleAlmanacRegistration(event: CosmosEvent): Promise { + await attemptHandling(event, _handleAlmanacRegistration, unprocessedEventHandler); +} + +export interface RegisterAttributes extends WasmdEventAttributesI { + expiry_height?: string, + sequence?: number; + signature?: string; + agent_address?: string; + record?: string, +} + +async function _handleAlmanacRegistration(event: CosmosEvent): Promise { + const id = messageId(event); + logger.info(`[handleAlmanacRegistration] (tx ${event.tx.hash}): indexing AlmanacRegistration ${id}`); + logger.debug(`[handleAlmanacRegistration] (event.log.log): ${JSON.stringify(event.log.log, null, 2)}`); + + /* NB: signature verification is handled by the almanac contract! + * This handler assumes that the contract will only emit events + * with valid signatures corresponding to the agent address. + */ + + const { + _contract_address, + agent_address, + expiry_height, + sequence, + signature, + record: recordStr, + } = parseAttributes(event.event.attributes); + + if (!agent_address) { + logger.warn("[handleAlmanacRegistration]: missing address"); + return; + } + + if (!signature) { + logger.warn("[handleAlmanacRegistration]: missing signature"); + return; + } + + if (!sequence) { + logger.warn("[handleAlmanacRegistration]: missing sequence"); + return; + } + + if (!expiry_height) { + logger.warn("[handleAlmanacRegistration]: missing expiry_height"); + return; + } + + if (!recordStr) { + logger.warn("[handleAlmanacRegistration]: missing record"); + return; + } + + const record = JSON.parse(recordStr); + if (!record || !record.service) { + logger.warn("[handleAlmanacRegistration]: missing record service"); + return; + } + + // NB: ensure agent exists + const agent = await Agent.get(agent_address); + if (!agent) { + await Agent.create({ + id: agent_address, + }).save(); + } + + // NB: ensure contract exists + const contract = await Contract.get(_contract_address); + if (!contract) { + logger.warn(`[handleAlmanacRegistration]: unable to find contract with address: ${_contract_address}`); + return; + } + + const recordEntity = AlmanacRecord.create({ + id, + service: record.service, + // eventId: id, + transactionId: event.tx.hash, + blockId: event.block.block.id, + }); + await recordEntity.save(); + + const registrationEntity = AlmanacRegistration.create({ + id, + expiryHeight: BigInt(expiry_height), + signature, + sequence, + agentId: agent_address, + recordId: id, + contractId: _contract_address, + // eventId: id, + transactionId: event.tx.hash, + blockId: event.block.block.id, + }); + await registrationEntity.save(); +} diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index d984aef73..e253d01da 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -1,4 +1,5 @@ export * from "./authz"; +export * from "./almanac"; export * from "./bank"; export * from "./dist"; export * from "./gov"; diff --git a/src/mappings/utils.ts b/src/mappings/utils.ts index 27755777a..f5f03efca 100644 --- a/src/mappings/utils.ts +++ b/src/mappings/utils.ts @@ -1,6 +1,16 @@ import {CosmosBlock, CosmosEvent, CosmosMessage, CosmosTransaction} from "@subql/types-cosmos"; import {Account, Interface, UnprocessedEntity} from "../types"; -import { createHash } from "crypto"; +import {createHash} from "crypto"; +import {Attribute} from "@cosmjs/stargate/build/logs"; + +export type Primitive = CosmosEvent | CosmosMessage | CosmosTransaction | CosmosBlock; + +export interface Primitives { + event?: CosmosEvent; + msg?: CosmosMessage; + tx?: CosmosTransaction; + block?: CosmosBlock; +} // messageId returns the id of the message passed or // that of the message which generated the event passed. @@ -43,15 +53,6 @@ export function getJaccardResult(payload: object): Interface { return prediction.getInterface(); // return best matched Interface to contract } -export type Primitive = CosmosEvent | CosmosMessage | CosmosTransaction | CosmosBlock; - -export interface Primitives { - event?: CosmosEvent; - msg?: CosmosMessage; - tx?: CosmosTransaction; - block?: CosmosBlock; -} - export async function attemptHandling(input: Primitive, handlerFn: (primitive) => Promise, errorFn: (Error, Primitive) => void): Promise { @@ -168,3 +169,20 @@ class LegacyBridgeSwapStructure extends Structure { return Interface.LegacyBridgeSwap; } } + +export interface BaseEventAttributesI { + action: string; +} + +// (see: https://github.com/CosmWasm/wasmd/blob/main/x/wasm/keeper/events.go) +export interface WasmdEventAttributesI extends BaseEventAttributesI { + _contract_address: string; +} + +export function parseAttributes(attributes: readonly Attribute[]): T { + // @ts-ignore + return attributes.reduce((acc, curr) => { + acc[curr.key] = curr.value; + return acc as T; + }, {}); +}