From ad6e548fa8d709891ac29024fe7e572c893b30d3 Mon Sep 17 00:00:00 2001 From: Sital999 Date: Wed, 6 Nov 2024 10:50:29 +0545 Subject: [PATCH 1/4] Add EventTriggerHandler class --- agent-node/src/constants/global.ts | 9 +- agent-node/src/service/EventTriggerHandler.ts | 101 ++++++++++++++++++ agent-node/src/service/RpcTopicHandler.ts | 41 +++---- agent-node/src/types/eventTriger.ts | 31 ++++++ agent-node/src/types/types.ts | 5 - agent-node/src/utils/agent.ts | 14 +-- agent-node/src/utils/scheduler.ts | 2 +- dbsync-api/src/repository/address.ts | 2 +- 8 files changed, 157 insertions(+), 48 deletions(-) create mode 100644 agent-node/src/service/EventTriggerHandler.ts create mode 100644 agent-node/src/types/eventTriger.ts diff --git a/agent-node/src/constants/global.ts b/agent-node/src/constants/global.ts index 148f37a89..a4fedbedd 100644 --- a/agent-node/src/constants/global.ts +++ b/agent-node/src/constants/global.ts @@ -1,13 +1,10 @@ -import { EventTriggerTypeDetails } from '../types/types' +import { IEventBasedAction } from '../types/eventTriger' export const globalState: { - eventTriggerTypeDetails: EventTriggerTypeDetails + eventTypeDetails: IEventBasedAction[] agentName: string } = { - eventTriggerTypeDetails: { - eventType: false, - function_name: '', - }, + eventTypeDetails: [], agentName: '', } diff --git a/agent-node/src/service/EventTriggerHandler.ts b/agent-node/src/service/EventTriggerHandler.ts new file mode 100644 index 000000000..cf4445265 --- /dev/null +++ b/agent-node/src/service/EventTriggerHandler.ts @@ -0,0 +1,101 @@ +import { ManagerInterface } from './ManagerInterfaceService' +import { Transaction } from 'libcardano/cardano/ledger-serialization/transaction' +import { AgentRunner } from '../executor/AgentRunner' +import { + compareValue, + getPropertyValue, + reduceBooleanArray, +} from '../utils/validator' +import { IEventBasedAction, IField, IFilterNode } from '../types/eventTriger' + +export class EventTriggerHandler { + eventBasedActions: IEventBasedAction[] = [] + managerInterface: ManagerInterface + constructor(managerInterface: ManagerInterface) { + this.managerInterface = managerInterface + } + + onBlock(transactions: Transaction[], agentRunners: AgentRunner[]) { + if (this.eventBasedActions.length) { + transactions.forEach((tx: Transaction) => { + this.eventBasedActions.forEach((eventBasedAction) => { + const handler = (this as any)[ + eventBasedAction.eventTrigger.id + 'Handler' + ] + if (handler !== undefined && handler !== 'constructor') { + handler.bind(this)(tx, eventBasedAction, agentRunners) + } + }) + }) + } + } + + transactionHandler( + tx: Transaction, + eventBasedAction: IEventBasedAction, + agentRunners?: AgentRunner[] + ) { + const txProperty = Array.isArray(eventBasedAction.eventTrigger.id) + ? getPropertyValue(tx, [ + 'body', + ...eventBasedAction.eventTrigger.id.splice(1), + ]) + : tx.body + const finalBooleanVals = this.getComparedBooleans( + txProperty, + eventBasedAction.eventTrigger.parameters + ) + const { operator, negate } = eventBasedAction.eventTrigger + const { function_name, parameters } = + eventBasedAction.triggeringFunction + const reducedFinalBoolValue = reduceBooleanArray( + finalBooleanVals, + operator, + negate + ) + if (reducedFinalBoolValue && agentRunners) { + agentRunners.forEach((runner, index) => { + runner.invokeFunction( + 'EVENT', + index, + function_name, + ...parameters + ) + }) + } + } + + getComparedBooleans(tx: any, params: IFilterNode[]) { + const comparedBooleanValues: Array = [] + params.forEach((param) => { + if ('id' in param) { + comparedBooleanValues.push( + this.compareFields(tx, param as IField) + ) + } else if ('children' in param) { + const bools = this.getComparedBooleans(tx, param.children) + comparedBooleanValues.push( + reduceBooleanArray(bools, param.operator, param.negate) + ) + } + }) + return comparedBooleanValues + } + + compareFields(tx: any, param: IField) { + const txPropertyValue = getPropertyValue(tx, param.id as []) + if (!txPropertyValue) { + return false + } + const comparisonResult = compareValue( + param.operator, + param.value, + txPropertyValue + ) + return param.negate ? !comparisonResult : comparisonResult + } + + addEventActions(actions: IEventBasedAction[]) { + this.eventBasedActions = actions + } +} diff --git a/agent-node/src/service/RpcTopicHandler.ts b/agent-node/src/service/RpcTopicHandler.ts index 1603ad50b..c9773001a 100644 --- a/agent-node/src/service/RpcTopicHandler.ts +++ b/agent-node/src/service/RpcTopicHandler.ts @@ -2,21 +2,22 @@ import { ManagerInterface } from './ManagerInterfaceService' import { TxListener } from '../executor/TxListener' import { BlockEvent } from 'libcardano/types' import { parseRawBlockBody } from 'libcardano/cardano/ledger-serialization/transaction' -import { globalState } from '../constants/global' import { clearScheduledTasks, scheduleFunctions } from '../utils/scheduler' -import { - checkIfAgentWithEventTriggerTypeExists, - createActionDtoForEventTrigger, -} from '../utils/agent' +import { checkIfAgentWithEventTriggerTypeExists } from '../utils/agent' import { ScheduledTask } from 'node-cron' import { AgentRunner } from '../executor/AgentRunner' +import { EventTriggerHandler } from './EventTriggerHandler' export class RpcTopicHandler { managerInterface: ManagerInterface txListener: TxListener + eventTriggerHandlers: EventTriggerHandler constructor(managerInterface: ManagerInterface, txListener: TxListener) { this.managerInterface = managerInterface this.txListener = txListener + this.eventTriggerHandlers = new EventTriggerHandler( + this.managerInterface + ) } handleEvent( eventName: string, @@ -42,29 +43,7 @@ export class RpcTopicHandler { 'txCount=' + transactions.length ) this.txListener.onBlock({ ...block, body: transactions }) - if ( - globalState.eventTriggerTypeDetails.eventType && - transactions.length - ) { - transactions.forEach((tx: any) => { - if (Array.isArray(tx.body.proposalProcedures)) { - tx.body.proposalProcedures.forEach( - (proposal: any, index: number) => { - const { function_name, parameters } = - createActionDtoForEventTrigger(tx, index) - agentRunners.forEach((runner, index) => { - runner.invokeFunction( - 'EVENT', - index, - function_name, - ...(parameters as any) - ) - }) - } - ) - } - }) - } + this.eventTriggerHandlers.onBlock(transactions, agentRunners) } initial_config( message: any, @@ -72,7 +51,11 @@ export class RpcTopicHandler { scheduledTasks: ScheduledTask[] ) { const { configurations } = message - checkIfAgentWithEventTriggerTypeExists(configurations) + const eventBasedActions = + checkIfAgentWithEventTriggerTypeExists(configurations) + if (eventBasedActions) { + this.eventTriggerHandlers.addEventActions(eventBasedActions) + } agentRunners.forEach((runner, index) => { scheduleFunctions( this.managerInterface, diff --git a/agent-node/src/types/eventTriger.ts b/agent-node/src/types/eventTriger.ts new file mode 100644 index 000000000..48b4036e5 --- /dev/null +++ b/agent-node/src/types/eventTriger.ts @@ -0,0 +1,31 @@ +import { Action } from '../service/triggerService' + +export type BooleanOperator = 'AND' | 'OR' +export type ComparisonOperator = 'equals' | 'greaterThan' | 'lessThan' | 'in' + +export interface IEventBasedAction { + eventTrigger: IEventTrigger + triggeringFunction: Action +} + +export interface IEventTrigger { + id: string | string[] + parameters: IFilterNode[] + negate: boolean + operator: BooleanOperator +} + +export interface IField { + id: string | string[] + value: any + negate: boolean + operator: ComparisonOperator +} + +interface IChildrenFields { + children: IFilterNode[] + negate: boolean + operator: BooleanOperator +} + +export type IFilterNode = IField | IChildrenFields diff --git a/agent-node/src/types/types.ts b/agent-node/src/types/types.ts index a96dce239..7b7c9ceff 100644 --- a/agent-node/src/types/types.ts +++ b/agent-node/src/types/types.ts @@ -6,8 +6,3 @@ export type AgentWalletDetails = { payment_verification_key_hash: string drep_id: string } - -export type EventTriggerTypeDetails = { - eventType: boolean - function_name: string -} diff --git a/agent-node/src/utils/agent.ts b/agent-node/src/utils/agent.ts index e164696b0..ca17b4820 100644 --- a/agent-node/src/utils/agent.ts +++ b/agent-node/src/utils/agent.ts @@ -4,11 +4,11 @@ import { Configuration, TriggerType, } from '../service/triggerService' -import { globalState } from '../constants/global' import { ManagerInterface } from '../service/ManagerInterfaceService' import { CallLog } from '../executor/Executor' import { ILog, InternalLog } from '../service/TriggerActionHandler' import { DateTime } from 'luxon' +import { IEventBasedAction, IEventTrigger } from '../types/eventTriger' export function getParameterValue( parameters: ActionParameter[] = [], @@ -21,19 +21,21 @@ export function getParameterValue( export function checkIfAgentWithEventTriggerTypeExists( configurations: Configuration[] ) { + const eventBasedAction: IEventBasedAction[] = [] configurations.forEach((config) => { if (config.type === 'EVENT') { - globalState.eventTriggerTypeDetails = { - eventType: true, - function_name: config.action.function_name, - } + eventBasedAction.push({ + eventTrigger: config.data as unknown as IEventTrigger, + triggeringFunction: config.action, + }) } }) + return eventBasedAction.flat() } export function createActionDtoForEventTrigger(tx: any, index: number): Action { return { - function_name: globalState.eventTriggerTypeDetails.function_name, + function_name: 'voteOnProposal', parameters: [ { name: 'proposal', diff --git a/agent-node/src/utils/scheduler.ts b/agent-node/src/utils/scheduler.ts index 664c81cc7..a837014e9 100644 --- a/agent-node/src/utils/scheduler.ts +++ b/agent-node/src/utils/scheduler.ts @@ -59,7 +59,7 @@ export function scheduleFunctions( const task = createTask( runner, manager, - action, + action as Action, frequency, probability, instanceIndex diff --git a/dbsync-api/src/repository/address.ts b/dbsync-api/src/repository/address.ts index fb19420ad..577313dd8 100644 --- a/dbsync-api/src/repository/address.ts +++ b/dbsync-api/src/repository/address.ts @@ -11,4 +11,4 @@ export const fetchFaucetBalance= async (address:string)=>{ GROUP BY stake_address.hash_raw ` as Array> return result.length?+result[0].total_value:0 -} \ No newline at end of file +} From 4ebea7ea87db023f2e2c0129ae4c86a86347e9a3 Mon Sep 17 00:00:00 2001 From: Sital999 Date: Wed, 6 Nov 2024 10:51:15 +0545 Subject: [PATCH 2/4] Add util fn for bech32 conversion --- agent-node/src/utils/cardano.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/agent-node/src/utils/cardano.ts b/agent-node/src/utils/cardano.ts index c687521df..aa9435e70 100644 --- a/agent-node/src/utils/cardano.ts +++ b/agent-node/src/utils/cardano.ts @@ -25,6 +25,29 @@ export function rewardAddressBech32( ) } +export function convertToBufferIfBech32(address: any): Buffer | string { + if (!address) return '' + else if (typeof address !== 'string') return address + else if ( + address.includes('stake') || + address.includes('drep') || + address.includes('addr') + ) { + const decoded = bech32.decode(address, 1000) + const data = bech32.fromWords(decoded.words) + return Buffer.from(data) + } + return address +} + +export function convertToHexIfBech32(address: string): string { + const bufferVal = convertToBufferIfBech32(address) + if (Buffer.isBuffer(bufferVal)) { + return bufferVal.toString('hex') + } + return bufferVal +} + export function loadRootKeyFromBuffer() { const rootKeyBuffer = globalRootKeyBuffer.value if (!rootKeyBuffer) { From 50d1dc8dc087c900d3e55176931d71d3bd03b94e Mon Sep 17 00:00:00 2001 From: Sital999 Date: Wed, 6 Nov 2024 10:52:07 +0545 Subject: [PATCH 3/4] Add util fn for eventHandler --- agent-node/src/utils/validator.ts | 61 +++++++++++++++++++ .../app/models/trigger/resposne_dto.py | 8 +-- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/agent-node/src/utils/validator.ts b/agent-node/src/utils/validator.ts index 32d9c31e9..9ff690fe1 100644 --- a/agent-node/src/utils/validator.ts +++ b/agent-node/src/utils/validator.ts @@ -1,3 +1,6 @@ +import { convertToBufferIfBech32 } from './cardano' +import { BooleanOperator, ComparisonOperator } from '../types/eventTriger' + const NetworkName = ['preview', 'preprod', 'sanchonet'] export function validateToken(token: string) { @@ -11,3 +14,61 @@ export function validateToken(token: string) { return 'Not a valid network name' return '' } + +export function reduceBooleanArray( + bools: Array, + reducer: BooleanOperator, + negate: boolean +) { + let reducedBooleanLogic + if (reducer === 'AND') { + reducedBooleanLogic = bools.reduce((acc, bool) => acc && bool, true) + } else if (reducer === 'OR') { + reducedBooleanLogic = bools.reduce((acc, bool) => acc || bool, false) + } + return negate ? !reducedBooleanLogic : !!reducedBooleanLogic +} + +export function compareValue( + operator: ComparisonOperator, + filterValue: any, + txPropertyVal: any +) { + filterValue = convertToBufferIfBech32(filterValue) + switch (operator) { + case 'greaterThan': + if (Array.isArray(txPropertyVal)) { + return txPropertyVal.some((item) => filterValue > item) + } + return filterValue > txPropertyVal + case 'lessThan': + if (Array.isArray(txPropertyVal)) { + return txPropertyVal.some((item) => filterValue < item) + } + return filterValue < txPropertyVal + case 'equals': + if (Array.isArray(txPropertyVal)) { + return txPropertyVal.some( + (item) => !Buffer.compare(filterValue, item) + ) + } + return !Buffer.compare(filterValue, txPropertyVal) + case 'in': + if (Array.isArray(txPropertyVal)) { + return txPropertyVal.includes(filterValue) + } + return false + default: + return false + } +} + +export function getPropertyValue(initial_value: any, keys: Array) { + return keys.reduce((acc, key) => { + if (Array.isArray(acc)) { + return acc.map((item) => item[key]).filter(Boolean) + } else { + return acc && acc[key] != undefined ? acc[key] : undefined + } + }, initial_value) +} diff --git a/api/backend/app/models/trigger/resposne_dto.py b/api/backend/app/models/trigger/resposne_dto.py index 81ed708b5..5f072d734 100644 --- a/api/backend/app/models/trigger/resposne_dto.py +++ b/api/backend/app/models/trigger/resposne_dto.py @@ -1,10 +1,10 @@ +from typing import Union, Optional, Any + from pydantic import BaseModel -from typing import Union, Optional from backend.app.models.trigger.trigger_dto import ( Action, CronTriggerDTO, - EventTriggerDTO, ) @@ -12,8 +12,8 @@ class TriggerResponse(BaseModel): id: str agent_id: str type: str - action: Optional[Action] = None - data: Optional[Union[CronTriggerDTO, EventTriggerDTO]] = None + action: Optional[Action | Any] = None + data: Optional[Union[CronTriggerDTO, Any]] = None # class TriggerResponse_agent_id(BaseModel): From 7169415646e259f76b606616eb57f5ee11f88de9 Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Tue, 19 Nov 2024 16:43:57 +0545 Subject: [PATCH 4/4] Support advance event trigger filter --- agent-node/package.json | 2 +- agent-node/src/executor/AgentRunner.ts | 1 + agent-node/src/service/EventTriggerHandler.ts | 123 +++++++++++------- agent-node/src/types/eventTriger.ts | 16 +-- agent-node/src/utils/agent.ts | 4 +- agent-node/src/utils/cardano.ts | 2 +- agent-node/src/utils/operatorSupport.ts | 121 +++++++++++++++++ agent-node/src/utils/validator.ts | 64 ++++----- agent-node/yarn.lock | 8 +- 9 files changed, 232 insertions(+), 109 deletions(-) create mode 100644 agent-node/src/utils/operatorSupport.ts diff --git a/agent-node/package.json b/agent-node/package.json index b13772a11..aa1202375 100644 --- a/agent-node/package.json +++ b/agent-node/package.json @@ -23,7 +23,7 @@ "bech32": "^2.0.0", "dotenv": "^16.4.5", "kuber-client": "^2.0.0", - "libcardano": "1.4.2", + "libcardano": "1.4.4", "luxon": "^3.4.4", "node-cron": "^3.0.3", "ws": "^8.18.0" diff --git a/agent-node/src/executor/AgentRunner.ts b/agent-node/src/executor/AgentRunner.ts index cd8e16dc6..74138a35a 100644 --- a/agent-node/src/executor/AgentRunner.ts +++ b/agent-node/src/executor/AgentRunner.ts @@ -22,6 +22,7 @@ export class AgentRunner { method: string, ...args: any ) { + this.executor.invokeFunction(method, ...args).then((result) => { saveTxLog(result, this.managerInterface, triggerType, instanceIndex) }) diff --git a/agent-node/src/service/EventTriggerHandler.ts b/agent-node/src/service/EventTriggerHandler.ts index cf4445265..bf0919589 100644 --- a/agent-node/src/service/EventTriggerHandler.ts +++ b/agent-node/src/service/EventTriggerHandler.ts @@ -3,10 +3,9 @@ import { Transaction } from 'libcardano/cardano/ledger-serialization/transaction import { AgentRunner } from '../executor/AgentRunner' import { compareValue, - getPropertyValue, - reduceBooleanArray, } from '../utils/validator' -import { IEventBasedAction, IField, IFilterNode } from '../types/eventTriger' +import { IBooleanNode, IEventBasedAction, IFieldNode, IFilterNode } from "../types/eventTriger"; +import { node } from "globals"; export class EventTriggerHandler { eventBasedActions: IEventBasedAction[] = [] @@ -20,7 +19,7 @@ export class EventTriggerHandler { transactions.forEach((tx: Transaction) => { this.eventBasedActions.forEach((eventBasedAction) => { const handler = (this as any)[ - eventBasedAction.eventTrigger.id + 'Handler' + (eventBasedAction.eventTrigger as IFieldNode).id + 'Handler' ] if (handler !== undefined && handler !== 'constructor') { handler.bind(this)(tx, eventBasedAction, agentRunners) @@ -35,64 +34,88 @@ export class EventTriggerHandler { eventBasedAction: IEventBasedAction, agentRunners?: AgentRunner[] ) { - const txProperty = Array.isArray(eventBasedAction.eventTrigger.id) - ? getPropertyValue(tx, [ - 'body', - ...eventBasedAction.eventTrigger.id.splice(1), - ]) - : tx.body - const finalBooleanVals = this.getComparedBooleans( - txProperty, - eventBasedAction.eventTrigger.parameters - ) - const { operator, negate } = eventBasedAction.eventTrigger - const { function_name, parameters } = - eventBasedAction.triggeringFunction - const reducedFinalBoolValue = reduceBooleanArray( - finalBooleanVals, - operator, - negate - ) - if (reducedFinalBoolValue && agentRunners) { + let result + try { + console.log("Tx:", tx.hash.toString('hex'), "tx.outputs=", tx.body.outputs.length, "inputs=", tx.body.inputs.length) + result = this.solveNode({ tx: tx.body, transaction: tx.body }, eventBasedAction.eventTrigger, []) + console.log("tx=", tx.hash.toString('hex'), "solution=", result) + }catch (e){ + console.error("Error handling event",e); + return + } + + const {function_name,parameters} = eventBasedAction.triggeringFunction + if (result && agentRunners) { agentRunners.forEach((runner, index) => { runner.invokeFunction( 'EVENT', index, - function_name, - ...parameters + function_name, + ...parameters ) }) } } + solveNode(targetObject: any,filterNode: IFilterNode,parentNodes:string[]){ + return this.solveNodeInternal(targetObject,filterNode,filterNode.id,parentNodes) + } + solveNodeInternal(targetObject: any,filterNode: IFilterNode,nodes:string[]|string|undefined,parent_nodes:string[]): boolean{ + + if(nodes ===undefined || nodes.length==0 ) { + let result= 'children' in filterNode ? this.solveBooleanNode(targetObject, filterNode, parent_nodes) + : this.solveFieldNode(targetObject, filterNode, parent_nodes) + return filterNode.negate?!result:result + }else if(typeof nodes==="string") { + nodes=[nodes] + } + + let result + const propertyValue = targetObject[nodes[0]] + parent_nodes.push(nodes[0]) + + let subArray = nodes.slice(1) + if (Array.isArray(propertyValue)) { + const node_pos=parent_nodes.length + parent_nodes.push('') + result= propertyValue.reduce((acc, node,index:number) => { + if(acc){ + return acc + } + parent_nodes[node_pos]=index.toString() + const result=this.solveNodeInternal(node, filterNode, subArray,parent_nodes) + console.log(parent_nodes.join('.'),": result=",result) + return result + }, false) + parent_nodes.pop() + } else { + result= this.solveNodeInternal(propertyValue, filterNode, subArray,parent_nodes) + } + parent_nodes.pop() + return result - getComparedBooleans(tx: any, params: IFilterNode[]) { - const comparedBooleanValues: Array = [] - params.forEach((param) => { - if ('id' in param) { - comparedBooleanValues.push( - this.compareFields(tx, param as IField) - ) - } else if ('children' in param) { - const bools = this.getComparedBooleans(tx, param.children) - comparedBooleanValues.push( - reduceBooleanArray(bools, param.operator, param.negate) - ) - } - }) - return comparedBooleanValues } - compareFields(tx: any, param: IField) { - const txPropertyValue = getPropertyValue(tx, param.id as []) - if (!txPropertyValue) { - return false + solveBooleanNode(targetObject: any,filterNode: IBooleanNode,parent_nodes:string[]=[]): boolean{ + let orOperator=(a:boolean,b:boolean)=> a || b + let operator=orOperator + if(filterNode.operator === 'AND'){ + operator= (a:boolean,b:boolean)=> a && b } - const comparisonResult = compareValue( - param.operator, - param.value, - txPropertyValue - ) - return param.negate ? !comparisonResult : comparisonResult + const result= filterNode.children.reduce((acc,node)=>{ + return operator(acc,this.solveNodeInternal(targetObject,node,node.id,parent_nodes)) + },operator !== orOperator) + console.log("id=",filterNode.id,"operator=",filterNode.operator,"result=",result) + return result + } + + solveFieldNode(targetObject: any,filterNode: IFieldNode,parent_nodes:string[]) : boolean{ + + return compareValue( + filterNode.operator, + filterNode.value, + targetObject, + parent_nodes + ) } addEventActions(actions: IEventBasedAction[]) { diff --git a/agent-node/src/types/eventTriger.ts b/agent-node/src/types/eventTriger.ts index 48b4036e5..d3eb207ae 100644 --- a/agent-node/src/types/eventTriger.ts +++ b/agent-node/src/types/eventTriger.ts @@ -4,28 +4,22 @@ export type BooleanOperator = 'AND' | 'OR' export type ComparisonOperator = 'equals' | 'greaterThan' | 'lessThan' | 'in' export interface IEventBasedAction { - eventTrigger: IEventTrigger + eventTrigger: IBooleanNode | IFieldNode triggeringFunction: Action } -export interface IEventTrigger { - id: string | string[] - parameters: IFilterNode[] - negate: boolean - operator: BooleanOperator -} - -export interface IField { +export interface IFieldNode { id: string | string[] value: any negate: boolean operator: ComparisonOperator } -interface IChildrenFields { +export interface IBooleanNode { + id?:string|string[] children: IFilterNode[] negate: boolean operator: BooleanOperator } -export type IFilterNode = IField | IChildrenFields +export type IFilterNode = IFieldNode | IBooleanNode diff --git a/agent-node/src/utils/agent.ts b/agent-node/src/utils/agent.ts index ca17b4820..b0be59cbc 100644 --- a/agent-node/src/utils/agent.ts +++ b/agent-node/src/utils/agent.ts @@ -8,7 +8,7 @@ import { ManagerInterface } from '../service/ManagerInterfaceService' import { CallLog } from '../executor/Executor' import { ILog, InternalLog } from '../service/TriggerActionHandler' import { DateTime } from 'luxon' -import { IEventBasedAction, IEventTrigger } from '../types/eventTriger' +import { IEventBasedAction, IFilterNode } from "../types/eventTriger"; export function getParameterValue( parameters: ActionParameter[] = [], @@ -25,7 +25,7 @@ export function checkIfAgentWithEventTriggerTypeExists( configurations.forEach((config) => { if (config.type === 'EVENT') { eventBasedAction.push({ - eventTrigger: config.data as unknown as IEventTrigger, + eventTrigger: config.data as unknown as IFilterNode, triggeringFunction: config.action, }) } diff --git a/agent-node/src/utils/cardano.ts b/agent-node/src/utils/cardano.ts index aa9435e70..c6abddef5 100644 --- a/agent-node/src/utils/cardano.ts +++ b/agent-node/src/utils/cardano.ts @@ -25,7 +25,7 @@ export function rewardAddressBech32( ) } -export function convertToBufferIfBech32(address: any): Buffer | string { +export function convertToBufferIfBech32(address: any): Buffer | string| any { if (!address) return '' else if (typeof address !== 'string') return address else if ( diff --git a/agent-node/src/utils/operatorSupport.ts b/agent-node/src/utils/operatorSupport.ts new file mode 100644 index 000000000..d5325cb8c --- /dev/null +++ b/agent-node/src/utils/operatorSupport.ts @@ -0,0 +1,121 @@ +type TypeHandler = (a: any, b: any) => boolean; + +const operatorNameToSymbol: Record = { + // Text-based comparison operators + greaterthan: '>', + greaterthanorequal: '>=', + lessthan: '<', + lessthanorequal: '<=', + equal: '==', + equals: '==', + notequal: '!=', + notequals: '!=', + + // Shorthand comparison operators + gt: '>', + gte: '>=', + lt: '<', + lte: '<=', + ne: '!=', +}; +class LogicalFunctions { + private typeHandlers: Map>; + + constructor() { + // Initialize a map to hold handlers for different types/classes + this.typeHandlers = new Map(); + } + + // Register operator for a specific class or type (e.g., Person, Animal, Number, etc.) + register(type: string, operator: string, handler: TypeHandler): void { + if (!this.typeHandlers.has(type)) { + this.typeHandlers.set(type, {}); + } + const typeRegistry = this.typeHandlers.get(type)!; + + if (typeRegistry[operator]) { + throw new Error(`Operator "${operator}" for type "${type}" is already registered.`); + } + + typeRegistry[operator] = handler; + } + + // Execute the logical operation, checking the types/classes of the operands + execute(operator: string, a: any, b: any): boolean { + const aClass = a.constructor ? a.constructor.name : typeof a; + const bClass = b.constructor ? b.constructor.name : typeof b; + + if(operatorNameToSymbol[operator.toLowerCase()]){ + operator=operatorNameToSymbol[operator.toLowerCase()] + } + // Try class-specific operator registration first + if (this.typeHandlers.has(aClass)) { + const aHandlers = this.typeHandlers.get(aClass)!; + if (aHandlers[operator]) { + return aHandlers[operator](a, b); + } + } + + // If class-specific handler not found, check for type (primitive) operators + if (this.typeHandlers.has(aClass.toLowerCase())) { + const aHandlers = this.typeHandlers.get(aClass.toLowerCase())!; + if (aHandlers[operator]) { + return aHandlers[operator](a, b); + } + } + + // Try class-specific operator registration for the second operand + if (this.typeHandlers.has(bClass)) { + const bHandlers = this.typeHandlers.get(bClass)!; + if (bHandlers[operator]) { + return bHandlers[operator](a, b); + } + } + + // If still not found, try for primitive types such as number, string, etc. + if (this.typeHandlers.has("number")) { + const numberHandlers = this.typeHandlers.get("number")!; + if (numberHandlers[operator]) { + return numberHandlers[operator](a, b); + } + } + + console.log("typeHandlers",JSON.stringify(Array.from( this.typeHandlers.keys()),undefined,2)); + console.log("typeHandlersvalues",JSON.stringify(Array.from( this.typeHandlers.values()),undefined,2)); + + // If operator is not found for the types of operands, throw an error + throw new Error(`Operator "${operator}" is not supported for operands of types "${aClass}" and "${bClass}".`); + } +} + + + +// Initialize the _logicalFunctions instance +const _logicalFunctions = new LogicalFunctions(); + +// Register handlers for numbers +_logicalFunctions.register("number", "==", (a: number, b: number) => a === b); +_logicalFunctions.register("number", "!=", (a: number, b: number) => a !== b); +_logicalFunctions.register("number", ">", (a: number, b: number) => a > b); +_logicalFunctions.register("number", "<", (a: number, b: number) => a < b); +_logicalFunctions.register("number", ">=", (a: number, b: number) => a >= b); +_logicalFunctions.register("number", "<=", (a: number, b: number) => a <= b); + +// Register handlers for strings +_logicalFunctions.register("string", "==", (a: string, b: string) => a === b); +_logicalFunctions.register("string", "!=", (a: string, b: string) => a !== b); +_logicalFunctions.register("string", ">", (a: string, b: string) => a > b); +_logicalFunctions.register("string", "<", (a: string, b: string) => a < b); +_logicalFunctions.register("string", ">=", (a: string, b: string) => a >= b); +_logicalFunctions.register("string", "<=", (a: string, b: string) => a <= b); + +// Register handlers for buffers +_logicalFunctions.register("buffer", "==", (a: Buffer, b: Buffer) => a.equals(b)); +_logicalFunctions.register("buffer", "!=", (a: Buffer, b: Buffer) => !a.equals(b)); +_logicalFunctions.register("buffer", ">", (a: Buffer, b: Buffer) => a.compare(b) > 0); +_logicalFunctions.register("buffer", "<", (a: Buffer, b: Buffer) => a.compare(b) < 0); +_logicalFunctions.register("buffer", ">=", (a: Buffer, b: Buffer) => a.compare(b) >= 0); +_logicalFunctions.register("buffer", "<=", (a: Buffer, b: Buffer) => a.compare(b) <= 0); + +export const logicalFunctions=_logicalFunctions; + diff --git a/agent-node/src/utils/validator.ts b/agent-node/src/utils/validator.ts index 9ff690fe1..8892a81d5 100644 --- a/agent-node/src/utils/validator.ts +++ b/agent-node/src/utils/validator.ts @@ -1,5 +1,7 @@ import { convertToBufferIfBech32 } from './cardano' import { BooleanOperator, ComparisonOperator } from '../types/eventTriger' +import * as buffer from "buffer"; +import { logicalFunctions } from "./operatorSupport"; const NetworkName = ['preview', 'preprod', 'sanchonet'] @@ -19,7 +21,7 @@ export function reduceBooleanArray( bools: Array, reducer: BooleanOperator, negate: boolean -) { +) : boolean { let reducedBooleanLogic if (reducer === 'AND') { reducedBooleanLogic = bools.reduce((acc, bool) => acc && bool, true) @@ -31,44 +33,26 @@ export function reduceBooleanArray( export function compareValue( operator: ComparisonOperator, - filterValue: any, - txPropertyVal: any + objectProperty: any, + txPropertyVal: any, + property_path:string[] ) { - filterValue = convertToBufferIfBech32(filterValue) - switch (operator) { - case 'greaterThan': - if (Array.isArray(txPropertyVal)) { - return txPropertyVal.some((item) => filterValue > item) - } - return filterValue > txPropertyVal - case 'lessThan': - if (Array.isArray(txPropertyVal)) { - return txPropertyVal.some((item) => filterValue < item) - } - return filterValue < txPropertyVal - case 'equals': - if (Array.isArray(txPropertyVal)) { - return txPropertyVal.some( - (item) => !Buffer.compare(filterValue, item) - ) - } - return !Buffer.compare(filterValue, txPropertyVal) - case 'in': - if (Array.isArray(txPropertyVal)) { - return txPropertyVal.includes(filterValue) - } - return false - default: - return false - } -} + if(txPropertyVal.constructor.name == 'Buffer') + console.debug(`compareValue[${property_path.join('.')}] (${operator},0x${txPropertyVal.toString('hex')}, ${objectProperty})`) + else + console.debug(`compareValue[${property_path.join('.')}] (${operator},${txPropertyVal}, ${objectProperty})`) -export function getPropertyValue(initial_value: any, keys: Array) { - return keys.reduce((acc, key) => { - if (Array.isArray(acc)) { - return acc.map((item) => item[key]).filter(Boolean) - } else { - return acc && acc[key] != undefined ? acc[key] : undefined - } - }, initial_value) -} + let filterValue = convertToBufferIfBech32(objectProperty) + console.debug("filterValue=",filterValue); + let result; + result = logicalFunctions.execute(operator,txPropertyVal,filterValue) + + + if(txPropertyVal.constructor.name == 'Buffer') + console.debug(`compareValue[${property_path.join('.')}] (${operator},0x${txPropertyVal.toString('hex')}, ${objectProperty}) = ${result}`) + else + console.debug(`compareValue[${property_path.join('.')}] (${operator},${txPropertyVal}, ${objectProperty}) = ${result}`) + return result + + +} \ No newline at end of file diff --git a/agent-node/yarn.lock b/agent-node/yarn.lock index d10466de9..b69eb3e36 100644 --- a/agent-node/yarn.lock +++ b/agent-node/yarn.lock @@ -2145,10 +2145,10 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -libcardano@1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/libcardano/-/libcardano-1.4.2.tgz#5ef6ae1ff1e1b44812e33c4a720ae2ffd816a3d0" - integrity sha512-+ubt/FI1Cks9AN5LfhgirbzEMTOxGJw6AFkBLTLsFvl+0iTDLihOrTQALtqE8m8dInanXcJE2OnsxtZ7VlB42w== +libcardano@1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/libcardano/-/libcardano-1.4.4.tgz#f7744398f99fa14e807ef8bf38eb1d465a3242c0" + integrity sha512-f40qcZAme8wzSF6bAfriWGJA0YhzcoMsSOo7r6QVsLzZ8ca8M6wHosYwfhXhpNgQQ5McktGJzcU8uKJnglfHUQ== dependencies: "@cardano-sdk/crypto" "^0.1.30" "@emurgo/cardano-serialization-lib-nodejs" "^11.5.0"