diff --git a/blockchain-watcher/config/mainnet.json b/blockchain-watcher/config/mainnet.json index afd5186c..b6826cb9 100644 --- a/blockchain-watcher/config/mainnet.json +++ b/blockchain-watcher/config/mainnet.json @@ -125,7 +125,7 @@ }, "wormchain": { "network": "mainnet", - "rpcs": ["https://wormchain-rpc.quickapi.com"] + "rpcs": ["https://tncnt-eu-wormchain-main-01.rpc.p2p.world"] } } } diff --git a/blockchain-watcher/src/domain/actions/index.ts b/blockchain-watcher/src/domain/actions/index.ts index 1867e45a..17746377 100644 --- a/blockchain-watcher/src/domain/actions/index.ts +++ b/blockchain-watcher/src/domain/actions/index.ts @@ -5,5 +5,12 @@ export * from "./evm/PollEvm"; export * from "./evm/types"; export * from "./solana/GetSolanaTransactions"; export * from "./solana/PollSolanaTransactions"; +export * from "./wormchain/HandleWormchainLogs"; +export * from "./wormchain/GetWormchainLogs"; +export * from "./wormchain/PollWormchain"; +export * from "./aptos/GetAptosTransactions"; +export * from "./aptos/GetAptosTransactionsByEvents"; +export * from "./aptos/HandleAptosTransactions"; +export * from "./aptos/PollAptos"; export * from "./RunPollingJob"; export * from "./StartJobs"; diff --git a/blockchain-watcher/src/domain/actions/wormchain/GetWormchainLogs.ts b/blockchain-watcher/src/domain/actions/wormchain/GetWormchainLogs.ts index 4adade61..8fdfc77d 100644 --- a/blockchain-watcher/src/domain/actions/wormchain/GetWormchainLogs.ts +++ b/blockchain-watcher/src/domain/actions/wormchain/GetWormchainLogs.ts @@ -39,7 +39,7 @@ export class GetWormchainLogs { } private populateLog(opts: GetWormchainOpts, fromBlock: bigint, toBlock: bigint): string { - return `[addresses:${opts.addresses}][topics:${opts.topics}][blocks:${fromBlock} - ${toBlock}]`; + return `[addresses:${opts.addresses}][blocks:${fromBlock} - ${toBlock}]`; } } diff --git a/blockchain-watcher/src/domain/actions/wormchain/PollWormchain.ts b/blockchain-watcher/src/domain/actions/wormchain/PollWormchain.ts index 1c767806..36ffdd76 100644 --- a/blockchain-watcher/src/domain/actions/wormchain/PollWormchain.ts +++ b/blockchain-watcher/src/domain/actions/wormchain/PollWormchain.ts @@ -216,9 +216,9 @@ export class PollWormchainLogsConfig { return this.props.chainId; } - static fromBlock(chain: string, fromBlock: bigint) { + static fromBlock(fromBlock: bigint) { return new PollWormchainLogsConfig({ - chain, + chain: "wormchain", fromBlock, addresses: [], topics: [], diff --git a/blockchain-watcher/src/infrastructure/mappers/wormchain/wormchainLogMessagePublishedMapper.ts b/blockchain-watcher/src/infrastructure/mappers/wormchain/wormchainLogMessagePublishedMapper.ts index de14d4cd..cc5f17b1 100644 --- a/blockchain-watcher/src/infrastructure/mappers/wormchain/wormchainLogMessagePublishedMapper.ts +++ b/blockchain-watcher/src/infrastructure/mappers/wormchain/wormchainLogMessagePublishedMapper.ts @@ -2,20 +2,19 @@ import { LogFoundEvent, LogMessagePublished } from "../../../domain/entities"; import { WormchainLog } from "../../../domain/entities/wormchain"; import winston from "winston"; -const CHAIN_ID_WORMCHAIN = 22; +const CHAIN_ID_WORMCHAIN = 3104; const CORE_ADDRESS = "wormhole1ufs3tlq4umljk0qfe8k5ya0x6hpavn897u2cnf9k0en9jr7qarqqaqfk2j"; let logger: winston.Logger = winston.child({ module: "wormchainLogMessagePublishedMapper" }); export const wormchainLogMessagePublishedMapper = ( - log: WormchainLog, - parsedArgs: ReadonlyArray + log: WormchainLog ): LogFoundEvent | undefined => { - const { coreContract, sequence, emitter, hash } = transactionAttibutes(log); + const { coreContract, sequence, payload, emitter, nonce, hash } = transactionAttibutes(log); - if (coreContract && sequence && emitter && hash) { + if (coreContract && sequence && payload && emitter && payload && nonce && hash) { logger.info( - `[wormchain] Source event info: [tx: ][emitterChain: ${CHAIN_ID_WORMCHAIN}][sender: }}][sequence: ]` + `[wormchain] Source event info: [tx: ${hash}][emitterChain: ${CHAIN_ID_WORMCHAIN}][sender: ${emitter} }}][sequence: ${sequence} ]` ); return { @@ -28,9 +27,9 @@ export const wormchainLogMessagePublishedMapper = ( attributes: { sender: emitter, sequence: sequence, - payload: parsedArgs[3], - nonce: parsedArgs[2], - consistencyLevel: parsedArgs[4], + payload: payload, + nonce: nonce, + consistencyLevel: 0, }, }; } @@ -39,7 +38,9 @@ export const wormchainLogMessagePublishedMapper = ( function transactionAttibutes(log: WormchainLog): TransactionAttributes { let coreContract; let sequence; + let payload; let emitter; + let nonce; let hash; log.transactions?.forEach((tx) => { @@ -50,11 +51,18 @@ function transactionAttibutes(log: WormchainLog): TransactionAttributes { const value = Buffer.from(attr.value, "base64").toString().toLowerCase(); switch (key) { + case "message.sequence": + console.log(key, value); + sequence = Number(value); + break; + case "message.message": + payload = value; + break; case "message.sender": emitter = value; break; - case "message.sequence": - sequence = Number(value); + case "message.nonce": + nonce = Number(value); break; case "_contract_address": case "contract_address": @@ -69,7 +77,9 @@ function transactionAttibutes(log: WormchainLog): TransactionAttributes { return { coreContract, sequence, + payload, emitter, + nonce, hash, }; } @@ -77,6 +87,8 @@ function transactionAttibutes(log: WormchainLog): TransactionAttributes { type TransactionAttributes = { coreContract: boolean | undefined; sequence: number | undefined; + payload: string | undefined; emitter: string | undefined; + nonce: number | undefined; hash: string | undefined; }; diff --git a/blockchain-watcher/test/domain/actions/wormchain/PollWormchain.test.ts b/blockchain-watcher/test/domain/actions/wormchain/PollWormchain.test.ts new file mode 100644 index 00000000..7bd360d0 --- /dev/null +++ b/blockchain-watcher/test/domain/actions/wormchain/PollWormchain.test.ts @@ -0,0 +1,150 @@ +import { afterEach, describe, it, expect, jest } from "@jest/globals"; +import { + PollEvmLogsConfig, + PollWormchain, + PollWormchainLogsConfig, + PollWormchainLogsMetadata, +} from "../../../../src/domain/actions"; +import { + MetadataRepository, + StatRepository, + WormchainRepository, +} from "../../../../src/domain/repositories"; +import { EvmBlock, EvmLog, ReceiptTransaction } from "../../../../src/domain/entities"; +import { thenWaitForAssertion } from "../../../wait-assertion"; + +let cfg = PollWormchainLogsConfig.fromBlock(7626734n); + +let getBlockHeightSpy: jest.SpiedFunction; +let getBlockLogsSpy: jest.SpiedFunction; +let handlerSpy: jest.SpiedFunction<(logs: EvmLog[]) => Promise>; +let metadataSaveSpy: jest.SpiedFunction["save"]>; + +let metadataRepo: MetadataRepository; +let wormchainBlockRepo: WormchainRepository; +let statsRepo: StatRepository; + +let handlers = { + working: (logs: EvmLog[]) => Promise.resolve(), + failing: (logs: EvmLog[]) => Promise.reject(), +}; +let pollWormchain: PollWormchain; + +describe("PollWormchain", () => { + afterEach(async () => { + await pollWormchain.stop(); + }); + + it("should be able to read logs from latest block when no fromBlock is configured", async () => { + const currentHeight = 10n; + + const logs = { + transactions: [ + { + hash: "0x47a54890a16ea9d924c32a1fa6fd1cf39176be532c8ba454d33f628d89be3388", + type: "wasm", + attributes: [ + { + key: "X2NvbnRyYWN0X2FkZHJlc3M=", + value: + "d29ybWhvbGUxNGhqMnRhdnE4ZnBlc2R3eHhjdTQ0cnR5M2hoOTB2aHVqcnZjbXN0bDR6cjN0eG1mdnc5c3JyZzQ2NQ==", + index: true, + }, + { key: "YWN0aW9u", value: "c3VibWl0X29ic2VydmF0aW9ucw==", index: true }, + { + key: "b3duZXI=", + value: "d29ybWhvbGUxOHl3NmY4OHA3Znc2bTk5eDlrbnJmejNwMHk2OTNoaDBhaDh5Mm0=", + index: true, + }, + ], + }, + { + hash: "0x56e974e33c5c7403d23a5fe7fa414d9f1d6dd4f1b67601342100093c604b5d70", + type: "wasm", + attributes: [ + { + key: "X2NvbnRyYWN0X2FkZHJlc3M=", + value: + "d29ybWhvbGUxNGhqMnRhdnE4ZnBlc2R3eHhjdTQ0cnR5M2hoOTB2aHVqcnZjbXN0bDR6cjN0eG1mdnc5c3JyZzQ2NQ==", + index: true, + }, + { key: "YWN0aW9u", value: "c3VibWl0X29ic2VydmF0aW9ucw==", index: true }, + { + key: "b3duZXI=", + value: "d29ybWhvbGUxYWNxYTV2bDJudW5oc250djBldGpzNnllZTN2NnZjOGw5bTRxOGU=", + index: true, + }, + ], + }, + ], + blockHeight: "7626735", + timestamp: 1711143216257, + }; + givenEvmBlockRepository(currentHeight, logs); + givenMetadataRepository(); + givenStatsRepository(); + givenPollWormchainLogs(); + + await whenPollWormchainLogsStarts(); + + await thenWaitForAssertion( + () => expect(getBlockHeightSpy).toHaveReturnedTimes(1), + () => expect(getBlockLogsSpy).toHaveBeenCalledWith(currentHeight) + ); + }); + + it("should be return an empty array because to block is more greater than from block", async () => { + const currentHeight = 10n; + + givenEvmBlockRepository(currentHeight); + givenMetadataRepository({ lastBlock: 15n }); + givenStatsRepository(); + givenPollWormchainLogs(); + + await whenPollWormchainLogsStarts(); + + await thenWaitForAssertion(() => expect(getBlockHeightSpy).toHaveReturnedTimes(1)); + }); +}); + +const givenEvmBlockRepository = (height?: bigint, logs: any = []) => { + wormchainBlockRepo = { + getBlockHeight: () => Promise.resolve(height), + getBlockLogs: () => Promise.resolve(logs), + }; + + getBlockHeightSpy = jest.spyOn(wormchainBlockRepo, "getBlockHeight"); + getBlockLogsSpy = jest.spyOn(wormchainBlockRepo, "getBlockLogs"); + handlerSpy = jest.spyOn(handlers, "working"); +}; + +const givenMetadataRepository = (data?: PollWormchainLogsMetadata) => { + metadataRepo = { + get: () => Promise.resolve(data), + save: () => Promise.resolve(), + }; + metadataSaveSpy = jest.spyOn(metadataRepo, "save"); +}; + +const givenStatsRepository = () => { + statsRepo = { + count: () => {}, + measure: () => {}, + report: () => Promise.resolve(""), + }; +}; + +const givenPollWormchainLogs = (from?: bigint) => { + cfg.setFromBlock(from); + pollWormchain = new PollWormchain( + wormchainBlockRepo, + metadataRepo, + statsRepo, + cfg, + "GetWormchainLogs" + ); +}; + +const whenPollWormchainLogsStarts = async () => { + pollWormchain.run([handlers.working]); +}; diff --git a/blockchain-watcher/test/infrastructure/mappers/wormchain/wormchainLogMessagePublishedMapper.test.ts b/blockchain-watcher/test/infrastructure/mappers/wormchain/wormchainLogMessagePublishedMapper.test.ts new file mode 100644 index 00000000..0c8a10d2 --- /dev/null +++ b/blockchain-watcher/test/infrastructure/mappers/wormchain/wormchainLogMessagePublishedMapper.test.ts @@ -0,0 +1,171 @@ +import { wormchainLogMessagePublishedMapper } from "../../../../src/infrastructure/mappers/wormchain/wormchainLogMessagePublishedMapper"; +import { describe, it, expect } from "@jest/globals"; +import { WormchainLog } from "../../../../src/domain/entities/wormchain"; + +describe("wormchainLogMessagePublishedMapper", () => { + it("should be able to map log to aptosLogMessagePublishedMapper", async () => { + // When + const result = wormchainLogMessagePublishedMapper(log); + + if (result) { + // Then + expect(result.name).toBe("log-message-published"); + expect(result.chainId).toBe(3104); + expect(result.txHash).toBe( + "0xa08b0ac6ee67e21d3dd89f48f60cc907fc867288f4439bcf72731b0884d8aff2" + ); + expect(result.address).toBe( + "wormhole1ufs3tlq4umljk0qfe8k5ya0x6hpavn897u2cnf9k0en9jr7qarqqaqfk2j" + ); + expect(result.attributes.consistencyLevel).toBe(0); + expect(result.attributes.nonce).toBe(7671); + expect(result.attributes.payload).toBe( + "0100000000000000000000000000000000000000000000000000000000555643a3f5edec8471c75624ebc4079a634326d96a689e6157d79abe8f5a6f94472853bc00018622b98735cb870ae0cb22bd4ea58cfb512bd4002247ccd0b250eb6d0c5032fc00010000000000000000000000000000000000000000000000000000000000000000" + ); + expect(result.attributes.sender).toBe( + "aeb534c45c3049d380b9d9b966f9895f53abd4301bfaff407fa09dea8ae7a924" + ); + expect(result.attributes.sequence).toBe(28603); + } + }); +}); + +const log: WormchainLog = { + transactions: [ + { + hash: "0x987e77d2d8cf8b9c0b3998dc62dc94fad9de47c4e3b50ad9bfd3083d7ab958ff", + type: "wasm", + attributes: [ + { + key: "X2NvbnRyYWN0X2FkZHJlc3M=", + value: + "d29ybWhvbGUxNGhqMnRhdnE4ZnBlc2R3eHhjdTQ0cnR5M2hoOTB2aHVqcnZjbXN0bDR6cjN0eG1mdnc5c3JyZzQ2NQ==", + index: true, + }, + { key: "YWN0aW9u", value: "c3VibWl0X29ic2VydmF0aW9ucw==", index: true }, + { + key: "b3duZXI=", + value: "d29ybWhvbGUxODc4a3h6M3VnZXN2YTRoNGtmeng2Y3F0ZHk5NmN3d2RqajBwaHc=", + index: true, + }, + ], + }, + { + hash: "0xa08b0ac6ee67e21d3dd89f48f60cc907fc867288f4439bcf72731b0884d8aff2", + type: "wasm", + attributes: [ + { + key: "X2NvbnRyYWN0X2FkZHJlc3M=", + value: + "d29ybWhvbGUxajYydGt5cWhqeWpscXN5MzB1bnVhcW5uZDhkdDV3cXFucndqemYwZms3bnc0dzdkeHBzcWhheWFuZw==", + index: true, + }, + { key: "YWN0aW9u", value: "aW5jcmVhc2VfYWxsb3dhbmNl", index: true }, + { key: "YW1vdW50", value: "MTQzMTcxNjc3MQ==", index: true }, + { + key: "b3duZXI=", + value: + "d29ybWhvbGUxNGVqcWp5cTh1bTRwM3hmcWo3NHlsZDV3YXFsamY4OGZ6MjV5eG5tYTBjbmdzcHhlM2xlczAwZnBqeA==", + index: true, + }, + { + key: "c3BlbmRlcg==", + value: + "d29ybWhvbGUxNDY2bmYzenV4cHlhOHE5ZW14dWtkN3ZmdGFmNmg0cHNyMGEwN3NybDV6dzc0emg4NHlqcTRseWptaA==", + index: true, + }, + ], + }, + { + hash: "0xa08b0ac6ee67e21d3dd89f48f60cc907fc867288f4439bcf72731b0884d8aff2", + type: "wasm", + attributes: [ + { + key: "X2NvbnRyYWN0X2FkZHJlc3M=", + value: + "d29ybWhvbGUxNDY2bmYzenV4cHlhOHE5ZW14dWtkN3ZmdGFmNmg0cHNyMGEwN3NybDV6dzc0emg4NHlqcTRseWptaA==", + index: true, + }, + { key: "dHJhbnNmZXIuYW1vdW50", value: "MTQzMTcxNjc3MQ==", index: true }, + { key: "dHJhbnNmZXIuYmxvY2tfdGltZQ==", value: "MTcxMTE0MzIyMg==", index: true }, + { key: "dHJhbnNmZXIubm9uY2U=", value: "NzY3MQ==", index: true }, + { + key: "dHJhbnNmZXIucmVjaXBpZW50", + value: + "ODYyMmI5ODczNWNiODcwYWUwY2IyMmJkNGVhNThjZmI1MTJiZDQwMDIyNDdjY2QwYjI1MGViNmQwYzUwMzJmYw==", + index: true, + }, + { key: "dHJhbnNmZXIucmVjaXBpZW50X2NoYWlu", value: "MQ==", index: true }, + { + key: "dHJhbnNmZXIuc2VuZGVy", + value: + "YWU2NDA5MTAwN2U2ZWExODk5MjA5N2FhNGZiNjhlZTgzZjI0OWNlOTEyYTg0MzRmN2Q3ZTI2ODgwNGQ5OGZmMw==", + index: true, + }, + { + key: "dHJhbnNmZXIudG9rZW4=", + value: + "ZjVlZGVjODQ3MWM3NTYyNGViYzQwNzlhNjM0MzI2ZDk2YTY4OWU2MTU3ZDc5YWJlOGY1YTZmOTQ0NzI4NTNiYw==", + index: true, + }, + { key: "dHJhbnNmZXIudG9rZW5fY2hhaW4=", value: "MQ==", index: true }, + ], + }, + { + hash: "0xa08b0ac6ee67e21d3dd89f48f60cc907fc867288f4439bcf72731b0884d8aff2", + type: "wasm", + attributes: [ + { + key: "X2NvbnRyYWN0X2FkZHJlc3M=", + value: + "d29ybWhvbGUxajYydGt5cWhqeWpscXN5MzB1bnVhcW5uZDhkdDV3cXFucndqemYwZms3bnc0dzdkeHBzcWhheWFuZw==", + index: true, + }, + { key: "YWN0aW9u", value: "YnVybl9mcm9t", index: true }, + { key: "YW1vdW50", value: "MTQzMTcxNjc3MQ==", index: true }, + { + key: "Ynk=", + value: + "d29ybWhvbGUxNDY2bmYzenV4cHlhOHE5ZW14dWtkN3ZmdGFmNmg0cHNyMGEwN3NybDV6dzc0emg4NHlqcTRseWptaA==", + index: true, + }, + { + key: "ZnJvbQ==", + value: + "d29ybWhvbGUxNGVqcWp5cTh1bTRwM3hmcWo3NHlsZDV3YXFsamY4OGZ6MjV5eG5tYTBjbmdzcHhlM2xlczAwZnBqeA==", + index: true, + }, + ], + }, + { + hash: "0xa08b0ac6ee67e21d3dd89f48f60cc907fc867288f4439bcf72731b0884d8aff2", + type: "wasm", + attributes: [ + { + key: "X2NvbnRyYWN0X2FkZHJlc3M=", + value: + "d29ybWhvbGUxdWZzM3RscTR1bWxqazBxZmU4azV5YTB4NmhwYXZuODk3dTJjbmY5azBlbjlqcjdxYXJxcWFxZmsyag==", + index: true, + }, + { key: "bWVzc2FnZS5ibG9ja190aW1l", value: "MTcxMTE0MzIyMg==", index: true }, + { key: "bWVzc2FnZS5jaGFpbl9pZA==", value: "MzEwNA==", index: true }, + { + key: "bWVzc2FnZS5tZXNzYWdl", + value: + "MDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDU1NTY0M2EzZjVlZGVjODQ3MWM3NTYyNGViYzQwNzlhNjM0MzI2ZDk2YTY4OWU2MTU3ZDc5YWJlOGY1YTZmOTQ0NzI4NTNiYzAwMDE4NjIyYjk4NzM1Y2I4NzBhZTBjYjIyYmQ0ZWE1OGNmYjUxMmJkNDAwMjI0N2NjZDBiMjUwZWI2ZDBjNTAzMmZjMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=", + index: true, + }, + { key: "bWVzc2FnZS5ub25jZQ==", value: "NzY3MQ==", index: true }, + { + key: "bWVzc2FnZS5zZW5kZXI=", + value: + "YWViNTM0YzQ1YzMwNDlkMzgwYjlkOWI5NjZmOTg5NWY1M2FiZDQzMDFiZmFmZjQwN2ZhMDlkZWE4YWU3YTkyNA==", + index: true, + }, + { key: "bWVzc2FnZS5zZXF1ZW5jZQ==", value: "Mjg2MDM=", index: true }, + ], + }, + ], + blockHeight: 7626736n, + timestamp: 1711143222043, +};