From b99d7a944a6f05482c8de8a6bf38b51d8206f30c Mon Sep 17 00:00:00 2001 From: Rares <6453351+raresserban@users.noreply.github.com> Date: Fri, 11 Aug 2023 17:48:46 +0300 Subject: [PATCH 1/2] Add basic integration with multiversx event notifier. --- .env.example | 3 + .gitignore | 2 + package.json | 2 + src/config/index.ts | 4 +- .../MultiversXListener/MultiversXListener.ts | 59 ++++++++ src/listeners/MultiversXListener/types.ts | 14 ++ src/relayer/relay.ts | 14 +- yarn.lock | 132 +++++++++++++++++- 8 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 src/listeners/MultiversXListener/MultiversXListener.ts create mode 100644 src/listeners/MultiversXListener/types.ts diff --git a/.env.example b/.env.example index 8e35c50..0e3c545 100644 --- a/.env.example +++ b/.env.example @@ -18,3 +18,6 @@ DATADOG_API_KEY="" # The chain env is used to selected a set of evm chains, axelar chain and cosmos chains. # The default value is "testnet". The available values are "devnet", "testnet", and "mainnet". CHAIN_ENV="testnet" + +MULTIVERSX_NOTIFIER_URL=amqp://user:password@rabbitmq:5672/ +MULTIVERSX_GATEWAY_ADDRESS= diff --git a/.gitignore b/.gitignore index b50142e..a8bcb9f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ src/types/contracts *.log secret.json unused + +docker-compose.override.yml diff --git a/package.json b/package.json index 80be7e0..38d094a 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,10 @@ "@hapi/hapi": "^21.1.0", "@hapi/inert": "^7.0.0", "@hapi/vision": "^7.0.0", + "@multiversx/sdk-core": "^12.6.1", "@prisma/client": "4.8.1", "@reactivex/rxjs": "^6.6.7", + "amqplib": "^0.10.3", "dotenv": "^16.0.3", "ethers": "^5.7.2", "hapi-swagger": "^15.0.0", diff --git a/src/config/index.ts b/src/config/index.ts index c91e6db..588338b 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -11,7 +11,9 @@ export const env = { PORT: process.env.PORT || 3000, DD_API_KEY: process.env.DD_API_KEY || '', CHAIN_ENV: process.env.CHAIN_ENV || 'testnet', - LOG_LEVEL: process.env.LOG_LEVEL || "info" + LOG_LEVEL: process.env.LOG_LEVEL || "info", + MULTIVERSX_NOTIFIER_URL: process.env.MULTIVERSX_NOTIFIER_URL || '', + MULTIVERSX_GATEWAY_ADDRESS: process.env.MULTIVERSX_GATEWAY_ADDRESS || '', }; export * from './chains'; diff --git a/src/listeners/MultiversXListener/MultiversXListener.ts b/src/listeners/MultiversXListener/MultiversXListener.ts new file mode 100644 index 0000000..cd99a64 --- /dev/null +++ b/src/listeners/MultiversXListener/MultiversXListener.ts @@ -0,0 +1,59 @@ +import { env } from '../../config'; +import { logger } from '../../logger'; +import { BlockEvent, NotifierEvent } from './types'; +import { + AbiRegistry, Address, + AddressType, ArrayVecType, + BinaryCodec, BytesType, + FieldDefinition, + StringType, + StructType +} from '@multiversx/sdk-core/out'; +import { promises } from "fs"; + +const amqp = require('amqplib/channel_api.js'); + +export class MultiversXListener { + async listenToMultiversXEvents() { + const connection = await amqp.connect(env.MULTIVERSX_NOTIFIER_URL, {}); + + const channel = await connection.createChannel(); + + const queue = await channel.checkQueue('events-638c4d10'); + + await channel.consume(queue.queue, async (msg: any) => { + const blockEvent: BlockEvent = JSON.parse(msg.content.toString()); + + for (const event of blockEvent.events) { + await this.handleEvent(event); + } + }); + + logger.info('[MultiversXListener] Listening to MultiversX Gateway events'); + } + + private async handleEvent(event: NotifierEvent) { + if (event.address !== env.MULTIVERSX_GATEWAY_ADDRESS) { + return; + } + + if (event.identifier === 'callContract') { + logger.info('[MultiversXListener] Received callContract event from MultiversX Gateway contract:'); + logger.info(JSON.stringify(event)); + + const sender = Address.fromBuffer(Buffer.from(event.topics[1], 'base64')); + const destinationChain = Buffer.from(event.topics[2], 'base64').toString(); + const destinationContractAddress = Buffer.from(event.topics[3], 'base64').toString('hex'); + + const attributes = Buffer.from(event.data, 'base64'); + const dataHash = attributes.slice(0, 32).toString('hex'); + const dataPayloadBuffer = attributes.slice(32); + + const codec = new BinaryCodec(); + const [decoded] = codec.decodeNested(dataPayloadBuffer, new BytesType()); + const dataPayload = (decoded.valueOf() as Buffer).toString('hex'); + + logger.info(`Event contains data: ${sender} ${destinationChain} ${destinationContractAddress} ${dataHash} ${dataPayload}`); + } + } +} diff --git a/src/listeners/MultiversXListener/types.ts b/src/listeners/MultiversXListener/types.ts new file mode 100644 index 0000000..72fad5e --- /dev/null +++ b/src/listeners/MultiversXListener/types.ts @@ -0,0 +1,14 @@ +export interface BlockEvent { + hash: string; + shardId: number; + timestamp: Number; + events: NotifierEvent[]; +} + +export interface NotifierEvent { + txHash: string; + address: string; + identifier: string; + data: string; + topics: string[]; +} diff --git a/src/relayer/relay.ts b/src/relayer/relay.ts index ebf7490..af09165 100644 --- a/src/relayer/relay.ts +++ b/src/relayer/relay.ts @@ -1,6 +1,6 @@ import { Subject, mergeMap } from 'rxjs'; import { AxelarClient, EvmClient, DatabaseClient } from '../clients'; -import { axelarChain, cosmosChains, evmChains } from '../config'; +import { axelarChain, cosmosChains, env, evmChains } from '../config'; import { ContractCallSubmitted, ContractCallWithTokenSubmitted, @@ -38,6 +38,8 @@ import { } from '../handler'; import { createCosmosEventSubject, createEvmEventSubject } from './subject'; import { filterCosmosDestination, mapEventToEvmClient } from './rxOperators'; +import { logger } from '../logger'; +import { MultiversXListener } from '../listeners/MultiversXListener/MultiversXListener'; const sEvmCallContract = createEvmEventSubject(); const sEvmCallContractWithToken = createEvmEventSubject(); @@ -198,4 +200,14 @@ export async function startRelayer() { axelarListener.listen(AxelarCosmosContractCallWithTokenEvent, sCosmosContractCallWithToken); axelarListener.listen(AxelarIBCCompleteEvent, sCosmosApproveAny); axelarListener.listen(AxelarEVMCompletedEvent, sEvmConfirmEvent); + + // Listening for MultiversX events + try { + const multiversXListener = new MultiversXListener(); + + await multiversXListener.listenToMultiversXEvents(); + } catch (e) { + logger.error('Could not listen to MultiversX events'); + logger.error(e); + } } diff --git a/yarn.lock b/yarn.lock index a1aaab2..d4dca37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,15 @@ # yarn lockfile v1 +"@acuminous/bitsyntax@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz#e0b31b9ee7ad1e4dd840c34864327c33d9f1f653" + integrity sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ== + dependencies: + buffer-more-ints "~1.0.0" + debug "^4.3.4" + safe-buffer "~5.1.2" + "@ampproject/remapping@^2.1.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -1435,6 +1444,27 @@ resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== +"@multiversx/sdk-core@^12.6.1": + version "12.6.1" + resolved "https://registry.yarnpkg.com/@multiversx/sdk-core/-/sdk-core-12.6.1.tgz#51cedbf7810d37e3c0dfdebf7b647fd1fc9de496" + integrity sha512-T21TMC3euAi3C0WtB6WOzNYQW4XbKTrH0Q1K7gxcNpLDqnQbMwh0SuVAossQRL/s1Ca829Zjt3zmuGQUtWAU1A== + dependencies: + "@multiversx/sdk-transaction-decoder" "1.0.2" + bech32 "1.1.4" + bignumber.js "9.0.1" + blake2b "2.1.3" + buffer "6.0.3" + json-duplicate-key-handle "1.0.0" + keccak "3.0.2" + protobufjs "7.2.4" + +"@multiversx/sdk-transaction-decoder@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@multiversx/sdk-transaction-decoder/-/sdk-transaction-decoder-1.0.2.tgz#83ded4f6d4b877b4421234856eb19709be2af31b" + integrity sha512-j43QsKquu8N51WLmVlJ7dV2P3A1448R7/ktvl8r3i6wRMpfdtzDPNofTdHmMRT7DdQdvs4+RNgz8hVKL11Etsw== + dependencies: + bech32 "^2.0.0" + "@noble/hashes@^1", "@noble/hashes@^1.0.0": version "1.1.5" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11" @@ -2030,6 +2060,16 @@ amp@0.3.1, amp@~0.3.1: resolved "https://registry.yarnpkg.com/amp/-/amp-0.3.1.tgz#6adf8d58a74f361e82c1fa8d389c079e139fc47d" integrity sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw== +amqplib@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.10.3.tgz#e186a2f74521eb55ec54db6d25ae82c29c1f911a" + integrity sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw== + dependencies: + "@acuminous/bitsyntax" "^0.1.2" + buffer-more-ints "~1.0.0" + readable-stream "1.x >=1.1.9" + url-parse "~1.5.10" + ansi-colors@^4.1.1: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" @@ -2212,6 +2252,11 @@ babel-preset-jest@^29.2.0: babel-plugin-jest-hoist "^29.2.0" babel-preset-current-node-syntax "^1.0.0" +backslash@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/backslash/-/backslash-0.2.0.tgz#6c3c1fce7e7e714ccfc10fd74f0f73410677375f" + integrity sha512-Avs+8FUZ1HF/VFP4YWwHQZSGzRPm37ukU1JQYQWijuHhtXdOuAzcZ8PcAzfIw898a8PyBzdn+RtnKA6MzW0X2A== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -2232,11 +2277,31 @@ bech32@^2.0.0: resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== +bignumber.js@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" + integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +blake2b-wasm@^1.1.0: + version "1.1.7" + resolved "https://registry.yarnpkg.com/blake2b-wasm/-/blake2b-wasm-1.1.7.tgz#e4d075da10068e5d4c3ec1fb9accc4d186c55d81" + integrity sha512-oFIHvXhlz/DUgF0kq5B1CqxIDjIJwh9iDeUUGQUcvgiGz7Wdw03McEO7CfLBy7QKGdsydcMCgO9jFNBAFCtFcA== + dependencies: + nanoassert "^1.0.0" + +blake2b@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/blake2b/-/blake2b-2.1.3.tgz#f5388be424768e7c6327025dad0c3c6d83351bca" + integrity sha512-pkDss4xFVbMb4270aCyGD3qLv92314Et+FsKzilCLxDz5DuZ2/1g3w4nmBbu6nKApPspnjG7JcwTjGZnduB1yg== + dependencies: + blake2b-wasm "^1.1.0" + nanoassert "^1.0.0" + blessed@0.1.81: version "0.1.81" resolved "https://registry.yarnpkg.com/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129" @@ -2306,7 +2371,12 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^6.0.3: +buffer-more-ints@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz#ef4f8e2dddbad429ed3828a9c55d44f05c611422" + integrity sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg== + +buffer@6.0.3, buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -4117,6 +4187,13 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-duplicate-key-handle@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-duplicate-key-handle/-/json-duplicate-key-handle-1.0.0.tgz#0678bd17822d23d8c0d0958b43011875fa37f363" + integrity sha512-OLIxL+UpfwUsqcLX3i6Z51ChTou/Vje+6bSeGUSubj96dF/SfjObDprLy++ZXYH07KITuEzsXS7PX7e/BGf4jw== + dependencies: + backslash "^0.2.0" + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -4333,6 +4410,11 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +long@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -4458,6 +4540,11 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +nanoassert@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/nanoassert/-/nanoassert-1.1.0.tgz#4f3152e09540fde28c76f44b19bbcd1d5a42478d" + integrity sha512-C40jQ3NzfkP53NsO8kEOFd79p4b9kDXQMwgiY1z8ZwrDZgUyom0AHwGegF4Dm99L+YoYhuaB0ceerUcXmqr1rQ== + napi-macros@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" @@ -4909,6 +4996,24 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +protobufjs@7.2.4: + version "7.2.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.4.tgz#3fc1ec0cdc89dd91aef9ba6037ba07408485c3ae" + integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: version "6.11.3" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" @@ -4957,6 +5062,11 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2, queue-microtask@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -4984,7 +5094,7 @@ read@^1.0.4: dependencies: mute-stream "~0.0.4" -readable-stream@1.1.x: +readable-stream@1.1.x, "readable-stream@1.x >=1.1.9": version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== @@ -5044,6 +5154,11 @@ require-in-the-middle@^5.0.0: module-details-from-path "^1.0.3" resolve "^1.22.1" +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -5111,6 +5226,11 @@ safe-buffer@^5.2.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-stable-stringify@^2.3.1: version "2.4.2" resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz#ec7b037768098bf65310d1d64370de0dc02353aa" @@ -5684,6 +5804,14 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-parse@~1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + utf-8-validate@5.0.7: version "5.0.7" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.7.tgz#c15a19a6af1f7ad9ec7ddc425747ca28c3644922" From de034b3cdf4170aa68aad1408845caaa2d647885 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raresserban@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:09:46 +0300 Subject: [PATCH 2/2] Save mvx event to database and add crude handle mvx event to Axelar. --- src/clients/DatabaseClient.ts | 20 +++++ src/config/index.ts | 1 + src/handler/eventHandler.ts | 76 ++++++++++++++++++- .../MultiversXListener/MultiversXListener.ts | 30 +++++--- src/relayer/relay.ts | 2 +- 5 files changed, 115 insertions(+), 14 deletions(-) diff --git a/src/clients/DatabaseClient.ts b/src/clients/DatabaseClient.ts index b4f5ede..9708068 100644 --- a/src/clients/DatabaseClient.ts +++ b/src/clients/DatabaseClient.ts @@ -14,6 +14,8 @@ import { } from '../types/contracts/IAxelarGateway'; import { logger } from '../logger'; import { ethers } from 'ethers'; +import { NotifierEvent } from '../listeners/MultiversXListener/types'; +import { Address } from '@multiversx/sdk-core/out'; export class DatabaseClient { private prisma: PrismaClient; @@ -255,6 +257,24 @@ export class DatabaseClient { logger.info(`[DBUpdate] ${JSON.stringify(executeDb)}`); } + createMvxCallContractEvent(event: NotifierEvent, messageId: string, sourceChain: string, destinationChain: any, payload: string, payloadHash: any, destinationContractAddress: any, sender: string) { + return this.prisma.relayData.create({ + data: { + id: messageId, + from: sourceChain, + to: destinationChain, + callContract: { + create: { + payload: payload.toLowerCase(), + payloadHash: payloadHash.toLowerCase(), + contractAddress: destinationContractAddress.toLowerCase(), + sourceAddress: sender.toLowerCase(), + }, + }, + }, + }); + } + connect() { return this.prisma.$connect(); } diff --git a/src/config/index.ts b/src/config/index.ts index 588338b..a0cf4e1 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -14,6 +14,7 @@ export const env = { LOG_LEVEL: process.env.LOG_LEVEL || "info", MULTIVERSX_NOTIFIER_URL: process.env.MULTIVERSX_NOTIFIER_URL || '', MULTIVERSX_GATEWAY_ADDRESS: process.env.MULTIVERSX_GATEWAY_ADDRESS || '', + MULTIVERSX_CHAIN_ID: process.env.MULTIVERSX_CHAIN_ID || '', }; export * from './chains'; diff --git a/src/handler/eventHandler.ts b/src/handler/eventHandler.ts index 5af8679..3f7ab94 100644 --- a/src/handler/eventHandler.ts +++ b/src/handler/eventHandler.ts @@ -1,4 +1,4 @@ -import { AxelarClient, DatabaseClient, EvmClient } from '..'; +import { AxelarClient, cosmosChains, DatabaseClient, EvmClient } from '..'; import { logger } from '../logger'; import { ContractCallSubmitted, @@ -15,6 +15,7 @@ import { ContractCallApprovedEventObject, ContractCallEventObject, } from '../types/contracts/IAxelarGateway'; +import { filterCosmosDestination } from '../relayer/rxOperators'; const getBatchCommandIdFromSignTx = (signTx: any) => { const rawLog = JSON.parse(signTx.rawLog || '{}'); @@ -275,6 +276,79 @@ export async function handleEvmToCosmosCompleteEvent(client: AxelarClient, event logger.info(`[handleEvmToCosmosCompleteEvent] Memo: ${event.memo}`); } +// TODO: This is all very much WIP and probably won't work :) +export async function handleMvxToEvmOrCosmosEvent( + vxClient: AxelarClient, + evmClients: EvmClient[], + messageId: string, + payload: string, + sourceChain: string, + destinationChain: string, +) { + const isCosmosDestination = cosmosChains.map((chain) => chain.chainId).includes(destinationChain); + + if (isCosmosDestination) { + // TODO: This expects a EVM txHash instead of this messageId, not sure how to handle it. + const confirmTx = await vxClient.confirmEvmTx(sourceChain, messageId); + if (confirmTx) { + logger.info(`[handleEvmToCosmosEvent] Confirmed: ${confirmTx.transactionHash}`); + } + + return; + } + + // TODO: Should also support other destination chains in the future + const evmClient = evmClients.find( + (client) => client.chainId.toLowerCase() === destinationChain.toLowerCase() + ); + + // If no evm client found, return + if (!evmClient) return; + + // TODO: This should contain the source_address, source_chain, destination_address, destination_chain, payload_hash + // For the Gateway contract running on Axelar CosmWASM. Are those passed through the payload? + const routeMessage = await vxClient.routeMessageRequest( + -1, + messageId, + payload + ); + + if (routeMessage) { + logger.info(`[handleMvxToEvmEvent] RouteMessage: ${routeMessage.transactionHash}`); + } + + const pendingCommands = await vxClient.getPendingCommands(destinationChain); + + logger.info(`[handleMvxToEvmEvent] PendingCommands: ${JSON.stringify(pendingCommands)}`); + if (pendingCommands.length === 0) return; + + const signCommand = await vxClient.signCommands(destinationChain); + logger.debug(`[handleMvxToEvmEvent] SignCommand: ${JSON.stringify(signCommand)}`); + + if (signCommand && signCommand.rawLog?.includes('failed')) { + throw new Error(signCommand.rawLog); + } + if (!signCommand) { + throw new Error('cannot sign command'); + } + + const batchedCommandId = getBatchCommandIdFromSignTx(signCommand); + logger.info(`[handleMvxToEvmEvent] BatchCommandId: ${batchedCommandId}`); + + const executeData = await vxClient.getExecuteDataFromBatchCommands( + destinationChain, + batchedCommandId + ); + + logger.info(`[handleMvxToEvmEvent] BatchCommands: ${JSON.stringify(executeData)}`); + + const tx = await evmClient.gatewayExecute(executeData); + if (!tx) return; + logger.info(`[handleMvxToEvmEvent] Execute: ${tx.transactionHash}`); + + return tx; +} + export async function prepareHandler(event: any, db: DatabaseClient, label = '') { // reconnect prisma db await db.connect(); diff --git a/src/listeners/MultiversXListener/MultiversXListener.ts b/src/listeners/MultiversXListener/MultiversXListener.ts index cd99a64..7baf2ba 100644 --- a/src/listeners/MultiversXListener/MultiversXListener.ts +++ b/src/listeners/MultiversXListener/MultiversXListener.ts @@ -1,19 +1,16 @@ import { env } from '../../config'; import { logger } from '../../logger'; import { BlockEvent, NotifierEvent } from './types'; -import { - AbiRegistry, Address, - AddressType, ArrayVecType, - BinaryCodec, BytesType, - FieldDefinition, - StringType, - StructType -} from '@multiversx/sdk-core/out'; -import { promises } from "fs"; +import { Address, BinaryCodec, BytesType } from '@multiversx/sdk-core/out'; +import { AxelarClient, DatabaseClient, EvmClient } from '../../clients'; +import { handleEvmToCosmosEvent, handleMvxToEvmOrCosmosEvent } from '../../handler'; const amqp = require('amqplib/channel_api.js'); export class MultiversXListener { + constructor(private readonly db: DatabaseClient, private readonly axelarClient: AxelarClient, private readonly evmClients: EvmClient[]) { + } + async listenToMultiversXEvents() { const connection = await amqp.connect(env.MULTIVERSX_NOTIFIER_URL, {}); @@ -46,14 +43,23 @@ export class MultiversXListener { const destinationContractAddress = Buffer.from(event.topics[3], 'base64').toString('hex'); const attributes = Buffer.from(event.data, 'base64'); - const dataHash = attributes.slice(0, 32).toString('hex'); + const payloadHash = attributes.slice(0, 32).toString('hex'); const dataPayloadBuffer = attributes.slice(32); const codec = new BinaryCodec(); const [decoded] = codec.decodeNested(dataPayloadBuffer, new BytesType()); - const dataPayload = (decoded.valueOf() as Buffer).toString('hex'); + const payload = (decoded.valueOf() as Buffer).toString('hex'); + + logger.info(`Event contains data: ${ sender } ${ destinationChain } ${ destinationContractAddress } ${ payloadHash } ${ payload }`); + + const sourceChain = 'multiversx-' + env.MULTIVERSX_CHAIN_ID; + + // TODO: Should this be hashed? + const messageId = event.txHash + sourceChain; + + await this.db.createMvxCallContractEvent(event, messageId, sourceChain, destinationChain, payload, payloadHash, destinationContractAddress, sender.bech32()); - logger.info(`Event contains data: ${sender} ${destinationChain} ${destinationContractAddress} ${dataHash} ${dataPayload}`); + await handleMvxToEvmOrCosmosEvent(this.axelarClient, this.evmClients, messageId, payload, sourceChain, destinationChain); } } } diff --git a/src/relayer/relay.ts b/src/relayer/relay.ts index af09165..0e0886b 100644 --- a/src/relayer/relay.ts +++ b/src/relayer/relay.ts @@ -203,7 +203,7 @@ export async function startRelayer() { // Listening for MultiversX events try { - const multiversXListener = new MultiversXListener(); + const multiversXListener = new MultiversXListener(db, axelarClient, evmClients); await multiversXListener.listenToMultiversXEvents(); } catch (e) {