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]: Implement an evaluator service and controller #48

Merged
merged 14 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ global:
retryInterval: 30000 # Time to wait before retrying a failed transaction
maxTries: 3 # Maximum tries for a transaction
maxPendingTransactions: 50 # Maximum number of transactions within the 'submit' pipeline.

# Evaluation properties
evaluationRetryInterval: 3600000 # Interval at which to reevaluate whether to relay a message.
maxEvaluationDuration: 86400000 # Time after which to drop an undelivered message.

evaluator:
verificationDeliveryGas: '55000' # Gas amount used for packet verification upon delivery.
unrewardedDeliveryGas: '25000' # Gas amount that will be unrewarded on delivery submission.
minDeliveryReward: 0.001 # In the 'pricingDenomination' specified below
Expand Down
30 changes: 20 additions & 10 deletions src/config/config.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const GLOBAL_SCHEMA = {
monitor: { $ref: "monitor-schema" },
getter: { $ref: "getter-schema" },
pricing: { $ref: "pricing-schema" },
evaluator: { $ref: "evaluator-schema" },
submitter: { $ref: "submitter-schema" },
persister: { $ref: "persister-schema" },
wallet: { $ref: "wallet-schema" },
Expand Down Expand Up @@ -134,6 +135,23 @@ export const PRICING_SCHEMA = {
additionalProperties: true // Allow for provider-specific configurations
}

const EVALUATOR_SCHEMA = {
$id: "evaluator-schema",
type: "object",
properties: {
unrewardedDeliveryGas: { $ref: "gas-field-schema" },
verificationDeliveryGas: { $ref: "gas-field-schema" },
minDeliveryReward: { $ref: "positive-number-schema" },
relativeMinDeliveryReward: { $ref: "positive-number-schema" },
unrewardedAckGas: { $ref: "gas-field-schema" },
verificationAckGas: { $ref: "gas-field-schema" },
minAckReward: { $ref: "positive-number-schema" },
relativeMinAckReward: { $ref: "positive-number-schema" },
profitabilityFactor: { $ref: "positive-number-schema" },
},
additionalProperties: false
}

const SUBMITTER_SCHEMA = {
$id: "submitter-schema",
type: "object",
Expand All @@ -147,18 +165,8 @@ const SUBMITTER_SCHEMA = {
maxTries: { $ref: "positive-number-schema" },
maxPendingTransactions: { $ref: "positive-number-schema" },

//TODO define 'evaluation' configuration somewhere else?
evaluationRetryInterval: { $ref: "positive-number-schema" },
maxEvaluationDuration: { $ref: "positive-number-schema" },
unrewardedDeliveryGas: { $ref: "gas-field-schema" },
verificationDeliveryGas: { $ref: "gas-field-schema" },
minDeliveryReward: { $ref: "positive-number-schema" },
relativeMinDeliveryReward: { $ref: "positive-number-schema" },
unrewardedAckGas: { $ref: "gas-field-schema" },
verificationAckGas: { $ref: "gas-field-schema" },
minAckReward: { $ref: "positive-number-schema" },
relativeMinAckReward: { $ref: "positive-number-schema" },
profitabilityFactor: { $ref: "positive-number-schema" },
},
additionalProperties: false
}
Expand Down Expand Up @@ -245,6 +253,7 @@ const CHAINS_SCHEMA = {
monitor: { $ref: "monitor-schema" },
getter: { $ref: "getter-schema" },
pricing: { $ref: "pricing-schema" },
evaluator: { $ref: "evaluator-schema" },
submitter: { $ref: "submitter-schema" },
wallet: { $ref: "wallet-schema" },
},
Expand All @@ -269,6 +278,7 @@ export function getConfigValidator(): AnyValidateFunction<unknown> {
ajv.addSchema(MONITOR_SCHEMA);
ajv.addSchema(GETTER_SCHEMA);
ajv.addSchema(PRICING_SCHEMA);
ajv.addSchema(EVALUATOR_SCHEMA);
ajv.addSchema(SUBMITTER_SCHEMA);
ajv.addSchema(PERSISTER_SCHEMA);
ajv.addSchema(WALLET_SCHEMA);
Expand Down
16 changes: 13 additions & 3 deletions src/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { readFileSync } from 'fs';
import * as yaml from 'js-yaml';
import dotenv from 'dotenv';
import { PRICING_SCHEMA, getConfigValidator } from './config.schema';
import { GlobalConfig, ChainConfig, AMBConfig, GetterGlobalConfig, SubmitterGlobalConfig, PersisterConfig, WalletGlobalConfig, GetterConfig, SubmitterConfig, WalletConfig, MonitorConfig, MonitorGlobalConfig, PricingConfig, PricingGlobalConfig } from './config.types';
import { GlobalConfig, ChainConfig, AMBConfig, GetterGlobalConfig, SubmitterGlobalConfig, PersisterConfig, WalletGlobalConfig, GetterConfig, SubmitterConfig, WalletConfig, MonitorConfig, MonitorGlobalConfig, PricingConfig, PricingGlobalConfig, EvaluatorGlobalConfig, EvaluatorConfig } from './config.types';
import { JsonRpcProvider } from 'ethers6';

@Injectable()
Expand Down Expand Up @@ -101,6 +101,7 @@ export class ConfigService {
monitor: this.formatMonitorGlobalConfig(rawGlobalConfig.monitor),
getter: this.formatGetterGlobalConfig(rawGlobalConfig.getter),
pricing: this.formatPricingGlobalConfig(rawGlobalConfig.pricing),
evaluator: this.formatEvaluatorGlobalConfig(rawGlobalConfig.evaluator),
submitter: this.formatSubmitterGlobalConfig(rawGlobalConfig.submitter),
persister: this.formatPersisterGlobalConfig(rawGlobalConfig.persister),
wallet: this.formatWalletGlobalConfig(rawGlobalConfig.wallet),
Expand All @@ -122,6 +123,7 @@ export class ConfigService {
monitor: this.formatMonitorConfig(rawChainConfig.monitor),
getter: this.formatGetterConfig(rawChainConfig.getter),
pricing: this.formatPricingConfig(rawChainConfig.pricing),
evaluator: this.formatEvaluatorConfig(rawChainConfig.evaluator),
submitter: this.formatSubmitterConfig(rawChainConfig.submitter),
wallet: this.formatWalletConfig(rawChainConfig.wallet),
});
Expand Down Expand Up @@ -227,7 +229,7 @@ export class ConfigService {
return formattedConfig as PricingGlobalConfig;
}

private formatSubmitterGlobalConfig(rawConfig: any): SubmitterGlobalConfig {
private formatEvaluatorGlobalConfig(rawConfig: any): EvaluatorGlobalConfig {
const config = { ...rawConfig };
if (config.unrewardedDeliveryGas != undefined) {
config.unrewardedDeliveryGas = BigInt(config.unrewardedDeliveryGas);
Expand All @@ -241,7 +243,11 @@ export class ConfigService {
if (config.verificationAckGas != undefined) {
config.verificationAckGas = BigInt(config.verificationAckGas);
}
return config as SubmitterGlobalConfig;
return config as EvaluatorGlobalConfig;
}

private formatSubmitterGlobalConfig(rawConfig: any): SubmitterGlobalConfig {
return { ...rawConfig } as SubmitterGlobalConfig;
}

private formatPersisterGlobalConfig(rawConfig: any): PersisterConfig {
Expand Down Expand Up @@ -278,6 +284,10 @@ export class ConfigService {
return this.formatPricingGlobalConfig(rawConfig);
}

private formatEvaluatorConfig(rawConfig: any): EvaluatorConfig {
return this.formatEvaluatorGlobalConfig(rawConfig);
}

private formatSubmitterConfig(rawConfig: any): SubmitterConfig {
return this.formatSubmitterGlobalConfig(rawConfig);
}
Expand Down
25 changes: 16 additions & 9 deletions src/config/config.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface GlobalConfig {
monitor: MonitorGlobalConfig;
getter: GetterGlobalConfig;
pricing: PricingGlobalConfig;
evaluator: EvaluatorGlobalConfig;
submitter: SubmitterGlobalConfig;
persister: PersisterConfig;
wallet: WalletGlobalConfig;
Expand Down Expand Up @@ -38,6 +39,20 @@ export interface PricingGlobalConfig {

export interface PricingConfig extends PricingGlobalConfig {}

export interface EvaluatorGlobalConfig {
unrewardedDeliveryGas?: bigint;
verificationDeliveryGas?: bigint;
minDeliveryReward?: number;
relativeMinDeliveryReward?: number;
unrewardedAckGas?: bigint;
verificationAckGas?: bigint;
minAckReward?: number;
relativeMinAckReward?: number;
profitabilityFactor?: number;
}

export interface EvaluatorConfig extends EvaluatorGlobalConfig {}

export interface SubmitterGlobalConfig {
enabled?: boolean;
newOrdersDelay?: number;
Expand All @@ -48,15 +63,6 @@ export interface SubmitterGlobalConfig {

evaluationRetryInterval?: number;
maxEvaluationDuration?: number;
unrewardedDeliveryGas?: bigint;
verificationDeliveryGas?: bigint;
minDeliveryReward?: number;
relativeMinDeliveryReward?: number;
unrewardedAckGas?: bigint;
verificationAckGas?: bigint;
minAckReward?: number;
relativeMinAckReward?: number;
profitabilityFactor?: number;
}

export interface SubmitterConfig extends SubmitterGlobalConfig {}
Expand Down Expand Up @@ -103,6 +109,7 @@ export interface ChainConfig {
monitor: MonitorConfig;
getter: GetterConfig;
pricing: PricingConfig;
evaluator: EvaluatorConfig;
submitter: SubmitterConfig;
wallet: WalletConfig;
}
113 changes: 113 additions & 0 deletions src/evaluator/evaluator.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { BadRequestException, Controller, Get, OnModuleInit, Query } from "@nestjs/common";
import { EvaluatorInterface } from "./evaluator.interface";
import { EvaluatorService } from "./evaluator.service";
import { EvaluateAckQuery, EvaluateAckQueryResponse, EvaluateDeliveryQuery, EvaluteDeliveryQueryResponse } from "./evaluator.types";

@Controller()
export class EvaluatorController implements OnModuleInit {
private evaluator!: EvaluatorInterface;

constructor(
private readonly evaluatorService: EvaluatorService,
) {}

async onModuleInit() {
await this.initializeEvaluatorInterface();
}

private async initializeEvaluatorInterface(): Promise<void> {
const port = await this.evaluatorService.attachToEvaluator();
this.evaluator = new EvaluatorInterface(port);
}

@Get('evaluateDelivery')
async evaluateDelivery(@Query() query: EvaluateDeliveryQuery): Promise<any> {

//TODO validate query format
const result = await this.evaluator.evaluateDelivery(
query.chainId,
query.messageIdentifier,
{
gasEstimate: BigInt(query.gasEstimate),
observedGasEstimate: BigInt(query.observedGasEstimate),
additionalFeeEstimate: BigInt(query.additionalFeeEstimate),
},
BigInt(query.value)
);

if (result.evaluation == undefined) {
throw new BadRequestException('Failed to generate an evaluation output for the given parameters.');
}

const response: EvaluteDeliveryQueryResponse = {
chainId: result.chainId,
messageIdentifier: result.messageIdentifier,
maxGasDelivery: result.evaluation.maxGasDelivery.toString(),
maxGasAck: result.evaluation.maxGasAck.toString(),
gasEstimate: result.evaluation.gasEstimate.toString(),
observedGasEstimate: result.evaluation.observedGasEstimate.toString(),
additionalFeeEstimate: result.evaluation.additionalFeeEstimate.toString(),
destinationGasPrice: result.evaluation.destinationGasPrice.toString(),
value: result.evaluation.value.toString(),
sourceGasPrice: result.evaluation.sourceGasPrice.toString(),
deliveryCost: result.evaluation.deliveryCost.toString(),
deliveryReward: result.evaluation.deliveryReward.toString(),
maxAckLoss: result.evaluation.maxAckLoss.toString(),
deliveryFiatCost: result.evaluation.deliveryFiatCost,
deliveryFiatReward: result.evaluation.deliveryFiatReward,
securedDeliveryFiatReward: result.evaluation.securedDeliveryFiatReward,
profitabilityFactor: result.evaluation.profitabilityFactor,
securedDeliveryFiatProfit: result.evaluation.securedDeliveryFiatProfit,
securedDeliveryRelativeProfit: result.evaluation.securedDeliveryRelativeProfit,
minDeliveryReward: result.evaluation.minDeliveryReward,
relativeMinDeliveryReward: result.evaluation.relativeMinDeliveryReward,
relayDelivery: result.evaluation.relayDelivery,
}

return response;
}

@Get('evaluateAck')
async evaluateAck(@Query() query: EvaluateAckQuery): Promise<any> {

//TODO validate query format
const result = await this.evaluator.evaluateAck(
query.chainId,
query.messageIdentifier,
{
gasEstimate: BigInt(query.gasEstimate),
observedGasEstimate: BigInt(query.observedGasEstimate),
additionalFeeEstimate: BigInt(query.additionalFeeEstimate),
},
BigInt(query.value)
);

if (result.evaluation == undefined) {
throw new BadRequestException('Failed to generate an evaluation output for the given parameters.');
}

const response: EvaluateAckQueryResponse = {
chainId: result.chainId,
messageIdentifier: result.messageIdentifier,
maxGasDelivery: result.evaluation.maxGasDelivery.toString(),
maxGasAck: result.evaluation.maxGasAck.toString(),
gasEstimate: result.evaluation.gasEstimate.toString(),
observedGasEstimate: result.evaluation.observedGasEstimate.toString(),
additionalFeeEstimate: result.evaluation.additionalFeeEstimate.toString(),
sourceGasPrice: result.evaluation.sourceGasPrice.toString(),
ackCost: result.evaluation.ackCost.toString(),
ackReward: result.evaluation.ackReward.toString(),
profitabilityFactor: result.evaluation.profitabilityFactor,
ackFiatProfit: result.evaluation.ackFiatProfit,
ackRelativeProfit: result.evaluation.ackRelativeProfit,
minAckReward: result.evaluation.minAckReward,
relativeMinAckReward: result.evaluation.relativeMinAckReward,
deliveryCost: result.evaluation.deliveryCost.toString(),
deliveryReward: result.evaluation.deliveryReward.toString(),
relayAckForDeliveryBounty: result.evaluation.relayAckForDeliveryBounty,
relayAck: result.evaluation.relayAck,
}

return response;
}
}
74 changes: 74 additions & 0 deletions src/evaluator/evaluator.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { GasEstimateComponents } from "src/resolvers/resolver";
import { MessagePort } from "worker_threads";
import { EvaluateAckMessage, EvaluateAckResponseMessage, EvaluateDeliveryMessage, EvaluateDeliveryResponseMessage, EvaluatorMessage, EvaluatorMessageType, EvaluatorPortData } from "./evaluator.types";


export class EvaluatorInterface {
private portMessageId = 0;

constructor(private readonly port: MessagePort) {}

private getNextPortMessageId(): number {
return this.portMessageId++;
}

private async submitMessage(message: EvaluatorMessage): Promise<any> {

const messageId = this.getNextPortMessageId();

const data: EvaluatorPortData = {
messageId,
message
};

const resultPromise = new Promise<any>(resolve => {
const listener = (responseData: EvaluatorPortData) => {
if (responseData.messageId === messageId) {
this.port.off("message", listener);
resolve(responseData.message)
}
}
this.port.on("message", listener);

this.port.postMessage(data);
});

return resultPromise;
}

async evaluateDelivery(
chainId: string,
messageIdentifier: string,
gasEstimateComponents: GasEstimateComponents,
value: bigint,
): Promise<EvaluateDeliveryResponseMessage> {

const message: EvaluateDeliveryMessage = {
type: EvaluatorMessageType.EvaluateDelivery,
chainId,
messageIdentifier,
gasEstimateComponents,
value
};

return this.submitMessage(message);
}

async evaluateAck(
chainId: string,
messageIdentifier: string,
gasEstimateComponents: GasEstimateComponents,
value: bigint,
): Promise<EvaluateAckResponseMessage> {

const message: EvaluateAckMessage = {
type: EvaluatorMessageType.EvaluateAck,
chainId,
messageIdentifier,
gasEstimateComponents,
value
};

return this.submitMessage(message);
}
}
14 changes: 14 additions & 0 deletions src/evaluator/evaluator.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Global, Module } from '@nestjs/common';
import { PricingModule } from './../pricing/pricing.module';
import { WalletModule } from 'src/wallet/wallet.module';
import { EvaluatorService } from './evaluator.service';
import { EvaluatorController } from './evaluator.controller';

@Global()
@Module({
controllers: [EvaluatorController],
providers: [EvaluatorService],
exports: [EvaluatorService],
imports: [PricingModule, WalletModule],
})
export class EvaluatorModule {}
Loading