Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cache / expire uAgent almanac resolutions #198

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ type AlmanacRecord @entity {
block: Block!
}

type AlmanacResolution @entity {
id: ID!
agent: Agent!
contract: Contract!
record: AlmanacRecord!
}

type AlmanacRegistration @entity {
id: ID!
expiryHeight: BigInt! @index
Expand Down
24 changes: 18 additions & 6 deletions src/genesis/helpers/field_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,23 +324,35 @@ class AlmanacRegistrations(NamedFields):
signature = 2
sequence = 3
agent_id = 4
record_id = 5
transaction_id = 6
block_id = 7
# event_id = 8
# record_id = 9
contract_id = 5
record_id = 6
transaction_id = 7
block_id = 8
# event_id = 9
# record_id = 10

@classmethod
def get_table(self):
return "almanac_registrations"


class AlmanacResolutions(NamedFields):
id = 0
agent_id = 1
contract_id = 2
record_id = 3

@classmethod
def get_table(self):
return "almanac_resolutions"


class AlmanacRecords(NamedFields):
id = 0
service = 1
transaction_id = 2
block_id = 3
# event_id = 4
# event_id = 4

@classmethod
def get_table(self):
Expand Down
2 changes: 2 additions & 0 deletions src/mappings/almanac/registrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "../utils";
import {CosmosEvent} from "@subql/types-cosmos";
import {Agent, AlmanacRecord, AlmanacRegistration, Contract} from "../../types";
import {cacheAlmanacResolution} from "./resolutions";

export async function handleAlmanacRegistration(event: CosmosEvent): Promise<void> {
await attemptHandling(event, _handleAlmanacRegistration, unprocessedEventHandler);
Expand Down Expand Up @@ -107,4 +108,5 @@ async function _handleAlmanacRegistration(event: CosmosEvent): Promise<void> {
blockId: event.block.block.id,
});
await registrationEntity.save();
await cacheAlmanacResolution(_contract_address, agent_address, recordEntity);
}
40 changes: 40 additions & 0 deletions src/mappings/almanac/resolutions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {AlmanacRecord, AlmanacResolution, Contract, Interface,} from "../../types";

export async function cacheAlmanacResolution(contractId: string, agentId: string, record: AlmanacRecord): Promise<void> {
try {
logger.info(`[cacheAlmanacResolution] (recordId: ${record.id}): caching record`);
const resolutionEntity = AlmanacResolution.create({
id: record.id,
agentId,
contractId,
recordId: record.id,
});
await resolutionEntity.save();
} catch (error) {
logger.error(`[cacheAlmanacResolution] (recordId: ${record.id}): ${error.stack}`);
}
}

export async function expireAlmanacResolutionsRelativeToHeight(height: bigint): Promise<void> {
// NB: resolution, record, and registration ID are the same across related entities.
const expiringResolutionIdsSql = `SELECT res.id
FROM app.almanac_resolutions res
JOIN app.almanac_registrations reg
ON res.id = reg.id
WHERE reg.expiry_height <= ${height}
`;
const expiringResolutionIds = await store.selectRaw(expiringResolutionIdsSql);

// NB: will throw an error if any promise rejects.
await Promise.all(expiringResolutionIds.map(r => expireAlmanacResolution(String(r.id))));
}

export async function expireAlmanacResolution(id: string): Promise<void> {
try {
logger.info(`[expireAlmanacResolution] (recordId: ${id}): expiring record`);
await AlmanacResolution.remove(id);
} catch (error) {
logger.warn(`[expireAlmanacResolution] (recordId: ${id}): ${error.stack}`);
}
}

7 changes: 5 additions & 2 deletions src/mappings/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "./utils";
import {createHash} from "crypto";
import {toBech32} from "@cosmjs/encoding";
import {expireAlmanacResolutionsRelativeToHeight} from "./almanac/resolutions";

export async function handleBlock(block: CosmosBlock): Promise<void> {
await attemptHandling(block, _handleBlock, _handleBlockError);
Expand All @@ -30,16 +31,18 @@ export async function handleEvent(event: CosmosEvent): Promise<void> {
async function _handleBlock(block: CosmosBlock): Promise<void> {
logger.info(`[handleBlock] (block.header.height): indexing block ${block.block.header.height}`);

const {id, header: {chainId, height, time}} = block.block;
const {id, header: {chainId, height: _height, time}} = block.block;
const timestamp = new Date(time);
const height = BigInt(_height);
const blockEntity = Block.create({
id,
chainId,
height: BigInt(height),
height,
timestamp
});

await blockEntity.save();
await expireAlmanacResolutionsRelativeToHeight(height);
}

async function _handleTransaction(tx: CosmosTransaction): Promise<void> {
Expand Down
61 changes: 6 additions & 55 deletions src/mappings/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import {CosmosBlock, CosmosEvent, CosmosMessage, CosmosTransaction} from "@subql
import {Account, Interface, UnprocessedEntity} from "../types";
import {createHash} from "crypto";
import {Attribute} from "@cosmjs/stargate/build/logs";
import {LegacyBridgeSwapStructure} from "./wasm/contracts/bridge";
import {CW20Structure} from "./wasm/contracts/cw20";
Comment on lines +5 to +6
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was supposed to be in d7c166f

import {Structure} from "./wasm/contracts/types";
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we actually need this import.

import {MicroAgentAlmanacStructure} from "./wasm/contracts/almanac";
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was supposed to be in d7c166f


export type Primitive = CosmosEvent | CosmosMessage | CosmosTransaction | CosmosBlock;

Expand Down Expand Up @@ -29,7 +33,8 @@ export async function checkBalancesAccount(address: string, chainId: string) {
export function getJaccardResult(payload: object): Interface {
let prediction = Structure, prediction_coefficient = 0.5; // prediction coefficient can be set as a minimum threshold for the certainty of an output
let diff = 0, match = 0, coefficient = 0; // where coefficient of 1 is a perfect property key match, 2 is a perfect match of property and type
const structs = [CW20Structure, LegacyBridgeSwapStructure];
// TODO: refactor
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more maintainable if we redesigned this a little bit. Perhaps we can add a contract structure registry which this function iterates over (as opposed to a hard-coded list of known structures). Each structure would import the registry and register itself. I suspect that the index (i.e. src/mappings/wasm/contracts) would have to be required (import value ignored) just so that they are included in the build tree (and their registration calls get run, subsequently). Effort may need to be taken to avoid circular dependencies when building and bootstrapping the registry. I think so long as the contract structure index isn't in the same module as the registry we should be good. I would imagine a good place for this import would be in the src/mappings/mappingHandler.ts file instead.

const structs = [CW20Structure, LegacyBridgeSwapStructure, MicroAgentAlmanacStructure];
structs.forEach((struct) => {
Object.keys(payload).forEach((payload_key) => {
if (struct.listProperties().some((prop) => prop === payload_key)) { // If payload property exists as a property within current structure
Expand Down Expand Up @@ -116,60 +121,6 @@ export async function trackUnprocessed(error: Error, primitives: Primitives): Pr
}
}

class Structure {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was supposed to be in d7c166f

static getInterface() {
return Interface.Uncertain;
}
}

class CW20Structure extends Structure {
private name = "";
private symbol = "";
private decimals = 0;
private initial_balances: [{ amount: bigint, address: string }] = [{amount: BigInt(0), address: ""}];
private mint: { minter: string } = {minter: ""};

static listProperties() {
const a = new CW20Structure();
return Object.getOwnPropertyNames(a);
}

static getPropertyType(prop: string) {
const a = new CW20Structure();
return typeof (a[prop]);
}

static getInterface() {
return Interface.CW20;
}
}

class LegacyBridgeSwapStructure extends Structure {
private cap = BigInt(0);
private reverse_aggregated_allowance = BigInt(0);
private reverse_aggregated_allowance_approver_cap = BigInt(0);
private lower_swap_limit = BigInt(0);
private upper_swap_limit = BigInt(0);
private swap_fee = BigInt(0);
private paused_since_block = BigInt(0);
private denom = "";
private next_swap_id = "";

static listProperties() {
const a = new LegacyBridgeSwapStructure();
return Object.getOwnPropertyNames(a);
}

static getPropertyType(prop: string) {
const a = new LegacyBridgeSwapStructure();
return typeof (a[prop]);
}

static getInterface() {
return Interface.LegacyBridgeSwap;
}
}

export interface BaseEventAttributesI {
action: string;
}
Expand Down
Loading